UNPKG

@starbemtech/star-db-query-builder

Version:

A query builder to be used with mysql or postgres

201 lines (185 loc) 5.88 kB
import { Pool, PoolConnection } from 'mysql2/promise' import promiseRetry from 'promise-retry' import { IDatabaseClient, ITransactionClient } from './IDatabaseClient' import { RetryOptions } from '../core/types' import { monitor, MonitorEvents } from '../monitor/monitor' const transientErrorCodes = new Set([ 'ECONNRESET', 'ETIMEDOUT', 'PROTOCOL_CONNECTION_LOST', 'ECONNREFUSED', ]) /** * Checks if the given error is a transient error that can be retried * * Transient errors are temporary network or connection issues that typically * resolve themselves and can be safely retried. This function checks if the * error code matches known transient error patterns for MySQL connections. * * @param error - The error object to check * @returns boolean - True if the error is transient and can be retried, false otherwise * * @example * try { * const result = await pool.execute(sql, params); * } catch (error) { * if (isTransientError(error)) { * // Retry the operation * console.log('Transient error detected, retrying...'); * } else { * // Handle permanent error * throw error; * } * } */ function isTransientError(error: any): boolean { return error && error.code && transientErrorCodes.has(error.code) } /** * Creates a MySQL database client * * This function initializes a MySQL database client with the provided pool configuration * and optional retry options. It sets up monitoring for connection events and query operations. * * @param pool - The MySQL pool instance * @param retryOptions - Optional retry options for failed queries * @returns IDatabaseClient - The MySQL database client instance * * @example * const mysqlClient = createMysqlClient(pool, { retries: 3, factor: 2, minTimeout: 1000 }); * * @example * const mysqlClient = createMysqlClient(pool, { retries: 3, factor: 2, minTimeout: 1000 }); * * @example * const mysqlClient = createMysqlClient(pool, { retries: 3, factor: 2, minTimeout: 1000 }); * * @example * const mysqlClient = createMysqlClient(pool, { retries: 3, factor: 2, minTimeout: 1000 }); */ export const createMysqlClient = ( pool: Pool, retryOptions?: RetryOptions ): IDatabaseClient => { monitor.emit(MonitorEvents.CONNECTION_CREATED, { clientType: 'mysql', poolOptions: pool.config, }) return { clientType: 'mysql', query: async <T>(sql: string, params?: any[]): Promise<T> => { return promiseRetry(async (retry, attempt) => { const startTime = Date.now() try { monitor.emit(MonitorEvents.QUERY_START, { clientType: 'mysql', sql, params, attempt, }) const [rows] = await pool.execute(sql, params) const elapsedTime = Date.now() - startTime monitor.emit(MonitorEvents.QUERY_END, { clientType: 'mysql', sql, params, attempt, elapsedTime, }) return rows as unknown as T } catch (error: any) { const elapsedTime = Date.now() - startTime monitor.emit(MonitorEvents.QUERY_ERROR, { clientType: 'mysql', sql, params, attempt, elapsedTime, error, }) if (isTransientError(error)) { console.warn( `MySQL query attempt ${attempt} failed, retrying...`, error ) monitor.emit(MonitorEvents.RETRY_ATTEMPT, { clientType: 'mysql', sql, params, attempt, error, }) return retry(error) } throw error } }, retryOptions) }, beginTransaction: async (): Promise<ITransactionClient> => { const connection: PoolConnection = await pool.getConnection() try { await connection.beginTransaction() return { query: async <T>(sql: string, params?: any[]): Promise<T> => { const startTime = Date.now() try { monitor.emit(MonitorEvents.QUERY_START, { clientType: 'mysql', sql, params, attempt: 1, inTransaction: true, }) const [rows] = await connection.execute(sql, params) const elapsedTime = Date.now() - startTime monitor.emit(MonitorEvents.QUERY_END, { clientType: 'mysql', sql, params, attempt: 1, elapsedTime, inTransaction: true, }) return rows as unknown as T } catch (error) { const elapsedTime = Date.now() - startTime monitor.emit(MonitorEvents.QUERY_ERROR, { clientType: 'mysql', sql, params, attempt: 1, elapsedTime, error, inTransaction: true, }) throw error } }, commit: async (): Promise<void> => { try { await connection.commit() monitor.emit(MonitorEvents.TRANSACTION_COMMIT, { clientType: 'mysql', }) } finally { connection.release() } }, rollback: async (): Promise<void> => { try { await connection.rollback() monitor.emit(MonitorEvents.TRANSACTION_ROLLBACK, { clientType: 'mysql', }) } finally { connection.release() } }, } } catch (error) { connection.release() throw error } }, } }