@electric-sql/client 1.4.0 → 1.4.1
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 +58 -6
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -1
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.legacy-esm.js +58 -6
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +58 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +70 -1
- package/src/constants.ts +2 -0
- package/src/error.ts +7 -0
package/dist/cjs/index.cjs
CHANGED
|
@@ -167,6 +167,12 @@ For more information visit the troubleshooting guide: /docs/guides/troubleshooti
|
|
|
167
167
|
super(msg);
|
|
168
168
|
}
|
|
169
169
|
};
|
|
170
|
+
var StaleCacheError = class extends Error {
|
|
171
|
+
constructor(message) {
|
|
172
|
+
super(message);
|
|
173
|
+
this.name = `StaleCacheError`;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
170
176
|
|
|
171
177
|
// src/parser.ts
|
|
172
178
|
var parseNumber = (value) => Number(value);
|
|
@@ -506,6 +512,7 @@ var SUBSET_PARAM_ORDER_BY = `subset__order_by`;
|
|
|
506
512
|
var SUBSET_PARAM_WHERE_PARAMS = `subset__params`;
|
|
507
513
|
var SUBSET_PARAM_WHERE_EXPR = `subset__where_expr`;
|
|
508
514
|
var SUBSET_PARAM_ORDER_BY_EXPR = `subset__order_by_expr`;
|
|
515
|
+
var CACHE_BUSTER_QUERY_PARAM = `cache-buster`;
|
|
509
516
|
var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
510
517
|
LIVE_QUERY_PARAM,
|
|
511
518
|
LIVE_SSE_QUERY_PARAM,
|
|
@@ -520,7 +527,8 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
520
527
|
SUBSET_PARAM_ORDER_BY,
|
|
521
528
|
SUBSET_PARAM_WHERE_PARAMS,
|
|
522
529
|
SUBSET_PARAM_WHERE_EXPR,
|
|
523
|
-
SUBSET_PARAM_ORDER_BY_EXPR
|
|
530
|
+
SUBSET_PARAM_ORDER_BY_EXPR,
|
|
531
|
+
CACHE_BUSTER_QUERY_PARAM
|
|
524
532
|
];
|
|
525
533
|
|
|
526
534
|
// src/fetch.ts
|
|
@@ -1126,7 +1134,8 @@ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
1126
1134
|
LIVE_CACHE_BUSTER_QUERY_PARAM,
|
|
1127
1135
|
SHAPE_HANDLE_QUERY_PARAM,
|
|
1128
1136
|
LIVE_QUERY_PARAM,
|
|
1129
|
-
OFFSET_QUERY_PARAM
|
|
1137
|
+
OFFSET_QUERY_PARAM,
|
|
1138
|
+
CACHE_BUSTER_QUERY_PARAM
|
|
1130
1139
|
]);
|
|
1131
1140
|
async function resolveValue(value) {
|
|
1132
1141
|
if (typeof value === `function`) {
|
|
@@ -1168,7 +1177,7 @@ function canonicalShapeKey(url) {
|
|
|
1168
1177
|
cleanUrl.searchParams.sort();
|
|
1169
1178
|
return cleanUrl.toString();
|
|
1170
1179
|
}
|
|
1171
|
-
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, _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;
|
|
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;
|
|
1172
1181
|
var ShapeStream = class {
|
|
1173
1182
|
constructor(options) {
|
|
1174
1183
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -1219,6 +1228,10 @@ var ShapeStream = class {
|
|
|
1219
1228
|
__privateAdd(this, _sseBackoffMaxDelay, 5e3);
|
|
1220
1229
|
// Maximum delay cap (ms)
|
|
1221
1230
|
__privateAdd(this, _unsubscribeFromVisibilityChanges);
|
|
1231
|
+
__privateAdd(this, _staleCacheBuster);
|
|
1232
|
+
// Cache buster set when stale CDN response detected, used on retry requests to bypass cache
|
|
1233
|
+
__privateAdd(this, _staleCacheRetryCount, 0);
|
|
1234
|
+
__privateAdd(this, _maxStaleCacheRetries, 3);
|
|
1222
1235
|
var _a, _b, _c, _d;
|
|
1223
1236
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
1224
1237
|
validateOptions(this.options);
|
|
@@ -1449,6 +1462,9 @@ _sseFallbackToLongPolling = new WeakMap();
|
|
|
1449
1462
|
_sseBackoffBaseDelay = new WeakMap();
|
|
1450
1463
|
_sseBackoffMaxDelay = new WeakMap();
|
|
1451
1464
|
_unsubscribeFromVisibilityChanges = new WeakMap();
|
|
1465
|
+
_staleCacheBuster = new WeakMap();
|
|
1466
|
+
_staleCacheRetryCount = new WeakMap();
|
|
1467
|
+
_maxStaleCacheRetries = new WeakMap();
|
|
1452
1468
|
_ShapeStream_instances = new WeakSet();
|
|
1453
1469
|
replayMode_get = function() {
|
|
1454
1470
|
return __privateGet(this, _lastSeenCursor) !== void 0;
|
|
@@ -1462,7 +1478,8 @@ start_fn = async function() {
|
|
|
1462
1478
|
__privateSet(this, _error, err);
|
|
1463
1479
|
if (__privateGet(this, _onError)) {
|
|
1464
1480
|
const retryOpts = await __privateGet(this, _onError).call(this, err);
|
|
1465
|
-
|
|
1481
|
+
const isRetryable = !(err instanceof MissingHeadersError);
|
|
1482
|
+
if (retryOpts && typeof retryOpts === `object` && isRetryable) {
|
|
1466
1483
|
if (retryOpts.params) {
|
|
1467
1484
|
this.options.params = __spreadValues(__spreadValues({}, (_a = this.options.params) != null ? _a : {}), retryOpts.params);
|
|
1468
1485
|
}
|
|
@@ -1524,6 +1541,9 @@ requestShape_fn = async function() {
|
|
|
1524
1541
|
}
|
|
1525
1542
|
return;
|
|
1526
1543
|
}
|
|
1544
|
+
if (e instanceof StaleCacheError) {
|
|
1545
|
+
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
1546
|
+
}
|
|
1527
1547
|
if (!(e instanceof FetchError)) throw e;
|
|
1528
1548
|
if (e.status == 409) {
|
|
1529
1549
|
if (__privateGet(this, _shapeHandle)) {
|
|
@@ -1656,6 +1676,12 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
|
1656
1676
|
if (expiredHandle) {
|
|
1657
1677
|
fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
|
|
1658
1678
|
}
|
|
1679
|
+
if (__privateGet(this, _staleCacheBuster)) {
|
|
1680
|
+
fetchUrl.searchParams.set(
|
|
1681
|
+
CACHE_BUSTER_QUERY_PARAM,
|
|
1682
|
+
__privateGet(this, _staleCacheBuster)
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1659
1685
|
fetchUrl.searchParams.sort();
|
|
1660
1686
|
return {
|
|
1661
1687
|
fetchUrl,
|
|
@@ -1678,7 +1704,7 @@ createAbortListener_fn = async function(signal) {
|
|
|
1678
1704
|
}
|
|
1679
1705
|
};
|
|
1680
1706
|
onInitialResponse_fn = async function(response) {
|
|
1681
|
-
var _a;
|
|
1707
|
+
var _a, _b, _c, _d;
|
|
1682
1708
|
const { headers, status } = response;
|
|
1683
1709
|
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
|
|
1684
1710
|
if (shapeHandle) {
|
|
@@ -1686,6 +1712,30 @@ onInitialResponse_fn = async function(response) {
|
|
|
1686
1712
|
const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
|
|
1687
1713
|
if (shapeHandle !== expiredHandle) {
|
|
1688
1714
|
__privateSet(this, _shapeHandle, shapeHandle);
|
|
1715
|
+
if (__privateGet(this, _staleCacheBuster)) {
|
|
1716
|
+
__privateSet(this, _staleCacheBuster, void 0);
|
|
1717
|
+
__privateSet(this, _staleCacheRetryCount, 0);
|
|
1718
|
+
}
|
|
1719
|
+
} else if (__privateGet(this, _shapeHandle) === void 0) {
|
|
1720
|
+
__privateWrapper(this, _staleCacheRetryCount)._++;
|
|
1721
|
+
await ((_a = response.body) == null ? void 0 : _a.cancel());
|
|
1722
|
+
if (__privateGet(this, _staleCacheRetryCount) > __privateGet(this, _maxStaleCacheRetries)) {
|
|
1723
|
+
throw new FetchError(
|
|
1724
|
+
502,
|
|
1725
|
+
void 0,
|
|
1726
|
+
void 0,
|
|
1727
|
+
{},
|
|
1728
|
+
(_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
|
|
1729
|
+
`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: https://electric-sql.com/docs/guides/troubleshooting`
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
console.warn(
|
|
1733
|
+
`[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: https://electric-sql.com/docs/guides/troubleshooting Retrying with a random cache buster to bypass the stale cache (attempt ${__privateGet(this, _staleCacheRetryCount)}/${__privateGet(this, _maxStaleCacheRetries)}).`
|
|
1734
|
+
);
|
|
1735
|
+
__privateSet(this, _staleCacheBuster, `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`);
|
|
1736
|
+
throw new StaleCacheError(
|
|
1737
|
+
`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.`
|
|
1738
|
+
);
|
|
1689
1739
|
} else {
|
|
1690
1740
|
console.warn(
|
|
1691
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 handle and continuing with handle "${__privateGet(this, _shapeHandle)}".`
|
|
@@ -1700,7 +1750,7 @@ onInitialResponse_fn = async function(response) {
|
|
|
1700
1750
|
if (liveCacheBuster) {
|
|
1701
1751
|
__privateSet(this, _liveCacheBuster, liveCacheBuster);
|
|
1702
1752
|
}
|
|
1703
|
-
__privateSet(this, _schema, (
|
|
1753
|
+
__privateSet(this, _schema, (_d = __privateGet(this, _schema)) != null ? _d : getSchemaFromHeaders(headers));
|
|
1704
1754
|
if (status === 204) {
|
|
1705
1755
|
__privateSet(this, _lastSyncedAt, Date.now());
|
|
1706
1756
|
}
|
|
@@ -1938,6 +1988,8 @@ reset_fn = function(handle) {
|
|
|
1938
1988
|
__privateSet(this, _activeSnapshotRequests, 0);
|
|
1939
1989
|
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1940
1990
|
__privateSet(this, _sseFallbackToLongPolling, false);
|
|
1991
|
+
__privateSet(this, _staleCacheBuster, void 0);
|
|
1992
|
+
__privateSet(this, _staleCacheRetryCount, 0);
|
|
1941
1993
|
};
|
|
1942
1994
|
ShapeStream.Replica = {
|
|
1943
1995
|
FULL: `full`,
|