UNPKG

@neurolint/cli

Version:

NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations

146 lines (123 loc) 3.23 kB
import chalk from "chalk"; export interface RetryOptions { maxAttempts?: number; delay?: number; backoffFactor?: number; maxDelay?: number; retryCondition?: (error: any) => boolean; onRetry?: (error: any, attempt: number) => void; } export async function withRetry<T>( operation: () => Promise<T>, options: RetryOptions = {}, ): Promise<T> { const { maxAttempts = 3, delay = 1000, backoffFactor = 2, maxDelay = 10000, retryCondition = (error) => isRetryableError(error), onRetry, } = options; let lastError: any; let currentDelay = delay; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt === maxAttempts || !retryCondition(error)) { throw error; } if (onRetry) { onRetry(error, attempt); } // Wait before retrying await sleep(Math.min(currentDelay, maxDelay)); currentDelay *= backoffFactor; } } throw lastError; } export function isRetryableError(error: any): boolean { if (!error) return false; // Network errors that are retryable const retryableCodes = [ "ECONNRESET", "ENOTFOUND", "ECONNREFUSED", "ETIMEDOUT", "TIMEOUT", ]; if (error.code && retryableCodes.includes(error.code)) { return true; } // HTTP status codes that are retryable const retryableStatuses = [408, 429, 500, 502, 503, 504]; if ( error.response?.status && retryableStatuses.includes(error.response.status) ) { return true; } return false; } export function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } export class RetryableOperation<T> { private options: Required<RetryOptions>; constructor(options: RetryOptions = {}) { this.options = { maxAttempts: 3, delay: 1000, backoffFactor: 2, maxDelay: 10000, retryCondition: isRetryableError, onRetry: () => {}, ...options, }; } async execute(operation: () => Promise<T>): Promise<T> { return withRetry(operation, this.options); } onRetry(callback: (error: any, attempt: number) => void): this { this.options.onRetry = callback; return this; } withCondition(condition: (error: any) => boolean): this { this.options.retryCondition = condition; return this; } withDelay(delay: number): this { this.options.delay = delay; return this; } withMaxAttempts(maxAttempts: number): this { this.options.maxAttempts = maxAttempts; return this; } } export function createRetryableApiCall( baseUrl: string, timeout: number = 30000, ) { return new RetryableOperation({ maxAttempts: 3, delay: 1000, onRetry: (error, attempt) => { console.log( chalk.white(`API call failed (attempt ${attempt}), retrying...`), ); if (error.response?.status) { console.log( chalk.gray( ` HTTP ${error.response.status}: ${error.response.statusText}`, ), ); } else if (error.code) { console.log(chalk.gray(` ${error.code}: ${error.message}`)); } }, }); }