Skip to content

Changelog - 2026-07-11

Postgres Driver Seam & Supabase

Breaking Change Security New Feature Bug Fix

In 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.

typescript
const transaction = await dataSource.beginTransaction();
await userRepository.create({ data: user, options: { transaction } });
await transaction.commit(); // resolved even when COMMIT failed

commit() 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 IRelationalDriver contract. Both node-postgres (pg) and postgres-js (postgres) implement it, so either can back a datasource without forking it.
  • Transactions fail loudly. commit() and rollback() 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 BEGIN no longer leaks the acquired connection either.
  • pg and postgres are 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) sets request.jwt.claims and SET LOCAL ROLE safely 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/grpc export pointed at a directory that no longer existed and threw ERR_MODULE_NOT_FOUND.

Who is affected

  • Every application using the Postgres connector. No action needed unless your code calls commit()/rollback() directly, or imports TNodePostgresTransactionConnector - 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

typescript
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

typescript
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

typescript
import type { TNodePostgresTransactionConnector } from '@venizia/ignis/postgres';

function useTx(connector: TNodePostgresTransactionConnector<typeof schema>) {}

After

typescript
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:

bash
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's rowCount, postgres-js's count) onto this shared vocabulary.
  • useDriver({ driver, schema? }) assigns this.driver and rebuilds this.connector from 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? }) binds request.jwt.claims as a parameter and sets a transaction-scoped SET LOCAL ROLE so auth.uid() resolves inside RLS policies. role defaults to the JWT's own role claim. The value is validated before any statement runs.
  • Driver asymmetry, deliberate: after a failed COMMIT, node-postgres can 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
FileChanges
src/connectors/postgres/drivers/driver.tsNew. IRelationalDriver / IRelationalConnection / IStatementResult contracts
src/connectors/postgres/drivers/node-postgres.tsNew. NodePostgresDriver
src/connectors/postgres/drivers/postgres-js.tsNew. PostgresJsDriver
src/connectors/postgres/drivers/resolve.tsNew. resolveDatabaseDriver
src/connectors/postgres/drivers/index.tsNew. Exports only the contract + resolveDatabaseDriver
src/connectors/postgres/supabase/pooler.tsNew. PoolerModes + buildPostgresJsOptions
src/connectors/postgres/supabase/rls.tsNew. withAuthContext
src/connectors/postgres/supabase/index.tsNew. Re-exports pooler/rls + Supabase Drizzle roles
src/utilities/drizzle-result.utility.tsNew. readAffectedRowCount / readResultRows driver-shape readers
src/connectors/postgres/datasources/abstract.tsdriver / resolveDriver / useDriver / getClient; pool deprecated
src/connectors/postgres/datasources/base.tsTransaction lifecycle: BEGIN-leak fix, commit/rollback throw + destroy, commit/rollback race guard
src/connectors/postgres/datasources/common/types.tsTRelationalConnector; IDatabaseTransaction.connector retyped; deprecated aliases retained
packages/core-server/package.jsonpg + postgres optional peers; new sub-path exports; grpc export path fixed

See also