UNPKG

pms-analysis-reports-mcp-server

Version:

PMS analysis reports server handling maintenance reports, equipment analysis, compliance tracking, and performance metrics with ERP access for data extraction

211 lines 7.38 kB
import { logger } from "../utils/logger.js"; import { ErrorHandler } from "../middleware/error-handler.js"; export class BaseService { constructor(config = {}) { this.circuitBreakerFailureCount = 0; this.config = { timeout: config.timeout || 30000, retries: config.retries || 3, retryDelay: config.retryDelay || 1000, circuitBreakerThreshold: config.circuitBreakerThreshold || 5, circuitBreakerTimeout: config.circuitBreakerTimeout || 60000 }; this.metrics = { totalRequests: 0, successCount: 0, errorCount: 0, averageResponseTime: 0, circuitBreakerState: 'CLOSED' }; } /** * Execute a service operation with retry logic, circuit breaker, and metrics */ async executeWithResilience(operation, context = {}) { const startTime = Date.now(); let retryCount = 0; let lastError; // Check circuit breaker if (this.isCircuitBreakerOpen()) { return { success: false, error: 'Service temporarily unavailable (circuit breaker open)', metadata: { responseTime: 0 } }; } this.metrics.totalRequests++; while (retryCount <= this.config.retries) { try { // Set timeout for operation const result = await this.withTimeout(operation(), this.config.timeout); // Success - update metrics const responseTime = Date.now() - startTime; this.updateMetrics(true, responseTime); this.resetCircuitBreaker(); return { success: true, data: result, metadata: { responseTime, retryCount: retryCount > 0 ? retryCount : undefined } }; } catch (error) { lastError = error; retryCount++; logger.warn(`Service operation failed (attempt ${retryCount}/${this.config.retries + 1})`, { error: error.message, context, retryCount }); // Don't retry on certain errors if (this.shouldNotRetry(error) || retryCount > this.config.retries) { break; } // Wait before retry with exponential backoff await this.sleep(this.config.retryDelay * Math.pow(2, retryCount - 1)); } } // All retries failed const responseTime = Date.now() - startTime; this.updateMetrics(false, responseTime); this.updateCircuitBreaker(); const standardError = ErrorHandler.handleError(lastError, context); return { success: false, error: standardError.message, metadata: { responseTime, retryCount } }; } /** * Add timeout to promise */ withTimeout(promise, timeoutMs) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`Operation timed out after ${timeoutMs}ms`)); }, timeoutMs); promise .then(result => { clearTimeout(timeoutId); resolve(result); }) .catch(error => { clearTimeout(timeoutId); reject(error); }); }); } /** * Check if operation should not be retried */ shouldNotRetry(error) { // Don't retry on authentication errors, validation errors, etc. const nonRetryableErrors = [400, 401, 403, 404, 422]; return error.response && nonRetryableErrors.includes(error.response.status); } /** * Sleep for specified milliseconds */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Update service metrics */ updateMetrics(success, responseTime) { if (success) { this.metrics.successCount++; } else { this.metrics.errorCount++; this.metrics.lastError = new Date(); } // Update average response time (rolling average) const totalOperations = this.metrics.successCount + this.metrics.errorCount; this.metrics.averageResponseTime = (this.metrics.averageResponseTime * (totalOperations - 1) + responseTime) / totalOperations; } /** * Check if circuit breaker should be open */ isCircuitBreakerOpen() { if (this.metrics.circuitBreakerState === 'OPEN') { // Check if we should try half-open if (this.circuitBreakerLastFailure && Date.now() - this.circuitBreakerLastFailure.getTime() > this.config.circuitBreakerTimeout) { this.metrics.circuitBreakerState = 'HALF_OPEN'; return false; } return true; } return false; } /** * Update circuit breaker state on failure */ updateCircuitBreaker() { this.circuitBreakerFailureCount++; this.circuitBreakerLastFailure = new Date(); if (this.circuitBreakerFailureCount >= this.config.circuitBreakerThreshold) { this.metrics.circuitBreakerState = 'OPEN'; logger.warn('Circuit breaker opened due to repeated failures', { service: this.constructor.name, failureCount: this.circuitBreakerFailureCount, threshold: this.config.circuitBreakerThreshold }); } } /** * Reset circuit breaker on success */ resetCircuitBreaker() { if (this.metrics.circuitBreakerState !== 'CLOSED') { this.metrics.circuitBreakerState = 'CLOSED'; this.circuitBreakerFailureCount = 0; delete this.circuitBreakerLastFailure; logger.info('Circuit breaker reset after successful operation', { service: this.constructor.name }); } } /** * Get service health status */ getHealth() { const errorRate = this.metrics.totalRequests > 0 ? this.metrics.errorCount / this.metrics.totalRequests : 0; let status = 'healthy'; if (this.metrics.circuitBreakerState === 'OPEN') { status = 'unhealthy'; } else if (errorRate > 0.1 || this.metrics.averageResponseTime > this.config.timeout * 0.8) { status = 'degraded'; } return { status, metrics: { ...this.metrics }, uptime: process.uptime() }; } /** * Reset service metrics (useful for testing) */ resetMetrics() { this.metrics = { totalRequests: 0, successCount: 0, errorCount: 0, averageResponseTime: 0, circuitBreakerState: 'CLOSED' }; this.circuitBreakerFailureCount = 0; delete this.circuitBreakerLastFailure; } } //# sourceMappingURL=base-service.js.map