@alexify/migronaut 2.1.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 +223 -0
- package/README.md +68 -10
- package/bullmq.d.ts +465 -7
- package/index.d.ts +1272 -18
- package/migronaut.schema.json +150 -2
- 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 +153 -15
- package/src/bullmq/producer.js +202 -27
- package/src/bullmq/service.js +480 -45
- package/src/cli/commands/background.js +500 -0
- package/src/cli/commands/converge.js +38 -10
- 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/cli/table.js +68 -9
- package/src/core/audit.js +98 -3
- 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 +125 -31
- package/src/core/config.js +133 -13
- package/src/core/converge-plan.js +343 -61
- package/src/core/converge-search-run.js +440 -0
- package/src/core/converge-search.js +404 -0
- package/src/core/converge.js +428 -183
- package/src/core/index-spec.js +27 -16
- package/src/core/lock.js +97 -32
- package/src/core/migrator.js +951 -26
- package/src/core/options.js +32 -1
- package/src/core/run.js +26 -12
- package/src/core/runner.js +1 -1
- package/src/core/search-index-spec.js +758 -0
- package/src/core/server-info.js +70 -0
- package/src/core/shard-info.js +76 -0
- package/src/core/versioning-spec.js +181 -0
- package/src/errors/index.js +97 -5
- package/src/index.js +16 -0
- package/src/utils/canonical.js +34 -1
- 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 +125 -1
- package/src/utils/template.js +69 -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,229 @@
|
|
|
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
|
+
|
|
113
|
+
## v2.2.0 — 2026-10-05
|
|
114
|
+
|
|
115
|
+
Atlas Search and Vector Search indexes in declared collections. Additive: a definition without
|
|
116
|
+
`searchIndexes` behaves exactly as in 2.1 — converge never even asks the server about Search for
|
|
117
|
+
it.
|
|
118
|
+
|
|
119
|
+
### Added
|
|
120
|
+
|
|
121
|
+
- **Declared search indexes** — a collection definition takes `searchIndexes: [{ name?, type?,
|
|
122
|
+
definition }]`: Atlas Search (`type: 'search'`, the default) and Atlas Vector Search
|
|
123
|
+
(`'vectorSearch'`) indexes, with the definition exactly as Atlas documents it — automated
|
|
124
|
+
embedding (`autoEmbed`) fields included. `converge` keeps them in step with the same lock,
|
|
125
|
+
history, events, re-plan and fixed-point check as regular indexes. Experimental.
|
|
126
|
+
- **Rows**: a new target, `searchIndex`. A missing index is created (one `createSearchIndexes`
|
|
127
|
+
per collection), a changed definition is updated **in place** (`modify` — the old definition
|
|
128
|
+
serves until the new one is built), an undeclared one is kept, or dropped last under
|
|
129
|
+
`prune`. A definition that declares only `searchIndexes` is valid, and its collection is
|
|
130
|
+
created when missing.
|
|
131
|
+
- **Never a rebuild**: a `$search` against a missing index returns nothing rather than fail, so
|
|
132
|
+
converge never drops a search index to build it again. A change no update can make — the
|
|
133
|
+
type, or an `autoEmbed` field's path, model, `numDimensions`, quantization or modality — is a
|
|
134
|
+
`conflict` naming the new-name recipe, and so is a declared index the server is still
|
|
135
|
+
deleting.
|
|
136
|
+
- **Comparison**: definitions compare whole, key order ignored, with the defaults the server
|
|
137
|
+
writes into what it reports filled in on both sides — top-level (`analyzer`,
|
|
138
|
+
`searchAnalyzer`, `dynamic`, `storedSource`, `numPartitions`), per field mapping (`string`,
|
|
139
|
+
`number`, `autocomplete`, `token`, `geo`, `document`, nested fields and `multi` included) and
|
|
140
|
+
per vector field (`quantization`, `indexingMethod`, `hnswOptions`; `autoEmbed`
|
|
141
|
+
`numDimensions` and `quantization`). Vector `fields`, and a field indexed as several types,
|
|
142
|
+
compare as sets. A server that reports no `type` (a self-managed `mongot`) has it inferred
|
|
143
|
+
from the definition, and its `latestVersion` is read as the definition version.
|
|
144
|
+
- **Server-only options**: an option the server reports that the declaration does not set,
|
|
145
|
+
and whose default migronaut does not know (a newer `mongot`'s), is left out of the
|
|
146
|
+
comparison — named on the row (`ignored`) and in one warning — instead of making every
|
|
147
|
+
converge update, and the server rebuild, the index. Only option objects are trimmed: a
|
|
148
|
+
field, a mapping type or a vector field only the server has is still a difference. The
|
|
149
|
+
cost: removing such an option from a declaration goes unnoticed; declare the value wanted.
|
|
150
|
+
- **What differs**: a `modify` row names it down to the option
|
|
151
|
+
(`mappings.fields.title.norms`) — the first five paths and how many more. A search index
|
|
152
|
+
that still differs after an update is `unstable`, with a warning that every update builds it
|
|
153
|
+
again.
|
|
154
|
+
- **Raw commands** (`createSearchIndexes`, `updateSearchIndex`, `dropSearchIndex`,
|
|
155
|
+
`$listSearchIndexes`), so every driver in the peer range works — the driver's helpers start
|
|
156
|
+
at 5.6. A vector index update is retried once with its type when a self-managed `mongot`
|
|
157
|
+
asks for it; where the server refuses that too (an Atlas CLI local deployment on 8.0 and
|
|
158
|
+
8.3), the step fails with the new-name recipe as its hint.
|
|
159
|
+
- **Build state**: rows carry `build` (`{ status, queryable, message?, updating? }`), and the
|
|
160
|
+
result `search` (`{ available, evidence, notReady, wait? }`) — how Search availability was
|
|
161
|
+
told, the declared indexes still building, updating, stale or failed, and how a wait ended.
|
|
162
|
+
A build under way does not count against `inSync`; a FAILED one fails `converge --check`
|
|
163
|
+
(exit 28). A STALE index (queryable, no longer replicating) is reported as such — table,
|
|
164
|
+
closing warning, `audit` — not as still building. A build message is kept to 500 characters.
|
|
165
|
+
- **`waitForSearchIndexes`** (config, `MIGRONAUT_WAIT_FOR_SEARCH_INDEXES`, `converge({
|
|
166
|
+
waitForSearchIndexes })`, `converge --wait-search` / `--no-wait-search`) — hold the converge
|
|
167
|
+
until every declared search index serves its declaration; a FAILED build of an index the run
|
|
168
|
+
created or changed, or `searchIndexWaitTimeoutMs` (`MIGRONAUT_SEARCH_INDEX_WAIT_TIMEOUT_MS`,
|
|
169
|
+
default 10 minutes), fails it with `ConvergeFailedError` `phase: 'wait'`. Off by default.
|
|
170
|
+
- **Without the lock**: the wait only reads, so the migration lock is released when it starts
|
|
171
|
+
(`lock:released` with `early: true`) — the next deploy or a queue's next job need not wait
|
|
172
|
+
out a build. The run itself (its id, span, history entry, `converge:end`) ends with the
|
|
173
|
+
wait.
|
|
174
|
+
- **Only what the run started holds it**: an index that failed or went STALE before the run,
|
|
175
|
+
its definition unchanged, is named and warned about instead — converge cannot fix it, and a
|
|
176
|
+
deploy is not held up by it. `--check` still fails on a FAILED build.
|
|
177
|
+
- **Resilient**: a list that fails with a network or failover blip is read again at the next
|
|
178
|
+
poll, up to three in a row (then `reason: 'unreadable'`); a stop cuts the pause between polls
|
|
179
|
+
short.
|
|
180
|
+
- **Observable**: `converge:wait` events (`started`, `progress` every 30 s, then `ready`,
|
|
181
|
+
`failed`, `timeout`, `unreadable` or `aborted`), `result.search.wait`, the history entry, a
|
|
182
|
+
queue job's log lines and `search-wait` progress phase, and — with `telemetry` — the
|
|
183
|
+
histogram `migronaut.converge.search.wait.duration` by `migronaut.converge.search.wait.outcome`.
|
|
184
|
+
- **`onSearchUnavailable`** (`MIGRONAUT_ON_SEARCH_UNAVAILABLE`) — declared search indexes on a
|
|
185
|
+
server without Atlas Search: `'fail'` (default) refuses the run before any write, with a hint;
|
|
186
|
+
`'skip'` converges everything else and reports `skip` rows. Detected once per run from the
|
|
187
|
+
server's own answer (checked against MongoDB 5.0, 6.0, 7.0, 8.0 and 8.2).
|
|
188
|
+
- **`migronaut audit`** — a `search` check, when the definitions declare search indexes: whether
|
|
189
|
+
the server has Atlas Search, and whether a declared index failed to build.
|
|
190
|
+
- **Types** — `SearchIndexDefinition`, `SearchDefinition`, `VectorSearchDefinition`,
|
|
191
|
+
`VectorSearchField`, `SearchIndexType`, `SearchIndexStatus`, `SearchIndexBuild`,
|
|
192
|
+
`SearchIndexNotReady`, `ConvergeSearchSummary`, `ConvergeWaitOutcome`, `ConvergeWaitEvent`;
|
|
193
|
+
`ConvergeTarget` gains `'searchIndex'`, `ConvergeActionKind` `'skip'`, `ConvergeAction`
|
|
194
|
+
`ignored`, `ConvergeHistoryEntry` `search`, `LockEvent` `early`, the event map
|
|
195
|
+
`'converge:wait'`; in `bullmq.d.ts` `ConvergeJobResult.search`, and `MigrationJobProgress`
|
|
196
|
+
gains the `'search-wait'` phase and `searchIndexes`. An exhaustive `switch` over one of these
|
|
197
|
+
unions needs a branch for the new member.
|
|
198
|
+
- **Queue** — a converge job's result carries `search`, and its log lines name search indexes.
|
|
199
|
+
Whether a job waits for builds is the worker kit's `waitForSearchIndexes`; the job payload is
|
|
200
|
+
unchanged.
|
|
201
|
+
|
|
202
|
+
### Changed
|
|
203
|
+
|
|
204
|
+
- A definition with none of `indexes`, `searchIndexes` and `validator` is refused with "declares
|
|
205
|
+
no indexes, searchIndexes or validator — nothing to manage" (was "declares neither indexes nor
|
|
206
|
+
a validator").
|
|
207
|
+
- The converge table's drop/rebuild count includes dropped search indexes, and `converge --yes`
|
|
208
|
+
is required for them in `--json` mode, like an index drop.
|
|
209
|
+
- `ConvergeFailedError`'s documentation names every phase: `plan`, `replan` (already thrown by
|
|
210
|
+
2.1, undocumented), `apply` and the new `wait`. A search index list that cannot be read is
|
|
211
|
+
reported in the phase that read it — `plan` only before the first write — and a run that stops
|
|
212
|
+
for any reason settles every row and carries the result so far in `context.converge`.
|
|
213
|
+
- `runWithLock` (internal) hands the work a `control` whose `release()` gives the lock up early;
|
|
214
|
+
`lock:released` may now come before `run:end`, with `early: true`.
|
|
215
|
+
|
|
216
|
+
### Tooling
|
|
217
|
+
|
|
218
|
+
- `tests/integration/search-atlas.test.js` — an opt-in, manual suite against
|
|
219
|
+
`mongodb/mongodb-atlas-local` (`MIGRONAUT_TEST_ATLAS_URI`): every scenario ends at a fixed
|
|
220
|
+
point. Passes against `mongodb/mongodb-atlas-local` 8.0 (8.0.32) and `latest` (8.3.11). CI does
|
|
221
|
+
not run it; the coverage gate comes from the unit tier's fake, which answers the search
|
|
222
|
+
commands with lag, normalization and build progress on demand. The suite stays strict about
|
|
223
|
+
server-only options (production tolerates them): one there is a default the tables in
|
|
224
|
+
`search-index-spec.js` lack. 9/9 on 8.0.32 and 8.3.11, the lock released during the wait
|
|
225
|
+
included.
|
|
226
|
+
- `src/core/converge.js` is split: `converge-search-run.js` (the search half of a run) and
|
|
227
|
+
`server-info.js` (read options, read pace, server version, not-found codes).
|
|
228
|
+
|
|
6
229
|
## v2.1.0 — 2026-10-04
|
|
7
230
|
|
|
8
231
|
Migrations as a queue, ids in your own format, OpenTelemetry, and declared collections. Additive:
|
package/README.md
CHANGED
|
@@ -59,8 +59,9 @@ change before it touches your database.
|
|
|
59
59
|
(warn by default, `onOutOfOrder: 'error'` to refuse) instead of silently applying.
|
|
60
60
|
- **Lifecycle hooks** — `beforeAll`, `afterAll`, `beforeEach`, `afterEach`, `onError`.
|
|
61
61
|
- **Opt-in transactions** — wrap a migration so it fully commits or fully aborts.
|
|
62
|
-
- **Indexes and validators as an end state** — declare them
|
|
63
|
-
|
|
62
|
+
- **Indexes, search indexes and validators as an end state** — declare them (Atlas Search and
|
|
63
|
+
Vector Search indexes included), and `migronaut converge` brings the database to match; no
|
|
64
|
+
migration file per index change ([details](#declared-collections)).
|
|
64
65
|
- **TypeScript, ESM & CommonJS** — all run with no `ts-node` plumbing.
|
|
65
66
|
- **Zero config files required** — drive everything from env vars if you prefer.
|
|
66
67
|
- **Pino-friendly logging** — the `logger` option is pino-compatible; pass a pino instance directly
|
|
@@ -74,6 +75,15 @@ change before it touches your database.
|
|
|
74
75
|
- **Migrations as a queue (optional)** — `@alexify/migronaut/bullmq` runs each migration as its own
|
|
75
76
|
BullMQ job, in order, so migronaut can be a migration service: trigger it over HTTP, on a
|
|
76
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)).
|
|
77
87
|
|
|
78
88
|
### How it compares to `migrate-mongo`
|
|
79
89
|
|
|
@@ -90,6 +100,7 @@ change before it touches your database.
|
|
|
90
100
|
| First-class TypeScript (built-in) | ❌ | ✅ |
|
|
91
101
|
| History preserved on rollback (never deleted) | ❌ | ✅ |
|
|
92
102
|
| Declared indexes & validators (`converge`) | ❌ | ✅ |
|
|
103
|
+
| Declared Atlas Search / Vector Search indexes | ❌ | ✅ |
|
|
93
104
|
| Adopt an existing `migrate-mongo` changelog | — | ✅ `migronaut import` |
|
|
94
105
|
|
|
95
106
|
<sub>Reflects `migrate-mongo`'s documented CLI as of mid-2026 (v14: optional
|
|
@@ -117,6 +128,7 @@ via a `client` argument; `migronaut` exposes the same plus a declarative per-fil
|
|
|
117
128
|
| Pino-compatible logger | ❌ | ✅ |
|
|
118
129
|
| BullMQ queue adapter (`/bullmq` entry point) | ❌ | ✅ |
|
|
119
130
|
| Declared indexes & validators (`converge`) | ❌ | ✅ |
|
|
131
|
+
| Declared Atlas Search / Vector Search indexes | ❌ | ✅ |
|
|
120
132
|
| Node floor | ≥ 18 | ≥ 22.18 |
|
|
121
133
|
|
|
122
134
|
<sub>Compared against `mongo-migrate-kit` 1.2.2 — the version this project forked from. The Node
|
|
@@ -196,7 +208,8 @@ Every command accepts the global flags `--uri`, `--db`, `--dir`, `--config`, `--
|
|
|
196
208
|
| `migronaut up [file]` | Run all pending migrations, one named file, or up to `--to <file>` |
|
|
197
209
|
| `migronaut down [file]` | Roll back the last batch, a chosen batch, the last N steps, one file, or to `--to <file>` |
|
|
198
210
|
| `migronaut redo [file]` | Roll back then re-apply (the last migration, or one file) |
|
|
199
|
-
| `migronaut converge` | Bring declared collections — indexes and validators — to their declared state |
|
|
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` |
|
|
200
213
|
| `migronaut status` | Print the full migration status table (`--check` to fail CI on pending) |
|
|
201
214
|
| `migronaut list` | List migrations, filtered by status |
|
|
202
215
|
| `migronaut dry-run <up\|down> [file]` | Preview a run without touching the database |
|
|
@@ -204,8 +217,8 @@ Every command accepts the global flags `--uri`, `--db`, `--dir`, `--config`, `--
|
|
|
204
217
|
| `migronaut lock` | Show who currently holds the migration lock |
|
|
205
218
|
| `migronaut unlock` | Force-release a stuck lock left behind by a crashed run |
|
|
206
219
|
|
|
207
|
-
Most data commands (`up`, `down`, `redo`, `converge`, `status`, `list`, `dry-run`,
|
|
208
|
-
`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
|
|
209
222
|
[CI & automation](#ci--automation).
|
|
210
223
|
|
|
211
224
|
<details>
|
|
@@ -272,15 +285,25 @@ migronaut redo <file> # a specific file
|
|
|
272
285
|
migronaut redo --no-lock # skip the lock (dev only)
|
|
273
286
|
migronaut redo --json # machine-readable output (array of run results)
|
|
274
287
|
|
|
275
|
-
# converge — declared indexes and validators → the database
|
|
288
|
+
# converge — declared indexes, search indexes and validators → the database
|
|
276
289
|
migronaut converge # plan, ask before any drop/rebuild, then apply
|
|
277
290
|
migronaut converge --dry-run # show the plan, change nothing
|
|
278
291
|
migronaut converge --check # exit 28 if anything would change (CI gate)
|
|
279
292
|
migronaut converge --prune # also drop indexes a definition does not declare
|
|
293
|
+
migronaut converge --wait-search # wait until every declared search index is queryable
|
|
280
294
|
migronaut converge --yes # no confirmation (required for drops/rebuilds with --json)
|
|
281
295
|
migronaut converge --no-lock # skip the concurrency lock (local dev only)
|
|
282
296
|
migronaut converge --json # machine-readable output (the converge result)
|
|
283
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
|
+
|
|
284
307
|
# status — full status table
|
|
285
308
|
migronaut status # the full status table
|
|
286
309
|
migronaut status --check # exit 2 if any migration is pending (CI gate)
|
|
@@ -630,12 +653,13 @@ All errors extend `MigronautError` and carry a typed `code` (`LOCK_ALREADY_HELD`
|
|
|
630
653
|
</details>
|
|
631
654
|
|
|
632
655
|
<details id="declared-collections">
|
|
633
|
-
<summary><b>Declared collections</b> — indexes and validators as an end state, with no migration file per change</summary>
|
|
656
|
+
<summary><b>Declared collections</b> — indexes, search indexes and validators as an end state, with no migration file per change</summary>
|
|
634
657
|
|
|
635
658
|
<br>
|
|
636
659
|
|
|
637
|
-
When what matters is *the final shape* of a collection's indexes
|
|
638
|
-
of how it got there — declare it and let `migronaut converge` make the
|
|
660
|
+
When what matters is *the final shape* of a collection's indexes, Atlas Search indexes and
|
|
661
|
+
validator — not the history of how it got there — declare it and let `migronaut converge` make the
|
|
662
|
+
difference:
|
|
639
663
|
|
|
640
664
|
```js
|
|
641
665
|
// migronaut.config.js
|
|
@@ -651,6 +675,19 @@ export default {
|
|
|
651
675
|
],
|
|
652
676
|
validator: { $jsonSchema: { bsonType: 'object', required: ['email'] } },
|
|
653
677
|
},
|
|
678
|
+
{
|
|
679
|
+
name: 'movies',
|
|
680
|
+
searchIndexes: [
|
|
681
|
+
{ definition: { mappings: { dynamic: true } } }, // Atlas Search, named "default"
|
|
682
|
+
{
|
|
683
|
+
name: 'plot_vectors',
|
|
684
|
+
type: 'vectorSearch',
|
|
685
|
+
definition: {
|
|
686
|
+
fields: [{ type: 'vector', path: 'embedding', numDimensions: 1536, similarity: 'cosine' }],
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
],
|
|
690
|
+
},
|
|
654
691
|
],
|
|
655
692
|
collectionsDir: './collections', // …and/or one file per collection
|
|
656
693
|
};
|
|
@@ -668,11 +705,15 @@ migronaut converge --check # exit 28 on drift: a CI gate
|
|
|
668
705
|
a changed index is rebuilt (and put back if the rebuild fails). An index you did not declare is
|
|
669
706
|
**kept and reported** — dropped only with `prune`. An identical index under another name is
|
|
670
707
|
accepted as is, never silently rebuilt.
|
|
708
|
+
- **Search indexes, too** — on Atlas, an Atlas CLI local deployment or MongoDB 8.3+ with `mongot`.
|
|
709
|
+
A changed definition is updated in place (on Atlas the old one serves until the new one is
|
|
710
|
+
built), never dropped and rebuilt; `--wait-search` waits until the builds are queryable. On a server without
|
|
711
|
+
Search, converge refuses — or skips them with `onSearchUnavailable: 'skip'`.
|
|
671
712
|
- **Locked like a migration**, and refused before the first write when the plan has a conflict.
|
|
672
713
|
- **After every deploy** with `convergeAfterUp: true` — a bulk `up` then ends by converging,
|
|
673
714
|
under the same lock — or as a [queue job](https://migronaut.vercel.app/guide/bullmq#converge-jobs).
|
|
674
715
|
|
|
675
|
-
Experimental
|
|
716
|
+
Experimental since 2.1 (search indexes: 2.2). → **[Declared Collections](https://migronaut.vercel.app/guide/collections)**
|
|
676
717
|
|
|
677
718
|
</details>
|
|
678
719
|
|
|
@@ -761,6 +802,15 @@ export default {
|
|
|
761
802
|
// collections: [{ name: 'users', indexes: [{ key: { email: 1 }, unique: true }] }],
|
|
762
803
|
// collectionsDir: './collections', // one definition file per collection
|
|
763
804
|
// convergeAfterUp: false, // true → every bulk `up` ends by converging
|
|
805
|
+
// onSearchUnavailable: 'fail', // 'skip' → converge without search indexes where Search is absent
|
|
806
|
+
// waitForSearchIndexes: false, // true → converge waits until search indexes are queryable
|
|
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
|
|
764
814
|
|
|
765
815
|
// ── Code-only options (omit in migronaut.config.json) ─────────────────────────
|
|
766
816
|
// hooks: { beforeAll, afterAll, beforeEach, afterEach, onError },
|
|
@@ -896,6 +946,14 @@ optional rather than merely discouraged:
|
|
|
896
946
|
| `MIGRONAUT_RELOAD_MIGRATIONS` | `reloadMigrations` | `false` |
|
|
897
947
|
| `MIGRONAUT_COLLECTIONS_DIR` | `collectionsDir` | — *(none read)* |
|
|
898
948
|
| `MIGRONAUT_CONVERGE_AFTER_UP` | `convergeAfterUp` | `false` |
|
|
949
|
+
| `MIGRONAUT_ON_SEARCH_UNAVAILABLE` | `onSearchUnavailable` | `fail` |
|
|
950
|
+
| `MIGRONAUT_WAIT_FOR_SEARCH_INDEXES` | `waitForSearchIndexes` | `false` |
|
|
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` |
|
|
899
957
|
| `MIGRONAUT_ENV_FILE` | `envFile` | `.env` |
|
|
900
958
|
|
|
901
959
|
`fileExtensions`, `clientOptions`, `collections`, `client`, `mongoose`, `hooks`, `logger`,
|