(
attempt: number,
retryAfterMs: number | null,
options: BackoffOptions = {}
)
| 17 | * Attempt is 1-indexed. |
| 18 | */ |
| 19 | export function backoffWithJitter( |
| 20 | attempt: number, |
| 21 | retryAfterMs: number | null, |
| 22 | options: BackoffOptions = {} |
| 23 | ): number { |
| 24 | const baseMs = options.baseMs ?? DEFAULT_BACKOFF_BASE_MS |
| 25 | const maxMs = options.maxMs ?? DEFAULT_BACKOFF_MAX_MS |
| 26 | if (retryAfterMs !== null) { |
| 27 | return Math.min(Math.max(retryAfterMs, baseMs), maxMs) |
| 28 | } |
| 29 | const exponential = Math.min(baseMs * 2 ** (attempt - 1), maxMs) |
| 30 | // Inline crypto float to avoid cross-file imports within the package (Turbopack limitation) |
| 31 | const jitter = crypto.getRandomValues(new Uint32Array(1))[0] / 0x100000000 |
| 32 | return exponential * (0.8 + jitter * 0.4) |
| 33 | } |
| 34 | |
| 35 | /** Maximum `Retry-After` value honored: 30 s. Prevents a misconfigured upstream from stalling callers. */ |
| 36 | const RETRY_AFTER_MAX_MS = 30_000 |
no outgoing calls
no test coverage detected