@znight/messaging 0.5.0 → 0.6.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
@@ -399,8 +399,9 @@ quorum queues.
399
399
  After a connection loss the driver reconnects (exponential backoff) and resumes the consumers;
400
400
  messages in flight on the lost connection are redelivered by RabbitMQ. A consumer channel closed by a
401
401
  channel error, or a consumer cancelled by the broker (queue deleted, node down: the queues are then
402
- declared again with `assertTopology`), is resumed the same way. The recovery retries until every
403
- consumer consumes again, including when the connection is lost again while it runs.
402
+ declared again with `assertTopology`), is resumed the same way; so is a consumer whose queue is gone
403
+ when it resumes (`404 NOT_FOUND`: declared again before the next attempt). The recovery retries until
404
+ every consumer consumes again, including when the connection is lost again while it runs.
404
405
 
405
406
  ### Amazon SQS
406
407
 
package/dist/rabbitmq.mjs CHANGED
@@ -527,6 +527,7 @@ var RabbitMQDriver = class {
527
527
  subscription.consumerTag = consumerTag;
528
528
  } catch (error) {
529
529
  subscription.channel = void 0;
530
+ if (isNotFound(error)) subscription.redeclare = true;
530
531
  await closeQuietly(channel);
531
532
  throw brokerUnavailableError(`RabbitMQ: cannot consume "${subscription.queue}": ${errorMessage(error)}`, error);
532
533
  }
@@ -676,6 +677,10 @@ function topologyError(queue, error) {
676
677
  const hints = message.includes("PRECONDITION_FAILED") ? ["The queue exists with different arguments: align queueType/queueArguments, or set assertTopology: false"] : [];
677
678
  return consumerError(`RabbitMQ: cannot declare the queue "${queue}": ${message}`, hints);
678
679
  }
