import { Injectable } from '@nestjs/common' ; import { EntityManager , Repository } from 'typeorm' ; import { ModuleRef } from '@nestjs/core' ; import { PARAMTYPES_METADATA } from '@nestjs/common/constants' ; type ClassType < T = any > = new ( ... args ) => T ; type ForwardRef = { forwardRef : ( ) => any ; } ; export interface WithTransactionOptions { /**
     * Class types, that will not rebuild in transaction,
     * for example - it can be a service without any repositories
     * or some cache service, and that service must not rebuild in
     * app in any time
     */ excluded ? : ClassType [ ] ; } @ Injectable ( ) export class TransactionFor < T = any > { private cache : Map < string , any > = new Map ( ) ; constructor ( private moduleRef ) { } public withTransaction ( manager , transactionOptions = { } ) { const newInstance = this . findArgumentsForProvider ( this . constructor as ClassType < this > , manager , transactionOptions . excluded ?? [ ] ) ; this . cache . clear ( ) ; return newInstance ; } private getArgument ( param , manager , excluded ) { if ( typeof param === 'object' && 'forwardRef' in param ) { return this . moduleRef . get ( param . forwardRef ( ) . name , { strict : false } ) ; } const id = typeof param === 'string' ? param : typeof param === 'function' ? param . name : undefined ; if ( id === undefined ) { throw new Error ( `Can't get injection token from ${ param }` ) ; } const isExcluded = excluded . length > 0 && excluded . some ( ( ex ) => ex . name === id ) ; if ( id === `${ ModuleRef . name }` ) { return this . moduleRef ; } if ( isExcluded ) { return this . moduleRef . get ( id , { strict : false } ) ; } let argument ; if ( this . cache . has ( id ) ) { return this . cache . get ( id ) ; } const canBeRepository = id . includes ( 'Repository' ) ; if ( typeof param === 'string' || canBeRepository ) { let dependency ; try { if ( canBeRepository ) { return manager . getCustomRepository ( param as any ) ; } } catch ( error ) { dependency = this . moduleRef . get ( param , { strict : false } ) ; } if ( dependency ! instanceof Repository || canBeRepository ) { const entity = dependency ! . metadata . target ; argument = manager . getRepository ( entity ) ; } else { argument = dependency ! ; } } else { argument = this . findArgumentsForProvider ( param as ClassType , manager , excluded ) ; } this . cache . set ( id , argument ) ; return argument ; } private findArgumentsForProvider ( constructor , manager , excluded ) { const args = [ ] ; const keys = Reflect . getMetadataKeys ( constructor ) ; keys . forEach ( ( key ) => { if ( key === PARAMTYPES_METADATA ) { const paramTypes = Reflect . getMetadata ( key , constructor ) ; for ( const param of paramTypes ) { const argument = this . getArgument ( param , manager , excluded ) ; args . push ( argument ) ; } } } ) ; return new constructor ( ... args ) ; } }