survey-mcp-server
Version:
Survey management server handling survey creation, response collection, analysis, and reporting with database access for data management
300 lines • 11.4 kB
JavaScript
export { BaseService, CircuitBreaker, RetryHandler, CircuitBreakerState, defaultServiceConfig } from './base.js';
export { DatabaseService, databaseService } from './database.js';
export { SearchService, searchService } from './search.js';
export { ExternalApiService, externalApiService } from './external-api.js';
import { databaseService } from './database.js';
import { searchService } from './search.js';
import { externalApiService } from './external-api.js';
import { logger } from '../utils/logger.js';
export class ServiceManager {
constructor(config = {}) {
this.services = new Map();
this.healthMonitoringInterval = null;
this.isShuttingDown = false;
this.config = {
enableHealthMonitoring: true,
healthCheckInterval: 30000, // 30 seconds
shutdownTimeout: 30000, // 30 seconds
...config
};
}
static getInstance(config) {
if (!ServiceManager.instance) {
ServiceManager.instance = new ServiceManager(config);
}
return ServiceManager.instance;
}
async initialize() {
try {
logger.info('Initializing service manager...');
// Register core services
this.registerService('database', databaseService);
this.registerService('search', searchService);
this.registerService('externalApi', externalApiService);
// Initialize all services
const initPromises = Array.from(this.services.entries()).map(async ([name, service]) => {
try {
logger.info(`Initializing service: ${name}`);
if ('initialize' in service && typeof service.initialize === 'function') {
await service.initialize();
}
logger.info(`Service initialized successfully: ${name}`);
}
catch (error) {
logger.error(`Failed to initialize service ${name}:`, error);
// Don't throw - allow partial initialization for graceful degradation
}
});
await Promise.allSettled(initPromises);
// Start health monitoring
if (this.config.enableHealthMonitoring) {
this.startHealthMonitoring();
}
logger.info('Service manager initialization completed');
}
catch (error) {
logger.error('Service manager initialization failed:', error);
throw error;
}
}
registerService(name, service) {
if (this.services.has(name)) {
logger.warn(`Service ${name} is already registered, replacing...`);
}
this.services.set(name, service);
logger.debug(`Service registered: ${name}`);
}
getService(name) {
return this.services.get(name);
}
getDatabaseService() {
const service = this.getService('database');
if (!service) {
throw new Error('Database service is not available');
}
return service;
}
getSearchService() {
const service = this.getService('search');
if (!service) {
throw new Error('Search service is not available');
}
return service;
}
getExternalApiService() {
const service = this.getService('externalApi');
if (!service) {
throw new Error('External API service is not available');
}
return service;
}
async getSystemHealth() {
const timestamp = new Date();
const services = {};
let healthyCount = 0;
let totalRequests = 0;
let totalErrors = 0;
for (const [name, service] of this.services) {
try {
const healthStatus = service.getHealthStatus();
const metrics = service.getServiceMetrics();
const isHealthy = healthStatus.isHealthy &&
metrics.circuitBreakerState !== 'OPEN';
if (isHealthy) {
healthyCount++;
}
services[name] = {
isHealthy,
status: isHealthy ? 'healthy' : 'unhealthy',
responseTime: healthStatus.responseTime,
error: healthStatus.error,
lastCheck: healthStatus.lastCheck,
metrics: {
circuitBreakerState: metrics.circuitBreakerState,
failureCount: metrics.failureCount
}
};
// Aggregate stats if available
const serviceStats = service.getServiceStats?.();
if (serviceStats) {
totalRequests += serviceStats.requestCount || serviceStats.queryCount || 0;
totalErrors += serviceStats.errorCount || 0;
services[name].metrics = {
...services[name].metrics,
...serviceStats
};
}
}
catch (error) {
logger.error(`Failed to get health status for service ${name}:`, error);
services[name] = {
isHealthy: false,
status: 'error',
error: error.message,
lastCheck: timestamp
};
}
}
const totalServices = this.services.size;
const unhealthyServices = totalServices - healthyCount;
const overallSuccessRate = totalRequests > 0 ?
((totalRequests - totalErrors) / totalRequests) * 100 : 100;
return {
isHealthy: unhealthyServices === 0,
timestamp,
services,
summary: {
totalServices,
healthyServices: healthyCount,
unhealthyServices,
overallSuccessRate: Math.round(overallSuccessRate * 100) / 100
}
};
}
startHealthMonitoring() {
if (this.healthMonitoringInterval) {
return; // Already started
}
this.healthMonitoringInterval = setInterval(async () => {
if (this.isShuttingDown) {
return;
}
try {
const healthReport = await this.getSystemHealth();
if (!healthReport.isHealthy) {
const unhealthyServices = Object.entries(healthReport.services)
.filter(([_, service]) => !service.isHealthy)
.map(([name, _]) => name);
logger.warn('System health check detected issues', {
unhealthyServices,
totalServices: healthReport.summary.totalServices,
healthyServices: healthReport.summary.healthyServices
});
}
else {
logger.debug('System health check passed', {
totalServices: healthReport.summary.totalServices,
successRate: healthReport.summary.overallSuccessRate
});
}
}
catch (error) {
logger.error('Health monitoring error:', error);
}
}, this.config.healthCheckInterval);
logger.info(`Health monitoring started with ${this.config.healthCheckInterval}ms interval`);
}
stopHealthMonitoring() {
if (this.healthMonitoringInterval) {
clearInterval(this.healthMonitoringInterval);
this.healthMonitoringInterval = null;
logger.info('Health monitoring stopped');
}
}
async resetAllCircuitBreakers() {
logger.info('Resetting all circuit breakers...');
for (const [name, service] of this.services) {
try {
service.resetCircuitBreaker();
logger.debug(`Circuit breaker reset for service: ${name}`);
}
catch (error) {
logger.error(`Failed to reset circuit breaker for service ${name}:`, error);
}
}
logger.info('All circuit breakers reset completed');
}
async gracefulShutdown() {
if (this.isShuttingDown) {
return;
}
this.isShuttingDown = true;
logger.info('Starting graceful shutdown of service manager...');
// Stop health monitoring
this.stopHealthMonitoring();
// Shutdown all services with timeout
const shutdownPromises = Array.from(this.services.entries()).map(async ([name, service]) => {
try {
logger.info(`Shutting down service: ${name}`);
await service.shutdown();
logger.info(`Service shutdown completed: ${name}`);
}
catch (error) {
logger.error(`Error shutting down service ${name}:`, error);
}
});
// Wait for all shutdowns with timeout
const shutdownTimeout = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Shutdown timeout after ${this.config.shutdownTimeout}ms`));
}, this.config.shutdownTimeout);
});
try {
await Promise.race([
Promise.allSettled(shutdownPromises),
shutdownTimeout
]);
}
catch (error) {
logger.warn('Some services did not shutdown gracefully:', error.message);
}
logger.info('Service manager shutdown completed');
}
getServiceList() {
return Array.from(this.services.keys());
}
isServiceRegistered(name) {
return this.services.has(name);
}
getServiceCount() {
return this.services.size;
}
}
// Create and export singleton instance
export const serviceManager = ServiceManager.getInstance();
// Graceful shutdown handler
process.on('SIGTERM', async () => {
logger.info('Received SIGTERM, starting graceful shutdown...');
try {
await serviceManager.gracefulShutdown();
process.exit(0);
}
catch (error) {
logger.error('Error during graceful shutdown:', error);
process.exit(1);
}
});
process.on('SIGINT', async () => {
logger.info('Received SIGINT, starting graceful shutdown...');
try {
await serviceManager.gracefulShutdown();
process.exit(0);
}
catch (error) {
logger.error('Error during graceful shutdown:', error);
process.exit(1);
}
});
// Handle uncaught exceptions
process.on('uncaughtException', async (error) => {
logger.error('Uncaught exception:', error);
try {
await serviceManager.gracefulShutdown();
}
catch (shutdownError) {
logger.error('Error during emergency shutdown:', shutdownError);
}
process.exit(1);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', async (reason, promise) => {
logger.error('Unhandled promise rejection:', { reason, promise });
try {
await serviceManager.gracefulShutdown();
}
catch (shutdownError) {
logger.error('Error during emergency shutdown:', shutdownError);
}
process.exit(1);
});
//# sourceMappingURL=index.js.map