@relaybus/rabbitmq 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -24
- package/dist/index.cjs +806 -319
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +231 -7
- package/dist/index.d.ts +231 -7
- package/dist/index.js +795 -321
- package/dist/index.js.map +1 -1
- package/package.json +22 -9
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import { NonRetryableError, RelayBusError, RetryPolicyOptions, Duration, RelayBusLogger, Transport,
|
|
2
|
+
import { NonRetryableError, RelayBusError, EventHeaders, EventEnvelope, RetryPolicyOptions, Duration, RelayBusLogger, Transport, TransportMessageHandler, TransportSubscriptionHooks, CloseOptions } from '@relaybus/core';
|
|
3
|
+
import { JsonEnvelopeMapping } from '@relaybus/core/internal';
|
|
4
|
+
export { fromHeader, fromPayloadField } from '@relaybus/core/internal';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* The slice of amqplib this package relies on. Declaring it here keeps the
|
|
@@ -74,18 +76,26 @@ interface AmqpConnection extends EventEmitter {
|
|
|
74
76
|
* Always retryable; the outbox keeps the delivery and tries again.
|
|
75
77
|
*/
|
|
76
78
|
declare class RabbitMqUnavailableError extends RelayBusError {
|
|
79
|
+
name: string;
|
|
80
|
+
readonly code: string;
|
|
77
81
|
}
|
|
78
82
|
/** The broker negatively acknowledged a published message. Retryable. */
|
|
79
83
|
declare class RabbitMqPublishRejectedError extends RelayBusError {
|
|
84
|
+
name: string;
|
|
85
|
+
readonly code: string;
|
|
80
86
|
}
|
|
81
87
|
/**
|
|
82
88
|
* The broker accepted the message but no queue is bound to its routing key, so
|
|
83
89
|
* it would have been discarded. Retryable: it succeeds once a consumer binds.
|
|
84
90
|
*/
|
|
85
91
|
declare class UnroutableMessageError extends RelayBusError {
|
|
92
|
+
name: string;
|
|
93
|
+
readonly code: string;
|
|
86
94
|
}
|
|
87
95
|
/** A received message is not a valid relaybus CloudEvent. It is dead-lettered. */
|
|
88
96
|
declare class MalformedMessageError extends RelayBusError {
|
|
97
|
+
name: string;
|
|
98
|
+
readonly code: string;
|
|
89
99
|
}
|
|
90
100
|
/**
|
|
91
101
|
* The payload of an event cannot be represented as JSON without losing or
|
|
@@ -94,6 +104,7 @@ declare class MalformedMessageError extends RelayBusError {
|
|
|
94
104
|
*/
|
|
95
105
|
declare class UnserializablePayloadError extends NonRetryableError {
|
|
96
106
|
name: string;
|
|
107
|
+
readonly code: string;
|
|
97
108
|
}
|
|
98
109
|
/**
|
|
99
110
|
* A value exceeds a protocol limit of AMQP 0-9-1, for example an event id or a
|
|
@@ -101,8 +112,191 @@ declare class UnserializablePayloadError extends NonRetryableError {
|
|
|
101
112
|
*/
|
|
102
113
|
declare class AmqpLimitExceededError extends NonRetryableError {
|
|
103
114
|
name: string;
|
|
115
|
+
readonly code: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* An event the configured codec cannot write: a header name the format
|
|
119
|
+
* reserves, or a type a fixed-type route does not carry. Never retried.
|
|
120
|
+
*/
|
|
121
|
+
declare class RabbitMqUnencodableEventError extends NonRetryableError {
|
|
122
|
+
name: string;
|
|
123
|
+
readonly code: string;
|
|
104
124
|
}
|
|
105
125
|
|
|
126
|
+
/**
|
|
127
|
+
* The event a decoder extracted from a message. `id`, `type` and `payload` are
|
|
128
|
+
* required; every other field may be left out and is then filled by the
|
|
129
|
+
* transport: `source` with the `appId` property (else the routing key), `key`
|
|
130
|
+
* with null, `headers` with none, `correlationId` and `causationId` with null,
|
|
131
|
+
* and `occurredAt` with the `timestamp` property (else the time of receipt).
|
|
132
|
+
*/
|
|
133
|
+
interface DecodedEvent {
|
|
134
|
+
readonly id: string;
|
|
135
|
+
readonly type: string;
|
|
136
|
+
readonly payload: unknown;
|
|
137
|
+
readonly source?: string;
|
|
138
|
+
readonly key?: string | null;
|
|
139
|
+
readonly headers?: EventHeaders;
|
|
140
|
+
readonly correlationId?: string | null;
|
|
141
|
+
readonly causationId?: string | null;
|
|
142
|
+
readonly occurredAt?: Date;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Turns a consumed message into an event, for queues fed by producers that do
|
|
146
|
+
* not use relaybus. Must be synchronous and free of side effects: it runs again
|
|
147
|
+
* on every redelivery, so the same message must always give the same event,
|
|
148
|
+
* above all the same `id`, which is what the inbox deduplicates on.
|
|
149
|
+
*
|
|
150
|
+
* Anything it throws marks the message as malformed: it is dead-lettered and
|
|
151
|
+
* the consumer moves on. An event the bus then rejects (empty id, invalid type
|
|
152
|
+
* or time) is dead-lettered the same way.
|
|
153
|
+
*/
|
|
154
|
+
type RabbitMqMessageDecoder = (message: AmqpMessage) => DecodedEvent;
|
|
155
|
+
/** A plain JSON message, decoded for the extractor options of {@link plainJsonDecoder}. */
|
|
156
|
+
interface PlainJsonMessage {
|
|
157
|
+
readonly routingKey: string;
|
|
158
|
+
/** The AMQP properties as delivered (`messageId`, `type`, `appId`, `timestamp`, ...). */
|
|
159
|
+
readonly properties: AmqpMessageProperties;
|
|
160
|
+
/**
|
|
161
|
+
* The message headers with string values, without the broker's and the
|
|
162
|
+
* transport's own (`x-death`, `x-first-death-*`, `x-last-death-*`, `x-relaybus-*`).
|
|
163
|
+
*/
|
|
164
|
+
readonly headers: EventHeaders;
|
|
165
|
+
/** The body parsed as JSON. */
|
|
166
|
+
readonly payload: unknown;
|
|
167
|
+
}
|
|
168
|
+
/** Options of {@link plainJsonDecoder}. Extractors returning undefined or `''` select the default. */
|
|
169
|
+
interface PlainJsonDecoderOptions {
|
|
170
|
+
/**
|
|
171
|
+
* Event type handlers subscribe to: a fixed name for a queue that carries one
|
|
172
|
+
* kind of event, or an extractor such as `fromHeader('event-type')`,
|
|
173
|
+
* `fromPayloadField('type')` or `(message) => message.properties.type`.
|
|
174
|
+
* Required; a message without a type is malformed.
|
|
175
|
+
*/
|
|
176
|
+
readonly type: string | ((message: PlainJsonMessage) => string | undefined);
|
|
177
|
+
/**
|
|
178
|
+
* Event id the inbox deduplicates on. Default, and fallback when the
|
|
179
|
+
* extractor finds none: the `messageId` property, which survives
|
|
180
|
+
* redeliveries. A message with neither is malformed, because AMQP has no
|
|
181
|
+
* other identity that is stable across redeliveries; give producers a
|
|
182
|
+
* `messageId` (Spring AMQP can generate one with `setCreateMessageIds(true)`).
|
|
183
|
+
*/
|
|
184
|
+
readonly id?: (message: PlainJsonMessage) => string | undefined;
|
|
185
|
+
/** Ordering key. Default: none. */
|
|
186
|
+
readonly key?: (message: PlainJsonMessage) => string | null | undefined;
|
|
187
|
+
/** Logical name of the producer. Default: the `appId` property, else the routing key. */
|
|
188
|
+
readonly source?: string | ((message: PlainJsonMessage) => string | undefined);
|
|
189
|
+
/** When the event happened. Default: the `timestamp` property, else the time of receipt. */
|
|
190
|
+
readonly occurredAt?: (message: PlainJsonMessage) => Date | undefined;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Decodes a CloudEvents 1.0 message in either content mode:
|
|
194
|
+
* - structured (`contentType` property `application/cloudevents+json`): the
|
|
195
|
+
* body is a JSON object with `specversion`, `id`, `type`, `source` and
|
|
196
|
+
* optionally `time` (else the `timestamp` property, else the time of
|
|
197
|
+
* receipt), `subject` (the key), `data` (the payload, null when absent) and
|
|
198
|
+
* a JSON `datacontenttype`. `data_base64` is refused, because relaybus
|
|
199
|
+
* payloads are JSON. Extension attributes with string values (`traceparent`,
|
|
200
|
+
* ...) become event headers, as do the string message headers;
|
|
201
|
+
* `correlationid` and `causationid` fill their envelope fields.
|
|
202
|
+
* - binary (anything else): exactly the default decoder, `cloudEvents:*`
|
|
203
|
+
* headers with the AMQP properties as fallback.
|
|
204
|
+
*
|
|
205
|
+
* @throws MalformedMessageError for a message that is neither.
|
|
206
|
+
*/
|
|
207
|
+
declare function decodeCloudEvent(message: AmqpMessage): EventEnvelope;
|
|
208
|
+
/**
|
|
209
|
+
* Creates a decoder for messages whose body is plain JSON, as published by a
|
|
210
|
+
* service that does not use relaybus (plain amqplib, Spring AMQP's
|
|
211
|
+
* `Jackson2JsonMessageConverter`, a Go or Python service): the body becomes the
|
|
212
|
+
* payload, the string headers become event headers, and the options say where
|
|
213
|
+
* the event type and id come from.
|
|
214
|
+
*
|
|
215
|
+
* A message that is a CloudEvent anyway (a `cloudEvents:specversion` header,
|
|
216
|
+
* or a structured `application/cloudevents+json` body) is decoded with
|
|
217
|
+
* {@link decodeCloudEvent} instead, so relaybus and foreign producers can feed
|
|
218
|
+
* one queue. A body that is not JSON, a missing or invalid type, a missing id
|
|
219
|
+
* and an extractor returning anything but a string are malformed.
|
|
220
|
+
*
|
|
221
|
+
* ```ts
|
|
222
|
+
* new RabbitMqTransport({
|
|
223
|
+
* url,
|
|
224
|
+
* consumerGroup: 'billing',
|
|
225
|
+
* decode: plainJsonDecoder({ type: (message) => message.properties.type as string }),
|
|
226
|
+
* });
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @throws ConfigurationError when a fixed `type` is not a valid event type.
|
|
230
|
+
*/
|
|
231
|
+
declare function plainJsonDecoder(options: PlainJsonDecoderOptions): RabbitMqMessageDecoder;
|
|
232
|
+
|
|
233
|
+
/** A serialized event, ready for `channel.publish`. */
|
|
234
|
+
interface EncodedMessage {
|
|
235
|
+
readonly content: Buffer;
|
|
236
|
+
readonly options: AmqpPublishOptions;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The wire format of a {@link RabbitMqTransport}: how an event becomes a
|
|
241
|
+
* message (`encode`, on publish) and a consumed message becomes an event
|
|
242
|
+
* (`decode`).
|
|
243
|
+
*
|
|
244
|
+
* Both must be synchronous and free of side effects. What `encode` throws
|
|
245
|
+
* fails the publish: the outbox retries it, unless it is a `NonRetryableError`
|
|
246
|
+
* such as {@link RabbitMqUnencodableEventError}. Give every message a
|
|
247
|
+
* `messageId`: the transport matches broker returns of unroutable messages by
|
|
248
|
+
* it. What `decode` throws marks the message as malformed: it is
|
|
249
|
+
* dead-lettered and the consumer moves on. See {@link RabbitMqMessageDecoder}
|
|
250
|
+
* for the decode contract.
|
|
251
|
+
*/
|
|
252
|
+
interface RabbitMqCodec {
|
|
253
|
+
readonly encode: (envelope: EventEnvelope) => EncodedMessage;
|
|
254
|
+
readonly decode: RabbitMqMessageDecoder;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* CloudEvents 1.0 binary content mode, the default: the body is the JSON
|
|
258
|
+
* payload, the attributes travel as `cloudEvents:*` headers mirrored in the
|
|
259
|
+
* AMQP properties. Decoding is strict: any other message is malformed.
|
|
260
|
+
*/
|
|
261
|
+
declare const cloudEventsBinary: RabbitMqCodec;
|
|
262
|
+
/**
|
|
263
|
+
* CloudEvents 1.0 structured content mode: the body is one
|
|
264
|
+
* `application/cloudevents+json` document with the attributes and `data`.
|
|
265
|
+
* Decoding accepts structured and binary mode ({@link decodeCloudEvent}).
|
|
266
|
+
* Event headers travel under their own names; names starting with `x-death`,
|
|
267
|
+
* `x-first-death-`, `x-last-death-` or `x-relaybus-` are refused.
|
|
268
|
+
*/
|
|
269
|
+
declare const cloudEventsStructured: RabbitMqCodec;
|
|
270
|
+
/**
|
|
271
|
+
* Where each value of a JSON message lives, for {@link jsonEnvelope}. Besides
|
|
272
|
+
* `{ body }` and `{ header }`, the id may be `'message-id-property'`: the
|
|
273
|
+
* AMQP `messageId` property, which the codec always writes and which survives
|
|
274
|
+
* redeliveries; a message without one is malformed.
|
|
275
|
+
*/
|
|
276
|
+
type RabbitMqJsonEnvelopeMapping = JsonEnvelopeMapping<'message-id-property'>;
|
|
277
|
+
/**
|
|
278
|
+
* A JSON wire format declared by a mapping, in both directions: for queues
|
|
279
|
+
* shared with services that wrap events in their own envelope
|
|
280
|
+
* (`{ "eventName": ..., "data": {...} }`, MassTransit) or send the bare
|
|
281
|
+
* payload. Every message also gets the AMQP properties of binary mode:
|
|
282
|
+
* `messageId` (the event id), `type`, `appId` (the source), `correlationId`,
|
|
283
|
+
* `timestamp` (seconds) and `persistent`. Event
|
|
284
|
+
* headers that are message headers are written as they are, so a name the
|
|
285
|
+
* mapping uses for another field, or one the broker reserves, is refused
|
|
286
|
+
* with {@link RabbitMqUnencodableEventError}.
|
|
287
|
+
*
|
|
288
|
+
* ```ts
|
|
289
|
+
* codec: jsonEnvelope({
|
|
290
|
+
* type: { body: 'eventName' },
|
|
291
|
+
* id: 'message-id-property',
|
|
292
|
+
* payload: { body: 'data' },
|
|
293
|
+
* })
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* @throws ConfigurationError for an incomplete or self-overlapping mapping.
|
|
297
|
+
*/
|
|
298
|
+
declare function jsonEnvelope(mapping: RabbitMqJsonEnvelopeMapping): RabbitMqCodec;
|
|
299
|
+
|
|
106
300
|
/** Connection settings accepted by `amqplib.connect`, given as an object instead of a URL. */
|
|
107
301
|
interface BrokerConnectionSettings {
|
|
108
302
|
readonly protocol?: string;
|
|
@@ -139,9 +333,11 @@ interface RabbitMqTransportOptions {
|
|
|
139
333
|
readonly url?: string | BrokerConnectionSettings;
|
|
140
334
|
/**
|
|
141
335
|
* Opens one connection. Called again after every connection loss. Use it for
|
|
142
|
-
* TLS options, custom credentials or an alternative amqplib instance.
|
|
336
|
+
* TLS options, custom credentials or an alternative amqplib instance. `signal` aborts when a
|
|
337
|
+
* shutdown runs past its deadline: pass it on (`amqplib.connect(url, { signal })`) so a broker
|
|
338
|
+
* that never answers the handshake cannot hold the socket open.
|
|
143
339
|
*/
|
|
144
|
-
readonly connect?: () => Promise<AmqpConnection>;
|
|
340
|
+
readonly connect?: (options: ConnectOptions) => Promise<AmqpConnection>;
|
|
145
341
|
/** Topic exchange events are published to. Durable, created when missing. Default `'relaybus'`. */
|
|
146
342
|
readonly exchange?: string;
|
|
147
343
|
/**
|
|
@@ -209,11 +405,36 @@ interface RabbitMqTransportOptions {
|
|
|
209
405
|
readonly publishTimeout?: Duration;
|
|
210
406
|
/** How long `close` waits for running handlers. Default `'30s'`. */
|
|
211
407
|
readonly shutdownTimeout?: Duration;
|
|
408
|
+
/**
|
|
409
|
+
* The wire format, both ways: how events become messages and consumed
|
|
410
|
+
* messages become events. Default {@link cloudEventsBinary}: CloudEvents
|
|
411
|
+
* binary mode (`cloudEvents:*` headers, AMQP properties as fallback), any
|
|
412
|
+
* other message is malformed and dead-lettered. {@link cloudEventsStructured}
|
|
413
|
+
* writes structured mode and reads both, {@link jsonEnvelope} maps a JSON
|
|
414
|
+
* format of your own (an envelope with the payload inside, or the bare
|
|
415
|
+
* payload), and an object with `encode` and `decode` can do anything else.
|
|
416
|
+
* Whatever `decode` throws marks the message as malformed: it is
|
|
417
|
+
* dead-lettered, the consumer keeps running.
|
|
418
|
+
*/
|
|
419
|
+
readonly codec?: RabbitMqCodec;
|
|
420
|
+
/**
|
|
421
|
+
* How consumed messages become events; events are still written in
|
|
422
|
+
* CloudEvents binary mode.
|
|
423
|
+
*
|
|
424
|
+
* @deprecated Use `codec`: `decode: fn` is `codec: { encode: encodeEnvelope, decode: fn }`.
|
|
425
|
+
* Giving both `decode` and `codec` is a configuration error.
|
|
426
|
+
*/
|
|
427
|
+
readonly decode?: RabbitMqMessageDecoder;
|
|
212
428
|
/** Default: `console`. */
|
|
213
429
|
readonly logger?: RelayBusLogger;
|
|
214
430
|
/** Random source for jitter. Default `Math.random`. Intended for tests. */
|
|
215
431
|
readonly random?: () => number;
|
|
216
432
|
}
|
|
433
|
+
/** What `connect` gets for each connection it opens. */
|
|
434
|
+
interface ConnectOptions {
|
|
435
|
+
/** Aborted when a shutdown runs past its deadline; destroys a socket still connecting. */
|
|
436
|
+
readonly signal: AbortSignal;
|
|
437
|
+
}
|
|
217
438
|
|
|
218
439
|
/**
|
|
219
440
|
* RabbitMQ {@link Transport} built on amqplib.
|
|
@@ -240,6 +461,7 @@ interface RabbitMqTransportOptions {
|
|
|
240
461
|
declare class RabbitMqTransport implements Transport {
|
|
241
462
|
#private;
|
|
242
463
|
readonly name: string;
|
|
464
|
+
readonly system = "rabbitmq";
|
|
243
465
|
constructor(options: RabbitMqTransportOptions);
|
|
244
466
|
/**
|
|
245
467
|
* True while the connection is open and publishing and consuming are set up.
|
|
@@ -262,6 +484,7 @@ declare class RabbitMqTransport implements Transport {
|
|
|
262
484
|
* @throws {RabbitMqUnavailableError} not connected, channel lost, or confirm timed out
|
|
263
485
|
* @throws {RabbitMqPublishRejectedError} the broker nacked the message
|
|
264
486
|
* @throws {UnroutableMessageError} no queue is bound to `destination` (unless `allowUnroutable`)
|
|
487
|
+
* @throws whatever the `codec`'s `encode` throws, such as {@link RabbitMqUnencodableEventError}
|
|
265
488
|
*/
|
|
266
489
|
publish(envelope: EventEnvelope, destination: string): Promise<void>;
|
|
267
490
|
/**
|
|
@@ -269,13 +492,14 @@ declare class RabbitMqTransport implements Transport {
|
|
|
269
492
|
* to `onMessage`. Call it once per transport; give each subscription its own
|
|
270
493
|
* transport instance, with its own `name` and `consumerGroup`.
|
|
271
494
|
*/
|
|
272
|
-
subscribe(destinations: readonly string[], onMessage: TransportMessageHandler): Promise<void>;
|
|
495
|
+
subscribe(destinations: readonly string[], onMessage: TransportMessageHandler, hooks?: TransportSubscriptionHooks): Promise<void>;
|
|
273
496
|
/**
|
|
274
497
|
* Stops consuming, waits up to `shutdownTimeout` for running handlers
|
|
275
498
|
* (messages still being retried go back to the queue), then closes the
|
|
276
|
-
* channels and the connection.
|
|
499
|
+
* channels and the connection. Once `options.signal` aborts (the bus shutdown deadline passed),
|
|
500
|
+
* the connection's socket is destroyed instead of waiting for the broker.
|
|
277
501
|
*/
|
|
278
|
-
close(): Promise<void>;
|
|
502
|
+
close(options?: CloseOptions): Promise<void>;
|
|
279
503
|
}
|
|
280
504
|
|
|
281
|
-
export { type AmqpChannel, type AmqpConfirmChannel, type AmqpConnection, AmqpLimitExceededError, type AmqpMessage, type AmqpMessageProperties, type AmqpPublishOptions, type BrokerConnectionSettings, MalformedMessageError, RabbitMqPublishRejectedError, RabbitMqTransport, type RabbitMqTransportOptions, RabbitMqUnavailableError, type ReconnectOptions, type RedeliveryOptions, UnroutableMessageError, UnserializablePayloadError };
|
|
505
|
+
export { type AmqpChannel, type AmqpConfirmChannel, type AmqpConnection, AmqpLimitExceededError, type AmqpMessage, type AmqpMessageProperties, type AmqpPublishOptions, type BrokerConnectionSettings, type DecodedEvent, type EncodedMessage, MalformedMessageError, type PlainJsonDecoderOptions, type PlainJsonMessage, type RabbitMqCodec, type RabbitMqJsonEnvelopeMapping, type RabbitMqMessageDecoder, RabbitMqPublishRejectedError, RabbitMqTransport, type RabbitMqTransportOptions, RabbitMqUnavailableError, RabbitMqUnencodableEventError, type ReconnectOptions, type RedeliveryOptions, UnroutableMessageError, UnserializablePayloadError, cloudEventsBinary, cloudEventsStructured, decodeCloudEvent, jsonEnvelope, plainJsonDecoder };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import { NonRetryableError, RelayBusError, RetryPolicyOptions, Duration, RelayBusLogger, Transport,
|
|
2
|
+
import { NonRetryableError, RelayBusError, EventHeaders, EventEnvelope, RetryPolicyOptions, Duration, RelayBusLogger, Transport, TransportMessageHandler, TransportSubscriptionHooks, CloseOptions } from '@relaybus/core';
|
|
3
|
+
import { JsonEnvelopeMapping } from '@relaybus/core/internal';
|
|
4
|
+
export { fromHeader, fromPayloadField } from '@relaybus/core/internal';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* The slice of amqplib this package relies on. Declaring it here keeps the
|
|
@@ -74,18 +76,26 @@ interface AmqpConnection extends EventEmitter {
|
|
|
74
76
|
* Always retryable; the outbox keeps the delivery and tries again.
|
|
75
77
|
*/
|
|
76
78
|
declare class RabbitMqUnavailableError extends RelayBusError {
|
|
79
|
+
name: string;
|
|
80
|
+
readonly code: string;
|
|
77
81
|
}
|
|
78
82
|
/** The broker negatively acknowledged a published message. Retryable. */
|
|
79
83
|
declare class RabbitMqPublishRejectedError extends RelayBusError {
|
|
84
|
+
name: string;
|
|
85
|
+
readonly code: string;
|
|
80
86
|
}
|
|
81
87
|
/**
|
|
82
88
|
* The broker accepted the message but no queue is bound to its routing key, so
|
|
83
89
|
* it would have been discarded. Retryable: it succeeds once a consumer binds.
|
|
84
90
|
*/
|
|
85
91
|
declare class UnroutableMessageError extends RelayBusError {
|
|
92
|
+
name: string;
|
|
93
|
+
readonly code: string;
|
|
86
94
|
}
|
|
87
95
|
/** A received message is not a valid relaybus CloudEvent. It is dead-lettered. */
|
|
88
96
|
declare class MalformedMessageError extends RelayBusError {
|
|
97
|
+
name: string;
|
|
98
|
+
readonly code: string;
|
|
89
99
|
}
|
|
90
100
|
/**
|
|
91
101
|
* The payload of an event cannot be represented as JSON without losing or
|
|
@@ -94,6 +104,7 @@ declare class MalformedMessageError extends RelayBusError {
|
|
|
94
104
|
*/
|
|
95
105
|
declare class UnserializablePayloadError extends NonRetryableError {
|
|
96
106
|
name: string;
|
|
107
|
+
readonly code: string;
|
|
97
108
|
}
|
|
98
109
|
/**
|
|
99
110
|
* A value exceeds a protocol limit of AMQP 0-9-1, for example an event id or a
|
|
@@ -101,8 +112,191 @@ declare class UnserializablePayloadError extends NonRetryableError {
|
|
|
101
112
|
*/
|
|
102
113
|
declare class AmqpLimitExceededError extends NonRetryableError {
|
|
103
114
|
name: string;
|
|
115
|
+
readonly code: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* An event the configured codec cannot write: a header name the format
|
|
119
|
+
* reserves, or a type a fixed-type route does not carry. Never retried.
|
|
120
|
+
*/
|
|
121
|
+
declare class RabbitMqUnencodableEventError extends NonRetryableError {
|
|
122
|
+
name: string;
|
|
123
|
+
readonly code: string;
|
|
104
124
|
}
|
|
105
125
|
|
|
126
|
+
/**
|
|
127
|
+
* The event a decoder extracted from a message. `id`, `type` and `payload` are
|
|
128
|
+
* required; every other field may be left out and is then filled by the
|
|
129
|
+
* transport: `source` with the `appId` property (else the routing key), `key`
|
|
130
|
+
* with null, `headers` with none, `correlationId` and `causationId` with null,
|
|
131
|
+
* and `occurredAt` with the `timestamp` property (else the time of receipt).
|
|
132
|
+
*/
|
|
133
|
+
interface DecodedEvent {
|
|
134
|
+
readonly id: string;
|
|
135
|
+
readonly type: string;
|
|
136
|
+
readonly payload: unknown;
|
|
137
|
+
readonly source?: string;
|
|
138
|
+
readonly key?: string | null;
|
|
139
|
+
readonly headers?: EventHeaders;
|
|
140
|
+
readonly correlationId?: string | null;
|
|
141
|
+
readonly causationId?: string | null;
|
|
142
|
+
readonly occurredAt?: Date;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Turns a consumed message into an event, for queues fed by producers that do
|
|
146
|
+
* not use relaybus. Must be synchronous and free of side effects: it runs again
|
|
147
|
+
* on every redelivery, so the same message must always give the same event,
|
|
148
|
+
* above all the same `id`, which is what the inbox deduplicates on.
|
|
149
|
+
*
|
|
150
|
+
* Anything it throws marks the message as malformed: it is dead-lettered and
|
|
151
|
+
* the consumer moves on. An event the bus then rejects (empty id, invalid type
|
|
152
|
+
* or time) is dead-lettered the same way.
|
|
153
|
+
*/
|
|
154
|
+
type RabbitMqMessageDecoder = (message: AmqpMessage) => DecodedEvent;
|
|
155
|
+
/** A plain JSON message, decoded for the extractor options of {@link plainJsonDecoder}. */
|
|
156
|
+
interface PlainJsonMessage {
|
|
157
|
+
readonly routingKey: string;
|
|
158
|
+
/** The AMQP properties as delivered (`messageId`, `type`, `appId`, `timestamp`, ...). */
|
|
159
|
+
readonly properties: AmqpMessageProperties;
|
|
160
|
+
/**
|
|
161
|
+
* The message headers with string values, without the broker's and the
|
|
162
|
+
* transport's own (`x-death`, `x-first-death-*`, `x-last-death-*`, `x-relaybus-*`).
|
|
163
|
+
*/
|
|
164
|
+
readonly headers: EventHeaders;
|
|
165
|
+
/** The body parsed as JSON. */
|
|
166
|
+
readonly payload: unknown;
|
|
167
|
+
}
|
|
168
|
+
/** Options of {@link plainJsonDecoder}. Extractors returning undefined or `''` select the default. */
|
|
169
|
+
interface PlainJsonDecoderOptions {
|
|
170
|
+
/**
|
|
171
|
+
* Event type handlers subscribe to: a fixed name for a queue that carries one
|
|
172
|
+
* kind of event, or an extractor such as `fromHeader('event-type')`,
|
|
173
|
+
* `fromPayloadField('type')` or `(message) => message.properties.type`.
|
|
174
|
+
* Required; a message without a type is malformed.
|
|
175
|
+
*/
|
|
176
|
+
readonly type: string | ((message: PlainJsonMessage) => string | undefined);
|
|
177
|
+
/**
|
|
178
|
+
* Event id the inbox deduplicates on. Default, and fallback when the
|
|
179
|
+
* extractor finds none: the `messageId` property, which survives
|
|
180
|
+
* redeliveries. A message with neither is malformed, because AMQP has no
|
|
181
|
+
* other identity that is stable across redeliveries; give producers a
|
|
182
|
+
* `messageId` (Spring AMQP can generate one with `setCreateMessageIds(true)`).
|
|
183
|
+
*/
|
|
184
|
+
readonly id?: (message: PlainJsonMessage) => string | undefined;
|
|
185
|
+
/** Ordering key. Default: none. */
|
|
186
|
+
readonly key?: (message: PlainJsonMessage) => string | null | undefined;
|
|
187
|
+
/** Logical name of the producer. Default: the `appId` property, else the routing key. */
|
|
188
|
+
readonly source?: string | ((message: PlainJsonMessage) => string | undefined);
|
|
189
|
+
/** When the event happened. Default: the `timestamp` property, else the time of receipt. */
|
|
190
|
+
readonly occurredAt?: (message: PlainJsonMessage) => Date | undefined;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Decodes a CloudEvents 1.0 message in either content mode:
|
|
194
|
+
* - structured (`contentType` property `application/cloudevents+json`): the
|
|
195
|
+
* body is a JSON object with `specversion`, `id`, `type`, `source` and
|
|
196
|
+
* optionally `time` (else the `timestamp` property, else the time of
|
|
197
|
+
* receipt), `subject` (the key), `data` (the payload, null when absent) and
|
|
198
|
+
* a JSON `datacontenttype`. `data_base64` is refused, because relaybus
|
|
199
|
+
* payloads are JSON. Extension attributes with string values (`traceparent`,
|
|
200
|
+
* ...) become event headers, as do the string message headers;
|
|
201
|
+
* `correlationid` and `causationid` fill their envelope fields.
|
|
202
|
+
* - binary (anything else): exactly the default decoder, `cloudEvents:*`
|
|
203
|
+
* headers with the AMQP properties as fallback.
|
|
204
|
+
*
|
|
205
|
+
* @throws MalformedMessageError for a message that is neither.
|
|
206
|
+
*/
|
|
207
|
+
declare function decodeCloudEvent(message: AmqpMessage): EventEnvelope;
|
|
208
|
+
/**
|
|
209
|
+
* Creates a decoder for messages whose body is plain JSON, as published by a
|
|
210
|
+
* service that does not use relaybus (plain amqplib, Spring AMQP's
|
|
211
|
+
* `Jackson2JsonMessageConverter`, a Go or Python service): the body becomes the
|
|
212
|
+
* payload, the string headers become event headers, and the options say where
|
|
213
|
+
* the event type and id come from.
|
|
214
|
+
*
|
|
215
|
+
* A message that is a CloudEvent anyway (a `cloudEvents:specversion` header,
|
|
216
|
+
* or a structured `application/cloudevents+json` body) is decoded with
|
|
217
|
+
* {@link decodeCloudEvent} instead, so relaybus and foreign producers can feed
|
|
218
|
+
* one queue. A body that is not JSON, a missing or invalid type, a missing id
|
|
219
|
+
* and an extractor returning anything but a string are malformed.
|
|
220
|
+
*
|
|
221
|
+
* ```ts
|
|
222
|
+
* new RabbitMqTransport({
|
|
223
|
+
* url,
|
|
224
|
+
* consumerGroup: 'billing',
|
|
225
|
+
* decode: plainJsonDecoder({ type: (message) => message.properties.type as string }),
|
|
226
|
+
* });
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @throws ConfigurationError when a fixed `type` is not a valid event type.
|
|
230
|
+
*/
|
|
231
|
+
declare function plainJsonDecoder(options: PlainJsonDecoderOptions): RabbitMqMessageDecoder;
|
|
232
|
+
|
|
233
|
+
/** A serialized event, ready for `channel.publish`. */
|
|
234
|
+
interface EncodedMessage {
|
|
235
|
+
readonly content: Buffer;
|
|
236
|
+
readonly options: AmqpPublishOptions;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The wire format of a {@link RabbitMqTransport}: how an event becomes a
|
|
241
|
+
* message (`encode`, on publish) and a consumed message becomes an event
|
|
242
|
+
* (`decode`).
|
|
243
|
+
*
|
|
244
|
+
* Both must be synchronous and free of side effects. What `encode` throws
|
|
245
|
+
* fails the publish: the outbox retries it, unless it is a `NonRetryableError`
|
|
246
|
+
* such as {@link RabbitMqUnencodableEventError}. Give every message a
|
|
247
|
+
* `messageId`: the transport matches broker returns of unroutable messages by
|
|
248
|
+
* it. What `decode` throws marks the message as malformed: it is
|
|
249
|
+
* dead-lettered and the consumer moves on. See {@link RabbitMqMessageDecoder}
|
|
250
|
+
* for the decode contract.
|
|
251
|
+
*/
|
|
252
|
+
interface RabbitMqCodec {
|
|
253
|
+
readonly encode: (envelope: EventEnvelope) => EncodedMessage;
|
|
254
|
+
readonly decode: RabbitMqMessageDecoder;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* CloudEvents 1.0 binary content mode, the default: the body is the JSON
|
|
258
|
+
* payload, the attributes travel as `cloudEvents:*` headers mirrored in the
|
|
259
|
+
* AMQP properties. Decoding is strict: any other message is malformed.
|
|
260
|
+
*/
|
|
261
|
+
declare const cloudEventsBinary: RabbitMqCodec;
|
|
262
|
+
/**
|
|
263
|
+
* CloudEvents 1.0 structured content mode: the body is one
|
|
264
|
+
* `application/cloudevents+json` document with the attributes and `data`.
|
|
265
|
+
* Decoding accepts structured and binary mode ({@link decodeCloudEvent}).
|
|
266
|
+
* Event headers travel under their own names; names starting with `x-death`,
|
|
267
|
+
* `x-first-death-`, `x-last-death-` or `x-relaybus-` are refused.
|
|
268
|
+
*/
|
|
269
|
+
declare const cloudEventsStructured: RabbitMqCodec;
|
|
270
|
+
/**
|
|
271
|
+
* Where each value of a JSON message lives, for {@link jsonEnvelope}. Besides
|
|
272
|
+
* `{ body }` and `{ header }`, the id may be `'message-id-property'`: the
|
|
273
|
+
* AMQP `messageId` property, which the codec always writes and which survives
|
|
274
|
+
* redeliveries; a message without one is malformed.
|
|
275
|
+
*/
|
|
276
|
+
type RabbitMqJsonEnvelopeMapping = JsonEnvelopeMapping<'message-id-property'>;
|
|
277
|
+
/**
|
|
278
|
+
* A JSON wire format declared by a mapping, in both directions: for queues
|
|
279
|
+
* shared with services that wrap events in their own envelope
|
|
280
|
+
* (`{ "eventName": ..., "data": {...} }`, MassTransit) or send the bare
|
|
281
|
+
* payload. Every message also gets the AMQP properties of binary mode:
|
|
282
|
+
* `messageId` (the event id), `type`, `appId` (the source), `correlationId`,
|
|
283
|
+
* `timestamp` (seconds) and `persistent`. Event
|
|
284
|
+
* headers that are message headers are written as they are, so a name the
|
|
285
|
+
* mapping uses for another field, or one the broker reserves, is refused
|
|
286
|
+
* with {@link RabbitMqUnencodableEventError}.
|
|
287
|
+
*
|
|
288
|
+
* ```ts
|
|
289
|
+
* codec: jsonEnvelope({
|
|
290
|
+
* type: { body: 'eventName' },
|
|
291
|
+
* id: 'message-id-property',
|
|
292
|
+
* payload: { body: 'data' },
|
|
293
|
+
* })
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* @throws ConfigurationError for an incomplete or self-overlapping mapping.
|
|
297
|
+
*/
|
|
298
|
+
declare function jsonEnvelope(mapping: RabbitMqJsonEnvelopeMapping): RabbitMqCodec;
|
|
299
|
+
|
|
106
300
|
/** Connection settings accepted by `amqplib.connect`, given as an object instead of a URL. */
|
|
107
301
|
interface BrokerConnectionSettings {
|
|
108
302
|
readonly protocol?: string;
|
|
@@ -139,9 +333,11 @@ interface RabbitMqTransportOptions {
|
|
|
139
333
|
readonly url?: string | BrokerConnectionSettings;
|
|
140
334
|
/**
|
|
141
335
|
* Opens one connection. Called again after every connection loss. Use it for
|
|
142
|
-
* TLS options, custom credentials or an alternative amqplib instance.
|
|
336
|
+
* TLS options, custom credentials or an alternative amqplib instance. `signal` aborts when a
|
|
337
|
+
* shutdown runs past its deadline: pass it on (`amqplib.connect(url, { signal })`) so a broker
|
|
338
|
+
* that never answers the handshake cannot hold the socket open.
|
|
143
339
|
*/
|
|
144
|
-
readonly connect?: () => Promise<AmqpConnection>;
|
|
340
|
+
readonly connect?: (options: ConnectOptions) => Promise<AmqpConnection>;
|
|
145
341
|
/** Topic exchange events are published to. Durable, created when missing. Default `'relaybus'`. */
|
|
146
342
|
readonly exchange?: string;
|
|
147
343
|
/**
|
|
@@ -209,11 +405,36 @@ interface RabbitMqTransportOptions {
|
|
|
209
405
|
readonly publishTimeout?: Duration;
|
|
210
406
|
/** How long `close` waits for running handlers. Default `'30s'`. */
|
|
211
407
|
readonly shutdownTimeout?: Duration;
|
|
408
|
+
/**
|
|
409
|
+
* The wire format, both ways: how events become messages and consumed
|
|
410
|
+
* messages become events. Default {@link cloudEventsBinary}: CloudEvents
|
|
411
|
+
* binary mode (`cloudEvents:*` headers, AMQP properties as fallback), any
|
|
412
|
+
* other message is malformed and dead-lettered. {@link cloudEventsStructured}
|
|
413
|
+
* writes structured mode and reads both, {@link jsonEnvelope} maps a JSON
|
|
414
|
+
* format of your own (an envelope with the payload inside, or the bare
|
|
415
|
+
* payload), and an object with `encode` and `decode` can do anything else.
|
|
416
|
+
* Whatever `decode` throws marks the message as malformed: it is
|
|
417
|
+
* dead-lettered, the consumer keeps running.
|
|
418
|
+
*/
|
|
419
|
+
readonly codec?: RabbitMqCodec;
|
|
420
|
+
/**
|
|
421
|
+
* How consumed messages become events; events are still written in
|
|
422
|
+
* CloudEvents binary mode.
|
|
423
|
+
*
|
|
424
|
+
* @deprecated Use `codec`: `decode: fn` is `codec: { encode: encodeEnvelope, decode: fn }`.
|
|
425
|
+
* Giving both `decode` and `codec` is a configuration error.
|
|
426
|
+
*/
|
|
427
|
+
readonly decode?: RabbitMqMessageDecoder;
|
|
212
428
|
/** Default: `console`. */
|
|
213
429
|
readonly logger?: RelayBusLogger;
|
|
214
430
|
/** Random source for jitter. Default `Math.random`. Intended for tests. */
|
|
215
431
|
readonly random?: () => number;
|
|
216
432
|
}
|
|
433
|
+
/** What `connect` gets for each connection it opens. */
|
|
434
|
+
interface ConnectOptions {
|
|
435
|
+
/** Aborted when a shutdown runs past its deadline; destroys a socket still connecting. */
|
|
436
|
+
readonly signal: AbortSignal;
|
|
437
|
+
}
|
|
217
438
|
|
|
218
439
|
/**
|
|
219
440
|
* RabbitMQ {@link Transport} built on amqplib.
|
|
@@ -240,6 +461,7 @@ interface RabbitMqTransportOptions {
|
|
|
240
461
|
declare class RabbitMqTransport implements Transport {
|
|
241
462
|
#private;
|
|
242
463
|
readonly name: string;
|
|
464
|
+
readonly system = "rabbitmq";
|
|
243
465
|
constructor(options: RabbitMqTransportOptions);
|
|
244
466
|
/**
|
|
245
467
|
* True while the connection is open and publishing and consuming are set up.
|
|
@@ -262,6 +484,7 @@ declare class RabbitMqTransport implements Transport {
|
|
|
262
484
|
* @throws {RabbitMqUnavailableError} not connected, channel lost, or confirm timed out
|
|
263
485
|
* @throws {RabbitMqPublishRejectedError} the broker nacked the message
|
|
264
486
|
* @throws {UnroutableMessageError} no queue is bound to `destination` (unless `allowUnroutable`)
|
|
487
|
+
* @throws whatever the `codec`'s `encode` throws, such as {@link RabbitMqUnencodableEventError}
|
|
265
488
|
*/
|
|
266
489
|
publish(envelope: EventEnvelope, destination: string): Promise<void>;
|
|
267
490
|
/**
|
|
@@ -269,13 +492,14 @@ declare class RabbitMqTransport implements Transport {
|
|
|
269
492
|
* to `onMessage`. Call it once per transport; give each subscription its own
|
|
270
493
|
* transport instance, with its own `name` and `consumerGroup`.
|
|
271
494
|
*/
|
|
272
|
-
subscribe(destinations: readonly string[], onMessage: TransportMessageHandler): Promise<void>;
|
|
495
|
+
subscribe(destinations: readonly string[], onMessage: TransportMessageHandler, hooks?: TransportSubscriptionHooks): Promise<void>;
|
|
273
496
|
/**
|
|
274
497
|
* Stops consuming, waits up to `shutdownTimeout` for running handlers
|
|
275
498
|
* (messages still being retried go back to the queue), then closes the
|
|
276
|
-
* channels and the connection.
|
|
499
|
+
* channels and the connection. Once `options.signal` aborts (the bus shutdown deadline passed),
|
|
500
|
+
* the connection's socket is destroyed instead of waiting for the broker.
|
|
277
501
|
*/
|
|
278
|
-
close(): Promise<void>;
|
|
502
|
+
close(options?: CloseOptions): Promise<void>;
|
|
279
503
|
}
|
|
280
504
|
|
|
281
|
-
export { type AmqpChannel, type AmqpConfirmChannel, type AmqpConnection, AmqpLimitExceededError, type AmqpMessage, type AmqpMessageProperties, type AmqpPublishOptions, type BrokerConnectionSettings, MalformedMessageError, RabbitMqPublishRejectedError, RabbitMqTransport, type RabbitMqTransportOptions, RabbitMqUnavailableError, type ReconnectOptions, type RedeliveryOptions, UnroutableMessageError, UnserializablePayloadError };
|
|
505
|
+
export { type AmqpChannel, type AmqpConfirmChannel, type AmqpConnection, AmqpLimitExceededError, type AmqpMessage, type AmqpMessageProperties, type AmqpPublishOptions, type BrokerConnectionSettings, type DecodedEvent, type EncodedMessage, MalformedMessageError, type PlainJsonDecoderOptions, type PlainJsonMessage, type RabbitMqCodec, type RabbitMqJsonEnvelopeMapping, type RabbitMqMessageDecoder, RabbitMqPublishRejectedError, RabbitMqTransport, type RabbitMqTransportOptions, RabbitMqUnavailableError, RabbitMqUnencodableEventError, type ReconnectOptions, type RedeliveryOptions, UnroutableMessageError, UnserializablePayloadError, cloudEventsBinary, cloudEventsStructured, decodeCloudEvent, jsonEnvelope, plainJsonDecoder };
|