CRUD SQLite (ORM + migrations)
defineModel donne un dépôt typé au-dessus du SQLite embarqué de bext ; migrate() applique un schéma suivi et idempotent (fini le CREATE TABLE IF NOT EXISTS paresseux). Le loader lit via le query builder, l'action écrit via le modèle, et une redirection 303 relance le loader afin que la liste reflète la mutation.
Astuce
Le modèle renvoie de vrais objets typés — il zippe pour vous les lignes positionnelles du bridge, donc plus de jonglage avec .columns/.rows. migrate() est idempotent : appelable à chaque requête, il n'exécute que les migrations absentes du registre _bext_migrations.
Ajouter une note
Notes (20)
| smoke-note | 2026-09-04 11:44:38 | |
| smoke-note | 2026-09-04 11:36:29 | |
| smoke-note | 2026-09-04 06:26:35 | |
| smoke-note | 2026-09-04 06:22:59 | |
| smoke-note | 2026-09-03 10:47:04 | |
| smoke-note | 2026-09-03 10:29:21 | |
| smoke-note | 2026-09-03 09:58:21 | |
| smoke-note | 2026-09-01 21:15:35 | |
| smoke-note | 2026-09-01 19:56:53 | |
| smoke-note | 2026-09-01 19:50:07 | |
| smoke-note | 2026-09-01 19:42:00 | |
| smoke-note | 2026-09-01 19:39:43 | |
| smoke-note | 2026-09-01 19:28:23 | |
| smoke-note | 2026-09-01 19:08:17 | |
| smoke-note | 2026-09-01 18:46:58 | |
| smoke-note | 2026-09-01 18:33:30 | |
| smoke-note | 2026-09-01 18:25:27 | |
| smoke-note | 2026-09-01 13:20:14 | |
| smoke-note | 2026-08-31 20:05:06 | |
| smoke-note | 2026-08-31 15:43:53 |
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" } });
}