@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.
@@ -504,6 +504,7 @@ var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
504
504
  var LIVE_SSE_QUERY_PARAM = `live_sse`;
505
505
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
506
506
  var PAUSE_STREAM = `pause-stream`;
507
+ var SYSTEM_WAKE = `system-wake`;
507
508
  var LOG_MODE_QUERY_PARAM = `log`;
508
509
  var SUBSET_PARAM_WHERE = `subset__where`;
509
510
  var SUBSET_PARAM_LIMIT = `subset__limit`;
@@ -1177,7 +1178,7 @@ function canonicalShapeKey(url) {
1177
1178
  cleanUrl.searchParams.sort();
1178
1179
  return cleanUrl.toString();
1179
1180
  }
1180
- 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;
1181
+ 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;
1181
1182
  var ShapeStream = class {
1182
1183
  constructor(options) {
1183
1184
  __privateAdd(this, _ShapeStream_instances);
@@ -1228,6 +1229,7 @@ var ShapeStream = class {
1228
1229
  __privateAdd(this, _sseBackoffMaxDelay, 5e3);
1229
1230
  // Maximum delay cap (ms)
1230
1231
  __privateAdd(this, _unsubscribeFromVisibilityChanges);
1232
+ __privateAdd(this, _unsubscribeFromWakeDetection);
1231
1233
  __privateAdd(this, _staleCacheBuster);
1232
1234
  // Cache buster set when stale CDN response detected, used on retry requests to bypass cache
1233
1235
  __privateAdd(this, _staleCacheRetryCount, 0);
@@ -1272,6 +1274,7 @@ var ShapeStream = class {
1272
1274
  ));
1273
1275
  __privateSet(this, _fetchClient2, createFetchWithConsumedMessages(__privateGet(this, _sseFetchClient)));
1274
1276
  __privateMethod(this, _ShapeStream_instances, subscribeToVisibilityChanges_fn).call(this);
1277
+ __privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
1275
1278
  }
1276
1279
  get shapeHandle() {
1277
1280
  return __privateGet(this, _shapeHandle);
@@ -1298,9 +1301,10 @@ var ShapeStream = class {
1298
1301
  };
1299
1302
  }
1300
1303
  unsubscribeAll() {
1301
- var _a;
1304
+ var _a, _b;
1302
1305
  __privateGet(this, _subscribers).clear();
1303
1306
  (_a = __privateGet(this, _unsubscribeFromVisibilityChanges)) == null ? void 0 : _a.call(this);
1307
+ (_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
1304
1308
  }
1305
1309
  /** Unix time at which we last synced. Undefined when `isLoading` is true. */
1306
1310
  lastSyncedAt() {
@@ -1420,7 +1424,21 @@ var ShapeStream = class {
1420
1424
  fetchUrl = result.fetchUrl;
1421
1425
  fetchOptions = { headers: result.requestHeaders };
1422
1426
  }
1423
- const response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
1427
+ const usedHandle = __privateGet(this, _shapeHandle);
1428
+ let response;
1429
+ try {
1430
+ response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
1431
+ } catch (e) {
1432
+ if (e instanceof FetchError && e.status === 409) {
1433
+ if (usedHandle) {
1434
+ const shapeKey = canonicalShapeKey(fetchUrl);
1435
+ expiredShapesCache.markExpired(shapeKey, usedHandle);
1436
+ }
1437
+ __privateSet(this, _shapeHandle, e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`);
1438
+ return this.fetchSnapshot(opts);
1439
+ }
1440
+ throw e;
1441
+ }
1424
1442
  if (!response.ok) {
1425
1443
  throw await FetchError.fromResponse(response, fetchUrl.toString());
1426
1444
  }
@@ -1473,6 +1491,7 @@ _sseFallbackToLongPolling = new WeakMap();
1473
1491
  _sseBackoffBaseDelay = new WeakMap();
1474
1492
  _sseBackoffMaxDelay = new WeakMap();
1475
1493
  _unsubscribeFromVisibilityChanges = new WeakMap();
1494
+ _unsubscribeFromWakeDetection = new WeakMap();
1476
1495
  _staleCacheBuster = new WeakMap();
1477
1496
  _staleCacheRetryCount = new WeakMap();
1478
1497
  _maxStaleCacheRetries = new WeakMap();
@@ -1481,7 +1500,7 @@ replayMode_get = function() {
1481
1500
  return __privateGet(this, _lastSeenCursor) !== void 0;
1482
1501
  };
1483
1502
  start_fn = async function() {
1484
- var _a, _b, _c, _d, _e;
1503
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1485
1504
  __privateSet(this, _started, true);
1486
1505
  try {
1487
1506
  await __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
@@ -1507,17 +1526,20 @@ start_fn = async function() {
1507
1526
  }
1508
1527
  __privateSet(this, _connected, false);
1509
1528
  (_c = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _c.call(this);
1529
+ (_d = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _d.call(this);
1510
1530
  return;
1511
1531
  }
1512
1532
  if (err instanceof Error) {
1513
1533
  __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
1514
1534
  }
1515
1535
  __privateSet(this, _connected, false);
1516
- (_d = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _d.call(this);
1536
+ (_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
1537
+ (_f = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _f.call(this);
1517
1538
  throw err;
1518
1539
  }
1519
1540
  __privateSet(this, _connected, false);
1520
- (_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
1541
+ (_g = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _g.call(this);
1542
+ (_h = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _h.call(this);
1521
1543
  };
1522
1544
  requestShape_fn = async function() {
1523
1545
  var _a, _b;
@@ -1542,7 +1564,9 @@ requestShape_fn = async function() {
1542
1564
  resumingFromPause
1543
1565
  });
1544
1566
  } catch (e) {
1545
- if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && requestAbortController.signal.aborted && requestAbortController.signal.reason === FORCE_DISCONNECT_AND_REFRESH) {
1567
+ const abortReason = requestAbortController.signal.reason;
1568
+ const isRestartAbort = requestAbortController.signal.aborted && (abortReason === FORCE_DISCONNECT_AND_REFRESH || abortReason === SYSTEM_WAKE);
1569
+ if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && isRestartAbort) {
1546
1570
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1547
1571
  }
1548
1572
  if (e instanceof FetchBackoffAbortError) {
@@ -1749,8 +1773,9 @@ onInitialResponse_fn = async function(response) {
1749
1773
  );
1750
1774
  } else {
1751
1775
  console.warn(
1752
- `[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)}".`
1776
+ `[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)}".`
1753
1777
  );
1778
+ return;
1754
1779
  }
1755
1780
  }
1756
1781
  const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
@@ -1970,8 +1995,11 @@ sendErrorToSubscribers_fn = function(error) {
1970
1995
  errorFn == null ? void 0 : errorFn(error);
1971
1996
  });
1972
1997
  };
