UNPKG

redis-smq

Version:

A high-performance, reliable, and scalable message queue for Node.js.

246 lines 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Producer = void 0; const redis_smq_common_1 = require("redis-smq-common"); const redis_connection_pool_js_1 = require("../common/redis/redis-connection-pool/redis-connection-pool.js"); const connection_pool_js_1 = require("../common/redis/redis-connection-pool/types/connection-pool.js"); const configuration_js_1 = require("../config-manager/configuration.js"); const index_js_1 = require("../errors/index.js"); const index_js_2 = require("../exchange/index.js"); const message_envelope_js_1 = require("../message/message-envelope.js"); const _publish_message_js_1 = require("./_/_publish-message.js"); const event_publisher_js_1 = require("./event-publisher.js"); const pub_sub_target_resolver_js_1 = require("./pub-sub-target-resolver.js"); class Producer extends redis_smq_common_1.Runnable { constructor() { super(); this.pubSubTargetResolver = null; this.redisClient = null; this._runPubSubTargetResolver = (cb) => { this.logger.debug('Starting PubSubTargetResolver...'); this.pubSubTargetResolver = new pub_sub_target_resolver_js_1.PubSubTargetResolver(this, this.logger); this.pubSubTargetResolver.run((err) => { if (err) { this.logger.error('Failed to start PubSubTargetResolver.', err); } else { this.logger.debug('PubSubTargetResolver has been started.'); } cb(err); }); }; this._shutdownPubSubTargetResolver = (cb) => { if (this.pubSubTargetResolver) { this.logger.debug('Shutting down PubSubTargetResolver...'); this.pubSubTargetResolver.shutdown(() => { this.logger.debug('PubSubTargetResolver has been shut down.'); this.pubSubTargetResolver = null; cb(); }); } else { cb(); } }; this.logger = (0, redis_smq_common_1.createLogger)(configuration_js_1.Configuration.getConfig().logger, `${this.constructor.name}-${this.getId()}`); this.directExchange = new index_js_2.ExchangeDirect(); this.topicExchange = new index_js_2.ExchangeTopic(); this.fanoutExchange = new index_js_2.ExchangeFanout(); (0, event_publisher_js_1.eventPublisher)(this); this.logger.info(`Producer initialized`); } getRedisClient() { if (!this.redisClient) throw new redis_smq_common_1.PanicError({ message: 'A RedisClient instance is required.' }); return this.redisClient; } goingUp() { return super.goingUp().concat([ (cb) => { redis_connection_pool_js_1.RedisConnectionPool.getInstance().acquire(connection_pool_js_1.ERedisConnectionAcquisitionMode.SHARED, (err, client) => { if (err) cb(err); else { this.redisClient = client !== null && client !== void 0 ? client : null; cb(); } }); }, (cb) => { this.emit('producer.goingUp', this.id); cb(); }, this._runPubSubTargetResolver, ]); } finalizeUp() { super.finalizeUp(); this.emit('producer.up', this.id); } goingDown() { this.emit('producer.goingDown', this.id); return [ this._shutdownPubSubTargetResolver, (cb) => { if (this.redisClient) { redis_connection_pool_js_1.RedisConnectionPool.getInstance().release(this.redisClient); this.redisClient = null; } cb(); }, ].concat(super.goingDown()); } finalizeDown() { super.finalizeDown(); this.emit('producer.down', this.id); } getPubSubTargetResolver() { if (!this.pubSubTargetResolver) { throw new redis_smq_common_1.PanicError({ message: 'Expected PubSubTargetResolver to be running.', }); } return this.pubSubTargetResolver; } _dispatch(message, queue, cb) { message.setDestinationQueue(queue); const messageId = message.getId(); const queueName = `${queue.name}@${queue.ns}`; const ts = Date.now(); if (message.isSchedulable()) { message .getMessageState() .setScheduledAt(ts) .setLastScheduledAt(ts) .incrScheduledTimes(); } else { message.getMessageState().setPublishedAt(ts); } (0, _publish_message_js_1._publishMessage)(this.getRedisClient(), message, this.logger, (err) => { if (err) { this.logger.error(`Failed to dispatch message [${messageId}] to queue [${queueName}].`, err); cb(err); } else { const action = message.isSchedulable() ? 'scheduled' : 'published'; this.logger.info(`Message [${messageId}] has been ${action} to queue [${queueName}].`); if (!message.isSchedulable()) { this.emit('producer.messagePublished', messageId, { queueParams: queue, groupId: message.getConsumerGroupId(), }, this.id); } cb(null, messageId); } }); } _produceToQueue(message, queue, cb) { const queueName = `${queue.name}@${queue.ns}`; const { isPubSub, targets } = this.getPubSubTargetResolver().resolveTargets(queue); if (isPubSub) { if (!targets.length) { this.logger.error(`Queue [${queueName}] is PUB/SUB but has no consumer groups.`); return cb(new index_js_1.QueueHasNoConsumerGroupsError()); } const ids = []; this.logger.debug(`Fanning out message to [${targets.length}] consumer groups for queue [${queueName}].`); redis_smq_common_1.async.eachOf(targets, (groupId, _, done) => { const msg = new message_envelope_js_1.MessageEnvelope(message).setConsumerGroupId(groupId); this._dispatch(msg, queue, (err, reply) => { if (err) return done(err); if (reply) ids.push(reply); done(); }); }, (err) => { if (err) { this.logger.error(`Failed to produce messages to one or more consumer groups for queue [${queueName}].`, err); return cb(err); } this.logger.info(`Successfully produced [${ids.length}] messages to queue [${queueName}].`); cb(null, ids); }); } else { const msg = new message_envelope_js_1.MessageEnvelope(message); this._dispatch(msg, queue, (err, reply) => { if (err) { this.logger.error(`Failed to produce message to queue [${queueName}].`, err); return cb(err); } this.logger.info(`Successfully produced message [${reply}] to queue [${queueName}].`); cb(null, reply ? [reply] : []); }); } } _matchExchangeQueues(exchange, routingKey, cb) { if (exchange.type === index_js_2.EExchangeType.DIRECT) { if (!routingKey) return cb(new index_js_1.RoutingKeyRequiredError()); return this.directExchange.matchQueues(exchange, routingKey, cb); } if (exchange.type === index_js_2.EExchangeType.TOPIC) { if (!routingKey) return cb(new index_js_1.RoutingKeyRequiredError()); return this.topicExchange.matchQueues(exchange, routingKey, cb); } if (exchange.type === index_js_2.EExchangeType.FANOUT) { return this.fanoutExchange.matchQueues(exchange, cb); } cb(new redis_smq_common_1.PanicError({ message: 'Unsupported exchange type.' })); } produce(msg, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { if (!this.isOperational()) { this.logger.error('Cannot produce message. Producer is not running.'); return callback(new index_js_1.ProducerNotRunningError()); } const queueParams = msg.getQueue(); if (queueParams) { return this._produceToQueue(msg, queueParams, callback); } const exchangeParams = msg.getExchange(); if (!exchangeParams) { this.logger.error('Message can not be produced without a queue or an exchange.'); return callback(new index_js_1.MessageExchangeRequiredError()); } this.logger.debug(`Looking up queues for exchange [${exchangeParams.name}@${exchangeParams.ns}]...`); this._matchExchangeQueues(exchangeParams, msg.getExchangeRoutingKey(), (err, queues) => { if (err) { this.logger.error('Failed to match queues for exchange.', err); return callback(err); } if (!(queues === null || queues === void 0 ? void 0 : queues.length)) { this.logger.error(`No queues found for exchange [${exchangeParams.name}@${exchangeParams.ns}].`); return callback(new index_js_1.NoMatchingQueuesError()); } this.logger.info(`Found [${queues.length}] matching queues for exchange.`); const messageIds = []; redis_smq_common_1.async.eachOf(queues, (queue, index, done) => { this.logger.debug(`Producing message to queue [${queue.name}@${queue.ns}] (${index + 1}/${queues.length}).`); this._produceToQueue(msg, queue, (err, reply) => { if (err) { this.logger.error(`Failed to produce message to queue [${queue.name}@${queue.ns}].`, err); return done(err); } if (reply) { messageIds.push(...reply); } done(); }); }, (err) => { if (err) { this.logger.error('An error occurred while producing messages to one or more queues.', err); return callback(err); } this.logger.info(`Successfully produced [${messageIds.length}] messages across [${queues.length}] queues.`); callback(null, messageIds); }); }); }); } } exports.Producer = Producer; //# sourceMappingURL=producer.js.map