UNPKG

datapilot-cli

Version:

Enterprise-grade streaming multi-format data analysis with comprehensive statistical insights and intelligent relationship detection - supports CSV, JSON, Excel, TSV, Parquet - memory-efficient, cross-platform

341 lines 11.5 kB
"use strict"; /** * Circuit Breaker Pattern Implementation * Prevents cascading failures and enables graceful degradation */ Object.defineProperty(exports, "__esModule", { value: true }); exports.CircuitBreakerManager = exports.CircuitBreakerTimeoutError = exports.CircuitBreakerOpenError = exports.CircuitBreaker = exports.CircuitState = void 0; exports.getGlobalCircuitBreakerManager = getGlobalCircuitBreakerManager; exports.shutdownGlobalCircuitBreakerManager = shutdownGlobalCircuitBreakerManager; const events_1 = require("events"); const perf_hooks_1 = require("perf_hooks"); const logger_1 = require("../utils/logger"); var CircuitState; (function (CircuitState) { CircuitState["CLOSED"] = "closed"; CircuitState["OPEN"] = "open"; CircuitState["HALF_OPEN"] = "half-open"; })(CircuitState || (exports.CircuitState = CircuitState = {})); class CircuitBreaker extends events_1.EventEmitter { state = CircuitState.CLOSED; failureCount = 0; successCount = 0; totalCalls = 0; lastFailureTime = 0; lastSuccessTime = 0; stateChangeTime = Date.now(); recentCalls = []; resetTimer; options; operation; name; constructor(operation, name, options = {}) { super(); this.operation = operation; this.name = name; this.options = { failureThreshold: options.failureThreshold || 5, resetTimeout: options.resetTimeout || 60000, // 1 minute monitoringPeriod: options.monitoringPeriod || 60000, // 1 minute successThreshold: options.successThreshold || 3, timeoutMs: options.timeoutMs || 30000, // 30 seconds volumeThreshold: options.volumeThreshold || 10, }; } /** * Execute the wrapped operation with circuit breaker protection */ async execute(...args) { this.cleanupOldCalls(); if (this.state === CircuitState.OPEN) { this.emit('circuit-open-rejection', { name: this.name, args }); throw new CircuitBreakerOpenError(`Circuit breaker ${this.name} is OPEN`); } const startTime = perf_hooks_1.performance.now(); this.totalCalls++; try { // Add timeout protection const result = await this.executeWithTimeout(args); const executionTime = perf_hooks_1.performance.now() - startTime; this.onSuccess(executionTime); return result; } catch (error) { const executionTime = perf_hooks_1.performance.now() - startTime; this.onFailure(error, executionTime); throw error; } } /** * Execute operation with timeout */ async executeWithTimeout(args) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new CircuitBreakerTimeoutError(`Operation ${this.name} timed out after ${this.options.timeoutMs}ms`)); }, this.options.timeoutMs); this.operation(...args) .then((result) => { clearTimeout(timeout); resolve(result); }) .catch((error) => { clearTimeout(timeout); reject(error); }); }); } /** * Handle successful execution */ onSuccess(executionTime) { this.successCount++; this.lastSuccessTime = Date.now(); this.recentCalls.push({ success: true, timestamp: Date.now() }); this.emit('success', { name: this.name, executionTime, state: this.state, metrics: this.getMetrics(), }); if (this.state === CircuitState.HALF_OPEN) { if (this.successCount >= this.options.successThreshold) { this.transitionTo(CircuitState.CLOSED); } } } /** * Handle failed execution */ onFailure(error, executionTime) { this.failureCount++; this.lastFailureTime = Date.now(); this.recentCalls.push({ success: false, timestamp: Date.now() }); this.emit('failure', { name: this.name, error: error.message, executionTime, state: this.state, metrics: this.getMetrics(), }); if (this.state === CircuitState.CLOSED || this.state === CircuitState.HALF_OPEN) { if (this.shouldOpenCircuit()) { this.transitionTo(CircuitState.OPEN); } } } /** * Determine if circuit should open based on failure criteria */ shouldOpenCircuit() { const recentFailures = this.recentCalls.filter((call) => !call.success).length; const failureRate = this.recentCalls.length > 0 ? recentFailures / this.recentCalls.length : 0; // Need minimum volume and either threshold failures or high failure rate return (this.recentCalls.length >= this.options.volumeThreshold && (recentFailures >= this.options.failureThreshold || failureRate > 0.5)); } /** * Transition circuit to new state */ transitionTo(newState) { const oldState = this.state; this.state = newState; this.stateChangeTime = Date.now(); logger_1.logger.info(`Circuit breaker ${this.name} transitioned from ${oldState} to ${newState}`); this.emit('state-change', { name: this.name, oldState, newState, metrics: this.getMetrics(), }); if (newState === CircuitState.OPEN) { this.scheduleReset(); } else if (newState === CircuitState.CLOSED) { this.reset(); } } /** * Schedule transition to half-open state */ scheduleReset() { if (this.resetTimer) { clearTimeout(this.resetTimer); } this.resetTimer = setTimeout(() => { if (this.state === CircuitState.OPEN) { this.transitionTo(CircuitState.HALF_OPEN); } }, this.options.resetTimeout); } /** * Reset circuit breaker metrics */ reset() { this.failureCount = 0; this.successCount = 0; this.recentCalls = []; if (this.resetTimer) { clearTimeout(this.resetTimer); this.resetTimer = undefined; } } /** * Clean up old call records outside monitoring period */ cleanupOldCalls() { const cutoff = Date.now() - this.options.monitoringPeriod; this.recentCalls = this.recentCalls.filter((call) => call.timestamp > cutoff); } /** * Force circuit to specific state (for testing/recovery) */ forceState(state) { this.transitionTo(state); } /** * Get current circuit breaker metrics */ getMetrics() { this.cleanupOldCalls(); const recentFailures = this.recentCalls.filter((call) => !call.success).length; const failureRate = this.recentCalls.length > 0 ? recentFailures / this.recentCalls.length : 0; return { state: this.state, failureCount: this.failureCount, successCount: this.successCount, totalCalls: this.totalCalls, lastFailureTime: this.lastFailureTime, lastSuccessTime: this.lastSuccessTime, stateChangeTime: this.stateChangeTime, failureRate, }; } /** * Check if circuit is available for requests */ isAvailable() { return this.state !== CircuitState.OPEN; } /** * Get current state */ getState() { return this.state; } /** * Shutdown circuit breaker */ shutdown() { if (this.resetTimer) { clearTimeout(this.resetTimer); this.resetTimer = undefined; } this.emit('shutdown', { name: this.name }); } } exports.CircuitBreaker = CircuitBreaker; /** * Circuit breaker specific error types */ class CircuitBreakerOpenError extends Error { constructor(message) { super(message); this.name = 'CircuitBreakerOpenError'; } } exports.CircuitBreakerOpenError = CircuitBreakerOpenError; class CircuitBreakerTimeoutError extends Error { constructor(message) { super(message); this.name = 'CircuitBreakerTimeoutError'; } } exports.CircuitBreakerTimeoutError = CircuitBreakerTimeoutError; /** * Circuit breaker manager for multiple operations */ class CircuitBreakerManager extends events_1.EventEmitter { breakers = new Map(); /** * Create or get circuit breaker for operation */ getCircuitBreaker(name, operation, options) { let breaker = this.breakers.get(name); if (!breaker) { breaker = new CircuitBreaker(operation, name, options); this.breakers.set(name, breaker); // Forward events breaker.on('state-change', (data) => this.emit('breaker-state-change', data)); breaker.on('failure', (data) => this.emit('breaker-failure', data)); breaker.on('success', (data) => this.emit('breaker-success', data)); } return breaker; } /** * Get all circuit breaker metrics */ getAllMetrics() { const metrics = {}; for (const [name, breaker] of this.breakers) { metrics[name] = breaker.getMetrics(); } return metrics; } /** * Get overall system health */ getSystemHealth() { const metrics = this.getAllMetrics(); const breakers = Object.values(metrics); const openCount = breakers.filter((b) => b.state === CircuitState.OPEN).length; const halfOpenCount = breakers.filter((b) => b.state === CircuitState.HALF_OPEN).length; const closedCount = breakers.filter((b) => b.state === CircuitState.CLOSED).length; const overallHealth = breakers.length > 0 ? (closedCount / breakers.length) * 100 : 100; return { totalBreakers: breakers.length, openBreakers: openCount, halfOpenBreakers: halfOpenCount, closedBreakers: closedCount, overallHealth, }; } /** * Force all circuits to closed state (emergency recovery) */ forceAllClosed() { for (const breaker of this.breakers.values()) { breaker.forceState(CircuitState.CLOSED); } logger_1.logger.warn('Forced all circuit breakers to CLOSED state'); this.emit('emergency-reset'); } /** * Shutdown all circuit breakers */ shutdown() { for (const breaker of this.breakers.values()) { breaker.shutdown(); } this.breakers.clear(); this.emit('shutdown'); } } exports.CircuitBreakerManager = CircuitBreakerManager; /** * Global circuit breaker manager */ let globalCircuitBreakerManager = null; function getGlobalCircuitBreakerManager() { if (!globalCircuitBreakerManager) { globalCircuitBreakerManager = new CircuitBreakerManager(); } return globalCircuitBreakerManager; } function shutdownGlobalCircuitBreakerManager() { if (globalCircuitBreakerManager) { globalCircuitBreakerManager.shutdown(); globalCircuitBreakerManager = null; } } //# sourceMappingURL=circuit-breaker.js.map