@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/cjs/index.cjs
CHANGED
|
@@ -369,13 +369,8 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
369
369
|
var HTTP_RETRY_STATUS_CODES = [429];
|
|
370
370
|
var BackoffDefaults = {
|
|
371
371
|
initialDelay: 100,
|
|
372
|
-
maxDelay:
|
|
373
|
-
|
|
374
|
-
multiplier: 1.3,
|
|
375
|
-
maxRetries: Infinity,
|
|
376
|
-
// Retry forever - clients may go offline and come back
|
|
377
|
-
retryBudgetPercent: 0.1
|
|
378
|
-
// 10% retry budget prevents amplification
|
|
372
|
+
maxDelay: 1e4,
|
|
373
|
+
multiplier: 1.3
|
|
379
374
|
};
|
|
380
375
|
function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
381
376
|
const {
|
|
@@ -383,29 +378,8 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
383
378
|
maxDelay,
|
|
384
379
|
multiplier,
|
|
385
380
|
debug = false,
|
|
386
|
-
onFailedAttempt
|
|
387
|
-
maxRetries = Infinity,
|
|
388
|
-
retryBudgetPercent = 0.1
|
|
381
|
+
onFailedAttempt
|
|
389
382
|
} = backoffOptions;
|
|
390
|
-
let totalRequests = 0;
|
|
391
|
-
let totalRetries = 0;
|
|
392
|
-
let budgetResetTime = Date.now() + 6e4;
|
|
393
|
-
function checkRetryBudget(percent) {
|
|
394
|
-
const now = Date.now();
|
|
395
|
-
if (now > budgetResetTime) {
|
|
396
|
-
totalRequests = 0;
|
|
397
|
-
totalRetries = 0;
|
|
398
|
-
budgetResetTime = now + 6e4;
|
|
399
|
-
}
|
|
400
|
-
totalRequests++;
|
|
401
|
-
if (totalRequests < 10) return true;
|
|
402
|
-
const currentRetryRate = totalRetries / totalRequests;
|
|
403
|
-
const hasCapacity = currentRetryRate < percent;
|
|
404
|
-
if (hasCapacity) {
|
|
405
|
-
totalRetries++;
|
|
406
|
-
}
|
|
407
|
-
return hasCapacity;
|
|
408
|
-
}
|
|
409
383
|
return (...args) => __async(this, null, function* () {
|
|
410
384
|
var _a;
|
|
411
385
|
const url = args[0];
|
|
@@ -415,10 +389,7 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
415
389
|
while (true) {
|
|
416
390
|
try {
|
|
417
391
|
const result = yield fetchClient(...args);
|
|
418
|
-
if (result.ok)
|
|
419
|
-
delay = initialDelay;
|
|
420
|
-
return result;
|
|
421
|
-
}
|
|
392
|
+
if (result.ok) return result;
|
|
422
393
|
const err = yield FetchError.fromResponse(result, url.toString());
|
|
423
394
|
throw err;
|
|
424
395
|
} catch (e) {
|
|
@@ -428,51 +399,12 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
428
399
|
} else if (e instanceof FetchError && !HTTP_RETRY_STATUS_CODES.includes(e.status) && e.status >= 400 && e.status < 500) {
|
|
429
400
|
throw e;
|
|
430
401
|
} else {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (debug) {
|
|
434
|
-
console.log(
|
|
435
|
-
`Max retries reached (${attempt}/${maxRetries}), giving up`
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
throw e;
|
|
439
|
-
}
|
|
440
|
-
if (!checkRetryBudget(retryBudgetPercent)) {
|
|
441
|
-
if (debug) {
|
|
442
|
-
console.log(
|
|
443
|
-
`Retry budget exhausted (attempt ${attempt}), backing off`
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
yield new Promise((resolve) => setTimeout(resolve, maxDelay));
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
let serverMinimumMs = 0;
|
|
450
|
-
if (e instanceof FetchError && e.headers) {
|
|
451
|
-
const retryAfter = e.headers[`retry-after`];
|
|
452
|
-
if (retryAfter) {
|
|
453
|
-
const retryAfterSec = Number(retryAfter);
|
|
454
|
-
if (Number.isFinite(retryAfterSec) && retryAfterSec > 0) {
|
|
455
|
-
serverMinimumMs = retryAfterSec * 1e3;
|
|
456
|
-
} else {
|
|
457
|
-
const retryDate = Date.parse(retryAfter);
|
|
458
|
-
if (!isNaN(retryDate)) {
|
|
459
|
-
const deltaMs = retryDate - Date.now();
|
|
460
|
-
serverMinimumMs = Math.max(0, Math.min(deltaMs, 36e5));
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
const jitter = Math.random() * delay;
|
|
466
|
-
const clientBackoffMs = Math.min(jitter, maxDelay);
|
|
467
|
-
const waitMs = Math.max(serverMinimumMs, clientBackoffMs);
|
|
402
|
+
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
403
|
+
delay = Math.min(delay * multiplier, maxDelay);
|
|
468
404
|
if (debug) {
|
|
469
|
-
|
|
470
|
-
console.log(
|
|
471
|
-
`Retry attempt #${attempt} after ${waitMs}ms (${source}, serverMin=${serverMinimumMs}ms, clientBackoff=${clientBackoffMs}ms)`
|
|
472
|
-
);
|
|
405
|
+
attempt++;
|
|
406
|
+
console.log(`Retry attempt #${attempt} after ${delay}ms`);
|
|
473
407
|
}
|
|
474
|
-
yield new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
475
|
-
delay = Math.min(delay * multiplier, maxDelay);
|
|
476
408
|
}
|
|
477
409
|
}
|
|
478
410
|
}
|
|
@@ -843,8 +775,9 @@ function canonicalShapeKey(url) {
|
|
|
843
775
|
cleanUrl.searchParams.sort();
|
|
844
776
|
return cleanUrl.toString();
|
|
845
777
|
}
|
|
846
|
-
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;
|
|
778
|
+
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;
|
|
847
779
|
var ShapeStream = class {
|
|
780
|
+
// Maximum delay cap (ms)
|
|
848
781
|
constructor(options) {
|
|
849
782
|
__privateAdd(this, _ShapeStream_instances);
|
|
850
783
|
__privateAdd(this, _error, null);
|
|
@@ -878,6 +811,16 @@ var ShapeStream = class {
|
|
|
878
811
|
// counter for concurrent snapshot requests
|
|
879
812
|
__privateAdd(this, _midStreamPromise);
|
|
880
813
|
__privateAdd(this, _midStreamPromiseResolver);
|
|
814
|
+
__privateAdd(this, _lastSseConnectionStartTime);
|
|
815
|
+
__privateAdd(this, _minSseConnectionDuration, 1e3);
|
|
816
|
+
// Minimum expected SSE connection duration (1 second)
|
|
817
|
+
__privateAdd(this, _consecutiveShortSseConnections, 0);
|
|
818
|
+
__privateAdd(this, _maxShortSseConnections, 3);
|
|
819
|
+
// Fall back to long polling after this many short connections
|
|
820
|
+
__privateAdd(this, _sseFallbackToLongPolling, false);
|
|
821
|
+
__privateAdd(this, _sseBackoffBaseDelay, 100);
|
|
822
|
+
// Base delay for exponential backoff (ms)
|
|
823
|
+
__privateAdd(this, _sseBackoffMaxDelay, 5e3);
|
|
881
824
|
var _a, _b, _c, _d;
|
|
882
825
|
this.options = __spreadValues({ subscribe: true }, options);
|
|
883
826
|
validateOptions(this.options);
|
|
@@ -1053,10 +996,17 @@ _snapshotTracker = new WeakMap();
|
|
|
1053
996
|
_activeSnapshotRequests = new WeakMap();
|
|
1054
997
|
_midStreamPromise = new WeakMap();
|
|
1055
998
|
_midStreamPromiseResolver = new WeakMap();
|
|
999
|
+
_lastSseConnectionStartTime = new WeakMap();
|
|
1000
|
+
_minSseConnectionDuration = new WeakMap();
|
|
1001
|
+
_consecutiveShortSseConnections = new WeakMap();
|
|
1002
|
+
_maxShortSseConnections = new WeakMap();
|
|
1003
|
+
_sseFallbackToLongPolling = new WeakMap();
|
|
1004
|
+
_sseBackoffBaseDelay = new WeakMap();
|
|
1005
|
+
_sseBackoffMaxDelay = new WeakMap();
|
|
1056
1006
|
_ShapeStream_instances = new WeakSet();
|
|
1057
1007
|
start_fn = function() {
|
|
1058
1008
|
return __async(this, null, function* () {
|
|
1059
|
-
var _a;
|
|
1009
|
+
var _a, _b, _c, _d, _e;
|
|
1060
1010
|
__privateSet(this, _started, true);
|
|
1061
1011
|
try {
|
|
1062
1012
|
yield __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
@@ -1064,24 +1014,34 @@ start_fn = function() {
|
|
|
1064
1014
|
__privateSet(this, _error, err);
|
|
1065
1015
|
if (__privateGet(this, _onError)) {
|
|
1066
1016
|
const retryOpts = yield __privateGet(this, _onError).call(this, err);
|
|
1067
|
-
if (typeof retryOpts === `object`) {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
this.options.params = retryOpts.params;
|
|
1017
|
+
if (retryOpts && typeof retryOpts === `object`) {
|
|
1018
|
+
if (retryOpts.params) {
|
|
1019
|
+
this.options.params = __spreadValues(__spreadValues({}, (_a = this.options.params) != null ? _a : {}), retryOpts.params);
|
|
1071
1020
|
}
|
|
1072
|
-
if (
|
|
1073
|
-
this.options.headers = retryOpts.headers;
|
|
1021
|
+
if (retryOpts.headers) {
|
|
1022
|
+
this.options.headers = __spreadValues(__spreadValues({}, (_b = this.options.headers) != null ? _b : {}), retryOpts.headers);
|
|
1074
1023
|
}
|
|
1024
|
+
__privateSet(this, _error, null);
|
|
1075
1025
|
__privateSet(this, _started, false);
|
|
1076
|
-
__privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
1026
|
+
yield __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (err instanceof Error) {
|
|
1030
|
+
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
|
|
1077
1031
|
}
|
|
1032
|
+
__privateSet(this, _connected, false);
|
|
1033
|
+
(_c = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _c.call(this);
|
|
1078
1034
|
return;
|
|
1079
1035
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1036
|
+
if (err instanceof Error) {
|
|
1037
|
+
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
|
|
1038
|
+
}
|
|
1082
1039
|
__privateSet(this, _connected, false);
|
|
1083
|
-
(
|
|
1040
|
+
(_d = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _d.call(this);
|
|
1041
|
+
throw err;
|
|
1084
1042
|
}
|
|
1043
|
+
__privateSet(this, _connected, false);
|
|
1044
|
+
(_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
|
|
1085
1045
|
});
|
|
1086
1046
|
};
|
|
1087
1047
|
requestShape_fn = function() {
|
|
@@ -1128,7 +1088,6 @@ requestShape_fn = function() {
|
|
|
1128
1088
|
yield __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, Array.isArray(e.json) ? e.json : [e.json]);
|
|
1129
1089
|
return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
|
|
1130
1090
|
} else {
|
|
1131
|
-
__privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, e);
|
|
1132
1091
|
throw e;
|
|
1133
1092
|
}
|
|
1134
1093
|
} finally {
|
|
@@ -1280,7 +1239,7 @@ fetchShape_fn = function(opts) {
|
|
|
1280
1239
|
return __async(this, null, function* () {
|
|
1281
1240
|
var _a;
|
|
1282
1241
|
const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
|
|
1283
|
-
if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause) {
|
|
1242
|
+
if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
|
|
1284
1243
|
opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
|
|
1285
1244
|
opts.fetchUrl.searchParams.set(LIVE_SSE_QUERY_PARAM, `true`);
|
|
1286
1245
|
return __privateMethod(this, _ShapeStream_instances, requestShapeSSE_fn).call(this, opts);
|
|
@@ -1308,6 +1267,7 @@ requestShapeSSE_fn = function(opts) {
|
|
|
1308
1267
|
return __async(this, null, function* () {
|
|
1309
1268
|
const { fetchUrl, requestAbortController, headers } = opts;
|
|
1310
1269
|
const fetch2 = __privateGet(this, _sseFetchClient);
|
|
1270
|
+
__privateSet(this, _lastSseConnectionStartTime, Date.now());
|
|
1311
1271
|
try {
|
|
1312
1272
|
let buffer = [];
|
|
1313
1273
|
yield (0, import_fetch_event_source.fetchEventSource)(fetchUrl.toString(), {
|
|
@@ -1341,6 +1301,27 @@ requestShapeSSE_fn = function(opts) {
|
|
|
1341
1301
|
throw new FetchBackoffAbortError();
|
|
1342
1302
|
}
|
|
1343
1303
|
throw error;
|
|
1304
|
+
} finally {
|
|
1305
|
+
const connectionDuration = Date.now() - __privateGet(this, _lastSseConnectionStartTime);
|
|
1306
|
+
const wasAborted = requestAbortController.signal.aborted;
|
|
1307
|
+
if (connectionDuration < __privateGet(this, _minSseConnectionDuration) && !wasAborted) {
|
|
1308
|
+
__privateWrapper(this, _consecutiveShortSseConnections)._++;
|
|
1309
|
+
if (__privateGet(this, _consecutiveShortSseConnections) >= __privateGet(this, _maxShortSseConnections)) {
|
|
1310
|
+
__privateSet(this, _sseFallbackToLongPolling, true);
|
|
1311
|
+
console.warn(
|
|
1312
|
+
`[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.`
|
|
1313
|
+
);
|
|
1314
|
+
} else {
|
|
1315
|
+
const maxDelay = Math.min(
|
|
1316
|
+
__privateGet(this, _sseBackoffMaxDelay),
|
|
1317
|
+
__privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _consecutiveShortSseConnections))
|
|
1318
|
+
);
|
|
1319
|
+
const delayMs = Math.floor(Math.random() * maxDelay);
|
|
1320
|
+
yield new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1321
|
+
}
|
|
1322
|
+
} else if (connectionDuration >= __privateGet(this, _minSseConnectionDuration)) {
|
|
1323
|
+
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1324
|
+
}
|
|
1344
1325
|
}
|
|
1345
1326
|
});
|
|
1346
1327
|
};
|
|
@@ -1439,6 +1420,8 @@ reset_fn = function(handle) {
|
|
|
1439
1420
|
__privateSet(this, _connected, false);
|
|
1440
1421
|
__privateSet(this, _schema, void 0);
|
|
1441
1422
|
__privateSet(this, _activeSnapshotRequests, 0);
|
|
1423
|
+
__privateSet(this, _consecutiveShortSseConnections, 0);
|
|
1424
|
+
__privateSet(this, _sseFallbackToLongPolling, false);
|
|
1442
1425
|
};
|
|
1443
1426
|
fetchSnapshot_fn = function(url, headers) {
|
|
1444
1427
|
return __async(this, null, function* () {
|