@electric-sql/experimental 3.0.2 → 4.0.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 +47 -77
- 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.mjs +47 -77
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cjs/index.cjs
CHANGED
|
@@ -5,10 +5,8 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
9
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
9
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
11
|
-
var __reflectGet = Reflect.get;
|
|
12
10
|
var __typeError = (msg) => {
|
|
13
11
|
throw TypeError(msg);
|
|
14
12
|
};
|
|
@@ -43,27 +41,6 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
|
|
|
43
41
|
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
44
42
|
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
45
43
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
46
|
-
var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj);
|
|
47
|
-
var __async = (__this, __arguments, generator) => {
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
var fulfilled = (value) => {
|
|
50
|
-
try {
|
|
51
|
-
step(generator.next(value));
|
|
52
|
-
} catch (e) {
|
|
53
|
-
reject(e);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
var rejected = (value) => {
|
|
57
|
-
try {
|
|
58
|
-
step(generator.throw(value));
|
|
59
|
-
} catch (e) {
|
|
60
|
-
reject(e);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
64
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
44
|
|
|
68
45
|
// src/index.ts
|
|
69
46
|
var src_exports = {};
|
|
@@ -158,20 +135,18 @@ var MultiShapeStream = class {
|
|
|
158
135
|
));
|
|
159
136
|
if (start) __privateMethod(this, _MultiShapeStream_instances, start_fn).call(this);
|
|
160
137
|
}
|
|
161
|
-
_publish(messages) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
);
|
|
174
|
-
});
|
|
138
|
+
async _publish(messages) {
|
|
139
|
+
await Promise.all(
|
|
140
|
+
Array.from(__privateGet(this, _subscribers).values()).map(async ([callback, __]) => {
|
|
141
|
+
try {
|
|
142
|
+
await callback(messages);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
queueMicrotask(() => {
|
|
145
|
+
throw err;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
);
|
|
175
150
|
}
|
|
176
151
|
/**
|
|
177
152
|
* The ShapeStreams that are being subscribed to.
|
|
@@ -231,7 +206,7 @@ start_fn = function() {
|
|
|
231
206
|
throw new Error(`Shape ${key} already started`);
|
|
232
207
|
}
|
|
233
208
|
shape.subscribe(
|
|
234
|
-
(messages) =>
|
|
209
|
+
async (messages) => {
|
|
235
210
|
const upToDateLsns = messages.filter(import_client2.isControlMessage).map(
|
|
236
211
|
({ headers }) => typeof headers.global_last_seen_lsn === `string` ? BigInt(headers.global_last_seen_lsn) : BigInt(0)
|
|
237
212
|
);
|
|
@@ -258,8 +233,8 @@ start_fn = function() {
|
|
|
258
233
|
shape: key
|
|
259
234
|
})
|
|
260
235
|
);
|
|
261
|
-
|
|
262
|
-
}
|
|
236
|
+
await this._publish(multiShapeMessages);
|
|
237
|
+
},
|
|
263
238
|
(error) => __privateMethod(this, _MultiShapeStream_instances, onError_fn).call(this, error)
|
|
264
239
|
);
|
|
265
240
|
}
|
|
@@ -272,17 +247,15 @@ scheduleCheckForUpdates_fn = function() {
|
|
|
272
247
|
__privateSet(this, _checkForUpdatesTimeout, void 0);
|
|
273
248
|
}, this.checkForUpdatesAfterMs));
|
|
274
249
|
};
|
|
275
|
-
checkForUpdates_fn = function() {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return shape.forceDisconnectAndRefresh();
|
|
283
|
-
});
|
|
284
|
-
yield Promise.all(refreshPromises);
|
|
250
|
+
checkForUpdates_fn = async function() {
|
|
251
|
+
const maxDataLsn = bigIntMax(Object.values(__privateGet(this, _lastDataLsns)));
|
|
252
|
+
const refreshPromises = __privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this).filter(([key]) => {
|
|
253
|
+
const lastUpToDateLsn = __privateGet(this, _lastUpToDateLsns)[key];
|
|
254
|
+
return lastUpToDateLsn < maxDataLsn;
|
|
255
|
+
}).map(([_, shape]) => {
|
|
256
|
+
return shape.forceDisconnectAndRefresh();
|
|
285
257
|
});
|
|
258
|
+
await Promise.all(refreshPromises);
|
|
286
259
|
};
|
|
287
260
|
onError_fn = function(error) {
|
|
288
261
|
__privateGet(this, _subscribers).forEach(([_, errorFn]) => {
|
|
@@ -298,7 +271,7 @@ shapeEntries_fn = function() {
|
|
|
298
271
|
return Object.entries(__privateGet(this, _shapes));
|
|
299
272
|
};
|
|
300
273
|
var _changeMessages, _completeLsns, _TransactionalMultiShapeStream_instances, getLowestCompleteLsn_fn, accumulate_fn;
|
|
301
|
-
var
|
|
274
|
+
var TransactionalMultiShapeStream = class extends MultiShapeStream {
|
|
302
275
|
constructor(options) {
|
|
303
276
|
super(options);
|
|
304
277
|
__privateAdd(this, _TransactionalMultiShapeStream_instances);
|
|
@@ -308,33 +281,31 @@ var _TransactionalMultiShapeStream = class _TransactionalMultiShapeStream extend
|
|
|
308
281
|
Object.entries(options.shapes).map(([key]) => [key, BigInt(-1)])
|
|
309
282
|
));
|
|
310
283
|
}
|
|
311
|
-
_publish(messages) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
).filter((messages2) => messages2 !== void 0).flat();
|
|
331
|
-
lsnsToPublish.forEach((lsn) => {
|
|
332
|
-
__privateGet(this, _changeMessages).delete(lsn);
|
|
333
|
-
});
|
|
334
|
-
if (messagesToPublish.length > 0) {
|
|
335
|
-
yield __superGet(_TransactionalMultiShapeStream.prototype, this, "_publish").call(this, messagesToPublish);
|
|
284
|
+
async _publish(messages) {
|
|
285
|
+
__privateMethod(this, _TransactionalMultiShapeStream_instances, accumulate_fn).call(this, messages);
|
|
286
|
+
const lowestCompleteLsn = __privateMethod(this, _TransactionalMultiShapeStream_instances, getLowestCompleteLsn_fn).call(this);
|
|
287
|
+
const lsnsToPublish = [...__privateGet(this, _changeMessages).keys()].filter(
|
|
288
|
+
(lsn) => lsn <= lowestCompleteLsn
|
|
289
|
+
);
|
|
290
|
+
const messagesToPublish = lsnsToPublish.sort((a, b) => bigIntCompare(a, b)).map(
|
|
291
|
+
(lsn) => {
|
|
292
|
+
var _a;
|
|
293
|
+
return (_a = __privateGet(this, _changeMessages).get(lsn)) == null ? void 0 : _a.sort((a, b) => {
|
|
294
|
+
const { headers: aHeaders } = a;
|
|
295
|
+
const { headers: bHeaders } = b;
|
|
296
|
+
if (typeof aHeaders.op_position !== `number` || typeof bHeaders.op_position !== `number`) {
|
|
297
|
+
return 0;
|
|
298
|
+
}
|
|
299
|
+
return aHeaders.op_position - bHeaders.op_position;
|
|
300
|
+
});
|
|
336
301
|
}
|
|
302
|
+
).filter((messages2) => messages2 !== void 0).flat();
|
|
303
|
+
lsnsToPublish.forEach((lsn) => {
|
|
304
|
+
__privateGet(this, _changeMessages).delete(lsn);
|
|
337
305
|
});
|
|
306
|
+
if (messagesToPublish.length > 0) {
|
|
307
|
+
await super._publish(messagesToPublish);
|
|
308
|
+
}
|
|
338
309
|
}
|
|
339
310
|
};
|
|
340
311
|
_changeMessages = new WeakMap();
|
|
@@ -374,7 +345,6 @@ accumulate_fn = function(messages) {
|
|
|
374
345
|
}
|
|
375
346
|
});
|
|
376
347
|
};
|
|
377
|
-
var TransactionalMultiShapeStream = _TransactionalMultiShapeStream;
|
|
378
348
|
// Annotate the CommonJS export names for ESM import in node:
|
|
379
349
|
0 && (module.exports = {
|
|
380
350
|
MultiShapeStream,
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/match.ts","../../src/bigint-utils.ts","../../src/multi-shape-stream.ts"],"sourcesContent":["export * from './match'\nexport * from './multi-shape-stream'\n","import {\n isChangeMessage,\n type ShapeStreamInterface,\n type ChangeMessage,\n type GetExtensions,\n type Operation,\n type Row,\n type Value,\n type Message,\n} from '@electric-sql/client'\n\nexport function matchStream<T extends Row<unknown>>(\n stream: ShapeStreamInterface<T>,\n operations: Array<Operation>,\n matchFn: (message: ChangeMessage<T>) => boolean,\n timeout = 60000 // ms\n): Promise<ChangeMessage<T>> {\n return new Promise<ChangeMessage<T>>((resolve, reject) => {\n const unsubscribe: () => void = stream.subscribe(\n (messages: Array<unknown>) => {\n const message = messages\n .filter((msg): msg is ChangeMessage<T> =>\n isChangeMessage(msg as Message<Row<never>>)\n )\n .find((message) => {\n const operation: Operation = message.headers.operation\n\n return operations.includes(operation) && matchFn(message)\n })\n\n if (message) {\n return finish(message)\n }\n }\n )\n\n const timeoutId: NodeJS.Timeout = setTimeout(() => {\n const msg: string = `matchStream timed out after ${timeout}ms`\n\n console.error(msg)\n\n reject(msg)\n }, timeout)\n\n function finish(message: ChangeMessage<T>): void {\n clearTimeout(timeoutId)\n\n unsubscribe()\n\n return resolve(message)\n }\n })\n}\n\nexport function matchBy<T extends Row<unknown>>(\n column: string,\n value: Value<GetExtensions<T>>\n): (message: ChangeMessage<T>) => boolean {\n return (message: ChangeMessage<T>) => message.value[column] === value\n}\n","export function bigIntMax(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e < m ? e : m)))\n}\n\nexport function bigIntCompare(a: bigint, b: bigint): 1 | -1 | 0 {\n return a > b ? 1 : a < b ? -1 : 0\n}\n","import { bigIntCompare, bigIntMax, bigIntMin } from './bigint-utils'\nimport {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n} from '@electric-sql/client'\nimport type {\n ChangeMessage,\n ControlMessage,\n FetchError,\n MaybePromise,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\ninterface MultiShapeStreamOptions<\n TShapeRows extends {\n [K: string]: Row<unknown>\n } = {\n [K: string]: Row<unknown>\n },\n> {\n shapes: {\n [K in keyof TShapeRows]:\n | ShapeStreamOptions<TShapeRows[K]>\n | ShapeStream<TShapeRows[K]>\n }\n start?: boolean\n checkForUpdatesAfterMs?: number // milliseconds\n}\n\ninterface MultiShapeChangeMessage<\n T extends Row<unknown>,\n ShapeNames extends string,\n> extends ChangeMessage<T> {\n shape: ShapeNames\n}\n\ninterface MultiShapeControlMessage<ShapeNames extends string>\n extends ControlMessage {\n shape: ShapeNames\n}\n\ntype MultiShapeMessage<T extends Row<unknown>, ShapeNames extends string> =\n | MultiShapeChangeMessage<T, ShapeNames>\n | MultiShapeControlMessage<ShapeNames>\n\nexport type MultiShapeMessages<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> = {\n [K in keyof TShapeRows & string]: MultiShapeMessage<TShapeRows[K], K>\n}[keyof TShapeRows & string]\n\nexport interface MultiShapeStreamInterface<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> {\n shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n checkForUpdatesAfterMs?: number\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ): () => void\n unsubscribeAll(): void\n\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n isLoading(): boolean\n\n isUpToDate: boolean\n}\n\n/**\n * A multi-shape stream is a stream that can subscribe to multiple shapes.\n * It ensures that all shapes will receive at least an `up-to-date` message from\n * Electric within the `checkForUpdatesAfterMs` interval.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * multiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class MultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> implements MultiShapeStreamInterface<TShapeRows>\n{\n #shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n #started = false\n checkForUpdatesAfterMs?: number\n\n #checkForUpdatesTimeout?: ReturnType<typeof setTimeout> | undefined\n\n // We keep track of the last lsn of data and up-to-date messages for each shape\n // so that we can skip checkForUpdates if the lsn of the up-to-date message is\n // greater than the last lsn of data.\n #lastDataLsns: { [K in keyof TShapeRows]: bigint }\n #lastUpToDateLsns: { [K in keyof TShapeRows]: bigint }\n\n readonly #subscribers = new Map<\n number,\n [\n (messages: MultiShapeMessages<TShapeRows>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n const {\n start = true, // By default we start the multi-shape stream\n checkForUpdatesAfterMs = 100, // Force a check for updates after 100ms\n shapes,\n } = options\n this.checkForUpdatesAfterMs = checkForUpdatesAfterMs\n this.#shapes = Object.fromEntries(\n Object.entries(shapes).map(([key, shape]) => [\n key,\n shape instanceof ShapeStream\n ? shape\n : new ShapeStream<TShapeRows[typeof key]>({\n ...shape,\n start: false,\n }),\n ])\n ) as { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n this.#lastDataLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n this.#lastUpToDateLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n if (start) this.#start()\n }\n\n #start() {\n if (this.#started) throw new Error(`Cannot start multi-shape stream twice`)\n for (const [key, shape] of this.#shapeEntries()) {\n if (shape.hasStarted()) {\n // The multi-shape stream needs to be started together as a whole, and so we\n // have to check that a shape is not already started.\n throw new Error(`Shape ${key} already started`)\n }\n shape.subscribe(\n async (messages) => {\n // Whats the max lsn of the up-to-date messages?\n const upToDateLsns = messages\n .filter(isControlMessage)\n .map(({ headers }) =>\n typeof headers.global_last_seen_lsn === `string`\n ? BigInt(headers.global_last_seen_lsn)\n : BigInt(0)\n )\n if (upToDateLsns.length > 0) {\n const maxUpToDateLsn = bigIntMax(upToDateLsns)\n const lastMaxUpToDateLsn = this.#lastUpToDateLsns[key]\n if (maxUpToDateLsn > lastMaxUpToDateLsn) {\n this.#lastUpToDateLsns[key] = maxUpToDateLsn\n }\n }\n\n // Whats the max lsn of the data messages?\n const dataLsns = messages\n .filter(isChangeMessage)\n .map(({ headers }) =>\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n )\n if (dataLsns.length > 0) {\n const maxDataLsn = bigIntMax(dataLsns)\n const lastMaxDataLsn = this.#lastDataLsns[key]\n if (maxDataLsn > lastMaxDataLsn) {\n this.#lastDataLsns[key] = maxDataLsn\n }\n // There is new data, so we need to schedule a check for updates on\n // other shapes\n this.#scheduleCheckForUpdates()\n }\n\n // Publish the messages to the multi-shape stream subscribers\n const multiShapeMessages = messages.map(\n (message) =>\n ({\n ...message,\n shape: key,\n }) as MultiShapeMessages<TShapeRows>\n )\n await this._publish(multiShapeMessages)\n },\n (error) => this.#onError(error)\n )\n }\n this.#started = true\n }\n\n #scheduleCheckForUpdates() {\n this.#checkForUpdatesTimeout ??= setTimeout(() => {\n this.#checkForUpdates()\n this.#checkForUpdatesTimeout = undefined\n }, this.checkForUpdatesAfterMs)\n }\n\n async #checkForUpdates() {\n const maxDataLsn = bigIntMax(Object.values(this.#lastDataLsns))\n const refreshPromises = this.#shapeEntries()\n .filter(([key]) => {\n // We only need to refresh shapes that have not seen an up-to-date message\n // lower than the max lsn of the data messages we have received.\n const lastUpToDateLsn = this.#lastUpToDateLsns[key]\n return lastUpToDateLsn < maxDataLsn\n })\n .map(([_, shape]) => {\n return shape.forceDisconnectAndRefresh()\n })\n await Promise.all(refreshPromises)\n }\n\n #onError(error: Error) {\n // TODO: we probably want to disconnect all shapes here on the first error\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n await Promise.all(\n Array.from(this.#subscribers.values()).map(async ([callback, __]) => {\n try {\n await callback(messages)\n } catch (err) {\n queueMicrotask(() => {\n throw err\n })\n }\n })\n )\n }\n\n /**\n * Returns an array of the shape entries.\n * Ensures that the shape entries are typed, as `Object.entries`\n * will not type the entries correctly.\n */\n #shapeEntries() {\n return Object.entries(this.#shapes) as [\n keyof TShapeRows & string,\n ShapeStream<TShapeRows[string]>,\n ][]\n }\n\n /**\n * The ShapeStreams that are being subscribed to.\n */\n get shapes() {\n return this.#shapes\n }\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) this.#start()\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n // Min of all the lastSyncedAt values\n const shapeEntries = this.#shapeEntries()\n if (shapeEntries.length === 0) return\n return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {\n return Math.min(minLastSyncedAt, shape.lastSyncedAt() ?? Infinity)\n }, Infinity)\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n const lastSyncedAt = this.lastSyncedAt()\n if (lastSyncedAt === undefined) return Infinity\n return Date.now() - lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#shapeEntries().every(([_, shape]) => shape.isConnected())\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading(): boolean {\n return this.#shapeEntries().some(([_, shape]) => shape.isLoading())\n }\n\n get isUpToDate() {\n return this.#shapeEntries().every(([_, shape]) => shape.isUpToDate)\n }\n}\n\n/**\n * A transactional multi-shape stream is a multi-shape stream that emits the\n * messages in transactional batches, ensuring that all shapes will receive\n * at least an `up-to-date` message from Electric within the `checkForUpdatesAfterMs`\n * interval.\n * It uses the `lsn` metadata to infer transaction boundaries, and the `op_position`\n * metadata to sort the messages within a transaction.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * transactionalMultiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class TransactionalMultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> extends MultiShapeStream<TShapeRows> {\n #changeMessages = new Map<bigint, MultiShapeMessage<Row<unknown>, string>[]>()\n #completeLsns: {\n [K in keyof TShapeRows]: bigint\n }\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n super(options)\n this.#completeLsns = Object.fromEntries(\n Object.entries(options.shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n }\n\n #getLowestCompleteLsn() {\n return bigIntMin(Object.values(this.#completeLsns))\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n this.#accumulate(messages)\n const lowestCompleteLsn = this.#getLowestCompleteLsn()\n const lsnsToPublish = [...this.#changeMessages.keys()].filter(\n (lsn) => lsn <= lowestCompleteLsn\n )\n const messagesToPublish = lsnsToPublish\n .sort((a, b) => bigIntCompare(a, b))\n .map((lsn) =>\n this.#changeMessages.get(lsn)?.sort((a, b) => {\n const { headers: aHeaders } = a\n const { headers: bHeaders } = b\n if (\n typeof aHeaders.op_position !== `number` ||\n typeof bHeaders.op_position !== `number`\n ) {\n return 0 // op_position is not present on the snapshot message\n }\n return aHeaders.op_position - bHeaders.op_position\n })\n )\n .filter((messages) => messages !== undefined)\n .flat() as MultiShapeMessages<TShapeRows>[]\n lsnsToPublish.forEach((lsn) => {\n this.#changeMessages.delete(lsn)\n })\n if (messagesToPublish.length > 0) {\n await super._publish(messagesToPublish)\n }\n }\n\n #accumulate(messages: MultiShapeMessages<TShapeRows>[]) {\n const isUpToDate = this.isUpToDate\n messages.forEach((message) => {\n const { shape, headers } = message\n if (isChangeMessage(message)) {\n // The snapshot message does not have an lsn, so we use 0\n const lsn =\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n if (!this.#changeMessages.has(lsn)) {\n this.#changeMessages.set(lsn, [])\n }\n this.#changeMessages.get(lsn)?.push(message)\n if (\n isUpToDate && // All shapes must be up to date\n typeof headers.last === `boolean` &&\n headers.last === true\n ) {\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n lsn,\n ])\n }\n } else if (isControlMessage(message)) {\n if (headers.control === `up-to-date`) {\n if (typeof headers.global_last_seen_lsn !== `string`) {\n throw new Error(`global_last_seen_lsn is not a number`)\n }\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n BigInt(headers.global_last_seen_lsn),\n ])\n }\n }\n })\n }\n}\n"],"mappings":"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","names":["message","import_client","messages"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/match.ts","../../src/bigint-utils.ts","../../src/multi-shape-stream.ts"],"sourcesContent":["export * from './match'\nexport * from './multi-shape-stream'\n","import {\n isChangeMessage,\n type ShapeStreamInterface,\n type ChangeMessage,\n type GetExtensions,\n type Operation,\n type Row,\n type Value,\n type Message,\n} from '@electric-sql/client'\n\nexport function matchStream<T extends Row<unknown>>(\n stream: ShapeStreamInterface<T>,\n operations: Array<Operation>,\n matchFn: (message: ChangeMessage<T>) => boolean,\n timeout = 60000 // ms\n): Promise<ChangeMessage<T>> {\n return new Promise<ChangeMessage<T>>((resolve, reject) => {\n const unsubscribe: () => void = stream.subscribe(\n (messages: Array<unknown>) => {\n const message = messages\n .filter((msg): msg is ChangeMessage<T> =>\n isChangeMessage(msg as Message<Row<never>>)\n )\n .find((message) => {\n const operation: Operation = message.headers.operation\n\n return operations.includes(operation) && matchFn(message)\n })\n\n if (message) {\n return finish(message)\n }\n }\n )\n\n const timeoutId: NodeJS.Timeout = setTimeout(() => {\n const msg: string = `matchStream timed out after ${timeout}ms`\n\n console.error(msg)\n\n reject(msg)\n }, timeout)\n\n function finish(message: ChangeMessage<T>): void {\n clearTimeout(timeoutId)\n\n unsubscribe()\n\n return resolve(message)\n }\n })\n}\n\nexport function matchBy<T extends Row<unknown>>(\n column: string,\n value: Value<GetExtensions<T>>\n): (message: ChangeMessage<T>) => boolean {\n return (message: ChangeMessage<T>) => message.value[column] === value\n}\n","export function bigIntMax(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e < m ? e : m)))\n}\n\nexport function bigIntCompare(a: bigint, b: bigint): 1 | -1 | 0 {\n return a > b ? 1 : a < b ? -1 : 0\n}\n","import { bigIntCompare, bigIntMax, bigIntMin } from './bigint-utils'\nimport {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n} from '@electric-sql/client'\nimport type {\n ChangeMessage,\n ControlMessage,\n FetchError,\n MaybePromise,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\ninterface MultiShapeStreamOptions<\n TShapeRows extends {\n [K: string]: Row<unknown>\n } = {\n [K: string]: Row<unknown>\n },\n> {\n shapes: {\n [K in keyof TShapeRows]:\n | ShapeStreamOptions<TShapeRows[K]>\n | ShapeStream<TShapeRows[K]>\n }\n start?: boolean\n checkForUpdatesAfterMs?: number // milliseconds\n}\n\ninterface MultiShapeChangeMessage<\n T extends Row<unknown>,\n ShapeNames extends string,\n> extends ChangeMessage<T> {\n shape: ShapeNames\n}\n\ninterface MultiShapeControlMessage<ShapeNames extends string>\n extends ControlMessage {\n shape: ShapeNames\n}\n\ntype MultiShapeMessage<T extends Row<unknown>, ShapeNames extends string> =\n | MultiShapeChangeMessage<T, ShapeNames>\n | MultiShapeControlMessage<ShapeNames>\n\nexport type MultiShapeMessages<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> = {\n [K in keyof TShapeRows & string]: MultiShapeMessage<TShapeRows[K], K>\n}[keyof TShapeRows & string]\n\nexport interface MultiShapeStreamInterface<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> {\n shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n checkForUpdatesAfterMs?: number\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ): () => void\n unsubscribeAll(): void\n\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n isLoading(): boolean\n\n isUpToDate: boolean\n}\n\n/**\n * A multi-shape stream is a stream that can subscribe to multiple shapes.\n * It ensures that all shapes will receive at least an `up-to-date` message from\n * Electric within the `checkForUpdatesAfterMs` interval.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * multiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class MultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> implements MultiShapeStreamInterface<TShapeRows>\n{\n #shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n #started = false\n checkForUpdatesAfterMs?: number\n\n #checkForUpdatesTimeout?: ReturnType<typeof setTimeout> | undefined\n\n // We keep track of the last lsn of data and up-to-date messages for each shape\n // so that we can skip checkForUpdates if the lsn of the up-to-date message is\n // greater than the last lsn of data.\n #lastDataLsns: { [K in keyof TShapeRows]: bigint }\n #lastUpToDateLsns: { [K in keyof TShapeRows]: bigint }\n\n readonly #subscribers = new Map<\n number,\n [\n (messages: MultiShapeMessages<TShapeRows>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n const {\n start = true, // By default we start the multi-shape stream\n checkForUpdatesAfterMs = 100, // Force a check for updates after 100ms\n shapes,\n } = options\n this.checkForUpdatesAfterMs = checkForUpdatesAfterMs\n this.#shapes = Object.fromEntries(\n Object.entries(shapes).map(([key, shape]) => [\n key,\n shape instanceof ShapeStream\n ? shape\n : new ShapeStream<TShapeRows[typeof key]>({\n ...shape,\n start: false,\n }),\n ])\n ) as { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n this.#lastDataLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n this.#lastUpToDateLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n if (start) this.#start()\n }\n\n #start() {\n if (this.#started) throw new Error(`Cannot start multi-shape stream twice`)\n for (const [key, shape] of this.#shapeEntries()) {\n if (shape.hasStarted()) {\n // The multi-shape stream needs to be started together as a whole, and so we\n // have to check that a shape is not already started.\n throw new Error(`Shape ${key} already started`)\n }\n shape.subscribe(\n async (messages) => {\n // Whats the max lsn of the up-to-date messages?\n const upToDateLsns = messages\n .filter(isControlMessage)\n .map(({ headers }) =>\n typeof headers.global_last_seen_lsn === `string`\n ? BigInt(headers.global_last_seen_lsn)\n : BigInt(0)\n )\n if (upToDateLsns.length > 0) {\n const maxUpToDateLsn = bigIntMax(upToDateLsns)\n const lastMaxUpToDateLsn = this.#lastUpToDateLsns[key]\n if (maxUpToDateLsn > lastMaxUpToDateLsn) {\n this.#lastUpToDateLsns[key] = maxUpToDateLsn\n }\n }\n\n // Whats the max lsn of the data messages?\n const dataLsns = messages\n .filter(isChangeMessage)\n .map(({ headers }) =>\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n )\n if (dataLsns.length > 0) {\n const maxDataLsn = bigIntMax(dataLsns)\n const lastMaxDataLsn = this.#lastDataLsns[key]\n if (maxDataLsn > lastMaxDataLsn) {\n this.#lastDataLsns[key] = maxDataLsn\n }\n // There is new data, so we need to schedule a check for updates on\n // other shapes\n this.#scheduleCheckForUpdates()\n }\n\n // Publish the messages to the multi-shape stream subscribers\n const multiShapeMessages = messages.map(\n (message) =>\n ({\n ...message,\n shape: key,\n }) as MultiShapeMessages<TShapeRows>\n )\n await this._publish(multiShapeMessages)\n },\n (error) => this.#onError(error)\n )\n }\n this.#started = true\n }\n\n #scheduleCheckForUpdates() {\n this.#checkForUpdatesTimeout ??= setTimeout(() => {\n this.#checkForUpdates()\n this.#checkForUpdatesTimeout = undefined\n }, this.checkForUpdatesAfterMs)\n }\n\n async #checkForUpdates() {\n const maxDataLsn = bigIntMax(Object.values(this.#lastDataLsns))\n const refreshPromises = this.#shapeEntries()\n .filter(([key]) => {\n // We only need to refresh shapes that have not seen an up-to-date message\n // lower than the max lsn of the data messages we have received.\n const lastUpToDateLsn = this.#lastUpToDateLsns[key]\n return lastUpToDateLsn < maxDataLsn\n })\n .map(([_, shape]) => {\n return shape.forceDisconnectAndRefresh()\n })\n await Promise.all(refreshPromises)\n }\n\n #onError(error: Error) {\n // TODO: we probably want to disconnect all shapes here on the first error\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n await Promise.all(\n Array.from(this.#subscribers.values()).map(async ([callback, __]) => {\n try {\n await callback(messages)\n } catch (err) {\n queueMicrotask(() => {\n throw err\n })\n }\n })\n )\n }\n\n /**\n * Returns an array of the shape entries.\n * Ensures that the shape entries are typed, as `Object.entries`\n * will not type the entries correctly.\n */\n #shapeEntries() {\n return Object.entries(this.#shapes) as [\n keyof TShapeRows & string,\n ShapeStream<TShapeRows[string]>,\n ][]\n }\n\n /**\n * The ShapeStreams that are being subscribed to.\n */\n get shapes() {\n return this.#shapes\n }\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) this.#start()\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n // Min of all the lastSyncedAt values\n const shapeEntries = this.#shapeEntries()\n if (shapeEntries.length === 0) return\n return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {\n return Math.min(minLastSyncedAt, shape.lastSyncedAt() ?? Infinity)\n }, Infinity)\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n const lastSyncedAt = this.lastSyncedAt()\n if (lastSyncedAt === undefined) return Infinity\n return Date.now() - lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#shapeEntries().every(([_, shape]) => shape.isConnected())\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading(): boolean {\n return this.#shapeEntries().some(([_, shape]) => shape.isLoading())\n }\n\n get isUpToDate() {\n return this.#shapeEntries().every(([_, shape]) => shape.isUpToDate)\n }\n}\n\n/**\n * A transactional multi-shape stream is a multi-shape stream that emits the\n * messages in transactional batches, ensuring that all shapes will receive\n * at least an `up-to-date` message from Electric within the `checkForUpdatesAfterMs`\n * interval.\n * It uses the `lsn` metadata to infer transaction boundaries, and the `op_position`\n * metadata to sort the messages within a transaction.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * transactionalMultiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class TransactionalMultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> extends MultiShapeStream<TShapeRows> {\n #changeMessages = new Map<bigint, MultiShapeMessage<Row<unknown>, string>[]>()\n #completeLsns: {\n [K in keyof TShapeRows]: bigint\n }\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n super(options)\n this.#completeLsns = Object.fromEntries(\n Object.entries(options.shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n }\n\n #getLowestCompleteLsn() {\n return bigIntMin(Object.values(this.#completeLsns))\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n this.#accumulate(messages)\n const lowestCompleteLsn = this.#getLowestCompleteLsn()\n const lsnsToPublish = [...this.#changeMessages.keys()].filter(\n (lsn) => lsn <= lowestCompleteLsn\n )\n const messagesToPublish = lsnsToPublish\n .sort((a, b) => bigIntCompare(a, b))\n .map((lsn) =>\n this.#changeMessages.get(lsn)?.sort((a, b) => {\n const { headers: aHeaders } = a\n const { headers: bHeaders } = b\n if (\n typeof aHeaders.op_position !== `number` ||\n typeof bHeaders.op_position !== `number`\n ) {\n return 0 // op_position is not present on the snapshot message\n }\n return aHeaders.op_position - bHeaders.op_position\n })\n )\n .filter((messages) => messages !== undefined)\n .flat() as MultiShapeMessages<TShapeRows>[]\n lsnsToPublish.forEach((lsn) => {\n this.#changeMessages.delete(lsn)\n })\n if (messagesToPublish.length > 0) {\n await super._publish(messagesToPublish)\n }\n }\n\n #accumulate(messages: MultiShapeMessages<TShapeRows>[]) {\n const isUpToDate = this.isUpToDate\n messages.forEach((message) => {\n const { shape, headers } = message\n if (isChangeMessage(message)) {\n // The snapshot message does not have an lsn, so we use 0\n const lsn =\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n if (!this.#changeMessages.has(lsn)) {\n this.#changeMessages.set(lsn, [])\n }\n this.#changeMessages.get(lsn)?.push(message)\n if (\n isUpToDate && // All shapes must be up to date\n typeof headers.last === `boolean` &&\n headers.last === true\n ) {\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n lsn,\n ])\n }\n } else if (isControlMessage(message)) {\n if (headers.control === `up-to-date`) {\n if (typeof headers.global_last_seen_lsn !== `string`) {\n throw new Error(`global_last_seen_lsn is not a number`)\n }\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n BigInt(headers.global_last_seen_lsn),\n ])\n }\n }\n })\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBASO;AAEA,SAAS,YACd,QACA,YACA,SACA,UAAU,KACiB;AAC3B,SAAO,IAAI,QAA0B,CAAC,SAAS,WAAW;AACxD,UAAM,cAA0B,OAAO;AAAA,MACrC,CAAC,aAA6B;AAC5B,cAAM,UAAU,SACb;AAAA,UAAO,CAAC,YACP,+BAAgB,GAA0B;AAAA,QAC5C,EACC,KAAK,CAACA,aAAY;AACjB,gBAAM,YAAuBA,SAAQ,QAAQ;AAE7C,iBAAO,WAAW,SAAS,SAAS,KAAK,QAAQA,QAAO;AAAA,QAC1D,CAAC;AAEH,YAAI,SAAS;AACX,iBAAO,OAAO,OAAO;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAA4B,WAAW,MAAM;AACjD,YAAM,MAAc,+BAA+B,OAAO;AAE1D,cAAQ,MAAM,GAAG;AAEjB,aAAO,GAAG;AAAA,IACZ,GAAG,OAAO;AAEV,aAAS,OAAO,SAAiC;AAC/C,mBAAa,SAAS;AAEtB,kBAAY;AAEZ,aAAO,QAAQ,OAAO;AAAA,IACxB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,QACd,QACA,OACwC;AACxC,SAAO,CAAC,YAA8B,QAAQ,MAAM,MAAM,MAAM;AAClE;;;AC3DO,SAAS,UAAU,MAAsC;AAC9D,SAAO,OAAO,KAAK,OAAO,CAAC,GAAG,MAAO,IAAI,IAAI,IAAI,CAAE,CAAC;AACtD;AAEO,SAAS,UAAU,MAAsC;AAC9D,SAAO,OAAO,KAAK,OAAO,CAAC,GAAG,MAAO,IAAI,IAAI,IAAI,CAAE,CAAC;AACtD;AAEO,SAAS,cAAc,GAAW,GAAuB;AAC9D,SAAO,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK;AAClC;;;ACTA,IAAAC,iBAIO;AALP;AAiHO,IAAM,mBAAN,MAKP;AAAA,EAqBE,YAAY,SAA8C;AA1BrD;AAML;AACA,iCAAW;AAGX;AAKA;AAAA;AAAA;AAAA;AACA;AAEA,uBAAS,cAAe,oBAAI,IAM1B;AAGA,UAAM;AAAA,MACJ,QAAQ;AAAA;AAAA,MACR,yBAAyB;AAAA;AAAA,MACzB;AAAA,IACF,IAAI;AACJ,SAAK,yBAAyB;AAC9B,uBAAK,SAAU,OAAO;AAAA,MACpB,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAAA,QAC3C;AAAA,QACA,iBAAiB,6BACb,QACA,IAAI,2BAAoC,iCACnC,QADmC;AAAA,UAEtC,OAAO;AAAA,QACT,EAAC;AAAA,MACP,CAAC;AAAA,IACH;AACA,uBAAK,eAAgB,OAAO;AAAA,MAC1B,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,IACzD;AACA,uBAAK,mBAAoB,OAAO;AAAA,MAC9B,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,IACzD;AACA,QAAI,MAAO,uBAAK,uCAAL;AAAA,EACb;AAAA,EA0FA,MAAgB,SACd,UACe;AACf,UAAM,QAAQ;AAAA,MACZ,MAAM,KAAK,mBAAK,cAAa,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,UAAU,EAAE,MAAM;AACnE,YAAI;AACF,gBAAM,SAAS,QAAQ;AAAA,QACzB,SAAS,KAAK;AACZ,yBAAe,MAAM;AACnB,kBAAM;AAAA,UACR,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAiBA,IAAI,SAAS;AACX,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,UACE,UAGA,SACA;AACA,UAAM,iBAAiB,KAAK,OAAO;AAEnC,uBAAK,cAAa,IAAI,gBAAgB,CAAC,UAAU,OAAO,CAAC;AACzD,QAAI,CAAC,mBAAK,UAAU,uBAAK,uCAAL;AAEpB,WAAO,MAAM;AACX,yBAAK,cAAa,OAAO,cAAc;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,iBAAuB;AACrB,uBAAK,cAAa,MAAM;AAAA,EAC1B;AAAA;AAAA,EAGA,eAAmC;AAEjC,UAAM,eAAe,sBAAK,8CAAL;AACrB,QAAI,aAAa,WAAW,EAAG;AAC/B,WAAO,aAAa,OAAO,CAAC,iBAAiB,CAAC,GAAG,KAAK,MAAM;AA1ThE;AA2TM,aAAO,KAAK,IAAI,kBAAiB,WAAM,aAAa,MAAnB,YAAwB,QAAQ;AAAA,IACnE,GAAG,QAAQ;AAAA,EACb;AAAA;AAAA,EAGA,aAAqB;AACnB,UAAM,eAAe,KAAK,aAAa;AACvC,QAAI,iBAAiB,OAAW,QAAO;AACvC,WAAO,KAAK,IAAI,IAAI;AAAA,EACtB;AAAA;AAAA,EAGA,cAAuB;AACrB,WAAO,sBAAK,8CAAL,WAAqB,MAAM,CAAC,CAAC,GAAG,KAAK,MAAM,MAAM,YAAY,CAAC;AAAA,EACvE;AAAA;AAAA,EAGA,YAAqB;AACnB,WAAO,sBAAK,8CAAL,WAAqB,KAAK,CAAC,CAAC,GAAG,KAAK,MAAM,MAAM,UAAU,CAAC;AAAA,EACpE;AAAA,EAEA,IAAI,aAAa;AACf,WAAO,sBAAK,8CAAL,WAAqB,MAAM,CAAC,CAAC,GAAG,KAAK,MAAM,MAAM,UAAU;AAAA,EACpE;AACF;AA5NE;AACA;AAGA;AAKA;AACA;AAES;AAlBJ;AAqDL,WAAM,WAAG;AACP,MAAI,mBAAK,UAAU,OAAM,IAAI,MAAM,uCAAuC;AAC1E,aAAW,CAAC,KAAK,KAAK,KAAK,sBAAK,8CAAL,YAAsB;AAC/C,QAAI,MAAM,WAAW,GAAG;AAGtB,YAAM,IAAI,MAAM,SAAS,GAAG,kBAAkB;AAAA,IAChD;AACA,UAAM;AAAA,MACJ,OAAO,aAAa;AAElB,cAAM,eAAe,SAClB,OAAO,+BAAgB,EACvB;AAAA,UAAI,CAAC,EAAE,QAAQ,MACd,OAAO,QAAQ,yBAAyB,WACpC,OAAO,QAAQ,oBAAoB,IACnC,OAAO,CAAC;AAAA,QACd;AACF,YAAI,aAAa,SAAS,GAAG;AAC3B,gBAAM,iBAAiB,UAAU,YAAY;AAC7C,gBAAM,qBAAqB,mBAAK,mBAAkB,GAAG;AACrD,cAAI,iBAAiB,oBAAoB;AACvC,+BAAK,mBAAkB,GAAG,IAAI;AAAA,UAChC;AAAA,QACF;AAGA,cAAM,WAAW,SACd,OAAO,8BAAe,EACtB;AAAA,UAAI,CAAC,EAAE,QAAQ,MACd,OAAO,QAAQ,QAAQ,WAAW,OAAO,QAAQ,GAAG,IAAI,OAAO,CAAC;AAAA,QAClE;AACF,YAAI,SAAS,SAAS,GAAG;AACvB,gBAAM,aAAa,UAAU,QAAQ;AACrC,gBAAM,iBAAiB,mBAAK,eAAc,GAAG;AAC7C,cAAI,aAAa,gBAAgB;AAC/B,+BAAK,eAAc,GAAG,IAAI;AAAA,UAC5B;AAGA,gCAAK,yDAAL;AAAA,QACF;AAGA,cAAM,qBAAqB,SAAS;AAAA,UAClC,CAAC,YACE,iCACI,UADJ;AAAA,YAEC,OAAO;AAAA,UACT;AAAA,QACJ;AACA,cAAM,KAAK,SAAS,kBAAkB;AAAA,MACxC;AAAA,MACA,CAAC,UAAU,sBAAK,yCAAL,WAAc;AAAA,IAC3B;AAAA,EACF;AACA,qBAAK,UAAW;AAClB;AAEA,6BAAwB,WAAG;AAjO7B;AAkOI,2BAAK,6BAAL,+BAAK,yBAA4B,WAAW,MAAM;AAChD,0BAAK,iDAAL;AACA,uBAAK,yBAA0B;AAAA,EACjC,GAAG,KAAK,sBAAsB;AAChC;AAEM,qBAAgB,iBAAG;AACvB,QAAM,aAAa,UAAU,OAAO,OAAO,mBAAK,cAAa,CAAC;AAC9D,QAAM,kBAAkB,sBAAK,8CAAL,WACrB,OAAO,CAAC,CAAC,GAAG,MAAM;AAGjB,UAAM,kBAAkB,mBAAK,mBAAkB,GAAG;AAClD,WAAO,kBAAkB;AAAA,EAC3B,CAAC,EACA,IAAI,CAAC,CAAC,GAAG,KAAK,MAAM;AACnB,WAAO,MAAM,0BAA0B;AAAA,EACzC,CAAC;AACH,QAAM,QAAQ,IAAI,eAAe;AACnC;AAEA,aAAQ,SAAC,OAAc;AAErB,qBAAK,cAAa,QAAQ,CAAC,CAAC,GAAG,OAAO,MAAM;AAC1C,uCAAU;AAAA,EACZ,CAAC;AACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBA,kBAAa,WAAG;AACd,SAAO,OAAO,QAAQ,mBAAK,QAAO;AAIpC;AAxRF;AA0XO,IAAM,gCAAN,cAIG,iBAA6B;AAAA,EAMrC,YAAY,SAA8C;AACxD,UAAM,OAAO;AAXV;AAKL,wCAAkB,oBAAI,IAAuD;AAC7E;AAME,uBAAK,eAAgB,OAAO;AAAA,MAC1B,OAAO,QAAQ,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,IACjE;AAAA,EACF;AAAA,EAMA,MAAgB,SACd,UACe;AACf,0BAAK,yDAAL,WAAiB;AACjB,UAAM,oBAAoB,sBAAK,mEAAL;AAC1B,UAAM,gBAAgB,CAAC,GAAG,mBAAK,iBAAgB,KAAK,CAAC,EAAE;AAAA,MACrD,CAAC,QAAQ,OAAO;AAAA,IAClB;AACA,UAAM,oBAAoB,cACvB,KAAK,CAAC,GAAG,MAAM,cAAc,GAAG,CAAC,CAAC,EAClC;AAAA,MAAI,CAAC,QAAK;AAzZjB;AA0ZQ,wCAAK,iBAAgB,IAAI,GAAG,MAA5B,mBAA+B,KAAK,CAAC,GAAG,MAAM;AAC5C,gBAAM,EAAE,SAAS,SAAS,IAAI;AAC9B,gBAAM,EAAE,SAAS,SAAS,IAAI;AAC9B,cACE,OAAO,SAAS,gBAAgB,YAChC,OAAO,SAAS,gBAAgB,UAChC;AACA,mBAAO;AAAA,UACT;AACA,iBAAO,SAAS,cAAc,SAAS;AAAA,QACzC;AAAA;AAAA,IACF,EACC,OAAO,CAACC,cAAaA,cAAa,MAAS,EAC3C,KAAK;AACR,kBAAc,QAAQ,CAAC,QAAQ;AAC7B,yBAAK,iBAAgB,OAAO,GAAG;AAAA,IACjC,CAAC;AACD,QAAI,kBAAkB,SAAS,GAAG;AAChC,YAAM,MAAM,SAAS,iBAAiB;AAAA,IACxC;AAAA,EACF;AAqCF;AApFE;AACA;AANK;AAiBL,0BAAqB,WAAG;AACtB,SAAO,UAAU,OAAO,OAAO,mBAAK,cAAa,CAAC;AACpD;AAmCA,gBAAW,SAAC,UAA4C;AACtD,QAAM,aAAa,KAAK;AACxB,WAAS,QAAQ,CAAC,YAAY;AAlblC;AAmbM,UAAM,EAAE,OAAO,QAAQ,IAAI;AAC3B,YAAI,gCAAgB,OAAO,GAAG;AAE5B,YAAM,MACJ,OAAO,QAAQ,QAAQ,WAAW,OAAO,QAAQ,GAAG,IAAI,OAAO,CAAC;AAClE,UAAI,CAAC,mBAAK,iBAAgB,IAAI,GAAG,GAAG;AAClC,2BAAK,iBAAgB,IAAI,KAAK,CAAC,CAAC;AAAA,MAClC;AACA,+BAAK,iBAAgB,IAAI,GAAG,MAA5B,mBAA+B,KAAK;AACpC,UACE;AAAA,MACA,OAAO,QAAQ,SAAS,aACxB,QAAQ,SAAS,MACjB;AACA,2BAAK,eAAc,KAAK,IAAI,UAAU;AAAA,UACpC,mBAAK,eAAc,KAAK;AAAA,UACxB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,eAAW,iCAAiB,OAAO,GAAG;AACpC,UAAI,QAAQ,YAAY,cAAc;AACpC,YAAI,OAAO,QAAQ,yBAAyB,UAAU;AACpD,gBAAM,IAAI,MAAM,sCAAsC;AAAA,QACxD;AACA,2BAAK,eAAc,KAAK,IAAI,UAAU;AAAA,UACpC,mBAAK,eAAc,KAAK;AAAA,UACxB,OAAO,QAAQ,oBAAoB;AAAA,QACrC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":["message","import_client","messages"]}
|
package/dist/index.browser.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var z=Object.defineProperty,Q=Object.defineProperties;var W=Object.getOwnPropertyDescriptors;var O=Object.getOwnPropertySymbols;var X=Object.prototype.hasOwnProperty,Y=Object.prototype.propertyIsEnumerable;var L=t=>{throw TypeError(t)};var U=(t,e,s)=>e in t?z(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s,I=(t,e)=>{for(var s in e||(e={}))X.call(e,s)&&U(t,s,e[s]);if(O)for(var s of O(e))Y.call(e,s)&&U(t,s,e[s]);return t},K=(t,e)=>Q(t,W(e));var E=(t,e,s)=>e.has(t)||L("Cannot "+s);var a=(t,e,s)=>(E(t,e,"read from private field"),s?s.call(t):e.get(t)),l=(t,e,s)=>e.has(t)?L("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,s),S=(t,e,s,n)=>(E(t,e,"write to private field"),n?n.call(t,s):e.set(t,s),s),c=(t,e,s)=>(E(t,e,"access private method"),s);import{isChangeMessage as Z}from"@electric-sql/client";function te(t,e,s,n=6e4){return new Promise((i,h)=>{let o=t.subscribe(m=>{let _=m.filter(b=>Z(b)).find(b=>{let J=b.headers.operation;return e.includes(J)&&s(b)});if(_)return u(_)}),r=setTimeout(()=>{let m=`matchStream timed out after ${n}ms`;console.error(m),h(m)},n);function u(m){return clearTimeout(r),o(),i(m)}})}function ne(t,e){return s=>s.value[t]===e}function y(t){return BigInt(t.reduce((e,s)=>s>e?s:e))}function D(t){return BigInt(t.reduce((e,s)=>s<e?s:e))}function B(t,e){return t>e?1:t<e?-1:0}import{ShapeStream as P,isChangeMessage as F,isControlMessage as N}from"@electric-sql/client";var R,k,C,M,T,d,p,A,q,G,H,w,v=class{constructor(e){l(this,p);l(this,R);l(this,k,!1);l(this,C);l(this,M);l(this,T);l(this,d,new Map);let{start:s=!0,checkForUpdatesAfterMs:n=100,shapes:i}=e;this.checkForUpdatesAfterMs=n,S(this,R,Object.fromEntries(Object.entries(i).map(([h,o])=>[h,o instanceof P?o:new P(K(I({},o),{start:!1}))]))),S(this,M,Object.fromEntries(Object.entries(i).map(([h])=>[h,BigInt(-1)]))),S(this,T,Object.fromEntries(Object.entries(i).map(([h])=>[h,BigInt(-1)]))),s&&c(this,p,A).call(this)}async _publish(e){await Promise.all(Array.from(a(this,d).values()).map(async([s,n])=>{try{await s(e)}catch(i){queueMicrotask(()=>{throw i})}}))}get shapes(){return a(this,R)}subscribe(e,s){let n=Math.random();return a(this,d).set(n,[e,s]),a(this,k)||c(this,p,A).call(this),()=>{a(this,d).delete(n)}}unsubscribeAll(){a(this,d).clear()}lastSyncedAt(){let e=c(this,p,w).call(this);if(e.length!==0)return e.reduce((s,[n,i])=>{var h;return Math.min(s,(h=i.lastSyncedAt())!=null?h:1/0)},1/0)}lastSynced(){let e=this.lastSyncedAt();return e===void 0?1/0:Date.now()-e}isConnected(){return c(this,p,w).call(this).every(([e,s])=>s.isConnected())}isLoading(){return c(this,p,w).call(this).some(([e,s])=>s.isLoading())}get isUpToDate(){return c(this,p,w).call(this).every(([e,s])=>s.isUpToDate)}};R=new WeakMap,k=new WeakMap,C=new WeakMap,M=new WeakMap,T=new WeakMap,d=new WeakMap,p=new WeakSet,A=function(){if(a(this,k))throw new Error("Cannot start multi-shape stream twice");for(let[e,s]of c(this,p,w).call(this)){if(s.hasStarted())throw new Error(`Shape ${e} already started`);s.subscribe(async n=>{let i=n.filter(N).map(({headers:r})=>typeof r.global_last_seen_lsn=="string"?BigInt(r.global_last_seen_lsn):BigInt(0));if(i.length>0){let r=y(i),u=a(this,T)[e];r>u&&(a(this,T)[e]=r)}let h=n.filter(F).map(({headers:r})=>typeof r.lsn=="string"?BigInt(r.lsn):BigInt(0));if(h.length>0){let r=y(h),u=a(this,M)[e];r>u&&(a(this,M)[e]=r),c(this,p,q).call(this)}let o=n.map(r=>K(I({},r),{shape:e}));await this._publish(o)},n=>c(this,p,H).call(this,n))}S(this,k,!0)},q=function(){var e;(e=a(this,C))!=null||S(this,C,setTimeout(()=>{c(this,p,G).call(this),S(this,C,void 0)},this.checkForUpdatesAfterMs))},G=async function(){let e=y(Object.values(a(this,M))),s=c(this,p,w).call(this).filter(([n])=>a(this,T)[n]<e).map(([n,i])=>i.forceDisconnectAndRefresh());await Promise.all(s)},H=function(e){a(this,d).forEach(([s,n])=>{n==null||n(e)})},w=function(){return Object.entries(a(this,R))};var g,f,x,V,$,j=class extends v{constructor(s){super(s);l(this,x);l(this,g,new Map);l(this,f);S(this,f,Object.fromEntries(Object.entries(s.shapes).map(([n])=>[n,BigInt(-1)])))}async _publish(s){c(this,x,$).call(this,s);let n=c(this,x,V).call(this),i=[...a(this,g).keys()].filter(o=>o<=n),h=i.sort((o,r)=>B(o,r)).map(o=>{var r;return(r=a(this,g).get(o))==null?void 0:r.sort((u,m)=>{let{headers:_}=u,{headers:b}=m;return typeof _.op_position!="number"||typeof b.op_position!="number"?0:_.op_position-b.op_position})}).filter(o=>o!==void 0).flat();i.forEach(o=>{a(this,g).delete(o)}),h.length>0&&await super._publish(h)}};g=new WeakMap,f=new WeakMap,x=new WeakSet,V=function(){return D(Object.values(a(this,f)))},$=function(s){let n=this.isUpToDate;s.forEach(i=>{var r;let{shape:h,headers:o}=i;if(F(i)){let u=typeof o.lsn=="string"?BigInt(o.lsn):BigInt(0);a(this,g).has(u)||a(this,g).set(u,[]),(r=a(this,g).get(u))==null||r.push(i),n&&typeof o.last=="boolean"&&o.last===!0&&(a(this,f)[h]=y([a(this,f)[h],u]))}else if(N(i)&&o.control==="up-to-date"){if(typeof o.global_last_seen_lsn!="string")throw new Error("global_last_seen_lsn is not a number");a(this,f)[h]=y([a(this,f)[h],BigInt(o.global_last_seen_lsn)])}})};export{v as MultiShapeStream,j as TransactionalMultiShapeStream,ne as matchBy,te as matchStream};
|
|
2
2
|
//# sourceMappingURL=index.browser.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/match.ts","../src/bigint-utils.ts","../src/multi-shape-stream.ts"],"sourcesContent":["import {\n isChangeMessage,\n type ShapeStreamInterface,\n type ChangeMessage,\n type GetExtensions,\n type Operation,\n type Row,\n type Value,\n type Message,\n} from '@electric-sql/client'\n\nexport function matchStream<T extends Row<unknown>>(\n stream: ShapeStreamInterface<T>,\n operations: Array<Operation>,\n matchFn: (message: ChangeMessage<T>) => boolean,\n timeout = 60000 // ms\n): Promise<ChangeMessage<T>> {\n return new Promise<ChangeMessage<T>>((resolve, reject) => {\n const unsubscribe: () => void = stream.subscribe(\n (messages: Array<unknown>) => {\n const message = messages\n .filter((msg): msg is ChangeMessage<T> =>\n isChangeMessage(msg as Message<Row<never>>)\n )\n .find((message) => {\n const operation: Operation = message.headers.operation\n\n return operations.includes(operation) && matchFn(message)\n })\n\n if (message) {\n return finish(message)\n }\n }\n )\n\n const timeoutId: NodeJS.Timeout = setTimeout(() => {\n const msg: string = `matchStream timed out after ${timeout}ms`\n\n console.error(msg)\n\n reject(msg)\n }, timeout)\n\n function finish(message: ChangeMessage<T>): void {\n clearTimeout(timeoutId)\n\n unsubscribe()\n\n return resolve(message)\n }\n })\n}\n\nexport function matchBy<T extends Row<unknown>>(\n column: string,\n value: Value<GetExtensions<T>>\n): (message: ChangeMessage<T>) => boolean {\n return (message: ChangeMessage<T>) => message.value[column] === value\n}\n","export function bigIntMax(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e < m ? e : m)))\n}\n\nexport function bigIntCompare(a: bigint, b: bigint): 1 | -1 | 0 {\n return a > b ? 1 : a < b ? -1 : 0\n}\n","import { bigIntCompare, bigIntMax, bigIntMin } from './bigint-utils'\nimport {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n} from '@electric-sql/client'\nimport type {\n ChangeMessage,\n ControlMessage,\n FetchError,\n MaybePromise,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\ninterface MultiShapeStreamOptions<\n TShapeRows extends {\n [K: string]: Row<unknown>\n } = {\n [K: string]: Row<unknown>\n },\n> {\n shapes: {\n [K in keyof TShapeRows]:\n | ShapeStreamOptions<TShapeRows[K]>\n | ShapeStream<TShapeRows[K]>\n }\n start?: boolean\n checkForUpdatesAfterMs?: number // milliseconds\n}\n\ninterface MultiShapeChangeMessage<\n T extends Row<unknown>,\n ShapeNames extends string,\n> extends ChangeMessage<T> {\n shape: ShapeNames\n}\n\ninterface MultiShapeControlMessage<ShapeNames extends string>\n extends ControlMessage {\n shape: ShapeNames\n}\n\ntype MultiShapeMessage<T extends Row<unknown>, ShapeNames extends string> =\n | MultiShapeChangeMessage<T, ShapeNames>\n | MultiShapeControlMessage<ShapeNames>\n\nexport type MultiShapeMessages<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> = {\n [K in keyof TShapeRows & string]: MultiShapeMessage<TShapeRows[K], K>\n}[keyof TShapeRows & string]\n\nexport interface MultiShapeStreamInterface<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> {\n shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n checkForUpdatesAfterMs?: number\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ): () => void\n unsubscribeAll(): void\n\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n isLoading(): boolean\n\n isUpToDate: boolean\n}\n\n/**\n * A multi-shape stream is a stream that can subscribe to multiple shapes.\n * It ensures that all shapes will receive at least an `up-to-date` message from\n * Electric within the `checkForUpdatesAfterMs` interval.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * multiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class MultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> implements MultiShapeStreamInterface<TShapeRows>\n{\n #shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n #started = false\n checkForUpdatesAfterMs?: number\n\n #checkForUpdatesTimeout?: ReturnType<typeof setTimeout> | undefined\n\n // We keep track of the last lsn of data and up-to-date messages for each shape\n // so that we can skip checkForUpdates if the lsn of the up-to-date message is\n // greater than the last lsn of data.\n #lastDataLsns: { [K in keyof TShapeRows]: bigint }\n #lastUpToDateLsns: { [K in keyof TShapeRows]: bigint }\n\n readonly #subscribers = new Map<\n number,\n [\n (messages: MultiShapeMessages<TShapeRows>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n const {\n start = true, // By default we start the multi-shape stream\n checkForUpdatesAfterMs = 100, // Force a check for updates after 100ms\n shapes,\n } = options\n this.checkForUpdatesAfterMs = checkForUpdatesAfterMs\n this.#shapes = Object.fromEntries(\n Object.entries(shapes).map(([key, shape]) => [\n key,\n shape instanceof ShapeStream\n ? shape\n : new ShapeStream<TShapeRows[typeof key]>({\n ...shape,\n start: false,\n }),\n ])\n ) as { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n this.#lastDataLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n this.#lastUpToDateLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n if (start) this.#start()\n }\n\n #start() {\n if (this.#started) throw new Error(`Cannot start multi-shape stream twice`)\n for (const [key, shape] of this.#shapeEntries()) {\n if (shape.hasStarted()) {\n // The multi-shape stream needs to be started together as a whole, and so we\n // have to check that a shape is not already started.\n throw new Error(`Shape ${key} already started`)\n }\n shape.subscribe(\n async (messages) => {\n // Whats the max lsn of the up-to-date messages?\n const upToDateLsns = messages\n .filter(isControlMessage)\n .map(({ headers }) =>\n typeof headers.global_last_seen_lsn === `string`\n ? BigInt(headers.global_last_seen_lsn)\n : BigInt(0)\n )\n if (upToDateLsns.length > 0) {\n const maxUpToDateLsn = bigIntMax(upToDateLsns)\n const lastMaxUpToDateLsn = this.#lastUpToDateLsns[key]\n if (maxUpToDateLsn > lastMaxUpToDateLsn) {\n this.#lastUpToDateLsns[key] = maxUpToDateLsn\n }\n }\n\n // Whats the max lsn of the data messages?\n const dataLsns = messages\n .filter(isChangeMessage)\n .map(({ headers }) =>\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n )\n if (dataLsns.length > 0) {\n const maxDataLsn = bigIntMax(dataLsns)\n const lastMaxDataLsn = this.#lastDataLsns[key]\n if (maxDataLsn > lastMaxDataLsn) {\n this.#lastDataLsns[key] = maxDataLsn\n }\n // There is new data, so we need to schedule a check for updates on\n // other shapes\n this.#scheduleCheckForUpdates()\n }\n\n // Publish the messages to the multi-shape stream subscribers\n const multiShapeMessages = messages.map(\n (message) =>\n ({\n ...message,\n shape: key,\n }) as MultiShapeMessages<TShapeRows>\n )\n await this._publish(multiShapeMessages)\n },\n (error) => this.#onError(error)\n )\n }\n this.#started = true\n }\n\n #scheduleCheckForUpdates() {\n this.#checkForUpdatesTimeout ??= setTimeout(() => {\n this.#checkForUpdates()\n this.#checkForUpdatesTimeout = undefined\n }, this.checkForUpdatesAfterMs)\n }\n\n async #checkForUpdates() {\n const maxDataLsn = bigIntMax(Object.values(this.#lastDataLsns))\n const refreshPromises = this.#shapeEntries()\n .filter(([key]) => {\n // We only need to refresh shapes that have not seen an up-to-date message\n // lower than the max lsn of the data messages we have received.\n const lastUpToDateLsn = this.#lastUpToDateLsns[key]\n return lastUpToDateLsn < maxDataLsn\n })\n .map(([_, shape]) => {\n return shape.forceDisconnectAndRefresh()\n })\n await Promise.all(refreshPromises)\n }\n\n #onError(error: Error) {\n // TODO: we probably want to disconnect all shapes here on the first error\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n await Promise.all(\n Array.from(this.#subscribers.values()).map(async ([callback, __]) => {\n try {\n await callback(messages)\n } catch (err) {\n queueMicrotask(() => {\n throw err\n })\n }\n })\n )\n }\n\n /**\n * Returns an array of the shape entries.\n * Ensures that the shape entries are typed, as `Object.entries`\n * will not type the entries correctly.\n */\n #shapeEntries() {\n return Object.entries(this.#shapes) as [\n keyof TShapeRows & string,\n ShapeStream<TShapeRows[string]>,\n ][]\n }\n\n /**\n * The ShapeStreams that are being subscribed to.\n */\n get shapes() {\n return this.#shapes\n }\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) this.#start()\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n // Min of all the lastSyncedAt values\n const shapeEntries = this.#shapeEntries()\n if (shapeEntries.length === 0) return\n return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {\n return Math.min(minLastSyncedAt, shape.lastSyncedAt() ?? Infinity)\n }, Infinity)\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n const lastSyncedAt = this.lastSyncedAt()\n if (lastSyncedAt === undefined) return Infinity\n return Date.now() - lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#shapeEntries().every(([_, shape]) => shape.isConnected())\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading(): boolean {\n return this.#shapeEntries().some(([_, shape]) => shape.isLoading())\n }\n\n get isUpToDate() {\n return this.#shapeEntries().every(([_, shape]) => shape.isUpToDate)\n }\n}\n\n/**\n * A transactional multi-shape stream is a multi-shape stream that emits the\n * messages in transactional batches, ensuring that all shapes will receive\n * at least an `up-to-date` message from Electric within the `checkForUpdatesAfterMs`\n * interval.\n * It uses the `lsn` metadata to infer transaction boundaries, and the `op_position`\n * metadata to sort the messages within a transaction.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * transactionalMultiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class TransactionalMultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> extends MultiShapeStream<TShapeRows> {\n #changeMessages = new Map<bigint, MultiShapeMessage<Row<unknown>, string>[]>()\n #completeLsns: {\n [K in keyof TShapeRows]: bigint\n }\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n super(options)\n this.#completeLsns = Object.fromEntries(\n Object.entries(options.shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n }\n\n #getLowestCompleteLsn() {\n return bigIntMin(Object.values(this.#completeLsns))\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n this.#accumulate(messages)\n const lowestCompleteLsn = this.#getLowestCompleteLsn()\n const lsnsToPublish = [...this.#changeMessages.keys()].filter(\n (lsn) => lsn <= lowestCompleteLsn\n )\n const messagesToPublish = lsnsToPublish\n .sort((a, b) => bigIntCompare(a, b))\n .map((lsn) =>\n this.#changeMessages.get(lsn)?.sort((a, b) => {\n const { headers: aHeaders } = a\n const { headers: bHeaders } = b\n if (\n typeof aHeaders.op_position !== `number` ||\n typeof bHeaders.op_position !== `number`\n ) {\n return 0 // op_position is not present on the snapshot message\n }\n return aHeaders.op_position - bHeaders.op_position\n })\n )\n .filter((messages) => messages !== undefined)\n .flat() as MultiShapeMessages<TShapeRows>[]\n lsnsToPublish.forEach((lsn) => {\n this.#changeMessages.delete(lsn)\n })\n if (messagesToPublish.length > 0) {\n await super._publish(messagesToPublish)\n }\n }\n\n #accumulate(messages: MultiShapeMessages<TShapeRows>[]) {\n const isUpToDate = this.isUpToDate\n messages.forEach((message) => {\n const { shape, headers } = message\n if (isChangeMessage(message)) {\n // The snapshot message does not have an lsn, so we use 0\n const lsn =\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n if (!this.#changeMessages.has(lsn)) {\n this.#changeMessages.set(lsn, [])\n }\n this.#changeMessages.get(lsn)?.push(message)\n if (\n isUpToDate && // All shapes must be up to date\n typeof headers.last === `boolean` &&\n headers.last === true\n ) {\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n lsn,\n ])\n }\n } else if (isControlMessage(message)) {\n if (headers.control === `up-to-date`) {\n if (typeof headers.global_last_seen_lsn !== `string`) {\n throw new Error(`global_last_seen_lsn is not a number`)\n }\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n BigInt(headers.global_last_seen_lsn),\n ])\n }\n }\n })\n }\n}\n"],"mappings":"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","names":["isChangeMessage","matchStream","stream","operations","matchFn","timeout","resolve","reject","unsubscribe","messages","message","msg","operation","finish","timeoutId","matchBy","column","value","bigIntMax","nums","m","e","bigIntMin","bigIntCompare","a","b","ShapeStream","isChangeMessage","isControlMessage","_shapes","_started","_checkForUpdatesTimeout","_lastDataLsns","_lastUpToDateLsns","_subscribers","_MultiShapeStream_instances","start_fn","scheduleCheckForUpdates_fn","checkForUpdates_fn","onError_fn","shapeEntries_fn","MultiShapeStream","options","__privateAdd","start","checkForUpdatesAfterMs","shapes","__privateSet","key","shape","ShapeStream","__spreadProps","__spreadValues","__privateMethod","messages","__async","__privateGet","_0","callback","__","err","onError","subscriptionId","shapeEntries","minLastSyncedAt","_","_a","lastSyncedAt","upToDateLsns","isControlMessage","headers","maxUpToDateLsn","bigIntMax","lastMaxUpToDateLsn","dataLsns","isChangeMessage","maxDataLsn","lastMaxDataLsn","multiShapeMessages","message","error","refreshPromises","errorFn","_changeMessages","_completeLsns","_TransactionalMultiShapeStream_instances","getLowestCompleteLsn_fn","accumulate_fn","_TransactionalMultiShapeStream","lowestCompleteLsn","lsnsToPublish","lsn","messagesToPublish","a","b","bigIntCompare","aHeaders","bHeaders","__superGet","bigIntMin","isUpToDate","TransactionalMultiShapeStream"]}
|
|
1
|
+
{"version":3,"sources":["../src/match.ts","../src/bigint-utils.ts","../src/multi-shape-stream.ts"],"sourcesContent":["import {\n isChangeMessage,\n type ShapeStreamInterface,\n type ChangeMessage,\n type GetExtensions,\n type Operation,\n type Row,\n type Value,\n type Message,\n} from '@electric-sql/client'\n\nexport function matchStream<T extends Row<unknown>>(\n stream: ShapeStreamInterface<T>,\n operations: Array<Operation>,\n matchFn: (message: ChangeMessage<T>) => boolean,\n timeout = 60000 // ms\n): Promise<ChangeMessage<T>> {\n return new Promise<ChangeMessage<T>>((resolve, reject) => {\n const unsubscribe: () => void = stream.subscribe(\n (messages: Array<unknown>) => {\n const message = messages\n .filter((msg): msg is ChangeMessage<T> =>\n isChangeMessage(msg as Message<Row<never>>)\n )\n .find((message) => {\n const operation: Operation = message.headers.operation\n\n return operations.includes(operation) && matchFn(message)\n })\n\n if (message) {\n return finish(message)\n }\n }\n )\n\n const timeoutId: NodeJS.Timeout = setTimeout(() => {\n const msg: string = `matchStream timed out after ${timeout}ms`\n\n console.error(msg)\n\n reject(msg)\n }, timeout)\n\n function finish(message: ChangeMessage<T>): void {\n clearTimeout(timeoutId)\n\n unsubscribe()\n\n return resolve(message)\n }\n })\n}\n\nexport function matchBy<T extends Row<unknown>>(\n column: string,\n value: Value<GetExtensions<T>>\n): (message: ChangeMessage<T>) => boolean {\n return (message: ChangeMessage<T>) => message.value[column] === value\n}\n","export function bigIntMax(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e < m ? e : m)))\n}\n\nexport function bigIntCompare(a: bigint, b: bigint): 1 | -1 | 0 {\n return a > b ? 1 : a < b ? -1 : 0\n}\n","import { bigIntCompare, bigIntMax, bigIntMin } from './bigint-utils'\nimport {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n} from '@electric-sql/client'\nimport type {\n ChangeMessage,\n ControlMessage,\n FetchError,\n MaybePromise,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\ninterface MultiShapeStreamOptions<\n TShapeRows extends {\n [K: string]: Row<unknown>\n } = {\n [K: string]: Row<unknown>\n },\n> {\n shapes: {\n [K in keyof TShapeRows]:\n | ShapeStreamOptions<TShapeRows[K]>\n | ShapeStream<TShapeRows[K]>\n }\n start?: boolean\n checkForUpdatesAfterMs?: number // milliseconds\n}\n\ninterface MultiShapeChangeMessage<\n T extends Row<unknown>,\n ShapeNames extends string,\n> extends ChangeMessage<T> {\n shape: ShapeNames\n}\n\ninterface MultiShapeControlMessage<ShapeNames extends string>\n extends ControlMessage {\n shape: ShapeNames\n}\n\ntype MultiShapeMessage<T extends Row<unknown>, ShapeNames extends string> =\n | MultiShapeChangeMessage<T, ShapeNames>\n | MultiShapeControlMessage<ShapeNames>\n\nexport type MultiShapeMessages<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> = {\n [K in keyof TShapeRows & string]: MultiShapeMessage<TShapeRows[K], K>\n}[keyof TShapeRows & string]\n\nexport interface MultiShapeStreamInterface<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> {\n shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n checkForUpdatesAfterMs?: number\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ): () => void\n unsubscribeAll(): void\n\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n isLoading(): boolean\n\n isUpToDate: boolean\n}\n\n/**\n * A multi-shape stream is a stream that can subscribe to multiple shapes.\n * It ensures that all shapes will receive at least an `up-to-date` message from\n * Electric within the `checkForUpdatesAfterMs` interval.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * multiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class MultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> implements MultiShapeStreamInterface<TShapeRows>\n{\n #shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n #started = false\n checkForUpdatesAfterMs?: number\n\n #checkForUpdatesTimeout?: ReturnType<typeof setTimeout> | undefined\n\n // We keep track of the last lsn of data and up-to-date messages for each shape\n // so that we can skip checkForUpdates if the lsn of the up-to-date message is\n // greater than the last lsn of data.\n #lastDataLsns: { [K in keyof TShapeRows]: bigint }\n #lastUpToDateLsns: { [K in keyof TShapeRows]: bigint }\n\n readonly #subscribers = new Map<\n number,\n [\n (messages: MultiShapeMessages<TShapeRows>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n const {\n start = true, // By default we start the multi-shape stream\n checkForUpdatesAfterMs = 100, // Force a check for updates after 100ms\n shapes,\n } = options\n this.checkForUpdatesAfterMs = checkForUpdatesAfterMs\n this.#shapes = Object.fromEntries(\n Object.entries(shapes).map(([key, shape]) => [\n key,\n shape instanceof ShapeStream\n ? shape\n : new ShapeStream<TShapeRows[typeof key]>({\n ...shape,\n start: false,\n }),\n ])\n ) as { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n this.#lastDataLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n this.#lastUpToDateLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n if (start) this.#start()\n }\n\n #start() {\n if (this.#started) throw new Error(`Cannot start multi-shape stream twice`)\n for (const [key, shape] of this.#shapeEntries()) {\n if (shape.hasStarted()) {\n // The multi-shape stream needs to be started together as a whole, and so we\n // have to check that a shape is not already started.\n throw new Error(`Shape ${key} already started`)\n }\n shape.subscribe(\n async (messages) => {\n // Whats the max lsn of the up-to-date messages?\n const upToDateLsns = messages\n .filter(isControlMessage)\n .map(({ headers }) =>\n typeof headers.global_last_seen_lsn === `string`\n ? BigInt(headers.global_last_seen_lsn)\n : BigInt(0)\n )\n if (upToDateLsns.length > 0) {\n const maxUpToDateLsn = bigIntMax(upToDateLsns)\n const lastMaxUpToDateLsn = this.#lastUpToDateLsns[key]\n if (maxUpToDateLsn > lastMaxUpToDateLsn) {\n this.#lastUpToDateLsns[key] = maxUpToDateLsn\n }\n }\n\n // Whats the max lsn of the data messages?\n const dataLsns = messages\n .filter(isChangeMessage)\n .map(({ headers }) =>\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n )\n if (dataLsns.length > 0) {\n const maxDataLsn = bigIntMax(dataLsns)\n const lastMaxDataLsn = this.#lastDataLsns[key]\n if (maxDataLsn > lastMaxDataLsn) {\n this.#lastDataLsns[key] = maxDataLsn\n }\n // There is new data, so we need to schedule a check for updates on\n // other shapes\n this.#scheduleCheckForUpdates()\n }\n\n // Publish the messages to the multi-shape stream subscribers\n const multiShapeMessages = messages.map(\n (message) =>\n ({\n ...message,\n shape: key,\n }) as MultiShapeMessages<TShapeRows>\n )\n await this._publish(multiShapeMessages)\n },\n (error) => this.#onError(error)\n )\n }\n this.#started = true\n }\n\n #scheduleCheckForUpdates() {\n this.#checkForUpdatesTimeout ??= setTimeout(() => {\n this.#checkForUpdates()\n this.#checkForUpdatesTimeout = undefined\n }, this.checkForUpdatesAfterMs)\n }\n\n async #checkForUpdates() {\n const maxDataLsn = bigIntMax(Object.values(this.#lastDataLsns))\n const refreshPromises = this.#shapeEntries()\n .filter(([key]) => {\n // We only need to refresh shapes that have not seen an up-to-date message\n // lower than the max lsn of the data messages we have received.\n const lastUpToDateLsn = this.#lastUpToDateLsns[key]\n return lastUpToDateLsn < maxDataLsn\n })\n .map(([_, shape]) => {\n return shape.forceDisconnectAndRefresh()\n })\n await Promise.all(refreshPromises)\n }\n\n #onError(error: Error) {\n // TODO: we probably want to disconnect all shapes here on the first error\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n await Promise.all(\n Array.from(this.#subscribers.values()).map(async ([callback, __]) => {\n try {\n await callback(messages)\n } catch (err) {\n queueMicrotask(() => {\n throw err\n })\n }\n })\n )\n }\n\n /**\n * Returns an array of the shape entries.\n * Ensures that the shape entries are typed, as `Object.entries`\n * will not type the entries correctly.\n */\n #shapeEntries() {\n return Object.entries(this.#shapes) as [\n keyof TShapeRows & string,\n ShapeStream<TShapeRows[string]>,\n ][]\n }\n\n /**\n * The ShapeStreams that are being subscribed to.\n */\n get shapes() {\n return this.#shapes\n }\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) this.#start()\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n // Min of all the lastSyncedAt values\n const shapeEntries = this.#shapeEntries()\n if (shapeEntries.length === 0) return\n return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {\n return Math.min(minLastSyncedAt, shape.lastSyncedAt() ?? Infinity)\n }, Infinity)\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n const lastSyncedAt = this.lastSyncedAt()\n if (lastSyncedAt === undefined) return Infinity\n return Date.now() - lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#shapeEntries().every(([_, shape]) => shape.isConnected())\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading(): boolean {\n return this.#shapeEntries().some(([_, shape]) => shape.isLoading())\n }\n\n get isUpToDate() {\n return this.#shapeEntries().every(([_, shape]) => shape.isUpToDate)\n }\n}\n\n/**\n * A transactional multi-shape stream is a multi-shape stream that emits the\n * messages in transactional batches, ensuring that all shapes will receive\n * at least an `up-to-date` message from Electric within the `checkForUpdatesAfterMs`\n * interval.\n * It uses the `lsn` metadata to infer transaction boundaries, and the `op_position`\n * metadata to sort the messages within a transaction.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * transactionalMultiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class TransactionalMultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> extends MultiShapeStream<TShapeRows> {\n #changeMessages = new Map<bigint, MultiShapeMessage<Row<unknown>, string>[]>()\n #completeLsns: {\n [K in keyof TShapeRows]: bigint\n }\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n super(options)\n this.#completeLsns = Object.fromEntries(\n Object.entries(options.shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n }\n\n #getLowestCompleteLsn() {\n return bigIntMin(Object.values(this.#completeLsns))\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n this.#accumulate(messages)\n const lowestCompleteLsn = this.#getLowestCompleteLsn()\n const lsnsToPublish = [...this.#changeMessages.keys()].filter(\n (lsn) => lsn <= lowestCompleteLsn\n )\n const messagesToPublish = lsnsToPublish\n .sort((a, b) => bigIntCompare(a, b))\n .map((lsn) =>\n this.#changeMessages.get(lsn)?.sort((a, b) => {\n const { headers: aHeaders } = a\n const { headers: bHeaders } = b\n if (\n typeof aHeaders.op_position !== `number` ||\n typeof bHeaders.op_position !== `number`\n ) {\n return 0 // op_position is not present on the snapshot message\n }\n return aHeaders.op_position - bHeaders.op_position\n })\n )\n .filter((messages) => messages !== undefined)\n .flat() as MultiShapeMessages<TShapeRows>[]\n lsnsToPublish.forEach((lsn) => {\n this.#changeMessages.delete(lsn)\n })\n if (messagesToPublish.length > 0) {\n await super._publish(messagesToPublish)\n }\n }\n\n #accumulate(messages: MultiShapeMessages<TShapeRows>[]) {\n const isUpToDate = this.isUpToDate\n messages.forEach((message) => {\n const { shape, headers } = message\n if (isChangeMessage(message)) {\n // The snapshot message does not have an lsn, so we use 0\n const lsn =\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n if (!this.#changeMessages.has(lsn)) {\n this.#changeMessages.set(lsn, [])\n }\n this.#changeMessages.get(lsn)?.push(message)\n if (\n isUpToDate && // All shapes must be up to date\n typeof headers.last === `boolean` &&\n headers.last === true\n ) {\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n lsn,\n ])\n }\n } else if (isControlMessage(message)) {\n if (headers.control === `up-to-date`) {\n if (typeof headers.global_last_seen_lsn !== `string`) {\n throw new Error(`global_last_seen_lsn is not a number`)\n }\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n BigInt(headers.global_last_seen_lsn),\n ])\n }\n }\n })\n }\n}\n"],"mappings":"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","names":["isChangeMessage","matchStream","stream","operations","matchFn","timeout","resolve","reject","unsubscribe","messages","message","msg","operation","finish","timeoutId","matchBy","column","value","bigIntMax","nums","m","e","bigIntMin","bigIntCompare","a","b","ShapeStream","isChangeMessage","isControlMessage","_shapes","_started","_checkForUpdatesTimeout","_lastDataLsns","_lastUpToDateLsns","_subscribers","_MultiShapeStream_instances","start_fn","scheduleCheckForUpdates_fn","checkForUpdates_fn","onError_fn","shapeEntries_fn","MultiShapeStream","options","__privateAdd","start","checkForUpdatesAfterMs","shapes","__privateSet","key","shape","ShapeStream","__spreadProps","__spreadValues","__privateMethod","messages","__privateGet","callback","__","err","onError","subscriptionId","shapeEntries","minLastSyncedAt","_","_a","lastSyncedAt","upToDateLsns","isControlMessage","headers","maxUpToDateLsn","bigIntMax","lastMaxUpToDateLsn","dataLsns","isChangeMessage","maxDataLsn","lastMaxDataLsn","multiShapeMessages","message","error","refreshPromises","errorFn","_changeMessages","_completeLsns","_TransactionalMultiShapeStream_instances","getLowestCompleteLsn_fn","accumulate_fn","TransactionalMultiShapeStream","lowestCompleteLsn","lsnsToPublish","lsn","messagesToPublish","a","b","bigIntCompare","aHeaders","bHeaders","bigIntMin","isUpToDate"]}
|
package/dist/index.mjs
CHANGED
|
@@ -2,10 +2,8 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defProps = Object.defineProperties;
|
|
3
3
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
6
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
|
-
var __reflectGet = Reflect.get;
|
|
9
7
|
var __typeError = (msg) => {
|
|
10
8
|
throw TypeError(msg);
|
|
11
9
|
};
|
|
@@ -27,27 +25,6 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
|
|
|
27
25
|
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
28
26
|
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
29
27
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
30
|
-
var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj);
|
|
31
|
-
var __async = (__this, __arguments, generator) => {
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
var fulfilled = (value) => {
|
|
34
|
-
try {
|
|
35
|
-
step(generator.next(value));
|
|
36
|
-
} catch (e) {
|
|
37
|
-
reject(e);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
var rejected = (value) => {
|
|
41
|
-
try {
|
|
42
|
-
step(generator.throw(value));
|
|
43
|
-
} catch (e) {
|
|
44
|
-
reject(e);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
48
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
28
|
|
|
52
29
|
// src/match.ts
|
|
53
30
|
import {
|
|
@@ -138,20 +115,18 @@ var MultiShapeStream = class {
|
|
|
138
115
|
));
|
|
139
116
|
if (start) __privateMethod(this, _MultiShapeStream_instances, start_fn).call(this);
|
|
140
117
|
}
|
|
141
|
-
_publish(messages) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
);
|
|
154
|
-
});
|
|
118
|
+
async _publish(messages) {
|
|
119
|
+
await Promise.all(
|
|
120
|
+
Array.from(__privateGet(this, _subscribers).values()).map(async ([callback, __]) => {
|
|
121
|
+
try {
|
|
122
|
+
await callback(messages);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
queueMicrotask(() => {
|
|
125
|
+
throw err;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
);
|
|
155
130
|
}
|
|
156
131
|
/**
|
|
157
132
|
* The ShapeStreams that are being subscribed to.
|
|
@@ -211,7 +186,7 @@ start_fn = function() {
|
|
|
211
186
|
throw new Error(`Shape ${key} already started`);
|
|
212
187
|
}
|
|
213
188
|
shape.subscribe(
|
|
214
|
-
(messages) =>
|
|
189
|
+
async (messages) => {
|
|
215
190
|
const upToDateLsns = messages.filter(isControlMessage).map(
|
|
216
191
|
({ headers }) => typeof headers.global_last_seen_lsn === `string` ? BigInt(headers.global_last_seen_lsn) : BigInt(0)
|
|
217
192
|
);
|
|
@@ -238,8 +213,8 @@ start_fn = function() {
|
|
|
238
213
|
shape: key
|
|
239
214
|
})
|
|
240
215
|
);
|
|
241
|
-
|
|
242
|
-
}
|
|
216
|
+
await this._publish(multiShapeMessages);
|
|
217
|
+
},
|
|
243
218
|
(error) => __privateMethod(this, _MultiShapeStream_instances, onError_fn).call(this, error)
|
|
244
219
|
);
|
|
245
220
|
}
|
|
@@ -252,17 +227,15 @@ scheduleCheckForUpdates_fn = function() {
|
|
|
252
227
|
__privateSet(this, _checkForUpdatesTimeout, void 0);
|
|
253
228
|
}, this.checkForUpdatesAfterMs));
|
|
254
229
|
};
|
|
255
|
-
checkForUpdates_fn = function() {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return shape.forceDisconnectAndRefresh();
|
|
263
|
-
});
|
|
264
|
-
yield Promise.all(refreshPromises);
|
|
230
|
+
checkForUpdates_fn = async function() {
|
|
231
|
+
const maxDataLsn = bigIntMax(Object.values(__privateGet(this, _lastDataLsns)));
|
|
232
|
+
const refreshPromises = __privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this).filter(([key]) => {
|
|
233
|
+
const lastUpToDateLsn = __privateGet(this, _lastUpToDateLsns)[key];
|
|
234
|
+
return lastUpToDateLsn < maxDataLsn;
|
|
235
|
+
}).map(([_, shape]) => {
|
|
236
|
+
return shape.forceDisconnectAndRefresh();
|
|
265
237
|
});
|
|
238
|
+
await Promise.all(refreshPromises);
|
|
266
239
|
};
|
|
267
240
|
onError_fn = function(error) {
|
|
268
241
|
__privateGet(this, _subscribers).forEach(([_, errorFn]) => {
|
|
@@ -278,7 +251,7 @@ shapeEntries_fn = function() {
|
|
|
278
251
|
return Object.entries(__privateGet(this, _shapes));
|
|
279
252
|
};
|
|
280
253
|
var _changeMessages, _completeLsns, _TransactionalMultiShapeStream_instances, getLowestCompleteLsn_fn, accumulate_fn;
|
|
281
|
-
var
|
|
254
|
+
var TransactionalMultiShapeStream = class extends MultiShapeStream {
|
|
282
255
|
constructor(options) {
|
|
283
256
|
super(options);
|
|
284
257
|
__privateAdd(this, _TransactionalMultiShapeStream_instances);
|
|
@@ -288,33 +261,31 @@ var _TransactionalMultiShapeStream = class _TransactionalMultiShapeStream extend
|
|
|
288
261
|
Object.entries(options.shapes).map(([key]) => [key, BigInt(-1)])
|
|
289
262
|
));
|
|
290
263
|
}
|
|
291
|
-
_publish(messages) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
).filter((messages2) => messages2 !== void 0).flat();
|
|
311
|
-
lsnsToPublish.forEach((lsn) => {
|
|
312
|
-
__privateGet(this, _changeMessages).delete(lsn);
|
|
313
|
-
});
|
|
314
|
-
if (messagesToPublish.length > 0) {
|
|
315
|
-
yield __superGet(_TransactionalMultiShapeStream.prototype, this, "_publish").call(this, messagesToPublish);
|
|
264
|
+
async _publish(messages) {
|
|
265
|
+
__privateMethod(this, _TransactionalMultiShapeStream_instances, accumulate_fn).call(this, messages);
|
|
266
|
+
const lowestCompleteLsn = __privateMethod(this, _TransactionalMultiShapeStream_instances, getLowestCompleteLsn_fn).call(this);
|
|
267
|
+
const lsnsToPublish = [...__privateGet(this, _changeMessages).keys()].filter(
|
|
268
|
+
(lsn) => lsn <= lowestCompleteLsn
|
|
269
|
+
);
|
|
270
|
+
const messagesToPublish = lsnsToPublish.sort((a, b) => bigIntCompare(a, b)).map(
|
|
271
|
+
(lsn) => {
|
|
272
|
+
var _a;
|
|
273
|
+
return (_a = __privateGet(this, _changeMessages).get(lsn)) == null ? void 0 : _a.sort((a, b) => {
|
|
274
|
+
const { headers: aHeaders } = a;
|
|
275
|
+
const { headers: bHeaders } = b;
|
|
276
|
+
if (typeof aHeaders.op_position !== `number` || typeof bHeaders.op_position !== `number`) {
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
return aHeaders.op_position - bHeaders.op_position;
|
|
280
|
+
});
|
|
316
281
|
}
|
|
282
|
+
).filter((messages2) => messages2 !== void 0).flat();
|
|
283
|
+
lsnsToPublish.forEach((lsn) => {
|
|
284
|
+
__privateGet(this, _changeMessages).delete(lsn);
|
|
317
285
|
});
|
|
286
|
+
if (messagesToPublish.length > 0) {
|
|
287
|
+
await super._publish(messagesToPublish);
|
|
288
|
+
}
|
|
318
289
|
}
|
|
319
290
|
};
|
|
320
291
|
_changeMessages = new WeakMap();
|
|
@@ -354,7 +325,6 @@ accumulate_fn = function(messages) {
|
|
|
354
325
|
}
|
|
355
326
|
});
|
|
356
327
|
};
|
|
357
|
-
var TransactionalMultiShapeStream = _TransactionalMultiShapeStream;
|
|
358
328
|
export {
|
|
359
329
|
MultiShapeStream,
|
|
360
330
|
TransactionalMultiShapeStream,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/match.ts","../src/bigint-utils.ts","../src/multi-shape-stream.ts"],"sourcesContent":["import {\n isChangeMessage,\n type ShapeStreamInterface,\n type ChangeMessage,\n type GetExtensions,\n type Operation,\n type Row,\n type Value,\n type Message,\n} from '@electric-sql/client'\n\nexport function matchStream<T extends Row<unknown>>(\n stream: ShapeStreamInterface<T>,\n operations: Array<Operation>,\n matchFn: (message: ChangeMessage<T>) => boolean,\n timeout = 60000 // ms\n): Promise<ChangeMessage<T>> {\n return new Promise<ChangeMessage<T>>((resolve, reject) => {\n const unsubscribe: () => void = stream.subscribe(\n (messages: Array<unknown>) => {\n const message = messages\n .filter((msg): msg is ChangeMessage<T> =>\n isChangeMessage(msg as Message<Row<never>>)\n )\n .find((message) => {\n const operation: Operation = message.headers.operation\n\n return operations.includes(operation) && matchFn(message)\n })\n\n if (message) {\n return finish(message)\n }\n }\n )\n\n const timeoutId: NodeJS.Timeout = setTimeout(() => {\n const msg: string = `matchStream timed out after ${timeout}ms`\n\n console.error(msg)\n\n reject(msg)\n }, timeout)\n\n function finish(message: ChangeMessage<T>): void {\n clearTimeout(timeoutId)\n\n unsubscribe()\n\n return resolve(message)\n }\n })\n}\n\nexport function matchBy<T extends Row<unknown>>(\n column: string,\n value: Value<GetExtensions<T>>\n): (message: ChangeMessage<T>) => boolean {\n return (message: ChangeMessage<T>) => message.value[column] === value\n}\n","export function bigIntMax(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e < m ? e : m)))\n}\n\nexport function bigIntCompare(a: bigint, b: bigint): 1 | -1 | 0 {\n return a > b ? 1 : a < b ? -1 : 0\n}\n","import { bigIntCompare, bigIntMax, bigIntMin } from './bigint-utils'\nimport {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n} from '@electric-sql/client'\nimport type {\n ChangeMessage,\n ControlMessage,\n FetchError,\n MaybePromise,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\ninterface MultiShapeStreamOptions<\n TShapeRows extends {\n [K: string]: Row<unknown>\n } = {\n [K: string]: Row<unknown>\n },\n> {\n shapes: {\n [K in keyof TShapeRows]:\n | ShapeStreamOptions<TShapeRows[K]>\n | ShapeStream<TShapeRows[K]>\n }\n start?: boolean\n checkForUpdatesAfterMs?: number // milliseconds\n}\n\ninterface MultiShapeChangeMessage<\n T extends Row<unknown>,\n ShapeNames extends string,\n> extends ChangeMessage<T> {\n shape: ShapeNames\n}\n\ninterface MultiShapeControlMessage<ShapeNames extends string>\n extends ControlMessage {\n shape: ShapeNames\n}\n\ntype MultiShapeMessage<T extends Row<unknown>, ShapeNames extends string> =\n | MultiShapeChangeMessage<T, ShapeNames>\n | MultiShapeControlMessage<ShapeNames>\n\nexport type MultiShapeMessages<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> = {\n [K in keyof TShapeRows & string]: MultiShapeMessage<TShapeRows[K], K>\n}[keyof TShapeRows & string]\n\nexport interface MultiShapeStreamInterface<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> {\n shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n checkForUpdatesAfterMs?: number\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ): () => void\n unsubscribeAll(): void\n\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n isLoading(): boolean\n\n isUpToDate: boolean\n}\n\n/**\n * A multi-shape stream is a stream that can subscribe to multiple shapes.\n * It ensures that all shapes will receive at least an `up-to-date` message from\n * Electric within the `checkForUpdatesAfterMs` interval.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * multiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class MultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> implements MultiShapeStreamInterface<TShapeRows>\n{\n #shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n #started = false\n checkForUpdatesAfterMs?: number\n\n #checkForUpdatesTimeout?: ReturnType<typeof setTimeout> | undefined\n\n // We keep track of the last lsn of data and up-to-date messages for each shape\n // so that we can skip checkForUpdates if the lsn of the up-to-date message is\n // greater than the last lsn of data.\n #lastDataLsns: { [K in keyof TShapeRows]: bigint }\n #lastUpToDateLsns: { [K in keyof TShapeRows]: bigint }\n\n readonly #subscribers = new Map<\n number,\n [\n (messages: MultiShapeMessages<TShapeRows>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n const {\n start = true, // By default we start the multi-shape stream\n checkForUpdatesAfterMs = 100, // Force a check for updates after 100ms\n shapes,\n } = options\n this.checkForUpdatesAfterMs = checkForUpdatesAfterMs\n this.#shapes = Object.fromEntries(\n Object.entries(shapes).map(([key, shape]) => [\n key,\n shape instanceof ShapeStream\n ? shape\n : new ShapeStream<TShapeRows[typeof key]>({\n ...shape,\n start: false,\n }),\n ])\n ) as { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n this.#lastDataLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n this.#lastUpToDateLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n if (start) this.#start()\n }\n\n #start() {\n if (this.#started) throw new Error(`Cannot start multi-shape stream twice`)\n for (const [key, shape] of this.#shapeEntries()) {\n if (shape.hasStarted()) {\n // The multi-shape stream needs to be started together as a whole, and so we\n // have to check that a shape is not already started.\n throw new Error(`Shape ${key} already started`)\n }\n shape.subscribe(\n async (messages) => {\n // Whats the max lsn of the up-to-date messages?\n const upToDateLsns = messages\n .filter(isControlMessage)\n .map(({ headers }) =>\n typeof headers.global_last_seen_lsn === `string`\n ? BigInt(headers.global_last_seen_lsn)\n : BigInt(0)\n )\n if (upToDateLsns.length > 0) {\n const maxUpToDateLsn = bigIntMax(upToDateLsns)\n const lastMaxUpToDateLsn = this.#lastUpToDateLsns[key]\n if (maxUpToDateLsn > lastMaxUpToDateLsn) {\n this.#lastUpToDateLsns[key] = maxUpToDateLsn\n }\n }\n\n // Whats the max lsn of the data messages?\n const dataLsns = messages\n .filter(isChangeMessage)\n .map(({ headers }) =>\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n )\n if (dataLsns.length > 0) {\n const maxDataLsn = bigIntMax(dataLsns)\n const lastMaxDataLsn = this.#lastDataLsns[key]\n if (maxDataLsn > lastMaxDataLsn) {\n this.#lastDataLsns[key] = maxDataLsn\n }\n // There is new data, so we need to schedule a check for updates on\n // other shapes\n this.#scheduleCheckForUpdates()\n }\n\n // Publish the messages to the multi-shape stream subscribers\n const multiShapeMessages = messages.map(\n (message) =>\n ({\n ...message,\n shape: key,\n }) as MultiShapeMessages<TShapeRows>\n )\n await this._publish(multiShapeMessages)\n },\n (error) => this.#onError(error)\n )\n }\n this.#started = true\n }\n\n #scheduleCheckForUpdates() {\n this.#checkForUpdatesTimeout ??= setTimeout(() => {\n this.#checkForUpdates()\n this.#checkForUpdatesTimeout = undefined\n }, this.checkForUpdatesAfterMs)\n }\n\n async #checkForUpdates() {\n const maxDataLsn = bigIntMax(Object.values(this.#lastDataLsns))\n const refreshPromises = this.#shapeEntries()\n .filter(([key]) => {\n // We only need to refresh shapes that have not seen an up-to-date message\n // lower than the max lsn of the data messages we have received.\n const lastUpToDateLsn = this.#lastUpToDateLsns[key]\n return lastUpToDateLsn < maxDataLsn\n })\n .map(([_, shape]) => {\n return shape.forceDisconnectAndRefresh()\n })\n await Promise.all(refreshPromises)\n }\n\n #onError(error: Error) {\n // TODO: we probably want to disconnect all shapes here on the first error\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n await Promise.all(\n Array.from(this.#subscribers.values()).map(async ([callback, __]) => {\n try {\n await callback(messages)\n } catch (err) {\n queueMicrotask(() => {\n throw err\n })\n }\n })\n )\n }\n\n /**\n * Returns an array of the shape entries.\n * Ensures that the shape entries are typed, as `Object.entries`\n * will not type the entries correctly.\n */\n #shapeEntries() {\n return Object.entries(this.#shapes) as [\n keyof TShapeRows & string,\n ShapeStream<TShapeRows[string]>,\n ][]\n }\n\n /**\n * The ShapeStreams that are being subscribed to.\n */\n get shapes() {\n return this.#shapes\n }\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) this.#start()\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n // Min of all the lastSyncedAt values\n const shapeEntries = this.#shapeEntries()\n if (shapeEntries.length === 0) return\n return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {\n return Math.min(minLastSyncedAt, shape.lastSyncedAt() ?? Infinity)\n }, Infinity)\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n const lastSyncedAt = this.lastSyncedAt()\n if (lastSyncedAt === undefined) return Infinity\n return Date.now() - lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#shapeEntries().every(([_, shape]) => shape.isConnected())\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading(): boolean {\n return this.#shapeEntries().some(([_, shape]) => shape.isLoading())\n }\n\n get isUpToDate() {\n return this.#shapeEntries().every(([_, shape]) => shape.isUpToDate)\n }\n}\n\n/**\n * A transactional multi-shape stream is a multi-shape stream that emits the\n * messages in transactional batches, ensuring that all shapes will receive\n * at least an `up-to-date` message from Electric within the `checkForUpdatesAfterMs`\n * interval.\n * It uses the `lsn` metadata to infer transaction boundaries, and the `op_position`\n * metadata to sort the messages within a transaction.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * transactionalMultiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class TransactionalMultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> extends MultiShapeStream<TShapeRows> {\n #changeMessages = new Map<bigint, MultiShapeMessage<Row<unknown>, string>[]>()\n #completeLsns: {\n [K in keyof TShapeRows]: bigint\n }\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n super(options)\n this.#completeLsns = Object.fromEntries(\n Object.entries(options.shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n }\n\n #getLowestCompleteLsn() {\n return bigIntMin(Object.values(this.#completeLsns))\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n this.#accumulate(messages)\n const lowestCompleteLsn = this.#getLowestCompleteLsn()\n const lsnsToPublish = [...this.#changeMessages.keys()].filter(\n (lsn) => lsn <= lowestCompleteLsn\n )\n const messagesToPublish = lsnsToPublish\n .sort((a, b) => bigIntCompare(a, b))\n .map((lsn) =>\n this.#changeMessages.get(lsn)?.sort((a, b) => {\n const { headers: aHeaders } = a\n const { headers: bHeaders } = b\n if (\n typeof aHeaders.op_position !== `number` ||\n typeof bHeaders.op_position !== `number`\n ) {\n return 0 // op_position is not present on the snapshot message\n }\n return aHeaders.op_position - bHeaders.op_position\n })\n )\n .filter((messages) => messages !== undefined)\n .flat() as MultiShapeMessages<TShapeRows>[]\n lsnsToPublish.forEach((lsn) => {\n this.#changeMessages.delete(lsn)\n })\n if (messagesToPublish.length > 0) {\n await super._publish(messagesToPublish)\n }\n }\n\n #accumulate(messages: MultiShapeMessages<TShapeRows>[]) {\n const isUpToDate = this.isUpToDate\n messages.forEach((message) => {\n const { shape, headers } = message\n if (isChangeMessage(message)) {\n // The snapshot message does not have an lsn, so we use 0\n const lsn =\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n if (!this.#changeMessages.has(lsn)) {\n this.#changeMessages.set(lsn, [])\n }\n this.#changeMessages.get(lsn)?.push(message)\n if (\n isUpToDate && // All shapes must be up to date\n typeof headers.last === `boolean` &&\n headers.last === true\n ) {\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n lsn,\n ])\n }\n } else if (isControlMessage(message)) {\n if (headers.control === `up-to-date`) {\n if (typeof headers.global_last_seen_lsn !== `string`) {\n throw new Error(`global_last_seen_lsn is not a number`)\n }\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n BigInt(headers.global_last_seen_lsn),\n ])\n }\n }\n })\n }\n}\n"],"mappings":"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","names":["message","isChangeMessage","isChangeMessage","messages"]}
|
|
1
|
+
{"version":3,"sources":["../src/match.ts","../src/bigint-utils.ts","../src/multi-shape-stream.ts"],"sourcesContent":["import {\n isChangeMessage,\n type ShapeStreamInterface,\n type ChangeMessage,\n type GetExtensions,\n type Operation,\n type Row,\n type Value,\n type Message,\n} from '@electric-sql/client'\n\nexport function matchStream<T extends Row<unknown>>(\n stream: ShapeStreamInterface<T>,\n operations: Array<Operation>,\n matchFn: (message: ChangeMessage<T>) => boolean,\n timeout = 60000 // ms\n): Promise<ChangeMessage<T>> {\n return new Promise<ChangeMessage<T>>((resolve, reject) => {\n const unsubscribe: () => void = stream.subscribe(\n (messages: Array<unknown>) => {\n const message = messages\n .filter((msg): msg is ChangeMessage<T> =>\n isChangeMessage(msg as Message<Row<never>>)\n )\n .find((message) => {\n const operation: Operation = message.headers.operation\n\n return operations.includes(operation) && matchFn(message)\n })\n\n if (message) {\n return finish(message)\n }\n }\n )\n\n const timeoutId: NodeJS.Timeout = setTimeout(() => {\n const msg: string = `matchStream timed out after ${timeout}ms`\n\n console.error(msg)\n\n reject(msg)\n }, timeout)\n\n function finish(message: ChangeMessage<T>): void {\n clearTimeout(timeoutId)\n\n unsubscribe()\n\n return resolve(message)\n }\n })\n}\n\nexport function matchBy<T extends Row<unknown>>(\n column: string,\n value: Value<GetExtensions<T>>\n): (message: ChangeMessage<T>) => boolean {\n return (message: ChangeMessage<T>) => message.value[column] === value\n}\n","export function bigIntMax(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(nums: Array<bigint | number>): bigint {\n return BigInt(nums.reduce((m, e) => (e < m ? e : m)))\n}\n\nexport function bigIntCompare(a: bigint, b: bigint): 1 | -1 | 0 {\n return a > b ? 1 : a < b ? -1 : 0\n}\n","import { bigIntCompare, bigIntMax, bigIntMin } from './bigint-utils'\nimport {\n ShapeStream,\n isChangeMessage,\n isControlMessage,\n} from '@electric-sql/client'\nimport type {\n ChangeMessage,\n ControlMessage,\n FetchError,\n MaybePromise,\n Row,\n ShapeStreamOptions,\n} from '@electric-sql/client'\n\ninterface MultiShapeStreamOptions<\n TShapeRows extends {\n [K: string]: Row<unknown>\n } = {\n [K: string]: Row<unknown>\n },\n> {\n shapes: {\n [K in keyof TShapeRows]:\n | ShapeStreamOptions<TShapeRows[K]>\n | ShapeStream<TShapeRows[K]>\n }\n start?: boolean\n checkForUpdatesAfterMs?: number // milliseconds\n}\n\ninterface MultiShapeChangeMessage<\n T extends Row<unknown>,\n ShapeNames extends string,\n> extends ChangeMessage<T> {\n shape: ShapeNames\n}\n\ninterface MultiShapeControlMessage<ShapeNames extends string>\n extends ControlMessage {\n shape: ShapeNames\n}\n\ntype MultiShapeMessage<T extends Row<unknown>, ShapeNames extends string> =\n | MultiShapeChangeMessage<T, ShapeNames>\n | MultiShapeControlMessage<ShapeNames>\n\nexport type MultiShapeMessages<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> = {\n [K in keyof TShapeRows & string]: MultiShapeMessage<TShapeRows[K], K>\n}[keyof TShapeRows & string]\n\nexport interface MultiShapeStreamInterface<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> {\n shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n checkForUpdatesAfterMs?: number\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ): () => void\n unsubscribeAll(): void\n\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n isLoading(): boolean\n\n isUpToDate: boolean\n}\n\n/**\n * A multi-shape stream is a stream that can subscribe to multiple shapes.\n * It ensures that all shapes will receive at least an `up-to-date` message from\n * Electric within the `checkForUpdatesAfterMs` interval.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * multiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const multiShapeStream = new MultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class MultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> implements MultiShapeStreamInterface<TShapeRows>\n{\n #shapes: { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n #started = false\n checkForUpdatesAfterMs?: number\n\n #checkForUpdatesTimeout?: ReturnType<typeof setTimeout> | undefined\n\n // We keep track of the last lsn of data and up-to-date messages for each shape\n // so that we can skip checkForUpdates if the lsn of the up-to-date message is\n // greater than the last lsn of data.\n #lastDataLsns: { [K in keyof TShapeRows]: bigint }\n #lastUpToDateLsns: { [K in keyof TShapeRows]: bigint }\n\n readonly #subscribers = new Map<\n number,\n [\n (messages: MultiShapeMessages<TShapeRows>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n const {\n start = true, // By default we start the multi-shape stream\n checkForUpdatesAfterMs = 100, // Force a check for updates after 100ms\n shapes,\n } = options\n this.checkForUpdatesAfterMs = checkForUpdatesAfterMs\n this.#shapes = Object.fromEntries(\n Object.entries(shapes).map(([key, shape]) => [\n key,\n shape instanceof ShapeStream\n ? shape\n : new ShapeStream<TShapeRows[typeof key]>({\n ...shape,\n start: false,\n }),\n ])\n ) as { [K in keyof TShapeRows]: ShapeStream<TShapeRows[K]> }\n this.#lastDataLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n this.#lastUpToDateLsns = Object.fromEntries(\n Object.entries(shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n if (start) this.#start()\n }\n\n #start() {\n if (this.#started) throw new Error(`Cannot start multi-shape stream twice`)\n for (const [key, shape] of this.#shapeEntries()) {\n if (shape.hasStarted()) {\n // The multi-shape stream needs to be started together as a whole, and so we\n // have to check that a shape is not already started.\n throw new Error(`Shape ${key} already started`)\n }\n shape.subscribe(\n async (messages) => {\n // Whats the max lsn of the up-to-date messages?\n const upToDateLsns = messages\n .filter(isControlMessage)\n .map(({ headers }) =>\n typeof headers.global_last_seen_lsn === `string`\n ? BigInt(headers.global_last_seen_lsn)\n : BigInt(0)\n )\n if (upToDateLsns.length > 0) {\n const maxUpToDateLsn = bigIntMax(upToDateLsns)\n const lastMaxUpToDateLsn = this.#lastUpToDateLsns[key]\n if (maxUpToDateLsn > lastMaxUpToDateLsn) {\n this.#lastUpToDateLsns[key] = maxUpToDateLsn\n }\n }\n\n // Whats the max lsn of the data messages?\n const dataLsns = messages\n .filter(isChangeMessage)\n .map(({ headers }) =>\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n )\n if (dataLsns.length > 0) {\n const maxDataLsn = bigIntMax(dataLsns)\n const lastMaxDataLsn = this.#lastDataLsns[key]\n if (maxDataLsn > lastMaxDataLsn) {\n this.#lastDataLsns[key] = maxDataLsn\n }\n // There is new data, so we need to schedule a check for updates on\n // other shapes\n this.#scheduleCheckForUpdates()\n }\n\n // Publish the messages to the multi-shape stream subscribers\n const multiShapeMessages = messages.map(\n (message) =>\n ({\n ...message,\n shape: key,\n }) as MultiShapeMessages<TShapeRows>\n )\n await this._publish(multiShapeMessages)\n },\n (error) => this.#onError(error)\n )\n }\n this.#started = true\n }\n\n #scheduleCheckForUpdates() {\n this.#checkForUpdatesTimeout ??= setTimeout(() => {\n this.#checkForUpdates()\n this.#checkForUpdatesTimeout = undefined\n }, this.checkForUpdatesAfterMs)\n }\n\n async #checkForUpdates() {\n const maxDataLsn = bigIntMax(Object.values(this.#lastDataLsns))\n const refreshPromises = this.#shapeEntries()\n .filter(([key]) => {\n // We only need to refresh shapes that have not seen an up-to-date message\n // lower than the max lsn of the data messages we have received.\n const lastUpToDateLsn = this.#lastUpToDateLsns[key]\n return lastUpToDateLsn < maxDataLsn\n })\n .map(([_, shape]) => {\n return shape.forceDisconnectAndRefresh()\n })\n await Promise.all(refreshPromises)\n }\n\n #onError(error: Error) {\n // TODO: we probably want to disconnect all shapes here on the first error\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n await Promise.all(\n Array.from(this.#subscribers.values()).map(async ([callback, __]) => {\n try {\n await callback(messages)\n } catch (err) {\n queueMicrotask(() => {\n throw err\n })\n }\n })\n )\n }\n\n /**\n * Returns an array of the shape entries.\n * Ensures that the shape entries are typed, as `Object.entries`\n * will not type the entries correctly.\n */\n #shapeEntries() {\n return Object.entries(this.#shapes) as [\n keyof TShapeRows & string,\n ShapeStream<TShapeRows[string]>,\n ][]\n }\n\n /**\n * The ShapeStreams that are being subscribed to.\n */\n get shapes() {\n return this.#shapes\n }\n\n subscribe(\n callback: (\n messages: MultiShapeMessages<TShapeRows>[]\n ) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n if (!this.#started) this.#start()\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n // Min of all the lastSyncedAt values\n const shapeEntries = this.#shapeEntries()\n if (shapeEntries.length === 0) return\n return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {\n return Math.min(minLastSyncedAt, shape.lastSyncedAt() ?? Infinity)\n }, Infinity)\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n const lastSyncedAt = this.lastSyncedAt()\n if (lastSyncedAt === undefined) return Infinity\n return Date.now() - lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#shapeEntries().every(([_, shape]) => shape.isConnected())\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading(): boolean {\n return this.#shapeEntries().some(([_, shape]) => shape.isLoading())\n }\n\n get isUpToDate() {\n return this.#shapeEntries().every(([_, shape]) => shape.isUpToDate)\n }\n}\n\n/**\n * A transactional multi-shape stream is a multi-shape stream that emits the\n * messages in transactional batches, ensuring that all shapes will receive\n * at least an `up-to-date` message from Electric within the `checkForUpdatesAfterMs`\n * interval.\n * It uses the `lsn` metadata to infer transaction boundaries, and the `op_position`\n * metadata to sort the messages within a transaction.\n *\n * @constructor\n * @param {MultiShapeStreamOptions} options - configure the multi-shape stream\n * @example\n * ```ts\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: {\n * url: 'http://localhost:3000/v1/shape1',\n * },\n * shape2: {\n * url: 'http://localhost:3000/v1/shape2',\n * },\n * },\n * })\n *\n * transactionalMultiShapeStream.subscribe((msgs) => {\n * console.log(msgs)\n * })\n *\n * // or with ShapeStream instances\n * const transactionalMultiShapeStream = new TransactionalMultiShapeStream({\n * shapes: {\n * shape1: new ShapeStream({ url: 'http://localhost:3000/v1/shape1' }),\n * shape2: new ShapeStream({ url: 'http://localhost:3000/v1/shape2' }),\n * },\n * })\n * ```\n */\n\nexport class TransactionalMultiShapeStream<\n TShapeRows extends {\n [K: string]: Row<unknown>\n },\n> extends MultiShapeStream<TShapeRows> {\n #changeMessages = new Map<bigint, MultiShapeMessage<Row<unknown>, string>[]>()\n #completeLsns: {\n [K in keyof TShapeRows]: bigint\n }\n\n constructor(options: MultiShapeStreamOptions<TShapeRows>) {\n super(options)\n this.#completeLsns = Object.fromEntries(\n Object.entries(options.shapes).map(([key]) => [key, BigInt(-1)])\n ) as { [K in keyof TShapeRows]: bigint }\n }\n\n #getLowestCompleteLsn() {\n return bigIntMin(Object.values(this.#completeLsns))\n }\n\n protected async _publish(\n messages: MultiShapeMessages<TShapeRows>[]\n ): Promise<void> {\n this.#accumulate(messages)\n const lowestCompleteLsn = this.#getLowestCompleteLsn()\n const lsnsToPublish = [...this.#changeMessages.keys()].filter(\n (lsn) => lsn <= lowestCompleteLsn\n )\n const messagesToPublish = lsnsToPublish\n .sort((a, b) => bigIntCompare(a, b))\n .map((lsn) =>\n this.#changeMessages.get(lsn)?.sort((a, b) => {\n const { headers: aHeaders } = a\n const { headers: bHeaders } = b\n if (\n typeof aHeaders.op_position !== `number` ||\n typeof bHeaders.op_position !== `number`\n ) {\n return 0 // op_position is not present on the snapshot message\n }\n return aHeaders.op_position - bHeaders.op_position\n })\n )\n .filter((messages) => messages !== undefined)\n .flat() as MultiShapeMessages<TShapeRows>[]\n lsnsToPublish.forEach((lsn) => {\n this.#changeMessages.delete(lsn)\n })\n if (messagesToPublish.length > 0) {\n await super._publish(messagesToPublish)\n }\n }\n\n #accumulate(messages: MultiShapeMessages<TShapeRows>[]) {\n const isUpToDate = this.isUpToDate\n messages.forEach((message) => {\n const { shape, headers } = message\n if (isChangeMessage(message)) {\n // The snapshot message does not have an lsn, so we use 0\n const lsn =\n typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)\n if (!this.#changeMessages.has(lsn)) {\n this.#changeMessages.set(lsn, [])\n }\n this.#changeMessages.get(lsn)?.push(message)\n if (\n isUpToDate && // All shapes must be up to date\n typeof headers.last === `boolean` &&\n headers.last === true\n ) {\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n lsn,\n ])\n }\n } else if (isControlMessage(message)) {\n if (headers.control === `up-to-date`) {\n if (typeof headers.global_last_seen_lsn !== `string`) {\n throw new Error(`global_last_seen_lsn is not a number`)\n }\n this.#completeLsns[shape] = bigIntMax([\n this.#completeLsns[shape],\n BigInt(headers.global_last_seen_lsn),\n ])\n }\n }\n })\n }\n}\n"],"mappings":"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","names":["message","isChangeMessage","isChangeMessage","messages"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-sql/experimental",
|
|
3
3
|
"description": "Experimental TypeScript features for ElectricSQL.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"author": "ElectricSQL team and contributors.",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/electric-sql/electric/issues"
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"typescript": "^5.5.2",
|
|
25
25
|
"uuid": "^10.0.0",
|
|
26
26
|
"vitest": "^4.0.15",
|
|
27
|
-
"@electric-sql/client": "1.
|
|
27
|
+
"@electric-sql/client": "1.3.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@electric-sql/client": "1.
|
|
30
|
+
"@electric-sql/client": "1.3.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependenciesMeta": {
|
|
33
33
|
"@electric-sql/client": {
|