@nestjs-transactional/outbox-microservices 1.0.0-alpha.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -266
- package/dist/externalizer/microservices-event-externalizer.d.ts +1 -1
- package/dist/externalizer/microservices-event-externalizer.js +3 -3
- package/dist/externalizer/microservices-event-externalizer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/module/outbox-microservices.module.d.ts +3 -3
- package/dist/module/outbox-microservices.module.js +3 -3
- package/dist/module/outbox-microservices.module.js.map +1 -1
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,173 +1,55 @@
|
|
|
1
1
|
# @nestjs-transactional/outbox-microservices
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@nestjs-transactional/outbox-microservices)
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
auto-configuration to the user's own `ClientsModule.register()`.
|
|
37
|
-
Function-based `routingKey` and `headers` callbacks replace SpEL
|
|
38
|
-
expressions; bring your own type system. Full table in the
|
|
39
|
-
architecture doc above.
|
|
40
|
-
|
|
41
|
-
## Status
|
|
42
|
-
|
|
43
|
-
Alpha. Public API may change between 0.x releases. Headers /
|
|
44
|
-
`routingKey` are accepted on `@Externalized` but not yet applied to
|
|
45
|
-
the wire payload — see *Limitations* below. End-to-end coverage
|
|
46
|
-
lives in the externalization examples (`externalization-kafka`,
|
|
47
|
-
`externalization-multi-broker`, `externalization-multi-datasource`,
|
|
48
|
-
`externalization-with-fallback`) and the
|
|
49
|
-
[`e-commerce-orders`](../../examples/e-commerce-orders) flagship —
|
|
50
|
-
see [Worked examples](#worked-examples).
|
|
51
|
-
|
|
52
|
-
## Important: reliability semantics (read before production use)
|
|
53
|
-
|
|
54
|
-
**Read this before adopting the package in production.** The
|
|
55
|
-
`@nestjs/microservices` `ClientProxy.emit()` API this package depends
|
|
56
|
-
on (per [DD-017](../../docs/dd/017-reuse-clients-module.md)) does NOT propagate broker-side
|
|
57
|
-
delivery failures in a way the externalizer can observe. In
|
|
58
|
-
fire-and-forget mode the Observable returned by `emit()` completes
|
|
59
|
-
when the proxy considers the dispatch *handed off to the transport*,
|
|
60
|
-
not when the broker has *durably acknowledged* the message.
|
|
61
|
-
|
|
62
|
-
Concretely, this means:
|
|
63
|
-
|
|
64
|
-
- A `ClientKafka` configured against an unreachable broker can resolve
|
|
65
|
-
`emit()` successfully, the externalizer reports success, and the
|
|
66
|
-
outbox publication is finalised as `COMPLETED` — even though no
|
|
67
|
-
message ever reached a broker.
|
|
68
|
-
- The same applies to RabbitMQ in default fire-and-forget mode and to
|
|
69
|
-
any other transport `@nestjs/microservices` supports.
|
|
70
|
-
- Silent broker failures bypass the outbox retry / staleness /
|
|
71
|
-
resubmit machinery: there is nothing to retry, because as far as
|
|
72
|
-
this layer is concerned delivery succeeded.
|
|
73
|
-
|
|
74
|
-
The outbox still gives you crash-consistent **enqueueing** of events
|
|
75
|
-
and at-least-once **local listener** delivery. What it does NOT give
|
|
76
|
-
you, in this version, is at-least-once **broker-side** delivery
|
|
77
|
-
through `ClientProxy`.
|
|
78
|
-
|
|
79
|
-
[ADR-016](../../docs/adr/016-externalization-reliability-semantics.md)
|
|
80
|
-
documents the finding in full and lays out the future path
|
|
81
|
-
(broker-aware externalizers using native producers under the same
|
|
82
|
-
`EVENT_EXTERNALIZER` SPI).
|
|
83
|
-
[ADR-015](../../docs/adr/015-event-externalization-architecture.md)
|
|
84
|
-
records why this trade-off is acceptable for the v1 scope —
|
|
85
|
-
[`docs/architecture/event-externalization.md`](../../docs/architecture/event-externalization.md)
|
|
86
|
-
has the full sequence diagram and the failure-mode table.
|
|
87
|
-
|
|
88
|
-
### Mitigation strategies for production
|
|
89
|
-
|
|
90
|
-
1. **Configure the underlying `ClientProxy` for stronger
|
|
91
|
-
acknowledgment.** Kafka: `producer.acks: 'all'` plus
|
|
92
|
-
`producer.idempotent: true`. RabbitMQ: confirm-channel via
|
|
93
|
-
`amqp-connection-manager`. NATS: JetStream with explicit ack. The
|
|
94
|
-
package reuses whatever proxy you registered (DD-017) — it does
|
|
95
|
-
not interfere with this configuration.
|
|
96
|
-
|
|
97
|
-
2. **Combine with consumer-side acknowledgment / inbox patterns.**
|
|
98
|
-
Track processed message ids on the receiving system and surface
|
|
99
|
-
gaps to operators. The outbox publication's listener id plus the
|
|
100
|
-
domain event id is enough to deduplicate.
|
|
101
|
-
|
|
102
|
-
3. **Wait for the broker-aware externalizer iteration** when neither
|
|
103
|
-
of the above is feasible. The
|
|
104
|
-
[`EVENT_EXTERNALIZER` SPI](../outbox/src/externalization/event-externalizer.ts)
|
|
105
|
-
is stable; native adapters will plug into the same place without
|
|
106
|
-
client-side changes.
|
|
107
|
-
|
|
108
|
-
## Installation
|
|
6
|
+
Forwards outbox events to a message broker through
|
|
7
|
+
`@nestjs/microservices`.
|
|
8
|
+
|
|
9
|
+
Mark an event `@Externalized`, and once its local handlers have
|
|
10
|
+
completed, [`@nestjs-transactional/outbox`](https://www.npmjs.com/package/@nestjs-transactional/outbox)
|
|
11
|
+
hands it to this package, which emits it over a `ClientProxy` you
|
|
12
|
+
already configured. One implementation covers every transport
|
|
13
|
+
`@nestjs/microservices` supports — Kafka, RabbitMQ, NATS, MQTT, Redis,
|
|
14
|
+
gRPC, and custom strategies.
|
|
15
|
+
|
|
16
|
+
> ## Read this before production
|
|
17
|
+
>
|
|
18
|
+
> `ClientProxy.emit()` cannot tell you whether the broker accepted the
|
|
19
|
+
> message. Its Observable completes when the transport has *accepted the
|
|
20
|
+
> handoff*, not when the broker has *durably acknowledged* — so a
|
|
21
|
+
> `ClientKafka` pointed at an unreachable broker resolves successfully,
|
|
22
|
+
> this package reports success, and the publication is finalised as
|
|
23
|
+
> `COMPLETED` even though nothing was ever delivered.
|
|
24
|
+
>
|
|
25
|
+
> Because the layer believes delivery succeeded, the outbox's retry,
|
|
26
|
+
> staleness and resubmit machinery never engages: there is no `FAILED`
|
|
27
|
+
> row to act on.
|
|
28
|
+
>
|
|
29
|
+
> What the outbox still guarantees is crash-consistent **enqueueing**
|
|
30
|
+
> and at-least-once delivery to **local** handlers. What it does not yet
|
|
31
|
+
> guarantee, through `ClientProxy`, is at-least-once delivery to the
|
|
32
|
+
> **broker**. Full analysis and the path forward:
|
|
33
|
+
> [ADR-016](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/016-externalization-reliability-semantics.md).
|
|
34
|
+
|
|
35
|
+
## Install
|
|
109
36
|
|
|
110
37
|
```bash
|
|
111
38
|
pnpm add @nestjs-transactional/outbox-microservices @nestjs-transactional/outbox @nestjs/microservices
|
|
112
39
|
```
|
|
113
40
|
|
|
114
|
-
|
|
115
|
-
`reflect-metadata`, and `rxjs` are peer dependencies (already present
|
|
116
|
-
in any NestJS application).
|
|
117
|
-
|
|
118
|
-
## Prerequisites
|
|
119
|
-
|
|
120
|
-
This package does NOT register `ClientProxy` instances — that is your
|
|
121
|
-
job (DD-017). Configure them through the standard
|
|
122
|
-
`@nestjs/microservices` `ClientsModule`:
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import { ClientsModule, Transport } from '@nestjs/microservices';
|
|
126
|
-
|
|
127
|
-
@Module({
|
|
128
|
-
imports: [
|
|
129
|
-
ClientsModule.register([
|
|
130
|
-
{
|
|
131
|
-
name: 'KAFKA_CLIENT',
|
|
132
|
-
transport: Transport.KAFKA,
|
|
133
|
-
options: {
|
|
134
|
-
client: { brokers: ['localhost:9092'] },
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
]),
|
|
138
|
-
],
|
|
139
|
-
})
|
|
140
|
-
export class AppModule {}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
`OutboxMicroservicesModule.forRoot({ defaultClient: 'KAFKA_CLIENT' })`
|
|
144
|
-
then resolves the same proxy via `ModuleRef.get(token, { strict: false })`
|
|
145
|
-
when an outbox publication is ready to be externalized — no parallel
|
|
146
|
-
connection pool, no second mental model.
|
|
41
|
+
## Quick start
|
|
147
42
|
|
|
148
|
-
|
|
43
|
+
This package does not create broker connections — you register clients
|
|
44
|
+
the standard way, and it reuses them.
|
|
149
45
|
|
|
150
|
-
```
|
|
151
|
-
import { Module } from '@nestjs/common';
|
|
46
|
+
```ts
|
|
152
47
|
import { ClientsModule, Transport } from '@nestjs/microservices';
|
|
153
|
-
import { TransactionalModule } from '@nestjs-transactional/core';
|
|
154
48
|
import { Externalized, OutboxModule } from '@nestjs-transactional/outbox';
|
|
155
49
|
import { OutboxMicroservicesModule } from '@nestjs-transactional/outbox-microservices';
|
|
156
50
|
|
|
157
|
-
@Externalized<OrderPlacedEvent>({
|
|
158
|
-
target: 'orders.placed',
|
|
159
|
-
routingKey: (e) => e.tenantId, // see Limitations — logged, not yet applied
|
|
160
|
-
})
|
|
161
|
-
export class OrderPlacedEvent {
|
|
162
|
-
constructor(
|
|
163
|
-
readonly orderId: string,
|
|
164
|
-
readonly tenantId: string,
|
|
165
|
-
) {}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
51
|
@Module({
|
|
169
52
|
imports: [
|
|
170
|
-
TransactionalModule.forRoot({ isGlobal: true }),
|
|
171
53
|
ClientsModule.register([
|
|
172
54
|
{
|
|
173
55
|
name: 'KAFKA_CLIENT',
|
|
@@ -175,137 +57,81 @@ export class OrderPlacedEvent {
|
|
|
175
57
|
options: { client: { brokers: ['localhost:9092'] } },
|
|
176
58
|
},
|
|
177
59
|
]),
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}),
|
|
60
|
+
|
|
61
|
+
// ...the usual outbox wiring...
|
|
62
|
+
|
|
63
|
+
OutboxMicroservicesModule.forRoot({ defaultClient: 'KAFKA_CLIENT' }),
|
|
183
64
|
],
|
|
184
65
|
})
|
|
185
66
|
export class AppModule {}
|
|
186
67
|
```
|
|
187
68
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
per [DD-019](../../docs/dd/019-hybrid-delivery-atomicity.md).
|
|
194
|
-
|
|
195
|
-
## Multiple clients
|
|
196
|
-
|
|
197
|
-
Register every transport you need under distinct tokens, then point
|
|
198
|
-
each event at its broker via the `client` option on `@Externalized`:
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
ClientsModule.register([
|
|
202
|
-
{ name: 'KAFKA_CLIENT', transport: Transport.KAFKA, options: { ... } },
|
|
203
|
-
{ name: 'AMQP_CLIENT', transport: Transport.RMQ, options: { ... } },
|
|
204
|
-
]),
|
|
205
|
-
|
|
206
|
-
@Externalized({ target: 'orders.placed', client: 'KAFKA_CLIENT' })
|
|
207
|
-
class OrderPlacedEvent { /* ... */ }
|
|
208
|
-
|
|
209
|
-
@Externalized({ target: 'audit', client: 'AMQP_CLIENT' })
|
|
210
|
-
class AuditableEvent { /* ... */ }
|
|
69
|
+
```ts
|
|
70
|
+
@Externalized({ target: 'orders.placed' })
|
|
71
|
+
export class OrderPlacedEvent {
|
|
72
|
+
constructor(public readonly orderId: string) {}
|
|
73
|
+
}
|
|
211
74
|
```
|
|
212
75
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
## Multi-dataSource setups
|
|
76
|
+
That is the whole integration: events without `@Externalized` stay
|
|
77
|
+
local, and events with it are emitted after their local handlers
|
|
78
|
+
finish. The proxy is resolved at publication time through `ModuleRef`,
|
|
79
|
+
so there is no second connection pool and no parallel configuration to
|
|
80
|
+
keep in sync
|
|
81
|
+
([DD-017](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/dd/017-reuse-clients-module.md)).
|
|
220
82
|
|
|
221
|
-
|
|
222
|
-
runs with multiple `OutboxModule.forRoot()` calls (one per
|
|
223
|
-
dataSource — ADR-019 multi-forRoot pattern). A single
|
|
224
|
-
`MicroservicesEventExternalizer` instance covers every dataSource;
|
|
225
|
-
per-DS processors all dispatch through it. Per-broker routing is
|
|
226
|
-
already handled by the per-event `client` parameter shown in
|
|
227
|
-
[*Multiple clients*](#multiple-clients) above — `@Externalized` is
|
|
228
|
-
dataSource-agnostic by design (an event class that lives in the
|
|
229
|
-
`'billing'` dataSource via `OutboxModule.forFeature([Event], { dataSource: 'billing' })`
|
|
230
|
-
still uses the same `client:` token resolution as a default-DS
|
|
231
|
-
event). See [`@nestjs-transactional/outbox` README](../outbox/README.md)
|
|
232
|
-
for the multi-`forRoot` pattern.
|
|
83
|
+
## Routing to several brokers
|
|
233
84
|
|
|
234
|
-
|
|
235
|
-
`EVENT_EXTERNALIZER` is visible to `OutboxModule`'s sibling-imported
|
|
236
|
-
per-DS processors without an explicit import chain.
|
|
85
|
+
Name a client per event; `defaultClient` covers the rest.
|
|
237
86
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
87
|
+
```ts
|
|
88
|
+
@Externalized({ target: 'orders.placed', client: 'KAFKA_CLIENT' })
|
|
89
|
+
export class OrderPlacedEvent {}
|
|
241
90
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
imports: [ConfigModule],
|
|
245
|
-
inject: [ConfigService],
|
|
246
|
-
useFactory: (config: ConfigService) => ({
|
|
247
|
-
defaultClient: config.getOrThrow<string>('outbox.defaultClient'),
|
|
248
|
-
}),
|
|
249
|
-
}),
|
|
91
|
+
@Externalized({ target: 'billing.invoice', client: 'RABBIT_CLIENT' })
|
|
92
|
+
export class InvoiceIssuedEvent {}
|
|
250
93
|
```
|
|
251
94
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
95
|
+
An unresolvable client name fails at bootstrap rather than at the first
|
|
96
|
+
publication, so a typo surfaces on deploy instead of in the middle of
|
|
97
|
+
the night. Pass `validateOnBootstrap: false` to defer resolution if you
|
|
98
|
+
register clients late.
|
|
99
|
+
|
|
100
|
+
## Reducing the risk
|
|
101
|
+
|
|
102
|
+
Given the reliability gap above, three things help — in order of how
|
|
103
|
+
much they buy you:
|
|
104
|
+
|
|
105
|
+
1. **Configure the proxy for stronger acknowledgement.** Kafka:
|
|
106
|
+
`producer.acks: 'all'` with `producer.idempotent: true`. RabbitMQ: a
|
|
107
|
+
confirm channel via `amqp-connection-manager`. NATS: JetStream with
|
|
108
|
+
explicit ack. This package reuses whatever you registered and does
|
|
109
|
+
not interfere.
|
|
110
|
+
2. **Deduplicate on the consumer.** Track processed message ids on the
|
|
111
|
+
receiving side and alert on gaps. The listener id plus the event id
|
|
112
|
+
is enough to identify a message.
|
|
113
|
+
3. **Wait for broker-aware externalizers** if neither is workable. The
|
|
114
|
+
`EVENT_EXTERNALIZER` SPI is stable, and native producer-based
|
|
115
|
+
implementations will slot into the same place without changes on your
|
|
116
|
+
side.
|
|
261
117
|
|
|
262
118
|
## Limitations
|
|
263
119
|
|
|
264
|
-
- **Headers and `routingKey
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
transport-specific (Kafka headers, AMQP properties, NATS subject
|
|
268
|
-
suffixes, ...). For now the externalizer logs resolved values at
|
|
269
|
-
debug level for visibility; the broker-aware message-construction
|
|
270
|
-
iteration ships in a later release. Wrap the event in a
|
|
271
|
-
transport-specific envelope inside your own code if you need them
|
|
272
|
-
before then.
|
|
273
|
-
- **Real-broker integration tests** (Postgres + Kafka / RabbitMQ via
|
|
274
|
-
testcontainers) are not bundled with this package — the unit and
|
|
275
|
-
module specs use a mock `ClientProxy` and cover the SPI contract
|
|
276
|
-
end-to-end without a live broker. The
|
|
277
|
-
[`externalization-with-fallback`](../../examples/externalization-with-fallback/)
|
|
278
|
-
example demonstrates a real-broker setup via docker-compose with
|
|
279
|
-
the ADR-016 silent-success limitation observable end-to-end.
|
|
280
|
-
|
|
281
|
-
## Testing
|
|
282
|
-
|
|
283
|
-
The package's own tests use a mock `ClientProxy` directly. To exercise
|
|
284
|
-
the externalizer in your application's tests, register a stub provider
|
|
285
|
-
under your client's token before the module imports:
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
const moduleRef = await Test.createTestingModule({
|
|
289
|
-
imports: [
|
|
290
|
-
/* ... ClientsModule.register([{ name: 'KAFKA_CLIENT', ... }]) */
|
|
291
|
-
OutboxMicroservicesModule.forRoot({ defaultClient: 'KAFKA_CLIENT' }),
|
|
292
|
-
],
|
|
293
|
-
})
|
|
294
|
-
.overrideProvider('KAFKA_CLIENT')
|
|
295
|
-
.useValue({ emit: jest.fn(() => of(undefined)) })
|
|
296
|
-
.compile();
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
## Worked examples
|
|
120
|
+
- **Headers and `routingKey`** are accepted by `@Externalized` but not
|
|
121
|
+
yet applied to the emitted payload.
|
|
122
|
+
- **Delivery is fire-and-forget** by design — see the note at the top.
|
|
300
123
|
|
|
301
|
-
|
|
302
|
-
- [`externalization-multi-broker`](../../examples/externalization-multi-broker) — Kafka + RabbitMQ + Redis pub/sub routed per event via `@Externalized({ client })`.
|
|
303
|
-
- [`externalization-multi-datasource`](../../examples/externalization-multi-datasource) — two physical Postgres × two `ClientProxy` registrations on a single broker.
|
|
304
|
-
- [`externalization-with-fallback`](../../examples/externalization-with-fallback) — ADR-016 silent-success demo + the three production mitigation patterns + `FailedEventPublications.resubmit` recovery flow.
|
|
305
|
-
- [`e-commerce-orders`](../../examples/e-commerce-orders) — flagship application; externalization is the terminal step of the order saga.
|
|
124
|
+
## Documentation
|
|
306
125
|
|
|
307
|
-
|
|
126
|
+
- [Getting started and full docs](https://github.com/igorgolovanov/nestjs-transactional#readme)
|
|
127
|
+
- [Reliability semantics (ADR-016)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/016-externalization-reliability-semantics.md)
|
|
128
|
+
- [Externalization architecture (ADR-015)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/adr/015-event-externalization-architecture.md)
|
|
129
|
+
- [Architecture: event externalization](https://github.com/igorgolovanov/nestjs-transactional/blob/main/docs/architecture/event-externalization.md)
|
|
130
|
+
- Runnable examples:
|
|
131
|
+
[`externalization-kafka`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/externalization-kafka),
|
|
132
|
+
[`externalization-multi-broker`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/externalization-multi-broker),
|
|
133
|
+
[`externalization-with-fallback`](https://github.com/igorgolovanov/nestjs-transactional/tree/main/examples/externalization-with-fallback)
|
|
308
134
|
|
|
309
135
|
## License
|
|
310
136
|
|
|
311
|
-
MIT
|
|
137
|
+
MIT
|
|
@@ -15,7 +15,7 @@ import { type OutboxMicroservicesOptions } from '../types/options';
|
|
|
15
15
|
* overrides via `@Externalized({ client })` resolve through the same
|
|
16
16
|
* `ModuleRef.get(token, { strict: false })` lookup.
|
|
17
17
|
*
|
|
18
|
-
* **Headers / routingKey limitation
|
|
18
|
+
* **Headers / routingKey limitation:** the
|
|
19
19
|
* `@nestjs/microservices` `ClientProxy.emit` API has no unified
|
|
20
20
|
* headers / routing-key parameter — handling is transport-specific
|
|
21
21
|
* (Kafka headers, AMQP properties, NATS subject suffixes, ...). For
|
|
@@ -32,7 +32,7 @@ const options_1 = require("../types/options");
|
|
|
32
32
|
* overrides via `@Externalized({ client })` resolve through the same
|
|
33
33
|
* `ModuleRef.get(token, { strict: false })` lookup.
|
|
34
34
|
*
|
|
35
|
-
* **Headers / routingKey limitation
|
|
35
|
+
* **Headers / routingKey limitation:** the
|
|
36
36
|
* `@nestjs/microservices` `ClientProxy.emit` API has no unified
|
|
37
37
|
* headers / routing-key parameter — handling is transport-specific
|
|
38
38
|
* (Kafka headers, AMQP properties, NATS subject suffixes, ...). For
|
|
@@ -91,11 +91,11 @@ let MicroservicesEventExternalizer = MicroservicesEventExternalizer_1 = class Mi
|
|
|
91
91
|
`Original error: ${causeMessage}`, metadata.eventType, metadata.target, cause);
|
|
92
92
|
}
|
|
93
93
|
if (metadata.headers !== undefined || metadata.routingKey !== undefined) {
|
|
94
|
-
//
|
|
94
|
+
// Current limitation — ClientProxy.emit has no unified
|
|
95
95
|
// headers / routing-key parameter. Logged for visibility; the
|
|
96
96
|
// broker-aware message construction iteration will route them
|
|
97
97
|
// through transport-specific envelopes.
|
|
98
|
-
this.logger.debug(`${metadata.eventType}: headers/routingKey are not applied to the wire payload in this version (
|
|
98
|
+
this.logger.debug(`${metadata.eventType}: headers/routingKey are not applied to the wire payload in this version (current limitation): ${JSON.stringify({ headers: metadata.headers, routingKey: metadata.routingKey })}`);
|
|
99
99
|
}
|
|
100
100
|
try {
|
|
101
101
|
await (0, rxjs_1.firstValueFrom)(client.emit(metadata.target, event));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"microservices-event-externalizer.js","sourceRoot":"","sources":["../../src/externalizer/microservices-event-externalizer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAMwB;AACxB,uCAAyC;AAEzC,yDAIsC;AACtC,+BAAsC;AAEtC,
|
|
1
|
+
{"version":3,"file":"microservices-event-externalizer.js","sourceRoot":"","sources":["../../src/externalizer/microservices-event-externalizer.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { MicroservicesEventExternalizer } from './externalizer/microservices-event-externalizer';
|
|
2
2
|
export { OutboxMicroservicesModule, type OutboxMicroservicesAsyncOptions, } from './module/outbox-microservices.module';
|
|
3
|
-
export { OUTBOX_MICROSERVICES_OPTIONS, type OutboxMicroservicesOptions
|
|
3
|
+
export { OUTBOX_MICROSERVICES_OPTIONS, type OutboxMicroservicesOptions } from './types/options';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,oGAAiG;AAAxF,kJAAA,8BAA8B,OAAA;AACvC,oFAG8C;AAF5C,wIAAA,yBAAyB,OAAA;AAG3B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,oGAAiG;AAAxF,kJAAA,8BAA8B,OAAA;AACvC,oFAG8C;AAF5C,wIAAA,yBAAyB,OAAA;AAG3B,2CAAgG;AAAvF,uHAAA,4BAA4B,OAAA"}
|
|
@@ -41,17 +41,17 @@ export interface OutboxMicroservicesAsyncOptions extends Pick<ModuleMetadata, 'i
|
|
|
41
41
|
* **Multi-dataSource setups**: a single externalizer covers every
|
|
42
42
|
* dataSource. Per-broker routing — when different events should land
|
|
43
43
|
* on different transports — happens via the per-event
|
|
44
|
-
* `@Externalized({ client })` parameter
|
|
44
|
+
* `@Externalized({ client })` parameter, not via a
|
|
45
45
|
* dataSource-keyed externalizer Map. See `outbox` README for the
|
|
46
46
|
* multi-`OutboxModule.forRoot()` pattern (ADR-019); each per-DS
|
|
47
47
|
* processor injects this same externalizer via `EVENT_EXTERNALIZER`.
|
|
48
48
|
*
|
|
49
|
-
* The module is registered as `@Global()`
|
|
49
|
+
* The module is registered as `@Global()` so the
|
|
50
50
|
* `EVENT_EXTERNALIZER` binding is visible to `OutboxModule`'s
|
|
51
51
|
* sibling-imported per-DS processors without an explicit import
|
|
52
52
|
* chain. Pre-Phase-14.6 the module was non-global, which silently
|
|
53
53
|
* broke the documented usage pattern in multi-module trees — fixed
|
|
54
|
-
*
|
|
54
|
+
* during verification work.
|
|
55
55
|
*/
|
|
56
56
|
export declare class OutboxMicroservicesModule {
|
|
57
57
|
static forRoot(options?: OutboxMicroservicesOptions): DynamicModule;
|
|
@@ -43,17 +43,17 @@ const options_1 = require("../types/options");
|
|
|
43
43
|
* **Multi-dataSource setups**: a single externalizer covers every
|
|
44
44
|
* dataSource. Per-broker routing — when different events should land
|
|
45
45
|
* on different transports — happens via the per-event
|
|
46
|
-
* `@Externalized({ client })` parameter
|
|
46
|
+
* `@Externalized({ client })` parameter, not via a
|
|
47
47
|
* dataSource-keyed externalizer Map. See `outbox` README for the
|
|
48
48
|
* multi-`OutboxModule.forRoot()` pattern (ADR-019); each per-DS
|
|
49
49
|
* processor injects this same externalizer via `EVENT_EXTERNALIZER`.
|
|
50
50
|
*
|
|
51
|
-
* The module is registered as `@Global()`
|
|
51
|
+
* The module is registered as `@Global()` so the
|
|
52
52
|
* `EVENT_EXTERNALIZER` binding is visible to `OutboxModule`'s
|
|
53
53
|
* sibling-imported per-DS processors without an explicit import
|
|
54
54
|
* chain. Pre-Phase-14.6 the module was non-global, which silently
|
|
55
55
|
* broke the documented usage pattern in multi-module trees — fixed
|
|
56
|
-
*
|
|
56
|
+
* during verification work.
|
|
57
57
|
*/
|
|
58
58
|
let OutboxMicroservicesModule = OutboxMicroservicesModule_1 = class OutboxMicroservicesModule {
|
|
59
59
|
static forRoot(options = {}) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbox-microservices.module.js","sourceRoot":"","sources":["../../src/module/outbox-microservices.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAMwB;AACxB,yDAAkE;AAElE,uGAAkG;AAClG,
|
|
1
|
+
{"version":3,"file":"outbox-microservices.module.js","sourceRoot":"","sources":["../../src/module/outbox-microservices.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAMwB;AACxB,yDAAkE;AAElE,uGAAkG;AAClG,8CAAiG;AAejG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEI,IAAM,yBAAyB,iCAA/B,MAAM,yBAAyB;IACpC,MAAM,CAAC,OAAO,CAAC,UAAsC,EAAE;QACrD,MAAM,SAAS,GAAe;YAC5B,EAAE,OAAO,EAAE,sCAA4B,EAAE,QAAQ,EAAE,OAAO,EAAE;YAC5D,iEAA8B;YAC9B,EAAE,OAAO,EAAE,2BAAkB,EAAE,WAAW,EAAE,iEAA8B,EAAE;SAC7E,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,2BAAyB;YACjC,MAAM,EAAE,IAAI;YACZ,SAAS;YACT,OAAO,EAAE,CAAC,2BAAkB,EAAE,iEAA8B,CAAC;SAC9D,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAAwC;QAC1D,MAAM,SAAS,GAAe;YAC5B;gBACE,OAAO,EAAE,sCAA4B;gBACrC,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;aACzD;YACD,iEAA8B;YAC9B,EAAE,OAAO,EAAE,2BAAkB,EAAE,WAAW,EAAE,iEAA8B,EAAE;SAC7E,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,2BAAyB;YACjC,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,SAAS;YACT,OAAO,EAAE,CAAC,2BAAkB,EAAE,iEAA8B,CAAC;SAC9D,CAAC;IACJ,CAAC;CACF,CAAA;AAnCY,8DAAyB;oCAAzB,yBAAyB;IADrC,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,yBAAyB,CAmCrC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs-transactional/outbox-microservices",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Event externalization for @nestjs-transactional/outbox via @nestjs/microservices ClientProxy — Spring Modulith @Externalized parity",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"sideEffects": false,
|
|
6
8
|
"author": "Igor Golovanov",
|
|
7
9
|
"repository": {
|
|
8
10
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/igorgolovanov/nestjs-transactional.git",
|
|
11
|
+
"url": "git+https://github.com/igorgolovanov/nestjs-transactional.git",
|
|
10
12
|
"directory": "packages/outbox-microservices"
|
|
11
13
|
},
|
|
12
14
|
"bugs": {
|
|
@@ -43,7 +45,6 @@
|
|
|
43
45
|
},
|
|
44
46
|
"publishConfig": {
|
|
45
47
|
"access": "public",
|
|
46
|
-
"tag": "alpha",
|
|
47
48
|
"provenance": true
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"@nestjs/microservices": "^10.0.0 || ^11.0.0",
|
|
53
54
|
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
54
55
|
"rxjs": "^7.0.0",
|
|
55
|
-
"@nestjs-transactional/outbox": "^1.0.0
|
|
56
|
+
"@nestjs-transactional/outbox": "^1.0.0"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@nestjs/common": "^11.0.0",
|
|
@@ -65,8 +66,8 @@
|
|
|
65
66
|
"rxjs": "^7.8.1",
|
|
66
67
|
"ts-jest": "^29.2.5",
|
|
67
68
|
"typescript": "^5.5.4",
|
|
68
|
-
"@nestjs-transactional/core": "1.0.0
|
|
69
|
-
"@nestjs-transactional/outbox": "1.0.0
|
|
69
|
+
"@nestjs-transactional/core": "1.0.0",
|
|
70
|
+
"@nestjs-transactional/outbox": "1.0.0"
|
|
70
71
|
},
|
|
71
72
|
"scripts": {
|
|
72
73
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -75,6 +76,9 @@
|
|
|
75
76
|
"test:watch": "jest --watch",
|
|
76
77
|
"test:cov": "jest --coverage",
|
|
77
78
|
"type-check": "tsc --noEmit",
|
|
78
|
-
"lint": "eslint \"src/**/*.ts\""
|
|
79
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
80
|
+
"api:check": "api-extractor run",
|
|
81
|
+
"api:update": "api-extractor run --local",
|
|
82
|
+
"publish:check": "publint && attw --pack ."
|
|
79
83
|
}
|
|
80
84
|
}
|