Skip to content

Commit 189ea2a

Browse files
committed
docs: Clarify transformer is for value transforms, not legacy
- Remove "Legacy" label from transformer documentation - Emphasize transformer is for VALUE transformations (e.g., decryption, computed fields) - Clarify columnMapper is for COLUMN NAME transformations - Run eslint --fix on column-mapper.ts
1 parent 00e6c05 commit 189ea2a

File tree

2 files changed

+63
-20
lines changed

2 files changed

+63
-20
lines changed

packages/typescript-client/src/column-mapper.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ export interface ColumnMapper<Extensions = never> {
5555
*/
5656
export function snakeToCamel(str: string): string {
5757
// Preserve leading underscores
58-
const leadingUnderscores = str.match(/^_+/)?.[0] ?? ''
58+
const leadingUnderscores = str.match(/^_+/)?.[0] ?? ``
5959
const withoutLeading = str.slice(leadingUnderscores.length)
6060

6161
// Remove trailing underscores and convert to lowercase
62-
const normalized = withoutLeading.replace(/_+$/, '').toLowerCase()
62+
const normalized = withoutLeading.replace(/_+$/, ``).toLowerCase()
6363

6464
// Convert snake_case to camelCase (handling multiple underscores)
6565
const camelCased = normalized.replace(/_+([a-z])/g, (_, letter) =>
@@ -88,10 +88,10 @@ export function camelToSnake(str: string): string {
8888
str
8989
// Insert underscore before uppercase letters that follow lowercase letters
9090
// e.g., userId -> user_Id
91-
.replace(/([a-z])([A-Z])/g, '$1_$2')
91+
.replace(/([a-z])([A-Z])/g, `$1_$2`)
9292
// Insert underscore before uppercase letters that are followed by lowercase letters
9393
// This handles acronyms: userID -> user_ID, but parseHTMLString -> parse_HTML_String
94-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
94+
.replace(/([A-Z]+)([A-Z][a-z])/g, `$1_$2`)
9595
.toLowerCase()
9696
)
9797
}
@@ -175,7 +175,7 @@ export function encodeWhereClause(
175175
whereClause: string | undefined,
176176
encode?: (columnName: string) => string
177177
): string {
178-
if (!whereClause || !encode) return whereClause ?? ''
178+
if (!whereClause || !encode) return whereClause ?? ``
179179

180180
// SQL keywords that should not be transformed (common ones)
181181
const sqlKeywords = new Set([
@@ -301,6 +301,8 @@ export function encodeWhereClause(
301301
* - Complex nested expressions
302302
* - Custom operators or functions
303303
* - Column names that conflict with SQL keywords
304+
* - Quoted identifiers (e.g., `"$price"`, `"user-id"`) - not supported
305+
* - Column names with special characters (non-alphanumeric except underscore)
304306
* - **Acronym ambiguity**: `userID` → `user_id` → `userId` (ID becomes Id after roundtrip)
305307
* Use `createColumnMapper()` with explicit mapping if you need exact control
306308
* - **Type conversion**: This only renames columns, not values. Use `parser` for type conversion
@@ -309,6 +311,7 @@ export function encodeWhereClause(
309311
* - You have column names that don't follow snake_case/camelCase patterns
310312
* - You need exact control over mappings (e.g., `id` → `identifier`)
311313
* - Your WHERE clauses are complex and automatic encoding fails
314+
* - You have quoted identifiers or column names with special characters
312315
*
313316
* @param schema - Optional database schema to constrain mapping to known columns
314317
* @returns A ColumnMapper for snake_case ↔ camelCase conversion

website/docs/api/clients/typescript.md

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -477,37 +477,77 @@ shape.subscribe((data) => {
477477
})
478478
```
479479

480-
**Transformer**
481-
482-
While the parser operates on individual fields, the transformer allows you to modify the entire record after the parser has run.
480+
**Column Mapping**
483481

484-
This can be used to convert field names to camelCase or rename fields.
482+
For transforming column names between database format (e.g., snake_case) and application format (e.g., camelCase), use the `columnMapper` option. This provides bidirectional transformation, automatically encoding column names in WHERE clauses and decoding them in query results.
485483

486484
```ts
485+
import { ShapeStream, snakeCamelMapper } from '@electric-sql/client'
486+
487487
type CustomRow = {
488488
id: number
489489
postTitle: string // post_title in database
490490
createdAt: Date // created_at in database
491491
}
492492

493-
// transformer example: camelCaseKeys
494-
const toCamelCase = (str: string) =>
495-
str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
496-
497-
const camelCaseKeys: TransformFunction = (row) =>
498-
Object.fromEntries(Object.entries(row).map(([k, v]) => [toCamelCase(k), v]))
499-
500493
const stream = new ShapeStream<CustomRow>({
501494
url: 'http://localhost:3000/v1/shape',
502495
params: {
503496
table: 'posts',
504497
},
505-
transformer: camelCaseKeys,
498+
parser: {
499+
timestamptz: (date: string) => new Date(date), // Type conversion
500+
},
501+
columnMapper: snakeCamelMapper(), // Column name transformation
506502
})
507503

508-
const shape = new Shape(stream)
509-
shape.subscribe((data) => {
510-
console.log(Object.keys(data)) // [id, postTitle, createdAt]
504+
// Now you can use camelCase in WHERE clauses too:
505+
await stream.requestSnapshot({
506+
where: "postTitle LIKE $1", // Automatically encoded to: post_title LIKE $1
507+
params: { "1": "%electric%" },
508+
orderBy: "createdAt DESC", // Automatically encoded to: created_at DESC
509+
limit: 10,
510+
})
511+
```
512+
513+
For custom mappings, use `createColumnMapper`:
514+
515+
```ts
516+
import { createColumnMapper } from '@electric-sql/client'
517+
518+
const mapper = createColumnMapper({
519+
post_title: 'postTitle',
520+
created_at: 'createdAt',
521+
})
522+
523+
const stream = new ShapeStream<CustomRow>({
524+
url: 'http://localhost:3000/v1/shape',
525+
params: { table: 'posts' },
526+
columnMapper: mapper,
527+
})
528+
```
529+
530+
**Transformer**
531+
532+
While the parser operates on individual fields and columnMapper renames columns, the transformer allows you to modify the entire record for value transformations like client-side decryption of end-to-end encrypted data, computing derived fields, or other data processing.
533+
534+
**Note:** For column name transformations (snake_case ↔ camelCase), use `columnMapper` instead. The transformer is specifically for transforming values, not column names.
535+
536+
```ts
537+
type CustomRow = {
538+
id: number
539+
title: string
540+
encryptedData: string
541+
}
542+
543+
const stream = new ShapeStream<CustomRow>({
544+
url: 'http://localhost:3000/v1/shape',
545+
params: { table: 'posts' },
546+
columnMapper: snakeCamelMapper(), // Rename columns
547+
transformer: (row) => ({
548+
...row,
549+
encryptedData: decrypt(row.encryptedData), // Transform values
550+
}),
511551
})
512552
```
513553

0 commit comments

Comments
 (0)