@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/cjs/index.cjs
CHANGED
|
@@ -678,11 +678,11 @@ function createFetchWithChunkBuffer(fetchClient, prefetchOptions = ChunkPrefetch
|
|
|
678
678
|
return prefetchClient;
|
|
679
679
|
}
|
|
680
680
|
var requiredElectricResponseHeaders = [
|
|
681
|
-
|
|
682
|
-
|
|
681
|
+
CHUNK_LAST_OFFSET_HEADER,
|
|
682
|
+
SHAPE_HANDLE_HEADER
|
|
683
683
|
];
|
|
684
|
-
var requiredLiveResponseHeaders = [
|
|
685
|
-
var requiredNonLiveResponseHeaders = [
|
|
684
|
+
var requiredLiveResponseHeaders = [LIVE_CACHE_BUSTER_HEADER];
|
|
685
|
+
var requiredNonLiveResponseHeaders = [SHAPE_SCHEMA_HEADER];
|
|
686
686
|
function createFetchWithResponseHeadersCheck(fetchClient) {
|
|
687
687
|
return async (...args) => {
|
|
688
688
|
const response = await fetchClient(...args);
|
|
@@ -1473,18 +1473,15 @@ var ActiveState = class extends ShapeStreamState {
|
|
|
1473
1473
|
if (!responseHandle || responseHandle !== expiredHandle) {
|
|
1474
1474
|
return null;
|
|
1475
1475
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
};
|
|
1486
|
-
}
|
|
1487
|
-
return { action: `ignored`, state: this };
|
|
1476
|
+
const retryCount = this.staleCacheRetryCount + 1;
|
|
1477
|
+
return {
|
|
1478
|
+
action: `stale-retry`,
|
|
1479
|
+
state: new StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
|
|
1480
|
+
staleCacheBuster: input.createCacheBuster(),
|
|
1481
|
+
staleCacheRetryCount: retryCount
|
|
1482
|
+
})),
|
|
1483
|
+
exceededMaxRetries: retryCount > input.maxStaleCacheRetries
|
|
1484
|
+
};
|
|
1488
1485
|
}
|
|
1489
1486
|
// --- handleMessageBatch: template method with onUpToDate override point ---
|
|
1490
1487
|
handleMessageBatch(input) {
|
|
@@ -1983,7 +1980,7 @@ function canonicalShapeKey(url) {
|
|
|
1983
1980
|
cleanUrl.searchParams.sort();
|
|
1984
1981
|
return cleanUrl.toString();
|
|
1985
1982
|
}
|
|
1986
|
-
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,
|
|
1983
|
+
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;
|
|
1987
1984
|
var ShapeStream = class {
|
|
1988
1985
|
constructor(options) {
|
|
1989
1986
|
__privateAdd(this, _ShapeStream_instances);
|
|
@@ -2030,7 +2027,8 @@ var ShapeStream = class {
|
|
|
2030
2027
|
__privateAdd(this, _fastLoopBackoffMaxMs, 5e3);
|
|
2031
2028
|
__privateAdd(this, _fastLoopConsecutiveCount, 0);
|
|
2032
2029
|
__privateAdd(this, _fastLoopMaxCount, 5);
|
|
2033
|
-
__privateAdd(this,
|
|
2030
|
+
__privateAdd(this, _pendingRequestShapeCacheBuster);
|
|
2031
|
+
__privateAdd(this, _maxSnapshotRetries, 5);
|
|
2034
2032
|
var _a, _b, _c, _d;
|
|
2035
2033
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
2036
2034
|
validateOptions(this.options);
|
|
@@ -2188,7 +2186,8 @@ var ShapeStream = class {
|
|
|
2188
2186
|
__privateGet(this, _pauseLock).acquire(snapshotReason);
|
|
2189
2187
|
const snapshotWarnTimer = setTimeout(() => {
|
|
2190
2188
|
console.warn(
|
|
2191
|
-
`[Electric] Snapshot "${snapshotReason}" has held the pause lock for 30s \u2014 possible hung request or leaked lock. Current holders: ${[.../* @__PURE__ */ new Set([snapshotReason])].join(`, `)}
|
|
2189
|
+
`[Electric] Snapshot "${snapshotReason}" has held the pause lock for 30s \u2014 possible hung request or leaked lock. Current holders: ${[.../* @__PURE__ */ new Set([snapshotReason])].join(`, `)}`,
|
|
2190
|
+
new Error(`stack trace`)
|
|
2192
2191
|
);
|
|
2193
2192
|
}, 3e4);
|
|
2194
2193
|
try {
|
|
@@ -2217,7 +2216,8 @@ var ShapeStream = class {
|
|
|
2217
2216
|
__privateSet(this, _syncState, transition.state);
|
|
2218
2217
|
} else {
|
|
2219
2218
|
console.warn(
|
|
2220
|
-
`[Electric] Snapshot response metadata was not accepted by state "${__privateGet(this, _syncState).kind}" (action: ${transition.action}). Stream offset was not advanced from snapshot
|
|
2219
|
+
`[Electric] Snapshot response metadata was not accepted by state "${__privateGet(this, _syncState).kind}" (action: ${transition.action}). Stream offset was not advanced from snapshot.`,
|
|
2220
|
+
new Error(`stack trace`)
|
|
2221
2221
|
);
|
|
2222
2222
|
}
|
|
2223
2223
|
}
|
|
@@ -2242,64 +2242,7 @@ var ShapeStream = class {
|
|
|
2242
2242
|
* @returns The metadata, data, and the response's offset/handle for state advancement.
|
|
2243
2243
|
*/
|
|
2244
2244
|
async fetchSnapshot(opts) {
|
|
2245
|
-
|
|
2246
|
-
const method = (_b = (_a = opts.method) != null ? _a : this.options.subsetMethod) != null ? _b : `GET`;
|
|
2247
|
-
const usePost = method === `POST`;
|
|
2248
|
-
let fetchUrl;
|
|
2249
|
-
let fetchOptions;
|
|
2250
|
-
if (usePost) {
|
|
2251
|
-
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true);
|
|
2252
|
-
fetchUrl = result.fetchUrl;
|
|
2253
|
-
fetchOptions = {
|
|
2254
|
-
method: `POST`,
|
|
2255
|
-
headers: __spreadProps(__spreadValues({}, result.requestHeaders), {
|
|
2256
|
-
"Content-Type": `application/json`
|
|
2257
|
-
}),
|
|
2258
|
-
body: bigintSafeStringify(__privateMethod(this, _ShapeStream_instances, buildSubsetBody_fn).call(this, opts))
|
|
2259
|
-
};
|
|
2260
|
-
} else {
|
|
2261
|
-
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
|
|
2262
|
-
fetchUrl = result.fetchUrl;
|
|
2263
|
-
fetchOptions = { headers: result.requestHeaders };
|
|
2264
|
-
}
|
|
2265
|
-
const usedHandle = __privateGet(this, _syncState).handle;
|
|
2266
|
-
let response;
|
|
2267
|
-
try {
|
|
2268
|
-
response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
|
|
2269
|
-
} catch (e) {
|
|
2270
|
-
if (e instanceof FetchError && e.status === 409) {
|
|
2271
|
-
if (usedHandle) {
|
|
2272
|
-
const shapeKey = canonicalShapeKey(fetchUrl);
|
|
2273
|
-
expiredShapesCache.markExpired(shapeKey, usedHandle);
|
|
2274
|
-
}
|
|
2275
|
-
const nextHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
2276
|
-
if (nextHandle) {
|
|
2277
|
-
__privateSet(this, _syncState, __privateGet(this, _syncState).withHandle(nextHandle));
|
|
2278
|
-
} else {
|
|
2279
|
-
console.warn(
|
|
2280
|
-
`[Electric] Received 409 response without a shape handle header. This likely indicates a proxy or CDN stripping required headers.`
|
|
2281
|
-
);
|
|
2282
|
-
__privateSet(this, _refetchCacheBuster, createCacheBuster());
|
|
2283
|
-
}
|
|
2284
|
-
return this.fetchSnapshot(opts);
|
|
2285
|
-
}
|
|
2286
|
-
throw e;
|
|
2287
|
-
}
|
|
2288
|
-
if (!response.ok) {
|
|
2289
|
-
throw await FetchError.fromResponse(response, fetchUrl.toString());
|
|
2290
|
-
}
|
|
2291
|
-
const schema = (_c = __privateGet(this, _syncState).schema) != null ? _c : getSchemaFromHeaders(response.headers, {
|
|
2292
|
-
required: true,
|
|
2293
|
-
url: fetchUrl.toString()
|
|
2294
|
-
});
|
|
2295
|
-
const { metadata, data: rawData } = await response.json();
|
|
2296
|
-
const data = __privateGet(this, _messageParser).parseSnapshotData(
|
|
2297
|
-
rawData,
|
|
2298
|
-
schema
|
|
2299
|
-
);
|
|
2300
|
-
const responseOffset = response.headers.get(CHUNK_LAST_OFFSET_HEADER) || null;
|
|
2301
|
-
const responseHandle = response.headers.get(SHAPE_HANDLE_HEADER);
|
|
2302
|
-
return { metadata, data, responseOffset, responseHandle };
|
|
2245
|
+
return __privateMethod(this, _ShapeStream_instances, fetchSnapshotWithRetry_fn).call(this, opts, 0);
|
|
2303
2246
|
}
|
|
2304
2247
|
};
|
|
2305
2248
|
_error = new WeakMap();
|
|
@@ -2341,7 +2284,8 @@ _fastLoopBackoffBaseMs = new WeakMap();
|
|
|
2341
2284
|
_fastLoopBackoffMaxMs = new WeakMap();
|
|
2342
2285
|
_fastLoopConsecutiveCount = new WeakMap();
|
|
2343
2286
|
_fastLoopMaxCount = new WeakMap();
|
|
2344
|
-
|
|
2287
|
+
_pendingRequestShapeCacheBuster = new WeakMap();
|
|
2288
|
+
_maxSnapshotRetries = new WeakMap();
|
|
2345
2289
|
start_fn = async function() {
|
|
2346
2290
|
var _a, _b;
|
|
2347
2291
|
__privateSet(this, _started, true);
|
|
@@ -2393,9 +2337,15 @@ teardown_fn = function() {
|
|
|
2393
2337
|
(_a = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _a.call(this);
|
|
2394
2338
|
(_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
|
|
2395
2339
|
};
|
|
2396
|
-
requestShape_fn = async function() {
|
|
2340
|
+
requestShape_fn = async function(requestShapeCacheBuster) {
|
|
2397
2341
|
var _a, _b;
|
|
2398
|
-
|
|
2342
|
+
const activeCacheBuster = requestShapeCacheBuster != null ? requestShapeCacheBuster : __privateGet(this, _pendingRequestShapeCacheBuster);
|
|
2343
|
+
if (__privateGet(this, _pauseLock).isPaused) {
|
|
2344
|
+
if (activeCacheBuster) {
|
|
2345
|
+
__privateSet(this, _pendingRequestShapeCacheBuster, activeCacheBuster);
|
|
2346
|
+
}
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2399
2349
|
if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _syncState).isUpToDate)) {
|
|
2400
2350
|
return;
|
|
2401
2351
|
}
|
|
@@ -2412,15 +2362,23 @@ requestShape_fn = async function() {
|
|
|
2412
2362
|
}
|
|
2413
2363
|
const { url, signal } = this.options;
|
|
2414
2364
|
const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, url, resumingFromPause);
|
|
2365
|
+
if (activeCacheBuster) {
|
|
2366
|
+
fetchUrl.searchParams.set(CACHE_BUSTER_QUERY_PARAM, activeCacheBuster);
|
|
2367
|
+
fetchUrl.searchParams.sort();
|
|
2368
|
+
}
|
|
2415
2369
|
const abortListener = await __privateMethod(this, _ShapeStream_instances, createAbortListener_fn).call(this, signal);
|
|
2416
2370
|
const requestAbortController = __privateGet(this, _requestAbortController);
|
|
2417
2371
|
if (__privateGet(this, _pauseLock).isPaused) {
|
|
2418
2372
|
if (abortListener && signal) {
|
|
2419
2373
|
signal.removeEventListener(`abort`, abortListener);
|
|
2420
2374
|
}
|
|
2375
|
+
if (activeCacheBuster) {
|
|
2376
|
+
__privateSet(this, _pendingRequestShapeCacheBuster, activeCacheBuster);
|
|
2377
|
+
}
|
|
2421
2378
|
__privateSet(this, _requestAbortController, void 0);
|
|
2422
2379
|
return;
|
|
2423
2380
|
}
|
|
2381
|
+
__privateSet(this, _pendingRequestShapeCacheBuster, void 0);
|
|
2424
2382
|
try {
|
|
2425
2383
|
await __privateMethod(this, _ShapeStream_instances, fetchShape_fn).call(this, {
|
|
2426
2384
|
fetchUrl,
|
|
@@ -2447,16 +2405,18 @@ requestShape_fn = async function() {
|
|
|
2447
2405
|
expiredShapesCache.markExpired(shapeKey, __privateGet(this, _syncState).handle);
|
|
2448
2406
|
}
|
|
2449
2407
|
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
2408
|
+
let nextRequestShapeCacheBuster;
|
|
2450
2409
|
if (!newShapeHandle) {
|
|
2451
2410
|
console.warn(
|
|
2452
|
-
`[Electric] Received 409 response without a shape handle header. This likely indicates a proxy or CDN stripping required headers
|
|
2411
|
+
`[Electric] Received 409 response without a shape handle header. This likely indicates a proxy or CDN stripping required headers.`,
|
|
2412
|
+
new Error(`stack trace`)
|
|
2453
2413
|
);
|
|
2454
|
-
|
|
2414
|
+
nextRequestShapeCacheBuster = createCacheBuster();
|
|
2455
2415
|
}
|
|
2456
2416
|
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
|
|
2457
2417
|
const messages409 = Array.isArray(e.json) ? e.json : e.json != null ? [e.json] : [];
|
|
2458
2418
|
await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messages409);
|
|
2459
|
-
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
2419
|
+
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this, nextRequestShapeCacheBuster);
|
|
2460
2420
|
} else {
|
|
2461
2421
|
throw e;
|
|
2462
2422
|
}
|
|
@@ -2497,7 +2457,8 @@ For more information visit the troubleshooting guide: ${TROUBLESHOOTING_URL}`
|
|
|
2497
2457
|
}
|
|
2498
2458
|
if (__privateGet(this, _fastLoopConsecutiveCount) === 1) {
|
|
2499
2459
|
console.warn(
|
|
2500
|
-
`[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}
|
|
2460
|
+
`[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}`,
|
|
2461
|
+
new Error(`stack trace`)
|
|
2501
2462
|
);
|
|
2502
2463
|
if (__privateGet(this, _currentFetchUrl)) {
|
|
2503
2464
|
const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
|
|
@@ -2621,13 +2582,6 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
|
|
|
2621
2582
|
if (expiredHandle) {
|
|
2622
2583
|
fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
|
|
2623
2584
|
}
|
|
2624
|
-
if (__privateGet(this, _refetchCacheBuster)) {
|
|
2625
|
-
fetchUrl.searchParams.set(
|
|
2626
|
-
CACHE_BUSTER_QUERY_PARAM,
|
|
2627
|
-
__privateGet(this, _refetchCacheBuster)
|
|
2628
|
-
);
|
|
2629
|
-
__privateSet(this, _refetchCacheBuster, void 0);
|
|
2630
|
-
}
|
|
2631
2585
|
fetchUrl.searchParams.sort();
|
|
2632
2586
|
return {
|
|
2633
2587
|
fetchUrl,
|
|
@@ -2680,7 +2634,8 @@ onInitialResponse_fn = async function(response) {
|
|
|
2680
2634
|
);
|
|
2681
2635
|
}
|
|
2682
2636
|
console.warn(
|
|
2683
|
-
`[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)})
|
|
2637
|
+
`[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)}).`,
|
|
2638
|
+
new Error(`stack trace`)
|
|
2684
2639
|
);
|
|
2685
2640
|
throw new StaleCacheError(
|
|
2686
2641
|
`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.`
|
|
@@ -2688,7 +2643,8 @@ onInitialResponse_fn = async function(response) {
|
|
|
2688
2643
|
}
|
|
2689
2644
|
if (transition.action === `ignored`) {
|
|
2690
2645
|
console.warn(
|
|
2691
|
-
`[Electric]
|
|
2646
|
+
`[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.`,
|
|
2647
|
+
new Error(`stack trace`)
|
|
2692
2648
|
);
|
|
2693
2649
|
return false;
|
|
2694
2650
|
}
|
|
@@ -2697,7 +2653,8 @@ onInitialResponse_fn = async function(response) {
|
|
|
2697
2653
|
onMessages_fn = async function(batch, isSseMessage = false) {
|
|
2698
2654
|
if (!Array.isArray(batch)) {
|
|
2699
2655
|
console.warn(
|
|
2700
|
-
`[Electric] #onMessages called with non-array argument (${typeof batch}). This is a client bug \u2014 please report it
|
|
2656
|
+
`[Electric] #onMessages called with non-array argument (${typeof batch}). This is a client bug \u2014 please report it.`,
|
|
2657
|
+
new Error(`stack trace`)
|
|
2701
2658
|
);
|
|
2702
2659
|
return;
|
|
2703
2660
|
}
|
|
@@ -2844,7 +2801,8 @@ requestShapeSSE_fn = async function(opts) {
|
|
|
2844
2801
|
__privateSet(this, _syncState, transition.state);
|
|
2845
2802
|
if (transition.fellBackToLongPolling) {
|
|
2846
2803
|
console.warn(
|
|
2847
|
-
`[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
|
|
2804
|
+
`[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.`,
|
|
2805
|
+
new Error(`stack trace`)
|
|
2848
2806
|
);
|
|
2849
2807
|
} else if (transition.wasShortConnection) {
|
|
2850
2808
|
const maxDelay = Math.min(
|
|
@@ -2965,6 +2923,86 @@ reset_fn = function(handle) {
|
|
|
2965
2923
|
__privateSet(this, _connected, false);
|
|
2966
2924
|
__privateGet(this, _pauseLock).releaseAllMatching(`snapshot`);
|
|
2967
2925
|
};
|
|
2926
|
+
fetchSnapshotWithRetry_fn = async function(opts, retryCount, cacheBuster) {
|
|
2927
|
+
var _a, _b, _c;
|
|
2928
|
+
const method = (_b = (_a = opts.method) != null ? _a : this.options.subsetMethod) != null ? _b : `GET`;
|
|
2929
|
+
const usePost = method === `POST`;
|
|
2930
|
+
let fetchUrl;
|
|
2931
|
+
let fetchOptions;
|
|
2932
|
+
if (usePost) {
|
|
2933
|
+
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true);
|
|
2934
|
+
fetchUrl = result.fetchUrl;
|
|
2935
|
+
fetchOptions = {
|
|
2936
|
+
method: `POST`,
|
|
2937
|
+
headers: __spreadProps(__spreadValues({}, result.requestHeaders), {
|
|
2938
|
+
"Content-Type": `application/json`
|
|
2939
|
+
}),
|
|
2940
|
+
body: bigintSafeStringify(__privateMethod(this, _ShapeStream_instances, buildSubsetBody_fn).call(this, opts))
|
|
2941
|
+
};
|
|
2942
|
+
} else {
|
|
2943
|
+
const result = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, this.options.url, true, opts);
|
|
2944
|
+
fetchUrl = result.fetchUrl;
|
|
2945
|
+
fetchOptions = { headers: result.requestHeaders };
|
|
2946
|
+
}
|
|
2947
|
+
if (cacheBuster) {
|
|
2948
|
+
fetchUrl.searchParams.set(CACHE_BUSTER_QUERY_PARAM, cacheBuster);
|
|
2949
|
+
fetchUrl.searchParams.sort();
|
|
2950
|
+
}
|
|
2951
|
+
const usedHandle = __privateGet(this, _syncState).handle;
|
|
2952
|
+
let response;
|
|
2953
|
+
try {
|
|
2954
|
+
response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
|
|
2955
|
+
} catch (e) {
|
|
2956
|
+
if (e instanceof FetchError && e.status === 409) {
|
|
2957
|
+
const nextRetryCount = retryCount + 1;
|
|
2958
|
+
if (nextRetryCount > __privateGet(this, _maxSnapshotRetries)) {
|
|
2959
|
+
throw new FetchError(
|
|
2960
|
+
502,
|
|
2961
|
+
void 0,
|
|
2962
|
+
void 0,
|
|
2963
|
+
{},
|
|
2964
|
+
fetchUrl.toString(),
|
|
2965
|
+
`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}`
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2968
|
+
if (usedHandle) {
|
|
2969
|
+
const shapeKey = canonicalShapeKey(fetchUrl);
|
|
2970
|
+
expiredShapesCache.markExpired(shapeKey, usedHandle);
|
|
2971
|
+
}
|
|
2972
|
+
const nextHandle = e.headers[SHAPE_HANDLE_HEADER];
|
|
2973
|
+
let nextCacheBuster;
|
|
2974
|
+
if (nextHandle) {
|
|
2975
|
+
__privateSet(this, _syncState, __privateGet(this, _syncState).withHandle(nextHandle));
|
|
2976
|
+
if (nextHandle === usedHandle) {
|
|
2977
|
+
nextCacheBuster = createCacheBuster();
|
|
2978
|
+
}
|
|
2979
|
+
} else {
|
|
2980
|
+
console.warn(
|
|
2981
|
+
`[Electric] Received 409 response without a shape handle header. This likely indicates a proxy or CDN stripping required headers.`,
|
|
2982
|
+
new Error(`stack trace`)
|
|
2983
|
+
);
|
|
2984
|
+
nextCacheBuster = createCacheBuster();
|
|
2985
|
+
}
|
|
2986
|
+
return __privateMethod(this, _ShapeStream_instances, fetchSnapshotWithRetry_fn).call(this, opts, nextRetryCount, nextCacheBuster);
|
|
2987
|
+
}
|
|
2988
|
+
throw e;
|
|
2989
|
+
}
|
|
2990
|
+
if (!response.ok) {
|
|
2991
|
+
throw await FetchError.fromResponse(response, fetchUrl.toString());
|
|
2992
|
+
}
|
|
2993
|
+
const schema = (_c = __privateGet(this, _syncState).schema) != null ? _c : getSchemaFromHeaders(response.headers, {
|
|
2994
|
+
required: true,
|
|
2995
|
+
url: fetchUrl.toString()
|
|
2996
|
+
});
|
|
2997
|
+
const { metadata, data: rawData } = await response.json();
|
|
2998
|
+
const data = __privateGet(this, _messageParser).parseSnapshotData(
|
|
2999
|
+
rawData,
|
|
3000
|
+
schema
|
|
3001
|
+
);
|
|
3002
|
+
const responseOffset = response.headers.get(CHUNK_LAST_OFFSET_HEADER) || null;
|
|
3003
|
+
const responseHandle = response.headers.get(SHAPE_HANDLE_HEADER);
|
|
3004
|
+
return { metadata, data, responseOffset, responseHandle };
|
|
3005
|
+
};
|
|
2968
3006
|
buildSubsetBody_fn = function(opts) {
|
|
2969
3007
|
var _a, _b, _c, _d;
|
|
2970
3008
|
const body = {};
|