redis-smq
Version:
A simple high-performance Redis message queue for Node.js.
331 lines • 18.9 kB
JavaScript
;
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