@alexify/migronaut 2.3.0 → 2.4.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 CHANGED
@@ -3,6 +3,89 @@
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.4.0 — 2026-10-07
7
+
8
+ Migration logs for the application's users. Additive: a migration that never touches the new
9
+ context fields, and a kit with no `migration:log` listener, behave as before. Everything new is
10
+ experimental — its shape may still change in a minor release (named here).
11
+
12
+ ### Added
13
+
14
+ - **`ctx.logger` in every migration** — the kit's logger with the run's correlation bound into the
15
+ fields of every line: `runId`, `migration`, `direction`, `batch`, `attempt`, and `jobId` /
16
+ `groupId` when a queue job runs it. Pino's `(fields, msg)` order is accepted too.
17
+ - **`ctx.run`** — that correlation as a frozen object: `{ id, direction, migration?, batch?,
18
+ attempt?, jobId?, groupId?, requestedBy?, reason? }`. `attempt` is 2 or more when the driver
19
+ retried the transaction and the body runs again (each attempt gets a context of its own).
20
+ - **The `migration:log` event** — a `ctx.logger` call whose fields hold `userland: true` is also
21
+ emitted, for the application to store and show its users; calls without the marker emit
22
+ nothing. The payload is `{ kind, runId, …correlation, level, msg, data, at, seq, truncated? }`:
23
+ `data` a bounded, redacted copy of the fields (8 levels, 1000 entries, 4096-character strings;
24
+ an `Error` as `{ name, message, code?, codeName? }` with the values a server error quotes masked,
25
+ as in `msg`; BSON values and binary data up to 4096 bytes kept as they are),
26
+ `seq` increasing within a run, `at` a `Date` (TTL-ready). It fires whatever the logger's level,
27
+ and with `logger: null`. Migronaut stores none of it — the
28
+ [Migration Logs](https://migronaut.vercel.app/guide/migration-logs) guide has the recipe.
29
+ - **Hooks get it too** — `beforeAll`/`afterAll` the run's logger and `ctx.run` (no migration, no
30
+ attempt), `beforeEach`/`afterEach`/`onError` the migration's.
31
+ - **`job: { id, groupId? }` on `up`, `down` and `redo`** — the queue job a run works for, bound into
32
+ `ctx.run`, its lines (the kit's own included) and its events; and `job: { id }` on
33
+ `runBackgroundSlice`.
34
+ - **Queue adapter** — the processor passes each job's id and group to its run, and writes the
35
+ migration's `userland: true` lines into the job's log as one-line `✎ …` rows (matched by job and
36
+ run id, so a timed-out body never writes into the next job's log — nor into a later run of the
37
+ same job). A background lane does the same for its slice, into the lane job's log. The new
38
+ `userlandLogRows` option (`createMigrationQueue`, both processors; default 1000) caps the rows one
39
+ job's log takes — the rest are counted in one closing row.
40
+ - **Background migrations** — `ctx.logger` is bound to `ctx.background`, which gains the lane's
41
+ `runId`, its `jobId` (and, for a lane an `up` drives with `backgroundInline`, the run's job and
42
+ `groupId` — its lines land in that migration job's log) and the transaction `attempt`; a
43
+ `userland: true` call emits `migration:log` with `kind: 'background'`. A dry run marks its lines
44
+ `dryRun: true` and emits nothing.
45
+ - **Retried transactions, visible** — when the driver retried a migration's transaction and its
46
+ body ran again, `migration:success` / `migration:error` and the kit's `✔ Applied` / `✖ Error`
47
+ lines carry `attempts`, a failure's context too, and the `migronaut.migration` span always has
48
+ `migronaut.migration.attempts`. A run that names a `job` puts `migronaut.job.id` /
49
+ `migronaut.job.group_id` on its `migronaut.run` span.
50
+ - **A `ctx.logger` call that is dropped** (a field whose getter throws) **or cut to the event's
51
+ bounds** leaves one debug line per run — never one per call.
52
+ - **Types** — `MigrationRunInfo`, `JobRef`, `MigrationLogEvent` (`OrdinaryMigrationLogEvent |
53
+ BackgroundMigrationLogEvent`), `MigrationLogEventBase`, `MigrationLogLevel`,
54
+ `BackgroundRunInfo`, `MigrationLogger` (`ctx.logger`: `(msg, fields?)`, pino's `(fields, msg?)`
55
+ and an `Error` as the message — any `MigronautLogger` or pino instance is one); `logger?` and
56
+ `run?` on `MigrationContext` (optional, so a context built by hand still type-checks);
57
+ `attempts?` on `MigrationEvent`.
58
+
59
+ ### Changed
60
+
61
+ - `ctx.background` of a background migration is now frozen, and typed as `BackgroundRunInfo`.
62
+ **A type change on an experimental API:** its `generation` is now `number | undefined` (as it
63
+ always was at runtime for the live drift watcher), so strict TypeScript that reads it as a
64
+ `number` needs a check; `ctx.logger` is typed `MigrationLogger`, which every `MigronautLogger`
65
+ still fits.
66
+ - The kit's log lines of a run that names a `job` carry `jobId` / `groupId`.
67
+
68
+ ### Fixed
69
+
70
+ - **An `async` event listener that rejects no longer crashes the process.** The kit is an
71
+ `EventEmitter` with `captureRejections`: a listener's rejected promise is logged at debug level,
72
+ like a listener that throws, instead of surfacing as an `unhandledRejection`.
73
+
74
+ ### Documentation
75
+
76
+ - **How It Works** — a new guide page with architecture diagrams: the engine and its entry points,
77
+ the life of a run, the lock, where the state lives, which tool fits which change, background
78
+ migrations and what a run reports.
79
+ - **Diagrams across the guides** — Core Concepts (a migration's states, batches, a run), Transactions,
80
+ Declared Collections (a converge run), Document Versioning (the contract, optimistic concurrency,
81
+ the revision invariant, expand → background → contract), Background Migrations (statuses, one
82
+ lane's batches, passes, drift), the queue adapter (the line, a failure, background jobs) and
83
+ Migration Logs. They are Mermaid, drawn in the browser by a small theme component; `mermaid` is a
84
+ docs-only devDependency, loaded only by a page that has a diagram.
85
+ - **The home page** shows what 2.1 – 2.4 added — declared collections, Atlas Search, document
86
+ versioning, background migrations, OpenTelemetry, migration logs — with a diagram of how the parts
87
+ fit together.
88
+
6
89
  ## v2.3.0 — 2026-10-06
7
90
 
8
91
  Document versioning and background migrations. Additive: nothing changes for a project that
package/README.md CHANGED
@@ -66,6 +66,10 @@ change before it touches your database.
66
66
  - **Zero config files required** — drive everything from env vars if you prefer.
67
67
  - **Pino-friendly logging** — the `logger` option is pino-compatible; pass a pino instance directly
68
68
  and migronaut logs through it (with a `component: 'migronaut'` child binding).
69
+ - **Migration logs for your users (experimental)** — every migration gets `ctx.logger`, bound to its
70
+ run (run id, migration, attempt, queue job), and `ctx.run`; a line marked `userland: true` is also
71
+ emitted as `migration:log` for your service to store and show — migronaut stores none of it
72
+ ([guide](https://migronaut.vercel.app/guide/migration-logs)).
69
73
  - **Your id format** — run ids and queue group ids are random UUIDs by default; pass
70
74
  `generateId: ulid` (or cuid2, nanoid, UUIDv7 — any `() => string`) and every id migronaut mints
71
75
  comes from your generator.
@@ -189,7 +193,7 @@ Full docs, guides, and the API reference live at
189
193
  - [Core Concepts](https://migronaut.vercel.app/guide/concepts) — migrations, batches, the changelog, locking
190
194
  - [Getting Started](https://migronaut.vercel.app/guide/getting-started) & [Tutorial](https://migronaut.vercel.app/guide/tutorial)
191
195
  - [Configuration](https://migronaut.vercel.app/guide/configuration) · [Writing Migrations](https://migronaut.vercel.app/guide/writing-migrations) · [Transactions](https://migronaut.vercel.app/guide/transactions) · [Hooks](https://migronaut.vercel.app/guide/hooks)
192
- - [Programmatic API](https://migronaut.vercel.app/guide/api) · [Migrations as a Queue (BullMQ)](https://migronaut.vercel.app/guide/bullmq) · [OpenTelemetry](https://migronaut.vercel.app/guide/opentelemetry) · [CI/CD](https://migronaut.vercel.app/guide/ci-cd) · [Troubleshooting](https://migronaut.vercel.app/guide/troubleshooting)
196
+ - [Programmatic API](https://migronaut.vercel.app/guide/api) · [Migrations as a Queue (BullMQ)](https://migronaut.vercel.app/guide/bullmq) · [Migration Logs](https://migronaut.vercel.app/guide/migration-logs) · [OpenTelemetry](https://migronaut.vercel.app/guide/opentelemetry) · [CI/CD](https://migronaut.vercel.app/guide/ci-cd) · [Troubleshooting](https://migronaut.vercel.app/guide/troubleshooting)
193
197
  - Reference: [CLI Cheatsheet](https://migronaut.vercel.app/reference/cli) · [Error Codes](https://migronaut.vercel.app/reference/error-codes)
194
198
 
195
199
  ---
@@ -753,6 +757,9 @@ await mq.enqueueConverge(); // declared indexes and validators, as
753
757
  failed one. Duplicate enqueues are deduplicated; an already-applied migration completes as
754
758
  `skipped`.
755
759
  - **Bring your own Worker** with `createMigrationProcessor()` (NestJS, BullMQ Pro).
760
+ - **What a migration logs for your users** (`logger.info(…, { userland: true })`) lands in the
761
+ job's log and reaches `mq.kit.on('migration:log', …)` with the job's id — keep it in a
762
+ collection of your own ([Migration Logs](https://migronaut.vercel.app/guide/migration-logs)).
756
763
 
757
764
  → **[Migrations as a Queue](https://migronaut.vercel.app/guide/bullmq)** ·
758
765
  [runnable example service](examples/migration-service)
package/bullmq.d.ts CHANGED
@@ -840,6 +840,13 @@ export interface CreateMigrationProcessorOptions {
840
840
  jobOptions?: MigrationJobOptions;
841
841
  /** What a job may ask for beyond the ordinary — see {@link MigrationJobPermissions} */
842
842
  allow?: MigrationJobPermissions;
843
+ /**
844
+ * How many of a migration's `userland: true` lines one job's log takes (a
845
+ * lane: one slice) — the rest are counted in one closing row. `0` mirrors
846
+ * none; `migration:log` still carries every line. Default 1000
847
+ * @experimental New in 2.4
848
+ */
849
+ userlandLogRows?: number;
843
850
  /**
844
851
  * The background queue: what an `up` (or `down`) job registers gets its
845
852
  * coordinator there at once, and every `sync` tick heals it. @experimental
@@ -857,6 +864,11 @@ export interface CreateMigrationProcessorOptions {
857
864
  * declares exactly three parameters, which is what makes BullMQ hand it the
858
865
  * cancellation signal. Jobs are processed one at a time even when the Worker
859
866
  * is configured for more.
867
+ *
868
+ * Each run is told which job it works for (the kit's `job` option), so a
869
+ * migration's `ctx.run` and every `migration:log` event carry `jobId` and
870
+ * `groupId`, and the migration's `userland: true` lines are written into the
871
+ * job's log next to the processor's own rows (`✎ …`).
860
872
  */
861
873
  export interface MigrationProcessor {
862
874
  (
@@ -864,7 +876,10 @@ export interface MigrationProcessor {
864
876
  token?: string,
865
877
  signal?: AbortSignal,
866
878
  ): Promise<MigrationJobResult | SyncJobResult | ConvergeJobResult>;
867
- /** The kit running the jobs — subscribe to its events for metrics */
879
+ /**
880
+ * The kit running the jobs — subscribe to its events for metrics, and to
881
+ * `migration:log` for what the migrations log for your users
882
+ */
868
883
  readonly kit: MigratorKit;
869
884
  /**
870
885
  * Stop taking the lock. Irreversible. A job that has not started its
@@ -911,10 +926,18 @@ export interface CreateBackgroundProcessorOptions {
911
926
  stallMs?: number;
912
927
  /** Failed slices in a row before a lane gives up (0–100). Default 8 */
913
928
  maxLaneRetries?: number;
929
+ /**
930
+ * See {@link CreateMigrationProcessorOptions.userlandLogRows} — counted per slice
931
+ * @experimental New in 2.4
932
+ */
933
+ userlandLogRows?: number;
914
934
  }
915
935
 
916
936
  /**
917
937
  * The function a Worker on the background queue runs. Jobs run side by side.
938
+ * A lane's slice is told its job (`runBackgroundSlice`'s `job`), so its
939
+ * `migration:log` events carry `jobId` and its `userland: true` lines are
940
+ * written into the lane job's log (`✎ …`).
918
941
  * @experimental New in 2.3
919
942
  */
920
943
  export interface BackgroundProcessor {
@@ -1108,6 +1131,12 @@ export interface CreateMigrationQueueOptions<
1108
1131
  * refuse fails at the call. Give every producer and worker the same policy.
1109
1132
  */
1110
1133
  allow?: MigrationJobPermissions;
1134
+ /**
1135
+ * See {@link CreateMigrationProcessorOptions.userlandLogRows} — for the
1136
+ * migration jobs and, with `background`, the lanes
1137
+ * @experimental New in 2.4
1138
+ */
1139
+ userlandLogRows?: number;
1111
1140
  /**
1112
1141
  * Background migrations on a queue of their own (`<queueName>-background`):
1113
1142
  * a coordinator job each, with lanes as its children. `true` takes every
@@ -1164,7 +1193,11 @@ export class MigrationQueue<
1164
1193
  > {
1165
1194
  constructor(options: CreateMigrationQueueOptions<Q, W, E>);
1166
1195
 
1167
- /** The kit behind the queue — `kit.on('migration:success', …)` for metrics */
1196
+ /**
1197
+ * The kit behind the queue — `kit.on('migration:success', …)` for metrics,
1198
+ * `kit.on('migration:log', …)` (in the worker's process) to keep what the
1199
+ * migrations log for your users
1200
+ */
1168
1201
  readonly kit: MigratorKit;
1169
1202
  /** Your Queue, with its own type */
1170
1203
  readonly queue: Q;
package/index.d.ts CHANGED
@@ -35,6 +35,57 @@ export interface MigrationContext {
35
35
  * migronaut cannot interrupt a running function by itself.
36
36
  */
37
37
  signal?: AbortSignal;
38
+ /**
39
+ * The kit's logger, with this run's correlation ({@link run}) bound into the
40
+ * fields of every line. A call whose fields hold `userland: true` is also
41
+ * emitted as the `migration:log` event, for the application to store and show
42
+ * its users — migronaut stores none of it:
43
+ *
44
+ * ```js
45
+ * logger.info('batch done', { userland: true, processed: 1000 });
46
+ * ```
47
+ *
48
+ * Always present when migronaut runs the migration; optional so a context
49
+ * built by hand (in a test) still type-checks.
50
+ * @experimental New in 2.4
51
+ */
52
+ logger?: MigrationLogger;
53
+ /**
54
+ * Who this is: the run id, the migration, the direction, the transaction
55
+ * attempt and, when the caller named them, the queue job and the actor.
56
+ * Frozen; the same values `logger` binds and `migration:log` carries.
57
+ * Always present when migronaut runs the migration.
58
+ * @experimental New in 2.4
59
+ */
60
+ run?: MigrationRunInfo;
61
+ }
62
+
63
+ /**
64
+ * The correlation of a run, as `ctx.run`. `migration`, `batch` and `attempt`
65
+ * describe one migration and are absent in `beforeAll`/`afterAll`.
66
+ * @experimental New in 2.4
67
+ */
68
+ export interface MigrationRunInfo {
69
+ /** The run id — the same on the lock, the changelog record and every event of the run */
70
+ readonly id: string;
71
+ readonly direction: 'up' | 'down';
72
+ /** The migration file */
73
+ readonly migration?: string;
74
+ /** The changelog batch (`up` only) */
75
+ readonly batch?: number;
76
+ /**
77
+ * 1 — or more when a transaction was retried and the body runs again. Not a
78
+ * queue retry: a migration job is never retried.
79
+ */
80
+ readonly attempt?: number;
81
+ /** The queue job that runs this migration (set by the BullMQ adapter, or the `job` option) */
82
+ readonly jobId?: string;
83
+ /** The queue group the job belongs to */
84
+ readonly groupId?: string;
85
+ /** Who asked for the run (`requestedBy` option) */
86
+ readonly requestedBy?: string;
87
+ /** Why (`reason` option) */
88
+ readonly reason?: string;
38
89
  }
39
90
 
40
91
  /** Shape of an imported migration file module */
@@ -89,9 +140,18 @@ export type Body<T, N extends ShapeFieldNames = DefaultShapeFieldNames> = T exte
89
140
  export interface BackgroundMigrationContext {
90
141
  /** Aborted when the slice is stopping (lease lost, pause, shutdown) */
91
142
  signal: AbortSignal;
92
- logger: MigronautLogger;
143
+ /**
144
+ * The kit's logger with {@link background} bound into every line. Fields
145
+ * with `userland: true` also emit `migration:log` (`kind: 'background'`) —
146
+ * but `migrate` runs once per document, and again for a document a
147
+ * concurrent write moved: log from `migrateBatch` or a `step` rather than
148
+ * per document. In a dry run the lines say `dryRun: true` and nothing is
149
+ * emitted.
150
+ */
151
+ logger: MigrationLogger;
93
152
  direction: 'forward' | 'revert';
94
- background: { name: string; generation: number; partition: string };
153
+ /** Where this runs — frozen */
154
+ background: BackgroundRunInfo;
95
155
  session?: ClientSession;
96
156
  db?: Db;
97
157
  client?: MongoClient;
@@ -99,6 +159,43 @@ export interface BackgroundMigrationContext {
99
159
  dryRun?: boolean;
100
160
  }
101
161
 
162
+ /**
163
+ * `ctx.background`: which background migration, generation and partition —
164
+ * and the lane, its queue job and the transaction attempt, as its log lines
165
+ * and `migration:log` events carry them.
166
+ */
167
+ export interface BackgroundRunInfo {
168
+ readonly name: string;
169
+ /** The plan generation — absent when the live drift watcher runs the transformation */
170
+ readonly generation?: number;
171
+ /** The partition (`''` for the drift watcher, `'dry-run'` in a dry run) */
172
+ readonly partition: string;
173
+ /**
174
+ * The lane's run id — its lease's owner, the runId of its `background:*`
175
+ * events. Absent in a dry run.
176
+ * @experimental New in 2.4
177
+ */
178
+ readonly runId?: string;
179
+ /**
180
+ * The queue job working the lane — a background queue's lane job, or the
181
+ * migration job whose run drives it inline (`backgroundInline`)
182
+ * @experimental New in 2.4
183
+ */
184
+ readonly jobId?: string;
185
+ /**
186
+ * The group of that migration job — only when a run drives it inline; a
187
+ * background queue's lanes have none
188
+ * @experimental New in 2.4
189
+ */
190
+ readonly groupId?: string;
191
+ /**
192
+ * 1 — or more when a transactional batch or step runs again in a new
193
+ * transaction (a transient error, a conflict, a smaller batch)
194
+ * @experimental New in 2.4
195
+ */
196
+ readonly attempt: number;
197
+ }
198
+
102
199
  /** How a background migration splits its collection into partitions */
103
200
  export interface BackgroundPartitionSettings {
104
201
  /** Partitions per lane (default 4), so a slow partition does not hold the pass */
@@ -1086,9 +1183,41 @@ export interface MigronautLogger {
1086
1183
  * `{ runId, migration, direction, batch, durationMs }` — so a machine-readable
1087
1184
  * logger does not have to parse the human string. A plain `(msg) => …` logger
1088
1185
  * remains valid: the extra argument is simply ignored.
1186
+ *
1187
+ * On a migration's `ctx.logger`, fields with `userland: true` also emit the
1188
+ * `migration:log` event (see {@link MigrationLogEvent}).
1089
1189
  */
1090
1190
  export type LogMethod = (msg: string, fields?: Record<string, unknown>) => void;
1091
1191
 
1192
+ /**
1193
+ * `ctx.logger`: the kit's logger with the run's correlation bound into every
1194
+ * line. Each method takes `(msg, fields?)` — or pino's own `(fields, msg?)` —
1195
+ * and an `Error` as the message (its message, credentials masked). Fields with
1196
+ * `userland: true` also emit the `migration:log` event.
1197
+ *
1198
+ * Any {@link MigronautLogger} — or a pino instance — is one, so a context built
1199
+ * by hand in a test can pass the logger it has.
1200
+ * @experimental New in 2.4
1201
+ */
1202
+ export interface MigrationLogger {
1203
+ debug(
1204
+ msgOrFields: string | Error | Record<string, unknown>,
1205
+ fieldsOrMsg?: Record<string, unknown> | string,
1206
+ ): void;
1207
+ info(
1208
+ msgOrFields: string | Error | Record<string, unknown>,
1209
+ fieldsOrMsg?: Record<string, unknown> | string,
1210
+ ): void;
1211
+ warn(
1212
+ msgOrFields: string | Error | Record<string, unknown>,
1213
+ fieldsOrMsg?: Record<string, unknown> | string,
1214
+ ): void;
1215
+ error(
1216
+ msgOrFields: string | Error | Record<string, unknown>,
1217
+ fieldsOrMsg?: Record<string, unknown> | string,
1218
+ ): void;
1219
+ }
1220
+
1092
1221
  // ─── Telemetry ────────────────────────────────────────────────────────────────
1093
1222
 
1094
1223
  /** A span or metric attribute value — the scalar subset migronaut sets */
@@ -1453,6 +1582,25 @@ export interface UpOptions {
1453
1582
  requestedBy?: string;
1454
1583
  /** Why (≤ 512 characters) — a ticket, a sentence; stamped like `requestedBy` */
1455
1584
  reason?: string;
1585
+ /**
1586
+ * The queue job this run works for — bound into `ctx.run`, the run's log
1587
+ * lines and `migration:log`. The BullMQ adapter sets it; set it yourself
1588
+ * when you drive the kit from a queue of your own.
1589
+ * @experimental New in 2.4
1590
+ */
1591
+ job?: JobRef;
1592
+ }
1593
+
1594
+ /**
1595
+ * The queue job a run works for. Nothing is stored: the ids only correlate
1596
+ * what the run logs with the job a dashboard shows.
1597
+ * @experimental New in 2.4
1598
+ */
1599
+ export interface JobRef {
1600
+ /** The job's id (≤ 1024 characters) */
1601
+ id: string;
1602
+ /** The group of jobs it was enqueued with (≤ 128 characters) */
1603
+ groupId?: string;
1456
1604
  }
1457
1605
 
1458
1606
  /** Options for {@link MigratorKit.down} */
@@ -1487,6 +1635,11 @@ export interface DownOptions {
1487
1635
  requestedBy?: string;
1488
1636
  /** Why (≤ 512 characters) — a ticket, a sentence; stamped as `revertReason` */
1489
1637
  reason?: string;
1638
+ /**
1639
+ * The queue job this run works for — see {@link UpOptions.job}
1640
+ * @experimental New in 2.4
1641
+ */
1642
+ job?: JobRef;
1490
1643
  }
1491
1644
 
1492
1645
  /** Payload common to every lifecycle event */
@@ -1500,6 +1653,12 @@ export interface MigrationEvent extends MigronautEventBase {
1500
1653
  direction: 'up' | 'down';
1501
1654
  batch?: number;
1502
1655
  durationMs?: number;
1656
+ /**
1657
+ * How many times the body ran, when the driver retried its transaction (on
1658
+ * `migration:success` and `migration:error`; absent when it ran once)
1659
+ * @experimental New in 2.4
1660
+ */
1661
+ attempts?: number;
1503
1662
  /**
1504
1663
  * Human-readable failure message (on `migration:error` only), with URI
1505
1664
  * credentials already redacted — safe to ship to metrics/alerting as-is.
@@ -1621,6 +1780,82 @@ export interface ConvergeEndEvent extends MigronautEventBase {
1621
1780
  error?: string;
1622
1781
  }
1623
1782
 
1783
+ /** The level of a `ctx.logger` call */
1784
+ export type MigrationLogLevel = 'debug' | 'info' | 'warn' | 'error';
1785
+
1786
+ /**
1787
+ * What every `migration:log` event carries.
1788
+ * @experimental New in 2.4
1789
+ */
1790
+ export interface MigrationLogEventBase {
1791
+ level: MigrationLogLevel;
1792
+ /**
1793
+ * The message — URI credentials and the values a server error quotes (an
1794
+ * E11000's duplicate key) masked — at most 2048 characters
1795
+ */
1796
+ msg: string;
1797
+ /**
1798
+ * The call's fields without the `userland` marker, as a document a driver can
1799
+ * store: a copy, its strings redacted, at most 8 levels and 1000 entries
1800
+ * deep, strings at most 4096 characters. Dates, regular expressions and BSON
1801
+ * values are kept as they are, binary data up to 4096 bytes too. An `Error`
1802
+ * becomes `{ name, message, code?, codeName? }`; a `Map` an object, a `Set`
1803
+ * an array; any other instance what `JSON.stringify` would see.
1804
+ */
1805
+ data: Record<string, unknown>;
1806
+ /** When the call was made, by this process's clock — a TTL index can expire on it */
1807
+ at: Date;
1808
+ /** Increasing within one `runId`: orders the events of one millisecond */
1809
+ seq: number;
1810
+ /** Present when `msg` or `data` was cut to those bounds */
1811
+ truncated?: true;
1812
+ }
1813
+
1814
+ /**
1815
+ * A `ctx.logger` call with `userland: true` in an ordinary migration or its
1816
+ * hooks — `migration`, `batch` and `attempt` are absent in `beforeAll`/`afterAll`.
1817
+ * @experimental New in 2.4
1818
+ */
1819
+ export interface OrdinaryMigrationLogEvent extends MigrationLogEventBase {
1820
+ kind: 'migration';
1821
+ runId: string;
1822
+ direction: 'up' | 'down';
1823
+ migration?: string;
1824
+ batch?: number;
1825
+ attempt?: number;
1826
+ jobId?: string;
1827
+ groupId?: string;
1828
+ requestedBy?: string;
1829
+ reason?: string;
1830
+ }
1831
+
1832
+ /**
1833
+ * A `ctx.logger` call with `userland: true` in a background migration's
1834
+ * `migrate`, `migrateBatch`, `step` (or their way back). `runId` is the lane's,
1835
+ * the one its `background:*` events carry.
1836
+ * @experimental New in 2.4
1837
+ */
1838
+ export interface BackgroundMigrationLogEvent extends MigrationLogEventBase {
1839
+ kind: 'background';
1840
+ /** The lane's run id (a dry run, which has none, emits nothing) */
1841
+ runId: string;
1842
+ migration: string;
1843
+ direction: 'forward' | 'revert';
1844
+ generation?: number;
1845
+ partition: string;
1846
+ attempt: number;
1847
+ jobId?: string;
1848
+ /** Only when a run drives it inline — see {@link BackgroundRunInfo.groupId} */
1849
+ groupId?: string;
1850
+ }
1851
+
1852
+ /**
1853
+ * The `migration:log` event — `kind` tells an ordinary migration's from a
1854
+ * background migration's.
1855
+ * @experimental New in 2.4
1856
+ */
1857
+ export type MigrationLogEvent = OrdinaryMigrationLogEvent | BackgroundMigrationLogEvent;
1858
+
1624
1859
  /**
1625
1860
  * Lifecycle events emitted by {@link MigratorKit}. Subscribe to feed metrics or
1626
1861
  * alerting without parsing log lines; a listener that throws is contained and
@@ -1634,6 +1869,12 @@ export interface MigronautEvents {
1634
1869
  'migration:success': (event: MigrationEvent) => void;
1635
1870
  'migration:skipped': (event: MigrationEvent) => void;
1636
1871
  'migration:error': (event: MigrationEvent) => void;
1872
+ /**
1873
+ * A `ctx.logger` call marked `userland: true` — for the application to keep.
1874
+ * Logged lines without the marker emit nothing.
1875
+ * @experimental New in 2.4
1876
+ */
1877
+ 'migration:log': (event: MigrationLogEvent) => void;
1637
1878
  'lock:acquired': (event: LockEvent) => void;
1638
1879
  'lock:released': (event: LockEvent) => void;
1639
1880
  'lock:lost': (event: LockEvent) => void;
@@ -1716,6 +1957,11 @@ export interface RedoOptions {
1716
1957
  requestedBy?: string;
1717
1958
  /** Why — stamped like `requestedBy` */
1718
1959
  reason?: string;
1960
+ /**
1961
+ * The queue job this run works for — both halves carry it; see {@link UpOptions.job}
1962
+ * @experimental New in 2.4
1963
+ */
1964
+ job?: JobRef;
1719
1965
  }
1720
1966
 
1721
1967
  /** Options for {@link MigratorKit.create} */
@@ -1952,7 +2198,15 @@ export class MigratorKit extends EventEmitter {
1952
2198
  /** One slice of one lane: claim a partition and a slot, work it, release. @experimental */
1953
2199
  runBackgroundSlice(
1954
2200
  name: string,
1955
- options?: { signal?: AbortSignal; sliceMs?: number },
2201
+ options?: {
2202
+ signal?: AbortSignal;
2203
+ sliceMs?: number;
2204
+ /**
2205
+ * The queue job working the lane — on its log lines and `migration:log` events
2206
+ * @experimental New in 2.4
2207
+ */
2208
+ job?: Pick<JobRef, 'id'>;
2209
+ },
1956
2210
  ): Promise<BackgroundSliceResult>;
1957
2211
  /**
1958
2212
  * Drive a background migration from this process until it is done (or one
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexify/migronaut",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Elegant, fast, fully-typed, zero-dependency MongoDB migrations for Node.js — adopts an existing migrate-mongo changelog in one command",
5
5
  "license": "MIT",
6
6
  "author": "Alex Dolid <dolid.sasha@gmail.com>",
@@ -102,6 +102,7 @@
102
102
  "c8": "^10.1.3",
103
103
  "esbuild": "^0.28.1",
104
104
  "ioredis": "^5.11.1",
105
+ "mermaid": "^11.17.2",
105
106
  "mongodb": "^6.12.0",
106
107
  "mongodb-memory-server": "10.4.3",
107
108
  "mongoose": "^8.9.2",