75 lines
3.4 KiB
Markdown
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.
|