@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.
@@ -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 {
@@ -188,8 +196,9 @@ function pgArrayParser(value, parser) {
188
196
  return loop(value)[0];
189
197
  }
190
198
  var MessageParser = class {
191
- constructor(parser) {
199
+ constructor(parser, transformer) {
192
200
  this.parser = __spreadValues(__spreadValues({}, defaultParser), parser);
201
+ this.transformer = transformer;
193
202
  }
194
203
  parse(messages, schema) {
195
204
  return JSON.parse(messages, (key, value) => {
@@ -198,6 +207,7 @@ var MessageParser = class {
198
207
  Object.keys(row).forEach((key2) => {
199
208
  row[key2] = this.parseRow(key2, row[key2], schema);
200
209
  });
210
+ if (this.transformer) value = this.transformer(value);
201
211
  }
202
212
  return value;
203
213
  });
@@ -254,6 +264,13 @@ function getOffset(message) {
254
264
  }
255
265
  return `${lsn}_0`;
256
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
+ }
257
274
 
258
275
  // src/constants.ts
259
276
  var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
@@ -263,6 +280,7 @@ var SHAPE_SCHEMA_HEADER = `electric-schema`;
263
280
  var CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`;
264
281
  var COLUMNS_QUERY_PARAM = `columns`;
265
282
  var LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`;
283
+ var EXPIRED_HANDLE_QUERY_PARAM = `expired_handle`;
266
284
  var SHAPE_HANDLE_QUERY_PARAM = `handle`;
267
285
  var LIVE_QUERY_PARAM = `live`;
268
286
  var OFFSET_QUERY_PARAM = `offset`;
@@ -273,11 +291,24 @@ var WHERE_PARAMS_PARAM = `params`;
273
291
  var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
274
292
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
275
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`;
276
300
  var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
277
301
  LIVE_QUERY_PARAM,
278
302
  SHAPE_HANDLE_QUERY_PARAM,
279
303
  OFFSET_QUERY_PARAM,
280
- LIVE_CACHE_BUSTER_QUERY_PARAM
304
+ LIVE_CACHE_BUSTER_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
281
312
  ];
282
313
 
283
314
  // src/fetch.ts
@@ -388,10 +419,20 @@ function createFetchWithResponseHeadersCheck(fetchClient) {
388
419
  const headers = response.headers;
389
420
  const missingHeaders = [];
390
421
  const addMissingHeaders = (requiredHeaders) => missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)));
391
- addMissingHeaders(requiredElectricResponseHeaders);
392
422
  const input = args[0];
393
423
  const urlString = input.toString();
394
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);
395
436
  if (url.searchParams.get(LIVE_QUERY_PARAM) === `true`) {
396
437
  addMissingHeaders(requiredLiveResponseHeaders);
397
438
  }
@@ -507,6 +548,123 @@ function noop() {
507
548
  import {
508
549
  fetchEventSource
509
550
  } from "@microsoft/fetch-event-source";
551
+
552
+ // src/expired-shapes-cache.ts
553
+ var ExpiredShapesCache = class {
554
+ constructor() {
555
+ this.data = {};
556
+ this.max = 250;
557
+ this.storageKey = `electric_expired_shapes`;
558
+ this.load();
559
+ }
560
+ getExpiredHandle(shapeUrl) {
561
+ const entry = this.data[shapeUrl];
562
+ if (entry) {
563
+ entry.lastUsed = Date.now();
564
+ this.save();
565
+ return entry.expiredHandle;
566
+ }
567
+ return null;
568
+ }
569
+ markExpired(shapeUrl, handle) {
570
+ this.data[shapeUrl] = { expiredHandle: handle, lastUsed: Date.now() };
571
+ const keys = Object.keys(this.data);
572
+ if (keys.length > this.max) {
573
+ const oldest = keys.reduce(
574
+ (min, k) => this.data[k].lastUsed < this.data[min].lastUsed ? k : min
575
+ );
576
+ delete this.data[oldest];
577
+ }
578
+ this.save();
579
+ }
580
+ save() {
581
+ if (typeof localStorage === `undefined`) return;
582
+ try {
583
+ localStorage.setItem(this.storageKey, JSON.stringify(this.data));
584
+ } catch (e) {
585
+ }
586
+ }
587
+ load() {
588
+ if (typeof localStorage === `undefined`) return;
589
+ try {
590
+ const stored = localStorage.getItem(this.storageKey);
591
+ if (stored) {
592
+ this.data = JSON.parse(stored);
593
+ }
594
+ } catch (e) {
595
+ this.data = {};
596
+ }
597
+ }
598
+ clear() {
599
+ this.data = {};
600
+ this.save();
601
+ }
602
+ };
603
+ var expiredShapesCache = new ExpiredShapesCache();
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
+
667
+ // src/client.ts
510
668
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
511
669
  LIVE_CACHE_BUSTER_QUERY_PARAM,
512
670
  SHAPE_HANDLE_QUERY_PARAM,
@@ -543,9 +701,18 @@ async function resolveHeaders(headers) {
543
701
  );
544
702
  return Object.fromEntries(resolvedEntries);
545
703
  }
546
- 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;
704
+ function canonicalShapeKey(url) {
705
+ const cleanUrl = new URL(url.origin + url.pathname);
706
+ for (const [key, value] of url.searchParams) {
707
+ if (!ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {
708
+ cleanUrl.searchParams.set(key, value);
709
+ }
710
+ }
711
+ cleanUrl.searchParams.sort();
712
+ return cleanUrl.toString();
713
+ }
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;
547
715
  var ShapeStream = class {
548
- // promise chain for incoming messages
549
716
  constructor(options) {
550
717
  __privateAdd(this, _ShapeStream_instances);
551
718
  __privateAdd(this, _error, null);
@@ -561,8 +728,10 @@ var ShapeStream = class {
561
728
  __privateAdd(this, _lastSyncedAt);
562
729
  // unix time
563
730
  __privateAdd(this, _isUpToDate, false);
731
+ __privateAdd(this, _isMidStream, true);
564
732
  __privateAdd(this, _connected, false);
565
733
  __privateAdd(this, _shapeHandle);
734
+ __privateAdd(this, _mode);
566
735
  __privateAdd(this, _schema);
567
736
  __privateAdd(this, _onError);
568
737
  __privateAdd(this, _requestAbortController);
@@ -571,16 +740,26 @@ var ShapeStream = class {
571
740
  __privateAdd(this, _tickPromiseResolver);
572
741
  __privateAdd(this, _tickPromiseRejecter);
573
742
  __privateAdd(this, _messageChain, Promise.resolve([]));
574
- 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;
575
750
  this.options = __spreadValues({ subscribe: true }, options);
576
751
  validateOptions(this.options);
577
752
  __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
578
753
  __privateSet(this, _liveCacheBuster, ``);
579
754
  __privateSet(this, _shapeHandle, this.options.handle);
580
- __privateSet(this, _messageParser, new MessageParser(options.parser));
755
+ __privateSet(this, _messageParser, new MessageParser(
756
+ options.parser,
757
+ options.transformer
758
+ ));
581
759
  __privateSet(this, _onError, this.options.onError);
582
- const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
583
- 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), {
584
763
  onFailedAttempt: () => {
585
764
  var _a2, _b2;
586
765
  __privateSet(this, _connected, false);
@@ -609,6 +788,9 @@ var ShapeStream = class {
609
788
  get lastOffset() {
610
789
  return __privateGet(this, _lastOffset);
611
790
  }
791
+ get mode() {
792
+ return __privateGet(this, _mode);
793
+ }
612
794
  subscribe(callback, onError = () => {
613
795
  }) {
614
796
  const subscriptionId = Math.random();
@@ -659,6 +841,54 @@ var ShapeStream = class {
659
841
  await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
660
842
  __privateSet(this, _isRefreshing, false);
661
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
+ }
662
892
  };
663
893
  _error = new WeakMap();
664
894
  _fetchClient2 = new WeakMap();
@@ -671,8 +901,10 @@ _lastOffset = new WeakMap();
671
901
  _liveCacheBuster = new WeakMap();
672
902
  _lastSyncedAt = new WeakMap();
673
903
  _isUpToDate = new WeakMap();
904
+ _isMidStream = new WeakMap();
674
905
  _connected = new WeakMap();
675
906
  _shapeHandle = new WeakMap();
907
+ _mode = new WeakMap();
676
908
  _schema = new WeakMap();
677
909
  _onError = new WeakMap();
678
910
  _requestAbortController = new WeakMap();
@@ -681,6 +913,10 @@ _tickPromise = new WeakMap();
681
913
  _tickPromiseResolver = new WeakMap();
682
914
  _tickPromiseRejecter = new WeakMap();
683
915
  _messageChain = new WeakMap();
916
+ _snapshotTracker = new WeakMap();
917
+ _activeSnapshotRequests = new WeakMap();
918
+ _midStreamPromise = new WeakMap();
919
+ _midStreamPromiseResolver = new WeakMap();
684
920
  _ShapeStream_instances = new WeakSet();
685
921
  start_fn = async function() {
686
922
  var _a;
@@ -744,6 +980,10 @@ requestShape_fn = async function() {
744
980
  }
745
981
  if (!(e instanceof FetchError)) throw e;
746
982
  if (e.status == 409) {
983
+ if (__privateGet(this, _shapeHandle)) {
984
+ const shapeKey = canonicalShapeKey(fetchUrl);
985
+ expiredShapesCache.markExpired(shapeKey, __privateGet(this, _shapeHandle));
986
+ }
747
987
  const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _shapeHandle)}-next`;
748
988
  __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
749
989
  await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
@@ -761,14 +1001,12 @@ requestShape_fn = async function() {
761
1001
  (_b = __privateGet(this, _tickPromiseResolver)) == null ? void 0 : _b.call(this);
762
1002
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
763
1003
  };
764
- constructUrl_fn = async function(url, resumingFromPause) {
1004
+ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
765
1005
  const [requestHeaders, params] = await Promise.all([
766
1006
  resolveHeaders(this.options.headers),
767
1007
  this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
768
1008
  ]);
769
- if (params) {
770
- validateParams(params);
771
- }
1009
+ if (params) validateParams(params);
772
1010
  const fetchUrl = new URL(url);
773
1011
  if (params) {
774
1012
  if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
@@ -788,7 +1026,20 @@ constructUrl_fn = async function(url, resumingFromPause) {
788
1026
  setQueryParam(fetchUrl, key, value);
789
1027
  }
790
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
+ }
791
1041
  fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
1042
+ fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
792
1043
  if (__privateGet(this, _isUpToDate)) {
793
1044
  if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
794
1045
  fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
@@ -801,6 +1052,11 @@ constructUrl_fn = async function(url, resumingFromPause) {
801
1052
  if (__privateGet(this, _shapeHandle)) {
802
1053
  fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shapeHandle));
803
1054
  }
1055
+ const shapeKey = canonicalShapeKey(fetchUrl);
1056
+ const expiredHandle = expiredShapesCache.getExpiredHandle(shapeKey);
1057
+ if (expiredHandle) {
1058
+ fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
1059
+ }
804
1060
  fetchUrl.searchParams.sort();
805
1061
  return {
806
1062
  fetchUrl,
@@ -847,7 +1103,9 @@ onInitialResponse_fn = async function(response) {
847
1103
  }
848
1104
  };
849
1105
  onMessages_fn = async function(batch, isSseMessage = false) {
1106
+ var _a;
850
1107
  if (batch.length > 0) {
1108
+ __privateSet(this, _isMidStream, true);
851
1109
  const lastMessage = batch[batch.length - 1];
852
1110
  if (isUpToDateMessage(lastMessage)) {
853
1111
  if (isSseMessage) {
@@ -858,8 +1116,16 @@ onMessages_fn = async function(batch, isSseMessage = false) {
858
1116
  }
859
1117
  __privateSet(this, _lastSyncedAt, Date.now());
860
1118
  __privateSet(this, _isUpToDate, true);
1119
+ __privateSet(this, _isMidStream, false);
1120
+ (_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
861
1121
  }
862
- 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);
863
1129
  }
864
1130
  };
865
1131
  fetchShape_fn = async function(opts) {
@@ -948,6 +1214,22 @@ nextTick_fn = async function() {
948
1214
  });
949
1215
  return __privateGet(this, _tickPromise);
950
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
+ };
951
1233
  publish_fn = async function(messages) {
952
1234
  __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
953
1235
  () => Promise.all(
@@ -990,8 +1272,31 @@ reset_fn = function(handle) {
990
1272
  __privateSet(this, _liveCacheBuster, ``);
991
1273
  __privateSet(this, _shapeHandle, handle);
992
1274
  __privateSet(this, _isUpToDate, false);
1275
+ __privateSet(this, _isMidStream, true);
993
1276
  __privateSet(this, _connected, false);
994
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
+ };
995
1300
  };
996
1301
  ShapeStream.Replica = {
997
1302
  FULL: `full`,
@@ -1013,7 +1318,7 @@ function validateOptions(options) {
1013
1318
  if (options.signal && !(options.signal instanceof AbortSignal)) {
1014
1319
  throw new InvalidSignalError();
1015
1320
  }
1016
- if (options.offset !== void 0 && options.offset !== `-1` && !options.handle) {
1321
+ if (options.offset !== void 0 && options.offset !== `-1` && options.offset !== `now` && !options.handle) {
1017
1322
  throw new MissingShapeHandleError();
1018
1323
  }
1019
1324
  validateParams(options.params);
@@ -1042,12 +1347,15 @@ function convertWhereParamsToObj(allPgParams) {
1042
1347
  }
1043
1348
 
1044
1349
  // src/shape.ts
1045
- 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;
1046
1351
  var Shape = class {
1047
1352
  constructor(stream) {
1048
1353
  __privateAdd(this, _Shape_instances);
1049
1354
  __privateAdd(this, _data, /* @__PURE__ */ new Map());
1050
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);
1051
1359
  __privateAdd(this, _status, `syncing`);
1052
1360
  __privateAdd(this, _error2, false);
1053
1361
  this.stream = stream;
@@ -1106,6 +1414,20 @@ var Shape = class {
1106
1414
  isConnected() {
1107
1415
  return this.stream.isConnected();
1108
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
+ }
1109
1431
  subscribe(callback) {
1110
1432
  const subscriptionId = Math.random();
1111
1433
  __privateGet(this, _subscribers2).set(subscriptionId, callback);
@@ -1122,6 +1444,9 @@ var Shape = class {
1122
1444
  };
1123
1445
  _data = new WeakMap();
1124
1446
  _subscribers2 = new WeakMap();
1447
+ _insertedKeys = new WeakMap();
1448
+ _requestedSubSnapshots = new WeakMap();
1449
+ _reexecuteSnapshotsPending = new WeakMap();
1125
1450
  _status = new WeakMap();
1126
1451
  _error2 = new WeakMap();
1127
1452
  _Shape_instances = new WeakSet();
@@ -1130,33 +1455,89 @@ process_fn = function(messages) {
1130
1455
  messages.forEach((message) => {
1131
1456
  if (isChangeMessage(message)) {
1132
1457
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1133
- switch (message.headers.operation) {
1134
- case `insert`:
1135
- __privateGet(this, _data).set(message.key, message.value);
1136
- break;
1137
- case `update`:
1138
- __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1139
- break;
1140
- case `delete`:
1141
- __privateGet(this, _data).delete(message.key);
1142
- 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
+ }
1143
1488
  }
1144
1489
  }
1145
1490
  if (isControlMessage(message)) {
1146
1491
  switch (message.headers.control) {
1147
1492
  case `up-to-date`:
1148
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
+ }
1149
1498
  break;
1150
1499
  case `must-refetch`:
1151
1500
  __privateGet(this, _data).clear();
1501
+ __privateGet(this, _insertedKeys).clear();
1152
1502
  __privateSet(this, _error2, false);
1153
1503
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1504
+ __privateSet(this, _reexecuteSnapshotsPending, true);
1154
1505
  break;
1155
1506
  }
1156
1507
  }
1157
1508
  });
1158
1509
  if (shouldNotify) __privateMethod(this, _Shape_instances, notify_fn).call(this);
1159
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
+ };
1160
1541
  updateShapeStatus_fn = function(status) {
1161
1542
  const stateChanged = __privateGet(this, _status) !== status;
1162
1543
  __privateSet(this, _status, status);
@@ -1181,6 +1562,7 @@ export {
1181
1562
  ShapeStream,
1182
1563
  isChangeMessage,
1183
1564
  isControlMessage,
1565
+ isVisibleInSnapshot,
1184
1566
  resolveValue
1185
1567
  };
1186
1568
  //# sourceMappingURL=index.legacy-esm.js.map