@graphql-hive/pubsub
Version:
97 lines (93 loc) • 2.87 kB
JavaScript
;
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;