Aliheym / typeorm-transactional

A Transactional Method Decorator for TypeORM that uses Async Local Storage or cls-hooked to handle and propagate transactions between different repositories and service methods.
MIT License
201 stars 27 forks source link

@Transactional not work with interceptor #41

Open thobui14ig opened 1 year ago

thobui14ig commented 1 year ago

I have multiple insert, delete, and update actions within several interceptors of a controller. I put the @Transactional on the controller, but it doesn't work with the interceptors.

galkin commented 10 months ago

@thobui14ig why should it work with interceptors?

Aliheym commented 10 months ago

Hi. Let me explain.

In Nest.js, interceptors, guards and pipes are either invoked before or after the controller function is executed. Not within it. Therefore, if you want to run their code within a transaction, you must use the @Transactional decorator directly within the interceptor/guard/pipe. Here's an example:

@Controller('entities')
export class EntityController {
  @Patch(':id')
  @Transactional()  // <------- 
  @UseInterceptors(CheckIfExistsInterceptor)
  async updateEntity(@Param('id') id: number) {
    await this.entityService.update(id, { name: 'Test' });
    console.log('Updated...');

    return { ok: true };
  }
}
@Injectable()
export class CheckIfExistsInterceptor implements NestInterceptor {
  constructor(
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
  ) {}

  @Transactional() // <------- 
  async intercept(
    context: ExecutionContext,
    next: CallHandler<any>,
  ): Promise<Observable<any>> {
    const { params } = context.switchToHttp().getRequest();

    const isEntityExists = await this.userRepository.findOne({
      where: { id: params.id },
    });
    if (!isEntityExists) {
      return throwError(() => new Error('Entity not found'));
    }

    return next.handle();
  }
}

Also, it's important to note that it's currently not possible to share code between an interceptor/pipe/guard and controller within the same transaction. Even if you specify a Propagation option, there will be separate transactions.

The provided code will produce the following "output":

query: START TRANSACTION -- <--- Interceptor's transaction was started
query: SELECT * FROM entity e WHERE e.id = ? LIMIT 1
query: COMMIT -- <-- Interceptor executed

query: START TRANSACTION -- <--- Controller's transaction was started
Updated
query: COMMIT

Here is a bit more information: https://github.com/Aliheym/typeorm-transactional/issues/1