UNPKG

@graphql-hive/pubsub

Version:
97 lines (93 loc) 2.87 kB
'use strict'; var disposablestack = require('@whatwg-node/disposablestack'); var promiseHelpers = require('@whatwg-node/promise-helpers'); class NATSPubSub { #disposed = false; #closeOnDispose; #activeSubscribers = /* @__PURE__ */ new Map(); #nats; #subjectPrefix; constructor(nats, options) { this.#nats = nats; this.#subjectPrefix = options.subjectPrefix; this.#closeOnDispose = !options.noCloseOnDispose; if (String(this.#subjectPrefix || "").trim() === "") { throw new Error("NATSPubSub requires a non-empty subjectPrefix"); } } #topicToSubject(topic) { return `${this.#subjectPrefix}:${String(topic)}`; } async subscribedTopics() { const distinctTopics = []; const activeTopics = Array.from(this.#activeSubscribers.values()); for (const activeTopic of activeTopics) { if (!distinctTopics.includes(activeTopic)) { distinctTopics.push(activeTopic); } } return distinctTopics; } publish(topic, data) { if (this.#disposed) { throw new Error("PubSub is disposed, cannot publish data"); } this.#nats.publish(this.#topicToSubject(topic), JSON.stringify(data)); } subscribe(topic, listener) { if (this.#disposed) { throw new Error("PubSub is disposed, cannot subscribe to topics"); } if (!listener) { const sub2 = this.#nats.subscribe(this.#topicToSubject(topic)); const drain2 = sub2.drain.bind(sub2); const drainRef2 = new WeakRef(drain2); this.#activeSubscribers.set(drain2, topic); const dispose = (err) => { const drain3 = drainRef2.deref(); if (drain3) this.#activeSubscribers.delete(drain3); return err; }; return promiseHelpers.mapAsyncIterator( sub2, (msg) => msg.json(), dispose, // @ts-expect-error void or undefined is ok dispose ); } const listenerRef = { ref: listener }; const sub = this.#nats.subscribe(this.#topicToSubject(topic), { callback: (_err, msg) => { listenerRef.ref?.(msg.json()); } }); const drain = sub.drain.bind(sub); const drainRef = new WeakRef(drain); const unsubscribe = async () => { listenerRef.ref = null; const drain2 = drainRef.deref(); if (drain2) { this.#activeSubscribers.delete(drain2); await drain2(); } }; this.#activeSubscribers.set(drain, topic); return unsubscribe; } async dispose() { this.#disposed = true; await this.#nats.flush(); await Promise.all( Array.from(this.#activeSubscribers.keys()).map((s) => s()) ); this.#activeSubscribers.clear(); if (this.#closeOnDispose) { await this.#nats.close(); } } [disposablestack.DisposableSymbols.asyncDispose]() { return this.dispose(); } } exports.NATSPubSub = NATSPubSub;