Extending Event Contracts

The Course
•
4 min read

Add a renamed event with a validated id and nonblank title in a local copy. The type map should make a new channel required. The exhaustive switch should require a dispatch branch. Neither mechanism automatically writes the decoder: add an unknown-data branch that validates and projects the new payload.

Keep the same contract around callback returns, duplicate subscriptions, snapshot behavior and disposal. A new event should not silently change the lifetime rules of every existing port. Test a valid renamed message, a blank title, extra-field removal and the actual subscriber payload.

Add a positive public consumer and a focused negative caller missing title. Then run the declaration-consumer checks and runtime suite. A clean mapped registry alone cannot prove the new parser accepts the intended wire data.

The exercise below rehearses coverage on a small formatter registry. The full extension remains your independent integration work; compare each affected file before calling it done.

Predict, check, and run

Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.

Predict, then runTypeScript
type Events = {
  added: { title: string };

  renamed: { title: string };
};

type Labels<E> = { [K in keyof E]: (payload: E[K]) => string };

const labels = {
  added: book => `Added ${book.title}`,
  renamed: book => `Renamed ${book.title}`
} satisfies Labels<Events>;

console.log(labels.renamed({ title: "Types" }));

Enable JavaScript to edit and type-check this example.

Check your prediction
Renamed Types

Add a vocabulary branch and its runtime evidence together

Adding renamed to EventMap demands channel coverage and an exhaustive dispatch branch. It does not instantiate a port, decode wire data or choose normalization rules. Write one accepted raw record and several rejected records before editing the type map.

Reuse checked id construction and preserve the existing lifetime policy. A new event must not accidentally permit asynchronous listeners or change how disposal and callback failure behave for every old event.

Boundary or caseWhat to inspect
Map and channelsRequire exactly the renamed payload contract
Decoder and dispatchValidate the raw branch and route a correlated payload
Consumer and runtimeReject missing title and verify actual normalized subscriber data

Follow coverage errors, then add invalid runtime fixtures the checker cannot inspect. Re-run existing snapshot, cleanup and failure tests after the new happy path works. Explain which file owns each guarantee instead of copying the branch blindly.

Build it yourself

Exercise

Extending Event Contracts: practise

Create a formatter registry covering added, removed and renamed payloads. Added and renamed use title; removed uses id. Print Renamed Types and Removed b1. Use satisfies to require every key.

Your workspaceTypeScript
// Start from the stated contract. Add a valid call and an edge case.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

The new key should be required by the mapped contract, not manually asserted as present.

Hint 2: a first step

Add renamed to the event map before constructing the complete registry.

Show one solution
One possible solutionTypeScript
type Events = {
  added: { title: string };

  removed: { id: string };

  renamed: { title: string };
};

type Labels<E> = { [K in keyof E]: (payload: E[K]) => string };

const labels = {
  added: book => `Added ${book.title}`,
  removed: book => `Removed ${book.id}`,
  renamed: book => `Renamed ${book.title}`
} satisfies Labels<Events>;

console.log(labels.renamed({ title: "Types" }));
console.log(labels.removed({ id: "b1" }));

Enable JavaScript to edit and type-check this example.

Renamed Types
Removed b1

Trace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.

Change it and test it

In the local project, list the expected compiler failures immediately after adding the map key. Then add decoder behavior and prove malformed renamed data never reaches its subscriber.

Was this article helpful?

Your response is saved on this device.