Reputation: 3602
I am playing with typescript by implementing a strongly typed rest request mechanism.
Let the code speak:
With this type I want to define the mapping between the routes and the related type of objects:
export interface RoutesMapping {
api1: {
users: UserApiModel
products: ProductApiModel,
}
api2: {
"other-route": OtherModel1,
"another-one-route": OtherModel2
}
}
export type ApiScope = keyof RoutesMapping
The following function is the one I am willing to use to make POST requests
export type RestApiPostResponse<T = any> = {
result: boolean
data: T
}
export function restPost<S extends keyof RoutesMapping = "api1", T extends keyof RoutesMapping[S] = keyof RoutesMapping[S]>(
route: T,
// nervermind this object, is out of scope for the question
options: ApiRequestOptions<S, any> = {}
): Promise<RestApiPostResponse<RoutesMapping[S][T]>> {
const url = apiUtils.buildUrl(route as string, options)
const { payload, headers, isProtected } = options
return post({
url,
isProtected,
payload,
headers
});
}
I expect to call this function in the following way
const data = await restPost("users")
An make typescript infer the return type by inferring it by the scope and the route.
Actually, using it with the default type parameters, it works:
The problem is when I when I want to call the other api in this way:
const data = await restPost<"api2">("other-route")
Unfortunately, it does not work and it infers all the possible types
The only way to solve the problem is to explicitly add the second type parameter
How can I use all of this without needing to add the second type parameter in the second scenario?
Here is a typescript playground
Upvotes: 0
Views: 126
Reputation: 15126
If you infer the api-key type parameter, you can actually construct a solution that does what you want:
type Model<Route> = // Returns the model value for key Route in RoutesMapping
keyof RoutesMapping extends infer Api
? Api extends keyof RoutesMapping
? Route extends keyof RoutesMapping[Api]
? RoutesMapping[Api][Route]
: never
: never
: never
type Routes<Api> = Api extends {} ? keyof Api : never // Helper to distribute keyof over a union of objects
type AllRoutes = Routes<RoutesMapping[keyof RoutesMapping]> // Union of all route keys: 'users' | 'products' | 'other-route' | 'another-one-route'
export function restPost<Route extends AllRoutes>(
route: Route,
options?:{url:string,payload:any}
): Promise<RestApiPostResponse<Model<Route>>> {
..
}
When applied to a route string, the correct return type for restPost
is inferred, without needing to specify any type parameters:
const data = await restPost("users") // data: RestApiPostResponse<UserApiModel>
const data2 = await restPost("other-route") // data2: RestApiPostResponse<OtherModel1>
Note that this assumes the route keys are unique, which seems to be the case since the api key is not passed to restPost
. I'm also not certain it is wise to introduce all this complexity, but at least it is possible.
Upvotes: 1