first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
# dry-run-reporter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`DryRunReporter` collects per-migration entries during a dry-run pass and produces a structured `DryRunReport` with an aggregate risk level. It allows callers to understand which migrations would be applied, their individual risk levels, and the worst-case overall risk without executing any SQL. This is particularly valuable in CI pipelines and change-review workflows.
## Features
- `record(entry)` — accumulates a `DryRunEntry` (migrationId, direction, riskLevel, optional reason).
- `report()` — returns a snapshot of all entries, the computed total risk, and a human-readable summary.
- Risk level aggregation: `totalRisk` is the maximum across all entries (`low` < `medium` < `high`).
- Summary string differentiates between `up` and `down` counts.
## Usage: Basic Example
```ts
import { DryRunReporter } from "@suaveplan/migration";
const reporter = new DryRunReporter();
reporter.record({ migrationId: "001", direction: "up", riskLevel: "low" });
const report = reporter.report();
// report.totalRisk === "low"
// report.summary === "Dry run: 1 up migration(s) would be applied. Overall risk: low."
```
## Usage: Advanced Example
```ts
import { DryRunReporter } from "@suaveplan/migration";
const reporter = new DryRunReporter();
reporter.record({ migrationId: "001", direction: "up", riskLevel: "low", reason: "add table" });
reporter.record({ migrationId: "002", direction: "up", riskLevel: "high", reason: "drop column" });
const report = reporter.report();
// report.totalRisk === "high"
```
## API Reference
### `class DryRunReporter`
- `record(entry: DryRunEntry): void` — appends an entry. `entry` fields: `migrationId`, `direction`, `riskLevel` (`"low"|"medium"|"high"`), optional `reason`.
- `report(): DryRunReport` — returns `{ entries, totalRisk, summary }`.
### `DryRunReport`
- `entries: DryRunEntry[]` — snapshot of all recorded entries.
- `totalRisk: RiskLevel` — maximum risk level across all entries.
- `summary: string` — human-readable description.
## Implementation Notes
`RISK_ORDER` maps risk levels to integers for comparison. `report()` returns a spread copy of `entries` so callers cannot mutate the internal list. When no entries are recorded, `totalRisk` is `"low"` and the summary says "No migrations to apply."

View File

@@ -0,0 +1,61 @@
# migration-executor
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`MigrationExecutor` takes a `MigrationPlan` and actually runs each migration in the specified direction, recording results in the store, emitting lifecycle events, and surfacing structured errors. It is the only component in the migration package that performs database writes. Separating execution from planning makes it easy to test each in isolation and to slot in different execution strategies (e.g. transactional, concurrent) without changing the planner logic.
## Features
- Runs `toApply` (direction `up`) or `toRollback` (direction `down`) from the plan.
- AbortSignal cancellation between each migration step.
- Checksum validation before applying already-recorded migrations.
- Dry-run mode: records which migrations would run without executing SQL.
- Event emission: `migration:plan`, `migration:before`, `migration:after`, `migration:failed`.
- Wraps all thrown errors in `MigrationError(MIGRATION.FAILED)` unless already a `MigrationError`.
## Usage: Basic Example
```ts
import { MigrationExecutor } from "@suaveplan/migration";
const executor = new MigrationExecutor({ store, context, dryRun: false });
const result = await executor.execute(plan, "up");
```
## Usage: Advanced Example
```ts
import { MigrationExecutor } from "@suaveplan/migration";
import { EventBus } from "@suaveplan/events";
const events = new EventBus<MigrationEventMap>();
events.on("migration:after", ({ migration, durationMs }) => {
console.log(`${migration.id} applied in ${durationMs}ms`);
});
const executor = new MigrationExecutor({ store, context, events });
const result = await executor.execute(plan, "up", { signal });
```
## API Reference
### `class MigrationExecutor`
**Constructor**: `(options: ExecutorOptions, clock?: Clock)`
- `options.store` — migration store adapter for recording results.
- `options.context` — migration context injected into each migration function.
- `options.events?` — optional event bus for lifecycle events.
- `options.dryRun?` — if `true`, collects applied IDs without running SQL (default `false`).
- `options.checksumValidation?` — if `true`, validates checksums on up-direction runs (default `true`).
### `execute(plan, direction, runOptions?): Promise<MigrationResult>`
Returns `{ applied, skipped, failed, direction, dryRun }`. Throws `MigrationError` on abort, checksum mismatch, or execution failure.
## Implementation Notes
Checksum validation runs only for `up` migrations that already have a ledger record with a checksum — it does not block the first application. The `down` direction requires `migration.down` to be defined; if absent, a `MIGRATION.NOT_FOUND` error is thrown.

