redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
245 lines • 11.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Consumer = void 0;
const redis_smq_common_1 = require("redis-smq-common");
const configuration_js_1 = require("../config-manager/configuration.js");
const _parse_queue_extended_params_js_1 = require("../queue-manager/_/_parse-queue-extended-params.js");
const message_handler_runner_js_1 = require("./message-handler-runner/message-handler-runner.js");
const multiplexed_message_handler_runner_js_1 = require("./message-handler-runner/multiplexed-message-handler-runner.js");
const event_publisher_js_1 = require("./event-publisher.js");
const redis_keys_js_1 = require("../common/redis/redis-keys/redis-keys.js");
const heartbeat_js_1 = require("../common/heartbeat/heartbeat.js");
const heartbeat_event_publisher_js_1 = require("./heartbeat-event-publisher.js");
const _parse_consumer_options_js_1 = require("./_/_parse-consumer-options.js");
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");
class Consumer extends redis_smq_common_1.Runnable {
constructor(consumerOptions) {
super();
this.heartbeat = null;
this.redisClient = null;
this.initRedisClient = (cb) => {
redis_connection_pool_js_1.RedisConnectionPool.getInstance().acquire(connection_pool_js_1.ERedisConnectionAcquisitionMode.SHARED, (err, client) => {
if (err)
return cb(err);
if (!client)
return cb(new redis_smq_common_1.CallbackEmptyReplyError());
this.redisClient = client;
cb();
});
};
this.releaseRedisClient = (cb) => {
if (this.redisClient) {
redis_connection_pool_js_1.RedisConnectionPool.getInstance().release(this.redisClient);
this.redisClient = null;
}
cb();
};
this.setUpHeartbeat = (cb) => {
if (!this.redisClient)
return cb(new redis_smq_common_1.PanicError({
message: 'Redis client is not initialized',
}));
this.logger.debug('Setting up consumer heartbeat');
const { keyConsumerHeartbeat } = redis_keys_js_1.redisKeys.getConsumerKeys(this.id);
try {
this.heartbeat = (0, heartbeat_js_1.HeartbeatFactory)(this.redisClient, this.logger, {
heartbeatKey: keyConsumerHeartbeat,
componentId: this.id,
componentType: this.constructor.name,
heartbeatTTL: this.consumerOptions.heartbeatTTL,
}, heartbeat_event_publisher_js_1.heartbeatEventPublisher);
this.heartbeat.on('heartbeat.error', (err) => {
this.logger.error(`Heartbeat error: ${err.message}`);
this.handleError(err);
});
this.logger.debug('Starting heartbeat');
this.heartbeat.run((err) => {
if (err) {
this.logger.error(`Failed to start heartbeat: ${err.message}`);
cb(err);
}
else {
this.logger.debug('Heartbeat started successfully');
cb();
}
});
}
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
cb(err);
}
};
this.shutdownHeartbeat = (cb) => {
if (this.heartbeat) {
this.logger.debug('Shutting down heartbeat');
this.heartbeat.shutdown((err) => {
if (err) {
this.logger.warn(`Error during heartbeat shutdown: ${err.message}`);
}
this.logger.debug('Heartbeat shut down');
this.heartbeat = null;
cb();
});
}
else {
this.logger.debug('No heartbeat to shut down');
cb();
}
};
this.runMessageHandlers = (cb) => {
this.logger.debug('Starting message handlers');
this.messageHandlerRunner.run((err) => {
if (err) {
this.logger.error(`Failed to start message handlers: ${err.message}`);
cb(err);
}
else {
this.logger.debug('Message handlers started successfully');
cb();
}
});
};
this.shutdownMessageHandlers = (cb) => {
this.logger.debug('Shutting down message handlers');
this.messageHandlerRunner.shutdown((err) => {
if (err) {
this.logger.warn(`Error during message handlers shutdown: ${err.message}`);
}
this.logger.debug('Message handlers shut down');
cb();
});
};
this.consumerOptions = (0, _parse_consumer_options_js_1._parseConsumerOptions)(consumerOptions, Consumer.defaultOptions);
const config = configuration_js_1.Configuration.getConfig();
this.logger = (0, redis_smq_common_1.createLogger)(config.logger, `${this.constructor.name.toLowerCase()}-${this.id}`);
this.consumerContext = {
consumerId: this.getId(),
config: config,
logger: this.logger,
consumerOptions: this.consumerOptions,
};
(0, event_publisher_js_1.eventPublisher)(this);
this.messageHandlerRunner = this.consumerOptions.enableMultiplexing
? new multiplexed_message_handler_runner_js_1.MultiplexedMessageHandlerRunner(this.consumerContext)
: new message_handler_runner_js_1.MessageHandlerRunner(this.consumerContext);
this.messageHandlerRunner.on('consumer.messageHandlerRunner.error', (err) => {
this.logger.error(`Message handler runner error: ${err.message}`);
this.handleError(err);
});
this.logger.info(`Consumer initialized${this.consumerOptions.enableMultiplexing ? ' with multiplexing enabled' : ''}`);
}
goingUp() {
return super.goingUp().concat([
(cb) => {
this.logger.debug(`Emitting consumer.goingUp event for consumer ${this.id}`);
this.emit('consumer.goingUp', this.id);
cb();
},
this.initRedisClient,
this.setUpHeartbeat,
this.runMessageHandlers,
]);
}
goingDown() {
this.emit('consumer.goingDown', this.id);
return [
this.shutdownMessageHandlers,
this.shutdownHeartbeat,
this.releaseRedisClient,
].concat(super.goingDown());
}
finalizeUp() {
super.finalizeUp();
this.emit('consumer.up', this.id);
}
finalizeDown() {
super.finalizeDown();
this.emit('consumer.down', this.id);
}
handleError(err) {
if (!this.isOperational())
return;
this.logger.error(`Consumer error: ${err.message}`, err);
this.logger.debug(`Emitting consumer.error event for consumer ${this.id}`);
this.emit('consumer.error', err, this.id);
super.handleError(err);
}
consume(queue, messageHandler, cb) {
return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => {
this.logger.info(`Setting up consumption for queue: ${typeof queue === 'string' ? queue : JSON.stringify(queue)}`);
const parsedQueueParams = (0, _parse_queue_extended_params_js_1._parseQueueExtendedParams)(queue);
if (parsedQueueParams instanceof Error) {
this.logger.error(`Failed to parse queue parameters: ${parsedQueueParams.message}`);
callback(parsedQueueParams);
}
else {
this.logger.debug(`Adding message handler for queue: ${JSON.stringify(parsedQueueParams)}`);
this.messageHandlerRunner.addMessageHandler(parsedQueueParams, messageHandler, (err) => {
if (err) {
this.logger.error(`Failed to add message handler: ${err.message}`);
return callback(err);
}
this.logger.info(`Successfully set up consumption for queue: ${parsedQueueParams.queueParams.name}@${parsedQueueParams.queueParams.ns}${parsedQueueParams.groupId ? `, group: ${parsedQueueParams.groupId}` : ''}`);
callback();
});
}
});
}
cancel(queue, cb) {
return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => {
this.logger.info(`Canceling consumption for queue: ${typeof queue === 'string' ? queue : JSON.stringify(queue)}`);
const parsedQueueParams = (0, _parse_queue_extended_params_js_1._parseQueueExtendedParams)(queue);
if (parsedQueueParams instanceof Error) {
this.logger.error(`Failed to parse queue parameters: ${parsedQueueParams.message}`);
callback(parsedQueueParams);
return;
}
this.logger.debug(`Removing message handler for queue: ${JSON.stringify(parsedQueueParams)}`);
this.messageHandlerRunner.removeMessageHandler(parsedQueueParams, (err) => {
if (err) {
this.logger.error(`Failed to remove message handler: ${err.message}`);
callback(err);
}
else {
this.logger.info(`Successfully canceled consumption for queue: ${parsedQueueParams.queueParams.name}@${parsedQueueParams.queueParams.ns}${parsedQueueParams.groupId ? `, group: ${parsedQueueParams.groupId}` : ''}`);
callback();
}
});
});
}
getQueues() {
this.logger.debug('Getting list of queues being consumed');
const queues = this.messageHandlerRunner.getQueues();
this.logger.debug(`Consumer is handling ${queues.length} queues: ${JSON.stringify(queues)}`);
return queues;
}
getQueuesWithStatus() {
this.logger.debug('Getting list of queues being consumed with consumption status...');
const queues = this.messageHandlerRunner.getQueueWithStatus();
this.logger.debug(`Consumer is handling ${queues.length} queues: ${JSON.stringify(queues)}`);
return queues;
}
static setDefaultOptions(options) {
this.defaultOptions = (0, _parse_consumer_options_js_1._parseConsumerOptions)(options, Consumer.defaultOptions);
}
static getDefaultOptions() {
return Object.assign({}, this.defaultOptions);
}
}
exports.Consumer = Consumer;
Consumer.defaultOptions = {
enableMultiplexing: false,
heartbeatTTL: 60000,
batchAcks: {
enabled: true,
batchSize: 100,
batchTimeoutMs: 10000,
},
batchUnacks: {
enabled: true,
batchSize: 100,
batchTimeoutMs: 10000,
},
};
//# sourceMappingURL=consumer.js.map