@electric-sql/client 1.1.2 → 1.1.4

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
@@ -322,6 +322,7 @@ var SUBSET_PARAM_ORDER_BY = `subset__order_by`;
322
322
  var SUBSET_PARAM_WHERE_PARAMS = `subset__params`;
323
323
  var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
324
324
  LIVE_QUERY_PARAM,
325
+ LIVE_SSE_QUERY_PARAM,
325
326
  SHAPE_HANDLE_QUERY_PARAM,
326
327
  OFFSET_QUERY_PARAM,
327
328
  LIVE_CACHE_BUSTER_QUERY_PARAM,
@@ -338,16 +339,33 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
338
339
  var HTTP_RETRY_STATUS_CODES = [429];
339
340
  var BackoffDefaults = {
340
341
  initialDelay: 100,
341
- maxDelay: 1e4,
342
- multiplier: 1.3
342
+ maxDelay: 6e4,
343
+ // Cap at 60s - reasonable for long-lived connections
344
+ multiplier: 1.3,
345
+ maxRetries: Infinity
346
+ // Retry forever - clients may go offline and come back
343
347
  };
348
+ function parseRetryAfterHeader(retryAfter) {
349
+ if (!retryAfter) return 0;
350
+ const retryAfterSec = Number(retryAfter);
351
+ if (Number.isFinite(retryAfterSec) && retryAfterSec > 0) {
352
+ return retryAfterSec * 1e3;
353
+ }
354
+ const retryDate = Date.parse(retryAfter);
355
+ if (!isNaN(retryDate)) {
356
+ const deltaMs = retryDate - Date.now();
357
+ return Math.max(0, Math.min(deltaMs, 36e5));
358
+ }
359
+ return 0;
360
+ }
344
361
  function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
345
362
  const {
346
363
  initialDelay,
347
364
  maxDelay,
348
365
  multiplier,
349
366
  debug = false,
350
- onFailedAttempt
367
+ onFailedAttempt,
368
+ maxRetries = Infinity
351
369
  } = backoffOptions;
