UNPKG

agent-team-composer

Version:

Transform README files into GitHub project plans with AI-powered agent teams

142 lines 4.54 kB
export class CircuitBreaker { options; failures = 0; successes = 0; state = 'closed'; nextAttempt = Date.now(); halfOpenAttempts = 0; constructor(options) { this.options = options; } async execute(fn) { if (this.state === 'open') { if (Date.now() < this.nextAttempt) { throw new Error('Circuit breaker is OPEN - service unavailable'); } this.state = 'half-open'; this.halfOpenAttempts = 0; } if (this.state === 'half-open' && this.halfOpenAttempts >= this.options.halfOpenRequests) { // Enough successful requests in half-open state, close the circuit if (this.successes >= this.options.halfOpenRequests) { this.close(); } else { this.open(); throw new Error('Circuit breaker is OPEN - service still failing'); } } try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } onSuccess() { this.failures = 0; if (this.state === 'half-open') { this.successes++; this.halfOpenAttempts++; if (this.successes >= this.options.halfOpenRequests) { this.close(); } } } onFailure() { this.failures++; this.successes = 0; if (this.failures >= this.options.failureThreshold) { this.open(); } } open() { this.state = 'open'; this.nextAttempt = Date.now() + this.options.resetTimeout; console.warn(`Circuit breaker opened. Will retry after ${new Date(this.nextAttempt).toISOString()}`); } close() { this.state = 'closed'; this.failures = 0; this.successes = 0; console.info('Circuit breaker closed'); } getState() { return this.state; } } export class RateLimiter { options; requests = []; constructor(options) { this.options = options; } async execute(fn) { const now = Date.now(); // Remove old requests outside the window this.requests = this.requests.filter(time => now - time < this.options.windowMs); // Check if we're at the limit if (this.requests.length >= this.options.maxRequests) { const oldestRequest = this.requests[0]; const waitTime = this.options.windowMs - (now - oldestRequest); throw new Error(`Rate limit exceeded. Try again in ${Math.ceil(waitTime / 1000)} seconds`); } // Add current request this.requests.push(now); // Execute the function return await fn(); } getRemainingRequests() { const now = Date.now(); this.requests = this.requests.filter(time => now - time < this.options.windowMs); return Math.max(0, this.options.maxRequests - this.requests.length); } } // Singleton instances for global rate limiting export class ResilienceManager { static instance; circuitBreakers = new Map(); rateLimiters = new Map(); static getInstance() { if (!ResilienceManager.instance) { ResilienceManager.instance = new ResilienceManager(); } return ResilienceManager.instance; } getCircuitBreaker(key, options) { if (!this.circuitBreakers.has(key)) { this.circuitBreakers.set(key, new CircuitBreaker(options || { failureThreshold: 5, resetTimeout: 60000, // 1 minute halfOpenRequests: 3 })); } return this.circuitBreakers.get(key); } getRateLimiter(key, options) { if (!this.rateLimiters.has(key)) { this.rateLimiters.set(key, new RateLimiter(options || { maxRequests: 10, windowMs: 60000 // 1 minute })); } return this.rateLimiters.get(key); } getStatus() { const status = { circuitBreakers: {}, rateLimiters: {} }; this.circuitBreakers.forEach((cb, key) => { status.circuitBreakers[key] = cb.getState(); }); this.rateLimiters.forEach((rl, key) => { status.rateLimiters[key] = rl.getRemainingRequests(); }); return status; } } //# sourceMappingURL=resilience.js.map