UNPKG

redis-smq

Version:

A simple high-performance Redis message queue for Node.js.

182 lines 7.61 kB
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