first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user