@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 CHANGED
@@ -1,76 +1,55 @@
1
1
  # @nestjs-transactional/typeorm
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/%40nestjs-transactional%2Ftypeorm/alpha?style=flat-square&label=npm)](https://www.npmjs.com/package/@nestjs-transactional/typeorm)
3
+ [![npm version](https://img.shields.io/npm/v/%40nestjs-transactional%2Ftypeorm?style=flat-square&label=npm)](https://www.npmjs.com/package/@nestjs-transactional/typeorm)
4
4
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/LICENSE)
5
5
 
6
- TypeORM adapter for [`@nestjs-transactional/core`](../core).
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).
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
- ## Compatibility
38
+ ## Module format
54
39
 
55
- | Peer | Supported range |
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
- ## Quick start
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
- Minimal single-DataSource setup:
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
- type: 'postgres',
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
- **Import order matters** — `TransactionalModule.forRoot({ isGlobal: true })`
99
- must be present (with `isGlobal`) so the `AdapterRegistry` is visible
100
- inside `TypeOrmTransactionalModule`'s DI scope. The actual
101
- `DataSource` is resolved via `@nestjs/typeorm`'s
102
- `getDataSourceToken(name)` `TypeOrmModule.forRoot(...)` registers
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
- ## Transparent transactional behaviour
78
+ ## What becomes transparent
107
79
 
108
- Once the module is imported, every Repository reachable through the
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
- ```ts
114
- import { Injectable } from '@nestjs/common';
115
- import { InjectRepository } from '@nestjs/typeorm';
116
- import { Transactional } from '@nestjs-transactional/core';
117
- import { Repository } from 'typeorm';
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
- @Injectable()
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
- @Transactional()
128
- async placeOrder(dto: PlaceOrderDto): Promise<Order> {
129
- // `orderRepo.save(...)` automatically uses the transactional
130
- // EntityManager. If the method throws, the save rolls back.
131
- // Outside a @Transactional scope, the same call autocommits.
132
- return this.orderRepo.save(dto);
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
- 1. **`@InjectEntityManager() em.save(Entity, ...)` direct call** is
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
- @Injectable()
186
- export class RawSqlService {
187
- constructor(@InjectDataSource() private readonly ds: DataSource) {}
188
-
189
- @Transactional()
190
- async runRawSql() {
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
- ## Multi-DataSource
114
+ ## Dialect-dependent behaviour
204
115
 
205
- ```ts
206
- @Module({
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
- TransactionalModule.forRoot({ isGlobal: true }),
212
- TypeOrmTransactionalModule.forRoot({ isDefault: true }), // 'default'
213
- TypeOrmTransactionalModule.forRoot({ dataSource: 'billing' }), // 'billing'
214
- ],
215
- })
216
- export class AppModule {}
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
- Target a specific dataSource in a transactional method:
135
+ One `forRoot` call per dataSource:
220
136
 
221
137
  ```ts
222
- import { Transactional } from '@nestjs-transactional/core';
138
+ TypeOrmTransactionalModule.forRoot({ isDefault: true }), // 'default'
139
+ TypeOrmTransactionalModule.forRoot({ dataSource: 'billing' }), // 'billing'
140
+ ```
223
141
 
224
- @Injectable()
225
- export class BillingService {
226
- constructor(
227
- @InjectRepository(Invoice, 'billing')
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
- **Cross-DS isolation (DD-023)**: a Repository bound to dataSource A
240
- inside a `@Transactional({ dataSource: 'B' })` method autocommits
241
- its patched `manager` getter looks up the active transaction for
242
- dataSource A, finds none, and falls back to its captured original
243
- manager. Distributed transactions across dataSources are explicitly
244
- NOT supported; cross-DS atomicity goes through the outbox.
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
- @Module({
254
- imports: [
255
- ConfigModule,
256
- TypeOrmModule.forRootAsync({
257
- inject: [ConfigService],
258
- useFactory: (cfg: ConfigService) => ({
259
- type: 'postgres',
260
- url: cfg.get('DATABASE_URL'),
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
- `forRootAsync` defers resolution of the dataSource name until the
280
- factory runs. Per-DS DI tokens (`getTransactionalAdapterToken(ds)`)
281
- are NOT registered in the async path because NestJS provider tokens
282
- must be declared statically if you need direct adapter injection
283
- by per-DS token, use sync `forRoot({ dataSource })` instead.
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
- The async path uses an `OnModuleInit`-driven registration class so
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
- ## Testing
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
- ### Unit tests in-memory SQLite
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
- For fast unit tests that don't need a real database, use TypeORM's
296
- `sqljs` driver:
190
+ ## Testing
297
191
 
298
- ```ts
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
- const ds = new DataSource({
303
- type: 'sqljs',
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
- ### Integration tests testcontainers-node + real Postgres
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
- Bundled helper for real Postgres integration:
207
+ ## Documentation
315
208
 
316
- ```ts
317
- import {
318
- startPostgresContainer,
319
- stopPostgresContainer,
320
- createAdditionalDatabase,
321
- } from '@nestjs-transactional/typeorm/test/setup-testcontainers';
322
-
323
- let ctx;
324
- beforeAll(async () => {
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
- // Multi-DS: a second database inside the same container.
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
- The bundled `docker-compose.yml` is for manual local use (`psql`
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 { TransactionAdapter, TransactionOptions } from '@nestjs-transactional/core';
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