mqrpc
Version:
💫 Easy RPC over RabbitMQ
106 lines (105 loc) • 4.29 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("./errors");
/**
* @private
*
* Publishes a message back to the client according to the given replyTo and
* correlationId as passed in the original message props.
*
* @param {Channel} channel The Amqp channel to use.
* @param {object} messageProps The `properties` key of the client's message.
* @param {object} response The object to publish as a response.
*/
const sendMessage = async (channel, { replyTo, correlationId, deliveryMode }, response) => {
await channel.publish('', replyTo, new Buffer(JSON.stringify(response)), { deliveryMode, correlationId });
};
/**
* Sends an ack message to the client. This indicates the server has received a
* procedure call and will start handling it.
*
* @param {Channel} channel The Amqp channel to use.
* @param {Message} message The client's original message.
*/
exports.ack = async (channel, message) => {
return sendMessage(channel, message.properties, { type: 'ack' });
};
/**
* Sends a `wait` message to the client. This indicates the server is still
* processing the call and the client should keep waiting for the reply.
*
* @param {Channel} channel The Amqp channel to use.
* @param {Message} message The client's original message.
*/
exports.wait = async (channel, message) => {
return sendMessage(channel, message.properties, { type: 'wait' });
};
/**
* Replies to the client with a given response. Because this is a reply, the client's
* message will be `ack`ed in the channel.
*
* If the `response` is an error, it will be serialized according to the error's config.
* Otherwise, the response is serialized and sent as-is.
*
* @param {Channel} channel The Amqp channel to use.
* @param {Message} message The client's original message.
* @param {RpcServerError | any} response What to send back to the client.
*/
exports.reply = async (channel, message, response) => {
response = response && response instanceof errors_1.RpcServerError
? { type: 'error', error: response.toObject() }
: { type: 'reply', reply: response };
await sendMessage(channel, message.properties, response);
await channel.ack(message);
};
/**
* Handles messages on the .call queue. If the call is valid, its contents are
* returned, otherwise an error is thrown.
*
* @param {Channel} channel The AMQP channel where the message arrived from.
* @param {Message} message The AMQP message received from the queue.
* @return {ClientPayload} The message content, if valid.
* @throws {Error} If the content cannot be retrieved or the message
* is in any way invalid.
*/
exports.extractCallContent = (message) => {
let content;
if (!message.properties.replyTo)
throw new Error('Message has no replyTo');
if (!message.properties.correlationId)
throw new Error('Message has no correlationId');
try {
content = JSON.parse(message.content.toString());
}
catch (err) {
throw new Error(`Message could not be parsed: ${err.message}`);
}
return content;
};
/**
* Returns a function that, when called with another function, sends the first
* `ack` message to the client and executes the given function. While waiting
* for a return, if `idleTimeout` is set, every idleTimeout / 3 a `wait`
* message is sent to the client.
*
* The inner function will return or raise whatever the given function returns.
*
* @param {Channel} channel The AMQP channel where the message arrived from.
* @param {Message} message The AMQP message received from the queue.
* @param {TimeoutDesc} timeouts The timeouts set on the RpcClient.
* @returns {Function}
*/
exports.whileSendingHeartbeats = (channel, message, timeouts) => {
return async (fn) => {
await exports.ack(channel, message);
const idleInterval = timeouts.idleTimeout &&
setInterval(exports.wait, Math.round(timeouts.idleTimeout / 3), channel, message);
try {
return await fn();
}
finally {
if (idleInterval)
clearInterval(idleInterval);
}
};
};