@electric-sql/client 1.0.10 → 1.0.11

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.
@@ -37,6 +37,14 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
37
37
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
38
38
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
39
39
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
40
+ var __privateWrapper = (obj, member, setter, getter) => ({
41
+ set _(value) {
42
+ __privateSet(obj, member, value, setter);
43
+ },
44
+ get _() {
45
+ return __privateGet(obj, member, getter);
46
+ }
47
+ });
40
48
 
41
49
  // src/error.ts
42
50
  var FetchError = class _FetchError extends Error {
@@ -256,6 +264,13 @@ function getOffset(message) {
256
264
  }
257
265
  return `${lsn}_0`;
258
266
  }
267
+ function isVisibleInSnapshot(txid, snapshot) {
268
+ const xid = BigInt(txid);
269
+ const xmin = BigInt(snapshot.xmin);
270
+ const xmax = BigInt(snapshot.xmax);
271
+ const xip = snapshot.xip_list.map(BigInt);
272
+ return xid < xmin || xid < xmax && !xip.includes(xid);
273
+ }
259
274
 
260
275
  // src/constants.ts
261
276
  var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
@@ -276,12 +291,24 @@ var WHERE_PARAMS_PARAM = `params`;
276
291
  var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
277
292
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
278
293
  var PAUSE_STREAM = `pause-stream`;
294
+ var LOG_MODE_QUERY_PARAM = `log`;
295
+ var SUBSET_PARAM_WHERE = `subset__where`;
296
+ var SUBSET_PARAM_LIMIT = `subset__limit`;
297
+ var SUBSET_PARAM_OFFSET = `subset__offset`;
298
+ var SUBSET_PARAM_ORDER_BY = `subset__order_by`;
299
+ var SUBSET_PARAM_WHERE_PARAMS = `subset__params`;
279
300
  var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
280
301
  LIVE_QUERY_PARAM,
281
302
  SHAPE_HANDLE_QUERY_PARAM,
282
303
  OFFSET_QUERY_PARAM,
283
304
  LIVE_CACHE_BUSTER_QUERY_PARAM,
284
- EXPIRED_HANDLE_QUERY_PARAM
305
+ EXPIRED_HANDLE_QUERY_PARAM,
306
+ LOG_MODE_QUERY_PARAM,
307
+ SUBSET_PARAM_WHERE,
308
+ SUBSET_PARAM_LIMIT,
309
+ SUBSET_PARAM_OFFSET,
310
+ SUBSET_PARAM_ORDER_BY,
311
+ SUBSET_PARAM_WHERE_PARAMS
285
312
  ];
286
313
 
287
314
  // src/fetch.ts
@@ -392,10 +419,20 @@ function createFetchWithResponseHeadersCheck(fetchClient) {
392
419
  const headers = response.headers;
393
420
  const missingHeaders = [];
394
421
  const addMissingHeaders = (requiredHeaders) => missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)));
395
- addMissingHeaders(requiredElectricResponseHeaders);
396
422
  const input = args[0];
397
423
  const urlString = input.toString();
398
424
  const url = new URL(urlString);
425
+ const isSnapshotRequest = [
426
+ SUBSET_PARAM_WHERE,
427
+ SUBSET_PARAM_WHERE_PARAMS,
428
+ SUBSET_PARAM_LIMIT,
429
+ SUBSET_PARAM_OFFSET,
430
+ SUBSET_PARAM_ORDER_BY
431
+ ].some((p) => url.searchParams.has(p));
432
+ if (isSnapshotRequest) {
433
+ return response;
434
+ }
435
+ addMissingHeaders(requiredElectricResponseHeaders);
399
436
  if (url.searchParams.get(LIVE_QUERY_PARAM) === `true`) {
400
437
  addMissingHeaders(requiredLiveResponseHeaders);
401
438
  }
@@ -565,6 +602,68 @@ var ExpiredShapesCache = class {
565
602
  };
566
603
  var expiredShapesCache = new ExpiredShapesCache();
567
604
 
