@electric-sql/client 1.0.9 → 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.
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) => {
@@ -210,8 +218,9 @@ function pgArrayParser(value, parser) {
210
218
  return loop(value)[0];
211
219
  }
212
220
  var MessageParser = class {
213
- constructor(parser) {
221
+ constructor(parser, transformer) {
214
222
  this.parser = __spreadValues(__spreadValues({}, defaultParser), parser);
223
+ this.transformer = transformer;
215
224
  }
216
225
  parse(messages, schema) {
217
226
  return JSON.parse(messages, (key, value) => {
@@ -220,6 +229,7 @@ var MessageParser = class {
220
229
  Object.keys(row).forEach((key2) => {
221
230
  row[key2] = this.parseRow(key2, row[key2], schema);
222
231
  });
232
+ if (this.transformer) value = this.transformer(value);
223
233
  }
224
234
  return value;
225
235
  });
@@ -276,6 +286,13 @@ function getOffset(message) {
276
286
  }
277
287
  return `${lsn}_0`;
278
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
+ }
279
296
 
280
297
  // src/constants.ts
281
298
  var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
@@ -285,6 +302,7 @@ var SHAPE_SCHEMA_HEADER = `electric-schema`;
285
302
  var CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`;
286
303
  var COLUMNS_QUERY_PARAM = `columns`;
287
304
  var LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`;
305
+ var EXPIRED_HANDLE_QUERY_PARAM = `expired_handle`;
288
306
  var SHAPE_HANDLE_QUERY_PARAM = `handle`;
289
307
  var LIVE_QUERY_PARAM = `live`;
290
308
  var OFFSET_QUERY_PARAM = `offset`;
@@ -295,11 +313,24 @@ var WHERE_PARAMS_PARAM = `params`;
295
313
  var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
296
314
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
297
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`;
298
322
  var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
299
323
  LIVE_QUERY_PARAM,
300
324
  SHAPE_HANDLE_QUERY_PARAM,
301
325
  OFFSET_QUERY_PARAM,
302
- LIVE_CACHE_BUSTER_QUERY_PARAM
326
+ LIVE_CACHE_BUSTER_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
303
334
  ];
304
335
 
305
336
  // src/fetch.ts
@@ -410,10 +441,20 @@ function createFetchWithResponseHeadersCheck(fetchClient) {
410
441
  const headers = response.headers;
411
442
  const missingHeaders = [];
412
443
  const addMissingHeaders = (requiredHeaders) => missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)));
413
- addMissingHeaders(requiredElectricResponseHeaders);
414
444
  const input = args[0];
415
445
  const urlString = input.toString();
416
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);
417
458
  if (url.searchParams.get(LIVE_QUERY_PARAM) === `true`) {
418
459
  addMissingHeaders(requiredLiveResponseHeaders);
419
460
  }
@@ -529,6 +570,123 @@ function noop() {
529
570
  import {
530
571
  fetchEventSource
531
572
  } from "@microsoft/fetch-event-source";
573
+
574
+ // src/expired-shapes-cache.ts
575
+ var ExpiredShapesCache = class {
576
+ constructor() {
577
+ this.data = {};
578
+ this.max = 250;
579
+ this.storageKey = `electric_expired_shapes`;
580
+ this.load();
581
+ }
582
+ getExpiredHandle(shapeUrl) {
583
+ const entry = this.data[shapeUrl];
584
+ if (entry) {
585
+ entry.lastUsed = Date.now();
586
+ this.save();
587
+ return entry.expiredHandle;
588
+ }
589
+ return null;
590
+ }
591
+ markExpired(shapeUrl, handle) {
592
+ this.data[shapeUrl] = { expiredHandle: handle, lastUsed: Date.now() };
593
+ const keys = Object.keys(this.data);
594
+ if (keys.length > this.max) {
595
+ const oldest = keys.reduce(
596
+ (min, k) => this.data[k].lastUsed < this.data[min].lastUsed ? k : min
597
+ );
598
+ delete this.data[oldest];
599
+ }
600
+ this.save();
601
+ }
602
+ save() {
603
+ if (typeof localStorage === `undefined`) return;
604
+ try {
605
+ localStorage.setItem(this.storageKey, JSON.stringify(this.data));
606
+ } catch (e) {
607
+ }
608
+ }
609
+ load() {
610
+ if (typeof localStorage === `undefined`) return;
611
+ try {
612
+ const stored = localStorage.getItem(this.storageKey);
613
+ if (stored) {
614
+ this.data = JSON.parse(stored);
615
+ }
616
+ } catch (e) {
617
+ this.data = {};
618
+ }
619
+ }
620
+ clear() {
621
+ this.data = {};
622
+ this.save();
623
+ }
624
+ };
625
+ var expiredShapesCache = new ExpiredShapesCache();
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
+
689
+ // src/client.ts
532
690
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
533
691
  LIVE_CACHE_BUSTER_QUERY_PARAM,
534
692
  SHAPE_HANDLE_QUERY_PARAM,
@@ -573,9 +731,18 @@ function resolveHeaders(headers) {
573
731
  return Object.fromEntries(resolvedEntries);
574
732
  });
575
733
  }
576
- 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;
734
+ function canonicalShapeKey(url) {
735
+ const cleanUrl = new URL(url.origin + url.pathname);
736
+ for (const [key, value] of url.searchParams) {
737
+ if (!ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {
738
+ cleanUrl.searchParams.set(key, value);
739
+ }
740
+ }
741
+ cleanUrl.searchParams.sort();
742
+ return cleanUrl.toString();
743
+ }
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;
577
745
  var ShapeStream = class {
578
- // promise chain for incoming messages
579
746
  constructor(options) {
580
747
  __privateAdd(this, _ShapeStream_instances);
581
748
  __privateAdd(this, _error, null);
@@ -591,8 +758,10 @@ var ShapeStream = class {
591
758
  __privateAdd(this, _lastSyncedAt);
592
759
  // unix time
593
760
  __privateAdd(this, _isUpToDate, false);
761
+ __privateAdd(this, _isMidStream, true);
594
762
  __privateAdd(this, _connected, false);
595
763
  __privateAdd(this, _shapeHandle);
764
+ __privateAdd(this, _mode);
596
765
  __privateAdd(this, _schema);
597
766
  __privateAdd(this, _onError);
598
767
  __privateAdd(this, _requestAbortController);
@@ -601,16 +770,26 @@ var ShapeStream = class {
601
770
  __privateAdd(this, _tickPromiseResolver);
602
771
  __privateAdd(this, _tickPromiseRejecter);
603
772
  __privateAdd(this, _messageChain, Promise.resolve([]));
604
- 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;
605
780
  this.options = __spreadValues({ subscribe: true }, options);
606
781
  validateOptions(this.options);
607
782
  __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
608
783
  __privateSet(this, _liveCacheBuster, ``);
609
784
  __privateSet(this, _shapeHandle, this.options.handle);
610
- __privateSet(this, _messageParser, new MessageParser(options.parser));
785
+ __privateSet(this, _messageParser, new MessageParser(
786
+ options.parser,
787
+ options.transformer
788
+ ));
611
789
  __privateSet(this, _onError, this.options.onError);
612
- const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
613
- const backOffOpts = __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
790
+ __privateSet(this, _mode, (_b = this.options.mode) != 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), {
614
793
  onFailedAttempt: () => {
615
794
  var _a2, _b2;
616
795
  __privateSet(this, _connected, false);
@@ -639,6 +818,9 @@ var ShapeStream = class {
639
818
  get lastOffset() {
640
819
  return __privateGet(this, _lastOffset);
641
820
  }
821
+ get mode() {
822
+ return __privateGet(this, _mode);
823
+ }
642
824
  subscribe(callback, onError = () => {
643
825
  }) {
644
826
  const subscriptionId = Math.random();
@@ -691,6 +873,56 @@ var ShapeStream = class {
691
873
  __privateSet(this, _isRefreshing, false);
692
874
  });
693
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
+ }
694
926
  };
695
927
  _error = new WeakMap();
696
928
  _fetchClient2 = new WeakMap();
@@ -703,8 +935,10 @@ _lastOffset = new WeakMap();
703
935
  _liveCacheBuster = new WeakMap();
704
936
  _lastSyncedAt = new WeakMap();
705
937
  _isUpToDate = new WeakMap();
938
+ _isMidStream = new WeakMap();
706
939
  _connected = new WeakMap();
707
940
  _shapeHandle = new WeakMap();
941
+ _mode = new WeakMap();
708
942
  _schema = new WeakMap();
709
943
  _onError = new WeakMap();
710
944
  _requestAbortController = new WeakMap();
@@ -713,6 +947,10 @@ _tickPromise = new WeakMap();
713
947
  _tickPromiseResolver = new WeakMap();
714
948
  _tickPromiseRejecter = new WeakMap();
715
949
  _messageChain = new WeakMap();
950
+ _snapshotTracker = new WeakMap();
951
+ _activeSnapshotRequests = new WeakMap();
952
+ _midStreamPromise = new WeakMap();
953
+ _midStreamPromiseResolver = new WeakMap();
716
954
  _ShapeStream_instances = new WeakSet();
717
955
  start_fn = function() {
718
956
  return __async(this, null, function* () {
@@ -779,6 +1017,10 @@ requestShape_fn = function() {
779
1017
  }
780
1018
  if (!(e instanceof FetchError)) throw e;
781
1019
  if (e.status == 409) {
1020
+ if (__privateGet(this, _shapeHandle)) {
1021
+ const shapeKey = canonicalShapeKey(fetchUrl);
1022
+ expiredShapesCache.markExpired(shapeKey, __privateGet(this, _shapeHandle));
1023
+ }
782
1024
  const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _shapeHandle)}-next`;
783
1025
  __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
784
1026
  yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
@@ -797,15 +1039,13 @@ requestShape_fn = function() {
797
1039
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
798
1040
  });
799
1041
  };
800
- constructUrl_fn = function(url, resumingFromPause) {
1042
+ constructUrl_fn = function(url, resumingFromPause, subsetParams) {
801
1043
  return __async(this, null, function* () {
802
1044
  const [requestHeaders, params] = yield Promise.all([
803
1045
  resolveHeaders(this.options.headers),
804
1046
  this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
805
1047
  ]);
806
- if (params) {
807
- validateParams(params);
808
- }
1048
+ if (params) validateParams(params);
809
1049
  const fetchUrl = new URL(url);
810
1050
  if (params) {
811
1051
  if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
@@ -825,7 +1065,20 @@ constructUrl_fn = function(url, resumingFromPause) {
825
1065
  setQueryParam(fetchUrl, key, value);
826
1066
  }
827
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
+ }
828
1080
  fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
1081
+ fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
829
1082
  if (__privateGet(this, _isUpToDate)) {
830
1083
  if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
831
1084
  fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
@@ -838,6 +1091,11 @@ constructUrl_fn = function(url, resumingFromPause) {
838
1091
  if (__privateGet(this, _shapeHandle)) {
839
1092
  fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shapeHandle));
840
1093
  }
1094
+ const shapeKey = canonicalShapeKey(fetchUrl);
1095
+ const expiredHandle = expiredShapesCache.getExpiredHandle(shapeKey);
1096
+ if (expiredHandle) {
1097
+ fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
1098
+ }
841
1099
  fetchUrl.searchParams.sort();
842
1100
  return {
843
1101
  fetchUrl,
@@ -890,7 +1148,9 @@ onInitialResponse_fn = function(response) {
890
1148
  };
891
1149
  onMessages_fn = function(batch, isSseMessage = false) {
892
1150
  return __async(this, null, function* () {
1151
+ var _a;
893
1152
  if (batch.length > 0) {
1153
+ __privateSet(this, _isMidStream, true);
894
1154
  const lastMessage = batch[batch.length - 1];
895
1155
  if (isUpToDateMessage(lastMessage)) {
896
1156
  if (isSseMessage) {
@@ -901,8 +1161,16 @@ onMessages_fn = function(batch, isSseMessage = false) {
901
1161
  }
902
1162
  __privateSet(this, _lastSyncedAt, Date.now());
903
1163
  __privateSet(this, _isUpToDate, true);
1164
+ __privateSet(this, _isMidStream, false);
1165
+ (_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
904
1166
  }
905
- 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);
906
1174
  }
907
1175
  });
908
1176
  };
@@ -1000,6 +1268,24 @@ nextTick_fn = function() {
1000
1268
  return __privateGet(this, _tickPromise);
1001
1269
  });
1002
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
+ };
1003
1289
  publish_fn = function(messages) {
1004
1290
  return __async(this, null, function* () {
1005
1291
  __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
@@ -1044,8 +1330,33 @@ reset_fn = function(handle) {
1044
1330
  __privateSet(this, _liveCacheBuster, ``);
1045
1331
  __privateSet(this, _shapeHandle, handle);
1046
1332
  __privateSet(this, _isUpToDate, false);
1333
+ __privateSet(this, _isMidStream, true);
1047
1334
  __privateSet(this, _connected, false);
1048
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
+ });
1049
1360
  };
1050
1361
  ShapeStream.Replica = {
1051
1362
  FULL: `full`,
@@ -1067,7 +1378,7 @@ function validateOptions(options) {
1067
1378
  if (options.signal && !(options.signal instanceof AbortSignal)) {
1068
1379
  throw new InvalidSignalError();
1069
1380
  }
1070
- if (options.offset !== void 0 && options.offset !== `-1` && !options.handle) {
1381
+ if (options.offset !== void 0 && options.offset !== `-1` && options.offset !== `now` && !options.handle) {
1071
1382
  throw new MissingShapeHandleError();
1072
1383
  }
1073
1384
  validateParams(options.params);
@@ -1096,12 +1407,15 @@ function convertWhereParamsToObj(allPgParams) {
1096
1407
  }
1097
1408
 
1098
1409
  // src/shape.ts
1099
- 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;
1100
1411
  var Shape = class {
1101
1412
  constructor(stream) {
1102
1413
  __privateAdd(this, _Shape_instances);
1103
1414
  __privateAdd(this, _data, /* @__PURE__ */ new Map());
1104
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);
1105
1419
  __privateAdd(this, _status, `syncing`);
1106
1420
  __privateAdd(this, _error2, false);
1107
1421
  this.stream = stream;
@@ -1160,6 +1474,22 @@ var Shape = class {
1160
1474
  isConnected() {
1161
1475
  return this.stream.isConnected();
1162
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
+ }
1163
1493
  subscribe(callback) {
1164
1494
  const subscriptionId = Math.random();
1165
1495
  __privateGet(this, _subscribers2).set(subscriptionId, callback);
@@ -1176,6 +1506,9 @@ var Shape = class {
1176
1506
  };
1177
1507
  _data = new WeakMap();
1178
1508
  _subscribers2 = new WeakMap();
1509
+ _insertedKeys = new WeakMap();
1510
+ _requestedSubSnapshots = new WeakMap();
1511
+ _reexecuteSnapshotsPending = new WeakMap();
1179
1512
  _status = new WeakMap();
1180
1513
  _error2 = new WeakMap();
1181
1514
  _Shape_instances = new WeakSet();
@@ -1184,33 +1517,93 @@ process_fn = function(messages) {
1184
1517
  messages.forEach((message) => {
1185
1518
  if (isChangeMessage(message)) {
1186
1519
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1187
- switch (message.headers.operation) {
1188
- case `insert`:
1189
- __privateGet(this, _data).set(message.key, message.value);
1190
- break;
1191
- case `update`:
1192
- __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1193
- break;
1194
- case `delete`:
1195
- __privateGet(this, _data).delete(message.key);
1196
- 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
+ }
1197
1550
  }
1198
1551
  }
1199
1552
  if (isControlMessage(message)) {
1200
1553
  switch (message.headers.control) {
1201
1554
  case `up-to-date`:
1202
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
+ }
1203
1560
  break;
1204
1561
  case `must-refetch`:
1205
1562
  __privateGet(this, _data).clear();
1563
+ __privateGet(this, _insertedKeys).clear();
1206
1564
  __privateSet(this, _error2, false);
1207
1565
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1566
+ __privateSet(this, _reexecuteSnapshotsPending, true);
1208
1567
  break;
1209
1568
  }
1210
1569
  }
1211
1570
  });
1212
1571
  if (shouldNotify) __privateMethod(this, _Shape_instances, notify_fn).call(this);
1213
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
+ };
1214
1607
  updateShapeStatus_fn = function(status) {
1215
1608
  const stateChanged = __privateGet(this, _status) !== status;
1216
1609
  __privateSet(this, _status, status);
@@ -1235,6 +1628,7 @@ export {
1235
1628
  ShapeStream,
1236
1629
  isChangeMessage,
1237
1630
  isControlMessage,
1631
+ isVisibleInSnapshot,
1238
1632
  resolveValue
1239
1633
  };
1240
1634
  //# sourceMappingURL=index.mjs.map