@nestjs-transactional/outbox 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.
Files changed (73) hide show
  1. package/README.md +152 -332
  2. package/dist/api/failed-event-publications.js.map +1 -1
  3. package/dist/api/incomplete-event-publications.js +1 -2
  4. package/dist/api/incomplete-event-publications.js.map +1 -1
  5. package/dist/decorators/outbox-events-handler.decorator.d.ts +1 -1
  6. package/dist/decorators/outbox-events-handler.decorator.js.map +1 -1
  7. package/dist/dispatcher/data-source-outbox-publisher.d.ts +2 -3
  8. package/dist/dispatcher/data-source-outbox-publisher.js +2 -3
  9. package/dist/dispatcher/data-source-outbox-publisher.js.map +1 -1
  10. package/dist/dispatcher/event-publication-processor.d.ts +19 -2
  11. package/dist/dispatcher/event-publication-processor.js +40 -3
  12. package/dist/dispatcher/event-publication-processor.js.map +1 -1
  13. package/dist/dispatcher/outbox-event-publisher.d.ts +1 -1
  14. package/dist/dispatcher/outbox-event-publisher.js +2 -2
  15. package/dist/dispatcher/outbox-event-publisher.js.map +1 -1
  16. package/dist/dispatcher/processor-options.d.ts +13 -1
  17. package/dist/dispatcher/processor-options.js +4 -1
  18. package/dist/dispatcher/processor-options.js.map +1 -1
  19. package/dist/externalization/event-externalizer.d.ts +2 -2
  20. package/dist/externalization/externalization-registry.js.map +1 -1
  21. package/dist/externalization/externalized.decorator.d.ts +3 -4
  22. package/dist/externalization/externalized.decorator.js +2 -3
  23. package/dist/externalization/externalized.decorator.js.map +1 -1
  24. package/dist/externalization/types.d.ts +2 -2
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.js +5 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/module/outbox-processing.module.d.ts +18 -2
  29. package/dist/module/outbox-processing.module.js +28 -8
  30. package/dist/module/outbox-processing.module.js.map +1 -1
  31. package/dist/module/outbox.module.d.ts +8 -0
  32. package/dist/module/outbox.module.js +61 -7
  33. package/dist/module/outbox.module.js.map +1 -1
  34. package/dist/recovery/outbox-retry-scheduler.d.ts +68 -0
  35. package/dist/recovery/outbox-retry-scheduler.js +163 -0
  36. package/dist/recovery/outbox-retry-scheduler.js.map +1 -0
  37. package/dist/recovery/staleness-monitor.d.ts +10 -2
  38. package/dist/recovery/staleness-monitor.js +28 -3
  39. package/dist/recovery/staleness-monitor.js.map +1 -1
  40. package/dist/registry/event-publication-registry.d.ts +1 -1
  41. package/dist/registry/event-publication-registry.js +1 -1
  42. package/dist/registry/event-publication-registry.js.map +1 -1
  43. package/dist/registry/listener-registry.d.ts +2 -2
  44. package/dist/registry/listener-registry.js +2 -2
  45. package/dist/registry/multi-ds-listener-registrar.d.ts +10 -4
  46. package/dist/registry/multi-ds-listener-registrar.js +1 -1
  47. package/dist/registry/multi-ds-listener-registrar.js.map +1 -1
  48. package/dist/registry/outbox-listener-scanner.d.ts +1 -1
  49. package/dist/registry/outbox-listener-scanner.js +1 -1
  50. package/dist/registry/outbox-listener-scanner.js.map +1 -1
  51. package/dist/repository/event-publication-repository.d.ts +25 -5
  52. package/dist/repository/event-publication-repository.js.map +1 -1
  53. package/dist/serialization/event-type-resolver.d.ts +1 -1
  54. package/dist/serialization/event-type-resolver.js +1 -1
  55. package/dist/serialization/json-event-serializer.js.map +1 -1
  56. package/dist/shutdown/drain.d.ts +32 -0
  57. package/dist/shutdown/drain.js +56 -0
  58. package/dist/shutdown/drain.js.map +1 -0
  59. package/dist/testing/in-memory-repository.js +1 -2
  60. package/dist/testing/in-memory-repository.js.map +1 -1
  61. package/dist/testing/published-events.js +4 -1
  62. package/dist/testing/published-events.js.map +1 -1
  63. package/dist/tokens/token-utils.js.map +1 -1
  64. package/dist/types/resubmission-options.d.ts +0 -4
  65. package/dist/types/resubmission-options.js +0 -9
  66. package/dist/types/resubmission-options.js.map +1 -1
  67. package/dist/types/retry-config.d.ts +51 -0
  68. package/dist/types/retry-config.js +16 -0
  69. package/dist/types/retry-config.js.map +1 -0
  70. package/dist/types/staleness-config.d.ts +8 -0
  71. package/dist/types/staleness-config.js +2 -0
  72. package/dist/types/staleness-config.js.map +1 -1
  73. package/package.json +17 -6
