Typed models
ts
import { defineModel, infer, toSource, type Infer } from "@flattype/models";
const Address = defineModel({ city: "string", zip: "string" });
const User = defineModel({
id: "number", // "7" is coerced to 7
name: "string",
createdAt: "date", // reads created_at; ISO string or epoch ms → Date
bio: "string?", // string | null, null when missing
role: { type: "string", default: "viewer" },
nickname: { type: "string", optional: true }, // left out when missing
address: Address, // nested model
tags: ["string"], // array
orderId: "bigint", // exact 64-bit IDs (BigInt)
avatar: "bytes?", // base64 → Uint8Array
});
type User = Infer<typeof User>;
User.parse(json); // a JSON value or a Flat Notation string
User.parseList(json.data); // arrays, including Flat Notation tables
User.safeParse(json); // { ok: true, value } | { ok: false, error }
User.serialize(user); // back to API JSON (dates as ISO strings, bytes as base64)
User.encode(users); // straight to Flat Notationparse throws a ModelError listing every problem with its path, for example address.zip: Required and tags[1]: Expected string, got object. Unknown keys such as password_hash are dropped.
| Option | Default | Effect |
|---|---|---|
keys | "auto" | "auto" reads createdAt or created_at. "snake" also serializes back to snake_case. "exact" only reads the field name. |
coerce | true | Converts "42" → 42 and "true"/1 → true. Set false to require exact types. |
Generate models from a real response:
ts
console.log(toSource(infer(await res.json()), "UsersPage"));ts
import { defineModel, type Infer } from "@flattype/models";
export const UsersPageData = defineModel({
id: "number",
fullName: "string",
createdAt: "date",
bio: "string?",
vip: { type: "boolean", optional: true },
}, { keys: "snake" });
export type UsersPageData = Infer<typeof UsersPageData>;
export const UsersPage = defineModel({
page: "number",
data: [UsersPageData],
});
export type UsersPage = Infer<typeof UsersPage>;