Schema
The schema lives in onebase/schema.ts. It declares your tables, the shape of their documents, and their indexes. Every write is checked against it, and your functions get types from it.
import { defineSchema, defineTable } from "onebase/server";
import { v } from "onebase/values";
export default defineSchema({
users: defineTable({
name: v.string(),
email: v.string(),
role: v.union(v.literal("admin"), v.literal("member")),
}).index("by_email", ["email"]),
posts: defineTable({
author: v.id("users"),
title: v.string(),
tags: v.array(v.string()),
published: v.boolean(),
views: v.optional(v.number()),
})
.index("by_author", ["author"])
.index("by_author_published", ["author", "published"]),
});#Validators
| Validator | TypeScript type |
|---|---|
v.string() | string |
v.number() | number (alias: v.float64()) |
v.boolean() | boolean |
v.null() | null |
v.id("table") | Id<"table"> |
v.literal("x") | "x" |
v.array(v.string()) | string[] |
v.object({ a: v.number() }) | { a: number } |
v.record(v.string(), v.number()) | Record<string, number> |
v.union(a, b) | A | B |
v.optional(x) | field may be missing |
v.any() | anything |
In function args, v.id("table") only accepts ids of that table (inside a component, the component's own table), so ctx.db.get(id) on a validated id can't reach another table.
Values are stored as JSON. Store dates as numbers (Date.now()), not Date objects; onebase rejects a Date and tells you so.
#System fields
Every document has _id and _creationTime (milliseconds). They're added automatically. Don't declare them, and don't pass them to insert.
Ids carry a tag for their table, so ctx.db.normalizeId("users", someString) can check an id without a database lookup.
#Indexes
defineTable({ ... }).index("by_author_published", ["author", "published"])- Usage: index names are yours to choose. Use them with
.withIndex()(see Database). - Automatic use: a
.filter()on indexed fields uses the matching index automatically. - Built-in index: every table has
by_creation_time. - Changes: indexes are created and dropped automatically at startup when
schema.tschanges. - Nested fields: use dots, e.g.
["profile.country"].
#Search indexes
defineTable({ ... }).searchIndex("search_body", { searchField: "body", filterFields: ["channel"] })Full-text search over one string field. See Search.
#Vector indexes
defineTable({ ... }).vectorIndex("by_embedding", { vectorField: "embedding", dimensions: 1536, filterFields: ["cuisine"] })Nearest-neighbour search over an array of numbers, from actions with ctx.vectorSearch. See Vector search.
#Changing a schema
When schema.ts changes, onebase checks every stored document against it at startup, like a Convex deploy. If some don't match, the server refuses to start and lists them:
schema.ts doesn't match 2 existing documents:
- people k57… : document.age is required (expected v.number())
- people k9a… : document.age is required (expected v.number())Either make the schema accept the data (v.optional(...), v.union(...)), or fix the data: start with { schemaValidation: false }, update the documents in a mutation or the dashboard, then turn validation back on. The check runs only when the schema changes.
#Validation errors
A bad write fails with a message that lists every problem:
Invalid "posts" document:
- document.title must be v.string(), got number (42)
- document.published is required (expected v.boolean())
- document.extra is not allowed (known fields: author, title, tags, published, views)To turn validation off (for example while migrating data), pass defineSchema({ ... }, { schemaValidation: false }).
#Without a schema
schema.ts is optional. Without it, any table accepts any document and ctx.db is untyped. onebase doctor will remind you to add one.