UNPKG

homebridge-pentair-intellicenter-ai

Version:

Homebridge plugin for the Pentair IntelliCenter pool control system (maintained with AI assistance)

227 lines 8.08 kB
"use strict"; /** * Error handling and resilience utilities for Pentair Platform */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RateLimiter = exports.DeadLetterQueue = exports.HealthMonitor = exports.RetryManager = exports.CircuitBreaker = exports.CircuitBreakerState = void 0; var CircuitBreakerState; (function (CircuitBreakerState) { CircuitBreakerState["CLOSED"] = "closed"; CircuitBreakerState["OPEN"] = "open"; CircuitBreakerState["HALF_OPEN"] = "half-open"; })(CircuitBreakerState || (exports.CircuitBreakerState = CircuitBreakerState = {})); class CircuitBreaker { constructor(options) { this.options = options; this.state = CircuitBreakerState.CLOSED; this.failureCount = 0; this.lastFailureTime = 0; this.successCount = 0; } async execute(operation) { if (this.state === CircuitBreakerState.OPEN) { if (Date.now() - this.lastFailureTime > this.options.resetTimeout) { this.state = CircuitBreakerState.HALF_OPEN; this.successCount = 0; } else { throw new Error('Circuit breaker is OPEN - operation rejected'); } } try { const result = await operation(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } onSuccess() { this.failureCount = 0; if (this.state === CircuitBreakerState.HALF_OPEN) { this.successCount++; if (this.successCount >= 3) { // Require 3 successes to close this.state = CircuitBreakerState.CLOSED; } } } onFailure() { this.failureCount++; this.lastFailureTime = Date.now(); if (this.failureCount >= this.options.failureThreshold) { this.state = CircuitBreakerState.OPEN; } } getState() { return this.state; } getStats() { return { state: this.state, failureCount: this.failureCount, lastFailureTime: this.lastFailureTime, successCount: this.successCount, }; } reset() { this.state = CircuitBreakerState.CLOSED; this.failureCount = 0; this.lastFailureTime = 0; this.successCount = 0; } } exports.CircuitBreaker = CircuitBreaker; class RetryManager { static async withRetry(operation, options, logger) { let lastError; for (let attempt = 1; attempt <= options.maxAttempts; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt === options.maxAttempts) { break; // Don't delay on final attempt } // Check if error is retryable if (options.retryableErrors && options.retryableErrors.length > 0) { const isRetryable = options.retryableErrors.some(retryableError => lastError.message.includes(retryableError)); if (!isRetryable) { logger === null || logger === void 0 ? void 0 : logger(`Non-retryable error encountered: ${lastError.message}`); throw lastError; } } const delay = Math.min(options.baseDelay * Math.pow(options.backoffFactor, attempt - 1), options.maxDelay); logger === null || logger === void 0 ? void 0 : logger(`Attempt ${attempt}/${options.maxAttempts} failed: ${lastError.message}. Retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } } exports.RetryManager = RetryManager; class HealthMonitor { constructor() { this.consecutiveFailures = 0; this.lastSuccessfulOperation = Date.now(); this.responseTimeHistory = []; this.maxHistorySize = 10; } recordSuccess(responseTime) { this.consecutiveFailures = 0; this.lastSuccessfulOperation = Date.now(); this.lastError = undefined; if (responseTime !== undefined) { this.responseTimeHistory.push(responseTime); if (this.responseTimeHistory.length > this.maxHistorySize) { this.responseTimeHistory.shift(); } } } recordFailure(error) { this.consecutiveFailures++; this.lastError = error; } getHealth() { const timeSinceLastSuccess = Date.now() - this.lastSuccessfulOperation; const avgResponseTime = this.responseTimeHistory.length > 0 ? this.responseTimeHistory.reduce((a, b) => a + b, 0) / this.responseTimeHistory.length : undefined; return { isHealthy: this.consecutiveFailures < 3 && timeSinceLastSuccess < 300000, // 5 minutes lastSuccessfulOperation: this.lastSuccessfulOperation, consecutiveFailures: this.consecutiveFailures, lastError: this.lastError, responseTime: avgResponseTime, }; } reset() { this.consecutiveFailures = 0; this.lastSuccessfulOperation = Date.now(); this.lastError = undefined; this.responseTimeHistory = []; } } exports.HealthMonitor = HealthMonitor; class DeadLetterQueue { constructor(maxSize = 100, maxRetentionMs = 24 * 60 * 60 * 1000) { this.queue = []; // 24 hours default this.maxSize = maxSize; this.maxRetentionMs = maxRetentionMs; } add(command, attempts, error, originalMessageId) { const item = { command, timestamp: Date.now(), attempts, lastError: error, originalMessageId, }; this.queue.push(item); // Maintain size limit if (this.queue.length > this.maxSize) { this.queue.shift(); // Remove oldest } } getFailedCommands() { this.cleanup(); return [...this.queue]; } getStats() { var _a, _b, _c, _d; this.cleanup(); return { queueSize: this.queue.length, maxSize: this.maxSize, oldestTimestamp: this.queue.length > 0 ? ((_b = (_a = this.queue[0]) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : null) : null, newestTimestamp: this.queue.length > 0 ? ((_d = (_c = this.queue[this.queue.length - 1]) === null || _c === void 0 ? void 0 : _c.timestamp) !== null && _d !== void 0 ? _d : null) : null, }; } clear() { this.queue = []; } cleanup() { const now = Date.now(); this.queue = this.queue.filter(item => now - item.timestamp < this.maxRetentionMs); } } exports.DeadLetterQueue = DeadLetterQueue; class RateLimiter { constructor(maxRequests, windowMs) { this.maxRequests = maxRequests; this.windowMs = windowMs; this.requests = []; } getNow() { return Date.now(); } canMakeRequest() { const now = this.getNow(); // Remove expired requests this.requests = this.requests.filter(timestamp => now - timestamp < this.windowMs); return this.requests.length < this.maxRequests; } recordRequest() { if (!this.canMakeRequest()) { return false; } this.requests.push(this.getNow()); return true; } getStats() { const now = this.getNow(); const activeRequests = this.requests.filter(timestamp => now - timestamp < this.windowMs); return { currentRequests: activeRequests.length, maxRequests: this.maxRequests, windowMs: this.windowMs, canMakeRequest: this.canMakeRequest(), }; } } exports.RateLimiter = RateLimiter; //# sourceMappingURL=errorHandling.js.map