nestjsx / crud

NestJs CRUD for RESTful APIs
https://github.com/nestjsx/crud/wiki
MIT License
4.08k stars 538 forks source link

Extend CrudService as mixin #715

Closed mitsos1os closed 3 years ago

mitsos1os commented 3 years ago

After taking a look at #209 and #710 I tried to write some factory fn in order to reduce the boilerplate required for creating a new TypeOrmCrudService class extensions and if it works I would like to try to also use this pattern for the controller.

I believe I am close enough but unfortunately it does not 100% work.

What I initially tried was:

BaseCrudService.ts

import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type';

export function BaseCrudService<T extends EntityClassOrSchema>(Entity: T) {
  class BaseCrudServiceHost extends TypeOrmCrudService<T> {
    constructor(@InjectRepository(Entity) repo: Repository<T>) {
      super(repo);
    }
  }
  return BaseCrudServiceHost;
}

However the Typescript compiler complained with the error: TS4060: Return type of exported function has or is using private name 'BaseCrudServiceHost'.

In order to fix this I tried to explicitly type the return result like this: BaseCrudService.ts

import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type';

export function BaseCrudService<T extends EntityClassOrSchema>(Entity: T) {
  class BaseCrudServiceHost extends TypeOrmCrudService<T> {
    constructor(@InjectRepository(Entity) repo: Repository<T>) {
      super(repo);
    }
  }
  return BaseCrudServiceHost as new (
    repo: Repository<T>,
  ) => TypeOrmCrudService<T>;
}

and used to create a service like this: UserService.ts

import { BaseCrudService } from '../../common/services/BaseCrudService';
import { User } from '../entities/user.entity';
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService extends BaseCrudService(User) {
  async test() {
    const res = await this.repo.findOne(1);
  }
}

This setup seems to work correctly except for the fact that this.repo in UserService is typed as protected TypeOrmCrudService<User>.repo: Repository<any> The problem is that it cannot receive the Repository<User> from the pass entity param, which results in an any return type of the repo function.

Any ideas how to fix this so that it properly assigns the passed Entity type to the repo?

mitsos1os commented 3 years ago

UPDATE I actually managed to type the repo correctly using this code typing: BaseCrudService.ts

import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

export function BaseCrudService<T>(Entity: new () => T) {
  class BaseCrudServiceHost extends TypeOrmCrudService<T> {
    constructor(@InjectRepository(Entity) repo: Repository<T>) {
      super(repo);
    }
  }
  return BaseCrudServiceHost as new (
    repo: Repository<T>,
  ) => TypeOrmCrudService<T>;
}

If not better way to do this, I can close this issue

hakimio commented 3 years ago

Nice to see that it got it working. That's something a lot of other user would like to do. Maybe you could add it to the wiki or create code PR?

mitsos1os commented 3 years ago

Hmmm, not sure how to add it? Under what context? Mixin usage?