127 lines
6.8 KiB
Markdown
127 lines
6.8 KiB
Markdown
# @suaveplan/api-migrations - Database-agnostic migration runner for the Suaveplan API — narrow MigrationAdapter surface with per-dialect DDL bootstrap, real SHA-256 checksum validation against on-disk files, AbortSignal cancellation between migrations, OTel counter + histogram per apply, MigrationError with six MIGRATION.* codes. Ships three adapters (surrealql, postgres, sqlite); MySQL slot reserved in the dialect union. Server-tier — Bun + Node.
|
|
|
|
Database-agnostic migration runner for the Suaveplan API. Ships a narrow
|
|
`MigrationAdapter` surface (six methods), three shipped adapters (`surrealql`,
|
|
`postgres`, `sqlite`), a `MigrationRunner` class that ACTUALLY validates
|
|
SHA-256 checksums against on-disk migration files before each apply phase,
|
|
`AbortSignal` propagation between migrations, OTel counter + histogram per
|
|
apply attempt, and `MigrationError` with six namespaced `MIGRATION.*` codes.
|
|
|
|
Splits and rewrites the upstream `@suaveplan/api`'s `database/migration-runner.ts`
|
|
(331 LOC). fixes baked in:
|
|
|
|
1. Narrow `MigrationAdapter` surface — the runner depends on six methods, not
|
|
the whole `DatabaseLike`. Each adapter ships its own `ensureTable` DDL.
|
|
2. Checksums are ACTUALLY validated — drift halts apply with
|
|
`MigrationError(MIGRATION.CHECKSUM_DRIFT)` and `{name, expected, actual}`
|
|
context. Upstream's `validateChecksums: true` was a no-op; this one isn't.
|
|
3. `AbortSignal` propagation — `runFromDirectory(dir, { signal })` honors
|
|
cancellation between migrations.
|
|
4. OTel counter `migrations.applied_total{dialect, outcome}` and histogram
|
|
`migrations.apply_duration_ms{name}` emit on every attempt regardless of
|
|
success.
|
|
5. `MigrationError extends SuaveplanError<MigrationErrorCode>` via
|
|
`defineCodes("MIGRATION", [...])` — six codes, every one raised from a
|
|
production call site.
|
|
|
|
<!-- begin:auto-docs -->
|
|
## Documentation tree
|
|
|
|
- [postgres](./src/adapters/postgres/postgres.md) — Implements the `MigrationAdapter` contract against any Postgres-wire-protocol server (Postgres ≥ 12, CockroachDB, Yugab…
|
|
- [postgres](./src/adapters/postgres/postgres.md) — Implements the `MigrationAdapter` contract against any Postgres-wire-protocol server (Postgres ≥ 12, CockroachDB, Yugab…
|
|
- [sqlite](./src/adapters/sqlite/sqlite.md) — Implements the `MigrationAdapter` contract for SQLite-family engines. The factory `createSqliteAdapter(client)` accepts…
|
|
- [sqlite](./src/adapters/sqlite/sqlite.md) — Implements the `MigrationAdapter` contract for SQLite-family engines. The factory `createSqliteAdapter(client)` accepts…
|
|
- [surrealql](./src/adapters/surrealql/surrealql.md) — Implements the `MigrationAdapter` contract for SurrealDB. The factory `createSurrealqlAdapter(client)` wraps any Surrea…
|
|
- [surrealql](./src/adapters/surrealql/surrealql.md) — Implements the `MigrationAdapter` contract for SurrealDB. The factory `createSurrealqlAdapter(client)` wraps any Surrea…
|
|
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable, namespaced error registry for `@suaveplan/api-migrations`. Every public failure path in the package…
|
|
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable, namespaced error registry for `@suaveplan/api-migrations`. Every public failure path in the package…
|
|
- [otel](./src/otel/instruments.md) — Lazily builds and caches the two OpenTelemetry instruments that `@suaveplan/api-migrations` emits on every migration ap…
|
|
- [instruments](./src/otel/instruments.md) — Lazily builds and caches the two OpenTelemetry instruments that `@suaveplan/api-migrations` emits on every migration ap…
|
|
- [runner](./src/runner/runner.md) — The `MigrationRunner` is the dialect-agnostic core of the package. It reads migration files from a directory, sorts the…
|
|
- [runner](./src/runner/runner.md) — The `MigrationRunner` is the dialect-agnostic core of the package. It reads migration files from a directory, sorts the…
|
|
- [types](./src/types/migration.md) — Declares every shared type that the runner, adapters, and consumers exchange across the `@suaveplan/api-migrations` pac…
|
|
- [migration](./src/types/migration.md) — Declares every shared type that the runner, adapters, and consumers exchange across the `@suaveplan/api-migrations` pac…
|
|
<!-- end:auto-docs -->
|
|
|
|
## Install
|
|
|
|
```bash
|
|
bun add @suaveplan/api-migrations
|
|
```
|
|
|
|
## Quick start
|
|
|
|
```ts
|
|
import { MigrationRunner } from "@suaveplan/api-migrations/runner/runner";
|
|
import { createSqliteAdapter } from "@suaveplan/api-migrations/adapters/sqlite/sqlite";
|
|
|
|
const runner = new MigrationRunner(createSqliteAdapter(client), {
|
|
fileExtension: ".sql",
|
|
});
|
|
|
|
const controller = new AbortController();
|
|
const results = await runner.runFromDirectory("./db/migrations", {
|
|
signal: controller.signal,
|
|
});
|
|
for (const r of results) {
|
|
if (!r.success) throw new Error(`Apply halted at ${r.name}: ${r.error}`);
|
|
}
|
|
```
|
|
|
|
## Checksum-drift recovery
|
|
|
|
When `validateChecksums: true` (default) and a previously applied
|
|
migration's on-disk content has changed, the runner halts before applying
|
|
any pending migration:
|
|
|
|
```ts
|
|
try {
|
|
await runner.runFromDirectory("./db/migrations");
|
|
} catch (err) {
|
|
if (
|
|
err instanceof MigrationError &&
|
|
err.code === MIGRATION_CODES.CHECKSUM_DRIFT
|
|
) {
|
|
// err.context = { name, expected, actual }
|
|
// Resolution:
|
|
// 1. Restore the migration file to its applied form (`git checkout`
|
|
// the file at the commit it was applied from).
|
|
// 2. If the change is intentional, ship a NEW migration file with
|
|
// the additive change. Never rewrite history.
|
|
}
|
|
throw err;
|
|
}
|
|
```
|
|
|
|
## Modules
|
|
|
|
- [`runner/runner`](./src/runner/runner.md) — `MigrationRunner` class +
|
|
`createMigrationRunner` factory + `computeChecksum`.
|
|
- [`adapters/surrealql/surrealql`](./src/adapters/surrealql/surrealql.md) —
|
|
SurrealDB adapter with SurrealQL DDL.
|
|
- [`adapters/postgres/postgres`](./src/adapters/postgres/postgres.md) —
|
|
PostgreSQL adapter with standard SQL DDL.
|
|
- [`adapters/sqlite/sqlite`](./src/adapters/sqlite/sqlite.md) — SQLite
|
|
adapter compatible with `sqlite-wasm` / `bun:sqlite` / `better-sqlite3`.
|
|
- [`otel/instruments`](./src/otel/instruments.md) — counter + histogram.
|
|
- [`error-codes/error-codes`](./src/error-codes/error-codes.md) — six
|
|
`MIGRATION.*` codes + `MigrationError`.
|
|
- [`types/migration`](./src/types/migration.md) — public type surface.
|
|
|
|
## Subpath exports
|
|
|
|
Every feature module is independently importable for maximum
|
|
tree-shakeability:
|
|
|
|
```ts
|
|
import { MigrationRunner } from "@suaveplan/api-migrations/runner/runner";
|
|
import { createPostgresAdapter } from "@suaveplan/api-migrations/adapters/postgres/postgres";
|
|
import { MIGRATION_CODES } from "@suaveplan/api-migrations/error-codes/error-codes";
|
|
```
|
|
|
|
## Tier
|
|
|
|
Server (Bun + Node). Uses `node:crypto` for SHA-256 and `node:fs/promises`
|
|
for directory scanning and file reads.
|
|
|