I'm trying to make a function that can call multiple APIs and gives strong typing for each argument: api - the name of the api, route - the desired route within 'api', and params - a JSON of the parameters required by the api and route chosen.
I've created an example of what I'd like to in a TS Playground here.
Here's the code:
type APINames = "google" | "yahoo";
type Routes<A extends APINames> = {
google: "endpoint1" | "endpoint2";
yahoo: "endpoint3";
}[A];
type RouteParams<A extends APINames, R extends Routes<A>> = {
google: {
endpoint1: {
name: string;
};
endpoint2: {
otherName: string;
};
};
yahoo: {
endpoint3: {
otherOtherName: string;
};
};
}[A][R];
const execute = <A extends APINames, R extends Routes<A>>(api: A, route: R, params: RouteParams<A, R>) => {
// ... code
}
execute('google', 'endpoint1', {
name: 'George Washington'
});
I appear to be getting a type error on my RouteParams type because while [A] is a legal first index, [R] isn't a legal second index because R is now the type 'endpoint1' | 'endpoint2' | 'endpoint3' and some subset of those don't exist on each key within the RouteParams object. The code above actually gives the desired typing help when using the execute function; I just need to resolve the TS error on RouteParams. I suspect there's a utility type that would solve this and I'm also open to solutions that are a completely different structure.
The ultimate goal is to have a function that takes 3 arguments, the first is an api name from a union type, the second is a route from a union type associated with the api name, and the third is a json parameters object associated with the api name and route combination.
The approach I'd take is to extract the object type you've mentioned in RouteParams into its own interface:
interface ApiMap {
google: {
endpoint1: {
name: string;
};
endpoint2: {
otherName: string;
};
};
yahoo: {
endpoint3: {
otherOtherName: string;
};
};
}
And then describe the operation explicitly in terms of generic keyof and indexed access types:
const execute = <A extends keyof ApiMap, R extends keyof ApiMap[A]>(
api: A, route: R, params: ApiMap[A][R] // no error
) => { // ... code
}
That compiles with no error and it still behaves as desired from the caller's side:
execute('google', 'endpoint1', { name: 'George Washington' });
execute('google', 'endpoint2', { otherName: 'Abraham Lincoln' });
execute('yahoo', 'endpoint3', { otherOtherName: 'one of them Roosevelts or something' });
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With