UNPKG

splitwise

Version:

A TypeScript SDK for the Splitwise API.

71 lines 2.77 kB
/** * Retry helper with exponential backoff and jitter. * * Used by the HTTP client to transparently retry transient failures * (network errors, 5xx, 429). Honors a server-provided Retry-After when * present. */ import { SplitwiseConnectionError, SplitwiseRateLimitError, SplitwiseServerError, } from './errors.js'; const DEFAULT_BASE_DELAY_MS = 500; const DEFAULT_MAX_DELAY_MS = 5000; /** * Default retry policy: retries on transient connection failures, server errors, * and rate-limit responses. */ export const defaultShouldRetry = ({ error }) => { return (error instanceof SplitwiseConnectionError || error instanceof SplitwiseRateLimitError || error instanceof SplitwiseServerError); }; function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } /** * Computes the backoff delay for a given attempt with full jitter. * Exposed for testing; the algorithm is otherwise an implementation detail. */ export function computeDelayMs(attempt, baseDelayMs, maxDelayMs, retryAfterSeconds, random = Math.random) { const exponential = Math.min(baseDelayMs * Math.pow(2, attempt - 1), maxDelayMs); // Jitter to 50%-100% of the computed delay to avoid thundering-herd retries. const jittered = exponential * (0.5 + random() * 0.5); if (retryAfterSeconds !== undefined) { return Math.max(retryAfterSeconds * 1000, jittered); } return jittered; } /** * Runs `fn`, retrying transient failures up to `options.maxRetries` times. * Throws the final error if retries are exhausted or the error isn't retryable. */ export async function withRetry(fn, options, shouldRetry = defaultShouldRetry) { const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS; const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS; const totalAttempts = options.maxRetries + 1; let lastError; for (let attempt = 1; attempt <= totalAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = error; const retryAfterSeconds = error instanceof SplitwiseRateLimitError ? error.retryAfter : undefined; const isLastAttempt = attempt === totalAttempts; if (isLastAttempt) { throw error; } const ctx = { attempt, error, retryAfterSeconds }; if (!shouldRetry(ctx)) { throw error; } const delayMs = computeDelayMs(attempt, baseDelayMs, maxDelayMs, retryAfterSeconds); await sleep(delayMs); } } // Unreachable: the loop either returns or throws on every iteration. throw lastError; } //# sourceMappingURL=retry.js.map