Schema
Define your extension's database tables in server/schema.ts using defineSchema and defineTable from @zaflun/lumio-sdk/server.
Basic example
// server/schema.ts
import { defineSchema, defineTable, v } from "@zaflun/lumio-sdk/server";
export default defineSchema({
rules: defineTable({
text: v.string(),
revealed: v.boolean(),
score: v.number().optional(),
tags: v.array(v.string()).optional(),
}).storage("instance"),
votes: defineTable({
choice: v.string(),
userId: v.string(),
weight: v.number().optional(),
}).storage("account"),
});
defineSchema
Wraps a map of table definitions. Each key is the table name used in functions (queryRows("rules"), etc.).
import { defineSchema, defineTable, v } from "@zaflun/lumio-sdk/server";
export default defineSchema({
tableName: defineTable({ /* field validators */ }).storage("scope"),
// ...
});
defineTable
Defines the columns of a table. Accepts a record of field names to validators.
defineTable({
fieldName: validator,
// ...
})
Every table automatically gets these system columns — you do not define them:
id—textprimary key (UUID, auto-generated)created_at—timestamptz(auto-set on insert)updated_at—timestamptz(auto-updated on patch)
Validators
| Validator | Type | Example |
|---|---|---|
v.string() | TEXT | v.string() |
v.number() | DOUBLE PRECISION | v.number() |
v.boolean() | BOOLEAN | v.boolean() |
v.array(v.string()) | TEXT[] | v.array(v.string()) |
v.array(v.number()) | DOUBLE PRECISION[] | v.array(v.number()) |
v.object({ ... }) | JSONB | v.object({ x: v.number(), y: v.number() }) |
.optional() modifier
Appending .optional() makes the field nullable in SQL and optional in TypeScript:
defineTable({
text: v.string(), // required, NOT NULL
score: v.number().optional(), // nullable, REAL | null
notes: v.string().optional(), // nullable, TEXT | null
})
Storage scopes
Chain .storage(scope) to control which installation instances share the same table data:
| Scope | Description | Use case |
|---|---|---|
"instance" | One set of rows per overlay install | Rules for a specific overlay |
"overlay" | Shared across all installs on the same overlay | Per-overlay leaderboard |
"account" | Shared across all overlays in an account | Account-wide vote history |
"global" | Shared across all installs of this extension everywhere | Global leaderboard |
export default defineSchema({
rules: defineTable({
text: v.string(),
revealed: v.boolean(),
}).storage("instance"), // each overlay install has its own rules
globalLeaderboard: defineTable({
userName: v.string(),
score: v.number(),
}).storage("global"), // all installs share this leaderboard
});
Migrations
Schema changes are additive only — you can add new tables and add nullable columns to existing tables. Removing tables or columns, or changing column types, requires a manual migration step via the extension dashboard.
Never rename a field in defineTable without a migration plan. The old column name remains in PostgreSQL; the new name creates a new column.
Full example
// server/schema.ts
import { defineSchema, defineTable, v } from "@zaflun/lumio-sdk/server";
export default defineSchema({
// Challenge rules — one set per overlay install
rules: defineTable({
text: v.string(),
revealed: v.boolean(),
category: v.string().optional(),
}).storage("instance"),
// Votes — shared across the account
votes: defineTable({
ruleId: v.string(),
userId: v.string(),
choice: v.string(),
}).storage("account"),
// Config snapshots — per instance
snapshots: defineTable({
label: v.string(),
data: v.object({
homeTeam: v.string(),
awayTeam: v.string(),
homeScore: v.number(),
awayScore: v.number(),
}),
}).storage("instance"),
});