Reputation: 1419
I'm working on a typescript project and, at some point, I need to collect the FindOneOrFailOptions
parameter from MikroORM in advance.
The code is far more complicated but I've managed to replicate the issue with fewer lines.
While this code works without a problem:
class ProviderRepository {
public async findOneByUrl(url: string): Promise<Provider> {
const repository = /* is of type EntityRepository<Provider> */
return await repository.findOneOrFail({ url }, { populate: ['profile'] });
}
}
The following one fails by raising TS2345: Argument of type '{ populate: string[]; }' is not assignable to parameter of type 'FindOneOrFailOptions<Provider, string>'.
:
class ProviderRepository {
public async findOneByUrl(url: string): Promise<Provider> {
const repository = /* is of type EntityRepository<Provider> */
const options = { populate: ['profile'] };
return await repository.findOneOrFail({ url }, options);
-------
}
}
Well, sure enought the solution should be to declare options
as FindOneOrFailOptions<Provider, string>
. However, if I do this, then it raises TS2322: Type 'string' is not assignable to type 'never'.
:
class ProviderRepository {
public async findOneByUrl(url: string): Promise<Provider> {
const repository = /* is of type EntityRepository<Provider> */
const options: FindOneOrFailOptions<Provider, string> = { populate: ['profile'] };
---------
return await repository.findOneOrFail({ url }, options);
}
}
And at this point I'm a bit lost as how to proceed.
I presume the never
in there comes from the function declaration:
class EntityRepository<T extends AnyEntity<T>> {
findOneOrFail<P extends string = never>(where: FilterQuery<T>, options?: FindOneOrFailOptions<T, P>): Promise<Loaded<T, P>>;
}
However even if I use this function like repository.findOneOrFail<string>(...)
it still produces the same error.
I reckon there must be some sort of auto-casting as if I inline the parameter, even without the <string>
, it happy takes it without a problem. This tells me that I have not nailed the exact type that it is expecting but I'm a bit clueless as per what this type could be.
How can I have the options
parameter declared in advance without any issue? Many thanks!
Upvotes: 4
Views: 1111
Reputation: 18389
It can't be typed just to a string
, if we allow that, we lost type safety as any string could be used. It needs to be string literal type with the actual values. For your specific use case, a const assertion should do the job:
await repository.findOneOrFail({ url }, { populate: ['profile'] as const });
How can I have the options parameter declared in advance without any issue? Many thanks!
You can't really have it defined upfront and expect any kind of type safety as there is no connection with the actual value - you need to cast things yourself if you want that. Or set the type arg to any
, not nice but in fact it is the most precise description of what you want, as it disables the strict types for populate hints. That is the only way to allow setting more populate hints after you create such object.
const options: FindOneOrFailOptions<Provider, any> = { populate: ['profile'] };
options.populate.push('otherProp');
If you just want to create the object and you know it will contain just one item, you can pass the type value explicitly.
const options: FindOneOrFailOptions<Provider, 'profile'> = { populate: ['profile'] };
If you don't provide the second type argument, the default is never
- so without it you can never pass anything to the populate
hint.
Upvotes: 2