I want to enhance return type of a function based on a type parameter.
Let's say I have entity with property ref1 of type Reference<User>, and based on the type parameter I want to enhance it to LoadedReference<User> if ref1 (or ref1.something, or ref1.something.something...) is part of the type parameter value.
The demonstration of this problem follows:
// T1 is what I want for all of those
type T1 = LoadedIfInKeyHint<Book, 'ref1', 'ref1.books'>
// T2 is the problem, as it resolves to a union
type T2 = LoadedIfInKeyHint<Book, 'ref1', 'ref1' | 'ref1.books'>
// T3 demonstrates that the problem is just with having both `ref1` and `ref1.books` in the input type
type T3 = LoadedIfInKeyHint<Book, 'ref1', 'ref1.books' | 'title' | 'id'>
declare const t1: T1;
declare const t2: T2;
declare const t3: T3;
console.log(t1.$.books.$[0].title) // works as expected
console.log(t2.$.books.$[0].title) // does not work as `books` resolves to union
console.log(t3.$.books.$[0].title) // works as expected
LoadedIfInKeyHint type is used to resolve the type of object properties. It is used recursively via the Loaded type. Their definition follows:
type MarkLoaded<T, P, H = unknown> = P extends Reference<infer U>
? LoadedReference<U, Loaded<U, H>>
: P extends Collection<infer U>
? LoadedCollection<U, Loaded<U, H>>
: T;
type LoadedIfInKeyHint<T, K extends keyof T, H> = H extends `${infer A}.${infer B}`
? A extends K
? MarkLoaded<T, T[A], B>
: never
: K extends H
? MarkLoaded<T, T[K]>
: never;
export type Loaded<T, P = unknown> = unknown extends P ? T : T & Simplify<{
[K in keyof RelationsIn<T>]: LoadedIfInKeyHint<T, K, P>;
}>
(the Simplify type just removes properties that would resolve to never)
Playground link that should not produce any errors.
(Follow up to Mapping of strict path notation array type question that describes the end goal.)