-
-
Notifications
You must be signed in to change notification settings - Fork 611
Description
Describe the bug
Summary:
Strict typing for passing an array to orderBy argument does not work if the entity has a property length. Contents of the array are not type checked.
Long description:
I ran into this issue while updating some of my utility functions to also accept an orderBy argument typed as QueryOrderMap<T> | QueryOrderMap<T>[] just like in FindOptions:
| orderBy?: QueryOrderMap<T> | QueryOrderMap<T>[]; |
My code seemed to work, but for some reason type checking on my orderBy argument did not work for one specific entity (but it did work for all others). Namely if I passed an array of objects as an argument to my function it would not throw any compiler errors even if the properties did not exist on that entity. Later I found out that this even occurs with regular EntityManager.find, i.e.
em.find(SomeEntity, {}, {
orderBy: [
{ someNonExistentProperty: 'and whatever value' },
]
})while if I manually specified the type as follows, then I would get a type error when compiling, as expected:
em.find(SomeEntity, {}, {
orderBy: [
{ someNonExistentProperty: 'and whatever value' },
] as QueryOrderMap<SomeEntity>[]
})For some reason the type of the array was being inferred as QueryOrderMap<SomeEntity> instead of QueryOrderMap<SomeEntity>[] and for some reason an array was accepted as that type... After being stumped for a while I found out that removing property length on my entity would cause compilation to throw the correct error.
Then it all clicked.
mikro-orm/packages/core/src/enums.ts
Lines 76 to 78 in 3c1441b
| export type QueryOrderMap<T> = { | |
| [K in keyof T as ExcludeFunctions<T, K>]?: QueryOrderKeys<ExpandProperty<T[K]>>; | |
| }; |
Any array is a valid QueryOrderMap<T> (even if it's not a valid QueryOrderMap<T>[]) if the entity T has a property length, because the only non-function property of any array is also length.
Passing an array to orderBy for an entity with a length property will throw all type safety out the window. The contents of the array can be anything.
To Reproduce
Define an entity with the property named length:
@Entity()
export class SomeEntity {
@PrimaryKey()
id!: number
@Property()
length!: number
@Property()
otherProperties!: number
}Now passing an array (with arbitrary contents) as orderBy argument passes compilation instead of giving a type error.
em.find(SomeEntity, {}, {
orderBy: [
{ someNonExistentProperty: 'and whatever value' },
]
})Expected behavior
Compilation should fail with for example the following type error:
src/test.ts:46:19 - error TS2322: Type '{ someNonExistentProperty: string; }' is not assignable to type 'QueryOrderMap<SomeEntity>'.
Object literal may only specify known properties, and 'someNonExistentProperty' does not exist in type 'QueryOrderMap<SomeEntity>'.
46 { someNonExistentProperty: 'and whatever value' },
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Additional context
My knowledge of Typescript types and generics is not deep enough to suggest any specific fix, and also I can see how this might just be an issue in general if you define your interface (any interface) to have a property length, that arrays will match that type as well.
Hope this rambling analysis made sense, and thanks for all the great work!
Versions
| Dependency | Version |
|---|---|
| node | v16.13.2 |
| typescript | 4.5.5 |
| mikro-orm | 5.0.5 |
| your-driver | postgres |