nestjs-typeorm3-kit 0.1.7 → 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 +803 -162
  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 +4 -0
  10. package/dist/helpers/index.d.ts.map +1 -1
  11. package/dist/helpers/index.js +81 -11
  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 -5
  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,253 +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
- });
115
+ ```ts
116
+ // main.ts
117
+ import { NestFactory } from "@nestjs/core";
118
+ import { setupTransactionContext } from "nestjs-typeorm3-kit";
119
+ import { AppModule } from "./app.module";
120
+
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();
66
127
  ```
67
128
 
68
- #### create Repository module Use @DefRepositoryModule
129
+ ```ts
130
+ // book.repo.ts
131
+ import { Repository } from "typeorm";
132
+ import { DefEntityRepository } from "nestjs-typeorm3-kit";
133
+ import { Book } from "./book.entity";
69
134
 
70
- ```typescript
135
+ @DefEntityRepository(Book)
136
+ export class BookRepo extends Repository<Book> {}
137
+ ```
138
+
139
+ ```ts
140
+ // app.module.ts
71
141
  import { Module } from "@nestjs/common";
72
- import { join } from "path";
142
+ import { TypeOrmModule } from "@nestjs/typeorm";
143
+ import { DataSource, DataSourceOptions } from "typeorm";
144
+ import { addTransactionalDataSource, DefRepositoryModule } from "nestjs-typeorm3-kit";
73
145
 
74
- import { PRIMARY_CONNECTION } from "~/common/constants";
75
- import { DefRepositoryModule } from "nestjs-typeorm3-kit";
76
146
  @Module({
77
147
  imports: [
78
- DefRepositoryModule.forRootAsync({
79
- useFactory: () => ({
80
- globPattern: join(__dirname, "./**/*.repo.{ts,js}"),
81
- dataSource: PRIMARY_CONNECTION,
82
- }),
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)),
83
153
  }),
154
+ DefRepositoryModule.forFeature([BookRepo]),
84
155
  ],
85
- exports: [DefRepositoryModule],
156
+ providers: [BookService],
86
157
  })
