first commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# `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.
|
||||
Reference in New Issue
Block a user