Extending Typed APIs

The Course
•
4 min read

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.

Predict, then runTypeScript
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 caseWhat to inspect
Empty booksZero without an absent-value fallback
Zero-page bookValid data contributes zero
Unequal page countsSum 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

Exercise

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.

Your workspaceTypeScript
// 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
One possible solutionTypeScript
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 verified

Explain 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.

Was this article helpful?

Your response is saved on this device.