agent-team-composer
Version:
Transform README files into GitHub project plans with AI-powered agent teams
142 lines • 4.54 kB
JavaScript
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