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, }); // Optional services - comment out for now to avoid circular dependencies // These can be registered manually by apps that need them // // Register ProxyManager // container.register({ // name: 'proxyManager', // factory: async () => { // const { ProxyManager } = await import('@stock-bot/utils'); // await ProxyManager.initialize(); // return ProxyManager.getInstance(); // }, // singleton: true, // }); // // Register Browser service // container.register({ // name: 'browser', // factory: async () => { // const { Browser } = await import('@stock-bot/browser'); // return Browser; // }, // singleton: true, // }); // // 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; }