added new di with connection pool and rebuild of the database/cache services

This commit is contained in:
Boki 2025-06-21 14:54:51 -04:00
parent be6afef832
commit 09d907a10c
26 changed files with 4844 additions and 205 deletions

View file

@ -0,0 +1,234 @@
import { getLogger, type Logger } from '@stock-bot/logger';
import { MongoDBClient, createMongoDBClient, type ConnectionEvents } from '@stock-bot/mongodb-client';
import { PostgreSQLClient, createPostgreSQLClient } from '@stock-bot/postgres-client';
import { createCache, type CacheProvider } from '@stock-bot/cache';
import type {
ConnectionFactory as IConnectionFactory,
ConnectionPool,
ConnectionFactoryConfig,
MongoDBPoolConfig,
PostgreSQLPoolConfig,
CachePoolConfig,
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;
}
}
getPool(type: 'mongodb' | 'postgres' | 'cache', 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,
};
}
}