@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
249 lines • 8.41 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CircuitBreakerRegistry = exports.CircuitBreakerFactory = exports.CircuitBreakerOpenError = exports.CircuitBreaker = void 0;
const common_1 = require("@nestjs/common");
class CircuitBreaker {
constructor(options) {
this.options = options;
this.logger = new common_1.Logger(`CircuitBreaker:${this.options.name || 'default'}`);
this.state = 'closed';
this.failures = 0;
this.successes = 0;
this.lastFailureTime = 0;
this.lastSuccessTime = 0;
this.nextAttempt = 0;
this.logger.log(`Circuit breaker initialized: ${JSON.stringify(options)}`);
}
async execute(operation) {
const currentTime = Date.now();
if (this.state === 'open') {
if (currentTime < this.nextAttempt) {
throw new CircuitBreakerOpenError(`Circuit breaker is open. Next attempt in ${this.nextAttempt - currentTime}ms`);
}
else {
this.state = 'half-open';
this.options.onHalfOpen?.(this);
this.logger.warn(`Circuit breaker transitioning to half-open state`);
}
}
try {
const result = await Promise.race([
operation(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timeout')), this.options.timeout))
]);
this.onSuccess();
return result;
}
catch (error) {
this.onFailure(error);
throw error;
}
}
onSuccess() {
this.successes++;
this.lastSuccessTime = Date.now();
if (this.state === 'half-open') {
this.state = 'closed';
this.failures = 0;
this.options.onClose?.(this);
this.logger.log(`Circuit breaker closed after successful operation`);
}
else if (this.state === 'closed') {
this.failures = Math.max(0, this.failures - 1);
}
}
onFailure(error) {
this.failures++;
this.lastFailureTime = Date.now();
if (this.state === 'half-open') {
this.state = 'open';
this.nextAttempt = Date.now() + this.options.resetTimeout;
this.options.onOpen?.(this);
this.logger.warn(`Circuit breaker opened from half-open state due to failure: ${error.message}`);
}
else if (this.failures >= this.options.failureThreshold) {
this.state = 'open';
this.nextAttempt = Date.now() + this.options.resetTimeout;
this.options.onOpen?.(this);
this.logger.warn(`Circuit breaker opened due to ${this.failures} failures. Error: ${error.message}`);
}
}
getState() {
return {
state: this.state,
failures: this.failures,
successes: this.successes,
lastFailureTime: this.lastFailureTime,
lastSuccessTime: this.lastSuccessTime,
nextAttempt: this.nextAttempt
};
}
getFailureCount() {
return this.failures;
}
getSuccessCount() {
return this.successes;
}
getLastFailureTime() {
return this.lastFailureTime;
}
getLastSuccessTime() {
return this.lastSuccessTime;
}
getCurrentState() {
return this.state;
}
isOpen() {
return this.state === 'open';
}
isClosed() {
return this.state === 'closed';
}
isHalfOpen() {
return this.state === 'half-open';
}
forceOpen() {
this.state = 'open';
this.nextAttempt = Date.now() + this.options.resetTimeout;
this.options.onOpen?.(this);
this.logger.warn('Circuit breaker force opened');
}
forceClose() {
this.state = 'closed';
this.failures = 0;
this.options.onClose?.(this);
this.logger.log('Circuit breaker force closed');
}
reset() {
this.state = 'closed';
this.failures = 0;
this.successes = 0;
this.lastFailureTime = 0;
this.lastSuccessTime = 0;
this.nextAttempt = 0;
this.logger.log('Circuit breaker reset');
}
getHealthInfo() {
const total = this.failures + this.successes;
const failureRate = total > 0 ? (this.failures / total) * 100 : 0;
const successRate = total > 0 ? (this.successes / total) * 100 : 0;
const uptime = this.lastSuccessTime > 0 ? Date.now() - this.lastSuccessTime : 0;
return {
isHealthy: this.state === 'closed',
state: this.state,
failureRate,
successRate,
uptime
};
}
}
exports.CircuitBreaker = CircuitBreaker;
class CircuitBreakerOpenError extends Error {
constructor(message) {
super(message);
this.name = 'CircuitBreakerOpenError';
}
}
exports.CircuitBreakerOpenError = CircuitBreakerOpenError;
class CircuitBreakerFactory {
static createForStorage(name = 'storage') {
return new CircuitBreaker({
name,
failureThreshold: 5,
timeout: 30000,
resetTimeout: 60000,
onOpen: (breaker) => console.warn(`Storage circuit breaker opened: ${name}`),
onHalfOpen: (breaker) => console.info(`Storage circuit breaker half-open: ${name}`),
onClose: (breaker) => console.info(`Storage circuit breaker closed: ${name}`)
});
}
static createForDevTools(name = 'devtools') {
return new CircuitBreaker({
name,
failureThreshold: 3,
timeout: 15000,
resetTimeout: 30000,
onOpen: (breaker) => console.warn(`DevTools circuit breaker opened: ${name}`),
onHalfOpen: (breaker) => console.info(`DevTools circuit breaker half-open: ${name}`),
onClose: (breaker) => console.info(`DevTools circuit breaker closed: ${name}`)
});
}
static createForNetwork(name = 'network') {
return new CircuitBreaker({
name,
failureThreshold: 10,
timeout: 5000,
resetTimeout: 15000,
onOpen: (breaker) => console.warn(`Network circuit breaker opened: ${name}`),
onHalfOpen: (breaker) => console.info(`Network circuit breaker half-open: ${name}`),
onClose: (breaker) => console.info(`Network circuit breaker closed: ${name}`)
});
}
static createCustom(options) {
return new CircuitBreaker(options);
}
}
exports.CircuitBreakerFactory = CircuitBreakerFactory;
class CircuitBreakerRegistry {
constructor() {
this.logger = new common_1.Logger('CircuitBreakerRegistry');
this.breakers = new Map();
}
register(name, breaker) {
this.breakers.set(name, breaker);
this.logger.log(`Circuit breaker registered: ${name}`);
}
get(name) {
return this.breakers.get(name);
}
getAll() {
return new Map(this.breakers);
}
getAllStates() {
const states = {};
for (const [name, breaker] of this.breakers) {
states[name] = breaker.getState();
}
return states;
}
getHealthStatus() {
let healthy = 0;
let unhealthy = 0;
const details = {};
for (const [name, breaker] of this.breakers) {
const health = breaker.getHealthInfo();
details[name] = health;
if (health.isHealthy) {
healthy++;
}
else {
unhealthy++;
}
}
return {
healthy,
unhealthy,
total: this.breakers.size,
details
};
}
resetAll() {
for (const [name, breaker] of this.breakers) {
breaker.reset();
this.logger.log(`Reset circuit breaker: ${name}`);
}
}
remove(name) {
const removed = this.breakers.delete(name);
if (removed) {
this.logger.log(`Circuit breaker removed: ${name}`);
}
return removed;
}
clear() {
this.breakers.clear();
this.logger.log('All circuit breakers cleared');
}
}
exports.CircuitBreakerRegistry = CircuitBreakerRegistry;
//# sourceMappingURL=circuit-breaker.js.map