Skip to content

Commit a8ea332

Browse files
committed
feat(postgres): added pool support
1 parent 322f6de commit a8ea332

12 files changed

+341
-14
lines changed

lib/postgres/src/__tests__/drizzle-orm.module.test.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,37 @@ import { PgDatabase } from 'drizzle-orm/pg-core';
22
import { afterAll, beforeAll, describe, expect, test } from 'vitest';
33
import { Test, TestingModule } from '@nestjs/testing';
44
import { DrizzleOrmModule } from '../nestjs/drizzle-orm.module.js';
5+
import { Pool } from 'pg';
56

6-
describe('PgTransactionManager', () => {
7+
describe('DrizzleOrmModule', () => {
78
let module: TestingModule;
89

910
beforeAll(async () => {
1011
module = await Test.createTestingModule({
11-
imports: [DrizzleOrmModule.forRoot({ client: globalThis.__pgClient })]
12+
imports: [DrizzleOrmModule.forRoot({ pg: globalThis.__pgClient })]
13+
}).compile();
14+
15+
await module.init();
16+
});
17+
18+
afterAll(async () => {
19+
await module?.close();
20+
});
21+
22+
test('return an instance of PgDatabase', () => {
23+
const db = module.get(PgDatabase);
24+
expect(db).toBeInstanceOf(PgDatabase);
25+
});
26+
});
27+
28+
describe('DrizzleOrmModule with a pool', () => {
29+
let module: TestingModule;
30+
31+
beforeAll(async () => {
32+
module = await Test.createTestingModule({
33+
imports: [
34+
DrizzleOrmModule.forRoot({ pg: new Pool(globalThis.__dbCredentials) })
35+
]
1236
}).compile();
1337

1438
await module.init();

lib/postgres/src/__tests__/pg-client.module.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,22 @@ describe('PgClientModule', () => {
3737

3838
await module.close();
3939
});
40+
41+
test('module connects clients on bootstrap and disconnects on shutdown', async () => {
42+
const module = await Test.createTestingModule({
43+
imports: [PgClientModule.forRoot(globalThis.__dbCredentials)]
44+
}).compile();
45+
46+
await module.init();
47+
48+
const client = module.get(pg.Client);
49+
50+
const res = await client.query('SELECT NOW()');
51+
52+
expect(res.rows[0].now).toBeDefined();
53+
54+
await module.close();
55+
56+
expect(() => client.query('SELECT NOW()')).rejects.toThrow();
57+
});
4058
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import pg from 'pg';
2+
import { describe, expect, test } from 'vitest';
3+
import { Test } from '@nestjs/testing';
4+
import { PgPoolModule } from '../nestjs/pg-pool.module.js';
5+
6+
describe('PgPoolModule', () => {
7+
test('module returns an instance of Pool using forRoot', async () => {
8+
const module = await Test.createTestingModule({
9+
imports: [
10+
PgPoolModule.forRoot({
11+
port: 5432
12+
})
13+
]
14+
}).compile();
15+
16+
const pool = module.get(pg.Pool);
17+
18+
expect(pool).toBeInstanceOf(pg.Pool);
19+
20+
await module.close();
21+
});
22+
23+
test('module returns an instance of Client using forRootAsync', async () => {
24+
const module = await Test.createTestingModule({
25+
imports: [
26+
PgPoolModule.forRootAsync({
27+
useFactory: () => ({
28+
port: 5432
29+
})
30+
})
31+
]
32+
}).compile();
33+
34+
const pool = module.get(pg.Pool);
35+
36+
expect(pool).toBeInstanceOf(pg.Pool);
37+
38+
await module.close();
39+
});
40+
41+
test('module disconnects pools on shutdown', async () => {
42+
const module = await Test.createTestingModule({
43+
imports: [PgPoolModule.forRoot(globalThis.__dbCredentials)]
44+
}).compile();
45+
46+
await module.init();
47+
48+
const pool = module.get(pg.Pool);
49+
50+
const poolRes = await pool.query('SELECT NOW()');
51+
52+
expect(poolRes.rows[0].now).toBeDefined();
53+
54+
const client = await pool.connect();
55+
56+
const clientRes = await client.query('SELECT NOW()');
57+
58+
expect(clientRes.rows[0].now).toBeDefined();
59+
60+
client.release();
61+
62+
expect(() => client.query('SELECT NOW()')).rejects.toThrow();
63+
64+
await module.close();
65+
66+
expect(() => pool.connect()).rejects.toThrow();
67+
68+
expect(() => pool.query('SELECT NOW()')).rejects.toThrow();
69+
});
70+
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('PgTransactionManagerModule', () => {
2424
beforeAll(async () => {
2525
module = await Test.createTestingModule({
2626
imports: [
27-
DrizzleOrmModule.forRoot({ client: globalThis.__pgClient }),
27+
DrizzleOrmModule.forRoot({ pg: globalThis.__pgClient }),
2828
PgTransactionManagerModule
2929
]
3030
}).compile();

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

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { PgTransactionManager } from '../pg-transaction-manager.js';
1717
import { testTable2 } from './test.table.js';
1818
import { InvalidOperationException } from '@ddd-framework/core';
1919
import { expectEntries } from './assertions.js';
20+
import { Pool } from 'pg';
2021

2122
describe('PgTransactionManager', () => {
2223
let module: TestingModule;
@@ -25,7 +26,7 @@ describe('PgTransactionManager', () => {
2526

2627
beforeAll(async () => {
2728
module = await Test.createTestingModule({
28-
imports: [DrizzleOrmModule.forRoot({ client: globalThis.__pgClient })]
29+
imports: [DrizzleOrmModule.forRoot({ pg: globalThis.__pgClient })]
2930
}).compile();
3031

3132
await module.init();
@@ -170,3 +171,83 @@ describe('PgTransactionManager', () => {
170171
await expectEntries(db, testTable2, [{ id: firstId }, { id: secondId }]);
171172
});
172173
});
174+
175+
describe('PgTransactionManager with a pool', () => {
176+
let module: TestingModule;
177+
let db: NodePgDatabase;
178+
let manager: PgTransactionManager;
179+
180+
beforeAll(async () => {
181+
module = await Test.createTestingModule({
182+
imports: [
183+
DrizzleOrmModule.forRoot({ pg: new Pool(globalThis.__dbCredentials) })
184+
]
185+
}).compile();
186+
187+
await module.init();
188+
189+
db = module.get(PgDatabase);
190+
191+
// This would be returned by a dependency injection container.
192+
manager = new PgTransactionManager(db);
193+
});
194+
195+
beforeEach(async () => {
196+
await db.delete(testTable2);
197+
});
198+
199+
afterEach(async () => {
200+
vi.clearAllMocks();
201+
});
202+
203+
afterAll(async () => {
204+
await module?.close();
205+
});
206+
207+
test('starts transaction and implicitly commits', async () => {
208+
const firstId = Uuid.generate();
209+
const secondId = Uuid.generate();
210+
211+
await manager.startTransaction(async (transaction) => {
212+
await transaction.context
213+
.insert(testTable2)
214+
.values([{ id: firstId }, { id: secondId }]);
215+
});
216+
217+
await expectEntries(db, testTable2, [{ id: firstId }, { id: secondId }]);
218+
});
219+
220+
test('returns result from transaction', async () => {
221+
const firstId = Uuid.generate();
222+
const secondId = Uuid.generate();
223+
224+
const result = await manager.startTransaction(async (transaction) => {
225+
await transaction.context
226+
.insert(testTable2)
227+
.values([{ id: firstId }, { id: secondId }]);
228+
229+
return true;
230+
});
231+
232+
expect(result).toBe(true);
233+
234+
await expectEntries(db, testTable2, [{ id: firstId }, { id: secondId }]);
235+
});
236+
237+
test('transactions are automatically rolled back when an error occurs', async () => {
238+
const firstId = Uuid.generate();
239+
const secondId = Uuid.generate();
240+
241+
await expect(() =>
242+
manager.startTransaction(async (transaction) => {
243+
await transaction.context
244+
.insert(testTable2)
245+
.values([{ id: firstId }, { id: secondId }]);
246+
247+
throw new InvalidOperationException('An error occurred');
248+
})
249+
).rejects.toThrow('An error occurred');
250+
251+
await expectEntries(db, testTable2, []);
252+
});
253+
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('PgTransaction', () => {
2222

2323
beforeAll(async () => {
2424
module = await Test.createTestingModule({
25-
imports: [DrizzleOrmModule.forRoot({ client: globalThis.__pgClient })]
25+
imports: [DrizzleOrmModule.forRoot({ pg: globalThis.__pgClient })]
2626
}).compile();
2727

2828
await module.init();

lib/postgres/src/nestjs/drizzle-orm.module.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '@nestjs/common';
1515

1616
export type DrizzleOrmModuleOptions = {
17-
client: pg.Client;
17+
pg: pg.Client | pg.Pool;
1818
options?: DrizzleConfig;
1919
};
2020

@@ -43,12 +43,12 @@ export class DrizzleOrmModule {
4343
* Configurations are only loaded once because the internal module is global.
4444
*/
4545
public static forRoot({
46-
client,
46+
pg,
4747
options
4848
}: DrizzleOrmModuleOptions): DynamicModule {
4949
const dbProvider: Provider<PgDatabase<PgQueryResultHKT>> = {
5050
provide: PgDatabase,
51-
useValue: drizzle(client, options)
51+
useValue: drizzle(pg, options)
5252
};
5353

5454
return {
@@ -69,9 +69,9 @@ export class DrizzleOrmModule {
6969
const dbProvider = {
7070
provide: PgDatabase,
7171
useFactory: async ({
72-
options = {},
73-
client
74-
}: DrizzleOrmModuleOptions): Promise<unknown> => drizzle(client, options),
72+
pg,
73+
options = {}
74+
}: DrizzleOrmModuleOptions): Promise<unknown> => drizzle(pg, options),
7575
inject: [this.OPTIONS_PROVIDER_TOKEN]
7676
};
7777

0 commit comments

Comments
 (0)