Changelog - 2026-07-11
Postgres Driver Seam & Supabase
Breaking Change Security New Feature Bug FixIn one line. The Postgres connector now supports two interchangeable drivers, node-postgres and postgres-js, and ships a Supabase submodule. commit()/rollback() now throw on failure instead of silently succeeding.
The problem it solves
A failed COMMIT used to resolve successfully. The transaction looked committed, but nothing was written - and a failed BEGIN leaked the acquired connection.
const transaction = await dataSource.beginTransaction();
await userRepository.create({ data: user, options: { transaction } });
await transaction.commit(); // resolved even when COMMIT failedcommit() and rollback() now throw when the underlying database operation fails. A poisoned connection is destroyed instead of returned to the pool.
What changed
- Two drivers, one connector. Postgres connections now go through a neutral
IRelationalDrivercontract. Bothnode-postgres(pg) andpostgres-js(postgres) implement it, so either can back a datasource without forking it. - Transactions fail loudly.
commit()androllback()now throw when the underlying database operation fails, instead of resolving successfully. - IGNIS destroys the connection behind a failed commit/rollback, instead of returning it to the pool. A failed
BEGINno longer leaks the acquired connection either. pgandpostgresare genuinely optional now. Both database client packages are optional peer dependencies - an app that uses one never needs to install the other.- New package sub-paths.
@venizia/ignis/postgres/node-postgres,@venizia/ignis/postgres/postgres-js, and@venizia/ignis/postgres/supabase. - New Supabase submodule. Pooler-mode presets for Supavisor (
PoolerModes,buildPostgresJsOptions). A transaction-scoped RLS helper (withAuthContext) setsrequest.jwt.claimsandSET LOCAL ROLEsafely under a transaction pooler. - Security fix. The RLS role helper now validates the role against a strict identifier pattern before use. This closes a SQL-injection path through a JWT-supplied role claim.
- Fixed. The
@venizia/ignis/grpcexport pointed at a directory that no longer existed and threwERR_MODULE_NOT_FOUND.
Who is affected
- Every application using the Postgres connector. No action needed unless your code calls
commit()/rollback()directly, or importsTNodePostgresTransactionConnector- see Breaking changes below. - Supabase users. New opt-in submodule at
@venizia/ignis/postgres/supabase. Nothing changes unless you adopt it. - Apps that only install one of
pg/postgres. No action needed; both are optional now.
Breaking changes
1. commit() and rollback() now throw on failure
Before
const transaction = await dataSource.beginTransaction();
try {
await userRepository.create({ data: user, options: { transaction } });
await transaction.commit(); // a failed COMMIT resolved silently
} catch (error) {
await transaction.rollback(); // could throw and mask `error`
throw error;
}After
const transaction = await dataSource.beginTransaction();
try {
await userRepository.create({ data: user, options: { transaction } });
await transaction.commit(); // now throws if COMMIT fails
} catch (error) {
try {
await transaction.rollback();
} catch (rollbackError) {
// log and continue - `error` is still what the caller must see
}
throw error;
}Nest the rollback in its own try so a rollback failure never masks the original error.
2. IDatabaseTransaction.connector is retyped
connector is now TRelationalConnector<Schema> instead of the node-postgres-specific TNodePostgresTransactionConnector.
Before
import type { TNodePostgresTransactionConnector } from '@venizia/ignis/postgres';
function useTx(connector: TNodePostgresTransactionConnector<typeof schema>) {}After
import type { TRelationalConnector } from '@venizia/ignis/postgres';
function useTx(connector: TRelationalConnector<typeof schema>) {}Not urgent - old names still compile
TNodePostgresTransactionConnector and TNodePostgresConnector remain as @deprecated compatibility aliases, so existing code keeps compiling. Migrate to TRelationalConnector at your convenience.
3. If your build tooling prunes optional peers
pg and postgres are now optional. If your tooling prunes unused optional peers, install the one you actually use:
bun add pg # node-postgres
bun add postgres # postgres-js (needed for the Supabase transaction pooler)Details
- Driver contract:
IRelationalDriver(createConnector,acquire,getClient,end),IRelationalConnection(execute,release),IStatementResult({ count }). Each driver maps its native result shape (pg'srowCount, postgres-js'scount) onto this shared vocabulary. useDriver({ driver, schema? })assignsthis.driverand rebuildsthis.connectorfrom it in one call, so a datasource can never end up half-wired.resolveDatabaseDriver({ client })structurally detects the client (postgres-js vs node-postgres) and dynamically imports only the matching driver. The losing package never loads.- Supabase pooler presets:
buildPostgresJsOptions({ mode: PoolerModes.TRANSACTION })returns{ prepare: false }. Supavisor's transaction pool mode requires it, since a prepared statement does not survive a backend swap. withAuthContext({ transaction, claims, role? })bindsrequest.jwt.claimsas a parameter and sets a transaction-scopedSET LOCAL ROLEsoauth.uid()resolves inside RLS policies.roledefaults to the JWT's ownroleclaim. The value is validated before any statement runs.- Driver asymmetry, deliberate: after a failed
COMMIT,node-postgrescan discard the poisoned connection.postgres-js's reserved-connection API cannot, so it returns the connection to its pool instead. - Both drivers accept
release({ destroy: true }), but only the one that can act on it honors the flag.
Files changed
| File | Changes |
|---|---|
src/connectors/postgres/drivers/driver.ts | New. IRelationalDriver / IRelationalConnection / IStatementResult contracts |
src/connectors/postgres/drivers/node-postgres.ts | New. NodePostgresDriver |
src/connectors/postgres/drivers/postgres-js.ts | New. PostgresJsDriver |
src/connectors/postgres/drivers/resolve.ts | New. resolveDatabaseDriver |
src/connectors/postgres/drivers/index.ts | New. Exports only the contract + resolveDatabaseDriver |
src/connectors/postgres/supabase/pooler.ts | New. PoolerModes + buildPostgresJsOptions |
src/connectors/postgres/supabase/rls.ts | New. withAuthContext |
src/connectors/postgres/supabase/index.ts | New. Re-exports pooler/rls + Supabase Drizzle roles |
src/utilities/drizzle-result.utility.ts | New. readAffectedRowCount / readResultRows driver-shape readers |
src/connectors/postgres/datasources/abstract.ts | driver / resolveDriver / useDriver / getClient; pool deprecated |
src/connectors/postgres/datasources/base.ts | Transaction lifecycle: BEGIN-leak fix, commit/rollback throw + destroy, commit/rollback race guard |
src/connectors/postgres/datasources/common/types.ts | TRelationalConnector; IDatabaseTransaction.connector retyped; deprecated aliases retained |
packages/core-server/package.json | pg + postgres optional peers; new sub-path exports; grpc export path fixed |
See also
- Postgres Drivers & Supabase - the how-to guide
- Transactions - the transaction lifecycle in full
- Connectors Consistency Hardening - the same-day connector review