UNPKG

@fedify/amqp

Version:

AMQP/RabbitMQ driver for Fedify

120 lines (118 loc) 3.8 kB
import { Buffer } from "node:buffer"; //#region src/mq.ts /** * A message queue that uses AMQP. * * @example * ``` typescript * import { createFederation } from "@fedify/fedify"; * import { AmqpMessageQueue } from "@fedify/amqp"; * import { connect } from "amqplib"; * * const federation = createFederation({ * queue: new AmqpMessageQueue(await connect("amqp://localhost")), * // ... other configurations * }); * ``` */ var AmqpMessageQueue = class { #connection; #queue; #delayedQueuePrefix; #durable; #senderChannel; nativeRetrial; /** * Creates a new `AmqpMessageQueue`. * @param connection A connection to the AMQP server. * @param options Options for the message queue. */ constructor(connection, options = {}) { this.#connection = connection; this.#queue = options.queue ?? "fedify_queue"; this.#delayedQueuePrefix = options.delayedQueuePrefix ?? "fedify_delayed_"; this.#durable = options.durable ?? true; this.nativeRetrial = options.nativeRetrial ?? false; } async #prepareQueue(channel) { await channel.assertQueue(this.#queue, { durable: this.#durable }); } async #getSenderChannel() { if (this.#senderChannel != null) return this.#senderChannel; const channel = await this.#connection.createChannel(); this.#senderChannel = channel; this.#prepareQueue(channel); return channel; } async enqueue(message, options) { const channel = await this.#getSenderChannel(); const delay = options?.delay?.total("millisecond"); let queue; if (delay == null || delay <= 0) queue = this.#queue; else { const delayStr = delay.toLocaleString("en", { useGrouping: false }); queue = this.#delayedQueuePrefix + delayStr; await channel.assertQueue(queue, { autoDelete: true, durable: this.#durable, deadLetterExchange: "", deadLetterRoutingKey: this.#queue, messageTtl: delay }); } channel.sendToQueue(queue, Buffer.from(JSON.stringify(message), "utf-8"), { persistent: this.#durable, contentType: "application/json" }); } async enqueueMany(messages, options) { const channel = await this.#getSenderChannel(); const delay = options?.delay?.total("millisecond"); let queue; if (delay == null || delay <= 0) queue = this.#queue; else { const delayStr = delay.toLocaleString("en", { useGrouping: false }); queue = this.#delayedQueuePrefix + delayStr; await channel.assertQueue(queue, { autoDelete: true, durable: this.#durable, deadLetterExchange: "", deadLetterRoutingKey: this.#queue, messageTtl: delay }); } for (const message of messages) channel.sendToQueue(queue, Buffer.from(JSON.stringify(message), "utf-8"), { persistent: this.#durable, contentType: "application/json" }); } async listen(handler, options = {}) { const channel = await this.#connection.createChannel(); await this.#prepareQueue(channel); await channel.prefetch(1); const reply = await channel.consume(this.#queue, (msg) => { if (msg == null) return; const message = JSON.parse(msg.content.toString("utf-8")); try { const result = handler(message); if (result instanceof Promise) if (this.nativeRetrial) result.then(() => channel.ack(msg)).catch(() => channel.nack(msg, void 0, true)); else result.finally(() => channel.ack(msg)); else if (this.nativeRetrial) channel.ack(msg); } catch { if (this.nativeRetrial) channel.nack(msg, void 0, true); } finally { if (!this.nativeRetrial) channel.ack(msg); } }, { noAck: false }); return await new Promise((resolve) => { if (options.signal?.aborted) resolve(); options.signal?.addEventListener("abort", () => { channel.cancel(reply.consumerTag).then(() => { channel.close().then(() => resolve()); }); }); }); } }; //#endregion export { AmqpMessageQueue };