UNPKG

nats

Version:

Node.js client for NATS, a lightweight, high-performance cloud native messaging system

517 lines 19.8 kB
"use strict"; /* * Copyright 2018-2023 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ServicesFactory = exports.NatsConnectionImpl = void 0; const util_1 = require("./util"); const protocol_1 = require("./protocol"); const encoders_1 = require("./encoders"); const types_1 = require("./types"); const semver_1 = require("./semver"); const options_1 = require("./options"); const queued_iterator_1 = require("./queued_iterator"); const request_1 = require("./request"); const msg_1 = require("./msg"); const jsm_1 = require("../jetstream/jsm"); const jsclient_1 = require("../jetstream/jsclient"); const service_1 = require("./service"); const serviceclient_1 = require("./serviceclient"); const core_1 = require("./core"); class NatsConnectionImpl { constructor(opts) { this.draining = false; this.options = (0, options_1.parseOptions)(opts); this.listeners = []; } static connect(opts = {}) { return new Promise((resolve, reject) => { const nc = new NatsConnectionImpl(opts); protocol_1.ProtocolHandler.connect(nc.options, nc) .then((ph) => { nc.protocol = ph; (function () { return __awaiter(this, void 0, void 0, function* () { var _a, e_1, _b, _c; try { for (var _d = true, _e = __asyncValues(ph.status()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) { _c = _f.value; _d = false; const s = _c; nc.listeners.forEach((l) => { l.push(s); }); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) yield _b.call(_e); } finally { if (e_1) throw e_1.error; } } }); })(); resolve(nc); }) .catch((err) => { reject(err); }); }); } closed() { return this.protocol.closed; } close() { return __awaiter(this, void 0, void 0, function* () { yield this.protocol.close(); }); } _check(subject, sub, pub) { if (this.isClosed()) { throw types_1.NatsError.errorForCode(core_1.ErrorCode.ConnectionClosed); } if (sub && this.isDraining()) { throw types_1.NatsError.errorForCode(core_1.ErrorCode.ConnectionDraining); } if (pub && this.protocol.noMorePublishing) { throw types_1.NatsError.errorForCode(core_1.ErrorCode.ConnectionDraining); } subject = subject || ""; if (subject.length === 0) { throw types_1.NatsError.errorForCode(core_1.ErrorCode.BadSubject); } } publish(subject, data, options) { this._check(subject, false, true); this.protocol.publish(subject, data, options); } publishMessage(msg) { return this.publish(msg.subject, msg.data, { reply: msg.reply, headers: msg.headers, }); } respondMessage(msg) { if (msg.reply) { this.publish(msg.reply, msg.data, { reply: msg.reply, headers: msg.headers, }); return true; } return false; } subscribe(subject, opts = {}) { this._check(subject, true, false); const sub = new protocol_1.SubscriptionImpl(this.protocol, subject, opts); this.protocol.subscribe(sub); return sub; } _resub(s, subject, max) { this._check(subject, true, false); const si = s; // FIXME: need way of understanding a callbacks processed // count without it, we cannot really do much - ie // for rejected messages, the count would be lower, etc. // To handle cases were for example KV is building a map // the consumer would say how many messages we need to do // a proper build before we can handle updates. si.max = max; // this might clear it if (max) { // we cannot auto-unsub, because we don't know the // number of messages we processed vs received // allow the auto-unsub on processMsg to work if they // we were called with a new max si.max = max + si.received; } this.protocol.resub(si, subject); } // possibilities are: // stop on error or any non-100 status // AND: // - wait for timer // - wait for n messages or timer // - wait for unknown messages, done when empty or reset timer expires (with possible alt wait) // - wait for unknown messages, done when an empty payload is received or timer expires (with possible alt wait) requestMany(subject, data = encoders_1.Empty, opts = { maxWait: 1000, maxMessages: -1 }) { const asyncTraces = !(this.protocol.options.noAsyncTraces || false); try { this._check(subject, true, true); } catch (err) { return Promise.reject(err); } opts.strategy = opts.strategy || core_1.RequestStrategy.Timer; opts.maxWait = opts.maxWait || 1000; if (opts.maxWait < 1) { return Promise.reject(new types_1.NatsError("timeout", core_1.ErrorCode.InvalidOption)); } // the iterator for user results const qi = new queued_iterator_1.QueuedIteratorImpl(); function stop(err) { //@ts-ignore: stop function qi.push(() => { qi.stop(err); }); } // callback for the subscription or the mux handler // simply pushes errors and messages into the iterator function callback(err, msg) { if (err || msg === null) { stop(err === null ? undefined : err); } else { qi.push(msg); } } if (opts.noMux) { // we setup a subscription and manage it const stack = asyncTraces ? new Error().stack : null; let max = typeof opts.maxMessages === "number" && opts.maxMessages > 0 ? opts.maxMessages : -1; const sub = this.subscribe((0, core_1.createInbox)(this.options.inboxPrefix), { callback: (err, msg) => { var _a, _b; // we only expect runtime errors or a no responders if (((_a = msg === null || msg === void 0 ? void 0 : msg.data) === null || _a === void 0 ? void 0 : _a.length) === 0 && ((_b = msg === null || msg === void 0 ? void 0 : msg.headers) === null || _b === void 0 ? void 0 : _b.status) === core_1.ErrorCode.NoResponders) { err = types_1.NatsError.errorForCode(core_1.ErrorCode.NoResponders); } // augment any error with the current stack to provide context // for the error on the suer code if (err) { if (stack) { err.stack += `\n\n${stack}`; } cancel(err); return; } // push the message callback(null, msg); // see if the m request is completed if (opts.strategy === core_1.RequestStrategy.Count) { max--; if (max === 0) { cancel(); } } if (opts.strategy === core_1.RequestStrategy.JitterTimer) { clearTimers(); timer = setTimeout(() => { cancel(); }, 300); } if (opts.strategy === core_1.RequestStrategy.SentinelMsg) { if (msg && msg.data.length === 0) { cancel(); } } }, }); sub.closed .then(() => { stop(); }) .catch((err) => { qi.stop(err); }); const cancel = (err) => { if (err) { //@ts-ignore: error qi.push(() => { throw err; }); } clearTimers(); sub.drain() .then(() => { stop(); }) .catch((_err) => { stop(); }); }; qi.iterClosed .then(() => { clearTimers(); sub === null || sub === void 0 ? void 0 : sub.unsubscribe(); }) .catch((_err) => { clearTimers(); sub === null || sub === void 0 ? void 0 : sub.unsubscribe(); }); try { this.publish(subject, data, { reply: sub.getSubject() }); } catch (err) { cancel(err); } let timer = setTimeout(() => { cancel(); }, opts.maxWait); const clearTimers = () => { if (timer) { clearTimeout(timer); } }; } else { // the ingestion is the RequestMany const rmo = opts; rmo.callback = callback; qi.iterClosed.then(() => { r.cancel(); }).catch((err) => { r.cancel(err); }); const r = new request_1.RequestMany(this.protocol.muxSubscriptions, subject, rmo); this.protocol.request(r); try { this.publish(subject, data, { reply: `${this.protocol.muxSubscriptions.baseInbox}${r.token}`, headers: opts.headers, }); } catch (err) { r.cancel(err); } } return Promise.resolve(qi); } request(subject, data, opts = { timeout: 1000, noMux: false }) { try { this._check(subject, true, true); } catch (err) { return Promise.reject(err); } const asyncTraces = !(this.protocol.options.noAsyncTraces || false); opts.timeout = opts.timeout || 1000; if (opts.timeout < 1) { return Promise.reject(new types_1.NatsError("timeout", core_1.ErrorCode.InvalidOption)); } if (!opts.noMux && opts.reply) { return Promise.reject(new types_1.NatsError("reply can only be used with noMux", core_1.ErrorCode.InvalidOption)); } if (opts.noMux) { const inbox = opts.reply ? opts.reply : (0, core_1.createInbox)(this.options.inboxPrefix); const d = (0, util_1.deferred)(); const errCtx = asyncTraces ? new Error() : null; const sub = this.subscribe(inbox, { max: 1, timeout: opts.timeout, callback: (err, msg) => { if (err) { // timeouts from `timeout()` will have the proper stack if (errCtx && err.code !== core_1.ErrorCode.Timeout) { err.stack += `\n\n${errCtx.stack}`; } d.reject(err); } else { err = (0, msg_1.isRequestError)(msg); if (err) { // if we failed here, help the developer by showing what failed if (errCtx) { err.stack += `\n\n${errCtx.stack}`; } d.reject(err); } else { d.resolve(msg); } } }, }); sub.requestSubject = subject; this.protocol.publish(subject, data, { reply: inbox, headers: opts.headers, }); return d; } else { const r = new request_1.RequestOne(this.protocol.muxSubscriptions, subject, opts, asyncTraces); this.protocol.request(r); try { this.publish(subject, data, { reply: `${this.protocol.muxSubscriptions.baseInbox}${r.token}`, headers: opts.headers, }); } catch (err) { r.cancel(err); } const p = Promise.race([r.timer, r.deferred]); p.catch(() => { r.cancel(); }); return p; } } /** * * Flushes to the server. Promise resolves when round-trip completes. * @returns {Promise<void>} */ flush() { if (this.isClosed()) { return Promise.reject(types_1.NatsError.errorForCode(core_1.ErrorCode.ConnectionClosed)); } return this.protocol.flush(); } drain() { if (this.isClosed()) { return Promise.reject(types_1.NatsError.errorForCode(core_1.ErrorCode.ConnectionClosed)); } if (this.isDraining()) { return Promise.reject(types_1.NatsError.errorForCode(core_1.ErrorCode.ConnectionDraining)); } this.draining = true; return this.protocol.drain(); } isClosed() { return this.protocol.isClosed(); } isDraining() { return this.draining; } getServer() { const srv = this.protocol.getServer(); return srv ? srv.listen : ""; } status() { const iter = new queued_iterator_1.QueuedIteratorImpl(); iter.iterClosed.then(() => { const idx = this.listeners.indexOf(iter); this.listeners.splice(idx, 1); }); this.listeners.push(iter); return iter; } get info() { return this.protocol.isClosed() ? undefined : this.protocol.info; } context() { return __awaiter(this, void 0, void 0, function* () { const r = yield this.request(`$SYS.REQ.USER.INFO`); return r.json((key, value) => { if (key === "time") { return new Date(Date.parse(value)); } return value; }); }); } stats() { return { inBytes: this.protocol.inBytes, outBytes: this.protocol.outBytes, inMsgs: this.protocol.inMsgs, outMsgs: this.protocol.outMsgs, }; } jetstreamManager() { return __awaiter(this, arguments, void 0, function* (opts = {}) { const adm = new jsm_1.JetStreamManagerImpl(this, opts); if (opts.checkAPI !== false) { try { yield adm.getAccountInfo(); } catch (err) { const ne = err; if (ne.code === core_1.ErrorCode.NoResponders) { ne.code = core_1.ErrorCode.JetStreamNotEnabled; } throw ne; } } return adm; }); } jetstream(opts = {}) { return new jsclient_1.JetStreamClientImpl(this, opts); } getServerVersion() { const info = this.info; return info ? (0, semver_1.parseSemVer)(info.version) : undefined; } rtt() { return __awaiter(this, void 0, void 0, function* () { if (!this.protocol._closed && !this.protocol.connected) { throw types_1.NatsError.errorForCode(core_1.ErrorCode.Disconnect); } const start = Date.now(); yield this.flush(); return Date.now() - start; }); } get features() { return this.protocol.features; } get services() { if (!this._services) { this._services = new ServicesFactory(this); } return this._services; } reconnect() { if (this.isClosed()) { return Promise.reject(types_1.NatsError.errorForCode(core_1.ErrorCode.ConnectionClosed)); } if (this.isDraining()) { return Promise.reject(types_1.NatsError.errorForCode(core_1.ErrorCode.ConnectionDraining)); } return this.protocol.reconnect(); } } exports.NatsConnectionImpl = NatsConnectionImpl; class ServicesFactory { constructor(nc) { this.nc = nc; } add(config) { try { const s = new service_1.ServiceImpl(this.nc, config); return s.start(); } catch (err) { return Promise.reject(err); } } client(opts, prefix) { return new serviceclient_1.ServiceClientImpl(this.nc, opts, prefix); } } exports.ServicesFactory = ServicesFactory; //# sourceMappingURL=nats.js.map