UNPKG

@shirayukikitsune/graphql-kafkajs-subscriptions

Version:

An implementation for the Apollo PubSubEngine using the KafkaJS as backend.

129 lines 6.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.KafkaPubSub = void 0; const graphql_subscriptions_1 = require("graphql-subscriptions"); const kafkajs_1 = require("kafkajs"); const pubsub_async_iterator_1 = require("./pubsub-async-iterator"); const rxjs_1 = require("rxjs"); class KafkaPubSub extends graphql_subscriptions_1.PubSubEngine { constructor(config) { super(); this.config = config; this.subscriptionMap = {}; this.subscriptionRefCount = {}; this.subscriptions = {}; this.idGenerator = KafkaPubSub.subscriptionIdGenerator(); this.kafka = new kafkajs_1.Kafka(config.global); this.log = this.config.log; } async publish(triggerName, payload) { var _a, _b, _c, _d, _e; if (!this.producer) { (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug({ topic: triggerName }, 'Creating producer instance'); await this.createProducer(); (_b = this.log) === null || _b === void 0 ? void 0 : _b.debug({ topic: triggerName }, 'Producer instance created'); } const message = typeof payload === 'string' || 'buffer' in payload ? { value: payload } : payload; (_c = this.log) === null || _c === void 0 ? void 0 : _c.debug({ topic: triggerName }, 'Sending message'); const metadata = await ((_d = this.producer) === null || _d === void 0 ? void 0 : _d.send({ topic: triggerName, messages: [message], })); (_e = this.log) === null || _e === void 0 ? void 0 : _e.debug({ metadata, topic: triggerName }, 'Message sent'); } async subscribe(triggerName, onMessage, options) { var _a, _b, _c; await this.createConsumer(); const subscriptionId = this.idGenerator.next().value; let subject; if (!this.subscriptionMap[triggerName] || this.subscriptionMap[triggerName].closed) { (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug({ topic: triggerName, subscriptionId }, 'No consumers for topic. Creating new subject.'); subject = new rxjs_1.Subject(); this.subscriptionMap[triggerName] = subject; this.subscriptionRefCount[triggerName] = 1; (_b = this.log) === null || _b === void 0 ? void 0 : _b.info({ topic: triggerName, subscriptionId }, 'Subject created'); await this.connectConsumer({ topic: triggerName, fromBeginning: options === null || options === void 0 ? void 0 : options.fromBeginning }); } else { (_c = this.log) === null || _c === void 0 ? void 0 : _c.debug({ topic: triggerName, subscriptionId }, 'Subject found, reusing'); subject = this.subscriptionMap[triggerName]; this.subscriptionRefCount[triggerName]++; } this.subscriptions[subscriptionId] = [ subject.subscribe({ next: value => onMessage(value), }), triggerName, ]; return Promise.resolve(subscriptionId); } asyncIterator(triggers, options = {}) { return new pubsub_async_iterator_1.PubSubAsyncIterator(this, triggers, options); } unsubscribe(subscriptionId) { var _a, _b, _c; (_a = this.log) === null || _a === void 0 ? void 0 : _a.info({ subscriptionId }, 'Closing subscription'); const [subscription, topic] = this.subscriptions[subscriptionId]; subscription.unsubscribe(); const log = (_b = this.log) === null || _b === void 0 ? void 0 : _b.child({ subscriptionId, topic }, true); log === null || log === void 0 ? void 0 : log.info('Decreasing topic RefCount'); if (--this.subscriptionRefCount[topic] === 0) { log === null || log === void 0 ? void 0 : log.debug('Deleting subscription RefCount'); delete this.subscriptionRefCount[topic]; log === null || log === void 0 ? void 0 : log.debug('Finding topic for subscription'); const ref = Object.entries(this.subscriptionMap).find(e => e[0] === topic); if (ref) { log === null || log === void 0 ? void 0 : log.debug('Closing subject'); ref[1].complete(); ref[1].unsubscribe(); log === null || log === void 0 ? void 0 : log.debug('Deleting subscription map entry'); delete this.subscriptionMap[ref[0]]; } if (Object.keys(this.subscriptionRefCount).length === 0) { log === null || log === void 0 ? void 0 : log.info('No subscribers left, pausing consumer'); (_c = this.consumer) === null || _c === void 0 ? void 0 : _c.stop(); } } } async createProducer() { this.producer = this.kafka.producer(this.config.producer); await this.producer.connect(); } async createConsumer() { var _a, _b, _c; if (this.consumer) { return; } (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug('Creating consumer instance'); this.consumer = this.kafka.consumer(this.config.consumer); (_b = this.log) === null || _b === void 0 ? void 0 : _b.debug('Connecting consumer'); await this.consumer.connect(); (_c = this.log) === null || _c === void 0 ? void 0 : _c.debug('Consumer instance created'); } async connectConsumer(options) { var _a, _b, _c; (_a = this.log) === null || _a === void 0 ? void 0 : _a.info('Connecting consumer'); await ((_b = this.consumer) === null || _b === void 0 ? void 0 : _b.subscribe(options)); (_c = this.consumer) === null || _c === void 0 ? void 0 : _c.run({ eachMessage: async (payload) => { var _a; (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(payload, 'Incoming message'); const subject = this.subscriptionMap[payload.topic]; if (subject && !subject.closed) { if (payload.message.value) { const data = JSON.parse(payload.message.value.toString()); subject.next(data); } } }, }); } static *subscriptionIdGenerator() { let index = 0; while (true) { yield ++index; } } } exports.KafkaPubSub = KafkaPubSub; //# sourceMappingURL=kafka-pub-sub.js.map