Reference

Schema and database

For your agentCopy this and give it to the AI you're building with.
Run `cubek skill`, read all of it and follow it exactly (no CLI: download https://cubek.dev/SKILL.md verbatim with curl; don't summarize it); when you change the schema or query the database, follow its "Schema and database" section.
Run `cubek check` after every edit until it is clean.
// schema.ts
import { table, t } from "cubek"

export const notes = table("notes", {
  id: t.id(),  // string ULID, generated
  title: t.string(),
  body: t.string(),
  tags: t.json<string[]>().nullable(),  // any JSON value
  pinned: t.boolean().default(false),
  views: t.number().default(0).min(0),
  status: t.enum(["draft", "published"]).default("draft"),
  authorId: t.string().nullable(),
  createdAt: t.timestamp().default("now"),  // set on insert only
}).index("status", "createdAt").search("title", "body")

export const comments = table("comments", { id: t.id(), noteId: t.ref(notes), text: t.string() })
export const sessions = table("sessions", { id: t.id(), tokenHash: t.string().unique(), userId: t.string() })
  • Also t.bytes(), .unique({ nocase: true }), .max(n) (strings: length), table .unique("pollId", "voterId"). Broken constraints throw in insert/update.
  • schema.ts changes migrate themselves (tables, nullable/defaulted columns, indexes, unique, search); dropping, retyping or a new check needs deploy --allow-destructive.
import { db, type Row } from "cubek"
import type { notes } from "../../schema"

export type Note = Row<typeof notes>

const recent = await db.notes.find({
  where: { status: "published", createdAt: { gte: since }, or: [{ pinned: true }, { title: { contains: q } }] },
  orderBy: [{ pinned: "desc" }, { createdAt: "desc" }, { id: "desc" }],  // id breaks ties
  limit: 20,
})
const one = await db.notes.findOne({ where: { title: "Hello" } })  // Note | null
const hits = await db.notes.search(q, { where: { status: "published" }, limit: 10 }) // best first
const n = await db.notes.count({ search: q })  // total search hits
const note = await db.notes.insert({ title: "Hi", body: "" })  // returns the row
await db.notes.update(note.id, { pinned: true })  // row | null; pass updatedAt yourself
await db.sql<{ views: number }>`UPDATE notes SET views = views + 1 WHERE id = ${note.id}`  // atomic
await db.comments.deleteWhere({ noteId: note.id })  // count
await db.transaction(async (tx) => {  // a throw rolls it back
  const a = await tx.notes.insert({ title: "A", body: "" })
  await tx.comments.insert({ noteId: a.id, text: "first" })
})
const rows = await db.sql<{ status: string; n: number }>`SELECT status, COUNT(*) AS n FROM notes GROUP BY status`
  • Also get(id), delete(id) → boolean, insertMany(rows), updateWhere(where, patch), sum(col, { where }), min/max.
  • Where: ne gt gte lt lte in isNull, text contains startsWith endsWith (literal), like, and/or.
  • Timestamps are UTC ISO strings (compare with toISOString() values).
  • db.transaction runs alone (serializable, ≤ 100 ms, no fetch); outside, read-then-write can interleave. No joins: two queries or db.sql (database.md).