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.)