@electric-sql/experimental 1.0.0 → 1.0.2

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.
@@ -109,11 +109,11 @@ function matchBy(column, value) {
109
109
  }
110
110
 
111
111
  // src/bigint-utils.ts
112
- function bigIntMax(...args) {
113
- return BigInt(args.reduce((m, e) => e > m ? e : m));
112
+ function bigIntMax(nums) {
113
+ return BigInt(nums.reduce((m, e) => e > m ? e : m));
114
114
  }
115
- function bigIntMin(...args) {
116
- return BigInt(args.reduce((m, e) => e < m ? e : m));
115
+ function bigIntMin(nums) {
116
+ return BigInt(nums.reduce((m, e) => e < m ? e : m));
117
117
  }
118
118
  function bigIntCompare(a, b) {
119
119
  return a > b ? 1 : a < b ? -1 : 0;
@@ -192,14 +192,12 @@ var MultiShapeStream = class {
192
192
  }
193
193
  /** Unix time at which we last synced. Undefined when `isLoading` is true. */
194
194
  lastSyncedAt() {
195
- return Math.min(
196
- ...__privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this).map(
197
- ([_, shape]) => {
198
- var _a;
199
- return (_a = shape.lastSyncedAt()) != null ? _a : Infinity;
200
- }
201
- )
202
- );
195
+ const shapeEntries = __privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this);
196
+ if (shapeEntries.length === 0) return;
197
+ return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {
198
+ var _a;
199
+ return Math.min(minLastSyncedAt, (_a = shape.lastSyncedAt()) != null ? _a : Infinity);
200
+ }, Infinity);
203
201
  }
204
202
  /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
