@electric-sql/client 1.5.12 → 1.5.13
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/bin/analyze-pr-risks.mjs +81 -0
- package/bin/analyze-shape-stream-risks.mjs +20 -0
- package/bin/lib/shape-stream-static-analysis.mjs +1067 -0
- package/dist/cjs/index.cjs +134 -96
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.browser.mjs +4 -4
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.legacy-esm.js +134 -96
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +134 -96
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/src/client.ts +90 -33
- package/src/fetch.ts +6 -4
- package/src/shape-stream-state.ts +13 -19
package/dist/index.mjs
CHANGED
|
@@ -641,11 +641,11 @@ function createFetchWithChunkBuffer(fetchClient, prefetchOptions = ChunkPrefetch
|
|
|
641
641
|
return prefetchClient;
|
|
642
642
|
}
|
|
643
643
|
var requiredElectricResponseHeaders = [
|
|
644
|
-
|
|
645
|
-
|
|
644
|
+
CHUNK_LAST_OFFSET_HEADER,
|
|
645
|
+
SHAPE_HANDLE_HEADER
|
|
646
646
|
];
|
|
647
|
-
var requiredLiveResponseHeaders = [
|
|
648
|
-
var requiredNonLiveResponseHeaders = [
|
|
647
|
+
var requiredLiveResponseHeaders = [LIVE_CACHE_BUSTER_HEADER];
|
|
648
|
+
var requiredNonLiveResponseHeaders = [SHAPE_SCHEMA_HEADER];
|
|
649
649
|
function createFetchWithResponseHeadersCheck(fetchClient) {
|
|
650
650
|
return async (...args) => {
|
|
651
651
|
const response = await fetchClient(...args);
|
|
@@ -1436,18 +1436,15 @@ var ActiveState = class extends ShapeStreamState {
|
|
|
1436
1436
|
if (!responseHandle || responseHandle !== expiredHandle) {
|
|
1437
1437
|
return null;
|
|
1438
1438
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
};
|
|
1449
|
-
}
|
|
1450
|
-
return { action: `ignored`, state: this };
|
|
1439
|
+
const retryCount = this.staleCacheRetryCount + 1;
|
|
1440
|
+
return {
|
|
1441
|
+
action: `stale-retry`,
|
|
1442
|
+
state: new StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
|
|
1443
|
+
staleCacheBuster: input.createCacheBuster(),
|
|
1444
|
+
staleCacheRetryCount: retryCount
|
|
1445
|
+
})),
|
|
1446
|
+
exceededMaxRetries: retryCount > input.maxStaleCacheRetries
|
|
1447
|
+
};
|
|
1451
1448
|
}
|
|
1452
1449
|
// --- handleMessageBatch: template method with onUpToDate override point ---
|
|
1453
1450
|
handleMessageBatch(input) {
|
|
@@ -1946,7 +1943,7 @@ function canonicalShapeKey(url) {
|
|
|
1946
1943
|
cleanUrl.searchParams.sort();
|
|
1947
1944
|
return cleanUrl.toString();
|
|
1948
1945
|
}
|
|
1949
|
-
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,
|
|
1946
|
+
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, _pendingRequestShapeCacheBuster, _maxSnapshotRetries, 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, fetchSnapshotWithRetry_fn, buildSubsetBody_fn;
|
|
1950
1947
|
var ShapeStream = class {
|
|
1951
1948
|
constructor(options) {
|
|
1952
1949
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -1993,7 +1990,8 @@ var ShapeStream = class {
|
|
|
1993
1990
|
__privateAdd(this, _fastLoopBackoffMaxMs, 5e3);
|
|
1994
1991
|
__privateAdd(this, _fastLoopConsecutiveCount, 0);
|
|
1995
1992
|
__privateAdd(this, _fastLoopMaxCount, 5);
|
|
1996
|
-
__privateAdd(this,
|
|
1993
|
+
__privateAdd(this, _pendingRequestShapeCacheBuster);
|
|
1994
|
+
__privateAdd(this, _maxSnapshotRetries, 5);
|
|
1997
1995
|
var _a, _b, _c, _d;
|
|
1998
1996
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
1999
1997
|
validateOptions(this.options);
|
|
@@ -2151,7 +2149,8 @@ var ShapeStream = class {
|
|
|
2151
2149
|
__privateGet(this, _pauseLock).acquire(snapshotReason);
|
|
2152
2150
|
const snapshotWarnTimer = setTimeout(() => {
|
|
2153
2151
|
console.warn(
|
|
2154
|
-
`[Electric] Snapshot "${snapshotReason}" has held the pause lock for 30s \u2014 possible hung request or leaked lock. Current holders: ${[.../* @__PURE__ */ new Set([snapshotReason])].join(`, `)}
|
|
2152
|
+
`[Electric] Snapshot "${snapshotReason}" has held the pause lock for 30s \u2014 possible hung request or leaked lock. Current holders: ${[.../* @__PURE__ */ new Set([snapshotReason])].join(`, `)}`,
|
|
2153
|
+
new Error(`stack trace`)
|
|
2155
2154
|
);
|
|
2156
2155
|
}, 3e4);
|
|
2157
2156
|
try {
|
|
@@ -2180,7 +2179,8 @@ var ShapeStream = class {
|
|
|
2180
2179
|
__privateSet(this, _syncState, transition.state);
|
|
2181
2180
|
} else {
|
|
2182
2181
|
console.warn(
|
|
2183
|
-
`[Electric] Snapshot response metadata was not accepted by state "${__privateGet(this, _syncState).kind}" (action: ${transition.action}). Stream offset was not advanced from snapshot
|
|
2182
|
+
`[Electric] Snapshot response metadata was not accepted by state "${__privateGet(this, _syncState).kind}" (action: ${transition.action}). Stream offset was not advanced from snapshot.`,
|
|
2183
|
+
new Error(`stack trace`)
|
|
2184
2184
|
);
|
|
2185
2185
|
}
|
|
2186
2186
|
}
|
|
@@ -2205,64 +2205,7 @@ var ShapeStream = class {
|
|
|
2205
2205
|
* @returns The metadata, data, and the response's offset/handle for state advancement.
|
|
2206
2206
|
*/
|
|
2207
2207
|
async fetchSnapshot(opts) {
|
|
2208
|
-
|
|
2209
|
-
const method = (_b = (_a = opts.method) != null ? _a : this.options.subsetMethod) != null ? _b : `GET`;
|
|
2210
|
-
const usePost = method === `POST`;
|
|
2211
|
-
let fetchUrl;
|
|
2212
|
-
let fetchOptions;
|
|
2213
|
-
if (usePost) {
|
|
2214
|
-
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true);
|
|
2215
|
-
fetchUrl = result.fetchUrl;
|
|
2216
|
-
fetchOptions = {
|
|
2217
|
-
method: `POST`,
|
|
2218
|
-
headers: __spreadProps(__spreadValues({}, result.requestHeaders), {
|
|
2219
|
-
"Content-Type": `application/json`
|
|
2220
|
-
}),
|
|
2221
|
-
body: bigintSafeStringify(__privateMethod(this, _ShapeStream_instances, buildSubsetBody_fn).call(this, opts))
|
|
2222
|
-
};
|
|
2223
|
-
} else {
|
|
2224
|
-
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
|
|
2225
|
-
fetchUrl = result.fetchUrl;
|
|
2226
|
-
fetchOptions = { headers: result.requestHeaders };
|
|
2227
|
-
}
|
|
2228
|
-
const usedHandle = __privateGet(this, _syncState).handle;
|
|
2229
|
-
let response;
|
|
2230
|
-
try {
|
|
2231
|
-
response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
|
|
2232
|
-
} catch (e) {
|
|
2233
|
-
if (e instanceof FetchError && e.status === 409) {
|
|
2234
|
-
if (usedHandle) {
|
|
2235
|
-
const shapeKey = canonicalShapeKey(fetchUrl);
|
|
2236
|
-
expiredShapesCache.markExpired(shapeKey, usedHandle);
|
|
2237
|
-
}
|
|
2238
|
-
const nextHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
2239
|
-
if (nextHandle) {
|
|
2240
|
-
__privateSet(this, _syncState, __privateGet(this, _syncState).withHandle(nextHandle));
|
|
2241
|
-
} else {
|
|
2242
|
-
console.warn(
|
|
2243
|
-
`[Electric] Received 409 response without a shape handle header. This likely indicates a proxy or CDN stripping required headers.`
|
|
2244
|
-
);
|
|
2245
|
-
__privateSet(this, _refetchCacheBuster, createCacheBuster());
|
|
2246
|
-
}
|
|
2247
|
-
return this.fetchSnapshot(opts);
|
|
2248
|
-
}
|
|
2249
|
-
throw e;
|
|
2250
|
-
}
|
|
2251
|
-
if (!response.ok) {
|
|
2252
|
-
throw await FetchError.fromResponse(response, fetchUrl.toString());
|
|
2253
|
-
}
|
|
2254
|
-
const schema = (_c = __privateGet(this, _syncState).schema) != null ? _c : getSchemaFromHeaders(response.headers, {
|
|
2255
|
-
required: true,
|
|
2256
|
-
url: fetchUrl.toString()
|
|
2257
|
-
});
|
|
2258
|
-
const { metadata, data: rawData } = await response.json();
|
|
2259
|
-
const data = __privateGet(this, _messageParser).parseSnapshotData(
|
|
2260
|
-
rawData,
|
|
2261
|
-
schema
|
|
2262
|
-
);
|
|
2263
|
-
const responseOffset = response.headers.get(CHUNK_LAST_OFFSET_HEADER) || null;
|
|
2264
|
-
const responseHandle = response.headers.get(SHAPE_HANDLE_HEADER);
|
|
2265
|
-
return { metadata, data, responseOffset, responseHandle };
|
|
2208
|
+
return __privateMethod(this, _ShapeStream_instances, fetchSnapshotWithRetry_fn).call(this, opts, 0);
|
|
2266
2209
|
}
|
|
2267
2210
|
};
|
|
2268
2211
|
_error = new WeakMap();
|
|
@@ -2304,7 +2247,8 @@ _fastLoopBackoffBaseMs = new WeakMap();
|
|
|
2304
2247
|
_fastLoopBackoffMaxMs = new WeakMap();
|
|
2305
2248
|
_fastLoopConsecutiveCount = new WeakMap();
|
|
2306
2249
|
_fastLoopMaxCount = new WeakMap();
|
|
2307
|
-
|
|
2250
|
+
_pendingRequestShapeCacheBuster = new WeakMap();
|
|
2251
|
+
_maxSnapshotRetries = new WeakMap();
|
|
2308
2252
|
start_fn = async function() {
|
|
2309
2253
|
var _a, _b;
|
|
2310
2254
|
__privateSet(this, _started, true);
|
|
@@ -2356,9 +2300,15 @@ teardown_fn = function() {
|
|
|
2356
2300
|
(_a = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _a.call(this);
|
|
2357
2301
|
(_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
|
|
2358
2302
|
};
|
|
2359
|
-
requestShape_fn = async function() {
|
|
2303
|
+
requestShape_fn = async function(requestShapeCacheBuster) {
|
|
2360
2304
|
var _a, _b;
|
|
2361
|
-
|
|
2305
|
+
const activeCacheBuster = requestShapeCacheBuster != null ? requestShapeCacheBuster : __privateGet(this, _pendingRequestShapeCacheBuster);
|
|
2306
|
+
if (__privateGet(this, _pauseLock).isPaused) {
|
|
2307
|
+
if (activeCacheBuster) {
|
|
2308
|
+
__privateSet(this, _pendingRequestShapeCacheBuster, activeCacheBuster);
|
|
2309
|
+
}
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2362
2312
|
if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _syncState).isUpToDate)) {
|
|
2363
2313
|
return;
|
|
2364
2314
|
}
|
|
@@ -2375,15 +2325,23 @@ requestShape_fn = async function() {
|
|
|
2375
2325
|
}
|
|
2376
2326
|
const { url, signal } = this.options;
|
|
2377
2327
|
const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, url, resumingFromPause);
|
|
2328
|
+
if (activeCacheBuster) {
|
|
2329
|
+
fetchUrl.searchParams.set(CACHE_BUSTER_QUERY_PARAM, activeCacheBuster);
|
|
2330
|
+
fetchUrl.searchParams.sort();
|
|
2331
|
+
}
|
|
2378
2332
|
const abortListener = await __privateMethod(this, _ShapeStream_instances, createAbortListener_fn).call(this, signal);
|
|
2379
2333
|
const requestAbortController = __privateGet(this, _requestAbortController);
|
|
2380
2334
|
if (__privateGet(this, _pauseLock).isPaused) {
|
|
2381
2335
|
if (abortListener && signal) {
|
|
2382
2336
|
signal.removeEventListener(`abort`, abortListener);
|
|
2383
2337
|
}
|
|
2338
|
+
if (activeCacheBuster) {
|
|
2339
|
+
__privateSet(this, _pendingRequestShapeCacheBuster, activeCacheBuster);
|
|
2340
|
+
}
|
|
2384
2341
|
__privateSet(this, _requestAbortController, void 0);
|
|
2385
2342
|
return;
|
|
2386
2343
|
}
|
|
2344
|
+
__privateSet(this, _pendingRequestShapeCacheBuster, void 0);
|
|
2387
2345
|
try {
|
|
2388
2346
|
await __privateMethod(this, _ShapeStream_instances, fetchShape_fn).call(this, {
|
|
2389
2347
|
fetchUrl,
|
|
@@ -2410,16 +2368,18 @@ requestShape_fn = async function() {
|
|
|
2410
2368
|
expiredShapesCache.markExpired(shapeKey, __privateGet(this, _syncState).handle);
|
|
2411
2369
|
}
|
|
2412
2370
|
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
2371
|
+
let nextRequestShapeCacheBuster;
|
|
2413
2372
|
if (!newShapeHandle) {
|
|
2414
2373
|
console.warn(
|
|
2415
|
-
`[Electric] Received 409 response without a shape handle header. This likely indicates a proxy or CDN stripping required headers
|
|
2374
|
+
`[Electric] Received 409 response without a shape handle header. This likely indicates a proxy or CDN stripping required headers.`,
|
|
2375
|
+
new Error(`stack trace`)
|
|
2416
2376
|
);
|
|
2417
|
-
|
|
2377
|
+
nextRequestShapeCacheBuster = createCacheBuster();
|
|
2418
2378
|
}
|
|
2419
2379
|
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
|
|
2420
2380
|
const messages409 = Array.isArray(e.json) ? e.json : e.json != null ? [e.json] : [];
|
|
2421
2381
|
await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messages409);
|
|
2422
|
-
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
2382
|
+
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this, nextRequestShapeCacheBuster);
|
|
2423
2383
|
} else {
|
|
2424
2384
|
throw e;
|
|
2425
2385
|
}
|
|
@@ -2460,7 +2420,8 @@ For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`
|
|
|
2460
2420
|
}
|
|
2461
2421
|
if (__privateGet(this, _fastLoopConsecutiveCount) === 1) {
|
|
2462
2422
|
console.warn(
|
|
2463
|
-
`[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}
|
|
2423
|
+
`[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}`,
|
|
2424
|
+
new Error(`stack trace`)
|
|
2464
2425
|
);
|
|
2465
2426
|
if (__privateGet(this, _currentFetchUrl)) {
|
|
2466
2427
|
const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
|
|
@@ -2584,13 +2545,6 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
|
2584
2545
|
if (expiredHandle) {
|
|
2585
2546
|
fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
|
|
2586
2547
|
}
|
|
2587
|
-
if (__privateGet(this, _refetchCacheBuster)) {
|
|
2588
|
-
fetchUrl.searchParams.set(
|
|
2589
|
-
CACHE_BUSTER_QUERY_PARAM,
|
|
2590
|
-
__privateGet(this, _refetchCacheBuster)
|
|
2591
|
-
);
|
|
2592
|
-
__privateSet(this, _refetchCacheBuster, void 0);
|
|
2593
|
-
}
|
|
2594
2548
|
fetchUrl.searchParams.sort();
|
|
2595
2549
|
return {
|
|
2596
2550
|
fetchUrl,
|
|
@@ -2643,7 +2597,8 @@ onInitialResponse_fn = async function(response) {
|
|
|
2643
2597
|
);
|
|
2644
2598
|
}
|
|
2645
2599
|
console.warn(
|
|
2646
|
-
`[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)})
|
|
2600
|
+
`[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)}).`,
|
|
2601
|
+
new Error(`stack trace`)
|
|
2647
2602
|
);
|
|
2648
2603
|
throw new StaleCacheError(
|
|
2649
2604
|
`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.`
|
|
@@ -2651,7 +2606,8 @@ onInitialResponse_fn = async function(response) {
|
|
|
2651
2606
|
}
|
|
2652
2607
|
if (transition.action === `ignored`) {
|
|
2653
2608
|
console.warn(
|
|
2654
|
-
`[Electric]
|
|
2609
|
+
`[Electric] Response was ignored by state "${__privateGet(this, _syncState).kind}". The response body will be skipped. This may indicate a proxy/CDN caching issue or a client state machine bug.`,
|
|
2610
|
+
new Error(`stack trace`)
|
|
2655
2611
|
);
|
|
2656
2612
|
return false;
|
|
2657
2613
|
}
|
|
@@ -2660,7 +2616,8 @@ onInitialResponse_fn = async function(response) {
|
|
|
2660
2616
|
onMessages_fn = async function(batch, isSseMessage = false) {
|
|
2661
2617
|
if (!Array.isArray(batch)) {
|
|
2662
2618
|
console.warn(
|
|
2663
|
-
`[Electric] #onMessages called with non-array argument (${typeof batch}). This is a client bug \u2014 please report it
|
|
2619
|
+
`[Electric] #onMessages called with non-array argument (${typeof batch}). This is a client bug \u2014 please report it.`,
|
|
2620
|
+
new Error(`stack trace`)
|
|
2664
2621
|
);
|
|
2665
2622
|
return;
|
|
2666
2623
|
}
|
|
@@ -2807,7 +2764,8 @@ requestShapeSSE_fn = async function(opts) {
|
|
|
2807
2764
|
__privateSet(this, _syncState, transition.state);
|
|
2808
2765
|
if (transition.fellBackToLongPolling) {
|
|
2809
2766
|
console.warn(
|
|
2810
|
-
`[Electric] SSE connections are closing immediately (possibly due to proxy buffering or misconfiguration). Falling back to long polling. Your proxy must support streaming SSE responses (not buffer the complete response). Configuration: Nginx add 'X-Accel-Buffering: no', Caddy add 'flush_interval -1' to reverse_proxy. Note: Do NOT disable caching entirely - Electric uses cache headers to enable request collapsing for efficiency
|
|
2767
|
+
`[Electric] SSE connections are closing immediately (possibly due to proxy buffering or misconfiguration). Falling back to long polling. Your proxy must support streaming SSE responses (not buffer the complete response). Configuration: Nginx add 'X-Accel-Buffering: no', Caddy add 'flush_interval -1' to reverse_proxy. Note: Do NOT disable caching entirely - Electric uses cache headers to enable request collapsing for efficiency.`,
|
|
2768
|
+
new Error(`stack trace`)
|
|
2811
2769
|
);
|
|
2812
2770
|
} else if (transition.wasShortConnection) {
|
|
2813
2771
|
const maxDelay = Math.min(
|
|
@@ -2928,6 +2886,86 @@ reset_fn = function(handle) {
|
|
|
2928
2886
|
__privateSet(this, _connected, false);
|
|
2929
2887
|
__privateGet(this, _pauseLock).releaseAllMatching(`snapshot`);
|
|
2930
2888
|
};
|
|
2889
|
+
fetchSnapshotWithRetry_fn = async function(opts, retryCount, cacheBuster) {
|
|
2890
|
+
var _a, _b, _c;
|
|
2891
|
+
const method = (_b = (_a = opts.method) != null ? _a : this.options.subsetMethod) != null ? _b : `GET`;
|
|
2892
|
+
const usePost = method === `POST`;
|
|
2893
|
+
let fetchUrl;
|
|
2894
|
+
let fetchOptions;
|
|
2895
|
+
if (usePost) {
|
|
2896
|
+
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true);
|
|
2897
|
+
fetchUrl = result.fetchUrl;
|
|
2898
|
+
fetchOptions = {
|
|
2899
|
+
method: `POST`,
|
|
2900
|
+
headers: __spreadProps(__spreadValues({}, result.requestHeaders), {
|
|
2901
|
+
"Content-Type": `application/json`
|
|
2902
|
+
}),
|
|
2903
|
+
body: bigintSafeStringify(__privateMethod(this, _ShapeStream_instances, buildSubsetBody_fn).call(this, opts))
|
|
2904
|
+
};
|
|
2905
|
+
} else {
|
|
2906
|
+
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
|
|
2907
|
+
fetchUrl = result.fetchUrl;
|
|
2908
|
+
fetchOptions = { headers: result.requestHeaders };
|
|
2909
|
+
}
|
|
2910
|
+
if (cacheBuster) {
|
|
2911
|
+
fetchUrl.searchParams.set(CACHE_BUSTER_QUERY_PARAM, cacheBuster);
|
|
2912
|
+
fetchUrl.searchParams.sort();
|
|
2913
|
+
}
|
|
2914
|
+
const usedHandle = __privateGet(this, _syncState).handle;
|
|
2915
|
+
let response;
|
|
2916
|
+
try {
|
|
2917
|
+
response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
|
|
2918
|
+
} catch (e) {
|
|
2919
|
+
if (e instanceof FetchError && e.status === 409) {
|
|
2920
|
+
const nextRetryCount = retryCount + 1;
|
|
2921
|
+
if (nextRetryCount > __privateGet(this, _maxSnapshotRetries)) {
|
|
2922
|
+
throw new FetchError(
|
|
2923
|
+
502,
|
|
2924
|
+
void 0,
|
|
2925
|
+
void 0,
|
|
2926
|
+
{},
|
|
2927
|
+
fetchUrl.toString(),
|
|
2928
|
+
`Snapshot request stuck in 409 retry loop after ${__privateGet(this, _maxSnapshotRetries)} attempts. This indicates a proxy/CDN misconfiguration. For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`
|
|
2929
|
+
);
|
|
2930
|
+
}
|
|
2931
|
+
if (usedHandle) {
|
|
2932
|
+
const shapeKey = canonicalShapeKey(fetchUrl);
|
|
2933
|
+
expiredShapesCache.markExpired(shapeKey, usedHandle);
|
|
2934
|
+
}
|
|
2935
|
+
const nextHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
2936
|
+
let nextCacheBuster;
|
|
2937
|
+
if (nextHandle) {
|
|
2938
|
+
__privateSet(this, _syncState, __privateGet(this, _syncState).withHandle(nextHandle));
|
|
2939
|
+
if (nextHandle === usedHandle) {
|
|
2940
|
+
nextCacheBuster = createCacheBuster();
|
|
2941
|
+
}
|
|
2942
|
+
} else {
|
|
2943
|
+
console.warn(
|
|
2944
|
+
`[Electric] Received 409 response without a shape handle header. This likely indicates a proxy or CDN stripping required headers.`,
|
|
2945
|
+
new Error(`stack trace`)
|
|
2946
|
+
);
|
|
2947
|
+
nextCacheBuster = createCacheBuster();
|
|
2948
|
+
}
|
|
2949
|
+
return __privateMethod(this, _ShapeStream_instances, fetchSnapshotWithRetry_fn).call(this, opts, nextRetryCount, nextCacheBuster);
|
|
2950
|
+
}
|
|
2951
|
+
throw e;
|
|
2952
|
+
}
|
|
2953
|
+
if (!response.ok) {
|
|
2954
|
+
throw await FetchError.fromResponse(response, fetchUrl.toString());
|
|
2955
|
+
}
|
|
2956
|
+
const schema = (_c = __privateGet(this, _syncState).schema) != null ? _c : getSchemaFromHeaders(response.headers, {
|
|
2957
|
+
required: true,
|
|
2958
|
+
url: fetchUrl.toString()
|
|
2959
|
+
});
|
|
2960
|
+
const { metadata, data: rawData } = await response.json();
|
|
2961
|
+
const data = __privateGet(this, _messageParser).parseSnapshotData(
|
|
2962
|
+
rawData,
|
|
2963
|
+
schema
|
|
2964
|
+
);
|
|
2965
|
+
const responseOffset = response.headers.get(CHUNK_LAST_OFFSET_HEADER) || null;
|
|
2966
|
+
const responseHandle = response.headers.get(SHAPE_HANDLE_HEADER);
|
|
2967
|
+
return { metadata, data, responseOffset, responseHandle };
|
|
2968
|
+
};
|
|
2931
2969
|
buildSubsetBody_fn = function(opts) {
|
|
2932
2970
|
var _a, _b, _c, _d;
|
|
2933
2971
|
const body = {};
|