redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
246 lines • 11.2 kB
JavaScript
"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