0

I have an interface of the following format which describes database methods like so:

export default interface IRepository {
  createAndSave(data: ICreateUserDTO): Promise<User>
  findById<T>({ id }: { id: number }): Promise<T | null> // right here
  ...
}

As you can see from the snippet above, findById method is meant to take in a type and return a resolved promise of type T or a null value. I go-ahead to implement this in a class like so.

class DatabaseOps {
private ormManager: Repository<User>
...
async findById<User>({ id }: { id: number }): Promise<User | null> {
    const t = await this.ormManager.findOne({
      where: { id },
    }) 

    return t
  }
...
}

When I try to create the findById method like that, typescript gives this error of this format

Type 'import("../src/user/infra/typeorm/entities/User").default' is not assignable to type 'User'.
  'User' could be instantiated with an arbitrary type which could be unrelated to 'import("../src/audience/infra/typeorm/entities/User").default'

I tried to use typescript assertion to override this error like so

class DatabaseOps {
private ormManager: Repository<User>
...
async findById<User>({ id }: { id: number }): Promise<User | null> {
    const t = await this.ormManager.findOne({
      where: { id },
    }) 

    return t as Promise<User> // here
  }
...
}

but I still get the error, I am not really sure what to do from this point onward.

Here is what the User model definition looks like, I am making use of TypeORM

export default class User {
  @PrimaryGeneratedColumn('uuid')
  id: string

  @Column({
    type: 'json',
    nullable: true,
  })
  data: object

  @Column({ type: 'tinyint', default: 1 })
  status: number
  ...
}

What could be the cause of this and how do I rectify it? Any help will be appreciated. Thank you very much!

1
  • 2
    You've got id: number in the in the interface definition, but id: string in the User class. Commented Feb 24, 2021 at 22:49

1 Answer 1

2

The IRepository.findById method's type signature doesn't mean what you think it means.

When you write findById<T>, it means that the method promises to work with any type T. Whoever calls the method chooses which type it is. Kind of like this:

const r : IRepository = ...
const x = r.findById<User>( ... )
const y = r.findById<number>( ... )
consy z = r.findById<string>( ... )
... and so on

And since the caller of the method can choose any type, it means that the implementer of the method must implement it such that it can work with any type. So it can't be just User. It has to be any type, whatever the caller happens to choose.


Now, what you probably meant to do was to create not just a repository, but a repository of a certain thing. To do this, the generic parameter should be on the interface, not on the method:

export default interface IRepository<T, DTO> {
  createAndSave(data: DTO): Promise<T>
  findById({ id }: { id: number }): Promise<T | null>
  ...
}

Then you can implement IRepository<User, ICreateUserDTO> in your class:

class UserRepository {
    ...

    async createAndSave(data: ICreateUserDTO): Promise<User> {
        ...
    }

    async findById({ id }: { id: number }): Promise<User | null> {
        const t = await this.ormManager.findOne({
          where: { id },
        }) 

        return t as Promise<User> // here
      }

    ...
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.