@ddd-ts-kit/rabbitmq 0.0.0-stage → 0.1.1

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 (174) hide show
  1. package/README.md +277 -2
  2. package/dist/codec/attempt-of.d.ts +4 -0
  3. package/dist/codec/attempt-of.d.ts.map +1 -0
  4. package/dist/codec/attempt-of.js +8 -0
  5. package/dist/codec/attempt-of.js.map +1 -0
  6. package/dist/codec/decode.d.ts +5 -0
  7. package/dist/codec/decode.d.ts.map +1 -0
  8. package/dist/codec/decode.js +39 -0
  9. package/dist/codec/decode.js.map +1 -0
  10. package/dist/codec/encode.d.ts +8 -0
  11. package/dist/codec/encode.d.ts.map +1 -0
  12. package/dist/codec/encode.js +23 -0
  13. package/dist/codec/encode.js.map +1 -0
  14. package/dist/codec/headers-of.d.ts +4 -0
  15. package/dist/codec/headers-of.d.ts.map +1 -0
  16. package/dist/codec/headers-of.js +6 -0
  17. package/dist/codec/headers-of.js.map +1 -0
  18. package/dist/codec/headers.d.ts +7 -0
  19. package/dist/codec/headers.d.ts.map +1 -0
  20. package/dist/codec/headers.js +7 -0
  21. package/dist/codec/headers.js.map +1 -0
  22. package/dist/codec/publication.d.ts +7 -0
  23. package/dist/codec/publication.d.ts.map +1 -0
  24. package/dist/codec/publication.js +2 -0
  25. package/dist/codec/publication.js.map +1 -0
  26. package/dist/codec/republication.d.ts +5 -0
  27. package/dist/codec/republication.d.ts.map +1 -0
  28. package/dist/codec/republication.js +20 -0
  29. package/dist/codec/republication.js.map +1 -0
  30. package/dist/connection/broker-link.d.ts +44 -0
  31. package/dist/connection/broker-link.d.ts.map +1 -0
  32. package/dist/connection/broker-link.js +133 -0
  33. package/dist/connection/broker-link.js.map +1 -0
  34. package/dist/consumption/consumption.d.ts +50 -0
  35. package/dist/consumption/consumption.d.ts.map +1 -0
  36. package/dist/consumption/consumption.js +214 -0
  37. package/dist/consumption/consumption.js.map +1 -0
  38. package/dist/consumption/failure-of.d.ts +7 -0
  39. package/dist/consumption/failure-of.d.ts.map +1 -0
  40. package/dist/consumption/failure-of.js +18 -0
  41. package/dist/consumption/failure-of.js.map +1 -0
  42. package/dist/consumption/poison-message.d.ts +3 -0
  43. package/dist/consumption/poison-message.d.ts.map +1 -0
  44. package/dist/consumption/poison-message.js +3 -0
  45. package/dist/consumption/poison-message.js.map +1 -0
  46. package/dist/consumption/track.d.ts +3 -0
  47. package/dist/consumption/track.d.ts.map +1 -0
  48. package/dist/consumption/track.js +9 -0
  49. package/dist/consumption/track.js.map +1 -0
  50. package/dist/errors/broker-unavailable-error.d.ts +8 -0
  51. package/dist/errors/broker-unavailable-error.d.ts.map +1 -0
  52. package/dist/errors/broker-unavailable-error.js +12 -0
  53. package/dist/errors/broker-unavailable-error.js.map +1 -0
  54. package/dist/errors/consumer-name-required-error.d.ts +5 -0
  55. package/dist/errors/consumer-name-required-error.d.ts.map +1 -0
  56. package/dist/errors/consumer-name-required-error.js +8 -0
  57. package/dist/errors/consumer-name-required-error.js.map +1 -0
  58. package/dist/errors/event-bus-closed-error.d.ts +5 -0
  59. package/dist/errors/event-bus-closed-error.d.ts.map +1 -0
  60. package/dist/errors/event-bus-closed-error.js +8 -0
  61. package/dist/errors/event-bus-closed-error.js.map +1 -0
  62. package/dist/errors/failure-code.d.ts +6 -0
  63. package/dist/errors/failure-code.d.ts.map +1 -0
  64. package/dist/errors/failure-code.js +15 -0
  65. package/dist/errors/failure-code.js.map +1 -0
  66. package/dist/errors/publish-not-confirmed-error.d.ts +7 -0
  67. package/dist/errors/publish-not-confirmed-error.d.ts.map +1 -0
  68. package/dist/errors/publish-not-confirmed-error.js +11 -0
  69. package/dist/errors/publish-not-confirmed-error.js.map +1 -0
  70. package/dist/errors/unroutable-event-error.d.ts +8 -0
  71. package/dist/errors/unroutable-event-error.d.ts.map +1 -0
  72. package/dist/errors/unroutable-event-error.js +13 -0
  73. package/dist/errors/unroutable-event-error.js.map +1 -0
  74. package/dist/ignore.d.ts +3 -0
  75. package/dist/ignore.d.ts.map +1 -0
  76. package/dist/ignore.js +5 -0
  77. package/dist/ignore.js.map +1 -0
  78. package/dist/index.d.ts +23 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +11 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/messaging-system.d.ts +3 -0
  83. package/dist/messaging-system.d.ts.map +1 -0
  84. package/dist/messaging-system.js +3 -0
  85. package/dist/messaging-system.js.map +1 -0
  86. package/dist/options/queue-type.d.ts +2 -0
  87. package/dist/options/queue-type.d.ts.map +1 -0
  88. package/dist/options/queue-type.js +2 -0
  89. package/dist/options/queue-type.js.map +1 -0
  90. package/dist/options/rabbit-mq-delivery.d.ts +12 -0
  91. package/dist/options/rabbit-mq-delivery.d.ts.map +1 -0
  92. package/dist/options/rabbit-mq-delivery.js +2 -0
  93. package/dist/options/rabbit-mq-delivery.js.map +1 -0
  94. package/dist/options/rabbit-mq-event-bus-options.d.ts +31 -0
  95. package/dist/options/rabbit-mq-event-bus-options.d.ts.map +1 -0
  96. package/dist/options/rabbit-mq-event-bus-options.js +2 -0
  97. package/dist/options/rabbit-mq-event-bus-options.js.map +1 -0
  98. package/dist/options/rabbit-mq-failure.d.ts +17 -0
  99. package/dist/options/rabbit-mq-failure.d.ts.map +1 -0
  100. package/dist/options/rabbit-mq-failure.js +2 -0
  101. package/dist/options/rabbit-mq-failure.js.map +1 -0
  102. package/dist/options/reconnect-options.d.ts +9 -0
  103. package/dist/options/reconnect-options.d.ts.map +1 -0
  104. package/dist/options/reconnect-options.js +2 -0
  105. package/dist/options/reconnect-options.js.map +1 -0
  106. package/dist/options/retry-options.d.ts +7 -0
  107. package/dist/options/retry-options.d.ts.map +1 -0
  108. package/dist/options/retry-options.js +2 -0
  109. package/dist/options/retry-options.js.map +1 -0
  110. package/dist/options/settings-of.d.ts +5 -0
  111. package/dist/options/settings-of.d.ts.map +1 -0
  112. package/dist/options/settings-of.js +38 -0
  113. package/dist/options/settings-of.js.map +1 -0
  114. package/dist/options/settings.d.ts +20 -0
  115. package/dist/options/settings.d.ts.map +1 -0
  116. package/dist/options/settings.js +2 -0
  117. package/dist/options/settings.js.map +1 -0
  118. package/dist/publication/publications.d.ts +28 -0
  119. package/dist/publication/publications.d.ts.map +1 -0
  120. package/dist/publication/publications.js +87 -0
  121. package/dist/publication/publications.js.map +1 -0
  122. package/dist/publication/send-confirmed.d.ts +5 -0
  123. package/dist/publication/send-confirmed.d.ts.map +1 -0
  124. package/dist/publication/send-confirmed.js +21 -0
  125. package/dist/publication/send-confirmed.js.map +1 -0
  126. package/dist/rabbit-mq-event-bus.d.ts +55 -0
  127. package/dist/rabbit-mq-event-bus.d.ts.map +1 -0
  128. package/dist/rabbit-mq-event-bus.js +168 -0
  129. package/dist/rabbit-mq-event-bus.js.map +1 -0
  130. package/dist/secrecy/conceal.d.ts +8 -0
  131. package/dist/secrecy/conceal.d.ts.map +1 -0
  132. package/dist/secrecy/conceal.js +12 -0
  133. package/dist/secrecy/conceal.js.map +1 -0
  134. package/dist/topology/binding-description.d.ts +6 -0
  135. package/dist/topology/binding-description.d.ts.map +1 -0
  136. package/dist/topology/binding-description.js +2 -0
  137. package/dist/topology/binding-description.js.map +1 -0
  138. package/dist/topology/consumer-binding.d.ts +6 -0
  139. package/dist/topology/consumer-binding.d.ts.map +1 -0
  140. package/dist/topology/consumer-binding.js +2 -0
  141. package/dist/topology/consumer-binding.js.map +1 -0
  142. package/dist/topology/declare-topology.d.ts +5 -0
  143. package/dist/topology/declare-topology.d.ts.map +1 -0
  144. package/dist/topology/declare-topology.js +13 -0
  145. package/dist/topology/declare-topology.js.map +1 -0
  146. package/dist/topology/exchange-description.d.ts +6 -0
  147. package/dist/topology/exchange-description.d.ts.map +1 -0
  148. package/dist/topology/exchange-description.js +2 -0
  149. package/dist/topology/exchange-description.js.map +1 -0
  150. package/dist/topology/queue-description.d.ts +8 -0
  151. package/dist/topology/queue-description.d.ts.map +1 -0
  152. package/dist/topology/queue-description.js +2 -0
  153. package/dist/topology/queue-description.js.map +1 -0
  154. package/dist/topology/queue-names-of.d.ts +4 -0
  155. package/dist/topology/queue-names-of.d.ts.map +1 -0
  156. package/dist/topology/queue-names-of.js +5 -0
  157. package/dist/topology/queue-names-of.js.map +1 -0
  158. package/dist/topology/queue-names.d.ts +6 -0
  159. package/dist/topology/queue-names.d.ts.map +1 -0
  160. package/dist/topology/queue-names.js +2 -0
  161. package/dist/topology/queue-names.js.map +1 -0
  162. package/dist/topology/rabbit-mq-topology.d.ts +10 -0
  163. package/dist/topology/rabbit-mq-topology.d.ts.map +1 -0
  164. package/dist/topology/rabbit-mq-topology.js +2 -0
  165. package/dist/topology/rabbit-mq-topology.js.map +1 -0
  166. package/dist/topology/topology-of.d.ts +5 -0
  167. package/dist/topology/topology-of.d.ts.map +1 -0
  168. package/dist/topology/topology-of.js +50 -0
  169. package/dist/topology/topology-of.js.map +1 -0
  170. package/dist/topology/topology-settings.d.ts +7 -0
  171. package/dist/topology/topology-settings.d.ts.map +1 -0
  172. package/dist/topology/topology-settings.js +2 -0
  173. package/dist/topology/topology-settings.js.map +1 -0
  174. package/package.json +37 -4
