Schema
Declarative tables, validators, indexes and sharding.
Last updated:
A Lunora schema is a single TypeScript file. It is the source of truth for your storage layout, the input to codegen, and the contract the type-safe client is generated against. There is no separate migrations DSL: editing the schema and running codegen is the migration.
// lunora/schema.ts
import { defineSchema, defineTable, v } from "lunorash/server";
export default defineSchema({
channels: defineTable({
name: v.string(),
createdBy: v.id("users"),
createdAt: v.number(),
})
.global()
.index("by_name", ["name"], { unique: true }),
messages: defineTable({
channelId: v.id("channels"),
userId: v.id("users"),
text: v.string(),
})
.shardBy("channelId")
.index("by_channel", ["channelId", "_creationTime"]),
users: defineTable({
email: v.string(),
name: v.string(),
})
.global()
.index("by_email", ["email"], { unique: true }),
});Validators
v exposes the primitive and composite validators used at the API boundary:
v.string(),v.number(),v.boolean(),v.null(),v.bytes()v.id("tableName")— typed foreign keyv.array(item),v.object({ ... }),v.union(a, b),v.literal("x")v.optional(inner)— makes a field optional
Tier modifiers
- (default) — table lives in the root
__root__Durable Object .shardBy(field)— one Durable Object per distinct value offield.global()— table is stored in Cloudflare D1 (cross-tenant reads)
Indexes
.index(name, fields, { unique? }) defines a B-tree index used by
q.withIndex(name, ...) in queries. .searchIndex(name, { field, filterFields })
adds a full-text index over field.