@nestjs-transactional/core 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 +131 -178
- package/dist/bootstrap/transactional-methods.bootstrap.d.ts +2 -2
- package/dist/bootstrap/transactional-methods.bootstrap.js +19 -22
- package/dist/bootstrap/transactional-methods.bootstrap.js.map +1 -1
- package/dist/context/transaction-context-view.d.ts +1 -1
- package/dist/context/transaction-context-view.js +3 -7
- package/dist/context/transaction-context-view.js.map +1 -1
- package/dist/context/transaction.context.d.ts +3 -3
- package/dist/context/transaction.context.js +6 -10
- package/dist/context/transaction.context.js.map +1 -1
- package/dist/decorators/inject-decorators.js +6 -12
- package/dist/decorators/inject-decorators.js.map +1 -1
- package/dist/decorators/transactional.decorator.d.ts +6 -1
- package/dist/decorators/transactional.decorator.js +15 -17
- package/dist/decorators/transactional.decorator.js.map +1 -1
- package/dist/index.d.ts +18 -18
- package/dist/index.js +18 -34
- package/dist/index.js.map +1 -1
- package/dist/interceptor/transactional.interceptor.d.ts +1 -1
- package/dist/interceptor/transactional.interceptor.js +12 -15
- package/dist/interceptor/transactional.interceptor.js.map +1 -1
- package/dist/internal/markers.js +1 -4
- package/dist/internal/markers.js.map +1 -1
- package/dist/manager/adapter.registry.d.ts +1 -1
- package/dist/manager/adapter.registry.js +12 -15
- package/dist/manager/adapter.registry.js.map +1 -1
- package/dist/manager/transaction.manager.d.ts +3 -3
- package/dist/manager/transaction.manager.js +38 -41
- package/dist/manager/transaction.manager.js.map +1 -1
- package/dist/module/transactional.module.d.ts +5 -5
- package/dist/module/transactional.module.js +48 -54
- package/dist/module/transactional.module.js.map +1 -1
- package/dist/observability/transaction-observer.d.ts +1 -1
- package/dist/observability/transaction-observer.js +1 -4
- package/dist/observability/transaction-observer.js.map +1 -1
- package/dist/testing/in-memory.adapter.d.ts +3 -3
- package/dist/testing/in-memory.adapter.js +4 -8
- package/dist/testing/in-memory.adapter.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -17
- package/dist/testing/index.js.map +1 -1
- package/dist/tokens/constants.js +1 -4
- package/dist/tokens/constants.js.map +1 -1
- package/dist/tokens/index.d.ts +2 -2
- package/dist/tokens/index.js +2 -10
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/token-utils.js +5 -11
- package/dist/tokens/token-utils.js.map +1 -1
- package/dist/types/domain-event.js +1 -2
- package/dist/types/errors.js +4 -11
- package/dist/types/errors.js.map +1 -1
- package/dist/types/isolation.js +1 -2
- package/dist/types/propagation.js +2 -5
- package/dist/types/propagation.js.map +1 -1
- package/dist/types/transaction-adapter.d.ts +2 -2
- package/dist/types/transaction-adapter.js +1 -2
- package/dist/types/transaction-handle.js +1 -2
- package/dist/types/transaction-options.d.ts +32 -9
- package/dist/types/transaction-options.js +1 -2
- package/package.json +29 -15
package/README.md
CHANGED
|
@@ -1,265 +1,218 @@
|
|
|
1
1
|
# @nestjs-transactional/core
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@nestjs-transactional/core)
|
|
4
4
|
[](https://github.com/igorgolovanov/nestjs-transactional/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- `TransactionalInterceptor` — wires `@Transactional` on controllers,
|
|
24
|
-
resolvers, gateways, and microservice handlers via `APP_INTERCEPTOR`.
|
|
25
|
-
- `TransactionalModule.forRoot` / `forRootAsync` — module wiring,
|
|
26
|
-
one call per dataSource (multi-`forRoot` pattern, see
|
|
27
|
-
[ADR-019](../../docs/adr/019-outbox-multi-forroot-pattern.md)).
|
|
28
|
-
- `TransactionAdapter<THandle>` SPI — the port for ORM-specific
|
|
29
|
-
adapters.
|
|
30
|
-
- `InMemoryTransactionAdapter` (via the `@nestjs-transactional/core/testing`
|
|
31
|
-
subpath) — drop-in adapter for unit tests.
|
|
32
|
-
|
|
33
|
-
This package does not depend on any concrete ORM. Install
|
|
34
|
-
`@nestjs-transactional/typeorm` for TypeORM integration, or implement
|
|
35
|
-
your own adapter against the `TransactionAdapter` interface.
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
6
|
+
Declarative transactions for NestJS, with Spring's semantics.
|
|
7
|
+
|
|
8
|
+
Put `@Transactional()` on a method and everything it touches runs in
|
|
9
|
+
one transaction — across `await` boundaries, without threading a
|
|
10
|
+
manager through your call stack. All seven Spring propagation modes are
|
|
11
|
+
implemented, including `NESTED` via savepoints.
|
|
12
|
+
|
|
13
|
+
This package is ORM-agnostic and does nothing on its own: it needs an
|
|
14
|
+
adapter. Most applications install
|
|
15
|
+
[`@nestjs-transactional/typeorm`](https://www.npmjs.com/package/@nestjs-transactional/typeorm)
|
|
16
|
+
alongside it, which also makes injected repositories transaction-aware
|
|
17
|
+
automatically. For event delivery that survives a crash, add
|
|
18
|
+
[`@nestjs-transactional/outbox`](https://www.npmjs.com/package/@nestjs-transactional/outbox);
|
|
19
|
+
for `@nestjs/cqrs` handlers,
|
|
20
|
+
[`@nestjs-transactional/cqrs`](https://www.npmjs.com/package/@nestjs-transactional/cqrs).
|
|
21
|
+
|
|
22
|
+
## Install
|
|
38
23
|
|
|
39
24
|
```bash
|
|
40
|
-
pnpm add @nestjs-transactional/core reflect-metadata
|
|
25
|
+
pnpm add @nestjs-transactional/core @nestjs-transactional/typeorm reflect-metadata
|
|
41
26
|
```
|
|
42
27
|
|
|
43
|
-
Load `reflect-metadata` once at
|
|
44
|
-
|
|
28
|
+
Load `reflect-metadata` once at your entry point, as NestJS itself
|
|
29
|
+
requires.
|
|
45
30
|
|
|
46
|
-
##
|
|
31
|
+
## Module format
|
|
32
|
+
|
|
33
|
+
This package ships **ESM only**, matching NestJS 12, which is ESM-only
|
|
34
|
+
across its own packages. There is no CommonJS build.
|
|
35
|
+
|
|
36
|
+
A CommonJS application still works: Node loads ESM from `require()`
|
|
37
|
+
since 22.12.0, which is why `engines.node` is `>=22.13.0`. What does not
|
|
38
|
+
follow Node here is tooling with its own module loader — Jest above all,
|
|
39
|
+
which needs `NODE_OPTIONS=--experimental-vm-modules` and a few config
|
|
40
|
+
settings. The 19 example applications in the repository all run their
|
|
41
|
+
suites that way and can be copied from.
|
|
42
|
+
|
|
43
|
+
Reasoning and measurements: [ADR-022](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/022-esm-only-packaging.md).
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
(like `@nestjs-transactional/typeorm`) which registers the adapter
|
|
50
|
-
into the `AdapterRegistry` automatically. The minimal application
|
|
51
|
-
shape is:
|
|
45
|
+
## Quick start
|
|
52
46
|
|
|
53
47
|
```ts
|
|
54
48
|
import { Module } from '@nestjs/common';
|
|
55
49
|
import { TransactionalModule } from '@nestjs-transactional/core';
|
|
56
50
|
import { TypeOrmTransactionalModule } from '@nestjs-transactional/typeorm';
|
|
57
|
-
// ...your TypeORM config
|
|
58
51
|
|
|
59
52
|
@Module({
|
|
60
53
|
imports: [
|
|
61
|
-
TypeOrmModule.forRoot({
|
|
54
|
+
TypeOrmModule.forRoot({
|
|
55
|
+
/* ... */
|
|
56
|
+
}),
|
|
62
57
|
|
|
63
|
-
// Infrastructure
|
|
64
|
-
//
|
|
65
|
-
//
|
|
58
|
+
// Infrastructure only: TransactionManager, AdapterRegistry, the
|
|
59
|
+
// interceptor. `isGlobal` matters — the adapter package below
|
|
60
|
+
// needs to see the registry from its own DI scope.
|
|
66
61
|
TransactionalModule.forRoot({ isGlobal: true }),
|
|
67
62
|
|
|
68
|
-
//
|
|
69
|
-
// for the default dataSource.
|
|
63
|
+
// Registers the TypeORM adapter for the default dataSource.
|
|
70
64
|
TypeOrmTransactionalModule.forRoot(),
|
|
71
65
|
],
|
|
72
66
|
})
|
|
73
67
|
export class AppModule {}
|
|
74
68
|
```
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
service method
|
|
70
|
+
That is the whole setup. Now any method — a controller handler, a
|
|
71
|
+
service method, a CQRS handler — becomes transactional by decoration:
|
|
78
72
|
|
|
79
73
|
```ts
|
|
80
|
-
import {
|
|
74
|
+
import { Injectable } from '@nestjs/common';
|
|
81
75
|
import { Transactional } from '@nestjs-transactional/core';
|
|
82
76
|
|
|
83
|
-
@
|
|
84
|
-
export class
|
|
85
|
-
constructor(private readonly orders: OrdersService) {}
|
|
86
|
-
|
|
87
|
-
@Get(':id')
|
|
77
|
+
@Injectable()
|
|
78
|
+
export class OrdersService {
|
|
88
79
|
@Transactional()
|
|
89
|
-
async
|
|
90
|
-
|
|
80
|
+
async placeOrder(dto: PlaceOrderDto): Promise<Order> {
|
|
81
|
+
const order = await this.orders.save(dto);
|
|
82
|
+
await this.stock.reserve(order); // same transaction
|
|
83
|
+
return order; // commits here; a throw rolls both back
|
|
91
84
|
}
|
|
92
85
|
}
|
|
93
86
|
```
|
|
94
87
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
When implementing a new `TransactionAdapter` (Prisma, Mongoose, ...),
|
|
98
|
-
pass it to `forRoot` directly:
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
import { TransactionalModule, type TransactionAdapter } from '@nestjs-transactional/core';
|
|
88
|
+
## Propagation
|
|
102
89
|
|
|
103
|
-
|
|
90
|
+
`@Transactional({ propagation })` decides what happens when a
|
|
91
|
+
transactional method is called from inside another one. The default,
|
|
92
|
+
`REQUIRED`, joins the caller — which is what you want almost always.
|
|
104
93
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
94
|
+
| Mode | Caller has a transaction | Caller has none |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `REQUIRED` *(default)* | join it | start one |
|
|
97
|
+
| `REQUIRES_NEW` | suspend it, run independently, resume | start one |
|
|
98
|
+
| `NESTED` | run in a savepoint | start one |
|
|
99
|
+
| `SUPPORTS` | join it | run without a transaction |
|
|
100
|
+
| `NOT_SUPPORTED` | suspend it, run without one, resume | run without one |
|
|
101
|
+
| `NEVER` | throw `IllegalTransactionStateError` | run without one |
|
|
102
|
+
| `MANDATORY` | join it | throw `IllegalTransactionStateError` |
|
|
103
|
+
|
|
104
|
+
`REQUIRES_NEW` is how you make a side effect survive the caller's
|
|
105
|
+
rollback — an audit row that must persist even when the operation
|
|
106
|
+
fails. `NESTED` gives you a partial rollback inside one transaction;
|
|
107
|
+
it needs a driver with savepoint support, and the TypeORM adapter
|
|
108
|
+
raises a clear error rather than silently degrading if the driver has
|
|
109
|
+
none.
|
|
110
|
+
|
|
111
|
+
## Options
|
|
120
112
|
|
|
121
113
|
```ts
|
|
122
|
-
import {
|
|
123
|
-
Transactional,
|
|
124
|
-
ReadOnly,
|
|
125
|
-
TransactionalOn,
|
|
126
|
-
PropagationMode,
|
|
127
|
-
} from '@nestjs-transactional/core';
|
|
128
|
-
|
|
129
114
|
class ReportsService {
|
|
130
|
-
// Explicit propagation + isolation.
|
|
131
115
|
@Transactional({
|
|
132
116
|
propagation: PropagationMode.REQUIRES_NEW,
|
|
133
117
|
isolation: 'SERIALIZABLE',
|
|
134
|
-
timeout: 10_000,
|
|
135
118
|
})
|
|
136
|
-
async
|
|
119
|
+
async rebuild() {}
|
|
120
|
+
|
|
121
|
+
// Roll back on anything except ValidationError.
|
|
122
|
+
@Transactional({ noRollbackFor: [ValidationError] })
|
|
123
|
+
async processBatch() {}
|
|
137
124
|
|
|
138
125
|
// Shorthand for { readOnly: true }.
|
|
139
126
|
@ReadOnly()
|
|
140
|
-
async exportCsv() {
|
|
127
|
+
async exportCsv() {}
|
|
141
128
|
|
|
142
|
-
//
|
|
143
|
-
@Transactional({ noRollbackFor: [ValidationError] })
|
|
144
|
-
async processBatch() { /* ... */ }
|
|
145
|
-
|
|
146
|
-
// Target a specific dataSource in multi-DataSource setups.
|
|
129
|
+
// Target one dataSource in a multi-dataSource application.
|
|
147
130
|
@TransactionalOn('billing')
|
|
148
|
-
async chargeCard() {
|
|
131
|
+
async chargeCard() {}
|
|
149
132
|
}
|
|
150
133
|
```
|
|
151
134
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
| Mode | Active outer transaction | No outer transaction |
|
|
155
|
-
| --- | --- | --- |
|
|
156
|
-
| `REQUIRED` (default) | join | start new |
|
|
157
|
-
| `REQUIRES_NEW` | suspend + start new, then resume | start new |
|
|
158
|
-
| `NESTED` | run inside a savepoint | start new |
|
|
159
|
-
| `SUPPORTS` | join | run without transaction |
|
|
160
|
-
| `NOT_SUPPORTED` | suspend + run without transaction, then resume | run without transaction |
|
|
161
|
-
| `NEVER` | throw `IllegalTransactionStateError` | run without transaction |
|
|
162
|
-
| `MANDATORY` | join | throw `IllegalTransactionStateError` |
|
|
163
|
-
|
|
164
|
-
## Async module configuration
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
import { TransactionalModule } from '@nestjs-transactional/core';
|
|
168
|
-
|
|
169
|
-
@Module({
|
|
170
|
-
imports: [
|
|
171
|
-
TransactionalModule.forRootAsync({
|
|
172
|
-
imports: [ConfigModule],
|
|
173
|
-
inject: [ConfigService],
|
|
174
|
-
useFactory: (config: ConfigService) => ({
|
|
175
|
-
adapter: buildAdapterFromConfig(config),
|
|
176
|
-
}),
|
|
177
|
-
}),
|
|
178
|
-
],
|
|
179
|
-
})
|
|
180
|
-
export class AppModule {}
|
|
181
|
-
```
|
|
135
|
+
Two options carry caveats worth knowing before you rely on them:
|
|
182
136
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
137
|
+
- **`readOnly`** is enforced by the database only on Postgres-family
|
|
138
|
+
dialects, where the adapter issues `SET TRANSACTION READ ONLY`.
|
|
139
|
+
Elsewhere it documents intent and nothing rejects a write. Spring
|
|
140
|
+
treats it as a hint too. See
|
|
141
|
+
[DD-027](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/dd/027-readonly-and-timeout-semantics.md).
|
|
142
|
+
- **`timeout`** is accepted by the type but **not implemented** by the
|
|
143
|
+
TypeORM adapter. It is deliberately not approximated: Postgres'
|
|
144
|
+
`statement_timeout` bounds each statement rather than the
|
|
145
|
+
transaction, so `timeout: 5000` on a method issuing four queries
|
|
146
|
+
would allow twenty seconds. It stays in the surface for adapters
|
|
147
|
+
whose driver exposes a real transaction budget.
|
|
187
148
|
|
|
188
|
-
##
|
|
149
|
+
## Commit and rollback hooks
|
|
189
150
|
|
|
190
|
-
Register
|
|
191
|
-
|
|
151
|
+
Register from inside a transactional method; the hook binds to the
|
|
152
|
+
transaction currently running.
|
|
192
153
|
|
|
193
154
|
```ts
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
constructor(private readonly manager: TransactionManager) {}
|
|
198
|
-
|
|
199
|
-
@Transactional()
|
|
200
|
-
async placeOrder(payload: PlaceOrderDto) {
|
|
201
|
-
const order = await this.orders.insert(payload);
|
|
155
|
+
@Transactional()
|
|
156
|
+
async placeOrder(dto: PlaceOrderDto) {
|
|
157
|
+
const order = await this.orders.save(dto);
|
|
202
158
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
await this.analytics.trackOrderPlaced(order.id);
|
|
206
|
-
});
|
|
159
|
+
// Runs only after the commit succeeds — never on rollback.
|
|
160
|
+
this.manager.registerAfterCommit(() => this.analytics.track(order.id));
|
|
207
161
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
await this.metrics.recordFailedOrder(order.id, error);
|
|
211
|
-
});
|
|
162
|
+
// Receives the error that caused the rollback.
|
|
163
|
+
this.manager.registerAfterRollback((error) => this.metrics.failed(error));
|
|
212
164
|
|
|
213
|
-
|
|
214
|
-
}
|
|
165
|
+
return order;
|
|
215
166
|
}
|
|
216
167
|
```
|
|
217
168
|
|
|
218
|
-
|
|
219
|
-
|
|
169
|
+
A throwing hook is logged and swallowed: it changes neither the
|
|
170
|
+
transaction's outcome nor its sibling hooks. For event handlers with
|
|
171
|
+
these semantics as first-class decorators, see the `cqrs` package.
|
|
220
172
|
|
|
221
173
|
## Testing
|
|
222
174
|
|
|
223
|
-
`InMemoryTransactionAdapter` from the `/testing` subpath
|
|
224
|
-
|
|
175
|
+
`InMemoryTransactionAdapter` from the `/testing` subpath records
|
|
176
|
+
commits, rollbacks and savepoints without a database:
|
|
225
177
|
|
|
226
178
|
```ts
|
|
227
179
|
import { InMemoryTransactionAdapter } from '@nestjs-transactional/core/testing';
|
|
228
|
-
import { TransactionalModule } from '@nestjs-transactional/core';
|
|
229
180
|
|
|
230
181
|
const adapter = new InMemoryTransactionAdapter();
|
|
231
182
|
|
|
232
|
-
|
|
233
|
-
imports: [
|
|
234
|
-
TransactionalModule.forRoot({ isGlobal: true, adapter }),
|
|
235
|
-
],
|
|
183
|
+
await Test.createTestingModule({
|
|
184
|
+
imports: [TransactionalModule.forRoot({ isGlobal: true, adapter })],
|
|
236
185
|
}).compile();
|
|
237
186
|
|
|
238
|
-
// After exercising the code under test:
|
|
239
187
|
expect(adapter.committedTransactions).toHaveLength(1);
|
|
240
188
|
expect(adapter.rolledBackTransactions).toHaveLength(0);
|
|
241
|
-
expect(adapter.savepointsReleased).toHaveLength(0);
|
|
242
189
|
```
|
|
243
190
|
|
|
244
|
-
`adapter.reset()` clears
|
|
245
|
-
|
|
246
|
-
|
|
191
|
+
`adapter.reset()` clears the arrays between cases. Pass a dataSource
|
|
192
|
+
name to the constructor for multi-dataSource tests.
|
|
193
|
+
|
|
194
|
+
## Custom adapters
|
|
195
|
+
|
|
196
|
+
To support another ORM, implement `TransactionAdapter<THandle>` and
|
|
197
|
+
hand it to `forRoot` directly:
|
|
247
198
|
|
|
248
199
|
```ts
|
|
249
|
-
|
|
250
|
-
const inventory = new InMemoryTransactionAdapter('inventory');
|
|
200
|
+
TransactionalModule.forRoot({ isGlobal: true, adapter: myAdapter });
|
|
251
201
|
```
|
|
252
202
|
|
|
253
|
-
|
|
203
|
+
One `forRoot` call registers one adapter. Multi-dataSource
|
|
204
|
+
applications call it once per dataSource.
|
|
254
205
|
|
|
255
|
-
|
|
256
|
-
`@Transactional()` on a plain service.
|
|
257
|
-
- [`testing-patterns`](../../examples/testing-patterns) —
|
|
258
|
-
`InMemoryTransactionAdapter` from `core/testing` plus the outbox /
|
|
259
|
-
integration test layers.
|
|
206
|
+
## Documentation
|
|
260
207
|
|
|
261
|
-
|
|
208
|
+
- [Getting started and full docs](https://github.com/igorgolovanov/nestjs-transactional#readme)
|
|
209
|
+
- [Architecture: core design](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/architecture/core-design.md)
|
|
210
|
+
- [How methods get wrapped (ADR-005)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/005-method-wrapping-strategy.md)
|
|
211
|
+
- [Known limitations](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/known-limitations.md)
|
|
212
|
+
- Runnable examples:
|
|
213
|
+
[`basic-transactional`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/basic-transactional),
|
|
214
|
+
[`testing-patterns`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/testing-patterns)
|
|
262
215
|
|
|
263
|
-
##
|
|
216
|
+
## License
|
|
264
217
|
|
|
265
|
-
|
|
218
|
+
MIT
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { type OnApplicationBootstrap } from '@nestjs/common';
|
|
3
3
|
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
|
|
4
|
-
import { TransactionManager } from '../manager/transaction.manager';
|
|
4
|
+
import { TransactionManager } from '../manager/transaction.manager.js';
|
|
5
5
|
/**
|
|
6
6
|
* `OnApplicationBootstrap` service that wraps every `@Transactional()`
|
|
7
7
|
* method on a plain `@Injectable()` provider with
|
|
@@ -10,7 +10,7 @@ import { TransactionManager } from '../manager/transaction.manager';
|
|
|
10
10
|
* This is the second of the three coordinated wrapping mechanisms
|
|
11
11
|
* described in ADR-005:
|
|
12
12
|
*
|
|
13
|
-
* 1. {@link import('../interceptor/transactional.interceptor').TransactionalInterceptor}
|
|
13
|
+
* 1. {@link import('../interceptor/transactional.interceptor.js').TransactionalInterceptor}
|
|
14
14
|
* — for controller / resolver / gateway request-boundary handlers.
|
|
15
15
|
* 2. This class — for regular `@Injectable` services.
|
|
16
16
|
* 3. `CqrsHandlerWrapper` (in `@nestjs-transactional/cqrs`) — for CQRS
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
2
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
3
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -9,14 +8,12 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
9
|
};
|
|
11
10
|
var TransactionalMethodsBootstrap_1;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const markers_1 = require("../internal/markers");
|
|
19
|
-
const transaction_manager_1 = require("../manager/transaction.manager");
|
|
11
|
+
import 'reflect-metadata';
|
|
12
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
13
|
+
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
|
|
14
|
+
import { TRANSACTIONAL_METADATA, getTransactionalMetadata, } from '../decorators/transactional.decorator.js';
|
|
15
|
+
import { WRAPPED_MARKER } from '../internal/markers.js';
|
|
16
|
+
import { TransactionManager } from '../manager/transaction.manager.js';
|
|
20
17
|
/**
|
|
21
18
|
* Metadata keys set by `@nestjs/cqrs`'s handler decorators. Mirrored here
|
|
22
19
|
* so the bootstrap skips CQRS handler classes — they are wrapped by
|
|
@@ -37,7 +34,7 @@ const CQRS_HANDLER_KEYS = [
|
|
|
37
34
|
* This is the second of the three coordinated wrapping mechanisms
|
|
38
35
|
* described in ADR-005:
|
|
39
36
|
*
|
|
40
|
-
* 1. {@link import('../interceptor/transactional.interceptor').TransactionalInterceptor}
|
|
37
|
+
* 1. {@link import('../interceptor/transactional.interceptor.js').TransactionalInterceptor}
|
|
41
38
|
* — for controller / resolver / gateway request-boundary handlers.
|
|
42
39
|
* 2. This class — for regular `@Injectable` services.
|
|
43
40
|
* 3. `CqrsHandlerWrapper` (in `@nestjs-transactional/cqrs`) — for CQRS
|
|
@@ -60,7 +57,7 @@ let TransactionalMethodsBootstrap = TransactionalMethodsBootstrap_1 = class Tran
|
|
|
60
57
|
discovery;
|
|
61
58
|
metadataScanner;
|
|
62
59
|
manager;
|
|
63
|
-
logger = new
|
|
60
|
+
logger = new Logger(TransactionalMethodsBootstrap_1.name);
|
|
64
61
|
constructor(discovery, metadataScanner, manager) {
|
|
65
62
|
this.discovery = discovery;
|
|
66
63
|
this.metadataScanner = metadataScanner;
|
|
@@ -81,7 +78,7 @@ let TransactionalMethodsBootstrap = TransactionalMethodsBootstrap_1 = class Tran
|
|
|
81
78
|
const prototype = Object.getPrototypeOf(instance);
|
|
82
79
|
if (prototype === null)
|
|
83
80
|
continue;
|
|
84
|
-
const classMetadata =
|
|
81
|
+
const classMetadata = getTransactionalMetadata(metatype);
|
|
85
82
|
const methodNames = this.metadataScanner.getAllMethodNames(prototype);
|
|
86
83
|
const methods = prototype;
|
|
87
84
|
const host = instance;
|
|
@@ -89,13 +86,13 @@ let TransactionalMethodsBootstrap = TransactionalMethodsBootstrap_1 = class Tran
|
|
|
89
86
|
const protoMethod = methods[methodName];
|
|
90
87
|
if (typeof protoMethod !== 'function')
|
|
91
88
|
continue;
|
|
92
|
-
const metadata =
|
|
89
|
+
const metadata = getTransactionalMetadata(protoMethod) ?? classMetadata;
|
|
93
90
|
if (metadata === undefined)
|
|
94
91
|
continue;
|
|
95
92
|
const currentMethod = host[methodName];
|
|
96
93
|
if (typeof currentMethod !== 'function')
|
|
97
94
|
continue;
|
|
98
|
-
if (Reflect.getMetadata(
|
|
95
|
+
if (Reflect.getMetadata(WRAPPED_MARKER, currentMethod) === true)
|
|
99
96
|
continue;
|
|
100
97
|
if (this.wrapMethod(host, methodName, currentMethod, instance, metadata)) {
|
|
101
98
|
wrappedCount++;
|
|
@@ -111,19 +108,19 @@ let TransactionalMethodsBootstrap = TransactionalMethodsBootstrap_1 = class Tran
|
|
|
111
108
|
const boundOriginal = currentMethod.bind(instance);
|
|
112
109
|
const manager = this.manager;
|
|
113
110
|
const wrapped = (...args) => manager.run(metadata, () => Promise.resolve(boundOriginal(...args)));
|
|
114
|
-
Reflect.defineMetadata(
|
|
115
|
-
Reflect.defineMetadata(
|
|
111
|
+
Reflect.defineMetadata(WRAPPED_MARKER, true, wrapped);
|
|
112
|
+
Reflect.defineMetadata(TRANSACTIONAL_METADATA, metadata, wrapped);
|
|
116
113
|
host[methodName] = wrapped;
|
|
117
114
|
this.logger.debug(`Wrapped ${instance.constructor.name}.${methodName} ` +
|
|
118
115
|
`(propagation=${metadata.propagation ?? 'REQUIRED'})`);
|
|
119
116
|
return true;
|
|
120
117
|
}
|
|
121
118
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
transaction_manager_1.TransactionManager])
|
|
119
|
+
TransactionalMethodsBootstrap = TransactionalMethodsBootstrap_1 = __decorate([
|
|
120
|
+
Injectable(),
|
|
121
|
+
__metadata("design:paramtypes", [DiscoveryService,
|
|
122
|
+
MetadataScanner,
|
|
123
|
+
TransactionManager])
|
|
128
124
|
], TransactionalMethodsBootstrap);
|
|
125
|
+
export { TransactionalMethodsBootstrap };
|
|
129
126
|
//# sourceMappingURL=transactional-methods.bootstrap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactional-methods.bootstrap.js","sourceRoot":"","sources":["../../src/bootstrap/transactional-methods.bootstrap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transactional-methods.bootstrap.js","sourceRoot":"","sources":["../../src/bootstrap/transactional-methods.bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActiveTransaction } from './transaction.context';
|
|
1
|
+
import { type ActiveTransaction } from './transaction.context.js';
|
|
2
2
|
/**
|
|
3
3
|
* Per-dataSource read-only view over {@link TransactionContext}'s active
|
|
4
4
|
* transaction Map (DD-022). Bound to a single dataSource name at
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TransactionContextView = void 0;
|
|
4
|
-
const transaction_context_1 = require("./transaction.context");
|
|
1
|
+
import { TransactionContext } from './transaction.context.js';
|
|
5
2
|
/**
|
|
6
3
|
* Per-dataSource read-only view over {@link TransactionContext}'s active
|
|
7
4
|
* transaction Map (DD-022). Bound to a single dataSource name at
|
|
@@ -18,7 +15,7 @@ const transaction_context_1 = require("./transaction.context");
|
|
|
18
15
|
* `getTransactionContextToken(dataSource)`. Inject via
|
|
19
16
|
* `@InjectTransactionContext(dataSource?)`.
|
|
20
17
|
*/
|
|
21
|
-
class TransactionContextView {
|
|
18
|
+
export class TransactionContextView {
|
|
22
19
|
dataSource;
|
|
23
20
|
/**
|
|
24
21
|
* @param dataSource - Public dataSource name this view is bound to.
|
|
@@ -33,7 +30,7 @@ class TransactionContextView {
|
|
|
33
30
|
* `TransactionContext.getActiveTransactionByDataSource(this.dataSource)`.
|
|
34
31
|
*/
|
|
35
32
|
getActiveTransaction() {
|
|
36
|
-
return
|
|
33
|
+
return TransactionContext.getActiveTransactionByDataSource(this.dataSource);
|
|
37
34
|
}
|
|
38
35
|
/**
|
|
39
36
|
* Predicate convenience over {@link getActiveTransaction}. Useful for
|
|
@@ -44,5 +41,4 @@ class TransactionContextView {
|
|
|
44
41
|
return this.getActiveTransaction() !== undefined;
|
|
45
42
|
}
|
|
46
43
|
}
|
|
47
|
-
exports.TransactionContextView = TransactionContextView;
|
|
48
44
|
//# sourceMappingURL=transaction-context-view.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction-context-view.js","sourceRoot":"","sources":["../../src/context/transaction-context-view.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transaction-context-view.js","sourceRoot":"","sources":["../../src/context/transaction-context-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEtF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,sBAAsB;IAKZ;IAJrB;;;OAGG;IACH,YAAqB,UAAkB;QAAlB,eAAU,GAAV,UAAU,CAAQ;IAAG,CAAC;IAE3C;;;;OAIG;IACH,oBAAoB;QAClB,OAAO,kBAAkB,CAAC,gCAAgC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9E,CAAC;IAED;;;;OAIG;IACH,oBAAoB;QAClB,OAAO,IAAI,CAAC,oBAAoB,EAAE,KAAK,SAAS,CAAC;IACnD,CAAC;CACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TransactionHandle } from '../types/transaction-handle';
|
|
2
|
-
import type { TransactionOptions } from '../types/transaction-options';
|
|
1
|
+
import type { TransactionHandle } from '../types/transaction-handle.js';
|
|
2
|
+
import type { TransactionOptions } from '../types/transaction-options.js';
|
|
3
3
|
/**
|
|
4
4
|
* A single transaction currently live on the async context. Adapters register
|
|
5
5
|
* one of these under their `adapterInstanceName` when they begin a transaction
|
|
@@ -55,7 +55,7 @@ export interface TransactionContextStore {
|
|
|
55
55
|
*
|
|
56
56
|
* **Public dataSource-name access**:
|
|
57
57
|
* {@link getActiveTransactionByDataSource} provides dataSource-name
|
|
58
|
-
* lookup for
|
|
58
|
+
* lookup for multi-adapter consumers — it scans the Map
|
|
59
59
|
* for the entry whose `adapterInstanceName === dataSource`. Both
|
|
60
60
|
* access patterns coexist; future cleanup is possible once
|
|
61
61
|
* cross-package consumers migrate to the dataSource-name lookup and
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const node_async_hooks_1 = require("node:async_hooks");
|
|
5
|
-
const errors_1 = require("../types/errors");
|
|
6
|
-
const als = new node_async_hooks_1.AsyncLocalStorage();
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { IllegalTransactionStateError } from '../types/errors.js';
|
|
3
|
+
const als = new AsyncLocalStorage();
|
|
7
4
|
/**
|
|
8
5
|
* Thin façade over `AsyncLocalStorage` that carries the active
|
|
9
6
|
* {@link TransactionContextStore} across async boundaries. This is the
|
|
@@ -19,13 +16,13 @@ const als = new node_async_hooks_1.AsyncLocalStorage();
|
|
|
19
16
|
*
|
|
20
17
|
* **Public dataSource-name access**:
|
|
21
18
|
* {@link getActiveTransactionByDataSource} provides dataSource-name
|
|
22
|
-
* lookup for
|
|
19
|
+
* lookup for multi-adapter consumers — it scans the Map
|
|
23
20
|
* for the entry whose `adapterInstanceName === dataSource`. Both
|
|
24
21
|
* access patterns coexist; future cleanup is possible once
|
|
25
22
|
* cross-package consumers migrate to the dataSource-name lookup and
|
|
26
23
|
* no backwards-compatibility constraints remain.
|
|
27
24
|
*/
|
|
28
|
-
class TransactionContext {
|
|
25
|
+
export class TransactionContext {
|
|
29
26
|
/**
|
|
30
27
|
* Run `fn` inside a transaction context scope.
|
|
31
28
|
*
|
|
@@ -95,7 +92,7 @@ class TransactionContext {
|
|
|
95
92
|
static setActiveTransaction(adapterInstanceName, tx) {
|
|
96
93
|
const store = als.getStore();
|
|
97
94
|
if (store === undefined) {
|
|
98
|
-
throw new
|
|
95
|
+
throw new IllegalTransactionStateError('Cannot set active transaction outside of TransactionContext.run()');
|
|
99
96
|
}
|
|
100
97
|
store.activeTransactions.set(adapterInstanceName, tx);
|
|
101
98
|
}
|
|
@@ -108,5 +105,4 @@ class TransactionContext {
|
|
|
108
105
|
als.getStore()?.activeTransactions.delete(adapterInstanceName);
|
|
109
106
|
}
|
|
110
107
|
}
|
|
111
|
-
exports.TransactionContext = TransactionContext;
|
|
112
108
|
//# sourceMappingURL=transaction.context.js.map
|