14.2 Types you write down
- Approved by
- Panaversity ·
In everyday life. A paper form has boxes for the date, with "MM/DD/YYYY" printed under them, and checkboxes for delivery: ground, two-day, overnight. Paper cannot stop anyone writing "whenever", so a clerk checks each answer once, when the form arrives.
What you will learn. How to write down which values your data may hold, and where text from outside becomes one of them.
Why it matters. Once those values are written down, the type checker checks every later change against them, the agent's included.
What can a type say that a name cannot?
Exactly which values are allowed. In Chapter 13, a type label said what kind of value a variable held. Here you decide the shape of your own data, and the agent writes it down. A record's fields are the pieces of data it holds. Four kinds of type do most of the work.
| Kind | Reads as | Brightline example |
|---|---|---|
| A record type | "An invoice has these fields, of these types" | Invoice, with number, vendorId, amountCents and four more |
| A union | "This, or that" | string | null: a due date, or nothing |
| An optional field | "This field may be missing" | A vendor's remit email, which may be missing |
| A literal type | "Only these exact values" | Terms: "Net 15", "Net 30", "Net 45", "Net 60" or "Due on receipt" |
A literal type is the form's row of checkboxes: only these answers exist. Here it is for the terms, with two calls. Predict which call the checker refuses.
// the checkboxes: Terms is one of these five values, and | means "or"
export type Terms = "Due on receipt" | "Net 15" | "Net 30" | "Net 45" | "Net 60";
export function dueDate(invoiceDate: string, terms: Terms): string { /* ... */ }
dueDate("2026-10-01", "Net 30");
dueDate("2026-10-01", "Net 10");$ npm run typecheck
exercises/wrong-call.ts(4,35): error TS2345: Argument of type '"Net 10"' is not assignable to parameter of type 'Terms'.The checker names the file, then the line and column of "Net 10". An agent calling dueDate cannot pass "net 30" in lowercase either, because the type checker refuses it.
Where does text from a file become a type?
Where it enters the program. Text read from a file is only a string. A literal type will not accept it, because it might be "Net 10". The type checker reads your code before the program runs, and never sees what arrives in a file.
So the program needs a type guard, such as Brightline's isTerms: code that runs with the program and reads the text that came in. It is the form's clerk: it answers true or false, once, where the text enters. The checker believes a true answer, and treats the text as Terms.
A cast, such as as Terms, skips the clerk: it tells the checker to believe the text without looking.
Here is the clerk. Predict both answers.
// the clerk: takes any text, and answers whether it is one of the Terms.
// "text is Terms" tells the checker to believe a true answer
function isTerms(text: string): text is Terms {
// is the text on the list?
return ["Due on receipt", "Net 15", "Net 30", "Net 45", "Net 60"].includes(text);
}
console.log(isTerms("Net 30"), isTerms("Net 10"));true falseAt work. Ana has the agent change one line: the terms parameter of dueDate goes from string to Terms. The type checker reports six errors. Four pass raw text from a file straight into the calculation. One is the test that checks unknown terms on purpose. One is a call an agent wrote with "Net 10", terms Brightline has never used. Ana decides two things, and the agent makes both changes: check the text once, where it is read, and have that one test expect the error.
What does a type do while the program runs?
Nothing, by itself. Node removes TypeScript's type labels before the program runs. Python keeps its type hints, the labels of 13.2, but does not enforce them. Libraries such as Pydantic use them to check data at run time, as 14.4 shows. So an ordinary type protects you only if the type checker runs, which is why it runs on every change.
Try it
The file in 14-2 has a small Terms type and two calls. Run it and investigate, as in 13.1.
Run the checker. Send: "Please run the file, then check it with the type checker. Show me what each prints." The file runs, and the checker refuses one line.
Modify. Send: "Please write the second call with a cast, so the checker believes it: as Terms in TypeScript, or cast(Terms, ...) in Python. Don't run anything yet." Will the checker accept it now? What will the file print? Then ask the agent to run the checker and the file.
Make. Send: "Please add a function that takes text, answers whether it is one of the allowed terms, and reports it if not. Use it for "Net 10" instead of the cast."
What you should see. The checker accepts the cast, and the run is still wrong: undefined in TypeScript, a KeyError in Python.
The operating rule: write the allowed values into the types, check raw text once where it enters the program, and let the type checker check every line after that.

Figure 14.2. The clerk's check, in code. Text from outside is checked once as it comes in. parseTerms is the same check, but it throws an error where isTerms answers false.
Next. A type describes one value. 14.3 holds several values together, with the functions that work on them.
Check yourself
Question 1 / 6
0 answered
The type checker passes this agent code, but it prints INV-5317 undefined.type Terms = "Due on receipt" | "Net 15" | "Net 30" | "Net 45" | "Net 60"
const DAYS: Record<Terms, number> = { "Due on receipt": 0, "Net 15": 15, "Net 30": 30, "Net 45": 45, "Net 60": 60 }
const row = { invoice: "INV-5317", terms: "Net 90" }
const terms = row.terms as Terms
console.log(row.invoice, DAYS[terms])What is the bug?
Sources
- Owner
- Panaversity