export interface CircuitBreakerConfig {
failureThreshold: number;
recoveryTimeout: number;
monitoringInterval: number;
}
export enum CircuitBreakerState {
CLOSED = "CLOSED",
OPEN = "OPEN",
HALF_OPEN = "HALF_OPEN",
}
export interface CircuitBreakerMetrics {
state: CircuitBreakerState;
failures: number;
successes: number;
lastFailureTime?: Date;
lastSuccessTime?: Date;
requestCount: number;
errorRate: number;
}
export class CircuitBreaker {
private state: CircuitBreakerState = CircuitBreakerState.CLOSED;
private failures = 0;
private successes = 0;
private lastFailureTime?: Date;
private lastSuccessTime?: Date;
private requestCount = 0;
private nextAttemptTime = 0;
constructor(private config: CircuitBreakerConfig) {}
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === CircuitBreakerState.OPEN) {
if (Date.now() < this.nextAttemptTime) {
throw new Error("Circuit breaker is OPEN - operation not allowed");
}
// Transition to HALF_OPEN to test if service has recovered
this.state = CircuitBreakerState.HALF_OPEN;
}
this.requestCount++;
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void {
this.successes++;
this.lastSuccessTime = new Date();
if (this.state === CircuitBreakerState.HALF_OPEN) {
// Service has recovered, close the circuit
this.state = CircuitBreakerState.CLOSED;
this.failures = 0; // Reset failure count
}
}
private onFailure(): void {
this.failures++;
this.lastFailureTime = new Date();
if (this.failures >= this.config.failureThreshold) {
this.state = CircuitBreakerState.OPEN;
this.nextAttemptTime = Date.now() + this.config.recoveryTimeout;
}
}
getMetrics(): CircuitBreakerMetrics {
const totalRequests = this.successes + this.failures;
const errorRate = totalRequests > 0 ? this.failures / totalRequests : 0;
return {
state: this.state,
failures: this.failures,
successes: this.successes,
lastFailureTime: this.lastFailureTime,
lastSuccessTime: this.lastSuccessTime,
requestCount: this.requestCount,
errorRate,
};
}
reset(): void {
this.state = CircuitBreakerState.CLOSED;
this.failures = 0;
this.successes = 0;
this.requestCount = 0;
this.lastFailureTime = undefined;
this.lastSuccessTime = undefined;
this.nextAttemptTime = 0;
}
isOpen(): boolean {
return this.state === CircuitBreakerState.OPEN;
}
isClosed(): boolean {
return this.state === CircuitBreakerState.CLOSED;
}
isHalfOpen(): boolean {
return this.state === CircuitBreakerState.HALF_OPEN;
}
}