Migrations
Adapter capability profiles
Choose a migration adapter based on what its driver and environment have been tested to support.
Every executable migration adapter opens a migration session and advertises the exact behavior the executor may use:
| Field | Contract |
|---|---|
dialect, serverVersion |
Physical target and optional version used for compatibility checks |
session |
pinned for the full lifecycle, or atomic-batch for one complete artifact per batch |
transactionalDdl |
Whether DDL effects can roll back |
optionalTransactions, transactions |
Whether optional phases join a transaction and which requirements are proven |
lease, leaseKind |
Database-backed exclusion of another migration runner |
locks |
Independently supported program DDL lock requirements |
journal |
Database storage, head compare-and-swap, and atomic applied-record/head advancement |
parameters |
Supported tagged parameter kinds |
commitAmbiguity |
Ambiguous commit becomes recovery-required |
forbiddenPhases |
Checkpointed support or explicit rejection |
features |
Named constraints an artifact may require |
The migrator lease and a program's DDL lock are different controls. The lease excludes another Qubu runner; a DDL lock protects the database operation. The executor never treats one as proof of the other.
Trusted migration SQL
Qubu validates program structure and adapter capabilities. It trusts the SQL you supply, including SQL conditions, and does not parse it for safety.
Migration SQL must preserve the executor’s transactions, connection settings,
and journal state. For example, an explicit COMMIT can apply schema changes
without their journal record, breaking the executor’s recovery guarantees.
Current profiles
The following stable profiles have live conformance coverage in this checkout:
| Migration entrypoint | Dialect | Transactions | Locks | Forbidden phases | Notes |
|---|---|---|---|---|---|
@qubu/adapter-libsql/migration |
SQLite | required, optional | none, exclusive | unsupported | Single-phase atomic batches through client.migrate() |
@qubu/adapter-node-sqlite/migration |
SQLite | required, optional | none, exclusive | unsupported | Pinned application-owned DatabaseSync |
@qubu/adapter-pg/migration |
PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Caller supplies an already-pinned client |
@qubu/adapter-postgresjs/migration |
PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Reserves and releases one connection |
@qubu/adapter-pglite/migration |
PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Uses the database query queue as the pinned session |
All five support:
- Every current tagged parameter kind.
- A journal and migrator lease stored in the database.
- An atomic update of the applied record and journal head.
- A recovery-required result when a commit’s outcome is uncertain.
Parameter kinds are:
nullandboolean.stringandnumber.bigintandbytes.json.
The artifact’s server, feature, transaction, and lock requirements must still match the adapter.
Unavailable profiles
MySQL has a basic SQL migration runner,
migrate, which executes pending statements and records completed
migrations. It runs independently of the shared executor and its stronger
capability contract. The MySQL profile below remains unavailable; possible
improvements are tracked in issue #1.
The same entry point exports baselineAdapter for reviewed MySQL adoption
through the shared CLI and @qubu/migrate/baseline API.
These exported profiles are unavailable and must not be passed to the executor:
| Export | Status | Reason |
|---|---|---|
d1MigrationProfile |
incompatible |
D1 exposes no pinned interactive transaction/session contract |
mysql2MigrationProfile |
not-yet-written |
MySQL implicit-commit lease, checkpoint, and recovery semantics are not live-proven |
bunSqliteMigrationProfile |
not-yet-written |
A Bun-native pinned-session and journal conformance run is missing |
Unavailable profiles expose reason and missingCapabilities; they do not
fall back to a generic executor.
Configure libSQL inspection
For libSQL, let the migration entrypoint exclude all reserved journal objects during strict inspection:
import { createClient } from "@libsql/client"
import { migrationAdapter, readMigrationSnapshot } from "@qubu/adapter-libsql/migration"
const client = createClient({ url: process.env.DATABASE_URL! })
const adapter = migrationAdapter(client, {
readSnapshot: readMigrationSnapshot,
})
DATABASE_URL remains application configuration; neither the adapter nor CLI
assigns deployment-provider meaning to it.
libSQL batch execution
Each executable artifact must contain exactly one phase and an embedded before
snapshot. The adapter submits its statements, SQL assertions, applied-history
record, head update, and terminal attempt state in one client.migrate() call.
For example, creating a table and recording that migration either both commit
or both roll back.
Multiple artifacts are separate batches; earlier successful artifacts remain applied if a later one fails.
Preparation reads the schema in a read transaction. The submitted batch checks
that the catalog still matches that inspection, the lease is still owned, and
the head still equals the expected parent. Foreign-key validation runs before
commit because libSQL temporarily disables enforcement during migrate().
Supported conditions
Schema fingerprint and property preconditions are checked against the embedded before snapshot. Preparation verifies its physical facts, and the batch asserts that the catalog still matches.
Object-presence and scalar SQL checks run inside the batch. Postconditions must use either:
- Object-presence or absence checks without fingerprints.
- Scalar SQL checks returning
1.
Unsupported conditions and multiple phases are rejected.
SQL is passed to the driver without safety validation. Each program entry must follow the driver’s statement contract.
Crashes and uncertain outcomes
The database-row lease has no expiry or heartbeat. A process crash can leave it held; ownership must be resolved before another runner can proceed. A lost batch response is an uncertain outcome requiring journal inspection and, when the attempt remains unresolved, explicit recovery. It is never assumed to be a successful rollback.