Skip to content

Schema Migrations

Layeron Database uses declarative SQL migrations to safely build and evolve your database schema.

Migrations are part of the db(...) declaration. Layeron stores the migration metadata with the Database Product instance, applies pending migrations during dev and deploy flows, and records applied migration state for each environment.

When you pass schema, Layeron generates the first migration named layeron_001_schema:

Terminal window
import { db, table, text, integer, rawSql } from "@layeron/modules"
const database = db({
name: "main",
schema: {
posts: table({
id: text().primaryKey(),
title: text().notNull(),
createdAt: integer().notNull().default(rawSql("(unixepoch())")),
}),
},
})

Use schema-generated migrations when you want typed Table API metadata and a concise source of truth for the initial table layout.

For quick prototypes or small stores, you can pass one SQL string. Layeron creates an internal migration name such as layeron_001_auto:

Terminal window
const database = db({
name: "main",
sql: `
create table if not exists posts (
id text primary key,
title text not null
);
`,
})

For production applications, define named migration objects:

Terminal window
const database = db({
name: "main",
sql: [
{
name: "001_create_posts",
sql: `
create table if not exists posts (
id text primary key,
title text not null
);
`,
},
{
name: "002_add_status",
sql: "alter table posts add column status text not null default 'draft';",
},
],
})

Names should be stable and ordered. A common convention is 001_create_posts, 002_add_status, and 003_create_comments.

You can declare a typed schema and append custom SQL migrations. Layeron places the schema-generated migration first, then the SQL migrations:

Terminal window
const database = db({
name: "main",
schema: {
posts: table({
id: text().primaryKey(),
title: text().notNull(),
createdAt: integer().notNull(),
}),
},
sql: [
{
name: "002_add_posts_status",
sql: "alter table posts add column status text not null default 'draft';",
},
],
})

This is useful when most tables live in the typed schema and one change needs hand-written SQL.

Layeron applies migrations with these rules:

  • Immutable history: Applied migration SQL and metadata are stable.
  • Append-only changes: Add a new migration for each schema change.
  • Sequential execution: Pending migrations run in order.
  • Forward fixes: Correct mistakes with a later forward migration.
  • Per-environment state: Dev, preview, and production environments track their own applied migrations.

For multi-shard databases, Layeron applies migrations to every physical D1 database in the active D1 Plus shard set. The logical Database instance owns the migration history; individual D1 shards are implementation resources.

Layeron flags potentially destructive SQL during migration planning:

  • drop table
  • truncate
  • alter table ... drop column
  • alter table ... rename
  • delete from ... (without a where clause)
  • update ... (without a where clause)

[!WARNING] Destructive migrations require explicit metadata and deployment approval.

Terminal window
const database = db({
name: "main",
sql: [
{
name: "003_drop_deprecated_table",
sql: "drop table if exists temp_logs;",
destructive: true,
},
],
})
  • Create tables with if not exists.
  • Add nullable columns or columns with defaults before writing application code that depends on them.
  • Backfill data with explicit where ranges or separate operational scripts.
  • Keep migration names descriptive enough for deployment review.
  • Use raw SQL migrations for indexes, backfills, and SQLite expressions that are clearer in SQL.
  • Schema: Review typed table declarations that generate migration SQL.
  • Raw SQL: Write direct SQLite/D1-compatible statements with parameters and result helpers.
  • Batch and transactions: Group migration-adjacent data changes inside runtime operations.
  • Capacity and sharding: Plan migration behavior for fixed, manual, and auto capacity.
  • API reference: Review migration input, capacity options, sharding options, and runtime helpers.