@electric-sql/client 1.5.0 → 1.5.2

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
@@ -467,6 +467,7 @@ var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
467
467
  var LIVE_SSE_QUERY_PARAM = `live_sse`;
468
468
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
469
469
  var PAUSE_STREAM = `pause-stream`;
470
+ var SYSTEM_WAKE = `system-wake`;
470
471
  var LOG_MODE_QUERY_PARAM = `log`;
471
472
  var SUBSET_PARAM_WHERE = `subset__where`;
472
473
  var SUBSET_PARAM_LIMIT = `subset__limit`;
@@ -1142,7 +1143,7 @@ function canonicalShapeKey(url) {
1142
1143
  cleanUrl.searchParams.sort();
1143
1144
  return cleanUrl.toString();
1144
1145
  }
1145
- 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, _lastSeenCursor, _currentFetchUrl, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _staleCacheBuster, _staleCacheRetryCount, _maxStaleCacheRetries, _ShapeStream_instances, replayMode_get, 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, buildSubsetBody_fn;
1146
+ 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, _lastSeenCursor, _currentFetchUrl, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _unsubscribeFromWakeDetection, _staleCacheBuster, _staleCacheRetryCount, _maxStaleCacheRetries, _ShapeStream_instances, replayMode_get, 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, hasBrowserVisibilityAPI_fn, subscribeToVisibilityChanges_fn, subscribeToWakeDetection_fn, reset_fn, buildSubsetBody_fn;
1146
1147
  var ShapeStream = class {
1147
1148
  constructor(options) {
1148
1149
  __privateAdd(this, _ShapeStream_instances);
@@ -1193,6 +1194,7 @@ var ShapeStream = class {
1193
1194
  __privateAdd(this, _sseBackoffMaxDelay, 5e3);
1194
1195
  // Maximum delay cap (ms)
1195
1196
  __privateAdd(this, _unsubscribeFromVisibilityChanges);
1197
+ __privateAdd(this, _unsubscribeFromWakeDetection);
1196
1198
  __privateAdd(this, _staleCacheBuster);
1197
1199
  // Cache buster set when stale CDN response detected, used on retry requests to bypass cache
1198
1200
  __privateAdd(this, _staleCacheRetryCount, 0);
@@ -1237,6 +1239,7 @@ var ShapeStream = class {
1237
1239
  ));
1238
1240
  __privateSet(this, _fetchClient2, createFetchWithConsumedMessages(__privateGet(this, _sseFetchClient)));
1239
1241
  __privateMethod(this, _ShapeStream_instances, subscribeToVisibilityChanges_fn).call(this);
1242
+ __privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
1240
1243
  }
1241
1244
  get shapeHandle() {
1242
1245
  return __privateGet(this, _shapeHandle);
@@ -1263,9 +1266,10 @@ var ShapeStream = class {
1263
1266
  };
1264
1267
  }
1265
1268
  unsubscribeAll() {
1266
- var _a;
1269
+ var _a, _b;
1267
1270
  __privateGet(this, _subscribers).clear();
1268
1271
  (_a = __privateGet(this, _unsubscribeFromVisibilityChanges)) == null ? void 0 : _a.call(this);
1272
+ (_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
1269
1273
  }
1270
1274
  /** Unix time at which we last synced. Undefined when `isLoading` is true. */
1271
1275
  lastSyncedAt() {
@@ -1385,7 +1389,21 @@ var ShapeStream = class {
1385
1389
  fetchUrl = result.fetchUrl;
1386
1390
  fetchOptions = { headers: result.requestHeaders };
1387
1391
  }
1388
- const response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
1392
+ const usedHandle = __privateGet(this, _shapeHandle);
1393
+ let response;
1394
+ try {
1395
+ response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
1396
+ } catch (e) {
1397
+ if (e instanceof FetchError && e.status === 409) {
1398
+ if (usedHandle) {
1399
+ const shapeKey = canonicalShapeKey(fetchUrl);
1400
+ expiredShapesCache.markExpired(shapeKey, usedHandle);
1401
+ }
1402
+ __privateSet(this, _shapeHandle, e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`);
1403
+ return this.fetchSnapshot(opts);
1404
+ }
1405
+ throw e;
1406
+ }
1389
1407
  if (!response.ok) {
1390
1408
  throw await FetchError.fromResponse(response, fetchUrl.toString());
1391
1409
  }
@@ -1438,6 +1456,7 @@ _sseFallbackToLongPolling = new WeakMap();
1438
1456
  _sseBackoffBaseDelay = new WeakMap();
1439
1457
  _sseBackoffMaxDelay = new WeakMap();
1440
1458
  _unsubscribeFromVisibilityChanges = new WeakMap();
1459
+ _unsubscribeFromWakeDetection = new WeakMap();
1441
1460
  _staleCacheBuster = new WeakMap();
1442
1461
  _staleCacheRetryCount = new WeakMap();
1443
1462
  _maxStaleCacheRetries = new WeakMap();
@@ -1446,7 +1465,7 @@ replayMode_get = function() {
1446
1465
  return __privateGet(this, _lastSeenCursor) !== void 0;
1447
1466
  };
1448
1467
  start_fn = async function() {
1449
- var _a, _b, _c, _d, _e;
1468
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1450
1469
  __privateSet(this, _started, true);
1451
1470
  try {
1452
1471
  await __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
@@ -1472,17 +1491,20 @@ start_fn = async function() {
1472
1491
  }
1473
1492
  __privateSet(this, _connected, false);
1474
1493
  (_c = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _c.call(this);
1494
+ (_d = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _d.call(this);
1475
1495
  return;
1476
1496
  }
1477
1497
  if (err instanceof Error) {
1478
1498
  __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
1479
1499
  }
1480
1500
  __privateSet(this, _connected, false);
1481
- (_d = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _d.call(this);
1501
+ (_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
1502
+ (_f = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _f.call(this);
1482
1503
  throw err;
1483
1504
  }
1484
1505
  __privateSet(this, _connected, false);
1485
- (_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
1506
+ (_g = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _g.call(this);
1507
+ (_h = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _h.call(this);
1486
1508
  };
1487
1509
  requestShape_fn = async function() {
1488
1510
  var _a, _b;
@@ -1507,7 +1529,9 @@ requestShape_fn = async function() {
1507
1529
  resumingFromPause
1508
1530
  });
1509
1531
  } catch (e) {
1510
- if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && requestAbortController.signal.aborted && requestAbortController.signal.reason === FORCE_DISCONNECT_AND_REFRESH) {
1532
+ const abortReason = requestAbortController.signal.reason;
1533
+ const isRestartAbort = requestAbortController.signal.aborted && (abortReason === FORCE_DISCONNECT_AND_REFRESH || abortReason === SYSTEM_WAKE);
1534
+ if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && isRestartAbort) {
1511
1535
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1512
1536
  }
1513
1537
  if (e instanceof FetchBackoffAbortError) {
@@ -1714,8 +1738,9 @@ onInitialResponse_fn = async function(response) {
1714
1738
  );
1715
1739
  } else {
1716
1740
  console.warn(
1717
- `[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. Ignoring the stale handle and continuing with handle "${__privateGet(this, _shapeHandle)}".`
1741
+ `[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. Ignoring the stale response and continuing with handle "${__privateGet(this, _shapeHandle)}".`
1718
1742
  );
1743
+ return;
1719
1744
  }
1720
1745
  }
1721
1746
  const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
@@ -1935,8 +1960,11 @@ sendErrorToSubscribers_fn = function(error) {
1935
1960
  errorFn == null ? void 0 : errorFn(error);
1936
1961
  });
1937
1962
  };
1963
+ hasBrowserVisibilityAPI_fn = function() {
1964
+ return typeof document === `object` && typeof document.hidden === `boolean` && typeof document.addEventListener === `function`;
1965
+ };
1938
1966
  subscribeToVisibilityChanges_fn = function() {
1939
- if (typeof document === `object` && typeof document.hidden === `boolean` && typeof document.addEventListener === `function`) {
1967
+ if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) {
1940
1968
  const visibilityHandler = () => {
1941
1969
  if (document.hidden) {
1942
1970
  __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
@@ -1950,6 +1978,44 @@ subscribeToVisibilityChanges_fn = function() {
1950
1978
  });
1951
1979
  }
1952
1980
  };
1981
+ /**
1982
+ * Detects system wake from sleep using timer gap detection.
1983
+ * When the system sleeps, setInterval timers are paused. On wake,
1984
+ * the elapsed wall-clock time since the last tick will be much larger
1985
+ * than the interval period, indicating the system was asleep.
1986
+ *
1987
+ * Only active in non-browser environments (Bun, Node.js) where
1988
+ * `document.visibilitychange` is not available. In browsers,
1989
+ * `#subscribeToVisibilityChanges` handles this instead. Without wake
1990
+ * detection, in-flight HTTP requests (long-poll or SSE) may hang until
1991
+ * the OS TCP timeout.
1992
+ */
1993
+ subscribeToWakeDetection_fn = function() {
1994
+ if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) return;
1995
+ const INTERVAL_MS = 2e3;
1996
+ const WAKE_THRESHOLD_MS = 4e3;
1997
+ let lastTickTime = Date.now();
1998
+ const timer = setInterval(() => {
1999
+ const now = Date.now();
2000
+ const elapsed = now - lastTickTime;
2001
+ lastTickTime = now;
2002
+ if (elapsed > INTERVAL_MS + WAKE_THRESHOLD_MS) {
2003
+ if (__privateGet(this, _state) === `active` && __privateGet(this, _requestAbortController)) {
2004
+ __privateSet(this, _isRefreshing, true);
2005
+ __privateGet(this, _requestAbortController).abort(SYSTEM_WAKE);
2006
+ queueMicrotask(() => {
2007
+ __privateSet(this, _isRefreshing, false);
2008
+ });
2009
+ }
2010
+ }
2011
+ }, INTERVAL_MS);
2012
+ if (typeof timer === `object` && `unref` in timer) {
2013
+ timer.unref();
2014
+ }
2015
+ __privateSet(this, _unsubscribeFromWakeDetection, () => {
2016
+ clearInterval(timer);
2017
+ });
2018
+ };
1953
2019
  /**
1954
2020
  * Resets the state of the stream, optionally with a provided
1955
2021
  * shape handle