UNPKG

@hotmeshio/hotmesh

Version:

Permanent-Memory Workflows & AI Agents

202 lines (201 loc) 7.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkerService = void 0; const key_1 = require("../../modules/key"); const utils_1 = require("../../modules/utils"); const factory_1 = require("../connector/factory"); const router_1 = require("../router"); const stream_1 = require("../../types/stream"); const enums_1 = require("../../modules/enums"); const factory_2 = require("../stream/factory"); const factory_3 = require("../sub/factory"); const factory_4 = require("../store/factory"); class WorkerService { /** * @private */ constructor() { this.reporting = false; } /** * @private */ static async init(namespace, appId, guid, config, logger) { const services = []; if (Array.isArray(config.workers)) { for (const worker of config.workers) { // Pass taskQueue from top-level config to worker for connection pooling if (config.taskQueue) { worker.taskQueue = config.taskQueue; } await factory_1.ConnectorService.initClients(worker); const service = new WorkerService(); service.verifyWorkerFields(worker); service.namespace = namespace; service.appId = appId; service.guid = guid; service.callback = worker.callback; service.topic = worker.topic; service.config = config; service.logger = logger; await service.initStoreChannel(service, worker.store); await service.initSubChannel(service, worker.sub, worker.pub ?? worker.store); await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId); await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId, service.topic); await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId, service.guid); await service.initStreamChannel(service, worker.stream, worker.store); service.router = await service.initRouter(worker, logger); const key = service.store.mintKey(key_1.KeyType.STREAMS, { appId: service.appId, topic: worker.topic, }); await service.router.consumeMessages(key, 'WORKER', service.guid, worker.callback); service.inited = (0, utils_1.formatISODate)(new Date()); services.push(service); } } return services; } /** * @private */ verifyWorkerFields(worker) { if (!(0, utils_1.identifyProvider)(worker.store) || !(0, utils_1.identifyProvider)(worker.stream) || !(0, utils_1.identifyProvider)(worker.sub) || !(worker.topic && worker.callback)) { throw new Error('worker must include `store`, `stream`, and `sub` fields along with a callback function and topic.'); } } /** * @private */ async initStoreChannel(service, store) { service.store = await factory_4.StoreServiceFactory.init(store, service.namespace, service.appId, service.logger); } /** * @private */ async initSubChannel(service, sub, store) { service.subscribe = await factory_3.SubServiceFactory.init(sub, store, service.namespace, service.appId, service.guid, service.logger); } /** * @private */ async initStreamChannel(service, stream, store) { service.stream = await factory_2.StreamServiceFactory.init(stream, store, service.namespace, service.appId, service.logger); } /** * @private */ async initRouter(worker, logger) { const throttle = await this.store.getThrottleRate(worker.topic); return new router_1.Router({ namespace: this.namespace, appId: this.appId, guid: this.guid, role: stream_1.StreamRole.WORKER, topic: worker.topic, reclaimDelay: worker.reclaimDelay, reclaimCount: worker.reclaimCount, throttle, }, this.stream, logger); } /** * @private */ subscriptionHandler() { const self = this; return async (topic, message) => { self.logger.debug('worker-event-received', { topic, type: message.type }); if (message.type === 'throttle') { if (message.topic !== null) { //undefined allows passthrough self.throttle(message.throttle); } } else if (message.type === 'ping') { self.sayPong(self.appId, self.guid, message.originator, message.details); } else if (message.type === 'rollcall') { if (message.topic !== null) { //undefined allows passthrough self.doRollCall(message); } } }; } /** * A quorum-wide command to broadcaset system details. * */ async doRollCall(message) { let iteration = 0; const max = !isNaN(message.max) ? message.max : enums_1.HMSH_QUORUM_ROLLCALL_CYCLES; if (this.rollCallInterval) clearTimeout(this.rollCallInterval); const base = message.interval / 2; const amount = base + Math.ceil(Math.random() * base); do { await (0, utils_1.sleepFor)(Math.ceil(Math.random() * 1000)); await this.sayPong(this.appId, this.guid, null, true, message.signature); if (!message.interval) return; const { promise, timerId } = (0, utils_1.XSleepFor)(amount * 1000); this.rollCallInterval = timerId; await promise; } while (this.rollCallInterval && iteration++ < max - 1); } cancelRollCall() { if (this.rollCallInterval) { clearTimeout(this.rollCallInterval); delete this.rollCallInterval; } } /** * @private */ stop() { this.cancelRollCall(); } /** * @private */ async sayPong(appId, guid, originator, details = false, signature = false) { let profile; if (details) { const params = { appId: this.appId, topic: this.topic, }; profile = { engine_id: this.guid, namespace: this.namespace, app_id: this.appId, worker_topic: this.topic, stream: this.store.mintKey(key_1.KeyType.STREAMS, params), counts: this.router?.counts, timestamp: (0, utils_1.formatISODate)(new Date()), inited: this.inited, throttle: this.router?.throttle, reclaimDelay: this.router?.reclaimDelay, reclaimCount: this.router?.reclaimCount, system: await (0, utils_1.getSystemHealth)(), signature: signature ? this.callback.toString() : undefined, }; } this.subscribe.publish(key_1.KeyType.QUORUM, { type: 'pong', guid, originator, profile, }, appId); } /** * @private */ async throttle(delayInMillis) { this.router?.setThrottle(delayInMillis); } } exports.WorkerService = WorkerService;