Field guide / Practical workflows
Type-driven development with Dotdog
Start with the states and operations your domain permits. Use the compiler to reject invalid combinations, then keep the resulting contracts connected to spec and test evidence.
What type-driven development means here
Specs describe the intended rule. Types express the parts your language can enforce. Graphs connect those artifacts for navigation. Test-driven development is a separate practice; spelling out “type-driven” avoids confusing the two uses of TDD.
This walkthrough uses a small order workflow. A shipped order needs tracking data, and only a paid order can be passed to the shipping operation. It does not arrange delivery or prove a payment happened.
1. Use a compatible project
Dotdog's optional adapter requires Node.js 22.12+ and a trusted project-installed TypeScript 7.0.2. Core Dotdog does not require a compiler. If your application uses another version, use a separate scratch project rather than changing its compiler for this guide.
mkdir type-guide
cd type-guide
npm init -y
npm install --save-dev typescript@7.0.2
mkdir srcCreate tsconfig.json with:
{
"compilerOptions": {
"strict": true,
"noEmit": true,
"target": "ES2022",
"module": "NodeNext"
},
"include": ["src/**/*.ts"]
}
2. Model valid states before implementation
Create src/order.ts:
export type Draft = { state: "draft"; id: string };
export type Paid = { state: "paid"; id: string; receipt: string };
export type Shipped = {
state: "shipped";
id: string;
receipt: string;
tracking: string;
};
export type Order = Draft | Paid | Shipped;
export function ship(order: Paid, tracking: string): Shipped {
return { ...order, state: "shipped", tracking };
}
The discriminant keeps receipt and tracking fields with the states that require them. A collection of unrelated booleans and optional fields would permit more invalid combinations. See TypeScript's discriminated-union documentation.
3. Inspect, save, and query a selected contract
npx dotdog@1.0.0 types inspect Order --file src/order.ts --project tsconfig.json --json > order-contract.json &&
npx dotdog@1.0.0 graph import order-contract.json --project types
npx dotdog@1.0.0 context 'src/order.ts#Paid' --dag .doghouse/imported/types.dag --json
npx dotdog@1.0.0 impact 'src/order.ts#Paid' --dag .doghouse/imported/types.dag --json
npx dotdog@1.0.0 graph audit --dag .doghouse/imported/types.dag --profile evidence --json
Only import a successful inspection: the shell's && prevents importing failed compiler output. Expect an ok: true inspection, an imported graph, and explicit partial coverage. Impact describes references in this selected graph, not every call site in your application.
Saved node evidence retains compiler/configuration/input identities. After changing source, configuration, compiler or dependencies, inspect again and replace the saved graph. A freshness audit checks recorded inputs; it cannot discover every new or unloaded file.
4. Try a call the compiler must reject
Create src/invalid.ts:
import { ship, type Draft } from "./order.js";
const draft: Draft = { state: "draft", id: "order-1" };
ship(draft, "TRACK-1");npx dotdog@1.0.0 types inspect Order --file src/order.ts --project tsconfig.json --json
The configured project now fails checking and Dotdog exits nonzero. Repair the example by passing a Paid value with a receipt, then repeat inspection. Keep negative compile checks alongside runtime and integration tests in real applications.
5. Keep the boundaries honest
- Parse untrusted input before treating it as a domain value. Type assertions do not perform runtime validation.
- The signature does not consume the old order or prevent repeated calls. Receipt strings do not prove payment; runtime business rules still apply.
- Inspection is read-only with respect to project configuration and compiler output. The shell redirection above deliberately creates the saved JSON file.
- Dotdog 1.0 supports this exact compiler interface, not every TypeScript version. Project-reference/composite/noCheck configurations fail explicitly.
- Keep spec-to-type/test mappings explicit. Matching names are not proof that a requirement is implemented.
For supported aliases, processing bounds and evidence limitations, read the full type-contract reference. For the design rationale and newer compiler research, read the research notes.