UNPKG

redis-smq

Version:

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

402 lines 25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExchangeTopic = void 0; const redis_smq_common_1 = require("redis-smq-common"); const with_shared_pool_connection_js_1 = require("../../common/redis/redis-connection-pool/with-shared-pool-connection.js"); const index_js_1 = require("../../queue-manager/index.js"); const _save_exchange_js_1 = require("../_/_save-exchange.js"); const _validate_queue_binding_js_1 = require("../_/_validate-queue-binding.js"); const index_js_2 = require("../index.js"); const configuration_js_1 = require("../../config-manager/configuration.js"); const _get_routing_patterns_js_1 = require("./_/_get-routing-patterns.js"); const _get_routing_pattern_bound_queues_js_1 = require("./_/_get-routing-pattern-bound-queues.js"); const _parse_exchange_params_js_1 = require("../_/_parse-exchange-params.js"); const redis_keys_js_1 = require("../../common/redis/redis-keys/redis-keys.js"); const _parse_queue_params_js_1 = require("../../queue-manager/_/_parse-queue-params.js"); const index_js_3 = require("../../errors/index.js"); const _validate_routing_pattern_js_1 = require("./_/_validate-routing-pattern.js"); const _match_routing_key_js_1 = require("./_/_match-routing-key.js"); const _validate_exchange_js_1 = require("../_/_validate-exchange.js"); const _validate_operation_js_1 = require("../../queue-operation-validator/_/_validate-operation.js"); const index_js_4 = require("../../queue-operation-validator/index.js"); class ExchangeTopic { constructor() { this.type = index_js_2.EExchangeType.TOPIC; this.logger = (0, redis_smq_common_1.createLogger)(configuration_js_1.Configuration.getConfig().logger, this.constructor.name); } matchQueues(exchange, routingKey, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const topicParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, this.type); if (topicParams instanceof Error) { return callback(topicParams); } const validatedRoutingKey = routingKey.trim(); if (!validatedRoutingKey) { return callback(new index_js_3.InvalidExchangeRoutingKeyError({ metadata: { exchange: topicParams, routingKey: routingKey, }, })); } (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, topCb) => { (0, _get_routing_patterns_js_1._getRoutingPatterns)(client, topicParams, (err, patterns) => { if (err) return topCb(err); const allPatterns = patterns !== null && patterns !== void 0 ? patterns : []; if (allPatterns.length === 0) return topCb(null, []); const matched = allPatterns.filter((p) => (0, _match_routing_key_js_1._matchRoutingKey)(validatedRoutingKey, p)); if (matched.length === 0) return topCb(null, []); const union = new Map(); const tasks = matched.map((p) => (tcb) => { (0, _get_routing_pattern_bound_queues_js_1._getRoutingPatternBoundQueues)(client, p, topicParams, (e, qs) => { if (e) return tcb(e); (qs !== null && qs !== void 0 ? qs : []).forEach((q) => union.set(`${q.name}@${q.ns}`, q)); tcb(); }); }); redis_smq_common_1.async.parallel(tasks, (e) => { if (e) return topCb(e); topCb(null, Array.from(union.values())); }); }); }, callback); }); } getRoutingPatterns(exchange, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, cb) => (0, _get_routing_patterns_js_1._getRoutingPatterns)(client, exchange, cb), callback); }); } getRoutingPatternBoundQueues(exchange, bindingPattern, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, cb) => (0, _get_routing_pattern_bound_queues_js_1._getRoutingPatternBoundQueues)(client, bindingPattern, exchange, cb), callback); }); } bindQueue(queue, exchange, routingPattern, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const queueParams = (0, _parse_queue_params_js_1._parseQueueParams)(queue); const exchangeParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, this.type); if (queueParams instanceof Error) return callback(queueParams); if (exchangeParams instanceof Error) return callback(exchangeParams); if (queueParams.ns !== exchangeParams.ns) { return callback(new index_js_3.NamespaceMismatchError()); } if (!(0, _validate_routing_pattern_js_1._validateRoutingPattern)(routingPattern)) { return callback(new index_js_3.InvalidTopicBindingPatternError({ metadata: { pattern: routingPattern }, })); } const { keyQueueProperties, keyQueueExchangeBindings } = redis_keys_js_1.redisKeys.getQueueKeys(queueParams.ns, queueParams.name, null); const { keyExchange } = redis_keys_js_1.redisKeys.getExchangeKeys(exchangeParams.ns, exchangeParams.name); const { keyExchangeBindingPatterns } = redis_keys_js_1.redisKeys.getExchangeTopicKeys(exchangeParams.ns, exchangeParams.name); const { keyBindingPatternQueues } = redis_keys_js_1.redisKeys.getExchangeTopicBindingPatternKeys(exchangeParams.ns, exchangeParams.name, routingPattern); const { keyExchanges } = redis_keys_js_1.redisKeys.getMainKeys(); const { keyNamespaceExchanges } = redis_keys_js_1.redisKeys.getNamespaceKeys(queueParams.ns); const queueStr = JSON.stringify(queueParams); const exchangeStr = JSON.stringify(exchangeParams); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, outerCb) => { redis_smq_common_1.async.series([ (cb) => (0, _validate_operation_js_1._validateOperation)(client, queueParams, index_js_4.EQueueOperation.BIND_EXCHANGE, cb), (cb) => (0, redis_smq_common_1.withWatchTransaction)(client, (c, watch, done) => { let exchangeQueuePolicy = null; redis_smq_common_1.async.waterfall([ (cb1) => watch([ keyExchange, keyQueueProperties, keyExchangeBindingPatterns, keyBindingPatternQueues, keyQueueExchangeBindings, keyExchanges, keyNamespaceExchanges, ], cb1), (_, cb1) => (0, _validate_queue_binding_js_1._validateQueueBinding)(c, exchangeParams, queueParams, (err, reply) => { if (err) return cb1(err); if (!reply) return cb1(new redis_smq_common_1.CallbackEmptyReplyError()); const [queueProperties] = reply; exchangeQueuePolicy = queueProperties.queueType === index_js_1.EQueueType.PRIORITY_QUEUE ? index_js_2.EExchangeQueuePolicy.PRIORITY : index_js_2.EExchangeQueuePolicy.STANDARD; cb1(); }), (_, cb1) => c.sismember(keyBindingPatternQueues, queueStr, (err, reply) => { if (err) return cb1(err); if (reply === 1) { this.logger.debug('bindQueue: already bound'); return cb1(new index_js_3.QueueAlreadyBound()); } cb1(); }), (_, cb1) => { const typeField = String(index_js_2.EExchangeProperty.TYPE); const queuePolicyField = String(index_js_2.EExchangeProperty.QUEUE_POLICY); const multi = c.multi(); multi.hset(keyExchange, typeField, index_js_2.EExchangeType.TOPIC); multi.hset(keyExchange, queuePolicyField, Number(exchangeQueuePolicy)); multi.sadd(keyExchanges, exchangeStr); multi.sadd(keyNamespaceExchanges, exchangeStr); multi.sadd(keyExchangeBindingPatterns, routingPattern); multi.sadd(keyBindingPatternQueues, queueStr); multi.sadd(keyQueueExchangeBindings, exchangeStr); cb1(null, { multi }); }, ], done); }, (err) => { if (err) { if (err instanceof index_js_3.QueueAlreadyBound) return cb(); return cb(err); } this.logger.info(`bindQueue: bound queue=${queueParams.name}@${queueParams.ns} -> ex=${exchangeParams.name}@${exchangeParams.ns} pat=${routingPattern}`); cb(); }, { maxAttempts: 5, onRetry: (attemptNo, maxAttempts) => this.logger.warn(`bindQueue: concurrent modification, retrying attempt=${attemptNo}/${maxAttempts}`), }), ], (err) => outerCb(err)); }, callback); }); } unbindQueue(queue, exchange, routingPattern, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const queueParams = (0, _parse_queue_params_js_1._parseQueueParams)(queue); const exchangeParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, this.type); if (queueParams instanceof Error) return callback(queueParams); if (exchangeParams instanceof Error) return callback(exchangeParams); if (queueParams.ns !== exchangeParams.ns) { return callback(new index_js_3.NamespaceMismatchError()); } if (!(0, _validate_routing_pattern_js_1._validateRoutingPattern)(routingPattern)) { return callback(new index_js_3.InvalidTopicBindingPatternError({ metadata: { pattern: routingPattern }, })); } const { keyQueueExchangeBindings } = redis_keys_js_1.redisKeys.getQueueKeys(queueParams.ns, queueParams.name, null); const { keyExchange } = redis_keys_js_1.redisKeys.getExchangeKeys(exchangeParams.ns, exchangeParams.name); const { keyExchangeBindingPatterns } = redis_keys_js_1.redisKeys.getExchangeTopicKeys(exchangeParams.ns, exchangeParams.name); const { keyBindingPatternQueues } = redis_keys_js_1.redisKeys.getExchangeTopicBindingPatternKeys(exchangeParams.ns, exchangeParams.name, routingPattern); const queueStr = JSON.stringify(queueParams); const exchangeStr = JSON.stringify(exchangeParams); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, outerCb) => { redis_smq_common_1.async.series([ (cb) => (0, _validate_operation_js_1._validateOperation)(client, queueParams, index_js_4.EQueueOperation.UNBIND_EXCHANGE, cb), (cb) => (0, redis_smq_common_1.withWatchTransaction)(client, (c, watch, done) => { let allPatterns = []; let currentPatternCount = 0; let stillBoundViaOtherPattern = false; redis_smq_common_1.async.waterfall([ (cb1) => watch([ keyExchange, keyExchangeBindingPatterns, keyBindingPatternQueues, keyQueueExchangeBindings, ], cb1), (_, cb1) => (0, _validate_exchange_js_1._validateExchange)(c, exchangeParams, true, cb1), (_, cb1) => c.sismember(keyBindingPatternQueues, queueStr, (err, reply) => { if (err) return cb1(err); if (reply !== 1) return cb1(new index_js_3.QueueNotBoundError()); cb1(); }), (_, cb1) => c.smembers(keyExchangeBindingPatterns, (err, pats) => { if (err) return cb1(err); allPatterns = (pats !== null && pats !== void 0 ? pats : []).filter((p) => p && p.length); cb1(); }), (_, cb1) => { const otherPatterns = allPatterns.filter((p) => p !== routingPattern); const otherPatternSets = otherPatterns.map((p) => { const { keyBindingPatternQueues: k } = redis_keys_js_1.redisKeys.getExchangeTopicBindingPatternKeys(exchangeParams.ns, exchangeParams.name, p); return k; }); const doWatch = (next) => otherPatternSets.length ? watch(otherPatternSets, next) : next(); doWatch((err) => { if (err) return cb1(err); redis_smq_common_1.async.series([ (cbx) => c.scard(keyBindingPatternQueues, (e, count) => { if (e) return cbx(e); currentPatternCount = count || 0; cbx(); }), (cbx) => { if (otherPatternSets.length === 0) return cbx(); redis_smq_common_1.async.eachOf(otherPatternSets, (setKey, _i, next) => { if (stillBoundViaOtherPattern) return next(); c.sismember(setKey, queueStr, (e2, rep) => { if (e2) return next(e2); if (rep === 1) stillBoundViaOtherPattern = true; next(); }); }, (e3) => cbx(e3 || null)); }, ], (err) => cb1(err)); }); }, (_, cb1) => { const multi = c.multi(); multi.srem(keyBindingPatternQueues, queueStr); if (currentPatternCount === 1) { multi.srem(keyExchangeBindingPatterns, routingPattern); } if (!stillBoundViaOtherPattern) { multi.srem(keyQueueExchangeBindings, exchangeStr); } cb1(null, { multi }); }, ], done); }, (err) => { if (err) return cb(err); this.logger.info(`unbindQueue: unbound queue=${queueParams.name}@${queueParams.ns} from ex=${exchangeParams.name}@${exchangeParams.ns} pat=${routingPattern}`); cb(); }, { maxAttempts: 5, onRetry: (attemptNo, maxAttempts) => this.logger.warn(`unbindQueue: concurrent modification, retrying attempt=${attemptNo}/${maxAttempts}`), }), ], (err) => outerCb(err)); }, callback); }); } create(exchange, queuePolicy, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const exchangeParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, this.type); if (exchangeParams instanceof Error) return callback(new index_js_3.InvalidTopicExchangeParamsError()); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, cb) => (0, _save_exchange_js_1._saveExchange)(client, exchangeParams, queuePolicy, cb), callback); }); } delete(exchange, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const exchangeParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, this.type); if (exchangeParams instanceof Error) return callback(exchangeParams); const { keyExchanges } = redis_keys_js_1.redisKeys.getMainKeys(); const { keyNamespaceExchanges } = redis_keys_js_1.redisKeys.getNamespaceKeys(exchangeParams.ns); const { keyExchange } = redis_keys_js_1.redisKeys.getExchangeKeys(exchangeParams.ns, exchangeParams.name); const { keyExchangeBindingPatterns } = redis_keys_js_1.redisKeys.getExchangeTopicKeys(exchangeParams.ns, exchangeParams.name); const exchangeStr = JSON.stringify(exchangeParams); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, outerCb) => { (0, redis_smq_common_1.withWatchTransaction)(client, (c, watch, done) => { let patterns = []; redis_smq_common_1.async.waterfall([ (cb1) => watch([ keyExchange, keyExchangeBindingPatterns, keyExchanges, keyNamespaceExchanges, ], cb1), (_, cb1) => (0, _validate_exchange_js_1._validateExchange)(c, exchangeParams, true, cb1), (_, cb1) => c.smembers(keyExchangeBindingPatterns, (err, pats) => { if (err) return cb1(err); patterns = (pats !== null && pats !== void 0 ? pats : []).filter((p) => p && p.length); cb1(); }), (_, cb1) => { if (patterns.length === 0) return cb1(); const derivedKeys = patterns.map((p) => { const { keyBindingPatternQueues } = redis_keys_js_1.redisKeys.getExchangeTopicBindingPatternKeys(exchangeParams.ns, exchangeParams.name, p); return keyBindingPatternQueues; }); watch(derivedKeys, cb1); }, (_, cb1) => { if (patterns.length === 0) return cb1(); let hasBoundQueues = false; redis_smq_common_1.async.eachOf(patterns, (p, _idx, next) => { const { keyBindingPatternQueues } = redis_keys_js_1.redisKeys.getExchangeTopicBindingPatternKeys(exchangeParams.ns, exchangeParams.name, p); c.scard(keyBindingPatternQueues, (err, count) => { if (!err && (count || 0) > 0) { hasBoundQueues = true; this.logger.debug(`delete: pattern "${p}" has ${count} bound queue(s)`); } next(err || null); }); }, (err) => { if (err) return cb1(err); if (hasBoundQueues) return cb1(new index_js_3.ExchangeHasBoundQueuesError()); cb1(); }); }, (_, cb1) => { const multi = c.multi(); multi.del(keyExchange); multi.del(keyExchangeBindingPatterns); multi.srem(keyExchanges, exchangeStr); multi.srem(keyNamespaceExchanges, exchangeStr); for (const p of patterns) { const { keyBindingPatternQueues } = redis_keys_js_1.redisKeys.getExchangeTopicBindingPatternKeys(exchangeParams.ns, exchangeParams.name, p); multi.del(keyBindingPatternQueues); } cb1(null, { multi }); }, ], done); }, (err) => { if (err) return outerCb(err); this.logger.info(`delete: exchange ${exchangeParams.name}@${exchangeParams.ns} deleted`); outerCb(); }); }, callback); }); } getBindings(exchange, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const exchangeParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, this.type); if (exchangeParams instanceof Error) return callback(exchangeParams); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, done) => { redis_smq_common_1.async.waterfall([ (cb) => { (0, _get_routing_patterns_js_1._getRoutingPatterns)(client, exchangeParams, cb); }, (bindingPatterns, done) => { const bindings = {}; redis_smq_common_1.async.eachOf(bindingPatterns, (bindingPattern, _, done) => { bindings[bindingPattern] = []; (0, _get_routing_pattern_bound_queues_js_1._getRoutingPatternBoundQueues)(client, bindingPattern, exchangeParams, (err, queues) => { if (err) return done(err); bindings[bindingPattern].push(...(queues !== null && queues !== void 0 ? queues : [])); done(); }); }, (err) => { if (err) return done(err); done(null, bindings); }); }, ], done); }, callback); }); } } exports.ExchangeTopic = ExchangeTopic; //# sourceMappingURL=exchange-topic.js.map