UNPKG

@ahmedhegazee/nestjs-telescope

Version:

Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling

327 lines 11.9 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var CircuitBreakerService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.CircuitBreakerService = exports.CircuitState = void 0; const common_1 = require("@nestjs/common"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); var CircuitState; (function (CircuitState) { CircuitState["CLOSED"] = "closed"; CircuitState["OPEN"] = "open"; CircuitState["HALF_OPEN"] = "half_open"; })(CircuitState || (exports.CircuitState = CircuitState = {})); let CircuitBreakerService = CircuitBreakerService_1 = class CircuitBreakerService { constructor() { this.logger = new common_1.Logger(CircuitBreakerService_1.name); this.circuits = new Map(); this.destroy$ = new rxjs_1.Subject(); this.startMonitoring(); } createCircuit(name, config = {}) { const defaultConfig = { failureThreshold: 5, timeoutThreshold: 5000, resetTimeout: 60000, halfOpenMaxCalls: 3, successThreshold: 2, monitoringInterval: 30000 }; const circuit = new Circuit(name, { ...defaultConfig, ...config }); this.circuits.set(name, circuit); this.logger.log(`Created circuit breaker: ${name}`); return name; } async execute(circuitName, operation, fallback) { const circuit = this.circuits.get(circuitName); if (!circuit) { throw new Error(`Circuit breaker not found: ${circuitName}`); } return circuit.execute(operation, fallback); } getStats(circuitName) { const circuit = this.circuits.get(circuitName); return circuit ? circuit.getStats() : null; } getAllStats() { const stats = new Map(); this.circuits.forEach((circuit, name) => { stats.set(name, circuit.getStats()); }); return stats; } forceOpen(circuitName) { const circuit = this.circuits.get(circuitName); if (circuit) { circuit.forceOpen(); this.logger.warn(`Circuit breaker manually opened: ${circuitName}`); return true; } return false; } forceClose(circuitName) { const circuit = this.circuits.get(circuitName); if (circuit) { circuit.forceClose(); this.logger.log(`Circuit breaker manually closed: ${circuitName}`); return true; } return false; } reset(circuitName) { const circuit = this.circuits.get(circuitName); if (circuit) { circuit.reset(); this.logger.log(`Circuit breaker reset: ${circuitName}`); return true; } return false; } getCircuitNames() { return Array.from(this.circuits.keys()); } removeCircuit(circuitName) { const removed = this.circuits.delete(circuitName); if (removed) { this.logger.log(`Removed circuit breaker: ${circuitName}`); } return removed; } startMonitoring() { (0, rxjs_1.interval)(10000) .pipe((0, operators_1.takeUntil)(this.destroy$)) .subscribe(() => { this.circuits.forEach((circuit, name) => { const stats = circuit.getStats(); if (stats.state === CircuitState.OPEN) { this.logger.warn(`Circuit breaker ${name} is OPEN - Failure rate: ${stats.failureRate.toFixed(2)}%`); } }); }); } onDestroy() { this.destroy$.next(); this.destroy$.complete(); } }; exports.CircuitBreakerService = CircuitBreakerService; exports.CircuitBreakerService = CircuitBreakerService = CircuitBreakerService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", []) ], CircuitBreakerService); class Circuit { constructor(name, config) { this.name = name; this.config = config; this.state = CircuitState.CLOSED; this.failureCount = 0; this.successCount = 0; this.totalRequests = 0; this.lastFailureTime = null; this.lastSuccessTime = null; this.halfOpenCallCount = 0; this.responseTimes = []; this.timeoutCount = 0; this.startTime = Date.now(); } async execute(operation, fallback) { const startTime = Date.now(); this.totalRequests++; if (this.shouldReject()) { const executionTime = Date.now() - startTime; if (fallback) { try { const data = await fallback(); return { success: true, data, fromCache: true, executionTime, circuitState: this.state }; } catch (fallbackError) { return { success: false, error: fallbackError instanceof Error ? fallbackError : new Error('Fallback failed'), executionTime, circuitState: this.state }; } } return { success: false, error: new Error(`Circuit breaker ${this.name} is ${this.state}`), executionTime, circuitState: this.state }; } try { const result = await this.executeWithTimeout(operation); const executionTime = Date.now() - startTime; this.onSuccess(executionTime); return { success: true, data: result, executionTime, circuitState: this.state }; } catch (error) { const executionTime = Date.now() - startTime; const isTimeout = error instanceof Error && error.message.includes('timeout'); this.onFailure(isTimeout); if (fallback) { try { const data = await fallback(); return { success: true, data, error: error instanceof Error ? error : new Error('Unknown error'), fromCache: true, executionTime, circuitState: this.state }; } catch (fallbackError) { return { success: false, error: fallbackError instanceof Error ? fallbackError : new Error('Fallback failed'), executionTime, circuitState: this.state }; } } return { success: false, error: error instanceof Error ? error : new Error('Unknown error'), executionTime, circuitState: this.state }; } } async executeWithTimeout(operation) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`Operation timeout after ${this.config.timeoutThreshold}ms`)); }, this.config.timeoutThreshold); operation() .then((result) => { clearTimeout(timeoutId); resolve(result); }) .catch((error) => { clearTimeout(timeoutId); reject(error); }); }); } shouldReject() { switch (this.state) { case CircuitState.CLOSED: return false; case CircuitState.OPEN: if (this.lastFailureTime && Date.now() - this.lastFailureTime.getTime() >= this.config.resetTimeout) { this.state = CircuitState.HALF_OPEN; this.halfOpenCallCount = 0; return false; } return true; case CircuitState.HALF_OPEN: if (this.halfOpenCallCount >= this.config.halfOpenMaxCalls) { return true; } this.halfOpenCallCount++; return false; default: return false; } } onSuccess(responseTime) { this.successCount++; this.lastSuccessTime = new Date(); this.responseTimes.push(responseTime); if (this.responseTimes.length > 100) { this.responseTimes.shift(); } switch (this.state) { case CircuitState.HALF_OPEN: if (this.successCount >= this.config.successThreshold) { this.state = CircuitState.CLOSED; this.failureCount = 0; this.halfOpenCallCount = 0; } break; case CircuitState.CLOSED: this.failureCount = 0; break; } } onFailure(isTimeout) { this.failureCount++; this.lastFailureTime = new Date(); if (isTimeout) { this.timeoutCount++; } switch (this.state) { case CircuitState.CLOSED: if (this.failureCount >= this.config.failureThreshold) { this.state = CircuitState.OPEN; } break; case CircuitState.HALF_OPEN: this.state = CircuitState.OPEN; this.halfOpenCallCount = 0; break; } } forceOpen() { this.state = CircuitState.OPEN; this.lastFailureTime = new Date(); } forceClose() { this.state = CircuitState.CLOSED; this.failureCount = 0; this.halfOpenCallCount = 0; } reset() { this.state = CircuitState.CLOSED; this.failureCount = 0; this.successCount = 0; this.halfOpenCallCount = 0; this.responseTimes = []; this.timeoutCount = 0; this.lastFailureTime = null; this.lastSuccessTime = null; } getStats() { const uptime = Date.now() - this.startTime; const failureRate = this.totalRequests > 0 ? (this.failureCount / this.totalRequests) * 100 : 0; const averageResponseTime = this.responseTimes.length > 0 ? this.responseTimes.reduce((sum, time) => sum + time, 0) / this.responseTimes.length : 0; return { state: this.state, failureCount: this.failureCount, successCount: this.successCount, totalRequests: this.totalRequests, lastFailureTime: this.lastFailureTime, lastSuccessTime: this.lastSuccessTime, uptime, failureRate, averageResponseTime, timeoutCount: this.timeoutCount }; } } //# sourceMappingURL=circuit-breaker.service.js.map