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-09-04 11:44:38
smoke-note2026-09-04 11:36:29
smoke-note2026-09-04 06:26:35
smoke-note2026-09-04 06:22:59
smoke-note2026-09-03 10:47:04
smoke-note2026-09-03 10:29:21
smoke-note2026-09-03 09:58:21
smoke-note2026-09-01 21:15:35
smoke-note2026-09-01 19:56:53
smoke-note2026-09-01 19:50:07
smoke-note2026-09-01 19:42:00
smoke-note2026-09-01 19:39:43
smoke-note2026-09-01 19:28:23
smoke-note2026-09-01 19:08:17
smoke-note2026-09-01 18:46:58
smoke-note2026-09-01 18:33:30
smoke-note2026-09-01 18:25:27
smoke-note2026-09-01 13:20:14
smoke-note2026-08-31 20:05:06
smoke-note2026-08-31 15:43:53
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" } });
}