@electric-sql/client 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -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
  var __async = (__this, __arguments, generator) => {
41
49
  return new Promise((resolve, reject) => {
42
50
  var fulfilled = (value) => {
@@ -278,6 +286,13 @@ function getOffset(message) {
278
286
  }
279
287
  return `${lsn}_0`;
280
288
  }
289
+ function isVisibleInSnapshot(txid, snapshot) {
290
+ const xid = BigInt(txid);
291
+ const xmin = BigInt(snapshot.xmin);
292
+ const xmax = BigInt(snapshot.xmax);
293
+ const xip = snapshot.xip_list.map(BigInt);
294
+ return xid < xmin || xid < xmax && !xip.includes(xid);
295
+ }
281
296
 
282
297
  // src/constants.ts
283
298
  var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
@@ -298,12 +313,24 @@ var WHERE_PARAMS_PARAM = `params`;
298
313
  var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
299
314
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
300
315
  var PAUSE_STREAM = `pause-stream`;
316
+ var LOG_MODE_QUERY_PARAM = `log`;
317
+ var SUBSET_PARAM_WHERE = `subset__where`;
318
+ var SUBSET_PARAM_LIMIT = `subset__limit`;
319
+ var SUBSET_PARAM_OFFSET = `subset__offset`;
320
+ var SUBSET_PARAM_ORDER_BY = `subset__order_by`;
321
+ var SUBSET_PARAM_WHERE_PARAMS = `subset__params`;
301
322
  var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
302
323
  LIVE_QUERY_PARAM,
303
324
  SHAPE_HANDLE_QUERY_PARAM,
304
325
  OFFSET_QUERY_PARAM,
305
326
  LIVE_CACHE_BUSTER_QUERY_PARAM,
306
- EXPIRED_HANDLE_QUERY_PARAM
327
+ EXPIRED_HANDLE_QUERY_PARAM,
328
+ LOG_MODE_QUERY_PARAM,
329
+ SUBSET_PARAM_WHERE,
330
+ SUBSET_PARAM_LIMIT,
331
+ SUBSET_PARAM_OFFSET,
332
+ SUBSET_PARAM_ORDER_BY,
333
+ SUBSET_PARAM_WHERE_PARAMS
307
334
  ];
308
335
 
309
336
  // src/fetch.ts
@@ -414,10 +441,20 @@ function createFetchWithResponseHeadersCheck(fetchClient) {
414
441
  const headers = response.headers;
415
442
  const missingHeaders = [];
416
443
  const addMissingHeaders = (requiredHeaders) => missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)));
417
- addMissingHeaders(requiredElectricResponseHeaders);
418
444
  const input = args[0];
419
445
  const urlString = input.toString();
420
446
  const url = new URL(urlString);
447
+ const isSnapshotRequest = [
448
+ SUBSET_PARAM_WHERE,
449
+ SUBSET_PARAM_WHERE_PARAMS,
450
+ SUBSET_PARAM_LIMIT,
451
+ SUBSET_PARAM_OFFSET,
452
+ SUBSET_PARAM_ORDER_BY
453
+ ].some((p) => url.searchParams.has(p));
454
+ if (isSnapshotRequest) {
455
+ return response;
456
+ }
457
+ addMissingHeaders(requiredElectricResponseHeaders);
421
458
  if (url.searchParams.get(LIVE_QUERY_PARAM) === `true`) {
422
459
  addMissingHeaders(requiredLiveResponseHeaders);
423
460
  }
@@ -587,6 +624,68 @@ var ExpiredShapesCache = class {
587
624
  };
588
625
  var expiredShapesCache = new ExpiredShapesCache();
589
626
 
