@electric-sql/client 1.4.0 → 1.4.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/cjs/index.cjs +61 -8
- 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 +61 -8
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +61 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +76 -5
- package/src/constants.ts +2 -0
- package/src/error.ts +7 -0
- package/src/shape.ts +2 -2
package/dist/index.mjs
CHANGED
|
@@ -130,6 +130,12 @@ For more information visit the troubleshooting guide: /docs/guides/troubleshooti
|
|
|
130
130
|
super(msg);
|
|
131
131
|
}
|
|
132
132
|
};
|
|
133
|
+
var StaleCacheError = class extends Error {
|
|
134
|
+
constructor(message) {
|
|
135
|
+
super(message);
|
|
136
|
+
this.name = `StaleCacheError`;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
133
139
|
|
|
134
140
|
// src/parser.ts
|
|
135
141
|
var parseNumber = (value) => Number(value);
|
|
@@ -469,6 +475,7 @@ var SUBSET_PARAM_ORDER_BY = `subset__order_by`;
|
|
|
469
475
|
var SUBSET_PARAM_WHERE_PARAMS = `subset__params`;
|
|
470
476
|
var SUBSET_PARAM_WHERE_EXPR = `subset__where_expr`;
|
|
471
477
|
var SUBSET_PARAM_ORDER_BY_EXPR = `subset__order_by_expr`;
|
|
478
|
+
var CACHE_BUSTER_QUERY_PARAM = `cache-buster`;
|
|
472
479
|
var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
473
480
|
LIVE_QUERY_PARAM,
|
|
474
481
|
LIVE_SSE_QUERY_PARAM,
|
|
@@ -483,7 +490,8 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
483
490
|
SUBSET_PARAM_ORDER_BY,
|
|
484
491
|
SUBSET_PARAM_WHERE_PARAMS,
|
|
485
492
|
SUBSET_PARAM_WHERE_EXPR,
|
|
486
|
-
SUBSET_PARAM_ORDER_BY_EXPR
|
|
493
|
+
SUBSET_PARAM_ORDER_BY_EXPR,
|
|
494
|
+
CACHE_BUSTER_QUERY_PARAM
|
|
487
495
|
];
|
|
488
496
|
|
|
489
497
|
// src/fetch.ts
|
|
@@ -1091,7 +1099,8 @@ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
1091
1099
|
LIVE_CACHE_BUSTER_QUERY_PARAM,
|
|
1092
1100
|
SHAPE_HANDLE_QUERY_PARAM,
|
|
1093
1101
|
LIVE_QUERY_PARAM,
|
|
1094
|
-
OFFSET_QUERY_PARAM
|
|
1102
|
+
OFFSET_QUERY_PARAM,
|
|
1103
|
+
CACHE_BUSTER_QUERY_PARAM
|
|
1095
1104
|
]);
|
|
1096
1105
|
async function resolveValue(value) {
|
|
1097
1106
|
if (typeof value === `function`) {
|
|
@@ -1133,7 +1142,7 @@ function canonicalShapeKey(url) {
|
|
|
1133
1142
|
cleanUrl.searchParams.sort();
|
|
1134
1143
|
return cleanUrl.toString();
|
|
1135
1144
|
}
|
|
1136
|
-
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;
|
|
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;
|
|
1137
1146
|
var ShapeStream = class {
|
|
1138
1147
|
constructor(options) {
|
|
1139
1148
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -1184,6 +1193,10 @@ var ShapeStream = class {
|
|
|
1184
1193
|
__privateAdd(this, _sseBackoffMaxDelay, 5e3);
|
|
1185
1194
|
// Maximum delay cap (ms)
|
|
1186
1195
|
__privateAdd(this, _unsubscribeFromVisibilityChanges);
|
|
1196
|
+
__privateAdd(this, _staleCacheBuster);
|
|
1197
|
+
// Cache buster set when stale CDN response detected, used on retry requests to bypass cache
|
|
1198
|
+
__privateAdd(this, _staleCacheRetryCount, 0);
|
|
1199
|
+
__privateAdd(this, _maxStaleCacheRetries, 3);
|
|
1187
1200
|
var _a, _b, _c, _d;
|
|
1188
1201
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
1189
1202
|
validateOptions(this.options);
|
|
@@ -1242,7 +1255,7 @@ var ShapeStream = class {
|
|
|
1242
1255
|
}
|
|
1243
1256
|
subscribe(callback, onError = () => {
|
|
1244
1257
|
}) {
|
|
1245
|
-
const subscriptionId =
|
|
1258
|
+
const subscriptionId = {};
|
|
1246
1259
|
__privateGet(this, _subscribers).set(subscriptionId, [callback, onError]);
|
|
1247
1260
|
if (!__privateGet(this, _started)) __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
1248
1261
|
return () => {
|
|
@@ -1414,6 +1427,9 @@ _sseFallbackToLongPolling = new WeakMap();
|
|
|
1414
1427
|
_sseBackoffBaseDelay = new WeakMap();
|
|
1415
1428
|
_sseBackoffMaxDelay = new WeakMap();
|
|
1416
1429
|
_unsubscribeFromVisibilityChanges = new WeakMap();
|
|
1430
|
+
_staleCacheBuster = new WeakMap();
|
|
1431
|
+
_staleCacheRetryCount = new WeakMap();
|
|
1432
|
+
_maxStaleCacheRetries = new WeakMap();
|
|
1417
1433
|
_ShapeStream_instances = new WeakSet();
|
|
1418
1434
|
replayMode_get = function() {
|
|
1419
1435
|
return __privateGet(this, _lastSeenCursor) !== void 0;
|
|
@@ -1427,7 +1443,8 @@ start_fn = async function() {
|
|
|
1427
1443
|
__privateSet(this, _error, err);
|
|
1428
1444
|
if (__privateGet(this, _onError)) {
|
|
1429
1445
|
const retryOpts = await __privateGet(this, _onError).call(this, err);
|
|
1430
|
-
|
|
1446
|
+
const isRetryable = !(err instanceof MissingHeadersError);
|
|
1447
|
+
if (retryOpts && typeof retryOpts === `object` && isRetryable) {
|
|
1431
1448
|
if (retryOpts.params) {
|
|
1432
1449
|
this.options.params = __spreadValues(__spreadValues({}, (_a = this.options.params) != null ? _a : {}), retryOpts.params);
|
|
1433
1450
|
}
|
|
@@ -1489,6 +1506,9 @@ requestShape_fn = async function() {
|
|
|
1489
1506
|
}
|
|
1490
1507
|
return;
|
|
1491
1508
|
}
|
|
1509
|
+
if (e instanceof StaleCacheError) {
|
|
1510
|
+
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
1511
|
+
}
|
|
1492
1512
|
if (!(e instanceof FetchError)) throw e;
|
|
1493
1513
|
if (e.status == 409) {
|
|
1494
1514
|
if (__privateGet(this, _shapeHandle)) {
|
|
@@ -1621,6 +1641,12 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
|
1621
1641
|
if (expiredHandle) {
|
|
1622
1642
|
fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
|
|
1623
1643
|
}
|
|
1644
|
+
if (__privateGet(this, _staleCacheBuster)) {
|
|
1645
|
+
fetchUrl.searchParams.set(
|
|
1646
|
+
CACHE_BUSTER_QUERY_PARAM,
|
|
1647
|
+
__privateGet(this, _staleCacheBuster)
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1624
1650
|
fetchUrl.searchParams.sort();
|
|
1625
1651
|
return {
|
|
1626
1652
|
fetchUrl,
|
|
@@ -1643,7 +1669,7 @@ createAbortListener_fn = async function(signal) {
|
|
|
1643
1669
|
}
|
|
1644
1670
|
};
|
|
1645
1671
|
onInitialResponse_fn = async function(response) {
|
|
1646
|
-
var _a;
|
|
1672
|
+
var _a, _b, _c, _d;
|
|
1647
1673
|
const { headers, status } = response;
|
|
1648
1674
|
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
|
|
1649
1675
|
if (shapeHandle) {
|
|
@@ -1651,6 +1677,30 @@ onInitialResponse_fn = async function(response) {
|
|
|
1651
1677
|
const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
|
|
1652
1678
|
if (shapeHandle !== expiredHandle) {
|
|
1653
1679
|
__privateSet(this, _shapeHandle, shapeHandle);
|
|
1680
|
+
if (__privateGet(this, _staleCacheBuster)) {
|
|
1681
|
+
__privateSet(this, _staleCacheBuster, void 0);
|
|
1682
|
+
__privateSet(this, _staleCacheRetryCount, 0);
|
|
1683
|
+
}
|
|
1684
|
+
} else if (__privateGet(this, _shapeHandle) === void 0) {
|
|
1685
|
+
__privateWrapper(this, _staleCacheRetryCount)._++;
|
|
1686
|
+
await ((_a = response.body) == null ? void 0 : _a.cancel());
|
|
1687
|
+
if (__privateGet(this, _staleCacheRetryCount) > __privateGet(this, _maxStaleCacheRetries)) {
|
|
1688
|
+
throw new FetchError(
|
|
1689
|
+
502,
|
|
1690
|
+
void 0,
|
|
1691
|
+
void 0,
|
|
1692
|
+
{},
|
|
1693
|
+
(_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
|
|
1694
|
+
`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`
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
console.warn(
|
|
1698
|
+
`[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)}).`
|
|
1699
|
+
);
|
|
1700
|
+
__privateSet(this, _staleCacheBuster, `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`);
|
|
1701
|
+
throw new StaleCacheError(
|
|
1702
|
+
`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.`
|
|
1703
|
+
);
|
|
1654
1704
|
} else {
|
|
1655
1705
|
console.warn(
|
|
1656
1706
|
`[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)}".`
|
|
@@ -1665,7 +1715,7 @@ onInitialResponse_fn = async function(response) {
|
|
|
1665
1715
|
if (liveCacheBuster) {
|
|
1666
1716
|
__privateSet(this, _liveCacheBuster, liveCacheBuster);
|
|
1667
1717
|
}
|
|
1668
|
-
__privateSet(this, _schema, (
|
|
1718
|
+
__privateSet(this, _schema, (_d = __privateGet(this, _schema)) != null ? _d : getSchemaFromHeaders(headers));
|
|
1669
1719
|
if (status === 204) {
|
|
1670
1720
|
__privateSet(this, _lastSyncedAt, Date.now());
|
|
1671
1721
|
}
|
|
@@ -1689,6 +1739,7 @@ onMessages_fn = async function(batch, isSseMessage = false) {
|
|
|
1689
1739
|
if (__privateGet(this, _ShapeStream_instances, replayMode_get) && !isSseMessage) {
|
|
1690
1740
|
const currentCursor = __privateGet(this, _liveCacheBuster);
|
|
1691
1741
|
if (currentCursor === __privateGet(this, _lastSeenCursor)) {
|
|
1742
|
+
__privateSet(this, _lastSeenCursor, void 0);
|
|
1692
1743
|
return;
|
|
1693
1744
|
}
|
|
1694
1745
|
}
|
|
@@ -1903,6 +1954,8 @@ reset_fn = function(handle) {
|
|
|
1903
1954
|
__privateSet(this, _activeSnapshotRequests, 0);
|
|
1904
1955
|
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1905
1956
|
__privateSet(this, _sseFallbackToLongPolling, false);
|
|
1957
|
+
__privateSet(this, _staleCacheBuster, void 0);
|
|
1958
|
+
__privateSet(this, _staleCacheRetryCount, 0);
|
|
1906
1959
|
};
|
|
1907
1960
|
ShapeStream.Replica = {
|
|
1908
1961
|
FULL: `full`,
|
|
@@ -2045,7 +2098,7 @@ var Shape = class {
|
|
|
2045
2098
|
await this.stream.requestSnapshot(params);
|
|
2046
2099
|
}
|
|
2047
2100
|
subscribe(callback) {
|
|
2048
|
-
const subscriptionId =
|
|
2101
|
+
const subscriptionId = {};
|
|
2049
2102
|
__privateGet(this, _subscribers2).set(subscriptionId, callback);
|
|
2050
2103
|
return () => {
|
|
2051
2104
|
__privateGet(this, _subscribers2).delete(subscriptionId);
|