import { getLogger, type Logger } from '@stock-bot/logger'; import { MongoDBClient, createMongoDBClient, type ConnectionEvents } from '@stock-bot/mongodb'; import { PostgreSQLClient, createPostgreSQLClient } from '@stock-bot/postgres'; import { createCache, type CacheProvider } from '@stock-bot/cache'; import { QueueManager } from '@stock-bot/queue'; import type { ConnectionFactory as IConnectionFactory, ConnectionPool, ConnectionFactoryConfig, MongoDBPoolConfig, PostgreSQLPoolConfig, CachePoolConfig, QueuePoolConfig, PoolMetrics, } from './types'; export class ConnectionFactory implements IConnectionFactory { private readonly logger: Logger; private readonly pools: Map> = new Map(); private readonly config: ConnectionFactoryConfig; constructor(config: ConnectionFactoryConfig) { this.config = config; this.logger = getLogger(`connection-factory:${config.service}`); } async createMongoDB(poolConfig: MongoDBPoolConfig): Promise> { const key = `mongodb:${poolConfig.name}`; if (this.pools.has(key)) { this.logger.debug('Reusing existing MongoDB pool', { name: poolConfig.name }); return this.pools.get(key)!; } this.logger.info('Creating MongoDB connection pool', { name: poolConfig.name, poolSize: poolConfig.poolSize, }); try { const events: ConnectionEvents = { onConnect: () => { this.logger.debug('MongoDB connected', { pool: poolConfig.name }); }, onDisconnect: () => { this.logger.debug('MongoDB disconnected', { pool: poolConfig.name }); }, onError: (error) => { this.logger.error('MongoDB error', { pool: poolConfig.name, error }); }, }; const client = createMongoDBClient({ ...poolConfig.config, poolSettings: { maxPoolSize: poolConfig.maxConnections || poolConfig.poolSize || 10, minPoolSize: poolConfig.minConnections || 2, maxIdleTime: 30000, } }, events); await client.connect(); // Warm up the pool if (poolConfig.minConnections) { await client.warmupPool(); } const pool: ConnectionPool = { name: poolConfig.name, client, metrics: client.getPoolMetrics(), health: async () => { try { await client.getDatabase().admin().ping(); return true; } catch { return false; } }, dispose: async () => { await client.disconnect(); this.pools.delete(key); }, }; this.pools.set(key, pool); return pool; } catch (error) { this.logger.error('Failed to create MongoDB pool', { name: poolConfig.name, error }); throw error; } } async createPostgreSQL(poolConfig: PostgreSQLPoolConfig): Promise> { const key = `postgres:${poolConfig.name}`; if (this.pools.has(key)) { this.logger.debug('Reusing existing PostgreSQL pool', { name: poolConfig.name }); return this.pools.get(key)!; } this.logger.info('Creating PostgreSQL connection pool', { name: poolConfig.name, poolSize: poolConfig.poolSize, }); try { const events: ConnectionEvents = { onConnect: () => { this.logger.debug('PostgreSQL connected', { pool: poolConfig.name }); }, onDisconnect: () => { this.logger.debug('PostgreSQL disconnected', { pool: poolConfig.name }); }, onError: (error) => { this.logger.error('PostgreSQL error', { pool: poolConfig.name, error }); }, }; const client = createPostgreSQLClient({ ...poolConfig.config, poolSettings: { max: poolConfig.maxConnections || poolConfig.poolSize || 10, min: poolConfig.minConnections || 2, idleTimeoutMillis: poolConfig.idleTimeoutMillis || 30000, }, }, undefined, events); await client.connect(); // Warm up the pool if (poolConfig.minConnections) { await client.warmupPool(); } const pool: ConnectionPool = { name: poolConfig.name, client, metrics: client.getPoolMetrics(), health: async () => client.connected, dispose: async () => { await client.disconnect(); this.pools.delete(key); }, }; this.pools.set(key, pool); return pool; } catch (error) { this.logger.error('Failed to create PostgreSQL pool', { name: poolConfig.name, error }); throw error; } } createCache(poolConfig: CachePoolConfig): ConnectionPool { const key = `cache:${poolConfig.name}`; if (this.pools.has(key)) { this.logger.debug('Reusing existing cache pool', { name: poolConfig.name }); return this.pools.get(key)!; } this.logger.info('Creating cache connection pool', { name: poolConfig.name, }); try { const cache = createCache({ ...poolConfig.config, keyPrefix: `${this.config.service}:${poolConfig.name}:`, shared: false, // Each pool gets its own connection }); const pool: ConnectionPool = { name: poolConfig.name, client: cache, metrics: this.createInitialMetrics(), health: async () => cache.health(), dispose: async () => { // Cache disposal handled internally this.pools.delete(key); }, }; this.pools.set(key, pool); return pool; } catch (error) { this.logger.error('Failed to create cache pool', { name: poolConfig.name, error }); throw error; } } createQueue(poolConfig: QueuePoolConfig): ConnectionPool { const key = `queue:${poolConfig.name}`; if (this.pools.has(key)) { this.logger.debug('Reusing existing queue manager', { name: poolConfig.name }); return this.pools.get(key)!; } this.logger.info('Creating queue manager', { name: poolConfig.name, }); try { // Initialize or get existing QueueManager instance const queueManager = QueueManager.getOrInitialize(poolConfig.config); const pool: ConnectionPool = { name: poolConfig.name, client: queueManager, metrics: this.createInitialMetrics(), health: async () => { try { // Check if QueueManager is initialized queueManager.getQueueNames(); return true; } catch { return false; } }, dispose: async () => { // QueueManager handles its own shutdown await queueManager.shutdown(); this.pools.delete(key); }, }; this.pools.set(key, pool); return pool; } catch (error) { this.logger.error('Failed to create queue manager', { name: poolConfig.name, error }); throw error; } } getPool(type: 'mongodb' | 'postgres' | 'cache' | 'queue', name: string): ConnectionPool | undefined { const key = `${type}:${name}`; return this.pools.get(key); } listPools(): Array<{ type: string; name: string; metrics: PoolMetrics }> { const result: Array<{ type: string; name: string; metrics: PoolMetrics }> = []; for (const [key, pool] of this.pools.entries()) { const [type, ...nameParts] = key.split(':'); result.push({ type: type || 'unknown', name: nameParts.join(':'), metrics: pool.metrics, }); } return result; } async disposeAll(): Promise { this.logger.info('Disposing all connection pools', { count: this.pools.size }); const disposePromises: Promise[] = []; for (const pool of this.pools.values()) { disposePromises.push(pool.dispose()); } await Promise.all(disposePromises); this.pools.clear(); } private createInitialMetrics(): PoolMetrics { return { created: new Date(), totalConnections: 0, activeConnections: 0, idleConnections: 0, waitingRequests: 0, errors: 0, }; } }