/** * Simple circuit breaker for the Python sidecar. * * States: CLOSED (normal) → OPEN (fail fast) → HALF_OPEN (probe) * Opens after `failureThreshold` consecutive failures. * Resets after `resetTimeoutMs` in OPEN state. */ type State = 'CLOSED' | 'OPEN' | 'HALF_OPEN'; export interface CircuitBreakerOptions { failureThreshold?: number; resetTimeoutMs?: number; } export class CircuitBreaker { private state: State = 'CLOSED'; private failureCount = 0; private lastFailureTime = 0; private readonly failureThreshold: number; private readonly resetTimeoutMs: number; constructor(opts: CircuitBreakerOptions = {}) { this.failureThreshold = opts.failureThreshold ?? 5; this.resetTimeoutMs = opts.resetTimeoutMs ?? 30_000; // 30s } /** * Check if request is allowed. Throws if circuit is OPEN. */ allowRequest(): boolean { if (this.state === 'CLOSED') return true; if (this.state === 'OPEN') { // Check if reset timeout has elapsed if (Date.now() - this.lastFailureTime >= this.resetTimeoutMs) { this.state = 'HALF_OPEN'; return true; // Allow one probe request } return false; // Still open, fail fast } // HALF_OPEN: allow the probe return true; } /** * Record a successful call — resets the breaker to CLOSED. */ recordSuccess(): void { this.failureCount = 0; this.state = 'CLOSED'; } /** * Record a failure — may trip the breaker to OPEN. */ recordFailure(): void { this.failureCount++; this.lastFailureTime = Date.now(); if (this.failureCount >= this.failureThreshold) { this.state = 'OPEN'; } } get currentState(): State { return this.state; } get stats(): { state: State; failureCount: number; threshold: number; resetMs: number } { return { state: this.state, failureCount: this.failureCount, threshold: this.failureThreshold, resetMs: this.resetTimeoutMs, }; } } // Module-level singleton for the sidecar circuit breaker export const sidecarBreaker = new CircuitBreaker({ failureThreshold: 5, resetTimeoutMs: 30_000, });