first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
# `adapters/surrealql` — SurrealDB migration adapter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements the `MigrationAdapter` contract for SurrealDB. The factory
`createSurrealqlAdapter(client)` wraps any SurrealDB client that exposes a
`query(sql, vars?)` method (the official `surrealdb` JavaScript driver and
the in-process `SurrealAdapter` from `@suaveplan/surrealdb` both satisfy this
structurally) and returns the six-method surface the runner consumes.
The SurrealQL DDL for the tracking table lives in this adapter — not in the
runner — so the runner stays dialect-agnostic and a sibling adapter package
may ship a different DDL for the same logical schema.
## Features
- `ensureTable(table)` issues idempotent `DEFINE TABLE / DEFINE FIELD / DEFINE
INDEX` statements gated on `IF NOT EXISTS`, so concurrent boots converge
rather than racing.
- `selectApplied(table)` returns canonical `MigrationRecord` shapes — the
adapter normalises the Surreal `id` (Record ID object or string) to a plain
string and `appliedAt` (ISO string) to a `Date`.
- `insertApplied(table, record)` writes the ledger row using a
backtick-quoted record-id literal (`` CREATE table:`id` SET ... ``) so the
CREATE is parsed as a record-id reference under SurrealDB v3. Field values
use named bindings, never string interpolation. The pre-fix form
`CREATE table:$name SET ...` was silently dropped by SurrealDB v3's
parser (the `$param` was consumed as a literal ident in the record-id
position, not substituted, so the statement returned no row).
- `deleteApplied(table, id)` removes the tracking row via the matching
`` table:`id` `` form so the `selectApplied` → `deleteApplied` round-trip
targets the same row regardless of whether the migration name is a bare
SurrealQL ident. Optionally locked behind `{ rollback: false }` to enforce
forward-only deploys.
- `execute(content)` runs the migration body as-is.
- Bootstrap failures raise `MigrationError(MIGRATION.TABLE_BOOTSTRAP_FAILED)`
with the underlying client error as `cause`.
- Disabled rollback raises `MigrationError(MIGRATION.ROLLBACK_NOT_SUPPORTED)`
from `deleteApplied`.
## Usage: Basic Example
```ts
import { Surreal } from "surrealdb";
import { createSurrealqlAdapter } from "@suaveplan/api-migrations/adapters/surrealql/surrealql";
import { MigrationRunner } from "@suaveplan/api-migrations/runner/runner";
const client = new Surreal();
await client.connect("ws://localhost:8000/rpc");
await client.use({ namespace: "test", database: "test" });
await client.signin({ username: "root", password: "root" });
const runner = new MigrationRunner(createSurrealqlAdapter(client));
const results = await runner.runFromDirectory("./db/migrations");
```
## Usage: Advanced Example
```ts
import { createSurrealqlAdapter } from "@suaveplan/api-migrations/adapters/surrealql/surrealql";
const forwardOnly = createSurrealqlAdapter(client, { rollback: false });
try {
await forwardOnly.deleteApplied("_migrations", "001_initial");
} catch (err) {
// err.code === "MIGRATION.ROLLBACK_NOT_SUPPORTED"
}
```
## API Reference
- `interface SurrealqlClient { query<T>(sql, vars?): Promise<T[]> }` —
structural client surface; satisfied by the official `surrealdb` driver.
- `interface SurrealqlAdapterOptions { rollback?: boolean }` — defaults to
`true`. When `false`, `deleteApplied` rejects with
`MIGRATION.ROLLBACK_NOT_SUPPORTED`.
- `createSurrealqlAdapter(client, options?): MigrationAdapter` — returns an
adapter with `dialect: "surrealql"`.
## Implementation Notes
- The DDL is emitted as a single multi-statement string so a single round
trip bootstraps the tracking table on first run.
- `appliedAt` is sent as an ISO string in `insertApplied`; SurrealQL accepts
ISO 8601 for `datetime` fields and the round-trip preserves wall-clock
ordering even when the client is in a different time zone.
- `selectApplied` does not enforce ordering — the runner sorts by `name`
before comparing against pending files, so order is a runner concern.
- `selectApplied` normalises the returned `id` to the bare migration name:
strips the `${table}:` prefix and any wrapping backticks, and for object-
form `RecordId { tb, id }` returns the inner string id when present. This
guarantees the value that round-trips into `deleteApplied(table, id)`
produces the same `` table:`id` `` reference that `insertApplied` wrote.
- The adapter never injects an `AbortSignal` into `client.query` because the
upstream `surrealdb` driver does not yet honor one; if a future driver
version gains signal support, the adapter will pick it up by forwarding
the optional `signal` argument through.