import type { AwilixContainer } from 'awilix'; import type { ServiceDefinitions } from '../container/types'; import { getLogger } from '@stock-bot/logger'; interface ServiceWithLifecycle { connect?: () => Promise; disconnect?: () => Promise; close?: () => Promise; initialize?: () => Promise; shutdown?: () => Promise; } export class ServiceLifecycleManager { private readonly logger = getLogger('service-lifecycle'); private readonly services = [ { name: 'cache', key: 'cache' as const }, { name: 'mongoClient', key: 'mongoClient' as const }, { name: 'postgresClient', key: 'postgresClient' as const }, { name: 'questdbClient', key: 'questdbClient' as const }, { name: 'proxyManager', key: 'proxyManager' as const }, { name: 'queueManager', key: 'queueManager' as const }, ]; async initializeServices( container: AwilixContainer, timeout = 30000 ): Promise { const initPromises: Promise[] = []; for (const { name, key } of this.services) { const service = container.cradle[key] as ServiceWithLifecycle | null; if (service) { const initPromise = this.initializeService(name, service); initPromises.push( Promise.race([ initPromise, this.createTimeoutPromise(timeout, `${name} initialization timed out after ${timeout}ms`), ]) ); } } await Promise.all(initPromises); this.logger.info('All services initialized successfully'); } async shutdownServices(container: AwilixContainer): Promise { const shutdownPromises: Promise[] = []; // Shutdown in reverse order for (const { name, key } of [...this.services].reverse()) { const service = container.cradle[key] as ServiceWithLifecycle | null; if (service) { shutdownPromises.push(this.shutdownService(name, service)); } } await Promise.allSettled(shutdownPromises); this.logger.info('All services shut down'); } private async initializeService(name: string, service: ServiceWithLifecycle): Promise { try { if (typeof service.connect === 'function') { await service.connect(); this.logger.info(`${name} connected`); } else if (typeof service.initialize === 'function') { await service.initialize(); this.logger.info(`${name} initialized`); } } catch (error) { this.logger.error(`Failed to initialize ${name}:`, error); throw error; } } private async shutdownService(name: string, service: ServiceWithLifecycle): Promise { try { if (typeof service.disconnect === 'function') { await service.disconnect(); } else if (typeof service.close === 'function') { await service.close(); } else if (typeof service.shutdown === 'function') { await service.shutdown(); } this.logger.info(`${name} shut down`); } catch (error) { this.logger.error(`Error shutting down ${name}:`, error); } } private createTimeoutPromise(timeout: number, message: string): Promise { return new Promise((_, reject) => { setTimeout(() => reject(new Error(message)), timeout); }); } }