@nestjs-transactional/typeorm 1.0.0-alpha.5 → 2.0.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.
- package/README.md +150 -308
- package/dist/adapter/typeorm.adapter.d.ts +26 -2
- package/dist/adapter/typeorm.adapter.js +81 -8
- package/dist/adapter/typeorm.adapter.js.map +1 -1
- package/dist/helpers/get-entity-manager.js +6 -10
- package/dist/helpers/get-entity-manager.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -20
- package/dist/index.js.map +1 -1
- package/dist/module/typeorm-transactional.module.d.ts +3 -3
- package/dist/module/typeorm-transactional.module.js +26 -29
- package/dist/module/typeorm-transactional.module.js.map +1 -1
- package/dist/patching/data-source-patches.js +10 -13
- package/dist/patching/data-source-patches.js.map +1 -1
- package/dist/patching/entity-manager-patches.d.ts +2 -2
- package/dist/patching/entity-manager-patches.js +11 -15
- package/dist/patching/entity-manager-patches.js.map +1 -1
- package/dist/patching/get-em-data-source.js +1 -4
- package/dist/patching/get-em-data-source.js.map +1 -1
- package/dist/patching/index.d.ts +6 -6
- package/dist/patching/index.js +14 -32
- package/dist/patching/index.js.map +1 -1
- package/dist/patching/managed-registry.js +10 -17
- package/dist/patching/managed-registry.js.map +1 -1
- package/dist/patching/repository-patches.js +17 -21
- package/dist/patching/repository-patches.js.map +1 -1
- package/dist/patching/symbols.d.ts +1 -1
- package/dist/patching/symbols.js +4 -7
- package/dist/patching/symbols.js.map +1 -1
- package/dist/types/typeorm-transaction-handle.js +1 -2
- package/package.json +30 -21
package/README.md
CHANGED
|
@@ -1,76 +1,55 @@
|
|
|
1
1
|
# @nestjs-transactional/typeorm
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@nestjs-transactional/typeorm)
|
|
4
4
|
[](https://github.com/igorgolovanov/nestjs-transactional/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
TypeORM adapter for
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
NestJS dynamic module that activates the transparent patches and
|
|
34
|
-
registers an adapter with the core `AdapterRegistry`. The
|
|
35
|
-
`DataSource` itself resolves from DI under
|
|
36
|
-
`getDataSourceToken(dataSource)` — the same convention
|
|
37
|
-
`@nestjs/typeorm` uses for `@InjectRepository(E, dataSource)`.
|
|
38
|
-
- `TypeOrmTransactionalModule.forRootAsync({ useFactory, inject?, imports? })`
|
|
39
|
-
— async variant for `ConfigService`-driven setups. Registers via
|
|
40
|
-
`OnModuleInit` to defer DataSource resolution past `@nestjs/typeorm`'s
|
|
41
|
-
async DataSource provider settling
|
|
42
|
-
([Convention #22](../../docs/status/conventions.md)).
|
|
43
|
-
- Multi-DataSource: call `forRoot` once per dataSource. Mirrors
|
|
44
|
-
[`OutboxModule`](../outbox) (ADR-019) and
|
|
45
|
-
[`TransactionalModule`](../core).
|
|
46
|
-
|
|
47
|
-
## Installation
|
|
6
|
+
TypeORM adapter for
|
|
7
|
+
[`@nestjs-transactional/core`](https://www.npmjs.com/package/@nestjs-transactional/core).
|
|
8
|
+
|
|
9
|
+
Two things come with it. The adapter itself, which maps `@Transactional()`
|
|
10
|
+
onto TypeORM's transactions and savepoints — and **transparent
|
|
11
|
+
transactional repositories**: your existing `@InjectRepository(Order)`
|
|
12
|
+
instances start honouring the active transaction on their own, with no
|
|
13
|
+
change to the code that uses them.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
@Injectable()
|
|
17
|
+
export class OrderService {
|
|
18
|
+
constructor(@InjectRepository(Order) private readonly orders: Repository<Order>) {}
|
|
19
|
+
|
|
20
|
+
@Transactional()
|
|
21
|
+
async place(dto: PlaceOrderDto) {
|
|
22
|
+
// Runs in the transaction. Rolls back if anything below throws.
|
|
23
|
+
// Outside a @Transactional method, the same call autocommits.
|
|
24
|
+
return this.orders.save(dto);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
No `getCurrentEntityManager()`, no passing an `EntityManager` down
|
|
30
|
+
through service layers, no separate "transactional" repository type.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
48
33
|
|
|
49
34
|
```bash
|
|
50
35
|
pnpm add @nestjs-transactional/typeorm @nestjs-transactional/core typeorm @nestjs/typeorm reflect-metadata
|
|
51
36
|
```
|
|
52
37
|
|
|
53
|
-
##
|
|
38
|
+
## Module format
|
|
54
39
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
| Node.js | `>=22.13.0` |
|
|
58
|
-
| `typeorm` | `^0.3.0 \|\| ^1.0.0` |
|
|
59
|
-
| `@nestjs/typeorm` | `^10.0.0 \|\| ^11.0.0` |
|
|
60
|
-
| `@nestjs/common` / `@nestjs/core` | `^10.0.0 \|\| ^11.0.0` |
|
|
61
|
-
| `reflect-metadata` | `^0.1.13 \|\| ^0.2.0` |
|
|
62
|
-
| `rxjs` | `^7.0.0` |
|
|
63
|
-
|
|
64
|
-
The TypeORM range covers both stable `0.3.x` and stable `1.x`
|
|
65
|
-
releases. CI runs the full unit and integration matrix
|
|
66
|
-
(testcontainers Postgres) against both TypeORM majors, so the
|
|
67
|
-
adapter is exercised end-to-end on every supported peer. TypeORM
|
|
68
|
-
nightly / beta builds are not in the declared range; install them
|
|
69
|
-
explicitly via `pnpm.overrides` if you need to pin to one.
|
|
40
|
+
This package ships **ESM only**, matching NestJS 12, which is ESM-only
|
|
41
|
+
across its own packages. There is no CommonJS build.
|
|
70
42
|
|
|
71
|
-
|
|
43
|
+
A CommonJS application still works: Node loads ESM from `require()`
|
|
44
|
+
since 22.12.0, which is why `engines.node` is `>=22.13.0`. What does not
|
|
45
|
+
follow Node here is tooling with its own module loader — Jest above all,
|
|
46
|
+
which needs `NODE_OPTIONS=--experimental-vm-modules` and a few config
|
|
47
|
+
settings. The 19 example applications in the repository all run their
|
|
48
|
+
suites that way and can be copied from.
|
|
72
49
|
|
|
73
|
-
|
|
50
|
+
Reasoning and measurements: [ADR-022](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/022-esm-only-packaging.md).
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
74
53
|
|
|
75
54
|
```ts
|
|
76
55
|
import { Module } from '@nestjs/common';
|
|
@@ -80,13 +59,8 @@ import { TypeOrmTransactionalModule } from '@nestjs-transactional/typeorm';
|
|
|
80
59
|
|
|
81
60
|
@Module({
|
|
82
61
|
imports: [
|
|
83
|
-
TypeOrmModule.forRoot({
|
|
84
|
-
|
|
85
|
-
url: process.env.DATABASE_URL,
|
|
86
|
-
entities: [User],
|
|
87
|
-
synchronize: false,
|
|
88
|
-
}),
|
|
89
|
-
TypeOrmModule.forFeature([User]),
|
|
62
|
+
TypeOrmModule.forRoot({ type: 'postgres', entities: [Order] }),
|
|
63
|
+
TypeOrmModule.forFeature([Order]),
|
|
90
64
|
|
|
91
65
|
TransactionalModule.forRoot({ isGlobal: true }),
|
|
92
66
|
TypeOrmTransactionalModule.forRoot(),
|
|
@@ -95,285 +69,153 @@ import { TypeOrmTransactionalModule } from '@nestjs-transactional/typeorm';
|
|
|
95
69
|
export class AppModule {}
|
|
96
70
|
```
|
|
97
71
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`
|
|
102
|
-
|
|
103
|
-
it globally, so `TypeOrmTransactionalModule.forRoot` finds it
|
|
104
|
-
automatically.
|
|
72
|
+
`TransactionalModule.forRoot({ isGlobal: true })` has to be there, with
|
|
73
|
+
`isGlobal` — that is how this module sees the core registry from its own
|
|
74
|
+
DI scope. The `DataSource` is found through `@nestjs/typeorm`'s
|
|
75
|
+
`getDataSourceToken(name)`, the same convention
|
|
76
|
+
`@InjectRepository(E, dataSource)` uses, so nothing else needs wiring.
|
|
105
77
|
|
|
106
|
-
##
|
|
78
|
+
## What becomes transparent
|
|
107
79
|
|
|
108
|
-
|
|
109
|
-
standard `@nestjs/typeorm` injection paths automatically dispatches
|
|
110
|
-
through the active `@Transactional()` scope. No
|
|
111
|
-
`getCurrentEntityManager()` calls in user code:
|
|
80
|
+
These all dispatch through the active transaction:
|
|
112
81
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
import { Order } from './order.entity';
|
|
82
|
+
- `@InjectRepository(Entity)` — the common case.
|
|
83
|
+
- `@InjectEntityManager() em.getRepository(E)`.
|
|
84
|
+
- `@InjectDataSource() ds.getRepository(E)` and `ds.manager`.
|
|
85
|
+
- Custom repositories built with `Repository.extend(...)`.
|
|
86
|
+
- `TreeRepository` and `MongoRepository`, which inherit from `Repository`.
|
|
119
87
|
|
|
120
|
-
|
|
121
|
-
export class OrderService {
|
|
122
|
-
constructor(
|
|
123
|
-
@InjectRepository(Order)
|
|
124
|
-
private readonly orderRepo: Repository<Order>,
|
|
125
|
-
) {}
|
|
88
|
+
Two patterns are **not** covered, and need an escape hatch:
|
|
126
89
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
Supported transparent patterns:
|
|
138
|
-
|
|
139
|
-
- `@InjectRepository(Entity) repo` — the headline case.
|
|
140
|
-
- `@InjectEntityManager() em.getRepository(E).save(...)`.
|
|
141
|
-
- `@InjectDataSource() ds.getRepository(E).save(...)`.
|
|
142
|
-
- `@InjectDataSource() ds.manager.save(Entity, ...)` (the patched
|
|
143
|
-
DataSource manager getter routes through the active EM).
|
|
144
|
-
- Custom repositories via `Repository.extend(...)`.
|
|
145
|
-
- `TreeRepository` and `MongoRepository` (inherit from `Repository`).
|
|
146
|
-
|
|
147
|
-
The mechanism is prototype-level patching modelled on the
|
|
148
|
-
`typeorm-transactional` library; patches install at module-load
|
|
149
|
-
time so they cover Repositories constructed by any DI factory,
|
|
150
|
-
regardless of resolution order.
|
|
151
|
-
|
|
152
|
-
### Documented limitations
|
|
153
|
-
|
|
154
|
-
Two patterns are NOT covered by the patches and require an escape
|
|
155
|
-
hatch:
|
|
90
|
+
1. **`em.save(Entity, ...)` called directly** on an injected
|
|
91
|
+
`EntityManager`. The patch covers `em.getRepository(E).save(...)`,
|
|
92
|
+
not the manager's own data methods — patching all ~14 of them would
|
|
93
|
+
require per-method recursion guards, which was judged not worth the
|
|
94
|
+
surface area.
|
|
95
|
+
2. **`BaseEntity` static methods** (`User.save(...)`).
|
|
96
|
+
`BaseEntity.useDataSource(...)` captures a `DataSource` reference
|
|
97
|
+
that bypasses the patch. The library `typeorm-transactional` has the
|
|
98
|
+
same limitation.
|
|
156
99
|
|
|
157
|
-
|
|
158
|
-
NOT transactional. The patches cover `em.getRepository(E).save(...)`
|
|
159
|
-
(the typical pattern) but not direct method calls on the injected
|
|
160
|
-
`EntityManager`. Use the Repository pattern instead, or call
|
|
161
|
-
`getCurrentEntityManager()`:
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
@Transactional()
|
|
165
|
-
async createUser(name: string) {
|
|
166
|
-
// Option A — Repository pattern (recommended).
|
|
167
|
-
return this.em.getRepository(User).save({ name });
|
|
168
|
-
|
|
169
|
-
// Option B — escape hatch.
|
|
170
|
-
// const em = getCurrentEntityManager();
|
|
171
|
-
// return em.save(User, { name });
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
2. **`BaseEntity` static methods** (`User.save(...)` etc.) are NOT
|
|
176
|
-
supported. The `BaseEntity.useDataSource(...)` API stores a
|
|
177
|
-
captured DataSource reference that bypasses the patches. Use the
|
|
178
|
-
Repository pattern.
|
|
179
|
-
|
|
180
|
-
The escape hatch:
|
|
100
|
+
For both, either use a repository or reach for the escape hatch:
|
|
181
101
|
|
|
182
102
|
```ts
|
|
183
103
|
import { getCurrentEntityManager } from '@nestjs-transactional/typeorm';
|
|
184
104
|
|
|
185
|
-
@
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// Pass `ds` as fallback so the helper returns ds.manager when
|
|
192
|
-
// no transaction is active (autocommit). Inside a tx, returns
|
|
193
|
-
// the transactional EM.
|
|
194
|
-
const em = getCurrentEntityManager('default', this.ds);
|
|
195
|
-
await em.query(
|
|
196
|
-
'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
|
|
197
|
-
[100, 1],
|
|
198
|
-
);
|
|
199
|
-
}
|
|
105
|
+
@Transactional()
|
|
106
|
+
async runRawSql() {
|
|
107
|
+
// Pass the DataSource as fallback so this also works outside a
|
|
108
|
+
// transaction, where it returns ds.manager.
|
|
109
|
+
const em = getCurrentEntityManager('default', this.ds);
|
|
110
|
+
await em.query('UPDATE accounts SET balance = balance - $1', [100]);
|
|
200
111
|
}
|
|
201
112
|
```
|
|
202
113
|
|
|
203
|
-
##
|
|
114
|
+
## Dialect-dependent behaviour
|
|
204
115
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
imports: [
|
|
208
|
-
TypeOrmModule.forRoot({ name: 'default', /* ... */ }),
|
|
209
|
-
TypeOrmModule.forRoot({ name: 'billing', /* ... */ }),
|
|
116
|
+
Two options behave differently per database, and both fail loudly or
|
|
117
|
+
harmlessly rather than surprisingly:
|
|
210
118
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
119
|
+
- **`readOnly`** is enforced on `postgres`, `cockroachdb` and
|
|
120
|
+
`aurora-postgres`, where the adapter issues `SET TRANSACTION READ ONLY`
|
|
121
|
+
as the transaction's first statement and the database refuses a write.
|
|
122
|
+
On other dialects it is a silent no-op. MySQL is not merely
|
|
123
|
+
unimplemented but unimplementable: `SET TRANSACTION` there applies to
|
|
124
|
+
the *next* transaction and errors inside a started one. Worth knowing
|
|
125
|
+
if you develop on SQLite and deploy to Postgres — the constraint
|
|
126
|
+
appears in production for the first time.
|
|
127
|
+
([DD-027](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/dd/027-readonly-and-timeout-semantics.md))
|
|
128
|
+
- **`PropagationMode.NESTED`** needs savepoints. The adapter checks
|
|
129
|
+
TypeORM's own `driver.transactionSupport` flag and throws
|
|
130
|
+
`IllegalTransactionStateError` naming the driver and the alternatives,
|
|
131
|
+
instead of running your "nested" transaction as part of the outer one.
|
|
132
|
+
|
|
133
|
+
## Multiple dataSources
|
|
218
134
|
|
|
219
|
-
|
|
135
|
+
One `forRoot` call per dataSource:
|
|
220
136
|
|
|
221
137
|
```ts
|
|
222
|
-
|
|
138
|
+
TypeOrmTransactionalModule.forRoot({ isDefault: true }), // 'default'
|
|
139
|
+
TypeOrmTransactionalModule.forRoot({ dataSource: 'billing' }), // 'billing'
|
|
140
|
+
```
|
|
223
141
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
private readonly invoiceRepo: Repository<Invoice>,
|
|
229
|
-
) {}
|
|
230
|
-
|
|
231
|
-
@Transactional({ dataSource: 'billing' })
|
|
232
|
-
async chargeCard(/* ... */) {
|
|
233
|
-
// Repository is bound to 'billing' DS — saves go to billing.
|
|
234
|
-
return this.invoiceRepo.save(/* ... */);
|
|
235
|
-
}
|
|
142
|
+
```ts
|
|
143
|
+
@Transactional({ dataSource: 'billing' })
|
|
144
|
+
async chargeCard() {
|
|
145
|
+
return this.invoiceRepo.save(/* ... */); // repo bound to 'billing'
|
|
236
146
|
}
|
|
237
147
|
```
|
|
238
148
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
Each `forRoot` call registers its adapter under
|
|
247
|
-
`typeorm:${dataSource}` in the core `AdapterRegistry`.
|
|
248
|
-
`TransactionManager` routes based on `options.dataSource`.
|
|
149
|
+
A repository bound to dataSource A, used inside a
|
|
150
|
+
`@Transactional({ dataSource: 'B' })` method, autocommits: its patched
|
|
151
|
+
manager looks for an active transaction on A, finds none, and falls back
|
|
152
|
+
to its original manager. **Distributed transactions across dataSources
|
|
153
|
+
are not supported** — that is deliberate, and cross-dataSource atomicity
|
|
154
|
+
is what the outbox is for.
|
|
249
155
|
|
|
250
156
|
## Async configuration
|
|
251
157
|
|
|
252
158
|
```ts
|
|
253
|
-
|
|
254
|
-
imports: [
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
entities: [User],
|
|
262
|
-
}),
|
|
263
|
-
}),
|
|
264
|
-
|
|
265
|
-
TransactionalModule.forRoot({ isGlobal: true }),
|
|
266
|
-
TypeOrmTransactionalModule.forRootAsync({
|
|
267
|
-
imports: [ConfigModule],
|
|
268
|
-
inject: [ConfigService],
|
|
269
|
-
useFactory: (cfg: ConfigService) => ({
|
|
270
|
-
dataSource: cfg.get('DATA_SOURCE_NAME', 'default'),
|
|
271
|
-
isDefault: true,
|
|
272
|
-
}),
|
|
273
|
-
}),
|
|
274
|
-
],
|
|
275
|
-
})
|
|
276
|
-
export class AppModule {}
|
|
159
|
+
TypeOrmTransactionalModule.forRootAsync({
|
|
160
|
+
imports: [ConfigModule],
|
|
161
|
+
inject: [ConfigService],
|
|
162
|
+
useFactory: (cfg: ConfigService) => ({
|
|
163
|
+
dataSource: cfg.get('DATA_SOURCE_NAME', 'default'),
|
|
164
|
+
isDefault: true,
|
|
165
|
+
}),
|
|
166
|
+
});
|
|
277
167
|
```
|
|
278
168
|
|
|
279
|
-
`
|
|
280
|
-
|
|
281
|
-
are
|
|
282
|
-
|
|
283
|
-
|
|
169
|
+
Registration is deferred to `OnModuleInit` so the dataSource resolves
|
|
170
|
+
correctly even when paired with `TypeOrmModule.forRootAsync`. Per-dataSource
|
|
171
|
+
adapter tokens are not registered on this path, because NestJS needs
|
|
172
|
+
provider tokens at module-definition time; use sync `forRoot({ dataSource })`
|
|
173
|
+
if you inject adapters by token.
|
|
284
174
|
|
|
285
|
-
|
|
286
|
-
the DataSource resolves correctly even when paired with
|
|
287
|
-
`TypeOrmModule.forRootAsync` (whose own DataSource provider is
|
|
288
|
-
async). Pinned by
|
|
289
|
-
[`packages/typeorm/test/integration/forrootasync.integration.spec.ts`](test/integration/forrootasync.integration.spec.ts).
|
|
175
|
+
## Compatibility
|
|
290
176
|
|
|
291
|
-
|
|
177
|
+
| Peer | Supported range |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| Node.js | `>=22.13.0` |
|
|
180
|
+
| `typeorm` | `^0.3.0 \|\| ^1.0.0` |
|
|
181
|
+
| `@nestjs/typeorm` | `^10.0.0 \|\| ^11.0.0 \|\| ^12.0.0` |
|
|
182
|
+
| `@nestjs/common` / `@nestjs/core` | `^10.0.0 \|\| ^11.0.0 \|\| ^12.0.0` |
|
|
183
|
+
| `reflect-metadata` | `^0.1.13 \|\| ^0.2.0` |
|
|
184
|
+
| `rxjs` | `^7.0.0` |
|
|
292
185
|
|
|
293
|
-
|
|
186
|
+
Both stable TypeORM lines are supported. CI runs the full unit and
|
|
187
|
+
integration matrix — including savepoints and isolation against a real
|
|
188
|
+
Postgres — at three points of that range: `0.3.31`, `1.0.0` and `1.1.0`.
|
|
294
189
|
|
|
295
|
-
|
|
296
|
-
`sqljs` driver:
|
|
190
|
+
## Testing
|
|
297
191
|
|
|
298
|
-
|
|
299
|
-
import { DataSource } from 'typeorm';
|
|
300
|
-
import { TypeOrmTransactionAdapter } from '@nestjs-transactional/typeorm';
|
|
192
|
+
For unit tests, TypeORM's in-memory `sqljs` driver is enough:
|
|
301
193
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
synchronize: true,
|
|
305
|
-
entities: [YourEntity],
|
|
306
|
-
});
|
|
194
|
+
```ts
|
|
195
|
+
const ds = new DataSource({ type: 'sqljs', synchronize: true, entities: [Order] });
|
|
307
196
|
await ds.initialize();
|
|
308
|
-
|
|
309
197
|
const adapter = new TypeOrmTransactionAdapter(ds, 'default');
|
|
310
198
|
```
|
|
311
199
|
|
|
312
|
-
|
|
200
|
+
Note that `readOnly` is not enforced on `sqljs`, so a read-only
|
|
201
|
+
violation your tests miss can still surface on Postgres. For tests that
|
|
202
|
+
need real dialect behaviour, run Postgres through
|
|
203
|
+
[testcontainers](https://node.testcontainers.org/); the
|
|
204
|
+
[`testing-patterns`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/testing-patterns)
|
|
205
|
+
example shows both layers.
|
|
313
206
|
|
|
314
|
-
|
|
207
|
+
## Documentation
|
|
315
208
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
ctx = await startPostgresContainer({ entities: [User], synchronize: true });
|
|
326
|
-
});
|
|
327
|
-
afterAll(async () => {
|
|
328
|
-
await stopPostgresContainer(ctx);
|
|
329
|
-
});
|
|
209
|
+
- [Getting started and full docs](https://github.com/igorgolovanov/nestjs-transactional#readme)
|
|
210
|
+
- [`readOnly` and `timeout` semantics (DD-027)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/dd/027-readonly-and-timeout-semantics.md)
|
|
211
|
+
- [Multi-adapter architecture (ADR-018)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/018-multi-adapter-architecture.md)
|
|
212
|
+
- [Known limitations](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/known-limitations.md)
|
|
213
|
+
- Runnable examples:
|
|
214
|
+
[`basic-transactional`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/basic-transactional),
|
|
215
|
+
[`multi-datasource-basic`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/multi-datasource-basic),
|
|
216
|
+
[`read-write-separation`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/read-write-separation),
|
|
217
|
+
[`e-commerce-orders`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/e-commerce-orders)
|
|
330
218
|
|
|
331
|
-
|
|
332
|
-
const secondary = await createAdditionalDatabase(ctx, 'billing_test', {
|
|
333
|
-
entities: [User],
|
|
334
|
-
synchronize: true,
|
|
335
|
-
});
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
Run integration tests:
|
|
339
|
-
|
|
340
|
-
```bash
|
|
341
|
-
pnpm --filter @nestjs-transactional/typeorm test:integration
|
|
342
|
-
```
|
|
219
|
+
## License
|
|
343
220
|
|
|
344
|
-
|
|
345
|
-
against a persistent instance). Testcontainers manages its own
|
|
346
|
-
containers and does not require compose.
|
|
347
|
-
|
|
348
|
-
## Savepoints and NESTED propagation
|
|
349
|
-
|
|
350
|
-
When a method uses `PropagationMode.NESTED` from inside an existing
|
|
351
|
-
TypeORM transaction, the adapter issues a `SAVEPOINT sp_<uuid-30>`
|
|
352
|
-
statement. Rollback rolls back to the savepoint; the outer
|
|
353
|
-
transaction continues. Savepoint names are at most 33 characters
|
|
354
|
-
long — valid on Postgres, MySQL, MariaDB, SQLite, and Oracle's
|
|
355
|
-
identifier limit.
|
|
356
|
-
|
|
357
|
-
## Worked examples
|
|
358
|
-
|
|
359
|
-
- [`basic-transactional`](../../examples/basic-transactional) —
|
|
360
|
-
`@Transactional()` on `@InjectRepository`, single DataSource.
|
|
361
|
-
Transparent repository showcase.
|
|
362
|
-
- [`multi-datasource-basic`](../../examples/multi-datasource-basic)
|
|
363
|
-
— two DataSources with `@Transactional({ dataSource })`, no
|
|
364
|
-
outbox.
|
|
365
|
-
- [`read-write-separation`](../../examples/read-write-separation) —
|
|
366
|
-
master + replica, only the master gets
|
|
367
|
-
`TypeOrmTransactionalModule`.
|
|
368
|
-
- [`async-config-from-environment`](../../examples/async-config-from-environment)
|
|
369
|
-
— `TypeOrmTransactionalModule.forRootAsync` end-to-end with
|
|
370
|
-
`ConfigService` + Joi profiles.
|
|
371
|
-
- [`e-commerce-orders`](../../examples/e-commerce-orders) —
|
|
372
|
-
three-DataSource flagship combining transparent repositories +
|
|
373
|
-
per-DS outbox + CQRS + REST + Kafka externalization.
|
|
374
|
-
|
|
375
|
-
Full catalogue: [examples/README.md](../../examples/README.md).
|
|
376
|
-
|
|
377
|
-
## Status
|
|
378
|
-
|
|
379
|
-
Alpha. Public API may change between 0.x releases.
|
|
221
|
+
MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type TransactionAdapter, type TransactionOptions } from '@nestjs-transactional/core';
|
|
2
2
|
import type { DataSource } from 'typeorm';
|
|
3
|
-
import type { TypeOrmTransactionHandle } from '../types/typeorm-transaction-handle';
|
|
3
|
+
import type { TypeOrmTransactionHandle } from '../types/typeorm-transaction-handle.js';
|
|
4
4
|
/**
|
|
5
5
|
* TypeORM implementation of {@link TransactionAdapter}. Delegates begin /
|
|
6
6
|
* commit / rollback to `DataSource.transaction` and emits raw `SAVEPOINT`
|
|
@@ -10,6 +10,12 @@ import type { TypeOrmTransactionHandle } from '../types/typeorm-transaction-hand
|
|
|
10
10
|
* Only `isolation` from {@link TransactionOptions} is forwarded today.
|
|
11
11
|
* `readOnly` and `timeout` are accepted for forward compatibility but do
|
|
12
12
|
* not yet map to per-dialect statements.
|
|
13
|
+
*
|
|
14
|
+
* `PropagationMode.NESTED` needs savepoints, so `runInSavepoint` first
|
|
15
|
+
* checks the driver's own capability flag and rejects with
|
|
16
|
+
* `IllegalTransactionStateError` on drivers that cannot do them (SQL
|
|
17
|
+
* Server, SAP HANA, MongoDB, Spanner, Cordova) instead of emitting SQL
|
|
18
|
+
* they will refuse.
|
|
13
19
|
*/
|
|
14
20
|
export declare class TypeOrmTransactionAdapter implements TransactionAdapter<TypeOrmTransactionHandle> {
|
|
15
21
|
private readonly dataSource;
|
|
@@ -35,5 +41,23 @@ export declare class TypeOrmTransactionAdapter implements TransactionAdapter<Typ
|
|
|
35
41
|
get dataSourceName(): string;
|
|
36
42
|
runInTransaction<T>(options: TransactionOptions, fn: (handle: TypeOrmTransactionHandle) => Promise<T>): Promise<T>;
|
|
37
43
|
runInSavepoint<T>(parent: TypeOrmTransactionHandle, fn: (handle: TypeOrmTransactionHandle) => Promise<T>): Promise<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Fail fast when the configured driver cannot do savepoints, rather
|
|
46
|
+
* than emitting `SAVEPOINT` SQL it will reject with an opaque
|
|
47
|
+
* driver-level error that says nothing about `NESTED` propagation.
|
|
48
|
+
*
|
|
49
|
+
* TypeORM reports the capability itself — `driver.transactionSupport`
|
|
50
|
+
* is `'nested'` for the savepoint-capable drivers (Postgres, MySQL,
|
|
51
|
+
* Oracle, SQLite, CockroachDB, ...), `'simple'` for SQL Server and
|
|
52
|
+
* SAP HANA, and `'none'` for MongoDB, Spanner and Cordova. Reading the
|
|
53
|
+
* flag beats maintaining our own dialect allowlist, which would rot
|
|
54
|
+
* with every driver TypeORM adds.
|
|
55
|
+
*
|
|
56
|
+
* Deliberately permissive when the flag is missing: a TypeORM version
|
|
57
|
+
* that renames or drops it must not turn every `NESTED` call into a
|
|
58
|
+
* hard failure, since absence of the signal is not evidence of absent
|
|
59
|
+
* support.
|
|
60
|
+
*/
|
|
61
|
+
private assertSavepointsSupported;
|
|
38
62
|
}
|
|
39
63
|
//# sourceMappingURL=typeorm.adapter.d.ts.map
|