205
203
  lastSynced() {
@@ -238,7 +236,7 @@ start_fn = function() {
238
236
  ({ headers }) => typeof headers.global_last_seen_lsn === `string` ? BigInt(headers.global_last_seen_lsn) : BigInt(0)
239
237
  );
240
238
  if (upToDateLsns.length > 0) {
241
- const maxUpToDateLsn = bigIntMax(...upToDateLsns);
239
+ const maxUpToDateLsn = bigIntMax(upToDateLsns);
242
240
  const lastMaxUpToDateLsn = __privateGet(this, _lastUpToDateLsns)[key];
243
241
  if (maxUpToDateLsn > lastMaxUpToDateLsn) {
244
242
  __privateGet(this, _lastUpToDateLsns)[key] = maxUpToDateLsn;
@@ -248,7 +246,7 @@ start_fn = function() {
248
246
  ({ headers }) => typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)
249
247
  );
250
248
  if (dataLsns.length > 0) {
251
- const maxDataLsn = bigIntMax(...dataLsns);
249
+ const maxDataLsn = bigIntMax(dataLsns);
252
250
  const lastMaxDataLsn = __privateGet(this, _lastDataLsns)[key];
253
251
  if (maxDataLsn > lastMaxDataLsn) {
254
252
  __privateGet(this, _lastDataLsns)[key] = maxDataLsn;
@@ -276,7 +274,7 @@ scheduleCheckForUpdates_fn = function() {
276
274
  };
277
275
  checkForUpdates_fn = function() {
278
276
  return __async(this, null, function* () {
279
- const maxDataLsn = bigIntMax(...Object.values(__privateGet(this, _lastDataLsns)));
277
+ const maxDataLsn = bigIntMax(Object.values(__privateGet(this, _lastDataLsns)));
280
278
  const refreshPromises = __privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this).filter(([key]) => {
281
279
  const lastUpToDateLsn = __privateGet(this, _lastUpToDateLsns)[key];
282
280
  return lastUpToDateLsn < maxDataLsn;
@@ -343,7 +341,7 @@ _changeMessages = new WeakMap();
343
341
  _completeLsns = new WeakMap();
344
342
  _TransactionalMultiShapeStream_instances = new WeakSet();
345
343
  getLowestCompleteLsn_fn = function() {
346
- return bigIntMin(...Object.values(__privateGet(this, _completeLsns)));
344
+ return bigIntMin(Object.values(__privateGet(this, _completeLsns)));
347
345
  };
348
346
  accumulate_fn = function(messages) {
349
347
  const isUpToDate = this.isUpToDate;
@@ -358,17 +356,20 @@ accumulate_fn = function(messages) {
358
356
  (_a = __privateGet(this, _changeMessages).get(lsn)) == null ? void 0 : _a.push(message);
359
357
  if (isUpToDate && // All shapes must be up to date
360
358
  typeof headers.last === `boolean` && headers.last === true) {
361
- __privateGet(this, _completeLsns)[shape] = bigIntMax(__privateGet(this, _completeLsns)[shape], lsn);
359
+ __privateGet(this, _completeLsns)[shape] = bigIntMax([
360
+ __privateGet(this, _completeLsns)[shape],
361
+ lsn
362
+ ]);
362
363
  }
363
364
  } else if ((0, import_client2.isControlMessage)(message)) {
364
365
  if (headers.control === `up-to-date`) {
365
366
  if (typeof headers.global_last_seen_lsn !== `string`) {
366
367
  throw new Error(`global_last_seen_lsn is not a number`);
367
368
  }
368
- __privateGet(this, _completeLsns)[shape] = bigIntMax(
369
+ __privateGet(this, _completeLsns)[shape] = bigIntMax([
369
370
  __privateGet(this, _completeLsns)[shape],
370
371
  BigInt(headers.global_last_seen_lsn)
371
- );
372
+ ]);
372
373
  }
373
374
  }
374
375
  });
@@ -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(...args: Array<bigint | number>): bigint {\n return BigInt(args.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(...args: Array<bigint | number>): bigint {\n return BigInt(args.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 return Math.min(\n ...this.#shapeEntries().map(\n ([_, shape]) => shape.lastSyncedAt() ?? Infinity\n )\n )\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(this.#completeLsns[shape], lsn)\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":"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","names":["message","import_client","messages"]}
@@ -1,2 +1,2 @@
1
- var X=Object.defineProperty,Y=Object.defineProperties;var Z=Object.getOwnPropertyDescriptors;var L=Object.getOwnPropertySymbols,ee=Object.getPrototypeOf,se=Object.prototype.hasOwnProperty,te=Object.prototype.propertyIsEnumerable,ne=Reflect.get;var B=t=>{throw TypeError(t)};var D=(t,e,s)=>e in t?X(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s,K=(t,e)=>{for(var s in e||(e={}))se.call(e,s)&&D(t,s,e[s]);if(L)for(var s of L(e))te.call(e,s)&&D(t,s,e[s]);return t},E=(t,e)=>Y(t,Z(e));var v=(t,e,s)=>e.has(t)||B("Cannot "+s);var a=(t,e,s)=>(v(t,e,"read from private field"),s?s.call(t):e.get(t)),g=(t,e,s)=>e.has(t)?B("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,s),S=(t,e,s,n)=>(v(t,e,"write to private field"),n?n.call(t,s):e.set(t,s),s),u=(t,e,s)=>(v(t,e,"access private method"),s);var P=(t,e,s)=>ne(ee(t),s,e);var y=(t,e,s)=>new Promise((n,r)=>{var h=p=>{try{i(s.next(p))}catch(l){r(l)}},o=p=>{try{i(s.throw(p))}catch(l){r(l)}},i=p=>p.done?n(p.value):Promise.resolve(p.value).then(h,o);i((s=s.apply(t,e)).next())});import{isChangeMessage as ae}from"@electric-sql/client";function re(t,e,s,n=6e4){return new Promise((r,h)=>{let o=t.subscribe(l=>{let C=l.filter(b=>ae(b)).find(b=>{let W=b.headers.operation;return e.includes(W)&&s(b)});if(C)return p(C)}),i=setTimeout(()=>{let l=`matchStream timed out after ${n}ms`;console.error(l),h(l)},n);function p(l){return clearTimeout(i),o(),r(l)}})}function he(t,e){return s=>s.value[t]===e}function R(...t){return BigInt(t.reduce((e,s)=>s>e?s:e))}function j(...t){return BigInt(t.reduce((e,s)=>s<e?s:e))}function F(t,e){return t>e?1:t<e?-1:0}import{ShapeStream as N,isChangeMessage as G,isControlMessage as H}from"@electric-sql/client";var k,x,I,M,T,d,c,O,V,$,J,w,A=class{constructor(e){g(this,c);g(this,k);g(this,x,!1);g(this,I);g(this,M);g(this,T);g(this,d,new Map);let{start:s=!0,checkForUpdatesAfterMs:n=100,shapes:r}=e;this.checkForUpdatesAfterMs=n,S(this,k,Object.fromEntries(Object.entries(r).map(([h,o])=>[h,o instanceof N?o:new N(E(K({},o),{start:!1}))]))),S(this,M,Object.fromEntries(Object.entries(r).map(([h])=>[h,BigInt(-1)]))),S(this,T,Object.fromEntries(Object.entries(r).map(([h])=>[h,BigInt(-1)]))),s&&u(this,c,O).call(this)}_publish(e){return y(this,null,function*(){yield Promise.all(Array.from(a(this,d).values()).map(r=>y(this,[r],function*([s,n]){try{yield s(e)}catch(h){queueMicrotask(()=>{throw h})}})))})}get shapes(){return a(this,k)}subscribe(e,s){let n=Math.random();return a(this,d).set(n,[e,s]),a(this,x)||u(this,c,O).call(this),()=>{a(this,d).delete(n)}}unsubscribeAll(){a(this,d).clear()}lastSyncedAt(){return Math.min(...u(this,c,w).call(this).map(([e,s])=>{var n;return(n=s.lastSyncedAt())!=null?n:1/0}))}lastSynced(){let e=this.lastSyncedAt();return e===void 0?1/0:Date.now()-e}isConnected(){return u(this,c,w).call(this).every(([e,s])=>s.isConnected())}isLoading(){return u(this,c,w).call(this).some(([e,s])=>s.isLoading())}get isUpToDate(){return u(this,c,w).call(this).every(([e,s])=>s.isUpToDate)}};k=new WeakMap,x=new WeakMap,I=new WeakMap,M=new WeakMap,T=new WeakMap,d=new WeakMap,c=new WeakSet,O=function(){if(a(this,x))throw new Error("Cannot start multi-shape stream twice");for(let[e,s]of u(this,c,w).call(this)){if(s.hasStarted())throw new Error(`Shape ${e} already started`);s.subscribe(n=>y(this,null,function*(){let r=n.filter(H).map(({headers:i})=>typeof i.global_last_seen_lsn=="string"?BigInt(i.global_last_seen_lsn):BigInt(0));if(r.length>0){let i=R(...r),p=a(this,T)[e];i>p&&(a(this,T)[e]=i)}let h=n.filter(G).map(({headers:i})=>typeof i.lsn=="string"?BigInt(i.lsn):BigInt(0));if(h.length>0){let i=R(...h),p=a(this,M)[e];i>p&&(a(this,M)[e]=i),u(this,c,V).call(this)}let o=n.map(i=>E(K({},i),{shape:e}));yield this._publish(o)}),n=>u(this,c,J).call(this,n))}S(this,x,!0)},V=function(){var e;(e=a(this,I))!=null||S(this,I,setTimeout(()=>{u(this,c,$).call(this),S(this,I,void 0)},this.checkForUpdatesAfterMs))},$=function(){return y(this,null,function*(){let e=R(...Object.values(a(this,M))),s=u(this,c,w).call(this).filter(([n])=>a(this,T)[n]<e).map(([n,r])=>r.forceDisconnectAndRefresh());yield Promise.all(s)})},J=function(e){a(this,d).forEach(([s,n])=>{n==null||n(e)})},w=function(){return Object.entries(a(this,k))};var f,m,_,z,Q,U=class U extends A{constructor(s){super(s);g(this,_);g(this,f,new Map);g(this,m);S(this,m,Object.fromEntries(Object.entries(s.shapes).map(([n])=>[n,BigInt(-1)])))}_publish(s){return y(this,null,function*(){u(this,_,Q).call(this,s);let n=u(this,_,z).call(this),r=[...a(this,f).keys()].filter(o=>o<=n),h=r.sort((o,i)=>F(o,i)).map(o=>{var i;return(i=a(this,f).get(o))==null?void 0:i.sort((p,l)=>{let{headers:C}=p,{headers:b}=l;return typeof C.op_position!="number"||typeof b.op_position!="number"?0:C.op_position-b.op_position})}).filter(o=>o!==void 0).flat();r.forEach(o=>{a(this,f).delete(o)}),h.length>0&&(yield P(U.prototype,this,"_publish").call(this,h))})}};f=new WeakMap,m=new WeakMap,_=new WeakSet,z=function(){return j(...Object.values(a(this,m)))},Q=function(s){let n=this.isUpToDate;s.forEach(r=>{var i;let{shape:h,headers:o}=r;if(G(r)){let p=typeof o.lsn=="string"?BigInt(o.lsn):BigInt(0);a(this,f).has(p)||a(this,f).set(p,[]),(i=a(this,f).get(p))==null||i.push(r),n&&typeof o.last=="boolean"&&o.last===!0&&(a(this,m)[h]=R(a(this,m)[h],p))}else if(H(r)&&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,m)[h]=R(a(this,m)[h],BigInt(o.global_last_seen_lsn))}})};var q=U;export{A as MultiShapeStream,q as TransactionalMultiShapeStream,he as matchBy,re as matchStream};
1
+ var X=Object.defineProperty,Y=Object.defineProperties;var Z=Object.getOwnPropertyDescriptors;var L=Object.getOwnPropertySymbols,ee=Object.getPrototypeOf,se=Object.prototype.hasOwnProperty,te=Object.prototype.propertyIsEnumerable,ne=Reflect.get;var B=t=>{throw TypeError(t)};var D=(t,e,s)=>e in t?X(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s,K=(t,e)=>{for(var s in e||(e={}))se.call(e,s)&&D(t,s,e[s]);if(L)for(var s of L(e))te.call(e,s)&&D(t,s,e[s]);return t},E=(t,e)=>Y(t,Z(e));var v=(t,e,s)=>e.has(t)||B("Cannot "+s);var a=(t,e,s)=>(v(t,e,"read from private field"),s?s.call(t):e.get(t)),g=(t,e,s)=>e.has(t)?B("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,s),S=(t,e,s,n)=>(v(t,e,"write to private field"),n?n.call(t,s):e.set(t,s),s),u=(t,e,s)=>(v(t,e,"access private method"),s);var P=(t,e,s)=>ne(ee(t),s,e);var y=(t,e,s)=>new Promise((n,h)=>{var r=p=>{try{i(s.next(p))}catch(l){h(l)}},o=p=>{try{i(s.throw(p))}catch(l){h(l)}},i=p=>p.done?n(p.value):Promise.resolve(p.value).then(r,o);i((s=s.apply(t,e)).next())});import{isChangeMessage as ae}from"@electric-sql/client";function re(t,e,s,n=6e4){return new Promise((h,r)=>{let o=t.subscribe(l=>{let C=l.filter(b=>ae(b)).find(b=>{let W=b.headers.operation;return e.includes(W)&&s(b)});if(C)return p(C)}),i=setTimeout(()=>{let l=`matchStream timed out after ${n}ms`;console.error(l),r(l)},n);function p(l){return clearTimeout(i),o(),h(l)}})}function he(t,e){return s=>s.value[t]===e}function R(t){return BigInt(t.reduce((e,s)=>s>e?s:e))}function j(t){return BigInt(t.reduce((e,s)=>s<e?s:e))}function F(t,e){return t>e?1:t<e?-1:0}import{ShapeStream as N,isChangeMessage as G,isControlMessage as H}from"@electric-sql/client";var k,x,I,M,T,d,c,O,V,$,J,w,A=class{constructor(e){g(this,c);g(this,k);g(this,x,!1);g(this,I);g(this,M);g(this,T);g(this,d,new Map);let{start:s=!0,checkForUpdatesAfterMs:n=100,shapes:h}=e;this.checkForUpdatesAfterMs=n,S(this,k,Object.fromEntries(Object.entries(h).map(([r,o])=>[r,o instanceof N?o:new N(E(K({},o),{start:!1}))]))),S(this,M,Object.fromEntries(Object.entries(h).map(([r])=>[r,BigInt(-1)]))),S(this,T,Object.fromEntries(Object.entries(h).map(([r])=>[r,BigInt(-1)]))),s&&u(this,c,O).call(this)}_publish(e){return y(this,null,function*(){yield Promise.all(Array.from(a(this,d).values()).map(h=>y(this,[h],function*([s,n]){try{yield s(e)}catch(r){queueMicrotask(()=>{throw r})}})))})}get shapes(){return a(this,k)}subscribe(e,s){let n=Math.random();return a(this,d).set(n,[e,s]),a(this,x)||u(this,c,O).call(this),()=>{a(this,d).delete(n)}}unsubscribeAll(){a(this,d).clear()}lastSyncedAt(){let e=u(this,c,w).call(this);if(e.length!==0)return e.reduce((s,[n,h])=>{var r;return Math.min(s,(r=h.lastSyncedAt())!=null?r:1/0)},1/0)}lastSynced(){let e=this.lastSyncedAt();return e===void 0?1/0:Date.now()-e}isConnected(){return u(this,c,w).call(this).every(([e,s])=>s.isConnected())}isLoading(){return u(this,c,w).call(this).some(([e,s])=>s.isLoading())}get isUpToDate(){return u(this,c,w).call(this).every(([e,s])=>s.isUpToDate)}};k=new WeakMap,x=new WeakMap,I=new WeakMap,M=new WeakMap,T=new WeakMap,d=new WeakMap,c=new WeakSet,O=function(){if(a(this,x))throw new Error("Cannot start multi-shape stream twice");for(let[e,s]of u(this,c,w).call(this)){if(s.hasStarted())throw new Error(`Shape ${e} already started`);s.subscribe(n=>y(this,null,function*(){let h=n.filter(H).map(({headers:i})=>typeof i.global_last_seen_lsn=="string"?BigInt(i.global_last_seen_lsn):BigInt(0));if(h.length>0){let i=R(h),p=a(this,T)[e];i>p&&(a(this,T)[e]=i)}let r=n.filter(G).map(({headers:i})=>typeof i.lsn=="string"?BigInt(i.lsn):BigInt(0));if(r.length>0){let i=R(r),p=a(this,M)[e];i>p&&(a(this,M)[e]=i),u(this,c,V).call(this)}let o=n.map(i=>E(K({},i),{shape:e}));yield this._publish(o)}),n=>u(this,c,J).call(this,n))}S(this,x,!0)},V=function(){var e;(e=a(this,I))!=null||S(this,I,setTimeout(()=>{u(this,c,$).call(this),S(this,I,void 0)},this.checkForUpdatesAfterMs))},$=function(){return y(this,null,function*(){let e=R(Object.values(a(this,M))),s=u(this,c,w).call(this).filter(([n])=>a(this,T)[n]<e).map(([n,h])=>h.forceDisconnectAndRefresh());yield Promise.all(s)})},J=function(e){a(this,d).forEach(([s,n])=>{n==null||n(e)})},w=function(){return Object.entries(a(this,k))};var f,m,_,z,Q,U=class U extends A{constructor(s){super(s);g(this,_);g(this,f,new Map);g(this,m);S(this,m,Object.fromEntries(Object.entries(s.shapes).map(([n])=>[n,BigInt(-1)])))}_publish(s){return y(this,null,function*(){u(this,_,Q).call(this,s);let n=u(this,_,z).call(this),h=[...a(this,f).keys()].filter(o=>o<=n),r=h.sort((o,i)=>F(o,i)).map(o=>{var i;return(i=a(this,f).get(o))==null?void 0:i.sort((p,l)=>{let{headers:C}=p,{headers:b}=l;return typeof C.op_position!="number"||typeof b.op_position!="number"?0:C.op_position-b.op_position})}).filter(o=>o!==void 0).flat();h.forEach(o=>{a(this,f).delete(o)}),r.length>0&&(yield P(U.prototype,this,"_publish").call(this,r))})}};f=new WeakMap,m=new WeakMap,_=new WeakSet,z=function(){return j(Object.values(a(this,m)))},Q=function(s){let n=this.isUpToDate;s.forEach(h=>{var i;let{shape:r,headers:o}=h;if(G(h)){let p=typeof o.lsn=="string"?BigInt(o.lsn):BigInt(0);a(this,f).has(p)||a(this,f).set(p,[]),(i=a(this,f).get(p))==null||i.push(h),n&&typeof o.last=="boolean"&&o.last===!0&&(a(this,m)[r]=R([a(this,m)[r],p]))}else if(H(h)&&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,m)[r]=R([a(this,m)[r],BigInt(o.global_last_seen_lsn)])}})};var q=U;export{A as MultiShapeStream,q as TransactionalMultiShapeStream,he as matchBy,re 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(...args: Array<bigint | number>): bigint {\n return BigInt(args.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(...args: Array<bigint | number>): bigint {\n return BigInt(args.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 return Math.min(\n ...this.#shapeEntries().map(\n ([_, shape]) => shape.lastSyncedAt() ?? Infinity\n )\n )\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(this.#completeLsns[shape], lsn)\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","args","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","_","_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","__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"]}
@@ -62,11 +62,11 @@ function matchBy(column, value) {
62
62
  }
63
63
 
64
64
  // src/bigint-utils.ts
65
- function bigIntMax(...args) {
66
- return BigInt(args.reduce((m, e) => e > m ? e : m));
65
+ function bigIntMax(nums) {
66
+ return BigInt(nums.reduce((m, e) => e > m ? e : m));
67
67
  }
68
- function bigIntMin(...args) {
69
- return BigInt(args.reduce((m, e) => e < m ? e : m));
68
+ function bigIntMin(nums) {
69
+ return BigInt(nums.reduce((m, e) => e < m ? e : m));
70
70
  }
71
71
  function bigIntCompare(a, b) {
72
72
  return a > b ? 1 : a < b ? -1 : 0;
@@ -147,14 +147,12 @@ var MultiShapeStream = class {
147
147
  }
148
148
  /** Unix time at which we last synced. Undefined when `isLoading` is true. */
149
149
  lastSyncedAt() {
150
- return Math.min(
151
- ...__privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this).map(
152
- ([_, shape]) => {
153
- var _a;
154
- return (_a = shape.lastSyncedAt()) != null ? _a : Infinity;
155
- }
156
- )
157
- );
150
+ const shapeEntries = __privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this);
151
+ if (shapeEntries.length === 0) return;
152
+ return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {
153
+ var _a;
154
+ return Math.min(minLastSyncedAt, (_a = shape.lastSyncedAt()) != null ? _a : Infinity);
155
+ }, Infinity);
158
156
  }
159
157
  /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
160
158
  lastSynced() {
@@ -193,7 +191,7 @@ start_fn = function() {
193
191
  ({ headers }) => typeof headers.global_last_seen_lsn === `string` ? BigInt(headers.global_last_seen_lsn) : BigInt(0)
194
192
  );
195
193
  if (upToDateLsns.length > 0) {
196
- const maxUpToDateLsn = bigIntMax(...upToDateLsns);
194
+ const maxUpToDateLsn = bigIntMax(upToDateLsns);
197
195
  const lastMaxUpToDateLsn = __privateGet(this, _lastUpToDateLsns)[key];
198
196
  if (maxUpToDateLsn > lastMaxUpToDateLsn) {
199
197
  __privateGet(this, _lastUpToDateLsns)[key] = maxUpToDateLsn;
@@ -203,7 +201,7 @@ start_fn = function() {
203
201
  ({ headers }) => typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)
204
202
  );
205
203
  if (dataLsns.length > 0) {
206
- const maxDataLsn = bigIntMax(...dataLsns);
204
+ const maxDataLsn = bigIntMax(dataLsns);
207
205
  const lastMaxDataLsn = __privateGet(this, _lastDataLsns)[key];
208
206
  if (maxDataLsn > lastMaxDataLsn) {
209
207
  __privateGet(this, _lastDataLsns)[key] = maxDataLsn;
@@ -230,7 +228,7 @@ scheduleCheckForUpdates_fn = function() {
230
228
  }, this.checkForUpdatesAfterMs));
231
229
  };
232
230
  checkForUpdates_fn = async function() {
233
- const maxDataLsn = bigIntMax(...Object.values(__privateGet(this, _lastDataLsns)));
231
+ const maxDataLsn = bigIntMax(Object.values(__privateGet(this, _lastDataLsns)));
234
232
  const refreshPromises = __privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this).filter(([key]) => {
235
233
  const lastUpToDateLsn = __privateGet(this, _lastUpToDateLsns)[key];
236
234
  return lastUpToDateLsn < maxDataLsn;
@@ -294,7 +292,7 @@ _changeMessages = new WeakMap();
294
292
  _completeLsns = new WeakMap();
295
293
  _TransactionalMultiShapeStream_instances = new WeakSet();
296
294
  getLowestCompleteLsn_fn = function() {
297
- return bigIntMin(...Object.values(__privateGet(this, _completeLsns)));
295
+ return bigIntMin(Object.values(__privateGet(this, _completeLsns)));
298
296
  };
299
297
  accumulate_fn = function(messages) {
300
298
  const isUpToDate = this.isUpToDate;
@@ -309,17 +307,20 @@ accumulate_fn = function(messages) {
309
307
  (_a = __privateGet(this, _changeMessages).get(lsn)) == null ? void 0 : _a.push(message);
310
308
  if (isUpToDate && // All shapes must be up to date
311
309
  typeof headers.last === `boolean` && headers.last === true) {
312
- __privateGet(this, _completeLsns)[shape] = bigIntMax(__privateGet(this, _completeLsns)[shape], lsn);
310
+ __privateGet(this, _completeLsns)[shape] = bigIntMax([
311
+ __privateGet(this, _completeLsns)[shape],
312
+ lsn
313
+ ]);
313
314
  }
314
315
  } else if (isControlMessage(message)) {
315
316
  if (headers.control === `up-to-date`) {
316
317
  if (typeof headers.global_last_seen_lsn !== `string`) {
317
318
  throw new Error(`global_last_seen_lsn is not a number`);
318
319
  }
319
- __privateGet(this, _completeLsns)[shape] = bigIntMax(
320
+ __privateGet(this, _completeLsns)[shape] = bigIntMax([
320
321
  __privateGet(this, _completeLsns)[shape],
321
322
  BigInt(headers.global_last_seen_lsn)
322
- );
323
+ ]);
323
324
  }
324
325
  }
325
326
  });
@@ -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(...args: Array<bigint | number>): bigint {\n return BigInt(args.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(...args: Array<bigint | number>): bigint {\n return BigInt(args.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 return Math.min(\n ...this.#shapeEntries().map(\n ([_, shape]) => shape.lastSyncedAt() ?? Infinity\n )\n )\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(this.#completeLsns[shape], lsn)\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/dist/index.mjs CHANGED
@@ -85,11 +85,11 @@ function matchBy(column, value) {
85
85
  }
86
86
 
87
87
  // src/bigint-utils.ts
88
- function bigIntMax(...args) {
89
- return BigInt(args.reduce((m, e) => e > m ? e : m));
88
+ function bigIntMax(nums) {
89
+ return BigInt(nums.reduce((m, e) => e > m ? e : m));
90
90
  }
91
- function bigIntMin(...args) {
92
- return BigInt(args.reduce((m, e) => e < m ? e : m));
91
+ function bigIntMin(nums) {
92
+ return BigInt(nums.reduce((m, e) => e < m ? e : m));
93
93
  }
94
94
  function bigIntCompare(a, b) {
95
95
  return a > b ? 1 : a < b ? -1 : 0;
@@ -172,14 +172,12 @@ var MultiShapeStream = class {
172
172
  }
173
173
  /** Unix time at which we last synced. Undefined when `isLoading` is true. */
174
174
  lastSyncedAt() {
175
- return Math.min(
176
- ...__privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this).map(
177
- ([_, shape]) => {
178
- var _a;
179
- return (_a = shape.lastSyncedAt()) != null ? _a : Infinity;
180
- }
181
- )
182
- );
175
+ const shapeEntries = __privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this);
176
+ if (shapeEntries.length === 0) return;
177
+ return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {
178
+ var _a;
179
+ return Math.min(minLastSyncedAt, (_a = shape.lastSyncedAt()) != null ? _a : Infinity);
180
+ }, Infinity);
183
181
  }
184
182
  /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
185
183
  lastSynced() {
@@ -218,7 +216,7 @@ start_fn = function() {
218
216
  ({ headers }) => typeof headers.global_last_seen_lsn === `string` ? BigInt(headers.global_last_seen_lsn) : BigInt(0)
219
217
  );
220
218
  if (upToDateLsns.length > 0) {
221
- const maxUpToDateLsn = bigIntMax(...upToDateLsns);
219
+ const maxUpToDateLsn = bigIntMax(upToDateLsns);
222
220
  const lastMaxUpToDateLsn = __privateGet(this, _lastUpToDateLsns)[key];
223
221
  if (maxUpToDateLsn > lastMaxUpToDateLsn) {
224
222
  __privateGet(this, _lastUpToDateLsns)[key] = maxUpToDateLsn;
@@ -228,7 +226,7 @@ start_fn = function() {
228
226
  ({ headers }) => typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)
229
227
  );
230
228
  if (dataLsns.length > 0) {
231
- const maxDataLsn = bigIntMax(...dataLsns);
229
+ const maxDataLsn = bigIntMax(dataLsns);
232
230
  const lastMaxDataLsn = __privateGet(this, _lastDataLsns)[key];
233
231
  if (maxDataLsn > lastMaxDataLsn) {
234
232
  __privateGet(this, _lastDataLsns)[key] = maxDataLsn;
@@ -256,7 +254,7 @@ scheduleCheckForUpdates_fn = function() {
256
254
  };
257
255
  checkForUpdates_fn = function() {
258
256
  return __async(this, null, function* () {
259
- const maxDataLsn = bigIntMax(...Object.values(__privateGet(this, _lastDataLsns)));
257
+ const maxDataLsn = bigIntMax(Object.values(__privateGet(this, _lastDataLsns)));
260
258
  const refreshPromises = __privateMethod(this, _MultiShapeStream_instances, shapeEntries_fn).call(this).filter(([key]) => {
261
259
  const lastUpToDateLsn = __privateGet(this, _lastUpToDateLsns)[key];
262
260
  return lastUpToDateLsn < maxDataLsn;
@@ -323,7 +321,7 @@ _changeMessages = new WeakMap();
323
321
  _completeLsns = new WeakMap();
324
322
  _TransactionalMultiShapeStream_instances = new WeakSet();
325
323
  getLowestCompleteLsn_fn = function() {
326
- return bigIntMin(...Object.values(__privateGet(this, _completeLsns)));
324
+ return bigIntMin(Object.values(__privateGet(this, _completeLsns)));
327
325
  };
328
326
  accumulate_fn = function(messages) {
329
327
  const isUpToDate = this.isUpToDate;
@@ -338,17 +336,20 @@ accumulate_fn = function(messages) {
338
336
  (_a = __privateGet(this, _changeMessages).get(lsn)) == null ? void 0 : _a.push(message);
339
337
  if (isUpToDate && // All shapes must be up to date
340
338
  typeof headers.last === `boolean` && headers.last === true) {
341
- __privateGet(this, _completeLsns)[shape] = bigIntMax(__privateGet(this, _completeLsns)[shape], lsn);
339
+ __privateGet(this, _completeLsns)[shape] = bigIntMax([
340
+ __privateGet(this, _completeLsns)[shape],
341
+ lsn
342
+ ]);
342
343
  }
343
344
  } else if (isControlMessage(message)) {
344
345
  if (headers.control === `up-to-date`) {
345
346
  if (typeof headers.global_last_seen_lsn !== `string`) {
346
347
  throw new Error(`global_last_seen_lsn is not a number`);
347
348
  }
348
- __privateGet(this, _completeLsns)[shape] = bigIntMax(
349
+ __privateGet(this, _completeLsns)[shape] = bigIntMax([
349
350
  __privateGet(this, _completeLsns)[shape],
350
351
  BigInt(headers.global_last_seen_lsn)
351
- );
352
+ ]);
352
353
  }
353
354
  }
354
355
  });
@@ -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(...args: Array<bigint | number>): bigint {\n return BigInt(args.reduce((m, e) => (e > m ? e : m)))\n}\n\nexport function bigIntMin(...args: Array<bigint | number>): bigint {\n return BigInt(args.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 return Math.min(\n ...this.#shapeEntries().map(\n ([_, shape]) => shape.lastSyncedAt() ?? Infinity\n )\n )\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(this.#completeLsns[shape], lsn)\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": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "author": "ElectricSQL team and contributors.",
6
6
  "bugs": {
7
7
  "url": "https://github.com/electric-sql/electric/issues"
@@ -23,10 +23,10 @@
23
23
  "typescript": "^5.5.2",
24
24
  "uuid": "^10.0.0",
25
25
  "vitest": "^2.0.2",
26
- "@electric-sql/client": "1.0.0"
26
+ "@electric-sql/client": "1.0.2"
27
27
  },
28
28
  "peerDependencies": {
29
- "@electric-sql/client": "1.0.0"
29
+ "@electric-sql/client": "1.0.2"
30
30
  },
31
31
  "peerDependenciesMeta": {
32
32
  "@electric-sql/client": {
@@ -51,7 +51,7 @@
51
51
  "src"
52
52
  ],
53
53
  "homepage": "https://electric-sql.com",
54
- "license": "Apache-2",
54
+ "license": "Apache-2.0",
55
55
  "main": "dist/cjs/index.cjs",
56
56
  "module": "dist/index.legacy-esm.js",
57
57
  "optionalDependencies": {
@@ -1,9 +1,9 @@
1
- export function bigIntMax(...args: Array<bigint | number>): bigint {
2
- return BigInt(args.reduce((m, e) => (e > m ? e : m)))
1
+ export function bigIntMax(nums: Array<bigint | number>): bigint {
2
+ return BigInt(nums.reduce((m, e) => (e > m ? e : m)))
3
3
  }
4
4
 
5
- export function bigIntMin(...args: Array<bigint | number>): bigint {
6
- return BigInt(args.reduce((m, e) => (e < m ? e : m)))
5
+ export function bigIntMin(nums: Array<bigint | number>): bigint {
6
+ return BigInt(nums.reduce((m, e) => (e < m ? e : m)))
7
7
  }
8
8
 
9
9
  export function bigIntCompare(a: bigint, b: bigint): 1 | -1 | 0 {
@@ -183,7 +183,7 @@ export class MultiShapeStream<
183
183
  : BigInt(0)
184
184
  )
185
185
  if (upToDateLsns.length > 0) {
186
- const maxUpToDateLsn = bigIntMax(...upToDateLsns)
186
+ const maxUpToDateLsn = bigIntMax(upToDateLsns)
187
187
  const lastMaxUpToDateLsn = this.#lastUpToDateLsns[key]
188
188
  if (maxUpToDateLsn > lastMaxUpToDateLsn) {
189
189
  this.#lastUpToDateLsns[key] = maxUpToDateLsn
@@ -197,7 +197,7 @@ export class MultiShapeStream<
197
197
  typeof headers.lsn === `string` ? BigInt(headers.lsn) : BigInt(0)
198
198
  )
199
199
  if (dataLsns.length > 0) {
200
- const maxDataLsn = bigIntMax(...dataLsns)
200
+ const maxDataLsn = bigIntMax(dataLsns)
201
201
  const lastMaxDataLsn = this.#lastDataLsns[key]
202
202
  if (maxDataLsn > lastMaxDataLsn) {
203
203
  this.#lastDataLsns[key] = maxDataLsn
@@ -231,7 +231,7 @@ export class MultiShapeStream<
231
231
  }
232
232
 
233
233
  async #checkForUpdates() {
234
- const maxDataLsn = bigIntMax(...Object.values(this.#lastDataLsns))
234
+ const maxDataLsn = bigIntMax(Object.values(this.#lastDataLsns))
235
235
  const refreshPromises = this.#shapeEntries()
236
236
  .filter(([key]) => {
237
237
  // We only need to refresh shapes that have not seen an up-to-date message
@@ -310,11 +310,11 @@ export class MultiShapeStream<
310
310
  /** Unix time at which we last synced. Undefined when `isLoading` is true. */
311
311
  lastSyncedAt(): number | undefined {
312
312
  // Min of all the lastSyncedAt values
313
- return Math.min(
314
- ...this.#shapeEntries().map(
315
- ([_, shape]) => shape.lastSyncedAt() ?? Infinity
316
- )
317
- )
313
+ const shapeEntries = this.#shapeEntries()
314
+ if (shapeEntries.length === 0) return
315
+ return shapeEntries.reduce((minLastSyncedAt, [_, shape]) => {
316
+ return Math.min(minLastSyncedAt, shape.lastSyncedAt() ?? Infinity)
317
+ }, Infinity)
318
318
  }
319
319
 
320
320
  /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
@@ -394,7 +394,7 @@ export class TransactionalMultiShapeStream<
394
394
  }
395
395
 
396
396
  #getLowestCompleteLsn() {
397
- return bigIntMin(...Object.values(this.#completeLsns))
397
+ return bigIntMin(Object.values(this.#completeLsns))
398
398
  }
399
399
 
400
400
  protected async _publish(
@@ -447,17 +447,20 @@ export class TransactionalMultiShapeStream<
447
447
  typeof headers.last === `boolean` &&
448
448
  headers.last === true
449
449
  ) {
450
- this.#completeLsns[shape] = bigIntMax(this.#completeLsns[shape], lsn)
450
+ this.#completeLsns[shape] = bigIntMax([
451
+ this.#completeLsns[shape],
452
+ lsn,
453
+ ])
451
454
  }
452
455
  } else if (isControlMessage(message)) {
453
456
  if (headers.control === `up-to-date`) {
454
457
  if (typeof headers.global_last_seen_lsn !== `string`) {
455
458
  throw new Error(`global_last_seen_lsn is not a number`)
456
459
  }
457
- this.#completeLsns[shape] = bigIntMax(
460
+ this.#completeLsns[shape] = bigIntMax([
458
461
  this.#completeLsns[shape],
459
- BigInt(headers.global_last_seen_lsn)
460
- )
462
+ BigInt(headers.global_last_seen_lsn),
463
+ ])
461
464
  }
462
465
  }
463
466
  })