UNPKG

redis-smq

Version:

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

439 lines 27.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExchangeDirect = 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 redis_keys_js_1 = require("../../common/redis/redis-keys/redis-keys.js"); const configuration_js_1 = require("../../config-manager/configuration.js"); const index_js_1 = require("../../errors/index.js"); const _parse_queue_params_js_1 = require("../../queue-manager/_/_parse-queue-params.js"); const index_js_2 = require("../../queue-manager/index.js"); const _parse_exchange_params_js_1 = require("../_/_parse-exchange-params.js"); const _save_exchange_js_1 = require("../_/_save-exchange.js"); const _validate_queue_binding_js_1 = require("../_/_validate-queue-binding.js"); const _validate_exchange_js_1 = require("../_/_validate-exchange.js"); const index_js_3 = require("../types/index.js"); const _get_routing_key_bound_queues_js_1 = require("./_/_get-routing-key-bound-queues.js"); const _get_routing_keys_js_1 = require("./_/_get-routing-keys.js"); const _validate_operation_js_1 = require("../../queue-operation-validator/_/_validate-operation.js"); const index_js_4 = require("../../queue-operation-validator/index.js"); const validator_js_1 = require("../../common/redis/redis-keys/validator.js"); class ExchangeDirect { constructor() { this.type = index_js_3.EExchangeType.DIRECT; this.logger = (0, redis_smq_common_1.createLogger)(configuration_js_1.Configuration.getConfig().logger, this.constructor.name); this.logger.debug('ExchangeDirect initialized'); } getRoutingKeys(exchange, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const exchangeParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, index_js_3.EExchangeType.DIRECT); if (exchangeParams instanceof Error) return callback(exchangeParams); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, done) => (0, _get_routing_keys_js_1._getRoutingKeys)(client, exchangeParams, done), callback); }); } getRoutingKeyBoundQueues(exchange, routingKey, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const exchangeParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, index_js_3.EExchangeType.DIRECT); if (exchangeParams instanceof Error) return callback(exchangeParams); const validatedRoutingKey = (0, validator_js_1.validateRedisKey)(routingKey); if (validatedRoutingKey instanceof Error) return callback(new index_js_1.InvalidDirectExchangeParametersError()); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, done) => { (0, _get_routing_key_bound_queues_js_1._getRoutingKeyBoundQueues)(client, exchangeParams, validatedRoutingKey, done); }, callback); }); } matchQueues(exchange, routingKey, 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) { this.logger.error('matchQueues: invalid exchange params'); return callback(exchangeParams); } const validatedRoutingKey = (0, validator_js_1.validateRedisKey)(routingKey); if (validatedRoutingKey instanceof Error) { this.logger.error(`matchQueues: invalid routing key "${routingKey}"`); return callback(new index_js_1.InvalidExchangeRoutingKeyError({ metadata: { exchange: exchangeParams, routingKey: routingKey, }, })); } this.logger.debug(`matchQueues: resolving queues ns=${exchangeParams.ns} ex=${exchangeParams.name} rk=${validatedRoutingKey}`); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, done) => { const result = []; redis_smq_common_1.async.series([ (cb1) => (0, _validate_exchange_js_1._validateExchange)(client, exchangeParams, true, cb1), (cb1) => (0, _get_routing_key_bound_queues_js_1._getRoutingKeyBoundQueues)(client, exchangeParams, validatedRoutingKey, (err, reply) => { if (err) return cb1(err); if (reply && reply.length) result.push(...reply); cb1(); }), ], (err) => { if (err) return done(err); this.logger.debug(`matchQueues: matched ${result.length} queue(s) ns=${exchangeParams.ns} ex=${exchangeParams.name} rk=${validatedRoutingKey}`); done(null, result); }); }, 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_1.InvalidDirectExchangeParametersError()); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, cb) => (0, _save_exchange_js_1._saveExchange)(client, exchangeParams, queuePolicy, cb), callback); }); } bindQueue(queue, exchange, routingKey, 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) { this.logger.error('bindQueue: invalid queue params'); return callback(queueParams); } if (exchangeParams instanceof Error) { this.logger.error('bindQueue: invalid exchange params'); return callback(exchangeParams); } if (queueParams.ns !== exchangeParams.ns) { this.logger.error(`bindQueue: namespace mismatch q.ns=${queueParams.ns} ex.ns=${exchangeParams.ns}`); return callback(new index_js_1.NamespaceMismatchError()); } const validatedRoutingKey = (0, validator_js_1.validateRedisKey)(routingKey); if (validatedRoutingKey instanceof Error) { this.logger.error(`bindQueue: invalid routing key "${routingKey}"`); return callback(new index_js_1.InvalidDirectExchangeParametersError()); } 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 { keyExchangeRoutingKeys } = redis_keys_js_1.redisKeys.getExchangeDirectKeys(exchangeParams.ns, exchangeParams.name); const { keyRoutingKeyQueues } = redis_keys_js_1.redisKeys.getExchangeDirectRoutingKeyKeys(exchangeParams.ns, exchangeParams.name, validatedRoutingKey); 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); this.logger.debug(`bindQueue: bind q=${queueParams.name} ns=${queueParams.ns} -> ex=${exchangeParams.name} ns=${exchangeParams.ns} rk=${validatedRoutingKey}`); (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, (client, watch, done) => { let exchangeQueuePolicy = null; redis_smq_common_1.async.waterfall([ (cb1) => watch([ keyExchange, keyQueueProperties, keyExchangeRoutingKeys, keyRoutingKeyQueues, keyQueueExchangeBindings, keyExchanges, keyNamespaceExchanges, ], cb1), (_, cb1) => (0, _validate_queue_binding_js_1._validateQueueBinding)(client, 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_2.EQueueType.PRIORITY_QUEUE ? index_js_3.EExchangeQueuePolicy.PRIORITY : index_js_3.EExchangeQueuePolicy.STANDARD; cb1(); }), (_, cb1) => client.sismember(keyRoutingKeyQueues, queueStr, (err, reply) => { if (err) return cb1(err); const already = reply === 1; if (already) { this.logger.debug('bindQueue: already bound'); return cb1(new index_js_1.QueueAlreadyBound()); } cb1(); }), (_, cb1) => { const typeField = String(index_js_3.EExchangeProperty.TYPE); const queuePolicyField = String(index_js_3.EExchangeProperty.QUEUE_POLICY); const multi = client.multi(); multi.hset(keyExchange, typeField, index_js_3.EExchangeType.DIRECT); multi.hset(keyExchange, queuePolicyField, Number(exchangeQueuePolicy)); multi.sadd(keyExchanges, exchangeStr); multi.sadd(keyNamespaceExchanges, exchangeStr); multi.sadd(keyExchangeRoutingKeys, validatedRoutingKey); multi.sadd(keyRoutingKeyQueues, queueStr); multi.sadd(keyQueueExchangeBindings, exchangeStr); cb1(null, { multi }); }, ], done); }, (err) => { if (err) { if (err instanceof index_js_1.QueueAlreadyBound) return cb(); return cb(err); } this.logger.info(`bindQueue: bound queue=${queueParams.name}@${queueParams.ns} -> ex=${exchangeParams.name}@${exchangeParams.ns} rk=${validatedRoutingKey}`); cb(); }), ], (err) => outerCb(err)); }, callback); }); } unbindQueue(queue, exchange, routingKey, 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) { this.logger.error('unbindQueue: invalid queue params'); return callback(queueParams); } if (exchangeParams instanceof Error) { this.logger.error('unbindQueue: invalid exchange params'); return callback(exchangeParams); } if (queueParams.ns !== exchangeParams.ns) { this.logger.error('unbindQueue: namespace mismatch'); return callback(new index_js_1.NamespaceMismatchError()); } const validatedRoutingKey = (0, validator_js_1.validateRedisKey)(routingKey); if (validatedRoutingKey instanceof Error) { this.logger.error(`unbindQueue: invalid routing key "${routingKey}"`); return callback(new index_js_1.InvalidDirectExchangeParametersError()); } 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 { keyExchangeRoutingKeys } = redis_keys_js_1.redisKeys.getExchangeDirectKeys(exchangeParams.ns, exchangeParams.name); const { keyRoutingKeyQueues } = redis_keys_js_1.redisKeys.getExchangeDirectRoutingKeyKeys(exchangeParams.ns, exchangeParams.name, validatedRoutingKey); const queueStr = JSON.stringify(queueParams); const exchangeStr = JSON.stringify(exchangeParams); this.logger.debug(`unbindQueue: unbinding q=${queueParams.name} ns=${queueParams.ns} from ex=${exchangeParams.name} ns=${exchangeParams.ns} rk=${validatedRoutingKey}`); (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, (client, watch, done) => { let routingKeysAll = []; let otherRoutingKeySets = []; let currentCount = 0; let stillBoundViaOtherRK = false; redis_smq_common_1.async.waterfall([ (cb1) => { const baseWatchKeys = [ keyExchange, keyExchangeRoutingKeys, keyRoutingKeyQueues, keyQueueExchangeBindings, ]; watch(baseWatchKeys, cb1); }, (_, cb1) => (0, _validate_exchange_js_1._validateExchange)(client, exchangeParams, true, cb1), (_, cb1) => { client.sismember(keyRoutingKeyQueues, queueStr, (err, reply) => { if (err) return cb1(err); if (reply !== 1) { this.logger.warn('unbindQueue: queue not bound'); return cb1(new index_js_1.QueueNotBoundError()); } cb1(); }); }, (_, cb1) => { client.smembers(keyExchangeRoutingKeys, (err, keys) => { if (err) return cb1(err); routingKeysAll = keys !== null && keys !== void 0 ? keys : []; cb1(); }); }, (_, cb1) => { const others = routingKeysAll.filter((rk) => rk !== validatedRoutingKey); otherRoutingKeySets = others.map((rk) => { const { keyRoutingKeyQueues: k } = redis_keys_js_1.redisKeys.getExchangeDirectRoutingKeyKeys(exchangeParams.ns, exchangeParams.name, rk); return k; }); if (otherRoutingKeySets.length === 0) return cb1(); watch(otherRoutingKeySets, cb1); }, (_, cb1) => { redis_smq_common_1.async.series([ (cbx) => client.scard(keyRoutingKeyQueues, (err, count) => { if (err) return cbx(err); currentCount = count || 0; cbx(); }), (cbx) => { if (otherRoutingKeySets.length === 0) return cbx(); redis_smq_common_1.async.eachOf(otherRoutingKeySets, (k, _i, next) => { if (stillBoundViaOtherRK) return next(); client.sismember(k, queueStr, (e, rep) => { if (e) return next(e); if (rep === 1) stillBoundViaOtherRK = true; next(); }); }, (e) => cbx(e || null)); }, ], (err) => cb1(err)); }, (_, cb1) => { const multi = client.multi(); multi.srem(keyRoutingKeyQueues, queueStr); if (currentCount === 1) { multi.srem(keyExchangeRoutingKeys, validatedRoutingKey); } if (!stillBoundViaOtherRK) { multi.srem(keyQueueExchangeBindings, exchangeStr); } cb1(null, { multi }); }, ], done); }, (err) => { if (err) return cb(err); this.logger.info(`unbindQueue: unbound q=${queueParams.name} ns=${queueParams.ns} from ex=${exchangeParams.name} ns=${exchangeParams.ns} rk=${validatedRoutingKey}`); cb(); }), ], (err) => outerCb(err)); }, 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) { this.logger.error('delete: invalid exchange params'); 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 { keyExchangeRoutingKeys } = redis_keys_js_1.redisKeys.getExchangeDirectKeys(exchangeParams.ns, exchangeParams.name); const exchangeStr = JSON.stringify(exchangeParams); this.logger.debug(`delete: direct exchange ex=${exchangeParams.name}@${exchangeParams.ns}`); (0, with_shared_pool_connection_js_1.withSharedPoolConnection)((client, outerCb) => { (0, redis_smq_common_1.withWatchTransaction)(client, (client, watch, done) => { let routingKeysAll = []; redis_smq_common_1.async.waterfall([ (cb1) => { const baseWatchKeys = [ keyExchange, keyExchangeRoutingKeys, keyExchanges, keyNamespaceExchanges, ]; watch(baseWatchKeys, cb1); }, (_, cb1) => (0, _validate_exchange_js_1._validateExchange)(client, exchangeParams, true, cb1), (_, cb1) => { client.smembers(keyExchangeRoutingKeys, (err, keys) => { if (err) return cb1(err); routingKeysAll = keys !== null && keys !== void 0 ? keys : []; this.logger.debug(`delete: routingKeys=${routingKeysAll.length} ns=${exchangeParams.ns} ex=${exchangeParams.name}`); cb1(null, routingKeysAll); }); }, (routingKeys, cb1) => { if (!routingKeys.length) return cb1(null, routingKeys); const derivedKeys = routingKeys.map((rk) => { const { keyRoutingKeyQueues } = redis_keys_js_1.redisKeys.getExchangeDirectRoutingKeyKeys(exchangeParams.ns, exchangeParams.name, rk); return keyRoutingKeyQueues; }); watch(derivedKeys, (err) => cb1(err || null, routingKeys)); }, (routingKeys, cb1) => { if (!routingKeys.length) return cb1(null, routingKeys); let hasBoundQueues = false; redis_smq_common_1.async.eachOf(routingKeys, (rk, _idx, next) => { const { keyRoutingKeyQueues } = redis_keys_js_1.redisKeys.getExchangeDirectRoutingKeyKeys(exchangeParams.ns, exchangeParams.name, rk); client.scard(keyRoutingKeyQueues, (err, count) => { if (!err && count && count > 0) { hasBoundQueues = true; this.logger.debug(`delete: rk has bound queues rk=${rk}`); } next(err || null); }); }, (err) => { if (err) return cb1(err); if (hasBoundQueues) { this.logger.warn('delete: exchange has bound queues, aborting'); return cb1(new index_js_1.ExchangeHasBoundQueuesError()); } cb1(null, routingKeys); }); }, (routingKeys, cb1) => { const multi = client.multi(); multi.del(keyExchange); multi.del(keyExchangeRoutingKeys); multi.srem(keyExchanges, exchangeStr); multi.srem(keyNamespaceExchanges, exchangeStr); for (const rk of routingKeys) { const { keyRoutingKeyQueues } = redis_keys_js_1.redisKeys.getExchangeDirectRoutingKeyKeys(exchangeParams.ns, exchangeParams.name, rk); multi.del(keyRoutingKeyQueues); } cb1(null, { multi }); }, ], done); }, (err) => { if (err) return outerCb(err); this.logger.info(`delete: deleted direct exchange ex=${exchangeParams.name}@${exchangeParams.ns}`); outerCb(); }, { maxAttempts: 5, onRetry: (attemptNo, maxAttempts) => { this.logger.warn(`delete: concurrent modification detected, retrying attempt=${attemptNo}/${maxAttempts}`); }, }); }, callback); }); } getBindings(exchange, cb) { return redis_smq_common_1.async.withOptionalCallback(cb, (callback) => { const exchangeParams = (0, _parse_exchange_params_js_1._parseExchangeParams)(exchange, index_js_3.EExchangeType.DIRECT); 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_keys_js_1._getRoutingKeys)(client, exchangeParams, cb); }, (routingKeys, done) => { const bindings = {}; redis_smq_common_1.async.eachOf(routingKeys, (routingKey, _, done) => { bindings[routingKey] = []; (0, _get_routing_key_bound_queues_js_1._getRoutingKeyBoundQueues)(client, exchangeParams, routingKey, (err, queues) => { if (err) return done(err); bindings[routingKey].push(...(queues !== null && queues !== void 0 ? queues : [])); done(); }); }, (err) => { if (err) return done(err); done(null, bindings); }); }, ], done); }, callback); }); } } exports.ExchangeDirect = ExchangeDirect; //# sourceMappingURL=exchange-direct.js.map