@electric-sql/client 1.5.7 → 1.5.9
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/cjs/index.cjs +94 -9
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.browser.mjs +7 -3
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.legacy-esm.js +94 -9
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +94 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +109 -3
- package/src/expired-shapes-cache.ts +5 -0
- package/src/fetch.ts +3 -3
- package/src/helpers.ts +1 -1
- package/src/up-to-date-tracker.ts +5 -0
package/dist/cjs/index.cjs
CHANGED
|
@@ -466,7 +466,7 @@ function isChangeMessage(message) {
|
|
|
466
466
|
return message != null && `key` in message;
|
|
467
467
|
}
|
|
468
468
|
function isControlMessage(message) {
|
|
469
|
-
return message != null &&
|
|
469
|
+
return message != null && `headers` in message && `control` in message.headers;
|
|
470
470
|
}
|
|
471
471
|
function isUpToDateMessage(message) {
|
|
472
472
|
return isControlMessage(message) && message.headers.control === `up-to-date`;
|
|
@@ -541,10 +541,9 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
541
541
|
// src/fetch.ts
|
|
542
542
|
var HTTP_RETRY_STATUS_CODES = [429];
|
|
543
543
|
var BackoffDefaults = {
|
|
544
|
-
initialDelay:
|
|
545
|
-
maxDelay:
|
|
546
|
-
|
|
547
|
-
multiplier: 1.3,
|
|
544
|
+
initialDelay: 1e3,
|
|
545
|
+
maxDelay: 32e3,
|
|
546
|
+
multiplier: 2,
|
|
548
547
|
maxRetries: Infinity
|
|
549
548
|
// Retry forever - clients may go offline and come back
|
|
550
549
|
};
|
|
@@ -1159,6 +1158,10 @@ var ExpiredShapesCache = class {
|
|
|
1159
1158
|
this.data = {};
|
|
1160
1159
|
this.save();
|
|
1161
1160
|
}
|
|
1161
|
+
delete(shapeUrl) {
|
|
1162
|
+
delete this.data[shapeUrl];
|
|
1163
|
+
this.save();
|
|
1164
|
+
}
|
|
1162
1165
|
};
|
|
1163
1166
|
var expiredShapesCache = new ExpiredShapesCache();
|
|
1164
1167
|
|
|
@@ -1280,6 +1283,10 @@ var UpToDateTracker = class {
|
|
|
1280
1283
|
}
|
|
1281
1284
|
this.save();
|
|
1282
1285
|
}
|
|
1286
|
+
delete(shapeKey) {
|
|
1287
|
+
delete this.data[shapeKey];
|
|
1288
|
+
this.save();
|
|
1289
|
+
}
|
|
1283
1290
|
};
|
|
1284
1291
|
var upToDateTracker = new UpToDateTracker();
|
|
1285
1292
|
|
|
@@ -1904,6 +1911,7 @@ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
1904
1911
|
OFFSET_QUERY_PARAM,
|
|
1905
1912
|
CACHE_BUSTER_QUERY_PARAM
|
|
1906
1913
|
]);
|
|
1914
|
+
var TROUBLESHOOTING_URL = `https://electric-sql.com/docs/guides/troubleshooting`;
|
|
1907
1915
|
async function resolveValue(value) {
|
|
1908
1916
|
if (typeof value === `function`) {
|
|
1909
1917
|
return value();
|
|
@@ -1944,7 +1952,7 @@ function canonicalShapeKey(url) {
|
|
|
1944
1952
|
cleanUrl.searchParams.sort();
|
|
1945
1953
|
return cleanUrl.toString();
|
|
1946
1954
|
}
|
|
1947
|
-
var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _syncState, _connected, _mode, _onError, _requestAbortController, _refreshCount, _snapshotCounter, _ShapeStream_instances, isRefreshing_get, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _snapshotTracker, _pauseLock, _currentFetchUrl, _lastSseConnectionStartTime, _minSseConnectionDuration, _maxShortSseConnections, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _unsubscribeFromWakeDetection, _maxStaleCacheRetries, start_fn, teardown_fn, requestShape_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, nextTick_fn, publish_fn, sendErrorToSubscribers_fn, hasBrowserVisibilityAPI_fn, subscribeToVisibilityChanges_fn, subscribeToWakeDetection_fn, reset_fn, buildSubsetBody_fn;
|
|
1955
|
+
var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _syncState, _connected, _mode, _onError, _requestAbortController, _refreshCount, _snapshotCounter, _ShapeStream_instances, isRefreshing_get, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _snapshotTracker, _pauseLock, _currentFetchUrl, _lastSseConnectionStartTime, _minSseConnectionDuration, _maxShortSseConnections, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _unsubscribeFromWakeDetection, _maxStaleCacheRetries, _recentRequestEntries, _fastLoopWindowMs, _fastLoopThreshold, _fastLoopBackoffBaseMs, _fastLoopBackoffMaxMs, _fastLoopConsecutiveCount, _fastLoopMaxCount, start_fn, teardown_fn, requestShape_fn, checkFastLoop_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, nextTick_fn, publish_fn, sendErrorToSubscribers_fn, hasBrowserVisibilityAPI_fn, subscribeToVisibilityChanges_fn, subscribeToWakeDetection_fn, reset_fn, buildSubsetBody_fn;
|
|
1948
1956
|
var ShapeStream = class {
|
|
1949
1957
|
constructor(options) {
|
|
1950
1958
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -1982,6 +1990,15 @@ var ShapeStream = class {
|
|
|
1982
1990
|
__privateAdd(this, _unsubscribeFromVisibilityChanges);
|
|
1983
1991
|
__privateAdd(this, _unsubscribeFromWakeDetection);
|
|
1984
1992
|
__privateAdd(this, _maxStaleCacheRetries, 3);
|
|
1993
|
+
// Fast-loop detection: track recent non-live requests to detect tight retry
|
|
1994
|
+
// loops caused by proxy/CDN misconfiguration or stale client-side caches
|
|
1995
|
+
__privateAdd(this, _recentRequestEntries, []);
|
|
1996
|
+
__privateAdd(this, _fastLoopWindowMs, 500);
|
|
1997
|
+
__privateAdd(this, _fastLoopThreshold, 5);
|
|
1998
|
+
__privateAdd(this, _fastLoopBackoffBaseMs, 100);
|
|
1999
|
+
__privateAdd(this, _fastLoopBackoffMaxMs, 5e3);
|
|
2000
|
+
__privateAdd(this, _fastLoopConsecutiveCount, 0);
|
|
2001
|
+
__privateAdd(this, _fastLoopMaxCount, 5);
|
|
1985
2002
|
var _a, _b, _c, _d;
|
|
1986
2003
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
1987
2004
|
validateOptions(this.options);
|
|
@@ -2039,7 +2056,6 @@ var ShapeStream = class {
|
|
|
2039
2056
|
));
|
|
2040
2057
|
__privateSet(this, _fetchClient2, createFetchWithConsumedMessages(__privateGet(this, _sseFetchClient)));
|
|
2041
2058
|
__privateMethod(this, _ShapeStream_instances, subscribeToVisibilityChanges_fn).call(this);
|
|
2042
|
-
__privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
|
|
2043
2059
|
}
|
|
2044
2060
|
get shapeHandle() {
|
|
2045
2061
|
return __privateGet(this, _syncState).handle;
|
|
@@ -2279,9 +2295,17 @@ _sseBackoffMaxDelay = new WeakMap();
|
|
|
2279
2295
|
_unsubscribeFromVisibilityChanges = new WeakMap();
|
|
2280
2296
|
_unsubscribeFromWakeDetection = new WeakMap();
|
|
2281
2297
|
_maxStaleCacheRetries = new WeakMap();
|
|
2298
|
+
_recentRequestEntries = new WeakMap();
|
|
2299
|
+
_fastLoopWindowMs = new WeakMap();
|
|
2300
|
+
_fastLoopThreshold = new WeakMap();
|
|
2301
|
+
_fastLoopBackoffBaseMs = new WeakMap();
|
|
2302
|
+
_fastLoopBackoffMaxMs = new WeakMap();
|
|
2303
|
+
_fastLoopConsecutiveCount = new WeakMap();
|
|
2304
|
+
_fastLoopMaxCount = new WeakMap();
|
|
2282
2305
|
start_fn = async function() {
|
|
2283
2306
|
var _a, _b;
|
|
2284
2307
|
__privateSet(this, _started, true);
|
|
2308
|
+
__privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
|
|
2285
2309
|
try {
|
|
2286
2310
|
await __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
2287
2311
|
} catch (err) {
|
|
@@ -2303,6 +2327,8 @@ start_fn = async function() {
|
|
|
2303
2327
|
if (__privateGet(this, _syncState) instanceof ErrorState) {
|
|
2304
2328
|
__privateSet(this, _syncState, __privateGet(this, _syncState).retry());
|
|
2305
2329
|
}
|
|
2330
|
+
__privateSet(this, _fastLoopConsecutiveCount, 0);
|
|
2331
|
+
__privateSet(this, _recentRequestEntries, []);
|
|
2306
2332
|
__privateSet(this, _started, false);
|
|
2307
2333
|
await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
2308
2334
|
return;
|
|
@@ -2333,6 +2359,12 @@ requestShape_fn = async function() {
|
|
|
2333
2359
|
if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _syncState).isUpToDate)) {
|
|
2334
2360
|
return;
|
|
2335
2361
|
}
|
|
2362
|
+
if (!__privateGet(this, _syncState).isUpToDate) {
|
|
2363
|
+
await __privateMethod(this, _ShapeStream_instances, checkFastLoop_fn).call(this);
|
|
2364
|
+
} else {
|
|
2365
|
+
__privateSet(this, _fastLoopConsecutiveCount, 0);
|
|
2366
|
+
__privateSet(this, _recentRequestEntries, []);
|
|
2367
|
+
}
|
|
2336
2368
|
let resumingFromPause = false;
|
|
2337
2369
|
if (__privateGet(this, _syncState) instanceof PausedState) {
|
|
2338
2370
|
resumingFromPause = true;
|
|
@@ -2391,6 +2423,56 @@ requestShape_fn = async function() {
|
|
|
2391
2423
|
(_b = __privateGet(this, _tickPromiseResolver)) == null ? void 0 : _b.call(this);
|
|
2392
2424
|
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
2393
2425
|
};
|
|
2426
|
+
checkFastLoop_fn = async function() {
|
|
2427
|
+
const now = Date.now();
|
|
2428
|
+
const currentOffset = __privateGet(this, _syncState).offset;
|
|
2429
|
+
__privateSet(this, _recentRequestEntries, __privateGet(this, _recentRequestEntries).filter(
|
|
2430
|
+
(e) => now - e.timestamp < __privateGet(this, _fastLoopWindowMs)
|
|
2431
|
+
));
|
|
2432
|
+
__privateGet(this, _recentRequestEntries).push({ timestamp: now, offset: currentOffset });
|
|
2433
|
+
const sameOffsetCount = __privateGet(this, _recentRequestEntries).filter(
|
|
2434
|
+
(e) => e.offset === currentOffset
|
|
2435
|
+
).length;
|
|
2436
|
+
if (sameOffsetCount < __privateGet(this, _fastLoopThreshold)) return;
|
|
2437
|
+
__privateWrapper(this, _fastLoopConsecutiveCount)._++;
|
|
2438
|
+
if (__privateGet(this, _fastLoopConsecutiveCount) >= __privateGet(this, _fastLoopMaxCount)) {
|
|
2439
|
+
throw new FetchError(
|
|
2440
|
+
502,
|
|
2441
|
+
void 0,
|
|
2442
|
+
void 0,
|
|
2443
|
+
{},
|
|
2444
|
+
this.options.url,
|
|
2445
|
+
`Client is stuck in a fast retry loop (${__privateGet(this, _fastLoopThreshold)} requests in ${__privateGet(this, _fastLoopWindowMs)}ms at the same offset, repeated ${__privateGet(this, _fastLoopMaxCount)} times). Client-side caches were cleared automatically on first detection, but the loop persists. This usually indicates a proxy or CDN misconfiguration. Common causes:
|
|
2446
|
+
- Proxy is not including query parameters (handle, offset) in its cache key
|
|
2447
|
+
- CDN is serving stale 409 responses
|
|
2448
|
+
- Proxy is stripping required Electric headers from responses
|
|
2449
|
+
For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
if (__privateGet(this, _fastLoopConsecutiveCount) === 1) {
|
|
2453
|
+
console.warn(
|
|
2454
|
+
`[Electric] Detected fast retry loop (${__privateGet(this, _fastLoopThreshold)} requests in ${__privateGet(this, _fastLoopWindowMs)}ms at the same offset). Clearing client-side caches and resetting stream to recover. If this persists, check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key, and that required Electric headers are forwarded to the client. For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`
|
|
2455
|
+
);
|
|
2456
|
+
if (__privateGet(this, _currentFetchUrl)) {
|
|
2457
|
+
const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
|
|
2458
|
+
expiredShapesCache.delete(shapeKey);
|
|
2459
|
+
upToDateTracker.delete(shapeKey);
|
|
2460
|
+
} else {
|
|
2461
|
+
expiredShapesCache.clear();
|
|
2462
|
+
upToDateTracker.clear();
|
|
2463
|
+
}
|
|
2464
|
+
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this);
|
|
2465
|
+
__privateSet(this, _recentRequestEntries, []);
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const maxDelay = Math.min(
|
|
2469
|
+
__privateGet(this, _fastLoopBackoffMaxMs),
|
|
2470
|
+
__privateGet(this, _fastLoopBackoffBaseMs) * Math.pow(2, __privateGet(this, _fastLoopConsecutiveCount))
|
|
2471
|
+
);
|
|
2472
|
+
const delayMs = Math.floor(Math.random() * maxDelay);
|
|
2473
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2474
|
+
__privateSet(this, _recentRequestEntries, []);
|
|
2475
|
+
};
|
|
2394
2476
|
constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
2395
2477
|
var _a, _b, _c, _d, _e, _f;
|
|
2396
2478
|
const [requestHeaders, params] = await Promise.all([
|
|
@@ -2541,11 +2623,11 @@ onInitialResponse_fn = async function(response) {
|
|
|
2541
2623
|
void 0,
|
|
2542
2624
|
{},
|
|
2543
2625
|
(_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
|
|
2544
|
-
`CDN continues serving stale cached responses after ${__privateGet(this, _maxStaleCacheRetries)} retry attempts. This indicates a severe proxy/CDN misconfiguration. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. For more information visit the troubleshooting guide:
|
|
2626
|
+
`CDN continues serving stale cached responses after ${__privateGet(this, _maxStaleCacheRetries)} retry attempts. This indicates a severe proxy/CDN misconfiguration. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`
|
|
2545
2627
|
);
|
|
2546
2628
|
}
|
|
2547
2629
|
console.warn(
|
|
2548
|
-
`[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. For more information visit the troubleshooting guide:
|
|
2630
|
+
`[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. For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL} Retrying with a random cache buster to bypass the stale cache (attempt ${__privateGet(this, _syncState).staleCacheRetryCount}/${__privateGet(this, _maxStaleCacheRetries)}).`
|
|
2549
2631
|
);
|
|
2550
2632
|
throw new StaleCacheError(
|
|
2551
2633
|
`Received stale cached response with expired handle "${shapeHandle}". This indicates a proxy/CDN caching misconfiguration. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key.`
|
|
@@ -2777,6 +2859,7 @@ subscribeToVisibilityChanges_fn = function() {
|
|
|
2777
2859
|
document.addEventListener(`visibilitychange`, visibilityHandler);
|
|
2778
2860
|
__privateSet(this, _unsubscribeFromVisibilityChanges, () => {
|
|
2779
2861
|
document.removeEventListener(`visibilitychange`, visibilityHandler);
|
|
2862
|
+
__privateSet(this, _unsubscribeFromVisibilityChanges, void 0);
|
|
2780
2863
|
});
|
|
2781
2864
|
}
|
|
2782
2865
|
};
|
|
@@ -2794,6 +2877,7 @@ subscribeToVisibilityChanges_fn = function() {
|
|
|
2794
2877
|
*/
|
|
2795
2878
|
subscribeToWakeDetection_fn = function() {
|
|
2796
2879
|
if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) return;
|
|
2880
|
+
if (__privateGet(this, _unsubscribeFromWakeDetection)) return;
|
|
2797
2881
|
const INTERVAL_MS = 2e3;
|
|
2798
2882
|
const WAKE_THRESHOLD_MS = 4e3;
|
|
2799
2883
|
let lastTickTime = Date.now();
|
|
@@ -2816,6 +2900,7 @@ subscribeToWakeDetection_fn = function() {
|
|
|
2816
2900
|
}
|
|
2817
2901
|
__privateSet(this, _unsubscribeFromWakeDetection, () => {
|
|
2818
2902
|
clearInterval(timer);
|
|
2903
|
+
__privateSet(this, _unsubscribeFromWakeDetection, void 0);
|
|
2819
2904
|
});
|
|
2820
2905
|
};
|
|
2821
2906
|
/**
|