vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
230 lines (229 loc) • 8.18 kB
JavaScript
import logger from '../logger.js';
export var CircuitState;
(function (CircuitState) {
CircuitState["CLOSED"] = "CLOSED";
CircuitState["OPEN"] = "OPEN";
CircuitState["HALF_OPEN"] = "HALF_OPEN";
})(CircuitState || (CircuitState = {}));
export class OperationCircuitBreaker {
name;
config;
static circuits = new Map();
static DEFAULT_CONFIG = {
failureThreshold: 5,
successThreshold: 3,
timeout: 60000,
operationTimeout: 30000,
monitoringWindow: 100
};
state = CircuitState.CLOSED;
failures = 0;
successes = 0;
lastFailureTime;
lastSuccessTime;
nextAttemptTime;
recentOperations = [];
constructor(name, config = OperationCircuitBreaker.DEFAULT_CONFIG) {
this.name = name;
this.config = config;
}
static getCircuit(name, config) {
if (!this.circuits.has(name)) {
const fullConfig = { ...this.DEFAULT_CONFIG, ...config };
this.circuits.set(name, new OperationCircuitBreaker(name, fullConfig));
}
return this.circuits.get(name);
}
static async safeExecute(operationName, operation, fallback, config) {
const circuit = this.getCircuit(operationName, config);
return circuit.execute(operation, fallback);
}
async execute(operation, fallback) {
const startTime = Date.now();
if (!this.shouldAllowOperation()) {
logger.debug({
circuit: this.name,
state: this.state
}, 'Circuit breaker preventing operation, using fallback');
const fallbackResult = await this.executeFallback(fallback);
return {
success: false,
result: fallbackResult,
usedFallback: true,
circuitState: this.state,
executionTime: Date.now() - startTime
};
}
try {
const result = await this.executeWithTimeout(operation);
this.recordSuccess();
logger.debug({
circuit: this.name,
state: this.state,
executionTime: Date.now() - startTime
}, 'Circuit breaker operation succeeded');
return {
success: true,
result,
usedFallback: false,
circuitState: this.state,
executionTime: Date.now() - startTime
};
}
catch (error) {
this.recordFailure();
logger.warn({
err: error,
circuit: this.name,
state: this.state,
executionTime: Date.now() - startTime
}, 'Circuit breaker operation failed, using fallback');
const fallbackResult = await this.executeFallback(fallback);
return {
success: false,
result: fallbackResult,
error: error,
usedFallback: true,
circuitState: this.state,
executionTime: Date.now() - startTime
};
}
}
shouldAllowOperation() {
const now = Date.now();
switch (this.state) {
case CircuitState.CLOSED:
return true;
case CircuitState.OPEN:
if (this.nextAttemptTime && now >= this.nextAttemptTime) {
this.state = CircuitState.HALF_OPEN;
logger.info({ circuit: this.name }, 'Circuit breaker transitioning to HALF_OPEN');
return true;
}
return false;
case CircuitState.HALF_OPEN:
return true;
default:
return false;
}
}
async executeWithTimeout(operation) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`Operation timeout after ${this.config.operationTimeout}ms`));
}, this.config.operationTimeout);
operation()
.then(result => {
clearTimeout(timeout);
resolve(result);
})
.catch(error => {
clearTimeout(timeout);
reject(error);
});
});
}
async executeFallback(fallback) {
if (typeof fallback === 'function') {
try {
const result = fallback();
return result instanceof Promise ? await result : result;
}
catch (error) {
logger.error({ err: error, circuit: this.name }, 'Fallback execution failed');
throw error;
}
}
return fallback;
}
recordSuccess() {
const now = Date.now();
this.successes++;
this.lastSuccessTime = now;
this.addToRecentOperations(true, now);
if (this.state === CircuitState.HALF_OPEN) {
if (this.successes >= this.config.successThreshold) {
this.state = CircuitState.CLOSED;
this.failures = 0;
logger.info({ circuit: this.name }, 'Circuit breaker closed after successful recovery');
}
}
}
recordFailure() {
const now = Date.now();
this.failures++;
this.lastFailureTime = now;
this.addToRecentOperations(false, now);
if (this.state === CircuitState.CLOSED || this.state === CircuitState.HALF_OPEN) {
const failureRate = this.calculateFailureRate();
if (this.failures >= this.config.failureThreshold || failureRate > 0.5) {
this.state = CircuitState.OPEN;
this.nextAttemptTime = now + this.config.timeout;
logger.warn({
circuit: this.name,
failures: this.failures,
failureRate,
nextAttemptTime: new Date(this.nextAttemptTime).toISOString()
}, 'Circuit breaker opened due to failures');
}
}
}
addToRecentOperations(success, timestamp) {
this.recentOperations.push({ success, timestamp });
if (this.recentOperations.length > this.config.monitoringWindow) {
this.recentOperations = this.recentOperations.slice(-this.config.monitoringWindow);
}
}
calculateFailureRate() {
if (this.recentOperations.length === 0) {
return 0;
}
const failures = this.recentOperations.filter(op => !op.success).length;
return failures / this.recentOperations.length;
}
getStats() {
return {
state: this.state,
failures: this.failures,
successes: this.successes,
totalRequests: this.failures + this.successes,
lastFailureTime: this.lastFailureTime,
lastSuccessTime: this.lastSuccessTime,
nextAttemptTime: this.nextAttemptTime,
failureRate: this.calculateFailureRate()
};
}
reset() {
this.state = CircuitState.CLOSED;
this.failures = 0;
this.successes = 0;
this.lastFailureTime = undefined;
this.lastSuccessTime = undefined;
this.nextAttemptTime = undefined;
this.recentOperations = [];
logger.info({ circuit: this.name }, 'Circuit breaker reset');
}
forceState(state) {
this.state = state;
if (state === CircuitState.OPEN) {
this.nextAttemptTime = Date.now() + this.config.timeout;
}
logger.info({ circuit: this.name, state }, 'Circuit breaker state forced');
}
static getAllStats() {
const stats = {};
for (const [name, circuit] of this.circuits.entries()) {
stats[name] = circuit.getStats();
}
return stats;
}
static resetAll() {
for (const circuit of this.circuits.values()) {
circuit.reset();
}
logger.info('All circuit breakers reset');
}
static removeCircuit(name) {
return this.circuits.delete(name);
}
}