Skip to content

Commit 9a4ec6a

Browse files
committed
fix(postgres): transaction-manager no longer catches rollaback errors
1 parent 5fdc105 commit 9a4ec6a

File tree

2 files changed

+65
-47
lines changed

2 files changed

+65
-47
lines changed

lib/postgres/src/__tests__/pg-transaction-manager.test.ts

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { DrizzleOrmModule } from '../nestjs/drizzle-orm.module.js';
1616
import { PgTransactionManager } from '../pg-transaction-manager.js';
1717
import { NodePgDatabaseTransaction } from '../pg-transaction.js';
1818
import { testTable } from './test.table.js';
19+
import { InvalidOperationException } from '@ddd-framework/core';
1920

2021
export const expectEntries = vi.fn(
2122
async (
@@ -80,34 +81,63 @@ describe('PgTransactionManager', () => {
8081
});
8182

8283
expect(result).toBe(true);
84+
85+
await expectEntries(db, [{ id: firstId }, { id: secondId }]);
8386
});
8487

85-
test('returns undefined when transaction is rolled back', async () => {
88+
test('transactions are automatically rolled back when an error occurs', async () => {
8689
const firstId = Uuid.generate();
8790
const secondId = Uuid.generate();
8891

89-
const result = await manager.startTransaction(async (transaction) => {
90-
await transaction.context
91-
.insert(testTable)
92-
.values([{ id: firstId }, { id: secondId }]);
92+
await expect(() =>
93+
manager.startTransaction(async (transaction) => {
94+
await transaction.context
95+
.insert(testTable)
96+
.values([{ id: firstId }, { id: secondId }]);
9397

94-
await transaction.rollback();
95-
});
98+
throw new InvalidOperationException('An error occurred');
99+
})
100+
).rejects.toThrow('An error occurred');
101+
102+
await expectEntries(db, []);
103+
});
104+
105+
test('returns undefined when transaction is rolled back (unreachable code)', async () => {
106+
const firstId = Uuid.generate();
107+
const secondId = Uuid.generate();
108+
109+
let result: boolean | undefined;
110+
111+
try {
112+
result = await manager.startTransaction(async (transaction) => {
113+
await transaction.context
114+
.insert(testTable)
115+
.values([{ id: firstId }, { id: secondId }]);
116+
117+
await transaction.rollback();
118+
119+
return false;
120+
});
121+
} catch {}
96122

97123
expect(result).toBeUndefined();
124+
125+
await expectEntries(db, []);
98126
});
99127

100-
test('transaction is not committed when rolled back', async () => {
128+
test('transaction is not committed when manually rolled back', async () => {
101129
const firstId = Uuid.generate();
102130
const secondId = Uuid.generate();
103131

104-
await manager.startTransaction(async (transaction) => {
105-
await transaction.context
106-
.insert(testTable)
107-
.values([{ id: firstId }, { id: secondId }]);
132+
await expect(() =>
133+
manager.startTransaction(async (transaction) => {
134+
await transaction.context
135+
.insert(testTable)
136+
.values([{ id: firstId }, { id: secondId }]);
108137

109-
await transaction.rollback();
110-
});
138+
await transaction.rollback();
139+
})
140+
).rejects.toThrow();
111141

112142
await expectEntries(db, []);
113143
});
@@ -128,20 +158,22 @@ describe('PgTransactionManager', () => {
128158
{ id: secondId }
129159
]);
130160

131-
await manager.savePoint(transaction, async (nestedTransaction) => {
132-
await nestedTransaction.context
133-
.insert(testTable)
134-
.values([{ id: thirdId }, { id: fourthId }]);
135-
136-
await expectEntries(nestedTransaction.context, [
137-
{ id: firstId },
138-
{ id: secondId },
139-
{ id: thirdId },
140-
{ id: fourthId }
141-
]);
142-
143-
await nestedTransaction.rollback();
144-
});
161+
await expect(() =>
162+
manager.savePoint(transaction, async (nestedTransaction) => {
163+
await nestedTransaction.context
164+
.insert(testTable)
165+
.values([{ id: thirdId }, { id: fourthId }]);
166+
167+
await expectEntries(nestedTransaction.context, [
168+
{ id: firstId },
169+
{ id: secondId },
170+
{ id: thirdId },
171+
{ id: fourthId }
172+
]);
173+
174+
await nestedTransaction.rollback();
175+
})
176+
).rejects.toThrow();
145177
});
146178

147179
await expectEntries(db, [{ id: firstId }, { id: secondId }]);

lib/postgres/src/pg-transaction-manager.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
import { TransactionRollbackError } from 'drizzle-orm';
21
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
32
import { AsyncLocalStorage } from 'node:async_hooks';
4-
import {
5-
InvalidOperationException,
6-
TransactionManager
7-
} from '@ddd-framework/core';
3+
import { TransactionManager } from '@ddd-framework/core';
84
import { NodePgDatabaseTransaction, PgTransaction } from './pg-transaction.js';
95

106
export class PgTransactionManager extends TransactionManager {
@@ -34,19 +30,9 @@ export class PgTransactionManager extends TransactionManager {
3430
pgDriver: NodePgDatabase | NodePgDatabaseTransaction,
3531
callback: (transaction: PgTransaction) => Promise<Result>
3632
) {
37-
try {
38-
const res = await pgDriver.transaction<Result>((context) => {
39-
const transaction = new PgTransaction(this.store);
40-
return this.store.run(context, () => callback(transaction));
41-
});
42-
return res;
43-
} catch (error) {
44-
// Needed to cast because unreachable code after a `rollback` is not detected by the TypeScript compiler,
45-
// even though the `ReturnType` of the `rollback` is `never`
46-
// This is a workaround to make it work for now until it's fixed.
47-
if (error instanceof TransactionRollbackError) return undefined as Result;
48-
const err = error instanceof Error ? error : new Error('Unknown error');
49-
throw new InvalidOperationException(err.message, error);
50-
}
33+
return pgDriver.transaction<Result>((context) => {
34+
const transaction = new PgTransaction(this.store);
35+
return this.store.run(context, () => callback(transaction));
36+
});
5137
}
5238
}

0 commit comments

Comments
 (0)