@alexify/migronaut 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/README.md +33 -2
- package/bullmq.d.ts +449 -6
- package/index.d.ts +1010 -9
- package/migronaut.schema.json +93 -1
- package/package.json +8 -2
- package/src/bullmq/background-processor.js +469 -0
- package/src/bullmq/index.js +12 -0
- package/src/bullmq/jobs.js +254 -7
- package/src/bullmq/processor.js +128 -14
- package/src/bullmq/producer.js +185 -13
- package/src/bullmq/service.js +480 -45
- package/src/cli/commands/background.js +500 -0
- package/src/cli/commands/create.js +6 -0
- package/src/cli/exit-codes.js +6 -0
- package/src/cli/index.js +2 -0
- package/src/core/audit.js +11 -1
- package/src/core/background-audit.js +139 -0
- package/src/core/background-drift.js +126 -0
- package/src/core/background-dry-run.js +366 -0
- package/src/core/background-engine.js +818 -0
- package/src/core/background-kit.js +425 -0
- package/src/core/background-partition.js +298 -0
- package/src/core/background-runner.js +305 -0
- package/src/core/background-sandbox.js +701 -0
- package/src/core/background-shard.js +542 -0
- package/src/core/background-spec.js +597 -0
- package/src/core/background-store.js +951 -0
- package/src/core/background-throttle.js +269 -0
- package/src/core/background-watch-plan.js +164 -0
- package/src/core/background-watch-store.js +78 -0
- package/src/core/background-watch.js +605 -0
- package/src/core/background.js +1121 -0
- package/src/core/bson-peer.js +23 -0
- package/src/core/changelog.js +32 -0
- package/src/core/collections.js +78 -8
- package/src/core/config.js +102 -12
- package/src/core/converge-plan.js +86 -7
- package/src/core/converge.js +88 -0
- package/src/core/lock.js +48 -21
- package/src/core/migrator.js +904 -12
- package/src/core/options.js +16 -0
- package/src/core/run.js +26 -12
- package/src/core/runner.js +1 -1
- package/src/core/server-info.js +9 -2
- package/src/core/shard-info.js +76 -0
- package/src/core/versioning-spec.js +181 -0
- package/src/errors/index.js +88 -0
- package/src/index.js +16 -0
- package/src/utils/error.js +11 -2
- package/src/utils/loader.js +77 -9
- package/src/utils/migration-name.js +33 -1
- package/src/utils/telemetry.js +107 -0
- package/src/utils/template.js +62 -1
- package/src/versioning/config.js +155 -0
- package/src/versioning/document.js +326 -0
- package/src/versioning/index.js +50 -0
- package/src/versioning/internal.js +279 -0
- package/src/versioning/mongoose.js +151 -0
- package/src/versioning/occ.js +318 -0
- package/src/versioning/registry.js +187 -0
- package/src/versioning/upcaster.js +213 -0
- package/versioning.d.ts +666 -0
- package/versioning.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,113 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
Release headings carry the publish date (`## vX.Y.Z — YYYY-MM-DD`).
|
|
5
5
|
|
|
6
|
+
## v2.3.0 — 2026-10-06
|
|
7
|
+
|
|
8
|
+
Document versioning and background migrations. Additive: nothing changes for a project that
|
|
9
|
+
declares no `versioning` and writes no `background` file. Everything new is experimental — its
|
|
10
|
+
shape may still change in a minor release (named here).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Document versioning** — a collection definition takes `versioning: { current, min?, field?,
|
|
15
|
+
revision?, revisionField?, index? }`: the shape version (`__v`) and the optimistic-concurrency
|
|
16
|
+
revision (`__rev`) as one contract. Converge folds it into the validator (the version an `int`
|
|
17
|
+
with a `minimum` of `min` and no `maximum`; the revision `int|long`; `moderate` when the
|
|
18
|
+
validator exists for versioning alone) and an index `{ __v: 1, _id: 1 }` — on a sharded
|
|
19
|
+
collection `{ __v: 1, …shard key, _id: 1 }`, the ordinary one kept with a row that says why.
|
|
20
|
+
Raising `min` over documents still below it is refused before any write (a `conflict` row, no
|
|
21
|
+
id named).
|
|
22
|
+
- **`@alexify/migronaut/versioning`** — a third entry point, with no engine behind it, for the
|
|
23
|
+
repository layer: `defineShapes` (`stamp`, `onInsert`, `stampUpsert`, `upcaster`, `plugin`),
|
|
24
|
+
`updateWithRevision` / `replaceWithRevision` / `findOneAndUpdateWithRevision`
|
|
25
|
+
(`RevisionConflictError` with `reason: 'conflict' | 'not-found' | 'unknown'`),
|
|
26
|
+
`retryOnConflict`, `bumpRevision`, the in-memory `upcaster` (`ShapeVersionError`),
|
|
27
|
+
`isVersion`, and `versioningPlugin` for Mongoose (its version key moved to `__rev`, optimistic
|
|
28
|
+
concurrency on). Per-version shape types without codegen: `AnyShape`, `CurrentShape`,
|
|
29
|
+
`ShapeAt`, `Stamped`, `Body`, `BackgroundMigrationFor`, a typed `defineShapes<Shapes>()(…)`
|
|
30
|
+
(`versioning.d.ts` needs TypeScript ≥ 5.0).
|
|
31
|
+
- **Background migrations** — a migration file with `export const background = { collection,
|
|
32
|
+
from, to, migrate | migrateBatch, revert?, … }` (or a free-form `step(ctx)`), which `up`
|
|
33
|
+
registers without running; the rewrite runs beside the migration line, never holding the
|
|
34
|
+
migration lock:
|
|
35
|
+
- **partitions and lanes**: the collection split into `_id` ranges from a sampled quantile per
|
|
36
|
+
BSON type — or, on a sharded collection, into runs of chunks per shard, with targeted reads
|
|
37
|
+
and writes, a shard-key guard (`shard-key-changed`) and `shardConcurrency`; leases are slots,
|
|
38
|
+
capped by a unique index at `maxParallel` across every process, every checkpoint fenced;
|
|
39
|
+
- **a coordinator** that plans passes, finalizes them and completes when nothing matches any
|
|
40
|
+
more (`maxPasses` against an old release that keeps writing); writes are `stampedDiff`s under
|
|
41
|
+
the optimistic filter, so an application write in between is never lost and untouched fields
|
|
42
|
+
keep their BSON types;
|
|
43
|
+
- **transactional batches** (`transaction: true`) with side writes through `ctx.session`;
|
|
44
|
+
- **pacing**: `pauseMs`, a `throttle` hook, replication lag and an adaptive (AIMD) controller;
|
|
45
|
+
- **`requires`** between background migrations (a DAG, unblocked as they complete) and from
|
|
46
|
+
ordinary migrations, which wait (`BackgroundPendingError`, or `onBackgroundPending: 'stop'`);
|
|
47
|
+
`backgroundInline` runs them inside `up`;
|
|
48
|
+
- **controls**: pause, resume, cancel, retry (`fromStart`), repin, unlock; **dry runs** on a
|
|
49
|
+
sample (`--validate` through the real write path in an always-aborted transaction) and of
|
|
50
|
+
`step` migrations in a sandbox (refusals exit 34);
|
|
51
|
+
- **drift after completion**: the drift watch (`verifyBackground`, every 10 minutes in each
|
|
52
|
+
runtime, `backgroundOnDrift: 'reopen' | 'report'`) and the **live drift watcher**
|
|
53
|
+
(`watchBackground`, change streams, one leader per collection, `backgroundDrift: 'poll' |
|
|
54
|
+
'stream' | 'both'`);
|
|
55
|
+
- **three runtimes**: `migronaut background run`, `startBackgroundRunner()` in the application,
|
|
56
|
+
and the queue (below); `status()`, `background status --partitions`, an audit check, events
|
|
57
|
+
`background:*`, and OpenTelemetry spans `migronaut.background.slice` /
|
|
58
|
+
`migronaut.background.coordinate` with their metrics.
|
|
59
|
+
- **`migronaut background <action>`** — status, run, pause, resume, cancel, retry, repin,
|
|
60
|
+
dry-run, unlock, verify and watch; `create --background`.
|
|
61
|
+
- **Background migrations on the queue** — `createMigrationQueue({ background: true | {…} })`: a
|
|
62
|
+
queue of their own, a coordinator job per background migration with its lanes as children,
|
|
63
|
+
heals from MongoDB (worker start, every sync tick, every drift-watch tick) and a stall
|
|
64
|
+
takeover; `startBackgroundWorker()`, `enqueueBackground()`, the `background-verify` schedule,
|
|
65
|
+
`createBackgroundProcessor()`. An `up` plan stops before a migration that waits for a
|
|
66
|
+
background one.
|
|
67
|
+
- **Config**: `backgroundCollection`, `backgroundInline`, `backgroundOnDrift`, `backgroundDrift`,
|
|
68
|
+
`backgroundShardAware`, each with its `MIGRONAUT_*` variable.
|
|
69
|
+
- **Errors and exit codes**: `REVISION_CONFLICT` (29), `SHAPE_VERSION_UNSUPPORTED` (30),
|
|
70
|
+
`BACKGROUND_PENDING` (31), `BACKGROUND_FAILED` (32), `BACKGROUND_CONFLICT` (33),
|
|
71
|
+
`SANDBOX_REFUSED` (34).
|
|
72
|
+
- **`shapes.occ(name)`** — the revision guards and `bumpRevision` bound to a collection's field
|
|
73
|
+
names; `RevisionConflictContext` types an error's `context`.
|
|
74
|
+
- **`startBackgroundRunner().stop({ timeoutMs })`**, `RunnableBackground` (what
|
|
75
|
+
`runnableBackground()` now returns: live leases, last progress, coordinator), `previous` on a
|
|
76
|
+
background status (the registration a re-registration replaced), `plan.atLeast`.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
For code written against 2.2 — the queue adapter's types and results, and two type-level details:
|
|
81
|
+
|
|
82
|
+
- **`SyncJobResult.held` means a failure again, only.** A tick whose next migration waits for a
|
|
83
|
+
background migration reports `waiting: { migration, waitsFor }` instead; while it still waits
|
|
84
|
+
for the same thing, later ticks say so without planning. A group that stops at such a migration
|
|
85
|
+
has `upToDate: false` (`MigrationGroup.waiting` says why).
|
|
86
|
+
- **`JOB_NAMES` has three more values** (`background`, `background-lane`, `background-verify`) and
|
|
87
|
+
`MigrationJobView.returnvalue` three more result types: an exhaustive `Record` over either needs
|
|
88
|
+
them.
|
|
89
|
+
- **`MigronautErrorCode` has six more members** (above): an exhaustive `switch` with a `never`
|
|
90
|
+
default needs a case for each — as with the codes 2.1 added.
|
|
91
|
+
- **`CollectionDefinition.indexes` and `searchIndexes` are `readonly` arrays**, so a definition
|
|
92
|
+
declared `as const` type-checks; code that pushes into a typed definition's array must copy it.
|
|
93
|
+
- **Background metric names** (experimental): `migronaut.background.throttled`,
|
|
94
|
+
`migronaut.background.drift.detected`, `migronaut.background.transaction.retried`.
|
|
95
|
+
|
|
96
|
+
### Notes
|
|
97
|
+
|
|
98
|
+
- A typed `current` past the highest declared shape is a compile error. Written inline it reads
|
|
99
|
+
"Type 'number' is not assignable to type 'never'"; from an imported `as const` definition, it
|
|
100
|
+
names the two versions ("Type '3' is not assignable to type '2'").
|
|
101
|
+
- The shard-aware mode needs `clusterMonitor` (it reads `config.collections` and
|
|
102
|
+
`config.chunks`); without it, a sharded collection is partitioned by `_id`.
|
|
103
|
+
- Fail-closed limits: `maxDocumentErrors` is at most 1000 (the ids a state keeps); a revision
|
|
104
|
+
guard refuses an `_id` given as an operator; a background `filter` may not run server-side
|
|
105
|
+
JavaScript; `create --background`'s placeholder collection (`'TODO'`) is refused by `up`;
|
|
106
|
+
`watchBackground`, `enqueueBackground` and `createBackgroundProcessor` refuse options they do
|
|
107
|
+
not know.
|
|
108
|
+
- A lane stopped by its process (a shutdown, a deploy) is not a failed slice, and
|
|
109
|
+
`maxSliceFailures` counts failed slices in a row. `down` of a one-way background migration pauses
|
|
110
|
+
it and waits for its lanes before it decides; a `step` migration counts as having rewritten
|
|
111
|
+
documents once one step was checkpointed.
|
|
112
|
+
|
|
6
113
|
## v2.2.0 — 2026-10-05
|
|
7
114
|
|
|
8
115
|
Atlas Search and Vector Search indexes in declared collections. Additive: a definition without
|
package/README.md
CHANGED
|
@@ -75,6 +75,15 @@ change before it touches your database.
|
|
|
75
75
|
- **Migrations as a queue (optional)** — `@alexify/migronaut/bullmq` runs each migration as its own
|
|
76
76
|
BullMQ job, in order, so migronaut can be a migration service: trigger it over HTTP, on a
|
|
77
77
|
schedule, or from a deploy hook that waits for the result.
|
|
78
|
+
- **Document versioning (experimental)** — declare a collection's shape version (`__v`) and
|
|
79
|
+
optimistic-concurrency revision (`__rev`) once; converge enforces them, and
|
|
80
|
+
`@alexify/migronaut/versioning` gives your repository layer `updateWithRevision`, typed
|
|
81
|
+
per-version shapes, an upcaster and a Mongoose plugin ([guide](https://migronaut.vercel.app/guide/versioning)).
|
|
82
|
+
- **Background migrations (experimental)** — long data rewrites (`export const background = {…}`)
|
|
83
|
+
that `up` only registers and that run beside the migration line: partitions and parallel lanes
|
|
84
|
+
across pods, checkpoints, pause/resume, transactional batches, shard-aware on sharded clusters,
|
|
85
|
+
dry runs, and a drift watcher that upgrades old-shape writes after completion — from the CLI,
|
|
86
|
+
inside your app, or on the queue ([guide](https://migronaut.vercel.app/guide/background-migrations)).
|
|
78
87
|
|
|
79
88
|
### How it compares to `migrate-mongo`
|
|
80
89
|
|
|
@@ -200,6 +209,7 @@ Every command accepts the global flags `--uri`, `--db`, `--dir`, `--config`, `--
|
|
|
200
209
|
| `migronaut down [file]` | Roll back the last batch, a chosen batch, the last N steps, one file, or to `--to <file>` |
|
|
201
210
|
| `migronaut redo [file]` | Roll back then re-apply (the last migration, or one file) |
|
|
202
211
|
| `migronaut converge` | Bring declared collections — indexes, search indexes and validators — to their declared state |
|
|
212
|
+
| `migronaut background <action> [name]` | Background migrations: `status`, `run`, `pause`, `resume`, `cancel`, `retry`, `repin`, `dry-run`, `unlock`, `verify`, `watch` |
|
|
203
213
|
| `migronaut status` | Print the full migration status table (`--check` to fail CI on pending) |
|
|
204
214
|
| `migronaut list` | List migrations, filtered by status |
|
|
205
215
|
| `migronaut dry-run <up\|down> [file]` | Preview a run without touching the database |
|
|
@@ -207,8 +217,8 @@ Every command accepts the global flags `--uri`, `--db`, `--dir`, `--config`, `--
|
|
|
207
217
|
| `migronaut lock` | Show who currently holds the migration lock |
|
|
208
218
|
| `migronaut unlock` | Force-release a stuck lock left behind by a crashed run |
|
|
209
219
|
|
|
210
|
-
Most data commands (`up`, `down`, `redo`, `converge`, `status`, `list`, `dry-run`,
|
|
211
|
-
`baseline`, `create`, `audit`, `lock`, `unlock`) accept **`--json`** for machine-readable output — see
|
|
220
|
+
Most data commands (`up`, `down`, `redo`, `converge`, `background`, `status`, `list`, `dry-run`,
|
|
221
|
+
`import`, `baseline`, `create`, `audit`, `lock`, `unlock`) accept **`--json`** for machine-readable output — see
|
|
212
222
|
[CI & automation](#ci--automation).
|
|
213
223
|
|
|
214
224
|
<details>
|
|
@@ -285,6 +295,15 @@ migronaut converge --yes # no confirmation (required for drops/rebuild
|
|
|
285
295
|
migronaut converge --no-lock # skip the concurrency lock (local dev only)
|
|
286
296
|
migronaut converge --json # machine-readable output (the converge result)
|
|
287
297
|
|
|
298
|
+
# background — background migrations (registered by up, rewritten in partitions)
|
|
299
|
+
migronaut background status # every background migration
|
|
300
|
+
migronaut background status --check # exit 32 if one failed, 31 if one is not completed
|
|
301
|
+
migronaut background run <name> # drive it from here (--concurrency N, --once, --all)
|
|
302
|
+
migronaut background pause <name> --wait # stop its lanes at the next batch
|
|
303
|
+
migronaut background dry-run <name> --validate # on a sample, in an always-aborted transaction
|
|
304
|
+
migronaut background verify # look for old-shape documents after completion
|
|
305
|
+
migronaut background watch # upgrade old-shape writes as they land (Ctrl-C stops)
|
|
306
|
+
|
|
288
307
|
# status — full status table
|
|
289
308
|
migronaut status # the full status table
|
|
290
309
|
migronaut status --check # exit 2 if any migration is pending (CI gate)
|
|
@@ -786,6 +805,13 @@ export default {
|
|
|
786
805
|
// onSearchUnavailable: 'fail', // 'skip' → converge without search indexes where Search is absent
|
|
787
806
|
// waitForSearchIndexes: false, // true → converge waits until search indexes are queryable
|
|
788
807
|
|
|
808
|
+
// ── Background migrations (experimental) — see `migronaut background` ───
|
|
809
|
+
// backgroundCollection: '_migronaut_background', // state (+ _partitions, _watch)
|
|
810
|
+
// backgroundInline: false, // true → run it to the end inside the registering `up`
|
|
811
|
+
// backgroundOnDrift: 'reopen', // 'report' → only report old-shape documents after completion
|
|
812
|
+
// backgroundDrift: 'poll', // 'stream' | 'both' → watch drift with change streams
|
|
813
|
+
// backgroundShardAware: 'auto', // 'off' → no shard-key partitions on sharded collections
|
|
814
|
+
|
|
789
815
|
// ── Code-only options (omit in migronaut.config.json) ─────────────────────────
|
|
790
816
|
// hooks: { beforeAll, afterAll, beforeEach, afterEach, onError },
|
|
791
817
|
// mongoose: myMongooseInstance, // pass if your migrations use Mongoose models
|
|
@@ -923,6 +949,11 @@ optional rather than merely discouraged:
|
|
|
923
949
|
| `MIGRONAUT_ON_SEARCH_UNAVAILABLE` | `onSearchUnavailable` | `fail` |
|
|
924
950
|
| `MIGRONAUT_WAIT_FOR_SEARCH_INDEXES` | `waitForSearchIndexes` | `false` |
|
|
925
951
|
| `MIGRONAUT_SEARCH_INDEX_WAIT_TIMEOUT_MS` | `searchIndexWaitTimeoutMs` | `600000` |
|
|
952
|
+
| `MIGRONAUT_BACKGROUND_COLLECTION` | `backgroundCollection` | `_migronaut_background` |
|
|
953
|
+
| `MIGRONAUT_BACKGROUND_INLINE` | `backgroundInline` | `false` |
|
|
954
|
+
| `MIGRONAUT_BACKGROUND_ON_DRIFT` | `backgroundOnDrift` | `reopen` |
|
|
955
|
+
| `MIGRONAUT_BACKGROUND_DRIFT` | `backgroundDrift` | `poll` |
|
|
956
|
+
| `MIGRONAUT_BACKGROUND_SHARD_AWARE` | `backgroundShardAware` | `auto` |
|
|
926
957
|
| `MIGRONAUT_ENV_FILE` | `envFile` | `.env` |
|
|
927
958
|
|
|
928
959
|
`fileExtensions`, `clientOptions`, `collections`, `client`, `mongoose`, `hooks`, `logger`,
|