280 lines
No EOL
8.4 KiB
TypeScript
280 lines
No EOL
8.4 KiB
TypeScript
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<string, ConnectionPool<any>> = new Map();
|
|
private readonly config: ConnectionFactoryConfig;
|
|
|
|
constructor(config: ConnectionFactoryConfig) {
|
|
this.config = config;
|
|
this.logger = getLogger(`connection-factory:${config.service}`);
|
|
}
|
|
|
|
async createMongoDB(poolConfig: MongoDBPoolConfig): Promise<ConnectionPool<MongoDBClient>> {
|
|
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<MongoDBClient> = {
|
|
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<ConnectionPool<PostgreSQLClient>> {
|
|
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<PostgreSQLClient> = {
|
|
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<CacheProvider> {
|
|
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<CacheProvider> = {
|
|
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<QueueManager> {
|
|
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<QueueManager> = {
|
|
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<any> | 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<void> {
|
|
this.logger.info('Disposing all connection pools', { count: this.pools.size });
|
|
|
|
const disposePromises: Promise<void>[] = [];
|
|
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,
|
|
};
|
|
}
|
|
} |