drizzle-team/drizzle-orm · info · Error
Nested transactions are handled by NetlifyDbTransaction via
Error message
Nested transactions are handled by NetlifyDbTransaction via savepoints
What it means
An Error 'Nested transactions are handled by NetlifyDbTransaction via savepoints' thrown by NetlifyDbWsSession.transaction() (drizzle-orm/src/netlify-db/session.ts:263). This is an internal guard: NetlifyDbSession.transaction() opens a real transaction using an internal WebSocket session (NetlifyDbWsSession), and nested transactions inside it must go through NetlifyDbTransaction.transaction() (which emits SAVEPOINTs). If something calls the WsSession's transaction() directly, it throws to redirect to the savepoint path.
Source
Thrown at drizzle-orm/src/netlify-db/session.ts:263
rowMode: 'array',
text: query,
values: params,
});
return result;
}
async queryObjects<T extends QueryResultRow>(
query: string,
params: unknown[],
): Promise<QueryResult<T>> {
return this.client.query<T>(query, params);
}
override async transaction<T>(
_transaction: (tx: NetlifyDbTransaction<TFullSchema, TSchema>) => Promise<T>,
_config?: PgTransactionConfig,
): Promise<T> {
throw new Error('Nested transactions are handled by NetlifyDbTransaction via savepoints');
}
}
export class NetlifyDbTransaction<
TFullSchema extends Record<string, unknown>,
TSchema extends V1.TablesRelationalConfig,
> extends PgTransaction<NeonHttpQueryResultHKT, TFullSchema, TSchema> {
static override readonly [entityKind]: string = 'NetlifyDbTransaction';
override async transaction<T>(
transaction: (tx: NetlifyDbTransaction<TFullSchema, TSchema>) => Promise<T>,
): Promise<T> {
const savepointName = `sp${this.nestedIndex + 1}`;
const tx = new NetlifyDbTransaction<TFullSchema, TSchema>(
this.dialect,
this.session,
this.schema,
this.nestedIndex + 1,View on GitHub (pinned to b7862528fd)
Solutions
- Call tx.transaction(...) on the NetlifyDbTransaction passed to your callback, never on db.session.
- Do not reach into internal session objects; use the public db.transaction() / tx.transaction() API.
- If it surfaces from library code, report it — the public API should route nesting through NetlifyDbTransaction.
Example fix
// before (misuse of internals)
await (db as any).session.transaction(async (sp) => { /* ... */ }); // throws — wrong entry point
// after — nest via the handed-back transaction object (savepoints)
await db.transaction(async (tx) => {
await tx.transaction(async (sp) => { /* SAVEPOINT sp1 */ });
}); Defensive patterns
Strategy: validation
Validate before calling
import { NetlifyDbWsSession } from 'drizzle-orm/netlify-db/session';
function isInternalWsSession(s: unknown): boolean {
return s instanceof NetlifyDbWsSession;
}
// ensure nesting goes through the transaction object, never db.session
function assertNotInternalSession(target: unknown) {
if (isInternalWsSession(target)) {
throw new Error('Call tx.transaction() on the NetlifyDbTransaction, not on the internal session.');
}
} Type guard
import { NetlifyDbWsSession } from 'drizzle-orm/netlify-db/session';
function isNetlifyWsSession(s: unknown): s is NetlifyDbWsSession<any, any> {
return s instanceof NetlifyDbWsSession;
} Prevention
- Use only the public API: db.transaction(tx => ...), and nest via tx.transaction(...).
- Never reach into db.session for transactional calls.
- If this surfaces from library code, report it as a bug.
When it happens
Trigger: Only reachable through internal misuse — calling transaction() on the private NetlifyDbWsSession rather than on the NetlifyDbTransaction handed to the db.transaction() callback. Normal nested usage (tx.transaction(...) inside db.transaction()) hits NetlifyDbTransaction.transaction() and works correctly via savepoints.
Common situations: Not a user-facing error in correct usage. Would appear only in tests or custom code that grabs the internal session object and calls .transaction() on it. Seeing it means the wrong transaction entry point was used.
Related errors
- Unexpected state: no column metadata found for index ${index
- Unexpected state: no column name for index ${index} found in
- Rollback
- Cannot pass undefined values to any set operator
- Rollback
AI-assisted analysis of drizzle-team/drizzle-orm@b7862528fd (2026-08-03).
Data as JSON: /data/errors/37de15e1aa9259f6.json.
Report an issue: GitHub.