@electric-sql/client 1.0.4 → 1.0.5

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.
@@ -245,6 +245,12 @@ function isControlMessage(message) {
245
245
  function isUpToDateMessage(message) {
246
246
  return isControlMessage(message) && message.headers.control === `up-to-date`;
247
247
  }
248
+ function getOffset(message) {
249
+ const lsn = Number(message.headers.global_last_seen_lsn);
250
+ if (lsn && !isNaN(lsn)) {
251
+ return `${lsn}_0`;
252
+ }
253
+ }
248
254
 
249
255
  // src/constants.ts
250
256
  var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`;
@@ -261,6 +267,7 @@ var TABLE_QUERY_PARAM = `table`;
261
267
  var WHERE_QUERY_PARAM = `where`;
262
268
  var REPLICA_PARAM = `replica`;
263
269
  var WHERE_PARAMS_PARAM = `params`;
270
+ var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
264
271
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
265
272
  var PAUSE_STREAM = `pause-stream`;
266
273
 
@@ -271,7 +278,7 @@ var BackoffDefaults = {
271
278
  maxDelay: 1e4,
272
279
  multiplier: 1.3
273
280
  };
274
- function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
281
+ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults, sseMode = false) {
275
282
  const {
276
283
  initialDelay,
277
284
  maxDelay,
@@ -289,7 +296,11 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
289
296
  try {
290
297
  const result = await fetchClient(...args);
291
298
  if (result.ok) return result;
292
- else throw await FetchError.fromResponse(result, url.toString());
299
+ const err = await FetchError.fromResponse(result, url.toString());
300
+ if (err.status === 409 && sseMode) {
301
+ err.json = [err.json];
302
+ }
303
+ throw err;
293
304
  } catch (e) {
294
305
  onFailedAttempt == null ? void 0 : onFailedAttempt();
295
306
  if ((_a = options == null ? void 0 : options.signal) == null ? void 0 : _a.aborted) {
@@ -487,6 +498,9 @@ function noop() {
487
498
  }
488
499
 
489
500
  // src/client.ts
501
+ import {
502
+ fetchEventSource
503
+ } from "@microsoft/fetch-event-source";
490
504
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
491
505
  LIVE_CACHE_BUSTER_QUERY_PARAM,
492
506
  SHAPE_HANDLE_QUERY_PARAM,
@@ -523,12 +537,14 @@ async function resolveHeaders(headers) {
523
537
  );
524
538
  return Object.fromEntries(resolvedEntries);
525
539
  }
526
- var _error, _fetchClient2, _messageParser, _subscribers, _started, _state, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _connected, _shapeHandle, _schema, _onError, _requestAbortController, _isRefreshing, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _ShapeStream_instances, start_fn, requestShape_fn, pause_fn, resume_fn, nextTick_fn, publish_fn, sendErrorToSubscribers_fn, subscribeToVisibilityChanges_fn, reset_fn;
540
+ 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;
527
541
  var ShapeStream = class {
542
+ // promise chain for incoming messages
528
543
  constructor(options) {
529
544
  __privateAdd(this, _ShapeStream_instances);
530
545
  __privateAdd(this, _error, null);
531
546
  __privateAdd(this, _fetchClient2);
547
+ __privateAdd(this, _sseFetchClient);
532
548
  __privateAdd(this, _messageParser);
533
549
  __privateAdd(this, _subscribers, /* @__PURE__ */ new Map());
534
550
  __privateAdd(this, _started, false);
@@ -548,6 +564,7 @@ var ShapeStream = class {
548
564
  __privateAdd(this, _tickPromise);
549
565
  __privateAdd(this, _tickPromiseResolver);
550
566
  __privateAdd(this, _tickPromiseRejecter);
567
+ __privateAdd(this, _messageChain, Promise.resolve([]));
551
568
  var _a, _b, _c;
552
569
  this.options = __spreadValues({ subscribe: true }, options);
553
570
  validateOptions(this.options);
@@ -557,18 +574,30 @@ var ShapeStream = class {
557
574
  __privateSet(this, _messageParser, new MessageParser(options.parser));
558
575
  __privateSet(this, _onError, this.options.onError);
559
576
  const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args);
560
- const fetchWithBackoffClient = createFetchWithBackoff(baseFetchClient, __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
577
+ const backOffOpts = __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), {
561
578
  onFailedAttempt: () => {
562
579
  var _a2, _b2;
563
580
  __privateSet(this, _connected, false);
564
581
  (_b2 = (_a2 = options.backoffOptions) == null ? void 0 : _a2.onFailedAttempt) == null ? void 0 : _b2.call(_a2);
565
582
  }
566
- }));
583
+ });
584
+ const fetchWithBackoffClient = createFetchWithBackoff(
585
+ baseFetchClient,
586
+ backOffOpts
587
+ );
567
588
  __privateSet(this, _fetchClient2, createFetchWithConsumedMessages(
568
589
  createFetchWithResponseHeadersCheck(
569
590
  createFetchWithChunkBuffer(fetchWithBackoffClient)
570
591
  )
571
592
  ));
593
+ const sseFetchWithBackoffClient = createFetchWithBackoff(
594
+ baseFetchClient,
595
+ backOffOpts,
596
+ true
597
+ );
598
+ __privateSet(this, _sseFetchClient, createFetchWithResponseHeadersCheck(
599
+ createFetchWithChunkBuffer(sseFetchWithBackoffClient)
600
+ ));
572
601
  __privateMethod(this, _ShapeStream_instances, subscribeToVisibilityChanges_fn).call(this);
573
602
  }
574
603
  get shapeHandle() {
@@ -636,6 +665,7 @@ var ShapeStream = class {
636
665
  };
637
666
  _error = new WeakMap();
638
667
  _fetchClient2 = new WeakMap();
668
+ _sseFetchClient = new WeakMap();
639
669
  _messageParser = new WeakMap();
640
670
  _subscribers = new WeakMap();
641
671
  _started = new WeakMap();
@@ -653,6 +683,7 @@ _isRefreshing = new WeakMap();
653
683
  _tickPromise = new WeakMap();
654
684
  _tickPromiseResolver = new WeakMap();
655
685
  _tickPromiseRejecter = new WeakMap();
686
+ _messageChain = new WeakMap();
656
687
  _ShapeStream_instances = new WeakSet();
657
688
  start_fn = async function() {
658
689
  var _a;
@@ -683,7 +714,7 @@ start_fn = async function() {
683
714
  }
684
715
  };
685
716
  requestShape_fn = async function() {
686
- var _a, _b, _c, _d;
717
+ var _a, _b;
687
718
  if (__privateGet(this, _state) === `pause-requested`) {
688
719
  __privateSet(this, _state, `paused`);
689
720
  return;
@@ -694,6 +725,46 @@ requestShape_fn = async function() {
694
725
  const resumingFromPause = __privateGet(this, _state) === `paused`;
695
726
  __privateSet(this, _state, `active`);
696
727
  const { url, signal } = this.options;
728
+ const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, url, resumingFromPause);
729
+ const abortListener = await __privateMethod(this, _ShapeStream_instances, createAbortListener_fn).call(this, signal);
730
+ const requestAbortController = __privateGet(this, _requestAbortController);
731
+ try {
732
+ await __privateMethod(this, _ShapeStream_instances, fetchShape_fn).call(this, {
733
+ fetchUrl,
734
+ requestAbortController,
735
+ headers: requestHeaders,
736
+ resumingFromPause: true
737
+ });
738
+ } catch (e) {
739
+ if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && requestAbortController.signal.aborted && requestAbortController.signal.reason === FORCE_DISCONNECT_AND_REFRESH) {
740
+ return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
741
+ }
742
+ if (e instanceof FetchBackoffAbortError) {
743
+ if (requestAbortController.signal.aborted && requestAbortController.signal.reason === PAUSE_STREAM) {
744
+ __privateSet(this, _state, `paused`);
745
+ }
746
+ return;
747
+ }
748
+ if (!(e instanceof FetchError)) throw e;
749
+ if (e.status == 409) {
750
+ const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER];
751
+ __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
752
+ await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
753
+ return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
754
+ } else {
755
+ __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, e);
756
+ throw e;
757
+ }
758
+ } finally {
759
+ if (abortListener && signal) {
760
+ signal.removeEventListener(`abort`, abortListener);
761
+ }
762
+ __privateSet(this, _requestAbortController, void 0);
763
+ }
764
+ (_b = __privateGet(this, _tickPromiseResolver)) == null ? void 0 : _b.call(this);
765
+ return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
766
+ };
767
+ constructUrl_fn = async function(url, resumingFromPause) {
697
768
  const [requestHeaders, params] = await Promise.all([
698
769
  resolveHeaders(this.options.headers),
699
770
  this.options.params ? toInternalParams(convertWhereParamsToObj(this.options.params)) : void 0
@@ -734,51 +805,28 @@ requestShape_fn = async function() {
734
805
  fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shapeHandle));
735
806
  }
736
807
  fetchUrl.searchParams.sort();
808
+ return {
809
+ fetchUrl,
810
+ requestHeaders
811
+ };
812
+ };
813
+ createAbortListener_fn = async function(signal) {
814
+ var _a;
737
815
  __privateSet(this, _requestAbortController, new AbortController());
738
- let abortListener;
739
816
  if (signal) {
740
- abortListener = () => {
817
+ const abortListener = () => {
741
818
  var _a2;
742
819
  (_a2 = __privateGet(this, _requestAbortController)) == null ? void 0 : _a2.abort(signal.reason);
743
820
  };
744
821
  signal.addEventListener(`abort`, abortListener, { once: true });
745
822
  if (signal.aborted) {
746
- (_b = __privateGet(this, _requestAbortController)) == null ? void 0 : _b.abort(signal.reason);
747
- }
748
- }
749
- let response;
750
- try {
751
- response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
752
- signal: __privateGet(this, _requestAbortController).signal,
753
- headers: requestHeaders
754
- });
755
- __privateSet(this, _connected, true);
756
- } catch (e) {
757
- if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && __privateGet(this, _requestAbortController).signal.aborted && __privateGet(this, _requestAbortController).signal.reason === FORCE_DISCONNECT_AND_REFRESH) {
758
- return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
759
- }
760
- if (e instanceof FetchBackoffAbortError) {
761
- if (__privateGet(this, _requestAbortController).signal.aborted && __privateGet(this, _requestAbortController).signal.reason === PAUSE_STREAM) {
762
- __privateSet(this, _state, `paused`);
763
- }
764
- return;
765
- }
766
- if (!(e instanceof FetchError)) throw e;
767
- if (e.status == 409) {
768
- const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER];
769
- __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
770
- await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json);
771
- return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
772
- } else {
773
- __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, e);
774
- throw e;
823
+ (_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.abort(signal.reason);
775
824
  }
776
- } finally {
777
- if (abortListener && signal) {
778
- signal.removeEventListener(`abort`, abortListener);
779
- }
780
- __privateSet(this, _requestAbortController, void 0);
825
+ return abortListener;
781
826
  }
827
+ };
828
+ onInitialResponse_fn = async function(response) {
829
+ var _a;
782
830
  const { headers, status } = response;
783
831
  const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
784
832
  if (shapeHandle) {
@@ -796,22 +844,77 @@ requestShape_fn = async function() {
796
844
  const schemaHeader = headers.get(SHAPE_SCHEMA_HEADER);
797
845
  return schemaHeader ? JSON.parse(schemaHeader) : {};
798
846
  };
799
- __privateSet(this, _schema, (_c = __privateGet(this, _schema)) != null ? _c : getSchema());
847
+ __privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchema());
800
848
  if (status === 204) {
801
849
  __privateSet(this, _lastSyncedAt, Date.now());
802
850
  }
803
- const messages = await response.text() || `[]`;
804
- const batch = __privateGet(this, _messageParser).parse(messages, __privateGet(this, _schema));
851
+ };
852
+ onMessages_fn = async function(messages, schema, isSseMessage = false) {
853
+ const batch = __privateGet(this, _messageParser).parse(messages, schema);
805
854
  if (batch.length > 0) {
806
855
  const lastMessage = batch[batch.length - 1];
807
856
  if (isUpToDateMessage(lastMessage)) {
857
+ if (isSseMessage) {
858
+ const offset = getOffset(lastMessage);
859
+ if (offset) {
860
+ __privateSet(this, _lastOffset, offset);
861
+ }
862
+ }
808
863
  __privateSet(this, _lastSyncedAt, Date.now());
809
864
  __privateSet(this, _isUpToDate, true);
810
865
  }
811
866
  await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, batch);
812
867
  }
813
- (_d = __privateGet(this, _tickPromiseResolver)) == null ? void 0 : _d.call(this);
814
- return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
868
+ };
869
+ fetchShape_fn = async function(opts) {
870
+ if (__privateGet(this, _isUpToDate) && this.options.experimentalLiveSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause) {
871
+ opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
872
+ return __privateMethod(this, _ShapeStream_instances, requestShapeSSE_fn).call(this, opts);
873
+ }
874
+ return __privateMethod(this, _ShapeStream_instances, requestShapeLongPoll_fn).call(this, opts);
875
+ };
876
+ requestShapeLongPoll_fn = async function(opts) {
877
+ const { fetchUrl, requestAbortController, headers } = opts;
878
+ const response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), {
879
+ signal: requestAbortController.signal,
880
+ headers
881
+ });
882
+ __privateSet(this, _connected, true);
883
+ await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
884
+ const schema = __privateGet(this, _schema);
885
+ const res = await response.text();
886
+ const messages = res || `[]`;
887
+ await __privateMethod(this, _ShapeStream_instances, onMessages_fn).call(this, messages, schema);
888
+ };
889
+ requestShapeSSE_fn = async function(opts) {
890
+ const { fetchUrl, requestAbortController, headers } = opts;
891
+ const fetch2 = __privateGet(this, _sseFetchClient);
892
+ try {
893
+ await fetchEventSource(fetchUrl.toString(), {
894
+ headers,
895
+ fetch: fetch2,
896
+ onopen: async (response) => {
897
+ __privateSet(this, _connected, true);
898
+ await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
899
+ },
900
+ onmessage: (event) => {
901
+ if (event.data) {
902
+ const messages = `[${event.data}]`;
903
+ const schema = __privateGet(this, _schema);
904
+ __privateMethod(this, _ShapeStream_instances, onMessages_fn).call(this, messages, schema, true);
905
+ }
906
+ },
907
+ onerror: (error) => {
908
+ throw error;
909
+ },
910
+ signal: requestAbortController.signal
911
+ });
912
+ } catch (error) {
913
+ if (requestAbortController.signal.aborted) {
914
+ throw new FetchBackoffAbortError();
915
+ }
916
+ throw error;
917
+ }
815
918
  };
816
919
  pause_fn = function() {
817
920
  var _a;
@@ -841,17 +944,20 @@ nextTick_fn = async function() {
841
944
  return __privateGet(this, _tickPromise);
842
945
  };
843
946
  publish_fn = async function(messages) {
844
- await Promise.all(
845
- Array.from(__privateGet(this, _subscribers).values()).map(async ([callback, __]) => {
846
- try {
847
- await callback(messages);
848
- } catch (err) {
849
- queueMicrotask(() => {
850
- throw err;
851
- });
852
- }
853
- })
854
- );
947
+ __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
948
+ () => Promise.all(
949
+ Array.from(__privateGet(this, _subscribers).values()).map(async ([callback, __]) => {
950
+ try {
951
+ await callback(messages);
952
+ } catch (err) {
953
+ queueMicrotask(() => {
954
+ throw err;
955
+ });
956
+ }
957
+ })
958
+ )
959
+ ));
960
+ return __privateGet(this, _messageChain);
855
961
  };
856
962
  sendErrorToSubscribers_fn = function(error) {
857
963
  __privateGet(this, _subscribers).forEach(([_, errorFn]) => {