Primitives and builders
Leaf primitives, combinators, and schema() for objects, arrays, records, and discriminated unions.
The @flare-ts/lib/schema entrypoint exports everything for parsing and validating JSON-shaped data: leaf primitives, combinators that wrap them, the schema() builder, and supporting types. Import from @flare-ts/lib/schema (or the default @flare-ts/lib entry). @flare-ts/core does not re-export these symbols.
This page teaches the workflow. For per-symbol signatures and constraint tables, see the API Reference. For named DTO classes, see Models (DTOs).
import { str, text, int, float, bool, uuid, email, url, date, enums, array, optional, defaultTo, schema,} from "@flare-ts/lib/schema";Primitives
Section titled “Primitives”Callable leaf primitives used as field values inside a descriptor. Each takes a string at the parse boundary and throws on invalid input; safeParse catches the throw and records a FieldError. Chain methods (.min, .max, .pattern, .format) return a new primitive and never mutate the original.
The alternative is parsing each boundary by hand: parseInt on one query value, Number(...) on the next, a typeof check wherever someone remembered one. Those coerce instead of reject, so parseInt("12x") is 12 and Number("") is 0, and the bad value travels on already wearing the type it violated, surfacing as a wrong result or a NaN several functions deep. A primitive makes the boundary the place the value is proven: int rejects anything that is not a whole number and turns the rejection into a FieldError carrying the field path, so malformed input fails at the edge with a located error instead of passing for a number.
| Primitive | Summary |
|---|---|
str | Short single-line strings; optional .min, .max, .pattern |
text | Multiline or untrusted text; JSON-escaped on serialize |
int | Integers; rejects non-safe integers and decimals |
float | Floating-point numbers with optional range |
bool | "true", "1", "false", "0" (case-insensitive) |
uuid | UUID v4 canonical format |
email | Simplified RFC 5322; returns lowercased |
url | WHATWG URL; http: and https: only; returns normalized href |
date | Date strings; chain .format("ISO" | "YMD" | "DMY" | "MDY" | "TIMESTAMP") |
Reserve str for short values. Use text when the value may contain quotes, newlines, or untrusted characters.
const User = schema({ id: uuid, name: str.min(1).max(50), bio: text.max(2_000), age: int.min(0),});Combinators
Section titled “Combinators”Functions that build or wrap a primitive. optional and defaultTo wrap a TypedPrimitive<T>; array primitives keep their string | string[] calling convention through the wrapper. For an optional nested object, call .optional() on the SchemaToken instead.
| Combinator | Summary |
|---|---|
array(primitive) | Comma-separated string or string array of parsed items |
optional(primitive) | Missing or empty input becomes undefined |
defaultTo(fallback, primitive) | Missing or empty input becomes fallback |
enums([...]) | Literal union from a readonly string tuple |
There are two ways to mark a field optional, at different levels:
optional(innerPrimitive)on a primitive field inside a descriptor.nestedToken.optional()when a whole nested object may be absent.
const Tags = schema({ labels: array(str), count: defaultTo(0, int.min(0)), role: enums(["admin", "user", "guest"]),});The schema() builder
Section titled “The schema() builder”schema() creates a SchemaToken that parses JSON field by field. Four forms:
- Object descriptor:
schema({ id: uuid, name: str }) - Top-level array:
schema([ItemSchema]) - Record:
schema([{ $record: ValueSchema }]) - Discriminated union:
schema<T, "union">(discriminantKey, branches)
safeParse materializes only declared keys; extra input properties are ignored. On success you get typed data. On failure you get { success: false, error: SchemaError } with a fields array. The call never throws.
const result = User.safeParse({ id: "…", name: "Ada" });if (result.success) { result.data; // { id: string; name: string; ... }}Use a schema token on an HTTP route descriptor’s body or response entry, or nest it inside another descriptor; route and query fields take primitives, not tokens. For a named, extendable class wrapper, use model() instead.
Related
Section titled “Related”- Models (DTOs):
model()for named DTO classes - Define a DTO: step-by-step reuse across contracts
- Serialization:
compileSerializerandtoJsonSchema - HTTP contracts: where descriptors meet the pipeline