contract domain
owns typed boundaries between runtimes: schemas, API behavior, generated transport, and shared cache identity.
why
every boundary between two runtimes — server and browser, producer and consumer — is a place where vocabularies drift. schemas, generated transport, and cache identity must all derive from one typed definition. hand-author any one of them and the boundary has two truths: drift becomes a runtime error instead of a compile error.
when to use
owns a decision when the question is the typed boundary between two runtimes — schemas, API route ownership, generated transport, query-key identity, versioning. who may cross the boundary belongs to the security domain.
good and bad
bad
route, frontend type, and cache tuple each describe the same response.
three sources of truth that drift independently. backend adds a status field → frontend types don't update (TypeScript out of sync), cache key isn't updated (stale data or runtime errors), code not expecting the field breaks. backend ↔ frontend coordination becomes manual and error-prone.
good
named schema → mounted route → generated client → shared query key factory
one typed boundary carries its vocabulary across runtime consumers:
- a Zod schema defines what the route returns
- the schema is mounted on the route and auto-generates OpenAPI
- Fern uses that OpenAPI to generate a typed client with the exact shape
- query keys are factory functions deriving from the schema
backend and frontend stay synchronized automatically. a vocabulary change that would have been a runtime surprise on an unrelated screen becomes a compile failure in the generated client — fixed before merge, not investigated after deploy.