Advanced · 18 min
Typed event contracts
Derive correlated event messages and handlers from one event map.
Derive a union of complete messages
Mapping each event key to a message and then indexing the result creates a discriminated union. Each event name stays tied to its own payload. An object with independent unions for name and payload would lose that relationship.
type Events = { opened: { page: string }; scored: { points: number } };
type EventMessage = { [K in keyof Events]: { type: K; payload: Events[K] } }[keyof Events];
function describe(event: EventMessage): string {
return event.type === "opened" ? event.payload.page : String(event.payload.points);
}
const message: EventMessage = { type: "scored", payload: { points: 5 } };
console.log(describe(message));Output
5
Check a handler table
A mapped handler type requires a callback for each event with the matching payload. The example invokes a known handler directly. A general dynamic dispatcher needs to preserve the same correlation; an assertion should not hide a mismatch.
type Events = { opened: { page: string }; scored: { points: number } };
type Handlers = { [K in keyof Events]: (payload: Events[K]) => string };
const handlers: Handlers = { opened: data => data.page, scored: data => String(data.points) };
console.log(handlers.opened({ page: "Learn" }));Output
Learn
Put it into practice
- Predict the output and explain which relationships the types enforce.
- Fix the task while preserving its type contract, then check both practice questions.
Try it yourself
Code runs on this device. When you are signed in, drafts sync to your account.
Fix an event payload
Give the scored message a payload with "points" set to 5. Keep the derived message union.
The selected event determines which payload shape is valid.
Show solution
type Events = { opened: { page: string }; scored: { points: number } };
type EventMessage = { [K in keyof Events]: { type: K; payload: Events[K] } }[keyof Events];
function describe(event: EventMessage): string {
return event.type === "opened" ? event.payload.page : String(event.payload.points);
}
const message: EventMessage = { type: "scored", payload: { points: 5 } };
console.log(describe(message));The mapped union checks complete event variants. The corrected payload belongs to the scored variant, so the narrowed handler can safely read points.