Skip to content

Data model

A schema defines fields, defaults, indexes, relations and access rules. One schema() call produces the engine’s table registry, the SQL migrations and the client’s types. Types are inferred from the schema; there is no codegen step.

tapedb.schema.ts
import { check, filter, model, role, schema, t } from "tapedb/schema";
export default schema({
board: model({
name: t.string(),
ownerId: t.string().index(),
archived: t.boolean().default(false),
columns: t.hasMany("column"),
}).access({
read: filter(({ auth }) => ({ ownerId: auth.sub })),
update: check(({ auth, row }) => row?.ownerId === auth.sub),
delete: role("moderator"),
}),
column: model({
name: t.string(),
position: t.number().default(0).index(),
board: t.belongsTo("board", { onDelete: "cascade" }),
tasks: t.hasMany("task"),
}),
task: model({
title: t.string(),
done: t.boolean().default(false),
due: t.date().optional(),
meta: t.json().optional(),
column: t.belongsTo("column").optional(),
}),
});

t.string(), t.number(), t.boolean(), t.date() (stored as epoch milliseconds) and t.json().

Modifiers: .optional(), .default(value), .index() and .unique().

Relations: t.belongsTo("model") owns the foreign key (board stores boardId); t.hasMany("model") is its inverse. onDelete can be set on a belongs-to relation.

Every row carries id (client-generated, time-ordered), createdAt and updatedAt.

Access rules are part of the model definition, next to its fields. See Access & identity for the three rule types and their defaults.