import { getLogger, type Logger } from '@stock-bot/logger'; import type { ConnectionFactory } from './connection-factory'; export interface ServiceRegistration { name: string; factory: () => T | Promise; singleton?: boolean; dispose?: (instance: T) => Promise; } export interface ServiceResolver { resolve(name: string, options?: any): T; resolveAsync(name: string, options?: any): Promise; } export class ServiceContainer implements ServiceResolver { private readonly logger: Logger; private readonly registrations = new Map(); private readonly instances = new Map(); private readonly scopedInstances = new Map(); private readonly parent?: ServiceContainer; constructor(name: string, parent?: ServiceContainer) { this.logger = getLogger(`service-container:${name}`); this.parent = parent; } register(registration: ServiceRegistration): void { this.registrations.set(registration.name, registration); this.logger.debug('Service registered', { name: registration.name, singleton: registration.singleton }); } resolve(name: string, options?: any): T { const instance = this.resolveAsync(name, options); if (instance instanceof Promise) { throw new Error(`Service ${name} is async. Use resolveAsync() instead.`); } return instance as T; } async resolveAsync(name: string, _options?: any): Promise { // Check scoped instances first if (this.scopedInstances.has(name)) { return this.scopedInstances.get(name); } // Check singleton instances if (this.instances.has(name)) { return this.instances.get(name); } // Get registration from this container or parent const registration = this.getRegistration(name); if (!registration) { throw new Error(`Service ${name} not registered`); } // Create instance const instance = await Promise.resolve(registration.factory()); // Store based on singleton flag if (registration.singleton) { this.instances.set(name, instance); } else { this.scopedInstances.set(name, instance); } return instance as T; } createScope(): ServiceContainer { return new ServiceContainer('scoped', this); } async dispose(): Promise { // Dispose scoped instances for (const [name, instance] of this.scopedInstances.entries()) { const registration = this.getRegistration(name); if (registration?.dispose) { await registration.dispose(instance); } } this.scopedInstances.clear(); // Only dispose singletons if this is the root container if (!this.parent) { for (const [name, instance] of this.instances.entries()) { const registration = this.registrations.get(name); if (registration?.dispose) { await registration.dispose(instance); } } this.instances.clear(); } } private getRegistration(name: string): ServiceRegistration | undefined { return this.registrations.get(name) || this.parent?.getRegistration(name); } } // Enhanced service container factory with infrastructure services export function createServiceContainer( serviceName: string, connectionFactory: ConnectionFactory, config?: any ): ServiceContainer { const container = new ServiceContainer(serviceName); // Register configuration if provided if (config) { container.register({ name: 'config', factory: () => config, singleton: true, }); } // Register connection factories container.register({ name: 'mongodb', factory: async () => { const pool = await connectionFactory.createMongoDB({ name: 'default', config: {} as any, // Config injected by factory }); return pool.client; }, singleton: true, }); container.register({ name: 'postgres', factory: async () => { const pool = await connectionFactory.createPostgreSQL({ name: 'default', config: {} as any, // Config injected by factory }); return pool.client; }, singleton: true, }); container.register({ name: 'cache', factory: () => { const pool = connectionFactory.createCache({ name: 'default', config: {} as any, // Config injected by factory }); return pool.client; }, singleton: true, }); container.register({ name: 'queue', factory: () => { const pool = connectionFactory.createQueue({ name: 'default', config: {} as any, // Config injected by factory }); return pool.client; }, singleton: true, }); // Register ProxyManager container.register({ name: 'proxyManager', factory: async () => { const { ProxyManager } = await import('@stock-bot/utils'); await ProxyManager.initialize(); return ProxyManager.getInstance(); }, singleton: true, dispose: async (proxyManager) => { // ProxyManager handles its own cleanup if (proxyManager && typeof proxyManager.shutdown === 'function') { await proxyManager.shutdown(); } } }); // Register Browser service container.register({ name: 'browser', factory: async () => { const { Browser } = await import('@stock-bot/browser'); return Browser; }, singleton: true, dispose: async (browser) => { if (browser && typeof browser.close === 'function') { await browser.close(); } } }); // Register HttpClient with default configuration container.register({ name: 'httpClient', factory: async () => { const { createHttpClient } = await import('@stock-bot/http'); return createHttpClient({ timeout: 30000, retries: 3, userAgent: 'stock-bot/1.0', }); }, singleton: true, }); return container; }