1- import { type Driver , isSyncDriver , type QueryResult } from "./drivers/types" ;
1+ import {
2+ type Driver ,
3+ isSyncDriver ,
4+ type QueryResult ,
5+ type TransactionIsolation ,
6+ type TransactionOptions ,
7+ } from "./drivers/types" ;
28import type { Fromable , RowType , RowTypeToTsType } from "./builder/query" ;
39import { QueryBuilder , hydrateRows } from "./builder/query" ;
410import { deserializeRows } from "./util" ;
511import type { Sql } from "./builder/sql" ;
6- import { compile , sql , Ident } from "./builder/sql" ;
12+ import { Ident } from "./builder/sql" ;
713import { Table , type TableBase , type TableOptions } from "./table" ;
814import { Values } from "./builder/values" ;
915import { InsertBuilder } from "./builder/insert" ;
@@ -15,10 +21,7 @@ import { PgExecutor } from "./live/pg/executor";
1521import { StatementExecutor , type Executor } from "./executor" ;
1622import type { DialectName } from "./builder/sql" ;
1723
18- export type TransactionIsolation = "read committed" | "repeatable read" | "serializable" ;
19- export type TransactionOptions = {
20- isolation ?: TransactionIsolation ;
21- } ;
24+ export type { TransactionIsolation , TransactionOptions } from "./drivers/types" ;
2225
2326// Postgres isolation levels are totally ordered. A nested call asking for
2427// weaker-or-equal isolation than the active txn flattens harmlessly (caller
@@ -30,10 +33,10 @@ export type TransactionOptions = {
3033// `default_transaction_isolation`). We can't prove what level we got, so
3134// any *explicit* nested request inside an ambient txn must throw — the
3235// alternative would silently downgrade the caller's expectation.
33- const ISOLATION : { [ K in TransactionIsolation ] : { rank : number ; begin : Sql } } = {
34- "read committed" : { rank : 0 , begin : sql `BEGIN ISOLATION LEVEL READ COMMITTED` } ,
35- "repeatable read" : { rank : 1 , begin : sql `BEGIN ISOLATION LEVEL REPEATABLE READ` } ,
36- "serializable" : { rank : 2 , begin : sql `BEGIN ISOLATION LEVEL SERIALIZABLE` } ,
36+ const ISOLATION : { [ K in TransactionIsolation ] : { rank : number } } = {
37+ "read committed" : { rank : 0 } ,
38+ "repeatable read" : { rank : 1 } ,
39+ "serializable" : { rank : 2 } ,
3740} ;
3841
3942// Provenance identity, no driver and no dialect of its own. Construction
@@ -323,61 +326,38 @@ export class Connection<C = undefined> {
323326 }
324327 return fn ( this ) ;
325328 }
329+ const driver = this . driver ;
326330 const bus = this . #bus;
327- return this . driver . runInSingleConnection ( async ( execute ) => {
328- const driver = this . driver ;
329- let txExecutor : Executor ;
330- if ( this . database . dialect === "postgres" ) {
331- txExecutor = new PgExecutor ( this . database , execute , true ) ;
332- } else if ( this . database . dialect === "sqlite" ) {
333- if ( ! isSyncDriver ( driver ) ) {
334- throw new Error ( "unreachable: sqlite Connection without a SyncDriver" ) ;
335- }
336- // Bound and pooled are the same channel on sqlite's one handle —
337- // checked, not assumed; the bound executor differs only in event
338- // timing (commit-deferred flush).
339- if ( execute !== driver . executeSync ) {
340- throw new Error (
341- "sync driver must pass its executeSync to runInSingleConnection — one handle, one channel" ,
342- ) ;
343- }
344- if ( ! bus ) {
345- throw new Error ( "sqlite Connection is missing its live bus" ) ;
346- }
347- txExecutor = new SqliteLiveExecutor ( this . database , driver , bus , true ) ;
348- } else {
349- txExecutor = new StatementExecutor ( this . database , execute , true ) ;
350- }
351- const tx = new Connection < C > ( this . database , this . driver , txExecutor , opts ?. isolation ) ;
352- // Drivers with a native transaction protocol (Durable Objects) own
353- // commit/rollback; everyone else gets BEGIN/COMMIT/ROLLBACK SQL.
354- if ( driver . runInTransaction ) {
355- try {
356- const result = await driver . runInTransaction ( ( ) => fn ( tx ) ) ;
357- txExecutor . onCommit ( ) ;
358- return result ;
359- } catch ( e ) {
360- txExecutor . onRollback ( ) ;
361- throw e ;
362- }
363- }
364- const runSql = async ( s : Sql ) => execute ( compile ( s , { database : this . database } ) ) ;
365- await runSql ( opts ?. isolation ? ISOLATION [ opts . isolation ] . begin : sql `BEGIN` ) ;
366- try {
367- const result = await fn ( tx ) ;
368- await runSql ( sql `COMMIT` ) ;
369- txExecutor . onCommit ( ) ;
370- return result ;
371- } catch ( e ) {
372- try {
373- await runSql ( sql `ROLLBACK` ) ;
374- } catch ( rollbackErr ) {
375- console . error ( "ROLLBACK failed after transaction error:" , rollbackErr ) ;
331+ let txExecutor : Executor | undefined ;
332+ try {
333+ const result = await driver . runInTransaction ( opts ?? { } , async ( execute ) => {
334+ if ( this . database . dialect === "postgres" ) {
335+ txExecutor = new PgExecutor ( this . database , execute , true ) ;
336+ } else if ( this . database . dialect === "sqlite" ) {
337+ if ( ! isSyncDriver ( driver ) ) {
338+ throw new Error ( "unreachable: sqlite Connection without a SyncDriver" ) ;
339+ }
340+ if ( execute !== driver . executeSync ) {
341+ throw new Error (
342+ "sync driver must pass its executeSync to the transaction — one handle, one channel" ,
343+ ) ;
344+ }
345+ if ( ! bus ) {
346+ throw new Error ( "sqlite Connection is missing its live bus" ) ;
347+ }
348+ txExecutor = new SqliteLiveExecutor ( this . database , driver , bus , true ) ;
349+ } else {
350+ txExecutor = new StatementExecutor ( this . database , execute , true ) ;
376351 }
377- txExecutor . onRollback ( ) ;
378- throw e ;
379- }
380- } ) ;
352+ const tx = new Connection < C > ( this . database , driver , txExecutor , opts ?. isolation ) ;
353+ return fn ( tx ) ;
354+ } ) ;
355+ txExecutor ?. onCommit ( ) ;
356+ return result ;
357+ } catch ( e ) {
358+ txExecutor ?. onRollback ( ) ;
359+ throw e ;
360+ }
381361 }
382362
383363 async close ( ) : Promise < void > {
0 commit comments