stock-bot/libs/core/di/src/utils/lifecycle.ts

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);
});
}
}