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

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.
  • AbortSignal propagation into the runner.
  • Pluggable clock and logger.
  • 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 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.