UNPKG

@myfunc/prisma-transactional

Version:

Decorator that wraps all prisma queries along the whole call stack to a single transaction.

68 lines (60 loc) 2.27 kB
import { PrismaClient } from '@prisma/client'; import { ClsService, ClsServiceManager } from 'nestjs-cls'; import { TX_CLIENT_KEY, TRANSACTION_TIMEOUT, TX_CLIENT_SUCCESS_CALLBACKS } from './const'; import { Manager } from './manager'; // That solution can join transactions. // Found here https://github.com/prisma/prisma/issues/5729 export { ClsService, ClsServiceManager }; /** That solution can creates and merge transactions. BE CAREFUL when using it, all queries inside transaction will be isolated and can lead to deadlock. Found here https://github.com/prisma/prisma/issues/5729 */ export function PrismaTransactional(isolationLevel?: string): MethodDecorator { return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { const originalMethod = descriptor.value; descriptor.value = function (...args: unknown[]) { return PrismaTransactional.execute( () => originalMethod.apply(this, [...args]), isolationLevel, ); }; }; } // Utility to manage success callback queue PrismaTransactional.onSuccess = <T>(callback: () => T | Promise<T>): T | Promise<T> | undefined => { const clsService = ClsServiceManager.getClsService(); const isActiveTransaction = clsService.get(TX_CLIENT_KEY); if (isActiveTransaction) { const existingCallbacks = clsService.get(TX_CLIENT_SUCCESS_CALLBACKS) || []; clsService.set(TX_CLIENT_SUCCESS_CALLBACKS, [...existingCallbacks, callback]); } else { return callback(); } }; // Run callback in transaction PrismaTransactional.execute = <T>( callback: () => Promise<T>, isolationLevel?: string, ): Promise<T> => { const cls = ClsServiceManager.getClsService(); if (cls.get(TX_CLIENT_KEY)) { return callback(); } else { return (Manager.prismaClient['$root'] as PrismaClient).$transaction( async () => { return callback(); }, { isolationLevel, timeout: TRANSACTION_TIMEOUT, }, ); } }; PrismaTransactional.prismaRoot = null as unknown as PrismaClient; // Run query with no transaction even if it exists. Object.defineProperty(PrismaTransactional, 'prismaRoot', { get(): PrismaClient { return Manager.prismaClient['$root'] as PrismaClient; }, });