UNPKG

valkey-redis-eventbus

Version:

A lightweight event bus implementation using Redis (Valkey) for scalable inter-process communication in Node.js applications.

162 lines (161 loc) 5.75 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EventBus = void 0; const redis_1 = require("redis"); class EventBus { /** * Private constructor for EventBus. Use EventBus.create() to instantiate. * @param name Name of the EventBus instance * @param prefix Prefix for Redis channels * @param pub Redis client for publishing * @param sub Redis client for subscribing */ constructor(name, prefix, pub, sub) { this._name = name; this._prefix = prefix; this._pub = pub; this._sub = sub; } /** * Initializes the EventBus by subscribing to internal 'ping' events. */ async init() { await this._on("ping", () => { this._emit("pong", "", true); }, true); } /** * Registers a callback for a specific event. * @param event Event name to listen for * @param callback Function to call when the event is received */ async on(event, callback) { return this._on(event, callback, false); } /** * Internal method to subscribe to an event channel. * @param event Event name * @param callback Callback to execute on event * @param internalCall Whether this is an internal event */ async _on(event, callback, internalCall = true) { if (!internalCall && this.isReservedEventName(event)) { throw new Error(`Reserved event name ${event} cannot be registered`); } const fullChannel = this.getPrefixedChannelName(event); // Subscribe to the event channel await this._sub.subscribe(fullChannel, (message) => { // Already an object callback(message); }); } /** * Emits an event with the given payload. * @param event Event name * @param payload Data to send */ emit(event, payload) { this._emit(event, payload, false); } /** * Internal method to publish an event. * @param event Event name * @param payload Data to send * @param internalCall Whether this is an internal event */ _emit(event, payload, internalCall = true) { if (!internalCall && this.isReservedEventName(event)) { throw new Error(`Reserved event name ${event} cannot be emitted`); } const message = typeof payload === "string" ? payload : JSON.stringify(payload); this._pub.publish(this.getPrefixedChannelName(event), message); } /** * Pings all EventBus instances and waits for pong responses. * @param timeout Timeout in ms to wait for responses * @param minResponseCount Minimum number of responses required * @returns Promise resolving to true if enough responses are received */ ping(timeout = 3000, minResponseCount = 1) { return new Promise(async (resolve) => { let responseCount = 0; const timeoutRef = setTimeout(() => { //+1 because our instance itself will respond resolve(responseCount >= minResponseCount + 1); }, timeout); await this._on("pong", () => { responseCount++; //+1 because our instance itself will respond if (responseCount >= minResponseCount + 1) { //Cleat timeout clearTimeout(timeoutRef); resolve(true); } }, true); this._emit("ping", "", true); }); } /** * Cleans up the EventBus instance and closes Redis connections. */ async destroy() { await this._sub.unsubscribe(); await this._sub.quit(); await this._pub.quit(); EventBus._eventBusInstances.delete(this._name); } /** * Returns true if both Redis clients are connected. */ get connected() { return this._pub.isOpen && this._sub.isOpen; } /** * Returns the channel name with prefix applied. * @param channel Channel name * @returns Prefixed channel name */ getPrefixedChannelName(channel) { return `${this._prefix.length > 0 ? `${this._prefix}:` : ""}${channel}`; } /** * Checks if the event name is reserved (internal use). * @param event Event name * @returns True if reserved */ isReservedEventName(event) { return event === "ping" || event === "pong"; } /** * Creates a new EventBus instance or returns an existing one. * @param name Name of the EventBus instance * @param clientOpts Redis client options * @returns EventBus instance */ static async create(name, clientOpts = {}, prefix = "") { const existing = EventBus._eventBusInstances.get(name); if (existing) return existing; const channelPrefix = `${prefix !== undefined ? prefix : ""}node-redis-eventbus:${name}`; const pub = (0, redis_1.createClient)(clientOpts); const sub = (0, redis_1.createClient)(clientOpts); await Promise.all([pub.connect(), sub.connect()]); const instance = new EventBus(name, channelPrefix, pub, sub); await instance.init(); EventBus._eventBusInstances.set(name, instance); return instance; } /** * Retrieves an EventBus instance by name. * @param name Name of the EventBus instance * @returns EventBus instance */ static getByName(name) { const instance = EventBus._eventBusInstances.get(name); if (!instance) throw new Error(`EventBus ${name} not found.`); return instance; } } exports.EventBus = EventBus; EventBus._eventBusInstances = new Map();