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
JavaScript
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