99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
import type { AwilixContainer } from 'awilix';
|
|
import type { ServiceDefinitions } from '../container/types';
|
|
import { getLogger } from '@stock-bot/logger';
|
|
|
|
interface ServiceWithLifecycle {
|
|
connect?: () => Promise<void>;
|
|
disconnect?: () => Promise<void>;
|
|
close?: () => Promise<void>;
|
|
initialize?: () => Promise<void>;
|
|
shutdown?: () => Promise<void>;
|
|
}
|
|
|
|
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<ServiceDefinitions>,
|
|
timeout = 30000
|
|
): Promise<void> {
|
|
const initPromises: Promise<void>[] = [];
|
|
|
|
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<ServiceDefinitions>): Promise<void> {
|
|
const shutdownPromises: Promise<void>[] = [];
|
|
|
|
// 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<void> {
|
|
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<void> {
|
|
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<never> {
|
|
return new Promise((_, reject) => {
|
|
setTimeout(() => reject(new Error(message)), timeout);
|
|
});
|
|
}
|
|
}
|