Migrations
Generate SQL, diff schemas, and run tracked runtime migrations
What the package provides
kunii/migrations exposes runtime helpers for schema migration workflows. The implementation tracks migration history in _kuratchi_migrations.
Initial SQL generation
Use the generator helpers when you need SQL from a normalized schema:
import { buildInitialSql } from '@kuratchi/kunii/migrations';
The generated SQL includes:
CREATE TABLE IF NOT EXISTSCREATE INDEX IF NOT EXISTS
Diffing schemas
The migration generator can diff two schemas and emit additive SQL plus warnings for unsafe changes.
That is especially important for operations like:
- adding
NOT NULLcolumns without defaults - dropping columns
- changing checks
- rebuilding constraint-heavy definitions
Runtime migrations
Use runMigrations() to apply schema changes at runtime:
import { runMigrations } from '@kuratchi/kunii/migrations';
await runMigrations({
execute: (sql, params) => env.DB.prepare(sql).bind(...(params || [])).all(),
schema: appSchema,
});
Behavior:
- first run builds the full schema
- later runs diff the current database and target schema
- identical schema hashes short-circuit
- migration history is recorded in
_kuratchi_migrations
Pending checks
Use hasPendingMigrations() when you only need to know whether the target schema differs from the last applied migration.
Cloudflare framework adapters
Most Cloudflare-hosted apps should use a framework adapter instead of calling
runMigrations() directly. The adapters wrap the framework's request lifecycle,
run migrateOnce(env) for D1 bindings, and then expose ORM clients.
import { initKuniiORM } from '@kuratchi/kunii';
import { appSchema } from './schemas/app';
const orm = initKuniiORM({ DB: appSchema });
export default {
async fetch(request, env, ctx) {
await orm.migrateOnce(env);
const db = orm.db(env, 'DB');
return Response.json((await db.todos.many()).data);
},
};
See Framework Adapters for SvelteKit, Next, Nuxt, Astro, Workers, and Koze examples.
Durable Object additive migrations
autoMigrate(ctx.storage, schema) handles Durable Object schema setup differently from tracked D1 runtime migrations:
- It snapshots existing DO tables.
- It runs initial
CREATE TABLE IF NOT EXISTSSQL. - It inspects existing columns with
PRAGMA table_info. - It adds missing columns with
ALTER TABLE ... ADD COLUMN.
That makes DO schema evolution additive by default. There is no migration history table on the DO side — every restart re-applies the idempotent IF NOT EXISTS statements, which is cheap and self-correcting.
Legacy auto-migration middleware
For D1 specifically, autoMigrate({ DB: schema }) returns a MiddlewareStep that calls runMigrations on the first request per worker isolate. Drop it into src/middleware.ts:
import { defineMiddleware } from '@kuratchi/koze';
import { autoMigrate } from '@kuratchi/kunii';
import { appSchema } from './server/schemas/app';
export default defineMiddleware({
migrate: autoMigrate({ DB: appSchema }),
});
This is the most common pattern. For total control (running migrations from a queue handler, a scheduled trigger, or a CLI script) call runMigrations({...}) directly.