UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

340 lines (339 loc) 11.6 kB
/** * Circuit Breaker Pattern Implementation * * Provides a robust circuit breaker pattern for protecting external dependencies * and preventing cascading failures across the system. * * Features: * - Three-state machine: CLOSED, OPEN, HALF_OPEN * - Configurable thresholds and timeouts * - Fallback support for graceful degradation * - Comprehensive metrics and monitoring * - Centralized registry for system-wide visibility * - Integration with StandardError and Prometheus metrics * * Usage: * ```typescript * const breaker = new CircuitBreaker('external-api', { * failureThreshold: 5, * successThreshold: 2, * timeout: 30000 * }); * * const result = await breaker.execute( * async () => await externalApiCall(), * async () => cachedFallbackData * ); * ``` * * Part of: HIGH-PRIORITY Circuit Breaker Enhancement */ import { StandardError } from './errors.js'; import { Logger } from '../core/logger.js'; const logger = Logger.getInstance(); /** * Circuit breaker states */ export var CircuitBreakerState = /*#__PURE__*/ function(CircuitBreakerState) { /** Normal operation - requests flow through */ CircuitBreakerState["CLOSED"] = "closed"; /** Failing - requests are rejected immediately */ CircuitBreakerState["OPEN"] = "open"; /** Testing recovery - limited requests allowed */ CircuitBreakerState["HALF_OPEN"] = "half_open"; return CircuitBreakerState; }({}); /** * Circuit breaker open error */ export class CircuitOpenError extends StandardError { serviceName; statusCode; isOperational; constructor(serviceName){ super('CIRCUIT_OPEN', `Circuit breaker is OPEN for service: ${serviceName}`, { serviceName }, undefined, false // Not retryable when circuit is open ); this.serviceName = serviceName; this.statusCode = 503; this.isOperational = true; this.name = 'CircuitOpenError'; } } /** * Circuit Breaker Implementation * * Implements the circuit breaker pattern to prevent cascading failures * by failing fast when a service is detected as unhealthy. */ export class CircuitBreaker { serviceName; config; metrics; constructor(serviceName, config){ this.serviceName = serviceName; this.config = { failureThreshold: config?.failureThreshold ?? 5, successThreshold: config?.successThreshold ?? 2, timeout: config?.timeout ?? 30000, windowSize: config?.windowSize ?? 60000 }; this.metrics = { state: "closed", failures: 0, successes: 0, totalCalls: 0, totalSuccesses: 0, totalFailures: 0, totalRejected: 0 }; logger.info(`Circuit breaker initialized for service: ${serviceName}`, { config: this.config }); } /** * Execute an operation with circuit breaker protection * * @param operation - The operation to execute * @param fallback - Optional fallback function if circuit is open * @returns Result of operation or fallback * @throws CircuitOpenError if circuit is open and no fallback provided */ async execute(operation, fallback) { this.metrics.totalCalls = (this.metrics.totalCalls ?? 0) + 1; // Check if circuit should transition from OPEN to HALF_OPEN if (this.shouldAttemptReset()) { this.transitionToHalfOpen(); } // Reject if circuit is OPEN if (this.metrics.state === "open") { this.metrics.totalRejected = (this.metrics.totalRejected ?? 0) + 1; logger.warn(`Circuit breaker rejected request for service: ${this.serviceName}`, { state: this.metrics.state, failures: this.metrics.failures }); if (fallback) { logger.info(`Executing fallback for service: ${this.serviceName}`); return await fallback(); } throw new CircuitOpenError(this.serviceName); } // Execute operation try { const result = await operation(); this.onSuccess(); return result; } catch (error) { this.onFailure(error); throw error; } } /** * Record successful operation */ onSuccess() { this.metrics.totalSuccesses = (this.metrics.totalSuccesses ?? 0) + 1; this.metrics.lastSuccessTime = new Date(); // In CLOSED state, reset failure count on success if (this.metrics.state === "closed") { this.metrics.failures = 0; logger.debug(`Success recorded for service: ${this.serviceName}`, { state: this.metrics.state, failuresReset: true }); return; } // Only track consecutive successes in HALF_OPEN state (for recovery) if (this.metrics.state === "half_open") { this.metrics.successes++; logger.debug(`Success recorded for service: ${this.serviceName}`, { state: this.metrics.state, successes: this.metrics.successes }); // Transition from HALF_OPEN to CLOSED if threshold met if (this.metrics.successes >= this.config.successThreshold) { this.transitionToClosed(); } } } /** * Record failed operation */ onFailure(error) { this.metrics.failures++; this.metrics.totalFailures = (this.metrics.totalFailures ?? 0) + 1; this.metrics.lastFailureTime = new Date(); logger.warn(`Failure recorded for service: ${this.serviceName}`, { state: this.metrics.state, failures: this.metrics.failures, error: error instanceof Error ? error.message : String(error) }); // Transition from HALF_OPEN to OPEN on any failure if (this.metrics.state === "half_open") { this.transitionToOpen(); return; } // Transition from CLOSED to OPEN if threshold met if (this.metrics.state === "closed" && this.metrics.failures >= this.config.failureThreshold) { this.transitionToOpen(); } } /** * Transition to OPEN state */ transitionToOpen() { this.metrics.state = "open"; this.metrics.openedAt = new Date(); logger.error(`Circuit breaker opened for service: ${this.serviceName}`, { failures: this.metrics.failures, threshold: this.config.failureThreshold }); // Emit metrics event this.emitMetrics('circuit_opened'); } /** * Transition to HALF_OPEN state */ transitionToHalfOpen() { this.metrics.state = "half_open"; this.metrics.successes = 0; logger.info(`Circuit breaker half-open for service: ${this.serviceName}`, { message: 'Testing service recovery' }); // Emit metrics event this.emitMetrics('circuit_half_opened'); } /** * Transition to CLOSED state */ transitionToClosed() { this.metrics.state = "closed"; this.metrics.failures = 0; this.metrics.successes = 0; this.metrics.openedAt = undefined; logger.info(`Circuit breaker closed for service: ${this.serviceName}`, { message: 'Service recovered' }); // Emit metrics event this.emitMetrics('circuit_closed'); } /** * Check if circuit should attempt reset (OPEN -> HALF_OPEN) */ shouldAttemptReset() { if (this.metrics.state !== "open") { return false; } if (!this.metrics.openedAt) { return false; } const timeSinceOpen = Date.now() - this.metrics.openedAt.getTime(); return timeSinceOpen >= this.config.timeout; } /** * Emit metrics for monitoring */ emitMetrics(event) { // Placeholder for Prometheus metrics integration // In production, this would push metrics to Prometheus/Grafana logger.debug(`Circuit breaker event: ${event}`, { service: this.serviceName, state: this.metrics.state, metrics: this.metrics }); } /** * Get current circuit state */ getState() { return this.metrics.state; } /** * Get current metrics */ getMetrics() { return { ...this.metrics }; } /** * Check if circuit is healthy (CLOSED state) */ isHealthy() { return this.metrics.state === "closed"; } /** * Manually open the circuit */ open() { logger.warn(`Manually opening circuit for service: ${this.serviceName}`); this.transitionToOpen(); } /** * Manually close the circuit and reset metrics */ close() { logger.info(`Manually closing circuit for service: ${this.serviceName}`); this.transitionToClosed(); } /** * Get service name */ getServiceName() { return this.serviceName; } } /** * Circuit Breaker Registry * * Centralized registry for managing multiple circuit breakers across the system. * Provides system-wide visibility and health monitoring. */ export class CircuitBreakerRegistry { static breakers = new Map(); /** * Get or create a circuit breaker for a service */ static getOrCreate(serviceName, config) { let breaker = this.breakers.get(serviceName); if (!breaker) { breaker = new CircuitBreaker(serviceName, config); this.breakers.set(serviceName, breaker); logger.info(`Circuit breaker registered: ${serviceName}`); } return breaker; } /** * Get existing circuit breaker */ static get(serviceName) { return this.breakers.get(serviceName); } /** * Get all registered circuit breakers */ static getAll() { return new Map(this.breakers); } /** * Get health status for all circuit breakers */ static getHealthStatus() { const status = {}; for (const [name, breaker] of this.breakers.entries()){ status[name] = breaker.isHealthy(); } return status; } /** * Get metrics for all circuit breakers */ static getAllMetrics() { const metrics = {}; for (const [name, breaker] of this.breakers.entries()){ metrics[name] = breaker.getMetrics(); } return metrics; } /** * Clear all circuit breakers (for testing) */ static clear() { this.breakers.clear(); logger.info('Circuit breaker registry cleared'); } /** * Remove specific circuit breaker */ static remove(serviceName) { const result = this.breakers.delete(serviceName); if (result) { logger.info(`Circuit breaker removed: ${serviceName}`); } return result; } } /** * Helper function to create a circuit breaker */ export function createCircuitBreaker(serviceName, config) { return CircuitBreakerRegistry.getOrCreate(serviceName, config); } /** * Helper function to execute with circuit breaker protection */ export async function executeWithCircuitBreaker(serviceName, operation, fallback, config) { const breaker = CircuitBreakerRegistry.getOrCreate(serviceName, config); return breaker.execute(operation, fallback); } //# sourceMappingURL=circuit-breaker.js.map