Search code examples
typescriptnestjstypeorm

Custom entity manager with transaction in typeorm


So, we need to implement a custom entity manager under the transaction function with typeorm. Why? Because we wrap the entity manager from typeorm and power it up with some custom function we needed.

@Injectable()
export class CustomEntityManager extends EntityManager implements IEntityManager {
  constructor(connection?: string) {
    super(getConnection(connection));
  }

  //#region CRUD methods

  async createOne<Entity, Dto extends CreateDto>(
    entity: EntityTarget<Entity>,
    dto: Dto,
    extraOptions?: ExtraOptions,
  ): Promise<Entity> {
    try {
      const _entity = this.create(entity, instanceToPlain(dto) as any);
      return await this.save(entity, _entity);
    } catch (err) {
      const entityName = extraOptions?.entityName || startCase((entity as Function).name);
      throw new CreateEntityException(entityName, err);
    }
  }
  // a lot of more functions

  customSoftDelete<Entity>(entity: EntityTarget<Entity>, criteria: any): Promise<UpdateResult> {
    try {
      return this.update(entity, criteria, { deleted: true } as any);
    } catch (e) {
      console.log(e);
      // @todo: open this error and check
      throw e;
    }
  }

  customTransaction<Entity>(
    runInTransaction: (entityManager: CustomEntityManager) => Promise<Entity>,
  ): Promise<Entity> {
    return super.transaction(runInTransaction);
  }

}

So, this is how our custom entity manager looks like. and now we are trying to use it ir order to soft delete some entities (before u say the typerom's soft delete is not working as we our needs)

  async deleteOne(id: Id): Promise<void> {
    const company = await this.findOneWithScopeOr404({ where: { id } });
    await this.manager.customTransaction(async (transactionManager) => {
      console.log(transactionManager);
      this.logger.log('removing company users');
      const userTransaction = await transactionManager.customSoftDelete(User, { companyId: company.id });
      this.logger.log(userTransaction);
    });
}

But when doing this, I thought that the transactionManager under the customTransaction was the "custom entity manager" but it wasn't. In compiling time there was no error but in runtime it was.. customSoftDelete wasn't a function of "entityManager" Is there anyway of modify this?


Solution

  • This is a possible solution, let me know if it works.

    First there is no typing problem because you declared this here :

    runInTransaction: (entityManager: CustomEntityManager) => Promise<Entity>,
    

    But in reality typeorm Transaction has the following signature:

        async transaction<T>(
            runInTransaction: (entityManager: EntityManager) => Promise<T>,
        ): Promise<T>
    
    

    When you call the method customTransaction, you are just returning the parent behavior so it's normal that the transactionManager is the parent (EntityManager).

    However you can bypass this by using function bindings :

      customTransaction<Entity>(
        runInTransaction: (entityManager: CustomEntityManager) => Promise<Entity>,
      ): Promise<Entity> {
        return super.transaction(runInTransaction.bind(null, this)); // this equals to an instance of CustomEntityManager
      }