@ddd-ts-kit/nats 0.0.0-stage → 0.1.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 (158) hide show
  1. package/README.md +344 -2
  2. package/dist/codec/decode.d.ts +4 -0
  3. package/dist/codec/decode.d.ts.map +1 -0
  4. package/dist/codec/decode.js +39 -0
  5. package/dist/codec/decode.js.map +1 -0
  6. package/dist/codec/encode.d.ts +8 -0
  7. package/dist/codec/encode.d.ts.map +1 -0
  8. package/dist/codec/encode.js +19 -0
  9. package/dist/codec/encode.js.map +1 -0
  10. package/dist/codec/headers.d.ts +14 -0
  11. package/dist/codec/headers.d.ts.map +1 -0
  12. package/dist/codec/headers.js +14 -0
  13. package/dist/codec/headers.js.map +1 -0
  14. package/dist/codec/publication.d.ts +7 -0
  15. package/dist/codec/publication.d.ts.map +1 -0
  16. package/dist/codec/publication.js +2 -0
  17. package/dist/codec/publication.js.map +1 -0
  18. package/dist/codec/republication.d.ts +11 -0
  19. package/dist/codec/republication.d.ts.map +1 -0
  20. package/dist/codec/republication.js +16 -0
  21. package/dist/codec/republication.js.map +1 -0
  22. package/dist/connection/broker-link.d.ts +49 -0
  23. package/dist/connection/broker-link.d.ts.map +1 -0
  24. package/dist/connection/broker-link.js +160 -0
  25. package/dist/connection/broker-link.js.map +1 -0
  26. package/dist/consumption/consumption.d.ts +56 -0
  27. package/dist/consumption/consumption.d.ts.map +1 -0
  28. package/dist/consumption/consumption.js +223 -0
  29. package/dist/consumption/consumption.js.map +1 -0
  30. package/dist/consumption/failure-of.d.ts +7 -0
  31. package/dist/consumption/failure-of.d.ts.map +1 -0
  32. package/dist/consumption/failure-of.js +18 -0
  33. package/dist/consumption/failure-of.js.map +1 -0
  34. package/dist/consumption/poison-message.d.ts +3 -0
  35. package/dist/consumption/poison-message.d.ts.map +1 -0
  36. package/dist/consumption/poison-message.js +3 -0
  37. package/dist/consumption/poison-message.js.map +1 -0
  38. package/dist/consumption/track.d.ts +3 -0
  39. package/dist/consumption/track.d.ts.map +1 -0
  40. package/dist/consumption/track.js +9 -0
  41. package/dist/consumption/track.js.map +1 -0
  42. package/dist/errors/broker-unavailable-error.d.ts +8 -0
  43. package/dist/errors/broker-unavailable-error.d.ts.map +1 -0
  44. package/dist/errors/broker-unavailable-error.js +12 -0
  45. package/dist/errors/broker-unavailable-error.js.map +1 -0
  46. package/dist/errors/consumer-name-required-error.d.ts +5 -0
  47. package/dist/errors/consumer-name-required-error.d.ts.map +1 -0
  48. package/dist/errors/consumer-name-required-error.js +8 -0
  49. package/dist/errors/consumer-name-required-error.js.map +1 -0
  50. package/dist/errors/event-bus-closed-error.d.ts +5 -0
  51. package/dist/errors/event-bus-closed-error.d.ts.map +1 -0
  52. package/dist/errors/event-bus-closed-error.js +8 -0
  53. package/dist/errors/event-bus-closed-error.js.map +1 -0
  54. package/dist/errors/failure-code.d.ts +8 -0
  55. package/dist/errors/failure-code.d.ts.map +1 -0
  56. package/dist/errors/failure-code.js +19 -0
  57. package/dist/errors/failure-code.js.map +1 -0
  58. package/dist/errors/publish-not-acknowledged-error.d.ts +11 -0
  59. package/dist/errors/publish-not-acknowledged-error.d.ts.map +1 -0
  60. package/dist/errors/publish-not-acknowledged-error.js +16 -0
  61. package/dist/errors/publish-not-acknowledged-error.js.map +1 -0
  62. package/dist/ignore.d.ts +3 -0
  63. package/dist/ignore.d.ts.map +1 -0
  64. package/dist/ignore.js +5 -0
  65. package/dist/ignore.js.map +1 -0
  66. package/dist/index.d.ts +22 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +11 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/messaging-system.d.ts +3 -0
  71. package/dist/messaging-system.d.ts.map +1 -0
  72. package/dist/messaging-system.js +3 -0
  73. package/dist/messaging-system.js.map +1 -0
  74. package/dist/nats-event-bus.d.ts +57 -0
  75. package/dist/nats-event-bus.d.ts.map +1 -0
  76. package/dist/nats-event-bus.js +168 -0
  77. package/dist/nats-event-bus.js.map +1 -0
  78. package/dist/options/nats-delivery.d.ts +14 -0
  79. package/dist/options/nats-delivery.d.ts.map +1 -0
  80. package/dist/options/nats-delivery.js +2 -0
  81. package/dist/options/nats-delivery.js.map +1 -0
  82. package/dist/options/nats-event-bus-options.d.ts +40 -0
  83. package/dist/options/nats-event-bus-options.d.ts.map +1 -0
  84. package/dist/options/nats-event-bus-options.js +3 -0
  85. package/dist/options/nats-event-bus-options.js.map +1 -0
  86. package/dist/options/nats-failure.d.ts +17 -0
  87. package/dist/options/nats-failure.d.ts.map +1 -0
  88. package/dist/options/nats-failure.js +2 -0
  89. package/dist/options/nats-failure.js.map +1 -0
  90. package/dist/options/reconnect-options.d.ts +9 -0
  91. package/dist/options/reconnect-options.d.ts.map +1 -0
  92. package/dist/options/reconnect-options.js +2 -0
  93. package/dist/options/reconnect-options.js.map +1 -0
  94. package/dist/options/retry-options.d.ts +7 -0
  95. package/dist/options/retry-options.d.ts.map +1 -0
  96. package/dist/options/retry-options.js +2 -0
  97. package/dist/options/retry-options.js.map +1 -0
  98. package/dist/options/settings-of.d.ts +5 -0
  99. package/dist/options/settings-of.d.ts.map +1 -0
  100. package/dist/options/settings-of.js +55 -0
  101. package/dist/options/settings-of.js.map +1 -0
  102. package/dist/options/settings.d.ts +23 -0
  103. package/dist/options/settings.d.ts.map +1 -0
  104. package/dist/options/settings.js +2 -0
  105. package/dist/options/settings.js.map +1 -0
  106. package/dist/options/stream-storage.d.ts +2 -0
  107. package/dist/options/stream-storage.d.ts.map +1 -0
  108. package/dist/options/stream-storage.js +2 -0
  109. package/dist/options/stream-storage.js.map +1 -0
  110. package/dist/publication/publications.d.ts +25 -0
  111. package/dist/publication/publications.d.ts.map +1 -0
  112. package/dist/publication/publications.js +47 -0
  113. package/dist/publication/publications.js.map +1 -0
  114. package/dist/secrecy/conceal.d.ts +8 -0
  115. package/dist/secrecy/conceal.d.ts.map +1 -0
  116. package/dist/secrecy/conceal.js +12 -0
  117. package/dist/secrecy/conceal.js.map +1 -0
  118. package/dist/topology/binding-names-of.d.ts +5 -0
  119. package/dist/topology/binding-names-of.d.ts.map +1 -0
  120. package/dist/topology/binding-names-of.js +13 -0
  121. package/dist/topology/binding-names-of.js.map +1 -0
  122. package/dist/topology/binding-names.d.ts +10 -0
  123. package/dist/topology/binding-names.d.ts.map +1 -0
  124. package/dist/topology/binding-names.js +2 -0
  125. package/dist/topology/binding-names.js.map +1 -0
  126. package/dist/topology/consumer-binding.d.ts +6 -0
  127. package/dist/topology/consumer-binding.d.ts.map +1 -0
  128. package/dist/topology/consumer-binding.js +2 -0
  129. package/dist/topology/consumer-binding.js.map +1 -0
  130. package/dist/topology/consumer-description.d.ts +9 -0
  131. package/dist/topology/consumer-description.d.ts.map +1 -0
  132. package/dist/topology/consumer-description.js +2 -0
  133. package/dist/topology/consumer-description.js.map +1 -0
  134. package/dist/topology/dead-stream-of.d.ts +2 -0
  135. package/dist/topology/dead-stream-of.d.ts.map +1 -0
  136. package/dist/topology/dead-stream-of.js +4 -0
  137. package/dist/topology/dead-stream-of.js.map +1 -0
  138. package/dist/topology/declare-topology.d.ts +8 -0
  139. package/dist/topology/declare-topology.d.ts.map +1 -0
  140. package/dist/topology/declare-topology.js +58 -0
  141. package/dist/topology/declare-topology.js.map +1 -0
  142. package/dist/topology/nats-topology.d.ts +8 -0
  143. package/dist/topology/nats-topology.d.ts.map +1 -0
  144. package/dist/topology/nats-topology.js +2 -0
  145. package/dist/topology/nats-topology.js.map +1 -0
  146. package/dist/topology/stream-description.d.ts +11 -0
  147. package/dist/topology/stream-description.d.ts.map +1 -0
  148. package/dist/topology/stream-description.js +2 -0
  149. package/dist/topology/stream-description.js.map +1 -0
  150. package/dist/topology/topology-of.d.ts +5 -0
  151. package/dist/topology/topology-of.d.ts.map +1 -0
  152. package/dist/topology/topology-of.js +29 -0
  153. package/dist/topology/topology-of.js.map +1 -0
  154. package/dist/topology/topology-settings.d.ts +11 -0
  155. package/dist/topology/topology-settings.d.ts.map +1 -0
  156. package/dist/topology/topology-settings.js +2 -0
  157. package/dist/topology/topology-settings.js.map +1 -0
  158. package/package.json +39 -4
