Search code examples
javascripttypescripttypesmapped-typestype-inference

In TypeScript, how can you extract a class instance type using generics?


Poked around SO looking for an answer for this but turned up dry – in my GraphQL server, I'm trying to add better typing around my subscriptions because the types I pulled from NPM use any too much. I wrote a map outlining my subscriptions, where each map value contains the channel name, subscription name, and an object class that is used to type the payload:

class ConversationCreatedEvent {
  conversationID: string;
  userID: string;
}

export const Subscriptions = {
  conversationCreated: {
    name: 'onConversationCreated',
    event: ConversationCreatedEvent,
    channelName: 'CONVERSATION_CREATED',
  },
};

I'd like to write a method that allows you to publish subscriptions that takes two arguments: the subscription ID (map key), and an object conforming to an instance of class type specified in the event field.

So far, I have:

const publishSubscription = <T extends keyof typeof Subscriptions>(
  name: T,
  data: typeof Subscriptions[T]['event']
) => {
 // TODO: implement
};

However, this approach makes the second argument typeof ConversationCreatedEvent, and expects keys such as length, name, apply, etc. (i.e. the keys you'd find in a class object, not an instance of the class). I need this to accept the actual class members, such as conversationID.

Hope this all makes sense. I feel like it's very straightforward, there's just something small I'm missing. I'm still learning mapped types.

Any help is appreciated!!


Solution

  • const Subscriptions has the class constructor as a value. So:

    typeof Subscriptions['consoversationCreated']['event']
    

    is the same as:

    typeof ConversationCreatedEvent
    

    Which means that you have a class constructor type, and you want to get the instance type from it.

    Typescript has a built in utility type to do just that called InstanceType.

    class Foo {}
    
    // The following type aliases are equivalent
    type FooInstance = Foo
    type InstanceTypeFoo = InstanceType<typeof Foo>
    

    Or in your case:

    InstanceType<typeof Subscriptions[T]['event']>
    

    See playground