@nestjs-transactional/typeorm 1.0.0-alpha.3 → 1.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 +138 -310
- package/dist/adapter/typeorm.adapter.d.ts +25 -1
- package/dist/adapter/typeorm.adapter.js +77 -0
- package/dist/adapter/typeorm.adapter.js.map +1 -1
- package/dist/module/typeorm-transactional.module.d.ts +3 -3
- package/dist/module/typeorm-transactional.module.js +2 -2
- package/dist/module/typeorm-transactional.module.js.map +1 -1
- package/dist/patching/entity-manager-patches.d.ts +2 -2
- package/dist/patching/entity-manager-patches.js +2 -2
- package/dist/patching/index.d.ts +2 -2
- package/dist/patching/index.js +1 -1
- package/dist/patching/index.js.map +1 -1
- package/dist/patching/symbols.d.ts +1 -1
- package/dist/patching/symbols.js +1 -1
- package/package.json +13 -7
package/README.md
CHANGED
|
@@ -1,76 +1,41 @@
|
|
|
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
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
- `TypeOrmTransactionAdapter` — implements the core
|
|
11
|
-
`TransactionAdapter` SPI over TypeORM's `DataSource`. Handles
|
|
12
|
-
BEGIN / COMMIT / ROLLBACK via `DataSource.transaction(...)` and
|
|
13
|
-
issues raw `SAVEPOINT` / `ROLLBACK TO SAVEPOINT` /
|
|
14
|
-
`RELEASE SAVEPOINT` SQL for nested transactions.
|
|
15
|
-
- **Transparent transactional repositories** —
|
|
16
|
-
`@InjectRepository(Entity)` instances,
|
|
17
|
-
`@InjectEntityManager() em.getRepository(E)`,
|
|
18
|
-
`@InjectDataSource() ds.manager.save(...)`, and
|
|
19
|
-
`ds.getRepository(E).save(...)` automatically dispatch through the
|
|
20
|
-
active `@Transactional()` scope's `EntityManager`. No
|
|
21
|
-
`getCurrentEntityManager()` boilerplate. Custom repositories via
|
|
22
|
-
`Repository.extend(...)` and `TreeRepository` work transparently.
|
|
23
|
-
See [Transparent transactional behaviour](#transparent-transactional-behaviour)
|
|
24
|
-
below.
|
|
25
|
-
- `getCurrentEntityManager(dataSource?, fallback?)` — escape-hatch
|
|
26
|
-
helper that returns the transaction-aware `EntityManager` from the
|
|
27
|
-
current async context (or falls back to `dataSource.manager`
|
|
28
|
-
outside a transaction). Mostly needed for the documented
|
|
29
|
-
limitations below; standard injection paths cover everything else.
|
|
30
|
-
- `isInTransaction(dataSource?)` — predicate for the current
|
|
31
|
-
context.
|
|
32
|
-
- `TypeOrmTransactionalModule.forRoot({ dataSource?, isDefault? })` —
|
|
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).
|
|
48
8
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
}
|
|
51
27
|
```
|
|
52
28
|
|
|
53
|
-
|
|
29
|
+
No `getCurrentEntityManager()`, no passing an `EntityManager` down
|
|
30
|
+
through service layers, no separate "transactional" repository type.
|
|
54
31
|
|
|
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.
|
|
32
|
+
## Install
|
|
70
33
|
|
|
71
|
-
|
|
34
|
+
```bash
|
|
35
|
+
pnpm add @nestjs-transactional/typeorm @nestjs-transactional/core typeorm @nestjs/typeorm reflect-metadata
|
|
36
|
+
```
|
|
72
37
|
|
|
73
|
-
|
|
38
|
+
## Quick start
|
|
74
39
|
|
|
75
40
|
```ts
|
|
76
41
|
import { Module } from '@nestjs/common';
|
|
@@ -80,13 +45,8 @@ import { TypeOrmTransactionalModule } from '@nestjs-transactional/typeorm';
|
|
|
80
45
|
|
|
81
46
|
@Module({
|
|
82
47
|
imports: [
|
|
83
|
-
TypeOrmModule.forRoot({
|
|
84
|
-
|
|
85
|
-
url: process.env.DATABASE_URL,
|
|
86
|
-
entities: [User],
|
|
87
|
-
synchronize: false,
|
|
88
|
-
}),
|
|
89
|
-
TypeOrmModule.forFeature([User]),
|
|
48
|
+
TypeOrmModule.forRoot({ type: 'postgres', entities: [Order] }),
|
|
49
|
+
TypeOrmModule.forFeature([Order]),
|
|
90
50
|
|
|
91
51
|
TransactionalModule.forRoot({ isGlobal: true }),
|
|
92
52
|
TypeOrmTransactionalModule.forRoot(),
|
|
@@ -95,285 +55,153 @@ import { TypeOrmTransactionalModule } from '@nestjs-transactional/typeorm';
|
|
|
95
55
|
export class AppModule {}
|
|
96
56
|
```
|
|
97
57
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`
|
|
102
|
-
|
|
103
|
-
it globally, so `TypeOrmTransactionalModule.forRoot` finds it
|
|
104
|
-
automatically.
|
|
58
|
+
`TransactionalModule.forRoot({ isGlobal: true })` has to be there, with
|
|
59
|
+
`isGlobal` — that is how this module sees the core registry from its own
|
|
60
|
+
DI scope. The `DataSource` is found through `@nestjs/typeorm`'s
|
|
61
|
+
`getDataSourceToken(name)`, the same convention
|
|
62
|
+
`@InjectRepository(E, dataSource)` uses, so nothing else needs wiring.
|
|
105
63
|
|
|
106
|
-
##
|
|
64
|
+
## What becomes transparent
|
|
107
65
|
|
|
108
|
-
|
|
109
|
-
standard `@nestjs/typeorm` injection paths automatically dispatches
|
|
110
|
-
through the active `@Transactional()` scope. No
|
|
111
|
-
`getCurrentEntityManager()` calls in user code:
|
|
66
|
+
These all dispatch through the active transaction:
|
|
112
67
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
import { Order } from './order.entity';
|
|
68
|
+
- `@InjectRepository(Entity)` — the common case.
|
|
69
|
+
- `@InjectEntityManager() em.getRepository(E)`.
|
|
70
|
+
- `@InjectDataSource() ds.getRepository(E)` and `ds.manager`.
|
|
71
|
+
- Custom repositories built with `Repository.extend(...)`.
|
|
72
|
+
- `TreeRepository` and `MongoRepository`, which inherit from `Repository`.
|
|
119
73
|
|
|
120
|
-
|
|
121
|
-
export class OrderService {
|
|
122
|
-
constructor(
|
|
123
|
-
@InjectRepository(Order)
|
|
124
|
-
private readonly orderRepo: Repository<Order>,
|
|
125
|
-
) {}
|
|
74
|
+
Two patterns are **not** covered, and need an escape hatch:
|
|
126
75
|
|
|
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:
|
|
76
|
+
1. **`em.save(Entity, ...)` called directly** on an injected
|
|
77
|
+
`EntityManager`. The patch covers `em.getRepository(E).save(...)`,
|
|
78
|
+
not the manager's own data methods — patching all ~14 of them would
|
|
79
|
+
require per-method recursion guards, which was judged not worth the
|
|
80
|
+
surface area.
|
|
81
|
+
2. **`BaseEntity` static methods** (`User.save(...)`).
|
|
82
|
+
`BaseEntity.useDataSource(...)` captures a `DataSource` reference
|
|
83
|
+
that bypasses the patch. The library `typeorm-transactional` has the
|
|
84
|
+
same limitation.
|
|
156
85
|
|
|
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:
|
|
86
|
+
For both, either use a repository or reach for the escape hatch:
|
|
181
87
|
|
|
182
88
|
```ts
|
|
183
89
|
import { getCurrentEntityManager } from '@nestjs-transactional/typeorm';
|
|
184
90
|
|
|
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
|
-
}
|
|
91
|
+
@Transactional()
|
|
92
|
+
async runRawSql() {
|
|
93
|
+
// Pass the DataSource as fallback so this also works outside a
|
|
94
|
+
// transaction, where it returns ds.manager.
|
|
95
|
+
const em = getCurrentEntityManager('default', this.ds);
|
|
96
|
+
await em.query('UPDATE accounts SET balance = balance - $1', [100]);
|
|
200
97
|
}
|
|
201
98
|
```
|
|
202
99
|
|
|
203
|
-
##
|
|
100
|
+
## Dialect-dependent behaviour
|
|
204
101
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
imports: [
|
|
208
|
-
TypeOrmModule.forRoot({ name: 'default', /* ... */ }),
|
|
209
|
-
TypeOrmModule.forRoot({ name: 'billing', /* ... */ }),
|
|
102
|
+
Two options behave differently per database, and both fail loudly or
|
|
103
|
+
harmlessly rather than surprisingly:
|
|
210
104
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
105
|
+
- **`readOnly`** is enforced on `postgres`, `cockroachdb` and
|
|
106
|
+
`aurora-postgres`, where the adapter issues `SET TRANSACTION READ ONLY`
|
|
107
|
+
as the transaction's first statement and the database refuses a write.
|
|
108
|
+
On other dialects it is a silent no-op. MySQL is not merely
|
|
109
|
+
unimplemented but unimplementable: `SET TRANSACTION` there applies to
|
|
110
|
+
the *next* transaction and errors inside a started one. Worth knowing
|
|
111
|
+
if you develop on SQLite and deploy to Postgres — the constraint
|
|
112
|
+
appears in production for the first time.
|
|
113
|
+
([DD-027](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/dd/027-readonly-and-timeout-semantics.md))
|
|
114
|
+
- **`PropagationMode.NESTED`** needs savepoints. The adapter checks
|
|
115
|
+
TypeORM's own `driver.transactionSupport` flag and throws
|
|
116
|
+
`IllegalTransactionStateError` naming the driver and the alternatives,
|
|
117
|
+
instead of running your "nested" transaction as part of the outer one.
|
|
118
|
+
|
|
119
|
+
## Multiple dataSources
|
|
218
120
|
|
|
219
|
-
|
|
121
|
+
One `forRoot` call per dataSource:
|
|
220
122
|
|
|
221
123
|
```ts
|
|
222
|
-
|
|
124
|
+
TypeOrmTransactionalModule.forRoot({ isDefault: true }), // 'default'
|
|
125
|
+
TypeOrmTransactionalModule.forRoot({ dataSource: 'billing' }), // 'billing'
|
|
126
|
+
```
|
|
223
127
|
|
|
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
|
-
}
|
|
128
|
+
```ts
|
|
129
|
+
@Transactional({ dataSource: 'billing' })
|
|
130
|
+
async chargeCard() {
|
|
131
|
+
return this.invoiceRepo.save(/* ... */); // repo bound to 'billing'
|
|
236
132
|
}
|
|
237
133
|
```
|
|
238
134
|
|
|
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`.
|
|
135
|
+
A repository bound to dataSource A, used inside a
|
|
136
|
+
`@Transactional({ dataSource: 'B' })` method, autocommits: its patched
|
|
137
|
+
manager looks for an active transaction on A, finds none, and falls back
|
|
138
|
+
to its original manager. **Distributed transactions across dataSources
|
|
139
|
+
are not supported** — that is deliberate, and cross-dataSource atomicity
|
|
140
|
+
is what the outbox is for.
|
|
249
141
|
|
|
250
142
|
## Async configuration
|
|
251
143
|
|
|
252
144
|
```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 {}
|
|
145
|
+
TypeOrmTransactionalModule.forRootAsync({
|
|
146
|
+
imports: [ConfigModule],
|
|
147
|
+
inject: [ConfigService],
|
|
148
|
+
useFactory: (cfg: ConfigService) => ({
|
|
149
|
+
dataSource: cfg.get('DATA_SOURCE_NAME', 'default'),
|
|
150
|
+
isDefault: true,
|
|
151
|
+
}),
|
|
152
|
+
});
|
|
277
153
|
```
|
|
278
154
|
|
|
279
|
-
`
|
|
280
|
-
|
|
281
|
-
are
|
|
282
|
-
|
|
283
|
-
|
|
155
|
+
Registration is deferred to `OnModuleInit` so the dataSource resolves
|
|
156
|
+
correctly even when paired with `TypeOrmModule.forRootAsync`. Per-dataSource
|
|
157
|
+
adapter tokens are not registered on this path, because NestJS needs
|
|
158
|
+
provider tokens at module-definition time; use sync `forRoot({ dataSource })`
|
|
159
|
+
if you inject adapters by token.
|
|
284
160
|
|
|
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).
|
|
161
|
+
## Compatibility
|
|
290
162
|
|
|
291
|
-
|
|
163
|
+
| Peer | Supported range |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| Node.js | `>=22.13.0` |
|
|
166
|
+
| `typeorm` | `^0.3.0 \|\| ^1.0.0` |
|
|
167
|
+
| `@nestjs/typeorm` | `^10.0.0 \|\| ^11.0.0` |
|
|
168
|
+
| `@nestjs/common` / `@nestjs/core` | `^10.0.0 \|\| ^11.0.0` |
|
|
169
|
+
| `reflect-metadata` | `^0.1.13 \|\| ^0.2.0` |
|
|
170
|
+
| `rxjs` | `^7.0.0` |
|
|
292
171
|
|
|
293
|
-
|
|
172
|
+
Both stable TypeORM lines are supported. CI runs the full unit and
|
|
173
|
+
integration matrix — including savepoints and isolation against a real
|
|
174
|
+
Postgres — at three points of that range: `0.3.31`, `1.0.0` and `1.1.0`.
|
|
294
175
|
|
|
295
|
-
|
|
296
|
-
`sqljs` driver:
|
|
176
|
+
## Testing
|
|
297
177
|
|
|
298
|
-
|
|
299
|
-
import { DataSource } from 'typeorm';
|
|
300
|
-
import { TypeOrmTransactionAdapter } from '@nestjs-transactional/typeorm';
|
|
178
|
+
For unit tests, TypeORM's in-memory `sqljs` driver is enough:
|
|
301
179
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
synchronize: true,
|
|
305
|
-
entities: [YourEntity],
|
|
306
|
-
});
|
|
180
|
+
```ts
|
|
181
|
+
const ds = new DataSource({ type: 'sqljs', synchronize: true, entities: [Order] });
|
|
307
182
|
await ds.initialize();
|
|
308
|
-
|
|
309
183
|
const adapter = new TypeOrmTransactionAdapter(ds, 'default');
|
|
310
184
|
```
|
|
311
185
|
|
|
312
|
-
|
|
186
|
+
Note that `readOnly` is not enforced on `sqljs`, so a read-only
|
|
187
|
+
violation your tests miss can still surface on Postgres. For tests that
|
|
188
|
+
need real dialect behaviour, run Postgres through
|
|
189
|
+
[testcontainers](https://node.testcontainers.org/); the
|
|
190
|
+
[`testing-patterns`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/testing-patterns)
|
|
191
|
+
example shows both layers.
|
|
313
192
|
|
|
314
|
-
|
|
193
|
+
## Documentation
|
|
315
194
|
|
|
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
|
-
});
|
|
195
|
+
- [Getting started and full docs](https://github.com/igorgolovanov/nestjs-transactional#readme)
|
|
196
|
+
- [`readOnly` and `timeout` semantics (DD-027)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/dd/027-readonly-and-timeout-semantics.md)
|
|
197
|
+
- [Multi-adapter architecture (ADR-018)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/018-multi-adapter-architecture.md)
|
|
198
|
+
- [Known limitations](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/known-limitations.md)
|
|
199
|
+
- Runnable examples:
|
|
200
|
+
[`basic-transactional`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/basic-transactional),
|
|
201
|
+
[`multi-datasource-basic`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/multi-datasource-basic),
|
|
202
|
+
[`read-write-separation`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/read-write-separation),
|
|
203
|
+
[`e-commerce-orders`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/e-commerce-orders)
|
|
330
204
|
|
|
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
|
-
```
|
|
205
|
+
## License
|
|
343
206
|
|
|
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.
|
|
207
|
+
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type TransactionAdapter, type TransactionOptions } from '@nestjs-transactional/core';
|
|
2
2
|
import type { DataSource } from 'typeorm';
|
|
3
3
|
import type { TypeOrmTransactionHandle } from '../types/typeorm-transaction-handle';
|
|
4
4
|
/**
|
|
@@ -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
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TypeOrmTransactionAdapter = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const core_1 = require("@nestjs-transactional/core");
|
|
5
6
|
/**
|
|
6
7
|
* TypeORM implementation of {@link TransactionAdapter}. Delegates begin /
|
|
7
8
|
* commit / rollback to `DataSource.transaction` and emits raw `SAVEPOINT`
|
|
@@ -11,6 +12,12 @@ const node_crypto_1 = require("node:crypto");
|
|
|
11
12
|
* Only `isolation` from {@link TransactionOptions} is forwarded today.
|
|
12
13
|
* `readOnly` and `timeout` are accepted for forward compatibility but do
|
|
13
14
|
* not yet map to per-dialect statements.
|
|
15
|
+
*
|
|
16
|
+
* `PropagationMode.NESTED` needs savepoints, so `runInSavepoint` first
|
|
17
|
+
* checks the driver's own capability flag and rejects with
|
|
18
|
+
* `IllegalTransactionStateError` on drivers that cannot do them (SQL
|
|
19
|
+
* Server, SAP HANA, MongoDB, Spanner, Cordova) instead of emitting SQL
|
|
20
|
+
* they will refuse.
|
|
14
21
|
*/
|
|
15
22
|
class TypeOrmTransactionAdapter {
|
|
16
23
|
dataSource;
|
|
@@ -37,6 +44,9 @@ class TypeOrmTransactionAdapter {
|
|
|
37
44
|
async runInTransaction(options, fn) {
|
|
38
45
|
const isolation = mapIsolation(options.isolation);
|
|
39
46
|
const runner = async (entityManager) => {
|
|
47
|
+
if (options.readOnly === true) {
|
|
48
|
+
await applyReadOnly(entityManager, this.dataSource.options.type);
|
|
49
|
+
}
|
|
40
50
|
const handle = {
|
|
41
51
|
id: (0, node_crypto_1.randomUUID)(),
|
|
42
52
|
adapterName: this.name,
|
|
@@ -50,6 +60,7 @@ class TypeOrmTransactionAdapter {
|
|
|
50
60
|
return this.dataSource.transaction(runner);
|
|
51
61
|
}
|
|
52
62
|
async runInSavepoint(parent, fn) {
|
|
63
|
+
this.assertSavepointsSupported();
|
|
53
64
|
const savepointName = `sp_${(0, node_crypto_1.randomUUID)().replace(/-/g, '_').substring(0, 30)}`;
|
|
54
65
|
await parent.entityManager.query(`SAVEPOINT ${savepointName}`);
|
|
55
66
|
try {
|
|
@@ -62,8 +73,74 @@ class TypeOrmTransactionAdapter {
|
|
|
62
73
|
throw err;
|
|
63
74
|
}
|
|
64
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Fail fast when the configured driver cannot do savepoints, rather
|
|
78
|
+
* than emitting `SAVEPOINT` SQL it will reject with an opaque
|
|
79
|
+
* driver-level error that says nothing about `NESTED` propagation.
|
|
80
|
+
*
|
|
81
|
+
* TypeORM reports the capability itself — `driver.transactionSupport`
|
|
82
|
+
* is `'nested'` for the savepoint-capable drivers (Postgres, MySQL,
|
|
83
|
+
* Oracle, SQLite, CockroachDB, ...), `'simple'` for SQL Server and
|
|
84
|
+
* SAP HANA, and `'none'` for MongoDB, Spanner and Cordova. Reading the
|
|
85
|
+
* flag beats maintaining our own dialect allowlist, which would rot
|
|
86
|
+
* with every driver TypeORM adds.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately permissive when the flag is missing: a TypeORM version
|
|
89
|
+
* that renames or drops it must not turn every `NESTED` call into a
|
|
90
|
+
* hard failure, since absence of the signal is not evidence of absent
|
|
91
|
+
* support.
|
|
92
|
+
*/
|
|
93
|
+
assertSavepointsSupported() {
|
|
94
|
+
const support = this.dataSource.driver
|
|
95
|
+
?.transactionSupport;
|
|
96
|
+
if (support !== undefined && support !== 'nested') {
|
|
97
|
+
throw new core_1.IllegalTransactionStateError(`PropagationMode.NESTED needs savepoints, which the '${this.dataSource.options.type}' ` +
|
|
98
|
+
`driver does not support (TypeORM reports transactionSupport: '${support}'). ` +
|
|
99
|
+
"Use PropagationMode.REQUIRED to join the caller's transaction, or " +
|
|
100
|
+
'REQUIRES_NEW for an independent one.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
65
103
|
}
|
|
66
104
|
exports.TypeOrmTransactionAdapter = TypeOrmTransactionAdapter;
|
|
105
|
+
/**
|
|
106
|
+
* Dialects where `SET TRANSACTION READ ONLY` is valid after `BEGIN`,
|
|
107
|
+
* provided it precedes the transaction's first query.
|
|
108
|
+
*
|
|
109
|
+
* An explicit allowlist, unlike the savepoint check above: TypeORM
|
|
110
|
+
* publishes a capability flag for nested transactions
|
|
111
|
+
* (`driver.transactionSupport`) but none for transaction access mode, so
|
|
112
|
+
* there is nothing to read. **Review this list when a driver is added.**
|
|
113
|
+
*
|
|
114
|
+
* MySQL and MariaDB are absent on purpose rather than by omission —
|
|
115
|
+
* `SET TRANSACTION` there applies to the *next* transaction and raises
|
|
116
|
+
* `ERROR 1568` inside a started one, so read-only would have to be set
|
|
117
|
+
* at `START TRANSACTION` time, which TypeORM does not expose. See
|
|
118
|
+
* DD-027.
|
|
119
|
+
*/
|
|
120
|
+
const READ_ONLY_DIALECTS = new Set([
|
|
121
|
+
'postgres',
|
|
122
|
+
'cockroachdb',
|
|
123
|
+
'aurora-postgres',
|
|
124
|
+
]);
|
|
125
|
+
/**
|
|
126
|
+
* Ask the database to reject writes for the remainder of this
|
|
127
|
+
* transaction, on the dialects that can do it.
|
|
128
|
+
*
|
|
129
|
+
* A silent no-op elsewhere, deliberately: `CqrsTransactionalModule`
|
|
130
|
+
* defaults every query handler to `readOnly: true`, so throwing here
|
|
131
|
+
* would break consumers on MySQL or SQLite over an option they never
|
|
132
|
+
* set. `readOnly` is a hint in Spring too — honoured where possible
|
|
133
|
+
* (DD-027).
|
|
134
|
+
*
|
|
135
|
+
* Must run before any user statement; Postgres rejects the statement
|
|
136
|
+
* once the transaction has executed its first query.
|
|
137
|
+
*/
|
|
138
|
+
async function applyReadOnly(entityManager, dialect) {
|
|
139
|
+
if (!READ_ONLY_DIALECTS.has(dialect)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
await entityManager.query('SET TRANSACTION READ ONLY');
|
|
143
|
+
}
|
|
67
144
|
/**
|
|
68
145
|
* Map the core's underscore-style {@link IsolationLevel} to TypeORM's
|
|
69
146
|
* space-separated string. Returns `undefined` when no level is set so the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeorm.adapter.js","sourceRoot":"","sources":["../../src/adapter/typeorm.adapter.ts"],"names":[],"mappings":";;;AAAA,6CAAyC;
|
|
1
|
+
{"version":3,"file":"typeorm.adapter.js","sourceRoot":"","sources":["../../src/adapter/typeorm.adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { type DynamicModule, type InjectionToken, type ModuleMetadata } from '@n
|
|
|
2
2
|
/**
|
|
3
3
|
* Options accepted by {@link TypeOrmTransactionalModule.forRoot}.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Reshaped so this module now resolves the actual TypeORM
|
|
6
6
|
* `DataSource` via DI (using `getDataSourceToken` from
|
|
7
7
|
* `@nestjs/typeorm`) instead of taking it as a constructor argument.
|
|
8
8
|
* The new contract is "TypeORM is configured by `@nestjs/typeorm`'s
|
|
@@ -41,7 +41,7 @@ export interface TypeOrmTransactionalOptions {
|
|
|
41
41
|
* populates as a side effect.
|
|
42
42
|
*
|
|
43
43
|
* Mirrors the documented limitation on
|
|
44
|
-
* `TransactionalModule.forRootAsync
|
|
44
|
+
* `TransactionalModule.forRootAsync`.
|
|
45
45
|
*/
|
|
46
46
|
export interface TypeOrmTransactionalAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
47
47
|
readonly useFactory: (...args: never[]) => Promise<TypeOrmTransactionalOptions> | TypeOrmTransactionalOptions;
|
|
@@ -50,7 +50,7 @@ export interface TypeOrmTransactionalAsyncOptions extends Pick<ModuleMetadata, '
|
|
|
50
50
|
/**
|
|
51
51
|
* NestJS module that binds a TypeORM {@link DataSource} to the core
|
|
52
52
|
* {@link AdapterRegistry} as a transactional adapter AND activates
|
|
53
|
-
* the transparent transactional patching machinery
|
|
53
|
+
* the transparent transactional patching machinery.
|
|
54
54
|
* Once registered:
|
|
55
55
|
*
|
|
56
56
|
* - Every `Repository` reachable via `@InjectRepository`,
|
|
@@ -46,7 +46,7 @@ const ASYNC_OPTIONS_TOKEN = (id) => Symbol(`TYPEORM_TRANSACTIONAL_ASYNC_OPTIONS[
|
|
|
46
46
|
/**
|
|
47
47
|
* NestJS module that binds a TypeORM {@link DataSource} to the core
|
|
48
48
|
* {@link AdapterRegistry} as a transactional adapter AND activates
|
|
49
|
-
* the transparent transactional patching machinery
|
|
49
|
+
* the transparent transactional patching machinery.
|
|
50
50
|
* Once registered:
|
|
51
51
|
*
|
|
52
52
|
* - Every `Repository` reachable via `@InjectRepository`,
|
|
@@ -190,7 +190,7 @@ let TypeOrmTransactionalModule = class TypeOrmTransactionalModule {
|
|
|
190
190
|
// (or even `moduleRef.get`) cascaded into a hard-to-diagnose
|
|
191
191
|
// `Invalid value used in weak set` followed by
|
|
192
192
|
// `this.postgres.Pool is not a constructor` when paired with
|
|
193
|
-
// `TypeOrmModule.forRootAsync` (
|
|
193
|
+
// `TypeOrmModule.forRootAsync` (Convention #22).
|
|
194
194
|
//
|
|
195
195
|
// The robust pattern is `OnModuleInit`: by the time the hook
|
|
196
196
|
// runs, every provider in the module tree has been instantiated
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeorm-transactional.module.js","sourceRoot":"","sources":["../../src/module/typeorm-transactional.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAWwB;AACxB,uCAAyC;AACzC,6CAAqD;AACrD,qDAIoC;AAGpC,gEAAuE;AACvE,0CAKqB;AAErB,kEAAkE;AAClE,oEAAoE;AACpE,uCAAuC;AACvC,EAAE;AACF,iEAAiE;AACjE,yDAAyD;AACzD,iEAAiE;AACjE,uEAAuE;AACvE,iEAAiE;AACjE,wDAAwD;AACxD,gEAAgE;AAChE,6DAA6D;AAC7D,+DAA+D;AAC/D,iBAAiB;AACjB,EAAE;AACF,iEAAiE;AACjE,+DAA+D;AAC/D,oEAAoE;AACpE,+DAA+D;AAC/D,oEAAoE;AACpE,kEAAkE;AAClE,IAAA,0BAAe,GAAE,CAAC;AAuDlB,MAAM,mBAAmB,GAAG,CAAC,EAAU,EAAU,EAAE,CACjD,MAAM,CAAC,uCAAuC,EAAE,GAAG,CAAC,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEI,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;;IACrC;;;;;;OAMG;IACK,MAAM,CAAC,YAAY,GAAG,CAAC,CAAC;IAEhC;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAC,eAAe;QACpB,IAAA,kCAAuB,GAAE,CAAC;QAC1B,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;IACxB,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,OAAO,CAAC,UAAuC,EAAE;QACtD,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,IAAI,SAAS,CAAC;QACvD,MAAM,eAAe,GAAG,IAAA,4BAAkB,EAAC,cAAc,CAAC,CAAC;QAC3D,MAAM,YAAY,GAAG,IAAA,mCAA4B,EAAC,cAAc,CAAC,CAAC;QAElE,MAAM,eAAe,GAAoB;YACvC,OAAO,EAAE,YAAY;YACrB,UAAU,EAAE,CAAC,EAAc,EAAE,QAAyB,EAA6B,EAAE,CACnF,yBAAyB,CAAC;gBACxB,UAAU,EAAE,EAAE;gBACd,cAAc;gBACd,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK;gBACrC,QAAQ;aACT,CAAC;YACJ,MAAM,EAAE,CAAC,eAAe,EAAE,uBAAgB,CAAC;SAC5C,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,4BAA0B;YAClC,SAAS,EAAE,CAAC,eAAe,CAAC;YAC5B,OAAO,EAAE,CAAC,YAAY,CAAC;SACxB,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAyC;QAC3D,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAC;QAE3C,MAAM,oBAAoB,GAAoB;YAC5C,OAAO,EAAE,UAAU;YACnB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;SACzD,CAAC;QAEF,8DAA8D;QAC9D,gEAAgE;QAChE,+DAA+D;QAC/D,6DAA6D;QAC7D,sDAAsD;QACtD,6DAA6D;QAC7D,gEAAgE;QAChE,+DAA+D;QAC/D,6DAA6D;QAC7D,+CAA+C;QAC/C,6DAA6D;QAC7D,
|
|
1
|
+
{"version":3,"file":"typeorm-transactional.module.js","sourceRoot":"","sources":["../../src/module/typeorm-transactional.module.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* This wrap matters specifically for the
|
|
10
10
|
* `@InjectEntityManager() em.getRepository(Entity).save(...)` user
|
|
11
|
-
* pattern (
|
|
11
|
+
* pattern (coverage proof in the integration tests): the injected
|
|
12
12
|
* `EntityManager` is the DataSource's default (non-transactional)
|
|
13
13
|
* manager. Calling `em.getRepository(Entity)` would, without this
|
|
14
14
|
* wrap, return a Repository whose only `manager` reference is `em`
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* `@InjectEntityManager` + direct method call (`em.save(Entity, ...)`)
|
|
24
24
|
* is NOT covered by this patch — that is the documented limitation
|
|
25
|
-
|
|
25
|
+
*. Use `getCurrentEntityManager()` as the escape
|
|
26
26
|
* hatch.
|
|
27
27
|
*/
|
|
28
28
|
export declare function applyEntityManagerPatches(): void;
|
|
@@ -35,7 +35,7 @@ let originalGetRepository;
|
|
|
35
35
|
*
|
|
36
36
|
* This wrap matters specifically for the
|
|
37
37
|
* `@InjectEntityManager() em.getRepository(Entity).save(...)` user
|
|
38
|
-
* pattern (
|
|
38
|
+
* pattern (coverage proof in the integration tests): the injected
|
|
39
39
|
* `EntityManager` is the DataSource's default (non-transactional)
|
|
40
40
|
* manager. Calling `em.getRepository(Entity)` would, without this
|
|
41
41
|
* wrap, return a Repository whose only `manager` reference is `em`
|
|
@@ -49,7 +49,7 @@ let originalGetRepository;
|
|
|
49
49
|
*
|
|
50
50
|
* `@InjectEntityManager` + direct method call (`em.save(Entity, ...)`)
|
|
51
51
|
* is NOT covered by this patch — that is the documented limitation
|
|
52
|
-
|
|
52
|
+
*. Use `getCurrentEntityManager()` as the escape
|
|
53
53
|
* hatch.
|
|
54
54
|
*/
|
|
55
55
|
function applyEntityManagerPatches() {
|
package/dist/patching/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Transparent transactional repositories — patching
|
|
3
3
|
* machinery. Exported so the module layer can drive `applyAllPatches`
|
|
4
4
|
* in `forRoot`, and so unit tests can probe state directly. None of
|
|
5
5
|
* these symbols are intended for application code — public API stays
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @internal
|
|
9
9
|
*/
|
|
10
|
-
export { applyRepositoryPatches, areRepositoryPatchesApplied
|
|
10
|
+
export { applyRepositoryPatches, areRepositoryPatchesApplied } from './repository-patches';
|
|
11
11
|
export { applyEntityManagerPatches, areEntityManagerPatchesApplied, } from './entity-manager-patches';
|
|
12
12
|
export { patchDataSourceInstance } from './data-source-patches';
|
|
13
13
|
export { getActiveEntityManager, getManagedDataSourceName, isManaged, markAsManaged, resetManagedRegistry, } from './managed-registry';
|
package/dist/patching/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Transparent transactional repositories — patching
|
|
4
4
|
* machinery. Exported so the module layer can drive `applyAllPatches`
|
|
5
5
|
* in `forRoot`, and so unit tests can probe state directly. None of
|
|
6
6
|
* these symbols are intended for application code — public API stays
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/patching/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/patching/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAmCH,0CAGC;AAwBD,0DAEC;AA9DD,qEAAqE;AACrE,yDAA0D;AAC1D,6DAA8D;AAE9D,2DAA2F;AAAlF,4HAAA,sBAAsB,OAAA;AAAE,iIAAA,2BAA2B,OAAA;AAC5D,mEAGkC;AAFhC,mIAAA,yBAAyB,OAAA;AACzB,wIAAA,8BAA8B,OAAA;AAEhC,6DAAgE;AAAvD,8HAAA,uBAAuB,OAAA;AAChC,uDAM4B;AAL1B,0HAAA,sBAAsB,OAAA;AACtB,4HAAA,wBAAwB,OAAA;AACxB,6GAAA,SAAS,OAAA;AACT,iHAAA,aAAa,OAAA;AACb,wHAAA,oBAAoB,OAAA;AAEtB,qCAImB;AAHjB,mHAAA,wBAAwB,OAAA;AACxB,sHAAA,2BAA2B,OAAA;AAC3B,sHAAA,2BAA2B,OAAA;AAG7B;;;;;;;;;GASG;AACH,SAAgB,eAAe;IAC7B,IAAA,2CAAsB,GAAE,CAAC;IACzB,IAAA,kDAAyB,GAAE,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAgB,uBAAuB;IACrC,IAAA,uCAAoB,GAAE,CAAC;AACzB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hidden property keys used by the transparent transactional patching
|
|
3
|
-
* machinery
|
|
3
|
+
* machinery.
|
|
4
4
|
*
|
|
5
5
|
* `Symbol.for(...)` is used (not module-local `Symbol(...)`) so the same
|
|
6
6
|
* key resolves identically across realms and across multiple copies of
|
package/dist/patching/symbols.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Hidden property keys used by the transparent transactional patching
|
|
4
|
-
* machinery
|
|
4
|
+
* machinery.
|
|
5
5
|
*
|
|
6
6
|
* `Symbol.for(...)` is used (not module-local `Symbol(...)`) so the same
|
|
7
7
|
* key resolves identically across realms and across multiple copies of
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs-transactional/typeorm",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "TypeORM adapter for @nestjs-transactional/core — EntityManager propagation, savepoints, multi-datasource",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"./dist/module/typeorm-transactional.module.js"
|
|
9
|
+
],
|
|
6
10
|
"author": "Igor Golovanov",
|
|
7
11
|
"repository": {
|
|
8
12
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/igorgolovanov/nestjs-transactional.git",
|
|
13
|
+
"url": "git+https://github.com/igorgolovanov/nestjs-transactional.git",
|
|
10
14
|
"directory": "packages/typeorm"
|
|
11
15
|
},
|
|
12
16
|
"bugs": {
|
|
@@ -41,7 +45,6 @@
|
|
|
41
45
|
},
|
|
42
46
|
"publishConfig": {
|
|
43
47
|
"access": "public",
|
|
44
|
-
"tag": "alpha",
|
|
45
48
|
"provenance": true
|
|
46
49
|
},
|
|
47
50
|
"peerDependencies": {
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
52
55
|
"rxjs": "^7.0.0",
|
|
53
56
|
"typeorm": "^0.3.0 || ^1.0.0",
|
|
54
|
-
"@nestjs-transactional/core": "^1.0.0
|
|
57
|
+
"@nestjs-transactional/core": "^1.0.0"
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
57
60
|
"@nestjs/common": "^11.0.0",
|
|
@@ -64,8 +67,8 @@
|
|
|
64
67
|
"rxjs": "^7.8.1",
|
|
65
68
|
"sql.js": "^1.11.0",
|
|
66
69
|
"testcontainers": "^10.13.0",
|
|
67
|
-
"typeorm": "^
|
|
68
|
-
"@nestjs-transactional/core": "1.0.0
|
|
70
|
+
"typeorm": "^1.1.0",
|
|
71
|
+
"@nestjs-transactional/core": "1.0.0"
|
|
69
72
|
},
|
|
70
73
|
"scripts": {
|
|
71
74
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -75,6 +78,9 @@
|
|
|
75
78
|
"test:cov": "jest --coverage",
|
|
76
79
|
"test:integration": "jest --config jest.integration.config.js",
|
|
77
80
|
"type-check": "tsc --noEmit",
|
|
78
|
-
"lint": "eslint \"src/**/*.ts\""
|
|
81
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
82
|
+
"api:check": "api-extractor run",
|
|
83
|
+
"api:update": "api-extractor run --local",
|
|
84
|
+
"publish:check": "publint && attw --pack ."
|
|
79
85
|
}
|
|
80
86
|
}
|