UNPKG

@rhofkens/mcp-quotes-server-claude-code

Version:

Model Context Protocol (MCP) server for managing and serving quotes

238 lines 7.21 kB
/** * Circuit Breaker Pattern Implementation * * Provides fault tolerance for external API calls by preventing * cascading failures and allowing systems to recover */ import { APIError, ErrorCode } from './errors.js'; import { logger } from './logger.js'; /** * Circuit breaker states */ export var CircuitState; (function (CircuitState) { CircuitState["CLOSED"] = "CLOSED"; CircuitState["OPEN"] = "OPEN"; CircuitState["HALF_OPEN"] = "HALF_OPEN"; })(CircuitState || (CircuitState = {})); /** * Circuit breaker implementation */ export class CircuitBreaker { state = CircuitState.CLOSED; failureCount = 0; successCount = 0; lastFailureTime; lastSuccessTime; nextAttempt; config; stats = { totalRequests: 0, rejectedRequests: 0, fallbacksExecuted: 0, }; constructor(config = {}) { this.config = { failureThreshold: config.failureThreshold || 5, successThreshold: config.successThreshold || 2, timeout: config.timeout || 60000, // 1 minute monitoringPeriod: config.monitoringPeriod || 60000, // 1 minute fallbackFunction: config.fallbackFunction || (() => { throw new APIError('Circuit breaker is open and no fallback provided', ErrorCode.API_ERROR, 'circuit-breaker'); }), healthCheckFunction: config.healthCheckFunction || (() => Promise.resolve(true)), }; } /** * Execute a function with circuit breaker protection */ execute(fn) { this.stats.totalRequests++; // Check if we should reset failure count based on monitoring period if (this.lastFailureTime && Date.now() - this.lastFailureTime > this.config.monitoringPeriod) { this.failureCount = 0; } switch (this.state) { case CircuitState.OPEN: return this.handleOpenState(); case CircuitState.HALF_OPEN: return this.handleHalfOpenState(fn); case CircuitState.CLOSED: default: return this.handleClosedState(fn); } } /** * Handle request when circuit is open */ async handleOpenState() { const now = Date.now(); // Check if timeout has passed if (this.nextAttempt && now >= this.nextAttempt) { logger.info('Circuit breaker attempting half-open state'); this.state = CircuitState.HALF_OPEN; // Try health check if configured try { const isHealthy = await this.config.healthCheckFunction(); if (isHealthy) { this.successCount = 0; logger.info('Health check passed, circuit breaker now half-open'); } else { this.open(); throw new Error('Health check failed'); } } catch (error) { this.open(); logger.error('Health check failed', error); } } // Circuit is still open, use fallback this.stats.rejectedRequests++; this.stats.fallbacksExecuted++; logger.warn('Circuit breaker open, using fallback'); return this.config.fallbackFunction(); } /** * Handle request when circuit is half-open */ async handleHalfOpenState(fn) { try { const result = await fn(); this.onSuccess(); // Check if we should close the circuit if (this.successCount >= this.config.successThreshold) { this.close(); } return result; } catch (error) { this.onFailure(error); throw error; } } /** * Handle request when circuit is closed */ async handleClosedState(fn) { try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(error); // Check if we should open the circuit if (this.failureCount >= this.config.failureThreshold) { this.open(); } throw error; } } /** * Record successful execution */ onSuccess() { this.failureCount = 0; this.successCount++; this.lastSuccessTime = Date.now(); logger.debug('Circuit breaker success recorded', { state: this.state, successCount: this.successCount, }); } /** * Record failed execution */ onFailure(error) { this.failureCount++; this.successCount = 0; this.lastFailureTime = Date.now(); logger.warn('Circuit breaker failure recorded', { state: this.state, failureCount: this.failureCount, error: error instanceof Error ? error.message : String(error), }); } /** * Open the circuit */ open() { this.state = CircuitState.OPEN; this.nextAttempt = Date.now() + this.config.timeout; logger.error('Circuit breaker opened', { failures: this.failureCount, nextAttempt: new Date(this.nextAttempt), }); } /** * Close the circuit */ close() { this.state = CircuitState.CLOSED; this.failureCount = 0; this.successCount = 0; this.nextAttempt = undefined; logger.info('Circuit breaker closed'); } /** * Get current state */ getState() { return this.state; } /** * Get circuit breaker statistics */ getStats() { return { state: this.state, failures: this.failureCount, successes: this.successCount, lastFailureTime: this.lastFailureTime || 0, lastSuccessTime: this.lastSuccessTime || 0, totalRequests: this.stats.totalRequests, rejectedRequests: this.stats.rejectedRequests, fallbacksExecuted: this.stats.fallbacksExecuted, }; } /** * Reset circuit breaker to initial state */ reset() { this.state = CircuitState.CLOSED; this.failureCount = 0; this.successCount = 0; this.lastFailureTime = undefined; this.lastSuccessTime = undefined; this.nextAttempt = undefined; this.stats = { totalRequests: 0, rejectedRequests: 0, fallbacksExecuted: 0, }; logger.info('Circuit breaker reset'); } /** * Force circuit to open state (for testing/maintenance) */ forceOpen() { this.open(); } /** * Force circuit to closed state (for testing/recovery) */ forceClose() { this.close(); } } /** * Factory function to create configured circuit breakers */ export function createCircuitBreaker(name, config) { logger.info('Creating circuit breaker', { name, config }); return new CircuitBreaker(config); } //# sourceMappingURL=circuitBreaker.js.map