forgedb migrate

Record, build, and run schema migrations that rewrite data-at-rest, and carry a data dir across an engine generation.

Schema evolution follows the generate-then-compile model: edit schema.forge, regenerate, rebuild. Additive changes (a new model, a new nullable field) survive automatically on reopen. Everything else rewrites data-at-rest, and forgedb migrate drives that through a generated offline transformer. The migrations feature guide has the full workflow.

Every subcommand requires --schema, and none of them discovers anything. The schema names the app: migrations/ is read beside it, and the build cache is keyed by it. Nothing is inferred from your working directory.

Subcommands#

CommandPurpose
create <description>Diff the schema against the recorded snapshot, record + classify the change, and ask about anything it cannot prove.
statusReport an app's lineage.
build --from F --to TGenerate + compile the offline transformer for a version range.
run --from F --to T --src S --dest DRun a built transformer over a source → destination data directory.
engine --src S --dest DCarry a data dir across a ForgeDB byte-format generation.

create#

forgedb migrate create <description> --schema <PATH> [--no-auto]
FlagType / defaultMeaning
<description>string (required)Human description of the migration.
-s, --schema <PATH>path (required)The app this migration belongs to. migrations/ is read beside it.
--no-autobool (false)Opt out of the interactive prompt, not of detection. The diff is identical either way; what this decides is whether a change ForgeDB cannot prove a value for stops the run with a hard error naming it, or asks you a question. A session with no terminal behaves as if this were passed.

Detection is what create does, so there is no flag for it — --auto was removed in 0.5.0 and errors, naming --no-auto. There is also no way to create a migration ForgeDB did not detect: a record's changes array is derived from a schema diff, so it cannot disagree with migrations/schemas/vN.forge.

A change ForgeDB can prove needs no input: a nullable add, a drop, a value-preserving widening, or an add whose field carries a resolvable @default. Anything else is asked about at create time — a constant, a copy of another field, or "I'll write the transform" — and your answer is recorded as data in the migration record, which migrate build then lowers into the generated transformer.

status#

forgedb migrate status --schema <PATH>

build#

forgedb migrate build --schema <PATH> --from <F> --to <T>
FlagType / defaultMeaning
-s, --schema <PATH>path (required)The app to build the transformer for.
--from <F>u32 (required)Origin (current on-disk) format version.
--to <T>u32 (required)Destination format version.

The transformer is emitted into the app's build-cache container as transform-<from>-<to>/ and compiled as a member of the project's cargo workspace. The command prints the binary's path — the one cargo reported, existence-checked.

--schema selects the app, and nothing else

The transformer is generated from the committed per-version schemas under the app's migrations/, so a drifted current schema cannot change a historical hop.

run#

forgedb migrate run --schema <PATH> --from <F> --to <T> --src <DATA> --dest <MIGRATED>
FlagType / defaultMeaning
-s, --schema <PATH>path (required)The app whose transformer to run.
--from <F> / --to <T>u32 (required)The range. Together with --schema these name the exact cache member build produced.
--src <DATA>path (required)Source data directory at the origin format version.
--dest <MIGRATED>path (required)Destination directory to materialize (must be absent/empty).

run compiles nothing. If the binary is missing it hard-errors naming the cache path and tells you to run migrate build — there is no fallback that quietly rebuilds somewhere else.

engine#

forgedb migrate engine --schema <PATH> --src <DATA> --dest <MIGRATED>
FlagType / defaultMeaning
-s, --schema <PATH>path (required)The app. The same schema is baked on both sides of the hop.
--src <DATA>path (required)Source data directory at the old engine generation.
--dest <MIGRATED>path (required)Destination directory to materialize (must be absent/empty).

Orthogonal to the schema-version transformer, and it needs its own command for that reason: an engine bump changes no .forge, so it produces no lineage hop and build/run would replay nothing. Versions are read from the data directory's own manifests; migrating backwards is refused and tells you to upgrade the CLI instead.

Removed: `migrate up`, `-o/--output`, `--bin-dir`

migrate up was a wrapper over build + run; run the two commands. -o/--output (on build and engine) and --bin-dir (on run) chose where the transformer was built — ForgeDB owns that now, and it is a member of the project's cache workspace rather than a [package] dropped under whatever cargo root you were standing in. All three still parse, and all three error naming their replacement. The per-tenant sweep and --from auto-detection that left with up are tracked for restoration as #373.

Examples#

Record an additive change and regenerate:

forgedb migrate create "add note field" --schema schema.forge
forgedb generate

Build a transformer and run it over one data directory:

forgedb migrate build --schema schema.forge --from 1 --to 2
forgedb migrate run   --schema schema.forge --from 1 --to 2 \
  --src ./data --dest ./data-migrated

Carry a data dir across an engine generation:

forgedb migrate engine --schema schema.forge --src ./data --dest ./data-v2

Search documentation

Find pages across the ForgeDB docs