@relaybus/rabbitmq 0.2.0 → 0.3.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 +88 -46
- package/dist/index.cjs +624 -398
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -45
- package/dist/index.d.ts +121 -45
- package/dist/index.js +611 -394
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -55,30 +55,31 @@ narrower check of the connection alone. relaybus health checks use `isReady()`.
|
|
|
55
55
|
|
|
56
56
|
## Options
|
|
57
57
|
|
|
58
|
-
| Option | Default | Description
|
|
59
|
-
| -------------------- | ------------------------------------------------- |
|
|
60
|
-
| `name` | `'rabbitmq'` | Name referenced by routes and subscriptions.
|
|
61
|
-
| `url` | - | `amqp://` URL or `{ hostname, port, username, password, vhost, heartbeat, protocol }`. Give exactly one of `url` and `connect`.
|
|
62
|
-
| `connect` | - | Factory returning an amqplib connection. Called again after every loss. Use for TLS options or credentials.
|
|
63
|
-
| `exchange` | `'relaybus'` | Durable topic exchange, created when missing.
|
|
64
|
-
| `prefetch` | `50` (`1` when `ordered`) | Unacknowledged messages held by the consumer; also the handler concurrency.
|
|
65
|
-
| `queuePrefix` | `'relaybus'` | Prefix of derived queue names.
|
|
66
|
-
| `consumerGroup` | - | Services sharing a group share one queue `<queuePrefix>.<consumerGroup>` and split the messages. Required to subscribe unless `queue` is set.
|
|
67
|
-
| `queue` | - | Explicit queue name; overrides `queuePrefix` and `consumerGroup`.
|
|
68
|
-
| `deadLetterExchange` | `'<exchange>.dead'` | Direct exchange poison messages are dead-lettered to.
|
|
69
|
-
| `deadLetterQueue` | `'<queue>.dead'` | Quorum queue that keeps dead-lettered messages.
|
|
70
|
-
| `deliveryLimit` | `10` | Quorum queue `x-delivery-limit`: a backstop that makes the broker dead-letter a message that crashes its consumer again and again. Failing handlers do not use it.
|
|
71
|
-
| `handlerRetry` | 3 attempts, 200 ms, x2, 5 s cap, equal jitter | Handler attempts, with short backoff, on one delivery before the message is scheduled for delayed redelivery (`RetryPolicyOptions`).
|
|
72
|
-
| `redelivery` | 2 s, x2, 5 min cap, equal jitter, `maxAge` `'1h'` | Delayed redelivery after `handlerRetry`: `initialDelay`, `factor`, `maxDelay`, `jitter`, and `maxAge`, how long a message may keep failing before it is dead-lettered.
|
|
73
|
-
| `ordered` | `false` | Consume strictly in order: single active consumer, one message at a time, failing messages retried in place. See Ordering.
|
|
74
|
-
| `reconnect` | 500 ms, x2, 30 s cap, equal jitter | Reconnect backoff (`initialDelay`, `factor`, `maxDelay`, `jitter`; attempts are unlimited), or `false`.
|
|
75
|
-
| `failOnConnectError` | `true` | `connect()` rejects when the broker is unreachable. When `false` it resolves and keeps trying in the background.
|
|
76
|
-
| `allowUnroutable` | `false` | When `true`, publishing a message no queue is bound to resolves (the broker discards it) instead of rejecting.
|
|
77
|
-
| `publishTimeout` | `'30s'` | How long `publish` waits for the confirm.
|
|
78
|
-
| `shutdownTimeout` | `'30s'` | How long `close()` waits for running handlers.
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
58
|
+
| Option | Default | Description |
|
|
59
|
+
| -------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `name` | `'rabbitmq'` | Name referenced by routes and subscriptions. |
|
|
61
|
+
| `url` | - | `amqp://` URL or `{ hostname, port, username, password, vhost, heartbeat, protocol }`. Give exactly one of `url` and `connect`. |
|
|
62
|
+
| `connect` | - | Factory returning an amqplib connection. Called again after every loss. Use for TLS options or credentials. |
|
|
63
|
+
| `exchange` | `'relaybus'` | Durable topic exchange, created when missing. |
|
|
64
|
+
| `prefetch` | `50` (`1` when `ordered`) | Unacknowledged messages held by the consumer; also the handler concurrency. |
|
|
65
|
+
| `queuePrefix` | `'relaybus'` | Prefix of derived queue names. |
|
|
66
|
+
| `consumerGroup` | - | Services sharing a group share one queue `<queuePrefix>.<consumerGroup>` and split the messages. Required to subscribe unless `queue` is set. |
|
|
67
|
+
| `queue` | - | Explicit queue name; overrides `queuePrefix` and `consumerGroup`. |
|
|
68
|
+
| `deadLetterExchange` | `'<exchange>.dead'` | Direct exchange poison messages are dead-lettered to. |
|
|
69
|
+
| `deadLetterQueue` | `'<queue>.dead'` | Quorum queue that keeps dead-lettered messages. |
|
|
70
|
+
| `deliveryLimit` | `10` | Quorum queue `x-delivery-limit`: a backstop that makes the broker dead-letter a message that crashes its consumer again and again. Failing handlers do not use it. |
|
|
71
|
+
| `handlerRetry` | 3 attempts, 200 ms, x2, 5 s cap, equal jitter | Handler attempts, with short backoff, on one delivery before the message is scheduled for delayed redelivery (`RetryPolicyOptions`). |
|
|
72
|
+
| `redelivery` | 2 s, x2, 5 min cap, equal jitter, `maxAge` `'1h'` | Delayed redelivery after `handlerRetry`: `initialDelay`, `factor`, `maxDelay`, `jitter`, and `maxAge`, how long a message may keep failing before it is dead-lettered. |
|
|
73
|
+
| `ordered` | `false` | Consume strictly in order: single active consumer, one message at a time, failing messages retried in place. See Ordering. |
|
|
74
|
+
| `reconnect` | 500 ms, x2, 30 s cap, equal jitter | Reconnect backoff (`initialDelay`, `factor`, `maxDelay`, `jitter`; attempts are unlimited), or `false`. |
|
|
75
|
+
| `failOnConnectError` | `true` | `connect()` rejects when the broker is unreachable. When `false` it resolves and keeps trying in the background. |
|
|
76
|
+
| `allowUnroutable` | `false` | When `true`, publishing a message no queue is bound to resolves (the broker discards it) instead of rejecting. |
|
|
77
|
+
| `publishTimeout` | `'30s'` | How long `publish` waits for the confirm. |
|
|
78
|
+
| `shutdownTimeout` | `'30s'` | How long `close()` waits for running handlers; when the bus shutdown deadline passes first, the connection's socket is destroyed instead. |
|
|
79
|
+
| `codec` | `cloudEventsBinary` | The wire format, both ways: `cloudEventsBinary` (CloudEvents binary mode, strict on read), `cloudEventsStructured`, `jsonEnvelope({...})` (your JSON envelope, MassTransit, or the bare payload) or `{ encode, decode }`. See [Interoperating with services that do not use relaybus](#interoperating-with-services-that-do-not-use-relaybus). |
|
|
80
|
+
| `decode` | none | Deprecated: reads with your function, writes binary mode. Same as `codec: { encode: encodeEnvelope, decode }`; not together with `codec`. |
|
|
81
|
+
| `logger` | `console` | `log`/`warn`/`error`. |
|
|
82
|
+
| `random` | `Math.random` | Jitter source, for tests. |
|
|
82
83
|
|
|
83
84
|
## Wire format
|
|
84
85
|
|
|
@@ -122,37 +123,75 @@ and the `cloudEvents:*` headers carry every attribute. A plain amqplib, Spring A
|
|
|
122
123
|
consumer only needs to parse the body; deduplicate on `messageId`, because an outbox delivers at
|
|
123
124
|
least once. Event headers are prefixed: `traceparent` arrives as `relaybus:traceparent`.
|
|
124
125
|
|
|
125
|
-
**Other
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
**Other formats, both ways.** By default the transport reads only binary-mode CloudEvents;
|
|
127
|
+
anything else is malformed and dead-lettered, so a misconfigured producer cannot slip events in.
|
|
128
|
+
The `codec` option sets another wire format for publishing and consuming (one transport is one
|
|
129
|
+
queue):
|
|
128
130
|
|
|
129
131
|
```ts
|
|
130
132
|
import {
|
|
131
133
|
RabbitMqTransport,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
cloudEventsStructured,
|
|
135
|
+
encodeEnvelope,
|
|
136
|
+
jsonEnvelope,
|
|
135
137
|
plainJsonDecoder,
|
|
136
|
-
type
|
|
138
|
+
type RabbitMqCodec,
|
|
137
139
|
} from '@relaybus/rabbitmq';
|
|
138
140
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
// CloudEvents structured mode (`contentType: application/cloudevents+json`); reads binary too.
|
|
142
|
+
new RabbitMqTransport({ url, consumerGroup: 'billing', codec: cloudEventsStructured });
|
|
143
|
+
|
|
144
|
+
// MassTransit's JSON envelope.
|
|
145
|
+
const massTransit = jsonEnvelope({
|
|
146
|
+
type: { body: 'messageType.0' },
|
|
147
|
+
id: { body: 'messageId' },
|
|
148
|
+
payload: { body: 'message' },
|
|
149
|
+
correlationId: { body: 'correlationId' },
|
|
150
|
+
causationId: { body: 'initiatorId' },
|
|
151
|
+
occurredAt: { body: 'sentTime' },
|
|
152
|
+
headers: { body: 'headers' },
|
|
153
|
+
contentType: 'application/vnd.masstransit+json',
|
|
144
154
|
});
|
|
145
155
|
|
|
146
|
-
// The
|
|
147
|
-
const
|
|
148
|
-
type:
|
|
149
|
-
|
|
156
|
+
// The bare JSON payload of a plain amqplib / Spring AMQP producer, the type in a header.
|
|
157
|
+
const plain = jsonEnvelope({
|
|
158
|
+
type: { header: 'event-type' },
|
|
159
|
+
id: 'message-id-property', // the AMQP messageId, or { header: 'message-id' }
|
|
160
|
+
payload: 'whole-body',
|
|
150
161
|
});
|
|
151
162
|
|
|
152
|
-
//
|
|
153
|
-
const
|
|
163
|
+
// Your own: binary mode out, plain JSON and CloudEvents in.
|
|
164
|
+
const custom: RabbitMqCodec = {
|
|
165
|
+
encode: encodeEnvelope,
|
|
166
|
+
decode: plainJsonDecoder({ type: (message) => message.properties.type as string | undefined }),
|
|
167
|
+
};
|
|
154
168
|
```
|
|
155
169
|
|
|
170
|
+
| Codec | Writes | Reads |
|
|
171
|
+
| ----------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
172
|
+
| `cloudEventsBinary` | Binary mode, as above. Default, byte for byte unchanged. | Binary mode only (AMQP properties as fallback). |
|
|
173
|
+
| `cloudEventsStructured` | One `application/cloudevents+json` document, binary-mode AMQP properties. | Structured and binary mode (`decodeCloudEvent`). |
|
|
174
|
+
| `jsonEnvelope(mapping)` | The mapped JSON body and headers, binary-mode AMQP properties. | The same mapping. A missing type, mapped id or payload is malformed. |
|
|
175
|
+
| `{ encode, decode }` | Whatever `encode` returns (`{ content, options }`; set `messageId`). | Whatever `decode` returns; missing fields are filled as for a decoder (table below). |
|
|
176
|
+
|
|
177
|
+
`jsonEnvelope` mappings name a body path (`{ body: 'meta.correlationId' }`, `'messageType.0'`
|
|
178
|
+
indexes an array) or a header (`{ header: 'event-type' }`) for `type`, `id`, `payload` (or
|
|
179
|
+
`'whole-body'`), `key`, `source`, `occurredAt`, `correlationId`, `causationId` and `headers`, plus a
|
|
180
|
+
`contentType`. `type`, `id` and `payload` are required by the types; `id: 'message-id-property'`
|
|
181
|
+
uses the `messageId` property, which the codec always writes. Unmapped, `source` is the `appId`
|
|
182
|
+
property and `occurredAt` the `timestamp` property (whole seconds). Event headers that are message
|
|
183
|
+
headers keep their names: a name the mapping uses, or one starting with `x-death`,
|
|
184
|
+
`x-first-death-`, `x-last-death-` or `x-relaybus-`, is refused with `RabbitMqUnencodableEventError`
|
|
185
|
+
(a `NonRetryableError`). The
|
|
186
|
+
[interoperability recipe](https://berkcansavur.github.io/relaybus/recipes/interoperating-with-other-services)
|
|
187
|
+
has a full table.
|
|
188
|
+
|
|
189
|
+
Decoded payloads go through the event's schema (`defineEvent(type, { schema })`) as relaybus's own
|
|
190
|
+
do; a payload that fails it makes the delivery dead with `EventValidationError`.
|
|
191
|
+
|
|
192
|
+
The decoders below remain available for a custom codec's `decode`, and for the deprecated `decode`
|
|
193
|
+
option:
|
|
194
|
+
|
|
156
195
|
| Decoder | Accepts |
|
|
157
196
|
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
158
197
|
| default | Binary-mode CloudEvents only, as described above. Unchanged. |
|
|
@@ -177,9 +216,9 @@ String message headers become event headers, except the broker's and the transpo
|
|
|
177
216
|
**Guarantees stay the same.** Decoding runs again on every redelivery (also after a retry-queue
|
|
178
217
|
round trip, which keeps `messageId` and restores the original routing key from the
|
|
179
218
|
`x-relaybus-routing-key` header it adds to retry copies) and must be deterministic; the inbox deduplicates on the id, so
|
|
180
|
-
a redelivered message is stored and handled once.
|
|
181
|
-
no type, an invalid type, an extractor returning a non-string) and
|
|
182
|
-
are dead-lettered (`x-relaybus-dead-letter-reason: malformed message`, the detail is logged) and the
|
|
219
|
+
a redelivered message is stored and handled once. Under every codec, a message that cannot be
|
|
220
|
+
decoded (not JSON, no id, no type, an invalid type, an extractor returning a non-string) and
|
|
221
|
+
anything a custom `decode` throws are dead-lettered (`x-relaybus-dead-letter-reason: malformed message`, the detail is logged) and the
|
|
183
222
|
consumer keeps running. Messages are handled in queue order only with `ordered: true`.
|
|
184
223
|
|
|
185
224
|
**Give producers a `messageId`.** Spring AMQP generates one when the message converter has
|
|
@@ -222,7 +261,10 @@ payload.
|
|
|
222
261
|
`InvalidEnvelopeError` from `bus.receive`. The consumer publishes a confirmed copy to the
|
|
223
262
|
dead-letter exchange, then acknowledges the original; the copy keeps the message headers, drops
|
|
224
263
|
the retry state (so replaying it starts a fresh retry window) and adds
|
|
225
|
-
`x-relaybus-dead-letter-reason
|
|
264
|
+
`x-relaybus-dead-letter-reason`, `x-relaybus-dead-letter-error-name` (the error class, such as
|
|
265
|
+
`MalformedMessageError`) and, for relaybus errors, `x-relaybus-dead-letter-error-code` (such as
|
|
266
|
+
`RELAYBUS_RABBITMQ_MALFORMED_MESSAGE`, see [Error codes](https://berkcansavur.github.io/relaybus/concepts/error-codes)).
|
|
267
|
+
The `error` log lines carry the same `errorName=` and `code=` fields. If that publish fails the consumer rejects the message without
|
|
226
268
|
requeue and the broker dead-letters it (at-least-once strategy), so it still ends in `<queue>.dead`.
|
|
227
269
|
- **Broker-level backstop.** `x-delivery-limit` (`deliveryLimit`) dead-letters a message that keeps
|
|
228
270
|
coming back because the consumer or its channel dies while holding it.
|