Models (DTOs)
model() returns a named, extendable schema-token class for contracts, services, and standalone parsing.
model() returns a ModelTokenBuilder<T>: an extendable class whose instances are typed as T, with the same safeParse and optional() surface as a schema() token. Static safeParse yields a plain object typed as T, not a class instance.
Use model() when a shape needs a named identifier (DTOs, contract types, shared test fixtures) instead of an anonymous schema({...}) value. Validation rules and wire parsing match schema() exactly.
ModelTokenBuilder<T>
Section titled “ModelTokenBuilder<T>”| Capability | Meaning |
|---|---|
| Extendable class | Subclass class User extends model({...}) {} or assign const User = model({...}) |
SchemaToken<T> | Usable on contract body / response, nested in descriptors, and in safeParse |
Static safeParse | Returns a plain object on success; failures use the same FieldError[] paths as schema() |
Static optional() | Returns a SchemaToken<T> (not another model builder) for optional nested fields |
| Response serialization | When the token is on a contract response entry, outbound plain objects serialize with schema-aware rules |
The model class has no argument-taking constructor. new MyModel({ ... }) does not type-check, and new MyModel() returns an empty object, not a parsed one. Call MyModel.safeParse(input) instead.
Overloads
Section titled “Overloads”model() has two public forms. It does not support the top-level array or record overloads that schema() accepts. Build those with schema([Item]) or schema([{ $record: Value }]) first, then pass the token to model(existingToken).
Inline descriptor
Section titled “Inline descriptor”import { model, str, int, optional } from "@flare-ts/lib/schema";
class CreateUser extends model({ name: str.min(1), email: str, age: optional(int),}) {}Promote an existing schema token
Section titled “Promote an existing schema token”import { schema, model, uuid, str } from "@flare-ts/lib/schema";
const UserSchema = schema({ id: uuid, name: str });class UserModel extends model(UserSchema) {}For top-level arrays or records:
const World = schema({ id: uuid, name: str });class Worlds extends model(schema([World])) {}Discriminated unions: use schema()
Section titled “Discriminated unions: use schema()”model() has no union form. A class cannot carry a union instance type, so the extendable face model() exists for is unavailable to unions; passing a union-typed schema token to model() is a type error. Declare the union with schema(), and when you want one name in both the value and type positions, give the token and the type the same name:
import { schema, int, str } from "@flare-ts/lib/schema";
type Pet = { kind: "cat"; lives: number } | { kind: "dog"; breed: string };const Pet = schema<Pet, "union">("kind", { cat: { lives: int }, dog: { breed: str },});
const cat = Pet.safeParse({ kind: "cat", lives: 9 });Pet now works exactly like a model class name: value position (Pet.safeParse, contract entries) and type position (function feed(pet: Pet)). See Define a DTO.
Static API
Section titled “Static API”safeParse(raw)
Section titled “safeParse(raw)”Accepted input matches schema(): a JSON string, an ArrayBuffer (UTF-8 JSON from the HTTP pipeline), or a plain object / array JsonValue.
const result = CreateUser.safeParse(requestBody);if (!result.success) { // result.error.fields: FieldError[] return;}const user = result.data; // plain object typed as CreateUser's shapeoptional()
Section titled “optional()”Returns an optional schema token for nesting:
const Envelope = schema({ user: UserModel.optional(),});For optional primitive fields, use optional(int) on the primitive, not on the model.
In contracts
Section titled “In contracts”Models slot into httpContract body and response the same way as schema(...) tokens. Controller methods read parsed inbound values with this.ctx.extract(entry). The pipeline validates once before the handler runs; failures become 400 with field errors.
Outbound: declare the model on the contract response map and return a plain object via this.ok(body), this.created(body), or new FlareResponse(status, body). Flare serializes with the per-status schema. See Serialization and HTTP responses.
Model vs schema
Section titled “Model vs schema”schema({...}) | model({...}) | |
|---|---|---|
| Runtime shape | Plain token object | Extendable class + token |
| TypeScript name | Inferred from variable | Class name |
extends | Not supported | Supported |
| Discriminated union | Supported | No union form; use schema() |
| Top-level array / record | Supported | Use schema first, then model(token) |
Contract body / response | Yes | Yes |
| Manual parse | Token.safeParse(raw) | ModelClass.safeParse(raw) |
Same validation rules and parsing. model() wraps a schema() token in an extendable class.
Related
Section titled “Related”- Define a DTO: how-to for reusable shapes
- Primitives and builders: primitives and
schema()overloads - Tutorial: Your first app: models in a full app