I'm experimenting with TypeScript's type system to build an API class that's able to infer the expected data based on a passed string (which is a specific route on the backend).
However some of those routes include a number which makes my current implementation a bit ugly. I was wondering if you could improve that generic type.
My current implementation looks like this:
type ResourceType = 'api_keys' | 'message_forwardings' | `devices/${number}/user_devices`
type ResourceMap = {
api_keys: IAkey,
user_devices: IUserDevice
message_forwardings: IMessageForwarding
}
type DataType<T extends ResourceType> =
T extends keyof ResourceMap
? ResourceMap[T]
: T extends `devices/${number}/user_devices`
? ResourceMap['user_devices']
: never
class RestApiService<T extends ResourceType> {
resource: T
data: DataType<T>
constructor(resource: T){
this.resource = resource
}
// all the request methods go here
}
with this i can do:
const data = new RestApiService('api_keys').data // typescript knows it's Array<IAkey>
new RestApiService('some_route') // Argument of type 'some_route' is not assignable to parameter of type 'ResourceType'
const data = new RestApiService('devices/1234/user_devices').data // typescript knows it's Array<IUserDevice>
This is already working well but the more routes I want to add to the ResourceType and ResourceMap the more unclear my DataType narrowing can get, especially if there are even more routes that need a template literal type.
Is there a way to tidy things up and that I don't need to add more conditional types?
TypeScript supports template string pattern index signatures, so you can write your ResourceMap
type directly as
type ResourceMap = {
api_keys: IAkey,
[k: `devices/${number}/user_devices`]: IUserDevice
message_forwardings: IMessageForwarding
}
and then do a pure indexed access instead of worrying about conditional types:
type DataType<T extends ResourceType> = ResourceMap[T]