UNPKG

@200systems/mf-db-postgres

Version:

PostgreSQL database client with connection pooling, migrations, and health monitoring

177 lines 6.23 kB
import { Pool } from 'pg'; import { ConnectionError, QueryError, TransactionError } from '@200systems/mf-db-core'; import { PostgresTransaction } from './transaction.js'; import { PostgresSqlAdapter } from './adapter.js'; /** * Unified PostgreSQL Database Client implementation * Follows the exact same interface as MySQL for easy switching */ export class PostgresClient { pool; logger; sqlAdapter; isInitialized = false; constructor(config, logger) { this.logger = logger.child('postgres-client'); this.sqlAdapter = new PostgresSqlAdapter(); this.pool = new Pool({ host: config.host, port: config.port, database: config.database, user: config.user, password: config.password, max: config.max || 10, idleTimeoutMillis: config.idleTimeoutMillis || 30000, connectionTimeoutMillis: config.connectionTimeoutMillis || 2000, ssl: config.ssl }); this.pool.on('error', (err) => { this.logger.error('PostgreSQL pool error', err); }); } async initialize() { if (this.isInitialized) { this.logger.warn('Database already initialized'); return; } try { // Test connection const client = await this.pool.connect(); await client.query('SELECT 1'); client.release(); this.isInitialized = true; this.logger.info('PostgreSQL client initialized'); } catch (error) { this.logger.error('Failed to initialize PostgreSQL client', error); throw error; } } async close() { if (!this.isInitialized || !this.pool) return; try { await this.pool.end(); this.isInitialized = false; this.logger.info('PostgreSQL client closed'); } catch (error) { this.logger.error('Failed to close PostgreSQL client', error); throw error; } } getDatabaseType() { return 'postgresql'; } getSqlAdapter() { return this.sqlAdapter; } supportsReturning() { return true; } async query(sql, params = []) { if (!this.isInitialized || !this.pool) { throw new ConnectionError('PostgreSQL client not initialized'); } const timer = this.logger.startTimer('query'); try { this.logger.debug('Executing query', { sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''), paramCount: params.length }); const startTime = Date.now(); const result = await this.pool.query(sql, params); const duration = Date.now() - startTime; this.logger.debug('Query executed successfully', { duration, rowCount: result.rowCount }); return { rows: result.rows, rowCount: result.rowCount || 0, fields: result.fields?.map(field => ({ name: field.name, dataTypeID: field.dataTypeID })) || [] }; } catch (error) { this.logger.error('Query execution failed', error instanceof Error ? error : new Error(String(error)), { error, sql, params }); throw new QueryError(`Query execution failed: ${error.message}`, sql, params, error); } finally { timer(); } } async transaction(callback) { if (!this.isInitialized || !this.pool) { throw new ConnectionError('PostgreSQL client not initialized'); } const client = await this.pool.connect(); const transactionLogger = this.logger.child('transaction'); const timer = transactionLogger.startTimer('transaction'); try { await client.query('BEGIN'); transactionLogger.debug('Transaction started'); // Create PostgreSQL transaction using existing BaseTransaction const transaction = new PostgresTransaction(client, this.logger); const result = await callback(transaction); // Pass DatabaseTransaction await transaction.commit(); // Use transaction's commit method transactionLogger.debug('PostgreSQL transaction committed'); return result; } catch (error) { await client.query('ROLLBACK'); transactionLogger.error('Transaction rolled back', error); throw new TransactionError(`Transaction failed: ${error.message}`, error); } finally { // don't release the client on a transaction, since the pool will handle it // client.release(); timer(); } } async healthCheck() { const startTime = Date.now(); try { if (!this.pool) { return { status: 'unhealthy', message: 'Connection pool not initialized', timestamp: new Date(), responseTime: Date.now() - startTime }; } await this.pool.query('SELECT 1'); return { status: 'healthy', message: 'Connection successful', timestamp: new Date(), responseTime: Date.now() - startTime }; } catch (error) { return { status: 'unhealthy', message: error.message, timestamp: new Date(), responseTime: Date.now() - startTime }; } } isReady() { return this.isInitialized && this.pool !== null; } getConnectionInfo() { if (!this.pool) { return { total: 0, idle: 0, waiting: 0 }; } return { total: this.pool.totalCount, idle: this.pool.idleCount, waiting: this.pool.waitingCount }; } } export { PostgresTransaction }; //# sourceMappingURL=client.js.map