SQLite CRUD (ORM + migrations)

defineModel gives a typed repository over bext's embedded SQLite; migrate() applies a tracked, idempotent schema (no more lazy CREATE TABLE IF NOT EXISTS). The loader reads via the query builder, the action writes via the model, and a 303 redirect re-runs the loader so the list reflects the mutation.

Tip
The model returns real typed objects — it zips the bridge's positional rows for you, so no more .columns/.rows juggling. migrate() is idempotent: safe to call every request, it runs only the migrations missing from the _bext_migrations ledger.

Add a note

Notes (20)

smoke-note2026-10-20 18:25:29
smoke-note2026-10-20 18:08:08
smoke-note2026-10-20 16:23:42
smoke-note2026-10-20 15:47:34
smoke-note2026-10-20 15:44:58
smoke-note2026-10-20 15:43:07
smoke-note2026-10-20 15:06:16
smoke-note2026-10-20 13:38:32
smoke-note2026-10-20 13:24:59
smoke-note2026-10-20 11:51:30
smoke-note2026-10-20 11:28:08
smoke-note2026-10-20 11:09:38
smoke-note2026-10-19 11:31:34
smoke-note2026-10-19 09:08:44
smoke-note2026-10-18 20:58:27
smoke-note2026-10-18 20:07:29
smoke-note2026-10-18 17:53:16
smoke-note2026-10-18 12:56:30
smoke-note2026-10-17 21:37:52
smoke-note2026-10-17 00:03:09
src/app/examples/db-crud/page.tsxTSX
import { defineModel } from "@bext-stack/framework/orm";
import { migrate } from "@bext-stack/framework/migrate";

// Tracked, idempotent schema — no lazy CREATE TABLE IF NOT EXISTS.
const migrations = [{
  id: "0001_create_notes",
  up: (db) => db.schema.createTable("notes", (t) => {
    t.id();
    t.text("body").notNull();
    t.timestamps();
  }),
  down: (db) => db.schema.dropTable("notes"),
}];

const Notes = defineModel<Note>({ table: "notes", db: ".bext/data/notes.db", timestamps: true });

export async function loader() {
  migrate({ db: DB }, migrations);            // applies pending only
  const notes = Notes.query().orderBy("id", "desc").limit(20).all();
  return { notes };                            // real typed objects
}

export async function action({ request }) {
  const form = await request.formData();
  if (form.get("op") === "add")    Notes.create({ body: form.get("body") });
  if (form.get("op") === "delete") Notes.delete(Number(form.get("id")));
  return new Response(null, { status: 303, headers: { Location: "/examples/db-crud" } });
}