@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/index.legacy-esm.js
CHANGED
|
@@ -429,7 +429,7 @@ function isChangeMessage(message) {
|
|
|
429
429
|
return message != null && `key` in message;
|
|
430
430
|
}
|
|
431
431
|
function isControlMessage(message) {
|
|
432
|
-
return message != null &&
|
|
432
|
+
return message != null && `headers` in message && `control` in message.headers;
|
|
433
433
|
}
|
|
434
434
|
function isUpToDateMessage(message) {
|
|
435
435
|
return isControlMessage(message) && message.headers.control === `up-to-date`;
|
|
@@ -504,10 +504,9 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
504
504
|
// src/fetch.ts
|
|
505
505
|
var HTTP_RETRY_STATUS_CODES = [429];
|
|
506
506
|
var BackoffDefaults = {
|
|
507
|
-
initialDelay:
|
|
508
|
-
maxDelay:
|
|
509
|
-
|
|
510
|
-
multiplier: 1.3,
|
|
507
|
+
initialDelay: 1e3,
|
|
508
|
+
maxDelay: 32e3,
|
|
509
|
+
multiplier: 2,
|
|
511
510
|
maxRetries: Infinity
|
|
512
511
|
// Retry forever - clients may go offline and come back
|
|
513
512
|
};
|
|
@@ -1122,6 +1121,10 @@ var ExpiredShapesCache = class {
|
|
|
1122
1121
|
this.data = {};
|
|
1123
1122
|
this.save();
|
|
1124
1123
|
}
|
|
1124
|
+
delete(shapeUrl) {
|
|
1125
|
+
delete this.data[shapeUrl];
|
|
1126
|
+
this.save();
|
|
1127
|
+
}
|
|
1125
1128
|
};
|
|
1126
1129
|
var expiredShapesCache = new ExpiredShapesCache();
|
|
1127
1130
|
|
|
@@ -1243,6 +1246,10 @@ var UpToDateTracker = class {
|
|
|
1243
1246
|
}
|
|
1244
1247
|
this.save();
|
|
1245
1248
|
}
|
|
1249
|
+
delete(shapeKey) {
|
|
1250
|
+
delete this.data[shapeKey];
|
|
1251
|
+
this.save();
|
|
1252
|
+
}
|
|
1246
1253
|
};
|
|
1247
1254
|
var upToDateTracker = new UpToDateTracker();
|
|
1248
1255
|
|
|
@@ -1867,6 +1874,7 @@ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
1867
1874
|
OFFSET_QUERY_PARAM,
|
|
1868
1875
|
CACHE_BUSTER_QUERY_PARAM
|
|
1869
1876
|
]);
|
|
1877
|
+
var TROUBLESHOOTING_URL = `https://electric-sql.com/docs/guides/troubleshooting`;
|
|
1870
1878
|
async function resolveValue(value) {
|
|
1871
1879
|
if (typeof value === `function`) {
|
|
1872
1880
|
return value();
|
|
@@ -1907,7 +1915,7 @@ function canonicalShapeKey(url) {
|
|
|
1907
1915
|
cleanUrl.searchParams.sort();
|
|
1908
1916
|
return cleanUrl.toString();
|
|
1909
1917
|
}
|
|
1910
|
-
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;
|
|
1918
|
+
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;
|
|
1911
1919
|
var ShapeStream = class {
|
|
1912
1920
|
constructor(options) {
|
|
1913
1921
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -1945,6 +1953,15 @@ var ShapeStream = class {
|
|
|
1945
1953
|
__privateAdd(this, _unsubscribeFromVisibilityChanges);
|
|
1946
1954
|
__privateAdd(this, _unsubscribeFromWakeDetection);
|
|
1947
1955
|
__privateAdd(this, _maxStaleCacheRetries, 3);
|
|
1956
|
+
// Fast-loop detection: track recent non-live requests to detect tight retry
|
|
1957
|
+
// loops caused by proxy/CDN misconfiguration or stale client-side caches
|
|
1958
|
+
__privateAdd(this, _recentRequestEntries, []);
|
|
1959
|
+
__privateAdd(this, _fastLoopWindowMs, 500);
|
|
1960
|
+
__privateAdd(this, _fastLoopThreshold, 5);
|
|
1961
|
+
__privateAdd(this, _fastLoopBackoffBaseMs, 100);
|
|
1962
|
+
__privateAdd(this, _fastLoopBackoffMaxMs, 5e3);
|
|
1963
|
+
__privateAdd(this, _fastLoopConsecutiveCount, 0);
|
|
1964
|
+
__privateAdd(this, _fastLoopMaxCount, 5);
|
|
1948
1965
|
var _a, _b, _c, _d;
|
|
1949
1966
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
1950
1967
|
validateOptions(this.options);
|
|
@@ -2002,7 +2019,6 @@ var ShapeStream = class {
|
|
|
2002
2019
|
));
|
|
2003
2020
|
__privateSet(this, _fetchClient2, createFetchWithConsumedMessages(__privateGet(this, _sseFetchClient)));
|
|
2004
2021
|
__privateMethod(this, _ShapeStream_instances, subscribeToVisibilityChanges_fn).call(this);
|
|
2005
|
-
__privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
|
|
2006
2022
|
}
|
|
2007
2023
|
get shapeHandle() {
|
|
2008
2024
|
return __privateGet(this, _syncState).handle;
|
|
@@ -2242,9 +2258,17 @@ _sseBackoffMaxDelay = new WeakMap();
|
|
|
2242
2258
|
_unsubscribeFromVisibilityChanges = new WeakMap();
|
|
2243
2259
|
_unsubscribeFromWakeDetection = new WeakMap();
|
|
2244
2260
|
_maxStaleCacheRetries = new WeakMap();
|
|
2261
|
+
_recentRequestEntries = new WeakMap();
|
|
2262
|
+
_fastLoopWindowMs = new WeakMap();
|
|
2263
|
+
_fastLoopThreshold = new WeakMap();
|
|
2264
|
+
_fastLoopBackoffBaseMs = new WeakMap();
|
|
2265
|
+
_fastLoopBackoffMaxMs = new WeakMap();
|
|
2266
|
+
_fastLoopConsecutiveCount = new WeakMap();
|
|
2267
|
+
_fastLoopMaxCount = new WeakMap();
|
|
2245
2268
|
start_fn = async function() {
|
|
2246
2269
|
var _a, _b;
|
|
2247
2270
|
__privateSet(this, _started, true);
|
|
2271
|
+
__privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
|
|
2248
2272
|
try {
|
|
2249
2273
|
await __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
2250
2274
|
} catch (err) {
|
|
@@ -2266,6 +2290,8 @@ start_fn = async function() {
|
|
|
2266
2290
|
if (__privateGet(this, _syncState) instanceof ErrorState) {
|
|
2267
2291
|
__privateSet(this, _syncState, __privateGet(this, _syncState).retry());
|
|
2268
2292
|
}
|
|
2293
|
+
__privateSet(this, _fastLoopConsecutiveCount, 0);
|
|
2294
|
+
__privateSet(this, _recentRequestEntries, []);
|
|
2269
2295
|
__privateSet(this, _started, false);
|
|
2270
2296
|
await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
2271
2297
|
return;
|
|
@@ -2296,6 +2322,12 @@ requestShape_fn = async function() {
|
|
|
2296
2322
|
if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _syncState).isUpToDate)) {
|
|
2297
2323
|
return;
|
|
2298
2324
|
}
|
|
2325
|
+
if (!__privateGet(this, _syncState).isUpToDate) {
|
|
2326
|
+
await __privateMethod(this, _ShapeStream_instances, checkFastLoop_fn).call(this);
|
|
2327
|
+
} else {
|
|
2328
|
+
__privateSet(this, _fastLoopConsecutiveCount, 0);
|
|
2329
|
+
__privateSet(this, _recentRequestEntries, []);
|
|
2330
|
+
}
|
|
2299
2331
|
let resumingFromPause = false;
|
|
2300
2332
|
if (__privateGet(this, _syncState) instanceof PausedState) {
|
|
2301
2333
|
resumingFromPause = true;
|
|
@@ -2354,6 +2386,56 @@ requestShape_fn = async function() {
|
|
|
2354
2386
|
(_b = __privateGet(this, _tickPromiseResolver)) == null ? void 0 : _b.call(this);
|
|
2355
2387
|
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
2356
2388
|
};
|
|
2389
|
+
checkFastLoop_fn = async function() {
|
|
2390
|
+
const now = Date.now();
|
|
2391
|
+
const currentOffset = __privateGet(this, _syncState).offset;
|
|
2392
|
+
__privateSet(this, _recentRequestEntries, __privateGet(this, _recentRequestEntries).filter(
|
|
2393
|
+
(e) => now - e.timestamp < __privateGet(this, _fastLoopWindowMs)
|
|
2394
|
+
));
|
|
2395
|
+
__privateGet(this, _recentRequestEntries).push({ timestamp: now, offset: currentOffset });
|
|
2396
|
+
const sameOffsetCount = __privateGet(this, _recentRequestEntries).filter(
|
|
2397
|
+
(e) => e.offset === currentOffset
|
|
2398
|
+
).length;
|
|
2399
|
+
if (sameOffsetCount < __privateGet(this, _fastLoopThreshold)) return;
|
|
2400
|
+
__privateWrapper(this, _fastLoopConsecutiveCount)._++;
|
|
2401
|
+
if (__privateGet(this, _fastLoopConsecutiveCount) >= __privateGet(this, _fastLoopMaxCount)) {
|
|
2402
|
+
throw new FetchError(
|
|
2403
|
+
502,
|
|
2404
|
+
void 0,
|
|
2405
|
+
void 0,
|
|
2406
|
+
{},
|
|
2407
|
+
this.options.url,
|
|
2408
|
+
`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:
|
|
2409
|
+
- Proxy is not including query parameters (handle, offset) in its cache key
|
|
2410
|
+
- CDN is serving stale 409 responses
|
|
2411
|
+
- Proxy is stripping required Electric headers from responses
|
|
2412
|
+
For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`
|
|
2413
|
+
);
|
|
2414
|
+
}
|
|
2415
|
+
if (__privateGet(this, _fastLoopConsecutiveCount) === 1) {
|
|
2416
|
+
console.warn(
|
|
2417
|
+
`[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}`
|
|
2418
|
+
);
|
|
2419
|
+
if (__privateGet(this, _currentFetchUrl)) {
|
|
2420
|
+
const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
|
|
2421
|
+
expiredShapesCache.delete(shapeKey);
|
|
2422
|
+
upToDateTracker.delete(shapeKey);
|
|
2423
|
+
} else {
|
|
2424
|
+
expiredShapesCache.clear();
|
|
2425
|
+
upToDateTracker.clear();
|
|
2426
|
+
}
|
|
2427
|
+
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this);
|
|
2428
|
+
__privateSet(this, _recentRequestEntries, []);
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
const maxDelay = Math.min(
|
|
2432
|
+
__privateGet(this, _fastLoopBackoffMaxMs),
|
|
2433
|
+
__privateGet(this, _fastLoopBackoffBaseMs) * Math.pow(2, __privateGet(this, _fastLoopConsecutiveCount))
|
|
2434
|
+
);
|
|
2435
|
+
const delayMs = Math.floor(Math.random() * maxDelay);
|
|
2436
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2437
|
+
__privateSet(this, _recentRequestEntries, []);
|
|
2438
|
+
};
|
|
2357
2439
|
constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
2358
2440
|
var _a, _b, _c, _d, _e, _f;
|
|
2359
2441
|
const [requestHeaders, params] = await Promise.all([
|
|
@@ -2504,11 +2586,11 @@ onInitialResponse_fn = async function(response) {
|
|
|
2504
2586
|
void 0,
|
|
2505
2587
|
{},
|
|
2506
2588
|
(_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
|
|
2507
|
-
`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:
|
|
2589
|
+
`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}`
|
|
2508
2590
|
);
|
|
2509
2591
|
}
|
|
2510
2592
|
console.warn(
|
|
2511
|
-
`[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:
|
|
2593
|
+
`[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)}).`
|
|
2512
2594
|
);
|
|
2513
2595
|
throw new StaleCacheError(
|
|
2514
2596
|
`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.`
|
|
@@ -2740,6 +2822,7 @@ subscribeToVisibilityChanges_fn = function() {
|
|
|
2740
2822
|
document.addEventListener(`visibilitychange`, visibilityHandler);
|
|
2741
2823
|
__privateSet(this, _unsubscribeFromVisibilityChanges, () => {
|
|
2742
2824
|
document.removeEventListener(`visibilitychange`, visibilityHandler);
|
|
2825
|
+
__privateSet(this, _unsubscribeFromVisibilityChanges, void 0);
|
|
2743
2826
|
});
|
|
2744
2827
|
}
|
|
2745
2828
|
};
|
|
@@ -2757,6 +2840,7 @@ subscribeToVisibilityChanges_fn = function() {
|
|
|
2757
2840
|
*/
|
|
2758
2841
|
subscribeToWakeDetection_fn = function() {
|
|
2759
2842
|
if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) return;
|
|
2843
|
+
if (__privateGet(this, _unsubscribeFromWakeDetection)) return;
|
|
2760
2844
|
const INTERVAL_MS = 2e3;
|
|
2761
2845
|
const WAKE_THRESHOLD_MS = 4e3;
|
|
2762
2846
|
let lastTickTime = Date.now();
|
|
@@ -2779,6 +2863,7 @@ subscribeToWakeDetection_fn = function() {
|
|
|
2779
2863
|
}
|
|
2780
2864
|
__privateSet(this, _unsubscribeFromWakeDetection, () => {
|
|
2781
2865
|
clearInterval(timer);
|
|
2866
|
+
__privateSet(this, _unsubscribeFromWakeDetection, void 0);
|
|
2782
2867
|
});
|
|
2783
2868
|
};
|
|
2784
2869
|
/**
|