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