Trying to make the populate
parameter in MikroORM strict for the variant with string paths. I managed to implement (more like adjust the one I found on TS discord) the AuthPath
type that works based on my needs. It works ok when used with a single parameter, but when used with array, it won't validate correctly if one of the array items is valid.
So the question is, how can I make it work with arrays? Is it even possible? I was trying to use tuple types to get around this, but failed to make it work. I kinda understand the problem is the shared generic type P
- I am planning to leverage it in the return type so I need something to bare the actual (inferred) types in the signature from params to return type.
The full playground in here.
Here is the demonstration of the problem:
declare const user: User;
declare function get1<O, P extends string>(obj: O, path: AutoPath<O, P>): void;
declare function get2<O, P extends string>(obj: O, path: AutoPath<O, P>[]): void;
// works fine with single item
get1(user, "friend.books.title")
get1(user, "friend.books.ref1.age")
get1(user, "friend.friend.name")
// @ts-expect-error
get1(user, "friend.friend.www")
// @ts-expect-error
get1(user, "friend.books.www")
// @ts-expect-error
get1(user, "friend.books.ref1.www")
// works fine with array when there is just one item
get2(user, ["friend.name"])
get2(user, ["friend.books.ref1.age"])
// @ts-expect-error
get2(user, ["friend.books.ref1.www"])
// if there are more items it works only sometimes
// @ts-expect-error
get2(user, ["friend.name", "books.author.www"])
// if we add one more item that is valid and on the root level, it will make it pass
get2(user, ["friend.name", "books.author.www", "age"])
Here is the code for AutoPath
and the entity type definitions:
class Collection<T> { items?: T[] }
class Reference<T> { item?: T }
type Book = {
id: string,
title: string,
author: User,
ref1: Reference<User>,
}
type User = {
id: string,
name: string,
age: number,
friend: User,
friends: Collection<User>,
books: Collection<Book>,
}
type ExtractType<T> = T extends Collection<infer U> ? U : (T extends Reference<infer U> ? U : T)
type StringKeys<T> = T extends Collection<any>
? `${Exclude<keyof ExtractType<T>, symbol>}`
: T extends Reference<any>
? `${Exclude<keyof ExtractType<T>, symbol>}`
: `${Exclude<keyof T, symbol>}`
type GetStringKey<T, K extends StringKeys<T>> = K extends keyof T ? ExtractType<T[K]> : never
type AutoPath<O, P extends string> =
(P & `${string}.` extends never ? P : P & `${string}.`) extends infer Q
? Q extends `${infer A}.${infer B}`
? A extends StringKeys<O>
? `${A}.${AutoPath<GetStringKey<O, A>, B>}`
: never
: Q extends StringKeys<O>
? (GetStringKey<O, Q> extends unknown ? Exclude<P, `${string}.`> : never) | (StringKeys<GetStringKey<O, Q>> extends never ? never : `${Q}.`)
: StringKeys<O>
: never
(the AutoPath
type still has some issues, but that is not really important - this question is about how to use it with array of strings instead of a single string parameter)