redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
439 lines • 27.5 kB
JavaScript
;
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