@relaybus/rabbitmq 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,281 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { NonRetryableError, RelayBusError, RetryPolicyOptions, Duration, RelayBusLogger, Transport, EventEnvelope, TransportMessageHandler } from '@relaybus/core';
3
+
4
+ /**
5
+ * The slice of amqplib this package relies on. Declaring it here keeps the
6
+ * public types independent of the amqplib version (peer range `>=0.10`) and
7
+ * lets tests substitute fakes. Real amqplib connections and channels satisfy it.
8
+ */
9
+ /** Message properties as delivered by the broker. */
10
+ interface AmqpMessageProperties {
11
+ readonly messageId?: unknown;
12
+ readonly type?: unknown;
13
+ readonly appId?: unknown;
14
+ readonly correlationId?: unknown;
15
+ readonly timestamp?: unknown;
16
+ readonly contentType?: unknown;
17
+ readonly headers?: Readonly<Record<string, unknown>> | undefined;
18
+ }
19
+ /** A message delivered to a consumer or returned by the broker. */
20
+ interface AmqpMessage {
21
+ readonly content: Buffer;
22
+ readonly fields: {
23
+ readonly routingKey: string;
24
+ };
25
+ readonly properties: AmqpMessageProperties;
26
+ }
27
+ /** Properties set on a published message. */
28
+ interface AmqpPublishOptions {
29
+ readonly contentType: string;
30
+ readonly persistent: boolean;
31
+ readonly mandatory?: boolean;
32
+ readonly messageId?: string;
33
+ readonly type?: string;
34
+ readonly appId?: string;
35
+ readonly timestamp?: number;
36
+ readonly correlationId?: string;
37
+ readonly headers: Readonly<Record<string, unknown>>;
38
+ }
39
+ /** Channel used for consuming. */
40
+ interface AmqpChannel extends EventEmitter {
41
+ assertExchange(exchange: string, type: string, options?: {
42
+ readonly durable?: boolean;
43
+ }): Promise<unknown>;
44
+ assertQueue(queue: string, options?: {
45
+ readonly durable?: boolean;
46
+ readonly arguments?: Record<string, unknown>;
47
+ }): Promise<unknown>;
48
+ bindQueue(queue: string, source: string, pattern: string): Promise<unknown>;
49
+ prefetch(count: number): Promise<unknown>;
50
+ consume(queue: string, onMessage: (message: AmqpMessage | null) => void, options?: {
51
+ readonly noAck?: boolean;
52
+ }): Promise<{
53
+ readonly consumerTag: string;
54
+ }>;
55
+ cancel(consumerTag: string): Promise<unknown>;
56
+ ack(message: AmqpMessage): void;
57
+ nack(message: AmqpMessage, allUpTo?: boolean, requeue?: boolean): void;
58
+ close(): Promise<void>;
59
+ }
60
+ /** Channel with publisher confirms. */
61
+ interface AmqpConfirmChannel extends AmqpChannel {
62
+ publish(exchange: string, routingKey: string, content: Buffer, options: AmqpPublishOptions, callback: (error: Error | null | undefined) => void): boolean;
63
+ }
64
+ /** A broker connection. Emits `close` and `error`. */
65
+ interface AmqpConnection extends EventEmitter {
66
+ createChannel(): Promise<AmqpChannel>;
67
+ createConfirmChannel(): Promise<AmqpConfirmChannel>;
68
+ close(): Promise<void>;
69
+ }
70
+
71
+ /**
72
+ * The broker cannot take or confirm a message right now: the transport is not
73
+ * connected, was closed, lost its channel, or the confirm did not arrive in time.
74
+ * Always retryable; the outbox keeps the delivery and tries again.
75
+ */
76
+ declare class RabbitMqUnavailableError extends RelayBusError {
77
+ }
78
+ /** The broker negatively acknowledged a published message. Retryable. */
79
+ declare class RabbitMqPublishRejectedError extends RelayBusError {
80
+ }
81
+ /**
82
+ * The broker accepted the message but no queue is bound to its routing key, so
83
+ * it would have been discarded. Retryable: it succeeds once a consumer binds.
84
+ */
85
+ declare class UnroutableMessageError extends RelayBusError {
86
+ }
87
+ /** A received message is not a valid relaybus CloudEvent. It is dead-lettered. */
88
+ declare class MalformedMessageError extends RelayBusError {
89
+ }
90
+ /**
91
+ * The payload of an event cannot be represented as JSON without losing or
92
+ * changing data (`undefined`, functions, symbols, BigInt, NaN, Infinity, Dates,
93
+ * Maps, class instances, cycles). Never retried: the payload will not change.
94
+ */
95
+ declare class UnserializablePayloadError extends NonRetryableError {
96
+ name: string;
97
+ }
98
+ /**
99
+ * A value exceeds a protocol limit of AMQP 0-9-1, for example an event id or a
100
+ * routing key longer than the 255 bytes of a short string. Never retried.
101
+ */
102
+ declare class AmqpLimitExceededError extends NonRetryableError {
103
+ name: string;
104
+ }
105
+
106
+ /** Connection settings accepted by `amqplib.connect`, given as an object instead of a URL. */
107
+ interface BrokerConnectionSettings {
108
+ readonly protocol?: string;
109
+ readonly hostname?: string;
110
+ readonly port?: number;
111
+ readonly username?: string;
112
+ readonly password?: string;
113
+ readonly vhost?: string;
114
+ /** Seconds. See "Operational notes" in the README for the heartbeat defaults of each amqplib version. */
115
+ readonly heartbeat?: number;
116
+ }
117
+ /** Backoff between reconnect attempts. Attempts are unlimited. */
118
+ type ReconnectOptions = Pick<RetryPolicyOptions, 'initialDelay' | 'factor' | 'maxDelay' | 'jitter'>;
119
+ /**
120
+ * Delayed redelivery of messages whose handler keeps failing. Each failed
121
+ * delivery goes to a retry queue and returns to the main queue after a growing
122
+ * delay, so an outage of the handler's dependency is waited out instead of
123
+ * burning through delivery attempts.
124
+ */
125
+ type RedeliveryOptions = Pick<RetryPolicyOptions, 'initialDelay' | 'factor' | 'maxDelay' | 'jitter'> & {
126
+ /**
127
+ * How long a message may keep failing, counted from its first delayed
128
+ * redelivery, before it is dead-lettered. Default `'1h'`.
129
+ */
130
+ readonly maxAge?: Duration;
131
+ };
132
+ interface RabbitMqTransportOptions {
133
+ /** Name referenced by routes and subscriptions. Default `'rabbitmq'`. */
134
+ readonly name?: string;
135
+ /**
136
+ * Broker location, for example `amqp://user:pass@host:5672/vhost?heartbeat=30`.
137
+ * Give exactly one of `url` and `connect`.
138
+ */
139
+ readonly url?: string | BrokerConnectionSettings;
140
+ /**
141
+ * Opens one connection. Called again after every connection loss. Use it for
142
+ * TLS options, custom credentials or an alternative amqplib instance.
143
+ */
144
+ readonly connect?: () => Promise<AmqpConnection>;
145
+ /** Topic exchange events are published to. Durable, created when missing. Default `'relaybus'`. */
146
+ readonly exchange?: string;
147
+ /**
148
+ * Unacknowledged messages the consumer holds, which is also its concurrency.
149
+ * Default 50, or 1 when `ordered`.
150
+ */
151
+ readonly prefetch?: number;
152
+ /** Prefix of derived queue names. Default `'relaybus'`. */
153
+ readonly queuePrefix?: string;
154
+ /**
155
+ * Consumer group: services sharing it share one queue and split the messages,
156
+ * services with different groups each receive every message. The queue is
157
+ * `<queuePrefix>.<consumerGroup>`. Required when subscribing, unless `queue` is set.
158
+ */
159
+ readonly consumerGroup?: string;
160
+ /** Explicit queue name. Overrides `queuePrefix` and `consumerGroup`. */
161
+ readonly queue?: string;
162
+ /** Exchange poison messages are dead-lettered to. Default `'<exchange>.dead'`. */
163
+ readonly deadLetterExchange?: string;
164
+ /** Queue that keeps dead-lettered messages for inspection. Default `'<queue>.dead'`. */
165
+ readonly deadLetterQueue?: string;
166
+ /**
167
+ * Quorum queue `x-delivery-limit`: deliveries after which the broker itself
168
+ * dead-letters a message. Failing handlers do not consume it (they use
169
+ * `redelivery`); it only stops a message that crashes the consumer or its
170
+ * channel again and again. Default 10.
171
+ */
172
+ readonly deliveryLimit?: number;
173
+ /**
174
+ * Handler attempts on the same delivery, with backoff, before the message is
175
+ * scheduled for delayed redelivery. Absorbs short blips without a broker round
176
+ * trip. Default: 3 attempts, 200 ms initial delay, factor 2, 5 s cap, equal jitter.
177
+ */
178
+ readonly handlerRetry?: RetryPolicyOptions;
179
+ /**
180
+ * Delayed redelivery after `handlerRetry` is spent. Default: 2 s initial
181
+ * delay, factor 2, 5 min cap, equal jitter, dead-lettered after 1 h of
182
+ * failing (about 18 redeliveries). Delays are quantized to the retry queues,
183
+ * one per step of the exponential ladder (9 queues with the defaults).
184
+ * Ignored when `ordered`, where the same delays are waited in place.
185
+ */
186
+ readonly redelivery?: RedeliveryOptions;
187
+ /**
188
+ * Consume the queue strictly in order: a single active consumer across all
189
+ * replicas, one message at a time, a failing message is retried in place
190
+ * (blocking the queue) instead of being delayed. Needed when
191
+ * `EventEnvelope.key` order matters; costs throughput, because one handler
192
+ * runs at a time. Default false: handlers run concurrently and a delayed
193
+ * redelivery overtakes later messages, so order is not preserved.
194
+ */
195
+ readonly ordered?: boolean;
196
+ /** Reconnect backoff, or `false` to never reconnect. Default: 500 ms initial, factor 2, 30 s cap, equal jitter. */
197
+ readonly reconnect?: ReconnectOptions | false;
198
+ /**
199
+ * When true (default) `connect()` rejects if the broker is unreachable, which
200
+ * fails startup. When false it resolves and keeps trying in the background.
201
+ */
202
+ readonly failOnConnectError?: boolean;
203
+ /**
204
+ * When false (default) a message that no queue is bound to makes `publish`
205
+ * reject. When true it resolves and the broker silently discards the message.
206
+ */
207
+ readonly allowUnroutable?: boolean;
208
+ /** How long `publish` waits for the broker confirm. Default `'30s'`. */
209
+ readonly publishTimeout?: Duration;
210
+ /** How long `close` waits for running handlers. Default `'30s'`. */
211
+ readonly shutdownTimeout?: Duration;
212
+ /** Default: `console`. */
213
+ readonly logger?: RelayBusLogger;
214
+ /** Random source for jitter. Default `Math.random`. Intended for tests. */
215
+ readonly random?: () => number;
216
+ }
217
+
218
+ /**
219
+ * RabbitMQ {@link Transport} built on amqplib.
220
+ *
221
+ * Publishing: each event goes to a durable topic exchange as a persistent,
222
+ * mandatory message with the destination as routing key. `publish` resolves
223
+ * only after the broker confirmed it was persisted and routed to a queue, and
224
+ * rejects when the transport is disconnected, the broker nacks, the message is
225
+ * unroutable or no confirm arrives in time. It never drops a message silently,
226
+ * so the outbox retries the delivery.
227
+ *
228
+ * Subscribing: one durable quorum queue per consumer group, bound to the
229
+ * destinations (routing-key patterns such as `orders.*`). A message is
230
+ * acknowledged only after the handler resolved. A failing handler is retried
231
+ * on the same delivery, then through retry queues with growing delays (see
232
+ * `redelivery`), and finally dead-lettered after the retry window; poison
233
+ * messages are dead-lettered at once. Nothing is lost on the way.
234
+ *
235
+ * Reconnection: after a connection or channel loss the transport reconnects
236
+ * with capped, jittered backoff and restores channels, topology and the
237
+ * consumer. Events published meanwhile are rejected, and therefore retried by
238
+ * the outbox, rather than buffered in memory.
239
+ */
240
+ declare class RabbitMqTransport implements Transport {
241
+ #private;
242
+ readonly name: string;
243
+ constructor(options: RabbitMqTransportOptions);
244
+ /**
245
+ * True while the connection is open and publishing and consuming are set up.
246
+ * Use it for readiness probes.
247
+ */
248
+ isConnected(): boolean;
249
+ /**
250
+ * True when the transport can publish and, once subscribed, is consuming: the
251
+ * connection is up, its channels are open and the consumer is registered. False while reconnecting and after a cancelled consumer.
252
+ */
253
+ isReady(): boolean;
254
+ /** Connects to the broker. Rejects when it is unreachable unless `failOnConnectError` is false. */
255
+ connect(): Promise<void>;
256
+ /**
257
+ * Publishes to the exchange with `destination` as routing key and resolves
258
+ * after the broker confirmed the message.
259
+ *
260
+ * @throws {UnserializablePayloadError} the payload is not representable as JSON (never retried)
261
+ * @throws {AmqpLimitExceededError} the event id, a header name or `destination` exceeds 255 bytes (never retried)
262
+ * @throws {RabbitMqUnavailableError} not connected, channel lost, or confirm timed out
263
+ * @throws {RabbitMqPublishRejectedError} the broker nacked the message
264
+ * @throws {UnroutableMessageError} no queue is bound to `destination` (unless `allowUnroutable`)
265
+ */
266
+ publish(envelope: EventEnvelope, destination: string): Promise<void>;
267
+ /**
268
+ * Binds one durable quorum queue to `destinations` and delivers its messages
269
+ * to `onMessage`. Call it once per transport; give each subscription its own
270
+ * transport instance, with its own `name` and `consumerGroup`.
271
+ */
272
+ subscribe(destinations: readonly string[], onMessage: TransportMessageHandler): Promise<void>;
273
+ /**
274
+ * Stops consuming, waits up to `shutdownTimeout` for running handlers
275
+ * (messages still being retried go back to the queue), then closes the
276
+ * channels and the connection.
277
+ */
278
+ close(): Promise<void>;
279
+ }
280
+
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 };