package/README.md CHANGED
@@ -1,3 +1,345 @@
1
- # Temporary Holding Version
1
+ # @ddd-ts-kit/nats
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The integration event provider for NATS JetStream: an `EventBus` of the kernel that publishes
4
+ to a stream and waits for its acknowledgement, with the event id as message id so the stream
5
+ drops a relay retry, and consumes from one durable pull consumer per consumer and event type
6
+ with explicit acknowledgement, a bounded number of delayed redeliveries and a dead-letter
7
+ stream. It is the JetStream equivalent of the "queue brokers" row of the strategy table of
8
+ `docs/ARCHITECTURE.md`, and it passes `eventBusContract` of `@ddd-ts-kit/testing`.
9
+
10
+ ```
11
+ npm i @ddd-ts-kit/nats @ddd-ts-kit/events @nats-io/transport-node @nats-io/jetstream
12
+ ```
13
+
14
+ Tested with `@nats-io/transport-node` and `@nats-io/jetstream` 3.4 against NATS Server 2.15
15
+ started with `-js`, on Node 24. Core NATS has no persistence: only JetStream fits the ports.
16
+
17
+ ## The client
18
+
19
+ The modular client of the NATS project: `@nats-io/transport-node` for the connection,
20
+ `@nats-io/jetstream` for streams and consumers. Both are at 3.4.0 (published 2026-05-13),
21
+ pure JavaScript, 1.7 MB installed with their three dependencies, without install script, and
22
+ downloaded about 400 000 and 300 000 times a week. The legacy `nats` package (2.29) still
23
+ receives fixes, but it is the previous line of the client; nothing made it necessary.
24
+
25
+ ## Composition root
26
+
27
+ Handlers never see this package. They publish to the outbox; the relay publishes to NATS;
28
+ each consuming context listens through the bus under its own name. The bus is given to the
29
+ application as two ports of the kernel: `IntegrationEventPublisher` stays the outbox, and
30
+ `EventSubscribers` hands each context the subscriber of its own consumer name.
31
+
32
+ `init --broker nats` writes this wiring in the platform of the project: the bus is built by
33
+ `platform/events/create-event-provider.ts`, `platform/platform.module.ts` rebinds
34
+ `EventSubscribers` on it, and `main.ts` gives it, traced, to the outbox relay. By hand, the
35
+ same thing reads:
36
+
37
+ ```ts
38
+ // platform/messaging.module.ts
39
+ import { defineContextModule } from '@ddd-ts-kit/di';
40
+ import { EventSubscribers, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
41
+ import { OutboxPublisher, OutboxStore } from '@ddd-ts-kit/events';
42
+ import { NatsEventBus } from '@ddd-ts-kit/nats';
43
+
44
+ export const bus = new NatsEventBus({
45
+ connection: config.natsUrl, // or a list, or options of the client, or a factory opening a new connection
46
+ stream: 'integration-events', // the default
47
+ subjectPrefix: 'events', // the default: events go to events.<event type>
48
+ replicas: 3, // default 1
49
+ retry: { maxAttempts: 5, delayMs: 5_000 }, // the defaults
50
+ prefetch: 10, // the default
51
+ onFailure: (failure) => logger.warn('event bus failure', { ...failure }), // codes only, never an error message
52
+ onDelivery: ({ consumer, outcome }) => deliveries.add(1, { consumer, outcome }),
53
+ });
54
+
55
+ export const messagingModule = defineContextModule({
56
+ name: 'messaging',
57
+ register(builder) {
58
+ // Publishing side: handlers write to the outbox, in the transaction of the command.
59
+ builder.rebind(IntegrationEventPublisher).toClass(OutboxPublisher, [OutboxStore]);
60
+ // Consuming side: one subscriber per consuming context, named after it.
61
+ builder.rebind(EventSubscribers).toValue({ forContext: (name) => bus.forConsumer(name) });
62
+ },
63
+ });
64
+ ```
65
+
66
+ ```ts
67
+ // contexts/billing/application/integration/handlers/invoice-on-order-placed.integration-handler.ts
68
+ import { CommandBus, systemActor, type IntegrationEvent } from '@ddd-ts-kit/kernel';
69
+ import { IntegrationEventHandler } from '@ddd-ts-kit/kernel/declarations';
70
+
71
+ @IntegrationEventHandler(orderPlaced, { consumer: 'billing.invoice-on-order-placed' }, [CommandBus])
72
+ export class InvoiceOnOrderPlacedIntegrationHandler {
73
+ constructor(private readonly commands: CommandBus) {}
74
+
75
+ // Nobody is calling: the handler acts as a system actor named after its consumer.
76
+ readonly actor = systemActor('billing.invoice-on-order-placed');
77
+
78
+ async handle(event: IntegrationEvent<OrderPlacedPayload>): Promise<void> {
79
+ await this.commands.execute(new IssueInvoice(this.actor, event.payload.orderId));
80
+ }
81
+ }
82
+ ```
83
+
84
+ ```ts
85
+ // main.ts
86
+ import { contextModule, createApplication, kernelModule } from '@ddd-ts-kit/di';
87
+ import { Clock } from '@ddd-ts-kit/kernel';
88
+ import { OutboxRelay, OutboxStore } from '@ddd-ts-kit/events';
89
+ import { bus, messagingModule } from '@/platform/messaging.module.js';
90
+
91
+ const app = createApplication({
92
+ modules: [
93
+ kernelModule(),
94
+ persistenceModule,
95
+ messagingModule,
96
+ contextModule('ordering', orderingDeclarations),
97
+ contextModule('billing', billingDeclarations),
98
+ ],
99
+ });
100
+
101
+ // The relay target is the bus. A record is marked published when the stream acknowledged it.
102
+ const relay = new OutboxRelay({ store: app.resolve(OutboxStore), publisher: bus, clock: app.resolve(Clock) });
103
+
104
+ await app.start(); // subscribes the handler of billing on bus.forConsumer('billing')
105
+ await bus.assertTopology(); // fail at startup, not at the first message
106
+ relay.start({ intervalMs: 500 });
107
+
108
+ process.on('SIGTERM', async () => {
109
+ await relay.stop(); // the tick in progress finishes
110
+ await app.stop(); // the handlers are unsubscribed
111
+ await bus.close(); // consumers stop, handlers in progress finish and acknowledge
112
+ });
113
+ ```
114
+
115
+ - `persistenceModule` binds `OutboxStore`, and rebinds `UnitOfWork` and `ProcessedEventStore`
116
+ to the database: see the README of the persistence adapter. A declared integration event
117
+ handler is subscribed on the subscriber of its own context (`forContext` receives the name
118
+ of its module), and since `UnitOfWork` and `ProcessedEventStore` are bound, its idempotency
119
+ mark and its work commit in one unit of work: a redelivery is deduplicated, a failure
120
+ leaves nothing behind.
121
+ - Without an outbox the bus is the event bus of the kernel module, and handlers publish
122
+ straight to the broker:
123
+ `kernelModule({ eventBus: bus, eventSubscribers: () => ({ forContext: (name) => bus.forConsumer(name) }) })`.
124
+ - Without a container the same objects are wired by hand: `bus.forConsumer('billing')` is an
125
+ `IntegrationEventSubscriber`, to give to `subscribeAll` and `consume` of `@ddd-ts-kit/events`.
126
+ - `publish(events)` resolves only when the stream acknowledged **every** message of the batch
127
+ as stored and rejects otherwise (`PUBLISH_NOT_ACKNOWLEDGED`, `BROKER_UNAVAILABLE`,
128
+ `EVENT_BUS_CLOSED`). Nothing is buffered for later: the outbox is the buffer.
129
+ - Each message carries the header `Nats-Msg-Id` = the event id. The stream drops a second
130
+ message with the same id inside its duplicate window (`duplicateWindowMs`, **two minutes**
131
+ by default, set when the stream is created) and still acknowledges it: a relay that
132
+ retries a publication whose acknowledgement was lost does not create a second message.
133
+ The window covers a retry, not a replay hours later: consumers still deduplicate.
134
+ - A consumer name is required to subscribe: `bus.forConsumer('billing')` gives an
135
+ `IntegrationEventSubscriber` for one context on the shared connection (a modular monolith
136
+ has several), and the `consumer` option names the one `bus.subscribe` works for (a service
137
+ that is a single context).
138
+ - Tracing is put around both sides from outside, as with any provider: `tracedPublisher(bus)`
139
+ as the relay target, and on the consuming side the instrumentation of the kernel module
140
+ (`kernelModule({ instrumentation: traceApplication() })`), which gives each declared handler
141
+ one span `process <consumer>` per attempt. `tracedSubscriber(bus.forConsumer(name))` is for
142
+ a composition without `kernelModule`. The bus carries `traceparent` and `correlationId`
143
+ untouched, in the envelope and in the message headers.
144
+ - The correlation makes the round trip. Publish through `scopedPublisher(publisher, scope)` of
145
+ the kernel: a declared handler (or `consume(definition, { ..., scope }, handler)` by hand,
146
+ or a callback wrapped with `inEventScope`) runs in an execution context whose
147
+ `correlationId` is the one of the request that published, whose `causationId` is the event,
148
+ and a redelivery runs in the same one.
149
+ - A context extracted to its own service keeps its consumer name and so its durable
150
+ consumers: nothing changes on the server.
151
+
152
+ ## Topology
153
+
154
+ `bus.describeTopology()` returns the streams and the durable consumers as data, for a review
155
+ or a generated document; `bus.assertTopology()` creates them, idempotently. The bus also
156
+ creates them on every new connection. For `forConsumer('billing')` subscribed to
157
+ `ordering.order.placed`:
158
+
159
+ | Object | Name | Properties |
160
+ |---|---|---|
161
+ | events stream | `integration-events` | subjects `events.>`, limits retention, file storage, `replicas`, duplicate window 2 min |
162
+ | subject | `events.ordering.order.placed` | `<subjectPrefix>.<event type>`: the type is several subject tokens, matched in full by the filter |
163
+ | durable consumer | `billing_ordering_order_placed` | pull, filter subject above, explicit acknowledgement, deliver all, `ack_wait` = `ackWaitMs` (30 s), `max_deliver` = 2 x `retry.maxAttempts` |
164
+ | dead-letter stream | `integration-events-dead` | subjects `dead.events.>`, same storage and replicas |
165
+ | dead-letter subject | `dead.events.billing.ordering.order.placed` | `dead.<subjectPrefix>.<consumer>.<event type>`: one per consumer and type |
166
+
167
+ A durable name holds neither dot nor wildcard: every character of the consumer name and of
168
+ the type outside `[A-Za-z0-9_-]` becomes `_`.
169
+
170
+ A message is the envelope as JSON (`id`, `type`, `version`, `occurredAt`, `payload`,
171
+ `traceparent`, `correlationId`) with the headers `Nats-Msg-Id` (the event id),
172
+ `x-event-type`, `x-event-version`, `traceparent` and `x-correlation-id`. Any NATS client can
173
+ consume it.
174
+
175
+ Two consumers of one type have a durable consumer each and both receive every event. Two
176
+ instances of one consumer pull from the same durable consumer: each event goes to one of them.
177
+
178
+ An existing stream is never altered: the bus accepts a stream of the same name whatever its
179
+ limits, so retention, maximum age and size are yours to set on it. An existing durable
180
+ consumer takes the `ack_wait` and the `max_deliver` of the bus that declares it.
181
+
182
+ ## Ordering
183
+
184
+ None. The stream keeps the publication order, but a consumer handles `prefetch` messages at
185
+ once, competing instances interleave, and a redelivery comes back after the messages that
186
+ followed. Carry a version in the payload, or use the Kafka provider when the order of an
187
+ aggregate matters.
188
+
189
+ ## Retry and dead-letter behaviour
190
+
191
+ 1. A message is acknowledged after every handler subscribed to its durable consumer
192
+ resolved. A declared `@IntegrationEventHandler` (or `consume` of `@ddd-ts-kit/events` by
193
+ hand) commits its work and its idempotency mark in one unit of work: the acknowledgement
194
+ then follows that commit, and a redelivery is deduplicated.
195
+ 2. When a handler throws, the bus refuses the message with a delay (`nak` with
196
+ `retry.delayMs`): the server delivers it again after that delay, to any instance. The
197
+ attempt number is the delivery count of the server. No hot loop.
198
+ 3. At the `retry.maxAttempts`-th failed delivery the bus publishes a copy to the dead-letter
199
+ subject, with `x-dead-reason: max-attempts`, `x-error-code`, `x-attempt` and `x-source`
200
+ (`<stream>/<sequence>`), waits for the acknowledgement of the dead-letter stream, then
201
+ terminates the original (`term`). If the copy cannot be stored the message is refused
202
+ with the delay instead, and parked at a later delivery.
203
+ 4. A message whose body is not an envelope (unparseable, or without `id`, `type`, `version`,
204
+ `occurredAt`) is poison: it goes straight to the dead-letter stream with
205
+ `x-dead-reason: poison` and `x-error-code: POISON_MESSAGE`, and no handler sees it.
206
+ 5. Only codes travel: `x-error-code` is the `code` of the error or its class name. The message
207
+ of an error is never logged, reported or attached, because it may quote payload data.
208
+
209
+ **Why an explicit copy and not the advisory.** When a message reaches `max_deliver` the
210
+ server stops delivering it and emits `$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES`. That
211
+ advisory is a core NATS message: lost if nobody listens at that instant, it carries a stream
212
+ sequence and no failure code, and the message itself stays in the stream where no consumer
213
+ will read it again. The bus knows the failure code and knows it is the last attempt, so it
214
+ parks the message itself, with an acknowledged publication.
215
+
216
+ **Why `max_deliver` is twice `maxAttempts`.** The delivery count also grows when a delivery
217
+ ends without a verdict: a process killed mid-handler, an acknowledgement wait that expired,
218
+ a connection cut. With `max_deliver` equal to `maxAttempts`, such a delivery could use the
219
+ last one, and the server would then drop the message from the consumer silently. The bus
220
+ parks at `maxAttempts`; the server limit is a backstop above it. A message that exhausts
221
+ even that stays in the stream, undelivered: the advisory above is the alert to have.
222
+
223
+ Delivery is at least once. The copy-then-terminate order means a crash in between delivers
224
+ the message again and parks it again: the dead-letter stream keeps one copy, because the copy
225
+ has a message id of its own (`<durable>.<sequence>`).
226
+
227
+ On a lost connection the client reconnects by itself and its pull consumers resume: streams
228
+ and durable consumers live on the server, nothing has to be declared again. While the server
229
+ is away `publish` rejects at once with `BROKER_UNAVAILABLE`, and a publication in flight
230
+ rejects after `publishTimeoutMs` (5 s by default) with `PUBLISH_NOT_ACKNOWLEDGED`, so the
231
+ outbox relay keeps the records and retries them. Messages that were being handled are
232
+ delivered again after `ackWaitMs`. The bus opens the first connection itself, with an
233
+ exponential backoff (`reconnect`, 200 ms doubling up to 30 s), and a new one if the client
234
+ ever gives up.
235
+
236
+ **Credentials.** The URL or the options of `connection` are held by the function that opens the connection, in no field. A `NatsEventBus` that is logged, spread or
237
+ serialised shows `NatsEventBus [connection concealed]` and nothing else: its fields are not enumerable,
238
+ and `toJSON` and `util.inspect` answer that name. The failures it reports are codes. What the
239
+ client library itself shows when one of its own objects is inspected is not in the hands of
240
+ this package: do not log those.
241
+
242
+ **What a handler is told.** Next to the event, each handler receives the delivery
243
+ (`EventDelivery` of the kernel): `{ attempt, maxAttempts, system: 'nats', destination, consumer }`,
244
+ where `destination` is the subject. Handlers of the application ignore it; `@ddd-ts-kit/tracing` reads it
245
+ to make one CONSUMER span per attempt and to mark the redeliveries and the attempt after which
246
+ the event is buried. The bus names itself `messagingSystem: 'nats'`, and `traceparent` travels in
247
+ the envelope and as a real header, intact across a retry.
248
+
249
+ ## Operations
250
+
251
+ What to watch:
252
+
253
+ | Signal | Where | Meaning |
254
+ |---|---|---|
255
+ | outbox `stats()` (`pending`, `failed`, `dead`) | the outbox store | the publishing side: see `@ddd-ts-kit/events` |
256
+ | `num_pending` of `<consumer>_<type>` | server (`nats consumer info`, the exporter) | the consumer lags or is stopped |
257
+ | `num_ack_pending` of the same | server | handled now or waiting for a redelivery; a plateau means a handler that keeps failing |
258
+ | messages in `<stream>-dead`, per subject | server (`nats stream subjects <stream>-dead`) | the dead-letter depth: must be 0, alert on anything else |
259
+ | `$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.<stream>.>` | server | a message the bus could not park before the server gave up on it |
260
+ | `onDelivery` counted by `outcome` | this bus | rate of `retried`, `dead`, `poison` against `acknowledged` |
261
+ | `onFailure` with `kind: 'connection'` | this bus | disconnections (`CONNECTION_LOST`) and requests the server refused (`JETSTREAM_<code>`) |
262
+ | `bus.deliveriesInFlight` | this bus | handlers running now; stuck above 0 means a handler that hangs |
263
+
264
+ Replaying dead messages, once the cause is fixed:
265
+
266
+ ```ts
267
+ const replayed = await bus.replayDead({ consumer: 'billing', type: 'ordering.order.placed' }, 100);
268
+ ```
269
+
270
+ It moves up to that many messages of that consumer from the dead-letter stream back to the
271
+ subject they came from, oldest first, each with an acknowledged copy before it is deleted
272
+ from the dead-letter stream, and with the header `x-replay-for` naming the durable consumer.
273
+ The other consumers of this kit acknowledge and skip a message marked for someone else; a
274
+ consumer written without this kit sees the event a second time and must deduplicate. The
275
+ copy is a new message: its delivery count starts again. Look at `x-dead-reason` and
276
+ `x-error-code` first: replaying a poison message only kills it again.
277
+
278
+ To stop a consumer for good, delete its durable consumers: unsubscribing leaves them, and a
279
+ stream with `interest` retention would keep their messages for ever.
280
+
281
+ ## Limits
282
+
283
+ - **No ordering**, see above.
284
+ - **One delay.** The wait is the same for every attempt. The `backoff` list of a JetStream
285
+ consumer is not used: it applies to expired acknowledgement waits, not to refusals.
286
+ - **A handler must finish within `ackWaitMs`** (30 s by default). Beyond it the server
287
+ delivers the message to another instance while the first still runs. The bus sends no
288
+ in-progress signal: raise `ackWaitMs` above the worst case of the handlers.
289
+ - **The duplicate window is short** and belongs to the stream: two minutes unless the stream
290
+ was created otherwise, and an existing stream keeps its own. It absorbs a relay retry, not
291
+ a record revived from the dead state of the outbox an hour later.
292
+ - **Retention is `limits`**, without limits: the events stream grows until you set a maximum
293
+ age or size on it. Do not switch it to `workqueue`: two consumers could no longer both get
294
+ every event. With `interest`, every durable consumer must exist before the first
295
+ publication it cares about.
296
+ - **A consumer created after a publication receives it** (deliver policy `all`, as long as
297
+ the stream retained it): a new consuming context replays the retained history of its types.
298
+ - **A redelivery runs every handler of the durable consumer again**, those that succeeded
299
+ included, when several handlers of one consumer subscribe to the same type. Each must be
300
+ idempotent.
301
+ - **A batch is not atomic.** When `publish` rejects, some events of the batch may have been
302
+ stored. The relay publishes one by one and retries; the stream deduplicates, then consumers.
303
+ - **An event no consumer listens to is stored anyway**: there is no equivalent of
304
+ `onUnroutable`.
305
+ - **Two names that differ only by a replaced character collide**: `billing.eu` and
306
+ `billing_eu` are the same durable consumer.
307
+ - **`replayDead` reads the dead-letter stream directly**, which needs `allow_direct` on it:
308
+ the bus sets it on the stream it creates; set it yourself on one you created.
309
+ - **A connection factory must open a new connection at each call** and should keep the
310
+ reconnection of the client unlimited (`maxReconnectAttempts: -1`, what the bus sets when it
311
+ is given a URL); the bus closes what it opened.
312
+ - Closing while the server is away does not wait for the acknowledgements in flight to be
313
+ sent: those messages are delivered again.
314
+
315
+ Not verified by the test suite: a cluster of several servers (replicas above 1, leader
316
+ election, a stream that loses its quorum), TLS and authentication, an acknowledgement wait
317
+ that expires during a handler, a consumer process killed mid-handler, the `MAX_DELIVERIES`
318
+ path beyond the bus limit, a durable consumer or a stream deleted while the bus runs (the bus
319
+ then reopens its connection and declares everything again; the path is not exercised),
320
+ `interest` retention, servers older than 2.15, and the WebSocket transport.
321
+
322
+ ## Other providers
323
+
324
+ `@ddd-ts-kit/rabbitmq` and `@ddd-ts-kit/kafka` implement the same two ports and pass the same
325
+ contract suite. The three share the envelope, the header names (`traceparent`,
326
+ `x-correlation-id`, `x-event-version`, `x-attempt`, `x-error-code`, `x-dead-reason`), the
327
+ option names (`consumer`, `retry`, `reconnect`, `onFailure`, `onDelivery`) and the methods
328
+ (`forConsumer`, `assertTopology`, `describeTopology`, `replayDead`, `close`).
329
+
330
+ ## Testing
331
+
332
+ The suites need a real server and are skipped without `DDD_TS_KIT_TEST_NATS_URL`:
333
+
334
+ ```
335
+ docker run -d --rm --name ddd-ts-kit-test-nats -p 127.0.0.1:4222:4222 nats:2.15.0-alpine -js
336
+ DDD_TS_KIT_TEST_NATS_URL=nats://127.0.0.1:4222 npx vitest run packages/nats
337
+ docker rm -f ddd-ts-kit-test-nats
338
+ ```
339
+
340
+ They run the contract suite on file and on memory storage, then the behaviours of this
341
+ provider, one suite per behaviour (`publishing`, with the deduplication inside and outside
342
+ the duplicate window, `consuming`, `correlation`, `retries`, `dead-letters`, `topology`,
343
+ `connection-loss` through a TCP relay the test cuts, `closing`) and the whole chain from the
344
+ outbox to an idempotent consumer. Every test works on streams of its own and
345
+ deletes them.
@@ -0,0 +1,4 @@
1
+ import type { IntegrationEvent } from '@ddd-ts-kit/kernel';
2
+ /** The envelope exactly as published, or `undefined` for a body that is not one (poison). */
3
+ export declare function decode(data: Uint8Array): IntegrationEvent | undefined;
4
+ //# sourceMappingURL=decode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../src/codec/decode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,6FAA6F;AAC7F,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,gBAAgB,GAAG,SAAS,CAgBrE"}
@@ -0,0 +1,39 @@
1
+ /** The envelope exactly as published, or `undefined` for a body that is not one (poison). */
2
+ export function decode(data) {
3
+ const body = parsed(data);
4
+ if (!isEnvelope(body)) {
5
+ return undefined;
6
+ }
7
+ return {
8
+ id: body.id,
9
+ type: body.type,
10
+ version: body.version,
11
+ occurredAt: body.occurredAt,
12
+ payload: body.payload,
13
+ ...(typeof body.traceparent === 'string' ? { traceparent: body.traceparent } : {}),
14
+ ...(typeof body.correlationId === 'string' ? { correlationId: body.correlationId } : {}),
15
+ };
16
+ }
17
+ function parsed(data) {
18
+ try {
19
+ return JSON.parse(new TextDecoder().decode(data));
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ function isEnvelope(body) {
26
+ if (typeof body !== 'object' || body === null) {
27
+ return false;
28
+ }
29
+ return (hasText(body, 'id') &&
30
+ hasText(body, 'type') &&
31
+ hasText(body, 'occurredAt') &&
32
+ 'version' in body &&
33
+ typeof body.version === 'number');
34
+ }
35
+ function hasText(body, field) {
36
+ const value = Reflect.get(body, field);
37
+ return typeof value === 'string' && value.length > 0;
38
+ }
39
+ //# sourceMappingURL=decode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode.js","sourceRoot":"","sources":["../../src/codec/decode.ts"],"names":[],"mappings":"AAEA,6FAA6F;AAC7F,MAAM,UAAU,MAAM,CAAC,IAAgB;IACrC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAE1B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,GAAG,CAAC,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,GAAG,CAAC,OAAO,IAAI,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzF,CAAC;AACJ,CAAC;AAYD,SAAS,MAAM,CAAC,IAAgB;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAa;IAC/B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CACL,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;QACnB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC;QAC3B,SAAS,IAAI,IAAI;QACjB,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CACjC,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,KAAa;IAC1C,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAEhD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { IntegrationEvent } from '@ddd-ts-kit/kernel';
2
+ import type { Publication } from './publication.js';
3
+ /**
4
+ * The body is the whole envelope as JSON, so any NATS client can consume it. The headers
5
+ * repeat what the stream, a tracer or an operator needs without opening the body.
6
+ */
7
+ export declare function encode(event: IntegrationEvent): Publication;
8
+ //# sourceMappingURL=encode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../src/codec/encode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAe3D"}
@@ -0,0 +1,19 @@
1
+ import { headers as emptyHeaders } from '@nats-io/transport-node';
2
+ import { CORRELATION_HEADER, EVENT_TYPE_HEADER, EVENT_VERSION_HEADER, TRACEPARENT_HEADER } from './headers.js';
3
+ /**
4
+ * The body is the whole envelope as JSON, so any NATS client can consume it. The headers
5
+ * repeat what the stream, a tracer or an operator needs without opening the body.
6
+ */
7
+ export function encode(event) {
8
+ const headers = emptyHeaders();
9
+ headers.set(EVENT_TYPE_HEADER, event.type);
10
+ headers.set(EVENT_VERSION_HEADER, String(event.version));
11
+ if (event.traceparent !== undefined) {
12
+ headers.set(TRACEPARENT_HEADER, event.traceparent);
13
+ }
14
+ if (event.correlationId !== undefined) {
15
+ headers.set(CORRELATION_HEADER, event.correlationId);
16
+ }
17
+ return { data: JSON.stringify(event), messageId: event.id, headers };
18
+ }
19
+ //# sourceMappingURL=encode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode.js","sourceRoot":"","sources":["../../src/codec/encode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAG/G;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,KAAuB;IAC5C,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;IAE/B,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAEzD,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC;AACvE,CAAC"}
@@ -0,0 +1,14 @@
1
+ /** The header the stream deduplicates on: the event id. */
2
+ export declare const MESSAGE_ID_HEADER = "Nats-Msg-Id";
3
+ export declare const EVENT_TYPE_HEADER = "x-event-type";
4
+ export declare const EVENT_VERSION_HEADER = "x-event-version";
5
+ export declare const TRACEPARENT_HEADER = "traceparent";
6
+ export declare const CORRELATION_HEADER = "x-correlation-id";
7
+ export declare const ATTEMPT_HEADER = "x-attempt";
8
+ export declare const ERROR_CODE_HEADER = "x-error-code";
9
+ export declare const DEAD_REASON_HEADER = "x-dead-reason";
10
+ /** Where a dead message came from: `<stream>/<sequence>`. */
11
+ export declare const SOURCE_HEADER = "x-source";
12
+ /** Set on a replayed message: the one durable consumer it is meant for. The others skip it. */
13
+ export declare const REPLAY_FOR_HEADER = "x-replay-for";
14
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/codec/headers.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAC/C,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AACtD,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAChD,eAAO,MAAM,kBAAkB,qBAAqB,CAAC;AACrD,eAAO,MAAM,cAAc,cAAc,CAAC;AAC1C,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,6DAA6D;AAC7D,eAAO,MAAM,aAAa,aAAa,CAAC;AACxC,+FAA+F;AAC/F,eAAO,MAAM,iBAAiB,iBAAiB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /** The header the stream deduplicates on: the event id. */
2
+ export const MESSAGE_ID_HEADER = 'Nats-Msg-Id';
3
+ export const EVENT_TYPE_HEADER = 'x-event-type';
4
+ export const EVENT_VERSION_HEADER = 'x-event-version';
5
+ export const TRACEPARENT_HEADER = 'traceparent';
6
+ export const CORRELATION_HEADER = 'x-correlation-id';
7
+ export const ATTEMPT_HEADER = 'x-attempt';
8
+ export const ERROR_CODE_HEADER = 'x-error-code';
9
+ export const DEAD_REASON_HEADER = 'x-dead-reason';
10
+ /** Where a dead message came from: `<stream>/<sequence>`. */
11
+ export const SOURCE_HEADER = 'x-source';
12
+ /** Set on a replayed message: the one durable consumer it is meant for. The others skip it. */
13
+ export const REPLAY_FOR_HEADER = 'x-replay-for';
14
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/codec/headers.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAC;AAC/C,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAChD,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AACtD,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAC;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB,CAAC;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAC;AAC1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAClD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;AACxC,+FAA+F;AAC/F,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { MsgHdrs } from '@nats-io/transport-node';
2
+ export interface Publication {
3
+ readonly data: string | Uint8Array;
4
+ readonly messageId: string;
5
+ readonly headers: MsgHdrs;
6
+ }
7
+ //# sourceMappingURL=publication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publication.d.ts","sourceRoot":"","sources":["../../src/codec/publication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=publication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publication.js","sourceRoot":"","sources":["../../src/codec/publication.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { type MsgHdrs } from '@nats-io/transport-node';
2
+ import type { Publication } from './publication.js';
3
+ export interface Republished {
4
+ readonly data: Uint8Array;
5
+ readonly headers: MsgHdrs | undefined;
6
+ /** Unique for this copy: the stream must not take it for a duplicate of the original. */
7
+ readonly messageId: string;
8
+ }
9
+ /** The same message, for another subject, with `added` headers set or replaced. */
10
+ export declare function republication(message: Republished, added: Record<string, string>): Publication;
11
+ //# sourceMappingURL=republication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"republication.d.ts","sourceRoot":"","sources":["../../src/codec/republication.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW,CAc9F"}
@@ -0,0 +1,16 @@
1
+ import { headers as emptyHeaders } from '@nats-io/transport-node';
2
+ import { MESSAGE_ID_HEADER } from './headers.js';
3
+ /** The same message, for another subject, with `added` headers set or replaced. */
4
+ export function republication(message, added) {
5
+ const headers = emptyHeaders();
6
+ for (const [name, values] of message.headers ?? []) {
7
+ for (const value of name === MESSAGE_ID_HEADER ? [] : values) {
8
+ headers.append(name, value);
9
+ }
10
+ }
11
+ for (const [name, value] of Object.entries(added)) {
12
+ headers.set(name, value);
13
+ }
14
+ return { data: message.data, messageId: message.messageId, headers };
15
+ }
16
+ //# sourceMappingURL=republication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"republication.js","sourceRoot":"","sources":["../../src/codec/republication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAgB,MAAM,yBAAyB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAUjD,mFAAmF;AACnF,MAAM,UAAU,aAAa,CAAC,OAAoB,EAAE,KAA6B;IAC/E,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACnD,KAAK,MAAM,KAAK,IAAI,IAAI,KAAK,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;YAC7D,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC;AACvE,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { inspect } from 'node:util';
2
+ import type { NatsConnection } from '@nats-io/transport-node';
3
+ import type { ReconnectOptions } from '../options/reconnect-options.js';
4
+ export interface LinkHooks {
5
+ /** Sets streams and consumers up on a fresh connection. A failure retries. */
6
+ opened(connection: NatsConnection): Promise<void>;
7
+ /** The connection is closed for good: what was set up on it is void. */
8
+ lost(): void;
9
+ failed(code: string): void;
10
+ }
11
+ export interface LinkSettings {
12
+ readonly open: () => Promise<NatsConnection>;
13
+ readonly reconnect: Required<ReconnectOptions>;
14
+ }
15
+ /**
16
+ * The one connection of a bus. The client reconnects by itself and resumes its consumers;
17
+ * the link opens the first connection, with a delay that grows with each failed attempt,
18
+ * knows when the server is away, and opens another connection if the client ever gives up.
19
+ */
20
+ export declare class BrokerLink {
21
+ private readonly settings;
22
+ private readonly hooks;
23
+ private connection;
24
+ private opening;
25
+ private retry;
26
+ private failures;
27
+ private away;
28
+ private closed;
29
+ constructor(settings: LinkSettings, hooks: LinkHooks);
30
+ /**
31
+ * The live connection. Opens the first one, joins an attempt in progress, and fails at
32
+ * once while the server is away so that a publisher never hangs on it.
33
+ */
34
+ opened(): Promise<NatsConnection>;
35
+ /** Drops the connection so that everything is rebuilt, after a consumer died on its own. */
36
+ restart(): void;
37
+ /** Sends what is buffered, acknowledgements included, unless the server is away; disconnects. */
38
+ close(): Promise<void>;
39
+ toJSON(): string;
40
+ [inspect.custom](): string;
41
+ private present;
42
+ private open;
43
+ private establish;
44
+ private connect;
45
+ private watch;
46
+ private lost;
47
+ private failed;
48
+ }
49
+ //# sourceMappingURL=broker-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker-link.d.ts","sourceRoot":"","sources":["../../src/connection/broker-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAK9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAGxE,MAAM,WAAW,SAAS;IACxB,8EAA8E;IAC9E,MAAM,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,wEAAwE;IACxE,IAAI,IAAI,IAAI,CAAC;IACb,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;CAChD;AAID;;;;GAIG;AACH,qBAAa,UAAU;IAcnB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAdxB,OAAO,CAAC,UAAU,CAA6B;IAE/C,OAAO,CAAC,OAAO,CAAsC;IAErD,OAAO,CAAC,KAAK,CAA4C;IAEzD,OAAO,CAAC,QAAQ,CAAK;IAErB,OAAO,CAAC,IAAI,CAAS;IAErB,OAAO,CAAC,MAAM,CAAS;IAEvB,YACmB,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,SAAS,EAGlC;IAED;;;OAGG;IACG,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC,CAkBtC;IAED,4FAA4F;IAC5F,OAAO,IAAI,IAAI,CAOd;IAED,iGAAiG;IAC3F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAa3B;IAED,MAAM,IAAI,MAAM,CAEf;IAED,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,IAAI;YAmBE,SAAS;YAuBT,OAAO;YASP,KAAK;IAmBnB,OAAO,CAAC,IAAI;IAaZ,OAAO,CAAC,MAAM;CAgBf"}