View File

@@ -0,0 +1,84 @@
# migration-runner
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`MigrationRunner` is the top-level orchestrator for the migration package. It wires together the planner, executor, store, lock, and event bus into a single high-level API. Consumers create a runner with their migration list and store adapter, then call `up()` to apply pending migrations, `down()` to roll back, `status()` to inspect the current state, and `validate()` to verify migration metadata consistency before running.
## Features
- `up(options?)` — plans and applies all pending migrations; respects advisory locks, AbortSignal, and dry-run mode.
- `down(options?)` — plans and rolls back the last N applied migrations.
- `status()` — returns combined status of all registered migrations.
- `validate()` — checks for duplicate IDs and returns structured validation errors.
- Injectable `Clock`, `LoggerLike`, `EventBus`, and `MigrationLockAdapter`.
- `createMigrationRunner` factory function for ergonomic construction.
## Usage: Basic Example
```ts
import { MigrationRunner, MemoryMigrationStore } from "@suaveplan/migration";
const store = new MemoryMigrationStore();
const runner = new MigrationRunner({ migrations, store });
const result = await runner.up();
// result.applied lists migration IDs that were applied
```
## Usage: Advanced Example
```ts
import { MigrationRunner, MemoryMigrationStore, MemoryMigrationLock } from "@suaveplan/migration";
import { EventBus } from "@suaveplan/events";
const store = new MemoryMigrationStore();
const lock = new MemoryMigrationLock();
const events = new EventBus<MigrationEventMap>();
events.on("migration:after", ({ migration, durationMs }) => {
console.log(`Applied ${migration.id} in ${durationMs}ms`);
});
const runner = new MigrationRunner({ migrations, store, lock, events, checksumValidation: true });
await runner.up({ signal: abortController.signal });
```
## API Reference
### `class MigrationRunner`
**Constructor**: `(config: MigrationRunnerConfig, clock?: Clock)`
- `config.migrations` — array of `Migration` objects.
- `config.store` — `MigrationStoreAdapter` for ledger persistence.
- `config.lock?` — optional `MigrationLockAdapter`.
- `config.logger?` — optional `LoggerLike` for structured log output.
- `config.events?` — optional `EventBus<MigrationEventMap>`.
- `config.dryRun?` — default `false`.
- `config.checksumValidation?` — default `true`.
### `up(options?: RunOptions): Promise<MigrationResult>`
Returns `{ applied, skipped, failed, direction: "up", dryRun }`.
### `down(options?: RunOptions & { steps?: number }): Promise<MigrationResult>`
Returns `{ applied, skipped, failed, direction: "down", dryRun }`.
### `status(): Promise<MigrationStatusEntry[]>`
Returns status for every registered migration sorted by id.
### `validate(): Promise<ValidationResult>`
Returns `{ valid, errors }`.
### `createMigrationRunner(config, clock?): MigrationRunner`
Factory function — equivalent to `new MigrationRunner(config, clock)`.
## Implementation Notes
The `buildContext()` method provides a no-op `execute`/`query` implementation suitable for runners that do not need direct database access in migration functions (e.g. when the migration functions close over an external adapter). Consumers requiring SQL execution in migration functions should provide a custom context via a subclass.