nats
Version:
Node.js client for NATS, a lightweight, high-performance cloud native messaging system
999 lines • 41.6 kB
JavaScript
"use strict";
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.OrderedPullConsumerImpl = exports.PullConsumerImpl = exports.OrderedConsumerMessages = exports.PullConsumerMessagesImpl = exports.ConsumerDebugEvents = exports.ConsumerEvents = void 0;
/*
* Copyright 2022-2024 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.
*/
const util_1 = require("../nats-base-client/util");
const nuid_1 = require("../nats-base-client/nuid");
const jsutil_1 = require("./jsutil");
const queued_iterator_1 = require("../nats-base-client/queued_iterator");
const core_1 = require("../nats-base-client/core");
const idleheartbeat_monitor_1 = require("../nats-base-client/idleheartbeat_monitor");
const jsmsg_1 = require("./jsmsg");
const jsapi_types_1 = require("./jsapi_types");
const types_1 = require("./types");
var PullConsumerType;
(function (PullConsumerType) {
PullConsumerType[PullConsumerType["Unset"] = -1] = "Unset";
PullConsumerType[PullConsumerType["Consume"] = 0] = "Consume";
PullConsumerType[PullConsumerType["Fetch"] = 1] = "Fetch";
})(PullConsumerType || (PullConsumerType = {}));
/**
* ConsumerEvents are informational notifications emitted by ConsumerMessages
* that may be of interest to a client.
*/
var ConsumerEvents;
(function (ConsumerEvents) {
/**
* Notification that heartbeats were missed. This notification is informational.
* The `data` portion of the status, is a number indicating the number of missed heartbeats.
* Note that when a client disconnects, heartbeat tracking is paused while
* the client is disconnected.
*/
ConsumerEvents["HeartbeatsMissed"] = "heartbeats_missed";
/**
* Notification that the consumer was not found. Consumers that were accessible at
* least once, will be retried for more messages regardless of the not being found
* or timeouts etc. This notification includes a count of consecutive attempts to
* find the consumer. Note that if you get this notification possibly your code should
* attempt to recreate the consumer. Note that this notification is only informational
* for ordered consumers, as the consumer will be created in those cases automatically.
*/
ConsumerEvents["ConsumerNotFound"] = "consumer_not_found";
/**
* Notification that the stream was not found. Consumers were accessible at least once,
* will be retried for more messages regardless of the not being found
* or timeouts etc. This notification includes a count of consecutive attempts to
* find the consumer. Note that if you get this notification possibly your code should
* attempt to recreate the consumer. Note that this notification is only informational
* for ordered consumers, as the consumer will be created in those cases automatically.
*/
ConsumerEvents["StreamNotFound"] = "stream_not_found";
/*
* Notification that the consumer was deleted. This notification
* means the consumer will not get messages unless it is recreated. The client
* will continue to attempt to pull messages. Ordered consumer will recreate it.
*/
ConsumerEvents["ConsumerDeleted"] = "consumer_deleted";
/**
* This notification is specific of ordered consumers and will be notified whenever
* the consumer is recreated. The argument is the name of the newly created consumer.
*/
ConsumerEvents["OrderedConsumerRecreated"] = "ordered_consumer_recreated";
})(ConsumerEvents || (exports.ConsumerEvents = ConsumerEvents = {}));
/**
* These events represent informational notifications emitted by ConsumerMessages
* that can be safely ignored by clients.
*/
var ConsumerDebugEvents;
(function (ConsumerDebugEvents) {
/**
* DebugEvents are effectively statuses returned by the server that were ignored
* by the client. The `data` portion of the
* status is just a string indicating the code/message of the status.
*/
ConsumerDebugEvents["DebugEvent"] = "debug";
/**
* Requests for messages can be terminated by the server, these notifications
* provide information on the number of messages and/or bytes that couldn't
* be satisfied by the consumer request. The `data` portion of the status will
* have the format of `{msgsLeft: number, bytesLeft: number}`.
*/
ConsumerDebugEvents["Discard"] = "discard";
/**
* Notifies whenever there's a request for additional messages from the server.
* This notification telegraphs the request options, which should be treated as
* read-only. This notification is only useful for debugging. Data is PullOptions.
*/
ConsumerDebugEvents["Next"] = "next";
})(ConsumerDebugEvents || (exports.ConsumerDebugEvents = ConsumerDebugEvents = {}));
class PullConsumerMessagesImpl extends queued_iterator_1.QueuedIteratorImpl {
// callback: ConsumerCallbackFn;
constructor(c, opts, refilling = false) {
super();
this.consumer = c;
const copts = opts;
this.opts = this.parseOptions(opts, refilling);
this.callback = copts.callback || null;
this.noIterator = typeof this.callback === "function";
this.monitor = null;
this.pong = null;
this.pending = { msgs: 0, bytes: 0, requests: 0 };
this.refilling = refilling;
this.timeout = null;
this.inbox = (0, core_1.createInbox)(c.api.nc.options.inboxPrefix);
this.listeners = [];
this.forOrderedConsumer = false;
this.abortOnMissingResource = copts.abort_on_missing_resource === true;
this.bind = copts.bind === true;
this.start();
}
start() {
const { max_messages, max_bytes, idle_heartbeat, threshold_bytes, threshold_messages, } = this.opts;
// ordered consumer requires the ability to reset the
// source pull consumer, if promise is registered and
// close is called, the pull consumer will emit a close
// which will close the ordered consumer, by registering
// the close with a handler, we can replace it.
this.closed().then((err) => {
if (this.cleanupHandler) {
try {
this.cleanupHandler(err);
}
catch (_err) {
// nothing
}
}
});
const { sub } = this;
if (sub) {
sub.unsubscribe();
}
this.sub = this.consumer.api.nc.subscribe(this.inbox, {
callback: (err, msg) => {
var _a, _b, _c, _d;
if (err) {
// this is possibly only a permissions error which means
// that the server rejected (eliminating the sub)
// or the client never had permissions to begin with
// so this is terminal
this.stop(err);
return;
}
(_a = this.monitor) === null || _a === void 0 ? void 0 : _a.work();
const isProtocol = msg.subject === this.inbox;
if (isProtocol) {
if ((0, jsutil_1.isHeartbeatMsg)(msg)) {
return;
}
const code = (_b = msg.headers) === null || _b === void 0 ? void 0 : _b.code;
const description = ((_d = (_c = msg.headers) === null || _c === void 0 ? void 0 : _c.description) === null || _d === void 0 ? void 0 : _d.toLowerCase()) ||
"unknown";
const { msgsLeft, bytesLeft } = this.parseDiscard(msg.headers);
if (msgsLeft > 0 || bytesLeft > 0) {
this.pending.msgs -= msgsLeft;
this.pending.bytes -= bytesLeft;
this.pending.requests--;
this.notify(ConsumerDebugEvents.Discard, { msgsLeft, bytesLeft });
}
else {
// FIXME: 408 can be a Timeout or bad request,
// or it can be sent if a nowait request was
// sent when other waiting requests are pending
// "Requests Pending"
// FIXME: 400 bad request Invalid Heartbeat or Unmarshalling Fails
// these are real bad values - so this is bad request
// fail on this
// we got a bad request - no progress here
if (code === 400) {
this.stop(new core_1.NatsError(description, `${code}`));
return;
}
else if (code === 409 && description === "consumer deleted") {
this.notify(ConsumerEvents.ConsumerDeleted, `${code} ${description}`);
if (!this.refilling || this.abortOnMissingResource) {
const error = new core_1.NatsError(description, `${code}`);
this.stop(error);
return;
}
}
else {
this.notify(ConsumerDebugEvents.DebugEvent, `${code} ${description}`);
}
}
}
else {
// push the user message
this._push((0, jsmsg_1.toJsMsg)(msg));
this.received++;
if (this.pending.msgs) {
this.pending.msgs--;
}
if (this.pending.bytes) {
this.pending.bytes -= msg.size();
}
}
// if we don't have pending bytes/messages we are done or starving
if (this.pending.msgs === 0 && this.pending.bytes === 0) {
this.pending.requests = 0;
}
if (this.refilling) {
// FIXME: this could result in 1/4 = 0
if ((max_messages &&
this.pending.msgs <= threshold_messages) ||
(max_bytes && this.pending.bytes <= threshold_bytes)) {
const batch = this.pullOptions();
// @ts-ignore: we are pushing the pull fn
this.pull(batch);
}
}
else if (this.pending.requests === 0) {
// @ts-ignore: we are pushing the pull fn
this._push(() => {
this.stop();
});
}
},
});
this.sub.closed.then(() => {
// for ordered consumer we cannot break the iterator
if (this.sub.draining) {
// @ts-ignore: we are pushing the pull fn
this._push(() => {
this.stop();
});
}
});
if (idle_heartbeat) {
this.monitor = new idleheartbeat_monitor_1.IdleHeartbeatMonitor(idle_heartbeat, (data) => {
// for the pull consumer - missing heartbeats may be corrected
// on the next pull etc - the only assumption here is we should
// reset and check if the consumer was deleted from under us
this.notify(ConsumerEvents.HeartbeatsMissed, data);
this.resetPending()
.then(() => {
})
.catch(() => {
});
return false;
}, { maxOut: 2 });
}
// now if we disconnect, the consumer could be gone
// or we were slow consumer'ed by the server
(() => __awaiter(this, void 0, void 0, function* () {
var _a, e_1, _b, _c;
var _d;
const status = this.consumer.api.nc.status();
this.statusIterator = status;
try {
for (var _e = true, status_1 = __asyncValues(status), status_1_1; status_1_1 = yield status_1.next(), _a = status_1_1.done, !_a; _e = true) {
_c = status_1_1.value;
_e = false;
const s = _c;
switch (s.type) {
case core_1.Events.Disconnect:
// don't spam hb errors if we are disconnected
// @ts-ignore: optional chaining
(_d = this.monitor) === null || _d === void 0 ? void 0 : _d.cancel();
break;
case core_1.Events.Reconnect:
// do some sanity checks and reset
// if that works resume the monitor
this.resetPending()
.then((ok) => {
var _a;
if (ok) {
// @ts-ignore: optional chaining
(_a = this.monitor) === null || _a === void 0 ? void 0 : _a.restart();
}
})
.catch(() => {
// ignored - this should have fired elsewhere
});
break;
default:
// ignored
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_e && !_a && (_b = status_1.return)) yield _b.call(status_1);
}
finally { if (e_1) throw e_1.error; }
}
}))();
// this is the initial pull
this.pull(this.pullOptions());
}
_push(r) {
if (!this.callback) {
super.push(r);
}
else {
const fn = typeof r === "function" ? r : null;
try {
if (!fn) {
this.callback(r);
}
else {
fn();
}
}
catch (err) {
this.stop(err);
}
}
}
notify(type, data) {
if (this.listeners.length > 0) {
(() => {
this.listeners.forEach((l) => {
if (!l.done) {
l.push({ type, data });
}
});
})();
}
}
resetPending() {
return this.bind ? this.resetPendingNoInfo() : this.resetPendingWithInfo();
}
resetPendingNoInfo() {
// here we are blind - we won't do an info, so all we are doing
// is invalidating the previous request results.
this.pending.msgs = 0;
this.pending.bytes = 0;
this.pending.requests = 0;
this.pull(this.pullOptions());
return Promise.resolve(true);
}
resetPendingWithInfo() {
return __awaiter(this, void 0, void 0, function* () {
let notFound = 0;
let streamNotFound = 0;
const bo = (0, util_1.backoff)();
let attempt = 0;
while (true) {
if (this.done) {
return false;
}
if (this.consumer.api.nc.isClosed()) {
console.error("aborting resetPending - connection is closed");
return false;
}
try {
// check we exist
yield this.consumer.info();
notFound = 0;
// we exist, so effectively any pending state is gone
// so reset and re-pull
this.pending.msgs = 0;
this.pending.bytes = 0;
this.pending.requests = 0;
this.pull(this.pullOptions());
return true;
}
catch (err) {
// game over
if (err.message === "stream not found") {
streamNotFound++;
this.notify(ConsumerEvents.StreamNotFound, streamNotFound);
if (!this.refilling || this.abortOnMissingResource) {
this.stop(err);
return false;
}
}
else if (err.message === "consumer not found") {
notFound++;
this.notify(ConsumerEvents.ConsumerNotFound, notFound);
if (this.resetHandler) {
try {
this.resetHandler();
}
catch (_) {
// ignored
}
}
if (!this.refilling || this.abortOnMissingResource) {
this.stop(err);
return false;
}
if (this.forOrderedConsumer) {
return false;
}
}
else {
notFound = 0;
streamNotFound = 0;
}
const to = bo.backoff(attempt);
// wait for delay or till the client closes
const de = (0, util_1.delay)(to);
yield Promise.race([de, this.consumer.api.nc.closed()]);
de.cancel();
attempt++;
}
}
});
}
pull(opts) {
var _a, _b;
this.pending.bytes += (_a = opts.max_bytes) !== null && _a !== void 0 ? _a : 0;
this.pending.msgs += (_b = opts.batch) !== null && _b !== void 0 ? _b : 0;
this.pending.requests++;
const nc = this.consumer.api.nc;
//@ts-ignore: iterator will pull
this._push(() => {
nc.publish(`${this.consumer.api.prefix}.CONSUMER.MSG.NEXT.${this.consumer.stream}.${this.consumer.name}`, this.consumer.api.jc.encode(opts), { reply: this.inbox });
this.notify(ConsumerDebugEvents.Next, opts);
});
}
pullOptions() {
const batch = this.opts.max_messages - this.pending.msgs;
const max_bytes = this.opts.max_bytes - this.pending.bytes;
const idle_heartbeat = (0, util_1.nanos)(this.opts.idle_heartbeat);
const expires = (0, util_1.nanos)(this.opts.expires);
return { batch, max_bytes, idle_heartbeat, expires };
}
parseDiscard(headers) {
const discard = {
msgsLeft: 0,
bytesLeft: 0,
};
const msgsLeft = headers === null || headers === void 0 ? void 0 : headers.get(types_1.JsHeaders.PendingMessagesHdr);
if (msgsLeft) {
discard.msgsLeft = parseInt(msgsLeft);
}
const bytesLeft = headers === null || headers === void 0 ? void 0 : headers.get(types_1.JsHeaders.PendingBytesHdr);
if (bytesLeft) {
discard.bytesLeft = parseInt(bytesLeft);
}
return discard;
}
trackTimeout(t) {
this.timeout = t;
}
close() {
this.stop();
return this.iterClosed;
}
closed() {
return this.iterClosed;
}
clearTimers() {
var _a, _b;
(_a = this.monitor) === null || _a === void 0 ? void 0 : _a.cancel();
this.monitor = null;
(_b = this.timeout) === null || _b === void 0 ? void 0 : _b.cancel();
this.timeout = null;
}
setCleanupHandler(fn) {
this.cleanupHandler = fn;
}
stop(err) {
var _a, _b;
if (this.done) {
return;
}
(_a = this.sub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
this.clearTimers();
(_b = this.statusIterator) === null || _b === void 0 ? void 0 : _b.stop();
//@ts-ignore: fn
this._push(() => {
super.stop(err);
this.listeners.forEach((n) => {
n.stop();
});
});
}
parseOptions(opts, refilling = false) {
const args = (opts || {});
args.max_messages = args.max_messages || 0;
args.max_bytes = args.max_bytes || 0;
if (args.max_messages !== 0 && args.max_bytes !== 0) {
throw new Error(`only specify one of max_messages or max_bytes`);
}
// we must have at least one limit - default to 100 msgs
// if they gave bytes but no messages, we will clamp
// if they gave byte limits, we still need a message limit
// or the server will send a single message and close the
// request
if (args.max_messages === 0) {
// FIXME: if the server gives end pull completion, then this is not
// needed - the client will get 1 message but, we'll know that it
// worked - but we'll add a lot of latency, since all requests
// will end after one message
args.max_messages = 100;
}
args.expires = args.expires || 30000;
if (args.expires < 1000) {
throw new Error("expires should be at least 1000ms");
}
// require idle_heartbeat
args.idle_heartbeat = args.idle_heartbeat || args.expires / 2;
args.idle_heartbeat = args.idle_heartbeat > 30000
? 30000
: args.idle_heartbeat;
if (refilling) {
const minMsgs = Math.round(args.max_messages * .75) || 1;
args.threshold_messages = args.threshold_messages || minMsgs;
const minBytes = Math.round(args.max_bytes * .75) || 1;
args.threshold_bytes = args.threshold_bytes || minBytes;
}
return args;
}
status() {
const iter = new queued_iterator_1.QueuedIteratorImpl();
this.listeners.push(iter);
return Promise.resolve(iter);
}
}
exports.PullConsumerMessagesImpl = PullConsumerMessagesImpl;
class OrderedConsumerMessages extends queued_iterator_1.QueuedIteratorImpl {
constructor() {
super();
this.listeners = [];
}
setSource(src) {
if (this.src) {
this.src.resetHandler = undefined;
this.src.setCleanupHandler();
this.src.stop();
}
this.src = src;
this.src.setCleanupHandler((err) => {
this.stop(err || undefined);
});
(() => __awaiter(this, void 0, void 0, function* () {
var _a, e_2, _b, _c;
const status = yield this.src.status();
try {
for (var _d = true, status_2 = __asyncValues(status), status_2_1; status_2_1 = yield status_2.next(), _a = status_2_1.done, !_a; _d = true) {
_c = status_2_1.value;
_d = false;
const s = _c;
this.notify(s.type, s.data);
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_d && !_a && (_b = status_2.return)) yield _b.call(status_2);
}
finally { if (e_2) throw e_2.error; }
}
}))().catch(() => { });
}
notify(type, data) {
if (this.listeners.length > 0) {
(() => {
this.listeners.forEach((l) => {
if (!l.done) {
l.push({ type, data });
}
});
})();
}
}
stop(err) {
var _a;
if (this.done) {
return;
}
(_a = this.src) === null || _a === void 0 ? void 0 : _a.stop(err);
super.stop(err);
this.listeners.forEach((n) => {
n.stop();
});
}
close() {
this.stop();
return this.iterClosed;
}
closed() {
return this.iterClosed;
}
status() {
const iter = new queued_iterator_1.QueuedIteratorImpl();
this.listeners.push(iter);
return Promise.resolve(iter);
}
}
exports.OrderedConsumerMessages = OrderedConsumerMessages;
class PullConsumerImpl {
constructor(api, info) {
this.api = api;
this._info = info;
this.stream = info.stream_name;
this.name = info.name;
}
consume(opts = {
max_messages: 100,
expires: 30000,
}) {
return Promise.resolve(new PullConsumerMessagesImpl(this, opts, true));
}
fetch(opts = {
max_messages: 100,
expires: 30000,
}) {
const m = new PullConsumerMessagesImpl(this, opts, false);
// FIXME: need some way to pad this correctly
const to = Math.round(m.opts.expires * 1.05);
const timer = (0, util_1.timeout)(to);
m.closed().catch(() => {
}).finally(() => {
timer.cancel();
});
timer.catch(() => {
m.close().catch();
});
m.trackTimeout(timer);
return Promise.resolve(m);
}
next(opts = { expires: 30000 }) {
const d = (0, util_1.deferred)();
const fopts = opts;
fopts.max_messages = 1;
const iter = new PullConsumerMessagesImpl(this, fopts, false);
// FIXME: need some way to pad this correctly
const to = Math.round(iter.opts.expires * 1.05);
// watch the messages for heartbeats missed
if (to >= 60000) {
(() => __awaiter(this, void 0, void 0, function* () {
var _a, e_3, _b, _c;
try {
for (var _d = true, _e = __asyncValues(yield iter.status()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const s = _c;
if (s.type === ConsumerEvents.HeartbeatsMissed &&
s.data >= 2) {
d.reject(new Error("consumer missed heartbeats"));
break;
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_3) throw e_3.error; }
}
}))().catch();
}
(() => __awaiter(this, void 0, void 0, function* () {
var _g, e_4, _h, _j;
try {
for (var _k = true, iter_1 = __asyncValues(iter), iter_1_1; iter_1_1 = yield iter_1.next(), _g = iter_1_1.done, !_g; _k = true) {
_j = iter_1_1.value;
_k = false;
const m = _j;
d.resolve(m);
break;
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (!_k && !_g && (_h = iter_1.return)) yield _h.call(iter_1);
}
finally { if (e_4) throw e_4.error; }
}
}))().catch(() => {
// iterator is going to throw, but we ignore it
// as it is handled by the closed promise
});
const timer = (0, util_1.timeout)(to);
iter.closed().then((err) => {
err ? d.reject(err) : d.resolve(null);
}).catch((err) => {
d.reject(err);
}).finally(() => {
timer.cancel();
});
timer.catch((_err) => {
d.resolve(null);
iter.close().catch();
});
iter.trackTimeout(timer);
return d;
}
delete() {
const { stream_name, name } = this._info;
return this.api.delete(stream_name, name);
}
info(cached = false) {
if (cached) {
return Promise.resolve(this._info);
}
const { stream_name, name } = this._info;
return this.api.info(stream_name, name)
.then((ci) => {
this._info = ci;
return this._info;
});
}
}
exports.PullConsumerImpl = PullConsumerImpl;
class OrderedPullConsumerImpl {
constructor(api, stream, opts = {}) {
this.api = api;
this.stream = stream;
this.cursor = { stream_seq: 1, deliver_seq: 0 };
this.namePrefix = nuid_1.nuid.next();
this.serial = 0;
this.currentConsumer = null;
this.userCallback = null;
this.iter = null;
this.type = PullConsumerType.Unset;
this.consumerOpts = opts;
// to support a random start sequence we need to update the cursor
this.startSeq = this.consumerOpts.opt_start_seq || 0;
this.cursor.stream_seq = this.startSeq > 0 ? this.startSeq - 1 : 0;
}
getConsumerOpts(seq) {
// change the serial - invalidating any callback not
// matching the serial
this.serial++;
const name = `${this.namePrefix}_${this.serial}`;
seq = seq === 0 ? 1 : seq;
const config = {
name,
deliver_policy: jsapi_types_1.DeliverPolicy.StartSequence,
opt_start_seq: seq,
ack_policy: jsapi_types_1.AckPolicy.None,
inactive_threshold: (0, util_1.nanos)(5 * 60 * 1000),
num_replicas: 1,
};
if (this.consumerOpts.headers_only === true) {
config.headers_only = true;
}
if (Array.isArray(this.consumerOpts.filterSubjects)) {
config.filter_subjects = this.consumerOpts.filterSubjects;
}
if (typeof this.consumerOpts.filterSubjects === "string") {
config.filter_subject = this.consumerOpts.filterSubjects;
}
if (this.consumerOpts.replay_policy) {
config.replay_policy = this.consumerOpts.replay_policy;
}
// this is the initial request - tweak some options
if (seq === this.startSeq + 1) {
config.deliver_policy = this.consumerOpts.deliver_policy ||
jsapi_types_1.DeliverPolicy.StartSequence;
if (this.consumerOpts.deliver_policy === jsapi_types_1.DeliverPolicy.LastPerSubject ||
this.consumerOpts.deliver_policy === jsapi_types_1.DeliverPolicy.New ||
this.consumerOpts.deliver_policy === jsapi_types_1.DeliverPolicy.Last) {
delete config.opt_start_seq;
config.deliver_policy = this.consumerOpts.deliver_policy;
}
// this requires a filter subject - we only set if they didn't
// set anything, and to be pre-2.10 we set it as filter_subject
if (config.deliver_policy === jsapi_types_1.DeliverPolicy.LastPerSubject) {
if (typeof config.filter_subjects === "undefined" &&
typeof config.filter_subject === "undefined") {
config.filter_subject = ">";
}
}
if (this.consumerOpts.opt_start_time) {
delete config.opt_start_seq;
config.deliver_policy = jsapi_types_1.DeliverPolicy.StartTime;
config.opt_start_time = this.consumerOpts.opt_start_time;
}
if (this.consumerOpts.inactive_threshold) {
config.inactive_threshold = (0, util_1.nanos)(this.consumerOpts.inactive_threshold);
}
}
return config;
}
resetConsumer() {
return __awaiter(this, arguments, void 0, function* (seq = 0) {
var _a, _b, _c, _d;
// try to delete the consumer
(_a = this.consumer) === null || _a === void 0 ? void 0 : _a.delete().catch(() => { });
seq = seq === 0 ? 1 : seq;
// reset the consumer sequence as JetStream will renumber from 1
this.cursor.deliver_seq = 0;
const config = this.getConsumerOpts(seq);
config.max_deliver = 1;
config.mem_storage = true;
const bo = (0, util_1.backoff)();
let ci;
for (let i = 0;; i++) {
try {
ci = yield this.api.add(this.stream, config);
(_b = this.iter) === null || _b === void 0 ? void 0 : _b.notify(ConsumerEvents.OrderedConsumerRecreated, ci.name);
break;
}
catch (err) {
if (err.message === "stream not found") {
// we are not going to succeed
(_c = this.iter) === null || _c === void 0 ? void 0 : _c.notify(ConsumerEvents.StreamNotFound, i);
// if we are not consume - fail it
if (this.type === PullConsumerType.Fetch ||
this.opts.abort_on_missing_resource === true) {
(_d = this.iter) === null || _d === void 0 ? void 0 : _d.stop(err);
return Promise.reject(err);
}
}
if (seq === 0 && i >= 30) {
// consumer was never created, so we can fail this
throw err;
}
else {
yield (0, util_1.delay)(bo.backoff(i + 1));
}
}
}
return ci;
});
}
internalHandler(serial) {
// this handler will be noop if the consumer's serial changes
return (m) => {
var _a;
if (this.serial !== serial) {
return;
}
const dseq = m.info.deliverySequence;
if (dseq !== this.cursor.deliver_seq + 1) {
this.reset(this.opts);
return;
}
this.cursor.deliver_seq = dseq;
this.cursor.stream_seq = m.info.streamSequence;
if (this.userCallback) {
this.userCallback(m);
}
else {
(_a = this.iter) === null || _a === void 0 ? void 0 : _a.push(m);
}
};
}
reset() {
return __awaiter(this, arguments, void 0, function* (opts = {
max_messages: 100,
expires: 30000,
}, fromFetch = false) {
this.currentConsumer = yield this.resetConsumer(this.cursor.stream_seq + 1);
if (this.iter === null) {
this.iter = new OrderedConsumerMessages();
}
this.consumer = new PullConsumerImpl(this.api, this.currentConsumer);
const copts = opts;
copts.callback = this.internalHandler(this.serial);
let msgs = null;
if (this.type === PullConsumerType.Fetch && fromFetch) {
// we only repull if client initiates
msgs = yield this.consumer.fetch(opts);
}
else if (this.type === PullConsumerType.Consume) {
msgs = yield this.consumer.consume(opts);
}
else {
return Promise.reject("reset called with unset consumer type");
}
const msgsImpl = msgs;
msgsImpl.forOrderedConsumer = true;
msgsImpl.resetHandler = () => {
this.reset(this.opts);
};
this.iter.setSource(msgsImpl);
return this.iter;
});
}
consume(opts = {
max_messages: 100,
expires: 30000,
}) {
const copts = opts;
if (copts.bind) {
return Promise.reject(new Error("bind is not supported"));
}
if (this.type === PullConsumerType.Fetch) {
return Promise.reject(new Error("ordered consumer initialized as fetch"));
}
if (this.type === PullConsumerType.Consume) {
return Promise.reject(new Error("ordered consumer doesn't support concurrent consume"));
}
const { callback } = opts;
if (callback) {
this.userCallback = callback;
}
this.type = PullConsumerType.Consume;
this.opts = opts;
return this.reset(opts);
}
fetch(opts = { max_messages: 100, expires: 30000 }) {
var _a;
const copts = opts;
if (copts.bind) {
return Promise.reject(new Error("bind is not supported"));
}
if (this.type === PullConsumerType.Consume) {
return Promise.reject(new Error("ordered consumer already initialized as consume"));
}
if (((_a = this.iter) === null || _a === void 0 ? void 0 : _a.done) === false) {
return Promise.reject(new Error("ordered consumer doesn't support concurrent fetch"));
}
//@ts-ignore: allow this for tests - api doesn't use it because
// iterator close is the user signal that the pull is done.
const { callback } = opts;
if (callback) {
this.userCallback = callback;
}
this.type = PullConsumerType.Fetch;
this.opts = opts;
this.iter = new OrderedConsumerMessages();
return this.reset(opts, true);
}
next() {
return __awaiter(this, arguments, void 0, function* (opts = { expires: 30000 }) {
const copts = opts;
if (copts.bind) {
return Promise.reject(new Error("bind is not supported"));
}
copts.max_messages = 1;
const d = (0, util_1.deferred)();
copts.callback = (m) => {
// we can clobber the callback, because they are not supported
// except on consume, which will fail when we try to fetch
this.userCallback = null;
d.resolve(m);
};
const iter = yield this.fetch(copts);
iter.iterClosed
.then((err) => {
if (err) {
d.reject(err);
}
d.resolve(null);
})
.catch((err) => {
d.reject(err);
});
return d;
});
}
delete() {
if (!this.currentConsumer) {
return Promise.resolve(false);
}
return this.api.delete(this.stream, this.currentConsumer.name)
.then((tf) => {
return Promise.resolve(tf);
})
.catch((err) => {
return Promise.reject(err);
})
.finally(() => {
this.currentConsumer = null;
});
}
info(cached) {
return __awaiter(this, void 0, void 0, function* () {
if (this.currentConsumer == null) {
this.currentConsumer = yield this.resetConsumer(this.serial);
return Promise.resolve(this.currentConsumer);
}
if (cached && this.currentConsumer) {
return Promise.resolve(this.currentConsumer);
}
return this.api.info(this.stream, this.currentConsumer.name);
});
}
}
exports.OrderedPullConsumerImpl = OrderedPullConsumerImpl;
//# sourceMappingURL=consumer.js.map