1998
+ hasBrowserVisibilityAPI_fn = function() {
1999
+ return typeof document === `object` && typeof document.hidden === `boolean` && typeof document.addEventListener === `function`;
2000
+ };
1973
2001
  subscribeToVisibilityChanges_fn = function() {
1974
- if (typeof document === `object` && typeof document.hidden === `boolean` && typeof document.addEventListener === `function`) {
2002
+ if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) {
1975
2003
  const visibilityHandler = () => {
1976
2004
  if (document.hidden) {
1977
2005
  __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
@@ -1985,6 +2013,44 @@ subscribeToVisibilityChanges_fn = function() {
1985
2013
  });
1986
2014
  }
1987
2015
  };
2016
+ /**
2017
+ * Detects system wake from sleep using timer gap detection.
2018
+ * When the system sleeps, setInterval timers are paused. On wake,
2019
+ * the elapsed wall-clock time since the last tick will be much larger
2020
+ * than the interval period, indicating the system was asleep.
2021
+ *
2022
+ * Only active in non-browser environments (Bun, Node.js) where
2023
+ * `document.visibilitychange` is not available. In browsers,
2024
+ * `#subscribeToVisibilityChanges` handles this instead. Without wake
2025
+ * detection, in-flight HTTP requests (long-poll or SSE) may hang until
2026
+ * the OS TCP timeout.
2027
+ */
2028
+ subscribeToWakeDetection_fn = function() {
2029
+ if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) return;
2030
+ const INTERVAL_MS = 2e3;
2031
+ const WAKE_THRESHOLD_MS = 4e3;
2032
+ let lastTickTime = Date.now();
2033
+ const timer = setInterval(() => {
2034
+ const now = Date.now();
2035
+ const elapsed = now - lastTickTime;
2036
+ lastTickTime = now;
2037
+ if (elapsed > INTERVAL_MS + WAKE_THRESHOLD_MS) {
2038
+ if (__privateGet(this, _state) === `active` && __privateGet(this, _requestAbortController)) {
2039
+ __privateSet(this, _isRefreshing, true);
2040
+ __privateGet(this, _requestAbortController).abort(SYSTEM_WAKE);
2041
+ queueMicrotask(() => {
2042
+ __privateSet(this, _isRefreshing, false);
2043
+ });
2044
+ }
2045
+ }
2046
+ }, INTERVAL_MS);
2047
+ if (typeof timer === `object` && `unref` in timer) {
2048
+ timer.unref();
2049
+ }
2050
+ __privateSet(this, _unsubscribeFromWakeDetection, () => {
2051
+ clearInterval(timer);
2052
+ });
2053
+ };
1988
2054
  /**
1989
2055
  * Resets the state of the stream, optionally with a provided
1990
2056
  * shape handle