Skip to content

Commit a210ed1

Browse files
xuyushun441-sysos-zhuangclaude
authored
docs(cli): document os migrate + dev autoMigrate self-heal (#2186 follow-up) (#2205)
Follow-up to #2196 — the schema-drift / `os migrate` feature shipped with no docs. - cli.mdx: new "Schema migrations" section — `os migrate plan/apply`, the safe/needs-confirm/destructive categories, `--allow-destructive`, and the dev-only `autoMigrate:'safe'` self-heal (force-disabled in production). - objectstack-platform SKILL: add `os migrate plan/apply` to the CLI cheat sheet. - objectstack-data SKILL: "Schema evolution on an existing database" — the sync is additive-only; relaxing `required` / type changes / drops on a live DB need `os migrate` (dev auto-heals loosening). Names the tell-tale: `/meta` says optional but a write 400s "<field> is required" = stale NOT NULL, not a validator bug. Body-only skill edits (frontmatter unchanged → generated skill docs don't drift). Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 49da36e commit a210ed1

3 files changed

Lines changed: 66 additions & 0 deletions

File tree

content/docs/getting-started/cli.mdx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,49 @@ os info --json # JSON output for tooling
419419
Loaded in 90ms
420420
```
421421

422+
### Schema migrations
423+
424+
The metadata→database sync is **additive-only**: on boot it creates missing
425+
tables and adds new columns, but never alters or drops existing ones. So a
426+
*non-additive* change to an object already backed by a database — relaxing
427+
`required` (drop `NOT NULL`), changing a field's type/length, or removing a
428+
field — silently diverges from the live schema, and the physical column wins at
429+
write time. `os migrate` reconciles the database to the metadata (the source of
430+
truth).
431+
432+
| Command | Description |
433+
|---------|-------------|
434+
| `os migrate plan` | Dry-run: show how the database has drifted from metadata, categorised safe / needs-confirm / destructive (no changes applied) |
435+
| `os migrate apply` | Reconcile the database to metadata. Applies loosening changes; destructive ones require `--allow-destructive` |
436+
437+
```bash
438+
os migrate plan # Preview drift (no changes)
439+
os migrate apply # Apply safe (loosening) changes, with a confirm prompt
440+
os migrate apply --yes # Skip the prompt (CI / scripts)
441+
os migrate apply --allow-destructive --yes # Also drop orphaned columns, tighten NOT NULL, narrow types
442+
os migrate plan --json # Machine-readable output
443+
```
444+
445+
| Category | Examples | Applied by |
446+
|----------|----------|------------|
447+
| `safe` | relax `NOT NULL` → nullable, widen a `varchar` | `os migrate apply` (and dev auto-reconcile) |
448+
| `needs_confirm` | non-narrowing type change | `os migrate apply` |
449+
| `destructive` | drop an orphaned column, tighten `NOT NULL`, narrow a type | `os migrate apply --allow-destructive` |
450+
451+
<Callout type="tip">
452+
**Dev self-heal.** `os dev` runs the SQL driver with `autoMigrate: 'safe'`, so
453+
loosening changes (e.g. you just made a field optional) are applied to your
454+
existing dev database automatically on restart — no `os migrate` needed, no data
455+
loss. Auto-reconcile is **dev-only and never destructive**; it is force-disabled
456+
under `NODE_ENV=production`, where you run `os migrate` deliberately.
457+
</Callout>
458+
459+
<Callout type="warn">
460+
`os migrate` only sees objects in your **compiled artifact** — run `os build`
461+
first. It never drops a table that is absent from your metadata, and on SQLite
462+
it reconciles via a table rebuild (copy → swap) that preserves your data.
463+
</Callout>
464+
422465
### Scaffolding
423466

424467
| Command | Alias | Description |

skills/objectstack-data/SKILL.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,27 @@ export default ObjectSchema.create({
260260

261261
---
262262

263+
## Schema evolution on an existing database
264+
265+
The metadata→DB sync is **additive-only**: new tables/columns are created on
266+
boot, but existing columns are **never** altered or dropped. A non-additive
267+
change to an object that already has data silently diverges from the physical
268+
schema, and the **database column wins at write time** (#2186):
269+
270+
| Change | Existing DB on restart |
271+
|--------|------------------------|
272+
| add object / field / index | ✅ applied automatically (additive) |
273+
| `required: true → false` (relax `NOT NULL`) | dev auto-heals (`autoMigrate:'safe'`); otherwise `os migrate apply` |
274+
| type / length change, drop field, rename | `os migrate apply` (`--allow-destructive` for drops / tightenings) |
275+
276+
Tell-tale: `/meta` reports a field optional but a write still 400s
277+
`"<field> is required"` — that is a stale `NOT NULL` column (physical drift),
278+
**not** a validator bug. `os dev` reconciles loosening automatically; otherwise
279+
`os migrate plan` to preview and `os migrate apply` to reconcile. CLI details:
280+
see **objectstack-platform**.
281+
282+
---
283+
263284
## Common Patterns
264285

265286
### Naming Rules Summary

skills/objectstack-platform/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,8 @@ cd my-app && pnpm install
563563
os dev --ui # dev server + Studio (auto-hops port if taken)
564564
os validate # metadata cross-reference checks
565565
os compile # produce dist/ artifact
566+
os migrate plan # preview metadata↔DB schema drift (additive sync never alters existing columns)
567+
os migrate apply # reconcile DB to metadata (loosening only; --allow-destructive for drops/tightenings)
566568
PORT=8080 os start # production — pin the port explicitly (see Ports & networking)
567569
```
568570

0 commit comments

Comments
 (0)