import { createContainer, InjectionMode, type AwilixContainer } from 'awilix'; import type { AppConfig as StockBotAppConfig } from '@stock-bot/config'; import { appConfigSchema, type AppConfig } from '../config/schemas'; import { registerCoreServices, registerCacheServices, registerDatabaseServices, registerApplicationServices } from '../registrations'; import { ServiceLifecycleManager } from '../utils/lifecycle'; import type { ServiceDefinitions, ContainerBuildOptions } from './types'; export class ServiceContainerBuilder { private config: Partial = {}; private options: ContainerBuildOptions = { enableCache: true, enableQueue: true, enableMongoDB: true, enablePostgres: true, enableQuestDB: true, enableBrowser: true, enableProxy: true, skipInitialization: false, initializationTimeout: 30000, }; withConfig(config: AppConfig | StockBotAppConfig): this { this.config = this.transformStockBotConfig(config); return this; } withOptions(options: Partial): this { Object.assign(this.options, options); return this; } enableService(service: keyof Omit, enabled = true): this { this.options[service] = enabled; return this; } skipInitialization(skip = true): this { this.options.skipInitialization = skip; return this; } async build(): Promise> { // Validate and prepare config const validatedConfig = this.prepareConfig(); // Create container const container = createContainer({ injectionMode: InjectionMode.PROXY, strict: true, }); // Register services this.registerServices(container, validatedConfig); // Initialize services if not skipped if (!this.options.skipInitialization) { const lifecycleManager = new ServiceLifecycleManager(); await lifecycleManager.initializeServices(container, this.options.initializationTimeout); } return container; } private prepareConfig(): AppConfig { const finalConfig = this.applyServiceOptions(this.config); return appConfigSchema.parse(finalConfig); } private applyServiceOptions(config: Partial): AppConfig { return { redis: { enabled: this.options.enableCache ?? true, host: config.redis?.host || 'localhost', port: config.redis?.port || 6379, password: config.redis?.password, username: config.redis?.username, db: config.redis?.db || 0, }, mongodb: { enabled: this.options.enableMongoDB ?? true, uri: config.mongodb?.uri || '', database: config.mongodb?.database || '', }, postgres: { enabled: this.options.enablePostgres ?? true, host: config.postgres?.host || 'localhost', port: config.postgres?.port || 5432, database: config.postgres?.database || '', user: config.postgres?.user || '', password: config.postgres?.password || '', }, questdb: this.options.enableQuestDB ? config.questdb : undefined, proxy: this.options.enableProxy ? (config.proxy || { cachePrefix: 'proxy:', ttl: 3600 }) : undefined, browser: this.options.enableBrowser ? config.browser : undefined, queue: this.options.enableQueue ? { enabled: this.options.enableQueue } : undefined, }; } private registerServices(container: AwilixContainer, config: AppConfig): void { registerCoreServices(container, config); registerCacheServices(container, config); registerDatabaseServices(container, config); registerApplicationServices(container, config); // Register service container aggregate container.register({ serviceContainer: asFunction(({ config, logger, cache, proxyManager, browser, queueManager, mongoClient, postgresClient, questdbClient }) => ({ logger, cache, proxy: proxyManager, // Map proxyManager to proxy browser, queue: queueManager, // Map queueManager to queue mongodb: mongoClient, // Map mongoClient to mongodb postgres: postgresClient, // Map postgresClient to postgres questdb: questdbClient, // Map questdbClient to questdb })).singleton(), }); } private transformStockBotConfig(config: AppConfig | StockBotAppConfig): Partial { // If it's already in the new format, return as is if ('redis' in config) { return config as AppConfig; } // Transform from StockBotAppConfig format const stockBotConfig = config as StockBotAppConfig; return { redis: stockBotConfig.database?.dragonfly ? { enabled: true, host: stockBotConfig.database.dragonfly.host || 'localhost', port: stockBotConfig.database.dragonfly.port || 6379, password: stockBotConfig.database.dragonfly.password, db: stockBotConfig.database.dragonfly.db || 0, } : undefined, mongodb: stockBotConfig.database?.mongodb ? { enabled: true, uri: stockBotConfig.database.mongodb.uri || `mongodb://${stockBotConfig.database.mongodb.user || ''}:${stockBotConfig.database.mongodb.password || ''}@${stockBotConfig.database.mongodb.host || 'localhost'}:${stockBotConfig.database.mongodb.port || 27017}/${stockBotConfig.database.mongodb.database || 'test'}?authSource=${stockBotConfig.database.mongodb.authSource || 'admin'}`, database: stockBotConfig.database.mongodb.database || 'test', } : undefined, postgres: stockBotConfig.database?.postgres ? { enabled: true, host: stockBotConfig.database.postgres.host || 'localhost', port: stockBotConfig.database.postgres.port || 5432, database: stockBotConfig.database.postgres.database || 'test', user: stockBotConfig.database.postgres.user || 'test', password: stockBotConfig.database.postgres.password || 'test', } : undefined, questdb: stockBotConfig.database?.questdb ? { enabled: true, host: stockBotConfig.database.questdb.host || 'localhost', httpPort: stockBotConfig.database.questdb.httpPort || 9000, pgPort: stockBotConfig.database.questdb.pgPort || 8812, influxPort: stockBotConfig.database.questdb.ilpPort || 9009, database: stockBotConfig.database.questdb.database || 'questdb', } : undefined, }; } } // Add missing import import { asFunction } from 'awilix';