@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
219 lines • 9.96 kB
JavaScript
"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 EnhancedCircuitBreakerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnhancedCircuitBreakerService = exports.CircuitBreakerState = void 0;
const common_1 = require("@nestjs/common");
var CircuitBreakerState;
(function (CircuitBreakerState) {
CircuitBreakerState["CLOSED"] = "CLOSED";
CircuitBreakerState["OPEN"] = "OPEN";
CircuitBreakerState["HALF_OPEN"] = "HALF_OPEN";
})(CircuitBreakerState || (exports.CircuitBreakerState = CircuitBreakerState = {}));
let EnhancedCircuitBreakerService = EnhancedCircuitBreakerService_1 = class EnhancedCircuitBreakerService {
constructor() {
this.logger = new common_1.Logger(EnhancedCircuitBreakerService_1.name);
this.circuitBreakers = new Map();
this.requestHistory = new Map();
this.defaultConfig = {
failureThreshold: 5,
recoveryTimeout: 30000,
monitoringWindow: 60000,
minimumRequests: 3,
successThreshold: 3
};
}
async execute(operationName, operation, config = {}) {
const finalConfig = { ...this.defaultConfig, ...config };
const stats = this.getOrCreateStats(operationName);
if (!this.canExecute(operationName, finalConfig)) {
throw new Error(`Circuit breaker is OPEN for operation: ${operationName}`);
}
const startTime = Date.now();
try {
const result = await operation();
this.recordSuccess(operationName, finalConfig);
this.logger.debug(`Circuit breaker operation succeeded: ${operationName} (${Date.now() - startTime}ms)`);
return result;
}
catch (error) {
this.recordFailure(operationName, finalConfig);
this.logger.warn(`Circuit breaker operation failed: ${operationName} (${Date.now() - startTime}ms)`, error.message);
throw error;
}
}
async executeWithFallback(operationName, operation, fallback, config = {}) {
try {
return await this.execute(operationName, operation, config);
}
catch (error) {
this.logger.warn(`Primary operation failed, executing fallback: ${operationName}`, error.message);
try {
return await fallback();
}
catch (fallbackError) {
this.logger.error(`Both primary and fallback operations failed: ${operationName}`, fallbackError.message);
throw fallbackError;
}
}
}
canExecute(operationName, config) {
const stats = this.getOrCreateStats(operationName);
const now = Date.now();
switch (stats.state) {
case CircuitBreakerState.CLOSED:
return true;
case CircuitBreakerState.OPEN:
if (stats.nextAttemptTime && now >= stats.nextAttemptTime) {
stats.state = CircuitBreakerState.HALF_OPEN;
stats.successCount = 0;
stats.failureCount = 0;
this.logger.debug(`Circuit breaker moved to HALF_OPEN: ${operationName}`);
return true;
}
return false;
case CircuitBreakerState.HALF_OPEN:
return true;
default:
return false;
}
}
getStats(operationName) {
return this.circuitBreakers.get(operationName) || null;
}
getAllStats() {
const stats = {};
for (const [name, stat] of this.circuitBreakers.entries()) {
stats[name] = { ...stat };
}
return stats;
}
reset(operationName) {
const stats = this.circuitBreakers.get(operationName);
if (stats) {
stats.state = CircuitBreakerState.CLOSED;
stats.failureCount = 0;
stats.successCount = 0;
stats.totalRequests = 0;
stats.lastFailureTime = null;
stats.lastSuccessTime = null;
stats.nextAttemptTime = null;
this.requestHistory.delete(operationName);
this.logger.debug(`Circuit breaker reset: ${operationName}`);
}
}
forceOpen(operationName, recoveryTimeout) {
const stats = this.getOrCreateStats(operationName);
stats.state = CircuitBreakerState.OPEN;
stats.nextAttemptTime = Date.now() + (recoveryTimeout || this.defaultConfig.recoveryTimeout);
this.logger.warn(`Circuit breaker forced OPEN: ${operationName}`);
}
forceClose(operationName) {
const stats = this.getOrCreateStats(operationName);
stats.state = CircuitBreakerState.CLOSED;
stats.failureCount = 0;
stats.nextAttemptTime = null;
this.logger.debug(`Circuit breaker forced CLOSED: ${operationName}`);
}
getOrCreateStats(operationName) {
let stats = this.circuitBreakers.get(operationName);
if (!stats) {
stats = {
state: CircuitBreakerState.CLOSED,
failureCount: 0,
successCount: 0,
totalRequests: 0,
lastFailureTime: null,
lastSuccessTime: null,
nextAttemptTime: null
};
this.circuitBreakers.set(operationName, stats);
this.requestHistory.set(operationName, []);
}
return stats;
}
recordSuccess(operationName, config) {
const stats = this.getOrCreateStats(operationName);
const history = this.requestHistory.get(operationName);
const now = Date.now();
stats.successCount++;
stats.totalRequests++;
stats.lastSuccessTime = now;
history.push({ timestamp: now, success: true });
this.cleanupHistory(operationName, config.monitoringWindow);
switch (stats.state) {
case CircuitBreakerState.HALF_OPEN:
if (stats.successCount >= config.successThreshold) {
stats.state = CircuitBreakerState.CLOSED;
stats.failureCount = 0;
stats.successCount = 0;
this.logger.debug(`Circuit breaker moved to CLOSED: ${operationName}`);
}
break;
case CircuitBreakerState.OPEN:
stats.state = CircuitBreakerState.CLOSED;
stats.failureCount = 0;
this.logger.debug(`Circuit breaker unexpectedly recovered to CLOSED: ${operationName}`);
break;
}
}
recordFailure(operationName, config) {
const stats = this.getOrCreateStats(operationName);
const history = this.requestHistory.get(operationName);
const now = Date.now();
stats.failureCount++;
stats.totalRequests++;
stats.lastFailureTime = now;
history.push({ timestamp: now, success: false });
this.cleanupHistory(operationName, config.monitoringWindow);
const recentRequests = this.getRecentRequests(operationName, config.monitoringWindow);
const recentFailures = recentRequests.filter(r => !r.success).length;
if (recentRequests.length >= config.minimumRequests &&
recentFailures >= config.failureThreshold) {
stats.state = CircuitBreakerState.OPEN;
stats.nextAttemptTime = now + config.recoveryTimeout;
this.logger.warn(`Circuit breaker opened for ${operationName}: ${recentFailures} failures in ${recentRequests.length} requests`);
}
if (stats.state === CircuitBreakerState.HALF_OPEN) {
stats.state = CircuitBreakerState.OPEN;
stats.nextAttemptTime = now + config.recoveryTimeout;
this.logger.warn(`Circuit breaker re-opened from HALF_OPEN: ${operationName}`);
}
}
getRecentRequests(operationName, windowMs) {
const history = this.requestHistory.get(operationName) || [];
const cutoff = Date.now() - windowMs;
return history.filter(request => request.timestamp >= cutoff);
}
cleanupHistory(operationName, windowMs) {
const history = this.requestHistory.get(operationName);
if (!history)
return;
const cutoff = Date.now() - windowMs;
const recentHistory = history.filter(request => request.timestamp >= cutoff);
if (recentHistory.length > 1000) {
recentHistory.splice(0, recentHistory.length - 1000);
}
this.requestHistory.set(operationName, recentHistory);
}
getFailureRate(operationName, windowMs = this.defaultConfig.monitoringWindow) {
const recentRequests = this.getRecentRequests(operationName, windowMs);
if (recentRequests.length === 0)
return 0;
const failures = recentRequests.filter(r => !r.success).length;
return failures / recentRequests.length;
}
getSuccessRate(operationName, windowMs = this.defaultConfig.monitoringWindow) {
return 1 - this.getFailureRate(operationName, windowMs);
}
};
exports.EnhancedCircuitBreakerService = EnhancedCircuitBreakerService;
exports.EnhancedCircuitBreakerService = EnhancedCircuitBreakerService = EnhancedCircuitBreakerService_1 = __decorate([
(0, common_1.Injectable)()
], EnhancedCircuitBreakerService);
//# sourceMappingURL=enhanced-circuit-breaker.service.js.map