@yepmind/nats-rx-client
Version:
292 lines • 16.2 kB
JavaScript
;
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.NatsClientService = void 0;
const nats_1 = require("nats");
const rxjs_1 = require("rxjs");
const nats_message_request_js_1 = require("./nats-message-request.js");
const nats_event_message_js_1 = require("./nats-event-message.js");
class NatsClientService {
constructor(options, logger = console) {
this.options = options;
this.logger = logger;
this.connection$ = new rxjs_1.ReplaySubject(1);
this.jsManager$ = new rxjs_1.ReplaySubject(1);
this.jsClient$ = new rxjs_1.ReplaySubject(1);
this.streamInfoMap = new Map();
this.jsonCodec = (0, nats_1.JSONCodec)();
this.DEFAULT_TIMEOUT_MS = 10000;
this.init();
}
init() {
return __awaiter(this, void 0, void 0, function* () {
this.initConnection(this.options)
.pipe((0, rxjs_1.switchMap)((connection) => this.initJetstream(this.options, connection)))
.subscribe();
});
}
// DATA QUEUE behaviour
enqueueData(streamName, event, data) {
this.jsClient$
.pipe((0, rxjs_1.timeout)(this.DEFAULT_TIMEOUT_MS), (0, rxjs_1.take)(1), (0, rxjs_1.switchMap)((jsClient) => jsClient.publish(`${streamName}.${event}`, JSON.stringify(data))))
.subscribe();
}
dequeueData(streamName, event = '*', consumerName = `durable_${streamName}_${event != '*' ? event : '_asterisk_'}`, clientId) {
return (0, rxjs_1.combineLatest)({
params: (0, rxjs_1.of)({
streamName,
event,
consumerName,
clientId,
streamOptions: this.options.streams.find((s) => s.name === streamName),
}),
jsClient: this.jsClient$,
jsManager: this.jsManager$,
}).pipe((0, rxjs_1.switchMap)((_a) => __awaiter(this, [_a], void 0, function* ({ params, jsClient, jsManager }) {
const existingConsumer = yield jsClient.consumers
.get(params.streamName, params.consumerName)
.catch(() => null);
const consumerInfo = yield (existingConsumer
? jsManager.consumers.update(params.streamName, consumerName, {
ack_wait: (0, nats_1.nanos)(this.DEFAULT_TIMEOUT_MS),
max_deliver: 2,
})
: jsManager.consumers.add(params.streamName, {
durable_name: params.consumerName,
ack_policy: nats_1.AckPolicy.Explicit,
max_ack_pending: 1,
filter_subjects: [
`${params.streamName}.${params.event}`,
],
}));
return jsClient.consumers
.get(consumerInfo.stream_name, consumerInfo.name)
.then((consumer) => ({ consumer, params }))
.catch((err) => ({ consumer: null, params }));
})), (0, rxjs_1.switchMap)(({ consumer, params }) => {
var _a;
this.logger.log(`new consumer loop for getting data from consumer ${params.consumerName} for event ${params.event} on stream ${params.streamName}, client id: ${(_a = params.clientId) !== null && _a !== void 0 ? _a : 'N/A'}`);
return new rxjs_1.Observable((subs) => {
var _a;
const consumerLoop$$ = (0, rxjs_1.of)(consumer)
.pipe((0, rxjs_1.switchMap)((consumer) => (0, rxjs_1.from)(consumer.next())), (0, rxjs_1.map)((msg) => {
const value = (msg === null || msg === void 0 ? void 0 : msg.data.length) > 0 ? msg.json() : null;
msg === null || msg === void 0 ? void 0 : msg.ack();
return value;
}), (0, rxjs_1.tap)((value) => value ? subs.next(value) : null), (0, rxjs_1.repeat)({
delay: (_a = params.streamOptions.consumerDelayMs) !== null && _a !== void 0 ? _a : 300,
}), (0, rxjs_1.catchError)(() => {
var _a;
this.logger.error(`error getting data from consumer ${params.consumerName} for event ${params.event} on stream ${params.streamName}, client id: ${(_a = params.clientId) !== null && _a !== void 0 ? _a : 'N/A'}`);
return (0, rxjs_1.of)(null);
}))
.subscribe({
error: (err) => subs.error(err),
complete: () => subs.complete(),
});
return () => consumerLoop$$.unsubscribe();
});
}));
}
// ----- end QUEUE
// MESSAGE behaviour
requestMessage(subject, data, timeoutMs = this.DEFAULT_TIMEOUT_MS) {
return this.connection$.pipe((0, rxjs_1.timeout)(this.DEFAULT_TIMEOUT_MS), (0, rxjs_1.take)(1), (0, rxjs_1.switchMap)((connection) => connection.request(subject, this.jsonCodec.encode(data), {
timeout: timeoutMs,
})), (0, rxjs_1.map)((resp) => this.jsonCodec.decode(resp.data)));
}
eventMessage(subject, data) {
this.connection$
.pipe((0, rxjs_1.timeout)(this.DEFAULT_TIMEOUT_MS), (0, rxjs_1.take)(1), (0, rxjs_1.tap)((connection) => connection.publish(subject, this.jsonCodec.encode(data))))
.subscribe();
}
subscribeToRequestMessage(subject, group) {
return this.connection$.pipe((0, rxjs_1.switchMap)((connection) => {
return new rxjs_1.Observable((obs) => {
const subscription = connection.subscribe(subject, {
queue: group,
});
((sub) => __awaiter(this, void 0, void 0, function* () {
var _a, sub_1, sub_1_1;
var _b, e_1, _c, _d;
this.logger.debug(`listening for ${sub.getSubject()} requests...`);
try {
for (_a = true, sub_1 = __asyncValues(sub); sub_1_1 = yield sub_1.next(), _b = sub_1_1.done, !_b; _a = true) {
_d = sub_1_1.value;
_a = false;
const m = _d;
obs.next(new nats_message_request_js_1.NatsMessageRequest(m));
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_a && !_b && (_c = sub_1.return)) yield _c.call(sub_1);
}
finally { if (e_1) throw e_1.error; }
}
this.logger.debug(`subscription ${sub.getSubject()} drained.`);
}))(subscription);
return () => subscription.unsubscribe();
});
}));
}
subscribeToEventMessage(subject, group) {
return this.connection$.pipe((0, rxjs_1.switchMap)((connection) => {
return new rxjs_1.Observable((obs) => {
const subscription = connection.subscribe(subject, {
queue: group,
});
((sub) => __awaiter(this, void 0, void 0, function* () {
var _a, sub_2, sub_2_1;
var _b, e_2, _c, _d;
this.logger.debug(`listening for ${sub.getSubject()} requests...`);
try {
for (_a = true, sub_2 = __asyncValues(sub); sub_2_1 = yield sub_2.next(), _b = sub_2_1.done, !_b; _a = true) {
_d = sub_2_1.value;
_a = false;
const m = _d;
obs.next(new nats_event_message_js_1.NatsEventMessage(m));
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_a && !_b && (_c = sub_2.return)) yield _c.call(sub_2);
}
finally { if (e_2) throw e_2.error; }
}
this.logger.debug(`subscription ${sub.getSubject()} drained.`);
}))(subscription);
return () => subscription.unsubscribe();
});
}));
}
// ----- end MESSAGE
// GENERIC
consumerInfo(streamName) {
return this.jsManager$.pipe((0, rxjs_1.take)(1), (0, rxjs_1.switchMap)((jsManager) => (0, rxjs_1.from)(jsManager.consumers.info(streamName, `durable_${streamName}`))));
}
purgeStream(streamName) {
return this.jsManager$.pipe((0, rxjs_1.take)(1), (0, rxjs_1.switchMap)((jsManager) => (0, rxjs_1.from)(jsManager.streams.purge(streamName))));
}
streamInfo(streamName) {
return this.jsManager$.pipe((0, rxjs_1.take)(1), (0, rxjs_1.switchMap)((jsManager) => (0, rxjs_1.from)(jsManager.streams.get(streamName)).pipe((0, rxjs_1.switchMap)((stream) => (0, rxjs_1.from)(stream.info())))));
}
// ----- end GENERIC
monitorClient(connection) {
return __awaiter(this, void 0, void 0, function* () {
var _a, e_3, _b, _c;
connection.closed().then((error) => {
this.init();
});
try {
for (var _d = true, _e = __asyncValues(connection.status()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const s = _c;
switch (s.type) {
case nats_1.Events.Disconnect:
this.logger.log(`client disconnected - ${s.data}`);
break;
case nats_1.Events.LDM:
this.logger.log('client has been requested to reconnect');
break;
case nats_1.Events.Update:
this.logger.log(`client received a cluster update - ${JSON.stringify(s.data)}`);
break;
case nats_1.Events.Reconnect:
this.logger.log(`client reconnected - ${s.data}`);
break;
case nats_1.Events.Error:
this.logger.log('client got a permissions error');
break;
case nats_1.DebugEvents.Reconnecting:
this.logger.log('client is attempting to reconnect');
break;
case nats_1.DebugEvents.StaleConnection:
this.logger.log('client has a stale connection');
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; }
}
});
}
initConnection(options) {
return (0, rxjs_1.of)(options).pipe((0, rxjs_1.switchMap)((options) => (0, rxjs_1.from)((0, nats_1.connect)({ servers: options.servers }))), (0, rxjs_1.tap)((connection) => {
this.logger.log(`Successfully connected to NATS server ${connection.info.host}`);
this.monitorClient(connection);
this.connection$.next(connection);
}), (0, rxjs_1.catchError)((err) => {
this.logger.error('Error connecting to NATS server!');
throw err;
}), (0, rxjs_1.retry)({
delay: (error, retryCount) => {
return (0, rxjs_1.of)(retryCount).pipe((0, rxjs_1.tap)(() => this.logger.warn(`Waiting 30s before retrying to reconnect to NATS server...`)), (0, rxjs_1.delay)(30000), (0, rxjs_1.tap)((retryCount) => this.logger.warn(`Trying to reconnect to NATS server (attempt no.${retryCount})...`)));
},
resetOnSuccess: true,
}));
}
initJetstream(options, connection) {
return (0, rxjs_1.of)(connection).pipe((0, rxjs_1.switchMap)((connection) => (0, rxjs_1.of)(connection).pipe((0, rxjs_1.switchMap)((connection) => (0, rxjs_1.combineLatest)({
jsClient: (0, rxjs_1.of)(connection.jetstream()).pipe((0, rxjs_1.catchError)((err) => {
this.logger.error('Error getting the JETSTREAM client!');
throw err;
})),
jsManager: (0, rxjs_1.from)(connection.jetstreamManager()).pipe((0, rxjs_1.catchError)((err) => {
this.logger.error('Error getting the JETSTREAM manager!');
throw err;
})),
})), (0, rxjs_1.tap)((jetstream) => {
this.jsClient$.next(jetstream.jsClient);
this.jsManager$.next(jetstream.jsManager);
}), (0, rxjs_1.retry)({
delay: (error, retryCount) => {
return (0, rxjs_1.of)(retryCount).pipe((0, rxjs_1.tap)(() => this.logger.warn(`Waiting 30s before retrying to get JETSTREAM from NATS server...`)), (0, rxjs_1.delay)(30000), (0, rxjs_1.tap)((retryCount) => this.logger.warn(`Trying to reconnect to get JETSTREAM from NATS server (attempt no.${retryCount})...`)));
},
resetOnSuccess: true,
}))), (0, rxjs_1.switchMap)((jetstream) => this.initStreams(options, jetstream.jsManager).pipe((0, rxjs_1.map)((streamsInfo) => (Object.assign(Object.assign({}, jetstream), { streamsInfo }))))));
}
initStreams(options, jsManager) {
return (0, rxjs_1.of)({ options, jsManager }).pipe((0, rxjs_1.switchMap)(({ options, jsManager }) => (0, rxjs_1.from)(options.streams.map((i) => i.name)).pipe((0, rxjs_1.mergeMap)((streamName) => (0, rxjs_1.from)(jsManager.streams.get(streamName).catch(() => null)).pipe((0, rxjs_1.map)((stream) => ({
stream,
streamName,
jsManager,
})))), (0, rxjs_1.mergeMap)(({ stream, streamName, jsManager }) => {
if (stream)
return (0, rxjs_1.from)(stream.info());
return (0, rxjs_1.from)(jsManager.streams.add({
name: streamName,
subjects: [`${streamName}.*`],
retention: nats_1.RetentionPolicy.Interest,
}));
}), (0, rxjs_1.tap)((streamInfo) => this.streamInfoMap.set(streamInfo.config.name, streamInfo)), (0, rxjs_1.catchError)((err) => {
this.logger.error('Error initializing NATS stream!');
throw err;
}))), (0, rxjs_1.toArray)());
}
}
exports.NatsClientService = NatsClientService;
//# sourceMappingURL=nats-client.service.js.map