package/README.md CHANGED
@@ -1,395 +1,215 @@
1
1
  # @nestjs-transactional/outbox
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/%40nestjs-transactional%2Foutbox/alpha?style=flat-square&label=npm)](https://www.npmjs.com/package/@nestjs-transactional/outbox)
3
+ [![npm version](https://img.shields.io/npm/v/%40nestjs-transactional%2Foutbox?style=flat-square&label=npm)](https://www.npmjs.com/package/@nestjs-transactional/outbox)
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
- Persistent Event Publication Registry for NestJS — an ORM-agnostic core
7
- that brings Spring Modulith-equivalent delivery guarantees to the
8
- `@nestjs-transactional` family of packages.
6
+ The transactional outbox for NestJS — a persistent Event Publication
7
+ Registry, modelled on Spring Modulith's.
9
8
 
10
- ## Overview
9
+ In-process event handlers have one failure mode you cannot design
10
+ around: if the process dies between the commit and the handler, the
11
+ event is gone. This package writes one row per handler per event, in the
12
+ same transaction as your business data. A worker picks the rows up
13
+ afterwards, retries what fails, and resumes what a restart interrupted.
11
14
 
12
- `@TransactionalEventsHandler` from `@nestjs-transactional/cqrs` provides
13
- phase-based dispatching (like Spring Framework core): handlers fire
14
- `AFTER_COMMIT`, `BEFORE_COMMIT`, `AFTER_ROLLBACK`, or `AFTER_COMPLETION`.
15
- That covers a lot, but it is purely in-memory — if the process dies
16
- between commit and handler invocation, the event is lost.
15
+ Either both happen or neither does. That is the whole point.
17
16
 
18
- `outbox` closes that gap. It gives you:
17
+ This package is storage-agnostic it ships the SPI, the worker, the
18
+ operator APIs and an in-memory implementation for tests, but no
19
+ production backend. Add
20
+ [`@nestjs-transactional/outbox-typeorm`](https://www.npmjs.com/package/@nestjs-transactional/outbox-typeorm)
21
+ for that. It builds on
22
+ [`@nestjs-transactional/core`](https://www.npmjs.com/package/@nestjs-transactional/core),
23
+ integrates with
24
+ [`@nestjs-transactional/cqrs`](https://www.npmjs.com/package/@nestjs-transactional/cqrs),
25
+ and can forward events to a broker through
26
+ [`@nestjs-transactional/outbox-microservices`](https://www.npmjs.com/package/@nestjs-transactional/outbox-microservices).
19
27
 
20
- - A persistent **Event Publication Registry** — every handler
21
- invocation is logged atomically with the business transaction.
22
- - **Retry on process restart** — publications that were not acknowledged
23
- before shutdown are replayed on next startup.
24
- - **Lifecycle states**: `PUBLISHED`, `PROCESSING`, `COMPLETED`, `FAILED`,
25
- `RESUBMITTED`.
26
- - **Staleness monitor** — detects publications stuck in `PROCESSING`.
27
- - **Failed / Incomplete / Completed** query APIs for operators.
28
- - **Completion modes**: `UPDATE`, `DELETE`, `ARCHIVE`.
29
-
30
- This package only defines types, the repository SPI, the in-memory
31
- reference implementation (for tests), and the Nest module wiring. It
32
- does **not** ship a production persistence backend — that lives in a
33
- sibling package such as `@nestjs-transactional/outbox-typeorm`.
34
-
35
- ## Installation
28
+ ## Install
36
29
 
37
30
  ```bash
38
- pnpm add @nestjs-transactional/core @nestjs-transactional/outbox
39
- # plus a persistence backend, e.g.:
40
- pnpm add @nestjs-transactional/outbox-typeorm
31
+ pnpm add @nestjs-transactional/outbox @nestjs-transactional/core
32
+ pnpm add @nestjs-transactional/outbox-typeorm # a persistence backend
41
33
  ```
42
34
 
43
- Peer dependencies: `@nestjs/common`, `@nestjs/core`, `reflect-metadata`,
44
- `rxjs`.
45
-
46
- ## Usage
47
-
48
- ### 1. Module wiring
35
+ ## Quick start
49
36
 
50
- `OutboxModule.forRoot()` provides global infrastructure (registry,
51
- serializer, processor, recovery). `OutboxModule.forFeature([...])`
52
- declares the event classes a feature module owns — matches
53
- `TypeOrmModule.forFeature(...)` ergonomics.
54
-
55
- ```typescript
37
+ ```ts
56
38
  import { Module } from '@nestjs/common';
57
39
  import { TransactionalModule } from '@nestjs-transactional/core';
58
40
  import { OutboxModule, OutboxProcessingModule } from '@nestjs-transactional/outbox';
41
+ import { typeOrmEventPublicationRepositoryProvider } from '@nestjs-transactional/outbox-typeorm';
59
42
 
60
43
  @Module({
61
44
  imports: [
62
- // isGlobal: true is REQUIRED so outbox providers can see
63
- // TransactionManager across module boundaries.
64
- TransactionalModule.forRoot({
65
- isGlobal: true,
66
- adapters: [/* your adapter registrations */],
67
- }),
45
+ // Must be global: outbox providers resolve TransactionManager
46
+ // across module boundaries.
47
+ TransactionalModule.forRoot({ isGlobal: true }),
48
+ TypeOrmTransactionalModule.forRoot(),
49
+ OutboxTypeOrmModule.forRoot(),
50
+
68
51
  OutboxModule.forRoot({
52
+ // Without this, the in-memory repository stays installed and
53
+ // nothing is ever persisted. See the note below.
54
+ repository: typeOrmEventPublicationRepositoryProvider(),
69
55
  republishOnStartup: true,
70
- processor: { pollingInterval: 1000, batchSize: 100 },
71
- staleness: { processing: 60_000, monitorInterval: 30_000 },
72
- // repository: { provide: EVENT_PUBLICATION_REPOSITORY, useClass: TypeOrmEventPublicationRepository },
73
56
  }),
74
- // Import OutboxProcessingModule ONLY in worker processes — not in
75
- // API-only apps that merely publish events.
76
- OutboxProcessingModule,
77
- // Register the event classes this module emits / consumes.
78
- OutboxModule.forFeature([OrderPlacedEvent, OrderShippedEvent]),
79
- ],
80
- })
81
- export class AppModule {}
82
- ```
83
-
84
- #### Modular pattern (multiple feature modules)
85
-
86
- Distribute event registrations to the modules that own them:
87
-
88
- ```typescript
89
- @Module({
90
- imports: [OutboxModule.forFeature([OrderPlacedEvent, OrderCancelledEvent])],
91
- providers: [OrderService],
92
- })
93
- export class OrderModule {}
94
57
 
95
- @Module({
96
- imports: [OutboxModule.forFeature([InventoryReservedEvent])],
97
- providers: [InventoryService],
98
- })
99
- export class InventoryModule {}
58
+ // Event classes this module owns. Feature modules normally call
59
+ // forFeature for their own events.
60
+ OutboxModule.forFeature([OrderPlacedEvent]),
100
61
 
101
- @Module({
102
- imports: [
103
- TransactionalModule.forRoot({ isGlobal: true /* ... */ }),
104
- OutboxModule.forRoot({ republishOnStartup: true /* ... */ }),
105
- OrderModule,
106
- InventoryModule,
62
+ // ONLY in worker processes. An API that merely publishes events
63
+ // must not import this.
64
+ OutboxProcessingModule,
107
65
  ],
108
66
  })
109
67
  export class AppModule {}
110
68
  ```
111
69
 
112
- Multiple `forFeature` calls accumulate into the singleton
113
- `EventTypeRegistry` provided by `forRoot`. Each event class can only
114
- be registered once duplicates throw at bootstrap with the offending
115
- class name:
70
+ > **Pass `repository`.** `OutboxModule.forRoot()` falls back to
71
+ > `InMemoryEventPublicationRepository` when the option is missing. Your
72
+ > application starts, publishes, and handles events perfectly and
73
+ > loses every one of them on restart, with nothing in the database and
74
+ > no error anywhere.
116
75
 
117
- ```
118
- Error: Event type 'OrderPlacedEvent' already registered.
119
- Each event type can only be registered once — check for duplicate
120
- entries across OutboxModule.forFeature() calls.
121
- ```
122
-
123
- `OutboxModule.forFeature([])` is accepted as a no-op (matches
124
- `TypeOrmModule.forFeature([])`).
125
-
126
- ### 2. Declaring a handler
127
-
128
- ```typescript
129
- import { Injectable } from '@nestjs/common';
130
- import {
131
- type IOutboxEventHandler,
132
- OutboxEventsHandler,
133
- } from '@nestjs-transactional/outbox';
76
+ Declare a handler:
134
77
 
78
+ ```ts
135
79
  @Injectable()
136
80
  @OutboxEventsHandler(OrderPlacedEvent)
137
- export class InventoryReservationHandler
138
- implements IOutboxEventHandler<OrderPlacedEvent>
139
- {
140
- async handle(event: OrderPlacedEvent): Promise<void> {
141
- // Runs inside a fresh REQUIRES_NEW transaction, only after the
142
- // publishing transaction has committed. Retried on exception,
143
- // resumable across restarts.
81
+ export class ChargeCustomer implements IOutboxEventHandler<OrderPlacedEvent> {
82
+ async handle(event: OrderPlacedEvent) {
83
+ // Invoked by the worker, in its own transaction. Throwing marks the
84
+ // publication FAILED, leaving it for retry or an operator.
85
+ await this.payments.charge(event.orderId);
144
86
  }
145
87
  }
146
88
  ```
147
89
 
148
- The decorator accepts either rest params or an options object:
149
-
150
- ```typescript
151
- // Short form — defaults (newTransaction: true).
152
- @OutboxEventsHandler(OrderPlacedEvent, OrderCancelledEvent)
153
-
154
- // Long form — explicit options.
155
- @OutboxEventsHandler({
156
- events: [OrderPlacedEvent],
157
- id: 'inventory.reservation', // stable base id, see "Listener ids" below
158
- newTransaction: false, // skip the REQUIRES_NEW wrapper
159
- })
160
- ```
161
-
162
- See ADR-014 for the rationale behind the class-level shape.
163
-
164
- ### 3. Publishing events
90
+ Publish from inside a transaction:
165
91
 
166
- ```typescript
167
- import { Injectable } from '@nestjs/common';
168
- import { Transactional } from '@nestjs-transactional/core';
169
- import { OutboxEventPublisher } from '@nestjs-transactional/outbox';
170
-
171
- @Injectable()
172
- export class PlaceOrderHandler {
173
- constructor(private readonly outbox: OutboxEventPublisher) {}
174
-
175
- @Transactional()
176
- async handle(command: PlaceOrder): Promise<void> {
177
- // ...persist business data in the same transaction...
178
- await this.outbox.publish(new OrderPlacedEvent(command.orderId));
179
- }
92
+ ```ts
93
+ @Transactional()
94
+ async placeOrder(dto: PlaceOrderDto) {
95
+ const order = await this.orders.save(dto);
96
+ await this.publisher.publish(new OrderPlacedEvent(order.id));
97
+ return order; // publication rows commit with the order, or not at all
180
98
  }
181
99
  ```
182
100
 
183
- ### 4. Operator APIs
184
-
185
- ```typescript
186
- const failed = await this.failedEventPublications.findAll();
187
- await this.failedEventPublications.resubmit(
188
- ResubmissionOptions.defaults().withBatchSize(50).withMaxAttempts(3),
189
- );
190
- await this.completedEventPublications.purge(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000));
191
- ```
192
-
193
- ## Listener ids
194
-
195
- Every stored publication carries a `listenerId` string that identifies
196
- which handler class should deliver it. The scanner composes the id as:
197
-
198
- ```
199
- ${baseId}#${EventName}
101
+ ## Lifecycle
102
+
103
+ A publication moves through five states:
104
+
105
+ `PUBLISHED` → `PROCESSING` → `COMPLETED`, or `FAILED` → `RESUBMITTED`
106
+ back to `PROCESSING`.
107
+
108
+ The worker polls for ready rows and then **claims** each one with a
109
+ single conditional `UPDATE`. That claim, not the poll, is what makes
110
+ concurrent workers safe: two workers may fetch the same row, but only
111
+ one wins the claim and the loser moves on without invoking the handler.
112
+ So scaling out costs a wasted read, never a duplicate dispatch
113
+ ([DD-025](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/dd/025-claim-atomicity-obligation.md)).
114
+
115
+ Completed rows can be kept (`UPDATE`, the default), deleted, or moved to
116
+ an archive table — `completionMode`. Kept rows need purging eventually;
117
+ `CompletedEventPublications.purge(olderThan)` does it.
118
+
119
+ ## Operating it
120
+
121
+ ```ts
122
+ OutboxModule.forRoot({
123
+ repository: typeOrmEventPublicationRepositoryProvider(),
124
+ republishOnStartup: true,
125
+ processor: {
126
+ pollingInterval: 1000, // end-to-end latency is dominated by this
127
+ batchSize: 100,
128
+ maxConcurrent: 10,
129
+ // How long shutdown waits for an in-flight batch. Keep it under
130
+ // your platform's grace period; 0 disables the wait.
131
+ shutdownTimeout: 10_000,
132
+ },
133
+ // Flip publications stuck in a non-terminal state to FAILED. 0 = off.
134
+ staleness: { processing: 60_000, monitorInterval: 30_000 },
135
+ // Automatic retry, off unless you ask for it. maxAttempts counts the
136
+ // first delivery, so 3 means the original plus two retries.
137
+ retry: { maxAttempts: 3, baseDelay: 1_000, factor: 2, maxDelay: 300_000 },
138
+ });
200
139
  ```
201
140
 
202
- - **No explicit `id`**: baseId is the class name. Example:
203
- `InventoryReservationHandler#OrderPlacedEvent`.
204
- - **With `options.id`**: baseId is the supplied string. Example:
205
- `inventory.reservation#OrderPlacedEvent`.
206
-
207
- A class that handles multiple event types produces one id per event.
208
- Renaming the class without supplying an explicit `id` breaks delivery
209
- for every stored publication under the old id — set `options.id` when
210
- you want stability across renames.
211
-
212
- ## Externalization (advanced)
213
-
214
- > The `EventExternalizer` SPI lives in this package. The
215
- > `@nestjs/microservices` `ClientProxy`-backed implementation ships
216
- > as a separate package —
217
- > [`@nestjs-transactional/outbox-microservices`](../outbox-microservices).
218
- > Without an externalizer bound, `@Externalized` mappings are
219
- > recorded but never delivered to a broker — local outbox listeners
220
- > still run.
221
- >
222
- > Architecture and design rationale:
223
- > [ADR-015](../../docs/adr/015-event-externalization-architecture.md),
224
- > [`docs/architecture/event-externalization.md`](../../docs/architecture/event-externalization.md).
225
- > Reliability caveat for the `@nestjs/microservices`-backed
226
- > implementation:
227
- > [ADR-016](../../docs/adr/016-externalization-reliability-semantics.md).
228
-
229
- `@Externalized` marks an event class for delivery to an external
230
- message broker (Kafka topic, RabbitMQ exchange, NATS subject, ...) in
231
- addition to local outbox listeners. The processor invokes the bound
232
- `EventExternalizer` AFTER the local listener has succeeded — single
233
- unit atomicity (DD-019): if either step fails, the publication is
234
- recorded as `FAILED` and can be resubmitted via `FailedEventPublications`.
235
-
236
- ```typescript
237
- import { Externalized } from '@nestjs-transactional/outbox';
238
-
239
- @Externalized<OrderPlacedEvent>({
240
- target: 'orders.placed', // broker-side destination
241
- routingKey: (e) => e.tenantId, // optional, brokers that support it
242
- headers: (e) => ({ 'x-tenant': e.tenantId }), // static record OR callback
243
- client: 'KAFKA_CLIENT', // optional ClientProxy token
244
- })
245
- export class OrderPlacedEvent {
246
- constructor(
247
- readonly orderId: string,
248
- readonly tenantId: string,
249
- ) {}
250
- }
251
- ```
141
+ Shutdown is drained, not cut off: `OutboxProcessingModule` awaits the
142
+ batch already running before NestJS tears down the DataSource, so a
143
+ publication is not stranded mid-transition.
252
144
 
253
- Bind a concrete externalizer under the `EVENT_EXTERNALIZER` token
254
- this is normally done by an extension package (`outbox-microservices`).
255
- Custom implementations can register the same way:
145
+ Retry is opt-in because Spring Modulith has none either recovery is
146
+ otherwise an operator action. A publication that exhausts its attempts
147
+ stays `FAILED`; there is no separate dead-letter state, and it remains
148
+ visible and resubmittable
149
+ ([DD-026](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/dd/026-automatic-retry-policy.md)).
256
150
 
257
- ```typescript
258
- import { EVENT_EXTERNALIZER, type EventExternalizer } from '@nestjs-transactional/outbox';
151
+ Three injectable APIs for operators, matching Spring Modulith's:
259
152
 
260
- @Module({
261
- providers: [
262
- {
263
- provide: EVENT_EXTERNALIZER,
264
- useClass: MyCustomExternalizer, // implements EventExternalizer
265
- },
266
- ],
267
- })
268
- export class MyAppModule {}
153
+ ```ts
154
+ await failed.findAll({ minAge: 60_000 }); // triage
155
+ await failed.resubmit({ maxAttempts: 5 }); // retry by hand
156
+ await incomplete.count(); // anything not COMPLETED
157
+ await completed.purge(olderThan); // retention
269
158
  ```
270
159
 
271
- The processor only invokes the externalizer when BOTH a binding under
272
- `EVENT_EXTERNALIZER` exists AND the event class carries an
273
- `@Externalized` mapping — keep one without the other if you only need
274
- half the contract (e.g. metadata-only mappings during a rollout, or a
275
- generic externalizer that resolves targets some other way). Inspect
276
- the resolved mapping at runtime via `ExternalizationRegistry`:
277
-
278
- ```typescript
279
- import { ExternalizationRegistry } from '@nestjs-transactional/outbox';
160
+ ## Listener ids
280
161
 
281
- @Injectable()
282
- export class MyDiagnostics {
283
- constructor(private readonly externalization: ExternalizationRegistry) {}
162
+ Each publication row is keyed by `${baseId}#${EventName}`, where
163
+ `baseId` defaults to the handler's class name. Renaming the class
164
+ therefore orphans any stored publication for it. Pass an explicit id
165
+ when that is a risk:
284
166
 
285
- isExternalized(typeName: string): boolean {
286
- return this.externalization.has(typeName);
287
- }
288
- }
167
+ ```ts
168
+ @OutboxEventsHandler({ events: [OrderPlacedEvent], id: 'Billing.charge' })
289
169
  ```
290
170
 
291
- Errors raised by the externalizer are wrapped in `ExternalizationError`
292
- (carrying `eventType`, `target`, and the underlying cause) and surface
293
- on the publication's `failureReason` for operator visibility.
171
+ ## Testing
294
172
 
295
- ## Testing utilities
173
+ The `/testing` subpath ships an in-memory repository and fluent
174
+ assertions, so outbox behaviour is testable without a database:
296
175
 
297
- Exported via the `/testing` subpath for assertions about which events
298
- the code under test published. Mirrors Spring Modulith's
299
- `PublishedEvents` / `AssertablePublishedEvents`.
300
-
301
- ```typescript
176
+ ```ts
302
177
  import {
303
- PublishedEvents,
304
178
  AssertablePublishedEvents,
179
+ InMemoryEventPublicationRepository,
305
180
  } from '@nestjs-transactional/outbox/testing';
306
181
 
307
- describe('PlaceOrder', () => {
308
- let app: TestingModule;
309
- let assertablePublishedEvents: AssertablePublishedEvents;
310
-
311
- beforeEach(async () => {
312
- app = await Test.createTestingModule({
313
- imports: [
314
- TransactionalModule.forRoot({ isGlobal: true /* ... */ }),
315
- OutboxModule.forRoot({}),
316
- OutboxModule.forFeature([OrderPlacedEvent]),
317
- ],
318
- providers: [
319
- PlaceOrderService,
320
- PublishedEvents,
321
- AssertablePublishedEvents,
322
- ],
323
- }).compile();
324
- await app.init();
325
- assertablePublishedEvents = app.get(AssertablePublishedEvents);
326
- });
327
-
328
- it('publishes OrderPlacedEvent for the placed order', async () => {
329
- const service = app.get(PlaceOrderService);
330
- await service.place('order-123');
331
-
332
- const view = await assertablePublishedEvents.contains(OrderPlacedEvent);
333
- view.matching((e) => e.orderId, 'order-123').hasSize(1);
334
- });
335
-
336
- it('publishes nothing when validation rejects the command', async () => {
337
- const service = app.get(PlaceOrderService);
338
- await service.placeInvalid().catch(() => undefined);
339
-
340
- await assertablePublishedEvents.doesNotContain(OrderPlacedEvent);
341
- });
342
- });
182
+ (await assertable.contains(OrderPlacedEvent))
183
+ .matching((e) => e.orderId, 'o-1')
184
+ .hasSize(1);
185
+
186
+ // The negative path matters just as much: after a rollback, the
187
+ // in-memory repository has removed the publication.
188
+ await assertable.doesNotContain(OrderCancelledEvent);
343
189
  ```
344
190
 
345
- `PublishedEvents.ofType(EventType)` returns a fluent `PublishedEventsView`
346
- for read-only inspection (`.get()`, `.count()`,
347
- `.matching(predicate)`, `.matching(getter, expected)`).
348
- `AssertablePublishedEvents.contains(EventType)` is the assertion-first
349
- counterpart it throws `PublishedEventsAssertionError` when zero
350
- events match, and returns an `AssertionView` whose `.matching(...)`
351
- and `.hasSize(...)` operate synchronously over the already-fetched
352
- events.
353
-
354
- The utilities read through the wired `EventPublicationRepository`
355
- implementation, so the default `InMemoryEventPublicationRepository`
356
- works out of the box for unit-level tests. Integration tests that use
357
- `@nestjs-transactional/outbox-typeorm` with a real Postgres get the
358
- same API for free.
359
-
360
- ## Worked examples
361
-
362
- - [`basic-outbox`](../../examples/basic-outbox) — `@OutboxEventsHandler` + `OutboxEventPublisher`, in-memory test repository.
363
- - [`basic-typeorm-outbox`](../../examples/basic-typeorm-outbox) — production-shape outbox with Postgres, atomicity proven via testcontainers.
364
- - [`multi-datasource-outbox`](../../examples/multi-datasource-outbox) — per-DS `event_publication` tables (ADR-019 multi-`forRoot`).
365
- - [`shared-database-modular-monolith`](../../examples/shared-database-modular-monolith) — one Postgres, multi-schema, per-module outbox stacks.
366
- - [`saga-pattern`](../../examples/saga-pattern) — choreographed multi-step business saga over outbox events.
367
- - [`audit-logging`](../../examples/audit-logging) — cross-DS audit trail with idempotency on the consumer.
368
- - [`e-commerce-orders`](../../examples/e-commerce-orders) — three-bounded-context flagship wired through outbox integration events.
369
-
370
- Full catalogue: [examples/README.md](../../examples/README.md).
371
-
372
- ## Status
373
-
374
- Alpha. Public API may change between 0.x releases. Current
375
- functionality is exercised end-to-end through the
376
- [Tier 1–5 example library](../../examples/README.md).
377
-
378
- Design notes:
379
- [`docs/roadmap/README.md`](../../docs/roadmap/README.md),
380
- [`docs/adr/006-outbox-pattern.md`](../../docs/adr/006-outbox-pattern.md).
381
-
382
- ## Inspired by Spring Modulith
383
-
384
- The design follows
385
- [Spring Modulith's Event Publication Registry](https://docs.spring.io/spring-modulith/reference/events.html)
386
- closely — lifecycle states, `@ApplicationModuleListener` semantics,
387
- completion modes, and staleness monitoring all map one-to-one. The
388
- deviations from Spring Modulith are limited to what is needed to fit
389
- the Node.js / NestJS runtime (async workers instead of thread pools,
390
- AsyncLocalStorage for transaction context, NestJS DI conventions,
391
- class-level handler decorators aligned with `@nestjs/cqrs`
392
- conventions — see ADR-014).
191
+ ## Sending events to a broker
192
+
193
+ `@Externalized` marks an event for forwarding after its local handlers
194
+ complete. The `EVENT_EXTERNALIZER` SPI is transport-agnostic;
195
+ `outbox-microservices` implements it over `@nestjs/microservices`.
196
+ **Read that package's reliability note before relying on it in
197
+ production** — `ClientProxy.emit()` cannot report broker-side failure,
198
+ so a publication can be marked `COMPLETED` when nothing reached the
199
+ broker
200
+ ([ADR-016](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/016-externalization-reliability-semantics.md)).
201
+
202
+ ## Documentation
203
+
204
+ - [Getting started and full docs](https://github.com/igorgolovanov/nestjs-transactional#readme)
205
+ - [Architecture: the outbox pattern](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/architecture/outbox-pattern.md)
206
+ - [Migrating to the outbox](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/guides/migrating-to-outbox.md)
207
+ - [One `forRoot` per dataSource (ADR-019)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/019-outbox-multi-forroot-pattern.md)
208
+ - Runnable examples:
209
+ [`basic-outbox`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/basic-outbox),
210
+ [`saga-pattern`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/saga-pattern),
211
+ [`audit-logging`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/audit-logging),
212
+ [`graceful-shutdown`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/graceful-shutdown)
393
213
 
394
214
  ## License
395
215
 
@@ -1 +1 @@
1
- {"version":3,"file":"failed-event-publications.js","sourceRoot":"","sources":["../../src/api/failed-event-publications.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAEpD,6FAGoD;AAEpD,oEAAgE;AAChE,wEAAoE;AAGpE;;;;GAIG;AAEI,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAGf;IAFnB,YAEmB,UAAsC;QAAtC,eAAU,GAAV,UAAU,CAA4B;IACtD,CAAC;IAEJ;;;;OAIG;IACH,KAAK,CAAC,OAAO,CAAC,OAGb;QACC,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,KAAK;QACT,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QAClD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,QAAQ,CACZ,UAA+B,0CAAmB,CAAC,QAAQ,EAAE;QAE7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAC9C,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,WAAW,EAAE,OAAO,CAAC,qBAAqB,IAAI,SAAS;SACxD,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAClF,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QAEvD,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,sCAAiB,CAAC,WAAW,EAAE;gBACxE,oBAAoB,EAAE,IAAI,IAAI,EAAE;aACjC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,SAAS,CAAC,MAAM,CAAC;IAC1B,CAAC;CACF,CAAA;AAtDY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,2DAA4B,CAAC,CAAA;;GAF5B,uBAAuB,CAsDnC"}
1
+ {"version":3,"file":"failed-event-publications.js","sourceRoot":"","sources":["../../src/api/failed-event-publications.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAEpD,6FAGoD;AAEpD,oEAAgE;AAChE,wEAAoE;AAEpE;;;;GAIG;AAEI,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAGf;IAFnB,YAEmB,UAAsC;QAAtC,eAAU,GAAV,UAAU,CAA4B;IACtD,CAAC;IAEJ;;;;OAIG;IACH,KAAK,CAAC,OAAO,CAAC,OAGb;QACC,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,KAAK;QACT,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QAClD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,QAAQ,CAAC,UAA+B,0CAAmB,CAAC,QAAQ,EAAE;QAC1E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAC9C,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,WAAW,EAAE,OAAO,CAAC,qBAAqB,IAAI,SAAS;SACxD,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAClF,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QAEvD,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,sCAAiB,CAAC,WAAW,EAAE;gBACxE,oBAAoB,EAAE,IAAI,IAAI,EAAE;aACjC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,SAAS,CAAC,MAAM,CAAC;IAC1B,CAAC;CACF,CAAA;AApDY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,2DAA4B,CAAC,CAAA;;GAF5B,uBAAuB,CAoDnC"}
@@ -52,8 +52,7 @@ let IncompleteEventPublications = class IncompleteEventPublications {
52
52
  const toProcess = filtered.slice(0, options.batchSize);
53
53
  let resubmitted = 0;
54
54
  for (const pub of toProcess) {
55
- if (pub.status === publication_status_1.PublicationStatus.FAILED ||
56
- pub.status === publication_status_1.PublicationStatus.PUBLISHED) {
55
+ if (pub.status === publication_status_1.PublicationStatus.FAILED || pub.status === publication_status_1.PublicationStatus.PUBLISHED) {
57
56
  await this.repository.updateStatus(pub.id, publication_status_1.PublicationStatus.RESUBMITTED, {
58
57
  lastResubmissionDate: new Date(),
59
58
  });
@@ -1 +1 @@
1
- {"version":3,"file":"incomplete-event-publications.js","sourceRoot":"","sources":["../../src/api/incomplete-event-publications.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAEpD,6FAGoD;AAEpD,oEAAgE;AAChE,wEAAoE;AAGpE;;;;;;;GAOG;AAEI,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;IAGnB;IAFnB,YAEmB,UAAsC;QAAtC,eAAU,GAAV,UAAU,CAA4B;IACtD,CAAC;IAEJ,gEAAgE;IAChE,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;IAC1C,CAAC;IAED,2DAA2D;IAC3D,KAAK,CAAC,KAAK;QACT,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1D,OAAO,UAAU,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,8BAA8B,CAClC,UAA+B,0CAAmB,CAAC,QAAQ,EAAE;QAE7D,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;QAE1D,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;QAC1F,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QAEvD,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,IACE,GAAG,CAAC,MAAM,KAAK,sCAAiB,CAAC,MAAM;gBACvC,GAAG,CAAC,MAAM,KAAK,sCAAiB,CAAC,SAAS,EAC1C,CAAC;gBACD,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,sCAAiB,CAAC,WAAW,EAAE;oBACxE,oBAAoB,EAAE,IAAI,IAAI,EAAE;iBACjC,CAAC,CAAC;gBACH,WAAW,EAAE,CAAC;YAChB,CAAC;QACH,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAA;AA/CY,kEAA2B;sCAA3B,2BAA2B;IADvC,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,2DAA4B,CAAC,CAAA;;GAF5B,2BAA2B,CA+CvC"}
1
+ {"version":3,"file":"incomplete-event-publications.js","sourceRoot":"","sources":["../../src/api/incomplete-event-publications.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAEpD,6FAGoD;AAEpD,oEAAgE;AAChE,wEAAoE;AAEpE;;;;;;;GAOG;AAEI,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;IAGnB;IAFnB,YAEmB,UAAsC;QAAtC,eAAU,GAAV,UAAU,CAA4B;IACtD,CAAC;IAEJ,gEAAgE;IAChE,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;IAC1C,CAAC;IAED,2DAA2D;IAC3D,KAAK,CAAC,KAAK;QACT,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;QAC1D,OAAO,UAAU,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,8BAA8B,CAClC,UAA+B,0CAAmB,CAAC,QAAQ,EAAE;QAE7D,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;QAE1D,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;QAC1F,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QAEvD,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,MAAM,KAAK,sCAAiB,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,sCAAiB,CAAC,SAAS,EAAE,CAAC;gBAC1F,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,sCAAiB,CAAC,WAAW,EAAE;oBACxE,oBAAoB,EAAE,IAAI,IAAI,EAAE;iBACjC,CAAC,CAAC;gBACH,WAAW,EAAE,CAAC;YAChB,CAAC;QACH,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAA;AA5CY,kEAA2B;sCAA3B,2BAA2B;IADvC,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,2DAA4B,CAAC,CAAA;;GAF5B,2BAA2B,CA4CvC"}
@@ -76,7 +76,7 @@ export interface OutboxEventsHandlerMetadata {
76
76
  * `Reflect.defineMetadata`. The actual registration happens at
77
77
  * application bootstrap via `OutboxListenerScanner`.
78
78
  *
79
- * **Multi-dataSource (Phase 14.3.1).** The scanner walks every
79
+ * **Multi-dataSource.** The scanner walks every
80
80
  * per-dataSource `EventTypeRegistry` and routes the handler to the
81
81
  * `OutboxListenerRegistry` whose dataSource owns the decorated
82
82
  * events — automatic, no decorator option required. A handler
@@ -1 +1 @@
1
- {"version":3,"file":"outbox-events-handler.decorator.js","sourceRoot":"","sources":["../../src/decorators/outbox-events-handler.decorator.ts"],"names":[],"mappings":";;;AA+FA,kDAeC;AAmCD,wEAKC;AAtJD,4BAA0B;AAI1B;;;;;;;;;GASG;AACU,QAAA,8BAA8B,GAAG,MAAM,CAAC,gCAAgC,CAAC,CAAC;AAiFvF,SAAgB,mBAAmB,CACjC,GAAG,IAA2C;IAE9C,MAAM,QAAQ,GAAgC,eAAe,CAAC,IAAI,CAAC,CAAC;IAEpE,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,yDAAyD;YACvD,uEAAuE,CAC1E,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,MAAc,EAAQ,EAAE;QAC9B,OAAO,CAAC,cAAc,CAAC,sCAA8B,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3E,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,IAA2C;IAE3C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACxB,OAAO;YACL,UAAU,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;YAC/B,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,IAAI;SAC/C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,UAAU,EAAE,IAAc;QAC1B,cAAc,EAAE,IAAI;KACrB,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,SAAkB;IACzC,OAAO,CACL,SAAS,KAAK,IAAI;QAClB,OAAO,SAAS,KAAK,QAAQ;QAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QACzB,OAAO,SAAS,KAAK,UAAU;QAC/B,QAAQ,IAAI,SAAS,CACtB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAgB,8BAA8B,CAC5C,MAAc;IAEd,MAAM,KAAK,GAAY,OAAO,CAAC,WAAW,CAAC,sCAA8B,EAAE,MAAM,CAAC,CAAC;IACnF,OAAO,KAAgD,CAAC;AAC1D,CAAC"}
1
+ {"version":3,"file":"outbox-events-handler.decorator.js","sourceRoot":"","sources":["../../src/decorators/outbox-events-handler.decorator.ts"],"names":[],"mappings":";;;AA+FA,kDAeC;AAiCD,wEAKC;AApJD,4BAA0B;AAI1B;;;;;;;;;GASG;AACU,QAAA,8BAA8B,GAAG,MAAM,CAAC,gCAAgC,CAAC,CAAC;AAiFvF,SAAgB,mBAAmB,CACjC,GAAG,IAA2C;IAE9C,MAAM,QAAQ,GAAgC,eAAe,CAAC,IAAI,CAAC,CAAC;IAEpE,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,yDAAyD;YACvD,uEAAuE,CAC1E,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,MAAc,EAAQ,EAAE;QAC9B,OAAO,CAAC,cAAc,CAAC,sCAA8B,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3E,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAA2C;IAClE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACxB,OAAO;YACL,UAAU,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;YAC/B,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,IAAI;SAC/C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,UAAU,EAAE,IAAc;QAC1B,cAAc,EAAE,IAAI;KACrB,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,SAAkB;IACzC,OAAO,CACL,SAAS,KAAK,IAAI;QAClB,OAAO,SAAS,KAAK,QAAQ;QAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QACzB,OAAO,SAAS,KAAK,UAAU;QAC/B,QAAQ,IAAI,SAAS,CACtB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAgB,8BAA8B,CAC5C,MAAc;IAEd,MAAM,KAAK,GAAY,OAAO,CAAC,WAAW,CAAC,sCAA8B,EAAE,MAAM,CAAC,CAAC;IACnF,OAAO,KAAgD,CAAC;AAC1D,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import type { EventPublicationRegistry } from '../registry/event-publication-registry';
2
2
  import type { OutboxListenerRegistry } from '../registry/listener-registry';
3
3
  /**
4
- * Per-dataSource publisher (Phase 14.3). Owns the per-transaction
4
+ * Per-dataSource publisher. Owns the per-transaction
5
5
  * buffer, lifecycle hook registration, and delegate calls into the
6
6
  * `EventPublicationRegistry` bound to a single dataSource.
7
7
  *
@@ -60,8 +60,7 @@ export declare class DataSourceOutboxPublisher {
60
60
  * — `transactionManager.registerBeforeCommit` is NOT used because it
61
61
  * always targets "the first active transaction on the context",
62
62
  * which is the wrong target when multiple dataSources have live
63
- * transactions in the same async stack (Phase 14.2 cross-DS
64
- * simultaneous scenario).
63
+ * transactions in the same async stack.
65
64
  */
66
65
  scheduleForPublication(event: unknown): void;
67
66
  private ensureActiveTransaction;
@@ -4,7 +4,7 @@ exports.DataSourceOutboxPublisher = void 0;
4
4
  const common_1 = require("@nestjs/common");
5
5
  const core_1 = require("@nestjs-transactional/core");
6
6
  /**
7
- * Per-dataSource publisher (Phase 14.3). Owns the per-transaction
7
+ * Per-dataSource publisher. Owns the per-transaction
8
8
  * buffer, lifecycle hook registration, and delegate calls into the
9
9
  * `EventPublicationRegistry` bound to a single dataSource.
10
10
  *
@@ -85,8 +85,7 @@ class DataSourceOutboxPublisher {
85
85
  * — `transactionManager.registerBeforeCommit` is NOT used because it
86
86
  * always targets "the first active transaction on the context",
87
87
  * which is the wrong target when multiple dataSources have live
88
- * transactions in the same async stack (Phase 14.2 cross-DS
89
- * simultaneous scenario).
88
+ * transactions in the same async stack.
90
89
  */
91
90
  scheduleForPublication(event) {
92
91
  const tx = core_1.TransactionContext.getActiveTransactionByDataSource(this.dataSource);