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 { // 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 synchronously const instance = registration.factory(); // Check if factory returned a promise if (instance instanceof Promise) { throw new Error(`Service ${name} is async. Use resolveAsync() instead.`); } // Store based on singleton flag if (registration.singleton) { this.instances.set(name, instance); } else { this.scopedInstances.set(name, instance); } 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: async () => { const pool = await connectionFactory.createCache({ name: 'default', config: {} as any, // Config injected by factory }); return pool.client; }, singleton: true, }); container.register({ name: 'queue', factory: async () => { const pool = await connectionFactory.createQueue({ name: 'default', config: {} as any, // Config injected by factory }); return pool.client; }, singleton: true, }); // Note: Additional services can be registered by individual applications as needed: // - ProxyManager: container.register({ name: 'proxyManager', factory: () => ProxyManager.getInstance() }) // - Browser: container.register({ name: 'browser', factory: () => Browser }) // - HttpClient: container.register({ name: 'httpClient', factory: () => createHttpClient(...) }) return container; }