680
+ /** A channel error `404 NOT_FOUND` (amqplib sets the reply code as `code`). */
681
+ function isNotFound(error) {
682
+ return typeof error === "object" && error !== null && error.code === 404;
683
+ }
679
684
  async function closeQuietly(channel) {
680
685
  try {
681
686
  await channel.close();
@@ -1 +1 @@
1
- {"version":3,"file":"rabbitmq.mjs","names":["amqpConnect"],"sources":["../src/drivers/rabbitmq.ts"],"sourcesContent":["/**\n * `@znight/messaging/rabbitmq`: RabbitMQ driver built on [amqplib](https://amqp-node.github.io/amqplib/)\n * 2.x (optional peer dependency).\n *\n * - Publishes through a confirm channel (`publish()` resolves on broker confirmation) with\n * `mandatory` routing: publishing to a queue that does not exist fails instead of dropping the message.\n * - One channel per consumer, `prefetch = concurrency`, manual acknowledgements.\n * - Retries are republished copies carrying `x-znight-attempt`: immediately to the queue, or through a\n * delay queue (`<queue>.delay.<ms>`, queue-level TTL dead-lettering back into the queue: no\n * head-of-line blocking between delays). The original is acknowledged once the copy is confirmed.\n * - Delayed publications go through a fixed set of 32 delay levels shared by every queue\n * (`znight.delays.<n>`: a topic exchange and a queue with a TTL of 2^n ms each). The delay is\n * written in binary in the routing key: the message waits in the level of each set bit, then the last\n * level dead-letters it to the default exchange, which routes it to its queue (`CC` header).\n * - Attempts = `x-znight-attempt` + broker redeliveries (`x-delivery-count` of quorum queues, or the\n * `redelivered` flag of classic queues).\n * - Dead letters are published (confirmed) to `<queue>.dlq`, then the original is acknowledged; without\n * dead-letter queue the message is rejected (`nack` without requeue), so a queue DLX policy applies.\n * - Declares the queues it uses (`assertTopology`, default) or verifies they exist. On RabbitMQ >= 4,\n * quorum queues are declared with `x-delivery-limit: -1`: Znight counts the attempts itself, the\n * broker's default limit (20 deliveries) would drop messages released repeatedly.\n * - Recovers from connection losses (reconnection with exponential backoff), closed consumer channels\n * and consumers cancelled by the broker: every subscription is resumed, with backoff, until it\n * consumes again.\n */\nimport type {\n Channel,\n ChannelModel,\n ConfirmChannel,\n ConsumeMessage,\n Message,\n MessageProperties,\n Options,\n SocketOptions,\n} from 'amqplib'\nimport type {\n DeadLetterRequest,\n DeliveryHandler,\n DriverCapabilities,\n DriverContext,\n DriverDelivery,\n MessagingDriver,\n RetryRequest,\n SubscribeOptions,\n Subscription,\n} from '../driver.ts'\nimport type { OutboundMessage } from '../message.ts'\n\nimport { connect as amqpConnect } from 'amqplib'\n\nimport { brokerUnavailableError, consumerError } from '../errors.ts'\nimport { attemptFromHeaders, MessagingHeaders } from '../headers.ts'\nimport { errorMessage, sleep, truncate } from '../internal/async.ts'\nimport { durationOption, MAX_ERROR_HEADER_LENGTH, readHeader, toHeaderRecord } from './shared.ts'\n\n/** Reconnection settings. */\nexport interface RabbitMQReconnectOptions {\n /** First delay before reconnecting. Default: 100 ms. */\n readonly initialDelayMs?: number\n /** Longest delay between attempts. Default: 30 000 ms. */\n readonly maxDelayMs?: number\n}\n\n/** Options of {@link rabbitmq}. */\nexport interface RabbitMQDriverOptions {\n /** `amqp://user:pass@host:5672/vhost` or amqplib connection options. */\n readonly url: string | Options.Connect\n /** Socket / TLS options passed to amqplib. */\n readonly socketOptions?: SocketOptions\n /**\n * Declare the queues used by consumers (consumed, dead-letter and delay queues) and by delayed\n * publications. When `false`, consumed and dead-letter queues must exist (verified at subscribe).\n * Default: `true`.\n */\n readonly assertTopology?: boolean\n /** Type of the declared queues. Default: `quorum` (replicated, native `x-delivery-count`). */\n readonly queueType?: 'quorum' | 'classic'\n /**\n * Extra arguments of declared consumed and dead-letter queues (`x-max-length`...). Quorum queues get\n * `x-delivery-limit: -1` on RabbitMQ >= 4 unless this sets `x-delivery-limit`.\n */\n readonly queueArguments?: Readonly<Record<string, unknown>>\n /** Longest retry delay waited in-process (holding the message). Default: 5000 ms. */\n readonly maxInProcessDelayMs?: number\n /** Name of the retry delay queue of `queue` for `delayMs`. Default: `${queue}.delay.${delayMs}`. */\n readonly delayQueueName?: (queue: string, delayMs: number) => string\n /**\n * Prefix of the delay levels of delayed publications: `<prefix>.0` to `<prefix>.31` (one topic\n * exchange and one queue each, shared by every queue of the virtual host). Default: `znight.delays`.\n */\n readonly delayLevelPrefix?: string\n /** Default dead-letter queue name. Default: `${queue}.dlq`. */\n readonly deadLetterName?: (queue: string) => string\n /** Publish with `mandatory`: unroutable messages make `publish()` fail. Default: `true`. */\n readonly mandatory?: boolean\n /** Persistent messages (delivery mode 2). Default: `true`. */\n readonly persistent?: boolean\n /** Use the routing key as message type when the message has none (topic exchanges). Default: `false`. */\n readonly typeFromRoutingKey?: boolean\n /** Reconnect after a connection loss. `false` disables. Default: enabled. */\n readonly reconnect?: RabbitMQReconnectOptions | false\n /** Connection name shown in the management UI. Default: `znight`. */\n readonly connectionName?: string\n}\n\n/** Largest TTL RabbitMQ accepts (2^32 - 1 ms). */\nconst MAX_TTL_MS = 4_294_967_295\nconst DEFAULT_IN_PROCESS_DELAY_MS = 5000\nconst DEFAULT_DELAY_LEVEL_PREFIX = 'znight.delays'\n/** Delay levels of delayed publications: level n waits 2^n ms, all of them together `MAX_TTL_MS`. */\nconst DELAY_LEVELS = 32\n/** First word of the routing key of a delayed publication, followed by one bit per level (highest first). */\nconst DELAY_ROUTING_WORD = 'znight-delay'\n/**\n * Headers never copied to republished messages (retries, dead letters): the ones managed by the broker,\n * and the sender-selected destinations (`CC`, `BCC`), which would route the copy to other queues.\n */\nconst UNCOPIED_HEADERS = new Set([\n 'x-delivery-count',\n 'x-death',\n 'x-first-death-exchange',\n 'x-first-death-queue',\n 'x-first-death-reason',\n 'x-last-death-exchange',\n 'x-last-death-queue',\n 'x-last-death-reason',\n 'CC',\n 'BCC',\n])\n\n/** Creates a {@link RabbitMQDriver}. */\nexport function rabbitmq(options: RabbitMQDriverOptions): RabbitMQDriver {\n return new RabbitMQDriver(options)\n}\n\ninterface PendingConfirm {\n returned: boolean\n}\n\ninterface ConsumerSubscription {\n readonly queue: string\n readonly handler: DeliveryHandler\n readonly options: SubscribeOptions\n channel: Channel | undefined\n consumerTag: string | undefined\n active: boolean\n /** In-progress (re)subscription. */\n consuming: Promise<void> | undefined\n /** The broker cancelled the consumer (queue deleted...): declare the queues again before consuming. */\n redeclare: boolean\n}\n\n/** RabbitMQ {@link MessagingDriver}. See the module documentation. */\nexport class RabbitMQDriver implements MessagingDriver {\n readonly name = 'rabbitmq'\n readonly capabilities: DriverCapabilities\n readonly #options: RabbitMQDriverOptions\n readonly #queueType: 'quorum' | 'classic'\n readonly #assertTopology: boolean\n readonly #subscriptions = new Set<ConsumerSubscription>()\n /** Retry delay queues already declared, by name. */\n readonly #delayQueues = new Map<string, Promise<void>>()\n /** Publications awaiting their confirmation, by destination and message id (to detect returned messages). */\n readonly #pending = new Map<string, PendingConfirm[]>()\n readonly #abort = new AbortController()\n #logger: DriverContext['logger'] = { debug() {}, info() {}, warn() {}, error() {} }\n #model: ChannelModel | undefined\n #publisher: Promise<ConfirmChannel> | undefined\n /** Declaration of the delay levels on the current connection. */\n #delayLevels: Promise<void> | undefined\n #closing = false\n /** Running recovery loop (reconnection and resubscriptions). */\n #recovering: Promise<void> | undefined\n\n constructor(options: RabbitMQDriverOptions) {\n if (options === null || typeof options !== 'object' || options.url === undefined) {\n throw new TypeError('[znight] rabbitmq(): the \"url\" option is required')\n }\n if (\n options.delayLevelPrefix !== undefined &&\n (typeof options.delayLevelPrefix !== 'string' || options.delayLevelPrefix === '')\n ) {\n throw new TypeError('[znight] rabbitmq(): \"delayLevelPrefix\" must be a non-empty string')\n }\n this.#options = options\n this.#queueType = options.queueType ?? 'quorum'\n this.#assertTopology = options.assertTopology ?? true\n this.capabilities = Object.freeze({\n nativeDelay: true,\n maxNativeDelayMs: MAX_TTL_MS,\n maxInProcessDelayMs: durationOption(\n options.maxInProcessDelayMs,\n DEFAULT_IN_PROCESS_DELAY_MS,\n 'rabbitmq maxInProcessDelayMs',\n ),\n deliveryCount: this.#queueType === 'quorum' ? 'native' : 'header',\n maxPublishDelayMs: MAX_TTL_MS,\n })\n }\n\n // -----------------------------------------------------------------------------------------------\n // Connection\n // -----------------------------------------------------------------------------------------------\n\n async connect(context: DriverContext): Promise<void> {\n this.#logger = context.logger\n if (this.#model !== undefined) return\n if (this.#closing) throw brokerUnavailableError('RabbitMQ: the driver is closed, create a new one')\n await this.#open()\n }\n\n async close(): Promise<void> {\n if (this.#closing) return\n this.#closing = true\n this.#abort.abort()\n for (const subscription of this.#subscriptions) {\n subscription.active = false\n const channel = subscription.channel\n subscription.channel = undefined\n if (channel !== undefined) await closeQuietly(channel)\n }\n this.#subscriptions.clear()\n const publisher = this.#publisher\n this.#publisher = undefined\n if (publisher !== undefined) {\n try {\n await closeQuietly(await publisher)\n } catch {\n // the channel never opened\n }\n }\n const model = this.#model\n this.#model = undefined\n if (model !== undefined) {\n try {\n await model.close()\n } catch {\n // already closed\n }\n }\n }\n\n deadLetterName(queue: string): string {\n return this.#options.deadLetterName?.(queue) ?? `${queue}.dlq`\n }\n\n async #open(): Promise<void> {\n const socketOptions: SocketOptions = {\n ...this.#options.socketOptions,\n clientProperties: {\n connection_name: this.#options.connectionName ?? 'znight',\n ...this.#options.socketOptions?.clientProperties,\n },\n }\n const model = await amqpConnect(this.#options.url, socketOptions)\n model.on('error', (error: Error) => {\n this.#logger.error('RabbitMQ connection error', { err: error })\n })\n model.on('close', (error?: Error) => this.#onConnectionClosed(model, error))\n model.on('blocked', (reason: string) => {\n this.#logger.warn(`RabbitMQ connection blocked: ${reason}`)\n })\n model.on('unblocked', () => {\n this.#logger.info('RabbitMQ connection unblocked')\n })\n this.#model = model\n this.#publisher = undefined\n this.#delayQueues.clear()\n this.#delayLevels = undefined\n }\n\n #onConnectionClosed(model: ChannelModel, error: Error | undefined): void {\n if (this.#model !== model) return\n this.#model = undefined\n this.#publisher = undefined\n for (const subscription of this.#subscriptions) {\n subscription.channel = undefined\n subscription.consumerTag = undefined\n }\n if (this.#closing) return\n const reconnect = this.#options.reconnect\n this.#logger.error(\n reconnect === false ? 'RabbitMQ connection lost' : 'RabbitMQ connection lost, reconnecting',\n error === undefined ? {} : { err: error },\n )\n if (reconnect !== false) this.#recover()\n }\n\n /**\n * Starts the recovery loop unless it is running. The loop re-reads the whole state (connection,\n * subscriptions without channel) before it ends, so a failure happening while it runs is never lost.\n */\n #recover(): void {\n if (this.#closing) return\n this.#recovering ??= this.#recoverLoop()\n }\n\n /** Reconnects if needed, then resumes every subscription, with backoff, until everything consumes. */\n async #recoverLoop(): Promise<void> {\n const reconnect = this.#options.reconnect\n const settings = reconnect === false ? {} : (reconnect ?? {})\n const initialDelay = settings.initialDelayMs ?? 100\n const maxDelay = settings.maxDelayMs ?? 30_000\n let delay = initialDelay\n let reconnected = false\n try {\n while (!this.#closing) {\n try {\n await sleep(delay, this.#abort.signal)\n } catch {\n return\n }\n if (this.#model === undefined) {\n if (reconnect === false) return\n try {\n await this.#open()\n reconnected = true\n } catch (error) {\n delay = Math.min(delay * 2, maxDelay)\n this.#logger.warn(`RabbitMQ reconnection failed, next attempt in ${delay} ms`, { err: error })\n continue\n }\n }\n const model = this.#model\n for (const subscription of this.#subscriptions) {\n if (!subscription.active || subscription.channel !== undefined) continue\n try {\n if (subscription.redeclare) await this.#redeclare(subscription)\n await this.#ensureConsuming(subscription)\n if (!reconnected) this.#logger.info(`RabbitMQ: consuming \"${subscription.queue}\" again`)\n } catch (error) {\n this.#logger.error(`RabbitMQ: cannot consume \"${subscription.queue}\" again`, { err: error })\n }\n }\n if (this.#closing) return\n if (this.#model !== model) {\n // the connection was lost again while the consumers were resuming\n delay = Math.min(delay * 2, maxDelay)\n continue\n }\n const waiting = [...this.#subscriptions].filter(\n subscription => subscription.active && subscription.channel === undefined,\n )\n if (waiting.length > 0) {\n delay = Math.min(delay * 2, maxDelay)\n this.#logger.warn(\n `RabbitMQ: ${waiting.map(subscription => `\"${subscription.queue}\"`).join(', ')} not consumed yet, next attempt in ${delay} ms`,\n )\n continue\n }\n if (reconnected) this.#logger.info('RabbitMQ connection restored')\n return\n }\n } finally {\n this.#recovering = undefined\n }\n }\n\n #connection(action: string): ChannelModel {\n const model = this.#model\n if (model === undefined) {\n throw brokerUnavailableError(\n this.#closing\n ? `RabbitMQ: cannot ${action}, the driver is closed`\n : `RabbitMQ: cannot ${action}, the connection is not available`,\n )\n }\n return model\n }\n\n /** Runs `fn` on a temporary channel (failed declarations close the channel they run on). */\n async #withChannel<T>(action: string, fn: (channel: Channel) => Promise<T>): Promise<T> {\n const channel = await this.#connection(action).createChannel()\n channel.on('error', () => {\n // reported through the rejected operation\n })\n try {\n return await fn(channel)\n } finally {\n await closeQuietly(channel)\n }\n }\n\n // -----------------------------------------------------------------------------------------------\n // Publishing\n // -----------------------------------------------------------------------------------------------\n\n async publish(queue: string, message: OutboundMessage): Promise<void> {\n const headers: Record<string, unknown> = { ...message.headers }\n const properties: Options.Publish = {\n messageId: message.id,\n contentType: message.contentType,\n timestamp: Math.floor(message.timestamp / 1000),\n headers,\n persistent: this.#options.persistent ?? true,\n ...(message.type === undefined ? {} : { type: message.type }),\n }\n const content = Buffer.from(message.body)\n if (message.delayMs > 0) {\n await this.#publishDelayed(queue, content, properties, message.delayMs)\n return\n }\n await this.#confirmPublish('', queue, content, properties, queue)\n }\n\n #publisherChannel(): Promise<ConfirmChannel> {\n const existing = this.#publisher\n if (existing !== undefined) return existing\n const model = this.#connection('publish')\n const opening = model.createConfirmChannel().then(channel => {\n channel.on('error', (error: Error) => {\n this.#logger.error('RabbitMQ publisher channel error', { err: error })\n })\n channel.on('close', () => {\n if (this.#publisher === opening) this.#publisher = undefined\n })\n // a return precedes the confirmation of its publication: mark the oldest pending publication\n // of the same exchange, routing key and message id (never one already returned)\n channel.on('return', (returned: Message) => {\n const key = pendingKey(returned.fields.exchange, returned.fields.routingKey, returned.properties.messageId)\n const pending = this.#pending.get(key)?.find(entry => !entry.returned)\n if (pending !== undefined) pending.returned = true\n })\n return channel\n })\n opening.catch(() => {\n if (this.#publisher === opening) this.#publisher = undefined\n })\n this.#publisher = opening\n return opening\n }\n\n /**\n * Publishes to `exchange` with `routingKey` and waits for the broker confirmation. `destination`\n * names the target queue in errors.\n */\n async #confirmPublish(\n exchange: string,\n routingKey: string,\n content: Buffer,\n properties: Options.Publish,\n destination: string,\n ): Promise<void> {\n const channel = await this.#publisherChannel()\n const mandatory = this.#options.mandatory ?? true\n const id = properties.messageId as string\n const key = pendingKey(exchange, routingKey, id)\n const pending: PendingConfirm = { returned: false }\n let waiting = this.#pending.get(key)\n if (waiting === undefined) {\n waiting = []\n this.#pending.set(key, waiting)\n }\n waiting.push(pending)\n try {\n await new Promise<void>((resolve, reject) => {\n try {\n channel.publish(exchange, routingKey, content, { ...properties, mandatory }, (error: unknown) => {\n if (error !== null && error !== undefined) reject(error instanceof Error ? error : new Error(String(error)))\n else resolve()\n })\n } catch (error) {\n reject(error)\n }\n })\n } catch (error) {\n throw brokerUnavailableError(\n `RabbitMQ: publishing to \"${destination}\" was not confirmed: ${errorMessage(error)}`,\n error,\n )\n } finally {\n const index = waiting.indexOf(pending)\n if (index !== -1) waiting.splice(index, 1)\n if (waiting.length === 0 && this.#pending.get(key) === waiting) this.#pending.delete(key)\n }\n if (pending.returned) {\n throw brokerUnavailableError(\n exchange === ''\n ? `RabbitMQ: message ${id} is unroutable, queue \"${destination}\" does not exist`\n : `RabbitMQ: message ${id} to \"${destination}\" is unroutable through the exchange \"${exchange}\"`,\n )\n }\n }\n\n /**\n * Publishes through the delay levels: the delay, rounded up to an odd number of milliseconds (so\n * that every route ends with level 0, whose queue dead-letters to the default exchange), is written\n * in binary in the routing key; the `CC` header routes the message to `queue` at the end.\n */\n async #publishDelayed(queue: string, content: Buffer, properties: Options.Publish, delayMs: number): Promise<void> {\n const rounded = Math.min(MAX_TTL_MS, Math.ceil(delayMs))\n const delay = rounded % 2 === 0 ? rounded + 1 : rounded\n await this.#declareDelayLevels()\n let top = 0\n while (top < DELAY_LEVELS - 1 && 2 ** (top + 1) <= delay) top++\n try {\n await this.#confirmPublish(\n this.#delayLevelName(top),\n delayRoutingKey(delay),\n content,\n { ...properties, headers: { ...properties.headers, CC: [queue] } },\n queue,\n )\n } catch (error) {\n // the levels may have been deleted: declare them again on the next delayed publication\n this.#delayLevels = undefined\n throw error\n }\n }\n\n #delayLevelName(level: number): string {\n return `${this.#options.delayLevelPrefix ?? DEFAULT_DELAY_LEVEL_PREFIX}.${level}`\n }\n\n /**\n * Declares (once per connection) the delay levels: for each level n, a topic exchange routing the\n * messages whose bit n is set to the queue of the level (TTL 2^n ms, dead-lettering to the level\n * below), and the others straight to the level below.\n */\n async #declareDelayLevels(): Promise<void> {\n let declared = this.#delayLevels\n if (declared === undefined) {\n const levels: Promise<void> = this.#withChannel('declare the delay levels', async channel => {\n for (let level = 0; level < DELAY_LEVELS; level++) {\n const name = this.#delayLevelName(level)\n const below = level === 0 ? '' : this.#delayLevelName(level - 1)\n await channel.assertExchange(name, 'topic', { durable: true })\n await channel.assertQueue(name, { durable: true, arguments: this.#delayQueueArguments(2 ** level, below) })\n await channel.bindQueue(name, name, delayBindingKey(level, '1'))\n if (level > 0) await channel.bindExchange(below, name, delayBindingKey(level, '0'))\n }\n })\n declared = levels\n this.#delayLevels = levels\n levels.catch(() => {\n if (this.#delayLevels === levels) this.#delayLevels = undefined\n })\n }\n try {\n await declared\n } catch (error) {\n const message = errorMessage(error)\n throw consumerError(\n `RabbitMQ: cannot declare the delay levels \"${this.#delayLevelName(0)}\" to \"${this.#delayLevelName(DELAY_LEVELS - 1)}\": ${message}`,\n message.includes('PRECONDITION_FAILED')\n ? [\n 'They exist with other arguments (another queueType?): set rabbitmq({ delayLevelPrefix }) to use other names',\n ]\n : [],\n )\n }\n }\n\n /** Arguments of a delay queue: TTL, then dead-lettering (at-least-once on quorum queues). */\n #delayQueueArguments(ttl: number, exchange: string, routingKey?: string): Record<string, unknown> {\n const args: Record<string, unknown> = {\n 'x-queue-type': this.#queueType,\n 'x-message-ttl': ttl,\n 'x-dead-letter-exchange': exchange,\n }\n if (routingKey !== undefined) args['x-dead-letter-routing-key'] = routingKey\n if (this.#queueType === 'quorum') {\n // never lose a message while moving it on\n args['x-dead-letter-strategy'] = 'at-least-once'\n args['x-overflow'] = 'reject-publish'\n }\n return args\n }\n\n /** Declares (once per connection) the retry delay queue of `queue` for `delayMs` and returns its name. */\n async #delayQueue(queue: string, delayMs: number): Promise<string> {\n const name = this.#options.delayQueueName?.(queue, delayMs) ?? `${queue}.delay.${delayMs}`\n let declared = this.#delayQueues.get(name)\n if (declared === undefined) {\n const args = this.#delayQueueArguments(delayMs, '', queue)\n declared = this.#withChannel(`declare the delay queue \"${name}\"`, async channel => {\n await channel.assertQueue(name, { durable: true, arguments: args })\n })\n this.#delayQueues.set(name, declared)\n declared.catch(() => this.#delayQueues.delete(name))\n }\n try {\n await declared\n } catch (error) {\n throw topologyError(name, error)\n }\n return name\n }\n\n // -----------------------------------------------------------------------------------------------\n // Consuming\n // -----------------------------------------------------------------------------------------------\n\n async subscribe(queue: string, handler: DeliveryHandler, options: SubscribeOptions): Promise<Subscription> {\n await this.#prepareTopology(queue, options)\n const subscription: ConsumerSubscription = {\n queue,\n handler,\n options,\n channel: undefined,\n consumerTag: undefined,\n active: true,\n consuming: undefined,\n redeclare: false,\n }\n // registered first: a channel closing right after the subscription is resumed by the recovery\n this.#subscriptions.add(subscription)\n try {\n await this.#ensureConsuming(subscription)\n } catch (error) {\n subscription.active = false\n this.#subscriptions.delete(subscription)\n throw error\n }\n return {\n cancel: async (): Promise<void> => {\n subscription.active = false\n const { channel, consumerTag } = subscription\n if (channel !== undefined && consumerTag !== undefined) {\n subscription.consumerTag = undefined\n try {\n await channel.cancel(consumerTag)\n } catch (error) {\n this.#logger.warn(`RabbitMQ: failed to cancel the consumer of \"${queue}\"`, { err: error })\n }\n }\n },\n close: async (): Promise<void> => {\n subscription.active = false\n this.#subscriptions.delete(subscription)\n const channel = subscription.channel\n subscription.channel = undefined\n if (channel !== undefined) await closeQuietly(channel)\n },\n }\n }\n\n async #prepareTopology(queue: string, options: SubscribeOptions): Promise<void> {\n const dlq = options.deadLetterQueue\n const names = dlq === undefined ? [queue] : [queue, dlq]\n if (this.#assertTopology) {\n for (const name of names) {\n try {\n await this.#declareQueue(name)\n } catch (error) {\n throw topologyError(name, error)\n }\n }\n } else {\n for (const name of names) {\n try {\n await this.#withChannel(`check the queue \"${name}\"`, channel => channel.checkQueue(name))\n } catch (error) {\n throw consumerError(\n `RabbitMQ: queue \"${name}\" of consumer ${options.consumer} does not exist (${errorMessage(error)})`,\n ['Declare it, or set assertTopology: true to let Znight declare it'],\n )\n }\n }\n }\n for (const delay of options.retryDelays) await this.#delayQueue(queue, Math.round(delay))\n }\n\n /**\n * Declares a consumed or dead-letter queue. Quorum queues get `x-delivery-limit: -1` on RabbitMQ >= 4\n * (whose default limit of 20 deliveries counts the releases); an existing queue declared without it\n * cannot be changed by a declaration: it is declared as it is, with a warning.\n */\n async #declareQueue(name: string): Promise<void> {\n const args: Record<string, unknown> = { 'x-queue-type': this.#queueType, ...this.#options.queueArguments }\n const declare = (queueArgs: Record<string, unknown>): Promise<void> =>\n this.#withChannel(`declare the queue \"${name}\"`, async channel => {\n await channel.assertQueue(name, { durable: true, arguments: queueArgs })\n })\n if (args['x-queue-type'] === 'quorum' && !Object.hasOwn(args, 'x-delivery-limit') && this.#serverMajor() >= 4) {\n try {\n await declare({ ...args, 'x-delivery-limit': -1 })\n return\n } catch (error) {\n if (!errorMessage(error).includes(\"'x-delivery-limit'\")) throw error\n this.#logger.warn(\n `RabbitMQ: queue \"${name}\" exists without \"x-delivery-limit\": unless a policy sets \"delivery-limit\" to -1, RabbitMQ drops (or dead-letters through a queue DLX) a message delivered 20 times, releases included (shutdowns, failed dead-lettering). Apply such a policy, or re-create the queue`,\n )\n }\n }\n await declare(args)\n }\n\n /** Major version of the connected RabbitMQ (0 when unknown). */\n #serverMajor(): number {\n const version = this.#model?.connection?.serverProperties?.version\n const major = typeof version === 'string' ? Number.parseInt(version, 10) : Number.NaN\n return Number.isNaN(major) ? 0 : major\n }\n\n /** After a consumer cancellation, declares its queues again (`assertTopology`) before consuming. */\n async #redeclare(subscription: ConsumerSubscription): Promise<void> {\n if (this.#assertTopology) {\n const dlq = subscription.options.deadLetterQueue\n for (const name of dlq === undefined ? [subscription.queue] : [subscription.queue, dlq]) {\n try {\n await this.#declareQueue(name)\n } catch (error) {\n throw topologyError(name, error)\n }\n }\n }\n subscription.redeclare = false\n }\n\n /** Opens the consumer channel of `subscription` unless it is open or opening. */\n #ensureConsuming(subscription: ConsumerSubscription): Promise<void> {\n if (!subscription.active || subscription.channel !== undefined) return Promise.resolve()\n subscription.consuming ??= this.#consume(subscription).finally(() => {\n subscription.consuming = undefined\n })\n return subscription.consuming\n }\n\n async #consume(subscription: ConsumerSubscription): Promise<void> {\n const channel = await this.#connection(`consume \"${subscription.queue}\"`).createChannel()\n channel.on('error', (error: Error) => {\n this.#logger.error(`RabbitMQ consumer channel of \"${subscription.queue}\" failed`, { err: error })\n })\n channel.on('close', () => {\n if (subscription.channel !== channel) return\n subscription.channel = undefined\n subscription.consumerTag = undefined\n // a channel error (or the loss of the connection, handled by the same loop): consume again\n if (subscription.active) this.#recover()\n })\n try {\n await channel.prefetch(subscription.options.concurrency)\n subscription.channel = channel\n const { consumerTag } = await channel.consume(\n subscription.queue,\n message => {\n if (message === null) {\n this.#onConsumerCancelled(subscription, channel)\n return\n }\n this.#deliver(subscription, channel, message)\n },\n { noAck: false },\n )\n subscription.consumerTag = consumerTag\n } catch (error) {\n subscription.channel = undefined\n await closeQuietly(channel)\n throw brokerUnavailableError(`RabbitMQ: cannot consume \"${subscription.queue}\": ${errorMessage(error)}`, error)\n }\n }\n\n /**\n * The broker cancelled the consumer (`basic.cancel`: queue deleted, node down...). The channel stays\n * open, so nothing else would notice: close it (its unsettled messages go back to the queue, when it\n * still exists) and resume the subscription.\n */\n #onConsumerCancelled(subscription: ConsumerSubscription, channel: Channel): void {\n if (subscription.channel !== channel || !subscription.active) return\n this.#logger.error(`RabbitMQ cancelled the consumer of \"${subscription.queue}\" (queue deleted?), consuming again`)\n subscription.channel = undefined\n subscription.consumerTag = undefined\n subscription.redeclare = true\n closeQuietly(channel).then(\n () => undefined,\n () => undefined,\n )\n this.#recover()\n }\n\n #deliver(subscription: ConsumerSubscription, channel: Channel, message: ConsumeMessage): void {\n const { queue } = subscription\n const properties = message.properties\n const rawHeaders: Record<string, unknown> = { ...(properties.headers ?? {}) }\n const headers = toHeaderRecord(Object.entries(rawHeaders))\n // quorum queues count redeliveries; classic queues only flag them\n const deliveryCount = Number(rawHeaders['x-delivery-count'])\n let redeliveries = message.fields.redelivered ? 1 : 0\n if (Number.isSafeInteger(deliveryCount) && deliveryCount > 0) redeliveries = deliveryCount\n const type =\n stringProperty(properties.type) ??\n readHeader(headers, MessagingHeaders.Type) ??\n (this.#options.typeFromRoutingKey === true && message.fields.exchange !== ''\n ? message.fields.routingKey\n : undefined)\n const id = stringProperty(properties.messageId) ?? `${queue}:${message.fields.deliveryTag}`\n const timestamp = typeof properties.timestamp === 'number' ? properties.timestamp * 1000 : undefined\n let settled = false\n const settle = (action: string): void => {\n if (settled) throw new Error(`[rabbitmq] message ${id} was already settled (${action})`)\n if (subscription.channel !== channel) {\n throw brokerUnavailableError(\n `RabbitMQ: cannot ${action} message ${id}, its channel is closed (it will be redelivered)`,\n )\n }\n }\n /** Properties of a republished copy: the original ones, with the type that selected the handler. */\n const copyProperties = (extraHeaders: Record<string, unknown>, removeAttempt: boolean): Options.Publish => {\n const copied: Record<string, unknown> = {}\n for (const [name, value] of Object.entries(rawHeaders)) {\n if (UNCOPIED_HEADERS.has(name) || (removeAttempt && name === MessagingHeaders.Attempt)) continue\n copied[name] = value\n }\n return {\n ...definedProperties(properties),\n // a type read from the routing key would be lost: copies go through the default exchange\n ...(type === undefined ? {} : { type }),\n messageId: id,\n headers: { ...copied, ...extraHeaders },\n persistent: this.#options.persistent ?? true,\n }\n }\n const delivery: DriverDelivery = {\n id,\n body: message.content,\n contentType: stringProperty(properties.contentType),\n headers,\n type,\n key: undefined,\n attempt: attemptFromHeaders(headers) + redeliveries,\n redelivered: message.fields.redelivered,\n timestamp,\n raw: message,\n ack: async (): Promise<void> => {\n await Promise.resolve()\n settle('ack')\n channel.ack(message)\n settled = true\n },\n release: async (): Promise<void> => {\n await Promise.resolve()\n settle('release')\n channel.nack(message, false, true)\n settled = true\n },\n retry: async (request: RetryRequest): Promise<void> => {\n settle('retry')\n const target = request.delayMs > 0 ? await this.#delayQueue(queue, Math.round(request.delayMs)) : queue\n try {\n await this.#confirmPublish(\n '',\n target,\n message.content,\n copyProperties({ [MessagingHeaders.Attempt]: String(request.attempt) }, false),\n target,\n )\n } catch (error) {\n // the delay queue may have been deleted: declare it again on the next retry\n if (target !== queue) this.#delayQueues.delete(target)\n throw error\n }\n settle('retry')\n channel.ack(message)\n settled = true\n },\n deadLetter: async (request: DeadLetterRequest): Promise<void> => {\n settle('dead-letter')\n if (request.queue === undefined) {\n channel.nack(message, false, false)\n settled = true\n return\n }\n await this.#confirmPublish(\n '',\n request.queue,\n message.content,\n copyProperties(\n {\n [MessagingHeaders.DeadLetterReason]: request.reason,\n [MessagingHeaders.Error]: truncate(errorMessage(request.error), MAX_ERROR_HEADER_LENGTH),\n [MessagingHeaders.OriginalQueue]: queue,\n [MessagingHeaders.Attempts]: String(request.attempt),\n },\n true,\n ),\n request.queue,\n )\n settle('dead-letter')\n channel.ack(message)\n settled = true\n },\n }\n subscription.handler(delivery).catch((error: unknown) => {\n this.#logger.error(`RabbitMQ: unexpected failure handling message ${id}`, { err: error })\n })\n }\n}\n\nfunction stringProperty(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\n/** Key of the publications awaiting their confirmation (a returned message carries the same fields). */\nfunction pendingKey(exchange: string, routingKey: string, messageId: unknown): string {\n return `${exchange}\\u0000${routingKey}\\u0000${String(messageId)}`\n}\n\n/** Routing key of a delayed publication: `znight-delay.<bit 31>.<bit 30>...<bit 0>`. */\nfunction delayRoutingKey(delayMs: number): string {\n const words = [DELAY_ROUTING_WORD]\n for (let level = DELAY_LEVELS - 1; level >= 0; level--) {\n words.push(Math.floor(delayMs / 2 ** level) % 2 === 1 ? '1' : '0')\n }\n return words.join('.')\n}\n\n/** Binding key matching the delayed publications whose bit `level` is `bit` (exact word count). */\nfunction delayBindingKey(level: number, bit: '0' | '1'): string {\n const words = [DELAY_ROUTING_WORD]\n for (let index = DELAY_LEVELS - 1; index >= 0; index--) words.push(index === level ? bit : '*')\n return words.join('.')\n}\n\n/** AMQP properties of a received message, reusable for a republication. */\nfunction definedProperties(properties: MessageProperties): Options.Publish {\n const result: Options.Publish = {}\n if (properties.contentType !== undefined) result.contentType = properties.contentType\n if (properties.contentEncoding !== undefined) result.contentEncoding = properties.contentEncoding\n if (properties.correlationId !== undefined) result.correlationId = properties.correlationId\n if (properties.replyTo !== undefined) result.replyTo = properties.replyTo\n if (properties.timestamp !== undefined) result.timestamp = properties.timestamp\n if (properties.type !== undefined) result.type = properties.type\n if (properties.appId !== undefined) result.appId = properties.appId\n if (properties.priority !== undefined) result.priority = properties.priority\n if (properties.userId !== undefined) result.userId = properties.userId\n return result\n}\n\nfunction topologyError(queue: string, error: unknown): Error {\n const message = errorMessage(error)\n const hints = message.includes('PRECONDITION_FAILED')\n ? ['The queue exists with different arguments: align queueType/queueArguments, or set assertTopology: false']\n : []\n return consumerError(`RabbitMQ: cannot declare the queue \"${queue}\": ${message}`, hints)\n}\n\nasync function closeQuietly(channel: Channel): Promise<void> {\n try {\n await channel.close()\n } catch {\n // already closed\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rabbitmq.mjs","names":["amqpConnect"],"sources":["../src/drivers/rabbitmq.ts"],"sourcesContent":["/**\n * `@znight/messaging/rabbitmq`: RabbitMQ driver built on [amqplib](https://amqp-node.github.io/amqplib/)\n * 2.x (optional peer dependency).\n *\n * - Publishes through a confirm channel (`publish()` resolves on broker confirmation) with\n * `mandatory` routing: publishing to a queue that does not exist fails instead of dropping the message.\n * - One channel per consumer, `prefetch = concurrency`, manual acknowledgements.\n * - Retries are republished copies carrying `x-znight-attempt`: immediately to the queue, or through a\n * delay queue (`<queue>.delay.<ms>`, queue-level TTL dead-lettering back into the queue: no\n * head-of-line blocking between delays). The original is acknowledged once the copy is confirmed.\n * - Delayed publications go through a fixed set of 32 delay levels shared by every queue\n * (`znight.delays.<n>`: a topic exchange and a queue with a TTL of 2^n ms each). The delay is\n * written in binary in the routing key: the message waits in the level of each set bit, then the last\n * level dead-letters it to the default exchange, which routes it to its queue (`CC` header).\n * - Attempts = `x-znight-attempt` + broker redeliveries (`x-delivery-count` of quorum queues, or the\n * `redelivered` flag of classic queues).\n * - Dead letters are published (confirmed) to `<queue>.dlq`, then the original is acknowledged; without\n * dead-letter queue the message is rejected (`nack` without requeue), so a queue DLX policy applies.\n * - Declares the queues it uses (`assertTopology`, default) or verifies they exist. On RabbitMQ >= 4,\n * quorum queues are declared with `x-delivery-limit: -1`: Znight counts the attempts itself, the\n * broker's default limit (20 deliveries) would drop messages released repeatedly.\n * - Recovers from connection losses (reconnection with exponential backoff), closed consumer channels\n * and consumers cancelled by the broker: every subscription is resumed, with backoff, until it\n * consumes again. A consumed queue that no longer exists (deleted) is declared again first\n * (`assertTopology`).\n */\nimport type {\n Channel,\n ChannelModel,\n ConfirmChannel,\n ConsumeMessage,\n Message,\n MessageProperties,\n Options,\n SocketOptions,\n} from 'amqplib'\nimport type {\n DeadLetterRequest,\n DeliveryHandler,\n DriverCapabilities,\n DriverContext,\n DriverDelivery,\n MessagingDriver,\n RetryRequest,\n SubscribeOptions,\n Subscription,\n} from '../driver.ts'\nimport type { OutboundMessage } from '../message.ts'\n\nimport { connect as amqpConnect } from 'amqplib'\n\nimport { brokerUnavailableError, consumerError } from '../errors.ts'\nimport { attemptFromHeaders, MessagingHeaders } from '../headers.ts'\nimport { errorMessage, sleep, truncate } from '../internal/async.ts'\nimport { durationOption, MAX_ERROR_HEADER_LENGTH, readHeader, toHeaderRecord } from './shared.ts'\n\n/** Reconnection settings. */\nexport interface RabbitMQReconnectOptions {\n /** First delay before reconnecting. Default: 100 ms. */\n readonly initialDelayMs?: number\n /** Longest delay between attempts. Default: 30 000 ms. */\n readonly maxDelayMs?: number\n}\n\n/** Options of {@link rabbitmq}. */\nexport interface RabbitMQDriverOptions {\n /** `amqp://user:pass@host:5672/vhost` or amqplib connection options. */\n readonly url: string | Options.Connect\n /** Socket / TLS options passed to amqplib. */\n readonly socketOptions?: SocketOptions\n /**\n * Declare the queues used by consumers (consumed, dead-letter and delay queues) and by delayed\n * publications. When `false`, consumed and dead-letter queues must exist (verified at subscribe).\n * Default: `true`.\n */\n readonly assertTopology?: boolean\n /** Type of the declared queues. Default: `quorum` (replicated, native `x-delivery-count`). */\n readonly queueType?: 'quorum' | 'classic'\n /**\n * Extra arguments of declared consumed and dead-letter queues (`x-max-length`...). Quorum queues get\n * `x-delivery-limit: -1` on RabbitMQ >= 4 unless this sets `x-delivery-limit`.\n */\n readonly queueArguments?: Readonly<Record<string, unknown>>\n /** Longest retry delay waited in-process (holding the message). Default: 5000 ms. */\n readonly maxInProcessDelayMs?: number\n /** Name of the retry delay queue of `queue` for `delayMs`. Default: `${queue}.delay.${delayMs}`. */\n readonly delayQueueName?: (queue: string, delayMs: number) => string\n /**\n * Prefix of the delay levels of delayed publications: `<prefix>.0` to `<prefix>.31` (one topic\n * exchange and one queue each, shared by every queue of the virtual host). Default: `znight.delays`.\n */\n readonly delayLevelPrefix?: string\n /** Default dead-letter queue name. Default: `${queue}.dlq`. */\n readonly deadLetterName?: (queue: string) => string\n /** Publish with `mandatory`: unroutable messages make `publish()` fail. Default: `true`. */\n readonly mandatory?: boolean\n /** Persistent messages (delivery mode 2). Default: `true`. */\n readonly persistent?: boolean\n /** Use the routing key as message type when the message has none (topic exchanges). Default: `false`. */\n readonly typeFromRoutingKey?: boolean\n /** Reconnect after a connection loss. `false` disables. Default: enabled. */\n readonly reconnect?: RabbitMQReconnectOptions | false\n /** Connection name shown in the management UI. Default: `znight`. */\n readonly connectionName?: string\n}\n\n/** Largest TTL RabbitMQ accepts (2^32 - 1 ms). */\nconst MAX_TTL_MS = 4_294_967_295\nconst DEFAULT_IN_PROCESS_DELAY_MS = 5000\nconst DEFAULT_DELAY_LEVEL_PREFIX = 'znight.delays'\n/** Delay levels of delayed publications: level n waits 2^n ms, all of them together `MAX_TTL_MS`. */\nconst DELAY_LEVELS = 32\n/** First word of the routing key of a delayed publication, followed by one bit per level (highest first). */\nconst DELAY_ROUTING_WORD = 'znight-delay'\n/**\n * Headers never copied to republished messages (retries, dead letters): the ones managed by the broker,\n * and the sender-selected destinations (`CC`, `BCC`), which would route the copy to other queues.\n */\nconst UNCOPIED_HEADERS = new Set([\n 'x-delivery-count',\n 'x-death',\n 'x-first-death-exchange',\n 'x-first-death-queue',\n 'x-first-death-reason',\n 'x-last-death-exchange',\n 'x-last-death-queue',\n 'x-last-death-reason',\n 'CC',\n 'BCC',\n])\n\n/** Creates a {@link RabbitMQDriver}. */\nexport function rabbitmq(options: RabbitMQDriverOptions): RabbitMQDriver {\n return new RabbitMQDriver(options)\n}\n\ninterface PendingConfirm {\n returned: boolean\n}\n\ninterface ConsumerSubscription {\n readonly queue: string\n readonly handler: DeliveryHandler\n readonly options: SubscribeOptions\n channel: Channel | undefined\n consumerTag: string | undefined\n active: boolean\n /** In-progress (re)subscription. */\n consuming: Promise<void> | undefined\n /**\n * The queue may be gone (the broker cancelled the consumer, or consuming failed with NOT_FOUND):\n * declare the queues again before consuming.\n */\n redeclare: boolean\n}\n\n/** RabbitMQ {@link MessagingDriver}. See the module documentation. */\nexport class RabbitMQDriver implements MessagingDriver {\n readonly name = 'rabbitmq'\n readonly capabilities: DriverCapabilities\n readonly #options: RabbitMQDriverOptions\n readonly #queueType: 'quorum' | 'classic'\n readonly #assertTopology: boolean\n readonly #subscriptions = new Set<ConsumerSubscription>()\n /** Retry delay queues already declared, by name. */\n readonly #delayQueues = new Map<string, Promise<void>>()\n /** Publications awaiting their confirmation, by destination and message id (to detect returned messages). */\n readonly #pending = new Map<string, PendingConfirm[]>()\n readonly #abort = new AbortController()\n #logger: DriverContext['logger'] = { debug() {}, info() {}, warn() {}, error() {} }\n #model: ChannelModel | undefined\n #publisher: Promise<ConfirmChannel> | undefined\n /** Declaration of the delay levels on the current connection. */\n #delayLevels: Promise<void> | undefined\n #closing = false\n /** Running recovery loop (reconnection and resubscriptions). */\n #recovering: Promise<void> | undefined\n\n constructor(options: RabbitMQDriverOptions) {\n if (options === null || typeof options !== 'object' || options.url === undefined) {\n throw new TypeError('[znight] rabbitmq(): the \"url\" option is required')\n }\n if (\n options.delayLevelPrefix !== undefined &&\n (typeof options.delayLevelPrefix !== 'string' || options.delayLevelPrefix === '')\n ) {\n throw new TypeError('[znight] rabbitmq(): \"delayLevelPrefix\" must be a non-empty string')\n }\n this.#options = options\n this.#queueType = options.queueType ?? 'quorum'\n this.#assertTopology = options.assertTopology ?? true\n this.capabilities = Object.freeze({\n nativeDelay: true,\n maxNativeDelayMs: MAX_TTL_MS,\n maxInProcessDelayMs: durationOption(\n options.maxInProcessDelayMs,\n DEFAULT_IN_PROCESS_DELAY_MS,\n 'rabbitmq maxInProcessDelayMs',\n ),\n deliveryCount: this.#queueType === 'quorum' ? 'native' : 'header',\n maxPublishDelayMs: MAX_TTL_MS,\n })\n }\n\n // -----------------------------------------------------------------------------------------------\n // Connection\n // -----------------------------------------------------------------------------------------------\n\n async connect(context: DriverContext): Promise<void> {\n this.#logger = context.logger\n if (this.#model !== undefined) return\n if (this.#closing) throw brokerUnavailableError('RabbitMQ: the driver is closed, create a new one')\n await this.#open()\n }\n\n async close(): Promise<void> {\n if (this.#closing) return\n this.#closing = true\n this.#abort.abort()\n for (const subscription of this.#subscriptions) {\n subscription.active = false\n const channel = subscription.channel\n subscription.channel = undefined\n if (channel !== undefined) await closeQuietly(channel)\n }\n this.#subscriptions.clear()\n const publisher = this.#publisher\n this.#publisher = undefined\n if (publisher !== undefined) {\n try {\n await closeQuietly(await publisher)\n } catch {\n // the channel never opened\n }\n }\n const model = this.#model\n this.#model = undefined\n if (model !== undefined) {\n try {\n await model.close()\n } catch {\n // already closed\n }\n }\n }\n\n deadLetterName(queue: string): string {\n return this.#options.deadLetterName?.(queue) ?? `${queue}.dlq`\n }\n\n async #open(): Promise<void> {\n const socketOptions: SocketOptions = {\n ...this.#options.socketOptions,\n clientProperties: {\n connection_name: this.#options.connectionName ?? 'znight',\n ...this.#options.socketOptions?.clientProperties,\n },\n }\n const model = await amqpConnect(this.#options.url, socketOptions)\n model.on('error', (error: Error) => {\n this.#logger.error('RabbitMQ connection error', { err: error })\n })\n model.on('close', (error?: Error) => this.#onConnectionClosed(model, error))\n model.on('blocked', (reason: string) => {\n this.#logger.warn(`RabbitMQ connection blocked: ${reason}`)\n })\n model.on('unblocked', () => {\n this.#logger.info('RabbitMQ connection unblocked')\n })\n this.#model = model\n this.#publisher = undefined\n this.#delayQueues.clear()\n this.#delayLevels = undefined\n }\n\n #onConnectionClosed(model: ChannelModel, error: Error | undefined): void {\n if (this.#model !== model) return\n this.#model = undefined\n this.#publisher = undefined\n for (const subscription of this.#subscriptions) {\n subscription.channel = undefined\n subscription.consumerTag = undefined\n }\n if (this.#closing) return\n const reconnect = this.#options.reconnect\n this.#logger.error(\n reconnect === false ? 'RabbitMQ connection lost' : 'RabbitMQ connection lost, reconnecting',\n error === undefined ? {} : { err: error },\n )\n if (reconnect !== false) this.#recover()\n }\n\n /**\n * Starts the recovery loop unless it is running. The loop re-reads the whole state (connection,\n * subscriptions without channel) before it ends, so a failure happening while it runs is never lost.\n */\n #recover(): void {\n if (this.#closing) return\n this.#recovering ??= this.#recoverLoop()\n }\n\n /** Reconnects if needed, then resumes every subscription, with backoff, until everything consumes. */\n async #recoverLoop(): Promise<void> {\n const reconnect = this.#options.reconnect\n const settings = reconnect === false ? {} : (reconnect ?? {})\n const initialDelay = settings.initialDelayMs ?? 100\n const maxDelay = settings.maxDelayMs ?? 30_000\n let delay = initialDelay\n let reconnected = false\n try {\n while (!this.#closing) {\n try {\n await sleep(delay, this.#abort.signal)\n } catch {\n return\n }\n if (this.#model === undefined) {\n if (reconnect === false) return\n try {\n await this.#open()\n reconnected = true\n } catch (error) {\n delay = Math.min(delay * 2, maxDelay)\n this.#logger.warn(`RabbitMQ reconnection failed, next attempt in ${delay} ms`, { err: error })\n continue\n }\n }\n const model = this.#model\n for (const subscription of this.#subscriptions) {\n if (!subscription.active || subscription.channel !== undefined) continue\n try {\n if (subscription.redeclare) await this.#redeclare(subscription)\n await this.#ensureConsuming(subscription)\n if (!reconnected) this.#logger.info(`RabbitMQ: consuming \"${subscription.queue}\" again`)\n } catch (error) {\n this.#logger.error(`RabbitMQ: cannot consume \"${subscription.queue}\" again`, { err: error })\n }\n }\n if (this.#closing) return\n if (this.#model !== model) {\n // the connection was lost again while the consumers were resuming\n delay = Math.min(delay * 2, maxDelay)\n continue\n }\n const waiting = [...this.#subscriptions].filter(\n subscription => subscription.active && subscription.channel === undefined,\n )\n if (waiting.length > 0) {\n delay = Math.min(delay * 2, maxDelay)\n this.#logger.warn(\n `RabbitMQ: ${waiting.map(subscription => `\"${subscription.queue}\"`).join(', ')} not consumed yet, next attempt in ${delay} ms`,\n )\n continue\n }\n if (reconnected) this.#logger.info('RabbitMQ connection restored')\n return\n }\n } finally {\n this.#recovering = undefined\n }\n }\n\n #connection(action: string): ChannelModel {\n const model = this.#model\n if (model === undefined) {\n throw brokerUnavailableError(\n this.#closing\n ? `RabbitMQ: cannot ${action}, the driver is closed`\n : `RabbitMQ: cannot ${action}, the connection is not available`,\n )\n }\n return model\n }\n\n /** Runs `fn` on a temporary channel (failed declarations close the channel they run on). */\n async #withChannel<T>(action: string, fn: (channel: Channel) => Promise<T>): Promise<T> {\n const channel = await this.#connection(action).createChannel()\n channel.on('error', () => {\n // reported through the rejected operation\n })\n try {\n return await fn(channel)\n } finally {\n await closeQuietly(channel)\n }\n }\n\n // -----------------------------------------------------------------------------------------------\n // Publishing\n // -----------------------------------------------------------------------------------------------\n\n async publish(queue: string, message: OutboundMessage): Promise<void> {\n const headers: Record<string, unknown> = { ...message.headers }\n const properties: Options.Publish = {\n messageId: message.id,\n contentType: message.contentType,\n timestamp: Math.floor(message.timestamp / 1000),\n headers,\n persistent: this.#options.persistent ?? true,\n ...(message.type === undefined ? {} : { type: message.type }),\n }\n const content = Buffer.from(message.body)\n if (message.delayMs > 0) {\n await this.#publishDelayed(queue, content, properties, message.delayMs)\n return\n }\n await this.#confirmPublish('', queue, content, properties, queue)\n }\n\n #publisherChannel(): Promise<ConfirmChannel> {\n const existing = this.#publisher\n if (existing !== undefined) return existing\n const model = this.#connection('publish')\n const opening = model.createConfirmChannel().then(channel => {\n channel.on('error', (error: Error) => {\n this.#logger.error('RabbitMQ publisher channel error', { err: error })\n })\n channel.on('close', () => {\n if (this.#publisher === opening) this.#publisher = undefined\n })\n // a return precedes the confirmation of its publication: mark the oldest pending publication\n // of the same exchange, routing key and message id (never one already returned)\n channel.on('return', (returned: Message) => {\n const key = pendingKey(returned.fields.exchange, returned.fields.routingKey, returned.properties.messageId)\n const pending = this.#pending.get(key)?.find(entry => !entry.returned)\n if (pending !== undefined) pending.returned = true\n })\n return channel\n })\n opening.catch(() => {\n if (this.#publisher === opening) this.#publisher = undefined\n })\n this.#publisher = opening\n return opening\n }\n\n /**\n * Publishes to `exchange` with `routingKey` and waits for the broker confirmation. `destination`\n * names the target queue in errors.\n */\n async #confirmPublish(\n exchange: string,\n routingKey: string,\n content: Buffer,\n properties: Options.Publish,\n destination: string,\n ): Promise<void> {\n const channel = await this.#publisherChannel()\n const mandatory = this.#options.mandatory ?? true\n const id = properties.messageId as string\n const key = pendingKey(exchange, routingKey, id)\n const pending: PendingConfirm = { returned: false }\n let waiting = this.#pending.get(key)\n if (waiting === undefined) {\n waiting = []\n this.#pending.set(key, waiting)\n }\n waiting.push(pending)\n try {\n await new Promise<void>((resolve, reject) => {\n try {\n channel.publish(exchange, routingKey, content, { ...properties, mandatory }, (error: unknown) => {\n if (error !== null && error !== undefined) reject(error instanceof Error ? error : new Error(String(error)))\n else resolve()\n })\n } catch (error) {\n reject(error)\n }\n })\n } catch (error) {\n throw brokerUnavailableError(\n `RabbitMQ: publishing to \"${destination}\" was not confirmed: ${errorMessage(error)}`,\n error,\n )\n } finally {\n const index = waiting.indexOf(pending)\n if (index !== -1) waiting.splice(index, 1)\n if (waiting.length === 0 && this.#pending.get(key) === waiting) this.#pending.delete(key)\n }\n if (pending.returned) {\n throw brokerUnavailableError(\n exchange === ''\n ? `RabbitMQ: message ${id} is unroutable, queue \"${destination}\" does not exist`\n : `RabbitMQ: message ${id} to \"${destination}\" is unroutable through the exchange \"${exchange}\"`,\n )\n }\n }\n\n /**\n * Publishes through the delay levels: the delay, rounded up to an odd number of milliseconds (so\n * that every route ends with level 0, whose queue dead-letters to the default exchange), is written\n * in binary in the routing key; the `CC` header routes the message to `queue` at the end.\n */\n async #publishDelayed(queue: string, content: Buffer, properties: Options.Publish, delayMs: number): Promise<void> {\n const rounded = Math.min(MAX_TTL_MS, Math.ceil(delayMs))\n const delay = rounded % 2 === 0 ? rounded + 1 : rounded\n await this.#declareDelayLevels()\n let top = 0\n while (top < DELAY_LEVELS - 1 && 2 ** (top + 1) <= delay) top++\n try {\n await this.#confirmPublish(\n this.#delayLevelName(top),\n delayRoutingKey(delay),\n content,\n { ...properties, headers: { ...properties.headers, CC: [queue] } },\n queue,\n )\n } catch (error) {\n // the levels may have been deleted: declare them again on the next delayed publication\n this.#delayLevels = undefined\n throw error\n }\n }\n\n #delayLevelName(level: number): string {\n return `${this.#options.delayLevelPrefix ?? DEFAULT_DELAY_LEVEL_PREFIX}.${level}`\n }\n\n /**\n * Declares (once per connection) the delay levels: for each level n, a topic exchange routing the\n * messages whose bit n is set to the queue of the level (TTL 2^n ms, dead-lettering to the level\n * below), and the others straight to the level below.\n */\n async #declareDelayLevels(): Promise<void> {\n let declared = this.#delayLevels\n if (declared === undefined) {\n const levels: Promise<void> = this.#withChannel('declare the delay levels', async channel => {\n for (let level = 0; level < DELAY_LEVELS; level++) {\n const name = this.#delayLevelName(level)\n const below = level === 0 ? '' : this.#delayLevelName(level - 1)\n await channel.assertExchange(name, 'topic', { durable: true })\n await channel.assertQueue(name, { durable: true, arguments: this.#delayQueueArguments(2 ** level, below) })\n await channel.bindQueue(name, name, delayBindingKey(level, '1'))\n if (level > 0) await channel.bindExchange(below, name, delayBindingKey(level, '0'))\n }\n })\n declared = levels\n this.#delayLevels = levels\n levels.catch(() => {\n if (this.#delayLevels === levels) this.#delayLevels = undefined\n })\n }\n try {\n await declared\n } catch (error) {\n const message = errorMessage(error)\n throw consumerError(\n `RabbitMQ: cannot declare the delay levels \"${this.#delayLevelName(0)}\" to \"${this.#delayLevelName(DELAY_LEVELS - 1)}\": ${message}`,\n message.includes('PRECONDITION_FAILED')\n ? [\n 'They exist with other arguments (another queueType?): set rabbitmq({ delayLevelPrefix }) to use other names',\n ]\n : [],\n )\n }\n }\n\n /** Arguments of a delay queue: TTL, then dead-lettering (at-least-once on quorum queues). */\n #delayQueueArguments(ttl: number, exchange: string, routingKey?: string): Record<string, unknown> {\n const args: Record<string, unknown> = {\n 'x-queue-type': this.#queueType,\n 'x-message-ttl': ttl,\n 'x-dead-letter-exchange': exchange,\n }\n if (routingKey !== undefined) args['x-dead-letter-routing-key'] = routingKey\n if (this.#queueType === 'quorum') {\n // never lose a message while moving it on\n args['x-dead-letter-strategy'] = 'at-least-once'\n args['x-overflow'] = 'reject-publish'\n }\n return args\n }\n\n /** Declares (once per connection) the retry delay queue of `queue` for `delayMs` and returns its name. */\n async #delayQueue(queue: string, delayMs: number): Promise<string> {\n const name = this.#options.delayQueueName?.(queue, delayMs) ?? `${queue}.delay.${delayMs}`\n let declared = this.#delayQueues.get(name)\n if (declared === undefined) {\n const args = this.#delayQueueArguments(delayMs, '', queue)\n declared = this.#withChannel(`declare the delay queue \"${name}\"`, async channel => {\n await channel.assertQueue(name, { durable: true, arguments: args })\n })\n this.#delayQueues.set(name, declared)\n declared.catch(() => this.#delayQueues.delete(name))\n }\n try {\n await declared\n } catch (error) {\n throw topologyError(name, error)\n }\n return name\n }\n\n // -----------------------------------------------------------------------------------------------\n // Consuming\n // -----------------------------------------------------------------------------------------------\n\n async subscribe(queue: string, handler: DeliveryHandler, options: SubscribeOptions): Promise<Subscription> {\n await this.#prepareTopology(queue, options)\n const subscription: ConsumerSubscription = {\n queue,\n handler,\n options,\n channel: undefined,\n consumerTag: undefined,\n active: true,\n consuming: undefined,\n redeclare: false,\n }\n // registered first: a channel closing right after the subscription is resumed by the recovery\n this.#subscriptions.add(subscription)\n try {\n await this.#ensureConsuming(subscription)\n } catch (error) {\n subscription.active = false\n this.#subscriptions.delete(subscription)\n throw error\n }\n return {\n cancel: async (): Promise<void> => {\n subscription.active = false\n const { channel, consumerTag } = subscription\n if (channel !== undefined && consumerTag !== undefined) {\n subscription.consumerTag = undefined\n try {\n await channel.cancel(consumerTag)\n } catch (error) {\n this.#logger.warn(`RabbitMQ: failed to cancel the consumer of \"${queue}\"`, { err: error })\n }\n }\n },\n close: async (): Promise<void> => {\n subscription.active = false\n this.#subscriptions.delete(subscription)\n const channel = subscription.channel\n subscription.channel = undefined\n if (channel !== undefined) await closeQuietly(channel)\n },\n }\n }\n\n async #prepareTopology(queue: string, options: SubscribeOptions): Promise<void> {\n const dlq = options.deadLetterQueue\n const names = dlq === undefined ? [queue] : [queue, dlq]\n if (this.#assertTopology) {\n for (const name of names) {\n try {\n await this.#declareQueue(name)\n } catch (error) {\n throw topologyError(name, error)\n }\n }\n } else {\n for (const name of names) {\n try {\n await this.#withChannel(`check the queue \"${name}\"`, channel => channel.checkQueue(name))\n } catch (error) {\n throw consumerError(\n `RabbitMQ: queue \"${name}\" of consumer ${options.consumer} does not exist (${errorMessage(error)})`,\n ['Declare it, or set assertTopology: true to let Znight declare it'],\n )\n }\n }\n }\n for (const delay of options.retryDelays) await this.#delayQueue(queue, Math.round(delay))\n }\n\n /**\n * Declares a consumed or dead-letter queue. Quorum queues get `x-delivery-limit: -1` on RabbitMQ >= 4\n * (whose default limit of 20 deliveries counts the releases); an existing queue declared without it\n * cannot be changed by a declaration: it is declared as it is, with a warning.\n */\n async #declareQueue(name: string): Promise<void> {\n const args: Record<string, unknown> = { 'x-queue-type': this.#queueType, ...this.#options.queueArguments }\n const declare = (queueArgs: Record<string, unknown>): Promise<void> =>\n this.#withChannel(`declare the queue \"${name}\"`, async channel => {\n await channel.assertQueue(name, { durable: true, arguments: queueArgs })\n })\n if (args['x-queue-type'] === 'quorum' && !Object.hasOwn(args, 'x-delivery-limit') && this.#serverMajor() >= 4) {\n try {\n await declare({ ...args, 'x-delivery-limit': -1 })\n return\n } catch (error) {\n if (!errorMessage(error).includes(\"'x-delivery-limit'\")) throw error\n this.#logger.warn(\n `RabbitMQ: queue \"${name}\" exists without \"x-delivery-limit\": unless a policy sets \"delivery-limit\" to -1, RabbitMQ drops (or dead-letters through a queue DLX) a message delivered 20 times, releases included (shutdowns, failed dead-lettering). Apply such a policy, or re-create the queue`,\n )\n }\n }\n await declare(args)\n }\n\n /** Major version of the connected RabbitMQ (0 when unknown). */\n #serverMajor(): number {\n const version = this.#model?.connection?.serverProperties?.version\n const major = typeof version === 'string' ? Number.parseInt(version, 10) : Number.NaN\n return Number.isNaN(major) ? 0 : major\n }\n\n /** After a consumer cancellation, declares its queues again (`assertTopology`) before consuming. */\n async #redeclare(subscription: ConsumerSubscription): Promise<void> {\n if (this.#assertTopology) {\n const dlq = subscription.options.deadLetterQueue\n for (const name of dlq === undefined ? [subscription.queue] : [subscription.queue, dlq]) {\n try {\n await this.#declareQueue(name)\n } catch (error) {\n throw topologyError(name, error)\n }\n }\n }\n subscription.redeclare = false\n }\n\n /** Opens the consumer channel of `subscription` unless it is open or opening. */\n #ensureConsuming(subscription: ConsumerSubscription): Promise<void> {\n if (!subscription.active || subscription.channel !== undefined) return Promise.resolve()\n subscription.consuming ??= this.#consume(subscription).finally(() => {\n subscription.consuming = undefined\n })\n return subscription.consuming\n }\n\n async #consume(subscription: ConsumerSubscription): Promise<void> {\n const channel = await this.#connection(`consume \"${subscription.queue}\"`).createChannel()\n channel.on('error', (error: Error) => {\n this.#logger.error(`RabbitMQ consumer channel of \"${subscription.queue}\" failed`, { err: error })\n })\n channel.on('close', () => {\n if (subscription.channel !== channel) return\n subscription.channel = undefined\n subscription.consumerTag = undefined\n // a channel error (or the loss of the connection, handled by the same loop): consume again\n if (subscription.active) this.#recover()\n })\n try {\n await channel.prefetch(subscription.options.concurrency)\n subscription.channel = channel\n const { consumerTag } = await channel.consume(\n subscription.queue,\n message => {\n if (message === null) {\n this.#onConsumerCancelled(subscription, channel)\n return\n }\n this.#deliver(subscription, channel, message)\n },\n { noAck: false },\n )\n subscription.consumerTag = consumerTag\n } catch (error) {\n subscription.channel = undefined\n // the queue does not exist (any more): declare it again before the next attempt. RabbitMQ cancels\n // the consumers of a deleted queue before the deletion completes, so the declaration that follows\n // a cancellation can still find the old queue, which is gone by the time `basic.consume` runs.\n if (isNotFound(error)) subscription.redeclare = true\n await closeQuietly(channel)\n throw brokerUnavailableError(`RabbitMQ: cannot consume \"${subscription.queue}\": ${errorMessage(error)}`, error)\n }\n }\n\n /**\n * The broker cancelled the consumer (`basic.cancel`: queue deleted, node down...). The channel stays\n * open, so nothing else would notice: close it (its unsettled messages go back to the queue, when it\n * still exists) and resume the subscription.\n */\n #onConsumerCancelled(subscription: ConsumerSubscription, channel: Channel): void {\n if (subscription.channel !== channel || !subscription.active) return\n this.#logger.error(`RabbitMQ cancelled the consumer of \"${subscription.queue}\" (queue deleted?), consuming again`)\n subscription.channel = undefined\n subscription.consumerTag = undefined\n subscription.redeclare = true\n closeQuietly(channel).then(\n () => undefined,\n () => undefined,\n )\n this.#recover()\n }\n\n #deliver(subscription: ConsumerSubscription, channel: Channel, message: ConsumeMessage): void {\n const { queue } = subscription\n const properties = message.properties\n const rawHeaders: Record<string, unknown> = { ...(properties.headers ?? {}) }\n const headers = toHeaderRecord(Object.entries(rawHeaders))\n // quorum queues count redeliveries; classic queues only flag them\n const deliveryCount = Number(rawHeaders['x-delivery-count'])\n let redeliveries = message.fields.redelivered ? 1 : 0\n if (Number.isSafeInteger(deliveryCount) && deliveryCount > 0) redeliveries = deliveryCount\n const type =\n stringProperty(properties.type) ??\n readHeader(headers, MessagingHeaders.Type) ??\n (this.#options.typeFromRoutingKey === true && message.fields.exchange !== ''\n ? message.fields.routingKey\n : undefined)\n const id = stringProperty(properties.messageId) ?? `${queue}:${message.fields.deliveryTag}`\n const timestamp = typeof properties.timestamp === 'number' ? properties.timestamp * 1000 : undefined\n let settled = false\n const settle = (action: string): void => {\n if (settled) throw new Error(`[rabbitmq] message ${id} was already settled (${action})`)\n if (subscription.channel !== channel) {\n throw brokerUnavailableError(\n `RabbitMQ: cannot ${action} message ${id}, its channel is closed (it will be redelivered)`,\n )\n }\n }\n /** Properties of a republished copy: the original ones, with the type that selected the handler. */\n const copyProperties = (extraHeaders: Record<string, unknown>, removeAttempt: boolean): Options.Publish => {\n const copied: Record<string, unknown> = {}\n for (const [name, value] of Object.entries(rawHeaders)) {\n if (UNCOPIED_HEADERS.has(name) || (removeAttempt && name === MessagingHeaders.Attempt)) continue\n copied[name] = value\n }\n return {\n ...definedProperties(properties),\n // a type read from the routing key would be lost: copies go through the default exchange\n ...(type === undefined ? {} : { type }),\n messageId: id,\n headers: { ...copied, ...extraHeaders },\n persistent: this.#options.persistent ?? true,\n }\n }\n const delivery: DriverDelivery = {\n id,\n body: message.content,\n contentType: stringProperty(properties.contentType),\n headers,\n type,\n key: undefined,\n attempt: attemptFromHeaders(headers) + redeliveries,\n redelivered: message.fields.redelivered,\n timestamp,\n raw: message,\n ack: async (): Promise<void> => {\n await Promise.resolve()\n settle('ack')\n channel.ack(message)\n settled = true\n },\n release: async (): Promise<void> => {\n await Promise.resolve()\n settle('release')\n channel.nack(message, false, true)\n settled = true\n },\n retry: async (request: RetryRequest): Promise<void> => {\n settle('retry')\n const target = request.delayMs > 0 ? await this.#delayQueue(queue, Math.round(request.delayMs)) : queue\n try {\n await this.#confirmPublish(\n '',\n target,\n message.content,\n copyProperties({ [MessagingHeaders.Attempt]: String(request.attempt) }, false),\n target,\n )\n } catch (error) {\n // the delay queue may have been deleted: declare it again on the next retry\n if (target !== queue) this.#delayQueues.delete(target)\n throw error\n }\n settle('retry')\n channel.ack(message)\n settled = true\n },\n deadLetter: async (request: DeadLetterRequest): Promise<void> => {\n settle('dead-letter')\n if (request.queue === undefined) {\n channel.nack(message, false, false)\n settled = true\n return\n }\n await this.#confirmPublish(\n '',\n request.queue,\n message.content,\n copyProperties(\n {\n [MessagingHeaders.DeadLetterReason]: request.reason,\n [MessagingHeaders.Error]: truncate(errorMessage(request.error), MAX_ERROR_HEADER_LENGTH),\n [MessagingHeaders.OriginalQueue]: queue,\n [MessagingHeaders.Attempts]: String(request.attempt),\n },\n true,\n ),\n request.queue,\n )\n settle('dead-letter')\n channel.ack(message)\n settled = true\n },\n }\n subscription.handler(delivery).catch((error: unknown) => {\n this.#logger.error(`RabbitMQ: unexpected failure handling message ${id}`, { err: error })\n })\n }\n}\n\nfunction stringProperty(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\n/** Key of the publications awaiting their confirmation (a returned message carries the same fields). */\nfunction pendingKey(exchange: string, routingKey: string, messageId: unknown): string {\n return `${exchange}\\u0000${routingKey}\\u0000${String(messageId)}`\n}\n\n/** Routing key of a delayed publication: `znight-delay.<bit 31>.<bit 30>...<bit 0>`. */\nfunction delayRoutingKey(delayMs: number): string {\n const words = [DELAY_ROUTING_WORD]\n for (let level = DELAY_LEVELS - 1; level >= 0; level--) {\n words.push(Math.floor(delayMs / 2 ** level) % 2 === 1 ? '1' : '0')\n }\n return words.join('.')\n}\n\n/** Binding key matching the delayed publications whose bit `level` is `bit` (exact word count). */\nfunction delayBindingKey(level: number, bit: '0' | '1'): string {\n const words = [DELAY_ROUTING_WORD]\n for (let index = DELAY_LEVELS - 1; index >= 0; index--) words.push(index === level ? bit : '*')\n return words.join('.')\n}\n\n/** AMQP properties of a received message, reusable for a republication. */\nfunction definedProperties(properties: MessageProperties): Options.Publish {\n const result: Options.Publish = {}\n if (properties.contentType !== undefined) result.contentType = properties.contentType\n if (properties.contentEncoding !== undefined) result.contentEncoding = properties.contentEncoding\n if (properties.correlationId !== undefined) result.correlationId = properties.correlationId\n if (properties.replyTo !== undefined) result.replyTo = properties.replyTo\n if (properties.timestamp !== undefined) result.timestamp = properties.timestamp\n if (properties.type !== undefined) result.type = properties.type\n if (properties.appId !== undefined) result.appId = properties.appId\n if (properties.priority !== undefined) result.priority = properties.priority\n if (properties.userId !== undefined) result.userId = properties.userId\n return result\n}\n\nfunction topologyError(queue: string, error: unknown): Error {\n const message = errorMessage(error)\n const hints = message.includes('PRECONDITION_FAILED')\n ? ['The queue exists with different arguments: align queueType/queueArguments, or set assertTopology: false']\n : []\n return consumerError(`RabbitMQ: cannot declare the queue \"${queue}\": ${message}`, hints)\n}\n\n/** A channel error `404 NOT_FOUND` (amqplib sets the reply code as `code`). */\nfunction isNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && (error as { code?: unknown }).code === 404\n}\n\nasync function closeQuietly(channel: Channel): Promise<void> {\n try {\n await channel.close()\n } catch {\n // already closed\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@znight/messaging",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Znight messaging plugin: consumers, retries and dead-letter queues with RabbitMQ, SQS or Kafka",
5
5
  "license": "MIT",
6
6
  "author": "Ugho STEPHAN",
@@ -73,13 +73,13 @@
73
73
  "access": "public"
74
74
  },
75
75
  "dependencies": {
76
- "@znight/core": "0.5.0"
76
+ "@znight/core": "0.6.0"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "@aws-sdk/client-sqs": "^3.600.0",
80
80
  "@platformatic/kafka": "^2.0.0",
81
81
  "amqplib": "^2.0.0",
82
- "@znight/compiler": "^0.5.0"
82
+ "@znight/compiler": "^0.6.0"
83
83
  },
84
84
  "peerDependenciesMeta": {
85
85
  "@aws-sdk/client-sqs": {
@@ -104,7 +104,7 @@
104
104
  "amqplib": "2.2.0",
105
105
  "testcontainers": "12.2.1",
106
106
  "zod": "4.6.7",
107
- "@znight/compiler": "0.5.0"
107
+ "@znight/compiler": "0.6.0"
108
108
  },
109
109
  "znight": {
110
110
  "apiVersion": 1,
@@ -21,7 +21,8 @@
21
21
  * broker's default limit (20 deliveries) would drop messages released repeatedly.
22
22
  * - Recovers from connection losses (reconnection with exponential backoff), closed consumer channels
23
23
  * and consumers cancelled by the broker: every subscription is resumed, with backoff, until it
24
- * consumes again.
24
+ * consumes again. A consumed queue that no longer exists (deleted) is declared again first
25
+ * (`assertTopology`).
25
26
  */
26
27
  import type {
27
28
  Channel,
@@ -146,7 +147,10 @@ interface ConsumerSubscription {
146
147
  active: boolean
147
148
  /** In-progress (re)subscription. */
148
149
  consuming: Promise<void> | undefined
149
- /** The broker cancelled the consumer (queue deleted...): declare the queues again before consuming. */
150
+ /**
151
+ * The queue may be gone (the broker cancelled the consumer, or consuming failed with NOT_FOUND):
152
+ * declare the queues again before consuming.
153
+ */
150
154
  redeclare: boolean
151
155
  }
152
156
 
@@ -746,6 +750,10 @@ export class RabbitMQDriver implements MessagingDriver {
746
750
  subscription.consumerTag = consumerTag
747
751
  } catch (error) {
748
752
  subscription.channel = undefined
753
+ // the queue does not exist (any more): declare it again before the next attempt. RabbitMQ cancels
754
+ // the consumers of a deleted queue before the deletion completes, so the declaration that follows
755
+ // a cancellation can still find the old queue, which is gone by the time `basic.consume` runs.
756
+ if (isNotFound(error)) subscription.redeclare = true
749
757
  await closeQuietly(channel)
750
758
  throw brokerUnavailableError(`RabbitMQ: cannot consume "${subscription.queue}": ${errorMessage(error)}`, error)
751
759
  }
@@ -935,6 +943,11 @@ function topologyError(queue: string, error: unknown): Error {
935
943
  return consumerError(`RabbitMQ: cannot declare the queue "${queue}": ${message}`, hints)
936
944
  }
937
945
 
946
+ /** A channel error `404 NOT_FOUND` (amqplib sets the reply code as `code`). */
947
+ function isNotFound(error: unknown): boolean {
948
+ return typeof error === 'object' && error !== null && (error as { code?: unknown }).code === 404
949
+ }
950
+
938
951
  async function closeQuietly(channel: Channel): Promise<void> {
939
952
  try {
940
953
  await channel.close()