Skip to content
Logo

Primitives overview

Every one of these is reached through T, the object initialize() returns — there's no other way to name a primitive. See Public API for why the entry point itself exports almost nothing else.

The rest of this page is grouped by the question you're holding. Each name below has its own page; the All sidebar lists them alphabetically.

Two things not in any group, deliberately: object.compute is only ever reached through .refine(({ compute }) => ...)'s closure-scoped compute, never named directly (see Objects); and self, used inside T.recursive(body), is only ever the placeholder body is handed, never T.self (see T.recursive).

Two things that trip people up early

  • T.string and T.bigint have no bare form. There's no natural bound to fuzz either to, so building T.string alone fails at fabricate-time — .whereby({ length }) (or .as(...)) is required first.
  • Weighted picks (T.enum, T.choice, .weighted() on the absence primitives) don't need weights that sum to anything in particular — they're read as relative proportions, numerator over the sum of all values given. A weight of 0 disables that outcome; every weight zero throws. Negative weights, NaN, and Infinity are invalid.

Scalars

A single value of one JavaScript type, drawn (or, for T.symbol, minted).

  • T.string — no bare form; .whereby({ length }) required
  • T.number — unbounded bare, or .whereby({ min, max }); .integer for wholes
  • T.bigint — no bare form, same reasoning as T.string
  • T.boolean — fair coin, or .weighted({ true, false })
  • T.date — bounded instants, plus .past / .future
  • T.symbolSymbol(), or .keyed(description)

Literals and choices

Four ways to pick from a fixed set — split along two axes: fixed value vs. weighted draw, and value vs. schema.

Picks aMembers areBare form
T.alwaysany fixed valueyes
T.enumvalueany valueno — .uniform/.weighted
T.choiceschema, then fabricates itany schemano — .uniform/.weighted

Absence

Seven ways for a value to not quite be there, and they are not interchangeable. Most confusion here comes from picking the wrong axis, not from the primitives themselves.

Two independent questions a schema can answer:

  • Is the key present on the object at all? — a structural fact about the enclosing T.object, meaningless outside one.
  • Is the value, once present, null or undefined or something else? — an ordinary fact about a value, usable anywhere a schema is usable: a top-level Fabricator, an array element, an object field.
I want...UseKey present?Value when "absent"Object-only?
A value that is always exactly nullT.nullnullno
A value that is always exactly undefinedT.undefinedundefinedno
A field that's always present, but the value might be undefinedT.undefinable(inner)alwaysundefinedno
A field that's always present, but the value might be nullT.nullable(inner)alwaysnullno
A field that's always present, value might be null or undefinedT.nullish(inner)alwaysnull or undefinedno
A field that's sometimes missing from the object entirelyT.omittable(inner)sometimes(key doesn't exist)yes
A field that might be missing, present-as-undefined, or present with a valueT.optional(inner)sometimes(key doesn't exist) or undefinedyes

T.omittable and T.optional only make sense as a field inside T.object(...). T.nullable, T.nullish, and T.undefinable are ordinary values, usable anywhere.

A field that lands on "omitted" or "null" or "undefined" never calls its wrapped schema's .fabricate() — the inner draw is skipped entirely on that outcome. Every field already has its own independent stream by then, so skipping a draw only affects that field's own next call, never a sibling's. See Reproducibility.

Collections

Three ways to fabricate more than one value, distinguished by whether the shape is repeated or fixed, and whether positions are indices or keys.

PositionsShapeCount
T.arrayindexsame for every elementfuzzed
T.tupleindexindependent per slotfixed
T.recordkey (drawn)same for every valuefuzzed, may collapse

Structures

  • T.object — the primitive most other things compose inside; Objects is the full treatment
  • T.recursive — trees, linked structures, a generic JSON value: a schema that references itself

Escape hatch

  • T.opaque — a Map, a Set, a URL, a class instance: anything this library has no dedicated kind for

Most other primitives also have .as(produce) for layering a custom producer over an existing kind. That's an override of something that already draws; T.opaque is when there is no kind to override. See T.opaque for the distinction, and Custom types for pairing .as() with a data-generation library.