/** * Circuit breaker implementation for resilient operations * Prevents cascading failures by opening the circuit after threshold failures */ export enum CircuitBreakerState { CLOSED = 'closed', OPEN = 'open', HALF_OPEN = 'half_open', } export interface CircuitBreakerOptions { failureThreshold?: number; resetTimeout?: number; halfOpenMaxCalls?: number; onStateChange?: (state: CircuitBreakerState) => void; } /** * Circuit breaker for resilient operations */ export class CircuitBreaker { private state: CircuitBreakerState = CircuitBreakerState.CLOSED; private failures = 0; private lastFailureTime: number | null = null; private halfOpenCalls = 0; constructor(private options: CircuitBreakerOptions = {}) {} /** * Execute a function with circuit breaker protection */ async execute(fn: () => Promise): Promise { const { failureThreshold = 5, resetTimeout = 60000, } = this.options; // Check if circuit should be opened if (this.state === CircuitBreakerState.CLOSED) { if (this.failures >= failureThreshold) { this.setState(CircuitBreakerState.OPEN); this.lastFailureTime = Date.now(); } } // Check if circuit should be half-opened if (this.state === CircuitBreakerState.OPEN) { if (this.lastFailureTime && Date.now() - this.lastFailureTime >= resetTimeout) { this.setState(CircuitBreakerState.HALF_OPEN); this.halfOpenCalls = 0; } else { throw new Error('Circuit breaker is OPEN'); } } // Execute function try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } /** * Handle successful execution */ private onSuccess(): void { if (this.state === CircuitBreakerState.HALF_OPEN) { this.setState(CircuitBreakerState.CLOSED); this.failures = 0; this.halfOpenCalls = 0; } else if (this.state === CircuitBreakerState.CLOSED) { this.failures = 0; } } /** * Handle failed execution */ private onFailure(): void { if (this.state === CircuitBreakerState.HALF_OPEN) { this.halfOpenCalls++; if (this.halfOpenCalls >= (this.options.halfOpenMaxCalls || 3)) { this.setState(CircuitBreakerState.OPEN); this.lastFailureTime = Date.now(); } } else if (this.state === CircuitBreakerState.CLOSED) { this.failures++; } } /** * Set circuit breaker state */ private setState(state: CircuitBreakerState): void { if (this.state !== state) { this.state = state; if (this.options.onStateChange) { this.options.onStateChange(state); } } } /** * Get current state */ getState(): CircuitBreakerState { return this.state; } /** * Reset circuit breaker */ reset(): void { this.state = CircuitBreakerState.CLOSED; this.failures = 0; this.lastFailureTime = null; this.halfOpenCalls = 0; } }