redis-smq
Version:
A simple high-performance Redis message queue for Node.js.
259 lines • 13.2 kB
JavaScript
import { async, CallbackEmptyReplyError, logger, withEventBus, withRedisClient, } from 'redis-smq-common';
import { EventBus } from '../../common/index.js';
import { RedisClient } from '../../common/redis-client/redis-client.js';
import { ELuaScriptName } from '../../common/redis-client/scripts/scripts.js';
import { redisKeys } from '../../common/redis-keys/redis-keys.js';
import { Configuration } from '../../config/index.js';
import { _deleteQueue } from './_/_delete-queue.js';
import { _getQueueConsumerIds } from './_/_get-queue-consumer-ids.js';
import { _getQueueConsumers } from './_/_get-queue-consumers.js';
import { _getQueueProperties } from './_/_get-queue-properties.js';
import { _getQueues } from './_/_get-queues.js';
import { _parseQueueParams } from './_/_parse-queue-params.js';
import { _queueExists } from './_/_queue-exists.js';
import { QueueQueueExistsError } from './errors/index.js';
import { EQueueDeliveryModel, EQueueProperty, EQueueType, } from './types/index.js';
export class Queue {
redisClient;
eventBus;
logger;
constructor() {
this.logger = logger.getLogger(Configuration.getSetConfig().logger, this.constructor.name.toLowerCase());
this.logger.debug('Initializing Queue instance');
this.eventBus = new EventBus();
this.eventBus.on('error', (err) => {
this.logger.error(`EventBus error: ${err.message}`, err);
});
this.logger.debug('EventBus initialized');
this.redisClient = new RedisClient();
this.redisClient.on('error', (err) => {
this.logger.error(`Redis client error: ${err.message}`, err);
});
this.logger.debug('Redis client initialized');
this.logger.info('Queue instance initialized successfully');
}
save(queue, queueType, deliveryModel, cb) {
const queueDesc = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Saving queue: ${queueDesc}, type: ${EQueueType[queueType]}, delivery model: ${EQueueDeliveryModel[deliveryModel]}`);
const queueParams = _parseQueueParams(queue);
if (queueParams instanceof Error) {
this.logger.error(`Error parsing queue parameters for ${queueDesc}: ${queueParams.message}`, queueParams);
return cb(queueParams);
}
const queueName = `${queueParams.name}@${queueParams.ns}`;
this.logger.debug(`Parsed queue parameters: ${queueName}`);
withRedisClient(this.redisClient, (client, cb) => {
async.withCallback((cb) => {
const { keyQueueProperties } = redisKeys.getQueueKeys(queueParams, null);
const { keyNamespaces, keyQueues } = redisKeys.getMainKeys();
const { keyNamespaceQueues } = redisKeys.getNamespaceKeys(queueParams.ns);
const queueParamsStr = JSON.stringify(queueParams);
this.logger.debug(`Executing CREATE_QUEUE script for ${queueName} with keys: [${keyNamespaces}, ${keyNamespaceQueues}, ${keyQueues}, ${keyQueueProperties}]`);
client.runScript(ELuaScriptName.CREATE_QUEUE, [
keyNamespaces,
keyNamespaceQueues,
keyQueues,
keyQueueProperties,
], [
queueParams.ns,
queueParamsStr,
EQueueProperty.QUEUE_TYPE,
queueType,
EQueueProperty.DELIVERY_MODEL,
deliveryModel,
], cb);
}, (reply, cb) => {
if (reply !== 'OK') {
const error = new QueueQueueExistsError();
this.logger.error(`Queue ${queueName} already exists`, error);
return cb(error);
}
this.logger.debug(`Queue ${queueName} created successfully, retrieving properties`);
async.withCallback((cb) => this.getProperties(queueParams, cb), (properties, cb) => {
this.logger.debug(`Retrieved properties for queue ${queueName}, emitting queue.queueCreated event`);
withEventBus(this.eventBus, (eventBus, cb) => {
this.logger.debug(`Emitting queue.queueCreated event for ${queueName}`);
eventBus.emit('queue.queueCreated', queueParams, properties);
this.logger.info(`Queue ${queueName} successfully created with type=${EQueueType[queueType]}, deliveryModel=${EQueueDeliveryModel[deliveryModel]}`);
cb(null, { queue: queueParams, properties });
}, cb);
}, cb);
}, cb);
}, cb);
}
exists(queue, cb) {
const queueDesc = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Checking if queue exists: ${queueDesc}`);
const queueParams = _parseQueueParams(queue);
if (queueParams instanceof Error) {
this.logger.error(`Error parsing queue parameters for ${queueDesc}: ${queueParams.message}`, queueParams);
cb(queueParams);
}
else {
const queueName = `${queueParams.name}@${queueParams.ns}`;
this.logger.debug(`Parsed queue parameters: ${queueName}`);
withRedisClient(this.redisClient, (client, cb) => {
this.logger.debug(`Checking if queue ${queueName} exists in Redis`);
_queueExists(client, queueParams, (err, exists) => {
if (err) {
this.logger.error(`Error checking if queue ${queueName} exists: ${err.message}`, err);
cb(err);
}
else {
this.logger.debug(`Queue ${queueName} exists: ${exists}`);
cb(null, exists);
}
});
}, cb);
}
}
delete(queue, cb) {
const queueDesc = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Deleting queue: ${queueDesc}`);
const queueParams = _parseQueueParams(queue);
if (queueParams instanceof Error) {
this.logger.error(`Error parsing queue parameters for ${queueDesc}: ${queueParams.message}`, queueParams);
return cb(queueParams);
}
const queueName = `${queueParams.name}@${queueParams.ns}`;
this.logger.debug(`Parsed queue parameters: ${queueName}`);
withRedisClient(this.redisClient, (client, cb) => {
this.logger.debug(`Executing delete queue operation for ${queueName}`);
_deleteQueue(client, queueParams, undefined, (err, multi) => {
if (err) {
this.logger.error(`Error preparing delete operation for queue ${queueName}: ${err.message}`, err);
return cb(err);
}
if (!multi) {
const error = new CallbackEmptyReplyError();
this.logger.error(`Multi command is empty for deleting queue ${queueName}`, error);
return cb(error);
}
this.logger.debug(`Executing multi command to delete queue ${queueName}`);
multi.exec((err) => {
if (err) {
this.logger.error(`Error executing multi command for deleting queue ${queueName}: ${err.message}`, err);
return cb(err);
}
this.logger.debug(`Queue ${queueName} deleted from Redis, emitting queue.queueDeleted event`);
withEventBus(this.eventBus, (eventBus, cb) => {
this.logger.debug(`Emitting queue.queueDeleted event for ${queueName}`);
eventBus.emit('queue.queueDeleted', queueParams);
this.logger.info(`Queue ${queueName} successfully deleted`);
cb();
}, cb);
});
});
}, cb);
}
getProperties(queue, cb) {
const queueDesc = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Getting properties for queue: ${queueDesc}`);
const queueParams = _parseQueueParams(queue);
if (queueParams instanceof Error) {
this.logger.error(`Error parsing queue parameters for ${queueDesc}: ${queueParams.message}`, queueParams);
cb(queueParams);
}
else {
const queueName = `${queueParams.name}@${queueParams.ns}`;
this.logger.debug(`Parsed queue parameters: ${queueName}`);
withRedisClient(this.redisClient, (client, cb) => {
this.logger.debug(`Retrieving properties for queue ${queueName}`);
_getQueueProperties(client, queueParams, (err, properties) => {
if (err) {
this.logger.error(`Error getting properties for queue ${queueName}: ${err.message}`, err);
cb(err);
}
else {
if (properties) {
this.logger.debug(`Retrieved properties for queue ${queueName}: type=${EQueueType[properties.queueType]}, deliveryModel=${EQueueDeliveryModel[properties.deliveryModel]}`);
}
else {
this.logger.debug(`No properties found for queue ${queueName}`);
}
cb(null, properties);
}
});
}, cb);
}
}
getQueues(cb) {
this.logger.debug('Getting all queues');
withRedisClient(this.redisClient, (client, cb) => {
this.logger.debug('Retrieving all queues from Redis');
_getQueues(client, (err, queues) => {
if (err) {
this.logger.error(`Error getting queues: ${err.message}`, err);
cb(err);
}
else {
const queueCount = queues?.length || 0;
this.logger.debug(`Retrieved ${queueCount} queues`);
cb(null, queues);
}
});
}, cb);
}
getConsumers(queue, cb) {
const queueDesc = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Getting consumers for queue: ${queueDesc}`);
const queueParams = _parseQueueParams(queue);
if (queueParams instanceof Error) {
this.logger.error(`Error parsing queue parameters for ${queueDesc}: ${queueParams.message}`, queueParams);
return cb(queueParams);
}
const queueName = `${queueParams.name}@${queueParams.ns}`;
this.logger.debug(`Parsed queue parameters: ${queueName}`);
withRedisClient(this.redisClient, (client, cb) => _getQueueConsumers(client, queueParams, cb), cb);
}
getConsumerIds(queue, cb) {
const queueDesc = typeof queue === 'string' ? queue : `${queue.name}@${queue.ns}`;
this.logger.debug(`Getting consumer Ids for queue: ${queueDesc}`);
const queueParams = _parseQueueParams(queue);
if (queueParams instanceof Error) {
this.logger.error(`Error parsing queue parameters for ${queueDesc}: ${queueParams.message}`, queueParams);
return cb(queueParams);
}
const queueName = `${queueParams.name}@${queueParams.ns}`;
this.logger.debug(`Parsed queue parameters: ${queueName}`);
withRedisClient(this.redisClient, (client, cb) => _getQueueConsumerIds(client, queueParams, cb), cb);
}
shutdown = (cb) => {
this.logger.info('Shutting down Queue instance');
async.series([
(next) => {
this.logger.debug('Shutting down Redis client');
this.redisClient.shutdown((err) => {
if (err) {
this.logger.error(`Error shutting down Redis client: ${err.message}`, err);
}
else {
this.logger.debug('Redis client shutdown successful');
}
next(err);
});
},
(next) => {
this.logger.debug('Shutting down EventBus');
this.eventBus.shutdown((err) => {
if (err) {
this.logger.error(`Error shutting down EventBus: ${err.message}`, err);
}
else {
this.logger.debug('EventBus shutdown successful');
}
next(err);
});
},
], (err) => {
if (err) {
this.logger.error(`Error during Queue shutdown: ${err.message}`, err);
}
else {
this.logger.info('Queue instance shutdown completed successfully');
}
cb(err);
});
};
}
//# sourceMappingURL=queue.js.map