@plakboek/db
v0.6.0
Published
Postgres connection factory and migration-lock primitives for Plakboek CMS installations
Readme
@plakboek/db
A Postgres connection factory and a forward-only, lock-safe migration runner for Plakboek CMS installations.
What this package provides
createDb-- a Drizzle (drizzle-orm/postgres-js) connection factory over thepostgres(porsager) driver.runMigrations-- applies this package's own statically registered migrations, taking a session-scoped Postgres advisory lock internally for the whole run so concurrent migrators can never double-apply (D-03). What Phase 6's deploy step invokes.MigrationLockTimeoutError-- thrown byrunMigrationswhen another migrator still holds that advisory lock afterlockWaitMs.- The migration authoring convention every migration -- this package's own
and every host/host-extension migration -- follows: see
MIGRATIONS.md.
Install
pnpm add @plakboek/db drizzle-ormdrizzle-orm is a peer dependency: bring your own version, matching the
one this package was built against.
createDb
import { createDb } from '@plakboek/db';
const { db, sql, close } = createDb({
connectionString: process.env.DATABASE_URL!,
maxConnections: 10, // optional, default 10
});
// db: a drizzle-orm/postgres-js database instance
// sql: the raw `postgres` tag function, for one-off queries
await close(); // ends the underlying connection poolcreateDb validates the connection string's scheme (postgres:// or
postgresql://) before connecting, and never echoes the supplied value in
an error -- DbConfigError always names the two expected schemes instead.
runMigrations
import { runMigrations } from '@plakboek/db';
const result = await runMigrations({
connectionString: process.env.DATABASE_URL!,
lockWaitMs: 60_000, // optional, default 60000
lockPollIntervalMs: 250, // optional, default 250
onLockWait: () => console.log('Waiting for the migration lock'), // optional
onMigrationStart: (name) => console.log(`Applying ${name}`), // optional
});
// result.applied: names of migrations applied by this call
// result.alreadyApplied: names that were already applied before this callonLockWait fires once, the first time another migrator holds the lock, however
long the wait lasts. onMigrationStart(name) fires once per pending migration,
just before it applies, and never for an already-applied one. Hooks receive
migration names only. A hook that throws is ignored: it never aborts, rolls back
or reorders the run.
runMigrations is safe to call from every instance of a rolling deploy: it
opens its own connection, holds the advisory lock for the duration of the
run, and applies this package's statically registered MIGRATIONS in
order. A second overlapping call either observes the first call's
migrations as already applied, or throws MigrationLockTimeoutError if it
gives up waiting for the lock before the first call finishes.
MIGRATIONS starts with 0001_auth_core, which creates the authentication
tables ("user", session, account, verification, two_factor) and
the append-only audit_log. 0002_content_engine follows it, creating the
content engine tables (content_types, content_type_fields,
content_entries, entry_revisions and their history, seed, reference and
settings tables) used by @plakboek/content. 0003_page_block_engine
creates the page and block-tree tables (pages, page_blocks,
block_revisions, page_publications, page_url_history,
page_engine_settings) used by @plakboek/pages. See
MIGRATIONS.md for how later migrations are added, the
transactional-vs-existence-guarded-statement decision, and what each thrown
error class means.
Errors
| Class | Meaning |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| DbConfigError | createDb was given an invalid connection string. |
| MigrationLockTimeoutError | Another migrator still holds the advisory lock after lockWaitMs. |
| MigrationRegistryError | The migration registry itself is structurally invalid. |
| MigrationChecksumMismatchError | An already-applied migration's SQL no longer matches its registry entry -- shipped migrations are immutable. |
| UnknownAppliedMigrationError | The database has a migration applied that the registry no longer contains. |
| MigrationOrderError | A new migration was inserted before one already applied -- migrations must never be reordered. |
| MigrationFailedError | A migration's SQL failed to apply; carries the failing statement index (non-transactional) and the original error as cause. |
Full detail, including operator guidance per error, lives in
MIGRATIONS.md.
