UNPKG

node-mysql-query-utils

Version:
186 lines (163 loc) 6.81 kB
import mysql, { format, type ConnectionOptions, type Pool, } from 'mysql2/promise'; import { TableModelConstructor, DatabaseConnectionConfig, DatabaseManagementOptions } from './types'; import logger from '@lib/logger'; import { TableModel } from '@src/table-model'; // Singleton class to manage database connections export class DatabaseManagement { private static instances: Map<string, DatabaseManagement> = new Map(); private config: ConnectionOptions private verbose: boolean; connectionName: string; pool: Pool | null; constructor(connectionName: string, config: ConnectionOptions, options: DatabaseManagementOptions = {}) { this.connectionName = `[db::${connectionName}]`; this.config = config; this.pool = null; this.verbose = options.verbose ?? true; } private logVerbose(message: string) { if (this.verbose) logger.log(message); } private initConnection() { try { this.pool = mysql.createPool(this.config); if (this.verbose) { logger.info(`Connection pool created for ${this.connectionName}`); } // Test the connection, and release it back to the pool // const connection = await this.pool.getConnection(); // connection.release(); // if (this.verbose) { // logger.log(`Connection tested and released for ${this.connectionName}`); // } } catch (error) { if (error instanceof Error) { logger.error(`Failed to connect database: ${error.message}`); throw error; // Re-throw the error after logging it } logger.error(`Failed to connect database begin`); console.log(error); logger.error(`Failed to connect database end`); throw error; // Re-throw the error after logging it } } private static initializeConnection(identifierName: string, config: ConnectionOptions, options?: DatabaseManagementOptions) { if (!DatabaseManagement.instances.has(identifierName)) { try { const instance = new DatabaseManagement(identifierName, config, options); instance.initConnection(); // Initialize the connection logger.info(`db.connection :: <${identifierName}> :: host >> ${config.host}, database >> ${config.database}`); DatabaseManagement.instances.set(identifierName, instance); } catch (error) { console.error(`Failed to initialize database connection for identifier ${identifierName}:`, error); throw error; // Re-throw the error after logging it } } } static connectSingleDatabase(identifierName: string, config: ConnectionOptions, options?: DatabaseManagementOptions) { DatabaseManagement.initializeConnection(identifierName, config, options); } static connectMultipleDatabases(configs: DatabaseConnectionConfig[]) { for (const { identifierName, config, options } of configs) { DatabaseManagement.initializeConnection(identifierName, config, options); } } static getInstance(identifierName: string): DatabaseManagement { const instance = DatabaseManagement.instances.get(identifierName); if (!instance) { throw new Error(`No instance found for identifierName: ${identifierName}`); } return instance; } static getInstanceList(logInstanceNames: boolean = true): Map<string, DatabaseManagement> | string[] { if (logInstanceNames) { const instanceNames = Array.from(DatabaseManagement.instances.keys()); logger.log('Instance Name List: ', instanceNames); return instanceNames } return DatabaseManagement.instances; } static formatQuery(sql: string, params?: any[]) { return format(sql, params); } formatQuery(sql: string, params?: any[]) { return DatabaseManagement.formatQuery(sql, params); } createTableModel<ColumnKeys extends string, PrimaryKey extends ColumnKeys>(BuildSQLConstructor: Omit<TableModelConstructor<ColumnKeys[], PrimaryKey>, 'queryFn'>) { return new TableModel({ ...BuildSQLConstructor, queryFn: this.executeQuery.bind(this) }); } async createTransactionConnection() { if (!this.pool) { throw new Error('Connection pool has not been initialized.'); } const connection = await this.pool.getConnection(); this.logVerbose(`${this.connectionName} :: transaction :: connection created`); await connection.beginTransaction(); this.logVerbose(`${this.connectionName} :: transaction :: transaction started`); return { query: async (sql: string, params?: any[]) => { this.logVerbose(`${this.connectionName} :: query :: stmt >> ${this.formatQuery(sql, params)}`); const [result, mysqlFieldMetaData] = await connection.query(sql, params); return result; }, commit: async (options: { release: boolean }) => { const { release = false } = options || {}; try { this.logVerbose(`${this.connectionName} :: transaction :: committing`); await connection.commit(); } finally { if (release) { this.logVerbose(`${this.connectionName} :: transaction :: connection released`); connection.release(); } } }, rollback: async (options: { release: boolean }) => { const { release = false } = options || {}; try { this.logVerbose(`${this.connectionName} :: transaction :: rolling back`); await connection.rollback(); } finally { if (release) { this.logVerbose(`${this.connectionName} :: transaction :: connection released`); connection.release(); } } }, release: () => { this.logVerbose(`${this.connectionName} :: transaction :: connection released`); connection.release(); }, } } async destroy() { if (this.pool) { await this.pool.end(); DatabaseManagement.instances.delete(this.connectionName); logger.log(`Database connection <${this.connectionName}> has been destroyed.`); } else { throw new Error('Connection pool has not been initialized.'); } } async executeQuery<T = any>(sql: string, params?: any[]): Promise<T> { // Log the constructed SQL query and parameters // if (this.verbose) { // console.log('Output SQL:', sql); // console.log('With parameters:', params); // } // Use mysql2 to format the query with parameters const formattedQuery = this.formatQuery(sql, params); this.logVerbose(`${this.connectionName} :: query :: stmt >> ${formattedQuery}`); if (!this.pool) { throw new Error('Connection pool is not initialized.'); } const connection = await this.pool.getConnection(); const [result, mysqlFieldMetaData] = await connection.query(sql, params); connection.release(); return result as T; } }