Initial Commit
This commit is contained in:
126
packages/api-stack/api/api-migrations/README.md
Normal file
126
packages/api-stack/api/api-migrations/README.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# @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.
|
||||
|
||||
Reference in New Issue
Block a user