@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.
@@ -53,6 +53,14 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
53
53
  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);
54
54
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
55
55
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
56
+ var __privateWrapper = (obj, member, setter, getter) => ({
57
+ set _(value) {
58
+ __privateSet(obj, member, value, setter);
59
+ },
60
+ get _() {
61
+ return __privateGet(obj, member, getter);
62
+ }
63
+ });
56
64
  var __async = (__this, __arguments, generator) => {
57
65
  return new Promise((resolve, reject) => {
58
66
  var fulfilled = (value) => {
@@ -84,6 +92,7 @@ __export(src_exports, {
84
92
  ShapeStream: () => ShapeStream,
85
93
  isChangeMessage: () => isChangeMessage,
86
94
  isControlMessage: () => isControlMessage,
95
+ isVisibleInSnapshot: () => isVisibleInSnapshot,
87
96
  resolveValue: () => resolveValue
88
97
  });
89
98
  module.exports = __toCommonJS(src_exports);
@@ -240,8 +249,9 @@ function pgArrayParser(value, parser) {
240
249
  return loop(value)[0];
241
250
  }
242
251
  var MessageParser = class {
243
- constructor(parser) {
252
+ constructor(parser, transformer) {
244
253
  this.parser = __spreadValues(__spreadValues({}, defaultParser), parser);
254
+ this.transformer = transformer;
245
255
  }
246
256
  parse(messages, schema) {
247
257
  return JSON.parse(messages, (key, value) => {
@@ -250,6 +260,7 @@ var MessageParser = class {
250
260
  Object.keys(row).forEach((key2) => {
251
261
  row[key2] = this.parseRow(key2, row[key2], schema);
252
262
  });
263
+ if (this.transformer) value = this.transformer(value);
253
264
  }
254
265
  return value;
255
266
  });
@@ -306,6 +317,13 @@ function getOffset(message) {
306
317
  }
307
318
  return `${lsn}_0`;
308
319
  }
320
+ function isVisibleInSnapshot(txid, snapshot) {
321
+ const xid = BigInt(txid);
322
+ const xmin = BigInt(snapshot.xmin);
323
+ const xmax = BigInt(snapshot.xmax);
324
+ const xip = snapshot.xip_list.map(BigInt);
325
+ return xid < xmin || xid < xmax && !xip.includes(xid);
326
+ }
309
327
 
310
328
  // src/constants.ts
311
329
  var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
@@ -315,6 +333,7 @@ var SHAPE_SCHEMA_HEADER = `electric-schema`;
315
333
  var CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`;
316
334
  var COLUMNS_QUERY_PARAM = `columns`;
317
335
  var LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`;
336
+ var EXPIRED_HANDLE_QUERY_PARAM = `expired_handle`;
318
337
  var SHAPE_HANDLE_QUERY_PARAM = `handle`;
319
338
  var LIVE_QUERY_PARAM = `live`;
320
339
  var OFFSET_QUERY_PARAM = `offset`;
@@ -325,11 +344,24 @@ var WHERE_PARAMS_PARAM = `params`;
325
344
  var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
326
345
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
327
346
  var PAUSE_STREAM = `pause-stream`;
347
+ var LOG_MODE_QUERY_PARAM = `log`;
348
+ var SUBSET_PARAM_WHERE = `subset__where`;
349
+ var SUBSET_PARAM_LIMIT = `subset__limit`;
350
+ var SUBSET_PARAM_OFFSET = `subset__offset`;
351
+ var SUBSET_PARAM_ORDER_BY = `subset__order_by`;
352
+ var SUBSET_PARAM_WHERE_PARAMS = `subset__params`;
328
353
  var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
329
354
  LIVE_QUERY_PARAM,
330
355
  SHAPE_HANDLE_QUERY_PARAM,
331
356
  OFFSET_QUERY_PARAM,
332
- LIVE_CACHE_BUSTER_QUERY_PARAM
357
+ LIVE_CACHE_BUSTER_QUERY_PARAM,
358
+ EXPIRED_HANDLE_QUERY_PARAM,
359
+ LOG_MODE_QUERY_PARAM,
360
+ SUBSET_PARAM_WHERE,
361
+ SUBSET_PARAM_LIMIT,
362
+ SUBSET_PARAM_OFFSET,
363
+ SUBSET_PARAM_ORDER_BY,
364
+ SUBSET_PARAM_WHERE_PARAMS
333
365
  ];
334
366
 
335
367
  // src/fetch.ts
@@ -440,10 +472,20 @@ function createFetchWithResponseHeadersCheck(fetchClient) {
440
472
  const headers = response.headers;
441
473
  const missingHeaders = [];
442
474
  const addMissingHeaders = (requiredHeaders) => missingHeaders.push(...requiredHeaders.filter((h) => !headers.has(h)));
443
- addMissingHeaders(requiredElectricResponseHeaders);
444
475
  const input = args[0];
445
476
  const urlString = input.toString();
446
477
  const url = new URL(urlString);
478
+ const isSnapshotRequest = [
479
+ SUBSET_PARAM_WHERE,
480
+ SUBSET_PARAM_WHERE_PARAMS,
481
+ SUBSET_PARAM_LIMIT,
482
+ SUBSET_PARAM_OFFSET,
483
+ SUBSET_PARAM_ORDER_BY
484
+ ].some((p) => url.searchParams.has(p));
485
+ if (isSnapshotRequest) {
486
+ return response;
487
+ }
488
+ addMissingHeaders(requiredElectricResponseHeaders);
447
489
  if (url.searchParams.get(LIVE_QUERY_PARAM) === `true`) {
448
490
  addMissingHeaders(requiredLiveResponseHeaders);
449
491
  }
@@ -557,6 +599,123 @@ function noop() {
557
599
 
558
600
  // src/client.ts
559
601
  var import_fetch_event_source = require("@microsoft/fetch-event-source");
602
+
603
+ // src/expired-shapes-cache.ts
604
+ var ExpiredShapesCache = class {
605
+ constructor() {
606
+ this.data = {};
607
+ this.max = 250;
608
+ this.storageKey = `electric_expired_shapes`;
609
+ this.load();
610
+ }
611
+ getExpiredHandle(shapeUrl) {
612
+ const entry = this.data[shapeUrl];
613
+ if (entry) {
614
+ entry.lastUsed = Date.now();
615
+ this.save();
616
+ return entry.expiredHandle;
617
+ }
618
+ return null;
619
+ }
620
+ markExpired(shapeUrl, handle) {
621
+ this.data[shapeUrl] = { expiredHandle: handle, lastUsed: Date.now() };
622
+ const keys = Object.keys(this.data);
623
+ if (keys.length > this.max) {
624
+ const oldest = keys.reduce(
625
+ (min, k) => this.data[k].lastUsed < this.data[min].lastUsed ? k : min
626
+ );
627
+ delete this.data[oldest];
628
+ }
629
+ this.save();
630
+ }
631
+ save() {
632
+ if (typeof localStorage === `undefined`) return;
633
+ try {
634
+ localStorage.setItem(this.storageKey, JSON.stringify(this.data));
635
+ } catch (e) {
636
+ }
637
+ }
638
+ load() {
639
+ if (typeof localStorage === `undefined`) return;
640
+ try {
641
+ const stored = localStorage.getItem(this.storageKey);
642
+ if (stored) {
643
+ this.data = JSON.parse(stored);
644
+ }
645
+ } catch (e) {
646
+ this.data = {};
647
+ }
648
+ }
649
+ clear() {
650
+ this.data = {};
651
+ this.save();
652
+ }
653
+ };
654
+ var expiredShapesCache = new ExpiredShapesCache();
655
+
656
+ // src/snapshot-tracker.ts
657
+ var SnapshotTracker = class {
658
+ constructor() {
659
+ this.activeSnapshots = /* @__PURE__ */ new Map();
660
+ this.xmaxSnapshots = /* @__PURE__ */ new Map();
661
+ this.snapshotsByDatabaseLsn = /* @__PURE__ */ new Map();
662
+ }
663
+ /**
664
+ * Add a new snapshot for tracking
665
+ */
666
+ addSnapshot(metadata, keys) {
667
+ var _a, _b, _c, _d;
668
+ this.activeSnapshots.set(metadata.snapshot_mark, {
669
+ xmin: BigInt(metadata.xmin),
670
+ xmax: BigInt(metadata.xmax),
671
+ xip_list: metadata.xip_list.map(BigInt),
672
+ keys
673
+ });
674
+ 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]);
675
+ this.xmaxSnapshots.set(BigInt(metadata.xmax), xmaxSet);
676
+ 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]);
677
+ this.snapshotsByDatabaseLsn.set(
678
+ BigInt(metadata.database_lsn),
679
+ databaseLsnSet
680
+ );
681
+ }
682
+ /**
683
+ * Remove a snapshot from tracking
684
+ */
685
+ removeSnapshot(snapshotMark) {
686
+ this.activeSnapshots.delete(snapshotMark);
687
+ }
688
+ /**
689
+ * Check if a change message should be filtered because its already in an active snapshot
690
+ * Returns true if the message should be filtered out (not processed)
691
+ */
692
+ shouldRejectMessage(message) {
693
+ const txids = message.headers.txids || [];
694
+ if (txids.length === 0) return false;
695
+ const xid = Math.max(...txids);
696
+ for (const [xmax, snapshots] of this.xmaxSnapshots.entries()) {
697
+ if (xid >= xmax) {
698
+ for (const snapshot of snapshots) {
699
+ this.removeSnapshot(snapshot);
700
+ }
701
+ }
702
+ }
703
+ return [...this.activeSnapshots.values()].some(
704
+ (x) => x.keys.has(message.key) && isVisibleInSnapshot(xid, x)
705
+ );
706
+ }
707
+ lastSeenUpdate(newDatabaseLsn) {
708
+ for (const [dbLsn, snapshots] of this.snapshotsByDatabaseLsn.entries()) {
709
+ if (dbLsn <= newDatabaseLsn) {
710
+ for (const snapshot of snapshots) {
711
+ this.removeSnapshot(snapshot);
712
+ }
713
+ }
714
+ }
715
+ }
716
+ };
717
+
718
+ // src/client.ts
560
719
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
561
720
  LIVE_CACHE_BUSTER_QUERY_PARAM,
562
721
  SHAPE_HANDLE_QUERY_PARAM,
@@ -601,9 +760,18 @@ function resolveHeaders(headers) {
601
760
  return Object.fromEntries(resolvedEntries);
602
761
  });
603
762
  }
604
- 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;
763
+ function canonicalShapeKey(url) {
764
+ const cleanUrl = new URL(url.origin + url.pathname);
765
+ for (const [key, value] of url.searchParams) {
766
+ if (!ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {
767
+ cleanUrl.searchParams.set(key, value);
768
+ }
769
+ }
770
+ cleanUrl.searchParams.sort();
771
+ return cleanUrl.toString();
772
+ }
773
+ 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;
605
774
  var ShapeStream = class {
606
- // promise chain for incoming messages
607
775
  constructor(options) {
608
776
  __privateAdd(this, _ShapeStream_instances);
609
777
  __privateAdd(this, _error, null);
@@ -619,8 +787,10 @@ var ShapeStream = class {
619
787
  __privateAdd(this, _lastSyncedAt);
620
788
  // unix time
621
789
  __privateAdd(this, _isUpToDate, false);
790
+ __privateAdd(this, _isMidStream, true);
622
791
  __privateAdd(this, _connected, false);
623
792
  __privateAdd(this, _shapeHandle);
793
+ __privateAdd(this, _mode);
624
794
  __privateAdd(this, _schema);
625
795
  __privateAdd(this, _onError);
626
796
  __privateAdd(this, _requestAbortController);
@@ -629,16 +799,26 @@ var ShapeStream = class {
629
799
  __privateAdd(this, _tickPromiseResolver);
630
800
  __privateAdd(this, _tickPromiseRejecter);
631
801
  __privateAdd(this, _messageChain, Promise.resolve([]));
632
- var _a, _b, _c;
802
+ // promise chain for incoming messages
803
+ __privateAdd(this, _snapshotTracker, new SnapshotTracker());
804
+ __privateAdd(this, _activeSnapshotRequests, 0);
805
+ // counter for concurrent snapshot requests
806
+ __privateAdd(this, _midStreamPromise);
807
+ __privateAdd(this, _midStreamPromiseResolver);
808
+ var _a, _b, _c, _d;
633
809
  this.options = __spreadValues({ subscribe: true }, options);
634
810
  validateOptions(this.options);
635
811
  __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
636
812
  __privateSet(this, _liveCacheBuster, ``);
637
813
  __privateSet(this, _shapeHandle, this.options.handle);
638
- __privateSet(this, _messageParser, new MessageParser(options.parser));
814
+ __privateSet(this, _messageParser, new MessageParser(
815
+ options.parser,
816
+ options.transformer
817
+ ));
639
818
  __privateSet(this, _onError, this.options.onError);
640
- const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
641
- const backOffOpts = __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
819
+ __privateSet(this, _mode, (_b = this.options.mode) != null ? _b : `full`);
820
+ const baseFetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
821
+ const backOffOpts = __spreadProps(__spreadValues({}, (_d = options.backoffOptions) != null ? _d : BackoffDefaults), {
642
822
  onFailedAttempt: () => {
643
823
  var _a2, _b2;
644
824
  __privateSet(this, _connected, false);
@@ -667,6 +847,9 @@ var ShapeStream = class {
667
847
  get lastOffset() {
668
848
  return __privateGet(this, _lastOffset);
669
849
  }
850
+ get mode() {
851
+ return __privateGet(this, _mode);
852
+ }
670
853
  subscribe(callback, onError = () => {
671
854
  }) {
672
855
  const subscriptionId = Math.random();
@@ -719,6 +902,56 @@ var ShapeStream = class {
719
902
  __privateSet(this, _isRefreshing, false);
720
903
  });
721
904
  }
905
+ /**
906
+ * Request a snapshot for subset of data.
907
+ *
908
+ * Only available when mode is `changes_only`.
909
+ * Returns the insertion point & the data, but more importantly injects the data
910
+ * into the subscribed data stream. Returned value is unlikely to be useful for the caller,
911
+ * unless the caller has complicated additional logic.
912
+ *
913
+ * Data will be injected in a way that's also tracking further incoming changes, and it'll
914
+ * skip the ones that are already in the snapshot.
915
+ *
916
+ * @param opts - The options for the snapshot request.
917
+ * @returns The metadata and the data for the snapshot.
918
+ */
919
+ requestSnapshot(opts) {
920
+ return __async(this, null, function* () {
921
+ if (__privateGet(this, _mode) === `full`) {
922
+ throw new Error(
923
+ `Snapshot requests are not supported in ${__privateGet(this, _mode)} mode, as the consumer is guaranteed to observe all data`
924
+ );
925
+ }
926
+ if (!__privateGet(this, _started)) yield __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
927
+ yield __privateMethod(this, _ShapeStream_instances, waitForStreamEnd_fn).call(this);
928
+ __privateWrapper(this, _activeSnapshotRequests)._++;
929
+ try {
930
+ if (__privateGet(this, _activeSnapshotRequests) === 1) {
931
+ __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
932
+ }
933
+ const { fetchUrl, requestHeaders } = yield __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
934
+ const { metadata, data } = yield __privateMethod(this, _ShapeStream_instances, fetchSnapshot_fn).call(this, fetchUrl, requestHeaders);
935
+ const dataWithEndBoundary = data.concat([
936
+ { headers: __spreadValues({ control: `snapshot-end` }, metadata) }
937
+ ]);
938
+ __privateGet(this, _snapshotTracker).addSnapshot(
939
+ metadata,
940
+ new Set(data.map((message) => message.key))
941
+ );
942
+ __privateMethod(this, _ShapeStream_instances, onMessages_fn).call(this, dataWithEndBoundary, false);
943
+ return {
944
+ metadata,
945
+ data
946
+ };
947
+ } finally {
948
+ __privateWrapper(this, _activeSnapshotRequests)._--;
949
+ if (__privateGet(this, _activeSnapshotRequests) === 0) {
950
+ __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
951
+ }
952
+ }
953
+ });
954
+ }
722
955
  };
723
956
  _error = new WeakMap();
724
957
  _fetchClient2 = new WeakMap();
@@ -731,8 +964,10 @@ _lastOffset = new WeakMap();
731
964
  _liveCacheBuster = new WeakMap();
732
965
  _lastSyncedAt = new WeakMap();
733
966
  _isUpToDate = new WeakMap();
967
+ _isMidStream = new WeakMap();
734
968
  _connected = new WeakMap();
735
969
  _shapeHandle = new WeakMap();
970
+ _mode = new WeakMap();
736
971
  _schema = new WeakMap();
737
972
  _onError = new WeakMap();
738
973
  _requestAbortController = new WeakMap();
@@ -741,6 +976,10 @@ _tickPromise = new WeakMap();
741
976
  _tickPromiseResolver = new WeakMap();
742
977
  _tickPromiseRejecter = new WeakMap();
743
978
  _messageChain = new WeakMap();
979
+ _snapshotTracker = new WeakMap();
980
+ _activeSnapshotRequests = new WeakMap();
981
+ _midStreamPromise = new WeakMap();
982
+ _midStreamPromiseResolver = new WeakMap();
744
983
  _ShapeStream_instances = new WeakSet();
745
984
  start_fn = function() {
746
985
  return __async(this, null, function* () {
@@ -807,6 +1046,10 @@ requestShape_fn = function() {
807
1046
  }
808
1047
  if (!(e instanceof FetchError)) throw e;
809
1048
  if (e.status == 409) {
1049
+ if (__privateGet(this, _shapeHandle)) {
1050
+ const shapeKey = canonicalShapeKey(fetchUrl);
1051
+ expiredShapesCache.markExpired(shapeKey, __privateGet(this, _shapeHandle));
1052
+ }
810
1053
  const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _shapeHandle)}-next`;
811
1054
  __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
812
1055
  yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
@@ -825,15 +1068,13 @@ requestShape_fn = function() {
825
1068
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
826
1069
  });
827
1070
  };
828
- constructUrl_fn = function(url, resumingFromPause) {
1071
+ constructUrl_fn = function(url, resumingFromPause, subsetParams) {
829
1072
  return __async(this, null, function* () {
830
1073
  const [requestHeaders, params] = yield Promise.all([
831
1074
  resolveHeaders(this.options.headers),
832
1075
  this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
833
1076
  ]);
834
- if (params) {
835
- validateParams(params);
836
- }
1077
+ if (params) validateParams(params);
837
1078
  const fetchUrl = new URL(url);
838
1079
  if (params) {
839
1080
  if (params.table) setQueryParam(fetchUrl, TABLE_QUERY_PARAM, params.table);
@@ -853,7 +1094,20 @@ constructUrl_fn = function(url, resumingFromPause) {
853
1094
  setQueryParam(fetchUrl, key, value);
854
1095
  }
855
1096
  }
1097
+ if (subsetParams) {
1098
+ if (subsetParams.where)
1099
+ setQueryParam(fetchUrl, SUBSET_PARAM_WHERE, subsetParams.where);
1100
+ if (subsetParams.params)
1101
+ setQueryParam(fetchUrl, SUBSET_PARAM_WHERE_PARAMS, subsetParams.params);
1102
+ if (subsetParams.limit)
1103
+ setQueryParam(fetchUrl, SUBSET_PARAM_LIMIT, subsetParams.limit);
1104
+ if (subsetParams.offset)
1105
+ setQueryParam(fetchUrl, SUBSET_PARAM_OFFSET, subsetParams.offset);
1106
+ if (subsetParams.orderBy)
1107
+ setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, subsetParams.orderBy);
1108
+ }
856
1109
  fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
1110
+ fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
857
1111
  if (__privateGet(this, _isUpToDate)) {
858
1112
  if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
859
1113
  fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
@@ -866,6 +1120,11 @@ constructUrl_fn = function(url, resumingFromPause) {
866
1120
  if (__privateGet(this, _shapeHandle)) {
867
1121
  fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shapeHandle));
868
1122
  }
1123
+ const shapeKey = canonicalShapeKey(fetchUrl);
1124
+ const expiredHandle = expiredShapesCache.getExpiredHandle(shapeKey);
1125
+ if (expiredHandle) {
1126
+ fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
1127
+ }
869
1128
  fetchUrl.searchParams.sort();
870
1129
  return {
871
1130
  fetchUrl,
@@ -918,7 +1177,9 @@ onInitialResponse_fn = function(response) {
918
1177
  };
919
1178
  onMessages_fn = function(batch, isSseMessage = false) {
920
1179
  return __async(this, null, function* () {
1180
+ var _a;
921
1181
  if (batch.length > 0) {
1182
+ __privateSet(this, _isMidStream, true);
922
1183
  const lastMessage = batch[batch.length - 1];
923
1184
  if (isUpToDateMessage(lastMessage)) {
924
1185
  if (isSseMessage) {
@@ -929,8 +1190,16 @@ onMessages_fn = function(batch, isSseMessage = false) {
929
1190
  }
930
1191
  __privateSet(this, _lastSyncedAt, Date.now());
931
1192
  __privateSet(this, _isUpToDate, true);
1193
+ __privateSet(this, _isMidStream, false);
1194
+ (_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
932
1195
  }
933
- yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, batch);
1196
+ const messagesToProcess = batch.filter((message) => {
1197
+ if (isChangeMessage(message)) {
1198
+ return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
1199
+ }
1200
+ return true;
1201
+ });
1202
+ yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
934
1203
  }
935
1204
  });
936
1205
  };
@@ -1028,6 +1297,24 @@ nextTick_fn = function() {
1028
1297
  return __privateGet(this, _tickPromise);
1029
1298
  });
1030
1299
  };
1300
+ waitForStreamEnd_fn = function() {
1301
+ return __async(this, null, function* () {
1302
+ if (!__privateGet(this, _isMidStream)) {
1303
+ return;
1304
+ }
1305
+ if (__privateGet(this, _midStreamPromise)) {
1306
+ return __privateGet(this, _midStreamPromise);
1307
+ }
1308
+ __privateSet(this, _midStreamPromise, new Promise((resolve) => {
1309
+ __privateSet(this, _midStreamPromiseResolver, resolve);
1310
+ }));
1311
+ __privateGet(this, _midStreamPromise).finally(() => {
1312
+ __privateSet(this, _midStreamPromise, void 0);
1313
+ __privateSet(this, _midStreamPromiseResolver, void 0);
1314
+ });
1315
+ return __privateGet(this, _midStreamPromise);
1316
+ });
1317
+ };
1031
1318
  publish_fn = function(messages) {
1032
1319
  return __async(this, null, function* () {
1033
1320
  __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
@@ -1072,8 +1359,33 @@ reset_fn = function(handle) {
1072
1359
  __privateSet(this, _liveCacheBuster, ``);
1073
1360
  __privateSet(this, _shapeHandle, handle);
1074
1361
  __privateSet(this, _isUpToDate, false);
1362
+ __privateSet(this, _isMidStream, true);
1075
1363
  __privateSet(this, _connected, false);
1076
1364
  __privateSet(this, _schema, void 0);
1365
+ __privateSet(this, _activeSnapshotRequests, 0);
1366
+ };
1367
+ fetchSnapshot_fn = function(url, headers) {
1368
+ return __async(this, null, function* () {
1369
+ const response = yield __privateGet(this, _fetchClient2).call(this, url.toString(), { headers });
1370
+ if (!response.ok) {
1371
+ throw new FetchError(
1372
+ response.status,
1373
+ void 0,
1374
+ void 0,
1375
+ Object.fromEntries([...response.headers.entries()]),
1376
+ url.toString()
1377
+ );
1378
+ }
1379
+ const { metadata, data } = yield response.json();
1380
+ const batch = __privateGet(this, _messageParser).parse(
1381
+ JSON.stringify(data),
1382
+ __privateGet(this, _schema)
1383
+ );
1384
+ return {
1385
+ metadata,
1386
+ data: batch
1387
+ };
1388
+ });
1077
1389
  };
1078
1390
  ShapeStream.Replica = {
1079
1391
  FULL: `full`,
@@ -1095,7 +1407,7 @@ function validateOptions(options) {
1095
1407
  if (options.signal && !(options.signal instanceof AbortSignal)) {
1096
1408
  throw new InvalidSignalError();
1097
1409
  }
1098
- if (options.offset !== void 0 && options.offset !== `-1` && !options.handle) {
1410
+ if (options.offset !== void 0 && options.offset !== `-1` && options.offset !== `now` && !options.handle) {
1099
1411
  throw new MissingShapeHandleError();
1100
1412
  }
1101
1413
  validateParams(options.params);
@@ -1124,12 +1436,15 @@ function convertWhereParamsToObj(allPgParams) {
1124
1436
  }
1125
1437
 
1126
1438
  // src/shape.ts
1127
- var _data, _subscribers2, _status, _error2, _Shape_instances, process_fn, updateShapeStatus_fn, handleError_fn, notify_fn;
1439
+ var _data, _subscribers2, _insertedKeys, _requestedSubSnapshots, _reexecuteSnapshotsPending, _status, _error2, _Shape_instances, process_fn, reexecuteSnapshots_fn, awaitUpToDate_fn, updateShapeStatus_fn, handleError_fn, notify_fn;
1128
1440
  var Shape = class {
1129
1441
  constructor(stream) {
1130
1442
  __privateAdd(this, _Shape_instances);
1131
1443
  __privateAdd(this, _data, /* @__PURE__ */ new Map());
1132
1444
  __privateAdd(this, _subscribers2, /* @__PURE__ */ new Map());
1445
+ __privateAdd(this, _insertedKeys, /* @__PURE__ */ new Set());
1446
+ __privateAdd(this, _requestedSubSnapshots, /* @__PURE__ */ new Set());
1447
+ __privateAdd(this, _reexecuteSnapshotsPending, false);
1133
1448
  __privateAdd(this, _status, `syncing`);
1134
1449
  __privateAdd(this, _error2, false);
1135
1450
  this.stream = stream;
@@ -1188,6 +1503,22 @@ var Shape = class {
1188
1503
  isConnected() {
1189
1504
  return this.stream.isConnected();
1190
1505
  }
1506
+ /** Current log mode of the underlying stream */
1507
+ get mode() {
1508
+ return this.stream.mode;
1509
+ }
1510
+ /**
1511
+ * Request a snapshot for subset of data. Only available when mode is changes_only.
1512
+ * Returns void; data will be emitted via the stream and processed by this Shape.
1513
+ */
1514
+ requestSnapshot(params) {
1515
+ return __async(this, null, function* () {
1516
+ const key = JSON.stringify(params);
1517
+ __privateGet(this, _requestedSubSnapshots).add(key);
1518
+ yield __privateMethod(this, _Shape_instances, awaitUpToDate_fn).call(this);
1519
+ yield this.stream.requestSnapshot(params);
1520
+ });
1521
+ }
1191
1522
  subscribe(callback) {
1192
1523
  const subscriptionId = Math.random();
1193
1524
  __privateGet(this, _subscribers2).set(subscriptionId, callback);
@@ -1204,6 +1535,9 @@ var Shape = class {
1204
1535
  };
1205
1536
  _data = new WeakMap();
1206
1537
  _subscribers2 = new WeakMap();
1538
+ _insertedKeys = new WeakMap();
1539
+ _requestedSubSnapshots = new WeakMap();
1540
+ _reexecuteSnapshotsPending = new WeakMap();
1207
1541
  _status = new WeakMap();
1208
1542
  _error2 = new WeakMap();
1209
1543
  _Shape_instances = new WeakSet();
@@ -1212,33 +1546,93 @@ process_fn = function(messages) {
1212
1546
  messages.forEach((message) => {
1213
1547
  if (isChangeMessage(message)) {
1214
1548
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1215
- switch (message.headers.operation) {
1216
- case `insert`:
1217
- __privateGet(this, _data).set(message.key, message.value);
1218
- break;
1219
- case `update`:
1220
- __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1221
- break;
1222
- case `delete`:
1223
- __privateGet(this, _data).delete(message.key);
1224
- break;
1549
+ if (this.mode === `full`) {
1550
+ switch (message.headers.operation) {
1551
+ case `insert`:
1552
+ __privateGet(this, _data).set(message.key, message.value);
1553
+ break;
1554
+ case `update`:
1555
+ __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1556
+ break;
1557
+ case `delete`:
1558
+ __privateGet(this, _data).delete(message.key);
1559
+ break;
1560
+ }
1561
+ } else {
1562
+ switch (message.headers.operation) {
1563
+ case `insert`:
1564
+ __privateGet(this, _insertedKeys).add(message.key);
1565
+ __privateGet(this, _data).set(message.key, message.value);
1566
+ break;
1567
+ case `update`:
1568
+ if (__privateGet(this, _insertedKeys).has(message.key)) {
1569
+ __privateGet(this, _data).set(message.key, __spreadValues(__spreadValues({}, __privateGet(this, _data).get(message.key)), message.value));
1570
+ }
1571
+ break;
1572
+ case `delete`:
1573
+ if (__privateGet(this, _insertedKeys).has(message.key)) {
1574
+ __privateGet(this, _data).delete(message.key);
1575
+ __privateGet(this, _insertedKeys).delete(message.key);
1576
+ }
1577
+ break;
1578
+ }
1225
1579
  }
1226
1580
  }
1227
1581
  if (isControlMessage(message)) {
1228
1582
  switch (message.headers.control) {
1229
1583
  case `up-to-date`:
1230
1584
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `up-to-date`);
1585
+ if (__privateGet(this, _reexecuteSnapshotsPending)) {
1586
+ __privateSet(this, _reexecuteSnapshotsPending, false);
1587
+ void __privateMethod(this, _Shape_instances, reexecuteSnapshots_fn).call(this);
1588
+ }
1231
1589
  break;
1232
1590
  case `must-refetch`:
1233
1591
  __privateGet(this, _data).clear();
1592
+ __privateGet(this, _insertedKeys).clear();
1234
1593
  __privateSet(this, _error2, false);
1235
1594
  shouldNotify = __privateMethod(this, _Shape_instances, updateShapeStatus_fn).call(this, `syncing`);
1595
+ __privateSet(this, _reexecuteSnapshotsPending, true);
1236
1596
  break;
1237
1597
  }
1238
1598
  }
1239
1599
  });
1240
1600
  if (shouldNotify) __privateMethod(this, _Shape_instances, notify_fn).call(this);
1241
1601
  };
1602
+ reexecuteSnapshots_fn = function() {
1603
+ return __async(this, null, function* () {
1604
+ yield __privateMethod(this, _Shape_instances, awaitUpToDate_fn).call(this);
1605
+ yield Promise.all(
1606
+ Array.from(__privateGet(this, _requestedSubSnapshots)).map((jsonParams) => __async(this, null, function* () {
1607
+ try {
1608
+ const snapshot = JSON.parse(jsonParams);
1609
+ yield this.stream.requestSnapshot(snapshot);
1610
+ } catch (_) {
1611
+ }
1612
+ }))
1613
+ );
1614
+ });
1615
+ };
1616
+ awaitUpToDate_fn = function() {
1617
+ return __async(this, null, function* () {
1618
+ if (this.stream.isUpToDate) return;
1619
+ yield new Promise((resolve) => {
1620
+ const check = () => {
1621
+ if (this.stream.isUpToDate) {
1622
+ clearInterval(interval);
1623
+ unsub();
1624
+ resolve();
1625
+ }
1626
+ };
1627
+ const interval = setInterval(check, 10);
1628
+ const unsub = this.stream.subscribe(
1629
+ () => check(),
1630
+ () => check()
1631
+ );
1632
+ check();
1633
+ });
1634
+ });
1635
+ };
1242
1636
  updateShapeStatus_fn = function(status) {
1243
1637
  const stateChanged = __privateGet(this, _status) !== status;
1244
1638
  __privateSet(this, _status, status);
@@ -1264,6 +1658,7 @@ notify_fn = function() {
1264
1658
  ShapeStream,
1265
1659
  isChangeMessage,
1266
1660
  isControlMessage,
1661
+ isVisibleInSnapshot,
1267
1662
  resolveValue
1268
1663
  });
1269
1664
  //# sourceMappingURL=index.cjs.map