first commit
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# `migrations/run-migrations` — per-engine migration runner
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`runMigrations` applies a plugin's pending migrations from a directory of
|
||||
`*.sql` files, recording applied versions in the engine-side
|
||||
`__plugin_migrations(plugin, version, applied_at)` table. It is the single
|
||||
entry point used by `installing` step of the plugin lifecycle FSM; the
|
||||
plugin host calls it once per `(plugin, engine)` pair during boot.
|
||||
|
||||
The runner is engine-neutral: it issues only standard SQL (`CREATE TABLE
|
||||
IF NOT EXISTS`, `SELECT`, `INSERT`, plus the migration body). When the
|
||||
underlying adapter exposes `transaction`, each migration is applied
|
||||
inside an atomic transaction; on failure the transaction rolls back and
|
||||
the row in `__plugin_migrations` is not written.
|
||||
|
||||
## Features
|
||||
|
||||
- Lexicographic file ordering (`001_init.sql` before `002_*.sql`).
|
||||
- Idempotent re-run — applied versions are tracked in
|
||||
`__plugin_migrations`; a second call with the same file set applies
|
||||
zero migrations.
|
||||
- Per-migration `transaction { body + INSERT }` for engines that support
|
||||
transactions; flat `execute + execute` fall-back for those that
|
||||
don't (e.g. some Surreal modes).
|
||||
- OTel instruments: `api.db.migrations_applied_total{plugin}` counter +
|
||||
`api.db.migration_duration_ms{plugin}` histogram.
|
||||
- Injectable `fs: MigrationsFsReader` — tests use an in-memory map,
|
||||
production wires `node:fs/promises`.
|
||||
- On any failure: `DB_ADAPTER.MIGRATION_FAILED` with cause + details
|
||||
including the failing file name.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { runMigrations } from "@suaveplan/api-db-adapter/migrations";
|
||||
import { promises as fs } from "node:fs";
|
||||
|
||||
const result = await runMigrations({
|
||||
plugin: "audit",
|
||||
capabilities: [],
|
||||
admin,
|
||||
migrationsDir: "./db/migrations",
|
||||
fs: {
|
||||
readdir: (dir) => fs.readdir(dir),
|
||||
readFile: (path) => fs.readFile(path, "utf8"),
|
||||
},
|
||||
});
|
||||
// result.applied = ["001_init.sql", "002_add_invoices.sql"]
|
||||
// result.skipped = [] // first run
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
const memFs = {
|
||||
readdir: async () => ["001.sql", "002.sql"],
|
||||
readFile: async (path: string) =>
|
||||
path.endsWith("001.sql")
|
||||
? "CREATE TABLE users(id TEXT)"
|
||||
: "ALTER TABLE users ADD COLUMN email TEXT",
|
||||
};
|
||||
|
||||
const first = await runMigrations({
|
||||
plugin: "user",
|
||||
capabilities: [],
|
||||
admin,
|
||||
migrationsDir: "/dev/null",
|
||||
fs: memFs,
|
||||
});
|
||||
// first.applied.length === 2
|
||||
|
||||
const second = await runMigrations({
|
||||
plugin: "user",
|
||||
capabilities: [],
|
||||
admin,
|
||||
migrationsDir: "/dev/null",
|
||||
fs: memFs,
|
||||
});
|
||||
// second.applied.length === 0, second.skipped.length === 2
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `runMigrations(options): Promise<RunMigrationsResult>`.
|
||||
- `RunMigrationsOptions` — `{ plugin, capabilities, admin, migrationsDir, fs }`.
|
||||
- `RunMigrationsResult` — `{ applied: string[]; skipped: string[] }`.
|
||||
- `MigrationsFsReader` — `{ readdir(dir); readFile(path) }`.
|
||||
- `getMigrationInstruments() / resetMigrationInstruments()`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The runner intentionally does not parse SQL inside the migration body — that
|
||||
is the engine's job. Failed migrations rely on `transaction` for atomic
|
||||
rollback; on engines without transactions, partial state is possible if the
|
||||
body succeeds but the `INSERT` into `__plugin_migrations` fails. That is
|
||||
out of scope for the runner and the responsibility of operator runbooks
|
||||
(`Surrealdb` mostly). The `applied_at` column is an ISO-8601 timestamp,
|
||||
matching the `MigrationCursor.appliedAt` shape on `@suaveplan/api-types/db`.
|
||||
Reference in New Issue
Block a user