redis-smq
Version:
A simple high-performance Redis message queue for Node.js.
215 lines • 10.6 kB
JavaScript
import { async, logger, withRedisClient, } from 'redis-smq-common';
import { redisKeys } from '../../../common/redis-keys/redis-keys.js';
import { Configuration } from '../../../config/index.js';
import { _deleteMessage } from '../../message/_/_delete-message.js';
import { _parseQueueExtendedParams } from '../../queue/_/_parse-queue-extended-params.js';
import { _validateQueueExtendedParams } from '../_/_validate-queue-extended-params.js';
export class QueueMessagesManagerAbstract {
redisClient;
message;
requireGroupId = false;
redisKey;
messageStorage;
logger;
constructor(redisClient, messagesStorage, message, redisKey) {
this.redisClient = redisClient;
this.messageStorage = messagesStorage;
this.message = message;
this.redisKey = redisKey;
this.logger = logger.getLogger(Configuration.getSetConfig().logger, this.constructor.name.toLowerCase());
this.redisClient.on('error', (err) => this.logger.error(err));
}
getTotalPages(pageSize, totalItems) {
if (pageSize <= 0) {
return 1;
}
const totalPages = Math.ceil(totalItems / pageSize);
return totalPages > 0 ? totalPages : 1;
}
getPaginationParams(cursor, totalItems, pageSize) {
const totalPages = this.getTotalPages(pageSize, totalItems);
const currentPage = cursor < 1 || cursor > totalPages ? 1 : cursor;
const offsetStart = (currentPage - 1) * pageSize;
const offsetEnd = offsetStart + pageSize - 1;
return { offsetStart, offsetEnd, currentPage, totalPages, pageSize };
}
getMessagesIds(queue, page, pageSize, cb) {
this.logger.debug(`Getting message IDs for queue ${queue.queueParams.name}, page ${page}, size ${pageSize}`);
const keys = redisKeys.getQueueKeys(queue.queueParams, queue.groupId);
const keyVal = keys[this.redisKey];
async.waterfall([
(next) => {
this.logger.debug(`Counting messages for queue ${queue.queueParams.name}`);
this.messageStorage.count(keyVal, next);
},
(totalItems, next) => {
this.logger.debug(`Found ${totalItems} total messages for queue ${queue.queueParams.name}`);
const { currentPage, offsetStart, offsetEnd, totalPages } = this.getPaginationParams(page, totalItems, pageSize);
const nextCursor = currentPage < totalPages ? currentPage + 1 : 0;
if (totalItems === 0) {
this.logger.debug(`No messages found for queue ${queue.queueParams.name}`);
return next(null, {
cursor: nextCursor,
totalItems,
items: [],
});
}
this.logger.debug(`Fetching messages for queue ${queue.queueParams.name} from index ${offsetStart} to ${offsetEnd}`);
this.messageStorage.fetchItems(keyVal, { page: currentPage, pageSize, offsetStart, offsetEnd }, (err, items) => {
if (err) {
this.logger.error(`Error fetching messages: ${err.message}`);
return next(err);
}
this.logger.debug(`Retrieved ${items?.length || 0} messages for queue ${queue.queueParams.name}`);
next(null, {
cursor: nextCursor,
totalItems,
items: items ?? [],
});
});
},
], (err, result) => {
if (err) {
this.logger.error(`Error in getMessagesIds: ${err.message}`);
}
cb(err, result);
});
}
purge(queue, cb) {
const parsedParams = _parseQueueExtendedParams(queue);
if (parsedParams instanceof Error) {
this.logger.error(`Error parsing queue parameters: ${parsedParams.message}`);
return cb(parsedParams);
}
this.logger.info(`Purging messages from queue ${parsedParams.queueParams.name}`);
withRedisClient(this.redisClient, (client, cb) => {
_validateQueueExtendedParams(client, parsedParams, this.requireGroupId, (err) => {
if (err) {
this.logger.error(`Error validating queue parameters: ${err.message}`);
return cb(err);
}
this._purgeMessages(client, parsedParams, cb);
});
}, cb);
}
_purgeMessages(client, parsedParams, cb) {
this.logger.debug(`Starting batch purge for queue ${parsedParams.queueParams.name}`);
const purgeBatch = (cursor = '0') => {
this.logger.debug(`Processing purge batch with cursor ${cursor} for queue ${parsedParams.queueParams.name}`);
async.waterfall([
(next) => {
this.getMessagesIds(parsedParams, Number(cursor), 1000, next);
},
(page, next) => {
const { items, cursor: nextCursor } = page;
if (items.length === 0) {
this.logger.debug(`No messages to purge in this batch for queue ${parsedParams.queueParams.name}`);
return next(null, String(nextCursor));
}
this.logger.debug(`Deleting batch of ${items.length} messages from queue ${parsedParams.queueParams.name}`);
_deleteMessage(client, items, (err, reply) => {
if (err) {
this.logger.error(`Error deleting messages: ${err.message}`);
return next(err);
}
this.logger.debug('Messages deletion completed', reply);
next(null, String(nextCursor));
});
},
], (err, nextCursor) => {
if (err) {
this.logger.error(`Error in purge batch: ${err.message}`);
return cb(err);
}
if (nextCursor !== '0') {
this.logger.debug(`More messages to purge, continuing with cursor ${nextCursor} for queue ${parsedParams.queueParams.name}`);
purgeBatch(String(nextCursor));
}
else {
this.logger.info(`Successfully purged all messages from queue ${parsedParams.queueParams.name}`);
cb();
}
});
};
purgeBatch('0');
}
getMessages(queue, page, pageSize, cb) {
const parsedParams = _parseQueueExtendedParams(queue);
if (parsedParams instanceof Error) {
this.logger.error(`Error parsing queue parameters: ${parsedParams.message}`);
return cb(parsedParams);
}
this.logger.debug(`Getting messages for queue ${parsedParams.queueParams.name}, page ${page}, size ${pageSize}`);
withRedisClient(this.redisClient, (client, cb) => {
_validateQueueExtendedParams(client, parsedParams, this.requireGroupId, (err) => {
if (err) {
this.logger.error(`Error validating queue parameters: ${err.message}`);
return cb(err);
}
async.withCallback((cb) => this.getMessagesIds(parsedParams, page, pageSize, cb), (pageResult, cb) => {
if (pageResult.items.length === 0) {
this.logger.debug(`No messages found for queue ${parsedParams.queueParams.name} on page ${page}`);
return cb(null, { ...pageResult, items: [] });
}
this.logger.debug(`Retrieving ${pageResult.items.length} message details for queue ${parsedParams.queueParams.name}`);
this.message.getMessagesByIds(pageResult.items, (err, messages) => {
if (err) {
this.logger.error(`Error getting message details: ${err.message}`);
return cb(err);
}
this.logger.debug(`Successfully retrieved ${messages?.length || 0} message details for queue ${parsedParams.queueParams.name}`);
cb(null, { ...pageResult, items: messages ?? [] });
});
}, cb);
});
}, cb);
}
countMessages(queue, cb) {
const parsedParams = _parseQueueExtendedParams(queue);
if (parsedParams instanceof Error) {
this.logger.error(`Error parsing queue parameters: ${parsedParams.message}`);
return cb(parsedParams);
}
this.logger.debug(`Counting messages for queue ${parsedParams.queueParams.name}`);
withRedisClient(this.redisClient, (client, cb) => {
_validateQueueExtendedParams(client, parsedParams, this.requireGroupId, (err) => {
if (err) {
this.logger.error(`Error validating queue parameters: ${err.message}`);
return cb(err);
}
const keys = redisKeys.getQueueKeys(parsedParams.queueParams, parsedParams.groupId);
const keyVal = keys[this.redisKey];
this.messageStorage.count(keyVal, (err, count) => {
if (err) {
this.logger.error(`Error counting messages: ${err.message}`);
return cb(err);
}
this.logger.debug(`Queue ${parsedParams.queueParams.name} has ${count} messages`);
cb(null, count);
});
});
}, cb);
}
shutdown(cb) {
this.logger.info('Shutting down queue messages manager');
async.series([
(next) => {
this.logger.debug('Shutting down message handler');
this.message.shutdown(next);
},
(next) => {
this.logger.debug('Shutting down Redis client');
this.redisClient.shutdown(next);
},
], (err) => {
if (err) {
this.logger.error(`Error during shutdown: ${err.message}`);
}
else {
this.logger.info('Queue messages manager shutdown complete');
}
cb(err);
});
}
}
//# sourceMappingURL=queue-messages-manager-abstract.js.map