@electric-sql/client 0.6.5 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  </p>
16
16
 
17
17
  <p align="center">
18
- <a href="https://github.com/electric-sql/electric/actions"><img src="https://github.com/electric-sql/electric/workflows/CI/badge.svg" alt="CI"></a>
18
+ <a href="https://github.com/electric-sql/electric/actions"><img src="https://github.com/electric-sql/electric/actions/workflows/ts_test.yml/badge.svg"></a>
19
19
  <a href="https://github.com/electric-sql/electric/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache_2.0-green" alt="License - Apache 2.0"></a>
20
20
  <a href="https://github.com/electric-sql/electric-n
21
21
  ext/milestones"><img src="https://img.shields.io/badge/status-alpha-orange" alt="Status - Alpha"></a>
@@ -50,7 +50,8 @@ import { ShapeStream } from '@electric-sql/client'
50
50
 
51
51
  // Passes subscribers rows as they're inserted, updated, or deleted
52
52
  const stream = new ShapeStream({
53
- url: `${BASE_URL}/v1/shape/foo`,
53
+ url: `${BASE_URL}/v1/shape`,
54
+ table: `foo`,
54
55
  })
55
56
 
56
57
  stream.subscribe(messages => {
@@ -66,16 +67,17 @@ stream.subscribe(messages => {
66
67
  import { ShapeStream, Shape } from '@electric-sql/client'
67
68
 
68
69
  const stream = new ShapeStream({
69
- url: `${BASE_URL}/v1/shape/foo`,
70
+ url: `${BASE_URL}/v1/shape`,
71
+ table: `foo`,
70
72
  })
71
73
  const shape = new Shape(stream)
72
74
 
73
75
  // Returns promise that resolves with the latest shape data once it's fully loaded
74
- await shape.value
76
+ await shape.rows
75
77
 
76
78
  // passes subscribers shape data when the shape updates
77
- shape.subscribe(shapeData => {
78
- // shapeData is a Map of the latest value of each row in a shape.
79
+ shape.subscribe(({ rows }) => {
80
+ // rows is an array of the latest value of each row in a shape.
79
81
  }
80
82
  ```
81
83
 
@@ -244,17 +244,20 @@ var FetchBackoffAbortError = class extends Error {
244
244
  };
245
245
 
246
246
  // src/constants.ts
247
- var SHAPE_ID_HEADER = `electric-shape-id`;
248
- var LIVE_CACHE_BUSTER_HEADER = `electric-next-cursor`;
249
- var LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`;
250
- var CHUNK_LAST_OFFSET_HEADER = `electric-chunk-last-offset`;
251
- var CHUNK_UP_TO_DATE_HEADER = `electric-chunk-up-to-date`;
247
+ var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
248
+ var SHAPE_HANDLE_HEADER = `electric-handle`;
249
+ var CHUNK_LAST_OFFSET_HEADER = `electric-offset`;
252
250
  var SHAPE_SCHEMA_HEADER = `electric-schema`;
253
- var SHAPE_ID_QUERY_PARAM = `shape_id`;
254
- var OFFSET_QUERY_PARAM = `offset`;
255
- var WHERE_QUERY_PARAM = `where`;
251
+ var CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`;
252
+ var DATABASE_ID_QUERY_PARAM = `database_id`;
256
253
  var COLUMNS_QUERY_PARAM = `columns`;
254
+ var LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`;
255
+ var SHAPE_HANDLE_QUERY_PARAM = `handle`;
257
256
  var LIVE_QUERY_PARAM = `live`;
257
+ var OFFSET_QUERY_PARAM = `offset`;
258
+ var TABLE_QUERY_PARAM = `table`;
259
+ var WHERE_QUERY_PARAM = `where`;
260
+ var REPLICA_PARAM = `replica`;
258
261
 
259
262
  // src/fetch.ts
260
263
  var HTTP_RETRY_STATUS_CODES = [429];
@@ -394,13 +397,13 @@ prefetch_fn = function(...args) {
394
397
  }
395
398
  };
396
399
  function getNextChunkUrl(url, res) {
397
- const shapeId = res.headers.get(SHAPE_ID_HEADER);
400
+ const shapeHandle = res.headers.get(SHAPE_HANDLE_HEADER);
398
401
  const lastOffset = res.headers.get(CHUNK_LAST_OFFSET_HEADER);
399
402
  const isUpToDate = res.headers.has(CHUNK_UP_TO_DATE_HEADER);
400
- if (!shapeId || !lastOffset || isUpToDate) return;
403
+ if (!shapeHandle || !lastOffset || isUpToDate) return;
401
404
  const nextUrl = new URL(url);
402
405
  if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return;
403
- nextUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, shapeId);
406
+ nextUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, shapeHandle);
404
407
  nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset);
405
408
  return nextUrl.toString();
406
409
  }
@@ -415,8 +418,8 @@ function chainAborter(aborter, sourceSignal) {
415
418
  }
416
419
 
417
420
  // src/client.ts
418
- var _fetchClient2, _messageParser, _subscribers, _upToDateSubscribers, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _connected, _shapeId, _schema, _error, _ShapeStream_instances, publish_fn, sendErrorToSubscribers_fn, notifyUpToDateSubscribers_fn, sendErrorToUpToDateSubscribers_fn, reset_fn;
419
- var ShapeStream = class {
421
+ var _fetchClient2, _messageParser, _subscribers, _upToDateSubscribers, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _connected, _shapeHandle, _databaseId, _schema, _error, _replica, _ShapeStream_instances, publish_fn, sendErrorToSubscribers_fn, notifyUpToDateSubscribers_fn, sendErrorToUpToDateSubscribers_fn, reset_fn;
422
+ var _ShapeStream = class _ShapeStream {
420
423
  constructor(options) {
421
424
  __privateAdd(this, _ShapeStream_instances);
422
425
  __privateAdd(this, _fetchClient2);
@@ -430,16 +433,20 @@ var ShapeStream = class {
430
433
  // unix time
431
434
  __privateAdd(this, _isUpToDate, false);
432
435
  __privateAdd(this, _connected, false);
433
- __privateAdd(this, _shapeId);
436
+ __privateAdd(this, _shapeHandle);
437
+ __privateAdd(this, _databaseId);
434
438
  __privateAdd(this, _schema);
435
439
  __privateAdd(this, _error);
440
+ __privateAdd(this, _replica);
436
441
  var _a, _b, _c;
437
442
  validateOptions(options);
438
443
  this.options = __spreadValues({ subscribe: true }, options);
439
444
  __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
440
445
  __privateSet(this, _liveCacheBuster, ``);
441
- __privateSet(this, _shapeId, this.options.shapeId);
446
+ __privateSet(this, _shapeHandle, this.options.shapeHandle);
447
+ __privateSet(this, _databaseId, this.options.databaseId);
442
448
  __privateSet(this, _messageParser, new MessageParser(options.parser));
449
+ __privateSet(this, _replica, this.options.replica);
443
450
  const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
444
451
  const fetchWithBackoffClient = createFetchWithBackoff(baseFetchClient, __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
445
452
  onFailedAttempt: () => {
@@ -451,8 +458,8 @@ var ShapeStream = class {
451
458
  __privateSet(this, _fetchClient2, createFetchWithChunkBuffer(fetchWithBackoffClient));
452
459
  this.start();
453
460
  }
454
- get shapeId() {
455
- return __privateGet(this, _shapeId);
461
+ get shapeHandle() {
462
+ return __privateGet(this, _shapeHandle);
456
463
  }
457
464
  get isUpToDate() {
458
465
  return __privateGet(this, _isUpToDate);
@@ -462,12 +469,13 @@ var ShapeStream = class {
462
469
  }
463
470
  start() {
464
471
  return __async(this, null, function* () {
465
- var _a;
472
+ var _a, _b;
466
473
  __privateSet(this, _isUpToDate, false);
467
- const { url, where, columns, signal } = this.options;
474
+ const { url, table, where, columns, signal } = this.options;
468
475
  try {
469
476
  while (!(signal == null ? void 0 : signal.aborted) && !__privateGet(this, _isUpToDate) || this.options.subscribe) {
470
477
  const fetchUrl = new URL(url);
478
+ if (table) fetchUrl.searchParams.set(TABLE_QUERY_PARAM, table);
471
479
  if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where);
472
480
  if (columns && columns.length > 0)
473
481
  fetchUrl.searchParams.set(COLUMNS_QUERY_PARAM, columns.join(`,`));
@@ -479,8 +487,17 @@ var ShapeStream = class {
479
487
  __privateGet(this, _liveCacheBuster)
480
488
  );
481
489
  }
482
- if (__privateGet(this, _shapeId)) {
483
- fetchUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, __privateGet(this, _shapeId));
490
+ if (__privateGet(this, _shapeHandle)) {
491
+ fetchUrl.searchParams.set(
492
+ SHAPE_HANDLE_QUERY_PARAM,
493
+ __privateGet(this, _shapeHandle)
494
+ );
495
+ }
496
+ if (__privateGet(this, _databaseId)) {
497
+ fetchUrl.searchParams.set(DATABASE_ID_QUERY_PARAM, __privateGet(this, _databaseId));
498
+ }
499
+ if (((_a = __privateGet(this, _replica)) != null ? _a : _ShapeStream.Replica.DEFAULT) != _ShapeStream.Replica.DEFAULT) {
500
+ fetchUrl.searchParams.set(REPLICA_PARAM, __privateGet(this, _replica));
484
501
  }
485
502
  let response;
486
503
  try {
@@ -493,8 +510,8 @@ var ShapeStream = class {
493
510
  if (e instanceof FetchBackoffAbortError) break;
494
511
  if (!(e instanceof FetchError)) throw e;
495
512
  if (e.status == 409) {
496
- const newShapeId = e.headers[SHAPE_ID_HEADER];
497
- __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeId);
513
+ const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER];
514
+ __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
498
515
  yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
499
516
  continue;
500
517
  } else if (e.status >= 400 && e.status < 500) {
@@ -504,9 +521,9 @@ var ShapeStream = class {
504
521
  }
505
522
  }
506
523
  const { headers, status } = response;
507
- const shapeId = headers.get(SHAPE_ID_HEADER);
508
- if (shapeId) {
509
- __privateSet(this, _shapeId, shapeId);
524
+ const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
525
+ if (shapeHandle) {
526
+ __privateSet(this, _shapeHandle, shapeHandle);
510
527
  }
511
528
  const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
512
529
  if (lastOffset) {
@@ -520,7 +537,7 @@ var ShapeStream = class {
520
537
  const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
521
538
  return schemaHeader ? JSON.parse(schemaHeader) : {};
522
539
  };
523
- __privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchema());
540
+ __privateSet(this, _schema, (_b = __privateGet(this, _schema)) != null ? _b : getSchema());
524
541
  const messages = status === 204 ? `[]` : yield response.text();
525
542
  if (status === 204) {
526
543
  __privateSet(this, _lastSyncedAt, Date.now());
@@ -593,9 +610,11 @@ _liveCacheBuster = new WeakMap();
593
610
  _lastSyncedAt = new WeakMap();
594
611
  _isUpToDate = new WeakMap();
595
612
  _connected = new WeakMap();
596
- _shapeId = new WeakMap();
613
+ _shapeHandle = new WeakMap();
614
+ _databaseId = new WeakMap();
597
615
  _schema = new WeakMap();
598
616
  _error = new WeakMap();
617
+ _replica = new WeakMap();
599
618
  _ShapeStream_instances = new WeakSet();
600
619
  publish_fn = function(messages) {
601
620
  return __async(this, null, function* () {
@@ -629,28 +648,33 @@ sendErrorToUpToDateSubscribers_fn = function(error) {
629
648
  };
630
649
  /**
631
650
  * Resets the state of the stream, optionally with a provided
632
- * shape ID
651
+ * shape handle
633
652
  */
634
- reset_fn = function(shapeId) {
653
+ reset_fn = function(shapeHandle) {
635
654
  __privateSet(this, _lastOffset, `-1`);
636
655
  __privateSet(this, _liveCacheBuster, ``);
637
- __privateSet(this, _shapeId, shapeId);
656
+ __privateSet(this, _shapeHandle, shapeHandle);
638
657
  __privateSet(this, _isUpToDate, false);
639
658
  __privateSet(this, _connected, false);
640
659
  __privateSet(this, _schema, void 0);
641
660
  };
661
+ _ShapeStream.Replica = {
662
+ FULL: `full`,
663
+ DEFAULT: `default`
664
+ };
665
+ var ShapeStream = _ShapeStream;
642
666
  function validateOptions(options) {
643
667
  if (!options.url) {
644
- throw new Error(`Invalid shape option. It must provide the url`);
668
+ throw new Error(`Invalid shape options. It must provide the url`);
645
669
  }
646
670
  if (options.signal && !(options.signal instanceof AbortSignal)) {
647
671
  throw new Error(
648
672
  `Invalid signal option. It must be an instance of AbortSignal.`
649
673
  );
650
674
  }
651
- if (options.offset !== void 0 && options.offset !== `-1` && !options.shapeId) {
675
+ if (options.offset !== void 0 && options.offset !== `-1` && !options.shapeHandle) {
652
676
  throw new Error(
653
- `shapeId is required if this isn't an initial fetch (i.e. offset > -1)`
677
+ `shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)`
654
678
  );
655
679
  }
656
680
  return;
@@ -684,20 +708,26 @@ var Shape = class {
684
708
  get isUpToDate() {
685
709
  return __privateGet(this, _stream).isUpToDate;
686
710
  }
711
+ get rows() {
712
+ return this.value.then((v) => Array.from(v.values()));
713
+ }
714
+ get currentRows() {
715
+ return Array.from(this.currentValue.values());
716
+ }
687
717
  get value() {
688
718
  return new Promise((resolve, reject) => {
689
719
  if (__privateGet(this, _stream).isUpToDate) {
690
- resolve(this.valueSync);
720
+ resolve(this.currentValue);
691
721
  } else {
692
- const unsubscribe = this.subscribe((shapeData) => {
722
+ const unsubscribe = this.subscribe(({ value }) => {
693
723
  unsubscribe();
694
724
  if (__privateGet(this, _error2)) reject(__privateGet(this, _error2));
695
- resolve(shapeData);
725
+ resolve(value);
696
726
  });
697
727
  }
698
728
  });
699
729
  }
700
- get valueSync() {
730
+ get currentValue() {
701
731
  return __privateGet(this, _data);
702
732
  }
703
733
  get error() {
@@ -791,7 +821,7 @@ handleError_fn = function(e) {
791
821
  };
792
822
  notify_fn = function() {
793
823
  __privateGet(this, _subscribers2).forEach((callback) => {
794
- callback(this.valueSync);
824
+ callback({ value: this.currentValue, rows: this.currentRows });
795
825
  });
796
826
  };
797
827
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/parser.ts","../../src/helpers.ts","../../src/error.ts","../../src/constants.ts","../../src/fetch.ts","../../src/client.ts","../../src/shape.ts"],"sourcesContent":["export * from './client'\nexport * from './shape'\nexport * from './types'\nexport { isChangeMessage, isControlMessage } from './helpers'\nexport { FetchError } from './error'\nexport { type BackoffOptions, BackoffDefaults } from './fetch'\n","import { ColumnInfo, GetExtensions, Message, Row, Schema, Value } from './types'\n\ntype NullToken = null | `NULL`\ntype Token = Exclude<string, NullToken>\ntype NullableToken = Token | NullToken\nexport type ParseFunction<Extensions = never> = (\n value: Token,\n additionalInfo?: Omit<ColumnInfo, `type` | `dims`>\n) => Value<Extensions>\ntype NullableParseFunction<Extensions = never> = (\n value: NullableToken,\n additionalInfo?: Omit<ColumnInfo, `type` | `dims`>\n) => Value<Extensions>\n/**\n * @typeParam Extensions - Additional types that can be parsed by this parser beyond the standard SQL types.\n * Defaults to no additional types.\n */\nexport type Parser<Extensions = never> = {\n [key: string]: ParseFunction<Extensions>\n}\n\nconst parseNumber = (value: string) => Number(value)\nconst parseBool = (value: string) => value === `true` || value === `t`\nconst parseBigInt = (value: string) => BigInt(value)\nconst parseJson = (value: string) => JSON.parse(value)\nconst identityParser: ParseFunction = (v: string) => v\n\nexport const defaultParser: Parser = {\n int2: parseNumber,\n int4: parseNumber,\n int8: parseBigInt,\n bool: parseBool,\n float4: parseNumber,\n float8: parseNumber,\n json: parseJson,\n jsonb: parseJson,\n}\n\n// Taken from: https://github.com/electric-sql/pglite/blob/main/packages/pglite/src/types.ts#L233-L279\nexport function pgArrayParser<Extensions>(\n value: Token,\n parser?: ParseFunction<Extensions>\n): Value<Extensions> {\n let i = 0\n let char = null\n let str = ``\n let quoted = false\n let last = 0\n let p: string | undefined = undefined\n\n function loop(x: string): Array<Value<Extensions>> {\n const xs = []\n for (; i < x.length; i++) {\n char = x[i]\n if (quoted) {\n if (char === `\\\\`) {\n str += x[++i]\n } else if (char === `\"`) {\n xs.push(parser ? parser(str) : str)\n str = ``\n quoted = x[i + 1] === `\"`\n last = i + 2\n } else {\n str += char\n }\n } else if (char === `\"`) {\n quoted = true\n } else if (char === `{`) {\n last = ++i\n xs.push(loop(x))\n } else if (char === `}`) {\n quoted = false\n last < i &&\n xs.push(parser ? parser(x.slice(last, i)) : x.slice(last, i))\n last = i + 1\n break\n } else if (char === `,` && p !== `}` && p !== `\"`) {\n xs.push(parser ? parser(x.slice(last, i)) : x.slice(last, i))\n last = i + 1\n }\n p = char\n }\n last < i &&\n xs.push(parser ? parser(x.slice(last, i + 1)) : x.slice(last, i + 1))\n return xs\n }\n\n return loop(value)[0]\n}\n\nexport class MessageParser<T extends Row<unknown>> {\n private parser: Parser<GetExtensions<T>>\n constructor(parser?: Parser<GetExtensions<T>>) {\n // Merge the provided parser with the default parser\n // to use the provided parser whenever defined\n // and otherwise fall back to the default parser\n this.parser = { ...defaultParser, ...parser }\n }\n\n parse(messages: string, schema: Schema): Message<T>[] {\n return JSON.parse(messages, (key, value) => {\n // typeof value === `object` && value !== null\n // is needed because there could be a column named `value`\n // and the value associated to that column will be a string or null.\n // But `typeof null === 'object'` so we need to make an explicit check.\n if (key === `value` && typeof value === `object` && value !== null) {\n // Parse the row values\n const row = value as Record<string, Value<GetExtensions<T>>>\n Object.keys(row).forEach((key) => {\n row[key] = this.parseRow(key, row[key] as NullableToken, schema)\n })\n }\n return value\n }) as Message<T>[]\n }\n\n // Parses the message values using the provided parser based on the schema information\n private parseRow(\n key: string,\n value: NullableToken,\n schema: Schema\n ): Value<GetExtensions<T>> {\n const columnInfo = schema[key]\n if (!columnInfo) {\n // We don't have information about the value\n // so we just return it\n return value\n }\n\n // Copy the object but don't include `dimensions` and `type`\n const { type: typ, dims: dimensions, ...additionalInfo } = columnInfo\n\n // Pick the right parser for the type\n // and support parsing null values if needed\n // if no parser is provided for the given type, just return the value as is\n const typeParser = this.parser[typ] ?? identityParser\n const parser = makeNullableParser(typeParser, columnInfo, key)\n\n if (dimensions && dimensions > 0) {\n // It's an array\n const nullablePgArrayParser = makeNullableParser(\n (value, _) => pgArrayParser(value, parser),\n columnInfo,\n key\n )\n return nullablePgArrayParser(value)\n }\n\n return parser(value, additionalInfo)\n }\n}\n\nfunction makeNullableParser<Extensions>(\n parser: ParseFunction<Extensions>,\n columnInfo: ColumnInfo,\n columnName?: string\n): NullableParseFunction<Extensions> {\n const isNullable = !(columnInfo.not_null ?? false)\n // The sync service contains `null` value for a column whose value is NULL\n // but if the column value is an array that contains a NULL value\n // then it will be included in the array string as `NULL`, e.g.: `\"{1,NULL,3}\"`\n return (value: NullableToken) => {\n if (isPgNull(value)) {\n if (!isNullable) {\n throw new Error(`Column ${columnName ?? `unknown`} is not nullable`)\n }\n return null\n }\n return parser(value, columnInfo)\n }\n}\n\nfunction isPgNull(value: NullableToken): value is NullToken {\n return value === null || value === `NULL`\n}\n","import { ChangeMessage, ControlMessage, Message, Row } from './types'\n\n/**\n * Type guard for checking {@link Message} is {@link ChangeMessage}.\n *\n * See [TS docs](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards)\n * for information on how to use type guards.\n *\n * @param message - the message to check\n * @returns true if the message is a {@link ChangeMessage}\n *\n * @example\n * ```ts\n * if (isChangeMessage(message)) {\n * const msgChng: ChangeMessage = message // Ok\n * const msgCtrl: ControlMessage = message // Err, type mismatch\n * }\n * ```\n */\nexport function isChangeMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ChangeMessage<T> {\n return `key` in message\n}\n\n/**\n * Type guard for checking {@link Message} is {@link ControlMessage}.\n *\n * See [TS docs](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards)\n * for information on how to use type guards.\n *\n * @param message - the message to check\n * @returns true if the message is a {@link ControlMessage}\n *\n * * @example\n * ```ts\n * if (isControlMessage(message)) {\n * const msgChng: ChangeMessage = message // Err, type mismatch\n * const msgCtrl: ControlMessage = message // Ok\n * }\n * ```\n */\nexport function isControlMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ControlMessage {\n return !isChangeMessage(message)\n}\n\nexport function isUpToDateMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ControlMessage & { up_to_date: true } {\n return isControlMessage(message) && message.headers.control === `up-to-date`\n}\n","export class FetchError extends Error {\n status: number\n text?: string\n json?: object\n headers: Record<string, string>\n\n constructor(\n status: number,\n text: string | undefined,\n json: object | undefined,\n headers: Record<string, string>,\n public url: string,\n message?: string\n ) {\n super(\n message ||\n `HTTP Error ${status} at ${url}: ${text ?? JSON.stringify(json)}`\n )\n this.name = `FetchError`\n this.status = status\n this.text = text\n this.json = json\n this.headers = headers\n }\n\n static async fromResponse(\n response: Response,\n url: string\n ): Promise<FetchError> {\n const status = response.status\n const headers = Object.fromEntries([...response.headers.entries()])\n let text: string | undefined = undefined\n let json: object | undefined = undefined\n\n const contentType = response.headers.get(`content-type`)\n if (contentType && contentType.includes(`application/json`)) {\n json = (await response.json()) as object\n } else {\n text = await response.text()\n }\n\n return new FetchError(status, text, json, headers, url)\n }\n}\n\nexport class FetchBackoffAbortError extends Error {\n constructor() {\n super(`Fetch with backoff aborted`)\n }\n}\n","export const SHAPE_ID_HEADER = `electric-shape-id`\nexport const LIVE_CACHE_BUSTER_HEADER = `electric-next-cursor`\nexport const LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`\nexport const CHUNK_LAST_OFFSET_HEADER = `electric-chunk-last-offset`\nexport const CHUNK_UP_TO_DATE_HEADER = `electric-chunk-up-to-date`\nexport const SHAPE_SCHEMA_HEADER = `electric-schema`\nexport const SHAPE_ID_QUERY_PARAM = `shape_id`\nexport const OFFSET_QUERY_PARAM = `offset`\nexport const WHERE_QUERY_PARAM = `where`\nexport const COLUMNS_QUERY_PARAM = `columns`\nexport const LIVE_QUERY_PARAM = `live`\n","import {\n CHUNK_LAST_OFFSET_HEADER,\n CHUNK_UP_TO_DATE_HEADER,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n SHAPE_ID_HEADER,\n SHAPE_ID_QUERY_PARAM,\n} from './constants'\nimport { FetchError, FetchBackoffAbortError } from './error'\n\n// Some specific 4xx and 5xx HTTP status codes that we definitely\n// want to retry\nconst HTTP_RETRY_STATUS_CODES = [429]\n\nexport interface BackoffOptions {\n /**\n * Initial delay before retrying in milliseconds\n */\n initialDelay: number\n /**\n * Maximum retry delay in milliseconds\n */\n maxDelay: number\n multiplier: number\n onFailedAttempt?: () => void\n debug?: boolean\n}\n\nexport const BackoffDefaults = {\n initialDelay: 100,\n maxDelay: 10_000,\n multiplier: 1.3,\n}\n\nexport function createFetchWithBackoff(\n fetchClient: typeof fetch,\n backoffOptions: BackoffOptions = BackoffDefaults\n): typeof fetch {\n const {\n initialDelay,\n maxDelay,\n multiplier,\n debug = false,\n onFailedAttempt,\n } = backoffOptions\n return async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const url = args[0]\n const options = args[1]\n\n let delay = initialDelay\n let attempt = 0\n\n /* eslint-disable no-constant-condition -- we re-fetch the shape log\n * continuously until we get a non-ok response. For recoverable errors,\n * we retry the fetch with exponential backoff. Users can pass in an\n * AbortController to abort the fetching an any point.\n * */\n while (true) {\n /* eslint-enable no-constant-condition */\n try {\n const result = await fetchClient(...args)\n if (result.ok) return result\n else throw await FetchError.fromResponse(result, url.toString())\n } catch (e) {\n onFailedAttempt?.()\n if (options?.signal?.aborted) {\n throw new FetchBackoffAbortError()\n } else if (\n e instanceof FetchError &&\n !HTTP_RETRY_STATUS_CODES.includes(e.status) &&\n e.status >= 400 &&\n e.status < 500\n ) {\n // Any client errors cannot be backed off on, leave it to the caller to handle.\n throw e\n } else {\n // Exponentially backoff on errors.\n // Wait for the current delay duration\n await new Promise((resolve) => setTimeout(resolve, delay))\n\n // Increase the delay for the next attempt\n delay = Math.min(delay * multiplier, maxDelay)\n\n if (debug) {\n attempt++\n console.log(`Retry attempt #${attempt} after ${delay}ms`)\n }\n }\n }\n }\n }\n}\n\ninterface ChunkPrefetchOptions {\n maxChunksToPrefetch: number\n}\n\nconst ChunkPrefetchDefaults = {\n maxChunksToPrefetch: 2,\n}\n\n/**\n * Creates a fetch client that prefetches subsequent log chunks for\n * consumption by the shape stream without waiting for the chunk bodies\n * themselves to be loaded.\n *\n * @param fetchClient the client to wrap\n * @param prefetchOptions options to configure prefetching\n * @returns wrapped client with prefetch capabilities\n */\nexport function createFetchWithChunkBuffer(\n fetchClient: typeof fetch,\n prefetchOptions: ChunkPrefetchOptions = ChunkPrefetchDefaults\n): typeof fetch {\n const { maxChunksToPrefetch } = prefetchOptions\n\n let prefetchQueue: PrefetchQueue\n\n const prefetchClient = async (...args: Parameters<typeof fetchClient>) => {\n const url = args[0].toString()\n\n // try to consume from the prefetch queue first, and if request is\n // not present abort the prefetch queue as it must no longer be valid\n const prefetchedRequest = prefetchQueue?.consume(...args)\n if (prefetchedRequest) {\n return prefetchedRequest\n }\n\n prefetchQueue?.abort()\n\n // perform request and fire off prefetch queue if request is eligible\n const response = await fetchClient(...args)\n const nextUrl = getNextChunkUrl(url, response)\n if (nextUrl) {\n prefetchQueue = new PrefetchQueue({\n fetchClient,\n maxPrefetchedRequests: maxChunksToPrefetch,\n url: nextUrl,\n requestInit: args[1],\n })\n }\n\n return response\n }\n\n return prefetchClient\n}\n\nclass PrefetchQueue {\n readonly #fetchClient: typeof fetch\n readonly #maxPrefetchedRequests: number\n readonly #prefetchQueue = new Map<\n string,\n [Promise<Response>, AbortController]\n >()\n #queueHeadUrl: string | void\n #queueTailUrl: string | void\n\n constructor(options: {\n url: Parameters<typeof fetch>[0]\n requestInit: Parameters<typeof fetch>[1]\n maxPrefetchedRequests: number\n fetchClient?: typeof fetch\n }) {\n this.#fetchClient =\n options.fetchClient ??\n ((...args: Parameters<typeof fetch>) => fetch(...args))\n this.#maxPrefetchedRequests = options.maxPrefetchedRequests\n this.#queueHeadUrl = options.url.toString()\n this.#queueTailUrl = this.#queueHeadUrl\n this.#prefetch(options.url, options.requestInit)\n }\n\n abort(): void {\n this.#prefetchQueue.forEach(([_, aborter]) => aborter.abort())\n }\n\n consume(...args: Parameters<typeof fetch>): Promise<Response> | void {\n const url = args[0].toString()\n\n const request = this.#prefetchQueue.get(url)?.[0]\n // only consume if request is in queue and is the queue \"head\"\n // if request is in the queue but not the head, the queue is being\n // consumed out of order and should be restarted\n if (!request || url !== this.#queueHeadUrl) return\n this.#prefetchQueue.delete(url)\n\n // fire off new prefetch since request has been consumed\n request\n .then((response) => {\n const nextUrl = getNextChunkUrl(url, response)\n this.#queueHeadUrl = nextUrl\n if (\n this.#queueTailUrl &&\n !this.#prefetchQueue.has(this.#queueTailUrl)\n ) {\n this.#prefetch(this.#queueTailUrl, args[1])\n }\n })\n .catch(() => {})\n\n return request\n }\n\n #prefetch(...args: Parameters<typeof fetch>): void {\n const url = args[0].toString()\n\n // only prefetch when queue is not full\n if (this.#prefetchQueue.size >= this.#maxPrefetchedRequests) return\n\n // initialize aborter per request, to avoid aborting consumed requests that\n // are still streaming their bodies to the consumer\n const aborter = new AbortController()\n\n try {\n const request = this.#fetchClient(url, {\n ...(args[1] ?? {}),\n signal: chainAborter(aborter, args[1]?.signal),\n })\n this.#prefetchQueue.set(url, [request, aborter])\n request\n .then((response) => {\n // only keep prefetching if response chain is uninterrupted\n if (!response.ok || aborter.signal.aborted) return\n\n const nextUrl = getNextChunkUrl(url, response)\n\n // only prefetch when there is a next URL\n if (!nextUrl || nextUrl === url) {\n this.#queueTailUrl = undefined\n return\n }\n\n this.#queueTailUrl = nextUrl\n return this.#prefetch(nextUrl, args[1])\n })\n .catch(() => {})\n } catch (_) {\n // ignore prefetch errors\n }\n }\n}\n\n/**\n * Generate the next chunk's URL if the url and response are valid\n */\nfunction getNextChunkUrl(url: string, res: Response): string | void {\n const shapeId = res.headers.get(SHAPE_ID_HEADER)\n const lastOffset = res.headers.get(CHUNK_LAST_OFFSET_HEADER)\n const isUpToDate = res.headers.has(CHUNK_UP_TO_DATE_HEADER)\n\n // only prefetch if shape ID and offset for next chunk are available, and\n // response is not already up-to-date\n if (!shapeId || !lastOffset || isUpToDate) return\n\n const nextUrl = new URL(url)\n\n // don't prefetch live requests, rushing them will only\n // potentially miss more recent data\n if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return\n\n nextUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, shapeId)\n nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset)\n return nextUrl.toString()\n}\n\n/**\n * Chains an abort controller on an optional source signal's\n * aborted state - if the source signal is aborted, the provided abort\n * controller will also abort\n */\nfunction chainAborter(\n aborter: AbortController,\n sourceSignal?: AbortSignal\n): AbortSignal {\n if (!sourceSignal) return aborter.signal\n if (sourceSignal.aborted) aborter.abort()\n else\n sourceSignal.addEventListener(`abort`, () => aborter.abort(), {\n once: true,\n })\n return aborter.signal\n}\n","import {\n Message,\n Offset,\n Schema,\n Row,\n MaybePromise,\n GetExtensions,\n} from './types'\nimport { MessageParser, Parser } from './parser'\nimport { isUpToDateMessage } from './helpers'\nimport { FetchError, FetchBackoffAbortError } from './error'\nimport {\n BackoffDefaults,\n BackoffOptions,\n createFetchWithBackoff,\n createFetchWithChunkBuffer,\n} from './fetch'\nimport {\n CHUNK_LAST_OFFSET_HEADER,\n LIVE_CACHE_BUSTER_HEADER,\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n COLUMNS_QUERY_PARAM,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n SHAPE_ID_HEADER,\n SHAPE_ID_QUERY_PARAM,\n SHAPE_SCHEMA_HEADER,\n WHERE_QUERY_PARAM,\n} from './constants'\n\n/**\n * Options for constructing a ShapeStream.\n */\nexport interface ShapeStreamOptions<T = never> {\n /**\n * The full URL to where the Shape is hosted. This can either be the Electric server\n * directly or a proxy. E.g. for a local Electric instance, you might set `http://localhost:3000/v1/shape/foo`\n */\n url: string\n /**\n * The where clauses for the shape.\n */\n where?: string\n\n /**\n * The columns to include in the shape.\n * Must include primary keys, and can only inlude valid columns.\n */\n columns?: string[]\n\n /**\n * The \"offset\" on the shape log. This is typically not set as the ShapeStream\n * will handle this automatically. A common scenario where you might pass an offset\n * is if you're maintaining a local cache of the log. If you've gone offline\n * and are re-starting a ShapeStream to catch-up to the latest state of the Shape,\n * you'd pass in the last offset and shapeId you'd seen from the Electric server\n * so it knows at what point in the shape to catch you up from.\n */\n offset?: Offset\n /**\n * Similar to `offset`, this isn't typically used unless you're maintaining\n * a cache of the shape log.\n */\n shapeId?: string\n backoffOptions?: BackoffOptions\n\n /**\n * HTTP headers to attach to requests made by the client.\n * Can be used for adding authentication headers.\n */\n headers?: Record<string, string>\n\n /**\n * Automatically fetch updates to the Shape. If you just want to sync the current\n * shape and stop, pass false.\n */\n subscribe?: boolean\n signal?: AbortSignal\n fetchClient?: typeof fetch\n parser?: Parser<T>\n}\n\nexport interface ShapeStreamInterface<T extends Row<unknown> = Row> {\n subscribe(\n callback: (messages: Message<T>[]) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ): void\n unsubscribeAllUpToDateSubscribers(): void\n unsubscribeAll(): void\n subscribeOnceToUpToDate(\n callback: () => MaybePromise<void>,\n error: (err: FetchError | Error) => void\n ): () => void\n\n isLoading(): boolean\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n\n isUpToDate: boolean\n shapeId?: string\n}\n\n/**\n * Reads updates to a shape from Electric using HTTP requests and long polling. Notifies subscribers\n * when new messages come in. Doesn't maintain any history of the\n * log but does keep track of the offset position and is the best way\n * to consume the HTTP `GET /v1/shape` api.\n *\n * @constructor\n * @param {ShapeStreamOptions} options - configure the shape stream\n * @example\n * Register a callback function to subscribe to the messages.\n * ```\n * const stream = new ShapeStream(options)\n * stream.subscribe(messages => {\n * // messages is 1 or more row updates\n * })\n * ```\n *\n * To abort the stream, abort the `signal`\n * passed in via the `ShapeStreamOptions`.\n * ```\n * const aborter = new AbortController()\n * const issueStream = new ShapeStream({\n * url: `${BASE_URL}/${table}`\n * subscribe: true,\n * signal: aborter.signal,\n * })\n * // Later...\n * aborter.abort()\n * ```\n */\n\nexport class ShapeStream<T extends Row<unknown> = Row>\n implements ShapeStreamInterface<T>\n{\n readonly options: ShapeStreamOptions<GetExtensions<T>>\n\n readonly #fetchClient: typeof fetch\n readonly #messageParser: MessageParser<T>\n\n readonly #subscribers = new Map<\n number,\n [\n (messages: Message<T>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n readonly #upToDateSubscribers = new Map<\n number,\n [() => void, (error: FetchError | Error) => void]\n >()\n\n #lastOffset: Offset\n #liveCacheBuster: string // Seconds since our Electric Epoch 😎\n #lastSyncedAt?: number // unix time\n #isUpToDate: boolean = false\n #connected: boolean = false\n #shapeId?: string\n #schema?: Schema\n #error?: unknown\n\n constructor(options: ShapeStreamOptions<GetExtensions<T>>) {\n validateOptions(options)\n this.options = { subscribe: true, ...options }\n this.#lastOffset = this.options.offset ?? `-1`\n this.#liveCacheBuster = ``\n this.#shapeId = this.options.shapeId\n this.#messageParser = new MessageParser<T>(options.parser)\n\n const baseFetchClient =\n options.fetchClient ??\n ((...args: Parameters<typeof fetch>) => fetch(...args))\n\n const fetchWithBackoffClient = createFetchWithBackoff(baseFetchClient, {\n ...(options.backoffOptions ?? BackoffDefaults),\n onFailedAttempt: () => {\n this.#connected = false\n options.backoffOptions?.onFailedAttempt?.()\n },\n })\n\n this.#fetchClient = createFetchWithChunkBuffer(fetchWithBackoffClient)\n\n this.start()\n }\n\n get shapeId() {\n return this.#shapeId\n }\n\n get isUpToDate() {\n return this.#isUpToDate\n }\n\n get error() {\n return this.#error\n }\n\n async start() {\n this.#isUpToDate = false\n\n const { url, where, columns, signal } = this.options\n\n try {\n while (\n (!signal?.aborted && !this.#isUpToDate) ||\n this.options.subscribe\n ) {\n const fetchUrl = new URL(url)\n if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where)\n if (columns && columns.length > 0)\n fetchUrl.searchParams.set(COLUMNS_QUERY_PARAM, columns.join(`,`))\n fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, this.#lastOffset)\n\n if (this.#isUpToDate) {\n fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`)\n fetchUrl.searchParams.set(\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n this.#liveCacheBuster\n )\n }\n\n if (this.#shapeId) {\n // This should probably be a header for better cache breaking?\n fetchUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, this.#shapeId!)\n }\n\n let response!: Response\n try {\n response = await this.#fetchClient(fetchUrl.toString(), {\n signal,\n headers: this.options.headers,\n })\n this.#connected = true\n } catch (e) {\n if (e instanceof FetchBackoffAbortError) break // interrupted\n if (!(e instanceof FetchError)) throw e // should never happen\n if (e.status == 409) {\n // Upon receiving a 409, we should start from scratch\n // with the newly provided shape ID\n const newShapeId = e.headers[SHAPE_ID_HEADER]\n this.#reset(newShapeId)\n await this.#publish(e.json as Message<T>[])\n continue\n } else if (e.status >= 400 && e.status < 500) {\n // Notify subscribers\n this.#sendErrorToUpToDateSubscribers(e)\n this.#sendErrorToSubscribers(e)\n\n // 400 errors are not actionable without additional user input,\n // so we exit the loop\n throw e\n }\n }\n\n const { headers, status } = response\n const shapeId = headers.get(SHAPE_ID_HEADER)\n if (shapeId) {\n this.#shapeId = shapeId\n }\n\n const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER)\n if (lastOffset) {\n this.#lastOffset = lastOffset as Offset\n }\n\n const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER)\n if (liveCacheBuster) {\n this.#liveCacheBuster = liveCacheBuster\n }\n\n const getSchema = (): Schema => {\n const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER)\n return schemaHeader ? JSON.parse(schemaHeader) : {}\n }\n this.#schema = this.#schema ?? getSchema()\n\n const messages = status === 204 ? `[]` : await response.text()\n\n if (status === 204) {\n // There's no content so we are live and up to date\n this.#lastSyncedAt = Date.now()\n }\n\n const batch = this.#messageParser.parse(messages, this.#schema)\n\n // Update isUpToDate\n if (batch.length > 0) {\n const prevUpToDate = this.#isUpToDate\n const lastMessage = batch[batch.length - 1]\n if (isUpToDateMessage(lastMessage)) {\n this.#lastSyncedAt = Date.now()\n this.#isUpToDate = true\n }\n\n await this.#publish(batch)\n if (!prevUpToDate && this.#isUpToDate) {\n this.#notifyUpToDateSubscribers()\n }\n }\n }\n } catch (err) {\n this.#error = err\n } finally {\n this.#connected = false\n }\n }\n\n subscribe(\n callback: (messages: Message<T>[]) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n subscribeOnceToUpToDate(\n callback: () => MaybePromise<void>,\n error: (err: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#upToDateSubscribers.set(subscriptionId, [callback, error])\n\n return () => {\n this.#upToDateSubscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAllUpToDateSubscribers(): void {\n this.#upToDateSubscribers.clear()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n return this.#lastSyncedAt\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n if (this.#lastSyncedAt === undefined) return Infinity\n return Date.now() - this.#lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#connected\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading(): boolean {\n return !this.isUpToDate\n }\n\n async #publish(messages: Message<T>[]): 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 #sendErrorToSubscribers(error: Error) {\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n #notifyUpToDateSubscribers() {\n this.#upToDateSubscribers.forEach(([callback]) => {\n callback()\n })\n }\n\n #sendErrorToUpToDateSubscribers(error: FetchError | Error) {\n this.#upToDateSubscribers.forEach(([_, errorCallback]) =>\n errorCallback(error)\n )\n }\n\n /**\n * Resets the state of the stream, optionally with a provided\n * shape ID\n */\n #reset(shapeId?: string) {\n this.#lastOffset = `-1`\n this.#liveCacheBuster = ``\n this.#shapeId = shapeId\n this.#isUpToDate = false\n this.#connected = false\n this.#schema = undefined\n }\n}\n\nfunction validateOptions<T>(options: Partial<ShapeStreamOptions<T>>): void {\n if (!options.url) {\n throw new Error(`Invalid shape option. It must provide the url`)\n }\n if (options.signal && !(options.signal instanceof AbortSignal)) {\n throw new Error(\n `Invalid signal option. It must be an instance of AbortSignal.`\n )\n }\n\n if (\n options.offset !== undefined &&\n options.offset !== `-1` &&\n !options.shapeId\n ) {\n throw new Error(\n `shapeId is required if this isn't an initial fetch (i.e. offset > -1)`\n )\n }\n return\n}\n","import { Message, Row } from './types'\nimport { isChangeMessage, isControlMessage } from './helpers'\nimport { FetchError } from './error'\nimport { ShapeStreamInterface } from './client'\n\nexport type ShapeData<T extends Row<unknown> = Row> = Map<string, T>\nexport type ShapeChangedCallback<T extends Row<unknown> = Row> = (\n value: ShapeData<T>\n) => void\n\n/**\n * A Shape is an object that subscribes to a shape log,\n * keeps a materialised shape `.value` in memory and\n * notifies subscribers when the value has changed.\n *\n * It can be used without a framework and as a primitive\n * to simplify developing framework hooks.\n *\n * @constructor\n * @param {ShapeStream<T extends Row>} - the underlying shape stream\n * @example\n * ```\n * const shapeStream = new ShapeStream<{ foo: number }>(url: 'http://localhost:3000/v1/shape/foo'})\n * const shape = new Shape(shapeStream)\n * ```\n *\n * `value` returns a promise that resolves the Shape data once the Shape has been\n * fully loaded (and when resuming from being offline):\n *\n * const value = await shape.value\n *\n * `valueSync` returns the current data synchronously:\n *\n * const value = shape.valueSync\n *\n * Subscribe to updates. Called whenever the shape updates in Postgres.\n *\n * shape.subscribe(shapeData => {\n * console.log(shapeData)\n * })\n */\nexport class Shape<T extends Row<unknown> = Row> {\n readonly #stream: ShapeStreamInterface<T>\n\n readonly #data: ShapeData<T> = new Map()\n readonly #subscribers = new Map<number, ShapeChangedCallback<T>>()\n\n #hasNotifiedSubscribersUpToDate: boolean = false\n #error: FetchError | false = false\n\n constructor(stream: ShapeStreamInterface<T>) {\n this.#stream = stream\n this.#stream.subscribe(\n this.#process.bind(this),\n this.#handleError.bind(this)\n )\n const unsubscribe = this.#stream.subscribeOnceToUpToDate(\n () => {\n unsubscribe()\n },\n (e) => {\n this.#handleError(e)\n throw e\n }\n )\n }\n\n get isUpToDate(): boolean {\n return this.#stream.isUpToDate\n }\n\n get value(): Promise<ShapeData<T>> {\n return new Promise((resolve, reject) => {\n if (this.#stream.isUpToDate) {\n resolve(this.valueSync)\n } else {\n const unsubscribe = this.subscribe((shapeData) => {\n unsubscribe()\n if (this.#error) reject(this.#error)\n resolve(shapeData)\n })\n }\n })\n }\n\n get valueSync() {\n return this.#data\n }\n\n get error() {\n return this.#error\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n return this.#stream.lastSyncedAt()\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced() {\n return this.#stream.lastSynced()\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading() {\n return this.#stream.isLoading()\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#stream.isConnected()\n }\n\n subscribe(callback: ShapeChangedCallback<T>): () => void {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, callback)\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n get numSubscribers() {\n return this.#subscribers.size\n }\n\n #process(messages: Message<T>[]): void {\n let dataMayHaveChanged = false\n let isUpToDate = false\n let newlyUpToDate = false\n\n messages.forEach((message) => {\n if (isChangeMessage(message)) {\n dataMayHaveChanged = [`insert`, `update`, `delete`].includes(\n message.headers.operation\n )\n\n switch (message.headers.operation) {\n case `insert`:\n this.#data.set(message.key, message.value)\n break\n case `update`:\n this.#data.set(message.key, {\n ...this.#data.get(message.key)!,\n ...message.value,\n })\n break\n case `delete`:\n this.#data.delete(message.key)\n break\n }\n }\n\n if (isControlMessage(message)) {\n switch (message.headers.control) {\n case `up-to-date`:\n isUpToDate = true\n if (!this.#hasNotifiedSubscribersUpToDate) {\n newlyUpToDate = true\n }\n break\n case `must-refetch`:\n this.#data.clear()\n this.#error = false\n this.#hasNotifiedSubscribersUpToDate = false\n isUpToDate = false\n newlyUpToDate = false\n break\n }\n }\n })\n\n // Always notify subscribers when the Shape first is up to date.\n // FIXME this would be cleaner with a simple state machine.\n if (newlyUpToDate || (isUpToDate && dataMayHaveChanged)) {\n this.#hasNotifiedSubscribersUpToDate = true\n this.#notify()\n }\n }\n\n #handleError(e: Error): void {\n if (e instanceof FetchError) {\n this.#error = e\n this.#notify()\n }\n }\n\n #notify(): void {\n this.#subscribers.forEach((callback) => {\n callback(this.valueSync)\n })\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACqBA,IAAM,cAAc,CAAC,UAAkB,OAAO,KAAK;AACnD,IAAM,YAAY,CAAC,UAAkB,UAAU,UAAU,UAAU;AACnE,IAAM,cAAc,CAAC,UAAkB,OAAO,KAAK;AACnD,IAAM,YAAY,CAAC,UAAkB,KAAK,MAAM,KAAK;AACrD,IAAM,iBAAgC,CAAC,MAAc;AAE9C,IAAM,gBAAwB;AAAA,EACnC,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AACT;AAGO,SAAS,cACd,OACA,QACmB;AACnB,MAAI,IAAI;AACR,MAAI,OAAO;AACX,MAAI,MAAM;AACV,MAAI,SAAS;AACb,MAAI,OAAO;AACX,MAAI,IAAwB;AAE5B,WAAS,KAAK,GAAqC;AACjD,UAAM,KAAK,CAAC;AACZ,WAAO,IAAI,EAAE,QAAQ,KAAK;AACxB,aAAO,EAAE,CAAC;AACV,UAAI,QAAQ;AACV,YAAI,SAAS,MAAM;AACjB,iBAAO,EAAE,EAAE,CAAC;AAAA,QACd,WAAW,SAAS,KAAK;AACvB,aAAG,KAAK,SAAS,OAAO,GAAG,IAAI,GAAG;AAClC,gBAAM;AACN,mBAAS,EAAE,IAAI,CAAC,MAAM;AACtB,iBAAO,IAAI;AAAA,QACb,OAAO;AACL,iBAAO;AAAA,QACT;AAAA,MACF,WAAW,SAAS,KAAK;AACvB,iBAAS;AAAA,MACX,WAAW,SAAS,KAAK;AACvB,eAAO,EAAE;AACT,WAAG,KAAK,KAAK,CAAC,CAAC;AAAA,MACjB,WAAW,SAAS,KAAK;AACvB,iBAAS;AACT,eAAO,KACL,GAAG,KAAK,SAAS,OAAO,EAAE,MAAM,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,CAAC;AAC9D,eAAO,IAAI;AACX;AAAA,MACF,WAAW,SAAS,OAAO,MAAM,OAAO,MAAM,KAAK;AACjD,WAAG,KAAK,SAAS,OAAO,EAAE,MAAM,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,CAAC;AAC5D,eAAO,IAAI;AAAA,MACb;AACA,UAAI;AAAA,IACN;AACA,WAAO,KACL,GAAG,KAAK,SAAS,OAAO,EAAE,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,MAAM,IAAI,CAAC,CAAC;AACtE,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,KAAK,EAAE,CAAC;AACtB;AAEO,IAAM,gBAAN,MAA4C;AAAA,EAEjD,YAAY,QAAmC;AAI7C,SAAK,SAAS,kCAAK,gBAAkB;AAAA,EACvC;AAAA,EAEA,MAAM,UAAkB,QAA8B;AACpD,WAAO,KAAK,MAAM,UAAU,CAAC,KAAK,UAAU;AAK1C,UAAI,QAAQ,WAAW,OAAO,UAAU,YAAY,UAAU,MAAM;AAElE,cAAM,MAAM;AACZ,eAAO,KAAK,GAAG,EAAE,QAAQ,CAACA,SAAQ;AAChC,cAAIA,IAAG,IAAI,KAAK,SAASA,MAAK,IAAIA,IAAG,GAAoB,MAAM;AAAA,QACjE,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA,EAGQ,SACN,KACA,OACA,QACyB;AAzH7B;AA0HI,UAAM,aAAa,OAAO,GAAG;AAC7B,QAAI,CAAC,YAAY;AAGf,aAAO;AAAA,IACT;AAGA,UAA2D,iBAAnD,QAAM,KAAK,MAAM,WAlI7B,IAkI+D,IAAnB,2BAAmB,IAAnB,CAAhC,QAAW;AAKnB,UAAM,cAAa,UAAK,OAAO,GAAG,MAAf,YAAoB;AACvC,UAAM,SAAS,mBAAmB,YAAY,YAAY,GAAG;AAE7D,QAAI,cAAc,aAAa,GAAG;AAEhC,YAAM,wBAAwB;AAAA,QAC5B,CAACC,QAAO,MAAM,cAAcA,QAAO,MAAM;AAAA,QACzC;AAAA,QACA;AAAA,MACF;AACA,aAAO,sBAAsB,KAAK;AAAA,IACpC;AAEA,WAAO,OAAO,OAAO,cAAc;AAAA,EACrC;AACF;AAEA,SAAS,mBACP,QACA,YACA,YACmC;AA5JrC;AA6JE,QAAM,aAAa,GAAE,gBAAW,aAAX,YAAuB;AAI5C,SAAO,CAAC,UAAyB;AAC/B,QAAI,SAAS,KAAK,GAAG;AACnB,UAAI,CAAC,YAAY;AACf,cAAM,IAAI,MAAM,UAAU,kCAAc,SAAS,kBAAkB;AAAA,MACrE;AACA,aAAO;AAAA,IACT;AACA,WAAO,OAAO,OAAO,UAAU;AAAA,EACjC;AACF;AAEA,SAAS,SAAS,OAA0C;AAC1D,SAAO,UAAU,QAAQ,UAAU;AACrC;;;AC3JO,SAAS,gBACd,SAC6B;AAC7B,SAAO,SAAS;AAClB;AAmBO,SAAS,iBACd,SAC2B;AAC3B,SAAO,CAAC,gBAAgB,OAAO;AACjC;AAEO,SAAS,kBACd,SACkD;AAClD,SAAO,iBAAiB,OAAO,KAAK,QAAQ,QAAQ,YAAY;AAClE;;;ACpDO,IAAM,aAAN,MAAM,oBAAmB,MAAM;AAAA,EAMpC,YACE,QACA,MACA,MACA,SACO,KACP,SACA;AACA;AAAA,MACE,WACE,cAAc,MAAM,OAAO,GAAG,KAAK,sBAAQ,KAAK,UAAU,IAAI,CAAC;AAAA,IACnE;AANO;AAOP,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,OAAa,aACX,UACA,KACqB;AAAA;AACrB,YAAM,SAAS,SAAS;AACxB,YAAM,UAAU,OAAO,YAAY,CAAC,GAAG,SAAS,QAAQ,QAAQ,CAAC,CAAC;AAClE,UAAI,OAA2B;AAC/B,UAAI,OAA2B;AAE/B,YAAM,cAAc,SAAS,QAAQ,IAAI,cAAc;AACvD,UAAI,eAAe,YAAY,SAAS,kBAAkB,GAAG;AAC3D,eAAQ,MAAM,SAAS,KAAK;AAAA,MAC9B,OAAO;AACL,eAAO,MAAM,SAAS,KAAK;AAAA,MAC7B;AAEA,aAAO,IAAI,YAAW,QAAQ,MAAM,MAAM,SAAS,GAAG;AAAA,IACxD;AAAA;AACF;AAEO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,cAAc;AACZ,UAAM,4BAA4B;AAAA,EACpC;AACF;;;ACjDO,IAAM,kBAAkB;AACxB,IAAM,2BAA2B;AACjC,IAAM,gCAAgC;AACtC,IAAM,2BAA2B;AACjC,IAAM,0BAA0B;AAChC,IAAM,sBAAsB;AAC5B,IAAM,uBAAuB;AAC7B,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB;AAC1B,IAAM,sBAAsB;AAC5B,IAAM,mBAAmB;;;ACEhC,IAAM,0BAA0B,CAAC,GAAG;AAgB7B,IAAM,kBAAkB;AAAA,EAC7B,cAAc;AAAA,EACd,UAAU;AAAA,EACV,YAAY;AACd;AAEO,SAAS,uBACd,aACA,iBAAiC,iBACnB;AACd,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF,IAAI;AACJ,SAAO,IAAU,SAAsD;AA7CzE;AA8CI,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,UAAU,KAAK,CAAC;AAEtB,QAAI,QAAQ;AACZ,QAAI,UAAU;AAOd,WAAO,MAAM;AAEX,UAAI;AACF,cAAM,SAAS,MAAM,YAAY,GAAG,IAAI;AACxC,YAAI,OAAO,GAAI,QAAO;AAAA,YACjB,OAAM,MAAM,WAAW,aAAa,QAAQ,IAAI,SAAS,CAAC;AAAA,MACjE,SAAS,GAAG;AACV;AACA,aAAI,wCAAS,WAAT,mBAAiB,SAAS;AAC5B,gBAAM,IAAI,uBAAuB;AAAA,QACnC,WACE,aAAa,cACb,CAAC,wBAAwB,SAAS,EAAE,MAAM,KAC1C,EAAE,UAAU,OACZ,EAAE,SAAS,KACX;AAEA,gBAAM;AAAA,QACR,OAAO;AAGL,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AAGzD,kBAAQ,KAAK,IAAI,QAAQ,YAAY,QAAQ;AAE7C,cAAI,OAAO;AACT;AACA,oBAAQ,IAAI,kBAAkB,OAAO,UAAU,KAAK,IAAI;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAMA,IAAM,wBAAwB;AAAA,EAC5B,qBAAqB;AACvB;AAWO,SAAS,2BACd,aACA,kBAAwC,uBAC1B;AACd,QAAM,EAAE,oBAAoB,IAAI;AAEhC,MAAI;AAEJ,QAAM,iBAAiB,IAAU,SAAyC;AACxE,UAAM,MAAM,KAAK,CAAC,EAAE,SAAS;AAI7B,UAAM,oBAAoB,+CAAe,QAAQ,GAAG;AACpD,QAAI,mBAAmB;AACrB,aAAO;AAAA,IACT;AAEA,mDAAe;AAGf,UAAM,WAAW,MAAM,YAAY,GAAG,IAAI;AAC1C,UAAM,UAAU,gBAAgB,KAAK,QAAQ;AAC7C,QAAI,SAAS;AACX,sBAAgB,IAAI,cAAc;AAAA,QAChC;AAAA,QACA,uBAAuB;AAAA,QACvB,KAAK;AAAA,QACL,aAAa,KAAK,CAAC;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAlJA;AAoJA,IAAM,gBAAN,MAAoB;AAAA,EAUlB,YAAY,SAKT;AAfL;AACE,uBAAS;AACT,uBAAS;AACT,uBAAS,gBAAiB,oBAAI,IAG5B;AACF;AACA;AA5JF;AAoKI,uBAAK,eACH,aAAQ,gBAAR,YACC,IAAI,SAAmC,MAAM,GAAG,IAAI;AACvD,uBAAK,wBAAyB,QAAQ;AACtC,uBAAK,eAAgB,QAAQ,IAAI,SAAS;AAC1C,uBAAK,eAAgB,mBAAK;AAC1B,0BAAK,uCAAL,WAAe,QAAQ,KAAK,QAAQ;AAAA,EACtC;AAAA,EAEA,QAAc;AACZ,uBAAK,gBAAe,QAAQ,CAAC,CAAC,GAAG,OAAO,MAAM,QAAQ,MAAM,CAAC;AAAA,EAC/D;AAAA,EAEA,WAAW,MAA0D;AAjLvE;AAkLI,UAAM,MAAM,KAAK,CAAC,EAAE,SAAS;AAE7B,UAAM,WAAU,wBAAK,gBAAe,IAAI,GAAG,MAA3B,mBAA+B;AAI/C,QAAI,CAAC,WAAW,QAAQ,mBAAK,eAAe;AAC5C,uBAAK,gBAAe,OAAO,GAAG;AAG9B,YACG,KAAK,CAAC,aAAa;AAClB,YAAM,UAAU,gBAAgB,KAAK,QAAQ;AAC7C,yBAAK,eAAgB;AACrB,UACE,mBAAK,kBACL,CAAC,mBAAK,gBAAe,IAAI,mBAAK,cAAa,GAC3C;AACA,8BAAK,uCAAL,WAAe,mBAAK,gBAAe,KAAK,CAAC;AAAA,MAC3C;AAAA,IACF,CAAC,EACA,MAAM,MAAM;AAAA,IAAC,CAAC;AAEjB,WAAO;AAAA,EACT;AAuCF;AA5FW;AACA;AACA;AAIT;AACA;AARF;AAwDE,cAAS,YAAI,MAAsC;AA5MrD;AA6MI,QAAM,MAAM,KAAK,CAAC,EAAE,SAAS;AAG7B,MAAI,mBAAK,gBAAe,QAAQ,mBAAK,wBAAwB;AAI7D,QAAM,UAAU,IAAI,gBAAgB;AAEpC,MAAI;AACF,UAAM,UAAU,mBAAK,cAAL,WAAkB,KAAK,kCACjC,UAAK,CAAC,MAAN,YAAW,CAAC,IADqB;AAAA,MAErC,QAAQ,aAAa,UAAS,UAAK,CAAC,MAAN,mBAAS,MAAM;AAAA,IAC/C;AACA,uBAAK,gBAAe,IAAI,KAAK,CAAC,SAAS,OAAO,CAAC;AAC/C,YACG,KAAK,CAAC,aAAa;AAElB,UAAI,CAAC,SAAS,MAAM,QAAQ,OAAO,QAAS;AAE5C,YAAM,UAAU,gBAAgB,KAAK,QAAQ;AAG7C,UAAI,CAAC,WAAW,YAAY,KAAK;AAC/B,2BAAK,eAAgB;AACrB;AAAA,MACF;AAEA,yBAAK,eAAgB;AACrB,aAAO,sBAAK,uCAAL,WAAe,SAAS,KAAK,CAAC;AAAA,IACvC,CAAC,EACA,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB,SAAS,GAAG;AAAA,EAEZ;AACF;AAMF,SAAS,gBAAgB,KAAa,KAA8B;AAClE,QAAM,UAAU,IAAI,QAAQ,IAAI,eAAe;AAC/C,QAAM,aAAa,IAAI,QAAQ,IAAI,wBAAwB;AAC3D,QAAM,aAAa,IAAI,QAAQ,IAAI,uBAAuB;AAI1D,MAAI,CAAC,WAAW,CAAC,cAAc,WAAY;AAE3C,QAAM,UAAU,IAAI,IAAI,GAAG;AAI3B,MAAI,QAAQ,aAAa,IAAI,gBAAgB,EAAG;AAEhD,UAAQ,aAAa,IAAI,sBAAsB,OAAO;AACtD,UAAQ,aAAa,IAAI,oBAAoB,UAAU;AACvD,SAAO,QAAQ,SAAS;AAC1B;AAOA,SAAS,aACP,SACA,cACa;AACb,MAAI,CAAC,aAAc,QAAO,QAAQ;AAClC,MAAI,aAAa,QAAS,SAAQ,MAAM;AAAA;AAEtC,iBAAa,iBAAiB,SAAS,MAAM,QAAQ,MAAM,GAAG;AAAA,MAC5D,MAAM;AAAA,IACR,CAAC;AACH,SAAO,QAAQ;AACjB;;;AC1RA,IAAAC,eAAA;AAsIO,IAAM,cAAN,MAEP;AAAA,EA2BE,YAAY,SAA+C;AA7BtD;AAKL,uBAASA;AACT,uBAAS;AAET,uBAAS,cAAe,oBAAI,IAM1B;AACF,uBAAS,sBAAuB,oBAAI,IAGlC;AAEF;AACA;AACA;AAAA;AACA;AAAA,oCAAuB;AACvB,mCAAsB;AACtB;AACA;AACA;AAjKF;AAoKI,oBAAgB,OAAO;AACvB,SAAK,UAAU,iBAAE,WAAW,QAAS;AACrC,uBAAK,cAAc,UAAK,QAAQ,WAAb,YAAuB;AAC1C,uBAAK,kBAAmB;AACxB,uBAAK,UAAW,KAAK,QAAQ;AAC7B,uBAAK,gBAAiB,IAAI,cAAiB,QAAQ,MAAM;AAEzD,UAAM,mBACJ,aAAQ,gBAAR,YACC,IAAI,SAAmC,MAAM,GAAG,IAAI;AAEvD,UAAM,yBAAyB,uBAAuB,iBAAiB,kCACjE,aAAQ,mBAAR,YAA0B,kBADuC;AAAA,MAErE,iBAAiB,MAAM;AAjL7B,YAAAC,KAAAC;AAkLQ,2BAAK,YAAa;AAClB,SAAAA,OAAAD,MAAA,QAAQ,mBAAR,gBAAAA,IAAwB,oBAAxB,gBAAAC,IAAA,KAAAD;AAAA,MACF;AAAA,IACF,EAAC;AAED,uBAAKD,eAAe,2BAA2B,sBAAsB;AAErE,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,IAAI,UAAU;AACZ,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAAa;AACf,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAQ;AACV,WAAO,mBAAK;AAAA,EACd;AAAA,EAEM,QAAQ;AAAA;AAxMhB;AAyMI,yBAAK,aAAc;AAEnB,YAAM,EAAE,KAAK,OAAO,SAAS,OAAO,IAAI,KAAK;AAE7C,UAAI;AACF,eACG,EAAC,iCAAQ,YAAW,CAAC,mBAAK,gBAC3B,KAAK,QAAQ,WACb;AACA,gBAAM,WAAW,IAAI,IAAI,GAAG;AAC5B,cAAI,MAAO,UAAS,aAAa,IAAI,mBAAmB,KAAK;AAC7D,cAAI,WAAW,QAAQ,SAAS;AAC9B,qBAAS,aAAa,IAAI,qBAAqB,QAAQ,KAAK,GAAG,CAAC;AAClE,mBAAS,aAAa,IAAI,oBAAoB,mBAAK,YAAW;AAE9D,cAAI,mBAAK,cAAa;AACpB,qBAAS,aAAa,IAAI,kBAAkB,MAAM;AAClD,qBAAS,aAAa;AAAA,cACpB;AAAA,cACA,mBAAK;AAAA,YACP;AAAA,UACF;AAEA,cAAI,mBAAK,WAAU;AAEjB,qBAAS,aAAa,IAAI,sBAAsB,mBAAK,SAAS;AAAA,UAChE;AAEA,cAAI;AACJ,cAAI;AACF,uBAAW,MAAM,mBAAKA,eAAL,WAAkB,SAAS,SAAS,GAAG;AAAA,cACtD;AAAA,cACA,SAAS,KAAK,QAAQ;AAAA,YACxB;AACA,+BAAK,YAAa;AAAA,UACpB,SAAS,GAAG;AACV,gBAAI,aAAa,uBAAwB;AACzC,gBAAI,EAAE,aAAa,YAAa,OAAM;AACtC,gBAAI,EAAE,UAAU,KAAK;AAGnB,oBAAM,aAAa,EAAE,QAAQ,eAAe;AAC5C,oCAAK,kCAAL,WAAY;AACZ,oBAAM,sBAAK,oCAAL,WAAc,EAAE;AACtB;AAAA,YACF,WAAW,EAAE,UAAU,OAAO,EAAE,SAAS,KAAK;AAE5C,oCAAK,2DAAL,WAAqC;AACrC,oCAAK,mDAAL,WAA6B;AAI7B,oBAAM;AAAA,YACR;AAAA,UACF;AAEA,gBAAM,EAAE,SAAS,OAAO,IAAI;AAC5B,gBAAM,UAAU,QAAQ,IAAI,eAAe;AAC3C,cAAI,SAAS;AACX,+BAAK,UAAW;AAAA,UAClB;AAEA,gBAAM,aAAa,QAAQ,IAAI,wBAAwB;AACvD,cAAI,YAAY;AACd,+BAAK,aAAc;AAAA,UACrB;AAEA,gBAAM,kBAAkB,QAAQ,IAAI,wBAAwB;AAC5D,cAAI,iBAAiB;AACnB,+BAAK,kBAAmB;AAAA,UAC1B;AAEA,gBAAM,YAAY,MAAc;AAC9B,kBAAM,eAAe,QAAQ,IAAI,mBAAmB;AACpD,mBAAO,eAAe,KAAK,MAAM,YAAY,IAAI,CAAC;AAAA,UACpD;AACA,6BAAK,UAAU,wBAAK,aAAL,YAAgB,UAAU;AAEzC,gBAAM,WAAW,WAAW,MAAM,OAAO,MAAM,SAAS,KAAK;AAE7D,cAAI,WAAW,KAAK;AAElB,+BAAK,eAAgB,KAAK,IAAI;AAAA,UAChC;AAEA,gBAAM,QAAQ,mBAAK,gBAAe,MAAM,UAAU,mBAAK,QAAO;AAG9D,cAAI,MAAM,SAAS,GAAG;AACpB,kBAAM,eAAe,mBAAK;AAC1B,kBAAM,cAAc,MAAM,MAAM,SAAS,CAAC;AAC1C,gBAAI,kBAAkB,WAAW,GAAG;AAClC,iCAAK,eAAgB,KAAK,IAAI;AAC9B,iCAAK,aAAc;AAAA,YACrB;AAEA,kBAAM,sBAAK,oCAAL,WAAc;AACpB,gBAAI,CAAC,gBAAgB,mBAAK,cAAa;AACrC,oCAAK,sDAAL;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,2BAAK,QAAS;AAAA,MAChB,UAAE;AACA,2BAAK,YAAa;AAAA,MACpB;AAAA,IACF;AAAA;AAAA,EAEA,UACE,UACA,SACA;AACA,UAAM,iBAAiB,KAAK,OAAO;AAEnC,uBAAK,cAAa,IAAI,gBAAgB,CAAC,UAAU,OAAO,CAAC;AAEzD,WAAO,MAAM;AACX,yBAAK,cAAa,OAAO,cAAc;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,iBAAuB;AACrB,uBAAK,cAAa,MAAM;AAAA,EAC1B;AAAA,EAEA,wBACE,UACA,OACA;AACA,UAAM,iBAAiB,KAAK,OAAO;AAEnC,uBAAK,sBAAqB,IAAI,gBAAgB,CAAC,UAAU,KAAK,CAAC;AAE/D,WAAO,MAAM;AACX,yBAAK,sBAAqB,OAAO,cAAc;AAAA,IACjD;AAAA,EACF;AAAA,EAEA,oCAA0C;AACxC,uBAAK,sBAAqB,MAAM;AAAA,EAClC;AAAA;AAAA,EAGA,eAAmC;AACjC,WAAO,mBAAK;AAAA,EACd;AAAA;AAAA,EAGA,aAAqB;AACnB,QAAI,mBAAK,mBAAkB,OAAW,QAAO;AAC7C,WAAO,KAAK,IAAI,IAAI,mBAAK;AAAA,EAC3B;AAAA;AAAA,EAGA,cAAuB;AACrB,WAAO,mBAAK;AAAA,EACd;AAAA;AAAA,EAGA,YAAqB;AACnB,WAAO,CAAC,KAAK;AAAA,EACf;AA8CF;AA9QWA,gBAAA;AACA;AAEA;AAOA;AAKT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3BK;AAuOC,aAAQ,SAAC,UAAuC;AAAA;AACpD,UAAM,QAAQ;AAAA,MACZ,MAAM,KAAK,mBAAK,cAAa,OAAO,CAAC,EAAE,IAAI,CAAO,OAAmB,eAAnB,KAAmB,WAAnB,CAAC,UAAU,EAAE,GAAM;AACnE,YAAI;AACF,gBAAM,SAAS,QAAQ;AAAA,QACzB,SAAS,KAAK;AACZ,yBAAe,MAAM;AACnB,kBAAM;AAAA,UACR,CAAC;AAAA,QACH;AAAA,MACF,EAAC;AAAA,IACH;AAAA,EACF;AAAA;AAEA,4BAAuB,SAAC,OAAc;AACpC,qBAAK,cAAa,QAAQ,CAAC,CAAC,GAAG,OAAO,MAAM;AAC1C,uCAAU;AAAA,EACZ,CAAC;AACH;AAEA,+BAA0B,WAAG;AAC3B,qBAAK,sBAAqB,QAAQ,CAAC,CAAC,QAAQ,MAAM;AAChD,aAAS;AAAA,EACX,CAAC;AACH;AAEA,oCAA+B,SAAC,OAA2B;AACzD,qBAAK,sBAAqB;AAAA,IAAQ,CAAC,CAAC,GAAG,aAAa,MAClD,cAAc,KAAK;AAAA,EACrB;AACF;AAAA;AAAA;AAAA;AAAA;AAMA,WAAM,SAAC,SAAkB;AACvB,qBAAK,aAAc;AACnB,qBAAK,kBAAmB;AACxB,qBAAK,UAAW;AAChB,qBAAK,aAAc;AACnB,qBAAK,YAAa;AAClB,qBAAK,SAAU;AACjB;AAGF,SAAS,gBAAmB,SAA+C;AACzE,MAAI,CAAC,QAAQ,KAAK;AAChB,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AACA,MAAI,QAAQ,UAAU,EAAE,QAAQ,kBAAkB,cAAc;AAC9D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MACE,QAAQ,WAAW,UACnB,QAAQ,WAAW,QACnB,CAAC,QAAQ,SACT;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA;AACF;;;AC/aA,oBAAAG,eAAA,iCAAAC,SAAA;AAyCO,IAAM,QAAN,MAA0C;AAAA,EAS/C,YAAY,QAAiC;AATxC;AACL,uBAAS;AAET,uBAAS,OAAsB,oBAAI,IAAI;AACvC,uBAASD,eAAe,oBAAI,IAAqC;AAEjE,wDAA2C;AAC3C,uBAAAC,SAA6B;AAG3B,uBAAK,SAAU;AACf,uBAAK,SAAQ;AAAA,MACX,sBAAK,8BAAS,KAAK,IAAI;AAAA,MACvB,sBAAK,kCAAa,KAAK,IAAI;AAAA,IAC7B;AACA,UAAM,cAAc,mBAAK,SAAQ;AAAA,MAC/B,MAAM;AACJ,oBAAY;AAAA,MACd;AAAA,MACA,CAAC,MAAM;AACL,8BAAK,kCAAL,WAAkB;AAClB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,aAAsB;AACxB,WAAO,mBAAK,SAAQ;AAAA,EACtB;AAAA,EAEA,IAAI,QAA+B;AACjC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,mBAAK,SAAQ,YAAY;AAC3B,gBAAQ,KAAK,SAAS;AAAA,MACxB,OAAO;AACL,cAAM,cAAc,KAAK,UAAU,CAAC,cAAc;AAChD,sBAAY;AACZ,cAAI,mBAAKA,SAAQ,QAAO,mBAAKA,QAAM;AACnC,kBAAQ,SAAS;AAAA,QACnB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,IAAI,YAAY;AACd,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAQ;AACV,WAAO,mBAAKA;AAAA,EACd;AAAA;AAAA,EAGA,eAAmC;AACjC,WAAO,mBAAK,SAAQ,aAAa;AAAA,EACnC;AAAA;AAAA,EAGA,aAAa;AACX,WAAO,mBAAK,SAAQ,WAAW;AAAA,EACjC;AAAA;AAAA,EAGA,YAAY;AACV,WAAO,mBAAK,SAAQ,UAAU;AAAA,EAChC;AAAA;AAAA,EAGA,cAAuB;AACrB,WAAO,mBAAK,SAAQ,YAAY;AAAA,EAClC;AAAA,EAEA,UAAU,UAA+C;AACvD,UAAM,iBAAiB,KAAK,OAAO;AAEnC,uBAAKD,eAAa,IAAI,gBAAgB,QAAQ;AAE9C,WAAO,MAAM;AACX,yBAAKA,eAAa,OAAO,cAAc;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,iBAAuB;AACrB,uBAAKA,eAAa,MAAM;AAAA,EAC1B;AAAA,EAEA,IAAI,iBAAiB;AACnB,WAAO,mBAAKA,eAAa;AAAA,EAC3B;AAoEF;AA3JW;AAEA;AACAA,gBAAA;AAET;AACAC,UAAA;AAPK;AA0FL,aAAQ,SAAC,UAA8B;AACrC,MAAI,qBAAqB;AACzB,MAAI,aAAa;AACjB,MAAI,gBAAgB;AAEpB,WAAS,QAAQ,CAAC,YAAY;AAC5B,QAAI,gBAAgB,OAAO,GAAG;AAC5B,2BAAqB,CAAC,UAAU,UAAU,QAAQ,EAAE;AAAA,QAClD,QAAQ,QAAQ;AAAA,MAClB;AAEA,cAAQ,QAAQ,QAAQ,WAAW;AAAA,QACjC,KAAK;AACH,6BAAK,OAAM,IAAI,QAAQ,KAAK,QAAQ,KAAK;AACzC;AAAA,QACF,KAAK;AACH,6BAAK,OAAM,IAAI,QAAQ,KAAK,kCACvB,mBAAK,OAAM,IAAI,QAAQ,GAAG,IAC1B,QAAQ,MACZ;AACD;AAAA,QACF,KAAK;AACH,6BAAK,OAAM,OAAO,QAAQ,GAAG;AAC7B;AAAA,MACJ;AAAA,IACF;AAEA,QAAI,iBAAiB,OAAO,GAAG;AAC7B,cAAQ,QAAQ,QAAQ,SAAS;AAAA,QAC/B,KAAK;AACH,uBAAa;AACb,cAAI,CAAC,mBAAK,kCAAiC;AACzC,4BAAgB;AAAA,UAClB;AACA;AAAA,QACF,KAAK;AACH,6BAAK,OAAM,MAAM;AACjB,6BAAKA,SAAS;AACd,6BAAK,iCAAkC;AACvC,uBAAa;AACb,0BAAgB;AAChB;AAAA,MACJ;AAAA,IACF;AAAA,EACF,CAAC;AAID,MAAI,iBAAkB,cAAc,oBAAqB;AACvD,uBAAK,iCAAkC;AACvC,0BAAK,6BAAL;AAAA,EACF;AACF;AAEA,iBAAY,SAAC,GAAgB;AAC3B,MAAI,aAAa,YAAY;AAC3B,uBAAKA,SAAS;AACd,0BAAK,6BAAL;AAAA,EACF;AACF;AAEA,YAAO,WAAS;AACd,qBAAKD,eAAa,QAAQ,CAAC,aAAa;AACtC,aAAS,KAAK,SAAS;AAAA,EACzB,CAAC;AACH;","names":["key","value","_fetchClient","_a","_b","_subscribers","_error"]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/parser.ts","../../src/helpers.ts","../../src/error.ts","../../src/constants.ts","../../src/fetch.ts","../../src/client.ts","../../src/shape.ts"],"sourcesContent":["export * from './client'\nexport * from './shape'\nexport * from './types'\nexport { isChangeMessage, isControlMessage } from './helpers'\nexport { FetchError } from './error'\nexport { type BackoffOptions, BackoffDefaults } from './fetch'\n","import { ColumnInfo, GetExtensions, Message, Row, Schema, Value } from './types'\n\ntype NullToken = null | `NULL`\ntype Token = Exclude<string, NullToken>\ntype NullableToken = Token | NullToken\nexport type ParseFunction<Extensions = never> = (\n value: Token,\n additionalInfo?: Omit<ColumnInfo, `type` | `dims`>\n) => Value<Extensions>\ntype NullableParseFunction<Extensions = never> = (\n value: NullableToken,\n additionalInfo?: Omit<ColumnInfo, `type` | `dims`>\n) => Value<Extensions>\n/**\n * @typeParam Extensions - Additional types that can be parsed by this parser beyond the standard SQL types.\n * Defaults to no additional types.\n */\nexport type Parser<Extensions = never> = {\n [key: string]: ParseFunction<Extensions>\n}\n\nconst parseNumber = (value: string) => Number(value)\nconst parseBool = (value: string) => value === `true` || value === `t`\nconst parseBigInt = (value: string) => BigInt(value)\nconst parseJson = (value: string) => JSON.parse(value)\nconst identityParser: ParseFunction = (v: string) => v\n\nexport const defaultParser: Parser = {\n int2: parseNumber,\n int4: parseNumber,\n int8: parseBigInt,\n bool: parseBool,\n float4: parseNumber,\n float8: parseNumber,\n json: parseJson,\n jsonb: parseJson,\n}\n\n// Taken from: https://github.com/electric-sql/pglite/blob/main/packages/pglite/src/types.ts#L233-L279\nexport function pgArrayParser<Extensions>(\n value: Token,\n parser?: ParseFunction<Extensions>\n): Value<Extensions> {\n let i = 0\n let char = null\n let str = ``\n let quoted = false\n let last = 0\n let p: string | undefined = undefined\n\n function loop(x: string): Array<Value<Extensions>> {\n const xs = []\n for (; i < x.length; i++) {\n char = x[i]\n if (quoted) {\n if (char === `\\\\`) {\n str += x[++i]\n } else if (char === `\"`) {\n xs.push(parser ? parser(str) : str)\n str = ``\n quoted = x[i + 1] === `\"`\n last = i + 2\n } else {\n str += char\n }\n } else if (char === `\"`) {\n quoted = true\n } else if (char === `{`) {\n last = ++i\n xs.push(loop(x))\n } else if (char === `}`) {\n quoted = false\n last < i &&\n xs.push(parser ? parser(x.slice(last, i)) : x.slice(last, i))\n last = i + 1\n break\n } else if (char === `,` && p !== `}` && p !== `\"`) {\n xs.push(parser ? parser(x.slice(last, i)) : x.slice(last, i))\n last = i + 1\n }\n p = char\n }\n last < i &&\n xs.push(parser ? parser(x.slice(last, i + 1)) : x.slice(last, i + 1))\n return xs\n }\n\n return loop(value)[0]\n}\n\nexport class MessageParser<T extends Row<unknown>> {\n private parser: Parser<GetExtensions<T>>\n constructor(parser?: Parser<GetExtensions<T>>) {\n // Merge the provided parser with the default parser\n // to use the provided parser whenever defined\n // and otherwise fall back to the default parser\n this.parser = { ...defaultParser, ...parser }\n }\n\n parse(messages: string, schema: Schema): Message<T>[] {\n return JSON.parse(messages, (key, value) => {\n // typeof value === `object` && value !== null\n // is needed because there could be a column named `value`\n // and the value associated to that column will be a string or null.\n // But `typeof null === 'object'` so we need to make an explicit check.\n if (key === `value` && typeof value === `object` && value !== null) {\n // Parse the row values\n const row = value as Record<string, Value<GetExtensions<T>>>\n Object.keys(row).forEach((key) => {\n row[key] = this.parseRow(key, row[key] as NullableToken, schema)\n })\n }\n return value\n }) as Message<T>[]\n }\n\n // Parses the message values using the provided parser based on the schema information\n private parseRow(\n key: string,\n value: NullableToken,\n schema: Schema\n ): Value<GetExtensions<T>> {\n const columnInfo = schema[key]\n if (!columnInfo) {\n // We don't have information about the value\n // so we just return it\n return value\n }\n\n // Copy the object but don't include `dimensions` and `type`\n const { type: typ, dims: dimensions, ...additionalInfo } = columnInfo\n\n // Pick the right parser for the type\n // and support parsing null values if needed\n // if no parser is provided for the given type, just return the value as is\n const typeParser = this.parser[typ] ?? identityParser\n const parser = makeNullableParser(typeParser, columnInfo, key)\n\n if (dimensions && dimensions > 0) {\n // It's an array\n const nullablePgArrayParser = makeNullableParser(\n (value, _) => pgArrayParser(value, parser),\n columnInfo,\n key\n )\n return nullablePgArrayParser(value)\n }\n\n return parser(value, additionalInfo)\n }\n}\n\nfunction makeNullableParser<Extensions>(\n parser: ParseFunction<Extensions>,\n columnInfo: ColumnInfo,\n columnName?: string\n): NullableParseFunction<Extensions> {\n const isNullable = !(columnInfo.not_null ?? false)\n // The sync service contains `null` value for a column whose value is NULL\n // but if the column value is an array that contains a NULL value\n // then it will be included in the array string as `NULL`, e.g.: `\"{1,NULL,3}\"`\n return (value: NullableToken) => {\n if (isPgNull(value)) {\n if (!isNullable) {\n throw new Error(`Column ${columnName ?? `unknown`} is not nullable`)\n }\n return null\n }\n return parser(value, columnInfo)\n }\n}\n\nfunction isPgNull(value: NullableToken): value is NullToken {\n return value === null || value === `NULL`\n}\n","import { ChangeMessage, ControlMessage, Message, Row } from './types'\n\n/**\n * Type guard for checking {@link Message} is {@link ChangeMessage}.\n *\n * See [TS docs](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards)\n * for information on how to use type guards.\n *\n * @param message - the message to check\n * @returns true if the message is a {@link ChangeMessage}\n *\n * @example\n * ```ts\n * if (isChangeMessage(message)) {\n * const msgChng: ChangeMessage = message // Ok\n * const msgCtrl: ControlMessage = message // Err, type mismatch\n * }\n * ```\n */\nexport function isChangeMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ChangeMessage<T> {\n return `key` in message\n}\n\n/**\n * Type guard for checking {@link Message} is {@link ControlMessage}.\n *\n * See [TS docs](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards)\n * for information on how to use type guards.\n *\n * @param message - the message to check\n * @returns true if the message is a {@link ControlMessage}\n *\n * * @example\n * ```ts\n * if (isControlMessage(message)) {\n * const msgChng: ChangeMessage = message // Err, type mismatch\n * const msgCtrl: ControlMessage = message // Ok\n * }\n * ```\n */\nexport function isControlMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ControlMessage {\n return !isChangeMessage(message)\n}\n\nexport function isUpToDateMessage<T extends Row<unknown> = Row>(\n message: Message<T>\n): message is ControlMessage & { up_to_date: true } {\n return isControlMessage(message) && message.headers.control === `up-to-date`\n}\n","export class FetchError extends Error {\n status: number\n text?: string\n json?: object\n headers: Record<string, string>\n\n constructor(\n status: number,\n text: string | undefined,\n json: object | undefined,\n headers: Record<string, string>,\n public url: string,\n message?: string\n ) {\n super(\n message ||\n `HTTP Error ${status} at ${url}: ${text ?? JSON.stringify(json)}`\n )\n this.name = `FetchError`\n this.status = status\n this.text = text\n this.json = json\n this.headers = headers\n }\n\n static async fromResponse(\n response: Response,\n url: string\n ): Promise<FetchError> {\n const status = response.status\n const headers = Object.fromEntries([...response.headers.entries()])\n let text: string | undefined = undefined\n let json: object | undefined = undefined\n\n const contentType = response.headers.get(`content-type`)\n if (contentType && contentType.includes(`application/json`)) {\n json = (await response.json()) as object\n } else {\n text = await response.text()\n }\n\n return new FetchError(status, text, json, headers, url)\n }\n}\n\nexport class FetchBackoffAbortError extends Error {\n constructor() {\n super(`Fetch with backoff aborted`)\n }\n}\n","export const LIVE_CACHE_BUSTER_HEADER = `electric-cursor`\nexport const SHAPE_HANDLE_HEADER = `electric-handle`\nexport const CHUNK_LAST_OFFSET_HEADER = `electric-offset`\nexport const SHAPE_SCHEMA_HEADER = `electric-schema`\nexport const CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`\nexport const DATABASE_ID_QUERY_PARAM = `database_id`\nexport const COLUMNS_QUERY_PARAM = `columns`\nexport const LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`\nexport const SHAPE_HANDLE_QUERY_PARAM = `handle`\nexport const LIVE_QUERY_PARAM = `live`\nexport const OFFSET_QUERY_PARAM = `offset`\nexport const TABLE_QUERY_PARAM = `table`\nexport const WHERE_QUERY_PARAM = `where`\nexport const REPLICA_PARAM = `replica`\n","import {\n CHUNK_LAST_OFFSET_HEADER,\n CHUNK_UP_TO_DATE_HEADER,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n SHAPE_HANDLE_HEADER,\n SHAPE_HANDLE_QUERY_PARAM,\n} from './constants'\nimport { FetchError, FetchBackoffAbortError } from './error'\n\n// Some specific 4xx and 5xx HTTP status codes that we definitely\n// want to retry\nconst HTTP_RETRY_STATUS_CODES = [429]\n\nexport interface BackoffOptions {\n /**\n * Initial delay before retrying in milliseconds\n */\n initialDelay: number\n /**\n * Maximum retry delay in milliseconds\n */\n maxDelay: number\n multiplier: number\n onFailedAttempt?: () => void\n debug?: boolean\n}\n\nexport const BackoffDefaults = {\n initialDelay: 100,\n maxDelay: 10_000,\n multiplier: 1.3,\n}\n\nexport function createFetchWithBackoff(\n fetchClient: typeof fetch,\n backoffOptions: BackoffOptions = BackoffDefaults\n): typeof fetch {\n const {\n initialDelay,\n maxDelay,\n multiplier,\n debug = false,\n onFailedAttempt,\n } = backoffOptions\n return async (...args: Parameters<typeof fetch>): Promise<Response> => {\n const url = args[0]\n const options = args[1]\n\n let delay = initialDelay\n let attempt = 0\n\n /* eslint-disable no-constant-condition -- we re-fetch the shape log\n * continuously until we get a non-ok response. For recoverable errors,\n * we retry the fetch with exponential backoff. Users can pass in an\n * AbortController to abort the fetching an any point.\n * */\n while (true) {\n /* eslint-enable no-constant-condition */\n try {\n const result = await fetchClient(...args)\n if (result.ok) return result\n else throw await FetchError.fromResponse(result, url.toString())\n } catch (e) {\n onFailedAttempt?.()\n if (options?.signal?.aborted) {\n throw new FetchBackoffAbortError()\n } else if (\n e instanceof FetchError &&\n !HTTP_RETRY_STATUS_CODES.includes(e.status) &&\n e.status >= 400 &&\n e.status < 500\n ) {\n // Any client errors cannot be backed off on, leave it to the caller to handle.\n throw e\n } else {\n // Exponentially backoff on errors.\n // Wait for the current delay duration\n await new Promise((resolve) => setTimeout(resolve, delay))\n\n // Increase the delay for the next attempt\n delay = Math.min(delay * multiplier, maxDelay)\n\n if (debug) {\n attempt++\n console.log(`Retry attempt #${attempt} after ${delay}ms`)\n }\n }\n }\n }\n }\n}\n\ninterface ChunkPrefetchOptions {\n maxChunksToPrefetch: number\n}\n\nconst ChunkPrefetchDefaults = {\n maxChunksToPrefetch: 2,\n}\n\n/**\n * Creates a fetch client that prefetches subsequent log chunks for\n * consumption by the shape stream without waiting for the chunk bodies\n * themselves to be loaded.\n *\n * @param fetchClient the client to wrap\n * @param prefetchOptions options to configure prefetching\n * @returns wrapped client with prefetch capabilities\n */\nexport function createFetchWithChunkBuffer(\n fetchClient: typeof fetch,\n prefetchOptions: ChunkPrefetchOptions = ChunkPrefetchDefaults\n): typeof fetch {\n const { maxChunksToPrefetch } = prefetchOptions\n\n let prefetchQueue: PrefetchQueue\n\n const prefetchClient = async (...args: Parameters<typeof fetchClient>) => {\n const url = args[0].toString()\n\n // try to consume from the prefetch queue first, and if request is\n // not present abort the prefetch queue as it must no longer be valid\n const prefetchedRequest = prefetchQueue?.consume(...args)\n if (prefetchedRequest) {\n return prefetchedRequest\n }\n\n prefetchQueue?.abort()\n\n // perform request and fire off prefetch queue if request is eligible\n const response = await fetchClient(...args)\n const nextUrl = getNextChunkUrl(url, response)\n if (nextUrl) {\n prefetchQueue = new PrefetchQueue({\n fetchClient,\n maxPrefetchedRequests: maxChunksToPrefetch,\n url: nextUrl,\n requestInit: args[1],\n })\n }\n\n return response\n }\n\n return prefetchClient\n}\n\nclass PrefetchQueue {\n readonly #fetchClient: typeof fetch\n readonly #maxPrefetchedRequests: number\n readonly #prefetchQueue = new Map<\n string,\n [Promise<Response>, AbortController]\n >()\n #queueHeadUrl: string | void\n #queueTailUrl: string | void\n\n constructor(options: {\n url: Parameters<typeof fetch>[0]\n requestInit: Parameters<typeof fetch>[1]\n maxPrefetchedRequests: number\n fetchClient?: typeof fetch\n }) {\n this.#fetchClient =\n options.fetchClient ??\n ((...args: Parameters<typeof fetch>) => fetch(...args))\n this.#maxPrefetchedRequests = options.maxPrefetchedRequests\n this.#queueHeadUrl = options.url.toString()\n this.#queueTailUrl = this.#queueHeadUrl\n this.#prefetch(options.url, options.requestInit)\n }\n\n abort(): void {\n this.#prefetchQueue.forEach(([_, aborter]) => aborter.abort())\n }\n\n consume(...args: Parameters<typeof fetch>): Promise<Response> | void {\n const url = args[0].toString()\n\n const request = this.#prefetchQueue.get(url)?.[0]\n // only consume if request is in queue and is the queue \"head\"\n // if request is in the queue but not the head, the queue is being\n // consumed out of order and should be restarted\n if (!request || url !== this.#queueHeadUrl) return\n this.#prefetchQueue.delete(url)\n\n // fire off new prefetch since request has been consumed\n request\n .then((response) => {\n const nextUrl = getNextChunkUrl(url, response)\n this.#queueHeadUrl = nextUrl\n if (\n this.#queueTailUrl &&\n !this.#prefetchQueue.has(this.#queueTailUrl)\n ) {\n this.#prefetch(this.#queueTailUrl, args[1])\n }\n })\n .catch(() => {})\n\n return request\n }\n\n #prefetch(...args: Parameters<typeof fetch>): void {\n const url = args[0].toString()\n\n // only prefetch when queue is not full\n if (this.#prefetchQueue.size >= this.#maxPrefetchedRequests) return\n\n // initialize aborter per request, to avoid aborting consumed requests that\n // are still streaming their bodies to the consumer\n const aborter = new AbortController()\n\n try {\n const request = this.#fetchClient(url, {\n ...(args[1] ?? {}),\n signal: chainAborter(aborter, args[1]?.signal),\n })\n this.#prefetchQueue.set(url, [request, aborter])\n request\n .then((response) => {\n // only keep prefetching if response chain is uninterrupted\n if (!response.ok || aborter.signal.aborted) return\n\n const nextUrl = getNextChunkUrl(url, response)\n\n // only prefetch when there is a next URL\n if (!nextUrl || nextUrl === url) {\n this.#queueTailUrl = undefined\n return\n }\n\n this.#queueTailUrl = nextUrl\n return this.#prefetch(nextUrl, args[1])\n })\n .catch(() => {})\n } catch (_) {\n // ignore prefetch errors\n }\n }\n}\n\n/**\n * Generate the next chunk's URL if the url and response are valid\n */\nfunction getNextChunkUrl(url: string, res: Response): string | void {\n const shapeHandle = res.headers.get(SHAPE_HANDLE_HEADER)\n const lastOffset = res.headers.get(CHUNK_LAST_OFFSET_HEADER)\n const isUpToDate = res.headers.has(CHUNK_UP_TO_DATE_HEADER)\n\n // only prefetch if shape handle and offset for next chunk are available, and\n // response is not already up-to-date\n if (!shapeHandle || !lastOffset || isUpToDate) return\n\n const nextUrl = new URL(url)\n\n // don't prefetch live requests, rushing them will only\n // potentially miss more recent data\n if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return\n\n nextUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, shapeHandle)\n nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset)\n return nextUrl.toString()\n}\n\n/**\n * Chains an abort controller on an optional source signal's\n * aborted state - if the source signal is aborted, the provided abort\n * controller will also abort\n */\nfunction chainAborter(\n aborter: AbortController,\n sourceSignal?: AbortSignal | null\n): AbortSignal {\n if (!sourceSignal) return aborter.signal\n if (sourceSignal.aborted) aborter.abort()\n else\n sourceSignal.addEventListener(`abort`, () => aborter.abort(), {\n once: true,\n })\n return aborter.signal\n}\n","import {\n Message,\n Offset,\n Schema,\n Row,\n MaybePromise,\n GetExtensions,\n} from './types'\nimport { MessageParser, Parser } from './parser'\nimport { isUpToDateMessage } from './helpers'\nimport { FetchError, FetchBackoffAbortError } from './error'\nimport {\n BackoffDefaults,\n BackoffOptions,\n createFetchWithBackoff,\n createFetchWithChunkBuffer,\n} from './fetch'\nimport {\n CHUNK_LAST_OFFSET_HEADER,\n LIVE_CACHE_BUSTER_HEADER,\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n COLUMNS_QUERY_PARAM,\n LIVE_QUERY_PARAM,\n OFFSET_QUERY_PARAM,\n SHAPE_HANDLE_HEADER,\n SHAPE_HANDLE_QUERY_PARAM,\n SHAPE_SCHEMA_HEADER,\n WHERE_QUERY_PARAM,\n DATABASE_ID_QUERY_PARAM,\n TABLE_QUERY_PARAM,\n REPLICA_PARAM,\n} from './constants'\n\ntype Replica = `full` | `default`\n\n/**\n * Options for constructing a ShapeStream.\n */\nexport interface ShapeStreamOptions<T = never> {\n /**\n * The full URL to where the Shape is served. This can either be the Electric server\n * directly or a proxy. E.g. for a local Electric instance, you might set `http://localhost:3000/v1/shape`\n */\n url: string\n\n /**\n * Which database to use.\n * This is optional unless Electric is used with multiple databases.\n */\n databaseId?: string\n\n /**\n * The root table for the shape. Passed as a query parameter. Not required if you set the table in your proxy.\n */\n table?: string\n\n /**\n * The where clauses for the shape.\n */\n where?: string\n\n /**\n * The columns to include in the shape.\n * Must include primary keys, and can only inlude valid columns.\n */\n columns?: string[]\n\n /**\n * If `replica` is `default` (the default) then Electric will only send the\n * changed columns in an update.\n *\n * If it's `full` Electric will send the entire row with both changed and\n * unchanged values.\n *\n * Setting `replica` to `full` will obviously result in higher bandwidth\n * usage and so is not recommended.\n */\n replica?: Replica\n /**\n * The \"offset\" on the shape log. This is typically not set as the ShapeStream\n * will handle this automatically. A common scenario where you might pass an offset\n * is if you're maintaining a local cache of the log. If you've gone offline\n * and are re-starting a ShapeStream to catch-up to the latest state of the Shape,\n * you'd pass in the last offset and shapeHandle you'd seen from the Electric server\n * so it knows at what point in the shape to catch you up from.\n */\n offset?: Offset\n /**\n * Similar to `offset`, this isn't typically used unless you're maintaining\n * a cache of the shape log.\n */\n shapeHandle?: string\n backoffOptions?: BackoffOptions\n\n /**\n * HTTP headers to attach to requests made by the client.\n * Can be used for adding authentication headers.\n */\n headers?: Record<string, string>\n\n /**\n * Automatically fetch updates to the Shape. If you just want to sync the current\n * shape and stop, pass false.\n */\n subscribe?: boolean\n signal?: AbortSignal\n fetchClient?: typeof fetch\n parser?: Parser<T>\n}\n\nexport interface ShapeStreamInterface<T extends Row<unknown> = Row> {\n subscribe(\n callback: (messages: Message<T>[]) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ): void\n unsubscribeAllUpToDateSubscribers(): void\n unsubscribeAll(): void\n subscribeOnceToUpToDate(\n callback: () => MaybePromise<void>,\n error: (err: FetchError | Error) => void\n ): () => void\n\n isLoading(): boolean\n lastSyncedAt(): number | undefined\n lastSynced(): number\n isConnected(): boolean\n\n isUpToDate: boolean\n shapeHandle?: string\n}\n\n/**\n * Reads updates to a shape from Electric using HTTP requests and long polling. Notifies subscribers\n * when new messages come in. Doesn't maintain any history of the\n * log but does keep track of the offset position and is the best way\n * to consume the HTTP `GET /v1/shape` api.\n *\n * @constructor\n * @param {ShapeStreamOptions} options - configure the shape stream\n * @example\n * Register a callback function to subscribe to the messages.\n * ```\n * const stream = new ShapeStream(options)\n * stream.subscribe(messages => {\n * // messages is 1 or more row updates\n * })\n * ```\n *\n * To abort the stream, abort the `signal`\n * passed in via the `ShapeStreamOptions`.\n * ```\n * const aborter = new AbortController()\n * const issueStream = new ShapeStream({\n * url: `${BASE_URL}/${table}`\n * subscribe: true,\n * signal: aborter.signal,\n * })\n * // Later...\n * aborter.abort()\n * ```\n */\n\nexport class ShapeStream<T extends Row<unknown> = Row>\n implements ShapeStreamInterface<T>\n{\n static readonly Replica = {\n FULL: `full` as Replica,\n DEFAULT: `default` as Replica,\n }\n\n readonly options: ShapeStreamOptions<GetExtensions<T>>\n\n readonly #fetchClient: typeof fetch\n readonly #messageParser: MessageParser<T>\n\n readonly #subscribers = new Map<\n number,\n [\n (messages: Message<T>[]) => MaybePromise<void>,\n ((error: Error) => void) | undefined,\n ]\n >()\n readonly #upToDateSubscribers = new Map<\n number,\n [() => void, (error: FetchError | Error) => void]\n >()\n\n #lastOffset: Offset\n #liveCacheBuster: string // Seconds since our Electric Epoch 😎\n #lastSyncedAt?: number // unix time\n #isUpToDate: boolean = false\n #connected: boolean = false\n #shapeHandle?: string\n #databaseId?: string\n #schema?: Schema\n #error?: unknown\n #replica?: Replica\n\n constructor(options: ShapeStreamOptions<GetExtensions<T>>) {\n validateOptions(options)\n this.options = { subscribe: true, ...options }\n this.#lastOffset = this.options.offset ?? `-1`\n this.#liveCacheBuster = ``\n this.#shapeHandle = this.options.shapeHandle\n this.#databaseId = this.options.databaseId\n this.#messageParser = new MessageParser<T>(options.parser)\n this.#replica = this.options.replica\n\n const baseFetchClient =\n options.fetchClient ??\n ((...args: Parameters<typeof fetch>) => fetch(...args))\n\n const fetchWithBackoffClient = createFetchWithBackoff(baseFetchClient, {\n ...(options.backoffOptions ?? BackoffDefaults),\n onFailedAttempt: () => {\n this.#connected = false\n options.backoffOptions?.onFailedAttempt?.()\n },\n })\n\n this.#fetchClient = createFetchWithChunkBuffer(fetchWithBackoffClient)\n\n this.start()\n }\n\n get shapeHandle() {\n return this.#shapeHandle\n }\n\n get isUpToDate() {\n return this.#isUpToDate\n }\n\n get error() {\n return this.#error\n }\n\n async start() {\n this.#isUpToDate = false\n\n const { url, table, where, columns, signal } = this.options\n\n try {\n while (\n (!signal?.aborted && !this.#isUpToDate) ||\n this.options.subscribe\n ) {\n const fetchUrl = new URL(url)\n if (table) fetchUrl.searchParams.set(TABLE_QUERY_PARAM, table)\n if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where)\n if (columns && columns.length > 0)\n fetchUrl.searchParams.set(COLUMNS_QUERY_PARAM, columns.join(`,`))\n fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, this.#lastOffset)\n\n if (this.#isUpToDate) {\n fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`)\n fetchUrl.searchParams.set(\n LIVE_CACHE_BUSTER_QUERY_PARAM,\n this.#liveCacheBuster\n )\n }\n\n if (this.#shapeHandle) {\n // This should probably be a header for better cache breaking?\n fetchUrl.searchParams.set(\n SHAPE_HANDLE_QUERY_PARAM,\n this.#shapeHandle!\n )\n }\n\n if (this.#databaseId) {\n fetchUrl.searchParams.set(DATABASE_ID_QUERY_PARAM, this.#databaseId!)\n }\n\n if (\n (this.#replica ?? ShapeStream.Replica.DEFAULT) !=\n ShapeStream.Replica.DEFAULT\n ) {\n fetchUrl.searchParams.set(REPLICA_PARAM, this.#replica as string)\n }\n\n let response!: Response\n try {\n response = await this.#fetchClient(fetchUrl.toString(), {\n signal,\n headers: this.options.headers,\n })\n this.#connected = true\n } catch (e) {\n if (e instanceof FetchBackoffAbortError) break // interrupted\n if (!(e instanceof FetchError)) throw e // should never happen\n if (e.status == 409) {\n // Upon receiving a 409, we should start from scratch\n // with the newly provided shape handle\n const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER]\n this.#reset(newShapeHandle)\n await this.#publish(e.json as Message<T>[])\n continue\n } else if (e.status >= 400 && e.status < 500) {\n // Notify subscribers\n this.#sendErrorToUpToDateSubscribers(e)\n this.#sendErrorToSubscribers(e)\n\n // 400 errors are not actionable without additional user input,\n // so we exit the loop\n throw e\n }\n }\n\n const { headers, status } = response\n const shapeHandle = headers.get(SHAPE_HANDLE_HEADER)\n if (shapeHandle) {\n this.#shapeHandle = shapeHandle\n }\n\n const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER)\n if (lastOffset) {\n this.#lastOffset = lastOffset as Offset\n }\n\n const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER)\n if (liveCacheBuster) {\n this.#liveCacheBuster = liveCacheBuster\n }\n\n const getSchema = (): Schema => {\n const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER)\n return schemaHeader ? JSON.parse(schemaHeader) : {}\n }\n this.#schema = this.#schema ?? getSchema()\n\n const messages = status === 204 ? `[]` : await response.text()\n\n if (status === 204) {\n // There's no content so we are live and up to date\n this.#lastSyncedAt = Date.now()\n }\n\n const batch = this.#messageParser.parse(messages, this.#schema)\n\n // Update isUpToDate\n if (batch.length > 0) {\n const prevUpToDate = this.#isUpToDate\n const lastMessage = batch[batch.length - 1]\n if (isUpToDateMessage(lastMessage)) {\n this.#lastSyncedAt = Date.now()\n this.#isUpToDate = true\n }\n\n await this.#publish(batch)\n if (!prevUpToDate && this.#isUpToDate) {\n this.#notifyUpToDateSubscribers()\n }\n }\n }\n } catch (err) {\n this.#error = err\n } finally {\n this.#connected = false\n }\n }\n\n subscribe(\n callback: (messages: Message<T>[]) => MaybePromise<void>,\n onError?: (error: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, [callback, onError])\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n subscribeOnceToUpToDate(\n callback: () => MaybePromise<void>,\n error: (err: FetchError | Error) => void\n ) {\n const subscriptionId = Math.random()\n\n this.#upToDateSubscribers.set(subscriptionId, [callback, error])\n\n return () => {\n this.#upToDateSubscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAllUpToDateSubscribers(): void {\n this.#upToDateSubscribers.clear()\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n return this.#lastSyncedAt\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced(): number {\n if (this.#lastSyncedAt === undefined) return Infinity\n return Date.now() - this.#lastSyncedAt\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#connected\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading(): boolean {\n return !this.isUpToDate\n }\n\n async #publish(messages: Message<T>[]): 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 #sendErrorToSubscribers(error: Error) {\n this.#subscribers.forEach(([_, errorFn]) => {\n errorFn?.(error)\n })\n }\n\n #notifyUpToDateSubscribers() {\n this.#upToDateSubscribers.forEach(([callback]) => {\n callback()\n })\n }\n\n #sendErrorToUpToDateSubscribers(error: FetchError | Error) {\n this.#upToDateSubscribers.forEach(([_, errorCallback]) =>\n errorCallback(error)\n )\n }\n\n /**\n * Resets the state of the stream, optionally with a provided\n * shape handle\n */\n #reset(shapeHandle?: string) {\n this.#lastOffset = `-1`\n this.#liveCacheBuster = ``\n this.#shapeHandle = shapeHandle\n this.#isUpToDate = false\n this.#connected = false\n this.#schema = undefined\n }\n}\n\nfunction validateOptions<T>(options: Partial<ShapeStreamOptions<T>>): void {\n if (!options.url) {\n throw new Error(`Invalid shape options. It must provide the url`)\n }\n if (options.signal && !(options.signal instanceof AbortSignal)) {\n throw new Error(\n `Invalid signal option. It must be an instance of AbortSignal.`\n )\n }\n\n if (\n options.offset !== undefined &&\n options.offset !== `-1` &&\n !options.shapeHandle\n ) {\n throw new Error(\n `shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)`\n )\n }\n return\n}\n","import { Message, Row } from './types'\nimport { isChangeMessage, isControlMessage } from './helpers'\nimport { FetchError } from './error'\nimport { ShapeStreamInterface } from './client'\n\nexport type ShapeData<T extends Row<unknown> = Row> = Map<string, T>\nexport type ShapeChangedCallback<T extends Row<unknown> = Row> = (data: {\n value: ShapeData<T>\n rows: T[]\n}) => void\n\n/**\n * A Shape is an object that subscribes to a shape log,\n * keeps a materialised shape `.rows` in memory and\n * notifies subscribers when the value has changed.\n *\n * It can be used without a framework and as a primitive\n * to simplify developing framework hooks.\n *\n * @constructor\n * @param {ShapeStream<T extends Row>} - the underlying shape stream\n * @example\n * ```\n * const shapeStream = new ShapeStream<{ foo: number }>(url: `http://localhost:3000/v1/shape`, table: `foo`})\n * const shape = new Shape(shapeStream)\n * ```\n *\n * `rows` returns a promise that resolves the Shape data once the Shape has been\n * fully loaded (and when resuming from being offline):\n *\n * const rows = await shape.rows\n *\n * `currentRows` returns the current data synchronously:\n *\n * const rows = shape.currentRows\n *\n * Subscribe to updates. Called whenever the shape updates in Postgres.\n *\n * shape.subscribe(({ rows }) => {\n * console.log(rows)\n * })\n */\nexport class Shape<T extends Row<unknown> = Row> {\n readonly #stream: ShapeStreamInterface<T>\n\n readonly #data: ShapeData<T> = new Map()\n readonly #subscribers = new Map<number, ShapeChangedCallback<T>>()\n\n #hasNotifiedSubscribersUpToDate: boolean = false\n #error: FetchError | false = false\n\n constructor(stream: ShapeStreamInterface<T>) {\n this.#stream = stream\n this.#stream.subscribe(\n this.#process.bind(this),\n this.#handleError.bind(this)\n )\n const unsubscribe = this.#stream.subscribeOnceToUpToDate(\n () => {\n unsubscribe()\n },\n (e) => {\n this.#handleError(e)\n throw e\n }\n )\n }\n\n get isUpToDate(): boolean {\n return this.#stream.isUpToDate\n }\n\n get rows(): Promise<T[]> {\n return this.value.then((v) => Array.from(v.values()))\n }\n\n get currentRows(): T[] {\n return Array.from(this.currentValue.values())\n }\n\n get value(): Promise<ShapeData<T>> {\n return new Promise((resolve, reject) => {\n if (this.#stream.isUpToDate) {\n resolve(this.currentValue)\n } else {\n const unsubscribe = this.subscribe(({ value }) => {\n unsubscribe()\n if (this.#error) reject(this.#error)\n resolve(value)\n })\n }\n })\n }\n\n get currentValue() {\n return this.#data\n }\n\n get error() {\n return this.#error\n }\n\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt(): number | undefined {\n return this.#stream.lastSyncedAt()\n }\n\n /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */\n lastSynced() {\n return this.#stream.lastSynced()\n }\n\n /** True during initial fetch. False afterwise. */\n isLoading() {\n return this.#stream.isLoading()\n }\n\n /** Indicates if we are connected to the Electric sync service. */\n isConnected(): boolean {\n return this.#stream.isConnected()\n }\n\n subscribe(callback: ShapeChangedCallback<T>): () => void {\n const subscriptionId = Math.random()\n\n this.#subscribers.set(subscriptionId, callback)\n\n return () => {\n this.#subscribers.delete(subscriptionId)\n }\n }\n\n unsubscribeAll(): void {\n this.#subscribers.clear()\n }\n\n get numSubscribers() {\n return this.#subscribers.size\n }\n\n #process(messages: Message<T>[]): void {\n let dataMayHaveChanged = false\n let isUpToDate = false\n let newlyUpToDate = false\n\n messages.forEach((message) => {\n if (isChangeMessage(message)) {\n dataMayHaveChanged = [`insert`, `update`, `delete`].includes(\n message.headers.operation\n )\n\n switch (message.headers.operation) {\n case `insert`:\n this.#data.set(message.key, message.value)\n break\n case `update`:\n this.#data.set(message.key, {\n ...this.#data.get(message.key)!,\n ...message.value,\n })\n break\n case `delete`:\n this.#data.delete(message.key)\n break\n }\n }\n\n if (isControlMessage(message)) {\n switch (message.headers.control) {\n case `up-to-date`:\n isUpToDate = true\n if (!this.#hasNotifiedSubscribersUpToDate) {\n newlyUpToDate = true\n }\n break\n case `must-refetch`:\n this.#data.clear()\n this.#error = false\n this.#hasNotifiedSubscribersUpToDate = false\n isUpToDate = false\n newlyUpToDate = false\n break\n }\n }\n })\n\n // Always notify subscribers when the Shape first is up to date.\n // FIXME this would be cleaner with a simple state machine.\n if (newlyUpToDate || (isUpToDate && dataMayHaveChanged)) {\n this.#hasNotifiedSubscribersUpToDate = true\n this.#notify()\n }\n }\n\n #handleError(e: Error): void {\n if (e instanceof FetchError) {\n this.#error = e\n this.#notify()\n }\n }\n\n #notify(): void {\n this.#subscribers.forEach((callback) => {\n callback({ value: this.currentValue, rows: this.currentRows })\n })\n }\n}\n"],"mappings":"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","names":["key","value","_fetchClient","_a","_b","_subscribers","_error"]}
@@ -1,2 +1,2 @@
1
- var Be=Object.defineProperty,Le=Object.defineProperties;var Ye=Object.getOwnPropertyDescriptors;var z=Object.getOwnPropertySymbols;var Ee=Object.prototype.hasOwnProperty,ge=Object.prototype.propertyIsEnumerable;var Te=s=>{throw TypeError(s)};var me=(s,e,t)=>e in s?Be(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,T=(s,e)=>{for(var t in e||(e={}))Ee.call(e,t)&&me(s,t,e[t]);if(z)for(var t of z(e))ge.call(e,t)&&me(s,t,e[t]);return s},X=(s,e)=>Le(s,Ye(e));var ye=(s,e)=>{var t={};for(var r in s)Ee.call(s,r)&&e.indexOf(r)<0&&(t[r]=s[r]);if(s!=null&&z)for(var r of z(s))e.indexOf(r)<0&&ge.call(s,r)&&(t[r]=s[r]);return t};var le=(s,e,t)=>e.has(s)||Te("Cannot "+t);var n=(s,e,t)=>(le(s,e,"read from private field"),t?t.call(s):e.get(s)),f=(s,e,t)=>e.has(s)?Te("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(s):e.set(s,t),c=(s,e,t,r)=>(le(s,e,"write to private field"),r?r.call(s,t):e.set(s,t),t),p=(s,e,t)=>(le(s,e,"access private method"),t);var S=(s,e,t)=>new Promise((r,i)=>{var o=l=>{try{h(t.next(l))}catch(u){i(u)}},a=l=>{try{h(t.throw(l))}catch(u){i(u)}},h=l=>l.done?r(l.value):Promise.resolve(l.value).then(o,a);h((t=t.apply(s,e)).next())});var Z=s=>Number(s),je=s=>s==="true"||s==="t",qe=s=>BigInt(s),Pe=s=>JSON.parse(s),Ve=s=>s,Qe={int2:Z,int4:Z,int8:qe,bool:je,float4:Z,float8:Z,json:Pe,jsonb:Pe};function Ge(s,e){let t=0,r=null,i="",o=!1,a=0,h;function l(u){let d=[];for(;t<u.length;t++){if(r=u[t],o)r==="\\"?i+=u[++t]:r==='"'?(d.push(e?e(i):i),i="",o=u[t+1]==='"',a=t+2):i+=r;else if(r==='"')o=!0;else if(r==="{")a=++t,d.push(l(u));else if(r==="}"){o=!1,a<t&&d.push(e?e(u.slice(a,t)):u.slice(a,t)),a=t+1;break}else r===","&&h!=="}"&&h!=='"'&&(d.push(e?e(u.slice(a,t)):u.slice(a,t)),a=t+1);h=r}return a<t&&d.push(e?e(u.slice(a,t+1)):u.slice(a,t+1)),d}return l(s)[0]}var ee=class{constructor(e){this.parser=T(T({},Qe),e)}parse(e,t){return JSON.parse(e,(r,i)=>{if(r==="value"&&typeof i=="object"&&i!==null){let o=i;Object.keys(o).forEach(a=>{o[a]=this.parseRow(a,o[a],t)})}return i})}parseRow(e,t,r){var k;let i=r[e];if(!i)return t;let d=i,{type:o,dims:a}=d,h=ye(d,["type","dims"]),l=(k=this.parser[o])!=null?k:Ve,u=Re(l,i,e);return a&&a>0?Re((m,J)=>Ge(m,u),i,e)(t):u(t,h)}};function Re(s,e,t){var i;let r=!((i=e.not_null)!=null&&i);return o=>{if(We(o)){if(!r)throw new Error(`Column ${t!=null?t:"unknown"} is not nullable`);return null}return s(o,e)}}function We(s){return s===null||s==="NULL"}function te(s){return"key"in s}function se(s){return!te(s)}function Se(s){return se(s)&&s.headers.control==="up-to-date"}var P=class s extends Error{constructor(t,r,i,o,a,h){super(h||`HTTP Error ${t} at ${a}: ${r!=null?r:JSON.stringify(i)}`);this.url=a;this.name="FetchError",this.status=t,this.text=r,this.json=i,this.headers=o}static fromResponse(t,r){return S(this,null,function*(){let i=t.status,o=Object.fromEntries([...t.headers.entries()]),a,h,l=t.headers.get("content-type");return l&&l.includes("application/json")?h=yield t.json():a=yield t.text(),new s(i,a,h,o,r)})}},L=class extends Error{constructor(){super("Fetch with backoff aborted")}};var q="electric-shape-id",we="electric-next-cursor",Ae="cursor",re="electric-chunk-last-offset",_e="electric-chunk-up-to-date",xe="electric-schema",ne="shape_id",oe="offset",ke="where",Ce="columns",ie="live";var $e=[429],ce={initialDelay:100,maxDelay:1e4,multiplier:1.3};function Me(s,e=ce){let{initialDelay:t,maxDelay:r,multiplier:i,debug:o=!1,onFailedAttempt:a}=e;return(...h)=>S(this,null,function*(){var B;let l=h[0],u=h[1],d=t,k=0;for(;;)try{let m=yield s(...h);if(m.ok)return m;throw yield P.fromResponse(m,l.toString())}catch(m){if(a==null||a(),(B=u==null?void 0:u.signal)!=null&&B.aborted)throw new L;if(m instanceof P&&!$e.includes(m.status)&&m.status>=400&&m.status<500)throw m;yield new Promise(J=>setTimeout(J,d)),d=Math.min(d*i,r),o&&(k++,console.log(`Retry attempt #${k} after ${d}ms`))}})}var Je={maxChunksToPrefetch:2};function Ue(s,e=Je){let{maxChunksToPrefetch:t}=e,r;return(...o)=>S(this,null,function*(){let a=o[0].toString(),h=r==null?void 0:r.consume(...o);if(h)return h;r==null||r.abort();let l=yield s(...o),u=fe(a,l);return u&&(r=new ue({fetchClient:s,maxPrefetchedRequests:t,url:u,requestInit:o[1]})),l})}var V,Q,w,O,A,Y,ae,ue=class{constructor(e){f(this,Y);f(this,V);f(this,Q);f(this,w,new Map);f(this,O);f(this,A);var t;c(this,V,(t=e.fetchClient)!=null?t:(...r)=>fetch(...r)),c(this,Q,e.maxPrefetchedRequests),c(this,O,e.url.toString()),c(this,A,n(this,O)),p(this,Y,ae).call(this,e.url,e.requestInit)}abort(){n(this,w).forEach(([e,t])=>t.abort())}consume(...e){var i;let t=e[0].toString(),r=(i=n(this,w).get(t))==null?void 0:i[0];if(!(!r||t!==n(this,O)))return n(this,w).delete(t),r.then(o=>{let a=fe(t,o);c(this,O,a),n(this,A)&&!n(this,w).has(n(this,A))&&p(this,Y,ae).call(this,n(this,A),e[1])}).catch(()=>{}),r}};V=new WeakMap,Q=new WeakMap,w=new WeakMap,O=new WeakMap,A=new WeakMap,Y=new WeakSet,ae=function(...e){var i,o;let t=e[0].toString();if(n(this,w).size>=n(this,Q))return;let r=new AbortController;try{let a=n(this,V).call(this,t,X(T({},(i=e[1])!=null?i:{}),{signal:Ke(r,(o=e[1])==null?void 0:o.signal)}));n(this,w).set(t,[a,r]),a.then(h=>{if(!h.ok||r.signal.aborted)return;let l=fe(t,h);if(!l||l===t){c(this,A,void 0);return}return c(this,A,l),p(this,Y,ae).call(this,l,e[1])}).catch(()=>{})}catch(a){}};function fe(s,e){let t=e.headers.get(q),r=e.headers.get(re),i=e.headers.has(_e);if(!t||!r||i)return;let o=new URL(s);if(!o.searchParams.has(ie))return o.searchParams.set(ne,t),o.searchParams.set(oe,r),o.toString()}function Ke(s,e){return e&&(e.aborted?s.abort():e.addEventListener("abort",()=>s.abort(),{once:!0})),s.signal}var G,W,C,M,H,F,U,y,D,_,N,$,E,de,ve,Ie,Oe,He,De=class{constructor(e){f(this,E);f(this,G);f(this,W);f(this,C,new Map);f(this,M,new Map);f(this,H);f(this,F);f(this,U);f(this,y,!1);f(this,D,!1);f(this,_);f(this,N);f(this,$);var i,o,a;ze(e),this.options=T({subscribe:!0},e),c(this,H,(i=this.options.offset)!=null?i:"-1"),c(this,F,""),c(this,_,this.options.shapeId),c(this,W,new ee(e.parser));let t=(o=e.fetchClient)!=null?o:(...h)=>fetch(...h),r=Me(t,X(T({},(a=e.backoffOptions)!=null?a:ce),{onFailedAttempt:()=>{var h,l;c(this,D,!1),(l=(h=e.backoffOptions)==null?void 0:h.onFailedAttempt)==null||l.call(h)}}));c(this,G,Ue(r)),this.start()}get shapeId(){return n(this,_)}get isUpToDate(){return n(this,y)}get error(){return n(this,$)}start(){return S(this,null,function*(){var o;c(this,y,!1);let{url:e,where:t,columns:r,signal:i}=this.options;try{for(;!(i!=null&&i.aborted)&&!n(this,y)||this.options.subscribe;){let a=new URL(e);t&&a.searchParams.set(ke,t),r&&r.length>0&&a.searchParams.set(Ce,r.join(",")),a.searchParams.set(oe,n(this,H)),n(this,y)&&(a.searchParams.set(ie,"true"),a.searchParams.set(Ae,n(this,F))),n(this,_)&&a.searchParams.set(ne,n(this,_));let h;try{h=yield n(this,G).call(this,a.toString(),{signal:i,headers:this.options.headers}),c(this,D,!0)}catch(b){if(b instanceof L)break;if(!(b instanceof P))throw b;if(b.status==409){let he=b.headers[q];p(this,E,He).call(this,he),yield p(this,E,de).call(this,b.json);continue}else if(b.status>=400&&b.status<500)throw p(this,E,Oe).call(this,b),p(this,E,ve).call(this,b),b}let{headers:l,status:u}=h,d=l.get(q);d&&c(this,_,d);let k=l.get(re);k&&c(this,H,k);let B=l.get(we);B&&c(this,F,B);let m=()=>{let b=l.get(xe);return b?JSON.parse(b):{}};c(this,N,(o=n(this,N))!=null?o:m());let J=u===204?"[]":yield h.text();u===204&&c(this,U,Date.now());let K=n(this,W).parse(J,n(this,N));if(K.length>0){let b=n(this,y),he=K[K.length-1];Se(he)&&(c(this,U,Date.now()),c(this,y,!0)),yield p(this,E,de).call(this,K),!b&&n(this,y)&&p(this,E,Ie).call(this)}}}catch(a){c(this,$,a)}finally{c(this,D,!1)}})}subscribe(e,t){let r=Math.random();return n(this,C).set(r,[e,t]),()=>{n(this,C).delete(r)}}unsubscribeAll(){n(this,C).clear()}subscribeOnceToUpToDate(e,t){let r=Math.random();return n(this,M).set(r,[e,t]),()=>{n(this,M).delete(r)}}unsubscribeAllUpToDateSubscribers(){n(this,M).clear()}lastSyncedAt(){return n(this,U)}lastSynced(){return n(this,U)===void 0?1/0:Date.now()-n(this,U)}isConnected(){return n(this,D)}isLoading(){return!this.isUpToDate}};G=new WeakMap,W=new WeakMap,C=new WeakMap,M=new WeakMap,H=new WeakMap,F=new WeakMap,U=new WeakMap,y=new WeakMap,D=new WeakMap,_=new WeakMap,N=new WeakMap,$=new WeakMap,E=new WeakSet,de=function(e){return S(this,null,function*(){yield Promise.all(Array.from(n(this,C).values()).map(i=>S(this,[i],function*([t,r]){try{yield t(e)}catch(o){queueMicrotask(()=>{throw o})}})))})},ve=function(e){n(this,C).forEach(([t,r])=>{r==null||r(e)})},Ie=function(){n(this,M).forEach(([e])=>{e()})},Oe=function(e){n(this,M).forEach(([t,r])=>r(e))},He=function(e){c(this,H,"-1"),c(this,F,""),c(this,_,e),c(this,y,!1),c(this,D,!1),c(this,N,void 0)};function ze(s){if(!s.url)throw new Error("Invalid shape option. It must provide the url");if(s.signal&&!(s.signal instanceof AbortSignal))throw new Error("Invalid signal option. It must be an instance of AbortSignal.");if(s.offset!==void 0&&s.offset!=="-1"&&!s.shapeId)throw new Error("shapeId is required if this isn't an initial fetch (i.e. offset > -1)")}var g,x,v,j,I,R,Ne,pe,be,Fe=class{constructor(e){f(this,R);f(this,g);f(this,x,new Map);f(this,v,new Map);f(this,j,!1);f(this,I,!1);c(this,g,e),n(this,g).subscribe(p(this,R,Ne).bind(this),p(this,R,pe).bind(this));let t=n(this,g).subscribeOnceToUpToDate(()=>{t()},r=>{throw p(this,R,pe).call(this,r),r})}get isUpToDate(){return n(this,g).isUpToDate}get value(){return new Promise((e,t)=>{if(n(this,g).isUpToDate)e(this.valueSync);else{let r=this.subscribe(i=>{r(),n(this,I)&&t(n(this,I)),e(i)})}})}get valueSync(){return n(this,x)}get error(){return n(this,I)}lastSyncedAt(){return n(this,g).lastSyncedAt()}lastSynced(){return n(this,g).lastSynced()}isLoading(){return n(this,g).isLoading()}isConnected(){return n(this,g).isConnected()}subscribe(e){let t=Math.random();return n(this,v).set(t,e),()=>{n(this,v).delete(t)}}unsubscribeAll(){n(this,v).clear()}get numSubscribers(){return n(this,v).size}};g=new WeakMap,x=new WeakMap,v=new WeakMap,j=new WeakMap,I=new WeakMap,R=new WeakSet,Ne=function(e){let t=!1,r=!1,i=!1;e.forEach(o=>{if(te(o))switch(t=["insert","update","delete"].includes(o.headers.operation),o.headers.operation){case"insert":n(this,x).set(o.key,o.value);break;case"update":n(this,x).set(o.key,T(T({},n(this,x).get(o.key)),o.value));break;case"delete":n(this,x).delete(o.key);break}if(se(o))switch(o.headers.control){case"up-to-date":r=!0,n(this,j)||(i=!0);break;case"must-refetch":n(this,x).clear(),c(this,I,!1),c(this,j,!1),r=!1,i=!1;break}}),(i||r&&t)&&(c(this,j,!0),p(this,R,be).call(this))},pe=function(e){e instanceof P&&(c(this,I,e),p(this,R,be).call(this))},be=function(){n(this,v).forEach(e=>{e(this.valueSync)})};export{ce as BackoffDefaults,P as FetchError,Fe as Shape,De as ShapeStream,te as isChangeMessage,se as isControlMessage};
1
+ var We=Object.defineProperty,$e=Object.defineProperties;var Je=Object.getOwnPropertyDescriptors;var ee=Object.getOwnPropertySymbols;var Ae=Object.prototype.hasOwnProperty,Re=Object.prototype.propertyIsEnumerable;var Pe=s=>{throw TypeError(s)};var Te=(s,e,t)=>e in s?We(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,T=(s,e)=>{for(var t in e||(e={}))Ae.call(e,t)&&Te(s,t,e[t]);if(ee)for(var t of ee(e))Re.call(e,t)&&Te(s,t,e[t]);return s},te=(s,e)=>$e(s,Je(e));var ye=(s,e)=>{var t={};for(var r in s)Ae.call(s,r)&&e.indexOf(r)<0&&(t[r]=s[r]);if(s!=null&&ee)for(var r of ee(s))e.indexOf(r)<0&&Re.call(s,r)&&(t[r]=s[r]);return t};var de=(s,e,t)=>e.has(s)||Pe("Cannot "+t);var n=(s,e,t)=>(de(s,e,"read from private field"),t?t.call(s):e.get(s)),u=(s,e,t)=>e.has(s)?Pe("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(s):e.set(s,t),l=(s,e,t,r)=>(de(s,e,"write to private field"),r?r.call(s,t):e.set(s,t),t),p=(s,e,t)=>(de(s,e,"access private method"),t);var y=(s,e,t)=>new Promise((r,i)=>{var o=c=>{try{h(t.next(c))}catch(f){i(f)}},a=c=>{try{h(t.throw(c))}catch(f){i(f)}},h=c=>c.done?r(c.value):Promise.resolve(c.value).then(o,a);h((t=t.apply(s,e)).next())});var se=s=>Number(s),Ke=s=>s==="true"||s==="t",ze=s=>BigInt(s),we=s=>JSON.parse(s),Xe=s=>s,Ze={int2:se,int4:se,int8:ze,bool:Ke,float4:se,float8:se,json:we,jsonb:we};function et(s,e){let t=0,r=null,i="",o=!1,a=0,h;function c(f){let d=[];for(;t<f.length;t++){if(r=f[t],o)r==="\\"?i+=f[++t]:r==='"'?(d.push(e?e(i):i),i="",o=f[t+1]==='"',a=t+2):i+=r;else if(r==='"')o=!0;else if(r==="{")a=++t,d.push(c(f));else if(r==="}"){o=!1,a<t&&d.push(e?e(f.slice(a,t)):f.slice(a,t)),a=t+1;break}else r===","&&h!=="}"&&h!=='"'&&(d.push(e?e(f.slice(a,t)):f.slice(a,t)),a=t+1);h=r}return a<t&&d.push(e?e(f.slice(a,t+1)):f.slice(a,t+1)),d}return c(s)[0]}var re=class{constructor(e){this.parser=T(T({},Ze),e)}parse(e,t){return JSON.parse(e,(r,i)=>{if(r==="value"&&typeof i=="object"&&i!==null){let o=i;Object.keys(o).forEach(a=>{o[a]=this.parseRow(a,o[a],t)})}return i})}parseRow(e,t,r){var M;let i=r[e];if(!i)return t;let d=i,{type:o,dims:a}=d,h=ye(d,["type","dims"]),c=(M=this.parser[o])!=null?M:Xe,f=_e(c,i,e);return a&&a>0?_e((E,q)=>et(E,f),i,e)(t):f(t,h)}};function _e(s,e,t){var i;let r=!((i=e.not_null)!=null&&i);return o=>{if(tt(o)){if(!r)throw new Error(`Column ${t!=null?t:"unknown"} is not nullable`);return null}return s(o,e)}}function tt(s){return s===null||s==="NULL"}function ne(s){return"key"in s}function oe(s){return!ne(s)}function Se(s){return oe(s)&&s.headers.control==="up-to-date"}var R=class s extends Error{constructor(t,r,i,o,a,h){super(h||`HTTP Error ${t} at ${a}: ${r!=null?r:JSON.stringify(i)}`);this.url=a;this.name="FetchError",this.status=t,this.text=r,this.json=i,this.headers=o}static fromResponse(t,r){return y(this,null,function*(){let i=t.status,o=Object.fromEntries([...t.headers.entries()]),a,h,c=t.headers.get("content-type");return c&&c.includes("application/json")?h=yield t.json():a=yield t.text(),new s(i,a,h,o,r)})}},B=class extends Error{constructor(){super("Fetch with backoff aborted")}};var xe="electric-cursor",G="electric-handle",ie="electric-offset",Me="electric-schema",Ce="electric-up-to-date",Ue="database_id",ke="columns",De="cursor",ae="handle",ce="live",le="offset",He="table",ve="where",Oe="replica";var st=[429],ue={initialDelay:100,maxDelay:1e4,multiplier:1.3};function Fe(s,e=ue){let{initialDelay:t,maxDelay:r,multiplier:i,debug:o=!1,onFailedAttempt:a}=e;return(...h)=>y(this,null,function*(){var N;let c=h[0],f=h[1],d=t,M=0;for(;;)try{let E=yield s(...h);if(E.ok)return E;throw yield R.fromResponse(E,c.toString())}catch(E){if(a==null||a(),(N=f==null?void 0:f.signal)!=null&&N.aborted)throw new B;if(E instanceof R&&!st.includes(E.status)&&E.status>=400&&E.status<500)throw E;yield new Promise(q=>setTimeout(q,d)),d=Math.min(d*i,r),o&&(M++,console.log(`Retry attempt #${M} after ${d}ms`))}})}var rt={maxChunksToPrefetch:2};function Ie(s,e=rt){let{maxChunksToPrefetch:t}=e,r;return(...o)=>y(this,null,function*(){let a=o[0].toString(),h=r==null?void 0:r.consume(...o);if(h)return h;r==null||r.abort();let c=yield s(...o),f=be(a,c);return f&&(r=new pe({fetchClient:s,maxPrefetchedRequests:t,url:f,requestInit:o[1]})),c})}var W,$,w,O,_,Y,he,pe=class{constructor(e){u(this,Y);u(this,W);u(this,$);u(this,w,new Map);u(this,O);u(this,_);var t;l(this,W,(t=e.fetchClient)!=null?t:(...r)=>fetch(...r)),l(this,$,e.maxPrefetchedRequests),l(this,O,e.url.toString()),l(this,_,n(this,O)),p(this,Y,he).call(this,e.url,e.requestInit)}abort(){n(this,w).forEach(([e,t])=>t.abort())}consume(...e){var i;let t=e[0].toString(),r=(i=n(this,w).get(t))==null?void 0:i[0];if(!(!r||t!==n(this,O)))return n(this,w).delete(t),r.then(o=>{let a=be(t,o);l(this,O,a),n(this,_)&&!n(this,w).has(n(this,_))&&p(this,Y,he).call(this,n(this,_),e[1])}).catch(()=>{}),r}};W=new WeakMap,$=new WeakMap,w=new WeakMap,O=new WeakMap,_=new WeakMap,Y=new WeakSet,he=function(...e){var i,o;let t=e[0].toString();if(n(this,w).size>=n(this,$))return;let r=new AbortController;try{let a=n(this,W).call(this,t,te(T({},(i=e[1])!=null?i:{}),{signal:nt(r,(o=e[1])==null?void 0:o.signal)}));n(this,w).set(t,[a,r]),a.then(h=>{if(!h.ok||r.signal.aborted)return;let c=be(t,h);if(!c||c===t){l(this,_,void 0);return}return l(this,_,c),p(this,Y,he).call(this,c,e[1])}).catch(()=>{})}catch(a){}};function be(s,e){let t=e.headers.get(G),r=e.headers.get(ie),i=e.headers.has(Ce);if(!t||!r||i)return;let o=new URL(s);if(!o.searchParams.has(ce))return o.searchParams.set(ae,t),o.searchParams.set(le,r),o.toString()}function nt(s,e){return e&&(e.aborted?s.abort():e.addEventListener("abort",()=>s.abort(),{once:!0})),s.signal}var K,z,C,U,F,I,k,A,D,S,V,L,X,Q,m,Ee,Ne,Be,Ye,Ve,J=class J{constructor(e){u(this,m);u(this,K);u(this,z);u(this,C,new Map);u(this,U,new Map);u(this,F);u(this,I);u(this,k);u(this,A,!1);u(this,D,!1);u(this,S);u(this,V);u(this,L);u(this,X);u(this,Q);var i,o,a;ot(e),this.options=T({subscribe:!0},e),l(this,F,(i=this.options.offset)!=null?i:"-1"),l(this,I,""),l(this,S,this.options.shapeHandle),l(this,V,this.options.databaseId),l(this,z,new re(e.parser)),l(this,Q,this.options.replica);let t=(o=e.fetchClient)!=null?o:(...h)=>fetch(...h),r=Fe(t,te(T({},(a=e.backoffOptions)!=null?a:ue),{onFailedAttempt:()=>{var h,c;l(this,D,!1),(c=(h=e.backoffOptions)==null?void 0:h.onFailedAttempt)==null||c.call(h)}}));l(this,K,Ie(r)),this.start()}get shapeHandle(){return n(this,S)}get isUpToDate(){return n(this,A)}get error(){return n(this,X)}start(){return y(this,null,function*(){var a,h;l(this,A,!1);let{url:e,table:t,where:r,columns:i,signal:o}=this.options;try{for(;!(o!=null&&o.aborted)&&!n(this,A)||this.options.subscribe;){let c=new URL(e);t&&c.searchParams.set(He,t),r&&c.searchParams.set(ve,r),i&&i.length>0&&c.searchParams.set(ke,i.join(",")),c.searchParams.set(le,n(this,F)),n(this,A)&&(c.searchParams.set(ce,"true"),c.searchParams.set(De,n(this,I))),n(this,S)&&c.searchParams.set(ae,n(this,S)),n(this,V)&&c.searchParams.set(Ue,n(this,V)),((a=n(this,Q))!=null?a:J.Replica.DEFAULT)!=J.Replica.DEFAULT&&c.searchParams.set(Oe,n(this,Q));let f;try{f=yield n(this,K).call(this,c.toString(),{signal:o,headers:this.options.headers}),l(this,D,!0)}catch(b){if(b instanceof B)break;if(!(b instanceof R))throw b;if(b.status==409){let fe=b.headers[G];p(this,m,Ve).call(this,fe),yield p(this,m,Ee).call(this,b.json);continue}else if(b.status>=400&&b.status<500)throw p(this,m,Ye).call(this,b),p(this,m,Ne).call(this,b),b}let{headers:d,status:M}=f,N=d.get(G);N&&l(this,S,N);let E=d.get(ie);E&&l(this,F,E);let q=d.get(xe);q&&l(this,I,q);let qe=()=>{let b=d.get(Me);return b?JSON.parse(b):{}};l(this,L,(h=n(this,L))!=null?h:qe());let Ge=M===204?"[]":yield f.text();M===204&&l(this,k,Date.now());let Z=n(this,z).parse(Ge,n(this,L));if(Z.length>0){let b=n(this,A),fe=Z[Z.length-1];Se(fe)&&(l(this,k,Date.now()),l(this,A,!0)),yield p(this,m,Ee).call(this,Z),!b&&n(this,A)&&p(this,m,Be).call(this)}}}catch(c){l(this,X,c)}finally{l(this,D,!1)}})}subscribe(e,t){let r=Math.random();return n(this,C).set(r,[e,t]),()=>{n(this,C).delete(r)}}unsubscribeAll(){n(this,C).clear()}subscribeOnceToUpToDate(e,t){let r=Math.random();return n(this,U).set(r,[e,t]),()=>{n(this,U).delete(r)}}unsubscribeAllUpToDateSubscribers(){n(this,U).clear()}lastSyncedAt(){return n(this,k)}lastSynced(){return n(this,k)===void 0?1/0:Date.now()-n(this,k)}isConnected(){return n(this,D)}isLoading(){return!this.isUpToDate}};K=new WeakMap,z=new WeakMap,C=new WeakMap,U=new WeakMap,F=new WeakMap,I=new WeakMap,k=new WeakMap,A=new WeakMap,D=new WeakMap,S=new WeakMap,V=new WeakMap,L=new WeakMap,X=new WeakMap,Q=new WeakMap,m=new WeakSet,Ee=function(e){return y(this,null,function*(){yield Promise.all(Array.from(n(this,C).values()).map(i=>y(this,[i],function*([t,r]){try{yield t(e)}catch(o){queueMicrotask(()=>{throw o})}})))})},Ne=function(e){n(this,C).forEach(([t,r])=>{r==null||r(e)})},Be=function(){n(this,U).forEach(([e])=>{e()})},Ye=function(e){n(this,U).forEach(([t,r])=>r(e))},Ve=function(e){l(this,F,"-1"),l(this,I,""),l(this,S,e),l(this,A,!1),l(this,D,!1),l(this,L,void 0)},J.Replica={FULL:"full",DEFAULT:"default"};var Le=J;function ot(s){if(!s.url)throw new Error("Invalid shape options. It must provide the url");if(s.signal&&!(s.signal instanceof AbortSignal))throw new Error("Invalid signal option. It must be an instance of AbortSignal.");if(s.offset!==void 0&&s.offset!=="-1"&&!s.shapeHandle)throw new Error("shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)")}var g,x,H,j,v,P,je,me,ge,Qe=class{constructor(e){u(this,P);u(this,g);u(this,x,new Map);u(this,H,new Map);u(this,j,!1);u(this,v,!1);l(this,g,e),n(this,g).subscribe(p(this,P,je).bind(this),p(this,P,me).bind(this));let t=n(this,g).subscribeOnceToUpToDate(()=>{t()},r=>{throw p(this,P,me).call(this,r),r})}get isUpToDate(){return n(this,g).isUpToDate}get rows(){return this.value.then(e=>Array.from(e.values()))}get currentRows(){return Array.from(this.currentValue.values())}get value(){return new Promise((e,t)=>{if(n(this,g).isUpToDate)e(this.currentValue);else{let r=this.subscribe(({value:i})=>{r(),n(this,v)&&t(n(this,v)),e(i)})}})}get currentValue(){return n(this,x)}get error(){return n(this,v)}lastSyncedAt(){return n(this,g).lastSyncedAt()}lastSynced(){return n(this,g).lastSynced()}isLoading(){return n(this,g).isLoading()}isConnected(){return n(this,g).isConnected()}subscribe(e){let t=Math.random();return n(this,H).set(t,e),()=>{n(this,H).delete(t)}}unsubscribeAll(){n(this,H).clear()}get numSubscribers(){return n(this,H).size}};g=new WeakMap,x=new WeakMap,H=new WeakMap,j=new WeakMap,v=new WeakMap,P=new WeakSet,je=function(e){let t=!1,r=!1,i=!1;e.forEach(o=>{if(ne(o))switch(t=["insert","update","delete"].includes(o.headers.operation),o.headers.operation){case"insert":n(this,x).set(o.key,o.value);break;case"update":n(this,x).set(o.key,T(T({},n(this,x).get(o.key)),o.value));break;case"delete":n(this,x).delete(o.key);break}if(oe(o))switch(o.headers.control){case"up-to-date":r=!0,n(this,j)||(i=!0);break;case"must-refetch":n(this,x).clear(),l(this,v,!1),l(this,j,!1),r=!1,i=!1;break}}),(i||r&&t)&&(l(this,j,!0),p(this,P,ge).call(this))},me=function(e){e instanceof R&&(l(this,v,e),p(this,P,ge).call(this))},ge=function(){n(this,H).forEach(e=>{e({value:this.currentValue,rows:this.currentRows})})};export{ue as BackoffDefaults,R as FetchError,Qe as Shape,Le as ShapeStream,ne as isChangeMessage,oe as isControlMessage};
2
2
  //# sourceMappingURL=index.browser.mjs.map