627
+ // src/snapshot-tracker.ts
628
+ var SnapshotTracker = class {
629
+ constructor() {
630
+ this.activeSnapshots = /* @__PURE__ */ new Map();
631
+ this.xmaxSnapshots = /* @__PURE__ */ new Map();
632
+ this.snapshotsByDatabaseLsn = /* @__PURE__ */ new Map();
633
+ }
634
+ /**
635
+ * Add a new snapshot for tracking
636
+ */
637
+ addSnapshot(metadata, keys) {
638
+ var _a, _b, _c, _d;
639
+ this.activeSnapshots.set(metadata.snapshot_mark, {
640
+ xmin: BigInt(metadata.xmin),
641
+ xmax: BigInt(metadata.xmax),
642
+ xip_list: metadata.xip_list.map(BigInt),
643
+ keys
644
+ });
645
+ 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]);
646
+ this.xmaxSnapshots.set(BigInt(metadata.xmax), xmaxSet);
647
+ 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]);
648
+ this.snapshotsByDatabaseLsn.set(
649
+ BigInt(metadata.database_lsn),
650
+ databaseLsnSet
651
+ );
652
+ }
653
+ /**
654
+ * Remove a snapshot from tracking
655
+ */
656
+ removeSnapshot(snapshotMark) {
657
+ this.activeSnapshots.delete(snapshotMark);
658
+ }
659
+ /**
660
+ * Check if a change message should be filtered because its already in an active snapshot
661
+ * Returns true if the message should be filtered out (not processed)
662
+ */
663
+ shouldRejectMessage(message) {
664
+ const txids = message.headers.txids || [];
665
+ if (txids.length === 0) return false;
666
+ const xid = Math.max(...txids);
667
+ for (const [xmax, snapshots] of this.xmaxSnapshots.entries()) {
668
+ if (xid >= xmax) {
669
+ for (const snapshot of snapshots) {
670
+ this.removeSnapshot(snapshot);
671
+ }
672
+ }
673
+ }
674
+ return [...this.activeSnapshots.values()].some(
675
+ (x) => x.keys.has(message.key) && isVisibleInSnapshot(xid, x)
676
+ );
677
+ }
678
+ lastSeenUpdate(newDatabaseLsn) {
679
+ for (const [dbLsn, snapshots] of this.snapshotsByDatabaseLsn.entries()) {
680
+ if (dbLsn <= newDatabaseLsn) {
681
+ for (const snapshot of snapshots) {
682
+ this.removeSnapshot(snapshot);
683
+ }
684
+ }
685
+ }
686
+ }
687
+ };
688
+
590
689
  // src/client.ts
591
690
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
592
691
  LIVE_CACHE_BUSTER_QUERY_PARAM,
@@ -642,9 +741,8 @@ function canonicalShapeKey(url) {
642
741
  cleanUrl.searchParams.sort();
643
742
  return cleanUrl.toString();
644
743
  }
