# `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` 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)` 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.