@linagora/rabbitmq-client 0.4.0 → 0.5.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 CHANGED
@@ -109,6 +109,19 @@ await client.subscribe('events', 'order.placed', 'order-queue', handler, {
109
109
  })
110
110
  ```
111
111
 
112
+ One queue can take messages from several exchanges. Pass the other bindings in `bindings`; they share the queue, its consumer and its DLQ, and are restored on reconnection. Name the dead letter exchange after the queue with `deadLetterExchange`, so it does not live under another service's exchange. For exchanges another service owns, `passiveExchanges` checks they exist instead of declaring them, and `subscribe` fails when one is missing:
113
+
114
+ ```typescript
115
+ await client.subscribe('space', 'twake.space.#', 'twake-space', handler, {
116
+ bindings: [
117
+ { exchange: 'b2b', routingKey: 'domain.user.deleted' },
118
+ { exchange: 'auth', routingKey: 'user.deleted' },
119
+ ],
120
+ deadLetterExchange: 'twake-space.dlx',
121
+ passiveExchanges: true,
122
+ })
123
+ ```
124
+
112
125
  ### Unsubscribing
113
126
 
114
127
  Cancel a consumer and remove it from the auto-restoration list:
@@ -123,10 +136,21 @@ After unsubscribing, the queue will not be re-subscribed on reconnection.
123
136
 
124
137
  Incoming messages are JSON-parsed first. If that fails, the message goes straight to the DLQ (no point retrying garbage). Otherwise, your handler runs up to `maxRetries` times. Success means ack, final failure means nack to the DLQ.
125
138
 
126
- The handler also receives what the publisher and the broker attached to the delivery: `headers`, `timestamp`, `messageId` and `correlationId`. A handler that only needs the body can ignore it.
139
+ A handler that knows retrying will not help throws `DeadLetterError`, and the message goes to the DLQ at once.
140
+
141
+ A subscription can set its own `maxRetries`, `Infinity` included, and `maxRetryDelay`, which doubles the delay between attempts from `retryDelay` up to that cap. With `maxRetries: Infinity` a message waits unacked while, say, the database is down, and keeps its place in the queue. `close()` and a reconnect stop the retries and leave the message unacked, so the broker redelivers it.
142
+
143
+ ```typescript
144
+ await client.subscribe('events', 'order.placed', 'order-queue', handler, {
145
+ maxRetries: Infinity,
146
+ maxRetryDelay: 60_000,
147
+ })
148
+ ```
149
+
150
+ The handler also receives where the message came from, `exchange` and `routingKey`, and what the publisher and the broker attached to the delivery: `headers`, `timestamp`, `messageId` and `correlationId`. A handler that only needs the body can ignore it.
127
151
 
128
152
  ```typescript