605
+ // src/snapshot-tracker.ts
606
+ var SnapshotTracker = class {
607
+ constructor() {
608
+ this.activeSnapshots = /* @__PURE__ */ new Map();
609
+ this.xmaxSnapshots = /* @__PURE__ */ new Map();
610
+ this.snapshotsByDatabaseLsn = /* @__PURE__ */ new Map();
611
+ }
612
+ /**
613
+ * Add a new snapshot for tracking
614
+ */
615
+ addSnapshot(metadata, keys) {
616
+ var _a, _b, _c, _d;
617
+ this.activeSnapshots.set(metadata.snapshot_mark, {
618
+ xmin: BigInt(metadata.xmin),
619
+ xmax: BigInt(metadata.xmax),
620
+ xip_list: metadata.xip_list.map(BigInt),
621
+ keys
622
+ });
623
+ const xmaxSet = (_b = (_a = this.xmaxSnapshots.get(BigInt(metadata.xmax))) == null ? void 0 : _a.add(metadata.snapshot_mark)) != null ? _b : /* @__PURE__ */ new Set([metadata.snapshot_mark]);
624
+ this.xmaxSnapshots.set(BigInt(metadata.xmax), xmaxSet);
625
+ const databaseLsnSet = (_d = (_c = this.snapshotsByDatabaseLsn.get(BigInt(metadata.database_lsn))) == null ? void 0 : _c.add(metadata.snapshot_mark)) != null ? _d : /* @__PURE__ */ new Set([metadata.snapshot_mark]);
626
+ this.snapshotsByDatabaseLsn.set(
627
+ BigInt(metadata.database_lsn),
628
+ databaseLsnSet
629
+ );
630
+ }
631
+ /**
632
+ * Remove a snapshot from tracking
633
+ */
634
+ removeSnapshot(snapshotMark) {
635
+ this.activeSnapshots.delete(snapshotMark);
636
+ }
637
+ /**
638
+ * Check if a change message should be filtered because its already in an active snapshot
639
+ * Returns true if the message should be filtered out (not processed)
640
+ */
641
+ shouldRejectMessage(message) {
642
+ const txids = message.headers.txids || [];
643
+ if (txids.length === 0) return false;
644
+ const xid = Math.max(...txids);
645
+ for (const [xmax, snapshots] of this.xmaxSnapshots.entries()) {
646
+ if (xid >= xmax) {
647
+ for (const snapshot of snapshots) {
648
+ this.removeSnapshot(snapshot);
649
+ }
650
+ }
651
+ }
652
+ return [...this.activeSnapshots.values()].some(
653
+ (x) => x.keys.has(message.key) && isVisibleInSnapshot(xid, x)
654
+ );
655
+ }
656
+ lastSeenUpdate(newDatabaseLsn) {
657
+ for (const [dbLsn, snapshots] of this.snapshotsByDatabaseLsn.entries()) {
658
+ if (dbLsn <= newDatabaseLsn) {
659
+ for (const snapshot of snapshots) {
660
+ this.removeSnapshot(snapshot);
661
+ }
662
+ }
663
+ }
664
+ }
665
+ };
666
+
568
667
  // src/client.ts
569
668
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
570
669
  LIVE_CACHE_BUSTER_QUERY_PARAM,
@@ -612,9 +711,8 @@ function canonicalShapeKey(url) {
612
711
  cleanUrl.searchParams.sort();
613
712
  return cleanUrl.toString();
614
713
  }
