@overengineered-solutions/db-tooling
v0.1.0
Published
Shared migration tooling for the OES portfolio — driver-agnostic apply-runner + check-applied, the comment/string/dollar-quote-aware runnability rewriter, supabase-file-timestamp and drizzle-hash ledger strategies, the ##PARITY_RESULT## fence helpers, and
Downloads
609
Maintainers
Readme
@overengineered-solutions/db-tooling
Shared Postgres migration tooling for the OverEngineered Solutions portfolio. One package, one definition of:
- the apply-runner (deterministic, per-migration-transaction, idempotent),
- the check-applied drift assertion,
- the runnability rewriter that makes committed migrations replay on a clean
Postgres (strip
CREATE INDEX CONCURRENTLY, neutralizepg_cron/pg_net, guardvault.create_secret, strip the verification do-blocks), - the timestamp-collision gate.
It collapses the Track C1.1 forks that were copied across primopicks,
makeros, and oesolutions into a single source of truth (Track D.1).
Status: staged, not yet published. The npm token is not seeded. The package builds, typechecks, tests, and dry-run-publishes cleanly. See Publishing for the two operator steps to ship
0.1.0.
Design goals
- Driver-agnostic core. The package never imports a database driver and
never reads
process.env. The runner/checker take a readySqlExecutor(query()+end()); the apply state machine issues literalBEGIN/COMMIT/ROLLBACK. Coredependenciesis empty.pgandpostgresare optional peer dependencies used only by the opt-in./adapters/*subpaths. - Per-repo variance as config, not branches. Supabase (file-timestamp
ledger,
public.*re-home) vs Drizzle (journal + SHA256 ledger) is expressed via pluggableDiscoveryStrategy/LedgerStrategy/Rewriter[]. - Connection resolution stays in the app. Each app resolves its own
connection URL (prefixed-first:
<APP>_PROD_DATABASE_URLthenDATABASE_URL) and constructs the executor; the package bakes in no env-var name.
Install
pnpm add @overengineered-solutions/db-tooling
# plus your driver, if you use a bundled adapter:
pnpm add pg # for ./adapters/pg
# or
pnpm add postgres # for ./adapters/postgres-jsEntry points
| Import | Contents |
| --- | --- |
| @overengineered-solutions/db-tooling | errors, parity fence, discovery, ledger, rewriters, createMigrationRunner/createMigrationChecker, types |
| …/rewriters | rewriteForRunnability, rewriteNamespace, the walker + cron/vault helpers |
| …/ledger | supabaseFileTimestampLedger, drizzleHashLedger |
| …/collisions | checkMigrationCollisions (zero-dep, no DB) |
| …/adapters/pg | pgExecutor (peer dep: pg) |
| …/adapters/postgres-js | postgresJsExecutor (peer dep: postgres) |
Usage
Supabase apply-runner (primopicks / oesolutions)
import { createMigrationRunner, supabaseDefaults } from '@overengineered-solutions/db-tooling';
import { rewriteForRunnability, rewriteNamespace } from '@overengineered-solutions/db-tooling/rewriters';
import { pgExecutor } from '@overengineered-solutions/db-tooling/adapters/pg';
// The app owns env resolution (prefixed-first):
const url = process.env.PRIMOPICKS_PROD_DATABASE_URL ?? process.env.DATABASE_URL;
const runnability = rewriteForRunnability({
neutralizeCron: true,
guardVault: true,
stripCronVaultDoBlocks: true,
});
const res = await createMigrationRunner({
sql: pgExecutor({ connectionString: url! }),
tool: 'apply-migrations',
...supabaseDefaults({ migrationsDir: 'supabase/migrations', schema: ledgerSchema, managed: isProd }),
// shared-test twin composes namespace FIRST, then runnability:
rewriters: mode === 'shared-test'
? [rewriteNamespace({ schema: 'primopicks' }), runnability]
: mode === 'runnability'
? [runnability]
: [],
strict,
dryRun,
}).run();
process.exit(res.ok ? 0 : res.infraReason ? 2 : 1);oesolutions is identical except rewriteNamespace({ schema: 'public' }) (an
identity — OES tables live in public on both ends).
Drizzle ephemeral apply-runner (makeros)
import { createMigrationRunner, drizzleDefaults } from '@overengineered-solutions/db-tooling';
import { rewriteForRunnability } from '@overengineered-solutions/db-tooling/rewriters';
import { pgExecutor } from '@overengineered-solutions/db-tooling/adapters/pg';
const res = await createMigrationRunner({
sql: pgExecutor({ connectionString: target }), // --target literal conn
tool: 'apply-migrations-ephemeral',
...drizzleDefaults({ migrationsDir: 'packages/db/drizzle' }), // journal + sha256
rewriters: [rewriteForRunnability()], // CONCURRENTLY only
strict: true,
}).run();Check-applied
import { createMigrationChecker, supabaseDefaults } from '@overengineered-solutions/db-tooling';
import { pgExecutor } from '@overengineered-solutions/db-tooling/adapters/pg';
const res = await createMigrationChecker({
sql: pgExecutor({ connectionString: url! }),
...supabaseDefaults({ migrationsDir: 'supabase/migrations', schema: 'supabase_migrations', managed: true }),
strict: true,
tool: 'db:check-applied',
}).check();
if (!res.ok) process.exit(res.infraReason ? 2 : res.strict ? 1 : 0);Collision gate
import { checkMigrationCollisions } from '@overengineered-solutions/db-tooling/collisions';
const res = checkMigrationCollisions({
migrationsDir: 'supabase/migrations',
repo: 'overengineered-solutions/primopicks',
strict: true, // the Vercel build chain
});
if (!res.ok) process.exit(1);The SqlExecutor contract
interface SqlExecutor {
query<T = Record<string, unknown>>(text: string, params?: unknown[]): Promise<T[]>;
end(): Promise<void>;
}Errors thrown by query() MUST carry the Postgres SQLSTATE on .code (both
pg and postgres do) so classifyConnectionError and the ALREADY_EXISTS
recovery path work. Bring your own client by implementing this directly.
Scope (Track D.1) and what's deferred
D.1 collapses the apply-runner + check + rewriters + collision gate only.
The full migration-parity engine is deferred to D.2 — but the
LedgerStrategy / DiscoveryStrategy abstractions and the ParityResultPayload
type ship now as its landing pad.
Developing
pnpm install
pnpm build # dual ESM (dist/esm) + CJS (dist/cjs) with .d.ts
pnpm typecheck
pnpm test # vitest
pnpm pack # inspect tarball: tar -tzf *.tgzPublishing
This package is staged one tag away from published. Two operator steps ship
0.1.0:
Seed the npm token (an npm automation token scoped to the
@overengineered-solutionsorg, with publish rights):gh secret set NPM_TOKEN -R overengineered-solutions/db-tooling # paste the npm automation token when prompted (do NOT pass it on the CLI)Tag and push — this fires
.github/workflows/publish.yml, which verifies the tag matchespackage.json, typechecks, builds, tests, packs, and publishes to npmjs with provenance:git tag v0.1.0 && git push origin v0.1.0
(You can also dry-run the whole pipeline without publishing via the workflow's
workflow_dispatch with dry_run=true.)
License
MIT © OverEngineered Solutions
