ENbext.dev
DocumentationOutils pour applications

Traduction automatique depuis l’anglais. Consulter l’original.

Sur cette page

Données et migrations

Deux modules complémentaires donnent aux applications PRISM une véritable couche de données sur le SQLite en cours de bext :

  • @bext-stack/framework/migrate — un constructeur de schéma et un **suivi, exécuteur de migration idempotent **. C'est la pièce qui manquait à bext : au lieu de CREATE TABLE IF NOT EXISTS paresseux à chaque requête (ou un appel hors bande) db push ), vous définissez des migrations ordonnées et un grand livre _bext_migrations enregistre ce qui a été exécuté.
  • @bext-stack/framework/orm — un modèle/générateur de requêtes typé Eloquent-lite. Sûr par construction (chaque valeur passe par le modèle balisé sql) et il renvoie des objets typés réels - il compresse les lignes du tableau de position que les mains du pont renvoient, de sorte que vous ne touchez plus jamais .columns / .rows.

Quand l'utiliser#

  • Toute application possédant des tables (pas seulement appelant les magasins SDK).
  • Vous souhaitez que les modifications du schéma soient révisables, ordonnées et appliquées une seule fois.
  • Vous souhaitez des lectures/écritures tapées sans écriture manuelle de SQL ni compression manuelle des lignes.

Pour une requête ponctuelle, le modèle balisé sql est toujours présent en dessous. Recherchez le modèle lorsque la table est un élément de première classe que votre application lit et écrit.

Migrations#

ts
import { migrate, type Migration } from "@bext-stack/framework/migrate";

const migrations: Migration[] = [
  {
    id: "0001_create_notes",                 // unique, sortable — runs in id order
    up: (db) => db.schema.createTable("notes", (t) => {
      t.id();                                 // INTEGER PRIMARY KEY AUTOINCREMENT
      t.text("body").notNull();
      t.integer("author_id").references("users");
      t.timestamps();                         // created_at + updated_at (epoch ms)
    }),
    down: (db) => db.schema.dropTable("notes"),
  },
];

migrate({ db: ".bext/data/app.db" }, migrations);   // applies pending only — idempotent

migrate() peut être appelé en toute sécurité en haut d'un chargeur/action ou au démarrage : il crée le grand livre, puis exécute uniquement les migrations dont id n'est pas encore enregistré.

Fonction Fait
migrate(source, migrations) appliquer les migrations en attente dans l'ordre id ; renvoie { applied, skipped }
rollback(source, migrations, steps?) inverser le dernier steps (par défaut 1), le plus récent en premier, via chaque down
migrationStatus(source, migrations) { id, applied, appliedAt? }[] pour chaque migration

source est { db } (un chemin SQLite → le pont natif) ou { executor } (voir Essai ).

Générateur de schéma#

createTable(name, build) et dropTable(name) renvoient des chaînes SQL (utilisées par db.schema.* dans le cadre d'une migration ou de manière autonome). Chaîne de constructeurs de colonnes :

ts
t.id();                                  // auto-increment PK
t.text("email").notNull().unique();
t.integer("age");
t.integer("org_id").references("orgs");  // FK → orgs(id)
t.real("price").default(0);
t.boolean("active").default(true);       // stored INTEGER 0/1
t.json("meta");                          // TEXT
t.text("created").defaultRaw("CURRENT_TIMESTAMP");
t.timestamps();                          // created_at + updated_at

Modèles et requêtes#

ts
import { defineModel } from "@bext-stack/framework/orm";

interface Note { id: number; body: string; author_id: number; created_at: number; updated_at: number }

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

Notes.create({ body: "hello", author_id: 1 });   // INSERT → returns the row (id + timestamps filled)
Notes.find(3);                                    // by primary key, or null
Notes.all();                                      // every row
Notes.count();

Notes.where("author_id", "=", 1)                  // chainable query builder
  .where("body", "like", "%draft%")
  .orderBy("created_at", "desc")
  .limit(20)
  .all();

Notes.update(3, { body: "edited" });              // by PK; bumps updated_at when timestamps:true
Notes.delete(3);
Notes.query().whereIn("id", [1, 2, 3]).delete();  // bulk
Membre Retours
create(data) la ligne insérée (relue par PK)
find(id) / first() T | null
all() / where(col, op, val) / query() lignes / un QueryBuilder<T>
count() number
update(id, data) / delete(id) nombre de personnes affectées
raw(sql\…`)` trappe de secours, rangées tapées T

Générateur de requêtes : .where(col, op, val), .whereIn(col, vals), .orderBy(col, dir), .limit(n), .offset(n), puis .all() / .first() / .count() / .update(data) / .delete(). Les opérateurs sont une liste verte fixe ( =, !=, <, <=, >, >=, like, not like ); .toSql() affiche la requête compilée. Chaque valeur est un paramètre lié — Notes.where("body", "=", userInput) ne peut jamais injecter.

Essai#

L'exécution est connectable via un Executor, de sorte que le code de modèle/migration identique s'exécute dans l'isolat (pont natif par défaut) ou sur n'importe quel SQLite dans un test unitaire - par exemple. bun:sqlite :

ts
import { Database } from "bun:sqlite";
const db = new Database(":memory:");
const executor = {
  rows: (text, params) => db.query(text).all(...params),
  run:  (text, params) => { const r = db.run(text, ...params); return { changes: r.changes, lastInsertRowid: Number(r.lastInsertRowid) }; },
};
migrate({ executor }, migrations);
const Notes = defineModel<Note>({ table: "notes", executor });

Essayez-le#

Le Démo SQLite CRUD en direct exécute une véritable migration et un modèle typé : ajoutez et supprimez des notes et regardez-les persister. Source dans sites/demo/src/app/examples/db-crud/page.tsx.

Voir aussi#

Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.