amqplib-envelop
Version:
A wrapper for working with RabbitMQ using the amqplib npm package
249 lines (227 loc) • 7.56 kB
JavaScript
const amqplib = require('amqplib');
/**
* @class RabbitMQ
* @desc Wrapper class for working with RabbitMQ.
*/
class RabbitMQ {
/**
* @private
* @desc Interval in seconds between timeouts for heartbeat pulses in the connection
* between the client and the RabbitMQ server. Used to check activity and connection status.
* @type {number}
*/
#HEARTBEAT = 60;
/**
* @private
* @desc Time (in milliseconds) within which the client must establish a connection with the
* RabbitMQ server. If the connection is not established within this period,
* a timeout error may occur.
* @type {number}
*/
#CONNECTION_TIMEOUT = 30_000;
/**
* @static
* @desc Active instance of RabbitMQ.
* @type {RabbitMQ|null}
*/
static activeInstance = null;
constructor(connection) {
this.connectionArgs = connection;
this.connection = null;
this.channel = null;
}
async init() {
try {
this.connection = await amqplib.connect(this.getConnectionString());
this.channel = await this.connection.createChannel();
} catch (err) {
console.error(`${__filename}/init error:`, err?.message);
}
}
getConnectionString() {
const {
protocol = 'amqp:',
hostname,
vhost = '',
port = 5672,
user,
password,
connectionName,
} = this.connectionArgs;
return `${protocol}//${user}:${password}@${hostname}:${port}/${vhost}?${this.connectionQuery(connectionName)}`;
}
connectionQuery(connectionName) {
const clientProperties = `${encodeURIComponent(`{"connection_name":"${connectionName}"}`)}`;
return `heartbeat=${this.#HEARTBEAT}&connection_timeout=${this.#CONNECTION_TIMEOUT}&client_properties=${clientProperties}`;
}
/**
* @returns {RabbitMQ|null} - active instance of RabbitMQ or null
*/
static getActiveInstance() {
return RabbitMQ.activeInstance;
}
/**
* Method for initialization and obtaining the active instance of RabbitMQ.
* @param {object} connection - Connection configuration.
* @returns {RabbitMQ} - Active instance of RabbitMQ.
*/
static async initAndGetInstance(connection) {
if (!RabbitMQ.activeInstance) {
RabbitMQ.activeInstance = new RabbitMQ(connection);
await RabbitMQ.activeInstance.init();
}
return RabbitMQ.activeInstance;
}
/**
* Binding a queue to an exchange with the specified parameters.
* @param {object} bindConfig - Configuration for binding a queue to an exchange.
* @param {string} bindConfig.queue - Queue name.
* @param {string} bindConfig.source - Exchange name to which the queue is bound.
* @param {string} bindConfig.pattern - Binding pattern (routing key or pattern).
*/
async bindQueue(bindConfig) {
try {
const { queue, source, pattern } = bindConfig;
await this.channel.bindQueue(queue, source, pattern);
} catch (err) {
console.error(`${__filename}/bindQueue error:`, err?.message);
}
}
/**
* Checking the existence of a queue and creating it if it does not exist.
* @param {object} queueConfig - Queue configuration.
* @param {string} queueConfig.name - Queue name.
* @param {boolean} [queueConfig.durable=true] - Marks the queue as durable (persisted).
* @param {object} [queueConfig.arguments] - Additional arguments for the queue.
*/
async assertQueue(queueConfig) {
try {
const { name, durable = true, arguments: queueArguments } = queueConfig;
await this.channel.assertQueue(name, { durable, arguments: queueArguments });
} catch (err) {
console.error(`${__filename}/assertQueue error:`, err?.message);
}
}
/**
* Deleting a queue.
* @param {String} queue - Queue name.
* @param {Object} options - Additional queue options (ifUnused, ifEmpty, etc.).
*/
async deleteQueue(queue, options) {
try {
await this.channel.deleteQueue(queue, options);
} catch (err) {
console.error(`${__filename}/deleteQueue error:`, err?.message);
}
}
/**
* Checking the existence of an exchange and creating it if it does not exist.
* @param {string} exchange - Exchange name.
* @param {string} type - Exchange type (e.g., 'direct', 'fanout', 'topic').
* @param {object} [options] - Additional options for creating the exchange.
*/
async assertExchange({ name: exchange, type, options }) {
try {
await this.channel.assertExchange(exchange, type, options);
} catch (err) {
console.error(`${__filename}/assertExchange error:`, err?.message);
}
}
/**
* Consuming messages from a queue with the ability to specify parameters.
* @param {object|string} queueConfig - Queue configuration or just the queue name
* (if specified as a string).
* @param {function} callback - Message handler function.
* @param {object} consumerOptions - additional configurations for consume setting up
*/
async consume(queueConfig, callback, consumerOptions) {
try {
const {
name,
durable = true,
arguments: queueArguments,
prefetch = 1,
} = typeof queueConfig === 'string' ? { name: queueConfig } : queueConfig;
this.channel.prefetch(prefetch);
await this.channel.assertQueue(name, {
durable,
arguments: queueArguments,
});
await this.channel.consume(name, callback, consumerOptions);
} catch (err) {
console.error(`${__filename}/consume error:`, err?.message);
}
}
/**
* Sending a message to a queue.
* @param {string} queue - Queue name.
* @param {object} message - Message object.
* @param {object} [options] - Options for sending the message.
*/
async sendToQueue(queue, message, options) {
try {
const messageBuffer = Buffer.from(JSON.stringify(message));
return this.channel.sendToQueue(queue, messageBuffer, options);
} catch (err) {
console.error(`${__filename}/sendToQueue error:`, err?.message);
return false;
}
}
/**
* Confirming message receipt.
* @param {object} msg - Message object.
*/
async ack(msg) {
try {
await this.channel.ack(msg);
} catch (err) {
console.error(`${__filename}/ack error:`, err?.message);
}
}
/**
* Negative message acknowledgment.
* @param {object} msg - Message object.
* @param {boolean} [requeue=true] - Requeue the message (default is true).
*/
async nack(msg, requeue = true) {
try {
await this.channel.nack(msg, false, requeue);
} catch (err) {
console.error(`${__filename}/nack error:`, err?.message);
}
}
/**
* Getting the message object as JSON.
* @param {object} message - Message object.
* @returns {object|null} - Message object as JSON or null in case of error.
*/
// eslint-disable-next-line class-methods-use-this
getMsgObj(message) {
try {
return JSON.parse(message.content.toString());
} catch (err) {
console.error(`${__filename}/getMsgObj error:`, err?.message);
return null;
}
}
/**
* @method closeConnection
* @desc Closes the channel and connection if they exist
* @return {Promise<void>}
*/
async closeConnection() {
try {
if (this.channel) {
await this.channel.close();
this.channel = null;
}
if (this.connection) {
await this.connection.close();
this.connection = null;
}
} catch (err) {
console.error(`${__filename}/closeConnection error:`, err?.message);
}
}
}
module.exports = RabbitMQ;