@electric-sql/client 0.3.3 → 0.4.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/README.md +3 -3
- package/dist/cjs/index.cjs +82 -53
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.browser.mjs +1 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +49 -46
- package/dist/index.legacy-esm.js +82 -53
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +82 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +146 -107
- package/src/helpers.ts +3 -3
- package/src/parser.ts +4 -4
- package/src/types.ts +5 -5
package/src/client.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Message,
|
|
1
|
+
import { Message, Offset, Schema, Row } from './types'
|
|
2
2
|
import { MessageParser, Parser } from './parser'
|
|
3
3
|
import { isChangeMessage, isControlMessage } from './helpers'
|
|
4
4
|
|
|
5
|
-
export type ShapeData = Map<string,
|
|
6
|
-
export type ShapeChangedCallback =
|
|
5
|
+
export type ShapeData<T extends Row = Row> = Map<string, T>
|
|
6
|
+
export type ShapeChangedCallback<T extends Row = Row> = (
|
|
7
|
+
value: ShapeData<T>
|
|
8
|
+
) => void
|
|
7
9
|
|
|
8
10
|
export interface BackoffOptions {
|
|
9
11
|
initialDelay: number
|
|
@@ -62,16 +64,16 @@ export interface ShapeStreamOptions {
|
|
|
62
64
|
* @constructor
|
|
63
65
|
* @param {(messages: Message[]) => void} callback function
|
|
64
66
|
*/
|
|
65
|
-
class MessageProcessor {
|
|
66
|
-
private messageQueue: Message[][] = []
|
|
67
|
+
class MessageProcessor<T extends Row = Row> {
|
|
68
|
+
private messageQueue: Message<T>[][] = []
|
|
67
69
|
private isProcessing = false
|
|
68
|
-
private callback: (messages: Message[]) => void | Promise<void>
|
|
70
|
+
private callback: (messages: Message<T>[]) => void | Promise<void>
|
|
69
71
|
|
|
70
|
-
constructor(callback: (messages: Message[]) => void | Promise<void>) {
|
|
72
|
+
constructor(callback: (messages: Message<T>[]) => void | Promise<void>) {
|
|
71
73
|
this.callback = callback
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
process(messages: Message[]) {
|
|
76
|
+
process(messages: Message<T>[]) {
|
|
75
77
|
this.messageQueue.push(messages)
|
|
76
78
|
|
|
77
79
|
if (!this.isProcessing) {
|
|
@@ -144,28 +146,30 @@ export class FetchError extends Error {
|
|
|
144
146
|
* to consume the HTTP `GET /v1/shape` api.
|
|
145
147
|
*
|
|
146
148
|
* @constructor
|
|
147
|
-
* @param {ShapeStreamOptions} options
|
|
148
|
-
*
|
|
149
|
+
* @param {ShapeStreamOptions} options - configure the shape stream
|
|
150
|
+
* @example
|
|
149
151
|
* Register a callback function to subscribe to the messages.
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
152
|
+
* ```
|
|
153
|
+
* const stream = new ShapeStream(options)
|
|
154
|
+
* stream.subscribe(messages => {
|
|
155
|
+
* // messages is 1 or more row updates
|
|
156
|
+
* })
|
|
157
|
+
* ```
|
|
155
158
|
*
|
|
156
159
|
* To abort the stream, abort the `signal`
|
|
157
160
|
* passed in via the `ShapeStreamOptions`.
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
161
|
+
* ```
|
|
162
|
+
* const aborter = new AbortController()
|
|
163
|
+
* const issueStream = new ShapeStream({
|
|
164
|
+
* url: `${BASE_URL}/${table}`
|
|
165
|
+
* subscribe: true,
|
|
166
|
+
* signal: aborter.signal,
|
|
167
|
+
* })
|
|
168
|
+
* // Later...
|
|
169
|
+
* aborter.abort()
|
|
170
|
+
* ```
|
|
167
171
|
*/
|
|
168
|
-
export class ShapeStream {
|
|
172
|
+
export class ShapeStream<T extends Row = Row> {
|
|
169
173
|
private options: ShapeStreamOptions
|
|
170
174
|
private backoffOptions: BackoffOptions
|
|
171
175
|
private fetchClient: typeof fetch
|
|
@@ -173,7 +177,7 @@ export class ShapeStream {
|
|
|
173
177
|
|
|
174
178
|
private subscribers = new Map<
|
|
175
179
|
number,
|
|
176
|
-
[MessageProcessor
|
|
180
|
+
[MessageProcessor<T>, ((error: Error) => void) | undefined]
|
|
177
181
|
>()
|
|
178
182
|
private upToDateSubscribers = new Map<
|
|
179
183
|
number,
|
|
@@ -181,8 +185,10 @@ export class ShapeStream {
|
|
|
181
185
|
>()
|
|
182
186
|
|
|
183
187
|
private lastOffset: Offset
|
|
184
|
-
private messageParser: MessageParser
|
|
188
|
+
private messageParser: MessageParser<T>
|
|
189
|
+
private lastSyncedAt?: number // unix time
|
|
185
190
|
public isUpToDate: boolean = false
|
|
191
|
+
private connected: boolean = false
|
|
186
192
|
|
|
187
193
|
shapeId?: string
|
|
188
194
|
|
|
@@ -191,7 +197,7 @@ export class ShapeStream {
|
|
|
191
197
|
this.options = { subscribe: true, ...options }
|
|
192
198
|
this.lastOffset = this.options.offset ?? `-1`
|
|
193
199
|
this.shapeId = this.options.shapeId
|
|
194
|
-
this.messageParser = new MessageParser(options.parser)
|
|
200
|
+
this.messageParser = new MessageParser<T>(options.parser)
|
|
195
201
|
|
|
196
202
|
this.backoffOptions = options.backoffOptions ?? BackoffDefaults
|
|
197
203
|
this.fetchClient =
|
|
@@ -206,85 +212,96 @@ export class ShapeStream {
|
|
|
206
212
|
|
|
207
213
|
const { url, where, signal } = this.options
|
|
208
214
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
try {
|
|
216
|
+
while ((!signal?.aborted && !this.isUpToDate) || this.options.subscribe) {
|
|
217
|
+
const fetchUrl = new URL(url)
|
|
218
|
+
if (where) fetchUrl.searchParams.set(`where`, where)
|
|
219
|
+
fetchUrl.searchParams.set(`offset`, this.lastOffset)
|
|
213
220
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
if (this.isUpToDate) {
|
|
222
|
+
fetchUrl.searchParams.set(`live`, `true`)
|
|
223
|
+
}
|
|
217
224
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
if (this.shapeId) {
|
|
226
|
+
// This should probably be a header for better cache breaking?
|
|
227
|
+
fetchUrl.searchParams.set(`shape_id`, this.shapeId!)
|
|
228
|
+
}
|
|
222
229
|
|
|
223
|
-
|
|
230
|
+
let response!: Response
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const maybeResponse = await this.fetchWithBackoff(fetchUrl)
|
|
234
|
+
if (maybeResponse) response = maybeResponse
|
|
235
|
+
else break
|
|
236
|
+
} catch (e) {
|
|
237
|
+
if (!(e instanceof FetchError)) throw e // should never happen
|
|
238
|
+
if (e.status == 409) {
|
|
239
|
+
// Upon receiving a 409, we should start from scratch
|
|
240
|
+
// with the newly provided shape ID
|
|
241
|
+
const newShapeId = e.headers[`x-electric-shape-id`]
|
|
242
|
+
this.reset(newShapeId)
|
|
243
|
+
this.publish(e.json as Message<T>[])
|
|
244
|
+
continue
|
|
245
|
+
} else if (e.status >= 400 && e.status < 500) {
|
|
246
|
+
// Notify subscribers
|
|
247
|
+
this.sendErrorToUpToDateSubscribers(e)
|
|
248
|
+
this.sendErrorToSubscribers(e)
|
|
249
|
+
|
|
250
|
+
// 400 errors are not actionable without additional user input, so we're throwing them.
|
|
251
|
+
throw e
|
|
252
|
+
}
|
|
253
|
+
}
|
|
224
254
|
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
} catch (e) {
|
|
230
|
-
if (!(e instanceof FetchError)) throw e // should never happen
|
|
231
|
-
if (e.status == 409) {
|
|
232
|
-
// Upon receiving a 409, we should start from scratch
|
|
233
|
-
// with the newly provided shape ID
|
|
234
|
-
const newShapeId = e.headers[`x-electric-shape-id`]
|
|
235
|
-
this.reset(newShapeId)
|
|
236
|
-
this.publish(e.json as Message[])
|
|
237
|
-
continue
|
|
238
|
-
} else if (e.status >= 400 && e.status < 500) {
|
|
239
|
-
// Notify subscribers
|
|
240
|
-
this.sendErrorToUpToDateSubscribers(e)
|
|
241
|
-
this.sendErrorToSubscribers(e)
|
|
242
|
-
|
|
243
|
-
// 400 errors are not actionable without additional user input, so we're throwing them.
|
|
244
|
-
throw e
|
|
255
|
+
const { headers, status } = response
|
|
256
|
+
const shapeId = headers.get(`X-Electric-Shape-Id`)
|
|
257
|
+
if (shapeId) {
|
|
258
|
+
this.shapeId = shapeId
|
|
245
259
|
}
|
|
246
|
-
}
|
|
247
260
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
261
|
+
const lastOffset = headers.get(`X-Electric-Chunk-Last-Offset`)
|
|
262
|
+
if (lastOffset) {
|
|
263
|
+
this.lastOffset = lastOffset as Offset
|
|
264
|
+
}
|
|
253
265
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
266
|
+
const getSchema = (): Schema => {
|
|
267
|
+
const schemaHeader = headers.get(`X-Electric-Schema`)
|
|
268
|
+
return schemaHeader ? JSON.parse(schemaHeader) : {}
|
|
269
|
+
}
|
|
270
|
+
this.schema = this.schema ?? getSchema()
|
|
258
271
|
|
|
259
|
-
|
|
260
|
-
const schemaHeader = headers.get(`X-Electric-Schema`)
|
|
261
|
-
return schemaHeader ? JSON.parse(schemaHeader) : {}
|
|
262
|
-
}
|
|
263
|
-
this.schema = this.schema ?? getSchema()
|
|
272
|
+
const messages = status === 204 ? `[]` : await response.text()
|
|
264
273
|
|
|
265
|
-
|
|
274
|
+
if (status === 204) {
|
|
275
|
+
// There's no content so we are live and up to date
|
|
276
|
+
this.lastSyncedAt = Date.now()
|
|
277
|
+
}
|
|
266
278
|
|
|
267
|
-
|
|
279
|
+
const batch = this.messageParser.parse(messages, this.schema)
|
|
280
|
+
|
|
281
|
+
// Update isUpToDate
|
|
282
|
+
if (batch.length > 0) {
|
|
283
|
+
const lastMessage = batch[batch.length - 1]
|
|
284
|
+
if (
|
|
285
|
+
isControlMessage(lastMessage) &&
|
|
286
|
+
lastMessage.headers.control === `up-to-date`
|
|
287
|
+
) {
|
|
288
|
+
this.lastSyncedAt = Date.now()
|
|
289
|
+
if (!this.isUpToDate) {
|
|
290
|
+
this.isUpToDate = true
|
|
291
|
+
this.notifyUpToDateSubscribers()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
268
294
|
|
|
269
|
-
|
|
270
|
-
if (batch.length > 0) {
|
|
271
|
-
const lastMessage = batch[batch.length - 1]
|
|
272
|
-
if (
|
|
273
|
-
isControlMessage(lastMessage) &&
|
|
274
|
-
lastMessage.headers.control === `up-to-date` &&
|
|
275
|
-
!this.isUpToDate
|
|
276
|
-
) {
|
|
277
|
-
this.isUpToDate = true
|
|
278
|
-
this.notifyUpToDateSubscribers()
|
|
295
|
+
this.publish(batch)
|
|
279
296
|
}
|
|
280
|
-
|
|
281
|
-
this.publish(batch)
|
|
282
297
|
}
|
|
298
|
+
} finally {
|
|
299
|
+
this.connected = false
|
|
283
300
|
}
|
|
284
301
|
}
|
|
285
302
|
|
|
286
303
|
subscribe(
|
|
287
|
-
callback: (messages: Message[]) => void | Promise<void>,
|
|
304
|
+
callback: (messages: Message<T>[]) => void | Promise<void>,
|
|
288
305
|
onError?: (error: FetchError | Error) => void
|
|
289
306
|
) {
|
|
290
307
|
const subscriptionId = Math.random()
|
|
@@ -301,7 +318,7 @@ export class ShapeStream {
|
|
|
301
318
|
this.subscribers.clear()
|
|
302
319
|
}
|
|
303
320
|
|
|
304
|
-
private publish(messages: Message[]) {
|
|
321
|
+
private publish(messages: Message<T>[]) {
|
|
305
322
|
this.subscribers.forEach(([subscriber, _]) => {
|
|
306
323
|
subscriber.process(messages)
|
|
307
324
|
})
|
|
@@ -330,6 +347,16 @@ export class ShapeStream {
|
|
|
330
347
|
this.upToDateSubscribers.clear()
|
|
331
348
|
}
|
|
332
349
|
|
|
350
|
+
/** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
|
|
351
|
+
lastSynced(): number {
|
|
352
|
+
if (this.lastSyncedAt === undefined) return Infinity
|
|
353
|
+
return Date.now() - this.lastSyncedAt
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
isConnected(): boolean {
|
|
357
|
+
return this.connected
|
|
358
|
+
}
|
|
359
|
+
|
|
333
360
|
private notifyUpToDateSubscribers() {
|
|
334
361
|
this.upToDateSubscribers.forEach(([callback]) => {
|
|
335
362
|
callback()
|
|
@@ -351,6 +378,7 @@ export class ShapeStream {
|
|
|
351
378
|
this.lastOffset = `-1`
|
|
352
379
|
this.shapeId = shapeId
|
|
353
380
|
this.isUpToDate = false
|
|
381
|
+
this.connected = false
|
|
354
382
|
this.schema = undefined
|
|
355
383
|
}
|
|
356
384
|
|
|
@@ -386,9 +414,14 @@ export class ShapeStream {
|
|
|
386
414
|
while (true) {
|
|
387
415
|
try {
|
|
388
416
|
const result = await this.fetchClient(url.toString(), { signal })
|
|
389
|
-
if (result.ok)
|
|
390
|
-
|
|
417
|
+
if (result.ok) {
|
|
418
|
+
if (this.options.subscribe) {
|
|
419
|
+
this.connected = true
|
|
420
|
+
}
|
|
421
|
+
return result
|
|
422
|
+
} else throw await FetchError.fromResponse(result, url.toString())
|
|
391
423
|
} catch (e) {
|
|
424
|
+
this.connected = false
|
|
392
425
|
if (signal?.aborted) {
|
|
393
426
|
return undefined
|
|
394
427
|
} else if (
|
|
@@ -423,10 +456,12 @@ export class ShapeStream {
|
|
|
423
456
|
* to simplify developing framework hooks.
|
|
424
457
|
*
|
|
425
458
|
* @constructor
|
|
426
|
-
* @param {
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
459
|
+
* @param {ShapeStream<T extends Row>} - the underlying shape stream
|
|
460
|
+
* @example
|
|
461
|
+
* ```
|
|
462
|
+
* const shapeStream = new ShapeStream<{ foo: number }>(url: 'http://localhost:3000/v1/shape/foo'})
|
|
463
|
+
* const shape = new Shape(shapeStream)
|
|
464
|
+
* ```
|
|
430
465
|
*
|
|
431
466
|
* `value` returns a promise that resolves the Shape data once the Shape has been
|
|
432
467
|
* fully loaded (and when resuming from being offline):
|
|
@@ -443,15 +478,15 @@ export class ShapeStream {
|
|
|
443
478
|
* console.log(shapeData)
|
|
444
479
|
* })
|
|
445
480
|
*/
|
|
446
|
-
export class Shape {
|
|
447
|
-
private stream: ShapeStream
|
|
481
|
+
export class Shape<T extends Row = Row> {
|
|
482
|
+
private stream: ShapeStream<T>
|
|
448
483
|
|
|
449
|
-
private data: ShapeData = new Map()
|
|
450
|
-
private subscribers = new Map<number, ShapeChangedCallback
|
|
484
|
+
private data: ShapeData<T> = new Map()
|
|
485
|
+
private subscribers = new Map<number, ShapeChangedCallback<T>>()
|
|
451
486
|
public error: FetchError | false = false
|
|
452
487
|
private hasNotifiedSubscribersUpToDate: boolean = false
|
|
453
488
|
|
|
454
|
-
constructor(stream: ShapeStream) {
|
|
489
|
+
constructor(stream: ShapeStream<T>) {
|
|
455
490
|
this.stream = stream
|
|
456
491
|
this.stream.subscribe(this.process.bind(this), this.handleError.bind(this))
|
|
457
492
|
const unsubscribe = this.stream.subscribeOnceToUpToDate(
|
|
@@ -465,11 +500,15 @@ export class Shape {
|
|
|
465
500
|
)
|
|
466
501
|
}
|
|
467
502
|
|
|
468
|
-
|
|
469
|
-
return this.stream.
|
|
503
|
+
lastSynced(): number {
|
|
504
|
+
return this.stream.lastSynced()
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
isConnected(): boolean {
|
|
508
|
+
return this.stream.isConnected()
|
|
470
509
|
}
|
|
471
510
|
|
|
472
|
-
get value(): Promise<ShapeData
|
|
511
|
+
get value(): Promise<ShapeData<T>> {
|
|
473
512
|
return new Promise((resolve) => {
|
|
474
513
|
if (this.stream.isUpToDate) {
|
|
475
514
|
resolve(this.valueSync)
|
|
@@ -491,7 +530,7 @@ export class Shape {
|
|
|
491
530
|
return this.data
|
|
492
531
|
}
|
|
493
532
|
|
|
494
|
-
subscribe(callback: ShapeChangedCallback): () => void {
|
|
533
|
+
subscribe(callback: ShapeChangedCallback<T>): () => void {
|
|
495
534
|
const subscriptionId = Math.random()
|
|
496
535
|
|
|
497
536
|
this.subscribers.set(subscriptionId, callback)
|
|
@@ -509,7 +548,7 @@ export class Shape {
|
|
|
509
548
|
return this.subscribers.size
|
|
510
549
|
}
|
|
511
550
|
|
|
512
|
-
private process(messages: Message[]): void {
|
|
551
|
+
private process(messages: Message<T>[]): void {
|
|
513
552
|
let dataMayHaveChanged = false
|
|
514
553
|
let isUpToDate = false
|
|
515
554
|
let newlyUpToDate = false
|
package/src/helpers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeMessage, ControlMessage, Message,
|
|
1
|
+
import { ChangeMessage, ControlMessage, Message, Row } from './types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Type guard for checking {@link Message} is {@link ChangeMessage}.
|
|
@@ -17,7 +17,7 @@ import { ChangeMessage, ControlMessage, Message, Value } from './types'
|
|
|
17
17
|
* }
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
export function isChangeMessage<T extends
|
|
20
|
+
export function isChangeMessage<T extends Row = Row>(
|
|
21
21
|
message: Message<T>
|
|
22
22
|
): message is ChangeMessage<T> {
|
|
23
23
|
return `key` in message
|
|
@@ -40,7 +40,7 @@ export function isChangeMessage<T extends Value = { [key: string]: Value }>(
|
|
|
40
40
|
* }
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
|
-
export function isControlMessage<T extends
|
|
43
|
+
export function isControlMessage<T extends Row = Row>(
|
|
44
44
|
message: Message<T>
|
|
45
45
|
): message is ControlMessage {
|
|
46
46
|
return !isChangeMessage(message)
|
package/src/parser.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColumnInfo, Message, Schema, Value } from './types'
|
|
1
|
+
import { ColumnInfo, Message, Row, Schema, Value } from './types'
|
|
2
2
|
|
|
3
3
|
type NullToken = null | `NULL`
|
|
4
4
|
type Token = Exclude<string, NullToken>
|
|
@@ -79,7 +79,7 @@ export function pgArrayParser(value: Token, parser?: ParseFunction): Value {
|
|
|
79
79
|
return loop(value)[0]
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
export class MessageParser {
|
|
82
|
+
export class MessageParser<T extends Row> {
|
|
83
83
|
private parser: Parser
|
|
84
84
|
constructor(parser?: Parser) {
|
|
85
85
|
// Merge the provided parser with the default parser
|
|
@@ -88,7 +88,7 @@ export class MessageParser {
|
|
|
88
88
|
this.parser = { ...defaultParser, ...parser }
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
parse(messages: string, schema: Schema): Message[] {
|
|
91
|
+
parse(messages: string, schema: Schema): Message<T>[] {
|
|
92
92
|
return JSON.parse(messages, (key, value) => {
|
|
93
93
|
// typeof value === `object` is needed because
|
|
94
94
|
// there could be a column named `value`
|
|
@@ -101,7 +101,7 @@ export class MessageParser {
|
|
|
101
101
|
})
|
|
102
102
|
}
|
|
103
103
|
return value
|
|
104
|
-
}) as Message[]
|
|
104
|
+
}) as Message<T>[]
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// Parses the message values using the provided parser based on the schema information
|
package/src/types.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type Value =
|
|
|
7
7
|
| Value[]
|
|
8
8
|
| { [key: string]: Value }
|
|
9
9
|
|
|
10
|
+
export type Row = { [key: string]: Value }
|
|
11
|
+
|
|
10
12
|
export type Offset = `-1` | `${number}_${number}`
|
|
11
13
|
|
|
12
14
|
interface Header {
|
|
@@ -17,7 +19,7 @@ export type ControlMessage = {
|
|
|
17
19
|
headers: Header & { control: `up-to-date` | `must-refetch` }
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
export type ChangeMessage<T> = {
|
|
22
|
+
export type ChangeMessage<T extends Row = Row> = {
|
|
21
23
|
key: string
|
|
22
24
|
value: T
|
|
23
25
|
headers: Header & { operation: `insert` | `update` | `delete` }
|
|
@@ -25,9 +27,7 @@ export type ChangeMessage<T> = {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
// Define the type for a record
|
|
28
|
-
export type Message<T extends
|
|
29
|
-
| ControlMessage
|
|
30
|
-
| ChangeMessage<T>
|
|
30
|
+
export type Message<T extends Row = Row> = ControlMessage | ChangeMessage<T>
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Common properties for all columns.
|
|
@@ -104,7 +104,7 @@ export type ColumnInfo =
|
|
|
104
104
|
|
|
105
105
|
export type Schema = { [key: string]: ColumnInfo }
|
|
106
106
|
|
|
107
|
-
export type TypedMessages<T extends
|
|
107
|
+
export type TypedMessages<T extends Row = Row> = {
|
|
108
108
|
messages: Array<Message<T>>
|
|
109
109
|
schema: ColumnInfo
|
|
110
110
|
}
|