UNPKG

@iota-big3/sdk-gateway

Version:

Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching

279 lines 8.84 kB
"use strict"; /** * @iota-big3/sdk-gateway * Health Checker - Service health monitoring with retry and timeout support * Phase 2g Implementation */ Object.defineProperty(exports, "__esModule", { value: true }); exports.HealthChecker = void 0; const tslib_1 = require("tslib"); const axios_1 = tslib_1.__importDefault(require("axios")); const events_1 = require("events"); /** * Health checker implementation with periodic monitoring */ class HealthChecker extends events_1.EventEmitter { constructor(config, httpClient) { super(); this.services = new Map(); this.running = false; this.config = { interval: config.interval, timeout: config.timeout, retries: config.retries ?? 2, endpoints: config.endpoints || [] }; // Use provided client or create default this.httpClient = httpClient || axios_1.default.create({ timeout: this.config.timeout, validateStatus: () => true // Don't throw on any status }); // Register default endpoint checks this.config.endpoints.forEach(endpoint => { this.addEndpointCheck(endpoint); }); } /** * Add a custom health check function */ addCheck(serviceId, checkFunction) { this.services.set(serviceId, { consecutiveFailures: 0, checkFunction }); this.emit('check:added', { serviceId }); } /** * Add health check for an HTTP endpoint */ addEndpointCheck(endpoint) { const checkFunction = async () => { const startTime = Date.now(); try { const response = await this.httpClient.request({ method: endpoint.method || 'GET', url: `${endpoint.url}${endpoint.healthPath || '/health'}`, headers: endpoint.headers }); const responseTime = Date.now() - startTime; const expectedStatus = endpoint.expectedStatus || 200; if (response.status === expectedStatus) { return { status: 'healthy', responseTime, details: { statusCode: response.status, data: response.data } }; } else { return { status: 'unhealthy', responseTime, error: `Unexpected status: ${response.status}`, details: { statusCode: response.status, expectedStatus } }; } } catch (error) { return { status: 'unhealthy', responseTime: Date.now() - startTime, error: error instanceof Error ? error.message : 'Unknown error' }; } }; this.addCheck(endpoint.id, checkFunction); } /** * Check health of a specific service */ async checkHealthAsync(serviceId) { const state = this.services.get(serviceId); if (!state) { return { status: 'unhealthy', error: `No health check registered for service: ${serviceId}` }; } // Perform check with retries let lastError; let attempts = 0; while (attempts <= this.config.retries) { try { const result = await this.executeCheckAsync(state.checkFunction); // Update state const previousStatus = state.lastStatus; state.lastStatus = result.status; state.lastCheck = new Date(); state.consecutiveFailures = result.status === 'unhealthy' ? state.consecutiveFailures + 1 : 0; // Emit status change event if (previousStatus && previousStatus !== result.status) { this.emit('health:status:changed', { serviceId, previousStatus, currentStatus: result.status }); } return { ...result, lastChecked: state.lastCheck }; } catch (error) { lastError = error instanceof Error ? error.message : 'Unknown error'; attempts++; if (attempts <= this.config.retries) { // Wait before retry (exponential backoff) await new Promise(resolve => setTimeout(resolve, 100 * Math.pow(2, attempts))); } } } // All retries failed state.consecutiveFailures++; state.lastStatus = 'unhealthy'; state.lastCheck = new Date(); return { status: 'unhealthy', error: lastError || 'Max retries exceeded', lastChecked: state.lastCheck }; } /** * Execute a single health check with timeout */ async executeCheckAsync(checkFunction) { return Promise.race([ checkFunction(), new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), this.config.timeout)) ]); } /** * Check health of all registered services */ async checkAllAsync() { const results = {}; // Check all services in parallel const checks = Array.from(this.services.keys()).map(async (serviceId) => { results[serviceId] = await this.checkHealthAsync(serviceId); }); await Promise.all(checks); return results; } /** * Get overall health status */ async getOverallHealthAsync() { const results = await this.checkAllAsync(); let healthy = 0; let unhealthy = 0; const services = []; for (const [serviceId, result] of Object.entries(results)) { if (result.status === 'healthy') { healthy++; } else { unhealthy++; } services.push({ id: serviceId, status: result.status, lastChecked: result.lastChecked, error: result.error }); } // Determine overall status let status; if (unhealthy === 0) { status = 'healthy'; } else if (healthy === 0) { status = 'unhealthy'; } else { status = 'degraded'; } return { status, healthy, unhealthy, services }; } /** * Start periodic health checks */ async startAsync() { if (this.running) { return; } this.running = true; // Initial check await this.checkAllAsync(); // Start periodic checks this.intervalId = setInterval(async () => { try { await this.checkAllAsync(); this.emit('health:check:completed'); } catch (error) { this.emit('health:check:error', error); } }, this.config.interval); this.emit('health:checker:started'); } /** * Stop periodic health checks */ stop() { if (!this.running) { return; } if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = undefined; } this.running = false; this.emit('health:checker:stopped'); } /** * Check if health checker is running */ isRunning() { return this.running; } /** * Remove a health check */ removeCheck(serviceId) { this.services.delete(serviceId); this.emit('check:removed', { serviceId }); } /** * Clear all health checks */ clear() { this.services.clear(); this.emit('checks:cleared'); } /** * Get current health states */ getHealthStates() { const states = {}; this.services.forEach((state, serviceId) => { states[serviceId] = { status: state.lastStatus, lastCheck: state.lastCheck, consecutiveFailures: state.consecutiveFailures }; }); return states; } } exports.HealthChecker = HealthChecker; //# sourceMappingURL=health-checker.js.map