UNPKG

redis-smq

Version:

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

331 lines 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExchangeFanOut = void 0; const redis_smq_common_1 = require("redis-smq-common"); const redis_keys_js_1 = require("../../../common/redis-keys/redis-keys.js"); const _get_queue_properties_js_1 = require("../../queue/_/_get-queue-properties.js"); const _parse_queue_params_js_1 = require("../../queue/_/_parse-queue-params.js"); const index_js_1 = require("../../queue/index.js"); const index_js_2 = require("../errors/index.js"); const exchange_abstract_js_1 = require("../exchange-abstract.js"); const _get_fan_out_exchange_queues_js_1 = require("./_/_get-fan-out-exchange-queues.js"); const _get_queue_fan_out_exchange_js_1 = require("./_/_get-queue-fan-out-exchange.js"); const _validate_exchange_fan_out_params_js_1 = require("./_/_validate-exchange-fan-out-params.js"); class ExchangeFanOut extends exchange_abstract_js_1.ExchangeAbstract { constructor() { super(); this.logger.info('ExchangeFanOut initialized'); } getQueues(exchangeParams, cb) { this.logger.debug(`Getting queues for exchange: ${exchangeParams}`); const fanOutName = (0, _validate_exchange_fan_out_params_js_1._validateExchangeFanOutParams)(exchangeParams); if (fanOutName instanceof Error) { this.logger.error(`Invalid exchange parameters: ${fanOutName.message}`); return cb(fanOutName); } (0, redis_smq_common_1.withRedisClient)(this.redisClient, (client, cb) => { this.logger.debug(`Fetching queues bound to exchange: ${fanOutName}`); (0, _get_fan_out_exchange_queues_js_1._getFanOutExchangeQueues)(client, fanOutName, (err, queues) => { if (err) { this.logger.error(`Error fetching bound queues: ${err.message}`); return cb(err); } this.logger.info(`Found ${(queues === null || queues === void 0 ? void 0 : queues.length) || 0} queues bound to exchange: ${fanOutName}`); if (queues && queues.length > 0) { this.logger.debug(`Bound queues: ${JSON.stringify(queues)}`); } cb(null, queues || []); }); }, cb); } saveExchange(exchangeParams, cb) { this.logger.debug(`Saving exchange: ${exchangeParams}`); const fanOutName = (0, _validate_exchange_fan_out_params_js_1._validateExchangeFanOutParams)(exchangeParams); if (fanOutName instanceof Error) { this.logger.error(`Invalid exchange parameters: ${fanOutName.message}`); return cb(fanOutName); } const { keyFanOutExchanges } = redis_keys_js_1.redisKeys.getMainKeys(); this.logger.debug(`Using Redis key: ${keyFanOutExchanges}`); (0, redis_smq_common_1.withRedisClient)(this.redisClient, (client, cb) => { this.logger.debug(`Adding exchange ${fanOutName} to set ${keyFanOutExchanges}`); client.sadd(keyFanOutExchanges, fanOutName, (err) => { if (err) { this.logger.error(`Failed to save exchange: ${err.message}`); return cb(err); } this.logger.info(`Exchange ${fanOutName} saved successfully`); cb(); }); }, cb); } deleteExchange(exchangeParams, cb) { this.logger.debug(`Deleting exchange: ${exchangeParams}`); const fanOutName = (0, _validate_exchange_fan_out_params_js_1._validateExchangeFanOutParams)(exchangeParams); if (fanOutName instanceof Error) { this.logger.error(`Invalid exchange parameters: ${fanOutName.message}`); return cb(fanOutName); } const { keyExchangeBindings } = redis_keys_js_1.redisKeys.getFanOutExchangeKeys(fanOutName); const { keyFanOutExchanges } = redis_keys_js_1.redisKeys.getMainKeys(); this.logger.debug(`Using Redis keys: ${keyFanOutExchanges}, ${keyExchangeBindings}`); (0, redis_smq_common_1.withRedisClient)(this.redisClient, (client, cb) => { this.logger.debug(`Watching keys for atomic operation: ${keyFanOutExchanges}, ${keyExchangeBindings}`); client.watch([keyFanOutExchanges, keyExchangeBindings], (err) => { if (err) { this.logger.error(`Failed to watch Redis keys: ${err.message}`); return cb(err); } this.logger.debug(`Checking if exchange ${fanOutName} has bound queues`); (0, _get_fan_out_exchange_queues_js_1._getFanOutExchangeQueues)(client, fanOutName, (err, reply = []) => { if (err) { this.logger.error(`Error checking bound queues: ${err.message}`); return cb(err); } if (reply.length) { this.logger.warn(`Cannot delete exchange ${fanOutName}: has ${reply.length} bound queues`); return cb(new index_js_2.ExchangeFanOutExchangeHasBoundQueuesError()); } this.logger.debug(`Executing multi commands to delete exchange ${fanOutName}`); const multi = client.multi(); multi.srem(keyFanOutExchanges, fanOutName); multi.del(keyExchangeBindings); multi.exec((err) => { if (err) { this.logger.error(`Failed to execute delete commands: ${err.message}`); return cb(err); } this.logger.info(`Exchange ${fanOutName} deleted successfully`); cb(); }); }); }); }, cb); } bindQueue(queue, exchangeParams, cb) { this.logger.debug(`Binding queue ${typeof queue === 'string' ? queue : JSON.stringify(queue)} to exchange: ${exchangeParams}`); const queueParams = (0, _parse_queue_params_js_1._parseQueueParams)(queue); const fanOutName = (0, _validate_exchange_fan_out_params_js_1._validateExchangeFanOutParams)(exchangeParams); if (queueParams instanceof Error) { this.logger.error(`Invalid queue parameters: ${queueParams.message}`); return cb(queueParams); } if (fanOutName instanceof Error) { this.logger.error(`Invalid exchange parameters: ${fanOutName.message}`); return cb(fanOutName); } const { keyQueueProperties } = redis_keys_js_1.redisKeys.getQueueKeys(queueParams, null); const { keyExchangeBindings } = redis_keys_js_1.redisKeys.getFanOutExchangeKeys(fanOutName); const { keyQueues, keyFanOutExchanges } = redis_keys_js_1.redisKeys.getMainKeys(); this.logger.debug(`Using Redis keys: ${keyQueues}, ${keyQueueProperties}, ${keyExchangeBindings}, ${keyFanOutExchanges}`); (0, redis_smq_common_1.withRedisClient)(this.redisClient, (client, cb) => { redis_smq_common_1.async.waterfall([ (cb) => { this.logger.debug(`Watching keys for atomic operation: ${keyQueues}, ${keyQueueProperties}, ${keyExchangeBindings}`); client.watch([keyQueues, keyQueueProperties, keyExchangeBindings], (err) => { if (err) { this.logger.error(`Failed to watch Redis keys: ${err.message}`); } cb(err); }); }, (_, cb) => { this.logger.debug(`Getting properties for queue: ${queueParams.name}`); (0, _get_queue_properties_js_1._getQueueProperties)(client, queueParams, (err, properties) => { if (err) { this.logger.error(`Failed to get queue properties: ${err.message}`); } cb(err, properties); }); }, (queueProperties, cb) => { this.logger.debug(`Checking queue type compatibility for exchange: ${fanOutName}`); (0, _get_fan_out_exchange_queues_js_1._getFanOutExchangeQueues)(client, fanOutName, (err, queues) => { if (err) { this.logger.error(`Error checking bound queues: ${err.message}`); return cb(err); } const eQueue = queues === null || queues === void 0 ? void 0 : queues.pop(); if (eQueue) { this.logger.debug(`Found existing queue ${eQueue.name}, checking type compatibility`); (0, _get_queue_properties_js_1._getQueueProperties)(client, eQueue, (err, exchangeQueueProperties) => { if (err) { this.logger.error(`Failed to get exchange queue properties: ${err.message}`); return cb(err); } if (!exchangeQueueProperties) { this.logger.error('Exchange queue properties returned empty'); return cb(new redis_smq_common_1.CallbackEmptyReplyError()); } if (exchangeQueueProperties.queueType !== queueProperties.queueType) { this.logger.warn(`Queue type mismatch: ${queueProperties.queueType} vs ${exchangeQueueProperties.queueType}`); return cb(new index_js_2.ExchangeFanOutQueueTypeError()); } this.logger.debug('Queue types are compatible'); cb(null, queueProperties); }); } else { this.logger.debug('No existing queues bound to exchange, proceeding with bind'); cb(null, queueProperties); } }); }, (queueProperties, cb) => { const currentExchangeParams = queueProperties.exchange; if (currentExchangeParams === fanOutName) { this.logger.info(`Queue ${queueParams.name} is already bound to exchange ${fanOutName}`); return cb(); } this.logger.debug(`Binding queue ${queueParams.name} to exchange ${fanOutName}`); const multi = client.multi(); const queueParamsStr = JSON.stringify(queueParams); multi.sadd(keyFanOutExchanges, fanOutName); multi.sadd(keyExchangeBindings, queueParamsStr); multi.hset(keyQueueProperties, String(index_js_1.EQueueProperty.EXCHANGE), fanOutName); if (currentExchangeParams) { this.logger.debug(`Queue was previously bound to exchange ${currentExchangeParams}, removing old binding`); const { keyExchangeBindings } = redis_keys_js_1.redisKeys.getFanOutExchangeKeys(currentExchangeParams); multi.srem(keyExchangeBindings, queueParamsStr); } multi.exec((err) => { if (err) { this.logger.error(`Failed to execute bind commands: ${err.message}`); } else { this.logger.info(`Queue ${queueParams.name} successfully bound to exchange ${fanOutName}`); } cb(err); }); }, ], (err) => { if (err) { this.logger.debug('Error occurred, unwatching Redis keys'); client.unwatch(() => cb(err)); } else { cb(); } }); }, cb); } unbindQueue(queue, exchangeParams, cb) { this.logger.debug(`Unbinding queue ${typeof queue === 'string' ? queue : JSON.stringify(queue)} from exchange: ${exchangeParams}`); const queueParams = (0, _parse_queue_params_js_1._parseQueueParams)(queue); const fanOutName = (0, _validate_exchange_fan_out_params_js_1._validateExchangeFanOutParams)(exchangeParams); if (queueParams instanceof Error) { this.logger.error(`Invalid queue parameters: ${queueParams.message}`); return cb(queueParams); } if (fanOutName instanceof Error) { this.logger.error(`Invalid exchange parameters: ${fanOutName.message}`); return cb(fanOutName); } const { keyQueueProperties } = redis_keys_js_1.redisKeys.getQueueKeys(queueParams, null); const { keyQueues } = redis_keys_js_1.redisKeys.getMainKeys(); const { keyExchangeBindings } = redis_keys_js_1.redisKeys.getFanOutExchangeKeys(fanOutName); this.logger.debug(`Using Redis keys: ${keyQueues}, ${keyQueueProperties}, ${keyExchangeBindings}`); (0, redis_smq_common_1.withRedisClient)(this.redisClient, (client, cb) => { redis_smq_common_1.async.series([ (cb) => { this.logger.debug(`Watching keys for atomic operation: ${keyQueues}, ${keyQueueProperties}, ${keyExchangeBindings}`); client.watch([keyQueues, keyQueueProperties, keyExchangeBindings], (err) => { if (err) { this.logger.error(`Failed to watch Redis keys: ${err.message}`); } cb(err); }); }, (cb) => { this.logger.debug(`Verifying queue ${queueParams.name} is bound to exchange ${fanOutName}`); (0, _get_queue_properties_js_1._getQueueProperties)(client, queueParams, (err, properties) => { if (err) { this.logger.error(`Failed to get queue properties: ${err.message}`); return cb(err); } if (!properties) { this.logger.error('Queue properties returned empty'); return cb(new redis_smq_common_1.CallbackEmptyReplyError()); } if (properties.exchange !== fanOutName) { this.logger.warn(`Queue ${queueParams.name} is not bound to exchange ${fanOutName} (bound to: ${properties.exchange || 'none'})`); return cb(new index_js_2.ExchangeQueueIsNotBoundToExchangeError()); } this.logger.debug(`Queue ${queueParams.name} is bound to exchange ${fanOutName}, proceeding with unbind`); cb(); }); }, (cb) => { this.logger.debug(`Executing unbind commands for queue ${queueParams.name} from exchange ${fanOutName}`); const multi = client.multi(); const queueParamsStr = JSON.stringify(queueParams); multi.srem(keyExchangeBindings, queueParamsStr); multi.hdel(keyQueueProperties, String(index_js_1.EQueueProperty.EXCHANGE)); multi.exec((err) => { if (err) { this.logger.error(`Failed to execute unbind commands: ${err.message}`); } else { this.logger.info(`Queue ${queueParams.name} successfully unbound from exchange ${fanOutName}`); } cb(err); }); }, ], (err) => { if (err) { this.logger.debug('Error occurred, unwatching Redis keys'); client.unwatch(() => cb(err)); } else { cb(); } }); }, cb); } getAllExchanges(cb) { this.logger.debug('Getting all fan-out exchanges'); const { keyFanOutExchanges } = redis_keys_js_1.redisKeys.getMainKeys(); this.logger.debug(`Using Redis key: ${keyFanOutExchanges}`); (0, redis_smq_common_1.withRedisClient)(this.redisClient, (client, cb) => { this.logger.debug(`Scanning all exchanges from set ${keyFanOutExchanges}`); client.sscanAll(keyFanOutExchanges, {}, (err, exchanges) => { if (err) { this.logger.error(`Failed to scan exchanges: ${err.message}`); return cb(err); } this.logger.info(`Found ${(exchanges === null || exchanges === void 0 ? void 0 : exchanges.length) || 0} fan-out exchanges`); if (exchanges && exchanges.length > 0) { this.logger.debug(`Exchanges: ${JSON.stringify(exchanges)}`); } cb(null, exchanges || []); }); }, cb); } getQueueExchange(queue, cb) { this.logger.debug(`Getting exchange for queue: ${typeof queue === 'string' ? queue : JSON.stringify(queue)}`); const queueParams = (0, _parse_queue_params_js_1._parseQueueParams)(queue); if (queueParams instanceof Error) { this.logger.error(`Invalid queue parameters: ${queueParams.message}`); return cb(queueParams); } (0, redis_smq_common_1.withRedisClient)(this.redisClient, (client, cb) => { this.logger.debug(`Fetching exchange for queue: ${queueParams.name}`); (0, _get_queue_fan_out_exchange_js_1._getQueueFanOutExchange)(client, queueParams, (err, exchange) => { if (err) { this.logger.error(`Error fetching queue exchange: ${err.message}`); return cb(err); } if (exchange) { this.logger.info(`Queue ${queueParams.name} is bound to exchange: ${exchange}`); } else { this.logger.info(`Queue ${queueParams.name} is not bound to any exchange`); } cb(null, exchange); }); }, cb); } } exports.ExchangeFanOut = ExchangeFanOut; //# sourceMappingURL=exchange-fan-out.js.map