typeorm / typeorm

ORM for TypeScript and JavaScript. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, SAP Hana, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.
http://typeorm.io
MIT License
34.24k stars 6.31k forks source link

Unable to use where clause with FindOptions due to incompatible types #9508

Open robertmain opened 2 years ago

robertmain commented 2 years ago

Issue Description

It does not appear to be possible to use the where option in Repository<T>.find(). This is because FindOneOptions<Entity>.where? lists all the columns under where as nullable i.e:

where: {
  id?: string,
  createdAt?: string,
}

But, those types are being typed as NonNullable by TypeORM - in this case: FindOptionsWhereProperty<NonNullable<Entity["id"]>>.

Thus, it seems to be complaining because FindOneOptions<Entity>.where properties are (rightfully) nullable, when the actual entity properties themselves that those fields refer to are not nullable.

Expected Behavior

This code should be valid:

this.repository.find({
  where: {
    id: 'my-id',
  },

Actual Behavior

I get an error message that says:

Type '{ id: "my-id"; }' is not assignable to type 'FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[]'.
  Types of property 'id' are incompatible.
    Type 'string' is not assignable to type 'FindOptionsWhereProperty<NonNullable<Entity["id"]>>'.ts(2322)
FindOneOptions.d.ts(23, 5): The expected type comes from property 'where' which is declared here on type 'FindManyOptions<Entity>'

My Environment

Dependency Version
Operating System Debian
Node.js version 14.17.0
Typescript version 4.8.4
TypeORM version 0.3.10

Additional Context

Relevant Database Driver(s)

DB Type Reproducible
aurora-mysql no
aurora-postgres no
better-sqlite3 no
cockroachdb no
cordova no
expo no
mongodb no
mysql no
nativescript no
oracle no
postgres yes
react-native no
sap no
spanner no
sqlite no
sqlite-abstract no
sqljs no
sqlserver no

Are you willing to resolve this issue by submitting a Pull Request?

kirisakiken commented 2 years ago

Can you share your entity class and its repository class please.

robertmain commented 2 years ago

@kirisakiken Sure! Here's my entity class:

import {
  CreateDateColumn,
  UpdateDateColumn,
  DeleteDateColumn,
  PrimaryColumn,
} from 'typeorm';
import { Exclude } from 'class-transformer';

export abstract class BaseEntity {
  @PrimaryColumn({
    generated: 'uuid',
    type: 'uuid',
  })
  public id: string;

  @CreateDateColumn()
  public createdAt: Date;

  @UpdateDateColumn()
  public updatedAt?: Date;

  @Exclude()
  @DeleteDateColumn()
  public deletedAt?: Date = null;
}

I'm not sure what exactly you mean by "its repository class", but under the assumption you meant the service class - here it is also:

import { Repository, DeepPartial, FindManyOptions, IsNull, In } from 'typeorm';
import { BaseEntity } from './base.entity';

export abstract class BaseService<Entity extends BaseEntity> {
  protected readonly repository: Repository<Entity>;

  public constructor(repository: Repository<Entity>) {
    this.repository = repository;
  }

  public async findById(
    ids: string[],
    withDeleted = false,
    options: FindManyOptions<Entity> = {}
  ): Promise<Entity[]> {
    const records = await this.repository.find({
      ...options,
      withDeleted,
      where: {
        id: In(ids),
      },
    });
    return records;
  }

  public async findAll(
    withDeleted = false,
    options: FindManyOptions<Entity> = {}
  ): Promise<Entity[]> {
    const records = await this.repository.find({
      ...options,
      withDeleted,
    });
    return records;
  }

  public async save(entitites: DeepPartial<Entity>[]): Promise<Entity[]> {
    const results = this.repository.save(entitites);
    return results;
  }
kirisakiken commented 1 year ago

Beware that your base entity class conflicts with typeorm's base entity class BaseEntity. And, have you tried casting your find options where? e.g. { id: In(ids) } as FindOptionsWhere<Entity>

robertmain commented 1 year ago

Why does it conflict? As long as BaseEntity isn't imported from TypeORM there shouldn't be an issue.

Also yes I have, the issue stems from the fact that entries in where are nullable whereas fields on BaseEntity like id, createdAt etc. are (by their definition) NOT nullable.

robertmain commented 1 year ago

Okay, so it's a temporary workaround, but this works:

const records = await this.repository.find({
  ...options,
  where: {
    id: In(ids),
  } as FindOptionsWhere<Entity>,
  withDeleted,
});