@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.
- package/README.md +277 -2
- package/dist/codec/attempt-of.d.ts +4 -0
- package/dist/codec/attempt-of.d.ts.map +1 -0
- package/dist/codec/attempt-of.js +8 -0
- package/dist/codec/attempt-of.js.map +1 -0
- package/dist/codec/decode.d.ts +5 -0
- package/dist/codec/decode.d.ts.map +1 -0
- package/dist/codec/decode.js +39 -0
- package/dist/codec/decode.js.map +1 -0
- package/dist/codec/encode.d.ts +8 -0
- package/dist/codec/encode.d.ts.map +1 -0
- package/dist/codec/encode.js +23 -0
- package/dist/codec/encode.js.map +1 -0
- package/dist/codec/headers-of.d.ts +4 -0
- package/dist/codec/headers-of.d.ts.map +1 -0
- package/dist/codec/headers-of.js +6 -0
- package/dist/codec/headers-of.js.map +1 -0
- package/dist/codec/headers.d.ts +7 -0
- package/dist/codec/headers.d.ts.map +1 -0
- package/dist/codec/headers.js +7 -0
- package/dist/codec/headers.js.map +1 -0
- package/dist/codec/publication.d.ts +7 -0
- package/dist/codec/publication.d.ts.map +1 -0
- package/dist/codec/publication.js +2 -0
- package/dist/codec/publication.js.map +1 -0
- package/dist/codec/republication.d.ts +5 -0
- package/dist/codec/republication.d.ts.map +1 -0
- package/dist/codec/republication.js +20 -0
- package/dist/codec/republication.js.map +1 -0
- package/dist/connection/broker-link.d.ts +44 -0
- package/dist/connection/broker-link.d.ts.map +1 -0
- package/dist/connection/broker-link.js +133 -0
- package/dist/connection/broker-link.js.map +1 -0
- package/dist/consumption/consumption.d.ts +50 -0
- package/dist/consumption/consumption.d.ts.map +1 -0
- package/dist/consumption/consumption.js +214 -0
- package/dist/consumption/consumption.js.map +1 -0
- package/dist/consumption/failure-of.d.ts +7 -0
- package/dist/consumption/failure-of.d.ts.map +1 -0
- package/dist/consumption/failure-of.js +18 -0
- package/dist/consumption/failure-of.js.map +1 -0
- package/dist/consumption/poison-message.d.ts +3 -0
- package/dist/consumption/poison-message.d.ts.map +1 -0
- package/dist/consumption/poison-message.js +3 -0
- package/dist/consumption/poison-message.js.map +1 -0
- package/dist/consumption/track.d.ts +3 -0
- package/dist/consumption/track.d.ts.map +1 -0
- package/dist/consumption/track.js +9 -0
- package/dist/consumption/track.js.map +1 -0
- package/dist/errors/broker-unavailable-error.d.ts +8 -0
- package/dist/errors/broker-unavailable-error.d.ts.map +1 -0
- package/dist/errors/broker-unavailable-error.js +12 -0
- package/dist/errors/broker-unavailable-error.js.map +1 -0
- package/dist/errors/consumer-name-required-error.d.ts +5 -0
- package/dist/errors/consumer-name-required-error.d.ts.map +1 -0
- package/dist/errors/consumer-name-required-error.js +8 -0
- package/dist/errors/consumer-name-required-error.js.map +1 -0
- package/dist/errors/event-bus-closed-error.d.ts +5 -0
- package/dist/errors/event-bus-closed-error.d.ts.map +1 -0
- package/dist/errors/event-bus-closed-error.js +8 -0
- package/dist/errors/event-bus-closed-error.js.map +1 -0
- package/dist/errors/failure-code.d.ts +6 -0
- package/dist/errors/failure-code.d.ts.map +1 -0
- package/dist/errors/failure-code.js +15 -0
- package/dist/errors/failure-code.js.map +1 -0
- package/dist/errors/publish-not-confirmed-error.d.ts +7 -0
- package/dist/errors/publish-not-confirmed-error.d.ts.map +1 -0
- package/dist/errors/publish-not-confirmed-error.js +11 -0
- package/dist/errors/publish-not-confirmed-error.js.map +1 -0
- package/dist/errors/unroutable-event-error.d.ts +8 -0
- package/dist/errors/unroutable-event-error.d.ts.map +1 -0
- package/dist/errors/unroutable-event-error.js +13 -0
- package/dist/errors/unroutable-event-error.js.map +1 -0
- package/dist/ignore.d.ts +3 -0
- package/dist/ignore.d.ts.map +1 -0
- package/dist/ignore.js +5 -0
- package/dist/ignore.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/messaging-system.d.ts +3 -0
- package/dist/messaging-system.d.ts.map +1 -0
- package/dist/messaging-system.js +3 -0
- package/dist/messaging-system.js.map +1 -0
- package/dist/options/queue-type.d.ts +2 -0
- package/dist/options/queue-type.d.ts.map +1 -0
- package/dist/options/queue-type.js +2 -0
- package/dist/options/queue-type.js.map +1 -0
- package/dist/options/rabbit-mq-delivery.d.ts +12 -0
- package/dist/options/rabbit-mq-delivery.d.ts.map +1 -0
- package/dist/options/rabbit-mq-delivery.js +2 -0
- package/dist/options/rabbit-mq-delivery.js.map +1 -0
- package/dist/options/rabbit-mq-event-bus-options.d.ts +31 -0
- package/dist/options/rabbit-mq-event-bus-options.d.ts.map +1 -0
- package/dist/options/rabbit-mq-event-bus-options.js +2 -0
- package/dist/options/rabbit-mq-event-bus-options.js.map +1 -0
- package/dist/options/rabbit-mq-failure.d.ts +17 -0
- package/dist/options/rabbit-mq-failure.d.ts.map +1 -0
- package/dist/options/rabbit-mq-failure.js +2 -0
- package/dist/options/rabbit-mq-failure.js.map +1 -0
- package/dist/options/reconnect-options.d.ts +9 -0
- package/dist/options/reconnect-options.d.ts.map +1 -0
- package/dist/options/reconnect-options.js +2 -0
- package/dist/options/reconnect-options.js.map +1 -0
- package/dist/options/retry-options.d.ts +7 -0
- package/dist/options/retry-options.d.ts.map +1 -0
- package/dist/options/retry-options.js +2 -0
- package/dist/options/retry-options.js.map +1 -0
- package/dist/options/settings-of.d.ts +5 -0
- package/dist/options/settings-of.d.ts.map +1 -0
- package/dist/options/settings-of.js +38 -0
- package/dist/options/settings-of.js.map +1 -0
- package/dist/options/settings.d.ts +20 -0
- package/dist/options/settings.d.ts.map +1 -0
- package/dist/options/settings.js +2 -0
- package/dist/options/settings.js.map +1 -0
- package/dist/publication/publications.d.ts +28 -0
- package/dist/publication/publications.d.ts.map +1 -0
- package/dist/publication/publications.js +87 -0
- package/dist/publication/publications.js.map +1 -0
- package/dist/publication/send-confirmed.d.ts +5 -0
- package/dist/publication/send-confirmed.d.ts.map +1 -0
- package/dist/publication/send-confirmed.js +21 -0
- package/dist/publication/send-confirmed.js.map +1 -0
- package/dist/rabbit-mq-event-bus.d.ts +55 -0
- package/dist/rabbit-mq-event-bus.d.ts.map +1 -0
- package/dist/rabbit-mq-event-bus.js +168 -0
- package/dist/rabbit-mq-event-bus.js.map +1 -0
- package/dist/secrecy/conceal.d.ts +8 -0
- package/dist/secrecy/conceal.d.ts.map +1 -0
- package/dist/secrecy/conceal.js +12 -0
- package/dist/secrecy/conceal.js.map +1 -0
- package/dist/topology/binding-description.d.ts +6 -0
- package/dist/topology/binding-description.d.ts.map +1 -0
- package/dist/topology/binding-description.js +2 -0
- package/dist/topology/binding-description.js.map +1 -0
- package/dist/topology/consumer-binding.d.ts +6 -0
- package/dist/topology/consumer-binding.d.ts.map +1 -0
- package/dist/topology/consumer-binding.js +2 -0
- package/dist/topology/consumer-binding.js.map +1 -0
- package/dist/topology/declare-topology.d.ts +5 -0
- package/dist/topology/declare-topology.d.ts.map +1 -0
- package/dist/topology/declare-topology.js +13 -0
- package/dist/topology/declare-topology.js.map +1 -0
- package/dist/topology/exchange-description.d.ts +6 -0
- package/dist/topology/exchange-description.d.ts.map +1 -0
- package/dist/topology/exchange-description.js +2 -0
- package/dist/topology/exchange-description.js.map +1 -0
- package/dist/topology/queue-description.d.ts +8 -0
- package/dist/topology/queue-description.d.ts.map +1 -0
- package/dist/topology/queue-description.js +2 -0
- package/dist/topology/queue-description.js.map +1 -0
- package/dist/topology/queue-names-of.d.ts +4 -0
- package/dist/topology/queue-names-of.d.ts.map +1 -0
- package/dist/topology/queue-names-of.js +5 -0
- package/dist/topology/queue-names-of.js.map +1 -0
- package/dist/topology/queue-names.d.ts +6 -0
- package/dist/topology/queue-names.d.ts.map +1 -0
- package/dist/topology/queue-names.js +2 -0
- package/dist/topology/queue-names.js.map +1 -0
- package/dist/topology/rabbit-mq-topology.d.ts +10 -0
- package/dist/topology/rabbit-mq-topology.d.ts.map +1 -0
- package/dist/topology/rabbit-mq-topology.js +2 -0
- package/dist/topology/rabbit-mq-topology.js.map +1 -0
- package/dist/topology/topology-of.d.ts +5 -0
- package/dist/topology/topology-of.d.ts.map +1 -0
- package/dist/topology/topology-of.js +50 -0
- package/dist/topology/topology-of.js.map +1 -0
- package/dist/topology/topology-settings.d.ts +7 -0
- package/dist/topology/topology-settings.d.ts.map +1 -0
- package/dist/topology/topology-settings.js +2 -0
- package/dist/topology/topology-settings.js.map +1 -0
- package/package.json +37 -4
package/README.md
CHANGED
|
@@ -1,3 +1,278 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ddd-ts-kit/rabbitmq
|
|
2
2
|
|
|
3
|
-
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|