Migrations | Kunii | Primitives Docs

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 EXISTS
  • CREATE 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 NULL columns 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:

  1. It snapshots existing DO tables.
  2. It runs initial CREATE TABLE IF NOT EXISTS SQL.
  3. It inspects existing columns with PRAGMA table_info.
  4. 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.