@kitmi/data
Version:
Jacaranda Framework Data Access Model
280 lines (279 loc) • 9.72 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _utils = require("@kitmi/utils");
const _jacaranda = require("@kitmi/jacaranda");
const _sys = require("@kitmi/sys");
const _types = require("@kitmi/types");
const _Connector = /*#__PURE__*/ _interop_require_default(require("../../Connector"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const rabbitmq = _jacaranda.runtime.get(_jacaranda.NS_MODULE, 'rabbitmq-client') ?? (0, _sys.tryRequire)('rabbitmq-client');
const { Connection } = rabbitmq;
/**
* A callback function to be called to handle a dequeued message.
* @callback workerFunction
* @param {Message} msg - Message object
*/ /**
* Rabbitmq data storage connector.
* @class
* @extends Connector
*/ class RabbitmqConnector extends _Connector.default {
getChannelName(opts) {
return opts.exchange ? opts.queue ? `[X]${opts.exchange}|${opts.queue}` : `[X]${opts.exchange}` : `[Q]${opts.queue}`;
}
async logEnqueue(queue, obj) {
if (this.options.logStatement) {
const logMsg = `${this.driver}: new message enqueued to [${queue}].`;
this.app.log('info', logMsg, {
msg: obj
});
}
}
async logDequeue(queue, obj) {
if (this.options.logStatement) {
const logMsg = `${this.driver}: new message dequeued from [${queue}].`;
this.app.log('info', logMsg, {
msg: obj
});
}
}
/**
* Close all connection initiated by this connector.
*/ async end_() {
if (this.channels) {
await (0, _utils.batchAsync_)(this.channels, async (ch)=>{
try {
await ch.close();
} catch (err) {}
});
delete this.channels;
}
await this.events.emit_('closing');
try {
this.conn?.close();
} catch (err) {}
this.events.allOff();
}
/**
* Create a database connection based on the default connection string of the connector and given options.
*/ connect() {
if (!this.conn) {
this.conn = new Connection(this.connectionString);
this.conn.on('error', (err)=>{
this.app.log('error', `${this.driver}: connection error: ${err.message}}`, _utils._.omit(err, [
'message',
'stack'
]));
});
this.conn.on('connection.blocked', (reason)=>{
this.app.log('warn', `${this.driver}: connection blocked: ${reason}}`);
});
this.conn.on('connection.unblocked', ()=>{
this.app.log('info', `${this.driver}: connection unblocked.`);
});
if (this.options.logConnection) {
this.conn.on('connection', ()=>{
this.app.log('info', `${this.driver}: connection successfully (re)established.`);
});
}
const { publishers, connectorName } = this.options;
_utils._.each(publishers, ({ confirm = true, maxAttempts = 2, routingKey = '', durable = true, ...opts }, index)=>{
if (!opts.exchange && !opts.queue) {
throw new _types.InvalidArgument(`${this.driver}[${connectorName}]: missing exchange or queue for publisher "#${index}".`);
}
opts.durable = durable;
const chKey = this.getChannelName(opts);
const chOpts = {
confirm,
maxAttempts
};
const arrayOpts = [
opts
];
if (opts.exchange) {
if (!opts.type) {
opts.type = 'fanout';
}
chOpts.exchanges = arrayOpts;
} else {
chOpts.queues = arrayOpts;
}
this.channels[chKey] = this.conn.createPublisher(chOpts);
});
}
return this.conn;
}
ensurePubChannel(opts) {
const chKey = this.getChannelName(opts);
const ch = this.channels[chKey];
if (ch == null) {
throw new _types.InvalidArgument(`${this.driver}: missing pre-configured channel for publisher "${chKey}".`);
}
return [
chKey,
ch
];
}
async createConsumer_(opts, handler) {
return new Promise((resolve, reject)=>{
const chKey = this.getChannelName(opts);
let _deleteOnStop = false;
let _timeout = 5000;
if (opts.exchange) {
let { exchange, type = 'fanout', durable = true, deleteOnStop, timeout, ..._opts } = opts;
if (deleteOnStop) {
_deleteOnStop = deleteOnStop;
}
if (timeout) {
_timeout = timeout;
}
opts = {
..._opts,
exchanges: [
{
exchange,
type,
durable
}
]
};
}
const timerHandle = setTimeout(()=>{
reject(new Error(`${this.driver}: consumer [${chKey}] timeout.`));
}, _timeout);
const sub = this.conn.createConsumer(opts, (msg)=>{
this.logDequeue(chKey, msg);
return handler(msg);
});
sub.on('error', (err)=>{
this.app.log('error', `${this.driver}: consumer [${chKey}] error: ${err.message}}`, _utils._.omit(err, [
'message',
'stack'
]));
});
sub.on('ready', ()=>{
clearTimeout(timerHandle);
if (this.options.logConnection) {
this.app.log('info', `${this.driver}: consumer [${chKey}] ready.`);
}
resolve(chKey);
});
if (_deleteOnStop) {
this.events.on('closing', async ()=>{
await this.conn.queueDelete({
queue: sub.queue
});
});
}
this.app.on('stopping', async ()=>{
await sub.close();
});
});
}
async ping_() {
return true;
}
/**
* Send a job to worker queue.
* @see https://github.com/cody-greene/node-rabbitmq-client
* @param {*} queue
* @param {*} obj
*/ async postJob_(queue, obj) {
if (!queue) {
throw new _types.InvalidArgument('Missing queue name.');
}
const [key, pub] = this.ensurePubChannel({
queue
});
await pub.send(queue, obj);
this.logEnqueue(key, obj);
}
/**
* Publish a message to an exchange.
* @param {String} exchange
* @param {Object} obj
* @param {Object} [opts]
*/ async publish_(exchange, obj, opts) {
if (!exchange) {
throw new _types.InvalidArgument('Missing exchange name.');
}
const [key, pub] = this.ensurePubChannel({
exchange
});
await pub.send({
exchange,
routingKey: '',
...opts
}, obj);
this.logEnqueue(key, obj);
}
/**
* Waiting for message from a queue by a worker.
* @see https://github.com/cody-greene/node-rabbitmq-client
* @param {*} queue
* @param {workerFunction} consumerMethod
* @param {boolean} [nonCritical=false] - Flag to indicate if the message is non-critical
*/ async waitForJob_(queue, consumerMethod, nonCritical) {
if (!queue) {
throw new _types.InvalidArgument('Missing queue name.');
}
return this.createConsumer_({
queue,
queueOptions: {
durable: true
},
noAck: nonCritical,
concurrency: 1,
qos: {
prefetchCount: 2
}
}, consumerMethod);
}
/**
* Subscribe to a message from an exchange.
* @param {String} exchange
* @param {workerFunction} consumerMethod
*/ async subscribe_(exchange, consumerMethod, { routingKey = '', ...options } = {}) {
if (!exchange) {
throw new _types.InvalidArgument('Missing exchange name.');
}
return this.createConsumer_({
exchange,
queueBindings: [
{
exchange,
routingKey
}
],
exclusive: true,
noAck: true,
...options
}, consumerMethod);
}
/**
* @param {App} app
* @param {string} connectionString
* @param {object} options
* @property {boolean} [options.logStatement] - Flag to log queued message
* @property {Array} [options.exchanges] - List of exchanges to be created
*/ constructor(app, connectionString, options){
super(app, 'rabbitmq', connectionString, options);
this.channels = {};
this.events = new _jacaranda.AsyncEmitter();
this.connect();
}
}
RabbitmqConnector.driverLib = rabbitmq;
const _default = RabbitmqConnector;
//# sourceMappingURL=Connector.js.map