splitwise
Version:
A TypeScript SDK for the Splitwise API.
71 lines • 2.77 kB
JavaScript
/**
* 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