Status: implemented
A schema has a TypeScript type twice over: statically, as Ts<typeof s>, which the type checker computes, and as text, which the runtime printer writes.
const s = { a: or(option, number), b: or(number, undefined) }
type S = Ts<typeof s>
// { readonly a?: number } & { readonly b: number | undefined }
printer()(s)
// '{readonly"a"?:number,readonly"b":undefined|number}'
Ts<> is what makes a reader's result typed: parse(s) and validate(s) return Ts<typeof s> on success, so a schema declared once is both the check and the static type, with no second declaration to drift.
The two renderings are spelled differently, and they can also differ in what they admit. The printer goes through the data form's normalization. Ts<> can't evaluate that normalization at the type level, so where it can't see a simplification it keeps a wider type. That covers a rest that is empty without being written or(), such as rest([42], [or()]), and a rest with no prefix: rest([], string) prints as readonly string[], while Ts<> gives readonly (string | undefined)[]. How RestTs renders a rest and ts/types.ts explain both. The wider type is sound for a reader's result.
Ts<typeof s> | printer()(s) | |
|---|---|---|
| computed by | the type checker | a function, at run time |
| goes through | the schema as written | the data form, toData(s) |
| a struct | required and optional keys as an intersection | one object type, keys sorted |
| a recursive schema | a recursive type | the alias name only; dataToTs also returns the type tree = … definitions |
Every container is readonly; printer(true) drops the modifier. A key admitting absence is optional, a?:, and its value type leaves out undefined unless the schema admits it too — the difference exactOptionalPropertyTypes draws (absence). A bare tuple renders at its exact length, and a rest as a tail (open containers).
The demo on this page runs the Optional or undefined lesson: { a: or(option, number), b: or(number, undefined) }, whose TypeScript type marks a optional and gives b the type undefined|number. Leave b out of the value to see both readers refuse it; give it undefined to see them accept it. The lesson is defined in sample.f.mjs, and the module page's demo lists it among all the others.
- TypeScript: object types, conditional types, which
Ts<>is built from
Ts<> in ts/types.ts, whose README explains how it avoids TS2589; printer, dataToTs and their marked forms in ts/module.f.mjs.