graphql-redis-subscriptions
Version:
A graphql-subscriptions PubSub Engine using redis
172 lines • 6.76 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisPubSub = void 0;
const pubsub_async_iterator_1 = require("./pubsub-async-iterator");
class RedisPubSub {
constructor(options = {}) {
const { triggerTransform, connection, connectionListener, subscriber, publisher, reviver, serializer, deserializer, messageEventName = 'message', pmessageEventName = 'pmessage', } = options;
this.triggerTransform = triggerTransform || (trigger => trigger);
if (reviver && deserializer) {
throw new Error("Reviver and deserializer can't be used together");
}
this.reviver = reviver;
this.serializer = serializer;
this.deserializer = deserializer;
if (subscriber && publisher) {
this.redisPublisher = publisher;
this.redisSubscriber = subscriber;
}
else {
try {
const IORedis = require('ioredis');
this.redisPublisher = new IORedis(connection);
this.redisSubscriber = new IORedis(connection);
if (connectionListener) {
this.redisPublisher
.on('connect', connectionListener)
.on('error', connectionListener);
this.redisSubscriber
.on('connect', connectionListener)
.on('error', connectionListener);
}
else {
this.redisPublisher.on('error', console.error);
this.redisSubscriber.on('error', console.error);
}
}
catch (error) {
console.error(`No publisher or subscriber instances were provided and the package 'ioredis' wasn't found. Couldn't create Redis clients.`);
}
}
this.redisSubscriber.on(pmessageEventName, this.onMessage.bind(this));
this.redisSubscriber.on(messageEventName, this.onMessage.bind(this, undefined));
this.subscriptionMap = {};
this.subsRefsMap = new Map();
this.subsPendingRefsMap = new Map();
this.currentSubscriptionId = 0;
}
async publish(trigger, payload) {
if (this.serializer) {
await this.redisPublisher.publish(trigger, this.serializer(payload));
}
else if (payload instanceof Buffer) {
await this.redisPublisher.publish(trigger, payload);
}
else {
await this.redisPublisher.publish(trigger, JSON.stringify(payload));
}
}
subscribe(trigger, onMessage, options = {}) {
const triggerName = this.triggerTransform(trigger, options);
const id = this.currentSubscriptionId++;
this.subscriptionMap[id] = [triggerName, onMessage];
if (!this.subsRefsMap.has(triggerName)) {
this.subsRefsMap.set(triggerName, new Set());
}
const refs = this.subsRefsMap.get(triggerName);
const pendingRefs = this.subsPendingRefsMap.get(triggerName);
if (pendingRefs != null) {
pendingRefs.refs.push(id);
return pendingRefs.pending.then(() => id);
}
else if (refs.size > 0) {
refs.add(id);
return Promise.resolve(id);
}
else {
const pending = new Deferred();
const subsPendingRefsMap = this.subsPendingRefsMap;
subsPendingRefsMap.set(triggerName, { refs: [], pending });
const sub = new Promise((resolve, reject) => {
const subscribeFn = options['pattern'] ? this.redisSubscriber.psubscribe : this.redisSubscriber.subscribe;
subscribeFn.call(this.redisSubscriber, triggerName, err => {
if (err) {
subsPendingRefsMap.delete(triggerName);
reject(err);
}
else {
const pendingRefs = subsPendingRefsMap.get(triggerName);
pendingRefs.refs.forEach((id) => refs.add(id));
subsPendingRefsMap.delete(triggerName);
refs.add(id);
resolve(id);
}
});
});
sub.then(pending.resolve).catch(pending.reject);
return sub;
}
}
unsubscribe(subId) {
const [triggerName = null] = this.subscriptionMap[subId] || [];
const refs = this.subsRefsMap.get(triggerName);
if (!refs)
throw new Error(`There is no subscription of id "${subId}"`);
if (refs.size === 1) {
this.redisSubscriber.unsubscribe(triggerName);
this.redisSubscriber.punsubscribe(triggerName);
this.subsRefsMap.delete(triggerName);
}
else {
refs.delete(subId);
}
delete this.subscriptionMap[subId];
}
asyncIterator(triggers, options) {
return new pubsub_async_iterator_1.PubSubAsyncIterator(this, triggers, options);
}
asyncIterableIterator(triggers, options) {
return new pubsub_async_iterator_1.PubSubAsyncIterator(this, triggers, options);
}
getSubscriber() {
return this.redisSubscriber;
}
getPublisher() {
return this.redisPublisher;
}
close() {
return Promise.all([
this.redisPublisher.quit(),
this.redisSubscriber.quit(),
]);
}
onMessage(pattern, channel, message) {
if (typeof channel === 'object')
channel = channel.toString('utf8');
const subscribers = this.subsRefsMap.get(pattern || channel);
if (!subscribers?.size)
return;
let parsedMessage;
try {
if (this.deserializer) {
parsedMessage = this.deserializer(Buffer.from(message), { pattern, channel });
}
else if (typeof message === 'string') {
parsedMessage = JSON.parse(message, this.reviver);
}
else {
parsedMessage = message;
}
}
catch (e) {
parsedMessage = message;
}
subscribers.forEach(subId => {
const [, listener] = this.subscriptionMap[subId];
listener(parsedMessage);
});
}
}
exports.RedisPubSub = RedisPubSub;
function Deferred() {
const p = this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
this.then = p.then.bind(p);
this.catch = p.catch.bind(p);
if (p.finally) {
this.finally = p.finally.bind(p);
}
}
//# sourceMappingURL=redis-pubsub.js.map