87
- export class PrimaryRepoModule {}
158
+ export class AppModule {}
88
159
  ```
89
160
 
90
- or
161
+ ```ts
162
+ // book.service.ts
163
+ import { Injectable } from "@nestjs/common";
164
+ import { DefTransaction, InjectRepo } from "nestjs-typeorm3-kit";
165
+
166
+ @Injectable()
167
+ export class BookService {
168
+ constructor(@InjectRepo(BookRepo) private readonly repo: BookRepo) {}
91
169
 
92
- ```typescript
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
93
268
  import { Module } from "@nestjs/common";
94
269
  import { join } from "path";
95
-
96
- import { PRIMARY_CONNECTION } from "~/common/constants";
97
270
  import { DefRepositoryModule } from "nestjs-typeorm3-kit";
271
+
98
272
  @Module({
99
273
  imports: [
100
- DefRepositoryModule.forFeature([BookRepo, PhotoRepo], PRIMARY_CONNECTION),
274
+ DefRepositoryModule.forRootAsync({
275
+ useFactory: () => ({
276
+ globPattern: join(__dirname, "./**/*.repo.{ts,js}"),
277
+ dataSource: "default",
278
+ }),
279
+ }),
101
280
  ],
102
281
  exports: [DefRepositoryModule],
103
282
  })
104
283
  export class PrimaryRepoModule {}
105
284
  ```
106
285
 
107
- #### Use @DefTransaction
286
+ Repeat with `dataSource: "secondary"` for the second connection.
108
287
 
109
- ```typescript
110
- import { Injectable } from "@nestjs/common";
111
- import { DefTransaction, InjectRepo } from "nestjs-typeorm3-kit";
112
- import { PhotoRepo } from "~/domains/primary/photo/photo.repo";
113
- import { BookRepo } from "~/domains/primary/book/book.repo";
288
+ ### 3.4 Inject the repository into a service
114
289
 
115
- const PRIMARY_CONNECTION = "default";
116
- const SECONDARY_CONNECTION = "secondary_db";
117
- @Injectable()
118
- export class DemoService {
119
- constructor(
120
- readonly photoRepo: PhotoRepo,
121
- @InjectRepo(BookRepo, PRIMARY_CONNECTION)
122
- readonly bookRepo: BookRepo
123
- ) {}
290
+ For the **default** connection, plain constructor injection works.
291
+ For a **named** connection, use `@InjectRepo(Repo, connectionName)`.
124
292
 
125
- @DefTransaction()
126
- create(body: any) {
127
- return this.photoRepo.save(body);
128
- }
129
- }
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";
130
299
 
131
- // secondary connection
132
300
  @Injectable()
133
- export class DemoService {
301
+ export class BookService {
134
302
  constructor(
135
- @InjectRepo(ExampleRepo, SECONDARY_CONNECTION)
136
- readonly exampleRepo: ExampleRepo,
137
- @InjectRepo(DataLogRepo, SECONDARY_CONNECTION)
138
- readonly dataLogRepo: DataLogRepo
303
+ readonly bookRepo: BookRepo, // default connection
304
+ @InjectRepo(LogRepo, "secondary") readonly logRepo: LogRepo, // secondary
139
305
  ) {}
140
306
 
141
- @DefTransaction({ connectionName: SECONDARY_CONNECTION })
142
- create(body: any) {
143
- return this.photoRepo.save(body);
307
+ list() {
308
+ return this.bookRepo.find();
144
309
  }
145
310
  }
146
311
  ```
147
312
 
148
- #### Use wraper Controller and ChildModule
313
+ ### 3.5 Add a controller
149
314
 
150
- ```typescript
151
- 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";
152
320
 
153
- @Controller("demo")
154
- export class DemoController {
155
- constructor(private readonly demoService: DemoService) {}
321
+ @DefController("books")
322
+ export class BookController {
323
+ constructor(private readonly svc: BookService) {}
156
324
 
157
- @DefGet()
158
- list(@Query() query: any) {
159
- return this.demoService.list(query);
325
+ @DefGet("", { summary: "List all books" })
326
+ list(@Query() q: any) {
327
+ return this.svc.list();
160
328
  }
161
329
 
162
- @DefPost()
163
- create(@Body() body: any) {
164
- return this.demoService.create(body);
330
+ @DefPost("", { summary: "Create a book" })
331
+ create(@Body() body: { title: string }) {
332
+ return this.svc.create(body.title);
165
333
  }
166
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";
167
346
 
168
- // child module suport prefix route and API TAG swagger
169
347
  @ChildModule({
170
- prefix: REFIX_MODULE.client,
348
+ prefix: "client",
171
349
  imports: [PrimaryRepoModule, SecondaryRepoModule],
172
- providers: [DemoService, ExampleService],
173
- controllers: [ExampleController, DemoController],
350
+ controllers: [BookController],
351
+ providers: [BookService],
174
352
  })
175
- export class ClientModule implements NestModule {
176
- 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;
177
373
  }
178
- // 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
179
404
  // main.ts
180
405
  import { NestFactory } from "@nestjs/core";
181
- import { AppModule } from "./app.module";
182
406
  import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
183
- import { getFromContainer, MetadataStorage } from "class-validator";
184
- import { SchemasObject } from "@nestjs/swagger/dist/interfaces/open-api-spec.interface";
407
+ import { configSwaggerDocument, setupTransactionContext } from "nestjs-typeorm3-kit";
185
408
 
186
- import { validationMetadatasToSchemas } from "class-validator-jsonschema";
187
- import { setupTransactionContext } from "nestjs-typeorm3-kit";
188
- import { INestApplication } from "@nestjs/common";
409
+ async function bootstrap() {
410
+ setupTransactionContext();
411
+ const app = await NestFactory.create(AppModule);
189
412
 
190
- const configSwagger = (app: INestApplication) => {
191
413
  const options = new DocumentBuilder()
192
- .setTitle("SWAGGER_TITLE")
193
- .setDescription("SWAGGER_DESCRIPTION")
194
- .setVersion("SWAGGER_VERSION")
195
- .addSecurity("bearer", {
196
- type: "http",
197
- scheme: "bearer",
198
- bearerFormat: "JWT",
199
- })
414
+ .setTitle("My API")
415
+ .setVersion("1.0")
416
+ .addBearerAuth()
200
417
  .build();
201
418
 
202
- const document = SwaggerModule.createDocument(app as any, options, {
203
- // extraModels: [PageResponse]
204
- });
205
-
206
- // Creating all the swagger schemas based on the class-validator decorators
207
- const metadatas = (getFromContainer(MetadataStorage) as any)
208
- .validationMetadatas;
209
- const targetSchemas = document.components.schemas || {};
210
- const schemasBinding = validationMetadatasToSchemas(metadatas) || {};
211
-
212
- Object.keys(schemasBinding).forEach((key) => {
213
- const value = schemasBinding[key] as SchemasObject;
214
- if (!targetSchemas[key]) {
215
- Object.assign(targetSchemas, { key: value });
216
- } else {
217
- const targetValue = targetSchemas[key] as SchemasObject;
218
-
219
- Object.assign(targetValue.properties, value.properties);
220
- targetValue.required = value.required;
221
- Object.assign(targetSchemas, { key: targetValue });
222
- }
223
- });
224
- document.components.schemas = Object.assign({}, targetSchemas);
225
- SwaggerModule.setup("swagger", app as any, document);
226
- };
419
+ const document = SwaggerModule.createDocument(app, options);
420
+ configSwaggerDocument(app, document, "swagger"); // → GET /swagger
227
421
 
228
- async function bootstrap() {
229
- setupTransactionContext();
230
- const app = await NestFactory.create(AppModule);
231
- const port = process.env.PORT || 3000;
232
- app.enableCors({ origin: "*" });
233
- configSwagger(app);
234
- await app.listen(port);
235
- console.log(
236
- `Server start on port ${port}. Open http://localhost:${port} to see results`
237
- );
238
- console.log(`API DOCUMENT Open http://localhost:${port}/swagger`);
239
- console.log(`API DOCUMENT JSON Open http://localhost:${port}/swagger-json`);
240
- console.log("TIMEZONE: ", process.env.TZ);
422
+ await app.listen(3000);
241
423
  }
242
424
  bootstrap();
243
425
  ```
244
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
+
245
800
  ![Swagger UI](image.png)
246
801
 
247
- #### 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>
248
889
 
249
- [Example](./example)
890
+ ---
250
891
 
251
- ### Full document in wiki github
892
+ ## License
252
893
 
253
- [FULL DOCUMENT](https://x302502.github.io/nestjs-typeorm3-kit/)
894
+ MIT © [x302502](mailto:thanhluan.bkhn@gmail.com)