352
370
  return (...args) => __async(this, null, function* () {
353
371
  var _a;
@@ -358,7 +376,9 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
358
376
  while (true) {
359
377
  try {
360
378
  const result = yield fetchClient(...args);
361
- if (result.ok) return result;
379
+ if (result.ok) {
380
+ return result;
381
+ }
362
382
  const err = yield FetchError.fromResponse(result, url.toString());
363
383
  throw err;
364
384
  } catch (e) {
@@ -368,12 +388,27 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
368
388
  } else if (e instanceof FetchError && !HTTP_RETRY_STATUS_CODES.includes(e.status) && e.status >= 400 && e.status < 500) {
369
389
  throw e;
370
390
  } else {
371
- yield new Promise((resolve) => setTimeout(resolve, delay));
372
- delay = Math.min(delay * multiplier, maxDelay);
391
+ attempt++;
392
+ if (attempt > maxRetries) {
393
+ if (debug) {
394
+ console.log(
395
+ `Max retries reached (${attempt}/${maxRetries}), giving up`
396
+ );
397
+ }
398
+ throw e;
399
+ }
400
+ const serverMinimumMs = e instanceof FetchError && e.headers ? parseRetryAfterHeader(e.headers[`retry-after`]) : 0;
401
+ const jitter = Math.random() * delay;
402
+ const clientBackoffMs = Math.min(jitter, maxDelay);
403
+ const waitMs = Math.max(serverMinimumMs, clientBackoffMs);
373
404
  if (debug) {
374
- attempt++;
375
- console.log(`Retry attempt #${attempt} after ${delay}ms`);
405
+ const source = serverMinimumMs > 0 ? `server+client` : `client`;
406
+ console.log(
407
+ `Retry attempt #${attempt} after ${waitMs}ms (${source}, serverMin=${serverMinimumMs}ms, clientBackoff=${clientBackoffMs}ms)`
408
+ );
376
409
  }
410
+ yield new Promise((resolve) => setTimeout(resolve, waitMs));
411
+ delay = Math.min(delay * multiplier, maxDelay);
377
412
  }
378
413
  }
379
414
  }
@@ -746,9 +781,8 @@ function canonicalShapeKey(url) {
746
781
  cleanUrl.searchParams.sort();
747
782
  return cleanUrl.toString();
748
783
  }
749
- 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, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _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;
784
+ 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, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _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;
750
785
  var ShapeStream = class {
751
- // Maximum delay cap (ms)
752
786
  constructor(options) {
753
787
  __privateAdd(this, _ShapeStream_instances);
754
788
  __privateAdd(this, _error, null);
@@ -792,6 +826,8 @@ var ShapeStream = class {
792
826
  __privateAdd(this, _sseBackoffBaseDelay, 100);
793
827
  // Base delay for exponential backoff (ms)
794
828
  __privateAdd(this, _sseBackoffMaxDelay, 5e3);
829
+ // Maximum delay cap (ms)
830
+ __privateAdd(this, _unsubscribeFromVisibilityChanges);
795
831
  var _a, _b, _c, _d;
796
832
  this.options = __spreadValues({ subscribe: true }, options);
797
833
  validateOptions(this.options);
@@ -847,7 +883,9 @@ var ShapeStream = class {
847
883
  };
848
884
  }
849
885
  unsubscribeAll() {
886
+ var _a;
850
887
  __privateGet(this, _subscribers).clear();
888
+ (_a = __privateGet(this, _unsubscribeFromVisibilityChanges)) == null ? void 0 : _a.call(this);
851
889
  }
852
890
  /** Unix time at which we last synced. Undefined when `isLoading` is true. */
853
891
  lastSyncedAt() {
@@ -974,6 +1012,7 @@ _maxShortSseConnections = new WeakMap();
974
1012
  _sseFallbackToLongPolling = new WeakMap();
975
1013
  _sseBackoffBaseDelay = new WeakMap();
976
1014
  _sseBackoffMaxDelay = new WeakMap();
1015
+ _unsubscribeFromVisibilityChanges = new WeakMap();
977
1016
  _ShapeStream_instances = new WeakSet();
978
1017
  start_fn = function() {
979
1018
  return __async(this, null, function* () {
@@ -1043,7 +1082,8 @@ requestShape_fn = function() {
1043
1082
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1044
1083
  }
1045
1084
  if (e instanceof FetchBackoffAbortError) {
1046
- if (requestAbortController.signal.aborted && requestAbortController.signal.reason === PAUSE_STREAM) {
1085
+ const currentState = __privateGet(this, _state);
1086
+ if (requestAbortController.signal.aborted && requestAbortController.signal.reason === PAUSE_STREAM && currentState === `pause-requested`) {
1047
1087
  __privateSet(this, _state, `paused`);
1048
1088
  }
1049
1089
  return;
@@ -1304,7 +1344,10 @@ pause_fn = function() {
1304
1344
  }
1305
1345
  };
1306
1346
  resume_fn = function() {
1307
- if (__privateGet(this, _started) && __privateGet(this, _state) === `paused`) {
1347
+ if (__privateGet(this, _started) && (__privateGet(this, _state) === `paused` || __privateGet(this, _state) === `pause-requested`)) {
1348
+ if (__privateGet(this, _state) === `pause-requested`) {
1349
+ __privateSet(this, _state, `active`);
1350
+ }
1308
1351
  __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1309
1352
  }
1310
1353
  };
@@ -1376,6 +1419,9 @@ subscribeToVisibilityChanges_fn = function() {
1376
1419
  }
1377
1420
  };
1378
1421
  document.addEventListener(`visibilitychange`, visibilityHandler);
1422
+ __privateSet(this, _unsubscribeFromVisibilityChanges, () => {
1423
+ document.removeEventListener(`visibilitychange`, visibilityHandler);
1424
+ });
1379
1425
  }
1380
1426
  };
1381
1427
  /**