diff --git a/packages/server/src/database/ITransactionManager.ts b/packages/server/src/database/ITransactionManager.ts index 0b6c046..dfa2b37 100644 --- a/packages/server/src/database/ITransactionManager.ts +++ b/packages/server/src/database/ITransactionManager.ts @@ -6,6 +6,7 @@ import { } from 'typeorm' export const ENTITY_MANAGER = 'ENTITY_MANAGER' +export const TRANSACTION_ID = 'TRANSACTION_ID' export interface ITransactionManager { getEntityManager: () => EntityManager @@ -13,6 +14,15 @@ export interface ITransactionManager { target: ObjectType | EntitySchema | string, ) => Repository isInTransaction: () => boolean + /** + * Start a new or reuse an existing transaction. + */ doInTransaction: ( fn: (entityManager: EntityManager) => Promise) => Promise + /** + * Always start a new transaction, regardless if there is one already in + * progress in the current context. + */ + doInNewTransaction: ( + fn: (entityManager: EntityManager) => Promise) => Promise } diff --git a/packages/server/src/database/TransactionManager.ts b/packages/server/src/database/TransactionManager.ts index bdc346d..5e22780 100644 --- a/packages/server/src/database/TransactionManager.ts +++ b/packages/server/src/database/TransactionManager.ts @@ -1,5 +1,8 @@ import {Namespace} from 'cls-hooked' -import {ENTITY_MANAGER, ITransactionManager} from './ITransactionManager' +import shortid from 'shortid' +import { + ENTITY_MANAGER, ITransactionManager, TRANSACTION_ID +} from './ITransactionManager' import { Connection, EntityManager, @@ -34,25 +37,38 @@ export class TransactionManager implements ITransactionManager { return !!this.ns.get(ENTITY_MANAGER) } - async doInTransaction(fn: (entityManager: EntityManager) => Promise) { + async doInTransaction(fn: (em: EntityManager) => Promise) { const alreadyInTransaction = this.isInTransaction() if (alreadyInTransaction) { return await fn(this.getEntityManager()) } - return this.getConnection().manager - .transaction(async entityManager => { - this.setEntityManager(entityManager) + return this.doInNewTransaction(fn) + } + + async doInNewTransaction(fn: (em: EntityManager) => Promise) { + return this.ns.runAndReturn(async () => { + this.setTransactionId(shortid()) try { - return await fn(entityManager) + return await this.getConnection().manager + .transaction(async entityManager => { + this.setEntityManager(entityManager) + try { + return await fn(entityManager) + } finally { + this.setEntityManager(undefined) + } + }) } finally { - if (!alreadyInTransaction) { - this.setEntityManager(undefined) - } + this.setTransactionId(undefined) } }) } + protected setTransactionId(transactionId: string | undefined) { + this.ns.set(TRANSACTION_ID, transactionId) + } + protected setEntityManager(entityManager: EntityManager | undefined) { this.ns.set(ENTITY_MANAGER, entityManager) }