@linagora/rabbitmq-client 0.1.2 → 0.2.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
@@ -39,7 +39,7 @@ Only `url` is required. Everything else has defaults.
39
39
  | `publishMaxAttempts` | `5` | Publish retries (exponential backoff, capped at 60s) |
40
40
  | `prefetch` | `10` | Channel prefetch count |
41
41
  | `closeTimeout` | `5000` | Milliseconds to wait for in-flight messages when closing |
42
- | `logger` | -- | Parent [pino](https://github.com/pinojs/pino) instance (the library creates its own if omitted) |
42
+ | `logger` | console (warn/error) | Any `ILogger` (`console`, Winston, pino, Bunyan). Omitted: logs warn/error to the console, stays quiet otherwise. Pass `silentLogger` for no output. |
43
43
  | `hooks` | -- | Observability callbacks (see [Hooks](#hooks)) |
44
44
 
45
45
  ## How it works
package/dist/index.cjs CHANGED
@@ -30,7 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
- RabbitMQClient: () => RabbitMQClient
33
+ RabbitMQClient: () => RabbitMQClient,
34
+ defaultLogger: () => defaultLogger,
35
+ silentLogger: () => silentLogger
34
36
  });
35
37
  module.exports = __toCommonJS(src_exports);
36
38
 
@@ -38,12 +40,23 @@ module.exports = __toCommonJS(src_exports);
38
40
  var import_amqplib = __toESM(require("amqplib"), 1);
39
41
 
40
42
  // src/logger.ts
41
- var import_pino = __toESM(require("pino"), 1);
42
- var createLogger = (parent) => {
43
- if (parent) {
44
- return parent.child({ service: "rabbitmq-client" });
43
+ var defaultLogger = {
44
+ debug: () => {
45
+ },
46
+ info: () => {
47
+ },
48
+ warn: (message, ...args) => console.warn("[rabbitmq-client]", message, ...args),
49
+ error: (message, ...args) => console.error("[rabbitmq-client]", message, ...args)
50
+ };
51
+ var silentLogger = {
52
+ debug: () => {
53
+ },
54
+ info: () => {
55
+ },
56
+ warn: () => {
57
+ },
58
+ error: () => {
45
59
  }
46
- return (0, import_pino.default)({ name: "rabbitmq-client" });
47
60
  };
48
61
 
49
62
  // src/client.ts
@@ -83,7 +96,7 @@ var RabbitMQClient = class {
83
96
  prefetch: options.prefetch ?? DEFAULTS.prefetch,
84
97
  closeTimeout: options.closeTimeout ?? DEFAULTS.closeTimeout
85
98
  };
86
- this.logger = createLogger(options.logger);
99
+ this.logger = options.logger ?? defaultLogger;
87
100
  this.hooks = options.hooks ?? {};
88
101
  }
89
102
  /**
@@ -117,10 +130,10 @@ var RabbitMQClient = class {
117
130
  this.channel = await this.connection.createConfirmChannel();
118
131
  this.logger.info("Confirm channel created");
119
132
  await this.channel.prefetch(this.options.prefetch);
120
- this.logger.info({ prefetch: this.options.prefetch }, "Channel prefetch set");
133
+ this.logger.info("Channel prefetch set", { prefetch: this.options.prefetch });
121
134
  this.connection.on("error", (error) => {
122
135
  this.connected = false;
123
- this.logger.error({ error }, "Connection error");
136
+ this.logger.error("Connection error", { error });
124
137
  });
125
138
  this.connection.on("close", () => {
126
139
  this.connected = false;
@@ -128,7 +141,7 @@ var RabbitMQClient = class {
128
141
  this.reconnectWithRetry();
129
142
  });
130
143
  this.channel.on("error", (error) => {
131
- this.logger.error({ error }, "Channel error");
144
+ this.logger.error("Channel error", { error });
132
145
  this.handleChannelFailure();
133
146
  });
134
147
  this.channel.on("close", () => {
@@ -141,20 +154,17 @@ var RabbitMQClient = class {
141
154
  attempts++;
142
155
  this.connected = false;
143
156
  if (maxAttempts !== void 0 && attempts >= maxAttempts) {
144
- this.logger.error({ error, attempts, maxAttempts }, "Connection failed after maximum attempts");
157
+ this.logger.error("Connection failed after maximum attempts", { error, attempts, maxAttempts });
145
158
  throw new Error(
146
159
  `Failed to connect to RabbitMQ after ${attempts} attempts. Check RABBITMQ_URL configuration and RabbitMQ server availability.`
147
160
  );
148
161
  }
149
- this.logger.warn(
150
- {
151
- error,
152
- attempt: attempts,
153
- maxAttempts: maxAttempts ?? "unlimited",
154
- retryDelayMs: this.options.connectionRetryDelay
155
- },
156
- "Connection attempt failed, retrying..."
157
- );
162
+ this.logger.warn("Connection attempt failed, retrying...", {
163
+ error,
164
+ attempt: attempts,
165
+ maxAttempts: maxAttempts ?? "unlimited",
166
+ retryDelayMs: this.options.connectionRetryDelay
167
+ });
158
168
  await this.sleep(this.options.connectionRetryDelay);
159
169
  }
160
170
  }
@@ -202,21 +212,18 @@ var RabbitMQClient = class {
202
212
  });
203
213
  await this.channel.waitForConfirms();
204
214
  if (attempts > 0) {
205
- this.logger.info({ exchange, routingKey, messageSize: content.length, attempts }, "Published message after retries");
215
+ this.logger.info("Published message after retries", { exchange, routingKey, messageSize: content.length, attempts });
206
216
  } else {
207
- this.logger.info({ exchange, routingKey, messageSize: content.length }, "Published message");
217
+ this.logger.info("Published message", { exchange, routingKey, messageSize: content.length });
208
218
  }
209
- this.logger.debug({ exchange, routingKey, payload: message }, "Published message payload");
219
+ this.logger.debug("Published message payload", { exchange, routingKey, payload: message });
210
220
  this.callHook(this.hooks.onPublish, { exchange, routingKey, attempts: attempts + 1 });
211
221
  return;
212
222
  } catch (error) {
213
223
  attempts++;
214
224
  this.connected = false;
215
225
  if (attempts >= maxAttempts) {
216
- this.logger.error(
217
- { error, exchange, routingKey, attempts, maxAttempts },
218
- "Publish failed after max attempts"
219
- );
226
+ this.logger.error("Publish failed after max attempts", { error, exchange, routingKey, attempts, maxAttempts });
220
227
  throw new Error(
221
228
  `Failed to publish to ${exchange}/${routingKey} after ${attempts} attempts: ${error instanceof Error ? error.message : String(error)}`
222
229
  );
@@ -225,10 +232,7 @@ var RabbitMQClient = class {
225
232
  baseDelay * Math.pow(2, attempts - 1),
226
233
  MAX_PUBLISH_RETRY_DELAY_MS
227
234
  );
228
- this.logger.warn(
229
- { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay },
230
- "Publish attempt failed, retrying"
231
- );
235
+ this.logger.warn("Publish attempt failed, retrying", { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay });
232
236
  await this.sleep(retryDelay);
233
237
  }
234
238
  }
@@ -242,7 +246,7 @@ var RabbitMQClient = class {
242
246
  async close(clearSubscriptions = true) {
243
247
  try {
244
248
  if (this.inflightCount > 0) {
245
- this.logger.info({ inflightCount: this.inflightCount }, "Waiting for in-flight messages to drain");
249
+ this.logger.info("Waiting for in-flight messages to drain", { inflightCount: this.inflightCount });
246
250
  await this.waitForDrain(this.options.closeTimeout);
247
251
  }
248
252
  await this.channel?.close();
@@ -259,7 +263,7 @@ var RabbitMQClient = class {
259
263
  }
260
264
  this.logger.info("Connection closed");
261
265
  } catch (error) {
262
- this.logger.error({ error }, "Error closing connection");
266
+ this.logger.error("Error closing connection", { error });
263
267
  throw error;
264
268
  }
265
269
  }
@@ -285,7 +289,7 @@ var RabbitMQClient = class {
285
289
  if (this.subscriptions.length === 0) {
286
290
  return;
287
291
  }
288
- this.logger.info({ count: this.subscriptions.length }, "Re-establishing subscriptions");
292
+ this.logger.info("Re-establishing subscriptions", { count: this.subscriptions.length });
289
293
  const subs = [...this.subscriptions];
290
294
  const results = await Promise.allSettled(
291
295
  subs.map(async (sub) => {
@@ -301,14 +305,14 @@ var RabbitMQClient = class {
301
305
  } else {
302
306
  const queue = subs[index].queue;
303
307
  failed.push(queue);
304
- this.logger.error({ error: result.reason, queue }, "Failed to re-subscribe to queue");
308
+ this.logger.error("Failed to re-subscribe to queue", { error: result.reason, queue });
305
309
  }
306
310
  });
307
311
  if (succeeded.length > 0) {
308
- this.logger.info({ count: succeeded.length, queues: succeeded }, "Successfully re-subscribed to queues");
312
+ this.logger.info("Successfully re-subscribed to queues", { count: succeeded.length, queues: succeeded });
309
313
  }
310
314
  if (failed.length > 0) {
311
- this.logger.warn({ count: failed.length, queues: failed }, "Some subscriptions failed to restore");
315
+ this.logger.warn("Some subscriptions failed to restore", { count: failed.length, queues: failed });
312
316
  }
313
317
  this.callHook(this.hooks.onReconnect, { subscriptionsRestored: succeeded.length, subscriptionsFailed: failed.length });
314
318
  }
@@ -341,7 +345,7 @@ var RabbitMQClient = class {
341
345
  }
342
346
  this.consumerTags.delete(queue);
343
347
  this.subscriptions = this.subscriptions.filter((s) => s.queue !== queue);
344
- this.logger.info({ queue }, "Unsubscribed from queue");
348
+ this.logger.info("Unsubscribed from queue", { queue });
345
349
  }
346
350
  async setupSubscription(sub) {
347
351
  if (!this.channel) {
@@ -378,19 +382,19 @@ var RabbitMQClient = class {
378
382
  (message) => {
379
383
  if (message) {
380
384
  this.handleWithRetry(message, handler).catch((error) => {
381
- this.logger.error({ error }, "Unhandled error in message handler");
385
+ this.logger.error("Unhandled error in message handler", { error });
382
386
  });
383
387
  }
384
388
  },
385
389
  { noAck: false }
386
390
  );
387
391
  this.consumerTags.set(queue, consumerTag);
388
- this.logger.info({ exchange, routingKey, queue }, "Subscribed to queue");
392
+ this.logger.info("Subscribed to queue", { exchange, routingKey, queue });
389
393
  }
390
394
  async handleWithRetry(message, handler) {
391
395
  const channel = this.channel;
392
396
  if (!channel) {
393
- this.logger.warn("Cannot process message -- channel unavailable");
397
+ this.logger.warn("Cannot process message, channel unavailable");
394
398
  return;
395
399
  }
396
400
  this.inflightCount++;
@@ -405,54 +409,42 @@ var RabbitMQClient = class {
405
409
  } catch (parseError) {
406
410
  const rawContent = message.content.toString();
407
411
  const rawPreview = rawContent.substring(0, 100);
408
- this.logger.error(
409
- {
410
- error: parseError,
411
- exchange,
412
- routingKey,
413
- rawContentPreview: rawPreview + (rawContent.length > 100 ? "..." : "")
414
- },
415
- "Failed to parse message JSON, sending to DLQ"
416
- );
412
+ this.logger.error("Failed to parse message JSON, sending to DLQ", {
413
+ error: parseError,
414
+ exchange,
415
+ routingKey,
416
+ rawContentPreview: rawPreview + (rawContent.length > 100 ? "..." : "")
417
+ });
417
418
  channel.nack(message, false, false);
418
419
  this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: 0, reason: "invalid_json" });
419
420
  return;
420
421
  }
421
- this.logger.debug({ exchange, routingKey, payload: content }, "Message received, processing");
422
+ this.logger.debug("Message received, processing", { exchange, routingKey, payload: content });
422
423
  while (attempts < this.options.maxRetries) {
423
424
  try {
424
425
  await handler(content);
425
426
  const duration2 = Date.now() - startTime;
426
- this.logger.info(
427
- { exchange, routingKey, duration: duration2, attempts: attempts + 1 },
428
- "Message processed successfully"
429
- );
427
+ this.logger.info("Message processed successfully", { exchange, routingKey, duration: duration2, attempts: attempts + 1 });
430
428
  channel.ack(message);
431
429
  this.callHook(this.hooks.onMessageProcessed, { exchange, routingKey, duration: duration2, attempts: attempts + 1 });
432
430
  return;
433
431
  } catch (error) {
434
432
  attempts++;
435
- this.logger.error(
436
- {
437
- error: error instanceof Error ? error.message : error,
438
- stack: error instanceof Error ? error.stack : void 0,
439
- exchange,
440
- routingKey,
441
- attempt: attempts,
442
- maxRetries: this.options.maxRetries
443
- },
444
- "Handler failed"
445
- );
433
+ this.logger.error("Handler failed", {
434
+ error: error instanceof Error ? error.message : error,
435
+ stack: error instanceof Error ? error.stack : void 0,
436
+ exchange,
437
+ routingKey,
438
+ attempt: attempts,
439
+ maxRetries: this.options.maxRetries
440
+ });
446
441
  if (attempts < this.options.maxRetries) {
447
442
  await this.sleep(this.options.retryDelay);
448
443
  }
449
444
  }
450
445
  }
451
446
  const duration = Date.now() - startTime;
452
- this.logger.error(
453
- { exchange, routingKey, maxRetries: this.options.maxRetries, duration },
454
- "Message failed after max retries, sending to DLQ"
455
- );
447
+ this.logger.error("Message failed after max retries, sending to DLQ", { exchange, routingKey, maxRetries: this.options.maxRetries, duration });
456
448
  channel.nack(message, false, false);
457
449
  this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: "max_retries_exhausted" });
458
450
  } finally {
@@ -478,7 +470,7 @@ var RabbitMQClient = class {
478
470
  await this.channel.deleteQueue(queue);
479
471
  return true;
480
472
  } catch (error) {
481
- this.logger.warn({ error }, "Health check failed");
473
+ this.logger.warn("Health check failed", { error });
482
474
  return false;
483
475
  }
484
476
  }
@@ -486,10 +478,7 @@ var RabbitMQClient = class {
486
478
  if (this.inflightCount === 0) return Promise.resolve();
487
479
  return new Promise((resolve) => {
488
480
  const timer = setTimeout(() => {
489
- this.logger.warn(
490
- { inflightCount: this.inflightCount },
491
- "Close timeout reached with messages still in flight"
492
- );
481
+ this.logger.warn("Close timeout reached with messages still in flight", { inflightCount: this.inflightCount });
493
482
  this.drainResolve = null;
494
483
  resolve();
495
484
  }, timeout);
@@ -505,7 +494,7 @@ var RabbitMQClient = class {
505
494
  try {
506
495
  hook?.(info);
507
496
  } catch (error) {
508
- this.logger.debug({ error }, "Observability hook error");
497
+ this.logger.debug("Observability hook error", { error });
509
498
  }
510
499
  }
511
500
  sleep(ms) {
@@ -514,6 +503,8 @@ var RabbitMQClient = class {
514
503
  };
515
504
  // Annotate the CommonJS export names for ESM import in node:
516
505
  0 && (module.exports = {
517
- RabbitMQClient
506
+ RabbitMQClient,
507
+ defaultLogger,
508
+ silentLogger
518
509
  });
519
510
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/logger.ts"],"sourcesContent":["export { RabbitMQClient } from './client.js'\nexport type {\n PublishOptions,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\n RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\n","import amqp from 'amqplib'\nimport type {\n PublishOptions,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\n RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\nimport { createLogger } from './logger.js'\nimport type { Logger } from 'pino'\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/**\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: Logger\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 private consumerTags = new Map<string, string>()\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 closeTimeout: options.closeTimeout ?? DEFAULTS.closeTimeout,\n }\n this.logger = createLogger(options.logger)\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 this.logger.info('Confirm channel created')\n await this.channel.prefetch(this.options.prefetch)\n this.logger.info({ prefetch: this.options.prefetch }, 'Channel prefetch set')\n\n this.connection.on('error', (error: Error) => {\n this.connected = false\n this.logger.error({ error }, 'Connection 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({ error }, 'Channel 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({ error, attempts, maxAttempts }, 'Connection failed after maximum attempts')\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(\n {\n error,\n attempt: attempts,\n maxAttempts: maxAttempts ?? 'unlimited',\n retryDelayMs: this.options.connectionRetryDelay,\n },\n 'Connection attempt failed, retrying...',\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.\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\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 if (!this.assertedExchanges.has(exchange)) {\n await this.channel.assertExchange(exchange, 'topic', { durable: true })\n this.assertedExchanges.add(exchange)\n }\n\n this.channel.publish(exchange, routingKey, content, {\n persistent: true,\n headers: options?.headers,\n correlationId: options?.correlationId,\n messageId: options?.messageId,\n expiration: options?.expiration,\n })\n\n await this.channel.waitForConfirms()\n\n if (attempts > 0) {\n this.logger.info({ exchange, routingKey, messageSize: content.length, attempts }, 'Published message after retries')\n } else {\n this.logger.info({ exchange, routingKey, messageSize: content.length }, 'Published message')\n }\n this.logger.debug({ exchange, routingKey, payload: message }, 'Published message payload')\n\n this.callHook(this.hooks.onPublish, { exchange, routingKey, attempts: attempts + 1 })\n\n return\n } catch (error) {\n attempts++\n this.connected = false\n\n if (attempts >= maxAttempts) {\n this.logger.error(\n { error, exchange, routingKey, attempts, maxAttempts },\n 'Publish failed after max attempts',\n )\n throw new Error(\n `Failed to publish to ${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(\n { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay },\n 'Publish attempt failed, retrying',\n )\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({ inflightCount: this.inflightCount }, 'Waiting for in-flight messages to drain')\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 }\n this.logger.info('Connection closed')\n } catch (error) {\n this.logger.error({ error }, 'Error closing connection')\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({ count: this.subscriptions.length }, 'Re-establishing subscriptions')\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({ error: result.reason, queue }, 'Failed to re-subscribe to queue')\n }\n })\n if (succeeded.length > 0) {\n this.logger.info({ count: succeeded.length, queues: succeeded }, 'Successfully re-subscribed to queues')\n }\n if (failed.length > 0) {\n this.logger.warn({ count: failed.length, queues: failed }, 'Some subscriptions failed to restore')\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 }\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.subscriptions = this.subscriptions.filter((s) => s.queue !== queue)\n this.logger.info({ queue }, 'Unsubscribed from queue')\n }\n\n private async setupSubscription(sub: RabbitMQSubscription): Promise<void> {\n if (!this.channel) {\n throw new Error('Channel not available')\n }\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 this.channel.assertExchange(dlxExchange, 'topic', { durable: true })\n await this.channel.assertQueue(dlqQueue, { durable: true })\n await this.channel.bindQueue(dlqQueue, dlxExchange, dlqRoutingKey)\n\n if (!this.assertedExchanges.has(exchange)) {\n await this.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 this.channel.assertQueue(queue, {\n durable: true,\n deadLetterExchange: dlxExchange,\n deadLetterRoutingKey: dlqRoutingKey,\n arguments: { ...queueArgs, ...options?.queueArguments },\n })\n\n await this.channel.bindQueue(queue, exchange, routingKey)\n const { consumerTag } = await this.channel.consume(\n queue,\n (message) => {\n if (message) {\n this.handleWithRetry(message, handler).catch((error) => {\n this.logger.error({ error }, 'Unhandled error in message handler')\n })\n }\n },\n { noAck: false },\n )\n this.consumerTags.set(queue, consumerTag)\n this.logger.info({ exchange, routingKey, queue }, 'Subscribed to queue')\n }\n\n private async handleWithRetry(\n message: amqp.ConsumeMessage,\n handler: RabbitMQMessageHandler,\n ): Promise<void> {\n // Capture the channel reference so ack/nack always targets the channel\n // that delivered this message, even if a reconnection swaps this.channel.\n const channel = this.channel\n if (!channel) {\n this.logger.warn('Cannot process message -- channel unavailable')\n return\n }\n this.inflightCount++\n try {\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(\n {\n error: parseError,\n exchange,\n routingKey,\n rawContentPreview: rawPreview + (rawContent.length > 100 ? '...' : ''),\n },\n 'Failed to parse message JSON, sending to DLQ',\n )\n channel.nack(message, false, false)\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: 0, reason: 'invalid_json' })\n return\n }\n\n this.logger.debug({ exchange, routingKey, payload: content }, 'Message received, processing')\n\n while (attempts < this.options.maxRetries) {\n try {\n await handler(content)\n const duration = Date.now() - startTime\n this.logger.info(\n { exchange, routingKey, duration, attempts: attempts + 1 },\n 'Message processed successfully',\n )\n channel.ack(message)\n this.callHook(this.hooks.onMessageProcessed, { exchange, routingKey, duration, attempts: attempts + 1 })\n return\n } catch (error) {\n attempts++\n this.logger.error(\n {\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 'Handler failed',\n )\n if (attempts < this.options.maxRetries) {\n await this.sleep(this.options.retryDelay)\n }\n }\n }\n\n const duration = Date.now() - startTime\n this.logger.error(\n { exchange, routingKey, maxRetries: this.options.maxRetries, duration },\n 'Message failed after max retries, sending to DLQ',\n )\n channel.nack(message, false, false)\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: 'max_retries_exhausted' })\n } finally {\n this.inflightCount--\n if (this.inflightCount === 0 && this.drainResolve) {\n this.drainResolve()\n }\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({ error }, 'Health check failed')\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(\n { inflightCount: this.inflightCount },\n 'Close timeout reached with messages still in flight',\n )\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({ error }, 'Observability hook error') }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n }\n}\n","import pino, { type Logger } from 'pino'\n\n/**\n * Creates a child pino logger for the RabbitMQ client.\n *\n * If a parent logger is provided, the child inherits its configuration\n * (level, transports, etc.) for unified log output. Otherwise, a new\n * root logger is created.\n */\nexport const createLogger = (parent?: Logger): Logger => {\n if (parent) {\n return parent.child({ service: 'rabbitmq-client' })\n }\n return pino({ name: 'rabbitmq-client' })\n}\n"],"mappings":"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","names":["pino","amqp","duration"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/logger.ts"],"sourcesContent":["export { RabbitMQClient } from './client.js'\nexport { defaultLogger, silentLogger } from './logger.js'\nexport type {\n ILogger,\n PublishOptions,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\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 RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\nimport { defaultLogger } from './logger.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/**\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 private consumerTags = new Map<string, string>()\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 closeTimeout: options.closeTimeout ?? DEFAULTS.closeTimeout,\n }\n this.logger = 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 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.\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\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 if (!this.assertedExchanges.has(exchange)) {\n await this.channel.assertExchange(exchange, 'topic', { durable: true })\n this.assertedExchanges.add(exchange)\n }\n\n this.channel.publish(exchange, routingKey, content, {\n persistent: true,\n headers: options?.headers,\n correlationId: options?.correlationId,\n messageId: options?.messageId,\n expiration: options?.expiration,\n })\n\n await this.channel.waitForConfirms()\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 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}/${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 }\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 }\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.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 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 this.channel.assertExchange(dlxExchange, 'topic', { durable: true })\n await this.channel.assertQueue(dlqQueue, { durable: true })\n await this.channel.bindQueue(dlqQueue, dlxExchange, dlqRoutingKey)\n\n if (!this.assertedExchanges.has(exchange)) {\n await this.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 this.channel.assertQueue(queue, {\n durable: true,\n deadLetterExchange: dlxExchange,\n deadLetterRoutingKey: dlqRoutingKey,\n arguments: { ...queueArgs, ...options?.queueArguments },\n })\n\n await this.channel.bindQueue(queue, exchange, routingKey)\n const { consumerTag } = await this.channel.consume(\n queue,\n (message) => {\n if (message) {\n this.handleWithRetry(message, handler).catch((error) => {\n this.logger.error('Unhandled error in message handler', { error })\n })\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 private async handleWithRetry(\n message: amqp.ConsumeMessage,\n handler: RabbitMQMessageHandler,\n ): Promise<void> {\n // Capture the channel reference so ack/nack always targets the channel\n // that delivered this message, even if a reconnection swaps this.channel.\n const channel = this.channel\n if (!channel) {\n this.logger.warn('Cannot process message, channel unavailable')\n return\n }\n this.inflightCount++\n try {\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 channel.nack(message, false, false)\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: 0, reason: 'invalid_json' })\n return\n }\n\n this.logger.debug('Message received, processing', { exchange, routingKey, payload: content })\n\n while (attempts < this.options.maxRetries) {\n try {\n await handler(content)\n const duration = Date.now() - startTime\n this.logger.info('Message processed successfully', { exchange, routingKey, duration, attempts: attempts + 1 })\n channel.ack(message)\n this.callHook(this.hooks.onMessageProcessed, { exchange, routingKey, duration, attempts: attempts + 1 })\n return\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 }\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 channel.nack(message, false, false)\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: 'max_retries_exhausted' })\n } finally {\n this.inflightCount--\n if (this.inflightCount === 0 && this.drainResolve) {\n this.drainResolve()\n }\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\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"],"mappings":"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","names":["amqp","duration"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,20 @@
1
- import { Logger } from 'pino';
2
-
1
+ /**
2
+ * Minimal logging contract required by this library.
3
+ *
4
+ * Calls are message-first (`logger.error('message', { context })`), matching
5
+ * `console`, Winston, and Bunyan, so you can pass any of them (or pino)
6
+ * directly without writing a wrapper. Only these four levels are used.
7
+ */
8
+ interface ILogger {
9
+ /** Detailed diagnostic information for developers. */
10
+ debug(message: string, ...args: unknown[]): void;
11
+ /** Routine informational messages about library operations. */
12
+ info(message: string, ...args: unknown[]): void;
13
+ /** Warnings about non-fatal issues. */
14
+ warn(message: string, ...args: unknown[]): void;
15
+ /** Errors indicating an operation failed. */
16
+ error(message: string, ...args: unknown[]): void;
17
+ }
3
18
  /**
4
19
  * Hooks for observability — optional callbacks invoked on key client events.
5
20
  * Use these to wire metrics, tracing, or custom logging.
@@ -56,8 +71,8 @@ interface RabbitMQClientOptions {
56
71
  publishMaxAttempts?: number;
57
72
  /** Channel prefetch count (default: 10) */
58
73
  prefetch?: number;
59
- /** Parent pino logger instance; library creates its own if omitted */
60
- logger?: Logger;
74
+ /** Logger satisfying the ILogger contract; defaults to console warn/error only if omitted */
75
+ logger?: ILogger;
61
76
  /** Timeout in ms to wait for in-flight messages during close (default: 5000) */
62
77
  closeTimeout?: number;
63
78
  /** Observability hooks for metrics and monitoring */
@@ -171,4 +186,20 @@ declare class RabbitMQClient {
171
186
  private sleep;
172
187
  }
173
188
 
174
- export { type PublishOptions, RabbitMQClient, type RabbitMQClientOptions, type RabbitMQHooks, type RabbitMQMessage, type RabbitMQMessageHandler, type RabbitMQSubscription, type SubscribeOptions };
189
+ /**
190
+ * Default logger used when the consumer does not inject one.
191
+ *
192
+ * Forwards `warn` and `error` to the console so connection drops, channel
193
+ * errors, and failed handlers stay visible out of the box, while staying
194
+ * silent for `info`/`debug` so routine operation never pollutes the
195
+ * consumer's output. Pass `silentLogger` for zero output, or any `ILogger`
196
+ * (console, Winston, pino, Bunyan) to take full control.
197
+ */
198
+ declare const defaultLogger: ILogger;
199
+ /**
200
+ * No-op logger that discards every message. Use this to silence the client
201
+ * entirely, including the `warn`/`error` output the default logger emits.
202
+ */
203
+ declare const silentLogger: ILogger;
204
+
205
+ export { type ILogger, type PublishOptions, RabbitMQClient, type RabbitMQClientOptions, type RabbitMQHooks, type RabbitMQMessage, type RabbitMQMessageHandler, type RabbitMQSubscription, type SubscribeOptions, defaultLogger, silentLogger };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,20 @@
1
- import { Logger } from 'pino';
2
-
1
+ /**
2
+ * Minimal logging contract required by this library.
3
+ *
4
+ * Calls are message-first (`logger.error('message', { context })`), matching
5
+ * `console`, Winston, and Bunyan, so you can pass any of them (or pino)
6
+ * directly without writing a wrapper. Only these four levels are used.
7
+ */
8
+ interface ILogger {
9
+ /** Detailed diagnostic information for developers. */
10
+ debug(message: string, ...args: unknown[]): void;
11
+ /** Routine informational messages about library operations. */
12
+ info(message: string, ...args: unknown[]): void;
13
+ /** Warnings about non-fatal issues. */
14
+ warn(message: string, ...args: unknown[]): void;
15
+ /** Errors indicating an operation failed. */
16
+ error(message: string, ...args: unknown[]): void;
17
+ }
3
18
  /**
4
19
  * Hooks for observability — optional callbacks invoked on key client events.
5
20
  * Use these to wire metrics, tracing, or custom logging.
@@ -56,8 +71,8 @@ interface RabbitMQClientOptions {
56
71
  publishMaxAttempts?: number;
57
72
  /** Channel prefetch count (default: 10) */
58
73
  prefetch?: number;
59
- /** Parent pino logger instance; library creates its own if omitted */
60
- logger?: Logger;
74
+ /** Logger satisfying the ILogger contract; defaults to console warn/error only if omitted */
75
+ logger?: ILogger;
61
76
  /** Timeout in ms to wait for in-flight messages during close (default: 5000) */
62
77
  closeTimeout?: number;
63
78
  /** Observability hooks for metrics and monitoring */
@@ -171,4 +186,20 @@ declare class RabbitMQClient {
171
186
  private sleep;
172
187
  }
173
188
 
174
- export { type PublishOptions, RabbitMQClient, type RabbitMQClientOptions, type RabbitMQHooks, type RabbitMQMessage, type RabbitMQMessageHandler, type RabbitMQSubscription, type SubscribeOptions };
189
+ /**
190
+ * Default logger used when the consumer does not inject one.
191
+ *
192
+ * Forwards `warn` and `error` to the console so connection drops, channel
193
+ * errors, and failed handlers stay visible out of the box, while staying
194
+ * silent for `info`/`debug` so routine operation never pollutes the
195
+ * consumer's output. Pass `silentLogger` for zero output, or any `ILogger`
196
+ * (console, Winston, pino, Bunyan) to take full control.
197
+ */
198
+ declare const defaultLogger: ILogger;
199
+ /**
200
+ * No-op logger that discards every message. Use this to silence the client
201
+ * entirely, including the `warn`/`error` output the default logger emits.
202
+ */
203
+ declare const silentLogger: ILogger;
204
+
205
+ export { type ILogger, type PublishOptions, RabbitMQClient, type RabbitMQClientOptions, type RabbitMQHooks, type RabbitMQMessage, type RabbitMQMessageHandler, type RabbitMQSubscription, type SubscribeOptions, defaultLogger, silentLogger };
package/dist/index.js CHANGED
@@ -2,12 +2,23 @@
2
2
  import amqp from "amqplib";
3
3
 
4
4
  // src/logger.ts
5
- import pino from "pino";
6
- var createLogger = (parent) => {
7
- if (parent) {
8
- return parent.child({ service: "rabbitmq-client" });
5
+ var defaultLogger = {
6
+ debug: () => {
7
+ },
8
+ info: () => {
9
+ },
10
+ warn: (message, ...args) => console.warn("[rabbitmq-client]", message, ...args),
11
+ error: (message, ...args) => console.error("[rabbitmq-client]", message, ...args)
12
+ };
13
+ var silentLogger = {
14
+ debug: () => {
15
+ },
16
+ info: () => {
17
+ },
18
+ warn: () => {
19
+ },
20
+ error: () => {
9
21
  }
10
- return pino({ name: "rabbitmq-client" });
11
22
  };
12
23
 
13
24
  // src/client.ts
@@ -47,7 +58,7 @@ var RabbitMQClient = class {
47
58
  prefetch: options.prefetch ?? DEFAULTS.prefetch,
48
59
  closeTimeout: options.closeTimeout ?? DEFAULTS.closeTimeout
49
60
  };
50
- this.logger = createLogger(options.logger);
61
+ this.logger = options.logger ?? defaultLogger;
51
62
  this.hooks = options.hooks ?? {};
52
63
  }
53
64
  /**
@@ -81,10 +92,10 @@ var RabbitMQClient = class {
81
92
  this.channel = await this.connection.createConfirmChannel();
82
93
  this.logger.info("Confirm channel created");
83
94
  await this.channel.prefetch(this.options.prefetch);
84
- this.logger.info({ prefetch: this.options.prefetch }, "Channel prefetch set");
95
+ this.logger.info("Channel prefetch set", { prefetch: this.options.prefetch });
85
96
  this.connection.on("error", (error) => {
86
97
  this.connected = false;
87
- this.logger.error({ error }, "Connection error");
98
+ this.logger.error("Connection error", { error });
88
99
  });
89
100
  this.connection.on("close", () => {
90
101
  this.connected = false;
@@ -92,7 +103,7 @@ var RabbitMQClient = class {
92
103
  this.reconnectWithRetry();
93
104
  });
94
105
  this.channel.on("error", (error) => {
95
- this.logger.error({ error }, "Channel error");
106
+ this.logger.error("Channel error", { error });
96
107
  this.handleChannelFailure();
97
108
  });
98
109
  this.channel.on("close", () => {
@@ -105,20 +116,17 @@ var RabbitMQClient = class {
105
116
  attempts++;
106
117
  this.connected = false;
107
118
  if (maxAttempts !== void 0 && attempts >= maxAttempts) {
108
- this.logger.error({ error, attempts, maxAttempts }, "Connection failed after maximum attempts");
119
+ this.logger.error("Connection failed after maximum attempts", { error, attempts, maxAttempts });
109
120
  throw new Error(
110
121
  `Failed to connect to RabbitMQ after ${attempts} attempts. Check RABBITMQ_URL configuration and RabbitMQ server availability.`
111
122
  );
112
123
  }
113
- this.logger.warn(
114
- {
115
- error,
116
- attempt: attempts,
117
- maxAttempts: maxAttempts ?? "unlimited",
118
- retryDelayMs: this.options.connectionRetryDelay
119
- },
120
- "Connection attempt failed, retrying..."
121
- );
124
+ this.logger.warn("Connection attempt failed, retrying...", {
125
+ error,
126
+ attempt: attempts,
127
+ maxAttempts: maxAttempts ?? "unlimited",
128
+ retryDelayMs: this.options.connectionRetryDelay
129
+ });
122
130
  await this.sleep(this.options.connectionRetryDelay);
123
131
  }
124
132
  }
@@ -166,21 +174,18 @@ var RabbitMQClient = class {
166
174
  });
167
175
  await this.channel.waitForConfirms();
168
176
  if (attempts > 0) {
169
- this.logger.info({ exchange, routingKey, messageSize: content.length, attempts }, "Published message after retries");
177
+ this.logger.info("Published message after retries", { exchange, routingKey, messageSize: content.length, attempts });
170
178
  } else {
171
- this.logger.info({ exchange, routingKey, messageSize: content.length }, "Published message");
179
+ this.logger.info("Published message", { exchange, routingKey, messageSize: content.length });
172
180
  }
173
- this.logger.debug({ exchange, routingKey, payload: message }, "Published message payload");
181
+ this.logger.debug("Published message payload", { exchange, routingKey, payload: message });
174
182
  this.callHook(this.hooks.onPublish, { exchange, routingKey, attempts: attempts + 1 });
175
183
  return;
176
184
  } catch (error) {
177
185
  attempts++;
178
186
  this.connected = false;
179
187
  if (attempts >= maxAttempts) {
180
- this.logger.error(
181
- { error, exchange, routingKey, attempts, maxAttempts },
182
- "Publish failed after max attempts"
183
- );
188
+ this.logger.error("Publish failed after max attempts", { error, exchange, routingKey, attempts, maxAttempts });
184
189
  throw new Error(
185
190
  `Failed to publish to ${exchange}/${routingKey} after ${attempts} attempts: ${error instanceof Error ? error.message : String(error)}`
186
191
  );
@@ -189,10 +194,7 @@ var RabbitMQClient = class {
189
194
  baseDelay * Math.pow(2, attempts - 1),
190
195
  MAX_PUBLISH_RETRY_DELAY_MS
191
196
  );
192
- this.logger.warn(
193
- { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay },
194
- "Publish attempt failed, retrying"
195
- );
197
+ this.logger.warn("Publish attempt failed, retrying", { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay });
196
198
  await this.sleep(retryDelay);
197
199
  }
198
200
  }
@@ -206,7 +208,7 @@ var RabbitMQClient = class {
206
208
  async close(clearSubscriptions = true) {
207
209
  try {
208
210
  if (this.inflightCount > 0) {
209
- this.logger.info({ inflightCount: this.inflightCount }, "Waiting for in-flight messages to drain");
211
+ this.logger.info("Waiting for in-flight messages to drain", { inflightCount: this.inflightCount });
210
212
  await this.waitForDrain(this.options.closeTimeout);
211
213
  }
212
214
  await this.channel?.close();
@@ -223,7 +225,7 @@ var RabbitMQClient = class {
223
225
  }
224
226
  this.logger.info("Connection closed");
225
227
  } catch (error) {
226
- this.logger.error({ error }, "Error closing connection");
228
+ this.logger.error("Error closing connection", { error });
227
229
  throw error;
228
230
  }
229
231
  }
@@ -249,7 +251,7 @@ var RabbitMQClient = class {
249
251
  if (this.subscriptions.length === 0) {
250
252
  return;
251
253
  }
252
- this.logger.info({ count: this.subscriptions.length }, "Re-establishing subscriptions");
254
+ this.logger.info("Re-establishing subscriptions", { count: this.subscriptions.length });
253
255
  const subs = [...this.subscriptions];
254
256
  const results = await Promise.allSettled(
255
257
  subs.map(async (sub) => {
@@ -265,14 +267,14 @@ var RabbitMQClient = class {
265
267
  } else {
266
268
  const queue = subs[index].queue;
267
269
  failed.push(queue);
268
- this.logger.error({ error: result.reason, queue }, "Failed to re-subscribe to queue");
270
+ this.logger.error("Failed to re-subscribe to queue", { error: result.reason, queue });
269
271
  }
270
272
  });
271
273
  if (succeeded.length > 0) {
272
- this.logger.info({ count: succeeded.length, queues: succeeded }, "Successfully re-subscribed to queues");
274
+ this.logger.info("Successfully re-subscribed to queues", { count: succeeded.length, queues: succeeded });
273
275
  }
274
276
  if (failed.length > 0) {
275
- this.logger.warn({ count: failed.length, queues: failed }, "Some subscriptions failed to restore");
277
+ this.logger.warn("Some subscriptions failed to restore", { count: failed.length, queues: failed });
276
278
  }
277
279
  this.callHook(this.hooks.onReconnect, { subscriptionsRestored: succeeded.length, subscriptionsFailed: failed.length });
278
280
  }
@@ -305,7 +307,7 @@ var RabbitMQClient = class {
305
307
  }
306
308
  this.consumerTags.delete(queue);
307
309
  this.subscriptions = this.subscriptions.filter((s) => s.queue !== queue);
308
- this.logger.info({ queue }, "Unsubscribed from queue");
310
+ this.logger.info("Unsubscribed from queue", { queue });
309
311
  }
310
312
  async setupSubscription(sub) {
311
313
  if (!this.channel) {
@@ -342,19 +344,19 @@ var RabbitMQClient = class {
342
344
  (message) => {
343
345
  if (message) {
344
346
  this.handleWithRetry(message, handler).catch((error) => {
345
- this.logger.error({ error }, "Unhandled error in message handler");
347
+ this.logger.error("Unhandled error in message handler", { error });
346
348
  });
347
349
  }
348
350
  },
349
351
  { noAck: false }
350
352
  );
351
353
  this.consumerTags.set(queue, consumerTag);
352
- this.logger.info({ exchange, routingKey, queue }, "Subscribed to queue");
354
+ this.logger.info("Subscribed to queue", { exchange, routingKey, queue });
353
355
  }
354
356
  async handleWithRetry(message, handler) {
355
357
  const channel = this.channel;
356
358
  if (!channel) {
357
- this.logger.warn("Cannot process message -- channel unavailable");
359
+ this.logger.warn("Cannot process message, channel unavailable");
358
360
  return;
359
361
  }
360
362
  this.inflightCount++;
@@ -369,54 +371,42 @@ var RabbitMQClient = class {
369
371
  } catch (parseError) {
370
372
  const rawContent = message.content.toString();
371
373
  const rawPreview = rawContent.substring(0, 100);
372
- this.logger.error(
373
- {
374
- error: parseError,
375
- exchange,
376
- routingKey,
377
- rawContentPreview: rawPreview + (rawContent.length > 100 ? "..." : "")
378
- },
379
- "Failed to parse message JSON, sending to DLQ"
380
- );
374
+ this.logger.error("Failed to parse message JSON, sending to DLQ", {
375
+ error: parseError,
376
+ exchange,
377
+ routingKey,
378
+ rawContentPreview: rawPreview + (rawContent.length > 100 ? "..." : "")
379
+ });
381
380
  channel.nack(message, false, false);
382
381
  this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: 0, reason: "invalid_json" });
383
382
  return;
384
383
  }
385
- this.logger.debug({ exchange, routingKey, payload: content }, "Message received, processing");
384
+ this.logger.debug("Message received, processing", { exchange, routingKey, payload: content });
386
385
  while (attempts < this.options.maxRetries) {
387
386
  try {
388
387
  await handler(content);
389
388
  const duration2 = Date.now() - startTime;
390
- this.logger.info(
391
- { exchange, routingKey, duration: duration2, attempts: attempts + 1 },
392
- "Message processed successfully"
393
- );
389
+ this.logger.info("Message processed successfully", { exchange, routingKey, duration: duration2, attempts: attempts + 1 });
394
390
  channel.ack(message);
395
391
  this.callHook(this.hooks.onMessageProcessed, { exchange, routingKey, duration: duration2, attempts: attempts + 1 });
396
392
  return;
397
393
  } catch (error) {
398
394
  attempts++;
399
- this.logger.error(
400
- {
401
- error: error instanceof Error ? error.message : error,
402
- stack: error instanceof Error ? error.stack : void 0,
403
- exchange,
404
- routingKey,
405
- attempt: attempts,
406
- maxRetries: this.options.maxRetries
407
- },
408
- "Handler failed"
409
- );
395
+ this.logger.error("Handler failed", {
396
+ error: error instanceof Error ? error.message : error,
397
+ stack: error instanceof Error ? error.stack : void 0,
398
+ exchange,
399
+ routingKey,
400
+ attempt: attempts,
401
+ maxRetries: this.options.maxRetries
402
+ });
410
403
  if (attempts < this.options.maxRetries) {
411
404
  await this.sleep(this.options.retryDelay);
412
405
  }
413
406
  }
414
407
  }
415
408
  const duration = Date.now() - startTime;
416
- this.logger.error(
417
- { exchange, routingKey, maxRetries: this.options.maxRetries, duration },
418
- "Message failed after max retries, sending to DLQ"
419
- );
409
+ this.logger.error("Message failed after max retries, sending to DLQ", { exchange, routingKey, maxRetries: this.options.maxRetries, duration });
420
410
  channel.nack(message, false, false);
421
411
  this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: "max_retries_exhausted" });
422
412
  } finally {
@@ -442,7 +432,7 @@ var RabbitMQClient = class {
442
432
  await this.channel.deleteQueue(queue);
443
433
  return true;
444
434
  } catch (error) {
445
- this.logger.warn({ error }, "Health check failed");
435
+ this.logger.warn("Health check failed", { error });
446
436
  return false;
447
437
  }
448
438
  }
@@ -450,10 +440,7 @@ var RabbitMQClient = class {
450
440
  if (this.inflightCount === 0) return Promise.resolve();
451
441
  return new Promise((resolve) => {
452
442
  const timer = setTimeout(() => {
453
- this.logger.warn(
454
- { inflightCount: this.inflightCount },
455
- "Close timeout reached with messages still in flight"
456
- );
443
+ this.logger.warn("Close timeout reached with messages still in flight", { inflightCount: this.inflightCount });
457
444
  this.drainResolve = null;
458
445
  resolve();
459
446
  }, timeout);
@@ -469,7 +456,7 @@ var RabbitMQClient = class {
469
456
  try {
470
457
  hook?.(info);
471
458
  } catch (error) {
472
- this.logger.debug({ error }, "Observability hook error");
459
+ this.logger.debug("Observability hook error", { error });
473
460
  }
474
461
  }
475
462
  sleep(ms) {
@@ -477,6 +464,8 @@ var RabbitMQClient = class {
477
464
  }
478
465
  };
479
466
  export {
480
- RabbitMQClient
467
+ RabbitMQClient,
468
+ defaultLogger,
469
+ silentLogger
481
470
  };
482
471
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/logger.ts"],"sourcesContent":["import amqp from 'amqplib'\nimport type {\n PublishOptions,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\n RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\nimport { createLogger } from './logger.js'\nimport type { Logger } from 'pino'\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/**\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: Logger\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 private consumerTags = new Map<string, string>()\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 closeTimeout: options.closeTimeout ?? DEFAULTS.closeTimeout,\n }\n this.logger = createLogger(options.logger)\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 this.logger.info('Confirm channel created')\n await this.channel.prefetch(this.options.prefetch)\n this.logger.info({ prefetch: this.options.prefetch }, 'Channel prefetch set')\n\n this.connection.on('error', (error: Error) => {\n this.connected = false\n this.logger.error({ error }, 'Connection 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({ error }, 'Channel 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({ error, attempts, maxAttempts }, 'Connection failed after maximum attempts')\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(\n {\n error,\n attempt: attempts,\n maxAttempts: maxAttempts ?? 'unlimited',\n retryDelayMs: this.options.connectionRetryDelay,\n },\n 'Connection attempt failed, retrying...',\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.\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\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 if (!this.assertedExchanges.has(exchange)) {\n await this.channel.assertExchange(exchange, 'topic', { durable: true })\n this.assertedExchanges.add(exchange)\n }\n\n this.channel.publish(exchange, routingKey, content, {\n persistent: true,\n headers: options?.headers,\n correlationId: options?.correlationId,\n messageId: options?.messageId,\n expiration: options?.expiration,\n })\n\n await this.channel.waitForConfirms()\n\n if (attempts > 0) {\n this.logger.info({ exchange, routingKey, messageSize: content.length, attempts }, 'Published message after retries')\n } else {\n this.logger.info({ exchange, routingKey, messageSize: content.length }, 'Published message')\n }\n this.logger.debug({ exchange, routingKey, payload: message }, 'Published message payload')\n\n this.callHook(this.hooks.onPublish, { exchange, routingKey, attempts: attempts + 1 })\n\n return\n } catch (error) {\n attempts++\n this.connected = false\n\n if (attempts >= maxAttempts) {\n this.logger.error(\n { error, exchange, routingKey, attempts, maxAttempts },\n 'Publish failed after max attempts',\n )\n throw new Error(\n `Failed to publish to ${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(\n { error, exchange, routingKey, attempt: attempts, maxAttempts, retryDelayMs: retryDelay },\n 'Publish attempt failed, retrying',\n )\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({ inflightCount: this.inflightCount }, 'Waiting for in-flight messages to drain')\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 }\n this.logger.info('Connection closed')\n } catch (error) {\n this.logger.error({ error }, 'Error closing connection')\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({ count: this.subscriptions.length }, 'Re-establishing subscriptions')\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({ error: result.reason, queue }, 'Failed to re-subscribe to queue')\n }\n })\n if (succeeded.length > 0) {\n this.logger.info({ count: succeeded.length, queues: succeeded }, 'Successfully re-subscribed to queues')\n }\n if (failed.length > 0) {\n this.logger.warn({ count: failed.length, queues: failed }, 'Some subscriptions failed to restore')\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 }\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.subscriptions = this.subscriptions.filter((s) => s.queue !== queue)\n this.logger.info({ queue }, 'Unsubscribed from queue')\n }\n\n private async setupSubscription(sub: RabbitMQSubscription): Promise<void> {\n if (!this.channel) {\n throw new Error('Channel not available')\n }\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 this.channel.assertExchange(dlxExchange, 'topic', { durable: true })\n await this.channel.assertQueue(dlqQueue, { durable: true })\n await this.channel.bindQueue(dlqQueue, dlxExchange, dlqRoutingKey)\n\n if (!this.assertedExchanges.has(exchange)) {\n await this.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 this.channel.assertQueue(queue, {\n durable: true,\n deadLetterExchange: dlxExchange,\n deadLetterRoutingKey: dlqRoutingKey,\n arguments: { ...queueArgs, ...options?.queueArguments },\n })\n\n await this.channel.bindQueue(queue, exchange, routingKey)\n const { consumerTag } = await this.channel.consume(\n queue,\n (message) => {\n if (message) {\n this.handleWithRetry(message, handler).catch((error) => {\n this.logger.error({ error }, 'Unhandled error in message handler')\n })\n }\n },\n { noAck: false },\n )\n this.consumerTags.set(queue, consumerTag)\n this.logger.info({ exchange, routingKey, queue }, 'Subscribed to queue')\n }\n\n private async handleWithRetry(\n message: amqp.ConsumeMessage,\n handler: RabbitMQMessageHandler,\n ): Promise<void> {\n // Capture the channel reference so ack/nack always targets the channel\n // that delivered this message, even if a reconnection swaps this.channel.\n const channel = this.channel\n if (!channel) {\n this.logger.warn('Cannot process message -- channel unavailable')\n return\n }\n this.inflightCount++\n try {\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(\n {\n error: parseError,\n exchange,\n routingKey,\n rawContentPreview: rawPreview + (rawContent.length > 100 ? '...' : ''),\n },\n 'Failed to parse message JSON, sending to DLQ',\n )\n channel.nack(message, false, false)\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: 0, reason: 'invalid_json' })\n return\n }\n\n this.logger.debug({ exchange, routingKey, payload: content }, 'Message received, processing')\n\n while (attempts < this.options.maxRetries) {\n try {\n await handler(content)\n const duration = Date.now() - startTime\n this.logger.info(\n { exchange, routingKey, duration, attempts: attempts + 1 },\n 'Message processed successfully',\n )\n channel.ack(message)\n this.callHook(this.hooks.onMessageProcessed, { exchange, routingKey, duration, attempts: attempts + 1 })\n return\n } catch (error) {\n attempts++\n this.logger.error(\n {\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 'Handler failed',\n )\n if (attempts < this.options.maxRetries) {\n await this.sleep(this.options.retryDelay)\n }\n }\n }\n\n const duration = Date.now() - startTime\n this.logger.error(\n { exchange, routingKey, maxRetries: this.options.maxRetries, duration },\n 'Message failed after max retries, sending to DLQ',\n )\n channel.nack(message, false, false)\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: 'max_retries_exhausted' })\n } finally {\n this.inflightCount--\n if (this.inflightCount === 0 && this.drainResolve) {\n this.drainResolve()\n }\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({ error }, 'Health check failed')\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(\n { inflightCount: this.inflightCount },\n 'Close timeout reached with messages still in flight',\n )\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({ error }, 'Observability hook error') }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n }\n}\n","import pino, { type Logger } from 'pino'\n\n/**\n * Creates a child pino logger for the RabbitMQ client.\n *\n * If a parent logger is provided, the child inherits its configuration\n * (level, transports, etc.) for unified log output. Otherwise, a new\n * root logger is created.\n */\nexport const createLogger = (parent?: Logger): Logger => {\n if (parent) {\n return parent.child({ service: 'rabbitmq-client' })\n }\n return pino({ name: 'rabbitmq-client' })\n}\n"],"mappings":"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","names":["duration"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/logger.ts"],"sourcesContent":["import amqp from 'amqplib'\nimport type {\n ILogger,\n PublishOptions,\n RabbitMQClientOptions,\n RabbitMQHooks,\n RabbitMQMessage,\n RabbitMQMessageHandler,\n RabbitMQSubscription,\n SubscribeOptions,\n} from './types.js'\nimport { defaultLogger } from './logger.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/**\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 private consumerTags = new Map<string, string>()\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 closeTimeout: options.closeTimeout ?? DEFAULTS.closeTimeout,\n }\n this.logger = 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 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.\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\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 if (!this.assertedExchanges.has(exchange)) {\n await this.channel.assertExchange(exchange, 'topic', { durable: true })\n this.assertedExchanges.add(exchange)\n }\n\n this.channel.publish(exchange, routingKey, content, {\n persistent: true,\n headers: options?.headers,\n correlationId: options?.correlationId,\n messageId: options?.messageId,\n expiration: options?.expiration,\n })\n\n await this.channel.waitForConfirms()\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 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}/${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 }\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 }\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.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 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 this.channel.assertExchange(dlxExchange, 'topic', { durable: true })\n await this.channel.assertQueue(dlqQueue, { durable: true })\n await this.channel.bindQueue(dlqQueue, dlxExchange, dlqRoutingKey)\n\n if (!this.assertedExchanges.has(exchange)) {\n await this.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 this.channel.assertQueue(queue, {\n durable: true,\n deadLetterExchange: dlxExchange,\n deadLetterRoutingKey: dlqRoutingKey,\n arguments: { ...queueArgs, ...options?.queueArguments },\n })\n\n await this.channel.bindQueue(queue, exchange, routingKey)\n const { consumerTag } = await this.channel.consume(\n queue,\n (message) => {\n if (message) {\n this.handleWithRetry(message, handler).catch((error) => {\n this.logger.error('Unhandled error in message handler', { error })\n })\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 private async handleWithRetry(\n message: amqp.ConsumeMessage,\n handler: RabbitMQMessageHandler,\n ): Promise<void> {\n // Capture the channel reference so ack/nack always targets the channel\n // that delivered this message, even if a reconnection swaps this.channel.\n const channel = this.channel\n if (!channel) {\n this.logger.warn('Cannot process message, channel unavailable')\n return\n }\n this.inflightCount++\n try {\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 channel.nack(message, false, false)\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration: 0, reason: 'invalid_json' })\n return\n }\n\n this.logger.debug('Message received, processing', { exchange, routingKey, payload: content })\n\n while (attempts < this.options.maxRetries) {\n try {\n await handler(content)\n const duration = Date.now() - startTime\n this.logger.info('Message processed successfully', { exchange, routingKey, duration, attempts: attempts + 1 })\n channel.ack(message)\n this.callHook(this.hooks.onMessageProcessed, { exchange, routingKey, duration, attempts: attempts + 1 })\n return\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 }\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 channel.nack(message, false, false)\n this.callHook(this.hooks.onMessageDlq, { exchange, routingKey, duration, reason: 'max_retries_exhausted' })\n } finally {\n this.inflightCount--\n if (this.inflightCount === 0 && this.drainResolve) {\n this.drainResolve()\n }\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\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"],"mappings":"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","names":["duration"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linagora/rabbitmq-client",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Production-grade RabbitMQ client with confirm channels, DLQ infrastructure, auto-reconnection, exponential backoff, graceful shutdown, and observability hooks",
5
5
  "type": "module",
6
6
  "exports": {
@@ -39,13 +39,12 @@
39
39
  "prepublishOnly": "npm run build"
40
40
  },
41
41
  "dependencies": {
42
- "amqplib": "^0.10.7",
43
- "pino": "^9.6.0"
42
+ "amqplib": "^0.10.7"
44
43
  },
45
44
  "devDependencies": {
46
45
  "@types/amqplib": "^0.10.7",
47
- "typescript": "^5.7.3",
48
46
  "tsup": "^8.4.0",
47
+ "typescript": "^5.7.3",
49
48
  "vitest": "^3.1.1"
50
49
  },
51
50
  "engines": {