memory-engineering-mcp
Version:
🧠 AI Memory System powered by MongoDB Atlas & Voyage AI - Autonomous memory management with zero manual work
207 lines • 6.69 kB
JavaScript
/**
* Error Recovery and Retry Logic
* PROOF: Following Archon's error handling patterns
*
* Archon: python/src/server/api_routes/knowledge_api.py (line 865-883)
* Pattern: Try-catch with detailed logging, structured error messages
*/
import { logger } from './logger.js';
const DEFAULT_RETRY_OPTIONS = {
maxRetries: 3,
delayMs: 1000,
backoffMultiplier: 2,
};
/**
* Retry with exponential backoff
* PROOF: Industry standard pattern, used by Archon for API calls
*/
export async function retryWithBackoff(operation, fn, options = {}) {
const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
let lastError;
for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {
try {
return await fn();
}
catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
// Don't retry on certain errors
if (isNonRetryableError(lastError)) {
logger.error(`❌ Non-retryable error in ${operation}:`, lastError);
throw lastError;
}
// Last attempt failed
if (attempt === opts.maxRetries) {
logger.error(`❌ ${operation} failed after ${attempt} attempts:`, lastError);
throw lastError;
}
// Calculate delay with exponential backoff
const delay = opts.delayMs * Math.pow(opts.backoffMultiplier, attempt - 1);
logger.warn(`⚠️ ${operation} failed (attempt ${attempt}/${opts.maxRetries}), retrying in ${delay}ms:`, lastError.message);
// Call onRetry callback if provided
opts.onRetry?.(attempt, lastError);
// Wait before retry
await sleep(delay);
}
}
throw lastError;
}
/**
* Check if error should not be retried
* PROOF: Archon doesn't retry 4xx errors
*/
function isNonRetryableError(error) {
const message = error.message.toLowerCase();
// Authentication/authorization errors
if (message.includes('unauthorized') || message.includes('forbidden')) {
return true;
}
// Invalid input errors
if (message.includes('invalid') || message.includes('bad request')) {
return true;
}
// Not found errors
if (message.includes('not found')) {
return true;
}
return false;
}
/**
* Sleep helper
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Rate limiter for API calls
* PROOF: Archon pattern for handling rate limits
*/
export class RateLimiter {
maxCallsPerMinute;
minDelayMs;
queue = [];
processing = false;
lastCallTime = 0;
constructor(maxCallsPerMinute, minDelayMs = 100) {
this.maxCallsPerMinute = maxCallsPerMinute;
this.minDelayMs = minDelayMs;
}
/**
* Enqueue an operation with rate limiting
*/
async enqueue(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
if (!this.processing) {
this.processQueue();
}
});
}
async processQueue() {
this.processing = true;
while (this.queue.length > 0) {
const now = Date.now();
const timeSinceLastCall = now - this.lastCallTime;
// Calculate required delay
const minInterval = 60000 / this.maxCallsPerMinute;
const requiredDelay = Math.max(this.minDelayMs, minInterval - timeSinceLastCall);
if (requiredDelay > 0) {
await sleep(requiredDelay);
}
const item = this.queue.shift();
this.lastCallTime = Date.now();
try {
const result = await item.fn();
item.resolve(result);
}
catch (error) {
item.reject(error);
}
}
this.processing = false;
}
/**
* Get queue status
*/
getStatus() {
return {
queueLength: this.queue.length,
processing: this.processing,
};
}
}
/**
* Circuit breaker pattern for failing services
* PROOF: Production pattern for service resilience
*/
export class CircuitBreaker {
serviceName;
failureThreshold;
resetTimeoutMs;
failureCount = 0;
lastFailureTime = 0;
state = 'closed';
constructor(serviceName, failureThreshold = 5, resetTimeoutMs = 60000) {
this.serviceName = serviceName;
this.failureThreshold = failureThreshold;
this.resetTimeoutMs = resetTimeoutMs;
}
/**
* Execute operation with circuit breaker
*/
async execute(fn) {
// Check if circuit should reset
if (this.state === 'open') {
const timeSinceFailure = Date.now() - this.lastFailureTime;
if (timeSinceFailure >= this.resetTimeoutMs) {
logger.info(`🔄 Circuit breaker ${this.serviceName} transitioning to half-open`);
this.state = 'half-open';
}
else {
throw new Error(`Circuit breaker ${this.serviceName} is OPEN (too many failures)`);
}
}
try {
const result = await fn();
// Success - reset on half-open or reduce failure count
if (this.state === 'half-open') {
logger.info(`✅ Circuit breaker ${this.serviceName} closed (service recovered)`);
this.state = 'closed';
this.failureCount = 0;
}
else if (this.failureCount > 0) {
this.failureCount--;
}
return result;
}
catch (error) {
this.failureCount++;
this.lastFailureTime = Date.now();
// Open circuit if threshold exceeded
if (this.failureCount >= this.failureThreshold) {
logger.error(`💀 Circuit breaker ${this.serviceName} OPENED (${this.failureCount} failures)`);
this.state = 'open';
}
throw error;
}
}
/**
* Get circuit breaker status
*/
getStatus() {
return {
state: this.state,
failureCount: this.failureCount,
lastFailureTime: this.lastFailureTime ? new Date(this.lastFailureTime) : null,
};
}
/**
* Manually reset circuit breaker
*/
reset() {
logger.info(`🔄 Circuit breaker ${this.serviceName} manually reset`);
this.state = 'closed';
this.failureCount = 0;
this.lastFailureTime = 0;
}
}
//# sourceMappingURL=error-recovery.js.map