UNPKG

@yepmind/nats-rx-client

Version:
292 lines 16.2 kB
"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.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