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 ​