T.opaque
The escape hatch: values with no dedicated primitive — a Map, a Set, a URL, a class instance.
T.opaque(
({ random }) =>
new Map([
["zone", Math.floor(random.next() * 5) + 1],
["days", Math.floor(random.next() * 10) + 1],
]),
);The field's type is whatever produce returns — T.opaque(({ random }) => new Map<string, number>()) needs no annotation.
produce is handed { random, clock } — this schema's own seeded stream, and the instance's resolved "now" (see What "now" means) — and that's the detail worth not skipping: draw from random.next() and the field stays reproducible from the seed, exactly like every other primitive. Reach for Math.random() instead and you opt that one field out of reproducibility, silently.
Why this exists given T.always / T.enum already hold arbitrary values: T.always(someMap) types correctly but hands back the same reference every call, with no .as(). T.opaque is the direct way to express "fabricate a fresh value of a kind this library doesn't model."
There's no .as() on T.opaque — the constructor argument already fully determines production, so there's nothing left to override.
.as(produce) on the other primitives
Most primitives — T.string, T.number, T.bigint, T.date, T.array, T.record, T.boolean — have their own .as(produce) for layering a custom producer over an existing kind:
T.string.as(() => crypto.randomUUID(), { format: "uuid" });Like T.opaque, these producers receive a { random, clock } context too — but unlike T.opaque, using it is optional: they're overriding a kind that already draws properly on its own, so the override is meant for one-off custom logic, not a from-scratch generator, and it's entirely reasonable to ignore the context and write Math.random() (or call an unseeded library like faker) inside one instead. Do that and that field falls outside the seeded-reproducibility guarantee, not by mistake — see Custom types for the common case of pairing this with a data-generation library.
hints (on string / number) are optional, neutral JSON-Schema-shaped metadata (format, pattern) — not used for fabrication, only forwarded to schema exporters like TypeBox.
