/**
* Retry utility with exponential backoff
* Retries a function with configurable delay strategy
*/
import { RETRY_CONFIG } from './constants/index.js';
export interface RetryOptions {
maxRetries?: number;
initialDelayMs?: number;
maxDelayMs?: number;
backoffMultiplier?: number;
/** If true, use exponential backoff. If false, use fixed delay. Default: true */
exponential?: boolean;
}
/**
* Retry a function with configurable delay strategy
*
* Supports two calling conventions:
* - retry(fn, options) — new style with options object
* - retry(fn, maxRetries, delayMs) — legacy style, backward compatible
*
* @returns Result of function execution
* @throws Last error if all retries fail
*/
export const retry = async <T>(
fn: () => Promise<T>,
optionsOrMaxRetries?: RetryOptions | number,
delayMs?: number
): Promise<T> => {
const opts: Required<RetryOptions> = typeof optionsOrMaxRetries === 'number'
? {
maxRetries: optionsOrMaxRetries,
initialDelayMs: delayMs ?? RETRY_CONFIG.DEFAULT_INITIAL_DELAY_MS,
maxDelayMs: RETRY_CONFIG.DEFAULT_MAX_DELAY_MS,
backoffMultiplier: RETRY_CONFIG.DEFAULT_BACKOFF_MULTIPLIER,
exponential: true,
}
: {
maxRetries: optionsOrMaxRetries?.maxRetries ?? RETRY_CONFIG.DEFAULT_MAX_RETRIES,
initialDelayMs: optionsOrMaxRetries?.initialDelayMs ?? RETRY_CONFIG.DEFAULT_INITIAL_DELAY_MS,
maxDelayMs: optionsOrMaxRetries?.maxDelayMs ?? RETRY_CONFIG.DEFAULT_MAX_DELAY_MS,
backoffMultiplier: optionsOrMaxRetries?.backoffMultiplier ?? RETRY_CONFIG.DEFAULT_BACKOFF_MULTIPLIER,
exponential: optionsOrMaxRetries?.exponential ?? true,
};
// Negative maxRetries would skip the loop and cause throw of undefined; clamp to >= 0
const safeMaxRetries = Math.max(0, Math.floor(opts.maxRetries) || 0);
let lastError: unknown;
let currentDelay = opts.initialDelayMs;
for (let i = 0; i <= safeMaxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (i < safeMaxRetries) {
await new Promise(resolve => setTimeout(resolve, currentDelay));
if (opts.exponential) {
currentDelay = Math.min(currentDelay * opts.backoffMultiplier, opts.maxDelayMs);
}
}
}
}
throw lastError;
};