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: config.redis || { enabled: this.options.enableCache ?? true, host: 'localhost', port: 6379, db: 0, }, mongodb: config.mongodb || { enabled: this.options.enableMongoDB ?? true, uri: '', database: '', }, postgres: config.postgres || { enabled: this.options.enablePostgres ?? true, host: 'localhost', port: 5432, database: 'postgres', user: 'postgres', password: 'postgres', }, questdb: this.options.enableQuestDB ? (config.questdb || { enabled: true, host: 'localhost', httpPort: 9000, pgPort: 8812, influxPort: 9009, database: 'questdb', }) : undefined, proxy: this.options.enableProxy ? (config.proxy || { cachePrefix: 'proxy:', ttl: 3600 }) : undefined, browser: this.options.enableBrowser ? (config.browser || { headless: true, timeout: 30000 }) : undefined, queue: this.options.enableQueue ? (config.queue || { enabled: true, workers: 1, concurrency: 1, enableScheduledJobs: true, delayWorkerStart: false, defaultJobOptions: { attempts: 3, backoff: { type: 'exponential' as const, delay: 1000 }, removeOnComplete: 100, removeOnFail: 50, } }) : 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 (has redis AND postgres at top level), return as is if ('redis' in config && 'postgres' in config && 'mongodb' 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: stockBotConfig.database.mongodb.enabled ?? true, uri: stockBotConfig.database.mongodb.uri, database: stockBotConfig.database.mongodb.database, } : undefined, postgres: stockBotConfig.database?.postgres ? { enabled: stockBotConfig.database.postgres.enabled ?? true, host: stockBotConfig.database.postgres.host, port: stockBotConfig.database.postgres.port, database: stockBotConfig.database.postgres.database, user: stockBotConfig.database.postgres.user, password: stockBotConfig.database.postgres.password, } : 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, queue: stockBotConfig.queue, browser: stockBotConfig.browser, proxy: stockBotConfig.proxy, }; } } // Add missing import import { asFunction } from 'awilix';