@200systems/mf-db-postgres
Version:
PostgreSQL database client with connection pooling, migrations, and health monitoring
177 lines • 6.23 kB
JavaScript
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