Migrations¶
surql supports code-first schema migrations with up/down SQL generation, versioning, and multi-environment orchestration.
Migration Structure¶
A migration is an object implementing the Migration interface:
import type { Migration } from 'jsr:@oneiriq/surql'
const migration: Migration = {
version: '20240101000001', // Sortable timestamp string
description: 'create_users',
up: () => Promise.resolve('DEFINE TABLE users SCHEMAFULL; DEFINE FIELD name ON TABLE users TYPE string;'),
down: () => Promise.resolve('REMOVE TABLE users;'),
}
Generating Migrations¶
From Schema Diffs¶
diffTables compares two arrays of TableDefinition — the current schema and the target schema — and returns the operations needed to go from one to the other. generateMigrationFromDiffs turns those into migration SQL.
import { diffTables, generateMigrationFromDiffs } from 'jsr:@oneiriq/surql'
// Diff an empty schema against the desired tables.
const diffs = diffTables([], [userSchema, postSchema])
const migration = generateMigrationFromDiffs(diffs, 'create_users_posts')
console.log(migration.filename) // e.g. 20240101000001_create_users_posts.surql
console.log(migration.upSql)
console.log(migration.downSql)
Blank Migration¶
import { createBlankMigration } from 'jsr:@oneiriq/surql'
const blank = createBlankMigration('add_index_to_users')
// blank.filename, blank.content (with -- UP / -- DOWN sections)
Initial Migration¶
import { generateInitialMigration } from 'jsr:@oneiriq/surql'
const initial = generateInitialMigration(
'DEFINE TABLE users SCHEMAFULL;',
'initial_schema',
)
Running Migrations¶
migrateUp and migrateDown take a connected Surreal connection and the list of migrations. migrateUp applies every migration not yet recorded in the migration-history table and returns the statuses of the migrations it applied.
import { migrateDown, migrateUp } from 'jsr:@oneiriq/surql'
// Apply all pending migrations.
const applied = await migrateUp(db, [migration1, migration2])
console.log(applied) // MigrationStatus[]
// Revert the most recent migrations down to a target version.
await migrateDown(db, [migration1, migration2], '20240101000001')
Migration Status¶
import { getAppliedVersions, getMigrationStatus, getPendingMigrations } from 'jsr:@oneiriq/surql'
const appliedVersions = await getAppliedVersions(db)
const status = getMigrationStatus(migrations, appliedVersions)
// MigrationStatus[] — each entry is APPLIED or PENDING
const pending = await getPendingMigrations(db, migrations)
Applying Specific Versions¶
Both migrateUp and migrateDown accept an optional target version. migrateUp applies pending migrations up to and including the target; migrateDown reverts every applied migration above the target.
// Apply pending migrations up to a specific version.
await migrateUp(db, migrations, '20240101000002')
// Revert down to a specific version.
await migrateDown(db, migrations, '20240101000001')
Migration Discovery¶
Discover and load migrations from a directory of .surql (or .ts) files named YYYYMMDDHHMMSS_description.surql:
import { discoverMigrations, loadMigration } from 'jsr:@oneiriq/surql'
const metadata = await discoverMigrations('./migrations')
const migrations = await Promise.all(
metadata.map((m) => loadMigration(m.filepath)),
)
await migrateUp(db, migrations)
Versioning¶
Migration versions are sortable 14-digit timestamps (YYYYMMDDHHMMSS). generateMigrationFilename produces a <version>_<slug>.surql filename for a new migration:
import { generateMigrationFilename } from 'jsr:@oneiriq/surql'
const filename = generateMigrationFilename('add index to users')
// e.g. 20240101120000_add_index_to_users.surql
Validation¶
Validate a set of migrations before applying them — validateMigrations reports duplicate versions and other consistency problems:
import { validateMigrations } from 'jsr:@oneiriq/surql'
const issues = validateMigrations(migrations)
if (issues.length > 0) {
console.error('Migration validation failed:', issues)
}
Next Steps¶
- Orchestration - Deploy migrations across multiple environments
- Versioning & Rollback - Rollback strategies