UNPKG

@xec-sh/core

Version:

Universal shell execution engine

94 lines 3.8 kB
export class RetryError extends Error { constructor(message, attempts, lastResult, results) { super(message); this.attempts = attempts; this.lastResult = lastResult; this.results = results; this.name = 'RetryError'; } } export async function withExecutionRetry(fn, options = {}, eventEmitter) { const { maxRetries = 3, initialDelay = 100, maxDelay = 30000, backoffMultiplier = 2, jitter = true, isRetryable = defaultIsRetryable, onRetry, } = options; const results = []; const startTime = Date.now(); for (let attempt = 0; attempt <= maxRetries; attempt++) { const result = await fn(); results.push(result); if (result.exitCode === 0) { if (attempt > 0 && eventEmitter) { eventEmitter.emitEnhanced('retry:success', { attempts: attempt + 1, totalDuration: Date.now() - startTime, command: result.command }, 'retry'); } return result; } if (attempt === maxRetries) { if (eventEmitter) { eventEmitter.emitEnhanced('retry:failed', { attempts: maxRetries + 1, totalDuration: Date.now() - startTime, command: result.command, lastError: result.stderr || `Exit code ${result.exitCode}` }, 'retry'); } throw new RetryError(`Failed after ${maxRetries + 1} attempts: ${result.command}`, maxRetries + 1, result, results); } if (!isRetryable(result)) { if (eventEmitter) { eventEmitter.emitEnhanced('retry:failed', { attempts: attempt + 1, totalDuration: Date.now() - startTime, command: result.command, lastError: 'Not retryable' }, 'retry'); } throw new RetryError(`Command failed and is not retryable: ${result.command}`, attempt + 1, result, results); } const baseDelay = Math.min(initialDelay * Math.pow(backoffMultiplier, attempt), maxDelay); const delay = jitter ? addJitter(baseDelay) : baseDelay; if (onRetry) { onRetry(attempt + 1, result, delay); } if (eventEmitter) { eventEmitter.emitEnhanced('retry:attempt', { attempt: attempt + 1, maxAttempts: maxRetries + 1, delay, command: result.command, error: result.stderr || `Exit code ${result.exitCode}` }, 'retry'); } await sleep(delay); } throw new Error('Unexpected retry state'); } function defaultIsRetryable(result) { return result.exitCode !== 0; } function addJitter(delay) { const jitterRange = delay * 0.25; return delay + (Math.random() * 2 - 1) * jitterRange; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } export function createRetryableAdapter(adapter, defaultRetryOptions) { return new Proxy(adapter, { get(target, prop, receiver) { if (prop === 'execute') { return async (command) => { const retryOptions = command.retry || defaultRetryOptions; const maxRetries = retryOptions?.maxRetries ?? 0; if (!retryOptions || maxRetries <= 0) { return target.execute(command); } return withExecutionRetry(() => target.execute(command), retryOptions); }; } return Reflect.get(target, prop, receiver); } }); } //# sourceMappingURL=retry-adapter.js.map