ADR 0005 — The typed client is inferred from the contract
- Status: Accepted
- Date: 2025-05 (core), 2026-05 (pilot fixes)
Context
One source project hand-wrote its frontend client — a fetch wrapper plus per-endpoint hooks in separate files. The contract was not used for client-side inference, so the client could silently drift from the server.
Another prototype showed the alternative: a TypedClient<C> type that derives
the entire client API from the contract, with one typed function per endpoint.
Decision
The typed client lives in the core. createClient / createClients build a
fully typed client from a contract with no hand-written per-endpoint code;
createHttpClient is the Ky-based transport adapter underneath (cookie auth,
SSR cookie forwarding, error parsing, transport retry).
Putting this through a real frontend (the pilot migration) exposed three subtle defects, all now fixed:
- Arguments are typed with
z.input, notz.output. The client sends pre-parse input; the server parses it to output. A field with.default(),.coerce()or.transform()differs between the two. Using the output type made server-defaulted fields required on the caller.EndpointArgsusesz.input; the handler’sctx.inputkeeps the output type. - The empty case is
{}, neverRecord<string, never>. The latter carries an index signature that poisons every intersected field tonever, collapsing the whole argument type.{} & X = X. ({}was later written asunknown, which has the same intersection identity and is lint-clean.) defineContract<const T>preserves string literals (multipart,method,path). Withoutconst,multipart: 'file'widened tostringand the multipart argument type degraded to an index signature.
A per-endpoint timeout was also added: a slow synchronous endpoint (AI
generation) declares its own client timeout once, in the contract.
Consequences
- Any contract yields a fully typed client; the transports cannot drift from it.
- Server-defaulted input fields are optional for the caller, as they should be.
- The client never needs an
ascast to satisfy a contract method signature.