import { assert } from "../_util/asserts.ts";
export class RetryError extends Error { constructor(cause: unknown, count: number) { super(`Exceeded max retry count (${count})`); this.name = "RetryError"; this.cause = cause; }}
export interface RetryOptions { multiplier?: number; maxTimeout?: number; maxAttempts?: number; minTimeout?: number;}
const defaultRetryOptions: Required<RetryOptions> = { multiplier: 2, maxTimeout: 60000, maxAttempts: 5, minTimeout: 1000,};
export async function retry<T>( fn: (() => Promise<T>) | (() => T), opts?: RetryOptions,) { const options: Required<RetryOptions> = { ...defaultRetryOptions, ...opts, };
assert(options.maxTimeout >= 0, "maxTimeout is less than 0"); assert( options.minTimeout <= options.maxTimeout, "minTimeout is greater than maxTimeout", );
let timeout = options.minTimeout; let error: unknown;
for (let i = 0; i < options.maxAttempts; i++) { try { return await fn(); } catch (err) { await new Promise((r) => setTimeout(r, timeout));
timeout = _exponentialBackoffWithJitter( options.maxTimeout, options.minTimeout, i, options.multiplier, );
error = err; } }
throw new RetryError(error, options.maxAttempts);}
export function _exponentialBackoffWithJitter( cap: number, base: number, attempt: number, multiplier: number,) { return Math.random() * Math.min(cap, base * multiplier ** attempt);}