Search code examples
javascriptnode.jstypescriptexpresstypeorm

TypeORM custom repository not overriding createQueryBuilder


I am trying to implement database i18n for TypeORM. I have been able to do that theoratically. But I also want to override built-in repository methods to use i18n (basically intercepting them and adding a query). How can I override createQueryBuilder in TypeORM repository? I can override methods like findMany, findOne and add new methods. But it is not working for createQueryBuilder. I know I can override all select methods in the repository. But I want to do it in one place and tried to override createQueryBuilder. The code shows my custom repository definitions.

    export function TranslatableRepository<T>() {
      return {
        createQueryBuilder<Entity>(
          alias?: string,
          queryRunner?: QueryRunner
        ): SelectQueryBuilder<Entity> {
          throw new Error();
          let qb = this.manager.createQueryBuilder<Entity>(
            this.metadata.target as any,
            alias || this.metadata.targetName,
            queryRunner || this.queryRunner
          );

          qb = qb.leftJoinAndSelect(
            `${alias || this.metadata.targetName}.translations`,
            'translation',
            'translation.locale = :locale',
            {
              locale: this.getLocale()
            }
          );

          return qb;
        },

        getLocale() {
          return getLocaleFromContext();
        }
      } as ThisType<Repository<T> & TTranslatableRepository<T>> &
        TTranslatableRepository<T>;
    }

And I use it like postRepository.extend(TranslatableRepository<Post>()). The createQueryBuilder method above should throw the error. But it is using built-in method. This is the copy of extend method in TypeORM souce code.

    extend(custom) {
        // return {
        //     ...this,
        //     ...custom
        // };
        const thisRepo = this.constructor;
        const { target, manager, queryRunner } = this;
        const cls = new (class extends thisRepo {})(target, manager, queryRunner);
        Object.assign(cls, custom);
        return cls;
    }

How can I override createQueryBuilder or is there any other ways to intercept queries?


Solution

  • Finally got it! I need to override the createQueryBuilder method in entity manager instead of in the repository.

    Edited: Warning, the following code will override the entityManager so if we don't want to mutate the default entityManager from appDataSource.manager, we need to provide a new entityManager, presumably from appDataSource.createEntityManager().

    export function TranslatableRepository(manager: EntityManager) {
      manager.createQueryBuilder = function createQueryBuilder<Entity>(
        entityClass?: EntityTarget<Entity> | QueryRunner,
        alias?: string,
        queryRunner?: QueryRunner
      ): SelectQueryBuilder<Entity> {
        let qb: SelectQueryBuilder<Entity>;
        if (alias) {
          qb = this.connection.createQueryBuilder(
            entityClass as EntityTarget<Entity>,
            alias,
            queryRunner || this.queryRunner
          );
        } else {
          qb = this.connection.createQueryBuilder(
            (entityClass as QueryRunner | undefined) ||
              queryRunner ||
              this.queryRunner
          );
        }
    
        return qb.leftJoinAndSelect(
          `${alias}.translations`,
          'translation',
          'translation.locale = :locale',
          {
            locale: getLocaleFromContext()
          }
        );
      };
    
      return {};
    }