Generators
Generators write a table’s migration and its Model from a short description, then regenerate src/db/schema.ts. The Command line reference lists every option.
Adding a table
Name the Model, and list its columns, with g model:
npx d1-record g model Product 'name:string!' 'priceCents:integer!=0' user:referencesWrote migrations/0003_create_products.sql
Wrote src/models/product.ts
Wrote src/db/schema.ts (3 tables) from migrations/
Next: npx wrangler d1 migrations apply my-app-db --localThen apply the migration to your local database:
npx wrangler d1 migrations apply my-app-db --localThe table is products, with an id generated in code, created_at, and updated_at.
--id integergives it a key D1 generates instead.--no-timestampsleaves out the timestamps.
Compared with Rails
Keys are UUIDs, not auto-incrementing integers. With --id integer, a record and the records built through it no longer save atomically. See Autosaving.
Use --remote instead of --local when you deploy.
Writing columns
Each column is name:type, with optional marks:
| Column | Means |
|---|---|
title | A string, the default type |
email:string!:unique | Required (NOT NULL), with a unique index |
count:integer!=0 | Required, with a default of 0 |
settings:json | JSON, checked by D1 (CHECK (json_valid(settings))) |
user:references | user_id, pointing at users, indexed; the Model gets user = belongsTo(User) |
The column syntax lists every type.
TIP
Quote columns that contain !, so bash and zsh leave it alone.
Beyond Rails
Rails’ generators have no syntax for a column’s default. d1-record adds =value (=0, =true, =draft), because SQLite can’t add a required (NOT NULL) column to an existing table without one.
Changing a table
Write a migration with g migration. It reads what to do from the migration’s name:
npx d1-record g migration AddSlugToPosts slug:string:unique # ADD COLUMN, with a unique index
npx d1-record g migration RemoveSlugFromPosts slug # DROP COLUMN
npx d1-record g migration BackfillSlugs # an empty migration to write yourselfThe name can be written AddSlugToPosts or add_slug_to_posts. Apply each migration afterwards, as above.
SQLite can’t add a required column without a default, so the generator refuses one. Give it a default, slug:string!=draft, or leave out the !.
A default and a unique index don’t mix on a table with rows
g migration AddSlugToPosts 'slug:string!=draft:unique' is accepted, but applying it to a table with more than one row fails: every row gets draft, which the unique index refuses. Add the column without the index, fill it in, then add the index in a later migration.
Referencing the same table
g model Category parent:references points parent_id at a parents table and writes parent = belongsTo(Parent), since it can’t know the parent is another category. Edit both files before applying the migration:
In the migration, point the column at
categories, and make it optional, since top-level categories have no parent:sqlparent_id TEXT REFERENCES categories (id),In the Model, use
Categoryitself, and add the other direction if you want it.
import { belongsTo, hasMany } from "@forestfuture/d1-record";
export class Category extends ApplicationRecord("categories") {
parent = belongsTo(Category); // foreign key: parentId, from the association's name
children = hasMany(Category, { foreignKey: "parentId" }); // not the default, categoryId
}Compared with Rails
Rails writes belongs_to :parent, class_name: "Category", optional: true. Here the association takes the class itself, so there’s no class_name, and whether a parent is required comes from the column: parentId can be null because the migration allows it.
Keeping Models in another folder
By default, Models and application-record.ts go in src/models/. To keep them elsewhere, pass --models to every command:
npx d1-record schema --models src/app/models
npx d1-record g model Product 'name:string!' --models src/app/modelsTIP
d1-record schema writes application-record.ts there, and the generators write Models that import it, so both need --models. A package.json script saves retyping it: "db:schema": "d1-record schema --models src/app/models".
Keeping the schema current
The generators regenerate src/db/schema.ts themselves. After writing or editing a migration by hand, regenerate it:
npx d1-record schema--watchregenerates it whenever a migration changes.--checkfails when the committed schema is out of date. Run it in CI.