615
- var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _state, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _connected, _shapeHandle, _schema, _onError, _requestAbortController, _isRefreshing, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _ShapeStream_instances, start_fn, requestShape_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, pause_fn, resume_fn, nextTick_fn, publish_fn, sendErrorToSubscribers_fn, subscribeToVisibilityChanges_fn, reset_fn;
714
+ var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _state, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _isMidStream, _connected, _shapeHandle, _mode, _schema, _onError, _requestAbortController, _isRefreshing, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _snapshotTracker, _activeSnapshotRequests, _midStreamPromise, _midStreamPromiseResolver, _ShapeStream_instances, start_fn, requestShape_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, pause_fn, resume_fn, nextTick_fn, waitForStreamEnd_fn, publish_fn, sendErrorToSubscribers_fn, subscribeToVisibilityChanges_fn, reset_fn, fetchSnapshot_fn;
616
715
  var ShapeStream = class {
617
- // promise chain for incoming messages
618
716
  constructor(options) {
619
717
  __privateAdd(this, _ShapeStream_instances);
620
718
  __privateAdd(this, _error, null);
@@ -630,8 +728,10 @@ var ShapeStream = class {
630
728
  __privateAdd(this, _lastSyncedAt);
631
729
  // unix time
632
730
  __privateAdd(this, _isUpToDate, false);
731
+ __privateAdd(this, _isMidStream, true);
633
732
  __privateAdd(this, _connected, false);
634
733
  __privateAdd(this, _shapeHandle);
734
+ __privateAdd(this, _mode);
635
735
  __privateAdd(this, _schema);
636
736
  __privateAdd(this, _onError);
637
737
  __privateAdd(this, _requestAbortController);
@@ -640,7 +740,13 @@ var ShapeStream = class {
640
740
  __privateAdd(this, _tickPromiseResolver);
641
741
  __privateAdd(this, _tickPromiseRejecter);
642
742
  __privateAdd(this, _messageChain, Promise.resolve([]));
643
- var _a, _b, _c;
743
+ // promise chain for incoming messages
744
+ __privateAdd(this, _snapshotTracker, new SnapshotTracker());
745
+ __privateAdd(this, _activeSnapshotRequests, 0);
746
+ // counter for concurrent snapshot requests
747
+ __privateAdd(this, _midStreamPromise);
748
+ __privateAdd(this, _midStreamPromiseResolver);
749
+ var _a, _b, _c, _d;
644
750
  this.options = __spreadValues({ subscribe: true }, options);
645
751
  validateOptions(this.options);
646
752
  __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
@@ -651,8 +757,9 @@ var ShapeStream = class {
651
757
  options.transformer
652
758
  ));
653
759
  __privateSet(this, _onError, this.options.onError);
654
- const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
655
- const backOffOpts = __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
760
+ __privateSet(this, _mode, (_b = this.options.mode) != null ? _b : `full`);
761
+ const baseFetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
762
+ const backOffOpts = __spreadProps(__spreadValues({}, (_d = options.backoffOptions) != null ? _d : BackoffDefaults), {
656
763
  onFailedAttempt: () => {
657
764
  var _a2, _b2;
658
765
  __privateSet(this, _connected, false);
@@ -681,6 +788,9 @@ var ShapeStream = class {
681
788
  get lastOffset() {
682
789
  return __privateGet(this, _lastOffset);
683
790
  }
791
+ get mode() {
792
+ return __privateGet(this, _mode);
793
+ }
684
794
  subscribe(callback, onError = () => {
685
795
  }) {
686
796
  const subscriptionId = Math.random();
@@ -731,6 +841,54 @@ var ShapeStream = class {
731
841
  await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
732
842
  __privateSet(this, _isRefreshing, false);
733
843
  }
844
+ /**
845
+ * Request a snapshot for subset of data.
846
+ *
847
+ * Only available when mode is `changes_only`.
848
+ * Returns the insertion point & the data, but more importantly injects the data
849
+ * into the subscribed data stream. Returned value is unlikely to be useful for the caller,
850
+ * unless the caller has complicated additional logic.
851
+ *
852
+ * Data will be injected in a way that's also tracking further incoming changes, and it'll
853
+ * skip the ones that are already in the snapshot.
854
+ *
855
+ * @param opts - The options for the snapshot request.
856
+ * @returns The metadata and the data for the snapshot.
857
+ */
858
+ async requestSnapshot(opts) {
859
+ if (__privateGet(this, _mode) === `full`) {
860
+ throw new Error(
861
+ `Snapshot requests are not supported in ${__privateGet(this, _mode)} mode, as the consumer is guaranteed to observe all data`
862
+ );
863
+ }
864
+ if (!__privateGet(this, _started)) await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
865
+ await __privateMethod(this, _ShapeStream_instances, waitForStreamEnd_fn).call(this);
866
+ __privateWrapper(this, _activeSnapshotRequests)._++;
867
+ try {
868
+ if (__privateGet(this, _activeSnapshotRequests) === 1) {
869
+ __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
870
+ }
871
+ const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
872
+ const { metadata, data } = await __privateMethod(this, _ShapeStream_instances, fetchSnapshot_fn).call(this, fetchUrl, requestHeaders);
873
+ const dataWithEndBoundary = data.concat([
874
+ { headers: __spreadValues({ control: `snapshot-end` }, metadata) }
875
+ ]);
876
+ __privateGet(this, _snapshotTracker).addSnapshot(
877
+ metadata,
878
+ new Set(data.map((message) => message.key))
879
+ );
880
+ __privateMethod(this, _ShapeStream_instances, onMessages_fn).call(this, dataWithEndBoundary, false);
881
+ return {
882
+ metadata,
883
+ data
884
+ };
885
+ } finally {
886
+ __privateWrapper(this, _activeSnapshotRequests)._--;
887
+ if (__privateGet(this, _activeSnapshotRequests) === 0) {
888
+ __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
889
+ }
890
+ }
891
+ }
734
892
  };
735
893
  _error = new WeakMap();
736
894
  _fetchClient2 = new WeakMap();
@@ -743,8 +901,10 @@ _lastOffset = new WeakMap();
743
901
  _liveCacheBuster = new WeakMap();
744
902
  _lastSyncedAt = new WeakMap();
745
903
  _isUpToDate = new WeakMap();
904
+ _isMidStream = new WeakMap();
746
905
  _connected = new WeakMap();
747
906
  _shapeHandle = new WeakMap();
907
+ _mode = new WeakMap();
748
908
  _schema = new WeakMap();
749
909
  _onError = new WeakMap();
750
910
  _requestAbortController = new WeakMap();
@@ -753,6 +913,10 @@ _tickPromise = new WeakMap();
753
913
  _tickPromiseResolver = new WeakMap();
754
914
  _tickPromiseRejecter = new WeakMap();
755
915
  _messageChain = new WeakMap();
916
+ _snapshotTracker = new WeakMap();
917
+ _activeSnapshotRequests = new WeakMap();
918
+ _midStreamPromise = new WeakMap();
919
+ _midStreamPromiseResolver = new WeakMap();
756
920
  _ShapeStream_instances = new WeakSet();
757
921
  start_fn = async function() {
758
922
  var _a;
@@ -837,14 +1001,12 @@ requestShape_fn = async function() {
837
1001
  (_b = __privateGet(this, _tickPromiseResolver)) == null ? void 0 : _b.call(this);
838
1002
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
839
1003
  };
840
- constructUrl_fn = async function(url, resumingFromPause) {
1004
+ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
841
1005
  const [requestHeaders, params] = await Promise.all([
842
1006
  resolveHeaders(this.options.headers),
843
1007
  this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
844
1008
  ]);
845
- if (params) {
846
- validateParams(params);
847
- }
1009
+ if (params) validateParams(params);
848
1010
  const fetchUrl = new URL(url);
849
1011
  if (params) {
850
1012
  if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
@@ -864,7 +1026,20 @@ constructUrl_fn = async function(url, resumingFromPause) {
864
1026
  setQueryParam(fetchUrl, key, value);
865
1027
  }
866
1028
  }
1029
+ if (subsetParams) {
1030
+ if (subsetParams.where)
1031
+ setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, subsetParams.where);
1032
+ if (subsetParams.params)
1033
+ setQueryParam(fetchUrl, SUBSET_PARAM_WHERE_PARAMS, subsetParams.params);
1034
+ if (subsetParams.limit)
1035
+ setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit);
1036
+ if (subsetParams.offset)
1037
+ setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset);
1038
+ if (subsetParams.orderBy)
1039
+ setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, subsetParams.orderBy);
1040
+ }
867
1041
  fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
1042
+ fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
868
1043
  if (__privateGet(this, _isUpToDate)) {
869
1044
  if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
870
1045
  fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
@@ -928,7 +1103,9 @@ onInitialResponse_fn = async function(response) {
928
1103
  }
929
1104
  };
930
1105
  onMessages_fn = async function(batch, isSseMessage = false) {
1106
+ var _a;
931
1107
  if (batch.length > 0) {
1108
+ __privateSet(this, _isMidStream, true);
932
1109
  const lastMessage = batch[batch.length - 1];
933
1110
  if (isUpToDateMessage(lastMessage)) {
934
1111
  if (isSseMessage) {
@@ -939,8 +1116,16 @@ onMessages_fn = async function(batch, isSseMessage = false) {
939
1116
  }
940
1117
  __privateSet(this, _lastSyncedAt, Date.now());
941
1118
  __privateSet(this, _isUpToDate, true);
1119
+ __privateSet(this, _isMidStream, false);
1120
+ (_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
942
1121
  }
943
- await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, batch);
1122
+ const messagesToProcess = batch.filter((message) => {
1123
+ if (isChangeMessage(message)) {
1124
+ return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
1125
+ }
1126
+ return true;
1127
+ });
1128
+ await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
944
1129
  }
945
1130
  };
946
1131
  fetchShape_fn = async function(opts) {
@@ -1029,6 +1214,22 @@ nextTick_fn = async function() {
1029
1214
  });
1030
1215
  return __privateGet(this, _tickPromise);
1031
1216
  };
1217
+ waitForStreamEnd_fn = async function() {
1218
+ if (!__privateGet(this, _isMidStream)) {
1219
+ return;
1220
+ }
1221
+ if (__privateGet(this, _midStreamPromise)) {
1222
+ return __privateGet(this, _midStreamPromise);
1223
+ }
1224
+ __privateSet(this, _midStreamPromise, new Promise((resolve) => {
1225
+ __privateSet(this, _midStreamPromiseResolver, resolve);
1226
+ }));
1227
+ __privateGet(this, _midStreamPromise).finally(() => {
1228
+ __privateSet(this, _midStreamPromise, void 0);
1229
+ __privateSet(this, _midStreamPromiseResolver, void 0);
1230
+ });
1231
+ return __privateGet(this, _midStreamPromise);
1232
+ };
1032
1233
  publish_fn = async function(messages) {
1033
1234
  __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
1034
1235
  () => Promise.all(
@@ -1071,8 +1272,31 @@ reset_fn = function(handle) {
1071
1272
  __privateSet(this, _liveCacheBuster, ``);
1072
1273
  __privateSet(this, _shapeHandle, handle);
1073
1274
  __privateSet(this, _isUpToDate, false);
1275
+ __privateSet(this, _isMidStream, true);
1074
1276
  __privateSet(this, _connected, false);
1075
1277
  __privateSet(this, _schema, void 0);
1278
+ __privateSet(this, _activeSnapshotRequests, 0);
1279
+ };
1280
+ fetchSnapshot_fn = async function(url, headers) {
1281
+ const response = await __privateGet(this, _fetchClient2).call(this, url.toString(), { headers });
1282
+ if (!response.ok) {
1283
+ throw new FetchError(
1284
+ response.status,
1285
+ void 0,
1286
+ void 0,
1287
+ Object.fromEntries([...response.headers.entries()]),
1288
+ url.toString()
1289
+ );
1290
+ }
1291
+ const { metadata, data } = await response.json();
1292
+ const batch = __privateGet(this, _messageParser).parse(
1293
+ JSON.stringify(data),
1294
+ __privateGet(this, _schema)
1295
+ );
1296
+ return {
1297
+ metadata,
1298
+ data: batch
1299
+ };
1076
1300
  };
1077
1301
  ShapeStream.Replica = {
1078
1302
  FULL: `full`,
@@ -1094,7 +1318,7 @@ function validateOptions(options) {
1094
1318
  if (options.signal && !(options.signal instanceof AbortSignal)) {
1095
1319
  throw new InvalidSignalError();
1096
1320
  }
1097
- if (options.offset !== void 0 && options.offset !== `-1` && !options.handle) {
1321
+ if (options.offset !== void 0 && options.offset !== `-1` && options.offset !== `now` && !options.handle) {
1098
1322
  throw new MissingShapeHandleError();
1099
1323
  }
1100
1324
  validateParams(options.params);
@@ -1123,12 +1347,15 @@ function convertWhereParamsToObj(allPgParams) {
1123
1347
  }
1124
1348
 
1125
1349
  // src/shape.ts
1126
- var _data, _subscribers2, _status, _error2, _Shape_instances, process_fn, updateShapeStatus_fn, handleError_fn, notify_fn;
1350
+ var _data, _subscribers2, _insertedKeys, _requestedSubSnapshots, _reexecuteSnapshotsPending, _status, _error2, _Shape_instances, process_fn, reexecuteSnapshots_fn, awaitUpToDate_fn, updateShapeStatus_fn, handleError_fn, notify_fn;
1127
1351
  var Shape = class {
1128
1352
  constructor(stream) {
1129
1353
  __privateAdd(this, _Shape_instances);
1130
1354
  __privateAdd(this, _data, /* @__PURE__ */ new Map());
1131
1355
  __privateAdd(this, _subscribers2, /* @__PURE__ */ new Map());
1356
+ __privateAdd(this, _insertedKeys, /* @__PURE__ */ new Set());
1357
+ __privateAdd(this, _requestedSubSnapshots, /* @__PURE__ */ new Set());
1358
+ __privateAdd(this, _reexecuteSnapshotsPending, false);
1132
1359
  __privateAdd(this, _status, `syncing`);
1133
1360
  __privateAdd(this, _error2, false);
1134
1361
  this.stream = stream;
@@ -1187,6 +1414,20 @@ var Shape = class {
1187
1414
  isConnected() {
1188
1415
  return this.stream.isConnected();
1189
1416
  }
1417
+ /** Current log mode of the underlying stream */
1418
+ get mode() {
1419
+ return this.stream.mode;
1420
+ }
1421
+ /**
1422
+ * Request a snapshot for subset of data. Only available when mode is changes_only.
1423
+ * Returns void; data will be emitted via the stream and processed by this Shape.
1424
+ */
1425
+ async requestSnapshot(params) {
1426
+ const key = JSON.stringify(params);
1427
+ __privateGet(this, _requestedSubSnapshots).add(key);
1428
+ await __privateMethod(this, _Shape_instances, awaitUpToDate_fn).call(this);
1429
+ await this.stream.requestSnapshot(params);
1430
+ }
1190
1431
  subscribe(callback) {
1191
1432
  const subscriptionId = Math.random();
1192
1433
  __privateGet(this, _subscribers2).set(subscriptionId, callback);
@@ -1203,6 +1444,9 @@ var Shape = class {
1203
1444
  };
1204
1445
  _data = new WeakMap();
1205
1446
  _subscribers2 = new WeakMap();
1447
+ _insertedKeys = new WeakMap();
1448
+ _requestedSubSnapshots = new WeakMap();
1449
+ _reexecuteSnapshotsPending = new WeakMap();
1206
1450
  _status = new WeakMap();
1207
1451
  _error2 = new WeakMap();
1208
1452
  _Shape_instances = new WeakSet();
@@ -1211,33 +1455,89 @@ process_fn = function(messages) {
1211
1455
  messages.forEach((message) => {
1212
1456
  if (isChangeMessage(message)) {
1213
1457
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1214
- switch (message.headers.operation) {
1215
- case `insert`:
1216
- __privateGet(this, _data).set(message.key, message.value);
1217
- break;
1218
- case `update`:
1219
- __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1220
- break;
1221
- case `delete`:
1222
- __privateGet(this, _data).delete(message.key);
1223
- break;
1458
+ if (this.mode === `full`) {
1459
+ switch (message.headers.operation) {
1460
+ case `insert`:
1461
+ __privateGet(this, _data).set(message.key, message.value);
1462
+ break;
1463
+ case `update`:
1464
+ __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1465
+ break;
1466
+ case `delete`:
1467
+ __privateGet(this, _data).delete(message.key);
1468
+ break;
1469
+ }
1470
+ } else {
1471
+ switch (message.headers.operation) {
1472
+ case `insert`:
1473
+ __privateGet(this, _insertedKeys).add(message.key);
1474
+ __privateGet(this, _data).set(message.key, message.value);
1475
+ break;
1476
+ case `update`:
1477
+ if (__privateGet(this, _insertedKeys).has(message.key)) {
1478
+ __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1479
+ }
1480
+ break;
1481
+ case `delete`:
1482
+ if (__privateGet(this, _insertedKeys).has(message.key)) {
1483
+ __privateGet(this, _data).delete(message.key);
1484
+ __privateGet(this, _insertedKeys).delete(message.key);
1485
+ }
1486
+ break;
1487
+ }
1224
1488
  }
1225
1489
  }
1226
1490
  if (isControlMessage(message)) {
1227
1491
  switch (message.headers.control) {
1228
1492
  case `up-to-date`:
1229
1493
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `up-to-date`);
1494
+ if (__privateGet(this, _reexecuteSnapshotsPending)) {
1495
+ __privateSet(this, _reexecuteSnapshotsPending, false);
1496
+ void __privateMethod(this, _Shape_instances, reexecuteSnapshots_fn).call(this);
1497
+ }
1230
1498
  break;
1231
1499
  case `must-refetch`:
1232
1500
  __privateGet(this, _data).clear();
1501
+ __privateGet(this, _insertedKeys).clear();
1233
1502
  __privateSet(this, _error2, false);
1234
1503
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1504
+ __privateSet(this, _reexecuteSnapshotsPending, true);
1235
1505
  break;
1236
1506
  }
1237
1507
  }
1238
1508
  });
1239
1509
  if (shouldNotify) __privateMethod(this, _Shape_instances, notify_fn).call(this);
1240
1510
  };
1511
+ reexecuteSnapshots_fn = async function() {
1512
+ await __privateMethod(this, _Shape_instances, awaitUpToDate_fn).call(this);
1513
+ await Promise.all(
1514
+ Array.from(__privateGet(this, _requestedSubSnapshots)).map(async (jsonParams) => {
1515
+ try {
1516
+ const snapshot = JSON.parse(jsonParams);
1517
+ await this.stream.requestSnapshot(snapshot);
1518
+ } catch (_) {
1519
+ }
1520
+ })
1521
+ );
1522
+ };
1523
+ awaitUpToDate_fn = async function() {
1524
+ if (this.stream.isUpToDate) return;
1525
+ await new Promise((resolve) => {
1526
+ const check = () => {
1527
+ if (this.stream.isUpToDate) {
1528
+ clearInterval(interval);
1529
+ unsub();
1530
+ resolve();
1531
+ }
1532
+ };
1533
+ const interval = setInterval(check, 10);
1534
+ const unsub = this.stream.subscribe(
1535
+ () => check(),
1536
+ () => check()
1537
+ );
1538
+ check();
1539
+ });
1540
+ };
1241
1541
  updateShapeStatus_fn = function(status) {
1242
1542
  const stateChanged = __privateGet(this, _status) !== status;
1243
1543
  __privateSet(this, _status, status);
@@ -1262,6 +1562,7 @@ export {
1262
1562
  ShapeStream,
1263
1563
  isChangeMessage,
1264
1564
  isControlMessage,
1565
+ isVisibleInSnapshot,
1265
1566
  resolveValue
1266
1567
  };
1267
1568
  //# sourceMappingURL=index.legacy-esm.js.map