nestjs-typeorm3-kit 0.1.8 → 0.2.0

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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +801 -148
  3. package/dist/common/decorator/index.d.ts.map +1 -1
  4. package/dist/common/decorator/index.js +1 -5
  5. package/dist/common/decorator/index.js.map +1 -1
  6. package/dist/controller/decorator/index.d.ts.map +1 -1
  7. package/dist/controller/decorator/index.js +66 -117
  8. package/dist/controller/decorator/index.js.map +1 -1
  9. package/dist/helpers/index.d.ts +2 -0
  10. package/dist/helpers/index.d.ts.map +1 -1
  11. package/dist/helpers/index.js +49 -10
  12. package/dist/helpers/index.js.map +1 -1
  13. package/dist/module/decorator/index.d.ts +1 -1
  14. package/dist/module/decorator/index.d.ts.map +1 -1
  15. package/dist/module/decorator/index.js +188 -19
  16. package/dist/module/decorator/index.js.map +1 -1
  17. package/dist/repository/config/constants.d.ts +1 -0
  18. package/dist/repository/config/constants.d.ts.map +1 -1
  19. package/dist/repository/config/constants.js +2 -1
  20. package/dist/repository/config/constants.js.map +1 -1
  21. package/dist/repository/config/types.d.ts +2 -2
  22. package/dist/repository/config/types.d.ts.map +1 -1
  23. package/dist/repository/config/types.js +34 -6
  24. package/dist/repository/config/types.js.map +1 -1
  25. package/dist/repository/config/utils.d.ts +1 -1
  26. package/dist/repository/config/utils.d.ts.map +1 -1
  27. package/dist/repository/config/utils.js +12 -13
  28. package/dist/repository/config/utils.js.map +1 -1
  29. package/dist/repository/decorator/index.d.ts +2 -2
  30. package/dist/repository/decorator/index.d.ts.map +1 -1
  31. package/dist/repository/decorator/index.js +1 -1
  32. package/dist/repository/decorator/index.js.map +1 -1
  33. package/dist/repository/module/index.d.ts +2 -5
  34. package/dist/repository/module/index.d.ts.map +1 -1
  35. package/dist/repository/module/index.js +23 -87
  36. package/dist/repository/module/index.js.map +1 -1
  37. package/dist/transaction/decorator/index.d.ts +5 -3
  38. package/dist/transaction/decorator/index.d.ts.map +1 -1
  39. package/dist/transaction/decorator/index.js +20 -19
  40. package/dist/transaction/decorator/index.js.map +1 -1
  41. package/dist/transaction/setup/index.d.ts +8 -3
  42. package/dist/transaction/setup/index.d.ts.map +1 -1
  43. package/dist/transaction/setup/index.js +19 -5
  44. package/dist/transaction/setup/index.js.map +1 -1
  45. package/dist/validate/decorator/index.d.ts.map +1 -1
  46. package/dist/validate/decorator/index.js +6 -4
  47. package/dist/validate/decorator/index.js.map +1 -1
  48. package/dist/vendor/transactional/common/constants.d.ts +6 -0
  49. package/dist/vendor/transactional/common/constants.d.ts.map +1 -0
  50. package/dist/vendor/transactional/common/constants.js +9 -0
  51. package/dist/vendor/transactional/common/constants.js.map +1 -0
  52. package/dist/vendor/transactional/common/index.d.ts +26 -0
  53. package/dist/vendor/transactional/common/index.d.ts.map +1 -0
  54. package/dist/vendor/transactional/common/index.js +136 -0
  55. package/dist/vendor/transactional/common/index.js.map +1 -0
  56. package/dist/vendor/transactional/decorators/transactional.d.ts +3 -0
  57. package/dist/vendor/transactional/decorators/transactional.d.ts.map +1 -0
  58. package/dist/vendor/transactional/decorators/transactional.js +20 -0
  59. package/dist/vendor/transactional/decorators/transactional.js.map +1 -0
  60. package/dist/vendor/transactional/enums/isolation-level.d.ts +7 -0
  61. package/dist/vendor/transactional/enums/isolation-level.d.ts.map +1 -0
  62. package/dist/vendor/transactional/enums/isolation-level.js +11 -0
  63. package/dist/vendor/transactional/enums/isolation-level.js.map +1 -0
  64. package/dist/vendor/transactional/enums/propagation.d.ts +10 -0
  65. package/dist/vendor/transactional/enums/propagation.d.ts.map +1 -0
  66. package/dist/vendor/transactional/enums/propagation.js +14 -0
  67. package/dist/vendor/transactional/enums/propagation.js.map +1 -0
  68. package/dist/vendor/transactional/enums/storage-driver.d.ts +5 -0
  69. package/dist/vendor/transactional/enums/storage-driver.d.ts.map +1 -0
  70. package/dist/vendor/transactional/enums/storage-driver.js +9 -0
  71. package/dist/vendor/transactional/enums/storage-driver.js.map +1 -0
  72. package/dist/vendor/transactional/errors/transactional.d.ts +4 -0
  73. package/dist/vendor/transactional/errors/transactional.d.ts.map +1 -0
  74. package/dist/vendor/transactional/errors/transactional.js +11 -0
  75. package/dist/vendor/transactional/errors/transactional.js.map +1 -0
  76. package/dist/vendor/transactional/errors/typeorm-updated-patch.d.ts +5 -0
  77. package/dist/vendor/transactional/errors/typeorm-updated-patch.d.ts.map +1 -0
  78. package/dist/vendor/transactional/errors/typeorm-updated-patch.js +11 -0
  79. package/dist/vendor/transactional/errors/typeorm-updated-patch.js.map +1 -0
  80. package/dist/vendor/transactional/hooks/index.d.ts +11 -0
  81. package/dist/vendor/transactional/hooks/index.d.ts.map +1 -0
  82. package/dist/vendor/transactional/hooks/index.js +79 -0
  83. package/dist/vendor/transactional/hooks/index.js.map +1 -0
  84. package/dist/vendor/transactional/index.d.ts +10 -0
  85. package/dist/vendor/transactional/index.d.ts.map +1 -0
  86. package/dist/vendor/transactional/index.js +27 -0
  87. package/dist/vendor/transactional/index.js.map +1 -0
  88. package/dist/vendor/transactional/storage/driver/async-local-storage/index.d.ts +9 -0
  89. package/dist/vendor/transactional/storage/driver/async-local-storage/index.d.ts.map +1 -0
  90. package/dist/vendor/transactional/storage/driver/async-local-storage/index.js +25 -0
  91. package/dist/vendor/transactional/storage/driver/async-local-storage/index.js.map +1 -0
  92. package/dist/vendor/transactional/storage/driver/interface.d.ts +10 -0
  93. package/dist/vendor/transactional/storage/driver/interface.d.ts.map +1 -0
  94. package/dist/vendor/transactional/storage/driver/interface.js +3 -0
  95. package/dist/vendor/transactional/storage/driver/interface.js.map +1 -0
  96. package/dist/vendor/transactional/storage/index.d.ts +3 -0
  97. package/dist/vendor/transactional/storage/index.d.ts.map +1 -0
  98. package/dist/vendor/transactional/storage/index.js +6 -0
  99. package/dist/vendor/transactional/storage/index.js.map +1 -0
  100. package/dist/vendor/transactional/storage/storage.d.ts +7 -0
  101. package/dist/vendor/transactional/storage/storage.d.ts.map +1 -0
  102. package/dist/vendor/transactional/storage/storage.js +21 -0
  103. package/dist/vendor/transactional/storage/storage.js.map +1 -0
  104. package/dist/vendor/transactional/transactions/nested-scope.d.ts +12 -0
  105. package/dist/vendor/transactional/transactions/nested-scope.d.ts.map +1 -0
  106. package/dist/vendor/transactional/transactions/nested-scope.js +42 -0
  107. package/dist/vendor/transactional/transactions/nested-scope.js.map +1 -0
  108. package/dist/vendor/transactional/transactions/run-in-transaction.d.ts +3 -0
  109. package/dist/vendor/transactional/transactions/run-in-transaction.d.ts.map +1 -0
  110. package/dist/vendor/transactional/transactions/run-in-transaction.js +9 -0
  111. package/dist/vendor/transactional/transactions/run-in-transaction.js.map +1 -0
  112. package/dist/vendor/transactional/transactions/wrap-in-transaction.d.ts +11 -0
  113. package/dist/vendor/transactional/transactions/wrap-in-transaction.d.ts.map +1 -0
  114. package/dist/vendor/transactional/transactions/wrap-in-transaction.js +109 -0
  115. package/dist/vendor/transactional/transactions/wrap-in-transaction.js.map +1 -0
  116. package/dist/vendor/transactional/utils/index.d.ts +3 -0
  117. package/dist/vendor/transactional/utils/index.d.ts.map +1 -0
  118. package/dist/vendor/transactional/utils/index.js +12 -0
  119. package/dist/vendor/transactional/utils/index.js.map +1 -0
  120. package/package.json +28 -25