645
- 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;
744
+ 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;
646
745
  var ShapeStream = class {
647
- // promise chain for incoming messages
648
746
  constructor(options) {
649
747
  __privateAdd(this, _ShapeStream_instances);
650
748
  __privateAdd(this, _error, null);
@@ -660,8 +758,10 @@ var ShapeStream = class {
660
758
  __privateAdd(this, _lastSyncedAt);
661
759
  // unix time
662
760
  __privateAdd(this, _isUpToDate, false);
761
+ __privateAdd(this, _isMidStream, true);
663
762
  __privateAdd(this, _connected, false);
664
763
  __privateAdd(this, _shapeHandle);
764
+ __privateAdd(this, _mode);
665
765
  __privateAdd(this, _schema);
666
766
  __privateAdd(this, _onError);
667
767
  __privateAdd(this, _requestAbortController);
@@ -670,7 +770,13 @@ var ShapeStream = class {
670
770
  __privateAdd(this, _tickPromiseResolver);
671
771
  __privateAdd(this, _tickPromiseRejecter);
672
772
  __privateAdd(this, _messageChain, Promise.resolve([]));
673
- var _a, _b, _c;
773
+ // promise chain for incoming messages
774
+ __privateAdd(this, _snapshotTracker, new SnapshotTracker());
775
+ __privateAdd(this, _activeSnapshotRequests, 0);
776
+ // counter for concurrent snapshot requests
777
+ __privateAdd(this, _midStreamPromise);
778
+ __privateAdd(this, _midStreamPromiseResolver);
779
+ var _a, _b, _c, _d;
674
780
  this.options = __spreadValues({ subscribe: true }, options);
675
781
  validateOptions(this.options);
676
782
  __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
@@ -681,8 +787,9 @@ var ShapeStream = class {
681
787
  options.transformer
682
788
  ));
683
789
  __privateSet(this, _onError, this.options.onError);
684
- const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
685
- const backOffOpts = __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
790
+ __privateSet(this, _mode, (_b = this.options.log) != null ? _b : `full`);
791
+ const baseFetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
792
+ const backOffOpts = __spreadProps(__spreadValues({}, (_d = options.backoffOptions) != null ? _d : BackoffDefaults), {
686
793
  onFailedAttempt: () => {
687
794
  var _a2, _b2;
688
795
  __privateSet(this, _connected, false);
@@ -711,6 +818,9 @@ var ShapeStream = class {
711
818
  get lastOffset() {
712
819
  return __privateGet(this, _lastOffset);
713
820
  }
821
+ get mode() {
822
+ return __privateGet(this, _mode);
823
+ }
714
824
  subscribe(callback, onError = () => {
715
825
  }) {
716
826
  const subscriptionId = Math.random();
@@ -763,6 +873,56 @@ var ShapeStream = class {
763
873
  __privateSet(this, _isRefreshing, false);
764
874
  });
765
875
  }
876
+ /**
877
+ * Request a snapshot for subset of data.
878
+ *
879
+ * Only available when mode is `changes_only`.
880
+ * Returns the insertion point & the data, but more importantly injects the data
881
+ * into the subscribed data stream. Returned value is unlikely to be useful for the caller,
882
+ * unless the caller has complicated additional logic.
883
+ *
884
+ * Data will be injected in a way that's also tracking further incoming changes, and it'll
885
+ * skip the ones that are already in the snapshot.
886
+ *
887
+ * @param opts - The options for the snapshot request.
888
+ * @returns The metadata and the data for the snapshot.
889
+ */
890
+ requestSnapshot(opts) {
891
+ return __async(this, null, function* () {
892
+ if (__privateGet(this, _mode) === `full`) {
893
+ throw new Error(
894
+ `Snapshot requests are not supported in ${__privateGet(this, _mode)} mode, as the consumer is guaranteed to observe all data`
895
+ );
896
+ }
897
+ if (!__privateGet(this, _started)) yield __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
898
+ yield __privateMethod(this, _ShapeStream_instances, waitForStreamEnd_fn).call(this);
899
+ __privateWrapper(this, _activeSnapshotRequests)._++;
900
+ try {
901
+ if (__privateGet(this, _activeSnapshotRequests) === 1) {
902
+ __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
903
+ }
904
+ const { fetchUrl, requestHeaders } = yield __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
905
+ const { metadata, data } = yield __privateMethod(this, _ShapeStream_instances, fetchSnapshot_fn).call(this, fetchUrl, requestHeaders);
906
+ const dataWithEndBoundary = data.concat([
907
+ { headers: __spreadValues({ control: `snapshot-end` }, metadata) }
908
+ ]);
909
+ __privateGet(this, _snapshotTracker).addSnapshot(
910
+ metadata,
911
+ new Set(data.map((message) => message.key))
912
+ );
913
+ __privateMethod(this, _ShapeStream_instances, onMessages_fn).call(this, dataWithEndBoundary, false);
914
+ return {
915
+ metadata,
916
+ data
917
+ };
918
+ } finally {
919
+ __privateWrapper(this, _activeSnapshotRequests)._--;
920
+ if (__privateGet(this, _activeSnapshotRequests) === 0) {
921
+ __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
922
+ }
923
+ }
924
+ });
925
+ }
766
926
  };
767
927
  _error = new WeakMap();
768
928
  _fetchClient2 = new WeakMap();
@@ -775,8 +935,10 @@ _lastOffset = new WeakMap();
775
935
  _liveCacheBuster = new WeakMap();
776
936
  _lastSyncedAt = new WeakMap();
777
937
  _isUpToDate = new WeakMap();
938
+ _isMidStream = new WeakMap();
778
939
  _connected = new WeakMap();
779
940
  _shapeHandle = new WeakMap();
941
+ _mode = new WeakMap();
780
942
  _schema = new WeakMap();
781
943
  _onError = new WeakMap();
782
944
  _requestAbortController = new WeakMap();
@@ -785,6 +947,10 @@ _tickPromise = new WeakMap();
785
947
  _tickPromiseResolver = new WeakMap();
786
948
  _tickPromiseRejecter = new WeakMap();
787
949
  _messageChain = new WeakMap();
950
+ _snapshotTracker = new WeakMap();
951
+ _activeSnapshotRequests = new WeakMap();
952
+ _midStreamPromise = new WeakMap();
953
+ _midStreamPromiseResolver = new WeakMap();
788
954
  _ShapeStream_instances = new WeakSet();
789
955
  start_fn = function() {
790
956
  return __async(this, null, function* () {
@@ -873,15 +1039,13 @@ requestShape_fn = function() {
873
1039
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
874
1040
  });
875
1041
  };
876
- constructUrl_fn = function(url, resumingFromPause) {
1042
+ constructUrl_fn = function(url, resumingFromPause, subsetParams) {
877
1043
  return __async(this, null, function* () {
878
1044
  const [requestHeaders, params] = yield Promise.all([
879
1045
  resolveHeaders(this.options.headers),
880
1046
  this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
881
1047
  ]);
882
- if (params) {
883
- validateParams(params);
884
- }
1048
+ if (params) validateParams(params);
885
1049
  const fetchUrl = new URL(url);
886
1050
  if (params) {
887
1051
  if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
@@ -901,7 +1065,20 @@ constructUrl_fn = function(url, resumingFromPause) {
901
1065
  setQueryParam(fetchUrl, key, value);
902
1066
  }
903
1067
  }
1068
+ if (subsetParams) {
1069
+ if (subsetParams.where)
1070
+ setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, subsetParams.where);
1071
+ if (subsetParams.params)
1072
+ setQueryParam(fetchUrl, SUBSET_PARAM_WHERE_PARAMS, subsetParams.params);
1073
+ if (subsetParams.limit)
1074
+ setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit);
1075
+ if (subsetParams.offset)
1076
+ setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset);
1077
+ if (subsetParams.orderBy)
1078
+ setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, subsetParams.orderBy);
1079
+ }
904
1080
  fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
1081
+ fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
905
1082
  if (__privateGet(this, _isUpToDate)) {
906
1083
  if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
907
1084
  fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
@@ -971,7 +1148,9 @@ onInitialResponse_fn = function(response) {
971
1148
  };
972
1149
  onMessages_fn = function(batch, isSseMessage = false) {
973
1150
  return __async(this, null, function* () {
1151
+ var _a;
974
1152
  if (batch.length > 0) {
1153
+ __privateSet(this, _isMidStream, true);
975
1154
  const lastMessage = batch[batch.length - 1];
976
1155
  if (isUpToDateMessage(lastMessage)) {
977
1156
  if (isSseMessage) {
@@ -982,8 +1161,16 @@ onMessages_fn = function(batch, isSseMessage = false) {
982
1161
  }
983
1162
  __privateSet(this, _lastSyncedAt, Date.now());
984
1163
  __privateSet(this, _isUpToDate, true);
1164
+ __privateSet(this, _isMidStream, false);
1165
+ (_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
985
1166
  }
986
- yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, batch);
1167
+ const messagesToProcess = batch.filter((message) => {
1168
+ if (isChangeMessage(message)) {
1169
+ return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
1170
+ }
1171
+ return true;
1172
+ });
1173
+ yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
987
1174
  }
988
1175
  });
989
1176
  };
@@ -1081,6 +1268,24 @@ nextTick_fn = function() {
1081
1268
  return __privateGet(this, _tickPromise);
1082
1269
  });
1083
1270
  };
1271
+ waitForStreamEnd_fn = function() {
1272
+ return __async(this, null, function* () {
1273
+ if (!__privateGet(this, _isMidStream)) {
1274
+ return;
1275
+ }
1276
+ if (__privateGet(this, _midStreamPromise)) {
1277
+ return __privateGet(this, _midStreamPromise);
1278
+ }
1279
+ __privateSet(this, _midStreamPromise, new Promise((resolve) => {
1280
+ __privateSet(this, _midStreamPromiseResolver, resolve);
1281
+ }));
1282
+ __privateGet(this, _midStreamPromise).finally(() => {
1283
+ __privateSet(this, _midStreamPromise, void 0);
1284
+ __privateSet(this, _midStreamPromiseResolver, void 0);
1285
+ });
1286
+ return __privateGet(this, _midStreamPromise);
1287
+ });
1288
+ };
1084
1289
  publish_fn = function(messages) {
1085
1290
  return __async(this, null, function* () {
1086
1291
  __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
@@ -1125,8 +1330,33 @@ reset_fn = function(handle) {
1125
1330
  __privateSet(this, _liveCacheBuster, ``);
1126
1331
  __privateSet(this, _shapeHandle, handle);
1127
1332
  __privateSet(this, _isUpToDate, false);
1333
+ __privateSet(this, _isMidStream, true);
1128
1334
  __privateSet(this, _connected, false);
1129
1335
  __privateSet(this, _schema, void 0);
1336
+ __privateSet(this, _activeSnapshotRequests, 0);
1337
+ };
1338
+ fetchSnapshot_fn = function(url, headers) {
1339
+ return __async(this, null, function* () {
1340
+ const response = yield __privateGet(this, _fetchClient2).call(this, url.toString(), { headers });
1341
+ if (!response.ok) {
1342
+ throw new FetchError(
1343
+ response.status,
1344
+ void 0,
1345
+ void 0,
1346
+ Object.fromEntries([...response.headers.entries()]),
1347
+ url.toString()
1348
+ );
1349
+ }
1350
+ const { metadata, data } = yield response.json();
1351
+ const batch = __privateGet(this, _messageParser).parse(
1352
+ JSON.stringify(data),
1353
+ __privateGet(this, _schema)
1354
+ );
1355
+ return {
1356
+ metadata,
1357
+ data: batch
1358
+ };
1359
+ });
1130
1360
  };
1131
1361
  ShapeStream.Replica = {
1132
1362
  FULL: `full`,
@@ -1148,7 +1378,7 @@ function validateOptions(options) {
1148
1378
  if (options.signal && !(options.signal instanceof AbortSignal)) {
1149
1379
  throw new InvalidSignalError();
1150
1380
  }
1151
- if (options.offset !== void 0 && options.offset !== `-1` && !options.handle) {
1381
+ if (options.offset !== void 0 && options.offset !== `-1` && options.offset !== `now` && !options.handle) {
1152
1382
  throw new MissingShapeHandleError();
1153
1383
  }
1154
1384
  validateParams(options.params);
@@ -1177,12 +1407,15 @@ function convertWhereParamsToObj(allPgParams) {
1177
1407
  }
1178
1408
 
1179
1409
  // src/shape.ts
1180
- var _data, _subscribers2, _status, _error2, _Shape_instances, process_fn, updateShapeStatus_fn, handleError_fn, notify_fn;
1410
+ var _data, _subscribers2, _insertedKeys, _requestedSubSnapshots, _reexecuteSnapshotsPending, _status, _error2, _Shape_instances, process_fn, reexecuteSnapshots_fn, awaitUpToDate_fn, updateShapeStatus_fn, handleError_fn, notify_fn;
1181
1411
  var Shape = class {
1182
1412
  constructor(stream) {
1183
1413
  __privateAdd(this, _Shape_instances);
1184
1414
  __privateAdd(this, _data, /* @__PURE__ */ new Map());
1185
1415
  __privateAdd(this, _subscribers2, /* @__PURE__ */ new Map());
1416
+ __privateAdd(this, _insertedKeys, /* @__PURE__ */ new Set());
1417
+ __privateAdd(this, _requestedSubSnapshots, /* @__PURE__ */ new Set());
1418
+ __privateAdd(this, _reexecuteSnapshotsPending, false);
1186
1419
  __privateAdd(this, _status, `syncing`);
1187
1420
  __privateAdd(this, _error2, false);
1188
1421
  this.stream = stream;
@@ -1241,6 +1474,22 @@ var Shape = class {
1241
1474
  isConnected() {
1242
1475
  return this.stream.isConnected();
1243
1476
  }
1477
+ /** Current log mode of the underlying stream */
1478
+ get mode() {
1479
+ return this.stream.mode;
1480
+ }
1481
+ /**
1482
+ * Request a snapshot for subset of data. Only available when mode is changes_only.
1483
+ * Returns void; data will be emitted via the stream and processed by this Shape.
1484
+ */
1485
+ requestSnapshot(params) {
1486
+ return __async(this, null, function* () {
1487
+ const key = JSON.stringify(params);
1488
+ __privateGet(this, _requestedSubSnapshots).add(key);
1489
+ yield __privateMethod(this, _Shape_instances, awaitUpToDate_fn).call(this);
1490
+ yield this.stream.requestSnapshot(params);
1491
+ });
1492
+ }
1244
1493
  subscribe(callback) {
1245
1494
  const subscriptionId = Math.random();
1246
1495
  __privateGet(this, _subscribers2).set(subscriptionId, callback);
@@ -1257,6 +1506,9 @@ var Shape = class {
1257
1506
  };
1258
1507
  _data = new WeakMap();
1259
1508
  _subscribers2 = new WeakMap();
1509
+ _insertedKeys = new WeakMap();
1510
+ _requestedSubSnapshots = new WeakMap();
1511
+ _reexecuteSnapshotsPending = new WeakMap();
1260
1512
  _status = new WeakMap();
1261
1513
  _error2 = new WeakMap();
1262
1514
  _Shape_instances = new WeakSet();
@@ -1265,33 +1517,93 @@ process_fn = function(messages) {
1265
1517
  messages.forEach((message) => {
1266
1518
  if (isChangeMessage(message)) {
1267
1519
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1268
- switch (message.headers.operation) {
1269
- case `insert`:
1270
- __privateGet(this, _data).set(message.key, message.value);
1271
- break;
1272
- case `update`:
1273
- __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1274
- break;
1275
- case `delete`:
1276
- __privateGet(this, _data).delete(message.key);
1277
- break;
1520
+ if (this.mode === `full`) {
1521
+ switch (message.headers.operation) {
1522
+ case `insert`:
1523
+ __privateGet(this, _data).set(message.key, message.value);
1524
+ break;
1525
+ case `update`:
1526
+ __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1527
+ break;
1528
+ case `delete`:
1529
+ __privateGet(this, _data).delete(message.key);
1530
+ break;
1531
+ }
1532
+ } else {
1533
+ switch (message.headers.operation) {
1534
+ case `insert`:
1535
+ __privateGet(this, _insertedKeys).add(message.key);
1536
+ __privateGet(this, _data).set(message.key, message.value);
1537
+ break;
1538
+ case `update`:
1539
+ if (__privateGet(this, _insertedKeys).has(message.key)) {
1540
+ __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1541
+ }
1542
+ break;
1543
+ case `delete`:
1544
+ if (__privateGet(this, _insertedKeys).has(message.key)) {
1545
+ __privateGet(this, _data).delete(message.key);
1546
+ __privateGet(this, _insertedKeys).delete(message.key);
1547
+ }
1548
+ break;
1549
+ }
1278
1550
  }
1279
1551
  }
1280
1552
  if (isControlMessage(message)) {
1281
1553
  switch (message.headers.control) {
1282
1554
  case `up-to-date`:
1283
1555
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `up-to-date`);
1556
+ if (__privateGet(this, _reexecuteSnapshotsPending)) {
1557
+ __privateSet(this, _reexecuteSnapshotsPending, false);
1558
+ void __privateMethod(this, _Shape_instances, reexecuteSnapshots_fn).call(this);
1559
+ }
1284
1560
  break;
1285
1561
  case `must-refetch`:
1286
1562
  __privateGet(this, _data).clear();
1563
+ __privateGet(this, _insertedKeys).clear();
1287
1564
  __privateSet(this, _error2, false);
1288
1565
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1566
+ __privateSet(this, _reexecuteSnapshotsPending, true);
1289
1567
  break;
1290
1568
  }
1291
1569
  }
1292
1570
  });
1293
1571
  if (shouldNotify) __privateMethod(this, _Shape_instances, notify_fn).call(this);
1294
1572
  };
1573
+ reexecuteSnapshots_fn = function() {
1574
+ return __async(this, null, function* () {
1575
+ yield __privateMethod(this, _Shape_instances, awaitUpToDate_fn).call(this);
1576
+ yield Promise.all(
1577
+ Array.from(__privateGet(this, _requestedSubSnapshots)).map((jsonParams) => __async(this, null, function* () {
1578
+ try {
1579
+ const snapshot = JSON.parse(jsonParams);
1580
+ yield this.stream.requestSnapshot(snapshot);
1581
+ } catch (_) {
1582
+ }
1583
+ }))
1584
+ );
1585
+ });
1586
+ };
1587
+ awaitUpToDate_fn = function() {
1588
+ return __async(this, null, function* () {
1589
+ if (this.stream.isUpToDate) return;
1590
+ yield new Promise((resolve) => {
1591
+ const check = () => {
1592
+ if (this.stream.isUpToDate) {
1593
+ clearInterval(interval);
1594
+ unsub();
1595
+ resolve();
1596
+ }
1597
+ };
1598
+ const interval = setInterval(check, 10);
1599
+ const unsub = this.stream.subscribe(
1600
+ () => check(),
1601
+ () => check()
1602
+ );
1603
+ check();
1604
+ });
1605
+ });
1606
+ };
1295
1607
  updateShapeStatus_fn = function(status) {
1296
1608
  const stateChanged = __privateGet(this, _status) !== status;
1297
1609
  __privateSet(this, _status, status);
@@ -1316,6 +1628,7 @@ export {
1316
1628
  ShapeStream,
1317
1629
  isChangeMessage,
1318
1630
  isControlMessage,
1631
+ isVisibleInSnapshot,
1319
1632
  resolveValue
1320
1633
  };
1321
1634
  //# sourceMappingURL=index.mjs.map