rabbitmq-client
Version:
Robust, typed, RabbitMQ (0-9-1) client library
167 lines (166 loc) • 5.12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EncoderStream = exports.READY_STATE = void 0;
exports.createDeferred = createDeferred;
exports.expBackoff = expBackoff;
exports.pick = pick;
exports.camelCase = camelCase;
exports.createAsyncReader = createAsyncReader;
exports.expectEvent = expectEvent;
exports.recaptureAndThrow = recaptureAndThrow;
const exception_1 = require("./exception");
const node_stream_1 = require("node:stream");
const node_util_1 = require("node:util");
/** @internal */
var READY_STATE;
(function (READY_STATE) {
READY_STATE[READY_STATE["CONNECTING"] = 0] = "CONNECTING";
READY_STATE[READY_STATE["OPEN"] = 1] = "OPEN";
READY_STATE[READY_STATE["CLOSING"] = 2] = "CLOSING";
READY_STATE[READY_STATE["CLOSED"] = 3] = "CLOSED";
})(READY_STATE || (exports.READY_STATE = READY_STATE = {}));
/** @internal */
function createDeferred(noUncaught) {
const dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
if (noUncaught)
dfd.promise.catch(() => { });
return dfd;
}
/**
* Calculate exponential backoff/retry delay.
* Where attempts >= 1, exp > 1
* @example expBackoff(1000, 30000, attempts)
* ---------------------------------
* attempts | possible delay
* ----------+----------------------
* 1 | 1000 to 2000
* 2 | 1000 to 4000
* 3 | 1000 to 8000
* 4 | 1000 to 16000
* 5 | 1000 to 30000
* ---------------------------------
* Attempts required before max delay is possible = Math.ceil(Math.log(high/step) / Math.log(exp))
* @internal
*/
function expBackoff(step, high, attempts, exp = 2) {
const slots = Math.ceil(Math.min(high / step, Math.pow(exp, attempts)));
const max = Math.min(slots * step, high);
return Math.floor(Math.random() * (max - step) + step);
}
/** @internal */
function pick(src, keys) {
const dest = {};
for (const key of keys) {
dest[key] = src[key];
}
return dest;
}
/** @internal */
function camelCase(string) {
const parts = string.match(/[^.|-]+/g);
if (!parts)
return string;
return parts.reduce((acc, word, index) => {
return acc + (index ? word.charAt(0).toUpperCase() + word.slice(1) : word);
});
}
/**
* Wrap Readable.read() to ensure that it either resolves with a Buffer of the
* requested length, waiting for more data when necessary, or is rejected.
* Assumes only a single pending read at a time.
* See also: https://nodejs.org/api/stream.html#readablereadsize
* @internal
*/
function createAsyncReader(socket) {
let bytes;
let cb;
function _read() {
if (!cb)
return;
const buf = socket.read(bytes);
if (!buf && socket.readable)
return; // wait for readable OR close
if (buf?.byteLength !== bytes) {
cb(new exception_1.AMQPConnectionError('READ_END', 'stream ended before all bytes received'), buf);
}
else {
cb(null, buf);
}
cb = undefined;
}
socket.on('close', _read);
socket.on('readable', _read);
return (0, node_util_1.promisify)((_bytes, _cb) => {
bytes = _bytes;
cb = _cb;
_read();
});
}
/**
* Consumes Iterators (like from a generator-fn).
* Writes Buffers (or whatever the iterators produce) to the output stream
* @internal
*/
class EncoderStream extends node_stream_1.Writable {
_cur;
_out;
constructor(out) {
super({ objectMode: true });
this._out = out;
this._loop = this._loop.bind(this);
out.on('drain', this._loop);
}
writeAsync = (0, node_util_1.promisify)(this.write);
_destroy(err, cb) {
this._out.removeListener('drain', this._loop);
if (this._cur) {
this._cur[1](err);
this._cur = undefined;
}
cb(err);
}
_write(it, enc, cb) {
this._cur = [it, cb];
this._loop();
}
/** Squeeze the current iterator until it's empty, but respect back-pressure. */
_loop() {
if (!this._cur)
return;
const [it, cb] = this._cur;
let res;
let ok = !this._out.writableNeedDrain;
try {
// if Nagle's algorithm is enabled, this will reduce latency
this._out.cork();
while (ok && (res = it.next()) && !res.done)
ok = this._out.write(res.value);
}
catch (err) {
return cb(err);
}
finally {
this._out.uncork();
// TODO consider this:
//process.nextTick(() => this._out.uncork())
}
if (res?.done) {
this._cur = undefined;
cb();
}
}
}
exports.EncoderStream = EncoderStream;
/** @internal */
function expectEvent(emitter, name) {
return new Promise((resolve) => { emitter.once(name, resolve); });
}
/** @internal */
function recaptureAndThrow(err) {
Error.captureStackTrace(err, recaptureAndThrow);
throw err;
}