@electric-sql/client 1.1.5 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -20,9 +20,31 @@ type NormalizedPgSnapshot = {
20
20
  xip_list: bigint[];
21
21
  };
22
22
  interface Header {
23
- [key: Exclude<string, `operation` | `control`>]: Value;
23
+ [key: Exclude<string, `operation` | `control` | `event`>]: Value;
24
24
  }
25
25
  type Operation = `insert` | `update` | `delete`;
26
+ /**
27
+ * A tag is a string identifying a reason for this row to be part of the shape.
28
+ *
29
+ * Tags can be composite, but they are always sent as a single string. Compound tags
30
+ * are separated by `|`. It's up to the client to split the tag into its components
31
+ * in order to react to move-outs correctly. Tag parts are guaranteed to not contain an
32
+ * unescaped `|` character (escaped as `\\|`) or be a literal `*`.
33
+ *
34
+ * Composite tag width is guaranteed to be fixed for a given shape.
35
+ */
36
+ type MoveTag = string;
37
+ /**
38
+ * A move-out pattern is a position and a value. The position is the index of the column
39
+ * that is being moved out. The value is the value of the column that is being moved out.
40
+ *
41
+ * Tag width and value order is fixed for a given shape, so the client can determine
42
+ * which tags match this pattern.
43
+ */
44
+ type MoveOutPattern = {
45
+ pos: number;
46
+ value: string;
47
+ };
26
48
  type ControlMessage = {
27
49
  headers: (Header & {
28
50
  control: `up-to-date` | `must-refetch`;
@@ -31,6 +53,12 @@ type ControlMessage = {
31
53
  control: `snapshot-end`;
32
54
  } & PostgresSnapshot);
33
55
  };
56
+ type EventMessage = {
57
+ headers: Header & {
58
+ event: `move-out`;
59
+ patterns: MoveOutPattern[];
60
+ };
61
+ };
34
62
  type ChangeMessage<T extends Row<unknown> = Row> = {
35
63
  key: string;
36
64
  value: T;
@@ -38,9 +66,12 @@ type ChangeMessage<T extends Row<unknown> = Row> = {
38
66
  headers: Header & {
39
67
  operation: Operation;
40
68
  txids?: number[];
69
+ /** Tags will always be present for changes if the shape has a subquery in its where clause, and are omitted otherwise.*/
70
+ tags?: MoveTag[];
71
+ removed_tags?: MoveTag[];
41
72
  };
42
73
  };
43
- type Message<T extends Row<unknown> = Row> = ControlMessage | ChangeMessage<T>;
74
+ type Message<T extends Row<unknown> = Row> = ControlMessage | EventMessage | ChangeMessage<T>;
44
75
  /**
45
76
  * Common properties for all columns.
46
77
  * `dims` is the number of dimensions of the column. Only provided if the column is an array.
@@ -118,6 +149,140 @@ type Parser<Extensions = never> = {
118
149
  };
119
150
  type TransformFunction<Extensions = never> = (message: Row<Extensions>) => Row<Extensions>;
120
151
 
152
+ type DbColumnName = string;
153
+ type AppColumnName = string;
154
+ /**
155
+ * A bidirectional column mapper that handles transforming column **names**
156
+ * between database format (e.g., snake_case) and application format (e.g., camelCase).
157
+ *
158
+ * **Important**: ColumnMapper only transforms column names, not column values or types.
159
+ * For type conversions (e.g., string → Date), use the `parser` option.
160
+ * For value transformations (e.g., encryption), use the `transformer` option.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * const mapper = snakeCamelMapper()
165
+ * mapper.decode('user_id') // 'userId'
166
+ * mapper.encode('userId') // 'user_id'
167
+ * ```
168
+ */
169
+ interface ColumnMapper {
170
+ /**
171
+ * Transform a column name from database format to application format.
172
+ * Applied to column names in query results.
173
+ */
174
+ decode: (dbColumnName: DbColumnName) => AppColumnName;
175
+ /**
176
+ * Transform a column name from application format to database format.
177
+ * Applied to column names in WHERE clauses and other query parameters.
178
+ */
179
+ encode: (appColumnName: AppColumnName) => DbColumnName;
180
+ }
181
+ /**
182
+ * Converts a snake_case string to camelCase.
183
+ *
184
+ * Handles edge cases:
185
+ * - Preserves leading underscores: `_user_id` → `_userId`
186
+ * - Preserves trailing underscores: `user_id_` → `userId_`
187
+ * - Collapses multiple underscores: `user__id` → `userId`
188
+ * - Normalizes to lowercase first: `user_Column` → `userColumn`
189
+ *
190
+ * @example
191
+ * snakeToCamel('user_id') // 'userId'
192
+ * snakeToCamel('project_id') // 'projectId'
193
+ * snakeToCamel('created_at') // 'createdAt'
194
+ * snakeToCamel('_private') // '_private'
195
+ * snakeToCamel('user__id') // 'userId'
196
+ * snakeToCamel('user_id_') // 'userId_'
197
+ */
198
+ declare function snakeToCamel(str: string): string;
199
+ /**
200
+ * Converts a camelCase string to snake_case.
201
+ *
202
+ * Handles consecutive capitals (acronyms) properly:
203
+ * - `userID` → `user_id`
204
+ * - `userHTTPSURL` → `user_https_url`
205
+ *
206
+ * @example
207
+ * camelToSnake('userId') // 'user_id'
208
+ * camelToSnake('projectId') // 'project_id'
209
+ * camelToSnake('createdAt') // 'created_at'
210
+ * camelToSnake('userID') // 'user_id'
211
+ * camelToSnake('parseHTMLString') // 'parse_html_string'
212
+ */
213
+ declare function camelToSnake(str: string): string;
214
+ /**
215
+ * Creates a column mapper from an explicit mapping of database columns to application columns.
216
+ *
217
+ * @param mapping - Object mapping database column names (keys) to application column names (values)
218
+ * @returns A ColumnMapper that can encode and decode column names bidirectionally
219
+ *
220
+ * @example
221
+ * const mapper = createColumnMapper({
222
+ * user_id: 'userId',
223
+ * project_id: 'projectId',
224
+ * created_at: 'createdAt'
225
+ * })
226
+ *
227
+ * // Use with ShapeStream
228
+ * const stream = new ShapeStream({
229
+ * url: 'http://localhost:3000/v1/shape',
230
+ * params: { table: 'todos' },
231
+ * columnMapper: mapper
232
+ * })
233
+ */
234
+ declare function createColumnMapper(mapping: Record<string, string>): ColumnMapper;
235
+ /**
236
+ * Creates a column mapper that automatically converts between snake_case and camelCase.
237
+ * This is the most common use case for column mapping.
238
+ *
239
+ * When a schema is provided, it will only map columns that exist in the schema.
240
+ * Otherwise, it will map any column name it encounters.
241
+ *
242
+ * **⚠️ Limitations and Edge Cases:**
243
+ * - **WHERE clause encoding**: Uses regex-based parsing which may not handle all complex
244
+ * SQL expressions. Test thoroughly with your queries, especially those with:
245
+ * - Complex nested expressions
246
+ * - Custom operators or functions
247
+ * - Column names that conflict with SQL keywords
248
+ * - Quoted identifiers (e.g., `"$price"`, `"user-id"`) - not supported
249
+ * - Column names with special characters (non-alphanumeric except underscore)
250
+ * - **Acronym ambiguity**: `userID` → `user_id` → `userId` (ID becomes Id after roundtrip)
251
+ * Use `createColumnMapper()` with explicit mapping if you need exact control
252
+ * - **Type conversion**: This only renames columns, not values. Use `parser` for type conversion
253
+ *
254
+ * **When to use explicit mapping instead:**
255
+ * - You have column names that don't follow snake_case/camelCase patterns
256
+ * - You need exact control over mappings (e.g., `id` → `identifier`)
257
+ * - Your WHERE clauses are complex and automatic encoding fails
258
+ * - You have quoted identifiers or column names with special characters
259
+ *
260
+ * @param schema - Optional database schema to constrain mapping to known columns
261
+ * @returns A ColumnMapper for snake_case ↔ camelCase conversion
262
+ *
263
+ * @example
264
+ * // Basic usage
265
+ * const mapper = snakeCamelMapper()
266
+ *
267
+ * // With schema - only maps columns in schema (recommended)
268
+ * const mapper = snakeCamelMapper(schema)
269
+ *
270
+ * // Use with ShapeStream
271
+ * const stream = new ShapeStream({
272
+ * url: 'http://localhost:3000/v1/shape',
273
+ * params: { table: 'todos' },
274
+ * columnMapper: snakeCamelMapper()
275
+ * })
276
+ *
277
+ * @example
278
+ * // If automatic encoding fails, fall back to manual column names in WHERE clauses:
279
+ * stream.requestSnapshot({
280
+ * where: "user_id = $1", // Use database column names directly if needed
281
+ * params: { "1": "123" }
282
+ * })
283
+ */
284
+ declare function snakeCamelMapper(schema?: Schema): ColumnMapper;
285
+
121
286
  declare class FetchError extends Error {
122
287
  url: string;
123
288
  status: number;
@@ -301,7 +466,84 @@ interface ShapeStreamOptions<T = never> {
301
466
  fetchClient?: typeof fetch;
302
467
  backoffOptions?: BackoffOptions;
303
468
  parser?: Parser<T>;
469
+ /**
470
+ * Function to transform rows after parsing (e.g., for encryption, type coercion).
471
+ * Applied to data received from Electric.
472
+ *
473
+ * **Note**: If you're using `transformer` solely for column name transformation
474
+ * (e.g., snake_case → camelCase), consider using `columnMapper` instead, which
475
+ * provides bidirectional transformation and automatically encodes WHERE clauses.
476
+ *
477
+ * **Execution order** when both are provided:
478
+ * 1. `columnMapper.decode` runs first (renames columns)
479
+ * 2. `transformer` runs second (transforms values)
480
+ *
481
+ * @example
482
+ * ```typescript
483
+ * // For column renaming only - use columnMapper
484
+ * import { snakeCamelMapper } from '@electric-sql/client'
485
+ * const stream = new ShapeStream({ columnMapper: snakeCamelMapper() })
486
+ * ```
487
+ *
488
+ * @example
489
+ * ```typescript
490
+ * // For value transformation (encryption, etc.) - use transformer
491
+ * const stream = new ShapeStream({
492
+ * transformer: (row) => ({
493
+ * ...row,
494
+ * encrypted_field: decrypt(row.encrypted_field)
495
+ * })
496
+ * })
497
+ * ```
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * // Use both together
502
+ * const stream = new ShapeStream({
503
+ * columnMapper: snakeCamelMapper(), // Runs first: renames columns
504
+ * transformer: (row) => ({ // Runs second: transforms values
505
+ * ...row,
506
+ * encryptedData: decrypt(row.encryptedData)
507
+ * })
508
+ * })
509
+ * ```
510
+ */
304
511
  transformer?: TransformFunction<T>;
512
+ /**
513
+ * Bidirectional column name mapper for transforming between database column names
514
+ * (e.g., snake_case) and application column names (e.g., camelCase).
515
+ *
516
+ * The mapper handles both:
517
+ * - **Decoding**: Database → Application (applied to query results)
518
+ * - **Encoding**: Application → Database (applied to WHERE clauses)
519
+ *
520
+ * @example
521
+ * ```typescript
522
+ * // Most common case: snake_case ↔ camelCase
523
+ * import { snakeCamelMapper } from '@electric-sql/client'
524
+ *
525
+ * const stream = new ShapeStream({
526
+ * url: 'http://localhost:3000/v1/shape',
527
+ * params: { table: 'todos' },
528
+ * columnMapper: snakeCamelMapper()
529
+ * })
530
+ * ```
531
+ *
532
+ * @example
533
+ * ```typescript
534
+ * // Custom mapping
535
+ * import { createColumnMapper } from '@electric-sql/client'
536
+ *
537
+ * const stream = new ShapeStream({
538
+ * columnMapper: createColumnMapper({
539
+ * user_id: 'userId',
540
+ * project_id: 'projectId',
541
+ * created_at: 'createdAt'
542
+ * })
543
+ * })
544
+ * ```
545
+ */
546
+ columnMapper?: ColumnMapper;
305
547
  /**
306
548
  * A function for handling shapestream errors.
307
549
  *
@@ -597,4 +839,4 @@ declare function isControlMessage<T extends Row<unknown> = Row>(message: Message
597
839
  */
598
840
  declare function isVisibleInSnapshot(txid: number | bigint | `${bigint}`, snapshot: PostgresSnapshot | NormalizedPgSnapshot): boolean;
599
841
 
600
- export { BackoffDefaults, type BackoffOptions, type BitColumn, type BpcharColumn, type ChangeMessage, type ColumnInfo, type CommonColumnProps, type ControlMessage, ELECTRIC_PROTOCOL_QUERY_PARAMS, type ExternalHeadersRecord, type ExternalParamsRecord, FetchError, type GetExtensions, type IntervalColumn, type IntervalColumnWithPrecision, type LogMode, type MaybePromise, type Message, type NormalizedPgSnapshot, type NumericColumn, type Offset, type Operation, type PostgresParams, type PostgresSnapshot, type RegularColumn, type Row, type Schema, Shape, type ShapeChangedCallback, type ShapeData, ShapeStream, type ShapeStreamInterface, type ShapeStreamOptions, type SnapshotMetadata, type SubsetParams, type TimeColumn, type TypedMessages, type Value, type VarcharColumn, isChangeMessage, isControlMessage, isVisibleInSnapshot, resolveValue };
842
+ export { BackoffDefaults, type BackoffOptions, type BitColumn, type BpcharColumn, type ChangeMessage, type ColumnInfo, type ColumnMapper, type CommonColumnProps, type ControlMessage, ELECTRIC_PROTOCOL_QUERY_PARAMS, type EventMessage, type ExternalHeadersRecord, type ExternalParamsRecord, FetchError, type GetExtensions, type IntervalColumn, type IntervalColumnWithPrecision, type LogMode, type MaybePromise, type Message, type MoveOutPattern, type MoveTag, type NormalizedPgSnapshot, type NumericColumn, type Offset, type Operation, type PostgresParams, type PostgresSnapshot, type RegularColumn, type Row, type Schema, Shape, type ShapeChangedCallback, type ShapeData, ShapeStream, type ShapeStreamInterface, type ShapeStreamOptions, type SnapshotMetadata, type SubsetParams, type TimeColumn, type TypedMessages, type Value, type VarcharColumn, camelToSnake, createColumnMapper, isChangeMessage, isControlMessage, isVisibleInSnapshot, resolveValue, snakeCamelMapper, snakeToCamel };