Files
skills/docs/api-stack/api/api-plugin-database/src/migrations/runner.md
2026-05-29 08:20:56 +01:00

75 lines
3.4 KiB
Markdown

# `migrations/runner`
## Purpose
Implements `runMigrations`, the top-level orchestrator for the migration phase of `createDatabasePlugin`'s `onReady` hook. It composes the advisory lock from `./lock.ts` with `@suaveplan/api-migrations`' battle-tested SurrealQL adapter and runner: lock first, run if acquired, release in `finally`. Calling code never has to touch the migration discovery, ledger, or checksum-drift detection — those concerns live entirely inside `api-migrations`.
The runner is intentionally non-throwing for the contention case: when another holder already owns the lock, the function returns `{ acquired: false, applied: [], skipped: [] }` so the caller can log and move on. Other failures (adapter errors, file system errors, ledger drift) propagate as `DatabaseError(DATABASE.MIGRATION_FAILED)` with the underlying error attached as `cause`.
## Features
- Advisory-locked migration run via `acquireLock` + `releaseLock`.
- Delegates discovery, hashing, ledger, and drift detection to `@suaveplan/api-migrations`' `createMigrationRunner`.
- `AbortSignal` propagation into the runner.
- Pluggable `clock` and `logger`.
- Wrapped error surface: every internal failure surfaces `DATABASE.MIGRATION_FAILED`.
## Usage: Basic
```ts
import { runMigrations } from "@suaveplan/api-plugin-database/migrations/runner";
const result = await runMigrations({
adapter: db,
config: {
autoRun: true,
directory: "./surql",
ledgerTable: "_migrations",
lockTable: "_migration_lock",
leaseMs: 60_000,
},
holder: `${process.pid}@${Date.now()}`,
});
if (result.acquired) {
console.log(`Applied ${result.applied.length} migrations`);
}
```
## Usage: Advanced
```ts
import { runMigrations } from "@suaveplan/api-plugin-database";
const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000);
let now = 1_000_000;
const result = await runMigrations({
adapter: pooledDb,
config: migrations,
holder: "ci-runner",
clock: () => now,
signal: controller.signal,
logger: pinoLogger,
});
```
## API Reference
### `runMigrations(options): Promise<MigrationRunResult>`
Acquires the advisory lock, runs `.surql` migrations through `api-migrations`, and releases the lock. Returns `{ acquired, applied, skipped }`.
### `interface RunMigrationsOptions`
`adapter: DatabaseAdapter`, `config: MigrationConfig`, `holder: string`, optional `logger`, `clock`, `signal`.
### `interface MigrationRunResult`
`{ acquired: boolean, applied: ApiMigrationResult[], skipped: string[] }`. `applied` is the runner's per-file outcome list; `skipped` is currently empty (the runner returns whichever files were applied or `[]`).
## Implementation Notes
- The intermediate `migrationAdapter` adapts the `DatabaseAdapter.query(sql, params: readonly unknown[])` signature to `api-migrations`' `SurrealqlClient.query(sql, vars: Record<string, unknown>)` shape — `vars` is normalised to positional `Object.values(vars)` since the surrealql adapter only uses `$name` placeholders in SurrealQL and the `query` resolves them through `paramsToRecord`.
- The lock is released in `finally`; an exception during release does not mask the original error because the runner phase already completed (or rethrew) at that point.
- `MigrationConfig.autoRun` is consulted by the plugin factory, not the runner — callers that invoke `runMigrations` directly should respect it themselves.