Schema — Derived Types
Your schema produces runtime validators and compile‑time types automatically. You don’t hand‑write model interfaces—the types are derived from the schema DSL and flow through the client and adapters.
What you get from the schema
- Zod bundle:
db.zodBundle— per‑table validators at runtime. - TypeScript inference: types for rows, inserts/updates, and query results.
- Relation‑aware projections: nested selection strings yield typed nested results.
Keep definitions focused on column kinds (
integer | varchar | boolean | timestamp | uuid | json | enum) and declare FKs withreferences(...). Constraints/nullability are out of scope for now.
Deriving table types (rows)
1import { z } from 'zod'
2import { db } from './schema' // defineSchema(...)
3
4// Row types come from the Zod bundle
5export type UserRow = z.infer<typeof db.zodBundle.shape.users>
6export type TodoRow = z.infer<typeof db.zodBundle.shape.todos>- These represent the stored row shape per table.
Insert / update shapes (practical pattern)
When creating or editing records, infer types from your Zod models and narrow to the fields you accept in that flow.
1// Example "insert payload" derived from the row type
2type NewTodo = Pick<TodoRow, 'id' | 'title' | 'user_id' | 'completed' | 'created_at' | 'updated_at'>
3
4async function addTodo(db: any, t: NewTodo) {
5 await db.from('todos').insert(t)
6}If your app generates IDs (e.g.,
uuid), model that in your payload type; if the DB generates them, omit the field.
Projection‑driven result types
Query result shapes align with your projection string (and declared relations).
1// Example: one-level M→1 projection (SQLite join limit today)
2const rows = await db
3 .from('todos')
4 .select('id, title, users(name)')
5
6// Each row is structurally like:
7type TodoWithUserName = {
8 id: string
9 title: string
10 users: { name: string }
11}Remember: chain filters/modifiers first, then call
.select(...)to execute.
Relation‑aware typing (what to expect)
- M→1 / 1→1: selecting parent fields nests a single object under the parent key (e.g.,
users(name)). - 1→M / M→M: inline expansion isn’t supported by SQLite join builder yet; prefer two queries and compose in code, or DB‑side views. Types reflect what you select.
Tips
- Co‑locate small helper types (like
NewTodo) next to features/components. - Avoid duplicating table definitions—always infer from
db.zodBundle. - Keep projection strings minimal; it tightens the result type and improves payloads.
Summary
Define once, infer everywhere: the schema yields Zod validators and TypeScript types for rows and queries, while your projection strings shape the returned types. This keeps your app strongly‑typed across adapters without manual interfaces.