feat(planner): add bridge target version strategy for auto-stamping ledger to app versions
This commit is contained in:
@@ -31,6 +31,7 @@ Report bugs and security issues at [community.foss.global](https://community.fos
|
||||
| **Resumable** | Mark a step `.resumable()` and it gets `ctx.checkpoint.read/write/clear` for restartable bulk operations; successful runs clear the step checkpoint automatically |
|
||||
| **Lockable** | Mongo-backed lock uses atomic updates plus TTL heartbeats to serialize concurrent SaaS instances — safe for rolling deploys |
|
||||
| **Fresh-install fast path** | Configure `freshInstallVersion` to skip migrations on a brand-new database |
|
||||
| **Target bridging** | Optionally bridge schema/data version to the app version without hand-written no-op steps |
|
||||
| **Dry-run** | `dryRun: true` or `.plan()` returns the execution plan without writing anything |
|
||||
| **Structured errors** | All failures throw `SmartMigrationError` with a stable `code` field for branching |
|
||||
|
||||
@@ -131,6 +132,29 @@ When `run()` reads the ledger and finds a current version, it computes a plan: t
|
||||
|
||||
If no step's `toVersion` is greater than `currentVersion` (the ledger is past the end of the chain), the runner throws `TARGET_NOT_REACHABLE`.
|
||||
|
||||
#### Target version strategies
|
||||
|
||||
By default, `smartmigration` is strict: the registered chain must contain a real step whose `toVersion` exactly matches `targetVersion`. This is best when you maintain an explicit schema/data version.
|
||||
|
||||
If your app uses its package version as `targetVersion`, releases without schema changes can leave the migration chain behind the app version. In that case, enable bridge mode:
|
||||
|
||||
```ts
|
||||
const migration = new SmartMigration({
|
||||
targetVersion: commitinfo.version,
|
||||
db,
|
||||
freshInstallVersion: commitinfo.version,
|
||||
targetVersionStrategy: 'bridge',
|
||||
});
|
||||
```
|
||||
|
||||
Bridge mode appends an internal no-op step from the last registered migration version to `targetVersion` when the chain ends before the target. It stamps the ledger to the app version without requiring hand-written no-op migrations for every release.
|
||||
|
||||
Important behavior:
|
||||
- Existing real migrations still run first.
|
||||
- If the ledger is already past the last registered migration but below `targetVersion`, only the internal bridge runs.
|
||||
- Future real migrations still work because skip-forward resume runs the first real step whose `toVersion` is above the ledger version.
|
||||
- Downgrades and steps that overshoot the target still fail.
|
||||
|
||||
### The ledger
|
||||
|
||||
The ledger is the source of truth for "what data version are we at, what steps have been applied, who holds the lock right now." It is persisted in one of two backends:
|
||||
@@ -300,6 +324,7 @@ const result = await m.run(); // returns plan, doesn't write
|
||||
| `ledgerName` | `string` | `"smartmigration"` | Logical name; lets multiple migrations coexist on the same db/bucket |
|
||||
| `ledgerBackend` | `'mongo'` \| `'s3'` | mongo if db, else s3 | Where to persist the ledger |
|
||||
| `freshInstallVersion` | `string` | undefined | When the resource is empty, jump straight to this version |
|
||||
| `targetVersionStrategy` | `'strict'` \| `'bridge'` | `'strict'` | Require the chain to end exactly at targetVersion, or auto-bridge to it |
|
||||
| `lockWaitMs` | `number` | `60_000` | How long to wait for a stale lock from another instance |
|
||||
| `lockTtlMs` | `number` | `600_000` | How long this instance's own lock auto-expires after |
|
||||
| `dryRun` | `boolean` | `false` | If true, `run()` returns the plan without executing or locking |
|
||||
@@ -314,6 +339,7 @@ The constructor throws `SmartMigrationError` with one of these `code`s on bad in
|
||||
- `INVALID_LEDGER_NAME` — `ledgerName` is blank or not a string
|
||||
- `INVALID_LOCK_WAIT_MS` — `lockWaitMs` is not an integer `>= 0`
|
||||
- `INVALID_LOCK_TTL_MS` — `lockTtlMs` is not an integer `>= 1`
|
||||
- `INVALID_TARGET_VERSION_STRATEGY` — `targetVersionStrategy` is not `strict` or `bridge`
|
||||
|
||||
### `migration.step(id: string).from(v).to(v).[description(t)].[resumable()].up(handler)`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user