I have a composite type called IncomingMessage
export type IncomingMessage =
| MessageText
| MessageGameStarted
//| ... rest
export type IncomingMessageType =
| IncomingMessage['messageType']
each member of type IncomingMessage
is represented as an interface with unique key messageType
interface MessageText {
messageType: MessageType.TEXT
content: string
}
interface MessageGameStarted {
messageType: MessageType.GAME_STARTED
}
// ... rest
Then, I have a class Network
that listens for those messages and delegates them to appropriate handlers. You can add handlers to Network
depending on IncomingMessageType
type Handler<T extends IncomingMessage> = (message: T) => any
type Handlers = {[messageType in IncomingMessageType]?: Handler</*???*/>}
class Network {
private readonly handlers: Handlers = {}
on<T extends IncomingMessage>(messageType: T['messageType'], handler: Handler<T>) {
this.handlers[messageType] = handler
return this
}
}
What I need to accompish:
Network.on(messageType, handler)
should be type-safe: when you typenetwork.on(MessageType.TEXT, message => {
// message type should be MessageText
})
typescript should infer type of 'message' parameter based on 'messageType' parameter
Handlers
. I want to be able to infer Handler generic from current messageTypeYou'll need to extract the right message from the union with Extract
:
type Handler<T extends IncomingMessage> = (message: T) => any
type Handlers = { [messageType in IncomingMessageType]?: Handler<Extract<IncomingMessage, { messageType: messageType }>>}
class Network {
private readonly handlers: Handlers = {}
on<T extends IncomingMessageType>(messageType: T, handler: Handler<Extract<IncomingMessage, { messageType: T }>>) {
this.handlers[messageType] = handler as Handler<any>;
return this
}
}
You also have to change the signature of on
to use the generic on the message type instead, as TypeScript cannot infer the message's type in the handler.
So now your on
should work correctly:
new Network().on(MessageType.TEXT, message => {
message
// ^?
});
However you will notice that I have used an assertion when assigning the handler, and I don't think it is possible to get around it.