diff --git a/src/Utils/retry-utils.ts b/src/Utils/retry-utils.ts index 9449f751..cce180d0 100644 --- a/src/Utils/retry-utils.ts +++ b/src/Utils/retry-utils.ts @@ -13,7 +13,6 @@ * @module Utils/retry-utils */ -import { EventEmitter } from 'events' import { metrics } from './prometheus-metrics.js' /** @@ -416,138 +415,6 @@ export function createRetrier(defaultOptions: RetryOptions = {}) { } } -/** - * Decorator to add retry to method - */ -export function withRetry(options: RetryOptions = {}) { - return function ( - _target: unknown, - propertyKey: string, - descriptor: TypedPropertyDescriptor<(...args: unknown[]) => unknown> - ) { - const originalMethod = descriptor.value - if (!originalMethod) return descriptor - - descriptor.value = async function (...args: unknown[]): Promise { - return retry(() => originalMethod.apply(this, args), { - ...options, - operationName: options.operationName || propertyKey - }) - } - - return descriptor - } -} - -/** - * Wrapper for function with retry - */ -// eslint-disable-next-line space-before-function-paren -export function retryable unknown>( - fn: T, - options: RetryOptions = {} -): (...args: Parameters) => Promise> { - return async (...args: Parameters): Promise> => { - return retry(() => fn(...args), options) as Promise> - } -} - -/** - * Class to manage retries with state - */ -export class RetryManager extends EventEmitter { - private activeRetries: Map void; context: RetryContext }> = new Map() - private defaultOptions: RetryOptions - - constructor(defaultOptions: RetryOptions = {}) { - super() - this.defaultOptions = defaultOptions - } - - /** - * Execute operation with retry - */ - async execute( - id: string, - operation: (context: RetryContext) => T | Promise, - options?: RetryOptions - ): Promise { - // Cancel previous retry with same ID - this.cancel(id) - - const abortController = new AbortController() - const mergedOptions = { ...this.defaultOptions, ...options, abortSignal: abortController.signal } - - const retryPromise = retry(context => { - this.activeRetries.set(id, { - cancel: () => abortController.abort(), - context - }) - this.emit('attempt', { id, attempt: context.attempt }) - return operation(context) - }, mergedOptions) - - try { - const result = await retryPromise - this.emit('success', { id }) - return result - } catch (error) { - this.emit('failure', { id, error }) - throw error - } finally { - this.activeRetries.delete(id) - } - } - - /** - * Cancel in-progress retry - */ - cancel(id: string): boolean { - const active = this.activeRetries.get(id) - if (active) { - active.cancel() - this.activeRetries.delete(id) - this.emit('cancelled', { id }) - return true - } - - return false - } - - /** - * Cancel all retries - */ - cancelAll(): void { - for (const [id, active] of this.activeRetries) { - active.cancel() - this.emit('cancelled', { id }) - } - - this.activeRetries.clear() - } - - /** - * Check if there is an active retry - */ - isActive(id: string): boolean { - return this.activeRetries.has(id) - } - - /** - * Return active retry context - */ - getContext(id: string): RetryContext | undefined { - return this.activeRetries.get(id)?.context - } - - /** - * Return active retry IDs - */ - getActiveIds(): string[] { - return Array.from(this.activeRetries.keys()) - } -} - /** * Common predicates for shouldRetry */ diff --git a/src/__tests__/Utils/retry-utils.test.ts b/src/__tests__/Utils/retry-utils.test.ts index 14ff473d..c06482cf 100644 --- a/src/__tests__/Utils/retry-utils.test.ts +++ b/src/__tests__/Utils/retry-utils.test.ts @@ -11,12 +11,10 @@ import { retry, RETRY_BACKOFF_DELAYS, RETRY_JITTER_FACTOR, - retryable, RetryAbortedError, retryConfigs, type RetryContext, RetryExhaustedError, - RetryManager, retryPredicates, retryWithResult } from '../../Utils/retry-utils.js' @@ -315,103 +313,6 @@ describe('createRetrier', () => { }) }) -describe('retryable', () => { - it('should wrap function with retry', async () => { - let attempts = 0 - - const fn = retryable( - () => { - attempts++ - if (attempts < 2) throw new Error('Failing') - return 'wrapped result' - }, - { - maxAttempts: 5, - baseDelay: 10, - collectMetrics: false - } - ) - - const result = await fn() - - expect(result).toBe('wrapped result') - expect(attempts).toBe(2) - }) -}) - -describe('RetryManager', () => { - let manager: RetryManager - - beforeEach(() => { - manager = new RetryManager({ baseDelay: 200, collectMetrics: false }) - }) - - it('should execute operation with id', async () => { - const result = await manager.execute('op1', () => 'result') - expect(result).toBe('result') - }) - - it('should cancel active retry', async () => { - let attempt = 0 - - const promise = manager.execute('cancelable', async () => { - attempt++ - if (attempt === 1) { - throw new Error('Temporary failure') - } - - await new Promise(resolve => setTimeout(resolve, 1000)) - return 'result' - }) - - // Cancel during retry delay - setTimeout(() => manager.cancel('cancelable'), 100) - - await expect(promise).rejects.toThrow() - }) - - it('should check if operation is active', async () => { - let resolve: () => void - const promise = manager.execute( - 'active', - () => - new Promise(r => { - resolve = () => r('done') - }) - ) - - expect(manager.isActive('active')).toBe(true) - - resolve!() - await promise - - expect(manager.isActive('active')).toBe(false) - }) - - it('should cancel all operations', async () => { - const promises = [ - manager.execute('op1', () => new Promise((_, reject) => setTimeout(() => reject(new Error()), 1000))), - manager.execute('op2', () => new Promise((_, reject) => setTimeout(() => reject(new Error()), 1000))) - ] - - setTimeout(() => manager.cancelAll(), 20) - - await expect(Promise.all(promises)).rejects.toThrow() - }) - - it('should emit events', async () => { - const events: string[] = [] - - manager.on('attempt', () => events.push('attempt')) - manager.on('success', () => events.push('success')) - - await manager.execute('test', () => 'result') - - expect(events).toContain('attempt') - expect(events).toContain('success') - }) -}) - describe('retryPredicates', () => { describe('always', () => { it('should always return true', () => {