One contract, two doors
read-01 · TypeScript
The contract that validates a document also types it: validate() returns findings as data, read() returns the typed model or throws.
Builds on: nothing — start here in Read markdown as typed data.
The artifact
Section titled “The artifact”A TypeScript program against the library API; inline comments show the resulting values and behavior.
import { contract, sections, section } from "markdown-contract";import { z } from "zod";
const decision = contract({ frontmatter: z.object({ status: z.enum(["proposed", "accepted"]) }), body: sections({ order: "strict", allowUnknown: true }, [ section("Summary"), section("Decision"), ]),});
// Door 1 — validate: findings as data, never throws. The shape CI wants.const result = decision.validate(src, { path: "decisions/D-0001.md" });result.findings; // Finding[] — every plane, sorted by source positionresult.doc; // the typed model, present iff no error-level finding
// Door 2 — read: the typed model, or a thrown ContractError. The shape a consumer wants.const doc = decision.read(src, { path: "decisions/D-0001.md" });doc.frontmatter.status; // "proposed" | "accepted" — typed by the schema abovedoc.body.Summary.text(); // the Summary section's prose, keyed by its headingWhat it exercises
Section titled “What it exercises”contract() with a Zod frontmatter schemavalidate() vs read()typed frontmatter and section text
Continue
Section titled “Continue”- Previous: this is the first example of the group
- Next: Tables as typed rows
- Group: Read markdown as typed data · All examples
- Reference: Typed model reference