3.4 KiB
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. AbortSignalpropagation into the runner.- Pluggable
clockandlogger. - Wrapped error surface: every internal failure surfaces
DATABASE.MIGRATION_FAILED.
Usage: Basic
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
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
migrationAdapteradapts theDatabaseAdapter.query(sql, params: readonly unknown[])signature toapi-migrations'SurrealqlClient.query(sql, vars: Record<string, unknown>)shape —varsis normalised to positionalObject.values(vars)since the surrealql adapter only uses$nameplaceholders in SurrealQL and thequeryresolves them throughparamsToRecord. - 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.autoRunis consulted by the plugin factory, not the runner — callers that invokerunMigrationsdirectly should respect it themselves.