SQLite or Postgres
Rows stay in memory until you name a database, and the boot warns you so. One entry in sources:
puts them in SQLite or Postgres, and fougere migrate writes the tables.
Name the database
Install the adapter and the driver of your engine:
npm install @fougere/adapter-sql better-sqlite3 # SQLite — pg, mysql2, or tedious tarn for the others
pnpm add @fougere/adapter-sql better-sqlite3 # SQLite — pg, mysql2, or tedious tarn for the others
bun add @fougere/adapter-sql better-sqlite3 # SQLite — pg, mysql2, or tedious tarn for the others
yarn add @fougere/adapter-sql better-sqlite3 # SQLite — pg, mysql2, or tedious tarn for the others
Then name the database:
// fougere.config.ts
import '@fougere/adapter-sql';
export default defineFougere({
sources: {
main: { type: 'sqlite', path: '.data/app.db' },
},
});
The entry with no rows: list holds every entity. Your handlers do not change: where rows live is a
fact about the app, not about the code that uses them.
type: names the engine, and the keys beside it belong to that engine:
type | Says | Imported from |
|---|---|---|
sqlite | path | @fougere/adapter-sql |
pg, mysql, mssql | url | @fougere/adapter-sql |
d1 | binding — the Worker's D1 binding | @fougere/adapter-sql |
file | path, format — one JSON or YAML file per row | @fougere/adapter-file |
memory | nothing — a Map | @fougere/adapter-memory |
env | nothing — the environment, read only | @fougere/adapter-env |
Every key of every type, read off the code: Source types.
The import is what makes the word exist. Without it, the editor refuses the entry and the boot
answers Unknown source type … To add one, import the adapter that provides it. A misspelled key —
pathh — is refused by both, by name, and so is { type: 'pg' } without a url.
Drivers
@fougere/adapter-sql loads on any runtime — Node, Bun, Deno, a Worker — and an engine loads its
driver only when it opens: better-sqlite3 for SQLite, pg, mysql2, or tedious and tarn,
installed by your app. A missing driver is refused with the command that adds it. Kysely comes with
the adapter.
- Under Bun,
type: 'sqlite'opens withbun:sqlite, sincebetter-sqlite3does not load there. - On a Worker,
type: 'd1'reads its binding fromcloudflare:workersat the first query.
Create the tables
The boot never writes the schema. It compares the database with the entities and refuses to start when the database is behind, naming what is missing:
Fougere boot refused: the database is missing 1 change(s) from the entities:
products — no table
On your machine: fougere migrate --latest --apply
On a shared database: fougere freeze, then fougere migrate --apply
On your machine, fougere migrate --latest --apply creates the tables. Keep it as a script beside
the others — "migrate": "fougere migrate --latest --apply" in package.json — and run it again each
time an entity gains a field; the boot tells you when.
Change a field
A rename is declared on the entity, and the rows follow:
export default class Product extends entity(
{ id: primary(), name: text(), cents: number({ min: 0 }) },
{ previous: { name: 'title' } },
) {}
npm run migrate renames the column on your database. Before the change leaves your machine, freeze it:
fougere freeze v2
A deployment runs fougere migrate --apply, which applies only what was frozen — a change nobody
froze is refused there, by name. A field removed without previous: is dropped: fougere freeze
warns that its column, and its data, go at the next migrate. The whole story:
Evolve a schema.
More than one database
Each further entry lists the entities it holds in rows:; the one without rows: keeps the rest:
sources: {
main: { type: 'sqlite', path: '.data/app.db' },
archive: { type: 'file', path: './rows', rows: ['Snapshot'] }, // import '@fougere/adapter-file'
legacy: { type: 'pg', url: process.env.DATABASE_URL, rows: ['Book'] },
},
- Two entries without
rows:refuse the boot: nothing would say which holds the rest. - When every entry lists its rows, an entity left out of all of them refuses the boot, by name.
- An entry may list
values:— entities kept as one value each: see Values. A source nothing is written to, such asenv, never holds the rest. - A view gets no table unless it says
.anchor(), on the entity — never in this file.
Querying across databases, and two fronds sharing a table: Several databases.