@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
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 __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