@electric-sql/client 1.1.1 → 1.1.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 +73 -90
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +0 -22
- package/dist/index.browser.mjs +3 -3
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +0 -22
- package/dist/index.legacy-esm.js +73 -90
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +73 -90
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +97 -17
- package/src/fetch.ts +9 -126
package/dist/index.mjs
CHANGED
|
@@ -338,13 +338,8 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
338
338
|
var HTTP_RETRY_STATUS_CODES = [429];
|
|
339
339
|
var BackoffDefaults = {
|
|
340
340
|
initialDelay: 100,
|
|
341
|
-
maxDelay:
|
|
342
|
-
|
|
343
|
-
multiplier: 1.3,
|
|
344
|
-
maxRetries: Infinity,
|
|
345
|
-
// Retry forever - clients may go offline and come back
|
|
346
|
-
retryBudgetPercent: 0.1
|
|
347
|
-
// 10% retry budget prevents amplification
|
|
341
|
+
maxDelay: 1e4,
|
|
342
|
+
multiplier: 1.3
|
|
348
343
|
};
|
|
349
344
|
function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
350
345
|
const {
|
|
@@ -352,29 +347,8 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
352
347
|
maxDelay,
|
|
353
348
|
multiplier,
|
|
354
349
|
debug = false,
|
|
355
|
-
onFailedAttempt
|
|
356
|
-
maxRetries = Infinity,
|
|
357
|
-
retryBudgetPercent = 0.1
|
|
350
|
+
onFailedAttempt
|
|
358
351
|
} = backoffOptions;
|
|
359
|
-
let totalRequests = 0;
|
|
360
|
-
let totalRetries = 0;
|
|
361
|
-
let budgetResetTime = Date.now() + 6e4;
|
|
362
|
-
function checkRetryBudget(percent) {
|
|
363
|
-
const now = Date.now();
|
|
364
|
-
if (now > budgetResetTime) {
|
|
365
|
-
totalRequests = 0;
|
|
366
|
-
totalRetries = 0;
|
|
367
|
-
budgetResetTime = now + 6e4;
|
|
368
|
-
}
|
|
369
|
-
totalRequests++;
|
|
370
|
-
if (totalRequests < 10) return true;
|
|
371
|
-
const currentRetryRate = totalRetries / totalRequests;
|
|
372
|
-
const hasCapacity = currentRetryRate < percent;
|
|
373
|
-
if (hasCapacity) {
|
|
374
|
-
totalRetries++;
|
|
375
|
-
}
|
|
376
|
-
return hasCapacity;
|
|
377
|
-
}
|
|
378
352
|
return (...args) => __async(this, null, function* () {
|
|
379
353
|
var _a;
|
|
380
354
|
const url = args[0];
|
|
@@ -384,10 +358,7 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
384
358
|
while (true) {
|
|
385
359
|
try {
|
|
386
360
|
const result = yield fetchClient(...args);
|
|
387
|
-
if (result.ok)
|
|
388
|
-
delay = initialDelay;
|
|
389
|
-
return result;
|
|
390
|
-
}
|
|
361
|
+
if (result.ok) return result;
|
|
391
362
|
const err = yield FetchError.fromResponse(result, url.toString());
|
|
392
363
|
throw err;
|
|
393
364
|
} catch (e) {
|
|
@@ -397,51 +368,12 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
397
368
|
} else if (e instanceof FetchError && !HTTP_RETRY_STATUS_CODES.includes(e.status) && e.status >= 400 && e.status < 500) {
|
|
398
369
|
throw e;
|
|
399
370
|
} else {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (debug) {
|
|
403
|
-
console.log(
|
|
404
|
-
`Max retries reached (${attempt}/${maxRetries}), giving up`
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
throw e;
|
|
408
|
-
}
|
|
409
|
-
if (!checkRetryBudget(retryBudgetPercent)) {
|
|
410
|
-
if (debug) {
|
|
411
|
-
console.log(
|
|
412
|
-
`Retry budget exhausted (attempt ${attempt}), backing off`
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
yield new Promise((resolve) => setTimeout(resolve, maxDelay));
|
|
416
|
-
continue;
|
|
417
|
-
}
|
|
418
|
-
let serverMinimumMs = 0;
|
|
419
|
-
if (e instanceof FetchError && e.headers) {
|
|
420
|
-
const retryAfter = e.headers[`retry-after`];
|
|
421
|
-
if (retryAfter) {
|
|
422
|
-
const retryAfterSec = Number(retryAfter);
|
|
423
|
-
if (Number.isFinite(retryAfterSec) && retryAfterSec > 0) {
|
|
424
|
-
serverMinimumMs = retryAfterSec * 1e3;
|
|
425
|
-
} else {
|
|
426
|
-
const retryDate = Date.parse(retryAfter);
|
|
427
|
-
if (!isNaN(retryDate)) {
|
|
428
|
-
const deltaMs = retryDate - Date.now();
|
|
429
|
-
serverMinimumMs = Math.max(0, Math.min(deltaMs, 36e5));
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const jitter = Math.random() * delay;
|
|
435
|
-
const clientBackoffMs = Math.min(jitter, maxDelay);
|
|
436
|
-
const waitMs = Math.max(serverMinimumMs, clientBackoffMs);
|
|
371
|
+
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
372
|
+
delay = Math.min(delay * multiplier, maxDelay);
|
|
437
373
|
if (debug) {
|
|
438
|
-
|
|
439
|
-
console.log(
|
|
440
|
-
`Retry attempt #${attempt} after ${waitMs}ms (${source}, serverMin=${serverMinimumMs}ms, clientBackoff=${clientBackoffMs}ms)`
|
|
441
|
-
);
|
|
374
|
+
attempt++;
|
|
375
|
+
console.log(`Retry attempt #${attempt} after ${delay}ms`);
|
|
442
376
|
}
|
|
443
|
-
yield new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
444
|
-
delay = Math.min(delay * multiplier, maxDelay);
|
|
445
377
|
}
|
|
446
378
|
}
|
|
447
379
|
}
|
|
@@ -814,8 +746,9 @@ function canonicalShapeKey(url) {
|
|
|
814
746
|
cleanUrl.searchParams.sort();
|
|
815
747
|
return cleanUrl.toString();
|
|
816
748
|
}
|
|
817
|
-
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, _ShapeStream_instances, 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, fetchSnapshot_fn;
|
|
749
|
+
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, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _ShapeStream_instances, 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, fetchSnapshot_fn;
|
|
818
750
|
var ShapeStream = class {
|
|
751
|
+
// Maximum delay cap (ms)
|
|
819
752
|
constructor(options) {
|
|
820
753
|
__privateAdd(this, _ShapeStream_instances);
|
|
821
754
|
__privateAdd(this, _error, null);
|
|
@@ -849,6 +782,16 @@ var ShapeStream = class {
|
|
|
849
782
|
// counter for concurrent snapshot requests
|
|
850
783
|
__privateAdd(this, _midStreamPromise);
|
|
851
784
|
__privateAdd(this, _midStreamPromiseResolver);
|
|
785
|
+
__privateAdd(this, _lastSseConnectionStartTime);
|
|
786
|
+
__privateAdd(this, _minSseConnectionDuration, 1e3);
|
|
787
|
+
// Minimum expected SSE connection duration (1 second)
|
|
788
|
+
__privateAdd(this, _consecutiveShortSseConnections, 0);
|
|
789
|
+
__privateAdd(this, _maxShortSseConnections, 3);
|
|
790
|
+
// Fall back to long polling after this many short connections
|
|
791
|
+
__privateAdd(this, _sseFallbackToLongPolling, false);
|
|
792
|
+
__privateAdd(this, _sseBackoffBaseDelay, 100);
|
|
793
|
+
// Base delay for exponential backoff (ms)
|
|
794
|
+
__privateAdd(this, _sseBackoffMaxDelay, 5e3);
|
|
852
795
|
var _a, _b, _c, _d;
|
|
853
796
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
854
797
|
validateOptions(this.options);
|
|
@@ -1024,10 +967,17 @@ _snapshotTracker = new WeakMap();
|
|
|
1024
967
|
_activeSnapshotRequests = new WeakMap();
|
|
1025
968
|
_midStreamPromise = new WeakMap();
|
|
1026
969
|
_midStreamPromiseResolver = new WeakMap();
|
|
970
|
+
_lastSseConnectionStartTime = new WeakMap();
|
|
971
|
+
_minSseConnectionDuration = new WeakMap();
|
|
972
|
+
_consecutiveShortSseConnections = new WeakMap();
|
|
973
|
+
_maxShortSseConnections = new WeakMap();
|
|
974
|
+
_sseFallbackToLongPolling = new WeakMap();
|
|
975
|
+
_sseBackoffBaseDelay = new WeakMap();
|
|
976
|
+
_sseBackoffMaxDelay = new WeakMap();
|
|
1027
977
|
_ShapeStream_instances = new WeakSet();
|
|
1028
978
|
start_fn = function() {
|
|
1029
979
|
return __async(this, null, function* () {
|
|
1030
|
-
var _a;
|
|
980
|
+
var _a, _b, _c, _d, _e;
|
|
1031
981
|
__privateSet(this, _started, true);
|
|
1032
982
|
try {
|
|
1033
983
|
yield __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
@@ -1035,24 +985,34 @@ start_fn = function() {
|
|
|
1035
985
|
__privateSet(this, _error, err);
|
|
1036
986
|
if (__privateGet(this, _onError)) {
|
|
1037
987
|
const retryOpts = yield __privateGet(this, _onError).call(this, err);
|
|
1038
|
-
if (typeof retryOpts === `object`) {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
this.options.params = retryOpts.params;
|
|
988
|
+
if (retryOpts && typeof retryOpts === `object`) {
|
|
989
|
+
if (retryOpts.params) {
|
|
990
|
+
this.options.params = __spreadValues(__spreadValues({}, (_a = this.options.params) != null ? _a : {}), retryOpts.params);
|
|
1042
991
|
}
|
|
1043
|
-
if (
|
|
1044
|
-
this.options.headers = retryOpts.headers;
|
|
992
|
+
if (retryOpts.headers) {
|
|
993
|
+
this.options.headers = __spreadValues(__spreadValues({}, (_b = this.options.headers) != null ? _b : {}), retryOpts.headers);
|
|
1045
994
|
}
|
|
995
|
+
__privateSet(this, _error, null);
|
|
1046
996
|
__privateSet(this, _started, false);
|
|
1047
|
-
__privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
997
|
+
yield __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (err instanceof Error) {
|
|
1001
|
+
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
|
|
1048
1002
|
}
|
|
1003
|
+
__privateSet(this, _connected, false);
|
|
1004
|
+
(_c = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _c.call(this);
|
|
1049
1005
|
return;
|
|
1050
1006
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1007
|
+
if (err instanceof Error) {
|
|
1008
|
+
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
|
|
1009
|
+
}
|
|
1053
1010
|
__privateSet(this, _connected, false);
|
|
1054
|
-
(
|
|
1011
|
+
(_d = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _d.call(this);
|
|
1012
|
+
throw err;
|
|
1055
1013
|
}
|
|
1014
|
+
__privateSet(this, _connected, false);
|
|
1015
|
+
(_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
|
|
1056
1016
|
});
|
|
1057
1017
|
};
|
|
1058
1018
|
requestShape_fn = function() {
|
|
@@ -1099,7 +1059,6 @@ requestShape_fn = function() {
|
|
|
1099
1059
|
yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, Array.isArray(e.json) ? e.json : [e.json]);
|
|
1100
1060
|
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
1101
1061
|
} else {
|
|
1102
|
-
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, e);
|
|
1103
1062
|
throw e;
|
|
1104
1063
|
}
|
|
1105
1064
|
} finally {
|
|
@@ -1251,7 +1210,7 @@ fetchShape_fn = function(opts) {
|
|
|
1251
1210
|
return __async(this, null, function* () {
|
|
1252
1211
|
var _a;
|
|
1253
1212
|
const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
|
|
1254
|
-
if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause) {
|
|
1213
|
+
if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
|
|
1255
1214
|
opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
|
|
1256
1215
|
opts.fetchUrl.searchParams.set(LIVE_SSE_QUERY_PARAM, `true`);
|
|
1257
1216
|
return __privateMethod(this, _ShapeStream_instances, requestShapeSSE_fn).call(this, opts);
|
|
@@ -1279,6 +1238,7 @@ requestShapeSSE_fn = function(opts) {
|
|
|
1279
1238
|
return __async(this, null, function* () {
|
|
1280
1239
|
const { fetchUrl, requestAbortController, headers } = opts;
|
|
1281
1240
|
const fetch2 = __privateGet(this, _sseFetchClient);
|
|
1241
|
+
__privateSet(this, _lastSseConnectionStartTime, Date.now());
|
|
1282
1242
|
try {
|
|
1283
1243
|
let buffer = [];
|
|
1284
1244
|
yield fetchEventSource(fetchUrl.toString(), {
|
|
@@ -1312,6 +1272,27 @@ requestShapeSSE_fn = function(opts) {
|
|
|
1312
1272
|
throw new FetchBackoffAbortError();
|
|
1313
1273
|
}
|
|
1314
1274
|
throw error;
|
|
1275
|
+
} finally {
|
|
1276
|
+
const connectionDuration = Date.now() - __privateGet(this, _lastSseConnectionStartTime);
|
|
1277
|
+
const wasAborted = requestAbortController.signal.aborted;
|
|
1278
|
+
if (connectionDuration < __privateGet(this, _minSseConnectionDuration) && !wasAborted) {
|
|
1279
|
+
__privateWrapper(this, _consecutiveShortSseConnections)._++;
|
|
1280
|
+
if (__privateGet(this, _consecutiveShortSseConnections) >= __privateGet(this, _maxShortSseConnections)) {
|
|
1281
|
+
__privateSet(this, _sseFallbackToLongPolling, true);
|
|
1282
|
+
console.warn(
|
|
1283
|
+
`[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.`
|
|
1284
|
+
);
|
|
1285
|
+
} else {
|
|
1286
|
+
const maxDelay = Math.min(
|
|
1287
|
+
__privateGet(this, _sseBackoffMaxDelay),
|
|
1288
|
+
__privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _consecutiveShortSseConnections))
|
|
1289
|
+
);
|
|
1290
|
+
const delayMs = Math.floor(Math.random() * maxDelay);
|
|
1291
|
+
yield new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1292
|
+
}
|
|
1293
|
+
} else if (connectionDuration >= __privateGet(this, _minSseConnectionDuration)) {
|
|
1294
|
+
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1295
|
+
}
|
|
1315
1296
|
}
|
|
1316
1297
|
});
|
|
1317
1298
|
};
|
|
@@ -1410,6 +1391,8 @@ reset_fn = function(handle) {
|
|
|
1410
1391
|
__privateSet(this, _connected, false);
|
|
1411
1392
|
__privateSet(this, _schema, void 0);
|
|
1412
1393
|
__privateSet(this, _activeSnapshotRequests, 0);
|
|
1394
|
+
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1395
|
+
__privateSet(this, _sseFallbackToLongPolling, false);
|
|
1413
1396
|
};
|
|
1414
1397
|
fetchSnapshot_fn = function(url, headers) {
|
|
1415
1398
|
return __async(this, null, function* () {
|