Skip to main content
Glama

Visa Design System MCP Server

by MarySuneela
  • Apple
circuit-breaker.js12.1 kB
/** * Circuit breaker pattern implementation for handling data source failures */ import { EventEmitter } from 'events'; import { logger } from './logger.js'; import { AppError, ErrorCode, ServiceUnavailableError, TimeoutError } from './errors.js'; export var CircuitState; (function (CircuitState) { CircuitState["CLOSED"] = "CLOSED"; CircuitState["OPEN"] = "OPEN"; CircuitState["HALF_OPEN"] = "HALF_OPEN"; // Testing if service is back })(CircuitState || (CircuitState = {})); /** * Circuit breaker implementation for protecting against cascading failures */ export class CircuitBreaker extends EventEmitter { config; state = CircuitState.CLOSED; failureCount = 0; successCount = 0; lastFailureTime; lastSuccessTime; nextAttemptTime; halfOpenCalls = 0; totalRequests = 0; totalFailures = 0; totalSuccesses = 0; monitoringWindow = []; constructor(config) { super(); this.config = config; logger.info(`Circuit breaker "${config.name}" initialized`, { service: 'CircuitBreaker', circuitName: config.name, config }); } /** * Execute a function with circuit breaker protection */ async execute(fn, context) { this.totalRequests++; // Check if circuit is open if (this.state === CircuitState.OPEN) { if (this.shouldAttemptReset()) { this.moveToHalfOpen(); } else { const error = new ServiceUnavailableError(this.config.name, `Circuit breaker is OPEN. Next attempt at ${this.nextAttemptTime?.toISOString()}`, { ...context, circuitName: this.config.name, circuitState: this.state }); this.emit('callRejected', { error, stats: this.getStats() }); throw error; } } // Check half-open state limits if (this.state === CircuitState.HALF_OPEN) { if (this.halfOpenCalls >= this.config.halfOpenMaxCalls) { const error = new ServiceUnavailableError(this.config.name, 'Circuit breaker is HALF_OPEN and at call limit', { ...context, circuitName: this.config.name, circuitState: this.state }); this.emit('callRejected', { error, stats: this.getStats() }); throw error; } this.halfOpenCalls++; } const startTime = Date.now(); try { // Execute with timeout const result = await this.executeWithTimeout(fn); const duration = Date.now() - startTime; this.onSuccess(duration, context); return result; } catch (error) { const duration = Date.now() - startTime; this.onFailure(error, duration, context); throw error; } } /** * Execute function with timeout */ async executeWithTimeout(fn) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new TimeoutError(this.config.name, this.config.requestTimeout, { circuitName: this.config.name })); }, this.config.requestTimeout); fn() .then(result => { clearTimeout(timeoutId); resolve(result); }) .catch(error => { clearTimeout(timeoutId); reject(error); }); }); } /** * Handle successful execution */ onSuccess(duration, context) { this.successCount++; this.totalSuccesses++; this.lastSuccessTime = new Date(); this.addToMonitoringWindow(true); logger.debug(`Circuit breaker "${this.config.name}" - Success`, { service: 'CircuitBreaker', circuitName: this.config.name, duration, state: this.state, ...context }); if (this.state === CircuitState.HALF_OPEN) { // If we've had enough successful calls, close the circuit if (this.successCount >= this.config.halfOpenMaxCalls) { this.moveToClosed(); } } this.emit('callSuccess', { duration, stats: this.getStats() }); } /** * Handle failed execution */ onFailure(error, duration, context) { this.failureCount++; this.totalFailures++; this.lastFailureTime = new Date(); this.addToMonitoringWindow(false); const appError = error instanceof AppError ? error : new AppError({ code: ErrorCode.SERVICE_ERROR, message: error instanceof Error ? error.message : 'Unknown error', cause: error instanceof Error ? error : undefined, context: { ...context, circuitName: this.config.name } }); logger.warn(`Circuit breaker "${this.config.name}" - Failure`, { service: 'CircuitBreaker', circuitName: this.config.name, duration, state: this.state, failureCount: this.failureCount, error: appError.message, ...context }); // Check if we should open the circuit if (this.shouldOpenCircuit()) { this.moveToOpen(); } this.emit('callFailure', { error: appError, duration, stats: this.getStats() }); } /** * Add result to monitoring window */ addToMonitoringWindow(success) { const now = new Date(); this.monitoringWindow.push({ timestamp: now, success }); // Remove old entries outside monitoring period const cutoff = new Date(now.getTime() - this.config.monitoringPeriod); this.monitoringWindow = this.monitoringWindow.filter(entry => entry.timestamp > cutoff); } /** * Check if circuit should be opened */ shouldOpenCircuit() { if (this.state === CircuitState.OPEN) { return false; } // Check failure threshold within monitoring period const recentFailures = this.monitoringWindow.filter(entry => !entry.success).length; return recentFailures >= this.config.failureThreshold; } /** * Check if we should attempt to reset from open state */ shouldAttemptReset() { if (!this.nextAttemptTime) { return true; } return new Date() >= this.nextAttemptTime; } /** * Move circuit to CLOSED state */ moveToClosed() { const previousState = this.state; this.state = CircuitState.CLOSED; this.failureCount = 0; this.successCount = 0; this.halfOpenCalls = 0; this.nextAttemptTime = undefined; logger.info(`Circuit breaker "${this.config.name}" moved to CLOSED`, { service: 'CircuitBreaker', circuitName: this.config.name, previousState, newState: this.state }); this.emit('stateChange', { from: previousState, to: this.state, stats: this.getStats() }); } /** * Move circuit to OPEN state */ moveToOpen() { const previousState = this.state; this.state = CircuitState.OPEN; this.nextAttemptTime = new Date(Date.now() + this.config.recoveryTimeout); this.halfOpenCalls = 0; logger.warn(`Circuit breaker "${this.config.name}" moved to OPEN`, { service: 'CircuitBreaker', circuitName: this.config.name, previousState, newState: this.state, nextAttemptTime: this.nextAttemptTime.toISOString(), failureCount: this.failureCount }); this.emit('stateChange', { from: previousState, to: this.state, stats: this.getStats() }); } /** * Move circuit to HALF_OPEN state */ moveToHalfOpen() { const previousState = this.state; this.state = CircuitState.HALF_OPEN; this.halfOpenCalls = 0; this.successCount = 0; this.failureCount = 0; logger.info(`Circuit breaker "${this.config.name}" moved to HALF_OPEN`, { service: 'CircuitBreaker', circuitName: this.config.name, previousState, newState: this.state }); this.emit('stateChange', { from: previousState, to: this.state, stats: this.getStats() }); } /** * Get current circuit breaker statistics */ getStats() { return { state: this.state, failureCount: this.failureCount, successCount: this.successCount, lastFailureTime: this.lastFailureTime, lastSuccessTime: this.lastSuccessTime, nextAttemptTime: this.nextAttemptTime, totalRequests: this.totalRequests, totalFailures: this.totalFailures, totalSuccesses: this.totalSuccesses }; } /** * Get current state */ getState() { return this.state; } /** * Force circuit to specific state (for testing) */ forceState(state) { const previousState = this.state; this.state = state; if (state === CircuitState.CLOSED) { this.moveToClosed(); } else if (state === CircuitState.OPEN) { this.moveToOpen(); } else if (state === CircuitState.HALF_OPEN) { this.moveToHalfOpen(); } } /** * Reset circuit breaker statistics */ reset() { this.state = CircuitState.CLOSED; this.failureCount = 0; this.successCount = 0; this.halfOpenCalls = 0; this.lastFailureTime = undefined; this.lastSuccessTime = undefined; this.nextAttemptTime = undefined; this.totalRequests = 0; this.totalFailures = 0; this.totalSuccesses = 0; this.monitoringWindow = []; logger.info(`Circuit breaker "${this.config.name}" reset`, { service: 'CircuitBreaker', circuitName: this.config.name }); this.emit('reset', { stats: this.getStats() }); } } /** * Circuit breaker manager for handling multiple circuit breakers */ export class CircuitBreakerManager { breakers = new Map(); static instance; /** * Get singleton instance */ static getInstance() { if (!CircuitBreakerManager.instance) { CircuitBreakerManager.instance = new CircuitBreakerManager(); } return CircuitBreakerManager.instance; } /** * Create or get circuit breaker */ getCircuitBreaker(config) { if (!this.breakers.has(config.name)) { const breaker = new CircuitBreaker(config); this.breakers.set(config.name, breaker); // Log state changes breaker.on('stateChange', ({ from, to, stats }) => { logger.info(`Circuit breaker state change: ${config.name}`, { service: 'CircuitBreakerManager', circuitName: config.name, from, to, stats }); }); } return this.breakers.get(config.name); } /** * Get all circuit breaker stats */ getAllStats() { const stats = {}; for (const [name, breaker] of this.breakers) { stats[name] = breaker.getStats(); } return stats; } /** * Reset all circuit breakers */ resetAll() { for (const breaker of this.breakers.values()) { breaker.reset(); } logger.info('All circuit breakers reset', { service: 'CircuitBreakerManager', breakerCount: this.breakers.size }); } } // Export singleton instance export const circuitBreakerManager = CircuitBreakerManager.getInstance(); //# sourceMappingURL=circuit-breaker.js.map

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MarySuneela/mcp-vpds'

If you have feedback or need assistance with the MCP directory API, please join our Discord server