Add totalPages(books:readonly Book[]):number as a trusted-domain report. This function should not perform transport I/O or parse JSON. Decoders establish valid Book values; the report consumes those values without mutation.
Then decide where to expose the result. A formatter may display it from a ready state. If you add it to the state shape, update every constructor and the reducer so the field stays consistent with books. Storing redundant derived data creates another invariant; calculating it on demand may be simpler.
Write runtime tests for empty input, one zero-page book and several books. Write a negative compile test that rejects a page count encoded as text. Keep both layers: number types do not guarantee the sum implements the intended rule, while runtime tests do not maintain the entire caller-facing type surface.
A type-level change can guide edits, but it cannot decide which layer should own the feature. Prefer a small pure function when no lifecycle or dependency is needed.
Predict, run, and explain
Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.
type Book = {
readonly title: string;
readonly pages: number;
};
function totalPages(books: readonly Book[]): number {
let total = 0;
for (const book of books) {
total += book.pages;
}
return total;
}
console.log(
totalPages([
{ title: "A", pages: 10 },
{ title: "B", pages: 20 }
])
);
console.log(totalPages([]));Enable JavaScript to edit and type-check this example.
Check your prediction
30
0 Keep trusted reporting outside the transport adapter
totalPages consumes trusted books, so it should not fetch, parse or silently repair records. Add a pure report at the layer that needs it and verify no input mutation. A numeric return type cannot prove that the loop sums the right field.
Storing the total alongside books adds a consistency rule to every state transition. Computing it when needed may be clearer for a small catalogue. Choose deliberately rather than assuming every derived display value deserves stored state.
| Boundary or case | What to inspect |
|---|---|
| Empty books | Zero without an absent-value fallback |
| Zero-page book | Valid data contributes zero |
| Unequal page counts | Sum pages while preserving records and order |
Add a negative caller with text pages and independent numeric fixtures. Then decide what exactness policy applies to very large totals; number annotations alone do not guarantee precision beyond safe integer bounds.
Your turn
Extending Typed APIs: practise
Write totalPages with the readonly input contract. Assert mixed page counts sum correctly, an empty list returns 0 and the input remains unchanged. Print report verified.
// Compare source data before and after the call in this flat JSON-safe example.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
The report must consume trusted records without editing them.
Hint 2: a first step
Use local numeric accumulation and return the computed value.
Show one solution
type Book = {
readonly title: string;
readonly pages: number;
};
function totalPages(books: readonly Book[]): number {
let total = 0;
for (const book of books) {
total += book.pages;
}
return total;
}
const books: readonly Book[] = [
{ title: "A", pages: 0 },
{ title: "B", pages: 20 }
];
const before = JSON.stringify(books);
if (
totalPages(books) !== 20 ||
totalPages([]) !== 0 ||
JSON.stringify(books) !== before
) {
throw new Error("Report failed");
}
console.log("report verified");Enable JavaScript to edit and type-check this example.
report verifiedExplain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?
Change it and test it
Implement the report in the local project, export it from domain.ts and use it only after a ready-state check. Add positive and negative type usage plus runtime cases. Explain whether storing totalPages in state is worth the extra consistency rule.
Completion is saved in this browser only. It does not measure mastery.