@fedify/amqp
Version:
AMQP/RabbitMQ driver for Fedify
120 lines (118 loc) • 3.8 kB
JavaScript
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 };