76 lines
1.9 KiB
TypeScript
76 lines
1.9 KiB
TypeScript
export interface RetryOptions {
|
|
maxAttempts?: number;
|
|
initialDelayMs?: number;
|
|
maxDelayMs?: number;
|
|
backoffMultiplier?: number;
|
|
shouldRetry?: (error: Error, attempt: number) => boolean;
|
|
}
|
|
|
|
const DEFAULT_OPTIONS: Required<RetryOptions> = {
|
|
maxAttempts: 3,
|
|
initialDelayMs: 1000,
|
|
maxDelayMs: 30000,
|
|
backoffMultiplier: 2,
|
|
shouldRetry: () => true,
|
|
};
|
|
|
|
/**
|
|
* Execute a function with exponential backoff retry logic
|
|
*/
|
|
export async function withRetry<T>(
|
|
fn: () => Promise<T>,
|
|
options: RetryOptions = {}
|
|
): Promise<T> {
|
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
let lastError: Error | undefined;
|
|
|
|
for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
|
|
try {
|
|
return await fn();
|
|
} catch (error) {
|
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
|
|
if (attempt >= opts.maxAttempts || !opts.shouldRetry(lastError, attempt)) {
|
|
throw lastError;
|
|
}
|
|
|
|
const delay = Math.min(
|
|
opts.initialDelayMs * Math.pow(opts.backoffMultiplier, attempt - 1),
|
|
opts.maxDelayMs
|
|
);
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
}
|
|
}
|
|
|
|
throw lastError || new Error('Retry failed');
|
|
}
|
|
|
|
/**
|
|
* Determine if an error is transient and should be retried
|
|
*/
|
|
export function isTransientError(error: Error): boolean {
|
|
const message = error.message.toLowerCase();
|
|
|
|
// Network errors
|
|
if (message.includes('econnreset') ||
|
|
message.includes('enotfound') ||
|
|
message.includes('etimedout') ||
|
|
message.includes('network')) {
|
|
return true;
|
|
}
|
|
|
|
// HTTP status codes that should be retried
|
|
if (message.includes('429') || // Too Many Requests
|
|
message.includes('500') || // Internal Server Error
|
|
message.includes('502') || // Bad Gateway
|
|
message.includes('503') || // Service Unavailable
|
|
message.includes('504')) { // Gateway Timeout
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export default withRetry;
|