5.1 KiB
Capability Spec Delta: api-migrations (fix-api-migrations-surrealql-ledger)
Package: @suaveplan/api-migrations
Status: PROPOSED
The existing
Requirement: MigrationAdapter is the per-dialect surfacecovers the full adapter contract, includinginsertApplied/deleteApplied/selectApplied. The body of that requirement is unchanged. This change adds a new requirement that pins the v3-safe record-id production for the SurrealQL adapter specifically — it is additive to the existing per-dialect surface, not a replacement.
ADDED Requirements
Requirement: SurrealQL adapter records the ledger row under SurrealDB v3
createSurrealqlAdapter.insertApplied(table, record) SHALL produce a record-id reference in the form <table>:\`whereisrecord.idwith every backtick doubled. The adapter SHALL NOT use the${table}:$nameform — SurrealDB v3 parses$paramin the record-id position as a literal ident (not a parameter substitution), so the CREATE is acknowledged but silently records no row, leaving the ledger empty and causing the runner to re-execute completed migrations. After a successfulinsertAppliedinvocation, a subsequentselectApplied(table)on the same connection SHALL return a row whosename, checksum, and duration` match the inserted record.
createSurrealqlAdapter.deleteApplied(table, id) SHALL produce the matching <table>:\`reference so the round-trip fromselectApplied→deleteAppliedcorrectly targets the row thatinsertAppliedwrote, regardless of whetherid` is a bare SurrealQL ident.
createSurrealqlAdapter.selectApplied(table) SHALL normalise the SurrealDB-returned id field to the bare migration name — strip the ${table}: prefix and any wrapping backticks, and for object-form RecordId { tb, id } return the inner id string when present. This guarantees rollback()'s call to deleteApplied(table, last.id) produces the same record-ref shape as the original insertApplied.
createSurrealqlAdapter.insertApplied SHALL bind record.appliedAt as the raw Date value, NOT as an ISO 8601 string. SurrealDB v3's TYPE datetime column refuses to coerce a string into datetime and rejects the CREATE with InternalError: Couldn't coerce value for field 'appliedAt'. The SDK CBOR-encodes the Date binding as a SurrealDB datetime value.
Scenario: insertApplied uses backticked record-id literal (v3-safe)
- WHEN
createSurrealqlAdapter(client).insertApplied("_migrations", { id: "001", name: "001", appliedAt, checksum: "h", duration: 5 })is called - THEN the issued SurrealQL is
CREATE _migrations:\001` SET name = $name, appliedAt = $appliedAt, checksum = $checksum, duration = $duration(NOTCREATE _migrations:$name SET ...`)
Scenario: backticks in id are escaped
- WHEN
record.idisweird\id` - THEN the issued SurrealQL contains
CREATE _migrations:\weird``id` SET ...`
Scenario: ledger row is queryable after the migration body succeeds (live v3)
- WHEN
runFromDirectory(dir)applies a single migration against a real SurrealDB v3.x testcontainer - THEN
SELECT name, checksum, duration FROM _migrationson the same connection returns exactly one row with the migration name and a 64-char-hexchecksum
Scenario: second run sees the ledger and skips already-applied migrations (live v3)
- WHEN
runFromDirectory(dir)runs a second time after the first has applied one migration - THEN the result array is empty AND the ledger still contains exactly one row
Scenario: rollback removes the ledger row written by insertApplied (live v3)
- WHEN
runner.rollback()is awaited after one successful apply - THEN the rollback result is
{ name, success: true }AND the ledger table is empty
Scenario: selectApplied normalises returned ids to the bare migration name
- WHEN SurrealDB returns
id: "_migrations:\001_initial`"` for a row - THEN
selectApplied("_migrations")returns{ id: "001_initial", ... }
Verified by: src/adapters/surrealql/surrealql.test.ts::"insertApplied uses backticked record-id literal (v3-safe)" AND src/adapters/surrealql/surrealql.test.ts::"insertApplied escapes backticks inside the migration id" AND src/adapters/surrealql/surrealql.test.ts::"insertApplied binds appliedAt as a Date (not a string)" AND src/adapters/surrealql/surrealql.test.ts::"deleteApplied uses backticked record-id literal (v3-safe)" AND src/adapters/surrealql/surrealql.test.ts::"selectApplied strips backtick-quoted id form" AND src/adapters/surrealql/surrealql.test.ts::"selectApplied strips table prefix from string ids" AND tests/integration/surrealql-ledger.integration.test.ts::"ledger row is queryable after a migration body succeeds (regression: silent CREATE drop)" AND tests/integration/surrealql-ledger.integration.test.ts::"second run sees the ledger and skips already-applied migrations" AND tests/integration/surrealql-ledger.integration.test.ts::"rollback() removes the ledger row written by insertApplied (round-trip)"