92 lines
2.1 KiB
TypeScript
92 lines
2.1 KiB
TypeScript
/**
|
|
* Retry utility with exponential backoff
|
|
*/
|
|
|
|
export interface RetryOptions {
|
|
maxRetries?: number
|
|
initialDelay?: number
|
|
maxDelay?: number
|
|
backoffMultiplier?: number
|
|
retryable?: (error: Error) => boolean
|
|
}
|
|
|
|
const DEFAULT_OPTIONS: Required<RetryOptions> = {
|
|
maxRetries: 3,
|
|
initialDelay: 1000,
|
|
maxDelay: 10000,
|
|
backoffMultiplier: 2,
|
|
retryable: () => true,
|
|
}
|
|
|
|
/**
|
|
* Retry a function with exponential backoff
|
|
*/
|
|
export async function retryWithBackoff<T>(
|
|
fn: () => Promise<T>,
|
|
options: RetryOptions = {}
|
|
): Promise<T> {
|
|
const opts = { ...DEFAULT_OPTIONS, ...options }
|
|
let lastError: Error | null = null
|
|
|
|
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
try {
|
|
return await fn()
|
|
} catch (error) {
|
|
lastError = error instanceof Error ? error : new Error(String(error))
|
|
|
|
// Check if error is retryable
|
|
if (!opts.retryable(lastError)) {
|
|
throw lastError
|
|
}
|
|
|
|
// Don't retry on last attempt
|
|
if (attempt === opts.maxRetries) {
|
|
throw lastError
|
|
}
|
|
|
|
// Calculate delay with exponential backoff
|
|
const delay = Math.min(
|
|
opts.initialDelay * Math.pow(opts.backoffMultiplier, attempt),
|
|
opts.maxDelay
|
|
)
|
|
|
|
// Wait before retrying
|
|
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
}
|
|
}
|
|
|
|
throw lastError || new Error('Retry failed')
|
|
}
|
|
|
|
/**
|
|
* Check if an error is a network error that should be retried
|
|
*/
|
|
export function isRetryableNetworkError(error: Error): boolean {
|
|
// Network errors
|
|
if (error.message.includes('network') || error.message.includes('fetch')) {
|
|
return true
|
|
}
|
|
|
|
// Timeout errors
|
|
if (error.message.includes('timeout') || error.message.includes('timed out')) {
|
|
return true
|
|
}
|
|
|
|
// Connection errors
|
|
if (error.message.includes('ECONNRESET') || error.message.includes('ECONNREFUSED')) {
|
|
return true
|
|
}
|
|
|
|
// Rate limiting (429)
|
|
if (error.message.includes('429') || error.message.includes('rate limit')) {
|
|
return true
|
|
}
|
|
|
|
// Server errors (5xx)
|
|
if (error.message.includes('50')) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|