I’m defining an RPC-like interface using single huge nested type that names, in order, the namespace, the method name, and the parameters:
interface IndexedActions {
foo: {
nested: {
parameter: string;
};
};
bar: {
second: {
argument: number;
};
};
}
I want to type-safely invoke these methods, so I define this signature:
function invoke<N extends keyof IndexedActions, M extends keyof IndexedActions[N]>(
namespace: N,
method: M,
args: IndexedActions[N][M]
);
This works as expected on the caller side, accepting and rejecting all three parameter types appropriately in my test cases and even autocompleting each argument based on the values of the previous arguments.
It does not work in the function’s implementation, where args
is some type that the compiler won’t say much about and somehow isn’t assignable to object
, even though both the doubly-nested types are object types:
Type 'IndexedActions[N][M]' is not assignable to type 'object'.
Type 'IndexedActions[N][keyof IndexedActions[N]]' is not assignable to type 'object'.
Type 'IndexedActions[N][string] | IndexedActions[N][number] | IndexedActions[N][symbol]' is not assignable to type 'object'.
Type 'IndexedActions[N][string]' is not assignable to type 'object'.
I’ve tried a number of workarounds, including:
- intersecting the
keyof
s with& string
(to remove thesymbol
seen in the error message above) - forgoing generics, and instead defining something like
invoke(namespace: keyof IndexedActions, method: keyof IndexedActions[typeof namespace], ...)
- forcing distribution using the
T extends T ? ...
idiom, which does improve the type ofmethod
but does not fix the whole issue - mixing-and-matching these approaches
How can I correctly type the implementation?
TypeScript playground link.