package/README.md CHANGED
@@ -1,3 +1,278 @@
1
- # Temporary Holding Version
1
+ # @ddd-ts-kit/rabbitmq
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 RabbitMQ: an `EventBus` of the kernel that publishes with
4
+ publisher confirms, so the outbox relay can mark a record published safely, and consumes from
5
+ one durable queue per consumer and event type with manual acknowledgement, a bounded number of
6
+ delayed retries and a dead queue. It is the "queue brokers" row of the strategy table of
7
+ `@ddd-ts-kit/events`, implemented, and it passes `eventBusContract` of `@ddd-ts-kit/testing`.
8
+
9
+ ```
10
+ npm i @ddd-ts-kit/rabbitmq @ddd-ts-kit/events amqplib
11
+ ```
12
+
13
+ Tested with amqplib 2.2 against RabbitMQ 4.3. Any AMQP 0-9-1 broker with publisher confirms
14
+ should work with `queueType: 'classic'`; only RabbitMQ was run.
15
+
16
+ ## Composition root
17
+
18
+ Handlers never see this package. They publish to the outbox; the relay publishes to RabbitMQ;
19
+ each consuming context listens through the bus under its own name. The bus is given to the
20
+ application as two ports of the kernel: `IntegrationEventPublisher` stays the outbox, and
21
+ `EventSubscribers` hands each context the subscriber of its own consumer name.
22
+
23
+ `init --broker rabbitmq` writes this wiring in the platform of the project: the bus is built by
24
+ `platform/events/create-event-provider.ts`, `platform/platform.module.ts` rebinds
25
+ `EventSubscribers` on it, and `main.ts` gives it, traced, to the outbox relay. By hand, the
26
+ same thing reads:
27
+
28
+ ```ts
29
+ // platform/messaging.module.ts
30
+ import { defineContextModule } from '@ddd-ts-kit/di';
31
+ import { EventSubscribers, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
32
+ import { OutboxPublisher, OutboxStore } from '@ddd-ts-kit/events';
33
+ import { RabbitMqEventBus } from '@ddd-ts-kit/rabbitmq';
34
+
35
+ export const bus = new RabbitMqEventBus({
36
+ connection: config.amqpUrl, // or amqplib options, or a factory opening a new connection
37
+ exchange: 'integration-events', // the default
38
+ retry: { maxAttempts: 5, delayMs: 5_000 }, // the defaults
39
+ prefetch: 10, // the default
40
+ onFailure: (failure) => logger.warn('event bus failure', { ...failure }), // codes only, never an error message
41
+ onDelivery: ({ queue, outcome }) => deliveries.add(1, { queue, outcome }),
42
+ });
43
+
44
+ export const messagingModule = defineContextModule({
45
+ name: 'messaging',
46
+ register(builder) {
47
+ // Publishing side: handlers write to the outbox, in the transaction of the command.
48
+ builder.rebind(IntegrationEventPublisher).toClass(OutboxPublisher, [OutboxStore]);
49
+ // Consuming side: one subscriber per consuming context, named after it.
50
+ builder.rebind(EventSubscribers).toValue({ forContext: (name) => bus.forConsumer(name) });
51
+ },
52
+ });
53
+ ```
54
+
55
+ ```ts
56
+ // contexts/billing/application/integration/handlers/invoice-on-order-placed.integration-handler.ts
57
+ import { CommandBus, systemActor, type IntegrationEvent } from '@ddd-ts-kit/kernel';
58
+ import { IntegrationEventHandler } from '@ddd-ts-kit/kernel/declarations';
59
+
60
+ @IntegrationEventHandler(orderPlaced, { consumer: 'billing.invoice-on-order-placed' }, [CommandBus])
61
+ export class InvoiceOnOrderPlacedIntegrationHandler {
62
+ constructor(private readonly commands: CommandBus) {}
63
+
64
+ // Nobody is calling: the handler acts as a system actor named after its consumer.
65
+ readonly actor = systemActor('billing.invoice-on-order-placed');
66
+
67
+ async handle(event: IntegrationEvent<OrderPlacedPayload>): Promise<void> {
68
+ await this.commands.execute(new IssueInvoice(this.actor, event.payload.orderId));
69
+ }
70
+ }
71
+ ```
72
+
73
+ ```ts
74
+ // main.ts
75
+ import { contextModule, createApplication, kernelModule } from '@ddd-ts-kit/di';
76
+ import { Clock } from '@ddd-ts-kit/kernel';
77
+ import { OutboxRelay, OutboxStore } from '@ddd-ts-kit/events';
78
+ import { bus, messagingModule } from '@/platform/messaging.module.js';
79
+
80
+ const app = createApplication({
81
+ modules: [
82
+ kernelModule(),
83
+ persistenceModule,
84
+ messagingModule,
85
+ contextModule('ordering', orderingDeclarations),
86
+ contextModule('billing', billingDeclarations),
87
+ ],
88
+ });
89
+
90
+ // The relay target is the bus. A record is marked published when the broker confirmed it.
91
+ const relay = new OutboxRelay({ store: app.resolve(OutboxStore), publisher: bus, clock: app.resolve(Clock) });
92
+
93
+ await app.start(); // subscribes the handler of billing on bus.forConsumer('billing')
94
+ await bus.assertTopology(); // fail at startup, not at the first message
95
+ relay.start({ intervalMs: 500 });
96
+
97
+ process.on('SIGTERM', async () => {
98
+ await relay.stop(); // the tick in progress finishes
99
+ await app.stop(); // the handlers are unsubscribed
100
+ await bus.close(); // consumers stop, handlers in progress finish and acknowledge
101
+ });
102
+ ```
103
+
104
+ - `persistenceModule` binds `OutboxStore`, and rebinds `UnitOfWork` and `ProcessedEventStore`
105
+ to the database: see the README of the persistence adapter. A declared integration event
106
+ handler is subscribed on the subscriber of its own context (`forContext` receives the name
107
+ of its module), and since `UnitOfWork` and `ProcessedEventStore` are bound, its idempotency
108
+ mark and its work commit in one unit of work: a redelivery is deduplicated, a failure
109
+ leaves nothing behind.
110
+ - Without an outbox the bus is the event bus of the kernel module, and handlers publish
111
+ straight to the broker:
112
+ `kernelModule({ eventBus: bus, eventSubscribers: () => ({ forContext: (name) => bus.forConsumer(name) }) })`.
113
+ - Without a container the same objects are wired by hand: `bus.forConsumer('billing')` is an
114
+ `IntegrationEventSubscriber`, to give to `subscribeAll` and `consume` of `@ddd-ts-kit/events`.
115
+ - `publish(events)` resolves only when the broker confirmed **every** message of the batch and
116
+ rejects otherwise (`PUBLISH_NOT_CONFIRMED`, `BROKER_UNAVAILABLE`, `UNROUTABLE_EVENT`,
117
+ `EVENT_BUS_CLOSED`). Nothing is buffered for later: the outbox is the buffer.
118
+ - A consumer name is required to subscribe: `bus.forConsumer('billing')` gives an
119
+ `IntegrationEventSubscriber` for one context on the shared connection (a modular monolith
120
+ has several), and the `consumer` option names the one `bus.subscribe` works for (a service
121
+ that is a single context).
122
+ - Tracing is put around both sides from outside, as with any provider: `tracedPublisher(bus)`
123
+ as the relay target, and on the consuming side the instrumentation of the kernel module
124
+ (`kernelModule({ instrumentation: traceApplication() })`), which gives each declared handler
125
+ one span `process <consumer>` per attempt. `tracedSubscriber(bus.forConsumer(name))` is for
126
+ a composition without `kernelModule`. The bus carries `traceparent` and `correlationId`
127
+ untouched, in the envelope and in the message headers.
128
+ - The correlation makes the round trip. Publish through `scopedPublisher(publisher, scope)` of
129
+ the kernel: a declared handler (or `consume(definition, { ..., scope }, handler)` by hand)
130
+ runs in an execution context whose `correlationId` is the one of the request that published,
131
+ whose `causationId` is the event, and a redelivery after a failure runs in the same one. An
132
+ operator reads it on the message without opening the body, in the `correlation_id` property
133
+ and the `x-correlation-id` header.
134
+ - A context extracted to its own service keeps its consumer name and so its queues: nothing
135
+ changes on the broker.
136
+
137
+ ## Topology
138
+
139
+ `bus.describeTopology()` returns the exchanges, queues and bindings as data, for a review or
140
+ a generated document; `bus.assertTopology()` declares them, idempotently. The bus also declares
141
+ them on every (re)connection. For `forConsumer('billing')` subscribed to
142
+ `ordering.order.placed`:
143
+
144
+ | Object | Name | Properties |
145
+ |---|---|---|
146
+ | exchange | `integration-events` | topic, durable |
147
+ | work queue | `billing.ordering.order.placed` | durable, quorum; bound to the exchange with the routing key `ordering.order.placed`; dead-letters to the dead queue |
148
+ | retry queue | `billing.ordering.order.placed.retry` | durable, quorum; `x-message-ttl` = `retry.delayMs`; dead-letters back to the work queue, at least once |
149
+ | dead queue | `billing.ordering.order.placed.dead` | durable, quorum; no consumer |
150
+
151
+ Retries and dead letters travel through the default exchange of the broker (routing key =
152
+ queue name), so there is one exchange to manage and no binding besides the one above.
153
+
154
+ A message is the envelope as JSON (`id`, `type`, `version`, `occurredAt`, `payload`,
155
+ `traceparent`, `correlationId`), persistent, with the routing key and the `type` property set
156
+ to the event type, `messageId` to the event id, `correlationId` to the correlation id, and the
157
+ headers `traceparent`, `x-correlation-id` and `x-event-version`. Any AMQP client can consume it.
158
+
159
+ Two consumers of one type have a queue each and both receive every event. Two instances of
160
+ one consumer share its queue: each event goes to one of them.
161
+
162
+ ## Retry and dead-letter behaviour
163
+
164
+ 1. A message is acknowledged after every handler subscribed to its queue resolved. A declared
165
+ `@IntegrationEventHandler` (or `consume` of `@ddd-ts-kit/events` by hand) commits its work
166
+ and its idempotency mark in one unit of work: the acknowledgement then follows that commit,
167
+ and a redelivery is deduplicated.
168
+ 2. When a handler throws, the bus sends a confirmed copy of the message to the retry queue
169
+ with the header `x-attempt` incremented and `x-error-code` set, then acknowledges the
170
+ original. The copy waits `retry.delayMs` there and comes back to the work queue. A failed
171
+ message is never requeued on the spot: no hot loop.
172
+ 3. At the `retry.maxAttempts`-th failed delivery the copy goes to the dead queue instead, with
173
+ `x-dead-reason: max-attempts`.
174
+ 4. A message whose body is not an envelope (unparseable, or without `id`, `type`, `version`,
175
+ `occurredAt`) is poison: it goes straight to the dead queue with
176
+ `x-dead-reason: poison` and `x-error-code: POISON_MESSAGE`, and no handler sees it.
177
+ 5. Only codes travel: `x-error-code` is the `code` of the error or its class name. The message
178
+ of an error is never logged, reported or attached, because it may quote payload data.
179
+
180
+ Delivery is at least once and unordered. The copy-then-acknowledge order means a crash in
181
+ between delivers the message twice, never zero times.
182
+
183
+ On a lost connection the bus reconnects with an exponential backoff (`reconnect`, 200 ms
184
+ doubling up to 30 s), declares its topology again and resumes its consumers. Publications in
185
+ flight reject, and so does `publish` between two attempts, so the outbox relay keeps the
186
+ records and retries them. Messages that were being handled are redelivered by the broker.
187
+
188
+ **Credentials.** The URL or the options of `connection` are held by the function that opens the connection, in no field. A `RabbitMqEventBus` that is logged, spread or
189
+ serialised shows `RabbitMqEventBus [connection concealed]` and nothing else: its fields are not enumerable,
190
+ and `toJSON` and `util.inspect` answer that name. The failures it reports are codes. What the
191
+ client library itself shows when one of its own objects is inspected is not in the hands of
192
+ this package: do not log those.
193
+
194
+ **What a handler is told.** Next to the event, each handler receives the delivery
195
+ (`EventDelivery` of the kernel): `{ attempt, maxAttempts, system: 'rabbitmq', destination, consumer }`,
196
+ where `destination` is the work queue. Handlers of the application ignore it; `@ddd-ts-kit/tracing` reads it
197
+ to make one CONSUMER span per attempt and to mark the redeliveries and the attempt after which
198
+ the event is buried. The bus names itself `messagingSystem: 'rabbitmq'`, and `traceparent` travels in
199
+ the envelope and as a real header, intact across a retry.
200
+
201
+ ## Operations
202
+
203
+ What to watch:
204
+
205
+ | Signal | Where | Meaning |
206
+ |---|---|---|
207
+ | outbox `stats()` (`pending`, `failed`, `dead`) | the outbox store | the publishing side: see `@ddd-ts-kit/events` |
208
+ | depth of `<consumer>.<type>` | broker | the consumer lags or is stopped |
209
+ | depth of `<consumer>.<type>.dead` | broker | must be 0: alert on anything else |
210
+ | depth of `<consumer>.<type>.retry` | broker | failures in progress; a plateau means a handler that keeps failing |
211
+ | `onDelivery` counted by `outcome` | this bus | rate of `retried`, `dead`, `poison` against `acknowledged` |
212
+ | `onFailure` with `kind: 'connection'` | this bus | reconnections, and declarations the broker refused (`AMQP_406`) |
213
+ | `bus.deliveriesInFlight` | this bus | handlers running now; stuck above 0 means a handler that hangs |
214
+
215
+ Replaying dead messages, once the cause is fixed:
216
+
217
+ ```ts
218
+ const replayed = await bus.replayDead({ consumer: 'billing', type: 'ordering.order.placed' }, 100);
219
+ ```
220
+
221
+ It moves up to that many messages from the dead queue back to the work queue with a fresh
222
+ attempt budget, each with a confirmed copy before the acknowledgement. The management UI or
223
+ a shovel can move them too; reset or drop the `x-attempt` header if you do, or the message
224
+ dies again at its first failure. Look at `x-dead-reason` and `x-error-code` first: replaying
225
+ a poison message only kills it again.
226
+
227
+ To stop a consumer for good, delete its three queues: unsubscribing leaves them bound, and
228
+ the work queue keeps filling.
229
+
230
+ ## Limits
231
+
232
+ - **No ordering.** Retries and competing instances reorder. Carry a version in the payload.
233
+ - **One delay.** The retry wait is the TTL of the retry queue: the same for every attempt, no
234
+ exponential backoff.
235
+ - **Queue arguments are fixed at creation.** Changing `retry.delayMs` or `queueType` for an
236
+ existing queue is refused by the broker (reported as `AMQP_406` while the bus keeps trying to
237
+ reconnect): delete the queue, or migrate under another consumer name.
238
+ - **A redelivery runs every handler of the queue again**, those that succeeded included, when
239
+ several handlers of one consumer subscribe to the same type. Each must be idempotent.
240
+ - **A batch is not atomic.** When `publish` rejects, some events of the batch may have been
241
+ taken by the broker. The relay publishes one by one and retries, consumers deduplicate.
242
+ - **`onUnroutable: 'fail'`** rejects an event no queue is bound for; behind the outbox relay
243
+ such a record is retried, then dead. Use it only when a missing consumer is an incident.
244
+ - **A consumer that crashes mid-handler** gets its messages back without `x-attempt` being
245
+ incremented. Quorum queues bound that with their delivery limit, after which the message is
246
+ dead-lettered to the dead queue by the broker; classic queues do not bound it. This path is
247
+ the broker's and is not exercised by the test suite.
248
+ - **Classic queues** dead-letter the expired retries at most once: a broker crash at that
249
+ instant can lose one. Quorum queues (the default) use the at-least-once strategy.
250
+ - **A connection factory must open a new connection at each call**; the bus closes what it
251
+ opened. A connection shared with other code cannot be given.
252
+ - Large batches are written without waiting for the socket to drain: memory grows with the
253
+ batch. The relay publishes record by record, which avoids it.
254
+ - Queue names are `<consumer>.<type>` plus a suffix, within the 255 bytes of AMQP.
255
+
256
+ ## Other brokers
257
+
258
+ [`@ddd-ts-kit/kafka`](../kafka/README.md), [`@ddd-ts-kit/nats`](../nats/README.md) and
259
+ [`@ddd-ts-kit/redis`](../redis/README.md) (Redis Streams) implement the same two ports and
260
+ pass the same contract suite. [docs/other-brokers.md](docs/other-brokers.md) says what every
261
+ provider owes, maps the concepts of this one to Kafka and JetStream as those packages do it,
262
+ and outlines SQS/SNS, which is not built.
263
+
264
+ ## Testing
265
+
266
+ The suites need a real broker and are skipped without `DDD_TS_KIT_TEST_AMQP_URL`:
267
+
268
+ ```
269
+ docker run -d --rm --name ddd-ts-kit-test-rabbitmq -e RABBITMQ_DEFAULT_USER=kit -e RABBITMQ_DEFAULT_PASS=kit -p 127.0.0.1:5672:5672 rabbitmq:4-management-alpine
270
+ DDD_TS_KIT_TEST_AMQP_URL=amqp://kit:kit@127.0.0.1:5672 npx vitest run packages/rabbitmq
271
+ docker rm -f ddd-ts-kit-test-rabbitmq
272
+ ```
273
+
274
+ They run the contract suite on quorum and on classic queues, then the behaviours of this
275
+ provider, one suite per behaviour (`publishing`, `consuming`, `correlation`, `retries`,
276
+ `dead-letters`, `topology`, `connection-loss` through a TCP relay the test cuts, `closing`)
277
+ and the whole chain from the outbox to an idempotent consumer. Every test works on an exchange and queues of its
278
+ own and deletes them.
@@ -0,0 +1,4 @@
1
+ import type { Message } from 'amqplib';
2
+ /** Which delivery this is, starting at 1: the header is set when the bus schedules a retry. */
3
+ export declare function attemptOf(message: Message): number;
4
+ //# sourceMappingURL=attempt-of.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attempt-of.d.ts","sourceRoot":"","sources":["../../src/codec/attempt-of.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIvC,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAIlD"}
@@ -0,0 +1,8 @@
1
+ import { ATTEMPT_HEADER } from './headers.js';
2
+ import { headersOf } from './headers-of.js';
3
+ /** Which delivery this is, starting at 1: the header is set when the bus schedules a retry. */
4
+ export function attemptOf(message) {
5
+ const attempt = headersOf(message)[ATTEMPT_HEADER];
6
+ return typeof attempt === 'number' && Number.isInteger(attempt) && attempt >= 1 ? attempt : 1;
7
+ }
8
+ //# sourceMappingURL=attempt-of.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attempt-of.js","sourceRoot":"","sources":["../../src/codec/attempt-of.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,+FAA+F;AAC/F,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC;IAEnD,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAChG,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Message } from 'amqplib';
2
+ import type { IntegrationEvent } from '@ddd-ts-kit/kernel';
3
+ /** The envelope exactly as published, or `undefined` for a body that is not one (poison). */
4
+ export declare function decode(message: Message): IntegrationEvent | undefined;
5
+ //# 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,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,6FAA6F;AAC7F,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,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(message) {
3
+ const body = parsed(message.content);
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(content) {
18
+ try {
19
+ return JSON.parse(content.toString('utf8'));
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":"AAGA,6FAA6F;AAC7F,MAAM,UAAU,MAAM,CAAC,OAAgB;IACrC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAErC,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,OAAe;IAC7B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,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 AMQP client can consume it. Properties and
5
+ * headers repeat what a broker, 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":"AAAA,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,CAgB3D"}
@@ -0,0 +1,23 @@
1
+ import { CORRELATION_HEADER, EVENT_VERSION_HEADER, TRACEPARENT_HEADER } from './headers.js';
2
+ /**
3
+ * The body is the whole envelope as JSON, so any AMQP client can consume it. Properties and
4
+ * headers repeat what a broker, a tracer or an operator needs without opening the body.
5
+ */
6
+ export function encode(event) {
7
+ return {
8
+ content: Buffer.from(JSON.stringify(event)),
9
+ properties: {
10
+ persistent: true,
11
+ contentType: 'application/json',
12
+ messageId: event.id,
13
+ type: event.type,
14
+ ...(event.correlationId === undefined ? {} : { correlationId: event.correlationId }),
15
+ headers: {
16
+ [EVENT_VERSION_HEADER]: event.version,
17
+ ...(event.traceparent === undefined ? {} : { [TRACEPARENT_HEADER]: event.traceparent }),
18
+ ...(event.correlationId === undefined ? {} : { [CORRELATION_HEADER]: event.correlationId }),
19
+ },
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=encode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode.js","sourceRoot":"","sources":["../../src/codec/encode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAG5F;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,KAAuB;IAC5C,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC3C,UAAU,EAAE;YACV,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE,kBAAkB;YAC/B,SAAS,EAAE,KAAK,CAAC,EAAE;YACnB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,GAAG,CAAC,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC;YACpF,OAAO,EAAE;gBACP,CAAC,oBAAoB,CAAC,EAAE,KAAK,CAAC,OAAO;gBACrC,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,kBAAkB,CAAC,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;gBACvF,GAAG,CAAC,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,kBAAkB,CAAC,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC;aAC5F;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Message } from 'amqplib';
2
+ /** The headers of a message as a plain record, empty when it carries none. */
3
+ export declare function headersOf(message: Message): Record<string, unknown>;
4
+ //# sourceMappingURL=headers-of.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers-of.d.ts","sourceRoot":"","sources":["../../src/codec/headers-of.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAInE"}
@@ -0,0 +1,6 @@
1
+ /** The headers of a message as a plain record, empty when it carries none. */
2
+ export function headersOf(message) {
3
+ const headers = message.properties.headers;
4
+ return typeof headers === 'object' && headers !== null ? { ...headers } : {};
5
+ }
6
+ //# sourceMappingURL=headers-of.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers-of.js","sourceRoot":"","sources":["../../src/codec/headers-of.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,MAAM,OAAO,GAAY,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;IAEpD,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare const ATTEMPT_HEADER = "x-attempt";
2
+ export declare const ERROR_CODE_HEADER = "x-error-code";
3
+ export declare const DEAD_REASON_HEADER = "x-dead-reason";
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
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/codec/headers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,cAAc,CAAC;AAC1C,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AACtD,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAChD,eAAO,MAAM,kBAAkB,qBAAqB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export const ATTEMPT_HEADER = 'x-attempt';
2
+ export const ERROR_CODE_HEADER = 'x-error-code';
3
+ export const DEAD_REASON_HEADER = 'x-dead-reason';
4
+ export const EVENT_VERSION_HEADER = 'x-event-version';
5
+ export const TRACEPARENT_HEADER = 'traceparent';
6
+ export const CORRELATION_HEADER = 'x-correlation-id';
7
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/codec/headers.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAC;AAC1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAClD,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AACtD,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAC;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Options } from 'amqplib';
2
+ /** A message ready to be sent: its body and its AMQP properties. */
3
+ export interface Publication {
4
+ readonly content: Buffer;
5
+ readonly properties: Options.Publish;
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,SAAS,CAAC;AAEvC,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC;CACtC"}
@@ -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,5 @@
1
+ import type { Message } from 'amqplib';
2
+ import type { Publication } from './publication.js';
3
+ /** The same message, to send to another queue with `headers` added or replaced. */
4
+ export declare function republication(message: Message, headers: Record<string, string | number>): Publication;
5
+ //# 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,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,WAAW,CAcrG"}
@@ -0,0 +1,20 @@
1
+ import { headersOf } from './headers-of.js';
2
+ /** The same message, to send to another queue with `headers` added or replaced. */
3
+ export function republication(message, headers) {
4
+ const { properties } = message;
5
+ return {
6
+ content: message.content,
7
+ properties: {
8
+ persistent: true,
9
+ contentType: 'application/json',
10
+ ...textProperty('messageId', properties.messageId),
11
+ ...textProperty('type', properties.type),
12
+ ...textProperty('correlationId', properties.correlationId),
13
+ headers: { ...headersOf(message), ...headers },
14
+ },
15
+ };
16
+ }
17
+ function textProperty(name, value) {
18
+ return typeof value === 'string' ? { [name]: value } : {};
19
+ }
20
+ //# sourceMappingURL=republication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"republication.js","sourceRoot":"","sources":["../../src/codec/republication.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,mFAAmF;AACnF,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,OAAwC;IACtF,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAE/B,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU,EAAE;YACV,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE,kBAAkB;YAC/B,GAAG,YAAY,CAAC,WAAW,EAAE,UAAU,CAAC,SAAS,CAAC;YAClD,GAAG,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC;YACxC,GAAG,YAAY,CAAC,eAAe,EAAE,UAAU,CAAC,aAAa,CAAC;YAC1D,OAAO,EAAE,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE;SAC/C;KACF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,KAAc;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { inspect } from 'node:util';
2
+ import type { ChannelModel } from 'amqplib';
3
+ import type { ReconnectOptions } from '../options/reconnect-options.js';
4
+ export interface LinkHooks {
5
+ /** Sets channels, topology and consumers up on a fresh connection. A failure retries. */
6
+ opened(model: ChannelModel): Promise<void>;
7
+ /** The connection is gone: 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<ChannelModel>;
13
+ readonly reconnect: Required<ReconnectOptions>;
14
+ }
15
+ /**
16
+ * The one connection of a bus, kept alive: when it drops, it is opened again after a delay
17
+ * that grows with each failed attempt, and the hooks rebuild what lived on it.
18
+ */
19
+ export declare class BrokerLink {
20
+ private readonly settings;
21
+ private readonly hooks;
22
+ private model;
23
+ private opening;
24
+ private retry;
25
+ private failures;
26
+ private closed;
27
+ constructor(settings: LinkSettings, hooks: LinkHooks);
28
+ /**
29
+ * The live connection. Opens the first one, joins an attempt in progress, and fails at
30
+ * once between two attempts so that a publisher never hangs on a broker that is down.
31
+ */
32
+ opened(): Promise<ChannelModel>;
33
+ /** Drops the connection so that everything is rebuilt, after a channel died on its own. */
34
+ restart(): void;
35
+ close(): Promise<void>;
36
+ toJSON(): string;
37
+ [inspect.custom](): string;
38
+ private open;
39
+ private establish;
40
+ private connect;
41
+ private lost;
42
+ private failed;
43
+ }
44
+ //# 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,YAAY,EAAE,MAAM,SAAS,CAAC;AAK5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAGxE,MAAM,WAAW,SAAS;IACxB,yFAAyF;IACzF,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,6DAA6D;IAC7D,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,YAAY,CAAC,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;CAChD;AAID;;;GAGG;AACH,qBAAa,UAAU;IAYnB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAZxB,OAAO,CAAC,KAAK,CAA2B;IAExC,OAAO,CAAC,OAAO,CAAoC;IAEnD,OAAO,CAAC,KAAK,CAA4C;IAEzD,OAAO,CAAC,QAAQ,CAAK;IAErB,OAAO,CAAC,MAAM,CAAS;IAEvB,YACmB,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,SAAS,EAGlC;IAED;;;OAGG;IACG,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC,CAkBpC;IAED,2FAA2F;IAC3F,OAAO,IAAI,IAAI,CAOd;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ3B;IAED,MAAM,IAAI,MAAM,CAEf;IAED,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,IAAI;YAmBE,SAAS;YA2BT,OAAO;IASrB,OAAO,CAAC,IAAI;IAaZ,OAAO,CAAC,MAAM;CAgBf"}