UNPKG

@hotmeshio/hotmesh

Version:

Permanent-Memory Workflows & AI Agents

149 lines (148 loc) 6.12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisSubService = void 0; const key_1 = require("../../../../modules/key"); const index_1 = require("../../index"); class RedisSubService extends index_1.SubService { constructor(eventClient, storeClient) { super(eventClient, storeClient); this.subscriptions = new Map(); } async init(namespace = key_1.HMNS, appId, engineId, logger) { this.namespace = namespace; this.logger = logger; this.appId = appId; this.engineId = engineId; } transact() { const multi = this.eventClient.multi(); return multi; } mintKey(type, params) { if (!this.namespace) throw new Error('namespace not set'); return key_1.KeyService.mintKey(this.namespace, type, params); } async subscribe(keyType, callback, appId, engineId) { if (this.eventClient) { const topic = this.mintKey(keyType, { appId, engineId }); // Add callback to set (supports multiple subscribers per topic) let callbacks = this.subscriptions.get(topic); const isFirstSubscription = !callbacks; if (!callbacks) { callbacks = new Set(); this.subscriptions.set(topic, callbacks); } callbacks.add(callback); this.logger.debug('redis-subscribe', { topic, isFirstSubscription, totalCallbacks: callbacks.size, }); // Only subscribe to Redis once per topic if (isFirstSubscription) { await this.eventClient.subscribe(topic, (message) => { try { const payload = JSON.parse(message); const cbs = this.subscriptions.get(topic); this.logger.debug('redis-message-received', { topic, callbackCount: cbs?.size || 0, payload, }); if (cbs) { // Call all callbacks for this topic cbs.forEach(cb => { try { cb(topic, payload); } catch (err) { this.logger.error(`Error in callback for ${topic}`, err); } }); } } catch (e) { this.logger.error(`Error parsing message: ${message}`, e); } }); } } } async unsubscribe(keyType, appId, engineId) { const topic = this.mintKey(keyType, { appId, engineId }); const callbacks = this.subscriptions.get(topic); this.logger.debug('redis-unsubscribe', { topic, hadCallbacks: !!callbacks, callbackCount: callbacks?.size || 0, }); // Only unsubscribe from Redis if no more callbacks exist if (callbacks && callbacks.size > 0) { this.subscriptions.delete(topic); await this.eventClient.unsubscribe(topic); } } async psubscribe(keyType, callback, appId, engineId) { if (this.eventClient) { const topic = this.mintKey(keyType, { appId, engineId }); // Add callback to set (supports multiple subscribers per topic) let callbacks = this.subscriptions.get(topic); if (!callbacks) { callbacks = new Set(); this.subscriptions.set(topic, callbacks); } callbacks.add(callback); await this.eventClient.pSubscribe(topic, (message, channel) => { try { const payload = JSON.parse(message); const cbs = this.subscriptions.get(topic); if (cbs) { // Call all callbacks for this topic cbs.forEach(cb => { try { cb(channel, payload); } catch (err) { this.logger.error(`Error in callback for ${topic}`, err); } }); } } catch (e) { this.logger.error(`Error parsing message: ${message}`, e); } }); } } async punsubscribe(keyType, appId, engineId) { const topic = this.mintKey(keyType, { appId, engineId }); const callbacks = this.subscriptions.get(topic); // Only unsubscribe from Redis if no more callbacks exist if (callbacks && callbacks.size > 0) { this.subscriptions.delete(topic); await this.eventClient.pUnsubscribe(topic); } } async publish(keyType, message, appId, engineId) { const topic = this.mintKey(keyType, { appId, engineId }); //NOTE: `storeClient.publish` is used, // because a Redis connection with subscriptions // may not publish (is read only). this.logger.debug('redis-publish', { topic, message }); try { const status = await this.storeClient.publish(topic, JSON.stringify(message)); return status > 0; } catch (error) { // Connection closed during test cleanup - log and continue gracefully if (error?.message?.includes('Connection is closed') || error?.message?.includes('connection closed')) { this.logger.debug('redis-publish-connection-closed', { topic, message: message.type }); return false; } // Re-throw unexpected errors throw error; } } } exports.RedisSubService = RedisSubService;