UNPKG

adpa-enterprise-framework-automation

Version:

Modular, standards-compliant Node.js/TypeScript automation framework for enterprise requirements, project, and data management. Provides CLI and API for BABOK v3, PMBOK 7th Edition, and DMBOK 2.0 (in progress). Production-ready Express.js API with TypeSpe

251 lines 9.92 kB
/** * Adobe Creative Suite Phase 2 - Enhanced Error Handling * * Provides comprehensive error handling, retry logic, and rate limiting * for all Adobe Creative Suite API interactions. */ import { CreativeSuiteAuthenticator } from './authenticator.js'; export class EnhancedErrorHandler { retryOptions; rateLimitConfig; requestQueue = []; constructor(retryOptions, rateLimitConfig) { this.retryOptions = { maxRetries: 3, baseDelay: 1000, maxDelay: 30000, backoffMultiplier: 2, retryableErrors: [ 'RATE_LIMITED', 'NETWORK_ERROR', 'TIMEOUT', 'SERVER_ERROR', 'AUTHENTICATION_EXPIRED' ], ...retryOptions }; this.rateLimitConfig = { requestsPerSecond: 10, requestsPerMinute: 100, burstLimit: 20, ...rateLimitConfig }; } /** * Execute a function with comprehensive error handling and retry logic */ async executeWithRetry(operation, context, customRetryOptions) { const options = { ...this.retryOptions, ...customRetryOptions }; let lastError; for (let attempt = 0; attempt <= options.maxRetries; attempt++) { try { // Check rate limits before executing await this.checkRateLimit(context); // Execute the operation const result = await operation(); // Record successful request this.recordRequest(context); return result; } catch (error) { lastError = error; const errorDetails = this.analyzeError(error, context); // Don't retry if this is the last attempt or error is not retryable if (attempt === options.maxRetries || !errorDetails.isRetryable) { throw this.enhanceError(lastError, errorDetails, attempt); } // Calculate delay for next retry const delay = this.calculateBackoffDelay(attempt, options); console.warn(`🔄 Retry attempt ${attempt + 1}/${options.maxRetries} for ${context} after ${delay}ms delay. Error: ${errorDetails.message}`); await this.sleep(delay); } } // This should never be reached, but TypeScript requires it throw this.enhanceError(lastError, this.analyzeError(lastError, context), options.maxRetries); } /** * Check if the request is within rate limits */ async checkRateLimit(service) { const now = new Date(); const oneSecondAgo = new Date(now.getTime() - 1000); const oneMinuteAgo = new Date(now.getTime() - 60000); // Clean old requests this.requestQueue = this.requestQueue.filter(req => req.timestamp > oneMinuteAgo); // Check rate limits const recentRequests = this.requestQueue.filter(req => req.timestamp > oneSecondAgo); const minuteRequests = this.requestQueue.length; if (recentRequests.length >= this.rateLimitConfig.requestsPerSecond) { const waitTime = 1000 - (now.getTime() - recentRequests[0].timestamp.getTime()); if (waitTime > 0) { console.warn(`⏱️ Rate limit hit for ${service}. Waiting ${waitTime}ms...`); await this.sleep(waitTime); } } if (minuteRequests >= this.rateLimitConfig.requestsPerMinute) { const waitTime = 60000 - (now.getTime() - this.requestQueue[0].timestamp.getTime()); if (waitTime > 0) { console.warn(`⏱️ Minute rate limit hit for ${service}. Waiting ${waitTime}ms...`); await this.sleep(waitTime); } } } /** * Record a successful request for rate limiting */ recordRequest(service) { this.requestQueue.push({ timestamp: new Date(), service }); // Keep only recent requests const oneMinuteAgo = new Date(Date.now() - 60000); this.requestQueue = this.requestQueue.filter(req => req.timestamp > oneMinuteAgo); } /** * Analyze an error to determine if it's retryable and extract details */ analyzeError(error, context) { const errorMessage = error.message.toLowerCase(); let code = 'UNKNOWN_ERROR'; let isRetryable = false; // Determine error type and retryability if (errorMessage.includes('rate limit') || errorMessage.includes('too many requests')) { code = 'RATE_LIMITED'; isRetryable = true; } else if (errorMessage.includes('network') || errorMessage.includes('connection')) { code = 'NETWORK_ERROR'; isRetryable = true; } else if (errorMessage.includes('timeout')) { code = 'TIMEOUT'; isRetryable = true; } else if (errorMessage.includes('server error') || errorMessage.includes('internal error')) { code = 'SERVER_ERROR'; isRetryable = true; } else if (errorMessage.includes('authentication') || errorMessage.includes('token')) { code = 'AUTHENTICATION_ERROR'; isRetryable = errorMessage.includes('expired'); } else if (errorMessage.includes('not found') || errorMessage.includes('invalid')) { code = 'CLIENT_ERROR'; isRetryable = false; } return { code, message: error.message, timestamp: new Date(), context, isRetryable: isRetryable && this.retryOptions.retryableErrors.includes(code) }; } /** * Enhance an error with additional context and details */ enhanceError(originalError, errorDetails, attempts) { const enhancedMessage = `${errorDetails.code}: ${originalError.message} (Context: ${errorDetails.context}, Attempts: ${attempts + 1})`; const enhancedError = new Error(enhancedMessage); enhancedError.name = errorDetails.code; enhancedError.stack = originalError.stack; // Add custom properties enhancedError.originalError = originalError; enhancedError.errorDetails = errorDetails; enhancedError.attempts = attempts + 1; return enhancedError; } /** * Calculate exponential backoff delay */ calculateBackoffDelay(attempt, options) { const baseDelay = options.baseDelay * Math.pow(options.backoffMultiplier, attempt); const jitter = Math.random() * 0.1 * baseDelay; // Add up to 10% jitter return Math.min(baseDelay + jitter, options.maxDelay); } /** * Sleep for specified milliseconds */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Get current rate limit status */ getRateLimitStatus() { const now = new Date(); const oneSecondAgo = new Date(now.getTime() - 1000); const oneMinuteAgo = new Date(now.getTime() - 60000); const recentRequests = this.requestQueue.filter(req => req.timestamp > oneSecondAgo); const minuteRequests = this.requestQueue.filter(req => req.timestamp > oneMinuteAgo); return { requestsInLastSecond: recentRequests.length, requestsInLastMinute: minuteRequests.length, remainingQuota: Math.max(0, this.rateLimitConfig.requestsPerMinute - minuteRequests.length) }; } } /** * Enhanced Creative Suite Authenticator with error handling */ export class RobustCreativeSuiteAuthenticator extends CreativeSuiteAuthenticator { errorHandler; constructor(errorHandlerOptions) { super(); this.errorHandler = new EnhancedErrorHandler(errorHandlerOptions?.retryOptions, errorHandlerOptions?.rateLimitConfig); } /** * Authenticate with comprehensive error handling and retry logic */ async authenticateWithRetry() { return this.errorHandler.executeWithRetry(() => super.authenticate(), 'CreativeSuite Authentication', { maxRetries: 3, retryableErrors: ['AUTHENTICATION_EXPIRED', 'NETWORK_ERROR', 'RATE_LIMITED'] }); } /** * Get rate limit status for authentication requests */ getAuthRateLimitStatus() { return this.errorHandler.getRateLimitStatus(); } } /** * Enhanced API Client wrapper with error handling */ export class RobustAPIClient { errorHandler; authenticator; constructor(authenticator, errorHandlerOptions) { this.authenticator = authenticator || new RobustCreativeSuiteAuthenticator(); this.errorHandler = new EnhancedErrorHandler(errorHandlerOptions?.retryOptions, errorHandlerOptions?.rateLimitConfig); } /** * Execute any API call with comprehensive error handling */ async executeAPICall(apiCall, context, customRetryOptions) { return this.errorHandler.executeWithRetry(apiCall, context, customRetryOptions); } /** * Get comprehensive API health status */ async getHealthStatus() { try { await this.authenticator.authenticateWithRetry(); return { authentication: 'healthy', rateLimits: this.errorHandler.getRateLimitStatus() }; } catch (error) { const errorDetails = this.errorHandler.analyzeError(error, 'Health Check'); return { authentication: errorDetails.isRetryable ? 'warning' : 'error', rateLimits: this.errorHandler.getRateLimitStatus(), lastError: errorDetails }; } } } //# sourceMappingURL=enhanced-error-handling.js.map