@hg-ts/knex 0.5.16 → 0.5.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/knex.module.d.ts +0 -3
- package/dist/knex.module.d.ts.map +1 -1
- package/dist/knex.module.js +2 -14
- package/dist/knex.module.js.map +1 -1
- package/dist/knex.repository.suite.d.ts.map +1 -1
- package/dist/knex.repository.suite.js +6 -4
- package/dist/knex.repository.suite.js.map +1 -1
- package/package.json +24 -24
- package/src/event.knex.repository.ts +112 -0
- package/src/event.migration.ts +26 -0
- package/src/external-config.ts +73 -0
- package/src/index.ts +13 -0
- package/src/knex.config.ts +16 -0
- package/src/knex.module.ts +25 -0
- package/src/knex.repository.suite.ts +157 -0
- package/src/knex.repository.ts +17 -0
- package/src/knex.service.ts +132 -0
- package/src/migrations/20250920124822_addEventTable.ts +1 -0
- package/src/seeds/.gitkeep +0 -0
- package/src/test-containers/index.ts +1 -0
- package/src/test-containers/postgres-testcontainer.module.ts +15 -0
- package/src/test-containers/postgres-testcontainer.service.ts +77 -0
- package/src/tests/event.knex.repository.test.ts +93 -0
- package/src/tests/index.ts +1 -0
- package/src/tests/repository.testing.module.ts +17 -0
- package/src/tests/test.event.ts +11 -0
package/dist/knex.module.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knex.module.d.ts","sourceRoot":"","sources":["../src/knex.module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"knex.module.d.ts","sourceRoot":"","sources":["../src/knex.module.ts"],"names":[],"mappings":"AAUA,qBAca,UAAU;CAAG"}
|
package/dist/knex.module.js
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
|
-
var KnexModule_1;
|
|
2
1
|
import { __decorate } from "tslib";
|
|
3
2
|
import { ConfigLoader } from '@hg-ts/config-loader';
|
|
4
3
|
import { Global, Module, } from '@nestjs/common';
|
|
5
4
|
import { KnexConfig } from './knex.config.js';
|
|
6
5
|
import { KnexService } from './knex.service.js';
|
|
7
|
-
let KnexModule =
|
|
8
|
-
static forRoot(configLoader) {
|
|
9
|
-
return {
|
|
10
|
-
module: KnexModule_1,
|
|
11
|
-
providers: [
|
|
12
|
-
{
|
|
13
|
-
provide: ConfigLoader,
|
|
14
|
-
useValue: configLoader,
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
};
|
|
18
|
-
}
|
|
6
|
+
let KnexModule = class KnexModule {
|
|
19
7
|
};
|
|
20
|
-
KnexModule =
|
|
8
|
+
KnexModule = __decorate([
|
|
21
9
|
Global(),
|
|
22
10
|
Module({
|
|
23
11
|
providers: [
|
package/dist/knex.module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knex.module.js","sourceRoot":"","sources":["../src/knex.module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"knex.module.js","sourceRoot":"","sources":["../src/knex.module.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EACN,MAAM,EACN,MAAM,GACN,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgBzC,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,UAAU;IAdtB,MAAM,EAAE;IACR,MAAM,CAAC;QACP,SAAS,EAAE;YACV,WAAW;YACX;gBACC,OAAO,EAAE,UAAU;gBACnB,KAAK,CAAC,UAAU,CAAC,YAA0B;oBAC1C,OAAO,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;gBAC9C,CAAC;gBACD,MAAM,EAAE,CAAC,YAAY,CAAC;aACtB;SACD;QACD,OAAO,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC;KAClC,CAAC;GACW,UAAU,CAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knex.repository.suite.d.ts","sourceRoot":"","sources":["../src/knex.repository.suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAK9C,OAAO,EAGN,KAAK,EAEL,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK1C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKhD,8BAAsB,mBAAmB,CAAC,SAAS,SAAS,aAAa,CAAC,GAAG,CAAC,CAAE,SAAQ,KAAK;IACrF,WAAW,EAAE,WAAW,CAAC;IAChC,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,eAAe,CAA6C;IAEpE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA0C;IAC1E,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IAGzD,SAAS,aAAa,eAAe,EAAE,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;IAMjE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"knex.repository.suite.d.ts","sourceRoot":"","sources":["../src/knex.repository.suite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAK9C,OAAO,EAGN,KAAK,EAEL,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK1C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKhD,8BAAsB,mBAAmB,CAAC,SAAS,SAAS,aAAa,CAAC,GAAG,CAAC,CAAE,SAAQ,KAAK;IACrF,WAAW,EAAE,WAAW,CAAC;IAChC,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,eAAe,CAA6C;IAEpE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA0C;IAC1E,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IAGzD,SAAS,aAAa,eAAe,EAAE,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;IAMjE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAY1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAa9B,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAW7B,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IASvC,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/C,SAAS,CAAC,sBAAsB,IAAI,QAAQ,EAAE;IAI9C,SAAS,CAAC,QAAQ,CAAC,eAAe,IAAI,SAAS;IAC/C,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;CAC5C"}
|
|
@@ -15,10 +15,12 @@ export class KnexRepositorySuite extends Suite {
|
|
|
15
15
|
this.repositoryClass = repositoryClass;
|
|
16
16
|
}
|
|
17
17
|
async getNextId() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
await this.knexService.runInTransaction(async () => {
|
|
19
|
+
const id = await this.repository.getNextId();
|
|
20
|
+
this.checkId(id);
|
|
21
|
+
const existsEntityWithId = await this.repository.has(id);
|
|
22
|
+
expect(existsEntityWithId).toBeFalsy();
|
|
23
|
+
});
|
|
22
24
|
}
|
|
23
25
|
async addSuccess() {
|
|
24
26
|
const aggregate = this.createAggregate();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knex.repository.suite.js","sourceRoot":"","sources":["../src/knex.repository.suite.ts"],"names":[],"mappings":";AACA,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,MAAM,EACN,eAAe,EACf,KAAK,EACL,IAAI,GACJ,MAAM,cAAc,CAAC;AAEtB,OAAO,EACN,IAAI,IAAI,WAAW,GAEnB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,MAAM,mBAAmB,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAC;AAE9D,MAAM,OAAgB,mBAA0D,SAAQ,KAAK;IACrF,WAAW,CAAc;IACxB,MAAM,CAAgB;IACtB,eAAe,GAAwC,IAAI,CAAC;IAEnD,eAAe,CAA0C;IAI1E,YAAsB,eAAwD;QAC7E,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACxC,CAAC;IAGY,AAAN,KAAK,CAAC,SAAS;QACrB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"knex.repository.suite.js","sourceRoot":"","sources":["../src/knex.repository.suite.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hg-ts/knex",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.18",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,19 +18,19 @@
|
|
|
18
18
|
"test:dev": "vitest watch"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@hg-ts-config/typescript": "0.5.
|
|
22
|
-
"@hg-ts/async-context": "0.5.
|
|
23
|
-
"@hg-ts/config-loader": "0.5.
|
|
24
|
-
"@hg-ts/domain": "0.5.
|
|
25
|
-
"@hg-ts/events": "0.5.
|
|
26
|
-
"@hg-ts/exception": "0.5.
|
|
27
|
-
"@hg-ts/execution-mode": "0.5.
|
|
28
|
-
"@hg-ts/linter": "0.5.
|
|
29
|
-
"@hg-ts/logger": "0.5.
|
|
30
|
-
"@hg-ts/repository": "0.5.
|
|
31
|
-
"@hg-ts/tests": "0.5.
|
|
32
|
-
"@hg-ts/types": "0.5.
|
|
33
|
-
"@hg-ts/validation": "0.5.
|
|
21
|
+
"@hg-ts-config/typescript": "0.5.18",
|
|
22
|
+
"@hg-ts/async-context": "0.5.18",
|
|
23
|
+
"@hg-ts/config-loader": "0.5.18",
|
|
24
|
+
"@hg-ts/domain": "0.5.18",
|
|
25
|
+
"@hg-ts/events": "0.5.18",
|
|
26
|
+
"@hg-ts/exception": "0.5.18",
|
|
27
|
+
"@hg-ts/execution-mode": "0.5.18",
|
|
28
|
+
"@hg-ts/linter": "0.5.18",
|
|
29
|
+
"@hg-ts/logger": "0.5.18",
|
|
30
|
+
"@hg-ts/repository": "0.5.18",
|
|
31
|
+
"@hg-ts/tests": "0.5.18",
|
|
32
|
+
"@hg-ts/types": "0.5.18",
|
|
33
|
+
"@hg-ts/validation": "0.5.18",
|
|
34
34
|
"@nestjs/common": "11.1.0",
|
|
35
35
|
"@nestjs/core": "11.1.0",
|
|
36
36
|
"@nestjs/testing": "11.1.0",
|
|
@@ -47,16 +47,16 @@
|
|
|
47
47
|
"vitest": "4.0.14"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@hg-ts/async-context": "0.5.
|
|
51
|
-
"@hg-ts/config-loader": "0.5.
|
|
52
|
-
"@hg-ts/domain": "0.5.
|
|
53
|
-
"@hg-ts/events": "0.5.
|
|
54
|
-
"@hg-ts/exception": "0.5.
|
|
55
|
-
"@hg-ts/execution-mode": "0.5.
|
|
56
|
-
"@hg-ts/logger": "0.5.
|
|
57
|
-
"@hg-ts/repository": "0.5.
|
|
58
|
-
"@hg-ts/tests": "0.5.
|
|
59
|
-
"@hg-ts/validation": "0.5.
|
|
50
|
+
"@hg-ts/async-context": "0.5.18",
|
|
51
|
+
"@hg-ts/config-loader": "0.5.18",
|
|
52
|
+
"@hg-ts/domain": "0.5.18",
|
|
53
|
+
"@hg-ts/events": "0.5.18",
|
|
54
|
+
"@hg-ts/exception": "0.5.18",
|
|
55
|
+
"@hg-ts/execution-mode": "0.5.18",
|
|
56
|
+
"@hg-ts/logger": "0.5.18",
|
|
57
|
+
"@hg-ts/repository": "0.5.18",
|
|
58
|
+
"@hg-ts/tests": "0.5.18",
|
|
59
|
+
"@hg-ts/validation": "0.5.18",
|
|
60
60
|
"@nestjs/common": "*",
|
|
61
61
|
"@nestjs/core": "*",
|
|
62
62
|
"@nestjs/testing": "*",
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DomainEvent,
|
|
3
|
+
STORED_PROPERTY,
|
|
4
|
+
} from '@hg-ts/domain';
|
|
5
|
+
import {
|
|
6
|
+
BaseEventRepository,
|
|
7
|
+
EventFindOptions,
|
|
8
|
+
NotEmptyList,
|
|
9
|
+
} from '@hg-ts/repository';
|
|
10
|
+
|
|
11
|
+
import { Inject } from '@nestjs/common';
|
|
12
|
+
|
|
13
|
+
import { KnexService } from './knex.service.js';
|
|
14
|
+
|
|
15
|
+
export type EventTable = {
|
|
16
|
+
id: string;
|
|
17
|
+
entityName: string;
|
|
18
|
+
entityId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
body: unknown;
|
|
21
|
+
traceId: Nullable<string>;
|
|
22
|
+
occurredOn: Date;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
declare module 'knex/types/tables.js' {
|
|
27
|
+
// eslint-disable-next-line @typescript/consistent-type-definitions
|
|
28
|
+
interface Tables {
|
|
29
|
+
event: EventTable;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class EventKnexRepository extends BaseEventRepository {
|
|
34
|
+
@Inject()
|
|
35
|
+
protected readonly knex: KnexService;
|
|
36
|
+
|
|
37
|
+
public override async add(entityOrEntities: DomainEvent[] | DomainEvent): Promise<void> {
|
|
38
|
+
const entities = Array.isArray(entityOrEntities) ? entityOrEntities : [entityOrEntities];
|
|
39
|
+
|
|
40
|
+
return super.add(entities.filter(entity => !entity[STORED_PROPERTY]));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected override async rawAdd(entities: NotEmptyList<DomainEvent>): Promise<void> {
|
|
44
|
+
const trx = await this.knex.getMasterTrx();
|
|
45
|
+
|
|
46
|
+
const dtos = entities.map(entity => entity.toDto());
|
|
47
|
+
|
|
48
|
+
await trx('event').insert(dtos);
|
|
49
|
+
|
|
50
|
+
entities.forEach(entity => {
|
|
51
|
+
entity[STORED_PROPERTY] = true;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected override async findEntities(
|
|
56
|
+
options: Partial<EventFindOptions> = {},
|
|
57
|
+
table: 'event' = 'event',
|
|
58
|
+
): Promise<DomainEvent[]> {
|
|
59
|
+
const { id, entityId, entityName, limit } = options;
|
|
60
|
+
|
|
61
|
+
const trx = await this.knex.getMasterTrx();
|
|
62
|
+
|
|
63
|
+
const qb = trx(table);
|
|
64
|
+
|
|
65
|
+
if (id) {
|
|
66
|
+
if (Array.isArray(id)) {
|
|
67
|
+
qb.whereIn('id', id);
|
|
68
|
+
} else {
|
|
69
|
+
qb.where('id', id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (entityId) {
|
|
74
|
+
if (Array.isArray(entityId)) {
|
|
75
|
+
qb.whereIn('entityId', entityId);
|
|
76
|
+
} else {
|
|
77
|
+
qb.where('entityId', entityId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (entityName) {
|
|
82
|
+
qb.where('entityName', entityName);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (limit) {
|
|
86
|
+
qb.limit(limit);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const rows = await qb;
|
|
90
|
+
|
|
91
|
+
return rows.map(row => {
|
|
92
|
+
const { name, ...eventData } = row;
|
|
93
|
+
const event = this.createEvent(name, eventData);
|
|
94
|
+
|
|
95
|
+
event[STORED_PROPERTY] = true;
|
|
96
|
+
|
|
97
|
+
return event;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
protected createEvent(name: string, row: Omit<EventTable, 'name'>): DomainEvent {
|
|
102
|
+
return new DomainEvent({
|
|
103
|
+
id: row.id,
|
|
104
|
+
body: row.body,
|
|
105
|
+
entityName: row.entityName,
|
|
106
|
+
entityId: row.entityId,
|
|
107
|
+
occurredOn: row.occurredOn,
|
|
108
|
+
traceId: row.traceId,
|
|
109
|
+
name,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Knex } from 'knex';
|
|
2
|
+
|
|
3
|
+
function createEventTable(table: Knex.CreateTableBuilder, knex: Knex): void {
|
|
4
|
+
table.uuid('id').primary();
|
|
5
|
+
|
|
6
|
+
table.string('entityName').notNullable();
|
|
7
|
+
table.string('entityId').notNullable();
|
|
8
|
+
|
|
9
|
+
table.string('name').notNullable();
|
|
10
|
+
table.json('body').notNullable();
|
|
11
|
+
|
|
12
|
+
table.uuid('traceId').nullable();
|
|
13
|
+
|
|
14
|
+
table.timestamp('occurredOn').notNullable();
|
|
15
|
+
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now());
|
|
16
|
+
|
|
17
|
+
table.index(['entityName', 'entityId'], 'eventEntityIdx', { storageEngineIndexType: 'btree' });
|
|
18
|
+
table.index('occurredOn', 'eventOccurredOnIdx', { storageEngineIndexType: 'btree' });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function up(knex: Knex): Promise<void> {
|
|
22
|
+
await knex.schema.createTable('event', table => createEventTable(table, knex));
|
|
23
|
+
}
|
|
24
|
+
export async function down(knex: Knex): Promise<void> {
|
|
25
|
+
await knex.schema.dropTable('event');
|
|
26
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ConfigLoader } from '@hg-ts/config-loader';
|
|
2
|
+
import { ExecutionMode } from '@hg-ts/execution-mode';
|
|
3
|
+
import { Logger } from '@hg-ts/logger';
|
|
4
|
+
|
|
5
|
+
import { Knex } from 'knex';
|
|
6
|
+
import { KnexConfig } from './knex.config.js';
|
|
7
|
+
|
|
8
|
+
export type ExternalConfigOptions = {
|
|
9
|
+
executionMode?: ExecutionMode;
|
|
10
|
+
logger?: Logger;
|
|
11
|
+
} & ({
|
|
12
|
+
config: KnexConfig;
|
|
13
|
+
loader?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
config?: undefined;
|
|
16
|
+
loader: ConfigLoader;
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export class ExternalConfig {
|
|
20
|
+
public static async build(options: ExternalConfigOptions): Promise<Knex.Config> {
|
|
21
|
+
const { executionMode, logger } = options;
|
|
22
|
+
const config = await ExternalConfig.getConfig(options);
|
|
23
|
+
const { migrations, seeds } = config;
|
|
24
|
+
const isDebug = executionMode?.isDebug() || executionMode?.isDev();
|
|
25
|
+
|
|
26
|
+
const knexConfig: Knex.Config = {
|
|
27
|
+
client: 'postgres',
|
|
28
|
+
dialect: 'postgres',
|
|
29
|
+
debug: isDebug ?? false,
|
|
30
|
+
asyncStackTraces: true,
|
|
31
|
+
connection: {
|
|
32
|
+
host: config.host,
|
|
33
|
+
port: config.port,
|
|
34
|
+
password: config.password,
|
|
35
|
+
user: config.user,
|
|
36
|
+
database: config.database,
|
|
37
|
+
},
|
|
38
|
+
compileSqlOnError: false,
|
|
39
|
+
migrations: {
|
|
40
|
+
loadExtensions: migrations.allowTypescript
|
|
41
|
+
? ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts']
|
|
42
|
+
: ['.js', '.cjs', '.mjs'],
|
|
43
|
+
directory: migrations.path,
|
|
44
|
+
extension: 'ts',
|
|
45
|
+
},
|
|
46
|
+
seeds: { directory: seeds.path },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (logger) {
|
|
50
|
+
knexConfig.log = {
|
|
51
|
+
debug: (message: unknown): void => {
|
|
52
|
+
logger.debug('Query executed:', message);
|
|
53
|
+
},
|
|
54
|
+
warn: (message: unknown): void => {
|
|
55
|
+
logger.warning('Warning on query execution:', message);
|
|
56
|
+
},
|
|
57
|
+
error: (error: unknown): void => {
|
|
58
|
+
logger.error('Query failed:', error);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return knexConfig;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static async getConfig(options: ExternalConfigOptions): Promise<KnexConfig> {
|
|
67
|
+
if (options.config) {
|
|
68
|
+
return options.config;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return options.loader.load(KnexConfig, 'knex');
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as eventMigration from './event.migration.js';
|
|
2
|
+
|
|
3
|
+
export * from './knex.module.js';
|
|
4
|
+
export * from './knex.service.js';
|
|
5
|
+
export * from './external-config.js';
|
|
6
|
+
export * from './test-containers/index.js';
|
|
7
|
+
|
|
8
|
+
export * from './knex.repository.js';
|
|
9
|
+
export * from './event.knex.repository.js';
|
|
10
|
+
export * from './knex.repository.suite.js';
|
|
11
|
+
export * from './tests/index.js';
|
|
12
|
+
|
|
13
|
+
export { eventMigration };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import zod from '@hg-ts/validation';
|
|
2
|
+
|
|
3
|
+
export const KNEX_CONFIG_SCHEMA = zod.object({
|
|
4
|
+
host: zod.string().enforceEnv('PG_HOST'),
|
|
5
|
+
port: zod.number().positive().int(),
|
|
6
|
+
user: zod.string().enforceEnv('PG_USER'),
|
|
7
|
+
database: zod.string().enforceEnv('PG_DATABASE'),
|
|
8
|
+
password: zod.string().enforceEnv('PG_PASSWORD'),
|
|
9
|
+
migrations: zod.object({
|
|
10
|
+
path: zod.string(),
|
|
11
|
+
allowTypescript: zod.boolean().optional(),
|
|
12
|
+
}),
|
|
13
|
+
seeds: zod.object({ path: zod.string() }),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export class KnexConfig extends KNEX_CONFIG_SCHEMA.toClass() {}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ConfigLoader } from '@hg-ts/config-loader';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Global,
|
|
5
|
+
Module,
|
|
6
|
+
} from '@nestjs/common';
|
|
7
|
+
|
|
8
|
+
import { KnexConfig } from './knex.config.js';
|
|
9
|
+
import { KnexService } from './knex.service.js';
|
|
10
|
+
|
|
11
|
+
@Global()
|
|
12
|
+
@Module({
|
|
13
|
+
providers: [
|
|
14
|
+
KnexService,
|
|
15
|
+
{
|
|
16
|
+
provide: KnexConfig,
|
|
17
|
+
async useFactory(configLoader: ConfigLoader): Promise<KnexConfig> {
|
|
18
|
+
return configLoader.load(KnexConfig, 'knex');
|
|
19
|
+
},
|
|
20
|
+
inject: [ConfigLoader],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
exports: [KnexService, KnexConfig],
|
|
24
|
+
})
|
|
25
|
+
export class KnexModule {}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { BaseAggregate } from '@hg-ts/domain';
|
|
2
|
+
import {
|
|
3
|
+
AggregateAlreadyExistsException,
|
|
4
|
+
AggregateNotFoundException,
|
|
5
|
+
} from '@hg-ts/repository';
|
|
6
|
+
import {
|
|
7
|
+
expect,
|
|
8
|
+
ExpectException,
|
|
9
|
+
Suite,
|
|
10
|
+
Test,
|
|
11
|
+
} from '@hg-ts/tests';
|
|
12
|
+
import { Provider } from '@nestjs/common';
|
|
13
|
+
import {
|
|
14
|
+
Test as TestFactory,
|
|
15
|
+
TestingModule,
|
|
16
|
+
} from '@nestjs/testing';
|
|
17
|
+
import { KnexRepository } from './knex.repository.js';
|
|
18
|
+
import { KnexService } from './knex.service.js';
|
|
19
|
+
import { RepositoryTestingModule } from './tests/index.js';
|
|
20
|
+
|
|
21
|
+
const IGNORE_ERROR_SYMBOL = Symbol('Transaction rolled back');
|
|
22
|
+
|
|
23
|
+
export abstract class KnexRepositorySuite<Aggregate extends BaseAggregate<any>> extends Suite {
|
|
24
|
+
public knexService: KnexService;
|
|
25
|
+
private module: TestingModule;
|
|
26
|
+
private resolveFunction: Nullable<(error?: unknown) => void> = null;
|
|
27
|
+
|
|
28
|
+
private readonly repositoryClass: Class<KnexRepository<Aggregate>, any[]>;
|
|
29
|
+
protected abstract repository: KnexRepository<Aggregate>;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
protected constructor(repositoryClass: Class<KnexRepository<Aggregate>, any[]>) {
|
|
33
|
+
super();
|
|
34
|
+
this.repositoryClass = repositoryClass;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Test()
|
|
38
|
+
public async getNextId(): Promise<void> {
|
|
39
|
+
await this.knexService.runInTransaction(async() => {
|
|
40
|
+
const id = await this.repository.getNextId();
|
|
41
|
+
|
|
42
|
+
this.checkId(id);
|
|
43
|
+
const existsEntityWithId = await this.repository.has(id);
|
|
44
|
+
|
|
45
|
+
expect(existsEntityWithId).toBeFalsy();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Test()
|
|
50
|
+
public async addSuccess(): Promise<void> {
|
|
51
|
+
const aggregate = this.createAggregate();
|
|
52
|
+
|
|
53
|
+
await this.knexService.runInTransaction(async() => {
|
|
54
|
+
await this.repository.add(aggregate);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Test()
|
|
59
|
+
public async addEmptyArray(): Promise<void> {
|
|
60
|
+
await this.knexService.runInTransaction(async() => {
|
|
61
|
+
await this.repository.add([]);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Test()
|
|
66
|
+
public async updateSuccess(): Promise<void> {
|
|
67
|
+
const aggregate = this.createAggregate();
|
|
68
|
+
|
|
69
|
+
await this.knexService.runInTransaction(async() => {
|
|
70
|
+
await this.repository.add(aggregate);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await this.knexService.runInTransaction(async() => {
|
|
74
|
+
await this.repository.update(aggregate);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Test()
|
|
79
|
+
public async updateEmptyArray(): Promise<void> {
|
|
80
|
+
await this.knexService.runInTransaction(async() => {
|
|
81
|
+
await this.repository.update([]);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Test()
|
|
86
|
+
public async idSearch(): Promise<void> {
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Test()
|
|
90
|
+
@ExpectException(AggregateAlreadyExistsException)
|
|
91
|
+
public async addDuplicate(): Promise<void> {
|
|
92
|
+
const aggregate = this.createAggregate();
|
|
93
|
+
|
|
94
|
+
await this.knexService.runInTransaction(async() => {
|
|
95
|
+
await this.repository.add(aggregate);
|
|
96
|
+
await this.repository.add(aggregate);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Test()
|
|
101
|
+
@ExpectException(AggregateNotFoundException)
|
|
102
|
+
public async notFoundExceptionOnGet(): Promise<void> {
|
|
103
|
+
await this.knexService.runInTransaction(async() => {
|
|
104
|
+
const nextId = await this.repository.getNextId();
|
|
105
|
+
await this.repository.getOrFail(nextId);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Test()
|
|
110
|
+
@ExpectException(AggregateNotFoundException)
|
|
111
|
+
public async notFoundExceptionOnUpdate(): Promise<void> {
|
|
112
|
+
const aggregate = this.createAggregate();
|
|
113
|
+
|
|
114
|
+
await this.knexService.runInTransaction(async() => {
|
|
115
|
+
await this.repository.update(aggregate);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public override async setUp(): Promise<void> {
|
|
120
|
+
this.module = await TestFactory.createTestingModule({
|
|
121
|
+
imports: [RepositoryTestingModule],
|
|
122
|
+
providers: [this.repositoryClass, ...this.getAdditionalProviders()],
|
|
123
|
+
}).compile();
|
|
124
|
+
|
|
125
|
+
await this.module.init();
|
|
126
|
+
|
|
127
|
+
this.repository = this.module.get(this.repositoryClass);
|
|
128
|
+
this.knexService = this.module.get(KnexService);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public override async beforeEach(): Promise<void> {
|
|
132
|
+
this.knexService.runInTransaction(async() => new Promise((_resolve, reject) => {
|
|
133
|
+
this.resolveFunction = reject;
|
|
134
|
+
})).catch(error => {
|
|
135
|
+
if (error === IGNORE_ERROR_SYMBOL) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
throw error;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public override async afterEach(): Promise<void> {
|
|
144
|
+
this.resolveFunction?.(IGNORE_ERROR_SYMBOL);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public override async tearDown(): Promise<void> {
|
|
148
|
+
await this.module.close();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
protected getAdditionalProviders(): Provider[] {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
protected abstract createAggregate(): Aggregate;
|
|
156
|
+
protected abstract checkId(id: string): void;
|
|
157
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseAggregate } from '@hg-ts/domain';
|
|
2
|
+
import {
|
|
3
|
+
BaseFindOptions,
|
|
4
|
+
BaseRepository,
|
|
5
|
+
} from '@hg-ts/repository';
|
|
6
|
+
|
|
7
|
+
import { Inject } from '@nestjs/common';
|
|
8
|
+
|
|
9
|
+
import { KnexService } from './knex.service.js';
|
|
10
|
+
|
|
11
|
+
export abstract class KnexRepository<
|
|
12
|
+
Aggregate extends BaseAggregate<any>,
|
|
13
|
+
FindOptions extends BaseFindOptions = BaseFindOptions
|
|
14
|
+
> extends BaseRepository<Aggregate, FindOptions> {
|
|
15
|
+
@Inject()
|
|
16
|
+
protected readonly knex: KnexService;
|
|
17
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { AsyncContextProvider } from '@hg-ts/async-context';
|
|
2
|
+
import { ExecutionMode } from '@hg-ts/execution-mode';
|
|
3
|
+
import { Logger } from '@hg-ts/logger';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Inject,
|
|
7
|
+
OnModuleDestroy,
|
|
8
|
+
OnModuleInit,
|
|
9
|
+
} from '@nestjs/common';
|
|
10
|
+
|
|
11
|
+
import knex, { Knex } from 'knex';
|
|
12
|
+
|
|
13
|
+
import { ExternalConfig } from './external-config.js';
|
|
14
|
+
import { KnexConfig } from './knex.config.js';
|
|
15
|
+
|
|
16
|
+
type ExtendedTransactionProvider = {
|
|
17
|
+
getTrx: Knex.TransactionProvider;
|
|
18
|
+
isOpened: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type StorageContext = {
|
|
22
|
+
master: ExtendedTransactionProvider;
|
|
23
|
+
replica: ExtendedTransactionProvider;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export class KnexService implements OnModuleInit, OnModuleDestroy {
|
|
27
|
+
@Inject()
|
|
28
|
+
private readonly config: KnexConfig;
|
|
29
|
+
@Inject()
|
|
30
|
+
private readonly logger: Logger;
|
|
31
|
+
@Inject()
|
|
32
|
+
private readonly executionMode: ExecutionMode;
|
|
33
|
+
|
|
34
|
+
private initHook: Promise<void> | null = null;
|
|
35
|
+
private readonly transactionStorage = new AsyncContextProvider<StorageContext>();
|
|
36
|
+
|
|
37
|
+
private connection: Knex | null;
|
|
38
|
+
|
|
39
|
+
public async getReplicaTrx(): Promise<Knex.Transaction> {
|
|
40
|
+
const { replica } = this.transactionStorage.getOrFail();
|
|
41
|
+
replica.isOpened = true;
|
|
42
|
+
|
|
43
|
+
return replica.getTrx();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async getMasterTrx(): Promise<Knex.Transaction> {
|
|
47
|
+
const { master } = this.transactionStorage.getOrFail();
|
|
48
|
+
|
|
49
|
+
master.isOpened = true;
|
|
50
|
+
|
|
51
|
+
return master.getTrx();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async runInTransaction<T>(callback: () => Promise<T>): Promise<T> {
|
|
55
|
+
const trxProvider = await this.getKnexTransactionProvider();
|
|
56
|
+
|
|
57
|
+
const context: StorageContext = {
|
|
58
|
+
master: { getTrx: trxProvider, isOpened: false },
|
|
59
|
+
replica: { getTrx: trxProvider, isOpened: false },
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return this.transactionStorage.run(context, async() => this.transactionStorageHandler(callback));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async onModuleInit(): Promise<void> {
|
|
66
|
+
if (!this.initHook) {
|
|
67
|
+
this.initHook = this.internalInit();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return this.initHook;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public async onModuleDestroy(): Promise<void> {
|
|
74
|
+
await this.connection?.destroy();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async internalInit(): Promise<void> {
|
|
78
|
+
this.logger.setContext('knex');
|
|
79
|
+
|
|
80
|
+
const config = await ExternalConfig.build({
|
|
81
|
+
config: this.config,
|
|
82
|
+
logger: this.logger,
|
|
83
|
+
executionMode: this.executionMode,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
this.connection = knex(config);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private async getKnexTransactionProvider(): Promise<Knex.TransactionProvider> {
|
|
90
|
+
await this.onModuleInit();
|
|
91
|
+
|
|
92
|
+
return this.connection!.transactionProvider();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private async transactionStorageHandler<T>(callback: () => Promise<T>): Promise<T> {
|
|
96
|
+
try {
|
|
97
|
+
const result = await callback();
|
|
98
|
+
|
|
99
|
+
const context = this.transactionStorage.getOrFail();
|
|
100
|
+
|
|
101
|
+
if (context.master.isOpened) {
|
|
102
|
+
const trx = await context.master.getTrx();
|
|
103
|
+
|
|
104
|
+
await trx.commit();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (context.replica.isOpened) {
|
|
108
|
+
const trx = await context.replica.getTrx();
|
|
109
|
+
|
|
110
|
+
// Replica transaction will not affect database
|
|
111
|
+
await trx.rollback();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
} catch (error: unknown) {
|
|
116
|
+
const context = this.transactionStorage.getOrFail();
|
|
117
|
+
|
|
118
|
+
if (context.master.isOpened) {
|
|
119
|
+
const trx = await context.master.getTrx();
|
|
120
|
+
|
|
121
|
+
await trx.rollback();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (context.replica.isOpened) {
|
|
125
|
+
const trx = await context.replica.getTrx();
|
|
126
|
+
|
|
127
|
+
await trx.rollback();
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { up, down } from '../event.migration.js';
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './postgres-testcontainer.module.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Global,
|
|
3
|
+
Module,
|
|
4
|
+
} from '@nestjs/common';
|
|
5
|
+
|
|
6
|
+
import { KnexModule } from '../knex.module.js';
|
|
7
|
+
|
|
8
|
+
import { PostgresTestcontainerService } from './postgres-testcontainer.service.js';
|
|
9
|
+
|
|
10
|
+
@Global()
|
|
11
|
+
@Module({
|
|
12
|
+
imports: [KnexModule],
|
|
13
|
+
providers: [PostgresTestcontainerService],
|
|
14
|
+
})
|
|
15
|
+
export class PostgresTestcontainerModule {}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Inject,
|
|
3
|
+
OnApplicationBootstrap,
|
|
4
|
+
OnModuleDestroy,
|
|
5
|
+
OnModuleInit,
|
|
6
|
+
} from '@nestjs/common';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
PostgreSqlContainer,
|
|
10
|
+
StartedPostgreSqlContainer,
|
|
11
|
+
} from '@testcontainers/postgresql';
|
|
12
|
+
|
|
13
|
+
import { KnexConfig } from '../knex.config.js';
|
|
14
|
+
import { KnexService } from '../knex.service.js';
|
|
15
|
+
|
|
16
|
+
export class PostgresTestcontainerService implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy {
|
|
17
|
+
@Inject()
|
|
18
|
+
private readonly config: KnexConfig;
|
|
19
|
+
@Inject()
|
|
20
|
+
private readonly postgres: KnexService;
|
|
21
|
+
|
|
22
|
+
private container: StartedPostgreSqlContainer;
|
|
23
|
+
|
|
24
|
+
private initPromise: Nullable<Promise<void>> = null;
|
|
25
|
+
|
|
26
|
+
public constructor() {
|
|
27
|
+
this.enrichClient();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async onModuleInit(): Promise<void> {
|
|
31
|
+
if (!this.initPromise) {
|
|
32
|
+
this.initPromise = this.startContainer();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return this.initPromise;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async onApplicationBootstrap(): Promise<void> {
|
|
39
|
+
await this.postgres.runInTransaction(async() => {
|
|
40
|
+
const trx = await this.postgres.getMasterTrx();
|
|
41
|
+
|
|
42
|
+
await trx.migrate.latest();
|
|
43
|
+
await trx.seed.run();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async onModuleDestroy(): Promise<void> {
|
|
48
|
+
await this.stopContainer();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async startContainer(): Promise<void> {
|
|
52
|
+
const container = new PostgreSqlContainer('postgres')
|
|
53
|
+
.withDatabase(this.config.database)
|
|
54
|
+
.withUsername(this.config.user)
|
|
55
|
+
.withPassword(this.config.password)
|
|
56
|
+
.withReuse()
|
|
57
|
+
.withExposedPorts({ container: 5432, host: this.config.port });
|
|
58
|
+
|
|
59
|
+
this.container = await container.start();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private async stopContainer(): Promise<void> {
|
|
63
|
+
await this.container.stop();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private enrichClient(): void {
|
|
67
|
+
// eslint-disable-next-line @typescript/unbound-method
|
|
68
|
+
const originalInitHook = KnexService.prototype.onModuleInit;
|
|
69
|
+
// eslint-disable-next-line @typescript/no-this-alias
|
|
70
|
+
const self = this;
|
|
71
|
+
|
|
72
|
+
KnexService.prototype.onModuleInit = async function(this: KnexService): Promise<void> {
|
|
73
|
+
await self.onModuleInit();
|
|
74
|
+
return originalInitHook.call(this);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DomainEvent,
|
|
3
|
+
STORED_PROPERTY,
|
|
4
|
+
} from '@hg-ts/domain';
|
|
5
|
+
import {
|
|
6
|
+
Describe,
|
|
7
|
+
expect,
|
|
8
|
+
Suite,
|
|
9
|
+
Test,
|
|
10
|
+
} from '@hg-ts/tests';
|
|
11
|
+
import {
|
|
12
|
+
Test as TestFactory,
|
|
13
|
+
TestingModule,
|
|
14
|
+
} from '@nestjs/testing';
|
|
15
|
+
|
|
16
|
+
import { EventKnexRepository } from '../event.knex.repository.js';
|
|
17
|
+
import { KnexService } from '../knex.service.js';
|
|
18
|
+
import { RepositoryTestingModule } from './repository.testing.module.js';
|
|
19
|
+
import { TestEvent } from './test.event.js';
|
|
20
|
+
|
|
21
|
+
@Describe()
|
|
22
|
+
export class EventKnexRepositoryTest extends Suite {
|
|
23
|
+
private repository: EventKnexRepository;
|
|
24
|
+
private module: TestingModule;
|
|
25
|
+
private knexService: KnexService;
|
|
26
|
+
|
|
27
|
+
@Test()
|
|
28
|
+
public async addEvents(): Promise<void> {
|
|
29
|
+
const events: DomainEvent[] = [];
|
|
30
|
+
|
|
31
|
+
events.push(new TestEvent());
|
|
32
|
+
events.push(new TestEvent());
|
|
33
|
+
|
|
34
|
+
await this.knexService.runInTransaction(async() => {
|
|
35
|
+
await this.repository.add(events);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
events.forEach(event => {
|
|
39
|
+
expect(event[STORED_PROPERTY]).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test()
|
|
44
|
+
public async addEventsTwice(): Promise<void> {
|
|
45
|
+
const events: DomainEvent[] = [];
|
|
46
|
+
|
|
47
|
+
events.push(new TestEvent());
|
|
48
|
+
events.push(new TestEvent());
|
|
49
|
+
|
|
50
|
+
await this.knexService.runInTransaction(async() => {
|
|
51
|
+
await this.repository.add(events);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await this.knexService.runInTransaction(async() => {
|
|
55
|
+
await this.repository.add(events);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Test()
|
|
60
|
+
public async getEvents(): Promise<void> {
|
|
61
|
+
const events: DomainEvent[] = [];
|
|
62
|
+
|
|
63
|
+
events.push(new TestEvent());
|
|
64
|
+
events.push(new TestEvent());
|
|
65
|
+
|
|
66
|
+
await this.knexService.runInTransaction(async() => {
|
|
67
|
+
await this.repository.add(events);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const foundEvents = await this.knexService.runInTransaction(
|
|
71
|
+
async() => this.repository.find({ entityId: events[0]!.entityId }),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(foundEvents).toHaveLength(1);
|
|
75
|
+
expect(foundEvents[0]).toMatchObject(events[0]!);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public override async setUp(): Promise<void> {
|
|
79
|
+
this.module = await TestFactory.createTestingModule({
|
|
80
|
+
imports: [RepositoryTestingModule],
|
|
81
|
+
providers: [EventKnexRepository],
|
|
82
|
+
}).compile();
|
|
83
|
+
|
|
84
|
+
await this.module.init();
|
|
85
|
+
|
|
86
|
+
this.repository = this.module.get(EventKnexRepository);
|
|
87
|
+
this.knexService = this.module.get(KnexService);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public override async tearDown(): Promise<void> {
|
|
91
|
+
await this.module.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './repository.testing.module.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ConfigModule } from '@hg-ts/config-loader';
|
|
2
|
+
import { ExecutionModeModule } from '@hg-ts/execution-mode';
|
|
3
|
+
import { LoggerModule } from '@hg-ts/logger';
|
|
4
|
+
import { Module } from '@nestjs/common';
|
|
5
|
+
import { KnexModule } from '../knex.module.js';
|
|
6
|
+
import { PostgresTestcontainerModule } from '../test-containers/index.js';
|
|
7
|
+
|
|
8
|
+
@Module({
|
|
9
|
+
imports: [
|
|
10
|
+
ExecutionModeModule,
|
|
11
|
+
LoggerModule,
|
|
12
|
+
ConfigModule,
|
|
13
|
+
PostgresTestcontainerModule,
|
|
14
|
+
KnexModule,
|
|
15
|
+
],
|
|
16
|
+
})
|
|
17
|
+
export class RepositoryTestingModule {}
|