UNPKG

@200systems/mf-db-mysql

Version:

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

188 lines 6.58 kB
// packages/db-mysql/src/client.ts import mysql from 'mysql2/promise'; import { ConnectionError, QueryError, TransactionError } from '@200systems/mf-db-core'; import { MySQLTransaction } from './transaction.js'; import { MySqlSqlAdapter } from './adapter.js'; /** * Unified MySQL Database Client implementation * Follows the exact same interface as PostgreSQL for easy switching */ export class MySQLClient { pool; logger; sqlAdapter; isInitialized = false; constructor(config, logger) { this.logger = logger.child('mysql-client'); this.sqlAdapter = new MySqlSqlAdapter(); this.pool = mysql.createPool({ host: config.host, port: config.port, database: config.database, user: config.user, password: config.password, charset: config.charset || 'utf8mb4', timezone: config.timezone || 'Z', connectionLimit: config.max || 10, queueLimit: 0, }); } async initialize() { if (this.isInitialized) return; try { // Test connection const connection = await this.pool.getConnection(); await connection.query('SELECT 1'); connection.release(); this.isInitialized = true; this.logger.info('MySQL client initialized'); } catch (error) { this.logger.error('Failed to initialize MySQL client', error); throw error; } } getDatabaseType() { return 'mysql'; } getSqlAdapter() { return this.sqlAdapter; } supportsReturning() { return false; } async close() { if (!this.isInitialized || !this.pool) return; try { await this.pool.end(); this.isInitialized = false; this.logger.info('MySQL client closed'); } catch (error) { this.logger.error('Failed to close MySQL client', error); throw error; } } async query(sql, params = []) { if (!this.isInitialized || !this.pool) { throw new ConnectionError('MySQL client not initialized'); } const timer = this.logger.startTimer('query'); try { const [rows, fields] = await this.pool.execute(sql, params); const rowsArray = Array.isArray(rows) ? rows : []; this.logger.debug('Query executed', { rowCount: rowsArray.length, sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : '') }); return { rows: rows, rowCount: Array.isArray(rows) ? rows.length : rows.affectedRows || 0, fields: fields?.map(field => ({ name: field.name, dataTypeID: field.type, })), }; } catch (error) { this.logger.error('Query failed', error, { sql, params }); throw new QueryError(`Query execution failed: ${error.message}`, sql, params, error); } finally { timer(); } } async transaction(callback) { if (!this.pool || !this.isInitialized) { throw new ConnectionError('Database not initialized'); } const connection = await this.pool.getConnection(); const transactionLogger = this.logger.child('transaction'); const timer = transactionLogger.startTimer('transaction'); try { // Create MySQL transaction using existing BaseTransaction const transaction = new MySQLTransaction(connection, this.logger); transactionLogger.debug('Transaction started'); const result = await callback(transaction); await connection.commit(); transactionLogger.debug('Transaction committed'); return result; } catch (error) { await connection.rollback(); transactionLogger.error('Transaction rolled back', error); throw new TransactionError(`Transaction failed: ${error.message}`, error); } finally { // don't release the connection on a transaction, since the pool will handle it // connection.release(); timer(); } } async healthCheck() { const startTime = Date.now(); try { if (!this.pool || !this.isInitialized) { return { status: 'unhealthy', message: 'Database not initialized', timestamp: new Date(), responseTime: Date.now() - startTime }; } // Simple health check query await this.pool.execute('SELECT 1 as health'); const responseTime = Date.now() - startTime; const poolStatus = this.getPoolStatus(); this.logger.debug('Health check passed', { responseTime, poolStatus }); return { status: 'healthy', message: 'Connection successful', timestamp: new Date(), responseTime, details: { activeConnections: poolStatus.active, totalConnections: poolStatus.total } }; } 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 }; } const poolStatus = this.getPoolStatus(); return { total: poolStatus.total, idle: poolStatus.idle, waiting: poolStatus.waiting }; } getPoolStatus() { if (!this.pool) { return { active: 0, total: 0, idle: 0, waiting: 0 }; } // MySQL2 pool properties (may need adjustment based on version) const pool = this.pool; return { active: pool._allConnections?.length || 0, total: pool._allConnections?.length || 0, idle: pool._freeConnections?.length || 0, waiting: pool._connectionQueue?.length || 0 }; } } //# sourceMappingURL=client.js.map