1

I have a REST api interface like this:

interface APIs {
  artist: {
    POST: { req: TypeA, resp: TypeB}
    DELETE: { req: TypeC, resp: TypeD },
  } 
  location: {
    GET: { req: TypeE, resp: TypeF }
  }
}

That I want to convert to a set of functions like this:

const server: Server<APIs> = {
  postArtist: (req: TypeA) => TypeB,
  deleteArtist: (req: TypeC) => TypeD,
  getLocation: (req: TypeE) => TypeF,
}

I have managed to create almost create a Server<T> type however I'm not able to reference the POST|DELETE|GET keys used in the template literal. They appear as the key of T[Key] below so that it can be used on the right side where the ? is.

type Server<T> = {
  [Key in keyof T as `${Lowercase<keyof T[Key] & string>}${Capitalize<Key & string>}`]: (req: T[Key][?]) => void
}
//                                |^=How to get this=^|                            and use it here -^^

How do I reference the key that was used to generate the function name so that I can reference it on the right side.

ssotangkur
  • 123
  • 1
  • 6

1 Answers1

1

Currently in ${Lowercase<keyof T[Key] & string>}${Capitalize<Key & string>}, keyof T[Key] will be a union, and so the type expression will generate a union of keys, resulting in all sub keys being preset in the result. This is a good approach if we just need the subkeys.

If we need to process each sub property individually though we should use another mapped type to produce each function individually and then merge them back into the result. To merge them back into a single object we can use UnionToIntersection from here

To extract the request and response types, the simplest solution would be to use a conditional type:


interface RequestDefinition<TReq, TResp> {
    req: TReq,
    resp: TResp
}

type FromDefinition<T> = 
    T extends RequestDefinition<infer TReq, infer TResp>? (req: TReq) => TResp: never

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type Server<T> = UnionToIntersection<{
  [Key in keyof T]: {
    [Http in keyof T[Key] as `${Lowercase<Http & string>}${Capitalize<Key & string>}`]: FromDefinition<T[Key][Http]>
  }
}[keyof T]>

Playground Link

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • Thanks! Conceptually, I think I understand how it works, but wow I don't think I would have ever come up with a solution like that. Impressive! – ssotangkur Sep 24 '22 at 02:40