amqplib
Version:
An AMQP 0-9-1 (e.g., RabbitMQ) library and client.
287 lines (247 loc) • 8.34 kB
JavaScript
const defs = require('./defs');
const EventEmitter = require('node:events');
const BaseChannel = require('./channel').BaseChannel;
const acceptMessage = require('./channel').acceptMessage;
const Args = require('./api_args');
class CallbackModel extends EventEmitter {
constructor(connection) {
super();
this.connection = connection;
['error', 'close', 'blocked', 'unblocked'].forEach((ev) => {
connection.on(ev, this.emit.bind(this, ev));
});
}
close(cb) {
this.connection.close(cb);
}
updateSecret(newSecret, reason, cb) {
this.connection._updateSecret(newSecret, reason, cb);
}
createChannel(options, cb) {
if (cb === undefined) {
cb = options;
options = undefined;
}
const ch = new Channel(this.connection);
ch.setOptions(options);
ch.open((err, _ok) => {
if (err === null) cb && cb(null, ch);
else cb && cb(err);
});
return ch;
}
createConfirmChannel(options, cb) {
if (cb === undefined) {
cb = options;
options = undefined;
}
const ch = new ConfirmChannel(this.connection);
ch.setOptions(options);
ch.open((err) => {
if (err !== null) return cb && cb(err);
else {
ch.rpc(defs.ConfirmSelect, { nowait: false }, defs.ConfirmSelectOk, (err, _ok) => {
if (err !== null) return cb && cb(err);
else cb && cb(null, ch);
});
}
});
return ch;
}
}
class Channel extends BaseChannel {
constructor(connection) {
super(connection);
this.on('delivery', this.handleDelivery.bind(this));
this.on('cancel', this.handleCancel.bind(this));
}
// This encodes straight-forward RPC: no side-effects and return the
// fields from the server response. It wraps the callback given it, so
// the calling method argument can be passed as-is. For anything that
// needs to have side-effects, or needs to change the server response,
// use `#_rpc(...)` and remember to dereference `.fields` of the
// server response.
rpc(method, fields, expect, cb0) {
const cb = callbackWrapper(this, cb0);
this._rpc(method, fields, expect, (err, ok) => {
cb(err, ok && ok.fields); // in case of an error, ok will be
// undefined
});
return this;
}
// === Public API ===
open(cb) {
try {
this.allocate();
} catch (e) {
return cb(e);
}
return this.rpc(defs.ChannelOpen, { outOfBand: '' }, defs.ChannelOpenOk, cb);
}
close(cb) {
return this.closeBecause('Goodbye', defs.constants.REPLY_SUCCESS, () => {
cb && cb(null);
});
}
assertQueue(queue, options, cb) {
return this.rpc(defs.QueueDeclare, Args.assertQueue(queue, options), defs.QueueDeclareOk, cb);
}
checkQueue(queue, cb) {
return this.rpc(defs.QueueDeclare, Args.checkQueue(queue), defs.QueueDeclareOk, cb);
}
deleteQueue(queue, options, cb) {
return this.rpc(defs.QueueDelete, Args.deleteQueue(queue, options), defs.QueueDeleteOk, cb);
}
purgeQueue(queue, cb) {
return this.rpc(defs.QueuePurge, Args.purgeQueue(queue), defs.QueuePurgeOk, cb);
}
bindQueue(queue, source, pattern, argt, cb) {
return this.rpc(defs.QueueBind, Args.bindQueue(queue, source, pattern, argt), defs.QueueBindOk, cb);
}
unbindQueue(queue, source, pattern, argt, cb) {
return this.rpc(defs.QueueUnbind, Args.unbindQueue(queue, source, pattern, argt), defs.QueueUnbindOk, cb);
}
assertExchange(ex, type, options, cb0) {
const cb = callbackWrapper(this, cb0);
this._rpc(defs.ExchangeDeclare, Args.assertExchange(ex, type, options), defs.ExchangeDeclareOk, (e, _) => {
cb(e, { exchange: ex });
});
return this;
}
checkExchange(exchange, cb) {
return this.rpc(defs.ExchangeDeclare, Args.checkExchange(exchange), defs.ExchangeDeclareOk, cb);
}
deleteExchange(exchange, options, cb) {
return this.rpc(defs.ExchangeDelete, Args.deleteExchange(exchange, options), defs.ExchangeDeleteOk, cb);
}
bindExchange(dest, source, pattern, argt, cb) {
return this.rpc(defs.ExchangeBind, Args.bindExchange(dest, source, pattern, argt), defs.ExchangeBindOk, cb);
}
unbindExchange(dest, source, pattern, argt, cb) {
return this.rpc(defs.ExchangeUnbind, Args.unbindExchange(dest, source, pattern, argt), defs.ExchangeUnbindOk, cb);
}
publish(exchange, routingKey, content, options) {
const fieldsAndProps = Args.publish(exchange, routingKey, options);
return this.sendMessage(fieldsAndProps, fieldsAndProps, content);
}
sendToQueue(queue, content, options) {
return this.publish('', queue, content, options);
}
consume(queue, callback, options, cb0) {
const cb = callbackWrapper(this, cb0);
const fields = Args.consume(queue, options);
this._rpc(defs.BasicConsume, fields, defs.BasicConsumeOk, (err, ok) => {
if (err === null) {
this.registerConsumer(ok.fields.consumerTag, callback);
cb(null, ok.fields);
} else cb(err);
});
return this;
}
cancel(consumerTag, cb0) {
const cb = callbackWrapper(this, cb0);
this._rpc(defs.BasicCancel, Args.cancel(consumerTag), defs.BasicCancelOk, (err, ok) => {
if (err === null) {
this.unregisterConsumer(consumerTag);
cb(null, ok.fields);
} else cb(err);
});
return this;
}
get(queue, options, cb0) {
const fields = Args.get(queue, options);
const cb = callbackWrapper(this, cb0);
this.sendOrEnqueue(defs.BasicGet, fields, (err, f) => {
if (err === null) {
if (f.id === defs.BasicGetEmpty) {
cb(null, false);
} else if (f.id === defs.BasicGetOk) {
this.handleMessage = acceptMessage((m) => {
m.fields = f.fields;
cb(null, m);
});
} else {
cb(new Error(`Unexpected response to BasicGet: ${inspect(f)}`));
}
}
});
return this;
}
ack(message, allUpTo) {
this.sendImmediately(defs.BasicAck, Args.ack(message.fields.deliveryTag, allUpTo));
return this;
}
ackAll() {
this.sendImmediately(defs.BasicAck, Args.ack(0, true));
return this;
}
nack(message, allUpTo, requeue) {
this.sendImmediately(defs.BasicNack, Args.nack(message.fields.deliveryTag, allUpTo, requeue));
return this;
}
nackAll(requeue) {
this.sendImmediately(defs.BasicNack, Args.nack(0, true, requeue));
return this;
}
reject(message, requeue) {
this.sendImmediately(defs.BasicReject, Args.reject(message.fields.deliveryTag, requeue));
return this;
}
prefetch(count, global, cb) {
return this.rpc(defs.BasicQos, Args.prefetch(count, global), defs.BasicQosOk, cb);
}
recover(cb) {
return this.rpc(defs.BasicRecover, Args.recover(), defs.BasicRecoverOk, cb);
}
}
// Wrap an RPC callback to make sure the callback is invoked with
// either `(null, value)` or `(error)`, i.e., never two non-null
// values. Also substitutes a stub if the callback is `undefined` or
// otherwise falsey, for convenience in methods for which the callback
// is optional (that is, most of them).
function callbackWrapper(_ch, cb) {
return cb
? (err, ok) => {
if (err === null) {
cb(null, ok);
} else cb(err);
}
: () => {};
}
class ConfirmChannel extends Channel {
publish(exchange, routingKey, content, options, cb) {
this.pushConfirmCallback(cb);
return Channel.prototype.publish.call(this, exchange, routingKey, content, options);
}
sendToQueue(queue, content, options, cb) {
return this.publish('', queue, content, options, cb);
}
waitForConfirms(k) {
const awaiting = [];
const unconfirmed = this.unconfirmed;
unconfirmed.forEach((val, index) => {
if (val === null); // already confirmed
else {
const confirmed = new Promise((resolve, reject) => {
unconfirmed[index] = (err) => {
if (val) val(err);
if (err === null) resolve();
else reject(err);
};
});
awaiting.push(confirmed);
}
});
return Promise.all(awaiting).then(
() => {
k();
},
(err) => {
k(err);
},
);
}
}
module.exports.CallbackModel = CallbackModel;
module.exports.Channel = Channel;
module.exports.ConfirmChannel = ConfirmChannel;