Schema
Defining a Schema
Section titled “Defining a Schema”Users create data models in zeroback/schema.ts, where they declare tables, fields, indexes, and search capabilities.
import { defineSchema, defineTable, v } from "@zeroback/server";
export const schema = defineSchema({ projects: defineTable({ name: v.string(), description: v.string(), color: v.string(), }),
tasks: defineTable({ title: v.string(), description: v.optional(v.string()), status: v.string(), priority: v.string(), projectId: v.string(), assignee: v.optional(v.string()), dueDate: v.optional(v.number()), labels: v.optional(v.array(v.string())), }) .index("by_project", ["projectId"]) .index("by_project_status", ["projectId", "status"]) .searchIndex("search_title", { searchField: "title" }),});defineSchema(tables)
Section titled “defineSchema(tables)”Creates a schema definition from a map of table names to table definitions.
function defineSchema<T extends Record<string, TableDefinition<any>>>( tables: T): SchemaDefinition<T>| Parameter | Type | Description |
|---|---|---|
tables | Record<string, TableDefinition> | An object mapping table names to defineTable() results |
Returns: SchemaDefinition<T> — used by codegen to generate typed DataModel, function factories, and API references.
defineTable(fields)
Section titled “defineTable(fields)”Declares a single table with typed fields.
function defineTable<F extends PropertyValidators>( fields: F): TableDefinition<ObjectType<F>>| Parameter | Type | Description |
|---|---|---|
fields | Record<string, Validator> | An object mapping field names to v.* validators |
Returns: TableDefinition with chainable .index() and .searchIndex() methods.
System Fields
Section titled “System Fields”Every document automatically includes two system fields that users do not declare:
| Field | Type | Description |
|---|---|---|
_id | Id<TableName> | Auto-generated TypeID in "prefix_base32uuidv7" format (e.g., "tasks_01aryz6p69z5wqvzqz4lxcdpxx") |
_creationTime | number | Unix timestamp in milliseconds, derived from the UUIDv7 embedded in the TypeID |
These fields cannot be set or modified by user code and are excluded from insert(), patch(), and replace() arguments.
.index(name, fields)
Section titled “.index(name, fields)”Declares a secondary index on the table.
.index(name: string, fields: string[]): TableDefinition| Parameter | Type | Description |
|---|---|---|
name | string | Index name, used in .withIndex() queries |
fields | string[] | Ordered list of field names to index on |
Indexes enable efficient queries via .withIndex() instead of full table scans. Compound indexes support multi-field queries where equality is specified on leading fields and an optional range on the last field.
Every table automatically gets two built-in indexes:
by_id— index on_idby_creation_time— index on_creationTime
Example:
defineTable({ title: v.string(), projectId: v.string(), status: v.string(),}) .index("by_project", ["projectId"]) .index("by_project_status", ["projectId", "status"]).idPrefix(prefix)
Section titled “.idPrefix(prefix)”Overrides the default ID prefix for this table. By default, the table name is used as the prefix. Use this when the table name isn’t a valid TypeID prefix (must be 1–63 lowercase alpha characters).
.idPrefix(prefix: string): TableDefinition| Parameter | Type | Description |
|---|---|---|
prefix | string | 1–63 lowercase alpha characters (e.g., "task", "projectmember") |
Example:
defineTable({ title: v.string(), status: v.string(),}).idPrefix("task")// IDs will be: "task_01aryz6p69z5wqvzqz4lxcdpxx"Throws at schema definition time if the prefix contains non-lowercase-alpha characters.
.searchIndex(name, opts)
Section titled “.searchIndex(name, opts)”Declares a full-text search index on a text field.
.searchIndex(name: string, opts: { searchField: string }): TableDefinition| Parameter | Type | Description |
|---|---|---|
name | string | Search index name (used internally) |
opts.searchField | string | The text field to index for full-text search |
Powered by SQLite FTS5. Search indexes are kept in sync automatically via database triggers.
Example:
defineTable({ title: v.string(), body: v.string(),}).searchIndex("search_title", { searchField: "title" })Validators (v)
Section titled “Validators (v)”Import validators from @zeroback/server:
import { v } from "@zeroback/server";Validators are used in three places:
- Schema fields —
defineTable({ name: v.string() }) - Function arguments —
query({ args: { id: v.string() }, ... }) - Return types —
query({ returns: v.number(), ... })
Primitive Validators
Section titled “Primitive Validators”| Validator | TypeScript Type | Description |
|---|---|---|
v.string() | string | String value |
v.number() | number | Number value (IEEE 754 double) |
v.boolean() | boolean | Boolean value |
v.null() | null | Null value |
v.any() | any | Any type (no validation) |
Reference Validators
Section titled “Reference Validators”| Validator | TypeScript Type | Description |
|---|---|---|
v.id(tableName) | Id<TableName> | Document ID referencing a specific table |
v.id("tasks") // Id<"tasks"> — e.g. "tasks_01aryz6p69z5wqvzqz4lxcdpxx"Compound Validators
Section titled “Compound Validators”| Validator | TypeScript Type | Description |
|---|---|---|
v.object(fields) | { ... } | Nested object with typed fields |
v.array(element) | T[] | Array of a single element type |
v.optional(validator) | T | undefined | Optional field (can be omitted) |
v.union(...members) | T1 | T2 | ... | Union of multiple types |
v.literal(value) | "value" | Exact literal value |
v.record(keys, values) | Record<K, V> | String-keyed map/record |
// Nested objectv.object({ street: v.string(), city: v.string() })
// Arrayv.array(v.string()) // string[]
// Optionalv.optional(v.string()) // string | undefined
// Unionv.union(v.literal("active"), v.literal("archived")) // "active" | "archived"
// Record / mapv.record(v.string(), v.number()) // Record<string, number>Numeric Validators
Section titled “Numeric Validators”| Validator | TypeScript Type | Description |
|---|---|---|
v.float64() | number | Explicit IEEE 754 double-precision float |
v.int64() | bigint | 64-bit integer |
Binary Validator
Section titled “Binary Validator”| Validator | TypeScript Type | Description |
|---|---|---|
v.bytes() | ArrayBuffer | Binary data |
Type Inference
Section titled “Type Inference”The Infer type helper extracts the TypeScript type from a validator:
import type { Infer } from "@zeroback/server";
const taskValidator = v.object({ title: v.string(), status: v.union(v.literal("todo"), v.literal("done")),});
type Task = Infer<typeof taskValidator>;// { title: string; status: "todo" | "done" }Schema Enforcement
Section titled “Schema Enforcement”Schema validation runs at runtime on every write operation (insert, patch, replace). If a document fails validation, the write is rejected with an error. This ensures the database always matches the schema definition.