@@ -28,7 +28,7 @@ import type {
2828 SqlRuntimeTargetDescriptor ,
2929} from '../src/sql-context' ;
3030import { createExecutionContext , createSqlExecutionStack } from '../src/sql-context' ;
31- import { withTransaction } from '../src/sql-runtime' ;
31+ import { type TempTableQuerySource , withTransaction } from '../src/sql-runtime' ;
3232import { createAsyncSecretCodec , decryptSecret } from './seeded-secret-codec' ;
3333import { defineTestCodec } from './test-codec' ;
3434import { createTestRuntime as createRuntime , descriptorsFromCodecs , stubAst } from './utils' ;
@@ -939,3 +939,110 @@ describe('withTransaction', () => {
939939 expect ( driver . __spies . transactionRollback ) . toHaveBeenCalledTimes ( 1 ) ;
940940 } ) ;
941941} ) ;
942+
943+ describe ( 'RuntimeTransaction.registerPreCommitHook' , ( ) => {
944+ function createRuntimeForHooks ( ) {
945+ const { stackInstance, context, driver } = createTestSetup ( ) ;
946+ const runtime = createRuntime ( { stackInstance, context, driver, verifyMarker : false } ) ;
947+ return { runtime, driver } ;
948+ }
949+
950+ it ( 'runs the hook before driverTx.commit()' , async ( ) => {
951+ const { runtime, driver } = createRuntimeForHooks ( ) ;
952+ const order : string [ ] = [ ] ;
953+ driver . __spies . transactionCommit . mockImplementation ( async ( ) => {
954+ order . push ( 'commit' ) ;
955+ } ) ;
956+
957+ const conn = await runtime . connection ( ) ;
958+ const tx = await conn . transaction ( ) ;
959+ tx . registerPreCommitHook ( async ( ) => {
960+ order . push ( 'hook' ) ;
961+ } ) ;
962+ await tx . commit ( ) ;
963+ await conn . release ( ) ;
964+
965+ expect ( order ) . toEqual ( [ 'hook' , 'commit' ] ) ;
966+ } ) ;
967+
968+ it ( 'runs multiple hooks in registration order before commit' , async ( ) => {
969+ const { runtime, driver } = createRuntimeForHooks ( ) ;
970+ const order : string [ ] = [ ] ;
971+ driver . __spies . transactionCommit . mockImplementation ( async ( ) => {
972+ order . push ( 'commit' ) ;
973+ } ) ;
974+
975+ const conn = await runtime . connection ( ) ;
976+ const tx = await conn . transaction ( ) ;
977+ tx . registerPreCommitHook ( async ( ) => {
978+ order . push ( 'hook-1' ) ;
979+ } ) ;
980+ tx . registerPreCommitHook ( async ( ) => {
981+ order . push ( 'hook-2' ) ;
982+ } ) ;
983+ tx . registerPreCommitHook ( async ( ) => {
984+ order . push ( 'hook-3' ) ;
985+ } ) ;
986+ await tx . commit ( ) ;
987+ await conn . release ( ) ;
988+
989+ expect ( order ) . toEqual ( [ 'hook-1' , 'hook-2' , 'hook-3' , 'commit' ] ) ;
990+ } ) ;
991+
992+ it ( 'aborts commit and propagates the error when a hook throws' , async ( ) => {
993+ const { runtime, driver } = createRuntimeForHooks ( ) ;
994+ const hookError = new Error ( 'hook failed' ) ;
995+
996+ const conn = await runtime . connection ( ) ;
997+ const tx = await conn . transaction ( ) ;
998+ tx . registerPreCommitHook ( async ( ) => {
999+ throw hookError ;
1000+ } ) ;
1001+
1002+ await expect ( tx . commit ( ) ) . rejects . toBe ( hookError ) ;
1003+ expect ( driver . __spies . transactionCommit ) . not . toHaveBeenCalled ( ) ;
1004+ await conn . release ( ) ;
1005+ } ) ;
1006+
1007+ it ( 'does not invoke subsequent hooks after an earlier hook throws' , async ( ) => {
1008+ const { runtime, driver } = createRuntimeForHooks ( ) ;
1009+ const hook2 = vi . fn ( ) ;
1010+
1011+ const conn = await runtime . connection ( ) ;
1012+ const tx = await conn . transaction ( ) ;
1013+ tx . registerPreCommitHook ( async ( ) => {
1014+ throw new Error ( 'hook-1 failed' ) ;
1015+ } ) ;
1016+ tx . registerPreCommitHook ( hook2 ) ;
1017+
1018+ await tx . commit ( ) . catch ( ( ) => { } ) ;
1019+
1020+ expect ( hook2 ) . not . toHaveBeenCalled ( ) ;
1021+ expect ( driver . __spies . transactionCommit ) . not . toHaveBeenCalled ( ) ;
1022+ await conn . release ( ) ;
1023+ } ) ;
1024+
1025+ it ( 'does not invoke hooks on rollback' , async ( ) => {
1026+ const { runtime } = createRuntimeForHooks ( ) ;
1027+ const hook = vi . fn ( ) ;
1028+
1029+ const conn = await runtime . connection ( ) ;
1030+ const tx = await conn . transaction ( ) ;
1031+ tx . registerPreCommitHook ( hook ) ;
1032+ await tx . rollback ( ) ;
1033+ await conn . release ( ) ;
1034+
1035+ expect ( hook ) . not . toHaveBeenCalled ( ) ;
1036+ } ) ;
1037+
1038+ it ( 'no hooks registered — commit proceeds normally' , async ( ) => {
1039+ const { runtime, driver } = createRuntimeForHooks ( ) ;
1040+
1041+ const conn = await runtime . connection ( ) ;
1042+ const tx = await conn . transaction ( ) ;
1043+ await tx . commit ( ) ;
1044+ await conn . release ( ) ;
1045+
1046+ expect ( driver . __spies . transactionCommit ) . toHaveBeenCalledOnce ( ) ;
1047+ } ) ;
1048+ } ) ;
0 commit comments