@electric-sql/client 1.1.4 → 1.2.0
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/cjs/index.cjs +426 -50
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +229 -9
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +229 -9
- package/dist/index.legacy-esm.js +419 -47
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +421 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +254 -42
- package/src/column-mapper.ts +357 -0
- package/src/index.ts +7 -0
- package/src/parser.ts +45 -7
- package/src/up-to-date-tracker.ts +157 -0
package/dist/index.d.ts
CHANGED
|
@@ -118,6 +118,140 @@ type Parser<Extensions = never> = {
|
|
|
118
118
|
};
|
|
119
119
|
type TransformFunction<Extensions = never> = (message: Row<Extensions>) => Row<Extensions>;
|
|
120
120
|
|
|
121
|
+
type DbColumnName = string;
|
|
122
|
+
type AppColumnName = string;
|
|
123
|
+
/**
|
|
124
|
+
* A bidirectional column mapper that handles transforming column **names**
|
|
125
|
+
* between database format (e.g., snake_case) and application format (e.g., camelCase).
|
|
126
|
+
*
|
|
127
|
+
* **Important**: ColumnMapper only transforms column names, not column values or types.
|
|
128
|
+
* For type conversions (e.g., string → Date), use the `parser` option.
|
|
129
|
+
* For value transformations (e.g., encryption), use the `transformer` option.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* const mapper = snakeCamelMapper()
|
|
134
|
+
* mapper.decode('user_id') // 'userId'
|
|
135
|
+
* mapper.encode('userId') // 'user_id'
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
interface ColumnMapper {
|
|
139
|
+
/**
|
|
140
|
+
* Transform a column name from database format to application format.
|
|
141
|
+
* Applied to column names in query results.
|
|
142
|
+
*/
|
|
143
|
+
decode: (dbColumnName: DbColumnName) => AppColumnName;
|
|
144
|
+
/**
|
|
145
|
+
* Transform a column name from application format to database format.
|
|
146
|
+
* Applied to column names in WHERE clauses and other query parameters.
|
|
147
|
+
*/
|
|
148
|
+
encode: (appColumnName: AppColumnName) => DbColumnName;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Converts a snake_case string to camelCase.
|
|
152
|
+
*
|
|
153
|
+
* Handles edge cases:
|
|
154
|
+
* - Preserves leading underscores: `_user_id` → `_userId`
|
|
155
|
+
* - Preserves trailing underscores: `user_id_` → `userId_`
|
|
156
|
+
* - Collapses multiple underscores: `user__id` → `userId`
|
|
157
|
+
* - Normalizes to lowercase first: `user_Column` → `userColumn`
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* snakeToCamel('user_id') // 'userId'
|
|
161
|
+
* snakeToCamel('project_id') // 'projectId'
|
|
162
|
+
* snakeToCamel('created_at') // 'createdAt'
|
|
163
|
+
* snakeToCamel('_private') // '_private'
|
|
164
|
+
* snakeToCamel('user__id') // 'userId'
|
|
165
|
+
* snakeToCamel('user_id_') // 'userId_'
|
|
166
|
+
*/
|
|
167
|
+
declare function snakeToCamel(str: string): string;
|
|
168
|
+
/**
|
|
169
|
+
* Converts a camelCase string to snake_case.
|
|
170
|
+
*
|
|
171
|
+
* Handles consecutive capitals (acronyms) properly:
|
|
172
|
+
* - `userID` → `user_id`
|
|
173
|
+
* - `userHTTPSURL` → `user_https_url`
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* camelToSnake('userId') // 'user_id'
|
|
177
|
+
* camelToSnake('projectId') // 'project_id'
|
|
178
|
+
* camelToSnake('createdAt') // 'created_at'
|
|
179
|
+
* camelToSnake('userID') // 'user_id'
|
|
180
|
+
* camelToSnake('parseHTMLString') // 'parse_html_string'
|
|
181
|
+
*/
|
|
182
|
+
declare function camelToSnake(str: string): string;
|
|
183
|
+
/**
|
|
184
|
+
* Creates a column mapper from an explicit mapping of database columns to application columns.
|
|
185
|
+
*
|
|
186
|
+
* @param mapping - Object mapping database column names (keys) to application column names (values)
|
|
187
|
+
* @returns A ColumnMapper that can encode and decode column names bidirectionally
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* const mapper = createColumnMapper({
|
|
191
|
+
* user_id: 'userId',
|
|
192
|
+
* project_id: 'projectId',
|
|
193
|
+
* created_at: 'createdAt'
|
|
194
|
+
* })
|
|
195
|
+
*
|
|
196
|
+
* // Use with ShapeStream
|
|
197
|
+
* const stream = new ShapeStream({
|
|
198
|
+
* url: 'http://localhost:3000/v1/shape',
|
|
199
|
+
* params: { table: 'todos' },
|
|
200
|
+
* columnMapper: mapper
|
|
201
|
+
* })
|
|
202
|
+
*/
|
|
203
|
+
declare function createColumnMapper(mapping: Record<string, string>): ColumnMapper;
|
|
204
|
+
/**
|
|
205
|
+
* Creates a column mapper that automatically converts between snake_case and camelCase.
|
|
206
|
+
* This is the most common use case for column mapping.
|
|
207
|
+
*
|
|
208
|
+
* When a schema is provided, it will only map columns that exist in the schema.
|
|
209
|
+
* Otherwise, it will map any column name it encounters.
|
|
210
|
+
*
|
|
211
|
+
* **⚠️ Limitations and Edge Cases:**
|
|
212
|
+
* - **WHERE clause encoding**: Uses regex-based parsing which may not handle all complex
|
|
213
|
+
* SQL expressions. Test thoroughly with your queries, especially those with:
|
|
214
|
+
* - Complex nested expressions
|
|
215
|
+
* - Custom operators or functions
|
|
216
|
+
* - Column names that conflict with SQL keywords
|
|
217
|
+
* - Quoted identifiers (e.g., `"$price"`, `"user-id"`) - not supported
|
|
218
|
+
* - Column names with special characters (non-alphanumeric except underscore)
|
|
219
|
+
* - **Acronym ambiguity**: `userID` → `user_id` → `userId` (ID becomes Id after roundtrip)
|
|
220
|
+
* Use `createColumnMapper()` with explicit mapping if you need exact control
|
|
221
|
+
* - **Type conversion**: This only renames columns, not values. Use `parser` for type conversion
|
|
222
|
+
*
|
|
223
|
+
* **When to use explicit mapping instead:**
|
|
224
|
+
* - You have column names that don't follow snake_case/camelCase patterns
|
|
225
|
+
* - You need exact control over mappings (e.g., `id` → `identifier`)
|
|
226
|
+
* - Your WHERE clauses are complex and automatic encoding fails
|
|
227
|
+
* - You have quoted identifiers or column names with special characters
|
|
228
|
+
*
|
|
229
|
+
* @param schema - Optional database schema to constrain mapping to known columns
|
|
230
|
+
* @returns A ColumnMapper for snake_case ↔ camelCase conversion
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* // Basic usage
|
|
234
|
+
* const mapper = snakeCamelMapper()
|
|
235
|
+
*
|
|
236
|
+
* // With schema - only maps columns in schema (recommended)
|
|
237
|
+
* const mapper = snakeCamelMapper(schema)
|
|
238
|
+
*
|
|
239
|
+
* // Use with ShapeStream
|
|
240
|
+
* const stream = new ShapeStream({
|
|
241
|
+
* url: 'http://localhost:3000/v1/shape',
|
|
242
|
+
* params: { table: 'todos' },
|
|
243
|
+
* columnMapper: snakeCamelMapper()
|
|
244
|
+
* })
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* // If automatic encoding fails, fall back to manual column names in WHERE clauses:
|
|
248
|
+
* stream.requestSnapshot({
|
|
249
|
+
* where: "user_id = $1", // Use database column names directly if needed
|
|
250
|
+
* params: { "1": "123" }
|
|
251
|
+
* })
|
|
252
|
+
*/
|
|
253
|
+
declare function snakeCamelMapper(schema?: Schema): ColumnMapper;
|
|
254
|
+
|
|
121
255
|
declare class FetchError extends Error {
|
|
122
256
|
url: string;
|
|
123
257
|
status: number;
|
|
@@ -301,7 +435,84 @@ interface ShapeStreamOptions<T = never> {
|
|
|
301
435
|
fetchClient?: typeof fetch;
|
|
302
436
|
backoffOptions?: BackoffOptions;
|
|
303
437
|
parser?: Parser<T>;
|
|
438
|
+
/**
|
|
439
|
+
* Function to transform rows after parsing (e.g., for encryption, type coercion).
|
|
440
|
+
* Applied to data received from Electric.
|
|
441
|
+
*
|
|
442
|
+
* **Note**: If you're using `transformer` solely for column name transformation
|
|
443
|
+
* (e.g., snake_case → camelCase), consider using `columnMapper` instead, which
|
|
444
|
+
* provides bidirectional transformation and automatically encodes WHERE clauses.
|
|
445
|
+
*
|
|
446
|
+
* **Execution order** when both are provided:
|
|
447
|
+
* 1. `columnMapper.decode` runs first (renames columns)
|
|
448
|
+
* 2. `transformer` runs second (transforms values)
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```typescript
|
|
452
|
+
* // For column renaming only - use columnMapper
|
|
453
|
+
* import { snakeCamelMapper } from '@electric-sql/client'
|
|
454
|
+
* const stream = new ShapeStream({ columnMapper: snakeCamelMapper() })
|
|
455
|
+
* ```
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* // For value transformation (encryption, etc.) - use transformer
|
|
460
|
+
* const stream = new ShapeStream({
|
|
461
|
+
* transformer: (row) => ({
|
|
462
|
+
* ...row,
|
|
463
|
+
* encrypted_field: decrypt(row.encrypted_field)
|
|
464
|
+
* })
|
|
465
|
+
* })
|
|
466
|
+
* ```
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* ```typescript
|
|
470
|
+
* // Use both together
|
|
471
|
+
* const stream = new ShapeStream({
|
|
472
|
+
* columnMapper: snakeCamelMapper(), // Runs first: renames columns
|
|
473
|
+
* transformer: (row) => ({ // Runs second: transforms values
|
|
474
|
+
* ...row,
|
|
475
|
+
* encryptedData: decrypt(row.encryptedData)
|
|
476
|
+
* })
|
|
477
|
+
* })
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
304
480
|
transformer?: TransformFunction<T>;
|
|
481
|
+
/**
|
|
482
|
+
* Bidirectional column name mapper for transforming between database column names
|
|
483
|
+
* (e.g., snake_case) and application column names (e.g., camelCase).
|
|
484
|
+
*
|
|
485
|
+
* The mapper handles both:
|
|
486
|
+
* - **Decoding**: Database → Application (applied to query results)
|
|
487
|
+
* - **Encoding**: Application → Database (applied to WHERE clauses)
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```typescript
|
|
491
|
+
* // Most common case: snake_case ↔ camelCase
|
|
492
|
+
* import { snakeCamelMapper } from '@electric-sql/client'
|
|
493
|
+
*
|
|
494
|
+
* const stream = new ShapeStream({
|
|
495
|
+
* url: 'http://localhost:3000/v1/shape',
|
|
496
|
+
* params: { table: 'todos' },
|
|
497
|
+
* columnMapper: snakeCamelMapper()
|
|
498
|
+
* })
|
|
499
|
+
* ```
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```typescript
|
|
503
|
+
* // Custom mapping
|
|
504
|
+
* import { createColumnMapper } from '@electric-sql/client'
|
|
505
|
+
*
|
|
506
|
+
* const stream = new ShapeStream({
|
|
507
|
+
* columnMapper: createColumnMapper({
|
|
508
|
+
* user_id: 'userId',
|
|
509
|
+
* project_id: 'projectId',
|
|
510
|
+
* created_at: 'createdAt'
|
|
511
|
+
* })
|
|
512
|
+
* })
|
|
513
|
+
* ```
|
|
514
|
+
*/
|
|
515
|
+
columnMapper?: ColumnMapper;
|
|
305
516
|
/**
|
|
306
517
|
* A function for handling shapestream errors.
|
|
307
518
|
*
|
|
@@ -368,16 +579,14 @@ interface ShapeStreamInterface<T extends Row<unknown> = Row> {
|
|
|
368
579
|
error?: unknown;
|
|
369
580
|
mode: LogMode;
|
|
370
581
|
forceDisconnectAndRefresh(): Promise<void>;
|
|
371
|
-
requestSnapshot(params: {
|
|
372
|
-
where?: string;
|
|
373
|
-
params?: Record<string, string>;
|
|
374
|
-
limit: number;
|
|
375
|
-
offset?: number;
|
|
376
|
-
orderBy: string;
|
|
377
|
-
}): Promise<{
|
|
582
|
+
requestSnapshot(params: SubsetParams): Promise<{
|
|
378
583
|
metadata: SnapshotMetadata;
|
|
379
584
|
data: Array<Message<T>>;
|
|
380
585
|
}>;
|
|
586
|
+
fetchSnapshot(opts: SubsetParams): Promise<{
|
|
587
|
+
metadata: SnapshotMetadata;
|
|
588
|
+
data: Array<ChangeMessage<T>>;
|
|
589
|
+
}>;
|
|
381
590
|
}
|
|
382
591
|
/**
|
|
383
592
|
* Reads updates to a shape from Electric using HTTP requests and long polling or
|
|
@@ -451,7 +660,7 @@ declare class ShapeStream<T extends Row<unknown> = Row> implements ShapeStreamIn
|
|
|
451
660
|
*/
|
|
452
661
|
forceDisconnectAndRefresh(): Promise<void>;
|
|
453
662
|
/**
|
|
454
|
-
* Request a snapshot for subset of data.
|
|
663
|
+
* Request a snapshot for subset of data and inject it into the subscribed data stream.
|
|
455
664
|
*
|
|
456
665
|
* Only available when mode is `changes_only`.
|
|
457
666
|
* Returns the insertion point & the data, but more importantly injects the data
|
|
@@ -468,6 +677,17 @@ declare class ShapeStream<T extends Row<unknown> = Row> implements ShapeStreamIn
|
|
|
468
677
|
metadata: SnapshotMetadata;
|
|
469
678
|
data: Array<ChangeMessage<T>>;
|
|
470
679
|
}>;
|
|
680
|
+
/**
|
|
681
|
+
* Fetch a snapshot for subset of data.
|
|
682
|
+
* Returns the metadata and the data, but does not inject it into the subscribed data stream.
|
|
683
|
+
*
|
|
684
|
+
* @param opts - The options for the snapshot request.
|
|
685
|
+
* @returns The metadata and the data for the snapshot.
|
|
686
|
+
*/
|
|
687
|
+
fetchSnapshot(opts: SubsetParams): Promise<{
|
|
688
|
+
metadata: SnapshotMetadata;
|
|
689
|
+
data: Array<ChangeMessage<T>>;
|
|
690
|
+
}>;
|
|
471
691
|
}
|
|
472
692
|
|
|
473
693
|
type ShapeData<T extends Row<unknown> = Row> = Map<string, T>;
|
|
@@ -588,4 +808,4 @@ declare function isControlMessage<T extends Row<unknown> = Row>(message: Message
|
|
|
588
808
|
*/
|
|
589
809
|
declare function isVisibleInSnapshot(txid: number | bigint | `${bigint}`, snapshot: PostgresSnapshot | NormalizedPgSnapshot): boolean;
|
|
590
810
|
|
|
591
|
-
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 };
|
|
811
|
+
export { BackoffDefaults, type BackoffOptions, type BitColumn, type BpcharColumn, type ChangeMessage, type ColumnInfo, type ColumnMapper, 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, camelToSnake, createColumnMapper, isChangeMessage, isControlMessage, isVisibleInSnapshot, resolveValue, snakeCamelMapper, snakeToCamel };
|