Imagine I have the following code:
const selectors = {
posts: {
getAllPosts(): Post[] {
return database.findPosts();
},
getPostsWithStatus(status: Post["status"]) {
return database.findPosts({ status });
}
},
users: {
getCurrentUser(): User {
return database.findUser();
}
}
};
I want to create a function to call one of the nested methods so I can do something like:
const deletedPosts = select("posts", "getPostsWithStatus", ["deleted"]);
So far here is my implementation:
type Selectors = typeof selectors;
function select<
SelectorType extends keyof Selectors,
SelectorFn extends keyof Selectors[SelectorType]
>(
selectorType: SelectorType,
selectorFn: SelectorFn,
args: Parameters<Selectors[SelectorType][SelectorFn]>
): ReturnType<Selectors[SelectorType][SelectorFn]> {
return selectors[selectorType][selectorFn](...args);
}
The types are properly inferred when calling the function but the implementation itself raises errors:
args: Parameters<Selectors[SelectorType][SelectorFn]>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Type '{ posts: { getAllPosts(): Post[]; getPostsWithStatus(status: "deleted" | undefined): never[]; }; users: { getCurrentUser(): User; }; }[SelectorType][SelectorFn]' does not satisfy the constraint '(...args: any) => any'.
): ReturnType<Selectors[SelectorType][SelectorFn]> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Type '{ posts: { getAllPosts(): Post[]; getPostsWithStatus(status: "deleted" | undefined): never[]; }; users: { getCurrentUser(): User; }; }[SelectorType][SelectorFn]' does not satisfy the constraint '(...args: any) => any'.
return selectors[selectorType][selectorFn](...args);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This expression is not callable.
So my question is, how can I create a function that dynamically calls another one nested in an object while preserving the types?