129
- await client.subscribe('events', 'order.placed', 'order-queue', async (message, { headers }) => {
153
+ await client.subscribe('events', 'order.#', 'order-queue', async (message, { routingKey, headers }) => {
130
154
  // `x-death` is set by the broker on a message that went through a DLQ
131
155
  const replayed = headers['x-death'] !== undefined
132
156
  })
@@ -184,7 +208,7 @@ const client = new RabbitMQClient({
184
208
  |------|-----------|
185
209
  | `onPublish` | A message is confirmed by the broker |
186
210
  | `onMessageProcessed` | A handler completes successfully (includes duration and retry count) |
187
- | `onMessageDlq` | A message is nacked to the DLQ — reason is `'invalid_json'` or `'max_retries_exhausted'` |
211
+ | `onMessageDlq` | A message is nacked to the DLQ, with reason `'invalid_json'`, `'max_retries_exhausted'` or `'dead_letter_error'` |
188
212
  | `onReconnect` | The client reconnects and re-establishes subscriptions |
189
213
 
190
214
  ## Test helpers
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ DeadLetterError: () => DeadLetterError,
33
34
  RabbitMQClient: () => RabbitMQClient,
34
35
  UnroutableMessageError: () => UnroutableMessageError,
35
36
  defaultLogger: () => defaultLogger,
@@ -128,6 +129,9 @@ var DEFAULTS = {
128
129
  prefetch: DEFAULT_PREFETCH,
129
130
  closeTimeout: 5e3
130
131
  };
132
+ function backoff(baseDelay, attempt, maxDelay) {
133
+ return Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
134
+ }
131
135
  var UnroutableMessageError = class extends Error {
132
136
  constructor(exchange, routingKey) {
133
137
  super(`No queue is bound to receive ${exchange || "(default exchange)"}/${routingKey}`);
@@ -138,10 +142,17 @@ var UnroutableMessageError = class extends Error {
138
142
  exchange;
139
143
  routingKey;
140
144
  };
145
+ var DeadLetterError = class extends Error {
146
+ constructor(message, options) {
147
+ super(message, options);
148
+ this.name = "DeadLetterError";
149
+ }
150
+ };
141
151
  var RabbitMQClient = class {
142
152
  connection = null;
143
153
  channel = null;
144
154
  connected = false;
155
+ closing = false;
145
156
  subscriptions = [];
146
157
  options;
147
158
  logger;
@@ -157,6 +168,7 @@ var RabbitMQClient = class {
157
168
  // connection drops keep holding their permits, and the new consumer cannot
158
169
  // exceed the ceiling while the old pipeline drains. `null` = no limit.
159
170
  subscriptionSemaphores = /* @__PURE__ */ new Map();
171
+ retryWakers = /* @__PURE__ */ new Set();
160
172
  inflightCount = 0;
161
173
  drainResolve = null;
162
174
  constructor(options) {
@@ -201,34 +213,43 @@ var RabbitMQClient = class {
201
213
  async doConnect(maxAttempts) {
202
214
  this.assertedExchanges.clear();
203
215
  this.consumerTags.clear();
216
+ this.connection?.close().catch(() => void 0);
217
+ this.connection = null;
204
218
  let attempts = 0;
205
- while (!this.connected) {
219
+ while (!this.connected && !this.closing) {
206
220
  try {
207
- this.connection = await import_amqplib.default.connect(this.options.url);
221
+ const connection = await import_amqplib.default.connect(this.options.url);
222
+ this.connection = connection;
208
223
  this.logger.info("Connected to server");
209
- this.channel = await this.connection.createConfirmChannel();
224
+ const channel = await connection.createConfirmChannel();
225
+ this.channel = channel;
226
+ this.wakeRetries();
210
227
  const returnWaiters = /* @__PURE__ */ new Set();
211
228
  this.returnWaiters = returnWaiters;
212
- this.channel.on("return", (msg) => {
229
+ channel.on("return", (msg) => {
213
230
  for (const waiter of returnWaiters) waiter(msg);
214
231
  });
215
232
  this.logger.info("Confirm channel created");
216
- await this.channel.prefetch(this.options.prefetch);
233
+ await channel.prefetch(this.options.prefetch);
217
234
  this.logger.info("Channel prefetch set", { prefetch: this.options.prefetch });
218
- this.connection.on("error", (error) => {
235
+ connection.on("error", (error) => {
236
+ if (connection !== this.connection) return;
219
237
  this.connected = false;
220
238
  this.logger.error("Connection error", { error });
221
239
  });
222
- this.connection.on("close", () => {
240
+ connection.on("close", () => {
241
+ if (connection !== this.connection) return;
223
242
  this.connected = false;
224
243
  this.logger.warn("Connection closed");
225
244
  this.reconnectWithRetry();
226
245
  });
227
- this.channel.on("error", (error) => {
246
+ channel.on("error", (error) => {
247
+ if (channel !== this.channel) return;
228
248
  this.logger.error("Channel error", { error });
229
249
  this.handleChannelFailure();
230
250
  });
231
- this.channel.on("close", () => {
251
+ channel.on("close", () => {
252
+ if (channel !== this.channel) return;
232
253
  this.logger.warn("Channel closed");
233
254
  this.handleChannelFailure();
234
255
  });
@@ -252,6 +273,9 @@ var RabbitMQClient = class {
252
273
  await this.sleep(this.options.connectionRetryDelay);
253
274
  }
254
275
  }
276
+ if (!this.connected) {
277
+ throw new Error("RabbitMQ client closed while connecting");
278
+ }
255
279
  }
256
280
  handleChannelFailure() {
257
281
  if (this.connected) {
@@ -338,10 +362,7 @@ var RabbitMQClient = class {
338
362
  `Failed to publish to ${exchange || "(default exchange)"}/${routingKey} after ${attempts} attempts: ${error instanceof Error ? error.message : String(error)}`
339
363
  );
340
364
  }
341
- const retryDelay = Math.min(
342
- baseDelay * Math.pow(2, attempts - 1),
343
- MAX_PUBLISH_RETRY_DELAY_MS
344
- );
365
+ const retryDelay = backoff(baseDelay, attempts, MAX_PUBLISH_RETRY_DELAY_MS);
345
366
  this.logger.warn("Publish attempt failed, retrying", { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay });
346
367
  await this.sleep(retryDelay);
347
368
  }
@@ -354,13 +375,19 @@ var RabbitMQClient = class {
354
375
  * `init()` / reconnect cycle.
355
376
  */
356
377
  async close(clearSubscriptions = true) {
378
+ this.closing = true;
357
379
  try {
380
+ await Promise.allSettled([this.initializationPromise, this.reconnectionPromise]);
381
+ this.wakeRetries();
358
382
  if (this.inflightCount > 0) {
359
383
  this.logger.info("Waiting for in-flight messages to drain", { inflightCount: this.inflightCount });
360
384
  await this.waitForDrain(this.options.closeTimeout);
361
385
  }
362
- await this.channel?.close();
363
- await this.connection?.close();
386
+ try {
387
+ await this.channel?.close();
388
+ } finally {
389
+ await this.connection?.close();
390
+ }
364
391
  this.connection = null;
365
392
  this.channel = null;
366
393
  this.connected = false;
@@ -376,9 +403,12 @@ var RabbitMQClient = class {
376
403
  } catch (error) {
377
404
  this.logger.error("Error closing connection", { error });
378
405
  throw error;
406
+ } finally {
407
+ this.closing = false;
379
408
  }
380
409
  }
381
410
  async reconnectWithRetry() {
411
+ if (this.closing) return;
382
412
  if (this.reconnectionPromise) {
383
413
  return this.reconnectionPromise;
384
414
  }
@@ -388,6 +418,8 @@ var RabbitMQClient = class {
388
418
  this.reconnectionPromise.catch(() => void 0);
389
419
  try {
390
420
  await this.reconnectionPromise;
421
+ } catch (error) {
422
+ if (!this.closing) throw error;
391
423
  } finally {
392
424
  this.reconnectionPromise = null;
393
425
  }
@@ -439,13 +471,19 @@ var RabbitMQClient = class {
439
471
  }
440
472
  const sub = { exchange, routingKey, queue, handler, options };
441
473
  const existingIndex = this.subscriptions.findIndex((s) => s.queue === queue);
474
+ const previous = existingIndex === -1 ? void 0 : this.subscriptions[existingIndex];
442
475
  if (existingIndex === -1) {
443
476
  this.subscriptions.push(sub);
444
477
  } else {
445
478
  this.subscriptions[existingIndex] = sub;
446
479
  this.subscriptionSemaphores.delete(queue);
447
480
  }
448
- await this.setupSubscription(sub);
481
+ try {
482
+ await this.setupSubscription(sub);
483
+ } catch (error) {
484
+ this.subscriptions = previous ? this.subscriptions.map((s) => s === sub ? previous : s) : this.subscriptions.filter((s) => s !== sub);
485
+ throw error;
486
+ }
449
487
  }
450
488
  /**
451
489
  * Cancels a queue subscription and removes it from the restoration list.
@@ -466,16 +504,22 @@ var RabbitMQClient = class {
466
504
  }
467
505
  const channel = this.channel;
468
506
  const { exchange, routingKey, queue, handler, options } = sub;
469
- const dlxExchange = `${exchange}.dlx`;
507
+ const dlxExchange = options?.deadLetterExchange ?? `${exchange}.dlx`;
470
508
  const dlqQueue = `${queue}.dlq`;
471
509
  const dlqRoutingKey = `${routingKey}.dead`;
510
+ const bindings = [{ exchange, routingKey }, ...options?.bindings ?? []];
472
511
  await channel.assertExchange(dlxExchange, "topic", { durable: true });
473
512
  await channel.assertQueue(dlqQueue, { durable: true });
474
513
  await channel.bindQueue(dlqQueue, dlxExchange, dlqRoutingKey);
475
- if (!this.assertedExchanges.has(exchange)) {
476
- await channel.assertExchange(exchange, "topic", { durable: true });
477
- this.assertedExchanges.add(exchange);
514
+ const missing = bindings.map((b) => b.exchange).filter((name) => !this.assertedExchanges.has(name));
515
+ if (options?.passiveExchanges && missing.length > 0) {
516
+ await this.checkExchanges(missing);
517
+ } else {
518
+ for (const name of missing) {
519
+ await channel.assertExchange(name, "topic", { durable: true });
520
+ }
478
521
  }
522
+ for (const name of missing) this.assertedExchanges.add(name);
479
523
  const queueType = options?.queueArguments?.["x-queue-type"] ?? "quorum";
480
524
  const queueArgs = {
481
525
  "x-queue-type": queueType,
@@ -490,7 +534,9 @@ var RabbitMQClient = class {
490
534
  deadLetterRoutingKey: dlqRoutingKey,
491
535
  arguments: { ...queueArgs, ...options?.queueArguments }
492
536
  });
493
- await channel.bindQueue(queue, exchange, routingKey);
537
+ for (const binding of bindings) {
538
+ await channel.bindQueue(queue, binding.exchange, binding.routingKey);
539
+ }
494
540
  if (!this.subscriptionSemaphores.has(queue)) {
495
541
  const concurrency = options?.concurrency ?? this.options.concurrency;
496
542
  this.subscriptionSemaphores.set(queue, concurrency > 0 ? new Semaphore(concurrency) : null);
@@ -500,13 +546,29 @@ var RabbitMQClient = class {
500
546
  queue,
501
547
  (message) => {
502
548
  if (message) {
503
- this.dispatch(message, handler, semaphore, channel);
549
+ this.dispatch(message, handler, semaphore, channel, options);
504
550
  }
505
551
  },
506
552
  { noAck: false }
507
553
  );
508
554
  this.consumerTags.set(queue, consumerTag);
509
- this.logger.info("Subscribed to queue", { exchange, routingKey, queue });
555
+ this.logger.info("Subscribed to queue", { queue, bindings });
556
+ }
557
+ // A failed check closes the channel it ran on, so it runs on a throwaway one
558
+ // rather than take down the shared channel and every consumer on it.
559
+ async checkExchanges(exchanges) {
560
+ if (!this.connection) {
561
+ throw new Error("Connection not available");
562
+ }
563
+ const probe = await this.connection.createChannel();
564
+ probe.on("error", () => void 0);
565
+ try {
566
+ for (const name of exchanges) {
567
+ await probe.checkExchange(name);
568
+ }
569
+ } finally {
570
+ await probe.close().catch(() => void 0);
571
+ }
510
572
  }
511
573
  /**
512
574
  * Gates a delivered message on the subscription's concurrency semaphore, then
@@ -514,10 +576,10 @@ var RabbitMQClient = class {
514
576
  * settles (including time spent waiting for a permit), so graceful `close()`
515
577
  * drains queued messages as well as actively-processing ones.
516
578
  */
517
- dispatch(message, handler, semaphore, channel) {
579
+ dispatch(message, handler, semaphore, channel, options) {
518
580
  this.inflightCount++;
519
581
  const acquire = semaphore ? semaphore.acquire() : Promise.resolve();
520
- acquire.then(() => this.handleWithRetry(message, handler, channel)).catch((error) => {
582
+ acquire.then(() => this.handleWithRetry(message, handler, channel, options)).catch((error) => {
521
583
  this.logger.error("Unhandled error in message handler", { error });
522
584
  }).finally(() => {
523
585
  semaphore?.release();
@@ -527,7 +589,7 @@ var RabbitMQClient = class {
527
589
  }
528
590
  });
529
591
  }
530
- async handleWithRetry(message, handler, channel) {
592
+ async handleWithRetry(message, handler, channel, options) {
531
593
  if (channel !== this.channel) {
532
594
  this.logger.warn("Skipping message from a superseded channel; it will be redelivered");
533
595
  return;
@@ -555,26 +617,41 @@ var RabbitMQClient = class {
555
617
  }
556
618
  this.logger.debug("Message received, processing", { exchange, routingKey, payload: content });
557
619
  const properties = {
620
+ exchange,
621
+ routingKey,
558
622
  headers: message.properties.headers ?? {},
559
623
  timestamp: message.properties.timestamp,
560
624
  messageId: message.properties.messageId,
561
625
  correlationId: message.properties.correlationId
562
626
  };
563
- while (attempts < this.options.maxRetries) {
627
+ const maxRetries = options?.maxRetries ?? this.options.maxRetries;
628
+ while (attempts < maxRetries) {
564
629
  try {
565
630
  await handler(content, properties);
566
631
  } catch (error) {
567
632
  attempts++;
633
+ if (error instanceof Error && error.name === "DeadLetterError") {
634
+ const duration3 = Date.now() - startTime;
635
+ this.logger.warn("Handler dead-lettered the message", { error: error.message, exchange, routingKey });
636
+ if (this.settle(channel, message, "nack")) {
637
+ this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: duration3, reason: "dead_letter_error" });
638
+ }
639
+ return;
640
+ }
568
641
  this.logger.error("Handler failed", {
569
642
  error: error instanceof Error ? error.message : error,
570
643
  stack: error instanceof Error ? error.stack : void 0,
571
644
  exchange,
572
645
  routingKey,
573
646
  attempt: attempts,
574
- maxRetries: this.options.maxRetries
647
+ maxRetries
575
648
  });
576
- if (attempts < this.options.maxRetries) {
577
- await this.sleep(this.options.retryDelay);
649
+ if (attempts < maxRetries) {
650
+ await this.sleepUntilClose(this.retryDelayFor(attempts, options));
651
+ if (this.closing || channel !== this.channel) {
652
+ this.logger.warn("Stopped retrying a message on close or reconnect; it will be redelivered", { exchange, routingKey });
653
+ return;
654
+ }
578
655
  }
579
656
  continue;
580
657
  }
@@ -586,7 +663,7 @@ var RabbitMQClient = class {
586
663
  return;
587
664
  }
588
665
  const duration = Date.now() - startTime;
589
- this.logger.error("Message failed after max retries, sending to DLQ", { exchange, routingKey, maxRetries: this.options.maxRetries, duration });
666
+ this.logger.error("Message failed after max retries, sending to DLQ", { exchange, routingKey, maxRetries, duration });
590
667
  if (this.settle(channel, message, "nack")) {
591
668
  this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: "max_retries_exhausted" });
592
669
  }
@@ -667,9 +744,31 @@ var RabbitMQClient = class {
667
744
  sleep(ms) {
668
745
  return new Promise((resolve) => setTimeout(resolve, ms));
669
746
  }
747
+ retryDelayFor(attempts, options) {
748
+ const { retryDelay } = this.options;
749
+ if (options?.maxRetryDelay === void 0) return retryDelay;
750
+ return backoff(retryDelay, attempts, options.maxRetryDelay);
751
+ }
752
+ wakeRetries() {
753
+ for (const wake of [...this.retryWakers]) wake();
754
+ }
755
+ /** Also cut short by a reconnect, via wakeRetries(). */
756
+ sleepUntilClose(ms) {
757
+ if (this.closing) return Promise.resolve();
758
+ return new Promise((resolve) => {
759
+ const wake = () => {
760
+ clearTimeout(timer);
761
+ this.retryWakers.delete(wake);
762
+ resolve();
763
+ };
764
+ const timer = setTimeout(wake, ms);
765
+ this.retryWakers.add(wake);
766
+ });
767
+ }
670
768
  };
671
769
  // Annotate the CommonJS export names for ESM import in node:
672
770
  0 && (module.exports = {
771
+ DeadLetterError,
673
772
  RabbitMQClient,
674
773
  UnroutableMessageError,
675
774
  defaultLogger,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/logger.ts","../src/semaphore.ts"],"sourcesContent":["export { RabbitMQClient, UnroutableMessageError } from './client.js'\nexport { defaultLogger, silentLogger } from './logger.js'\nexport type {\n ILogger,\n PublishOptions,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\n RabbitMQMessageProperties,\n RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\n","import amqp from 'amqplib'\nimport type {\n ILogger,\n PublishOptions,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\n RabbitMQMessageProperties,\n RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\nimport { defaultLogger, forLibraryCalls } from './logger.js'\nimport { Semaphore } from './semaphore.js'\n\nconst DEFAULT_PREFETCH = 10\nconst MAX_PUBLISH_RETRY_DELAY_MS = 60_000\n\nconst DEFAULTS = {\n maxRetries: 3,\n retryDelay: 1000,\n connectionRetryDelay: 5000,\n initMaxAttempts: 5,\n publishMaxAttempts: 5,\n prefetch: DEFAULT_PREFETCH,\n closeTimeout: 5000,\n} as const\n\n/** A `mandatory` publish the broker could route to no queue. */\nexport class UnroutableMessageError extends Error {\n constructor(\n readonly exchange: string,\n readonly routingKey: string,\n ) {\n super(`No queue is bound to receive ${exchange || '(default exchange)'}/${routingKey}`)\n this.name = 'UnroutableMessageError'\n }\n}\n\n/**\n * Production-grade RabbitMQ client with confirm channels, automatic DLQ\n * infrastructure, reconnection with subscription restoration, and\n * exponential-backoff publishing.\n *\n * @example\n * ```ts\n * const client = new RabbitMQClient({ url: 'amqp://localhost' })\n * await client.init()\n * await client.publish('events', 'user.created', { userId: '123' })\n * await client.subscribe('events', 'user.created', 'user-service', handler)\n * ```\n */\nexport class RabbitMQClient {\n private connection: amqp.ChannelModel | null = null\n private channel: amqp.ConfirmChannel | null = null\n private connected = false\n private subscriptions: RabbitMQSubscription[] = []\n private readonly options: Required<Omit<RabbitMQClientOptions, 'logger' | 'hooks'>>\n private readonly logger: ILogger\n private readonly hooks: RabbitMQHooks\n private initializationPromise: Promise<void> | null = null\n private reconnectionPromise: Promise<void> | null = null\n private assertedExchanges = new Set<string>()\n // One per channel, so a late return on a replaced channel reaches no waiter.\n private returnWaiters = new Set<(msg: amqp.Message) => void>()\n private consumerTags = new Map<string, string>()\n // Per-queue concurrency limiter, keyed by queue name. Persisted across\n // reconnects (unlike consumerTags) so handlers still running when a\n // connection drops keep holding their permits, and the new consumer cannot\n // exceed the ceiling while the old pipeline drains. `null` = no limit.\n private subscriptionSemaphores = new Map<string, Semaphore | null>()\n private inflightCount = 0\n private drainResolve: (() => void) | null = null\n\n constructor(options: RabbitMQClientOptions) {\n this.options = {\n url: options.url,\n maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,\n retryDelay: options.retryDelay ?? DEFAULTS.retryDelay,\n connectionRetryDelay: options.connectionRetryDelay ?? DEFAULTS.connectionRetryDelay,\n initMaxAttempts: options.initMaxAttempts ?? DEFAULTS.initMaxAttempts,\n publishMaxAttempts: options.publishMaxAttempts ?? DEFAULTS.publishMaxAttempts,\n prefetch: options.prefetch ?? DEFAULTS.prefetch,\n // Default concurrency tracks prefetch, preserving the previous behaviour\n // (handlers ran fire-and-forget, so up to `prefetch` ran at once) while\n // making the ceiling explicit and independently tunable. A value <= 0\n // (including an unlimited `prefetch: 0`) means \"no concurrency limit\".\n concurrency: options.concurrency ?? options.prefetch ?? DEFAULTS.prefetch,\n closeTimeout: options.closeTimeout ?? DEFAULTS.closeTimeout,\n }\n this.logger = forLibraryCalls(options.logger ?? defaultLogger)\n this.hooks = options.hooks ?? {}\n }\n\n /**\n * Opens a connection and creates a confirm channel. Retries up to\n * `initMaxAttempts` times, then throws. Idempotent and safe to call\n * concurrently — duplicate calls share the same in-flight promise.\n */\n async init(): Promise<void> {\n if (this.connection && this.connected) {\n return\n }\n if (this.initializationPromise) {\n return this.initializationPromise\n }\n // Attach a no-op catch to prevent Node from briefly treating this as an\n // unhandled rejection before the await below registers its own handler.\n this.initializationPromise = this.doConnect(this.options.initMaxAttempts)\n this.initializationPromise.catch(() => undefined)\n try {\n await this.initializationPromise\n } finally {\n this.initializationPromise = null\n }\n }\n\n private async doConnect(maxAttempts?: number): Promise<void> {\n this.assertedExchanges.clear()\n this.consumerTags.clear()\n let attempts = 0\n while (!this.connected) {\n try {\n this.connection = await amqp.connect(this.options.url)\n this.logger.info('Connected to server')\n this.channel = await this.connection.createConfirmChannel()\n const returnWaiters = new Set<(msg: amqp.Message) => void>()\n this.returnWaiters = returnWaiters\n this.channel.on('return', (msg: amqp.Message) => {\n for (const waiter of returnWaiters) waiter(msg)\n })\n this.logger.info('Confirm channel created')\n await this.channel.prefetch(this.options.prefetch)\n this.logger.info('Channel prefetch set', { prefetch: this.options.prefetch })\n\n this.connection.on('error', (error: Error) => {\n this.connected = false\n this.logger.error('Connection error', { error })\n })\n this.connection.on('close', () => {\n this.connected = false\n this.logger.warn('Connection closed')\n this.reconnectWithRetry()\n })\n this.channel.on('error', (error: Error) => {\n this.logger.error('Channel error', { error })\n this.handleChannelFailure()\n })\n this.channel.on('close', () => {\n this.logger.warn('Channel closed')\n this.handleChannelFailure()\n })\n\n this.connected = true\n this.logger.info('Client initialized successfully')\n } catch (error) {\n attempts++\n this.connected = false\n if (maxAttempts !== undefined && attempts >= maxAttempts) {\n this.logger.error('Connection failed after maximum attempts', { error, attempts, maxAttempts })\n throw new Error(\n `Failed to connect to RabbitMQ after ${attempts} attempts. ` +\n 'Check RABBITMQ_URL configuration and RabbitMQ server availability.',\n )\n }\n this.logger.warn('Connection attempt failed, retrying...', {\n error,\n attempt: attempts,\n maxAttempts: maxAttempts ?? 'unlimited',\n retryDelayMs: this.options.connectionRetryDelay,\n })\n await this.sleep(this.options.connectionRetryDelay)\n }\n }\n }\n\n private handleChannelFailure(): void {\n if (this.connected) {\n this.connected = false\n this.reconnectWithRetry()\n }\n }\n\n /**\n * Returns whether the client currently has an active connection and channel.\n */\n isConnected(): boolean {\n return this.connected\n }\n\n /**\n * Publishes a JSON message to a topic exchange with publisher confirms, or\n * straight to a queue through the default exchange (`''`, routing key = queue name).\n * Retries with exponential backoff (capped at 60 s), forcing a reconnect\n * on each failure. Throws after `publishMaxAttempts` exhausted.\n */\n async publish(\n exchange: string,\n routingKey: string,\n message: RabbitMQMessage,\n options?: PublishOptions,\n ): Promise<void> {\n let attempts = 0\n const maxAttempts = options?.maxAttempts ?? this.options.publishMaxAttempts\n const baseDelay = this.options.connectionRetryDelay\n const content = Buffer.from(JSON.stringify(message))\n // Taken once, not per attempt: a retried publish must not look newer than\n // a message published after it succeeded.\n const timestamp = Math.floor(Date.now() / 1000)\n\n while (attempts < maxAttempts) {\n try {\n if (!this.connected) {\n await this.reconnectWithRetry()\n }\n\n if (!this.channel) {\n throw new Error('Channel not available')\n }\n\n // The default exchange ('') always exists and the broker refuses to\n // declare it, so publishing straight to a queue skips the assertion.\n if (exchange !== '' && !this.assertedExchanges.has(exchange)) {\n await this.channel.assertExchange(exchange, 'topic', { durable: true })\n this.assertedExchanges.add(exchange)\n }\n\n const channel = this.channel\n const returnWaiters = this.returnWaiters\n // The broker sends basic.return before the confirm of the same message,\n // so the flag is settled by the time waitForConfirms resolves. A return\n // carries no delivery tag, so it is matched on where it was sent plus the\n // message id, or the content when there is none.\n let returned = false\n const messageId = options?.messageId\n const onReturn = (msg: amqp.Message) => {\n if (msg.fields.exchange !== exchange || msg.fields.routingKey !== routingKey) return\n if (messageId !== undefined ? msg.properties.messageId === messageId : msg.content.equals(content)) {\n returned = true\n }\n }\n if (options?.mandatory) returnWaiters.add(onReturn)\n\n try {\n channel.publish(exchange, routingKey, content, {\n persistent: true,\n timestamp,\n headers: options?.headers,\n correlationId: options?.correlationId,\n messageId: options?.messageId,\n expiration: options?.expiration,\n mandatory: options?.mandatory,\n })\n\n await channel.waitForConfirms()\n } finally {\n returnWaiters.delete(onReturn)\n }\n\n if (returned) {\n throw new UnroutableMessageError(exchange, routingKey)\n }\n\n if (attempts > 0) {\n this.logger.info('Published message after retries', { exchange, routingKey, messageSize: content.length, attempts })\n } else {\n this.logger.info('Published message', { exchange, routingKey, messageSize: content.length })\n }\n this.logger.debug('Published message payload', { exchange, routingKey, payload: message })\n\n this.callHook(this.hooks.onPublish, { exchange, routingKey, attempts: attempts + 1 })\n\n return\n } catch (error) {\n // The broker answered, and the same routing would fail the same way.\n if (error instanceof UnroutableMessageError) {\n this.logger.warn('Published message was unroutable', { exchange, routingKey })\n throw error\n }\n\n attempts++\n this.connected = false\n\n if (attempts >= maxAttempts) {\n this.logger.error('Publish failed after max attempts', { error, exchange, routingKey, attempts, maxAttempts })\n throw new Error(\n `Failed to publish to ${exchange || '(default exchange)'}/${routingKey} after ${attempts} attempts: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n\n const retryDelay = Math.min(\n baseDelay * Math.pow(2, attempts - 1),\n MAX_PUBLISH_RETRY_DELAY_MS,\n )\n\n this.logger.warn('Publish attempt failed, retrying', { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay })\n\n await this.sleep(retryDelay)\n }\n }\n }\n\n /**\n * Gracefully shuts down the client. Waits up to `closeTimeout` ms for\n * in-flight message handlers to finish before closing the channel and\n * connection. Pass `false` to preserve subscriptions for a later\n * `init()` / reconnect cycle.\n */\n async close(clearSubscriptions = true): Promise<void> {\n try {\n if (this.inflightCount > 0) {\n this.logger.info('Waiting for in-flight messages to drain', { inflightCount: this.inflightCount })\n await this.waitForDrain(this.options.closeTimeout)\n }\n await this.channel?.close()\n await this.connection?.close()\n this.connection = null\n this.channel = null\n this.connected = false\n this.initializationPromise = null\n this.reconnectionPromise = null\n this.assertedExchanges.clear()\n this.consumerTags.clear()\n if (clearSubscriptions) {\n this.subscriptions = []\n this.subscriptionSemaphores.clear()\n }\n this.logger.info('Connection closed')\n } catch (error) {\n this.logger.error('Error closing connection', { error })\n throw error\n }\n }\n\n private async reconnectWithRetry(): Promise<void> {\n if (this.reconnectionPromise) {\n return this.reconnectionPromise\n }\n this.connected = false\n this.logger.info('Starting reconnection...')\n this.reconnectionPromise = this.doReconnect()\n this.reconnectionPromise.catch(() => undefined)\n try {\n await this.reconnectionPromise\n } finally {\n this.reconnectionPromise = null\n }\n }\n\n private async doReconnect(): Promise<void> {\n await this.doConnect()\n await this.resubscribeAll()\n }\n\n private async resubscribeAll(): Promise<void> {\n if (this.subscriptions.length === 0) {\n return\n }\n this.logger.info('Re-establishing subscriptions', { count: this.subscriptions.length })\n const subs = [...this.subscriptions]\n const results = await Promise.allSettled(\n subs.map(async (sub) => {\n await this.setupSubscription(sub)\n return sub.queue\n }),\n )\n const succeeded: string[] = []\n const failed: string[] = []\n results.forEach((result, index) => {\n if (result.status === 'fulfilled') {\n succeeded.push(result.value)\n } else {\n const queue = subs[index].queue\n failed.push(queue)\n this.logger.error('Failed to re-subscribe to queue', { error: result.reason, queue })\n }\n })\n if (succeeded.length > 0) {\n this.logger.info('Successfully re-subscribed to queues', { count: succeeded.length, queues: succeeded })\n }\n if (failed.length > 0) {\n this.logger.warn('Some subscriptions failed to restore', { count: failed.length, queues: failed })\n }\n this.callHook(this.hooks.onReconnect, { subscriptionsRestored: succeeded.length, subscriptionsFailed: failed.length })\n }\n\n /**\n * Subscribes to a queue with automatic DLQ infrastructure setup.\n *\n * Pass `options.queueArguments` to override the default quorum-queue\n * arguments (merged with the DLQ wiring defaults).\n */\n async subscribe(\n exchange: string,\n routingKey: string,\n queue: string,\n handler: RabbitMQMessageHandler,\n options?: SubscribeOptions,\n ): Promise<void> {\n if (!this.connected || !this.channel) {\n throw new Error('RabbitMQ client not connected. Call init() first.')\n }\n const sub: RabbitMQSubscription = { exchange, routingKey, queue, handler, options }\n const existingIndex = this.subscriptions.findIndex((s) => s.queue === queue)\n if (existingIndex === -1) {\n this.subscriptions.push(sub)\n } else {\n this.subscriptions[existingIndex] = sub\n // Re-subscribing may change concurrency; drop the old limiter so\n // setupSubscription rebuilds it from the new options. (A reconnect goes\n // through setupSubscription directly and keeps the existing limiter.)\n this.subscriptionSemaphores.delete(queue)\n }\n await this.setupSubscription(sub)\n }\n\n /**\n * Cancels a queue subscription and removes it from the restoration list.\n */\n async unsubscribe(queue: string): Promise<void> {\n const tag = this.consumerTags.get(queue)\n if (tag && this.channel) {\n await this.channel.cancel(tag)\n }\n this.consumerTags.delete(queue)\n this.subscriptionSemaphores.delete(queue)\n this.subscriptions = this.subscriptions.filter((s) => s.queue !== queue)\n this.logger.info('Unsubscribed from queue', { queue })\n }\n\n private async setupSubscription(sub: RabbitMQSubscription): Promise<void> {\n if (!this.channel) {\n throw new Error('Channel not available')\n }\n // Bind this consumer to the exact channel that will deliver its messages,\n // so ack/nack always target that channel even after a reconnect swaps\n // `this.channel`.\n const channel = this.channel\n const { exchange, routingKey, queue, handler, options } = sub\n const dlxExchange = `${exchange}.dlx`\n const dlqQueue = `${queue}.dlq`\n const dlqRoutingKey = `${routingKey}.dead`\n\n await channel.assertExchange(dlxExchange, 'topic', { durable: true })\n await channel.assertQueue(dlqQueue, { durable: true })\n await channel.bindQueue(dlqQueue, dlxExchange, dlqRoutingKey)\n\n if (!this.assertedExchanges.has(exchange)) {\n await channel.assertExchange(exchange, 'topic', { durable: true })\n this.assertedExchanges.add(exchange)\n }\n\n const queueType = options?.queueArguments?.['x-queue-type'] ?? 'quorum'\n const queueArgs: Record<string, unknown> = {\n 'x-queue-type': queueType,\n 'x-overflow': 'reject-publish',\n }\n // at-least-once DLQ strategy is only supported by quorum queues\n if (queueType === 'quorum') {\n queueArgs['x-dead-letter-strategy'] = 'at-least-once'\n }\n\n await channel.assertQueue(queue, {\n durable: true,\n deadLetterExchange: dlxExchange,\n deadLetterRoutingKey: dlqRoutingKey,\n arguments: { ...queueArgs, ...options?.queueArguments },\n })\n\n await channel.bindQueue(queue, exchange, routingKey)\n // Reuse the queue's existing limiter across reconnects; only build a new\n // one the first time (or after subscribe()/unsubscribe() cleared it).\n // A concurrency <= 0 means \"no limit\" (null), matching an unlimited prefetch.\n if (!this.subscriptionSemaphores.has(queue)) {\n const concurrency = options?.concurrency ?? this.options.concurrency\n this.subscriptionSemaphores.set(queue, concurrency > 0 ? new Semaphore(concurrency) : null)\n }\n const semaphore = this.subscriptionSemaphores.get(queue) ?? null\n const { consumerTag } = await channel.consume(\n queue,\n (message) => {\n if (message) {\n this.dispatch(message, handler, semaphore, channel)\n }\n },\n { noAck: false },\n )\n this.consumerTags.set(queue, consumerTag)\n this.logger.info('Subscribed to queue', { exchange, routingKey, queue })\n }\n\n /**\n * Gates a delivered message on the subscription's concurrency semaphore, then\n * processes it. A message counts as in-flight from delivery until its handler\n * settles (including time spent waiting for a permit), so graceful `close()`\n * drains queued messages as well as actively-processing ones.\n */\n private dispatch(\n message: amqp.ConsumeMessage,\n handler: RabbitMQMessageHandler,\n semaphore: Semaphore | null,\n channel: amqp.ConfirmChannel,\n ): void {\n this.inflightCount++\n const acquire = semaphore ? semaphore.acquire() : Promise.resolve()\n acquire\n .then(() => this.handleWithRetry(message, handler, channel))\n .catch((error) => {\n this.logger.error('Unhandled error in message handler', { error })\n })\n .finally(() => {\n semaphore?.release()\n this.inflightCount--\n if (this.inflightCount === 0 && this.drainResolve) {\n this.drainResolve()\n }\n })\n }\n\n private async handleWithRetry(\n message: amqp.ConsumeMessage,\n handler: RabbitMQMessageHandler,\n channel: amqp.ConfirmChannel,\n ): Promise<void> {\n // `channel` is the one that delivered this message. If a reconnect has\n // since replaced it, this message was never acked and the broker will\n // redeliver it on the new channel, so drop this stale attempt rather than\n // run the handler again or ack a tag the new channel does not know.\n if (channel !== this.channel) {\n this.logger.warn('Skipping message from a superseded channel; it will be redelivered')\n return\n }\n const startTime = Date.now()\n let attempts = 0\n const routingKey = message.fields.routingKey\n const exchange = message.fields.exchange\n\n let content: RabbitMQMessage\n try {\n content = JSON.parse(message.content.toString())\n } catch (parseError) {\n const rawContent = message.content.toString()\n const rawPreview = rawContent.substring(0, 100)\n this.logger.error('Failed to parse message JSON, sending to DLQ', {\n error: parseError,\n exchange,\n routingKey,\n rawContentPreview: rawPreview + (rawContent.length > 100 ? '...' : ''),\n })\n if (this.settle(channel, message, 'nack')) {\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: 0, reason: 'invalid_json' })\n }\n return\n }\n\n this.logger.debug('Message received, processing', { exchange, routingKey, payload: content })\n\n const properties: RabbitMQMessageProperties = {\n headers: message.properties.headers ?? {},\n timestamp: message.properties.timestamp,\n messageId: message.properties.messageId,\n correlationId: message.properties.correlationId,\n }\n\n while (attempts < this.options.maxRetries) {\n // Only the handler call belongs in this try. Acking inside it would make\n // a dead channel look like a failed handler and re-run its side effects.\n try {\n await handler(content, properties)\n } catch (error) {\n attempts++\n this.logger.error('Handler failed', {\n error: error instanceof Error ? error.message : error,\n stack: error instanceof Error ? error.stack : undefined,\n exchange,\n routingKey,\n attempt: attempts,\n maxRetries: this.options.maxRetries,\n })\n if (attempts < this.options.maxRetries) {\n await this.sleep(this.options.retryDelay)\n }\n continue\n }\n const duration = Date.now() - startTime\n this.logger.info('Message processed successfully', { exchange, routingKey, duration, attempts: attempts + 1 })\n if (this.settle(channel, message, 'ack')) {\n this.callHook(this.hooks.onMessageProcessed, { exchange, routingKey, duration, attempts: attempts + 1 })\n }\n return\n }\n\n const duration = Date.now() - startTime\n this.logger.error('Message failed after max retries, sending to DLQ', { exchange, routingKey, maxRetries: this.options.maxRetries, duration })\n if (this.settle(channel, message, 'nack')) {\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: 'max_retries_exhausted' })\n }\n }\n\n /**\n * Acks or nacks a delivery, reporting whether the broker accepted it.\n *\n * A settle failure is a transport error, never a handler failure: by this\n * point the handler has already run, so it must not be retried just because\n * the channel went away. The delivery stays unacked and the broker redelivers\n * it once the channel closes.\n */\n private settle(\n channel: amqp.ConfirmChannel,\n message: amqp.ConsumeMessage,\n action: 'ack' | 'nack',\n ): boolean {\n // A handler can run for minutes, so the channel that delivered this\n // message may have been replaced by a reconnect since the entry check.\n // Settling on it would throw, and replaying the delivery tag on the\n // current channel would settle an unrelated message, since tags are scoped\n // to the channel that issued them. The broker requeues a closed channel's\n // unacked deliveries, so let it be redelivered instead.\n if (channel !== this.channel) {\n this.logger.warn('Skipping message from a superseded channel; it will be redelivered')\n return false\n }\n try {\n if (action === 'ack') {\n channel.ack(message)\n } else {\n channel.nack(message, false, false)\n }\n return true\n } catch (error) {\n this.logger.error('Failed to settle message; leaving it unacked for redelivery', {\n error,\n action,\n exchange: message.fields.exchange,\n routingKey: message.fields.routingKey,\n })\n return false\n }\n }\n\n /**\n * Lightweight liveness probe: creates and immediately deletes a temporary\n * exclusive queue. Returns `false` when disconnected or on broker error.\n */\n async checkHealth(): Promise<boolean> {\n if (!this.connected || !this.channel) {\n return false\n }\n try {\n const { queue } = await this.channel.assertQueue('', {\n exclusive: true,\n autoDelete: true,\n })\n await this.channel.deleteQueue(queue)\n return true\n } catch (error) {\n this.logger.warn('Health check failed', { error })\n return false\n }\n }\n\n private waitForDrain(timeout: number): Promise<void> {\n if (this.inflightCount === 0) return Promise.resolve()\n return new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n this.logger.warn('Close timeout reached with messages still in flight', { inflightCount: this.inflightCount })\n this.drainResolve = null\n resolve()\n }, timeout)\n this.drainResolve = () => {\n clearTimeout(timer)\n this.drainResolve = null\n resolve()\n }\n })\n }\n\n /** Invokes a hook callback, swallowing errors so hooks never break message flow. */\n private callHook<T>(hook: ((info: T) => void) | undefined, info: T): void {\n try { hook?.(info) } catch (error) { this.logger.debug('Observability hook error', { error }) }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n }\n}\n","import type { ILogger } from './types.js'\n\n/**\n * Default logger used when the consumer does not inject one.\n *\n * Forwards `warn` and `error` to the console so connection drops, channel\n * errors, and failed handlers stay visible out of the box, while staying\n * silent for `info`/`debug` so routine operation never pollutes the\n * consumer's output. Pass `silentLogger` for zero output, or any `ILogger`\n * (console, Winston, pino, Bunyan) to take full control.\n */\nexport const defaultLogger: ILogger = {\n debug: () => {},\n info: () => {},\n warn: (message, ...args) => console.warn('[rabbitmq-client]', message, ...args),\n error: (message, ...args) => console.error('[rabbitmq-client]', message, ...args),\n}\n\ntype Level = keyof ILogger\ntype ObjectFirst = (context: object, message: string) => void\n\n/**\n * pino takes `(context, message)` and drops a context passed after the\n * message, and only serializes an Error under `err`. It is the one logger\n * with a `bindings()` method, so it is recognised by that and called its way.\n */\nexport const forLibraryCalls = (logger: ILogger): ILogger => {\n if (typeof (logger as { bindings?: unknown }).bindings !== 'function') return logger\n const call =\n (level: Level) =>\n (message: string, context?: unknown): void => {\n const log = (logger[level] as unknown as ObjectFirst).bind(logger)\n if (typeof context !== 'object' || context === null) return log({}, message)\n const { error, ...rest } = context as { error?: unknown }\n log(error === undefined ? rest : { ...rest, err: error }, message)\n }\n return { debug: call('debug'), info: call('info'), warn: call('warn'), error: call('error') }\n}\n\n/**\n * No-op logger that discards every message. Use this to silence the client\n * entirely, including the `warn`/`error` output the default logger emits.\n */\nexport const silentLogger: ILogger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n}\n","/**\n * Minimal FIFO counting semaphore used to cap how many message handlers run\n * concurrently, independently of channel prefetch.\n *\n * Prefetch bounds how many messages the broker delivers into consumer memory;\n * the semaphore bounds how many of those are actively being processed at once.\n * Keeping the two separate lets a consumer buffer a large prefetch window while\n * still protecting a slow downstream (a database, an HTTP API) from a burst.\n */\nexport class Semaphore {\n private permits: number\n private readonly waiters: Array<() => void> = []\n\n constructor(permits: number) {\n if (!Number.isInteger(permits) || permits < 1) {\n throw new Error(`Semaphore requires a positive integer of permits, got ${permits}`)\n }\n this.permits = permits\n }\n\n /**\n * Acquires a permit, resolving immediately when one is free or queueing\n * (FIFO) until another holder releases. Always pair with `release()` in a\n * `finally` so a throwing handler never leaks a permit.\n */\n acquire(): Promise<void> {\n if (this.permits > 0) {\n this.permits--\n return Promise.resolve()\n }\n return new Promise<void>((resolve) => {\n this.waiters.push(resolve)\n })\n }\n\n /**\n * Releases a permit. Hands it directly to the next waiter if any are queued,\n * otherwise returns it to the pool.\n */\n release(): void {\n const next = this.waiters.shift()\n if (next) {\n next()\n } else {\n this.permits++\n }\n }\n\n /** Permits currently available. Exposed for tests and introspection. */\n get available(): number {\n return this.permits\n }\n\n /** Number of callers currently queued waiting for a permit. */\n get waiting(): number {\n return this.waiters.length\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAAiB;;;ACWV,IAAM,gBAAyB;AAAA,EACpC,OAAO,MAAM;AAAA,EAAC;AAAA,EACd,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,MAAM,CAAC,YAAY,SAAS,QAAQ,KAAK,qBAAqB,SAAS,GAAG,IAAI;AAAA,EAC9E,OAAO,CAAC,YAAY,SAAS,QAAQ,MAAM,qBAAqB,SAAS,GAAG,IAAI;AAClF;AAUO,IAAM,kBAAkB,CAAC,WAA6B;AAC3D,MAAI,OAAQ,OAAkC,aAAa,WAAY,QAAO;AAC9E,QAAM,OACJ,CAAC,UACD,CAAC,SAAiB,YAA4B;AAC5C,UAAM,MAAO,OAAO,KAAK,EAA6B,KAAK,MAAM;AACjE,QAAI,OAAO,YAAY,YAAY,YAAY,KAAM,QAAO,IAAI,CAAC,GAAG,OAAO;AAC3E,UAAM,EAAE,OAAO,GAAG,KAAK,IAAI;AAC3B,QAAI,UAAU,SAAY,OAAO,EAAE,GAAG,MAAM,KAAK,MAAM,GAAG,OAAO;AAAA,EACnE;AACF,SAAO,EAAE,OAAO,KAAK,OAAO,GAAG,MAAM,KAAK,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG,OAAO,KAAK,OAAO,EAAE;AAC9F;AAMO,IAAM,eAAwB;AAAA,EACnC,OAAO,MAAM;AAAA,EAAC;AAAA,EACd,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,OAAO,MAAM;AAAA,EAAC;AAChB;;;ACvCO,IAAM,YAAN,MAAgB;AAAA,EACb;AAAA,EACS,UAA6B,CAAC;AAAA,EAE/C,YAAY,SAAiB;AAC3B,QAAI,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,GAAG;AAC7C,YAAM,IAAI,MAAM,yDAAyD,OAAO,EAAE;AAAA,IACpF;AACA,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAyB;AACvB,QAAI,KAAK,UAAU,GAAG;AACpB,WAAK;AACL,aAAO,QAAQ,QAAQ;AAAA,IACzB;AACA,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,WAAK,QAAQ,KAAK,OAAO;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAgB;AACd,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAI,MAAM;AACR,WAAK;AAAA,IACP,OAAO;AACL,WAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,UAAkB;AACpB,WAAO,KAAK,QAAQ;AAAA,EACtB;AACF;;;AF1CA,IAAM,mBAAmB;AACzB,IAAM,6BAA6B;AAEnC,IAAM,WAAW;AAAA,EACf,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,cAAc;AAChB;AAGO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YACW,UACA,YACT;AACA,UAAM,gCAAgC,YAAY,oBAAoB,IAAI,UAAU,EAAE;AAH7E;AACA;AAGT,SAAK,OAAO;AAAA,EACd;AAAA,EALW;AAAA,EACA;AAKb;AAeO,IAAM,iBAAN,MAAqB;AAAA,EAClB,aAAuC;AAAA,EACvC,UAAsC;AAAA,EACtC,YAAY;AAAA,EACZ,gBAAwC,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACT,wBAA8C;AAAA,EAC9C,sBAA4C;AAAA,EAC5C,oBAAoB,oBAAI,IAAY;AAAA;AAAA,EAEpC,gBAAgB,oBAAI,IAAiC;AAAA,EACrD,eAAe,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKvC,yBAAyB,oBAAI,IAA8B;AAAA,EAC3D,gBAAgB;AAAA,EAChB,eAAoC;AAAA,EAE5C,YAAY,SAAgC;AAC1C,SAAK,UAAU;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,YAAY,QAAQ,cAAc,SAAS;AAAA,MAC3C,YAAY,QAAQ,cAAc,SAAS;AAAA,MAC3C,sBAAsB,QAAQ,wBAAwB,SAAS;AAAA,MAC/D,iBAAiB,QAAQ,mBAAmB,SAAS;AAAA,MACrD,oBAAoB,QAAQ,sBAAsB,SAAS;AAAA,MAC3D,UAAU,QAAQ,YAAY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,MAKvC,aAAa,QAAQ,eAAe,QAAQ,YAAY,SAAS;AAAA,MACjE,cAAc,QAAQ,gBAAgB,SAAS;AAAA,IACjD;AACA,SAAK,SAAS,gBAAgB,QAAQ,UAAU,aAAa;AAC7D,SAAK,QAAQ,QAAQ,SAAS,CAAC;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAsB;AAC1B,QAAI,KAAK,cAAc,KAAK,WAAW;AACrC;AAAA,IACF;AACA,QAAI,KAAK,uBAAuB;AAC9B,aAAO,KAAK;AAAA,IACd;AAGA,SAAK,wBAAwB,KAAK,UAAU,KAAK,QAAQ,eAAe;AACxE,SAAK,sBAAsB,MAAM,MAAM,MAAS;AAChD,QAAI;AACF,YAAM,KAAK;AAAA,IACb,UAAE;AACA,WAAK,wBAAwB;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAc,UAAU,aAAqC;AAC3D,SAAK,kBAAkB,MAAM;AAC7B,SAAK,aAAa,MAAM;AACxB,QAAI,WAAW;AACf,WAAO,CAAC,KAAK,WAAW;AACtB,UAAI;AACF,aAAK,aAAa,MAAM,eAAAA,QAAK,QAAQ,KAAK,QAAQ,GAAG;AACrD,aAAK,OAAO,KAAK,qBAAqB;AACtC,aAAK,UAAU,MAAM,KAAK,WAAW,qBAAqB;AAC1D,cAAM,gBAAgB,oBAAI,IAAiC;AAC3D,aAAK,gBAAgB;AACrB,aAAK,QAAQ,GAAG,UAAU,CAAC,QAAsB;AAC/C,qBAAW,UAAU,cAAe,QAAO,GAAG;AAAA,QAChD,CAAC;AACD,aAAK,OAAO,KAAK,yBAAyB;AAC1C,cAAM,KAAK,QAAQ,SAAS,KAAK,QAAQ,QAAQ;AACjD,aAAK,OAAO,KAAK,wBAAwB,EAAE,UAAU,KAAK,QAAQ,SAAS,CAAC;AAE5E,aAAK,WAAW,GAAG,SAAS,CAAC,UAAiB;AAC5C,eAAK,YAAY;AACjB,eAAK,OAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC;AAAA,QACjD,CAAC;AACD,aAAK,WAAW,GAAG,SAAS,MAAM;AAChC,eAAK,YAAY;AACjB,eAAK,OAAO,KAAK,mBAAmB;AACpC,eAAK,mBAAmB;AAAA,QAC1B,CAAC;AACD,aAAK,QAAQ,GAAG,SAAS,CAAC,UAAiB;AACzC,eAAK,OAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC;AAC5C,eAAK,qBAAqB;AAAA,QAC5B,CAAC;AACD,aAAK,QAAQ,GAAG,SAAS,MAAM;AAC7B,eAAK,OAAO,KAAK,gBAAgB;AACjC,eAAK,qBAAqB;AAAA,QAC5B,CAAC;AAED,aAAK,YAAY;AACjB,aAAK,OAAO,KAAK,iCAAiC;AAAA,MACpD,SAAS,OAAO;AACd;AACA,aAAK,YAAY;AACjB,YAAI,gBAAgB,UAAa,YAAY,aAAa;AACxD,eAAK,OAAO,MAAM,4CAA4C,EAAE,OAAO,UAAU,YAAY,CAAC;AAC9F,gBAAM,IAAI;AAAA,YACR,uCAAuC,QAAQ;AAAA,UAEjD;AAAA,QACF;AACA,aAAK,OAAO,KAAK,0CAA0C;AAAA,UACzD;AAAA,UACA,SAAS;AAAA,UACT,aAAa,eAAe;AAAA,UAC5B,cAAc,KAAK,QAAQ;AAAA,QAC7B,CAAC;AACD,cAAM,KAAK,MAAM,KAAK,QAAQ,oBAAoB;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,uBAA6B;AACnC,QAAI,KAAK,WAAW;AAClB,WAAK,YAAY;AACjB,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QACJ,UACA,YACA,SACA,SACe;AACf,QAAI,WAAW;AACf,UAAM,cAAc,SAAS,eAAe,KAAK,QAAQ;AACzD,UAAM,YAAY,KAAK,QAAQ;AAC/B,UAAM,UAAU,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC;AAGnD,UAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAE9C,WAAO,WAAW,aAAa;AAC7B,UAAI;AACF,YAAI,CAAC,KAAK,WAAW;AACnB,gBAAM,KAAK,mBAAmB;AAAA,QAChC;AAEA,YAAI,CAAC,KAAK,SAAS;AACjB,gBAAM,IAAI,MAAM,uBAAuB;AAAA,QACzC;AAIA,YAAI,aAAa,MAAM,CAAC,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AAC5D,gBAAM,KAAK,QAAQ,eAAe,UAAU,SAAS,EAAE,SAAS,KAAK,CAAC;AACtE,eAAK,kBAAkB,IAAI,QAAQ;AAAA,QACrC;AAEA,cAAM,UAAU,KAAK;AACrB,cAAM,gBAAgB,KAAK;AAK3B,YAAI,WAAW;AACf,cAAM,YAAY,SAAS;AAC3B,cAAM,WAAW,CAAC,QAAsB;AACtC,cAAI,IAAI,OAAO,aAAa,YAAY,IAAI,OAAO,eAAe,WAAY;AAC9E,cAAI,cAAc,SAAY,IAAI,WAAW,cAAc,YAAY,IAAI,QAAQ,OAAO,OAAO,GAAG;AAClG,uBAAW;AAAA,UACb;AAAA,QACF;AACA,YAAI,SAAS,UAAW,eAAc,IAAI,QAAQ;AAElD,YAAI;AACF,kBAAQ,QAAQ,UAAU,YAAY,SAAS;AAAA,YAC7C,YAAY;AAAA,YACZ;AAAA,YACA,SAAS,SAAS;AAAA,YAClB,eAAe,SAAS;AAAA,YACxB,WAAW,SAAS;AAAA,YACpB,YAAY,SAAS;AAAA,YACrB,WAAW,SAAS;AAAA,UACtB,CAAC;AAED,gBAAM,QAAQ,gBAAgB;AAAA,QAChC,UAAE;AACA,wBAAc,OAAO,QAAQ;AAAA,QAC/B;AAEA,YAAI,UAAU;AACZ,gBAAM,IAAI,uBAAuB,UAAU,UAAU;AAAA,QACvD;AAEA,YAAI,WAAW,GAAG;AAChB,eAAK,OAAO,KAAK,mCAAmC,EAAE,UAAU,YAAY,aAAa,QAAQ,QAAQ,SAAS,CAAC;AAAA,QACrH,OAAO;AACL,eAAK,OAAO,KAAK,qBAAqB,EAAE,UAAU,YAAY,aAAa,QAAQ,OAAO,CAAC;AAAA,QAC7F;AACA,aAAK,OAAO,MAAM,6BAA6B,EAAE,UAAU,YAAY,SAAS,QAAQ,CAAC;AAEzF,aAAK,SAAS,KAAK,MAAM,WAAW,EAAE,UAAU,YAAY,UAAU,WAAW,EAAE,CAAC;AAEpF;AAAA,MACF,SAAS,OAAO;AAEd,YAAI,iBAAiB,wBAAwB;AAC3C,eAAK,OAAO,KAAK,oCAAoC,EAAE,UAAU,WAAW,CAAC;AAC7E,gBAAM;AAAA,QACR;AAEA;AACA,aAAK,YAAY;AAEjB,YAAI,YAAY,aAAa;AAC3B,eAAK,OAAO,MAAM,qCAAqC,EAAE,OAAO,UAAU,YAAY,UAAU,YAAY,CAAC;AAC7G,gBAAM,IAAI;AAAA,YACR,wBAAwB,YAAY,oBAAoB,IAAI,UAAU,UAAU,QAAQ,cAAc,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,UAC9J;AAAA,QACF;AAEA,cAAM,aAAa,KAAK;AAAA,UACtB,YAAY,KAAK,IAAI,GAAG,WAAW,CAAC;AAAA,UACpC;AAAA,QACF;AAEA,aAAK,OAAO,KAAK,oCAAoC,EAAE,OAAO,UAAU,YAAY,SAAS,UAAU,aAAa,cAAc,WAAW,CAAC;AAE9I,cAAM,KAAK,MAAM,UAAU;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,MAAM,qBAAqB,MAAqB;AACpD,QAAI;AACF,UAAI,KAAK,gBAAgB,GAAG;AAC1B,aAAK,OAAO,KAAK,2CAA2C,EAAE,eAAe,KAAK,cAAc,CAAC;AACjG,cAAM,KAAK,aAAa,KAAK,QAAQ,YAAY;AAAA,MACnD;AACA,YAAM,KAAK,SAAS,MAAM;AAC1B,YAAM,KAAK,YAAY,MAAM;AAC7B,WAAK,aAAa;AAClB,WAAK,UAAU;AACf,WAAK,YAAY;AACjB,WAAK,wBAAwB;AAC7B,WAAK,sBAAsB;AAC3B,WAAK,kBAAkB,MAAM;AAC7B,WAAK,aAAa,MAAM;AACxB,UAAI,oBAAoB;AACtB,aAAK,gBAAgB,CAAC;AACtB,aAAK,uBAAuB,MAAM;AAAA,MACpC;AACA,WAAK,OAAO,KAAK,mBAAmB;AAAA,IACtC,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC;AACvD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,qBAAoC;AAChD,QAAI,KAAK,qBAAqB;AAC5B,aAAO,KAAK;AAAA,IACd;AACA,SAAK,YAAY;AACjB,SAAK,OAAO,KAAK,0BAA0B;AAC3C,SAAK,sBAAsB,KAAK,YAAY;AAC5C,SAAK,oBAAoB,MAAM,MAAM,MAAS;AAC9C,QAAI;AACF,YAAM,KAAK;AAAA,IACb,UAAE;AACA,WAAK,sBAAsB;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,KAAK,UAAU;AACrB,UAAM,KAAK,eAAe;AAAA,EAC5B;AAAA,EAEA,MAAc,iBAAgC;AAC5C,QAAI,KAAK,cAAc,WAAW,GAAG;AACnC;AAAA,IACF;AACA,SAAK,OAAO,KAAK,iCAAiC,EAAE,OAAO,KAAK,cAAc,OAAO,CAAC;AACtF,UAAM,OAAO,CAAC,GAAG,KAAK,aAAa;AACnC,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,KAAK,IAAI,OAAO,QAAQ;AACtB,cAAM,KAAK,kBAAkB,GAAG;AAChC,eAAO,IAAI;AAAA,MACb,CAAC;AAAA,IACH;AACA,UAAM,YAAsB,CAAC;AAC7B,UAAM,SAAmB,CAAC;AAC1B,YAAQ,QAAQ,CAAC,QAAQ,UAAU;AACjC,UAAI,OAAO,WAAW,aAAa;AACjC,kBAAU,KAAK,OAAO,KAAK;AAAA,MAC7B,OAAO;AACL,cAAM,QAAQ,KAAK,KAAK,EAAE;AAC1B,eAAO,KAAK,KAAK;AACjB,aAAK,OAAO,MAAM,mCAAmC,EAAE,OAAO,OAAO,QAAQ,MAAM,CAAC;AAAA,MACtF;AAAA,IACF,CAAC;AACD,QAAI,UAAU,SAAS,GAAG;AACxB,WAAK,OAAO,KAAK,wCAAwC,EAAE,OAAO,UAAU,QAAQ,QAAQ,UAAU,CAAC;AAAA,IACzG;AACA,QAAI,OAAO,SAAS,GAAG;AACrB,WAAK,OAAO,KAAK,wCAAwC,EAAE,OAAO,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAAA,IACnG;AACA,SAAK,SAAS,KAAK,MAAM,aAAa,EAAE,uBAAuB,UAAU,QAAQ,qBAAqB,OAAO,OAAO,CAAC;AAAA,EACvH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UACJ,UACA,YACA,OACA,SACA,SACe;AACf,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,SAAS;AACpC,YAAM,IAAI,MAAM,mDAAmD;AAAA,IACrE;AACA,UAAM,MAA4B,EAAE,UAAU,YAAY,OAAO,SAAS,QAAQ;AAClF,UAAM,gBAAgB,KAAK,cAAc,UAAU,CAAC,MAAM,EAAE,UAAU,KAAK;AAC3E,QAAI,kBAAkB,IAAI;AACxB,WAAK,cAAc,KAAK,GAAG;AAAA,IAC7B,OAAO;AACL,WAAK,cAAc,aAAa,IAAI;AAIpC,WAAK,uBAAuB,OAAO,KAAK;AAAA,IAC1C;AACA,UAAM,KAAK,kBAAkB,GAAG;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,OAA8B;AAC9C,UAAM,MAAM,KAAK,aAAa,IAAI,KAAK;AACvC,QAAI,OAAO,KAAK,SAAS;AACvB,YAAM,KAAK,QAAQ,OAAO,GAAG;AAAA,IAC/B;AACA,SAAK,aAAa,OAAO,KAAK;AAC9B,SAAK,uBAAuB,OAAO,KAAK;AACxC,SAAK,gBAAgB,KAAK,cAAc,OAAO,CAAC,MAAM,EAAE,UAAU,KAAK;AACvE,SAAK,OAAO,KAAK,2BAA2B,EAAE,MAAM,CAAC;AAAA,EACvD;AAAA,EAEA,MAAc,kBAAkB,KAA0C;AACxE,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,uBAAuB;AAAA,IACzC;AAIA,UAAM,UAAU,KAAK;AACrB,UAAM,EAAE,UAAU,YAAY,OAAO,SAAS,QAAQ,IAAI;AAC1D,UAAM,cAAc,GAAG,QAAQ;AAC/B,UAAM,WAAW,GAAG,KAAK;AACzB,UAAM,gBAAgB,GAAG,UAAU;AAEnC,UAAM,QAAQ,eAAe,aAAa,SAAS,EAAE,SAAS,KAAK,CAAC;AACpE,UAAM,QAAQ,YAAY,UAAU,EAAE,SAAS,KAAK,CAAC;AACrD,UAAM,QAAQ,UAAU,UAAU,aAAa,aAAa;AAE5D,QAAI,CAAC,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AACzC,YAAM,QAAQ,eAAe,UAAU,SAAS,EAAE,SAAS,KAAK,CAAC;AACjE,WAAK,kBAAkB,IAAI,QAAQ;AAAA,IACrC;AAEA,UAAM,YAAY,SAAS,iBAAiB,cAAc,KAAK;AAC/D,UAAM,YAAqC;AAAA,MACzC,gBAAgB;AAAA,MAChB,cAAc;AAAA,IAChB;AAEA,QAAI,cAAc,UAAU;AAC1B,gBAAU,wBAAwB,IAAI;AAAA,IACxC;AAEA,UAAM,QAAQ,YAAY,OAAO;AAAA,MAC/B,SAAS;AAAA,MACT,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,MACtB,WAAW,EAAE,GAAG,WAAW,GAAG,SAAS,eAAe;AAAA,IACxD,CAAC;AAED,UAAM,QAAQ,UAAU,OAAO,UAAU,UAAU;AAInD,QAAI,CAAC,KAAK,uBAAuB,IAAI,KAAK,GAAG;AAC3C,YAAM,cAAc,SAAS,eAAe,KAAK,QAAQ;AACzD,WAAK,uBAAuB,IAAI,OAAO,cAAc,IAAI,IAAI,UAAU,WAAW,IAAI,IAAI;AAAA,IAC5F;AACA,UAAM,YAAY,KAAK,uBAAuB,IAAI,KAAK,KAAK;AAC5D,UAAM,EAAE,YAAY,IAAI,MAAM,QAAQ;AAAA,MACpC;AAAA,MACA,CAAC,YAAY;AACX,YAAI,SAAS;AACX,eAAK,SAAS,SAAS,SAAS,WAAW,OAAO;AAAA,QACpD;AAAA,MACF;AAAA,MACA,EAAE,OAAO,MAAM;AAAA,IACjB;AACA,SAAK,aAAa,IAAI,OAAO,WAAW;AACxC,SAAK,OAAO,KAAK,uBAAuB,EAAE,UAAU,YAAY,MAAM,CAAC;AAAA,EACzE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,SACN,SACA,SACA,WACA,SACM;AACN,SAAK;AACL,UAAM,UAAU,YAAY,UAAU,QAAQ,IAAI,QAAQ,QAAQ;AAClE,YACG,KAAK,MAAM,KAAK,gBAAgB,SAAS,SAAS,OAAO,CAAC,EAC1D,MAAM,CAAC,UAAU;AAChB,WAAK,OAAO,MAAM,sCAAsC,EAAE,MAAM,CAAC;AAAA,IACnE,CAAC,EACA,QAAQ,MAAM;AACb,iBAAW,QAAQ;AACnB,WAAK;AACL,UAAI,KAAK,kBAAkB,KAAK,KAAK,cAAc;AACjD,aAAK,aAAa;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACL;AAAA,EAEA,MAAc,gBACZ,SACA,SACA,SACe;AAKf,QAAI,YAAY,KAAK,SAAS;AAC5B,WAAK,OAAO,KAAK,oEAAoE;AACrF;AAAA,IACF;AACA,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,WAAW;AACf,UAAM,aAAa,QAAQ,OAAO;AAClC,UAAM,WAAW,QAAQ,OAAO;AAEhC,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,QAAQ,QAAQ,SAAS,CAAC;AAAA,IACjD,SAAS,YAAY;AACnB,YAAM,aAAa,QAAQ,QAAQ,SAAS;AAC5C,YAAM,aAAa,WAAW,UAAU,GAAG,GAAG;AAC9C,WAAK,OAAO,MAAM,gDAAgD;AAAA,QAChE,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,mBAAmB,cAAc,WAAW,SAAS,MAAM,QAAQ;AAAA,MACrE,CAAC;AACD,UAAI,KAAK,OAAO,SAAS,SAAS,MAAM,GAAG;AACzC,aAAK,SAAS,KAAK,MAAM,cAAc,EAAE,UAAU,YAAY,UAAU,GAAG,QAAQ,eAAe,CAAC;AAAA,MACtG;AACA;AAAA,IACF;AAEA,SAAK,OAAO,MAAM,gCAAgC,EAAE,UAAU,YAAY,SAAS,QAAQ,CAAC;AAE5F,UAAM,aAAwC;AAAA,MAC5C,SAAS,QAAQ,WAAW,WAAW,CAAC;AAAA,MACxC,WAAW,QAAQ,WAAW;AAAA,MAC9B,WAAW,QAAQ,WAAW;AAAA,MAC9B,eAAe,QAAQ,WAAW;AAAA,IACpC;AAEA,WAAO,WAAW,KAAK,QAAQ,YAAY;AAGzC,UAAI;AACF,cAAM,QAAQ,SAAS,UAAU;AAAA,MACnC,SAAS,OAAO;AACd;AACA,aAAK,OAAO,MAAM,kBAAkB;AAAA,UAClC,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAChD,OAAO,iBAAiB,QAAQ,MAAM,QAAQ;AAAA,UAC9C;AAAA,UACA;AAAA,UACA,SAAS;AAAA,UACT,YAAY,KAAK,QAAQ;AAAA,QAC3B,CAAC;AACD,YAAI,WAAW,KAAK,QAAQ,YAAY;AACtC,gBAAM,KAAK,MAAM,KAAK,QAAQ,UAAU;AAAA,QAC1C;AACA;AAAA,MACF;AACA,YAAMC,YAAW,KAAK,IAAI,IAAI;AAC9B,WAAK,OAAO,KAAK,kCAAkC,EAAE,UAAU,YAAY,UAAAA,WAAU,UAAU,WAAW,EAAE,CAAC;AAC7G,UAAI,KAAK,OAAO,SAAS,SAAS,KAAK,GAAG;AACxC,aAAK,SAAS,KAAK,MAAM,oBAAoB,EAAE,UAAU,YAAY,UAAAA,WAAU,UAAU,WAAW,EAAE,CAAC;AAAA,MACzG;AACA;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAK,OAAO,MAAM,oDAAoD,EAAE,UAAU,YAAY,YAAY,KAAK,QAAQ,YAAY,SAAS,CAAC;AAC7I,QAAI,KAAK,OAAO,SAAS,SAAS,MAAM,GAAG;AACzC,WAAK,SAAS,KAAK,MAAM,cAAc,EAAE,UAAU,YAAY,UAAU,QAAQ,wBAAwB,CAAC;AAAA,IAC5G;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,OACN,SACA,SACA,QACS;AAOT,QAAI,YAAY,KAAK,SAAS;AAC5B,WAAK,OAAO,KAAK,oEAAoE;AACrF,aAAO;AAAA,IACT;AACA,QAAI;AACF,UAAI,WAAW,OAAO;AACpB,gBAAQ,IAAI,OAAO;AAAA,MACrB,OAAO;AACL,gBAAQ,KAAK,SAAS,OAAO,KAAK;AAAA,MACpC;AACA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,+DAA+D;AAAA,QAC/E;AAAA,QACA;AAAA,QACA,UAAU,QAAQ,OAAO;AAAA,QACzB,YAAY,QAAQ,OAAO;AAAA,MAC7B,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAgC;AACpC,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,SAAS;AACpC,aAAO;AAAA,IACT;AACA,QAAI;AACF,YAAM,EAAE,MAAM,IAAI,MAAM,KAAK,QAAQ,YAAY,IAAI;AAAA,QACnD,WAAW;AAAA,QACX,YAAY;AAAA,MACd,CAAC;AACD,YAAM,KAAK,QAAQ,YAAY,KAAK;AACpC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,OAAO,KAAK,uBAAuB,EAAE,MAAM,CAAC;AACjD,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,aAAa,SAAgC;AACnD,QAAI,KAAK,kBAAkB,EAAG,QAAO,QAAQ,QAAQ;AACrD,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,OAAO,KAAK,uDAAuD,EAAE,eAAe,KAAK,cAAc,CAAC;AAC7G,aAAK,eAAe;AACpB,gBAAQ;AAAA,MACV,GAAG,OAAO;AACV,WAAK,eAAe,MAAM;AACxB,qBAAa,KAAK;AAClB,aAAK,eAAe;AACpB,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGQ,SAAY,MAAuC,MAAe;AACxE,QAAI;AAAE,aAAO,IAAI;AAAA,IAAE,SAAS,OAAO;AAAE,WAAK,OAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC;AAAA,IAAE;AAAA,EAChG;AAAA,EAEQ,MAAM,IAA2B;AACvC,WAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACzD;AACF;","names":["amqp","duration"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/logger.ts","../src/semaphore.ts"],"sourcesContent":["export { DeadLetterError, RabbitMQClient, UnroutableMessageError } from './client.js'\nexport { defaultLogger, silentLogger } from './logger.js'\nexport type {\n ILogger,\n PublishOptions,\n RabbitMQBinding,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\n RabbitMQMessageProperties,\n RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\n","import amqp from 'amqplib'\nimport type {\n ILogger,\n PublishOptions,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\n RabbitMQMessageProperties,\n RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\nimport { defaultLogger, forLibraryCalls } from './logger.js'\nimport { Semaphore } from './semaphore.js'\n\nconst DEFAULT_PREFETCH = 10\nconst MAX_PUBLISH_RETRY_DELAY_MS = 60_000\n\nconst DEFAULTS = {\n maxRetries: 3,\n retryDelay: 1000,\n connectionRetryDelay: 5000,\n initMaxAttempts: 5,\n publishMaxAttempts: 5,\n prefetch: DEFAULT_PREFETCH,\n closeTimeout: 5000,\n} as const\n\nfunction backoff(baseDelay: number, attempt: number, maxDelay: number): number {\n return Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay)\n}\n\n/** A `mandatory` publish the broker could route to no queue. */\nexport class UnroutableMessageError extends Error {\n constructor(\n readonly exchange: string,\n readonly routingKey: string,\n ) {\n super(`No queue is bound to receive ${exchange || '(default exchange)'}/${routingKey}`)\n this.name = 'UnroutableMessageError'\n }\n}\n\n/** Thrown by a handler to send its message to the DLQ at once, without retrying. */\nexport class DeadLetterError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'DeadLetterError'\n }\n}\n\n/**\n * Production-grade RabbitMQ client with confirm channels, automatic DLQ\n * infrastructure, reconnection with subscription restoration, and\n * exponential-backoff publishing.\n *\n * @example\n * ```ts\n * const client = new RabbitMQClient({ url: 'amqp://localhost' })\n * await client.init()\n * await client.publish('events', 'user.created', { userId: '123' })\n * await client.subscribe('events', 'user.created', 'user-service', handler)\n * ```\n */\nexport class RabbitMQClient {\n private connection: amqp.ChannelModel | null = null\n private channel: amqp.ConfirmChannel | null = null\n private connected = false\n private closing = false\n private subscriptions: RabbitMQSubscription[] = []\n private readonly options: Required<Omit<RabbitMQClientOptions, 'logger' | 'hooks'>>\n private readonly logger: ILogger\n private readonly hooks: RabbitMQHooks\n private initializationPromise: Promise<void> | null = null\n private reconnectionPromise: Promise<void> | null = null\n private assertedExchanges = new Set<string>()\n // One per channel, so a late return on a replaced channel reaches no waiter.\n private returnWaiters = new Set<(msg: amqp.Message) => void>()\n private consumerTags = new Map<string, string>()\n // Per-queue concurrency limiter, keyed by queue name. Persisted across\n // reconnects (unlike consumerTags) so handlers still running when a\n // connection drops keep holding their permits, and the new consumer cannot\n // exceed the ceiling while the old pipeline drains. `null` = no limit.\n private subscriptionSemaphores = new Map<string, Semaphore | null>()\n private retryWakers = new Set<() => void>()\n private inflightCount = 0\n private drainResolve: (() => void) | null = null\n\n constructor(options: RabbitMQClientOptions) {\n this.options = {\n url: options.url,\n maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,\n retryDelay: options.retryDelay ?? DEFAULTS.retryDelay,\n connectionRetryDelay: options.connectionRetryDelay ?? DEFAULTS.connectionRetryDelay,\n initMaxAttempts: options.initMaxAttempts ?? DEFAULTS.initMaxAttempts,\n publishMaxAttempts: options.publishMaxAttempts ?? DEFAULTS.publishMaxAttempts,\n prefetch: options.prefetch ?? DEFAULTS.prefetch,\n // Default concurrency tracks prefetch, preserving the previous behaviour\n // (handlers ran fire-and-forget, so up to `prefetch` ran at once) while\n // making the ceiling explicit and independently tunable. A value <= 0\n // (including an unlimited `prefetch: 0`) means \"no concurrency limit\".\n concurrency: options.concurrency ?? options.prefetch ?? DEFAULTS.prefetch,\n closeTimeout: options.closeTimeout ?? DEFAULTS.closeTimeout,\n }\n this.logger = forLibraryCalls(options.logger ?? defaultLogger)\n this.hooks = options.hooks ?? {}\n }\n\n /**\n * Opens a connection and creates a confirm channel. Retries up to\n * `initMaxAttempts` times, then throws. Idempotent and safe to call\n * concurrently — duplicate calls share the same in-flight promise.\n */\n async init(): Promise<void> {\n if (this.connection && this.connected) {\n return\n }\n if (this.initializationPromise) {\n return this.initializationPromise\n }\n // Attach a no-op catch to prevent Node from briefly treating this as an\n // unhandled rejection before the await below registers its own handler.\n this.initializationPromise = this.doConnect(this.options.initMaxAttempts)\n this.initializationPromise.catch(() => undefined)\n try {\n await this.initializationPromise\n } finally {\n this.initializationPromise = null\n }\n }\n\n private async doConnect(maxAttempts?: number): Promise<void> {\n this.assertedExchanges.clear()\n this.consumerTags.clear()\n // A channel failure leaves its connection open; drop it so it does not\n // outlive the client and keep the process alive.\n this.connection?.close().catch(() => undefined)\n this.connection = null\n let attempts = 0\n while (!this.connected && !this.closing) {\n try {\n const connection = await amqp.connect(this.options.url)\n this.connection = connection\n this.logger.info('Connected to server')\n const channel = await connection.createConfirmChannel()\n this.channel = channel\n // Retries sleeping on the replaced channel give up; the broker redelivers their messages.\n this.wakeRetries()\n const returnWaiters = new Set<(msg: amqp.Message) => void>()\n this.returnWaiters = returnWaiters\n channel.on('return', (msg: amqp.Message) => {\n for (const waiter of returnWaiters) waiter(msg)\n })\n this.logger.info('Confirm channel created')\n await channel.prefetch(this.options.prefetch)\n this.logger.info('Channel prefetch set', { prefetch: this.options.prefetch })\n\n // Events from a connection or channel a reconnect has replaced must not\n // tear down the current one.\n connection.on('error', (error: Error) => {\n if (connection !== this.connection) return\n this.connected = false\n this.logger.error('Connection error', { error })\n })\n connection.on('close', () => {\n if (connection !== this.connection) return\n this.connected = false\n this.logger.warn('Connection closed')\n this.reconnectWithRetry()\n })\n channel.on('error', (error: Error) => {\n if (channel !== this.channel) return\n this.logger.error('Channel error', { error })\n this.handleChannelFailure()\n })\n channel.on('close', () => {\n if (channel !== this.channel) return\n this.logger.warn('Channel closed')\n this.handleChannelFailure()\n })\n\n this.connected = true\n this.logger.info('Client initialized successfully')\n } catch (error) {\n attempts++\n this.connected = false\n if (maxAttempts !== undefined && attempts >= maxAttempts) {\n this.logger.error('Connection failed after maximum attempts', { error, attempts, maxAttempts })\n throw new Error(\n `Failed to connect to RabbitMQ after ${attempts} attempts. ` +\n 'Check RABBITMQ_URL configuration and RabbitMQ server availability.',\n )\n }\n this.logger.warn('Connection attempt failed, retrying...', {\n error,\n attempt: attempts,\n maxAttempts: maxAttempts ?? 'unlimited',\n retryDelayMs: this.options.connectionRetryDelay,\n })\n await this.sleep(this.options.connectionRetryDelay)\n }\n }\n if (!this.connected) {\n throw new Error('RabbitMQ client closed while connecting')\n }\n }\n\n private handleChannelFailure(): void {\n if (this.connected) {\n this.connected = false\n this.reconnectWithRetry()\n }\n }\n\n /**\n * Returns whether the client currently has an active connection and channel.\n */\n isConnected(): boolean {\n return this.connected\n }\n\n /**\n * Publishes a JSON message to a topic exchange with publisher confirms, or\n * straight to a queue through the default exchange (`''`, routing key = queue name).\n * Retries with exponential backoff (capped at 60 s), forcing a reconnect\n * on each failure. Throws after `publishMaxAttempts` exhausted.\n */\n async publish(\n exchange: string,\n routingKey: string,\n message: RabbitMQMessage,\n options?: PublishOptions,\n ): Promise<void> {\n let attempts = 0\n const maxAttempts = options?.maxAttempts ?? this.options.publishMaxAttempts\n const baseDelay = this.options.connectionRetryDelay\n const content = Buffer.from(JSON.stringify(message))\n // Taken once, not per attempt: a retried publish must not look newer than\n // a message published after it succeeded.\n const timestamp = Math.floor(Date.now() / 1000)\n\n while (attempts < maxAttempts) {\n try {\n if (!this.connected) {\n await this.reconnectWithRetry()\n }\n\n if (!this.channel) {\n throw new Error('Channel not available')\n }\n\n // The default exchange ('') always exists and the broker refuses to\n // declare it, so publishing straight to a queue skips the assertion.\n if (exchange !== '' && !this.assertedExchanges.has(exchange)) {\n await this.channel.assertExchange(exchange, 'topic', { durable: true })\n this.assertedExchanges.add(exchange)\n }\n\n const channel = this.channel\n const returnWaiters = this.returnWaiters\n // The broker sends basic.return before the confirm of the same message,\n // so the flag is settled by the time waitForConfirms resolves. A return\n // carries no delivery tag, so it is matched on where it was sent plus the\n // message id, or the content when there is none.\n let returned = false\n const messageId = options?.messageId\n const onReturn = (msg: amqp.Message) => {\n if (msg.fields.exchange !== exchange || msg.fields.routingKey !== routingKey) return\n if (messageId !== undefined ? msg.properties.messageId === messageId : msg.content.equals(content)) {\n returned = true\n }\n }\n if (options?.mandatory) returnWaiters.add(onReturn)\n\n try {\n channel.publish(exchange, routingKey, content, {\n persistent: true,\n timestamp,\n headers: options?.headers,\n correlationId: options?.correlationId,\n messageId: options?.messageId,\n expiration: options?.expiration,\n mandatory: options?.mandatory,\n })\n\n await channel.waitForConfirms()\n } finally {\n returnWaiters.delete(onReturn)\n }\n\n if (returned) {\n throw new UnroutableMessageError(exchange, routingKey)\n }\n\n if (attempts > 0) {\n this.logger.info('Published message after retries', { exchange, routingKey, messageSize: content.length, attempts })\n } else {\n this.logger.info('Published message', { exchange, routingKey, messageSize: content.length })\n }\n this.logger.debug('Published message payload', { exchange, routingKey, payload: message })\n\n this.callHook(this.hooks.onPublish, { exchange, routingKey, attempts: attempts + 1 })\n\n return\n } catch (error) {\n // The broker answered, and the same routing would fail the same way.\n if (error instanceof UnroutableMessageError) {\n this.logger.warn('Published message was unroutable', { exchange, routingKey })\n throw error\n }\n\n attempts++\n this.connected = false\n\n if (attempts >= maxAttempts) {\n this.logger.error('Publish failed after max attempts', { error, exchange, routingKey, attempts, maxAttempts })\n throw new Error(\n `Failed to publish to ${exchange || '(default exchange)'}/${routingKey} after ${attempts} attempts: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n\n const retryDelay = backoff(baseDelay, attempts, MAX_PUBLISH_RETRY_DELAY_MS)\n\n this.logger.warn('Publish attempt failed, retrying', { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay })\n\n await this.sleep(retryDelay)\n }\n }\n }\n\n /**\n * Gracefully shuts down the client. Waits up to `closeTimeout` ms for\n * in-flight message handlers to finish before closing the channel and\n * connection. Pass `false` to preserve subscriptions for a later\n * `init()` / reconnect cycle.\n */\n async close(clearSubscriptions = true): Promise<void> {\n // Closing the channel fires its 'close' handler, which would otherwise\n // reconnect and leave a connection behind that keeps the process alive.\n this.closing = true\n try {\n // A connect already running gives up after its current attempt; whatever\n // it opened is closed below.\n await Promise.allSettled([this.initializationPromise, this.reconnectionPromise])\n this.wakeRetries()\n if (this.inflightCount > 0) {\n this.logger.info('Waiting for in-flight messages to drain', { inflightCount: this.inflightCount })\n await this.waitForDrain(this.options.closeTimeout)\n }\n try {\n await this.channel?.close()\n } finally {\n await this.connection?.close()\n }\n this.connection = null\n this.channel = null\n this.connected = false\n this.initializationPromise = null\n this.reconnectionPromise = null\n this.assertedExchanges.clear()\n this.consumerTags.clear()\n if (clearSubscriptions) {\n this.subscriptions = []\n this.subscriptionSemaphores.clear()\n }\n this.logger.info('Connection closed')\n } catch (error) {\n this.logger.error('Error closing connection', { error })\n throw error\n } finally {\n this.closing = false\n }\n }\n\n private async reconnectWithRetry(): Promise<void> {\n if (this.closing) return\n if (this.reconnectionPromise) {\n return this.reconnectionPromise\n }\n this.connected = false\n this.logger.info('Starting reconnection...')\n this.reconnectionPromise = this.doReconnect()\n this.reconnectionPromise.catch(() => undefined)\n try {\n await this.reconnectionPromise\n } catch (error) {\n // The event handlers call this without awaiting it, so a reconnect that\n // close() interrupted must not surface as an unhandled rejection.\n if (!this.closing) throw error\n } finally {\n this.reconnectionPromise = null\n }\n }\n\n private async doReconnect(): Promise<void> {\n await this.doConnect()\n await this.resubscribeAll()\n }\n\n private async resubscribeAll(): Promise<void> {\n if (this.subscriptions.length === 0) {\n return\n }\n this.logger.info('Re-establishing subscriptions', { count: this.subscriptions.length })\n const subs = [...this.subscriptions]\n const results = await Promise.allSettled(\n subs.map(async (sub) => {\n await this.setupSubscription(sub)\n return sub.queue\n }),\n )\n const succeeded: string[] = []\n const failed: string[] = []\n results.forEach((result, index) => {\n if (result.status === 'fulfilled') {\n succeeded.push(result.value)\n } else {\n const queue = subs[index].queue\n failed.push(queue)\n this.logger.error('Failed to re-subscribe to queue', { error: result.reason, queue })\n }\n })\n if (succeeded.length > 0) {\n this.logger.info('Successfully re-subscribed to queues', { count: succeeded.length, queues: succeeded })\n }\n if (failed.length > 0) {\n this.logger.warn('Some subscriptions failed to restore', { count: failed.length, queues: failed })\n }\n this.callHook(this.hooks.onReconnect, { subscriptionsRestored: succeeded.length, subscriptionsFailed: failed.length })\n }\n\n /**\n * Subscribes to a queue with automatic DLQ infrastructure setup.\n *\n * Pass `options.queueArguments` to override the default quorum-queue\n * arguments (merged with the DLQ wiring defaults).\n */\n async subscribe(\n exchange: string,\n routingKey: string,\n queue: string,\n handler: RabbitMQMessageHandler,\n options?: SubscribeOptions,\n ): Promise<void> {\n if (!this.connected || !this.channel) {\n throw new Error('RabbitMQ client not connected. Call init() first.')\n }\n const sub: RabbitMQSubscription = { exchange, routingKey, queue, handler, options }\n const existingIndex = this.subscriptions.findIndex((s) => s.queue === queue)\n const previous = existingIndex === -1 ? undefined : this.subscriptions[existingIndex]\n if (existingIndex === -1) {\n this.subscriptions.push(sub)\n } else {\n this.subscriptions[existingIndex] = sub\n // Re-subscribing may change concurrency; drop the old limiter so\n // setupSubscription rebuilds it from the new options. (A reconnect goes\n // through setupSubscription directly and keeps the existing limiter.)\n this.subscriptionSemaphores.delete(queue)\n }\n try {\n await this.setupSubscription(sub)\n } catch (error) {\n // A subscription that cannot be set up must not be retried on every reconnect.\n this.subscriptions = previous\n ? this.subscriptions.map((s) => (s === sub ? previous : s))\n : this.subscriptions.filter((s) => s !== sub)\n throw error\n }\n }\n\n /**\n * Cancels a queue subscription and removes it from the restoration list.\n */\n async unsubscribe(queue: string): Promise<void> {\n const tag = this.consumerTags.get(queue)\n if (tag && this.channel) {\n await this.channel.cancel(tag)\n }\n this.consumerTags.delete(queue)\n this.subscriptionSemaphores.delete(queue)\n this.subscriptions = this.subscriptions.filter((s) => s.queue !== queue)\n this.logger.info('Unsubscribed from queue', { queue })\n }\n\n private async setupSubscription(sub: RabbitMQSubscription): Promise<void> {\n if (!this.channel) {\n throw new Error('Channel not available')\n }\n // Bind this consumer to the exact channel that will deliver its messages,\n // so ack/nack always target that channel even after a reconnect swaps\n // `this.channel`.\n const channel = this.channel\n const { exchange, routingKey, queue, handler, options } = sub\n const dlxExchange = options?.deadLetterExchange ?? `${exchange}.dlx`\n const dlqQueue = `${queue}.dlq`\n // Set once on the queue, so every binding's dead letters reach the DLQ.\n const dlqRoutingKey = `${routingKey}.dead`\n const bindings = [{ exchange, routingKey }, ...(options?.bindings ?? [])]\n\n await channel.assertExchange(dlxExchange, 'topic', { durable: true })\n await channel.assertQueue(dlqQueue, { durable: true })\n await channel.bindQueue(dlqQueue, dlxExchange, dlqRoutingKey)\n\n const missing = bindings.map((b) => b.exchange).filter((name) => !this.assertedExchanges.has(name))\n if (options?.passiveExchanges && missing.length > 0) {\n await this.checkExchanges(missing)\n } else {\n for (const name of missing) {\n await channel.assertExchange(name, 'topic', { durable: true })\n }\n }\n for (const name of missing) this.assertedExchanges.add(name)\n\n const queueType = options?.queueArguments?.['x-queue-type'] ?? 'quorum'\n const queueArgs: Record<string, unknown> = {\n 'x-queue-type': queueType,\n 'x-overflow': 'reject-publish',\n }\n // at-least-once DLQ strategy is only supported by quorum queues\n if (queueType === 'quorum') {\n queueArgs['x-dead-letter-strategy'] = 'at-least-once'\n }\n\n await channel.assertQueue(queue, {\n durable: true,\n deadLetterExchange: dlxExchange,\n deadLetterRoutingKey: dlqRoutingKey,\n arguments: { ...queueArgs, ...options?.queueArguments },\n })\n\n for (const binding of bindings) {\n await channel.bindQueue(queue, binding.exchange, binding.routingKey)\n }\n // Reuse the queue's existing limiter across reconnects; only build a new\n // one the first time (or after subscribe()/unsubscribe() cleared it).\n // A concurrency <= 0 means \"no limit\" (null), matching an unlimited prefetch.\n if (!this.subscriptionSemaphores.has(queue)) {\n const concurrency = options?.concurrency ?? this.options.concurrency\n this.subscriptionSemaphores.set(queue, concurrency > 0 ? new Semaphore(concurrency) : null)\n }\n const semaphore = this.subscriptionSemaphores.get(queue) ?? null\n const { consumerTag } = await channel.consume(\n queue,\n (message) => {\n if (message) {\n this.dispatch(message, handler, semaphore, channel, options)\n }\n },\n { noAck: false },\n )\n this.consumerTags.set(queue, consumerTag)\n this.logger.info('Subscribed to queue', { queue, bindings })\n }\n\n // A failed check closes the channel it ran on, so it runs on a throwaway one\n // rather than take down the shared channel and every consumer on it.\n private async checkExchanges(exchanges: string[]): Promise<void> {\n if (!this.connection) {\n throw new Error('Connection not available')\n }\n const probe = await this.connection.createChannel()\n probe.on('error', () => undefined)\n try {\n for (const name of exchanges) {\n await probe.checkExchange(name)\n }\n } finally {\n await probe.close().catch(() => undefined)\n }\n }\n\n /**\n * Gates a delivered message on the subscription's concurrency semaphore, then\n * processes it. A message counts as in-flight from delivery until its handler\n * settles (including time spent waiting for a permit), so graceful `close()`\n * drains queued messages as well as actively-processing ones.\n */\n private dispatch(\n message: amqp.ConsumeMessage,\n handler: RabbitMQMessageHandler,\n semaphore: Semaphore | null,\n channel: amqp.ConfirmChannel,\n options: SubscribeOptions | undefined,\n ): void {\n this.inflightCount++\n const acquire = semaphore ? semaphore.acquire() : Promise.resolve()\n acquire\n .then(() => this.handleWithRetry(message, handler, channel, options))\n .catch((error) => {\n this.logger.error('Unhandled error in message handler', { error })\n })\n .finally(() => {\n semaphore?.release()\n this.inflightCount--\n if (this.inflightCount === 0 && this.drainResolve) {\n this.drainResolve()\n }\n })\n }\n\n private async handleWithRetry(\n message: amqp.ConsumeMessage,\n handler: RabbitMQMessageHandler,\n channel: amqp.ConfirmChannel,\n options: SubscribeOptions | undefined,\n ): Promise<void> {\n // `channel` is the one that delivered this message. If a reconnect has\n // since replaced it, this message was never acked and the broker will\n // redeliver it on the new channel, so drop this stale attempt rather than\n // run the handler again or ack a tag the new channel does not know.\n if (channel !== this.channel) {\n this.logger.warn('Skipping message from a superseded channel; it will be redelivered')\n return\n }\n const startTime = Date.now()\n let attempts = 0\n const routingKey = message.fields.routingKey\n const exchange = message.fields.exchange\n\n let content: RabbitMQMessage\n try {\n content = JSON.parse(message.content.toString())\n } catch (parseError) {\n const rawContent = message.content.toString()\n const rawPreview = rawContent.substring(0, 100)\n this.logger.error('Failed to parse message JSON, sending to DLQ', {\n error: parseError,\n exchange,\n routingKey,\n rawContentPreview: rawPreview + (rawContent.length > 100 ? '...' : ''),\n })\n if (this.settle(channel, message, 'nack')) {\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: 0, reason: 'invalid_json' })\n }\n return\n }\n\n this.logger.debug('Message received, processing', { exchange, routingKey, payload: content })\n\n const properties: RabbitMQMessageProperties = {\n exchange,\n routingKey,\n headers: message.properties.headers ?? {},\n timestamp: message.properties.timestamp,\n messageId: message.properties.messageId,\n correlationId: message.properties.correlationId,\n }\n\n const maxRetries = options?.maxRetries ?? this.options.maxRetries\n while (attempts < maxRetries) {\n // Only the handler call belongs in this try. Acking inside it would make\n // a dead channel look like a failed handler and re-run its side effects.\n try {\n await handler(content, properties)\n } catch (error) {\n attempts++\n // By name, not instanceof: the ESM and CJS builds each define their own class.\n if (error instanceof Error && error.name === 'DeadLetterError') {\n const duration = Date.now() - startTime\n this.logger.warn('Handler dead-lettered the message', { error: error.message, exchange, routingKey })\n if (this.settle(channel, message, 'nack')) {\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: 'dead_letter_error' })\n }\n return\n }\n this.logger.error('Handler failed', {\n error: error instanceof Error ? error.message : error,\n stack: error instanceof Error ? error.stack : undefined,\n exchange,\n routingKey,\n attempt: attempts,\n maxRetries,\n })\n if (attempts < maxRetries) {\n await this.sleepUntilClose(this.retryDelayFor(attempts, options))\n if (this.closing || channel !== this.channel) {\n this.logger.warn('Stopped retrying a message on close or reconnect; it will be redelivered', { exchange, routingKey })\n return\n }\n }\n continue\n }\n const duration = Date.now() - startTime\n this.logger.info('Message processed successfully', { exchange, routingKey, duration, attempts: attempts + 1 })\n if (this.settle(channel, message, 'ack')) {\n this.callHook(this.hooks.onMessageProcessed, { exchange, routingKey, duration, attempts: attempts + 1 })\n }\n return\n }\n\n const duration = Date.now() - startTime\n this.logger.error('Message failed after max retries, sending to DLQ', { exchange, routingKey, maxRetries, duration })\n if (this.settle(channel, message, 'nack')) {\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: 'max_retries_exhausted' })\n }\n }\n\n /**\n * Acks or nacks a delivery, reporting whether the broker accepted it.\n *\n * A settle failure is a transport error, never a handler failure: by this\n * point the handler has already run, so it must not be retried just because\n * the channel went away. The delivery stays unacked and the broker redelivers\n * it once the channel closes.\n */\n private settle(\n channel: amqp.ConfirmChannel,\n message: amqp.ConsumeMessage,\n action: 'ack' | 'nack',\n ): boolean {\n // A handler can run for minutes, so the channel that delivered this\n // message may have been replaced by a reconnect since the entry check.\n // Settling on it would throw, and replaying the delivery tag on the\n // current channel would settle an unrelated message, since tags are scoped\n // to the channel that issued them. The broker requeues a closed channel's\n // unacked deliveries, so let it be redelivered instead.\n if (channel !== this.channel) {\n this.logger.warn('Skipping message from a superseded channel; it will be redelivered')\n return false\n }\n try {\n if (action === 'ack') {\n channel.ack(message)\n } else {\n channel.nack(message, false, false)\n }\n return true\n } catch (error) {\n this.logger.error('Failed to settle message; leaving it unacked for redelivery', {\n error,\n action,\n exchange: message.fields.exchange,\n routingKey: message.fields.routingKey,\n })\n return false\n }\n }\n\n /**\n * Lightweight liveness probe: creates and immediately deletes a temporary\n * exclusive queue. Returns `false` when disconnected or on broker error.\n */\n async checkHealth(): Promise<boolean> {\n if (!this.connected || !this.channel) {\n return false\n }\n try {\n const { queue } = await this.channel.assertQueue('', {\n exclusive: true,\n autoDelete: true,\n })\n await this.channel.deleteQueue(queue)\n return true\n } catch (error) {\n this.logger.warn('Health check failed', { error })\n return false\n }\n }\n\n private waitForDrain(timeout: number): Promise<void> {\n if (this.inflightCount === 0) return Promise.resolve()\n return new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n this.logger.warn('Close timeout reached with messages still in flight', { inflightCount: this.inflightCount })\n this.drainResolve = null\n resolve()\n }, timeout)\n this.drainResolve = () => {\n clearTimeout(timer)\n this.drainResolve = null\n resolve()\n }\n })\n }\n\n /** Invokes a hook callback, swallowing errors so hooks never break message flow. */\n private callHook<T>(hook: ((info: T) => void) | undefined, info: T): void {\n try { hook?.(info) } catch (error) { this.logger.debug('Observability hook error', { error }) }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n }\n\n private retryDelayFor(attempts: number, options: SubscribeOptions | undefined): number {\n const { retryDelay } = this.options\n if (options?.maxRetryDelay === undefined) return retryDelay\n return backoff(retryDelay, attempts, options.maxRetryDelay)\n }\n\n private wakeRetries(): void {\n for (const wake of [...this.retryWakers]) wake()\n }\n\n /** Also cut short by a reconnect, via wakeRetries(). */\n private sleepUntilClose(ms: number): Promise<void> {\n if (this.closing) return Promise.resolve()\n return new Promise((resolve) => {\n const wake = () => {\n clearTimeout(timer)\n this.retryWakers.delete(wake)\n resolve()\n }\n const timer = setTimeout(wake, ms)\n this.retryWakers.add(wake)\n })\n }\n}\n","import type { ILogger } from './types.js'\n\n/**\n * Default logger used when the consumer does not inject one.\n *\n * Forwards `warn` and `error` to the console so connection drops, channel\n * errors, and failed handlers stay visible out of the box, while staying\n * silent for `info`/`debug` so routine operation never pollutes the\n * consumer's output. Pass `silentLogger` for zero output, or any `ILogger`\n * (console, Winston, pino, Bunyan) to take full control.\n */\nexport const defaultLogger: ILogger = {\n debug: () => {},\n info: () => {},\n warn: (message, ...args) => console.warn('[rabbitmq-client]', message, ...args),\n error: (message, ...args) => console.error('[rabbitmq-client]', message, ...args),\n}\n\ntype Level = keyof ILogger\ntype ObjectFirst = (context: object, message: string) => void\n\n/**\n * pino takes `(context, message)` and drops a context passed after the\n * message, and only serializes an Error under `err`. It is the one logger\n * with a `bindings()` method, so it is recognised by that and called its way.\n */\nexport const forLibraryCalls = (logger: ILogger): ILogger => {\n if (typeof (logger as { bindings?: unknown }).bindings !== 'function') return logger\n const call =\n (level: Level) =>\n (message: string, context?: unknown): void => {\n const log = (logger[level] as unknown as ObjectFirst).bind(logger)\n if (typeof context !== 'object' || context === null) return log({}, message)\n const { error, ...rest } = context as { error?: unknown }\n log(error === undefined ? rest : { ...rest, err: error }, message)\n }\n return { debug: call('debug'), info: call('info'), warn: call('warn'), error: call('error') }\n}\n\n/**\n * No-op logger that discards every message. Use this to silence the client\n * entirely, including the `warn`/`error` output the default logger emits.\n */\nexport const silentLogger: ILogger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n}\n","/**\n * Minimal FIFO counting semaphore used to cap how many message handlers run\n * concurrently, independently of channel prefetch.\n *\n * Prefetch bounds how many messages the broker delivers into consumer memory;\n * the semaphore bounds how many of those are actively being processed at once.\n * Keeping the two separate lets a consumer buffer a large prefetch window while\n * still protecting a slow downstream (a database, an HTTP API) from a burst.\n */\nexport class Semaphore {\n private permits: number\n private readonly waiters: Array<() => void> = []\n\n constructor(permits: number) {\n if (!Number.isInteger(permits) || permits < 1) {\n throw new Error(`Semaphore requires a positive integer of permits, got ${permits}`)\n }\n this.permits = permits\n }\n\n /**\n * Acquires a permit, resolving immediately when one is free or queueing\n * (FIFO) until another holder releases. Always pair with `release()` in a\n * `finally` so a throwing handler never leaks a permit.\n */\n acquire(): Promise<void> {\n if (this.permits > 0) {\n this.permits--\n return Promise.resolve()\n }\n return new Promise<void>((resolve) => {\n this.waiters.push(resolve)\n })\n }\n\n /**\n * Releases a permit. Hands it directly to the next waiter if any are queued,\n * otherwise returns it to the pool.\n */\n release(): void {\n const next = this.waiters.shift()\n if (next) {\n next()\n } else {\n this.permits++\n }\n }\n\n /** Permits currently available. Exposed for tests and introspection. */\n get available(): number {\n return this.permits\n }\n\n /** Number of callers currently queued waiting for a permit. */\n get waiting(): number {\n return this.waiters.length\n }\n}\n"],"mappings":"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","names":["amqp","duration"]}