New:Socket for Asana Is Now Available.Learn more
Get Started

@ultimat3/db

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ultimat3/db - npm Package Compare versions

Comparing version
16.0.0
to
17.0.0
+32
src/bun-sql.ts
// Single responsibility: the slice of `Bun.SQL` this package uses, declared structurally, and the
// lazy lookup of the global that provides it. Reached through a function so importing the client
// never touches `Bun` at module evaluation — the CLI imports it to print help.
import { dbUnavailable } from './errors';
/** One connection pinned out of `Bun.SQL`'s pool, released back by hand. */
export interface BunSqlReserved {
unsafe(text: string, values?: readonly unknown[]): Promise<unknown>;
release(): void;
}
/** The slice of `Bun.SQL` we use. Declared structurally so this package has no dependency. */
export interface BunSqlDriver {
unsafe(text: string, values?: readonly unknown[]): Promise<unknown>;
reserve(): Promise<BunSqlReserved>;
close(options?: { readonly timeout?: number }): Promise<void>;
}
export type BunSqlFactory = new (
url: string,
options?: Readonly<Record<string, unknown>>,
) => BunSqlDriver;
export function bunSqlFactory(): BunSqlFactory {
const host = globalThis as unknown as { readonly Bun?: { readonly SQL?: unknown } };
const factory = host.Bun?.SQL;
if (typeof factory !== 'function') {
throw dbUnavailable('Bun.SQL is unavailable — this package requires Bun >= 1.3');
}
return factory as BunSqlFactory;
}
// Single responsibility: turning `DATABASE_URL` plus a resolved pool profile into the connection
// string the driver opens — the libpq `options` merge and the `application_name` label. Split from
// `client.ts` because which settings reach a connection is a rule, not a step of connecting.
import { describeValue } from '@ultimat3/core';
import { dbUnavailable } from './errors';
import { declaresLibpqOption, mergeLibpqOptions } from './libpq-options';
import type { PoolProfile } from './pool-profile';
export interface ConnectionUrlOptions {
readonly url?: string | undefined;
readonly applicationName?: string | undefined;
}
export function connectionUrl(options: ConnectionUrlOptions, profile: PoolProfile): string {
const raw = options.url ?? process.env['DATABASE_URL'];
if (raw === undefined || raw === '') {
throw dbUnavailable('DATABASE_URL is not set, so there is no database to connect to');
}
let url: URL;
try {
url = new URL(raw);
} catch (error) {
// The SHAPE of the rejected value, never the value. A connection string is
// `user:password@host` by construction, and this `cause` is the boot log line AND the `--json`
// payload — the logger redacts `fields` by key, so a password baked into a message has no key
// left to redact it by. `@ultimat3/core`'s `defineEnv` reached the identical conclusion about
// the identical variable (`packages/core/src/env.ts`); this is the same rule at the second
// reader, not a second rule. The value still has a printer: `x env check`, through
// `maskedEnvValues`.
throw dbUnavailable(`DATABASE_URL is not a valid url: received ${describeValue(raw)}`, error);
}
// libpq `options` is the portable way to pin a statement timeout for every pooled connection —
// MERGED into the operator's own, never assigned over it, and emitted for every role including
// the two whose bound is 0. `set` here dropped a `?options=-c search_path=app` on `web`, `sync`,
// `worker` and `scheduler` and kept it on `migrate` and `replicator`, so the role that runs the
// migrations and the role that serves the traffic read different schemas. 0 is a value, not a
// silence: it is `migrate` saying it may take as long as it takes, and left unsaid a server-side
// `alter database ... set statement_timeout` kills the one role that must outlive it.
// `application_name` is a LABEL, not a bound: 'ultimate' is a DEFAULT, and a default may not
// overwrite what the operator wrote — `?application_name=billing-api` is the filter their
// `pg_stat_activity` query, their pooler rule and their audit rule all match on, and losing it
// is silent. Both spellings count, or the URL parameter and a `-c application_name=` in
// `options` disagree and which one the backend honours is argument order nobody here measured.
const named = options.applicationName;
const settings: Record<string, string> = {
statement_timeout: String(profile.statementTimeoutMs),
};
const inOptions = declaresLibpqOption(url.searchParams.get('options'), 'application_name');
// An explicit `applicationName` is a deliberate call by the role that opened the pool, so it
// wins. Only then is the setting named to the merge, and only when the operator wrote the other
// spelling: `mergeLibpqOptions` drops their assignment before appending, so the two cannot
// disagree — and a URL with no assignment in it keeps the exact `options` it always had.
if (named !== undefined && inOptions) settings['application_name'] = named;
const declared = url.searchParams.has('application_name') || inOptions;
url.searchParams.set('options', mergeLibpqOptions(url.searchParams.get('options'), settings));
if (named !== undefined) url.searchParams.set('application_name', named);
else if (!declared) url.searchParams.set('application_name', 'ultimate');
return url.toString();
}
// Single responsibility: the database's readiness answer — one `select 1` timed and reported, never
// thrown. Split from `client.ts` because a probe's report is a different job from opening a pool,
// and every role's `/readyz` reads this one and nothing else of the client.
import { renderThrowable } from '@ultimat3/core';
import { baseClient, type DbClient } from './client';
import { sql } from './sql';
/** Named `Db*` because `@ultimat3/core` already exports a `HealthReport` for the lifecycle. */
export interface DbHealthReport {
readonly ok: boolean;
readonly latencyMs: number;
readonly error?: string | undefined;
}
/** Backs `/readyz` for every role. Never throws — the probe wants a report, not an exception. */
export async function checkDb(client: DbClient = baseClient()): Promise<DbHealthReport> {
const started = performance.now();
try {
await client.query(sql`select 1`);
return { ok: true, latencyMs: Math.round(performance.now() - started) };
} catch (error) {
return {
ok: false,
latencyMs: Math.round(performance.now() - started),
// `renderThrowable`, never `error.message`: the probe wants a report, and a render that
// throws is an exception out of `/readyz` — the one caller that cannot catch it.
error: renderThrowable(error),
};
}
}
// Single responsibility: the `X_DB_DRIFT` constructor — the error form of the finding
// `drift-findings.ts`'s `unexpectedColumn` reports, thrown where a caller has no report to hand
// back. Split out of `errors.ts` for the import it needs and nothing else: the fix line puts a
// column name into a shell command, so it screens through `shellInertIdentifier` (`sql.ts`) —
// and `sql.ts` imports `errors.ts`, so this cannot live there without a cycle around the module
// whose evaluation registers every code. The code is still declared, titled and registered in
// `errors.ts`, exactly as `migration-errors.ts` and `invariant-errors.ts` are.
import { DbError } from './errors';
import { shellInertIdentifier } from './sql';
/**
* The contract's pinned wording. Mirror of `@ultimat3/entity`'s `dbDrift()` — keep in sync; that
* one screens the column through the same `@ultimat3/db` export, so the two lines are the same
* text on both sides of the tier seam.
*
* The column name is the CATALOG's, so it is data: whoever can add a column picks the text that
* lands here, and `x db gen "add C"` puts it inside SHELL DOUBLE QUOTES, where `$(…)` and a
* backtick substitute before `x` is reached at all. The argument is a migration DESCRIPTION and
* not an identifier, so no quoted form makes a hostile name safe to pass — a name the screen
* refuses is left OUT of the command rather than escaped into it. The command still runs and
* still generates the migration; the name is read off `cause` and `meta`, which are prose nobody
* pastes.
*/
export const dbDrift = (tableName: string, columnName: string): DbError =>
new DbError({
code: 'X_DB_DRIFT',
cause: `table "${tableName}" has column "${columnName}" not present in any migration`,
fix:
shellInertIdentifier(columnName) === null
? 'x db gen "add the column named in this error" # its name carries a backtick, a ' +
'dollar sign, a quote, a backslash or whitespace, so it is in the cause and not in ' +
'this command'
: `x db gen "add ${columnName}"`,
meta: { table: tableName, column: columnName },
});
// TEST-ONLY. The two builders every drift suite compares with — a `TableDescription` of text
// columns and the `SchemaDescription` around it. One copy, because three suites arguing about
// schemas built differently would each be judging a different fixture. Never exported from
// `index.ts`.
import type { SchemaDescription, TableDescription } from './introspect';
export const table = (name: string, columns: readonly string[]): TableDescription => ({
schema: 'public',
name,
columns: columns.map((column, index) => ({
name: column,
dataType: 'text',
nullable: true,
default: null,
position: index + 1,
})),
primaryKey: ['id'],
indexes: [],
foreignKeys: [],
});
export const schema = (...tables: readonly TableDescription[]): SchemaDescription => ({ tables });
// Single responsibility: the five numbers a Postgres pool runs on — the per-role defaults, the one
// environment override an operator may layer over them, and the screen every resolved profile
// passes. Split from `client.ts`, which now owns connecting and nothing about sizing.
import { assert, type Role, resolveRole } from '@ultimat3/core';
import { poolMaxInvalid } from './errors';
export interface PoolProfile {
readonly max: number;
/** 0 disables the timeout — only `migrate`, which is allowed to take as long as it takes. */
readonly statementTimeoutMs: number;
readonly idleTimeoutMs: number;
/**
* How long a statement may **wait for a lock** before `55P03`, distinct from how long it may run.
* 0 everywhere but `migrate`, which is the only role that takes `ACCESS EXCLUSIVE`: an `alter
* table` queued behind a long `SELECT` puts every later query on that table behind it too,
* because Postgres' lock queue is FIFO — and `migrate` runs `statement_timeout = 0`, so nothing
* else would ever end the wait. Read by `migrate()` as a `SET LOCAL`, never by the pool.
*/
readonly lockTimeoutMs: number;
/**
* How long `reserve()` may wait for a free connection before `X_DB_POOL_EXHAUSTED`. 0 waits
* forever, which is what a run-once role wants and what a request-serving one must never do:
* queueing turns exhaustion into a hang, `/readyz`'s `select 1` joins the same queue, the kubelet
* kills the pod, and the replacement inherits the same saturated database.
*/
readonly acquireTimeoutMs: number;
}
/** Sized per role because the failure modes differ: RPS bursts vs. queue depth vs. run-once. */
export const POOL_PROFILES = Object.freeze<Record<Role, PoolProfile>>({
web: {
max: 20,
statementTimeoutMs: 10_000,
idleTimeoutMs: 30_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 5_000,
},
sync: {
max: 10,
statementTimeoutMs: 10_000,
idleTimeoutMs: 60_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 5_000,
},
worker: {
max: 8,
statementTimeoutMs: 120_000,
idleTimeoutMs: 30_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 10_000,
},
scheduler: {
max: 2,
statementTimeoutMs: 15_000,
idleTimeoutMs: 60_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 10_000,
},
// `migrate` waits: its pool is `max: 1` and the advisory-lock pin holds it for the whole run, so
// a deadline here would refuse the migration's own session. The wait that needed bounding is the
// advisory lock's, and `MIGRATION_LOCK_WAIT_MS` bounds it.
migrate: {
max: 1,
statementTimeoutMs: 0,
idleTimeoutMs: 10_000,
lockTimeoutMs: 3_000,
acquireTimeoutMs: 0,
},
replicator: {
max: 4,
statementTimeoutMs: 0,
idleTimeoutMs: 60_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 0,
},
});
export function poolProfileFor(role: Role = resolveRole()): PoolProfile {
return POOL_PROFILES[role];
}
/** The one pool knob an operator can turn without a rebuild. Layered over the role default. */
export const POOL_MAX_ENV = 'DATABASE_POOL_MAX';
/**
* `DATABASE_POOL_MAX`, or nothing. `POOL_PROFILES` is frozen into the build, so before this the
* only way to change a fleet's connection count was to ship a new image — and 400 `web` pods at
* `max: 20` is 8,000 backends against a `max_connections` of 450. An unparseable value **refuses**
* rather than falling back: a fleet that ignored the number it was given is the failure the
* variable exists to prevent, and it would only be found in `pg_stat_activity` at 3am.
*/
export function poolMaxFromEnv(): Partial<PoolProfile> {
const raw = process.env[POOL_MAX_ENV];
if (raw === undefined || raw.trim() === '') return {};
const max = Number(raw);
if (!Number.isSafeInteger(max) || max < 1) throw poolMaxInvalid(raw);
return { max };
}
/**
* The five numbers a pool runs on, screened on the MERGED profile — an override is spread over a
* role default the caller never restated, so the resolved object is the only one that can be
* judged. Every one of them is a plausible `Number(process.env.…)`, which is `NaN` for an unset
* variable and not nullish, so `??` and the spread both keep it. None of the five then fails
* loudly: `idleTimeout: NaN` goes to `Bun.SQL`, `statement_timeout=NaN` goes into the libpq
* options string for the SERVER to reject on connect, and a timer given `NaN` fires at 1ms in this
* Bun — so a pool with free connections reports itself exhausted. `0` stays legal for the three
* budgets that document it as "no bound"; `max` is at least one connection, or nothing can run.
*/
export function assertPoolProfile(profile: PoolProfile): PoolProfile {
const whole = (option: string, value: number, min: 0 | 1): void => {
assert(
Number.isSafeInteger(value) && value >= min,
`pool profile ${option} is ${String(value)}; it must be a whole number of ${min === 1 ? 'at least 1' : '0 or more, where 0 is the documented "no bound"'}`,
`pass a whole number for ${option} in createPostgresClient({ profile }), and parse an environment value first — Number(process.env.DATABASE_${option.toUpperCase()} ?? '') is NaN when the variable is unset`,
);
};
whole('max', profile.max, 1);
whole('statementTimeoutMs', profile.statementTimeoutMs, 0);
whole('idleTimeoutMs', profile.idleTimeoutMs, 0);
whole('lockTimeoutMs', profile.lockTimeoutMs, 0);
whole('acquireTimeoutMs', profile.acquireTimeoutMs, 0);
return profile;
}
// Single responsibility: pinning a connection out of the pool under the profile's acquire deadline,
// and giving back a reservation that arrives after the deadline has passed. Split from `client.ts`,
// which now asks for a pin rather than owning what "waited too long" means.
import type { BunSqlDriver, BunSqlReserved } from './bun-sql';
import { poolAcquireTimeout } from './errors';
import type { PoolProfile } from './pool-profile';
/**
* `pool.reserve()` under a deadline. Without one an exhausted pool does not fail, it **queues** —
* so a slow endpoint filling all 20 slots turns every later request, `/readyz`'s `select 1`
* included, into a wait with no end and no error, and the pod is killed for being unready rather
* than answering 503 for the requests it cannot serve.
*
* The losing reservation is released, never dropped: the pool hands out a connection whenever one
* frees, deadline or no deadline, and a pin nobody holds is a connection nobody gets back. That is
* the whole reason this is not a bare `Promise.race`.
*/
export async function reserveWithin(
pool: Pick<BunSqlDriver, 'reserve'>,
profile: PoolProfile,
): Promise<BunSqlReserved> {
const budget = profile.acquireTimeoutMs;
if (budget <= 0) return pool.reserve();
let timer: ReturnType<typeof setTimeout> | undefined;
let expired = false;
const pending = pool.reserve();
try {
return await Promise.race([
pending,
new Promise<never>((_resolve, reject) => {
timer = setTimeout(() => {
expired = true;
reject(poolAcquireTimeout(budget, profile.max));
}, budget);
// The deadline must not be what keeps a finished process alive.
timer.unref?.();
}),
]);
} finally {
if (timer !== undefined) clearTimeout(timer);
// Attached unconditionally so a rejection arriving after we gave up is handled, not unhandled.
void pending.then(
(late) => {
if (expired) late.release();
},
() => undefined,
);
}
}
+2
-2
{
"name": "@ultimat3/db",
"version": "16.0.0",
"version": "17.0.0",
"description": "Postgres access, transactions, migrations and drift detection",

@@ -34,3 +34,3 @@ "license": "MIT",

"dependencies": {
"@ultimat3/core": "16.0.0"
"@ultimat3/core": "17.0.0"
},

@@ -37,0 +37,0 @@ "peerDependencies": {

@@ -29,2 +29,3 @@ # @ultimat3/db 🐘

| `sql` / `raw` / `identifier` / `literal` / `join` | fragment builders |
| `shellInertIdentifier()` | `As of 2026-08-26`: a quoted identifier that is also inert wherever a human PASTES it — or `null`. The one screen a catalog name goes through before it reaches a `fix:`. `identifier()` answers about SQL and **accepts** a backtick and a `$`, which are exactly what a shell substitutes inside double quotes, so a column called `$(id)` inside `x db gen "add $(id)"` runs `id` on paste |
| `db()` / `baseClient()` / `setDbClient()` | the ambient client; `db()` returns the open tx if any |

@@ -220,5 +221,5 @@ | `DbTx.origin` | `As of 2026-08`: the client the transaction was **opened on** — `options.client` or `baseClient()`, never the reservation it runs statements through. `@ultimat3/entity` compares a pinned repository's client against it, so a pinned repo joins its own shard's transaction instead of being refused |

|---|---|---|
| live column, no migration | `table "T" has column "C" not present in any migration` | `x db gen "add C"` |
| live column, no migration | `table "T" has column "C" not present in any migration` | `x db gen "add C"` — the name goes through `shellInertIdentifier()`, and one it refuses is left OUT of the command rather than escaped into it (`x db gen "add the undeclared column"`, the name in the cause) |
| migrated column, not live | `table "T" is missing column "C" that migrations declare` | `x db migrate` |
| live table, no migration | `table "T" is not present in any migration` | `x db gen "add T"` |
| live table, no migration | `table "T" is not present in any migration` | a `create table if not exists` in a migration, then `x db migrate` — or `drop table` in `psql` where nothing owns it. Never `x db gen`, which diffs a table nothing declares against nothing and writes no file (issue #345). The name goes through `shellInertIdentifier()`, and one it refuses leaves the fix as prose |
| migrated table, not live | `table "T" is declared by migrations but does not exist` | `x db migrate` |

@@ -225,0 +226,0 @@ | index rebuilt differently | `index "I" on "T" covers (…)` / `is unique` / `is descending` / `is partial`, `not what migrations declare` | `x db migrate` |

@@ -1,15 +0,18 @@

// Single responsibility: the Postgres connection and the ambient `db()` handle. Pool size and
// statement timeout are chosen by runtime ROLE — a `worker` draining a queue must not size its
// pool like a `web` process behind a CDN. `Bun.SQL` is reached lazily so importing this module
// never opens a socket (the CLI imports it to print help).
// Single responsibility: the pooled Postgres client and the ambient `db()` handle — one statement
// funnel, the reserved-connection pin, and the process-wide client every repository reaches
// through. Sizing lives in `pool-profile.ts`, the connection string in `connection-url.ts`, the
// `Bun.SQL` slice in `bun-sql.ts`, so importing this module never opens a socket.
import { type Role, renderThrowable, resolveRole } from '@ultimat3/core';
import { type Role, resolveRole } from '@ultimat3/core';
import { statementAttribution } from './attribution';
import { type BunSqlDriver, type BunSqlReserved, bunSqlFactory } from './bun-sql';
import { connectionUrl } from './connection-url';
// Deliberate cycle, the same shape as `client.ts ⇄ transaction.ts`: nothing here is referenced at
// module evaluation, and both sides are `function` declarations, so hoisting covers the TDZ.
import { defaultClient } from './default-client';
import { DbError, dbUnavailable, driverError, poolAcquireTimeout, poolMaxInvalid } from './errors';
import { DbError, driverError } from './errors';
import { expectedQueryLoopReason } from './expected-loop';
import { declaresLibpqOption, mergeLibpqOptions } from './libpq-options';
import { statementObserver } from './observe';
import { assertPoolProfile, type PoolProfile, poolProfileFor } from './pool-profile';
import { reserveWithin } from './pool-reserve';
import { type SqlFragment, sql } from './sql';

@@ -44,119 +47,2 @@ import { withStatementSpan } from './statement-span';

export interface PoolProfile {
readonly max: number;
/** 0 disables the timeout — only `migrate`, which is allowed to take as long as it takes. */
readonly statementTimeoutMs: number;
readonly idleTimeoutMs: number;
/**
* How long a statement may **wait for a lock** before `55P03`, distinct from how long it may run.
* 0 everywhere but `migrate`, which is the only role that takes `ACCESS EXCLUSIVE`: an `alter
* table` queued behind a long `SELECT` puts every later query on that table behind it too,
* because Postgres' lock queue is FIFO — and `migrate` runs `statement_timeout = 0`, so nothing
* else would ever end the wait. Read by `migrate()` as a `SET LOCAL`, never by the pool.
*/
readonly lockTimeoutMs: number;
/**
* How long `reserve()` may wait for a free connection before `X_DB_POOL_EXHAUSTED`. 0 waits
* forever, which is what a run-once role wants and what a request-serving one must never do:
* queueing turns exhaustion into a hang, `/readyz`'s `select 1` joins the same queue, the kubelet
* kills the pod, and the replacement inherits the same saturated database.
*/
readonly acquireTimeoutMs: number;
}
/** Sized per role because the failure modes differ: RPS bursts vs. queue depth vs. run-once. */
export const POOL_PROFILES = Object.freeze<Record<Role, PoolProfile>>({
web: {
max: 20,
statementTimeoutMs: 10_000,
idleTimeoutMs: 30_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 5_000,
},
sync: {
max: 10,
statementTimeoutMs: 10_000,
idleTimeoutMs: 60_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 5_000,
},
worker: {
max: 8,
statementTimeoutMs: 120_000,
idleTimeoutMs: 30_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 10_000,
},
scheduler: {
max: 2,
statementTimeoutMs: 15_000,
idleTimeoutMs: 60_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 10_000,
},
// `migrate` waits: its pool is `max: 1` and the advisory-lock pin holds it for the whole run, so
// a deadline here would refuse the migration's own session. The wait that needed bounding is the
// advisory lock's, and `MIGRATION_LOCK_WAIT_MS` bounds it.
migrate: {
max: 1,
statementTimeoutMs: 0,
idleTimeoutMs: 10_000,
lockTimeoutMs: 3_000,
acquireTimeoutMs: 0,
},
replicator: {
max: 4,
statementTimeoutMs: 0,
idleTimeoutMs: 60_000,
lockTimeoutMs: 0,
acquireTimeoutMs: 0,
},
});
export function poolProfileFor(role: Role = resolveRole()): PoolProfile {
return POOL_PROFILES[role];
}
/** The one pool knob an operator can turn without a rebuild. Layered over the role default. */
export const POOL_MAX_ENV = 'DATABASE_POOL_MAX';
/**
* `DATABASE_POOL_MAX`, or nothing. `POOL_PROFILES` is frozen into the build, so before this the
* only way to change a fleet's connection count was to ship a new image — and 400 `web` pods at
* `max: 20` is 8,000 backends against a `max_connections` of 450. An unparseable value **refuses**
* rather than falling back: a fleet that ignored the number it was given is the failure the
* variable exists to prevent, and it would only be found in `pg_stat_activity` at 3am.
*/
export function poolMaxFromEnv(): Partial<PoolProfile> {
const raw = process.env[POOL_MAX_ENV];
if (raw === undefined || raw.trim() === '') return {};
const max = Number(raw);
if (!Number.isSafeInteger(max) || max < 1) throw poolMaxInvalid(raw);
return { max };
}
/** One connection pinned out of `Bun.SQL`'s pool, released back by hand. */
interface BunSqlReserved {
unsafe(text: string, values?: readonly unknown[]): Promise<unknown>;
release(): void;
}
/** The slice of `Bun.SQL` we use. Declared structurally so this package has no dependency. */
interface BunSqlDriver {
unsafe(text: string, values?: readonly unknown[]): Promise<unknown>;
reserve(): Promise<BunSqlReserved>;
close(options?: { readonly timeout?: number }): Promise<void>;
}
type BunSqlFactory = new (url: string, options?: Readonly<Record<string, unknown>>) => BunSqlDriver;
function bunSqlFactory(): BunSqlFactory {
const host = globalThis as unknown as { readonly Bun?: { readonly SQL?: unknown } };
const factory = host.Bun?.SQL;
if (typeof factory !== 'function') {
throw dbUnavailable('Bun.SQL is unavailable — this package requires Bun >= 1.3');
}
return factory as BunSqlFactory;
}
export interface PostgresClientOptions {

@@ -169,42 +55,2 @@ readonly url?: string | undefined;

function connectionUrl(options: PostgresClientOptions, profile: PoolProfile): string {
const raw = options.url ?? process.env['DATABASE_URL'];
if (raw === undefined || raw === '') {
throw dbUnavailable('DATABASE_URL is not set, so there is no database to connect to');
}
let url: URL;
try {
url = new URL(raw);
} catch (error) {
throw dbUnavailable(`DATABASE_URL is not a valid url: ${raw}`, error);
}
// libpq `options` is the portable way to pin a statement timeout for every pooled connection —
// MERGED into the operator's own, never assigned over it, and emitted for every role including
// the two whose bound is 0. `set` here dropped a `?options=-c search_path=app` on `web`, `sync`,
// `worker` and `scheduler` and kept it on `migrate` and `replicator`, so the role that runs the
// migrations and the role that serves the traffic read different schemas. 0 is a value, not a
// silence: it is `migrate` saying it may take as long as it takes, and left unsaid a server-side
// `alter database ... set statement_timeout` kills the one role that must outlive it.
// `application_name` is a LABEL, not a bound: 'ultimate' is a DEFAULT, and a default may not
// overwrite what the operator wrote — `?application_name=billing-api` is the filter their
// `pg_stat_activity` query, their pooler rule and their audit rule all match on, and losing it
// is silent. Both spellings count, or the URL parameter and a `-c application_name=` in
// `options` disagree and which one the backend honours is argument order nobody here measured.
const named = options.applicationName;
const settings: Record<string, string> = {
statement_timeout: String(profile.statementTimeoutMs),
};
const inOptions = declaresLibpqOption(url.searchParams.get('options'), 'application_name');
// An explicit `applicationName` is a deliberate call by the role that opened the pool, so it
// wins. Only then is the setting named to the merge, and only when the operator wrote the other
// spelling: `mergeLibpqOptions` drops their assignment before appending, so the two cannot
// disagree — and a URL with no assignment in it keeps the exact `options` it always had.
if (named !== undefined && inOptions) settings['application_name'] = named;
const declared = url.searchParams.has('application_name') || inOptions;
url.searchParams.set('options', mergeLibpqOptions(url.searchParams.get('options'), settings));
if (named !== undefined) url.searchParams.set('application_name', named);
else if (!declared) url.searchParams.set('application_name', 'ultimate');
return url.toString();
}
function rowsOf<T>(result: unknown): readonly T[] {

@@ -224,45 +70,2 @@ return Array.isArray(result) ? (result as readonly T[]) : [];

/**
* `pool.reserve()` under a deadline. Without one an exhausted pool does not fail, it **queues** —
* so a slow endpoint filling all 20 slots turns every later request, `/readyz`'s `select 1`
* included, into a wait with no end and no error, and the pod is killed for being unready rather
* than answering 503 for the requests it cannot serve.
*
* The losing reservation is released, never dropped: the pool hands out a connection whenever one
* frees, deadline or no deadline, and a pin nobody holds is a connection nobody gets back. That is
* the whole reason this is not a bare `Promise.race`.
*/
async function reserveWithin(
pool: Pick<BunSqlDriver, 'reserve'>,
profile: PoolProfile,
): Promise<BunSqlReserved> {
const budget = profile.acquireTimeoutMs;
if (budget <= 0) return pool.reserve();
let timer: ReturnType<typeof setTimeout> | undefined;
let expired = false;
const pending = pool.reserve();
try {
return await Promise.race([
pending,
new Promise<never>((_resolve, reject) => {
timer = setTimeout(() => {
expired = true;
reject(poolAcquireTimeout(budget, profile.max));
}, budget);
// The deadline must not be what keeps a finished process alive.
timer.unref?.();
}),
]);
} finally {
if (timer !== undefined) clearTimeout(timer);
// Attached unconditionally so a rejection arriving after we gave up is handled, not unhandled.
void pending.then(
(late) => {
if (expired) late.release();
},
() => undefined,
);
}
}
export interface PostgresClient extends ReservableClient {

@@ -277,3 +80,6 @@ readonly profile: PoolProfile;

const role = options.role ?? resolveRole();
const profile: PoolProfile = { ...poolProfileFor(role), ...(options.profile ?? {}) };
const profile: PoolProfile = assertPoolProfile({
...poolProfileFor(role),
...(options.profile ?? {}),
});
let driver: BunSqlDriver | undefined;

@@ -462,25 +268,1 @@

}
/** Named `Db*` because `@ultimat3/core` already exports a `HealthReport` for the lifecycle. */
export interface DbHealthReport {
readonly ok: boolean;
readonly latencyMs: number;
readonly error?: string | undefined;
}
/** Backs `/readyz` for every role. Never throws — the probe wants a report, not an exception. */
export async function checkDb(client: DbClient = baseClient()): Promise<DbHealthReport> {
const started = performance.now();
try {
await client.query(sql`select 1`);
return { ok: true, latencyMs: Math.round(performance.now() - started) };
} catch (error) {
return {
ok: false,
latencyMs: Math.round(performance.now() - started),
// `renderThrowable`, never `error.message`: the probe wants a report, and a render that
// throws is an exception out of `/readyz` — the one caller that cannot catch it.
error: renderThrowable(error),
};
}
}

@@ -6,3 +6,4 @@ // Single responsibility: the client `baseClient()` builds when an app installed none — the primary

import { createPostgresClient, type DbClient, poolMaxFromEnv } from './client';
import { createPostgresClient, type DbClient } from './client';
import { poolMaxFromEnv } from './pool-profile';
import { replicatedClient } from './replica-client';

@@ -9,0 +10,0 @@

@@ -17,2 +17,3 @@ // Single responsibility: what a schema difference is CALLED and what its `fix:` line says — one

import type { Migration } from './migrate';
import { shellInertIdentifier } from './sql';

@@ -45,2 +46,11 @@ export type DriftKind =

/**
* The one `fix:` here whose second layer no quoting closes. `x db gen "add C"` puts the column
* inside SHELL DOUBLE QUOTES, where `$(…)` and a backtick substitute before `x` is reached at all
* — and the argument is a migration DESCRIPTION, not an identifier, so there is no quoted form
* that would make a hostile name safe to pass. A name `shellInertIdentifier` (`sql.ts`) refuses
* is therefore left out of the command rather than escaped into it: the command still runs and
* still generates the migration, and the name is read off `cause` and `column`, which are prose
* nobody pastes.
*/
export function unexpectedColumn(table: string, column: string): DriftDifference {

@@ -53,3 +63,8 @@ return {

cause: `table "${table}" has column "${column}" not present in any migration`,
fix: `x db gen "add ${column}"`,
fix:
shellInertIdentifier(column) === null
? 'x db gen "add the undeclared column" # the live column name carries a backtick, a ' +
'dollar sign, a quote, a backslash or whitespace, so it is in the cause and not in ' +
'this command'
: `x db gen "add ${column}"`,
};

@@ -88,2 +103,4 @@ }

const clause = liveNullable ? 'set not null' : 'drop not null';
const relation = shellInertIdentifier(table);
const attribute = shellInertIdentifier(column);
return {

@@ -96,9 +113,30 @@ kind: 'changed-column',

: `table "${table}" forbids NULL in column "${column}" that migrations declare nullable`,
// Both identifiers are the catalog's, so both go through the one screen. A refusal names the
// column as the thing it could not spell, which is what tells this line apart from
// `missingCheck`'s refusal in a report that carries both.
fix:
`alter table "${table}" alter column "${column}" ${clause}; # in a new migration` +
(liveNullable ? ' — backfill the existing NULLs first' : ''),
relation === null || attribute === null
? `${clause} on the column named in this difference, in a new migration, then ` +
'x db migrate — its table or column name carries a backtick, a dollar sign, a quote, ' +
'a backslash or whitespace, so no statement here can spell it'
: `alter table ${relation} alter column ${attribute} ${clause}; # in a new migration` +
(liveNullable ? ' — backfill the existing NULLs first' : ''),
};
}
/**
* The `fix:` names the two edits that actually resolve this, and neither is `x db gen` (issue
* #345). That command diffs the ENTITY REGISTRY against the newest snapshot, and a table nothing
* declares is absent from both sides of that diff — so it wrote an EMPTY migration, and the
* generator's own empty-diff branch writes no file at all, leaving the reader with nothing to run
* and the same finding on the next deploy.
*
* What is left once `@ultimat3/cli`'s `acceptCreatedTables` has run is a table no migration's SQL
* creates and no entity declares — so either a migration should claim it (`if not exists`, because
* the relation is already there, and `x db migrate` then accepts a table its own SQL creates), or
* nothing owns it and it should not be in this schema. No migration PATH is named: where an app
* keeps its migrations is the CLI's fact, not this package's.
*/
export function unexpectedTable(table: string): DriftDifference {
const name = shellInertIdentifier(table);
return {

@@ -109,3 +147,10 @@ kind: 'unexpected-table',

cause: `table "${table}" is not present in any migration`,
fix: `x db gen "add ${table}"`,
fix:
name === null
? 'claim it in a migration with create table if not exists, or drop it by hand — its ' +
'table name carries a backtick, a dollar sign, a quote, a backslash or whitespace, so ' +
'no statement here can spell it'
: `put a create table if not exists ${name} (…) statement in a migration — x db migrate ` +
'then accepts a table its own SQL creates — or, if nothing owns it, run ' +
`drop table ${name}; inside psql "$DATABASE_URL"`,
};

@@ -183,2 +228,4 @@ }

export function missingCheck(table: string, check: CheckDescription): DriftDifference {
const relation = shellInertIdentifier(table);
const constraint = shellInertIdentifier(check.name);
return {

@@ -193,5 +240,15 @@ kind: 'missing-check',

// migration` leaves the second half to be guessed.
//
// Both NAMES go through the one screen; the EXPRESSION deliberately does not, and cannot. It
// is a predicate, so no screen could accept `status in ('draft', 'published')` and reject a
// second statement — and it is the DECLARED side's own text, out of the author's migration,
// where both names are the catalog's and a sidecar's. Narrower than "this line is safe", and
// it is the honest claim.
fix:
`alter table "${table}" add constraint "${check.name}" ` +
`check (${check.expression}); # in a new migration, then x db migrate`,
relation === null || constraint === null
? 'add the constraint named in this difference back in a new migration, then ' +
'x db migrate — its table or constraint name carries a backtick, a dollar sign, a ' +
'quote, a backslash or whitespace, so no statement here can spell it'
: `alter table ${relation} add constraint ${constraint} ` +
`check (${check.expression}); # in a new migration, then x db migrate`,
};

@@ -198,0 +255,0 @@ }

@@ -288,11 +288,2 @@ // The database layer's stable error codes. Every factory produces the exact command that

/** The contract's pinned wording. Mirror of `@ultimat3/entity`'s `dbDrift()` — keep in sync. */
export const dbDrift = (tableName: string, columnName: string): DbError =>
new DbError({
code: 'X_DB_DRIFT',
cause: `table "${tableName}" has column "${columnName}" not present in any migration`,
fix: `x db gen "add ${columnName}"`,
meta: { table: tableName, column: columnName },
});
export const sqlUnsafe = (received: string, position: number): DbError =>

@@ -299,0 +290,0 @@ new DbError({

@@ -26,4 +26,2 @@ // Single responsibility: the public API of @ultimat3/db. Explicit named exports only —

DbConnection,
DbHealthReport,
PoolProfile,
PostgresClient,

@@ -33,15 +31,7 @@ PostgresClientOptions,

} from './client';
export {
baseClient,
checkDb,
createPostgresClient,
db,
isReservable,
POOL_MAX_ENV,
POOL_PROFILES,
poolProfileFor,
setDbClient,
} from './client';
export { baseClient, createPostgresClient, db, isReservable, setDbClient } from './client';
export type { ColumnDefaultLike } from './column-default';
export { defaultExpression } from './column-default';
export type { DbHealthReport } from './db-health';
export { checkDb } from './db-health';
export { defaultClient, REPLICA_URL_ENV } from './default-client';

@@ -67,2 +57,3 @@ export type { DestructiveKind, DestructiveStatement } from './destructive';

} from './drift';
export { dbDrift } from './drift-errors';
export type {

@@ -82,3 +73,2 @@ ColumnDescriptionLike,

DbError,
dbDrift,
dbNotImplemented,

@@ -176,2 +166,4 @@ dbUnavailable,

export { branchPglite, pgliteBranchDir } from './pglite-branch';
export type { PoolProfile } from './pool-profile';
export { POOL_MAX_ENV, POOL_PROFILES, poolProfileFor } from './pool-profile';
export type { ReadOnlyQueryOptions, ReadOnlyQueryResult } from './readonly-query';

@@ -193,3 +185,11 @@ export { READONLY_TIMEOUT_MS, readOnlyQuery } from './readonly-query';

export type { SqlFragment } from './sql';
export { identifier, isSqlFragment, join, literal, raw, sql } from './sql';
export {
identifier,
isSqlFragment,
join,
literal,
raw,
shellInertIdentifier,
sql,
} from './sql';
export { stripSqlNoise } from './sql-noise';

@@ -196,0 +196,0 @@ export type { DbSqlStateCode } from './sqlstate';

@@ -6,10 +6,4 @@ // Single responsibility: apply pending migrations and keep the `x_migrations` ledger honest.

import { appVersion } from '@ultimat3/core';
import {
baseClient,
type DbClient,
type DbConnection,
isReservable,
poolProfileFor,
} from './client';
import { appVersion, finiteCount } from '@ultimat3/core';
import { baseClient, type DbClient, type DbConnection, isReservable } from './client';
import { refuseDependentViews } from './dependent-view';

@@ -19,2 +13,3 @@ import { expectedQueryLoop } from './expected-loop';

import { migrateConcurrent, migrationConflict, rollbackStepsInvalid } from './migration-errors';
import { poolProfileFor } from './pool-profile';
import { raw, sql } from './sql';

@@ -242,2 +237,5 @@ import { SQLSTATE, sqlState } from './sqlstate';

if (row?.locked === true) return;
// `waitMs` is screened at both call sites: `NaN - elapsed` is `NaN`, `NaN <= 0` is false
// and `Bun.sleep(Math.min(POLL, NaN))` does not sleep — so an unbounded wait is not what a
// non-finite one produced, a tight spin re-taking `pg_try_advisory_lock` was.
const remaining = waitMs - (performance.now() - started);

@@ -323,3 +321,13 @@ if (remaining <= 0) {

function migrationLockTimeoutMs(explicit: number | undefined): number {
return explicit ?? poolProfileFor('migrate').lockTimeoutMs;
// Screened here, where BOTH `migrate()` and `rollback()` resolve it, and before either takes the
// advisory lock. `lockWaitMs` beside it was screened from the start and this one was not, so
// `Number(process.env.…)` on an unset variable travelled all the way to `SET LOCAL lock_timeout
// = NaN`, which the server rejects inside the migration's own transaction — the option that was
// wrong appears nowhere in what the deploy prints. `finiteCount`, never a second bound checker.
return finiteCount(
'migrate',
'lockTimeoutMs',
explicit ?? poolProfileFor('migrate').lockTimeoutMs,
0,
);
}

@@ -336,3 +344,3 @@

options.lock !== false,
options.lockWaitMs ?? MIGRATION_LOCK_WAIT_MS,
finiteCount('migrate', 'lockWaitMs', options.lockWaitMs ?? MIGRATION_LOCK_WAIT_MS, 0),
async (session) => {

@@ -427,3 +435,3 @@ await ensureLedger(session);

options.lock !== false,
options.lockWaitMs ?? MIGRATION_LOCK_WAIT_MS,
finiteCount('migrate', 'lockWaitMs', options.lockWaitMs ?? MIGRATION_LOCK_WAIT_MS, 0),
async (session) => {

@@ -430,0 +438,0 @@ const ledger = await readLedger(session);

@@ -6,2 +6,3 @@ // Single responsibility: LAYER 2 of `db.query`'s defence-in-depth — an isolated `BEGIN READ

import { finiteCount } from '@ultimat3/core';
import { baseClient, type DbClient, type DbConnection, isReservable } from './client';

@@ -93,2 +94,22 @@ import { multipleStatements } from './errors';

// Decided before anything is opened, for the reason `rollback({ steps })` screens before it
// takes the advisory lock: a value this build cannot honour is not a fact about the pool. It
// was computed after `reserve()` and after `BEGIN READ ONLY`, so an unbounded `timeoutMs`
// against an exhausted pool answered with the pool's error — or waited for a connection it was
// never going to use — in place of the `X_INVARIANT` naming the option that was wrong.
//
// Clamped and truncated to an integer: the result is a JS number the caller never touches
// as text, so there is nothing here for `raw()` to inject — `SET LOCAL` can't bind `$n`
// parameters, which is why this can't go through `sql` the normal, parameterised way.
// REFUSED rather than normalised: `NaN` used to take the default silently, so a config typo
// ran under a timeout nobody wrote. Only an explicit 0 disables the layer, which is why the
// floor is 0 and not 1; the hour ceiling is a clamp on a number that IS one.
const asked = finiteCount(
'readonlyQuery',
'timeoutMs',
options.timeoutMs ?? READONLY_TIMEOUT_MS,
0,
);
const ms = Math.min(3_600_000, asked);
const client = options.client ?? baseClient();

@@ -109,10 +130,2 @@ // A pooled BEGIN that lands on a different physical connection than the query that follows is

// Clamped and truncated to an integer: the result is a JS number the caller never touches
// as text, so there is nothing here for `raw()` to inject — `SET LOCAL` can't bind `$n`
// parameters, which is why this can't go through `sql` the normal, parameterised way.
// NaN normalises to the default first: only an explicit 0 disables the timeout, and
// `Math.min(3_600_000, NaN)` is NaN, which would fail `ms > 0` and silently skip the layer.
const asked = options.timeoutMs ?? READONLY_TIMEOUT_MS;
const requested = Number.isNaN(asked) ? READONLY_TIMEOUT_MS : asked;
const ms = Math.max(0, Math.min(3_600_000, Math.trunc(requested)));
if (ms > 0) {

@@ -119,0 +132,0 @@ // `LOCAL`, so the setting dies with the transaction — one agent read must not re-time

@@ -6,3 +6,3 @@ // Single responsibility: one `DbClient` over a primary and a read replica. It decides nothing about

import { type Clock, logger, renderThrowable, systemClock } from '@ultimat3/core';
import { type Clock, finiteCount, logger, renderThrowable, systemClock } from '@ultimat3/core';
import { type DbClient, type DbConnection, isReservable, type ReservableClient } from './client';

@@ -57,4 +57,17 @@ import { isPlainRead } from './replica-route';

const clock = options.clock ?? systemClock;
const limit = options.breakerFailures ?? BREAKER_FAILURES;
const cooldown = options.breakerCooldownMs ?? BREAKER_COOLDOWN_MS;
// Both are comparisons and nothing else — `consecutiveFailures >= limit` opens the breaker,
// `monotonic() < parkedUntil` holds it open — so a `NaN` in either is a breaker that never trips
// and never parks, with every read still going to the replica that is failing.
const limit = finiteCount(
'replicatedClient',
'breakerFailures',
options.breakerFailures ?? BREAKER_FAILURES,
1,
);
const cooldown = finiteCount(
'replicatedClient',
'breakerCooldownMs',
options.breakerCooldownMs ?? BREAKER_COOLDOWN_MS,
1,
);
let replicaCount = 0;

@@ -61,0 +74,0 @@ let primaryCount = 0;

@@ -135,2 +135,38 @@ // Single responsibility: build parameterised SQL. String interpolation is how every SQL

/**
* The two characters a shell substitutes INSIDE double quotes. Rejected before `identifier` is
* consulted at all, because `identifier` accepts both.
*/
const SHELL_ACTIVE = /[`$]/;
/**
* A quoted identifier that is ALSO inert wherever a human pastes it — or `null` for a name no line
* built here may spell. The one screen a catalog name goes through before it reaches a `fix:`.
*
* TWO layers, and `identifier` closes only the first. It answers about SQL: it refuses `"`, `\`
* and whitespace, and ACCEPTS a backtick and a `$` — `SAFE_IDENTIFIER` above allows `$` on its
* fast path — which are exactly the two characters `$(…)` and a command substitution are built
* from. A column called `$(id)` inside `x db gen "add $(id)"` therefore RUNS `id` the moment its
* reader pastes the line, and a screen reusing `identifier` unchanged would ship a green suite
* over that.
*
* The name is DATA at every caller: `create table "x""; drop table users; --" ("id" int)` is legal
* DDL, so whoever can create a table or a column picks the text that lands in a `fix:`. A refusal
* is degraded to prose by its caller and never escaped — the argument to `x db gen` is a migration
* DESCRIPTION, not an identifier, so there is no quoted form that makes a hostile name safe to
* pass, and a fix naming no command beats one running a second command the reader never read.
*
* `'` is deliberately NOT refused: it is legal in an identifier and inert both in a psql session
* and inside shell double quotes. The price of the two that ARE refused is a legal `a$b` losing
* its executable fix line, which is prose in place of something nobody read running.
*/
export function shellInertIdentifier(name: string): string | null {
if (SHELL_ACTIVE.test(name)) return null;
try {
return identifier(name).text;
} catch {
return null;
}
}
/**
* A quoted string literal Postgres reads IDENTICALLY under both settings of

@@ -137,0 +173,0 @@ * `standard_conforming_strings`. Utility and DDL statements (`CREATE DATABASE`, `COMMENT ON`,

Sorry, the diff of this file is too big to display