@alexify/migronaut 2.0.0 → 2.1.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 +320 -0
- package/README.md +208 -6
- package/bullmq.d.ts +845 -0
- package/bullmq.js +1 -0
- package/index.d.ts +634 -18
- package/migronaut.schema.json +182 -1
- package/package.json +21 -5
- package/src/bullmq/index.js +55 -0
- package/src/bullmq/jobs.js +454 -0
- package/src/bullmq/processor.js +608 -0
- package/src/bullmq/producer.js +424 -0
- package/src/bullmq/service.js +653 -0
- package/src/bullmq/wait.js +124 -0
- package/src/cli/args.js +12 -2
- package/src/cli/commands/converge.js +160 -0
- package/src/cli/commands/down.js +2 -0
- package/src/cli/commands/lock.js +2 -1
- package/src/cli/commands/redo.js +8 -1
- package/src/cli/commands/up.js +14 -1
- package/src/cli/exit-codes.js +9 -2
- package/src/cli/index.js +2 -0
- package/src/cli/shared.js +14 -4
- package/src/cli/table.js +105 -0
- package/src/core/changelog.js +71 -6
- package/src/core/collections.js +372 -0
- package/src/core/config.js +100 -25
- package/src/core/converge-log.js +47 -0
- package/src/core/converge-plan.js +483 -0
- package/src/core/converge.js +867 -0
- package/src/core/index-spec.js +496 -0
- package/src/core/lock-wait.js +260 -0
- package/src/core/lock.js +45 -16
- package/src/core/migrator.js +563 -283
- package/src/core/options.js +251 -0
- package/src/core/run-recorder.js +157 -0
- package/src/core/run.js +58 -90
- package/src/core/sequence.js +134 -0
- package/src/errors/index.js +56 -0
- package/src/index.js +8 -0
- package/src/utils/actor.js +48 -0
- package/src/utils/canonical.js +179 -0
- package/src/utils/collection-name.js +21 -0
- package/src/utils/error.js +18 -1
- package/src/utils/id.js +77 -0
- package/src/utils/loader.js +39 -21
- package/src/utils/migration-name.js +32 -0
- package/src/utils/redact.js +21 -1
- package/src/utils/telemetry.js +393 -0
- package/src/utils/template.js +36 -2
package/index.d.ts
CHANGED
|
@@ -143,6 +143,16 @@ export interface MigrationHooks {
|
|
|
143
143
|
/** File type a created migration is written as */
|
|
144
144
|
export type MigrationExtension = 'ts' | 'js';
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Mints one identifier. Called with **no arguments** and no `this`, so a
|
|
148
|
+
* third-party generator passes straight through (`generateId: ulid`,
|
|
149
|
+
* `generateId: createId`, `generateId: nanoid`). It must be **synchronous** and
|
|
150
|
+
* return a non-empty string of at most 128 characters, different on every
|
|
151
|
+
* call; anything else — a throw and a returned promise included — fails the
|
|
152
|
+
* run with a {@link ConfigInvalidError}.
|
|
153
|
+
*/
|
|
154
|
+
export type IdGenerator = () => string;
|
|
155
|
+
|
|
146
156
|
/**
|
|
147
157
|
* Every **scalar** option below is also settable from the environment as
|
|
148
158
|
* `MIGRONAUT_<SCREAMING_SNAKE>` (`migrationsDir` → `MIGRONAUT_MIGRATIONS_DIR`,
|
|
@@ -152,9 +162,9 @@ export type MigrationExtension = 'ts' | 'js';
|
|
|
152
162
|
* file and are outranked by CLI flags. A value that does not parse is rejected
|
|
153
163
|
* with a {@link ConfigInvalidError} naming the variable — never coerced.
|
|
154
164
|
*
|
|
155
|
-
* `fileExtensions`, `clientOptions` and the live
|
|
156
|
-
* `hooks`, `logger`) are
|
|
157
|
-
* cannot express them.
|
|
165
|
+
* `fileExtensions`, `clientOptions`, `collections`, `generateId` and the live
|
|
166
|
+
* handles (`client`, `mongoose`, `hooks`, `logger`, `telemetry`) are
|
|
167
|
+
* config-file/API only: a single environment string cannot express them.
|
|
158
168
|
*/
|
|
159
169
|
export interface MigronautConfig {
|
|
160
170
|
/** MongoDB connection URI. Not required when `client` is supplied */
|
|
@@ -179,6 +189,12 @@ export interface MigronautConfig {
|
|
|
179
189
|
migrationsCollection: string;
|
|
180
190
|
/** Collection name for distributed lock. Default: '_migronaut_locks' */
|
|
181
191
|
lockCollection: string;
|
|
192
|
+
/**
|
|
193
|
+
* Collection holding the converge history — one entry per converge that
|
|
194
|
+
* changed something or failed. Created by the first such converge, never
|
|
195
|
+
* before. Default: '_migronaut_converge'
|
|
196
|
+
*/
|
|
197
|
+
convergeLogCollection: string;
|
|
182
198
|
/** How long (seconds) a lock is considered stale. Default: 60 */
|
|
183
199
|
lockTTLSeconds: number;
|
|
184
200
|
/**
|
|
@@ -255,11 +271,52 @@ export interface MigronautConfig {
|
|
|
255
271
|
* A single-file `up` (an explicit, deliberate target) is never checked.
|
|
256
272
|
*/
|
|
257
273
|
onOutOfOrder?: 'warn' | 'error' | 'allow';
|
|
274
|
+
/**
|
|
275
|
+
* Declared collections: their indexes and validator, as the end state you
|
|
276
|
+
* want. `converge()` (`migronaut converge`) compares them with the live
|
|
277
|
+
* database and makes the difference — no migration file per change, and
|
|
278
|
+
* nothing recorded. Combined with the files in `collectionsDir`; a
|
|
279
|
+
* collection declared twice is a {@link ConfigInvalidError}. Experimental.
|
|
280
|
+
*/
|
|
281
|
+
collections?: CollectionDefinition[];
|
|
282
|
+
/**
|
|
283
|
+
* Directory of collection definition files, one collection per file (a
|
|
284
|
+
* `.ts`/`.js` default export or a `.json` document; the collection name
|
|
285
|
+
* defaults to the file name). Opt-in — nothing is read unless this is set.
|
|
286
|
+
* Files are loaded when a converge runs, not at config resolution.
|
|
287
|
+
*/
|
|
288
|
+
collectionsDir?: string;
|
|
289
|
+
/**
|
|
290
|
+
* End every bulk `up` — no file, no `to` — by converging the declared
|
|
291
|
+
* collections, under the same lock and even when no migration was pending.
|
|
292
|
+
* Default: false
|
|
293
|
+
*/
|
|
294
|
+
convergeAfterUp?: boolean;
|
|
258
295
|
/** Mongoose instance — required only if your migrations use Mongoose models */
|
|
259
296
|
mongoose?: MongooseLike;
|
|
260
297
|
hooks?: MigrationHooks;
|
|
261
298
|
/** Custom logger — set to null to silence all output (useful in tests) */
|
|
262
299
|
logger?: MigronautLogger | null;
|
|
300
|
+
/**
|
|
301
|
+
* Your own identifier format (ULID, CUID, UUIDv7, …) for every id migronaut
|
|
302
|
+
* mints: the run id — stamped on changelog records, events and log lines,
|
|
303
|
+
* and stored as the lock's owner token — and, through
|
|
304
|
+
* `@alexify/migronaut/bullmq`, the group id of an enqueue call.
|
|
305
|
+
* Default: `crypto.randomUUID()`.
|
|
306
|
+
*
|
|
307
|
+
* Ids are for correlation. The lock adds a token of its own, so a generator
|
|
308
|
+
* that repeats a value blurs which run wrote what but never lets two runs
|
|
309
|
+
* hold the lock at once.
|
|
310
|
+
*/
|
|
311
|
+
generateId?: IdGenerator;
|
|
312
|
+
/**
|
|
313
|
+
* OpenTelemetry, from your own `@opentelemetry/api`: a tracer, a meter, or
|
|
314
|
+
* both. Every run and every migration becomes a span — the migration's span
|
|
315
|
+
* is the active one while its `up`/`down` runs, so an instrumented MongoDB
|
|
316
|
+
* driver nests its command spans under it — and their durations are
|
|
317
|
+
* recorded as histograms. Absent, `null` or empty turns it off.
|
|
318
|
+
*/
|
|
319
|
+
telemetry?: MigronautTelemetry | null;
|
|
263
320
|
}
|
|
264
321
|
|
|
265
322
|
/**
|
|
@@ -275,6 +332,237 @@ export type MigronautConfigInput =
|
|
|
275
332
|
| Partial<MigronautConfig>
|
|
276
333
|
| (() => Partial<MigronautConfig> | Promise<Partial<MigronautConfig>>);
|
|
277
334
|
|
|
335
|
+
// ─── Collections (converge) ───────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/** An index key direction: ascending, descending, or a special index type */
|
|
338
|
+
export type IndexKeyDirection = 1 | -1 | 'text' | 'hashed' | '2d' | '2dsphere';
|
|
339
|
+
|
|
340
|
+
/** Collation of an index — `locale` required, the rest as MongoDB defines them */
|
|
341
|
+
export interface IndexCollation {
|
|
342
|
+
locale: string;
|
|
343
|
+
caseLevel?: boolean;
|
|
344
|
+
caseFirst?: 'upper' | 'lower' | 'off';
|
|
345
|
+
strength?: 1 | 2 | 3 | 4 | 5;
|
|
346
|
+
numericOrdering?: boolean;
|
|
347
|
+
alternate?: 'non-ignorable' | 'shifted';
|
|
348
|
+
maxVariable?: 'punct' | 'space';
|
|
349
|
+
backwards?: boolean;
|
|
350
|
+
normalization?: boolean;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* One declared index, in the driver's own flat `createIndexes` shape. Every
|
|
355
|
+
* option is checked: an unknown one is a {@link ConfigInvalidError} rather
|
|
356
|
+
* than dropped, because the driver drops it silently and the index would be
|
|
357
|
+
* built without it.
|
|
358
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
359
|
+
*/
|
|
360
|
+
export interface IndexDefinition {
|
|
361
|
+
/**
|
|
362
|
+
* Field → direction, in index order. A compound key with an integer-like
|
|
363
|
+
* field name must be a `Map` (a plain object reorders such names), with that
|
|
364
|
+
* field first — the live key is read back as a plain object.
|
|
365
|
+
*/
|
|
366
|
+
key: Record<string, IndexKeyDirection> | Map<string, IndexKeyDirection>;
|
|
367
|
+
/** Defaults to the name MongoDB generates: `email_1`, `a_1_b_-1` */
|
|
368
|
+
name?: string;
|
|
369
|
+
unique?: boolean;
|
|
370
|
+
sparse?: boolean;
|
|
371
|
+
/** Changed in place (`collMod`) — no rebuild */
|
|
372
|
+
hidden?: boolean;
|
|
373
|
+
/** TTL in seconds. Changed in place when the live index already has one */
|
|
374
|
+
expireAfterSeconds?: number;
|
|
375
|
+
partialFilterExpression?: Record<string, unknown>;
|
|
376
|
+
collation?: IndexCollation;
|
|
377
|
+
/** For a wildcard (`$**`) index */
|
|
378
|
+
wildcardProjection?: Record<string, 0 | 1 | boolean>;
|
|
379
|
+
/** Text index field weights (default 1) */
|
|
380
|
+
weights?: Record<string, number>;
|
|
381
|
+
default_language?: string;
|
|
382
|
+
language_override?: string;
|
|
383
|
+
textIndexVersion?: number;
|
|
384
|
+
'2dsphereIndexVersion'?: number;
|
|
385
|
+
bits?: number;
|
|
386
|
+
min?: number;
|
|
387
|
+
max?: number;
|
|
388
|
+
storageEngine?: Record<string, unknown>;
|
|
389
|
+
/** Accepted and ignored — a no-op since MongoDB 4.2 */
|
|
390
|
+
background?: boolean;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export type ValidationLevel = 'off' | 'strict' | 'moderate';
|
|
394
|
+
export type ValidationAction = 'error' | 'warn' | 'errorAndLog';
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* A declared collection: the end state `converge()` keeps it in. Leave
|
|
398
|
+
* `indexes` or `validator` out to leave that part unmanaged.
|
|
399
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
400
|
+
*/
|
|
401
|
+
export interface CollectionDefinition {
|
|
402
|
+
name: string;
|
|
403
|
+
/**
|
|
404
|
+
* Every index besides `_id`. Undeclared live indexes are kept (and reported)
|
|
405
|
+
* unless `prune` is on.
|
|
406
|
+
*/
|
|
407
|
+
indexes?: IndexDefinition[];
|
|
408
|
+
/** A query or `{ $jsonSchema }` document; `null` (or `{}`) for no validator */
|
|
409
|
+
validator?: Record<string, unknown> | null;
|
|
410
|
+
/** Default: 'strict'. Only with a validator */
|
|
411
|
+
validationLevel?: ValidationLevel;
|
|
412
|
+
/** Default: 'error'. Only with a validator */
|
|
413
|
+
validationAction?: ValidationAction;
|
|
414
|
+
/** Drop live indexes this definition does not declare. Default: the call's `prune`, else false */
|
|
415
|
+
prune?: boolean;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** What a `collectionsDir` file exports: a definition whose name defaults to the file name */
|
|
419
|
+
export type CollectionDefinitionFile = Omit<CollectionDefinition, 'name'> & { name?: string };
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Options for {@link MigratorKit.converge}
|
|
423
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
424
|
+
*/
|
|
425
|
+
export interface ConvergeOptions {
|
|
426
|
+
/** Plan without writing: no lock, no events. The result's rows are `'planned'` */
|
|
427
|
+
dryRun?: boolean;
|
|
428
|
+
/** Drop undeclared indexes in collections whose definition does not set `prune` */
|
|
429
|
+
prune?: boolean;
|
|
430
|
+
/** Skip lock acquisition (dev only) */
|
|
431
|
+
noLock?: boolean;
|
|
432
|
+
/**
|
|
433
|
+
* Refuse ({@link MigrationBlockedError}) while any migration is still
|
|
434
|
+
* pending — checked under the lock. How the queue adapter runs a converge
|
|
435
|
+
* as the tail of a deploy.
|
|
436
|
+
*/
|
|
437
|
+
ordered?: boolean;
|
|
438
|
+
/**
|
|
439
|
+
* Allow a rebuild that drops a unique index and builds a unique one back.
|
|
440
|
+
* Without it such a rebuild plans as a `conflict`: the constraint is gone
|
|
441
|
+
* until the new index is built, and a duplicate written in between leaves
|
|
442
|
+
* neither index buildable. The after-up hook and queue jobs never set it.
|
|
443
|
+
* CLI: `--rebuild-unique`.
|
|
444
|
+
*/
|
|
445
|
+
rebuildUnique?: boolean;
|
|
446
|
+
/** Who asked for this converge — recorded in the converge history */
|
|
447
|
+
requestedBy?: string;
|
|
448
|
+
/** Why — recorded in the converge history */
|
|
449
|
+
reason?: string;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export type ConvergeTarget = 'collection' | 'validator' | 'index';
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* What converge does to one target. `keep` is an undeclared index left alone
|
|
456
|
+
* (prune off); `conflict` refuses the run — an undeclared index covers the
|
|
457
|
+
* declared one's key under another name, a unique index would be rebuilt
|
|
458
|
+
* without {@link ConvergeOptions.rebuildUnique}, or the collection is a view
|
|
459
|
+
* or a time-series collection.
|
|
460
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
461
|
+
*/
|
|
462
|
+
export type ConvergeActionKind =
|
|
463
|
+
| 'create'
|
|
464
|
+
| 'modify'
|
|
465
|
+
| 'recreate'
|
|
466
|
+
| 'drop'
|
|
467
|
+
| 'keep'
|
|
468
|
+
| 'unchanged'
|
|
469
|
+
| 'conflict';
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* `planned` in a dry run; otherwise `applied`, `failed`, or `skipped` — no
|
|
473
|
+
* change was needed, or the run stopped before reaching it.
|
|
474
|
+
*/
|
|
475
|
+
export type ConvergeActionStatus = 'planned' | 'applied' | 'failed' | 'skipped';
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* One row of a converge result
|
|
479
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
480
|
+
*/
|
|
481
|
+
export interface ConvergeAction {
|
|
482
|
+
target: ConvergeTarget;
|
|
483
|
+
/** The index name; the collection name for a `collection` or `validator` row */
|
|
484
|
+
name: string;
|
|
485
|
+
action: ConvergeActionKind;
|
|
486
|
+
status: ConvergeActionStatus;
|
|
487
|
+
/** What differs (`'unique, expireAfterSeconds'`), or why a row is what it is */
|
|
488
|
+
reason?: string;
|
|
489
|
+
/** The live index the row refers to when its name differs from the declared one */
|
|
490
|
+
liveName?: string;
|
|
491
|
+
durationMs?: number;
|
|
492
|
+
/**
|
|
493
|
+
* What is there now — the live index (`{ key, name, ...options }`) or
|
|
494
|
+
* validator (`{ validator, validationLevel, validationAction }`) — on rows
|
|
495
|
+
* that change or drop it, and on `keep` rows. Plain JSON.
|
|
496
|
+
*/
|
|
497
|
+
from?: Record<string, unknown>;
|
|
498
|
+
/** What the row puts there — the declared index or validator — on rows that create or change it */
|
|
499
|
+
to?: Record<string, unknown>;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
504
|
+
*/
|
|
505
|
+
export interface CollectionConvergeResult {
|
|
506
|
+
name: string;
|
|
507
|
+
actions: ConvergeAction[];
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* One entry of the converge history (`convergeLogCollection`): a converge that
|
|
512
|
+
* changed something or failed.
|
|
513
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
514
|
+
*/
|
|
515
|
+
export interface ConvergeHistoryEntry {
|
|
516
|
+
runId?: string;
|
|
517
|
+
/** `'converge'`, or `'up'` for the converge that ended a bulk `up` */
|
|
518
|
+
trigger: ConvergeTrigger;
|
|
519
|
+
startedAt: Date;
|
|
520
|
+
finishedAt: Date;
|
|
521
|
+
durationMs: number;
|
|
522
|
+
success: boolean;
|
|
523
|
+
/** Redacted failure message (`success: false` only) */
|
|
524
|
+
error?: string;
|
|
525
|
+
executedBy: string;
|
|
526
|
+
host: string;
|
|
527
|
+
environment: string;
|
|
528
|
+
requestedBy?: string;
|
|
529
|
+
reason?: string;
|
|
530
|
+
/** Changes applied */
|
|
531
|
+
changed: number;
|
|
532
|
+
/** The rows that changed, failed or refused the run — each with its collection and `from` / `to` */
|
|
533
|
+
actions: Array<ConvergeAction & { collection: string }>;
|
|
534
|
+
unstable?: ConvergeUnstable[];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Something applied that still compares as changed — reported, never rebuilt in a loop
|
|
539
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
540
|
+
*/
|
|
541
|
+
export interface ConvergeUnstable {
|
|
542
|
+
collection: string;
|
|
543
|
+
target: ConvergeTarget;
|
|
544
|
+
name: string;
|
|
545
|
+
action: ConvergeActionKind;
|
|
546
|
+
reason?: string;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Outcome of {@link MigratorKit.converge}
|
|
551
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
552
|
+
*/
|
|
553
|
+
export interface ConvergeResult {
|
|
554
|
+
dryRun: boolean;
|
|
555
|
+
/** Changes applied — or, in a dry run, changes the run would make */
|
|
556
|
+
changed: number;
|
|
557
|
+
/**
|
|
558
|
+
* True when the database matches the declarations: nothing left to do and
|
|
559
|
+
* no conflict. Undeclared indexes kept with prune off do not count against it.
|
|
560
|
+
*/
|
|
561
|
+
inSync: boolean;
|
|
562
|
+
collections: CollectionConvergeResult[];
|
|
563
|
+
unstable?: ConvergeUnstable[];
|
|
564
|
+
}
|
|
565
|
+
|
|
278
566
|
// ─── Logger ───────────────────────────────────────────────────────────────────
|
|
279
567
|
|
|
280
568
|
/**
|
|
@@ -303,6 +591,92 @@ export interface MigronautLogger {
|
|
|
303
591
|
*/
|
|
304
592
|
export type LogMethod = (msg: string, fields?: Record<string, unknown>) => void;
|
|
305
593
|
|
|
594
|
+
// ─── Telemetry ────────────────────────────────────────────────────────────────
|
|
595
|
+
|
|
596
|
+
/** A span or metric attribute value — the scalar subset migronaut sets */
|
|
597
|
+
export type MigronautAttributes = Record<string, string | number | boolean>;
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* The slice of an OpenTelemetry `Span` migronaut calls. Declared structurally —
|
|
601
|
+
* `@opentelemetry/api` is deliberately not imported, so the package's types
|
|
602
|
+
* resolve for users who never installed it. A real `Span` satisfies it.
|
|
603
|
+
*/
|
|
604
|
+
export interface MigronautSpan {
|
|
605
|
+
setAttribute(key: string, value: string | number | boolean): unknown;
|
|
606
|
+
/** `code` is OpenTelemetry's `SpanStatusCode` — migronaut only ever sets ERROR (2) */
|
|
607
|
+
setStatus(status: { code: number; message?: string }): unknown;
|
|
608
|
+
end(): void;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* The slice of an OpenTelemetry `Tracer` migronaut calls — what
|
|
613
|
+
* `trace.getTracer('@alexify/migronaut')` returns. Only `startActiveSpan` is
|
|
614
|
+
* used: it is what makes a span the active context for the migration's own
|
|
615
|
+
* code, and so for any instrumentation running underneath it.
|
|
616
|
+
*/
|
|
617
|
+
export interface MigronautTracer {
|
|
618
|
+
startActiveSpan<T>(
|
|
619
|
+
name: string,
|
|
620
|
+
options: { attributes?: MigronautAttributes },
|
|
621
|
+
fn: (span: MigronautSpan) => T,
|
|
622
|
+
): T;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** An OpenTelemetry `Histogram`, as far as migronaut uses one */
|
|
626
|
+
export interface MigronautHistogram {
|
|
627
|
+
record(value: number, attributes?: MigronautAttributes): void;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** An OpenTelemetry `Counter`, as far as migronaut uses one */
|
|
631
|
+
export interface MigronautCounter {
|
|
632
|
+
add(value: number, attributes?: MigronautAttributes): void;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Options migronaut passes when it creates an instrument */
|
|
636
|
+
export interface MigronautMetricOptions {
|
|
637
|
+
description?: string;
|
|
638
|
+
unit?: string;
|
|
639
|
+
/** Histogram bucket boundaries, in the instrument's unit (seconds) */
|
|
640
|
+
advice?: { explicitBucketBoundaries?: number[] };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* The slice of an OpenTelemetry `Meter` migronaut calls — what
|
|
645
|
+
* `metrics.getMeter('@alexify/migronaut')` returns.
|
|
646
|
+
*/
|
|
647
|
+
export interface MigronautMeter {
|
|
648
|
+
createHistogram(name: string, options?: MigronautMetricOptions): MigronautHistogram;
|
|
649
|
+
createCounter(name: string, options?: MigronautMetricOptions): MigronautCounter;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* The `telemetry` config option. Both parts are optional and independent:
|
|
654
|
+
* a tracer alone gives spans, a meter alone gives metrics.
|
|
655
|
+
*
|
|
656
|
+
* Spans: `migronaut.run` (one per run that held the lock) and
|
|
657
|
+
* `migronaut.migration` (one per migration executed, a child of the run).
|
|
658
|
+
* Metrics: `migronaut.run.duration`, `migronaut.migration.duration` and
|
|
659
|
+
* `migronaut.lock.acquire.duration` (histograms, seconds), plus the counters
|
|
660
|
+
* `migronaut.lock.refused` and `migronaut.lock.lost`. A failure sets the span's
|
|
661
|
+
* status to ERROR with a redacted message, and `error.type` — on the span and
|
|
662
|
+
* the metric point — to the {@link MigronautErrorCode}, or for an error that is
|
|
663
|
+
* not migronaut's to its class name (`_OTHER` when it has none).
|
|
664
|
+
*
|
|
665
|
+
* A tracer or meter that throws never fails a run.
|
|
666
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
667
|
+
*/
|
|
668
|
+
export interface MigronautTelemetry {
|
|
669
|
+
tracer?: MigronautTracer | null;
|
|
670
|
+
meter?: MigronautMeter | null;
|
|
671
|
+
/**
|
|
672
|
+
* Static attributes added to every span and every metric point — your own
|
|
673
|
+
* low-cardinality dimensions (`{ tenant: 'acme' }`). At most 20. They cannot
|
|
674
|
+
* replace migronaut's own: `db.namespace` (the database name, always
|
|
675
|
+
* present) and the `migronaut.*` attributes win.
|
|
676
|
+
*/
|
|
677
|
+
attributes?: Record<string, string | number | boolean>;
|
|
678
|
+
}
|
|
679
|
+
|
|
306
680
|
// ─── Progress Reporter ─────────────────────────────────────────────────────────
|
|
307
681
|
|
|
308
682
|
/**
|
|
@@ -375,6 +749,20 @@ export interface StatusRow {
|
|
|
375
749
|
* the whole status/audit call.
|
|
376
750
|
*/
|
|
377
751
|
invalid?: true;
|
|
752
|
+
/**
|
|
753
|
+
* The file's current checksum (SHA-256 hex) — on `dryRun('up')` rows, so a
|
|
754
|
+
* caller that applies them later can insist on exactly this version
|
|
755
|
+
* (`up(file, { checksum })`).
|
|
756
|
+
*/
|
|
757
|
+
checksum?: string;
|
|
758
|
+
/** Who asked for the apply, and why — when the run said (`requestedBy` / `reason` options) */
|
|
759
|
+
requestedBy?: string;
|
|
760
|
+
reason?: string;
|
|
761
|
+
/** Who asked for the revert, and why — on reverted history rows */
|
|
762
|
+
revertRequestedBy?: string;
|
|
763
|
+
revertReason?: string;
|
|
764
|
+
/** The checksum of the file version that failed (status `'failed'` only) */
|
|
765
|
+
failedChecksum?: string;
|
|
378
766
|
}
|
|
379
767
|
|
|
380
768
|
// ─── Import (migrate-mongo adoption) ────────────────────────────────────────────
|
|
@@ -432,6 +820,13 @@ export interface LockInfo {
|
|
|
432
820
|
host: string;
|
|
433
821
|
/** Username of the holder */
|
|
434
822
|
executedBy: string;
|
|
823
|
+
/**
|
|
824
|
+
* The holder's run id — the `runId` of its events, log lines and changelog
|
|
825
|
+
* records. Absent for a document written by hand.
|
|
826
|
+
*/
|
|
827
|
+
runId?: string;
|
|
828
|
+
/** The holder's lock TTL (ms), which paces its heartbeat. Absent before 2.1 */
|
|
829
|
+
ttlMs?: number;
|
|
435
830
|
}
|
|
436
831
|
|
|
437
832
|
// ─── Error Codes ──────────────────────────────────────────────────────────────
|
|
@@ -456,7 +851,11 @@ export type MigronautErrorCode =
|
|
|
456
851
|
| 'NOT_APPLIED'
|
|
457
852
|
| 'IMPORT_TARGET_NOT_EMPTY'
|
|
458
853
|
| 'MIGRATION_IRREVERSIBLE'
|
|
459
|
-
| 'MIGRATION_OUT_OF_ORDER'
|
|
854
|
+
| 'MIGRATION_OUT_OF_ORDER'
|
|
855
|
+
| 'MIGRATION_BLOCKED'
|
|
856
|
+
| 'QUEUE_JOB_INVALID'
|
|
857
|
+
| 'QUEUE_JOB_FAILED'
|
|
858
|
+
| 'CONVERGE_FAILED';
|
|
460
859
|
|
|
461
860
|
// ─── Config file format ─────────────────────────────────────────────────────────
|
|
462
861
|
|
|
@@ -488,6 +887,44 @@ export interface UpOptions {
|
|
|
488
887
|
* Mutually exclusive with a filename and `steps`.
|
|
489
888
|
*/
|
|
490
889
|
to?: string;
|
|
890
|
+
/**
|
|
891
|
+
* Stamp this batch number on what the run applies, instead of the next free
|
|
892
|
+
* one ({@link MigratorKit.nextBatch}). A label, not a reservation: it may
|
|
893
|
+
* equal a batch already in use, which is how several single-file runs become
|
|
894
|
+
* one rollback unit — the queue adapter gives every job of an enqueue group
|
|
895
|
+
* the same value. Positive integer; mutually exclusive with `step`.
|
|
896
|
+
*/
|
|
897
|
+
batch?: number;
|
|
898
|
+
/**
|
|
899
|
+
* Refuse ({@link MigrationBlockedError}) to apply the named file while an
|
|
900
|
+
* earlier file on disk is still pending — the invariant a bulk `up` gets by
|
|
901
|
+
* construction, enforced from the changelog rather than from the caller's
|
|
902
|
+
* memory. Also makes the single-file run honour `strict` drift checks and
|
|
903
|
+
* `onOutOfOrder` like a bulk run. Requires a filename.
|
|
904
|
+
*/
|
|
905
|
+
ordered?: boolean;
|
|
906
|
+
/**
|
|
907
|
+
* The SHA-256 (hex) the named file must have — refuse ({@link
|
|
908
|
+
* ChecksumMismatchError}, `context.planned: true`) to apply any other
|
|
909
|
+
* version of it. A queue job carries the checksum its plan saw, so a worker
|
|
910
|
+
* from another deploy never applies a different file under the same name.
|
|
911
|
+
* Requires a filename; an already-applied file is skipped as usual.
|
|
912
|
+
*/
|
|
913
|
+
checksum?: string;
|
|
914
|
+
/**
|
|
915
|
+
* Converge the declared collections after the migrations, under the same
|
|
916
|
+
* lock — overrides `convergeAfterUp` for this call. Bulk runs only: refused
|
|
917
|
+
* with a filename or `to`.
|
|
918
|
+
*/
|
|
919
|
+
converge?: boolean;
|
|
920
|
+
/**
|
|
921
|
+
* Who asked for this run (≤ 128 characters) — stamped on the changelog
|
|
922
|
+
* records it writes. `executedBy` is the OS user that ran it; on a queue
|
|
923
|
+
* worker that is the container's, which is why the requester is separate.
|
|
924
|
+
*/
|
|
925
|
+
requestedBy?: string;
|
|
926
|
+
/** Why (≤ 512 characters) — a ticket, a sentence; stamped like `requestedBy` */
|
|
927
|
+
reason?: string;
|
|
491
928
|
}
|
|
492
929
|
|
|
493
930
|
/** Options for {@link MigratorKit.down} */
|
|
@@ -508,6 +945,20 @@ export interface DownOptions {
|
|
|
508
945
|
* to the same state. Mutually exclusive with `batch`, `steps` and a filename.
|
|
509
946
|
*/
|
|
510
947
|
to?: string;
|
|
948
|
+
/**
|
|
949
|
+
* Refuse ({@link MigrationBlockedError}) to revert the named file while a
|
|
950
|
+
* migration applied *after* it is still applied — reverts must go newest
|
|
951
|
+
* first (by `appliedAt`, the order `steps` uses). Requires a filename.
|
|
952
|
+
*/
|
|
953
|
+
ordered?: boolean;
|
|
954
|
+
/**
|
|
955
|
+
* Who asked for this run (≤ 128 characters) — stamped on the records it
|
|
956
|
+
* reverts (`revertRequestedBy`). `executedBy` is the OS user that ran it; on a queue
|
|
957
|
+
* worker that is the container's, which is why the requester is separate.
|
|
958
|
+
*/
|
|
959
|
+
requestedBy?: string;
|
|
960
|
+
/** Why (≤ 512 characters) — a ticket, a sentence; stamped as `revertReason` */
|
|
961
|
+
reason?: string;
|
|
511
962
|
}
|
|
512
963
|
|
|
513
964
|
/** Payload common to every lifecycle event */
|
|
@@ -531,7 +982,7 @@ export interface MigrationEvent extends MigronautEventBase {
|
|
|
531
982
|
}
|
|
532
983
|
|
|
533
984
|
export interface RunStartEvent extends MigronautEventBase {
|
|
534
|
-
/** Which command started the run: 'up' | 'down' | 'redo' | 'import' | 'baseline' */
|
|
985
|
+
/** Which command started the run: 'up' | 'down' | 'redo' | 'import' | 'baseline' | 'converge' */
|
|
535
986
|
command?: string;
|
|
536
987
|
direction?: 'up' | 'down';
|
|
537
988
|
}
|
|
@@ -565,10 +1016,62 @@ export interface LockEvent extends MigronautEventBase {
|
|
|
565
1016
|
acquireMs?: number;
|
|
566
1017
|
}
|
|
567
1018
|
|
|
1019
|
+
/** Who started a converge: the `converge` call itself, or a bulk `up` (`convergeAfterUp`) */
|
|
1020
|
+
export type ConvergeTrigger = 'converge' | 'up';
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
1024
|
+
*/
|
|
1025
|
+
export interface ConvergeStartEvent extends MigronautEventBase {
|
|
1026
|
+
trigger: ConvergeTrigger;
|
|
1027
|
+
/** Declared collections being converged */
|
|
1028
|
+
collections: number;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* One step a converge carried out (or failed)
|
|
1033
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
1034
|
+
*/
|
|
1035
|
+
export interface ConvergeActionEvent extends MigronautEventBase {
|
|
1036
|
+
collection: string;
|
|
1037
|
+
target: ConvergeTarget;
|
|
1038
|
+
name: string;
|
|
1039
|
+
action: ConvergeActionKind;
|
|
1040
|
+
/**
|
|
1041
|
+
* `'started'` fires before the step runs — an index build can take hours,
|
|
1042
|
+
* and this is how a subscriber sees which one is in progress; `'applied'`
|
|
1043
|
+
* or `'failed'` follows when it ends.
|
|
1044
|
+
*/
|
|
1045
|
+
status: 'started' | 'applied' | 'failed';
|
|
1046
|
+
/** `'applied'` only */
|
|
1047
|
+
durationMs?: number;
|
|
1048
|
+
reason?: string;
|
|
1049
|
+
/** Redacted failure message (status `'failed'` only) */
|
|
1050
|
+
error?: string;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* @experimental New in 2.1 — the shape may still change in a minor release (named in the CHANGELOG).
|
|
1055
|
+
*/
|
|
1056
|
+
export interface ConvergeEndEvent extends MigronautEventBase {
|
|
1057
|
+
trigger: ConvergeTrigger;
|
|
1058
|
+
success: boolean;
|
|
1059
|
+
durationMs: number;
|
|
1060
|
+
changed: number;
|
|
1061
|
+
inSync: boolean;
|
|
1062
|
+
/** Rows per action kind */
|
|
1063
|
+
counts: Partial<Record<ConvergeActionKind, number>>;
|
|
1064
|
+
/** The full result — partial on the failure path */
|
|
1065
|
+
result: ConvergeResult;
|
|
1066
|
+
/** Redacted failure message */
|
|
1067
|
+
error?: string;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
568
1070
|
/**
|
|
569
1071
|
* Lifecycle events emitted by {@link MigratorKit}. Subscribe to feed metrics or
|
|
570
1072
|
* alerting without parsing log lines; a listener that throws is contained and
|
|
571
|
-
* never fails the run.
|
|
1073
|
+
* never fails the run. The `converge:*` events fire for real converge runs
|
|
1074
|
+
* only, not for a dry run.
|
|
572
1075
|
*/
|
|
573
1076
|
export interface MigronautEvents {
|
|
574
1077
|
'run:start': (event: RunStartEvent) => void;
|
|
@@ -580,6 +1083,9 @@ export interface MigronautEvents {
|
|
|
580
1083
|
'lock:acquired': (event: LockEvent) => void;
|
|
581
1084
|
'lock:released': (event: LockEvent) => void;
|
|
582
1085
|
'lock:lost': (event: LockEvent) => void;
|
|
1086
|
+
'converge:start': (event: ConvergeStartEvent) => void;
|
|
1087
|
+
'converge:action': (event: ConvergeActionEvent) => void;
|
|
1088
|
+
'converge:end': (event: ConvergeEndEvent) => void;
|
|
583
1089
|
}
|
|
584
1090
|
|
|
585
1091
|
/** One check performed by {@link MigratorKit.audit} */
|
|
@@ -599,10 +1105,20 @@ export interface AuditReport {
|
|
|
599
1105
|
checks: AuditCheck[];
|
|
600
1106
|
}
|
|
601
1107
|
|
|
1108
|
+
/** Options for {@link MigratorKit.status} and {@link MigratorKit.list} */
|
|
1109
|
+
export interface StatusOptions {
|
|
1110
|
+
/** Hash applied files to fill `checksumOk`. Default true */
|
|
1111
|
+
checksums?: boolean;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
602
1114
|
/** Options for {@link MigratorKit.redo} */
|
|
603
1115
|
export interface RedoOptions {
|
|
604
1116
|
/** Skip lock acquisition (dev only) */
|
|
605
1117
|
noLock?: boolean;
|
|
1118
|
+
/** Who asked — stamped on the revert and on the re-apply */
|
|
1119
|
+
requestedBy?: string;
|
|
1120
|
+
/** Why — stamped like `requestedBy` */
|
|
1121
|
+
reason?: string;
|
|
606
1122
|
}
|
|
607
1123
|
|
|
608
1124
|
/** Options for {@link MigratorKit.create} */
|
|
@@ -720,6 +1236,19 @@ export class MigratorKit extends EventEmitter {
|
|
|
720
1236
|
* removed, or null if no lock was held.
|
|
721
1237
|
*/
|
|
722
1238
|
forceUnlock(): Promise<LockInfo | null>;
|
|
1239
|
+
/**
|
|
1240
|
+
* The batch number the next `up` would use (highest recorded batch + 1,
|
|
1241
|
+
* reverted and failed records included). A peek, not a reservation — pair it
|
|
1242
|
+
* with `up(name, { batch })` to stamp several single-file runs as one batch.
|
|
1243
|
+
* Connects if needed.
|
|
1244
|
+
*/
|
|
1245
|
+
nextBatch(): Promise<number>;
|
|
1246
|
+
/**
|
|
1247
|
+
* A new id in this kit's configured format — the `generateId` option, else a
|
|
1248
|
+
* random UUID. The same source every run id comes from, for code that wants
|
|
1249
|
+
* its own ids to match. Resolves the config; does not connect.
|
|
1250
|
+
*/
|
|
1251
|
+
generateId(): Promise<string>;
|
|
723
1252
|
/** Run all pending migrations, or a specific named file */
|
|
724
1253
|
up(filename?: string, options?: UpOptions): Promise<RunResult[]>;
|
|
725
1254
|
/** Rollback the last batch, a specific batch, a specific file, or the last N steps */
|
|
@@ -749,16 +1278,22 @@ export class MigratorKit extends EventEmitter {
|
|
|
749
1278
|
filename?: string,
|
|
750
1279
|
options?: { steps?: number; batch?: number; to?: string },
|
|
751
1280
|
): Promise<StatusRow[]>;
|
|
752
|
-
/**
|
|
753
|
-
|
|
1281
|
+
/**
|
|
1282
|
+
* Full migration status for all known files and records. `checksums: false`
|
|
1283
|
+
* skips hashing the applied files (`checksumOk` stays null).
|
|
1284
|
+
*/
|
|
1285
|
+
status(options?: StatusOptions): Promise<StatusRow[]>;
|
|
754
1286
|
/**
|
|
755
1287
|
* Read-only health check: configuration, connectivity, transaction support,
|
|
756
1288
|
* changelog indexes, lock state, checksum drift and runtime. Reports
|
|
757
1289
|
* problems; fixes none of them.
|
|
758
1290
|
*/
|
|
759
1291
|
audit(): Promise<AuditReport>;
|
|
760
|
-
/**
|
|
761
|
-
|
|
1292
|
+
/**
|
|
1293
|
+
* Filtered list of migrations. Default: 'all'. `checksums: false` skips
|
|
1294
|
+
* hashing the applied files — for a caller that needs names and dates only.
|
|
1295
|
+
*/
|
|
1296
|
+
list(filter?: 'all' | 'pending' | 'applied', options?: StatusOptions): Promise<StatusRow[]>;
|
|
762
1297
|
/** Create a new migration file and return its absolute path */
|
|
763
1298
|
create(name: string, options?: CreateOptions): Promise<string>;
|
|
764
1299
|
/** Create a migronaut config file in the working directory and return its path */
|
|
@@ -776,6 +1311,25 @@ export class MigratorKit extends EventEmitter {
|
|
|
776
1311
|
* are skipped, so a partial baseline can simply be re-run.
|
|
777
1312
|
*/
|
|
778
1313
|
baseline(options?: BaselineOptions): Promise<BaselineSummary>;
|
|
1314
|
+
/**
|
|
1315
|
+
* Bring the declared collections (`collections`, `collectionsDir`) to their
|
|
1316
|
+
* declared indexes and validators. Stateless: the live database is read and
|
|
1317
|
+
* compared on every call; what a run changed is appended to the converge
|
|
1318
|
+
* history ({@link MigratorKit.convergeHistory}), which no run reads back. A
|
|
1319
|
+
* real run holds the migration lock; a plan with a conflict is refused before
|
|
1320
|
+
* any write, and a failed step throws {@link ConvergeFailedError}. Experimental.
|
|
1321
|
+
*/
|
|
1322
|
+
converge(options?: ConvergeOptions): Promise<ConvergeResult>;
|
|
1323
|
+
/**
|
|
1324
|
+
* Whether a bulk `up` on this kit ends by converging: `convergeAfterUp` is
|
|
1325
|
+
* on and something is declared. Resolves the config; does not connect.
|
|
1326
|
+
*/
|
|
1327
|
+
convergesAfterUp(): Promise<boolean>;
|
|
1328
|
+
/**
|
|
1329
|
+
* The converge history, newest first (`limit` 1–1000, default 20): one entry
|
|
1330
|
+
* per converge that changed something or failed. Read-only.
|
|
1331
|
+
*/
|
|
1332
|
+
convergeHistory(options?: { limit?: number }): Promise<ConvergeHistoryEntry[]>;
|
|
779
1333
|
}
|
|
780
1334
|
|
|
781
1335
|
// ─── Programmatic entry points ─────────────────────────────────────────────────
|
|
@@ -799,11 +1353,24 @@ export interface RunMigrationsOptions extends MigratorKitOptions {
|
|
|
799
1353
|
* progress**. While the holder's heartbeat visibly advances its lock, the
|
|
800
1354
|
* deadline is re-armed — a healthy peer working through a long backlog never
|
|
801
1355
|
* times its waiting peers out; only a stalled holder runs this budget down.
|
|
802
|
-
* Default: 90000.
|
|
1356
|
+
* Default: 90000, or 1.5× the holder's lock TTL when that is longer — its
|
|
1357
|
+
* heartbeat only moves the lock every TTL/2, and a crashed holder's lock is
|
|
1358
|
+
* reclaimable only after a full TTL. An explicit value is used as given.
|
|
803
1359
|
*/
|
|
804
1360
|
lockWaitTimeoutMs?: number;
|
|
805
|
-
/**
|
|
1361
|
+
/**
|
|
1362
|
+
* First poll interval (ms) while waiting for the lock. Polls back off from
|
|
1363
|
+
* it, doubling, up to 5 s (and never more than a quarter of the wait budget).
|
|
1364
|
+
* Default: 500
|
|
1365
|
+
*/
|
|
806
1366
|
lockPollIntervalMs?: number;
|
|
1367
|
+
/**
|
|
1368
|
+
* Abort the call: a wait for the lock stops between polls, and a run that
|
|
1369
|
+
* holds it stops between migrations (one already executing finishes), with
|
|
1370
|
+
* a {@link RunAbortedError}. Wire it to SIGTERM so a pod being shut down
|
|
1371
|
+
* does not take the lock just before it is killed.
|
|
1372
|
+
*/
|
|
1373
|
+
signal?: AbortSignal;
|
|
807
1374
|
/**
|
|
808
1375
|
* Receives the internally-constructed {@link MigratorKit} right after
|
|
809
1376
|
* construction (before connect), so an embedding application can subscribe
|
|
@@ -822,10 +1389,12 @@ export interface MigrationSummary {
|
|
|
822
1389
|
upToDate: boolean;
|
|
823
1390
|
/** True when this instance waited for a peer to release the lock before running */
|
|
824
1391
|
waited: boolean;
|
|
825
|
-
/**
|
|
1392
|
+
/** Time (ms) from the first refusal to the run, by the clock. 0 when the lock was free */
|
|
826
1393
|
waitedMs: number;
|
|
827
1394
|
/** Number of `up` attempts made — 1 when the lock was free on the first try */
|
|
828
1395
|
attempts: number;
|
|
1396
|
+
/** The converge that ended the run — present only when `convergeAfterUp` converged */
|
|
1397
|
+
converge?: ConvergeResult;
|
|
829
1398
|
}
|
|
830
1399
|
|
|
831
1400
|
/**
|
|
@@ -849,14 +1418,17 @@ export function pendingMigrations(
|
|
|
849
1418
|
): Promise<StatusRow[]>;
|
|
850
1419
|
|
|
851
1420
|
/**
|
|
852
|
-
* The CLI's exit-code map: one entry per {@link MigronautErrorCode}, plus
|
|
1421
|
+
* The CLI's exit-code map: one entry per {@link MigronautErrorCode}, plus three
|
|
853
1422
|
* CLI-condition codes with no error class — `PENDING_MIGRATIONS` (from
|
|
854
|
-
* `status --check`)
|
|
855
|
-
*
|
|
856
|
-
* success is 0.
|
|
1423
|
+
* `status --check`), `AUDIT_FAILED` and `COLLECTIONS_DRIFT` (from
|
|
1424
|
+
* `converge --check`). Lets a wrapper script mirror the CLI's exit semantics
|
|
1425
|
+
* without hardcoding numbers. Anything unmapped exits 1; success is 0.
|
|
857
1426
|
*/
|
|
858
1427
|
export const EXIT_CODES: Readonly<
|
|
859
|
-
Record<
|
|
1428
|
+
Record<
|
|
1429
|
+
MigronautErrorCode | 'PENDING_MIGRATIONS' | 'AUDIT_FAILED' | 'COLLECTIONS_DRIFT',
|
|
1430
|
+
number
|
|
1431
|
+
>
|
|
860
1432
|
>;
|
|
861
1433
|
|
|
862
1434
|
// ─── Logger factory ───────────────────────────────────────────────────────────
|
|
@@ -1013,3 +1585,47 @@ export class IrreversibleMigrationError extends MigronautError {
|
|
|
1013
1585
|
export class OutOfOrderMigrationError extends MigronautError {
|
|
1014
1586
|
constructor(message: string, context?: Record<string, unknown>, options?: MigronautErrorOptions);
|
|
1015
1587
|
}
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* Thrown by an `ordered` single-file `up`/`down` that would run out of
|
|
1591
|
+
* sequence: an earlier migration is still pending (`up`), or one applied later
|
|
1592
|
+
* is still applied (`down`). `context.name`, `context.direction` and
|
|
1593
|
+
* `context.blockedBy` (the migrations that must go first).
|
|
1594
|
+
*/
|
|
1595
|
+
export class MigrationBlockedError extends MigronautError {
|
|
1596
|
+
constructor(message: string, context?: Record<string, unknown>, options?: MigronautErrorOptions);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/**
|
|
1600
|
+
* Thrown by the queue adapter (`@alexify/migronaut/bullmq`) when a job's
|
|
1601
|
+
* payload fails the contract check — an unknown job name or data version, a
|
|
1602
|
+
* migration name that is not a bare filename, malformed group fields. Job data
|
|
1603
|
+
* is untrusted input. `context.jobId`, `context.issue`.
|
|
1604
|
+
*/
|
|
1605
|
+
export class QueueJobInvalidError extends MigronautError {
|
|
1606
|
+
constructor(message: string, context?: Record<string, unknown>, options?: MigronautErrorOptions);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* Thrown by a queue group's `wait()` when one of its jobs failed or the wait
|
|
1611
|
+
* timed out (one budget for the whole call). `context.failedReason` is the
|
|
1612
|
+
* worker's (redacted) message, `context.code` the job's own typed error code
|
|
1613
|
+
* when it reported one (`MIGRATION_BLOCKED`, `CHECKSUM_MISMATCH`, …),
|
|
1614
|
+
* `context.results` the jobs that finished before it, plus `groupId`, `jobId`,
|
|
1615
|
+
* `migration`, `direction` and `timedOut`.
|
|
1616
|
+
*/
|
|
1617
|
+
export class QueueJobFailedError extends MigronautError {
|
|
1618
|
+
constructor(message: string, context?: Record<string, unknown>, options?: MigronautErrorOptions);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* Thrown by {@link MigratorKit.converge} when the database cannot be brought
|
|
1623
|
+
* to the declared state. `context.phase` is `'plan'` for a refused plan
|
|
1624
|
+
* (`context.conflicts` lists why; nothing was written) or `'apply'` for a
|
|
1625
|
+
* failed step (`collection`, `target`, `name`, `action`, `cause`, and
|
|
1626
|
+
* `mongoCode`, `hint` and — after a failed rebuild — `restored` when they
|
|
1627
|
+
* apply). `context.converge` is the {@link ConvergeResult} so far.
|
|
1628
|
+
*/
|
|
1629
|
+
export class ConvergeFailedError extends MigronautError {
|
|
1630
|
+
constructor(message: string, context?: Record<string, unknown>, options?: MigronautErrorOptions);
|
|
1631
|
+
}
|