@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.
@@ -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: 1e4,
373
- multiplier: 1.3
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) return result;
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
- yield new Promise((resolve) => setTimeout(resolve, delay));
403
- delay = Math.min(delay * multiplier, maxDelay);
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
- attempt++;
406
- console.log(`Retry attempt #${attempt} after ${delay}ms`);
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
  }