@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.
Files changed (71) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/README.md +68 -10
  3. package/bullmq.d.ts +465 -7
  4. package/index.d.ts +1272 -18
  5. package/migronaut.schema.json +150 -2
  6. package/package.json +8 -2
  7. package/src/bullmq/background-processor.js +469 -0
  8. package/src/bullmq/index.js +12 -0
  9. package/src/bullmq/jobs.js +254 -7
  10. package/src/bullmq/processor.js +153 -15
  11. package/src/bullmq/producer.js +202 -27
  12. package/src/bullmq/service.js +480 -45
  13. package/src/cli/commands/background.js +500 -0
  14. package/src/cli/commands/converge.js +38 -10
  15. package/src/cli/commands/create.js +6 -0
  16. package/src/cli/exit-codes.js +6 -0
  17. package/src/cli/index.js +2 -0
  18. package/src/cli/table.js +68 -9
  19. package/src/core/audit.js +98 -3
  20. package/src/core/background-audit.js +139 -0
  21. package/src/core/background-drift.js +126 -0
  22. package/src/core/background-dry-run.js +366 -0
  23. package/src/core/background-engine.js +818 -0
  24. package/src/core/background-kit.js +425 -0
  25. package/src/core/background-partition.js +298 -0
  26. package/src/core/background-runner.js +305 -0
  27. package/src/core/background-sandbox.js +701 -0
  28. package/src/core/background-shard.js +542 -0
  29. package/src/core/background-spec.js +597 -0
  30. package/src/core/background-store.js +951 -0
  31. package/src/core/background-throttle.js +269 -0
  32. package/src/core/background-watch-plan.js +164 -0
  33. package/src/core/background-watch-store.js +78 -0
  34. package/src/core/background-watch.js +605 -0
  35. package/src/core/background.js +1121 -0
  36. package/src/core/bson-peer.js +23 -0
  37. package/src/core/changelog.js +32 -0
  38. package/src/core/collections.js +125 -31
  39. package/src/core/config.js +133 -13
  40. package/src/core/converge-plan.js +343 -61
  41. package/src/core/converge-search-run.js +440 -0
  42. package/src/core/converge-search.js +404 -0
  43. package/src/core/converge.js +428 -183
  44. package/src/core/index-spec.js +27 -16
  45. package/src/core/lock.js +97 -32
  46. package/src/core/migrator.js +951 -26
  47. package/src/core/options.js +32 -1
  48. package/src/core/run.js +26 -12
  49. package/src/core/runner.js +1 -1
  50. package/src/core/search-index-spec.js +758 -0
  51. package/src/core/server-info.js +70 -0
  52. package/src/core/shard-info.js +76 -0
  53. package/src/core/versioning-spec.js +181 -0
  54. package/src/errors/index.js +97 -5
  55. package/src/index.js +16 -0
  56. package/src/utils/canonical.js +34 -1
  57. package/src/utils/error.js +11 -2
  58. package/src/utils/loader.js +77 -9
  59. package/src/utils/migration-name.js +33 -1
  60. package/src/utils/telemetry.js +125 -1
  61. package/src/utils/template.js +69 -1
  62. package/src/versioning/config.js +155 -0
  63. package/src/versioning/document.js +326 -0
  64. package/src/versioning/index.js +50 -0
  65. package/src/versioning/internal.js +279 -0
  66. package/src/versioning/mongoose.js +151 -0
  67. package/src/versioning/occ.js +318 -0
  68. package/src/versioning/registry.js +187 -0
  69. package/src/versioning/upcaster.js +213 -0
  70. package/versioning.d.ts +666 -0
  71. 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, and `migronaut converge` brings the
63
- database to match; no migration file per index change ([details](#declared-collections)).
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`, `import`,
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 and validator — not the history
638
- of how it got there — declare it and let `migronaut converge` make the difference:
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 in 2.1. → **[Declared Collections](https://migronaut.vercel.app/guide/collections)**
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`,