kitcn
Version:
kitcn - React Query integration and CLI tools for Convex
165 lines (125 loc) • 5.13 kB
Markdown
# Migrations Reference
Built-in online data migrations for kitcn. Prerequisites: `setup/server.md`.
## When to Migrate
Convex is not SQL — skip migrations for backward-compatible changes.
**Skip migrations:**
- Adding optional fields
- Adding new tables or indexes
- Code-level defaults on read (`doc.field ?? 'default'`)
- Keeping deprecated fields while new code rolls out
**Use migrations:**
- Optional → required (field must exist on every row)
- Type or enum narrowing
- Field rename or removal that would violate schema
- Semantic rewrites or one-time backfills
**Rule:** Old docs still pass schema + app logic → no migration. Old docs would fail → migrate.
## Core API
### `defineMigration`
```ts
import { defineMigration } from 'kitcn/orm';
export const migration = defineMigration({
id: '20260227_080239_backfill_todo_priority', // timestamped unique id
description: 'backfill todo priority', // optional
up: {
table: 'todos',
migrateOne: async (_ctx, doc) => {
if (doc.priority === undefined || doc.priority === null) {
return { priority: 'medium' }; // partial patch
}
// return nothing to skip
},
},
down: { // optional — omit if not safely reversible
table: 'todos',
migrateOne: async (_ctx, doc) => {
if (doc.priority === 'medium') {
return { priority: undefined };
}
},
},
});
```
### MigrationStep Fields
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `table` | `string` | — | Table to iterate |
| `migrateOne` | `(ctx, doc) => patch \| void` | — | Per-document transform |
| `batchSize` | `number` | `128` (runtime) / `256` (CLI) | Docs per batch |
| `writeMode` | `'safe_bypass' \| 'normal'` | `'safe_bypass'` | Bypass or run ORM rules/triggers |
### `migrateOne` Context
| Field | Type | Description |
|-------|------|-------------|
| `db` | `DatabaseWriter` | Raw Convex database writer |
| `orm` | `OrmWriter` | ORM writer for complex ops |
| `migrationId` | `string` | Current migration id |
| `runId` | `string` | Current run id |
| `direction` | `'up' \| 'down'` | Current direction |
| `dryRun` | `boolean` | Whether dry run |
| `writeMode` | `'safe_bypass' \| 'normal'` | Current write mode |
### `defineMigrationSet`
Auto-generated manifest. Collects migrations, computes checksums, sorts by id:
```ts
import { defineMigrationSet } from 'kitcn/orm';
export const migrations = defineMigrationSet([migration1, migration2]);
```
## CLI Commands
| Command | Description |
|---------|-------------|
| `migrate create <name>` | Scaffold timestamped migration + update manifest |
| `migrate up [--prod]` | Apply all pending migrations in order |
| `migrate down --steps N [--prod]` | Roll back N migrations |
| `migrate down --to <id> [--prod]` | Roll back to specific migration |
| `migrate status [--prod]` | Show applied/pending/drift state |
| `migrate cancel [--prod]` | Cancel active run |
## Deploy Integration
`kitcn deploy` auto-runs: `convex deploy` → `migrate up` → `aggregate backfill`.
Convex deploy flags pass through first, including `--message` for deployment
history and `--preview-name` for reusable preview deployments.
Config in `kitcn.json`:
```json
{
"deploy": {
"migrations": {
"enabled": "auto",
"wait": true,
"batchSize": 256,
"pollIntervalMs": 1000,
"timeoutMs": 900000,
"strict": true,
"allowDrift": false
}
}
}
}
}
```
`kitcn dev` uses relaxed defaults (`strict: false`, `allowDrift: true`).
## Drift Safety
Applied migrations are immutable. Two drift checks:
| Drift | Cause | Effect |
|-------|-------|--------|
| Checksum mismatch | Applied migration file edited | Blocks next run |
| Missing from manifest | Applied migration deleted | Blocks next run |
`allowDrift` is emergency-only. Create new migrations for follow-up behavior.
## Internal Tables
- `migration_state` — applied checksums and progress per migration
- `migration_run` — run lifecycle (start, status, failures)
Reserved names — do not create tables with these names.
## Runtime Statuses
`pending` → `running` → `completed` | `failed` | `canceled` | `dry_run` | `noop` | `drift_blocked`
## Best Practices
1. **One migration per schema change** — don't bundle unrelated backfills.
2. **Codegen before migrate up** — deterministic order prevents stale function code.
3. **`safe_bypass` by default** — bypasses ORM rules/triggers for speed. Set `writeMode: 'normal'` when you need hooks.
4. **Don't edit applied migrations** — triggers checksum drift. Create new migration instead.
5. **Prefer code defaults** — `doc.field ?? 'default'` over migration when backward-compatible.
## Common Workflow: Optional → Required
1. `migrate create backfill_field`
2. Implement `migrateOne` to fill missing values
3. `codegen` then `migrate up`
4. Harden schema (`.notNull()`)
5. `codegen` to confirm
## Related References
- ORM: `./orm.md`
- Triggers: docs at `/docs/orm/schema/triggers`
- CLI Backend: docs at `/docs/cli/backend`