@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/index.mjs
CHANGED
|
@@ -338,8 +338,13 @@ var ELECTRIC_PROTOCOL_QUERY_PARAMS = [
|
|
|
338
338
|
var HTTP_RETRY_STATUS_CODES = [429];
|
|
339
339
|
var BackoffDefaults = {
|
|
340
340
|
initialDelay: 100,
|
|
341
|
-
maxDelay:
|
|
342
|
-
|
|
341
|
+
maxDelay: 6e4,
|
|
342
|
+
// Cap at 60s - reasonable for long-lived connections
|
|
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
|
|
343
348
|
};
|
|
344
349
|
function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
345
350
|
const {
|
|
@@ -347,8 +352,29 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
347
352
|
maxDelay,
|
|
348
353
|
multiplier,
|
|
349
354
|
debug = false,
|
|
350
|
-
onFailedAttempt
|
|
355
|
+
onFailedAttempt,
|
|
356
|
+
maxRetries = Infinity,
|
|
357
|
+
retryBudgetPercent = 0.1
|
|
351
358
|
} = 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
|
+
}
|
|
352
378
|
return (...args) => __async(this, null, function* () {
|
|
353
379
|
var _a;
|
|
354
380
|
const url = args[0];
|
|
@@ -358,7 +384,10 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
358
384
|
while (true) {
|
|
359
385
|
try {
|
|
360
386
|
const result = yield fetchClient(...args);
|
|
361
|
-
if (result.ok)
|
|
387
|
+
if (result.ok) {
|
|
388
|
+
delay = initialDelay;
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
362
391
|
const err = yield FetchError.fromResponse(result, url.toString());
|
|
363
392
|
throw err;
|
|
364
393
|
} catch (e) {
|
|
@@ -368,12 +397,51 @@ function createFetchWithBackoff(fetchClient, backoffOptions = BackoffDefaults) {
|
|
|
368
397
|
} else if (e instanceof FetchError && !HTTP_RETRY_STATUS_CODES.includes(e.status) && e.status >= 400 && e.status < 500) {
|
|
369
398
|
throw e;
|
|
370
399
|
} else {
|
|
371
|
-
|
|
372
|
-
|
|
400
|
+
attempt++;
|
|
401
|
+
if (attempt >= maxRetries) {
|
|
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);
|
|
373
437
|
if (debug) {
|
|
374
|
-
|
|
375
|
-
console.log(
|
|
438
|
+
const source = serverMinimumMs > 0 ? `server+client` : `client`;
|
|
439
|
+
console.log(
|
|
440
|
+
`Retry attempt #${attempt} after ${waitMs}ms (${source}, serverMin=${serverMinimumMs}ms, clientBackoff=${clientBackoffMs}ms)`
|
|
441
|
+
);
|
|
376
442
|
}
|
|
443
|
+
yield new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
444
|
+
delay = Math.min(delay * multiplier, maxDelay);
|
|
377
445
|
}
|
|
378
446
|
}
|
|
379
447
|
}
|