package/README.md CHANGED
@@ -1,241 +1,894 @@
1
1
  # nestjs-typeorm3-kit
2
2
 
3
- [![npm](https://img.shields.io/npm/v/nestjs-typeorm3-kit)](https://www.npmjs.com/package/nestjs-typeorm3-kit) ![MIT License](https://img.shields.io/npm/l/nestjs-typeorm3-kit.svg)
3
+ <p>
4
+ <a href="https://www.npmjs.com/package/nestjs-typeorm3-kit"><img alt="npm version" src="https://img.shields.io/npm/v/nestjs-typeorm3-kit?color=cb3837&label=npm" /></a>
5
+ <a href="https://www.npmjs.com/package/nestjs-typeorm3-kit"><img alt="npm downloads" src="https://img.shields.io/npm/dm/nestjs-typeorm3-kit?color=0a7bbb" /></a>
6
+ <a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/nestjs-typeorm3-kit.svg?color=informational" /></a>
7
+ <img alt="Node" src="https://img.shields.io/badge/node-%3E%3D16.4-43853d?logo=node.js&logoColor=white" />
8
+ <img alt="Bun" src="https://img.shields.io/badge/bun-supported-f9f1e1?logo=bun&logoColor=black" />
9
+ <img alt="TypeORM" src="https://img.shields.io/badge/typeorm-0.3.x-e83524" />
10
+ <img alt="NestJS" src="https://img.shields.io/badge/nestjs-8%E2%80%9312-ea2845?logo=nestjs&logoColor=white" />
11
+ <img alt="Types" src="https://img.shields.io/npm/types/nestjs-typeorm3-kit?color=3178c6&logo=typescript&logoColor=white" />
12
+ </p>
13
+
14
+ A NestJS toolkit for **TypeORM 0.3.x** that brings back the ergonomic `@EntityRepository` pattern, adds first-class multi-DataSource support, decorator-based transactions, Swagger helpers, and a safer `findOne`.
15
+
16
+ This package is also a **wrapper fork of [`typeorm-transactional`](https://github.com/Aliheym/typeorm-transactional) `v0.5.0`** — the transactional layer is vendored inside the package with `cls-hooked` removed, so transactions work on both Node.js and Bun without installing anything extra.
17
+
18
+ > 📚 **Looking for deep dives?** The [`docs/`](./docs) folder has bilingual
19
+ > (EN / VI) guides for every feature with full option references and
20
+ > common pitfalls.
21
+
22
+ ## Highlights
23
+
24
+ - 🧩 `@DefEntityRepository` + `@InjectRepo` — repository pattern with multi-DataSource support
25
+ - 🔁 `@DefTransaction` — decorator-based transactions, works on Node **and** Bun
26
+ - 🛡️ `RepositoryWrapper` — hardens `findOne` against TypeORM's "first row on undefined where" footgun
27
+ - 🎯 `@DefController`, `@DefGet/Post/Put/Patch/Delete` — less boilerplate, auto Swagger metadata
28
+ - 🌳 `@ChildModule` — nested route prefixes + auto `ApiTags`
29
+ - 📜 `configSwaggerDocument` — merges `class-validator` metadata into the OpenAPI doc
30
+ - ⚡ `lazyLoadClasses` — auto-discover controllers/services by file suffix
31
+
32
+ ---
33
+
34
+ ## Table of contents
35
+
36
+ 1. [Installation](#1-installation)
37
+ 2. [Quick start](#2-quick-start)
38
+ 3. [Tutorial — build a multi-DataSource app step by step](#3-tutorial--build-a-multi-datasource-app-step-by-step)
39
+ - [3.1 Set up DataSources](#31-set-up-datasources)
40
+ - [3.2 Write your first repository](#32-write-your-first-repository)
41
+ - [3.3 Wire the repository module](#33-wire-the-repository-module)
42
+ - [3.4 Inject the repository into a service](#34-inject-the-repository-into-a-service)
43
+ - [3.5 Add a controller](#35-add-a-controller)
44
+ - [3.6 Group modules with `@ChildModule`](#36-group-modules-with-childmodule)
45
+ - [3.7 Add transactions](#37-add-transactions)
46
+ - [3.8 Enable Swagger](#38-enable-swagger)
47
+ 4. [Relationship to `typeorm-transactional`](#4-relationship-to-typeorm-transactional)
48
+ 5. [Repository API](#5-repository-api)
49
+ 6. [Transactions](#6-transactions)
50
+ 7. [Controller & module helpers](#7-controller--module-helpers)
51
+ 8. [Swagger helper](#8-swagger-helper)
52
+ 9. [Validation decorators](#9-validation-decorators)
53
+ 10. [Utilities](#10-utilities)
54
+ 11. [Full example](#11-full-example)
55
+ 12. [Testing](#12-testing)
56
+ 13. [Compatibility](#13-compatibility)
57
+ 14. [Troubleshooting](#14-troubleshooting)
58
+
59
+ ---
60
+
61
+ ## 1. Installation
62
+
63
+ <details>
64
+ <summary><b>npm</b></summary>
4
65
 
5
- ## [Node.js] nestjs-typeorm3-kit
66
+ ```bash
67
+ npm install nestjs-typeorm3-kit
68
+ ```
6
69
 
7
- ### Installation
70
+ </details>
8
71
 
9
- #### NPM
72
+ <details open>
73
+ <summary><b>yarn</b></summary>
10
74
 
11
75
  ```bash
12
- npm install nestjs-typeorm3-kit typeorm-transactional
76
+ yarn add nestjs-typeorm3-kit
13
77
  ```
14
78
 
15
- #### Yarn
79
+ </details>
80
+
81
+ <details>
82
+ <summary><b>pnpm</b></summary>
16
83
 
17
84
  ```bash
18
- yarn add nestjs-typeorm3-kit typeorm-transactional
85
+ pnpm add nestjs-typeorm3-kit
19
86
  ```
20
87
 
21
- #### pnpm
88
+ </details>
89
+
90
+ <details>
91
+ <summary><b>bun</b></summary>
22
92
 
23
93
  ```bash
24
- pnpm add nestjs-typeorm3-kit typeorm-transactional
94
+ bun add nestjs-typeorm3-kit
25
95
  ```
26
96
 
27
- #### bun
97
+ </details>
98
+
99
+ > Starting from **v0.2.0**, `typeorm-transactional` is vendored inside the package — no separate install needed.
100
+
101
+ **Peer dependencies** (install if you don't have them):
28
102
 
29
103
  ```bash
30
- bun add nestjs-typeorm3-kit typeorm-transactional
104
+ npm install @nestjs/common @nestjs/core @nestjs/typeorm @nestjs/swagger typeorm reflect-metadata rxjs class-validator class-transformer class-validator-jsonschema libphonenumber-js
31
105
  ```
32
106
 
33
- ### Config after install
107
+ **Runtime requirement:** Node.js **>= 16.4** or **Bun** for the transactional layer; **NestJS 12 requires Node.js >= 20.19**. The vendored transactional layer uses Node's built-in `AsyncLocalStorage` — there is no `cls-hooked` fallback.
34
108
 
35
- #### Use @DefEntityRepository
109
+ ---
36
110
 
37
- For using `@DefEntityRepository` instead of `@EntityRepository` of typeorm 0.2, you need to add the following code in `main.ts` or `app.module.ts`:
111
+ ## 2. Quick start
38
112
 
39
- ```typescript
40
- import { TypeOrmModule } from "@nestjs/typeorm";
113
+ A 30-second peek. The full tutorial in [section 3](#3-tutorial--build-a-multi-datasource-app-step-by-step) walks through the same code with explanations.
41
114
 
42
- TypeOrmModule.forRootAsync({
43
- useFactory: () => ({
44
- type: "postgres",
45
- host: "localhost",
46
- port: 5432,
47
- username: "postgres",
48
- password: "Abc12345",
49
- database: "primary_db",
50
- entities: [join(__dirname, "./domains/primary/**/*.entity.{ts,js}")],
51
- synchronize: true,
52
- autoLoadEntities: true,
53
- retryAttempts: 2,
54
- retryDelay: 1000,
55
- }),
56
- dataSourceFactory: async (options: DataSourceOptions) => {
57
- if (!options) {
58
- throw new Error("Invalid options passed");
59
- }
60
- return addTransactionalDataSource({
61
- dataSource: new DataSource(options),
62
- name: PRIMARY_CONNECTION,
63
- });
64
- },
65
- });
66
- ```
115
+ ```ts
116
+ // main.ts
117
+ import { NestFactory } from "@nestjs/core";
118
+ import { setupTransactionContext } from "nestjs-typeorm3-kit";
119
+ import { AppModule } from "./app.module";
67
120
 
68
- #### Use RepositoryWrapper fix findOne typeOrm (change return first to return null)
121
+ async function bootstrap() {
122
+ setupTransactionContext(); // MUST run before NestFactory.create
123
+ const app = await NestFactory.create(AppModule);
124
+ await app.listen(3000);
125
+ }
126
+ bootstrap();
127
+ ```
69
128
 
70
- ```typescript
71
- import { RepositoryWrapper } from "nestjs-typeorm3-kit";
129
+ ```ts
130
+ // book.repo.ts
131
+ import { Repository } from "typeorm";
132
+ import { DefEntityRepository } from "nestjs-typeorm3-kit";
133
+ import { Book } from "./book.entity";
72
134
 
73
- export class BaseRepo<Entity> extends RepositoryWrapper<Entity> {}
135
+ @DefEntityRepository(Book)
136
+ export class BookRepo extends Repository<Book> {}
74
137
  ```
75
138
 
76
- #### create Repository module Use @DefRepositoryModule
77
-
78
- ```typescript
139
+ ```ts
140
+ // app.module.ts
79
141
  import { Module } from "@nestjs/common";
80
- import { join } from "path";
142
+ import { TypeOrmModule } from "@nestjs/typeorm";
143
+ import { DataSource, DataSourceOptions } from "typeorm";
144
+ import { addTransactionalDataSource, DefRepositoryModule } from "nestjs-typeorm3-kit";
81
145
 
82
- import { PRIMARY_CONNECTION } from "~/common/constants";
83
- import { DefRepositoryModule } from "nestjs-typeorm3-kit";
84
146
  @Module({
85
147
  imports: [
86
- DefRepositoryModule.forRootAsync({
87
- useFactory: () => ({
88
- globPattern: join(__dirname, "./**/*.repo.{ts,js}"),
89
- dataSource: PRIMARY_CONNECTION,
90
- }),
148
+ TypeOrmModule.forRootAsync({
149
+ useFactory: () => ({ type: "postgres", /* ... */ entities: [Book] }),
150
+ // @DefTransaction only works on DataSources registered here
151
+ dataSourceFactory: async (options) =>
152
+ addTransactionalDataSource(new DataSource(options as DataSourceOptions)),
91
153
  }),
154
+ DefRepositoryModule.forFeature([BookRepo]),
92
155
  ],
93
- exports: [DefRepositoryModule],
156
+ providers: [BookService],
94
157
  })
95
- export class PrimaryRepoModule {}
158
+ export class AppModule {}
96
159
  ```
97
160
 
98
- or
161
+ ```ts
162
+ // book.service.ts
163
+ import { Injectable } from "@nestjs/common";
164
+ import { DefTransaction, InjectRepo } from "nestjs-typeorm3-kit";
99
165
 
100
- ```typescript
166
+ @Injectable()
167
+ export class BookService {
168
+ constructor(@InjectRepo(BookRepo) private readonly repo: BookRepo) {}
169
+
170
+ @DefTransaction()
171
+ create(title: string) {
172
+ return this.repo.save({ title });
173
+ }
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## 3. Tutorial — build a multi-DataSource app step by step
180
+
181
+ This walkthrough builds a small Nest app with **two Postgres databases**, repositories, a service that writes across both inside a transaction, a controller, and Swagger. By the end you'll have used every major feature of the kit.
182
+
183
+ ### Architecture at a glance
184
+
185
+ ```mermaid
186
+ flowchart LR
187
+ Client[HTTP Client] --> Ctrl[BookController]
188
+ Ctrl --> Svc[BookService]
189
+ Svc -- "@InjectRepo(BookRepo)" --> BookRepo
190
+ Svc -- "@InjectRepo(LogRepo, 'secondary')" --> LogRepo
191
+ BookRepo --> Primary[(Primary DB)]
192
+ LogRepo --> Secondary[(Secondary DB)]
193
+ Svc -. "@DefTransaction()" .-> TxCtx[AsyncLocalStorage<br/>transaction context]
194
+ TxCtx --> Primary
195
+ ```
196
+
197
+ ### 3.1 Set up DataSources
198
+
199
+ Register each DataSource with `TypeOrmModule` **and** wrap it in `addTransactionalDataSource` so `@DefTransaction` can see it.
200
+
201
+ ```ts
202
+ // databases.ts
203
+ import { TypeOrmModule } from "@nestjs/typeorm";
204
+ import { DataSource, DataSourceOptions } from "typeorm";
205
+ import { addTransactionalDataSource } from "nestjs-typeorm3-kit";
206
+
207
+ const PRIMARY = "default";
208
+ const SECONDARY = "secondary";
209
+
210
+ export const primaryDb = TypeOrmModule.forRootAsync({
211
+ name: PRIMARY,
212
+ useFactory: () => ({ type: "postgres", /* ... */ }),
213
+ dataSourceFactory: async (options) =>
214
+ addTransactionalDataSource({
215
+ name: PRIMARY,
216
+ dataSource: new DataSource(options as DataSourceOptions),
217
+ }),
218
+ });
219
+
220
+ export const secondaryDb = TypeOrmModule.forRootAsync({
221
+ name: SECONDARY,
222
+ // repeat the name here: @nestjs/typeorm reads it from these options on shutdown
223
+ useFactory: () => ({ name: SECONDARY, type: "postgres", /* ... */ }),
224
+ dataSourceFactory: async (options) =>
225
+ addTransactionalDataSource({
226
+ name: SECONDARY,
227
+ dataSource: new DataSource(options as DataSourceOptions),
228
+ }),
229
+ });
230
+ ```
231
+
232
+ > 💡 `addTransactionalDataSource` is re-exported from `nestjs-typeorm3-kit` so you don't need a second import from `typeorm-transactional`.
233
+
234
+ ### 3.2 Write your first repository
235
+
236
+ ```ts
237
+ // book.entity.ts
238
+ import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
239
+
240
+ @Entity()
241
+ export class Book {
242
+ @PrimaryGeneratedColumn() id!: number;
243
+ @Column() title!: string;
244
+ }
245
+ ```
246
+
247
+ ```ts
248
+ // book.repo.ts
249
+ import { DefEntityRepository, RepositoryWrapper } from "nestjs-typeorm3-kit";
250
+ import { Book } from "./book.entity";
251
+
252
+ @DefEntityRepository(Book)
253
+ export class BookRepo extends RepositoryWrapper<Book> {
254
+ findByTitle(title: string) {
255
+ return this.findOne({ where: { title } });
256
+ }
257
+ }
258
+ ```
259
+
260
+ > ✨ Extending `RepositoryWrapper` instead of plain `Repository` makes `findOne` return `null` (instead of a random first row) when the `where` clause has any `undefined` value. See [`RepositoryWrapper`](#repositorywrapper).
261
+
262
+ ### 3.3 Wire the repository module
263
+
264
+ `DefRepositoryModule` auto-discovers all `*.repo.{ts,js}` files that have a `@DefEntityRepository` decorator and registers them as providers.
265
+
266
+ ```ts
267
+ // primary-repo.module.ts
101
268
  import { Module } from "@nestjs/common";
102
269
  import { join } from "path";
103
-
104
- import { PRIMARY_CONNECTION } from "~/common/constants";
105
270
  import { DefRepositoryModule } from "nestjs-typeorm3-kit";
271
+
106
272
  @Module({
107
273
  imports: [
108
- DefRepositoryModule.forFeature([BookRepo, PhotoRepo], PRIMARY_CONNECTION),
274
+ DefRepositoryModule.forRootAsync({
275
+ useFactory: () => ({
276
+ globPattern: join(__dirname, "./**/*.repo.{ts,js}"),
277
+ dataSource: "default",
278
+ }),
279
+ }),
109
280
  ],
110
281
  exports: [DefRepositoryModule],
111
282
  })
112
283
  export class PrimaryRepoModule {}
113
284
  ```
114
285
 
115
- #### Use @DefTransaction
286
+ Repeat with `dataSource: "secondary"` for the second connection.
116
287
 
117
- ```typescript
118
- import { Injectable } from "@nestjs/common";
119
- import { DefTransaction, InjectRepo } from "nestjs-typeorm3-kit";
120
- import { PhotoRepo } from "~/domains/primary/photo/photo.repo";
121
- import { BookRepo } from "~/domains/primary/book/book.repo";
288
+ ### 3.4 Inject the repository into a service
122
289
 
123
- const PRIMARY_CONNECTION = "default";
124
- const SECONDARY_CONNECTION = "secondary_db";
125
- @Injectable()
126
- export class DemoService {
127
- constructor(
128
- readonly photoRepo: PhotoRepo,
129
- @InjectRepo(BookRepo, PRIMARY_CONNECTION)
130
- readonly bookRepo: BookRepo
131
- ) {}
290
+ For the **default** connection, plain constructor injection works.
291
+ For a **named** connection, use `@InjectRepo(Repo, connectionName)`.
132
292
 
133
- @DefTransaction()
134
- create(body: any) {
135
- return this.photoRepo.save(body);
136
- }
137
- }
293
+ ```ts
294
+ // book.service.ts
295
+ import { Injectable } from "@nestjs/common";
296
+ import { InjectRepo } from "nestjs-typeorm3-kit";
297
+ import { BookRepo } from "./book.repo";
298
+ import { LogRepo } from "./log.repo";
138
299
 
139
- // secondary connection
140
300
  @Injectable()
141
- export class DemoService {
301
+ export class BookService {
142
302
  constructor(
143
- @InjectRepo(ExampleRepo, SECONDARY_CONNECTION)
144
- readonly exampleRepo: ExampleRepo,
145
- @InjectRepo(DataLogRepo, SECONDARY_CONNECTION)
146
- readonly dataLogRepo: DataLogRepo
303
+ readonly bookRepo: BookRepo, // default connection
304
+ @InjectRepo(LogRepo, "secondary") readonly logRepo: LogRepo, // secondary
147
305
  ) {}
148
306
 
149
- @DefTransaction({ connectionName: SECONDARY_CONNECTION })
150
- create(body: any) {
151
- return this.photoRepo.save(body);
307
+ list() {
308
+ return this.bookRepo.find();
152
309
  }
153
310
  }
154
311
  ```
155
312
 
156
- #### Use wraper Controller and ChildModule
313
+ ### 3.5 Add a controller
157
314
 
158
- ```typescript
159
- import { DefController } from "nestjs-typeorm3-kit";
315
+ ```ts
316
+ // book.controller.ts
317
+ import { Body, Param, Query } from "@nestjs/common";
318
+ import { DefController, DefGet, DefPost } from "nestjs-typeorm3-kit";
319
+ import { BookService } from "./book.service";
160
320
 
161
- @Controller("demo")
162
- export class DemoController {
163
- constructor(private readonly demoService: DemoService) {}
321
+ @DefController("books")
322
+ export class BookController {
323
+ constructor(private readonly svc: BookService) {}
164
324
 
165
- @DefGet()
166
- list(@Query() query: any) {
167
- return this.demoService.list(query);
325
+ @DefGet("", { summary: "List all books" })
326
+ list(@Query() q: any) {
327
+ return this.svc.list();
168
328
  }
169
329
 
170
- @DefPost()
171
- create(@Body() body: any) {
172
- return this.demoService.create(body);
330
+ @DefPost("", { summary: "Create a book" })
331
+ create(@Body() body: { title: string }) {
332
+ return this.svc.create(body.title);
173
333
  }
174
334
  }
335
+ ```
336
+
337
+ `@DefGet/Post/...` bundle `@Get/@Post`, `@HttpCode`, `@ApiOperation`, `@ApiResponse` (and optionally `@ApiBody`) into one call.
338
+
339
+ ### 3.6 Group modules with `@ChildModule`
340
+
341
+ `@ChildModule` mounts the module under a route prefix and auto-tags its controllers in Swagger.
342
+
343
+ ```ts
344
+ // client.module.ts
345
+ import { ChildModule } from "nestjs-typeorm3-kit";
175
346
 
176
- // child module suport prefix route and API TAG swagger
177
347
  @ChildModule({
178
- prefix: REFIX_MODULE.client,
348
+ prefix: "client",
179
349
  imports: [PrimaryRepoModule, SecondaryRepoModule],
180
- providers: [DemoService, ExampleService],
181
- controllers: [ExampleController, DemoController],
350
+ controllers: [BookController],
351
+ providers: [BookService],
182
352
  })
183
- export class ClientModule implements NestModule {
184
- configure(consumer: MiddlewareConsumer) {}
353
+ export class ClientModule {}
354
+ ```
355
+
356
+ Resulting routes: `GET /client/books`, `POST /client/books`. Swagger tag: `[Client] BookController`.
357
+
358
+ ### 3.7 Add transactions
359
+
360
+ Decorate any service method with `@DefTransaction()`. Without `connectionName` it opens a transaction on the **default** DataSource only: `bookRepo` writes inside it, while `logRepo` (on `secondary`) writes outside it. Two databases cannot share one SQL transaction — see [Multi-DataSource transactions](./docs/en/transaction.md#multi-datasource-transactions).
361
+
362
+ ```ts
363
+ // book.service.ts (continued)
364
+ import { DefTransaction } from "nestjs-typeorm3-kit";
365
+
366
+ @DefTransaction()
367
+ async create(title: string) {
368
+ const book = await this.bookRepo.save({ title });
369
+ // If this throws, the bookRepo.save above is rolled back too.
370
+ // The log row itself is not transactional: it lives on another DataSource.
371
+ await this.logRepo.save({ action: "book.created", refId: book.id });
372
+ return book;
185
373
  }
186
- // setup swagger beautiful in main.ts
374
+ ```
375
+
376
+ #### How propagation works
377
+
378
+ ```mermaid
379
+ flowchart TD
380
+ Call[Method call] --> HasTx{In a transaction<br/>already?}
381
+ HasTx -- "REQUIRED (default)" --> Join1[Join existing] & New1[Start new]
382
+ HasTx -- "REQUIRES_NEW" --> Suspend[Suspend outer<br/>and start fresh]
383
+ HasTx -- "NESTED" --> Save[Savepoint inside outer]
384
+ HasTx -- "MANDATORY" --> Throw1{No tx? throw}
385
+ HasTx -- "NEVER" --> Throw2{Has tx? throw}
386
+ HasTx -- "SUPPORTS" --> Pass[Run with or without]
387
+ HasTx -- "NOT_SUPPORTED" --> Pause[Pause tx, run plain]
388
+ ```
389
+
390
+ Pass `propagation`, `isoLevel`, and `connectionName` to target a specific DataSource:
391
+
392
+ ```ts
393
+ @DefTransaction({
394
+ connectionName: "secondary",
395
+ propagation: Propagation.REQUIRES_NEW,
396
+ isoLevel: "SERIALIZABLE",
397
+ })
398
+ async audit(payload: AuditPayload) { /* ... */ }
399
+ ```
400
+
401
+ ### 3.8 Enable Swagger
402
+
403
+ ```ts
187
404
  // main.ts
188
405
  import { NestFactory } from "@nestjs/core";
189
- import { AppModule } from "./app.module";
190
406
  import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
191
- import { getFromContainer, MetadataStorage } from "class-validator";
192
- import { SchemasObject } from "@nestjs/swagger/dist/interfaces/open-api-spec.interface";
407
+ import { configSwaggerDocument, setupTransactionContext } from "nestjs-typeorm3-kit";
193
408
 
194
- import { validationMetadatasToSchemas } from "class-validator-jsonschema";
195
- import { setupTransactionContext } from "nestjs-typeorm3-kit";
196
- import { INestApplication } from "@nestjs/common";
409
+ async function bootstrap() {
410
+ setupTransactionContext();
411
+ const app = await NestFactory.create(AppModule);
197
412
 
198
- const configSwagger = (app: INestApplication) => {
199
413
  const options = new DocumentBuilder()
200
- .setTitle("SWAGGER_TITLE")
201
- .setDescription("SWAGGER_DESCRIPTION")
202
- .setVersion("SWAGGER_VERSION")
203
- .addSecurity("bearer", {
204
- type: "http",
205
- scheme: "bearer",
206
- bearerFormat: "JWT",
207
- })
414
+ .setTitle("My API")
415
+ .setVersion("1.0")
416
+ .addBearerAuth()
208
417
  .build();
209
418
 
210
- const document = SwaggerModule.createDocument(app as any, options, {
211
- // extraModels: [PageResponse]
212
- });
213
- configSwaggerDocument(app, document, "swagger");
214
- };
419
+ const document = SwaggerModule.createDocument(app, options);
420
+ configSwaggerDocument(app, document, "swagger"); // → GET /swagger
215
421
 
216
- async function bootstrap() {
217
- setupTransactionContext();
218
- const app = await NestFactory.create(AppModule);
219
- const port = process.env.PORT || 3000;
220
- app.enableCors({ origin: "*" });
221
- configSwagger(app);
222
- await app.listen(port);
223
- console.log(
224
- `Server start on port ${port}. Open http://localhost:${port} to see results`
225
- );
226
- console.log(`API DOCUMENT Open http://localhost:${port}/swagger`);
227
- console.log(`API DOCUMENT JSON Open http://localhost:${port}/swagger-json`);
228
- console.log("TIMEZONE: ", process.env.TZ);
422
+ await app.listen(3000);
229
423
  }
230
424
  bootstrap();
231
425
  ```
232
426
 
427
+ That's a full app. The rest of the README is the API reference.
428
+
429
+ ---
430
+
431
+ ## 4. Relationship to `typeorm-transactional`
432
+
433
+ `nestjs-typeorm3-kit` is a **wrapper fork** of [`typeorm-transactional`](https://github.com/Aliheym/typeorm-transactional). Concretely:
434
+
435
+ - **Vendored:** the entire `typeorm-transactional@v0.5.0` source lives in [`src/vendor/transactional/`](./src/vendor/transactional). The MIT [LICENSE](./src/vendor/transactional/LICENSE) (Copyright © Ohad David) is preserved verbatim.
436
+ - **Wrapped:** `setupTransactionContext` and `@DefTransaction` are thin wrappers over the vendored `initializeTransactionalContext` and `@Transactional` decorators.
437
+ - **Re-exported:** `Transactional`, `Propagation`, `IsolationLevel`, `StorageDriver`, `addTransactionalDataSource`, `runInTransaction`, `wrapInTransaction`, `runOnTransactionCommit`, `runOnTransactionRollback`, `runOnTransactionComplete`, `TransactionalError`, `getDataSourceByName`, `deleteDataSourceByName` are all re-exported from `nestjs-typeorm3-kit` so you only need one import path.
438
+
439
+ <details>
440
+ <summary><b>What changed vs. upstream</b></summary>
441
+
442
+ | Area | Upstream `v0.5.0` | This fork (v0.2.0+) |
443
+ | ----------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------- |
444
+ | Storage drivers | `cls-hooked` + `AsyncLocalStorage` | Only `AsyncLocalStorage` |
445
+ | Runtime dependencies | `cls-hooked`, `@types/cls-hooked`, `semver` | None |
446
+ | Bun support | ❌ broken (`async_hooks` incomplete) | ✅ works |
447
+ | Node requirement | `>= 14` | `>= 16.4` |
448
+ | Public API (decorators, hooks, propagation, isolation)| — | **Identical** |
449
+ | `initializeTransactionalContext` options | `{ maxHookHandlers, storageDriver }` | `{ maxHookHandlers }` — `storageDriver` removed (only one driver) |
450
+ | `StorageDriver` enum values | `AUTO`, `CLS_HOOKED`, `ASYNC_LOCAL_STORAGE` | `AUTO` (alias), `ASYNC_LOCAL_STORAGE` |
451
+
452
+ </details>
453
+
454
+ <details>
455
+ <summary><b>Migrating from <code>typeorm-transactional</code></b></summary>
456
+
457
+ ```diff
458
+ - import { Transactional, Propagation, addTransactionalDataSource } from 'typeorm-transactional';
459
+ + import { Transactional, Propagation, addTransactionalDataSource } from 'nestjs-typeorm3-kit';
460
+
461
+ - import { initializeTransactionalContext, StorageDriver } from 'typeorm-transactional';
462
+ - initializeTransactionalContext({ storageDriver: StorageDriver.CLS_HOOKED });
463
+ + import { setupTransactionContext } from 'nestjs-typeorm3-kit';
464
+ + setupTransactionContext();
465
+ ```
466
+
467
+ Then **uninstall** `typeorm-transactional` — it is no longer needed.
468
+
469
+ </details>
470
+
471
+ ---
472
+
473
+ ## 5. Repository API
474
+
475
+ ### `@DefEntityRepository`
476
+
477
+ Marks a class as a TypeORM repository bound to an entity. Your class should extend `Repository<Entity>` or `RepositoryWrapper<Entity>`.
478
+
479
+ ```ts
480
+ @DefEntityRepository(Book)
481
+ export class BookRepo extends Repository<Book> {
482
+ findPublished() { return this.find({ where: { published: true } }); }
483
+ }
484
+ ```
485
+
486
+ Under the hood the decorator stores the entity on the class via `Reflect.defineMetadata`. `DefRepositoryModule` reads that metadata to wire up providers.
487
+
488
+ ### `DefRepositoryModule`
489
+
490
+ Registers repository providers for one or more DataSources. Three styles:
491
+
492
+ #### `forFeature(repositories, dataSource?)`
493
+
494
+ ```ts
495
+ DefRepositoryModule.forFeature([BookRepo, AuthorRepo]);
496
+ DefRepositoryModule.forFeature([LogRepo], "secondary");
497
+ ```
498
+
499
+ #### `forRoot({ globPattern, dataSource? })`
500
+
501
+ Returns a `Promise<DynamicModule>`. Use `forRootAsync` when the options come from a factory or options class, or the module needs extra `imports`.
502
+
503
+ #### `forRootAsync({ useFactory | useClass })`
504
+
505
+ ```ts
506
+ DefRepositoryModule.forRootAsync({
507
+ useFactory: async () => ({
508
+ globPattern: join(__dirname, "./**/*.repo.{ts,js}"),
509
+ dataSource: "default",
510
+ }),
511
+ });
512
+ ```
513
+
514
+ Repository discovery and provider tokens are resolved **before Nest bootstraps**.
515
+ `useFactory` runs once without injected arguments; `useClass` constructs a
516
+ dependency-free options factory once. `inject` and `useExisting` are not
517
+ supported: Nest providers do not exist yet when the glob must be scanned.
518
+ `imports` remains available for the resulting dynamic module.
519
+
520
+ <details>
521
+ <summary><b><code>DefRepositoryModuleOptions</code></b></summary>
522
+
523
+ | Field | Type | Required | Description |
524
+ | ------------- | ------------------------------------------- | :------: | ------------------------------------------------------------------------- |
525
+ | `globPattern` | `string` | ✔ | Absolute glob used to discover `*.repo.ts` / `*.repo.js` files. |
526
+ | `dataSource` | `string \| DataSource \| DataSourceOptions` | | DataSource name. Defaults to `"default"`. Determines the injection token. |
527
+
528
+ </details>
529
+
530
+ ### `@InjectRepo`
531
+
532
+ ```ts
533
+ constructor(
534
+ @InjectRepo(BookRepo) readonly bookRepo: BookRepo, // default
535
+ @InjectRepo(LogRepo, "secondary") readonly logRepo: LogRepo, // named
536
+ @InjectRepo(AuditRepo, opts) readonly auditRepo: AuditRepo, // by DataSourceOptions
537
+ ) {}
538
+ ```
539
+
540
+ For the default connection you can also use plain constructor injection (`readonly repo: BookRepo`).
541
+
542
+ ### `RepositoryWrapper`
543
+
544
+ `TypeOrmRepository.findOne({ where: { id: undefined } })` silently strips `undefined` and returns the **first row of the table** — a footgun that can cause data leaks or auth bypasses.
545
+
546
+ `RepositoryWrapper` overrides `findOne` to return `null` when:
547
+
548
+ - `options` is missing or not an object
549
+ - `options.where` is falsy or not an object
550
+ - `where` is empty (`{}`, `[]`, or an OR branch with `{}`)
551
+ - any mapped column or relation/embedded predicate in `where` has an `undefined` value or an empty nested condition
552
+
553
+ ```ts
554
+ export class BaseRepo<Entity> extends RepositoryWrapper<Entity> {}
555
+
556
+ @DefEntityRepository(Book)
557
+ export class BookRepo extends BaseRepo<Book> {}
558
+
559
+ await bookRepo.findOne({ where: { id: undefined } }); // → null (safe)
560
+ await bookRepo.findOne({ where: {} }); // → null (no filter)
561
+ await bookRepo.findOne({ where: { author: { id: undefined } } }); // → null
562
+ await bookRepo.findOne({ where: { id: 1 } }); // → Book | null
563
+ ```
564
+
565
+ ---
566
+
567
+ ## 6. Transactions
568
+
569
+ ### `setupTransactionContext`
570
+
571
+ Must be called in `main.ts` **before** `NestFactory.create`. Initialises the vendored transactional layer.
572
+
573
+ ```ts
574
+ setupTransactionContext();
575
+ // or with options:
576
+ setupTransactionContext({ maxHookHandlers: 50 });
577
+ ```
578
+
579
+ <details>
580
+ <summary><b>Options</b></summary>
581
+
582
+ | Field | Type | Default | Description |
583
+ | ----------------- | -------- | ------- | ---------------------------------------------------- |
584
+ | `maxHookHandlers` | `number` | `10` | Hooks of one type per transaction before Node warns about a possible leak (not a hard limit). |
585
+
586
+ </details>
587
+
588
+ #### Runtime support
589
+
590
+ | Runtime | Supported | Why |
591
+ | ----------------- | :--------: | -------------------------------------------------------------------------------------------------------------------- |
592
+ | Node.js >= 16.4 | ✅ | `AsyncLocalStorage` is native. |
593
+ | Bun | ✅ | The vendored fork dropped `cls-hooked` (which Bun can't run) — only `AsyncLocalStorage` is used. |
594
+ | Node.js < 16.4 | ❌ | Upgrade Node, or stay on `nestjs-typeorm3-kit@0.1.x`. |
595
+
596
+ ### `@DefTransaction`
597
+
598
+ Decorator-based transactions — wraps the vendored `@Transactional`.
599
+
600
+ ```ts
601
+ import { DefTransaction, IsolationLevel, Propagation } from "nestjs-typeorm3-kit";
602
+
603
+ class BookService {
604
+ @DefTransaction() async create(dto: CreateBookDto) { /* ... */ }
605
+
606
+ @DefTransaction({ connectionName: "secondary" })
607
+ async audit(dto: AuditDto) { /* ... */ }
608
+
609
+ @DefTransaction({
610
+ propagation: Propagation.REQUIRES_NEW,
611
+ isolationLevel: IsolationLevel.READ_COMMITTED,
612
+ })
613
+ async complex() { /* ... */ }
614
+ }
615
+ ```
616
+
617
+ <details>
618
+ <summary><b>Options</b></summary>
619
+
620
+ | Field | Type | Description |
621
+ | ---------------- | ------------------------------------------------------------------------------- | -------------------------------------- |
622
+ | `connectionName` | `string \| (() => string \| undefined)` | DataSource name. |
623
+ | `propagation` | `Propagation` | Propagation strategy. |
624
+ | `isolationLevel` | `IsolationLevel` or its enum key | SQL transaction isolation level. |
625
+ | `isoLevel` | `"READ_UNCOMMITTED" \| "READ_COMMITTED" \| "REPEATABLE_READ" \| "SERIALIZABLE"` | Legacy alias for `isolationLevel`. |
626
+
627
+ </details>
628
+
629
+ Parallel `NESTED` calls on the same transaction are serialized at the
630
+ savepoint boundary; a failed child cannot roll back a sibling's write.
631
+ Transactions on different DataSources remain independent.
632
+
633
+ #### Transaction hooks
634
+
635
+ ```ts
636
+ import { runOnTransactionCommit, runOnTransactionRollback, runOnTransactionComplete } from "nestjs-typeorm3-kit";
637
+
638
+ @DefTransaction()
639
+ async create(dto: CreateBookDto) {
640
+ const book = await this.repo.save(dto);
641
+ runOnTransactionCommit(() => this.events.emit("book.created", book));
642
+ runOnTransactionRollback((err) => this.logger.warn("rolled back", err));
643
+ return book;
644
+ }
645
+ ```
646
+
647
+ ---
648
+
649
+ ## 7. Controller & module helpers
650
+
651
+ ### `@DefController` and method decorators
652
+
653
+ `@DefController` is like `@Controller` but also applies `@ApiBearerAuth()`. Each method decorator bundles `@Get/Post/...`, `@HttpCode`, `@ApiOperation`, `@ApiResponse`, optionally `@ApiBody`.
654
+
655
+ ```ts
656
+ @DefController("books")
657
+ export class BookController {
658
+ @DefGet("", { summary: "List books", responseType: [BookDto] })
659
+ list(@Query() q: ListQuery) { /* ... */ }
660
+
661
+ @DefPost("", { bodyType: CreateBookDto, responseType: BookDto })
662
+ create(@Body() dto: CreateBookDto) { /* ... */ }
663
+
664
+ @DefPatch(":id", { bodyType: UpdateBookDto })
665
+ update(@Param("id") id: string, @Body() dto: UpdateBookDto) { /* ... */ }
666
+
667
+ @DefDelete(":id", { statusCode: 204 })
668
+ remove(@Param("id") id: string) { /* ... */ }
669
+ }
670
+ ```
671
+
672
+ <details>
673
+ <summary><b>Method options</b></summary>
674
+
675
+ | Field | Type | Default |
676
+ | -------------- | ---------------------------------------- | --------------- |
677
+ | `summary` | `string` | `""` |
678
+ | `statusCode` | `HttpStatus` | `HttpStatus.OK` |
679
+ | `responseType` | `Type \| Function \| [Function] \| string` | `undefined` |
680
+ | `bodyType`\* | `Type \| Function \| [Function] \| string` | `undefined` |
681
+
682
+ \* `bodyType` is only on `DefPost`, `DefPut`, `DefPatch`, `DefDelete`.
683
+
684
+ </details>
685
+
686
+ ### `@ChildModule`
687
+
688
+ Mounts a module under a route prefix, auto-tags its controllers in Swagger as `[PascalPrefix] ControllerName`, and propagates the prefix down to imported modules.
689
+
690
+ ```ts
691
+ @ChildModule({
692
+ prefix: "client",
693
+ imports: [BookRepoModule],
694
+ controllers: [BookController],
695
+ providers: [BookService],
696
+ })
697
+ export class ClientModule {}
698
+ ```
699
+
700
+ An imported child can be mounted under different parent prefixes. Each mount
701
+ has its own module instance and providers; the child class's original route
702
+ metadata is not overwritten.
703
+
704
+ When selecting Swagger routes with `include: [ChildModuleClass]`, use
705
+ `createScopedSwaggerDocument` instead of `SwaggerModule.createDocument` to
706
+ include every prefixed mount of that public module class.
707
+
708
+ ---
709
+
710
+ ## 8. Swagger helper
711
+
712
+ ### `createScopedSwaggerDocument(app, config, options?)` and `configSwaggerDocument`
713
+
714
+ `createScopedSwaggerDocument` expands `include` to prefixed child mounts. With
715
+ no `include`, it behaves like `SwaggerModule.createDocument`. Then
716
+ `configSwaggerDocument` merges `class-validator` metadata into
717
+ `components.schemas` without replacing Swagger's existing property definitions
718
+ and mounts the Swagger UI.
719
+
720
+ ```ts
721
+ const document = createScopedSwaggerDocument(app, options, {
722
+ include: [ClientModule],
723
+ });
724
+ configSwaggerDocument(app, document, "swagger"); // → GET /swagger
725
+ ```
726
+
727
+ > 🐛 **Historical note:** versions before `0.2.0` had a bug where every schema was merged under the literal key `"key"` due to a missing computed property name. Use the helper instead of copy-pasting it.
728
+
729
+ ---
730
+
731
+ ## 9. Validation decorators
732
+
733
+ ### `@IsNumber({ required? })`
734
+
735
+ Coerces to number. Optional by default — `{ required: true }` makes it required.
736
+
737
+ ```ts
738
+ class Query {
739
+ @IsNumber() page?: number;
740
+ @IsNumber({ required: true }) limit!: number;
741
+ }
742
+ ```
743
+
744
+ ### `@IsPhoneNumber(region, options?)`
745
+
746
+ Casts to `string` and delegates to `class-validator`'s `IsPhoneNumber`.
747
+
748
+ ```ts
749
+ class Dto {
750
+ @IsPhoneNumber("VN") phone!: string;
751
+ }
752
+ ```
753
+
754
+ ### `@IsLongerThan(otherProperty, options?)`
755
+
756
+ Passes when the decorated string is strictly longer than another property on the same object.
757
+
758
+ ```ts
759
+ class ResetPasswordDto {
760
+ oldPassword!: string;
761
+ @IsLongerThan("oldPassword") newPassword!: string;
762
+ }
763
+ ```
764
+
765
+ ---
766
+
767
+ ## 10. Utilities
768
+
769
+ ### `lazyLoadClasses(baseDir, suffixes, additional?)`
770
+
771
+ Scans `baseDir`'s immediate subdirectories for files matching the given suffixes (e.g. `.controller`, `.service`) and returns the first exported class from each.
772
+
773
+ ```ts
774
+ @Module({
775
+ controllers: lazyLoadClasses(__dirname, [".controller"]),
776
+ providers: lazyLoadClasses(__dirname, [".service"]),
777
+ })
778
+ export class ClientModule {}
779
+ ```
780
+
781
+ > Assumes one exported class per file.
782
+
783
+ ### `addTransactionalDataSource` (re-export)
784
+
785
+ Re-exported from the vendored `typeorm-transactional` for convenience.
786
+
787
+ ---
788
+
789
+ ## 11. Full example
790
+
791
+ A runnable Nest project lives in [`./example`](./example) — two Postgres connections, `ChildModule`, `@DefTransaction` across connections, Swagger wired up end-to-end.
792
+
793
+ ```bash
794
+ cd example
795
+ yarn install
796
+ yarn start:dev
797
+ # http://localhost:3000/swagger
798
+ ```
799
+
233
800
  ![Swagger UI](image.png)
234
801
 
235
- #### Example
802
+ ---
803
+
804
+ ## 12. Testing
805
+
806
+ The package ships with two test configurations:
807
+
808
+ - **Unit tests** (`yarn test`) — fast, no external dependencies (in-memory `sql.js`). They live next to the source as `src/**/*.spec.ts`, with fixtures in `__fixtures__/`, and are excluded from the build by `tsconfig.build.json`. Run `yarn test --coverage` for a coverage report.
809
+ - **Integration tests** (`yarn test:integration`) — spin up the Nest module against a real Postgres (see `tests/src/app.module.ts`). Requires a Postgres instance matching the configured host/port.
810
+
811
+ ```bash
812
+ yarn test # unit tests
813
+ yarn test:integration # integration tests (needs Postgres)
814
+ ```
815
+
816
+ ---
817
+
818
+ ## 13. Compatibility
819
+
820
+ **Runtime:** Node.js `>= 16.4` or Bun for the package itself; NestJS 12 and `@nestjs/typeorm` 12 require Node.js `>= 20.19`.
821
+
822
+ <details>
823
+ <summary><b>Peer dependency matrix</b></summary>
824
+
825
+ | Peer dependency | Range |
826
+ | ----------------------------- | ------------------ |
827
+ | `@nestjs/common`, `core` | `>= 8.0.0 < 13` |
828
+ | `@nestjs/typeorm` | `>= 9.0.0 < 13` |
829
+ | `@nestjs/swagger` | `>= 5.0.0 < 13` |
830
+ | `typeorm` | `>= 0.3.0 < 1` |
831
+ | `reflect-metadata` | `>= 0.1.13 < 0.3` |
832
+ | `rxjs` | `>= 7.2.0 < 8` |
833
+ | `class-validator-jsonschema` | `^5.0.2` |
834
+
835
+ </details>
836
+
837
+ `fast-glob` is installed as a regular dependency. Swagger and JSON-schema are peers because public decorator and helper exports import them at module load, even when the application only uses repositories.
838
+
839
+ Developed against NestJS v12 / TypeORM 0.3.x. NestJS v8–v11 remain in the declared range, but exercise your application's integration tests before upgrading. Use a matching `@nestjs/typeorm` major for the NestJS version in your application.
840
+
841
+ Since v0.2.0, `typeorm-transactional` is **vendored**; the original package is no longer a runtime or peer dependency.
842
+
843
+ ---
844
+
845
+ ## 14. Troubleshooting
846
+
847
+ <details>
848
+ <summary><b><code>UnknownDependencyException: Nest can't resolve dependencies of ...</code></b></summary>
849
+
850
+ You forgot to import the `DefRepositoryModule`-wrapping module, or the repository's `@DefEntityRepository` decorator is missing. Also make sure the `globPattern` actually matches your file names (the module only picks up classes decorated with `@DefEntityRepository`).
851
+
852
+ </details>
853
+
854
+ <details>
855
+ <summary><b><code>No storage driver defined in your app</code> / <code>No data sources defined in your app</code></b></summary>
856
+
857
+ - `No storage driver defined …` — `setupTransactionContext()` was not called, or ran too late. It must run **before** `NestFactory.create(AppModule)`.
858
+ - `No data sources defined …` — the DataSource named by `connectionName` (default `"default"`) was never passed to `addTransactionalDataSource(...)`.
859
+
860
+ </details>
861
+
862
+ <details>
863
+ <summary><b>Transactions silently lose context on Bun</b></summary>
864
+
865
+ Upgrade to **v0.2.0+**. The vendored fork dropped `cls-hooked` entirely and uses `AsyncLocalStorage` only, which works on Bun.
866
+
867
+ </details>
868
+
869
+ <details>
870
+ <summary><b><code>findOne</code> returns <code>null</code> for a query I expect to work</b></summary>
871
+
872
+ That's the `RepositoryWrapper` guard at work — one of the values passed in `where` is `undefined`. Either provide a real value or an `IsNull()`/`Not()` clause. If you need raw TypeORM behaviour, use `Repository` instead of `RepositoryWrapper`.
873
+
874
+ </details>
875
+
876
+ <details>
877
+ <summary><b>Swagger schemas merged into a key literally called <code>"key"</code></b></summary>
878
+
879
+ Upgrade to `nestjs-typeorm3-kit >= 0.2.0` and call `configSwaggerDocument(app, document)` instead of copy-pasting the legacy snippet.
880
+
881
+ </details>
882
+
883
+ <details>
884
+ <summary><b>Repositories not picked up by the glob pattern</b></summary>
885
+
886
+ Paths in the glob **must be absolute** (use `join(__dirname, '...')`). On Windows the module rewrites `\` → `/` automatically.
887
+
888
+ </details>
236
889
 
237
- [Example](./example)
890
+ ---
238
891
 
239
- ### Full document in wiki github
892
+ ## License
240
893
 
241
- [FULL DOCUMENT](https://x302502.github.io/nestjs-typeorm3-kit/)
894
+ MIT © [x302502](mailto:thanhluan.bkhn@gmail.com)