UNPKG

@js-ak/db-manager

Version:
158 lines (157 loc) 6.04 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TransactionManager = void 0; /** * Enumeration of valid transaction isolation levels. */ const TransactionIsolationLevel = Object.freeze({ "READ COMMITTED": "READ COMMITTED", "READ UNCOMMITTED": "READ UNCOMMITTED", "REPEATABLE READ": "REPEATABLE READ", SERIALIZABLE: "SERIALIZABLE", }); /** * @experimental */ class TransactionManager { #client; #isolationLevel; /** * Creates an instance of TransactionManager. * * @param client - The MySQL client to manage transactions. * @param [isolationLevel] - Optional isolation level for the transaction. * * @throws {Error} If an invalid transaction isolation level is provided. */ constructor(client, isolationLevel) { this.#client = client; this.#isolationLevel = isolationLevel; if (isolationLevel && !TransactionIsolationLevel[isolationLevel]) { throw new Error("Invalid transaction isolation level provided."); } } /** * Begins a transaction and sets the isolation level if specified. */ async #beginTransaction() { await this.#client.query("BEGIN;"); if (this.#isolationLevel) { await this.#client.query(`SET TRANSACTION ISOLATION LEVEL ${this.#isolationLevel};`); } } /** * Commits the current transaction. */ async #commitTransaction() { await this.#client.query("COMMIT;"); } /** * Rolls back the current transaction. */ async #rollbackTransaction() { await this.#client.query("ROLLBACK;"); } /** * Releases the database client back to the pool. */ #endConnection() { this.#client.release(); } /** * Executes a function within a transaction. * * @param fn - The function to execute within the transaction. * @param options - Configuration options for the transaction. * @param [options.isLoggerEnabled] - Optional flag to enable logging. * @param [options.isolationLevel] - Optional isolation level for the transaction. * @param [options.logger] - Optional logger function. * @param options.pool - The MySQL connection pool. * @param [options.transactionId] - Optional transaction ID. * @param [options.timeToRollback] - Optional time to wait before rolling back the transaction in milliseconds. * * @returns The result of the function. * * @throws {Error} If the transaction fails, it is rolled back and the error is rethrown. */ static async execute(fn, options) { const client = await options.pool.getConnection(); const manager = new TransactionManager(client, options.isolationLevel); if (options.isLoggerEnabled) { const start = performance.now(); const { logger } = options || {}; // eslint-disable-next-line no-console const resultLogger = logger || { error: console.error, info: console.log }; const transactionId = options.transactionId || "::transactionId is not defined::"; let isTransactionFailed = false; try { await manager.#beginTransaction(); if (options.timeToRollback) { let timeout = null; const result = await Promise.race([ fn(client), new Promise((_, reject) => { timeout = setTimeout(() => reject(new Error(`Transaction (${options.transactionId || "::transactionId is not defined::"}) timed out`)), options.timeToRollback); return timeout; }), ]); timeout && clearTimeout(timeout); await manager.#commitTransaction(); return result; } else { const result = await fn(client); await manager.#commitTransaction(); return result; } } catch (error) { isTransactionFailed = true; await manager.#rollbackTransaction(); throw error; } finally { manager.#endConnection(); const execTime = Math.round(performance.now() - start); if (!isTransactionFailed) { resultLogger.info(`Transaction (${transactionId}) executed successfully in ${execTime} ms.`); } else { resultLogger.error(`Transaction (${transactionId}) failed in ${execTime} ms.`); } } } else { try { await manager.#beginTransaction(); if (options.timeToRollback) { let timeout = null; const result = await Promise.race([ fn(client), new Promise((_, reject) => { timeout = setTimeout(() => reject(new Error(`Transaction (${options.transactionId || "::transactionId is not defined::"}) timed out`)), options.timeToRollback); return timeout; }), ]); timeout && clearTimeout(timeout); await manager.#commitTransaction(); return result; } else { const result = await fn(client); await manager.#commitTransaction(); return result; } } catch (error) { await manager.#rollbackTransaction(); throw error; } finally { manager.#endConnection(); } } } } exports.TransactionManager = TransactionManager;