Skip to content

Commit

Permalink
feat: updated pg and mysql TransactionManager execute
Browse files Browse the repository at this point in the history
added for options
isLoggerEnabled
logger
transactionId
timeToRollback
  • Loading branch information
JS-AK committed Dec 8, 2024
1 parent 4c7fe85 commit ba49c5d
Show file tree
Hide file tree
Showing 5 changed files with 1,045 additions and 25 deletions.
118 changes: 106 additions & 12 deletions src/lib/mysql/transaction-manager/transaction-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import mysql from "mysql2/promise";

import * as SharedTypes from "../../../shared-types/index.js";

/**
* Enumeration of valid transaction isolation levels.
*/
Expand Down Expand Up @@ -76,8 +78,12 @@ export class TransactionManager {
*
* @param fn - The function to execute within the transaction.
* @param options - Configuration options for the transaction.
* @param options.pool - The MySQL connection pool.
* @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.
*
Expand All @@ -86,25 +92,113 @@ export class TransactionManager {
static async execute<R>(
fn: (client: mysql.PoolConnection) => Promise<R>,
options: {
pool: mysql.Pool;
isLoggerEnabled?: true;
isolationLevel?: keyof typeof TransactionIsolationLevel;
logger?: SharedTypes.TLogger;
pool: mysql.Pool;
transactionId?: string;
timeToRollback?: number;
},
): Promise<R> {
const client = await options.pool.getConnection();
const manager = new TransactionManager(client, options.isolationLevel);

try {
await manager.#beginTransaction();
const result = await fn(client);
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: NodeJS.Timeout | null = 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;
}),
]) as R;

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: NodeJS.Timeout | null = 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;
}),
]) as R;

timeout && clearTimeout(timeout);

await manager.#commitTransaction();

return result;
} else {
const result = await fn(client);

await manager.#commitTransaction();
await manager.#commitTransaction();

return result;
} catch (error) {
await manager.#rollbackTransaction();
throw error;
} finally {
manager.#endConnection();
return result;
}
} catch (error) {
await manager.#rollbackTransaction();
throw error;
} finally {
manager.#endConnection();
}
}
}
}
118 changes: 106 additions & 12 deletions src/lib/pg/transaction-manager/transaction-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pg from "pg";

import * as SharedTypes from "../../../shared-types/index.js";

/**
* Enumeration of valid transaction isolation levels.
*/
Expand Down Expand Up @@ -76,8 +78,12 @@ export class TransactionManager {
*
* @param fn - The function to execute within the transaction.
* @param options - Configuration options for the transaction.
* @param options.pool - The PostgreSQL connection pool.
* @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 PostgreSQL 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.
*
Expand All @@ -86,25 +92,113 @@ export class TransactionManager {
static async execute<R>(
fn: (client: pg.PoolClient) => Promise<R>,
options: {
pool: pg.Pool;
isLoggerEnabled?: true;
isolationLevel?: keyof typeof TransactionIsolationLevel;
logger?: SharedTypes.TLogger;
pool: pg.Pool;
transactionId?: string;
timeToRollback?: number;
},
): Promise<R> {
const client = await options.pool.connect();
const manager = new TransactionManager(client, options.isolationLevel);

try {
await manager.#beginTransaction();
const result = await fn(client);
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: NodeJS.Timeout | null = 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;
}),
]) as R;

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: NodeJS.Timeout | null = 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;
}),
]) as R;

timeout && clearTimeout(timeout);

await manager.#commitTransaction();

return result;
} else {
const result = await fn(client);

await manager.#commitTransaction();
await manager.#commitTransaction();

return result;
} catch (error) {
await manager.#rollbackTransaction();
throw error;
} finally {
manager.#endConnection();
return result;
}
} catch (error) {
await manager.#rollbackTransaction();
throw error;
} finally {
manager.#endConnection();
}
}
}
}
2 changes: 1 addition & 1 deletion src/test/MYSQL/02/test-table-01/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const model = (creds: MYSQL.ModelTypes.TDBCreds) => new MYSQL.Model.BaseT
createField: null,
isPKAutoIncremented: false,
primaryKey: null,
tableFields: ["title"] as const,
tableFields: ["title"],
tableName: "test_table_01",
updateField: null,
},
Expand Down
Loading

0 comments on commit ba49c5d

Please sign in to comment.