@electric-sql/client 1.1.0 → 1.1.1
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 +76 -8
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +22 -0
- package/dist/index.browser.mjs +3 -3
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +22 -0
- package/dist/index.legacy-esm.js +76 -8
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +76 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/fetch.ts +126 -9
package/dist/cjs/index.cjs
CHANGED
|
@@ -369,8 +369,13 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
369
369
|
var HTTP_RETRY_STATUS_CODES = [429];
|
|
370
370
|
var BackoffDefaults = {
|
|
371
371
|
initialDelay: 100,
|
|
372
|
-
maxDelay:
|
|
373
|
-
|
|
372
|
+
maxDelay: 6e4,
|
|
373
|
+
// Cap at 60s - reasonable for long-lived connections
|
|
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
|
|
374
379
|
};
|
|
375
380
|
function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
376
381
|
const {
|
|
@@ -378,8 +383,29 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
378
383
|
maxDelay,
|
|
379
384
|
multiplier,
|
|
380
385
|
debug = false,
|
|
381
|
-
onFailedAttempt
|
|
386
|
+
onFailedAttempt,
|
|
387
|
+
maxRetries = Infinity,
|
|
388
|
+
retryBudgetPercent = 0.1
|
|
382
389
|
} = 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
|
+
}
|
|
383
409
|
return (...args) => __async(this, null, function* () {
|
|
384
410
|
var _a;
|
|
385
411
|
const url = args[0];
|
|
@@ -389,7 +415,10 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
389
415
|
while (true) {
|
|
390
416
|
try {
|
|
391
417
|
const result = yield fetchClient(...args);
|
|
392
|
-
if (result.ok)
|
|
418
|
+
if (result.ok) {
|
|
419
|
+
delay = initialDelay;
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
393
422
|
const err = yield FetchError.fromResponse(result, url.toString());
|
|
394
423
|
throw err;
|
|
395
424
|
} catch (e) {
|
|
@@ -399,12 +428,51 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
399
428
|
} else if (e instanceof FetchError && !HTTP_RETRY_STATUS_CODES.includes(e.status) && e.status >= 400 && e.status < 500) {
|
|
400
429
|
throw e;
|
|
401
430
|
} else {
|
|
402
|
-
|
|
403
|
-
|
|
431
|
+
attempt++;
|
|
432
|
+
if (attempt >= maxRetries) {
|
|
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);
|
|
404
468
|
if (debug) {
|
|
405
|
-
|
|
406
|
-
console.log(
|
|
469
|
+
const source = serverMinimumMs > 0 ? `server+client` : `client`;
|
|
470
|
+
console.log(
|
|
471
|
+
`Retry attempt #${attempt} after ${waitMs}ms (${source}, serverMin=${serverMinimumMs}ms, clientBackoff=${clientBackoffMs}ms)`
|
|
472
|
+
);
|
|
407
473
|
}
|
|
474
|
+
yield new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
475
|
+
delay = Math.min(delay * multiplier, maxDelay);
|
|
408
476
|
}
|
|
409
477
|
}
|
|
410
478
|
}
|