redis-smq
Version:
A simple high-performance Redis message queue for Node.js.
182 lines • 7.61 kB
JavaScript
import { stat } from 'fs';
import path from 'path';
import { CallbackEmptyReplyError, Runnable, WorkerCallable, } from 'redis-smq-common';
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 { EMessageProperty, EMessagePropertyStatus, } from '../../../message/index.js';
import { EMessageUnknowledgmentAction, EMessageUnknowledgmentReason, } from '../../types/index.js';
import { ConsumerMessageHandlerFileError, ConsumerMessageHandlerFilenameExtensionError, } from '../errors/index.js';
import { processingQueue } from '../processing-queue/processing-queue.js';
import { eventBusPublisher } from './event-bus-publisher.js';
export class ConsumeMessage extends Runnable {
logger;
keyQueueProcessing;
keyQueueAcknowledged;
queue;
consumerId;
messageHandler;
messageHandlerId;
redisClient;
consumeMessageWorker = null;
constructor(redisClient, consumer, queue, messageHandlerId, messageHandler, logger, eventBus) {
super();
this.queue = queue;
this.consumerId = consumer.getId();
this.messageHandler = messageHandler;
this.messageHandlerId = messageHandlerId;
this.redisClient = redisClient;
const { keyQueueProcessing } = redisKeys.getQueueConsumerKeys(this.queue.queueParams, this.consumerId);
const { keyQueueAcknowledged } = redisKeys.getQueueKeys(this.queue.queueParams, this.queue.groupId);
this.keyQueueAcknowledged = keyQueueAcknowledged;
this.keyQueueProcessing = keyQueueProcessing;
this.logger = logger;
if (eventBus) {
eventBusPublisher(this, eventBus, logger);
}
}
getLogger() {
return this.logger;
}
acknowledgeMessage(message) {
const messageId = message.getId();
const { store, queueSize, expire } = Configuration.getSetConfig().messages.store.acknowledged;
const { keyMessage } = redisKeys.getMessageKeys(messageId);
const redisClient = this.redisClient.getInstance();
if (redisClient instanceof Error) {
this.handleError(redisClient);
return void 0;
}
redisClient.runScript(ELuaScriptName.ACKNOWLEDGE_MESSAGE, [this.keyQueueProcessing, this.keyQueueAcknowledged, keyMessage], [
EMessageProperty.STATUS,
EMessagePropertyStatus.ACKNOWLEDGED,
Number(store),
expire,
queueSize * -1,
], (err) => {
if (err)
this.handleError(err);
else {
this.emit('consumer.consumeMessage.messageAcknowledged', messageId, this.queue, this.messageHandlerId, this.consumerId);
}
});
}
unacknowledgeMessage(msg, unknowledgmentReason) {
const redisClient = this.redisClient.getInstance();
if (redisClient instanceof Error) {
this.handleError(redisClient);
return void 0;
}
processingQueue.unknowledgeMessage(redisClient, this.consumerId, [this.queue.queueParams], this.logger, unknowledgmentReason, (err, reply) => {
if (err)
this.handleError(err);
else if (!reply)
this.handleError(new CallbackEmptyReplyError());
else {
const messageId = msg.getId();
this.emit('consumer.consumeMessage.messageUnacknowledged', messageId, this.queue, this.messageHandlerId, this.consumerId, unknowledgmentReason);
const unknowledgment = reply[messageId];
if (unknowledgment.action === EMessageUnknowledgmentAction.DEAD_LETTER) {
this.emit('consumer.consumeMessage.messageDeadLettered', messageId, this.queue, this.messageHandlerId, this.consumerId, unknowledgment.deadLetterReason);
}
else if (unknowledgment.action === EMessageUnknowledgmentAction.DELAY) {
this.emit('consumer.consumeMessage.messageDelayed', messageId, this.queue, this.messageHandlerId, this.consumerId);
}
else {
this.emit('consumer.consumeMessage.messageRequeued', messageId, this.queue, this.messageHandlerId, this.consumerId);
}
}
});
}
getConsumeMessageWorker(messageHandlerFilename) {
if (!this.consumeMessageWorker) {
this.consumeMessageWorker = new WorkerCallable(messageHandlerFilename);
}
return this.consumeMessageWorker;
}
invokeMessageHandler(messageHandler, msg, cb) {
if (typeof messageHandler === 'string') {
this.getConsumeMessageWorker(messageHandler).call(msg, cb);
}
else
messageHandler(msg, cb);
}
consumeMessage(msg) {
let isTimeout = false;
let timer = null;
try {
const consumeTimeout = msg.producibleMessage.getConsumeTimeout();
if (consumeTimeout) {
timer = setTimeout(() => {
isTimeout = true;
timer = null;
this.unacknowledgeMessage(msg, EMessageUnknowledgmentReason.TIMEOUT);
}, consumeTimeout);
}
const onConsumed = (err) => {
if (this.isRunning() && !isTimeout) {
if (timer)
clearTimeout(timer);
if (err) {
this.logger.error(err);
this.unacknowledgeMessage(msg, EMessageUnknowledgmentReason.UNACKNOWLEDGED);
}
else {
this.acknowledgeMessage(msg);
}
}
};
this.invokeMessageHandler(this.messageHandler, msg.transfer(), onConsumed);
}
catch (error) {
this.logger.error(error);
this.unacknowledgeMessage(msg, EMessageUnknowledgmentReason.CONSUME_ERROR);
}
}
validateMessageHandler = (cb) => {
if (typeof this.messageHandler === 'string') {
if (!['.js', '.cjs'].includes(path.extname(this.messageHandler))) {
cb(new ConsumerMessageHandlerFilenameExtensionError());
}
else
stat(this.messageHandler, (err) => {
if (err)
cb(new ConsumerMessageHandlerFileError());
else
cb();
});
}
else
cb();
};
goingUp() {
return super
.goingUp()
.concat([this.redisClient.init, this.validateMessageHandler]);
}
goingDown() {
return [
(cb) => {
if (this.consumeMessageWorker) {
this.consumeMessageWorker.shutdown(cb);
}
else
cb();
},
].concat(super.goingDown());
}
handleError(err) {
this.emit('consumer.consumeMessage.error', err, this.consumerId, this.queue);
super.handleError(err);
}
handleReceivedMessage(message) {
if (this.isRunning()) {
if (message.getSetExpired()) {
this.unacknowledgeMessage(message, EMessageUnknowledgmentReason.TTL_EXPIRED);
}
else
this.consumeMessage(message);
}
}
}
//# sourceMappingURL=consume-message.js.map