UNPKG

messageworks

Version:

MessageWorks is a flexible framework for task orchestration, messaging, and workflow management across distributed systems. Simplify your processes with seamless communication and coordination.

981 lines (913 loc) 38.5 kB
'use strict'; var uuid = require('uuid'); exports.MessageType = void 0; (function (MessageType) { MessageType["GENERAL"] = "general"; MessageType["REQUEST"] = "request"; MessageType["RESPONSE"] = "response"; })(exports.MessageType || (exports.MessageType = {})); /** * Convert a Messenger object into an array of strings. * If the Messenger is already an array, return it as is. * If it's a string, split it into an array of strings using '/' as the separator. * @param messenger The Messenger object * @returns An array of strings */ function messengerAsArray(messenger) { if (Array.isArray(messenger)) { // If it's already an array, return it return normalizeMessenger(messenger); } else { // If it's a string, split it into an array of strings return normalizeMessenger(messenger) .split('/') .map((item) => item.trim()) .filter(Boolean); } } /** * Convert a Messenger object into a string. * If the Messenger is already a string, return it as is. * If it's an array, join the elements into a single string using '/' as the separator. * @param messenger The Messenger object * @returns A string representation of the Messenger object */ function messengerAsString(messenger) { if (typeof messenger === 'string') { // If it's already a string, return it return '/' + normalizeMessenger(messenger); } else { // If it's an array, join the elements into a single string using '/' separator return ('/' + normalizeMessenger(messenger) .map((item) => item.trim()) .filter(Boolean) .join('/')); } } /** * Normalize a Messenger object by applying path normalization techniques: * - Trim leading/trailing slashes * - Remove redundant slashes (multiple consecutive slashes) * - Remove empty segments * @param messenger The Messenger object (string or array) * @returns A normalized Messenger object (string or array) */ function normalizeMessenger(messenger) { if (messenger === null || messenger === undefined) { throw new Error(`Invalid messenger: ${messenger}`); } if (Array.isArray(messenger)) { return messenger .map((item) => item.trim()) // Trim each element in the array .filter(Boolean) // Remove any empty elements (e.g., "") .join('/') // Join into a string using '/' .split('/') // Split back into array to handle redundant slashes .filter(Boolean); // Remove any potential empty segments } else { // Normalize the string: // 1. Trim leading/trailing spaces first // 2. Trim leading/trailing slashes // 3. Replace multiple consecutive slashes with a single slash // 4. Remove any empty segments return messenger .trim() // Trim leading/trailing spaces .replace(/^\/+|\/+$/g, '') // Trim leading/trailing slashes .replace(/\/+/g, '/') // Replace multiple consecutive slashes with a single slash .split('/') // Split into segments .filter(Boolean) // Remove empty segments .join('/'); // Rejoin into a normalized string with single slashes } } /** * Compares two Messenger objects to check if they are equivalent. * This function ensures both `messenger1` and `messenger2` are valid (not null or undefined) * and compares them as arrays. * * @param {Messenger} messenger1 The first Messenger to compare. * @param {Messenger} messenger2 The second Messenger to compare. * @returns {boolean} Returns `true` if both messengers are equal after converting them to strings, * otherwise returns `false`. */ function messengersAreEqual(messenger1, messenger2) { if (!messenger1 || !messenger2) { return false; } return messengerAsString(messenger1) === messengerAsString(messenger2); } /** * Determines if one Messenger (`there`) is upstream from another (`here`), based on their relative levels * in a hierarchical messenger structure. * * This function compares two Messenger objects and returns `true` if the second Messenger (`there`) * is upstream from the first Messenger (`here`), according to their hierarchical levels and names. * The comparison is made based on the level and name of the destination in each Messenger. * * A "higher" or "upstream" Messenger is considered to be a "parent" or "ancestor" * in the hierarchical structure, whereas a "lower" or "downstream" Messenger is a "child" * or "descendant". * * @param {Messenger} here The first Messenger to compare (the one that is the starting point). * @param {Messenger} there The second Messenger to compare (the one being checked to see if it is upstream or downstream). * @returns {boolean} Returns `true` if `there` is upstream from `here`, otherwise returns `false`. */ function messengerIsUpstream(here, there) { const from = messengerAsArray(here); const fromLevel = from.length; const to = messengerAsArray(there); const toLevel = to.length; if (fromLevel === 0) { return false; } if (toLevel < fromLevel) { return true; } for (let i = 0; i < fromLevel; i++) { if (from[i] !== to[i]) { return true; } } if (fromLevel === toLevel) { return false; } return false; } var loglevel$1 = {exports: {}}; /* * loglevel - https://github.com/pimterry/loglevel * * Copyright (c) 2013 Tim Perry * Licensed under the MIT license. */ var loglevel = loglevel$1.exports; var hasRequiredLoglevel; function requireLoglevel () { if (hasRequiredLoglevel) return loglevel$1.exports; hasRequiredLoglevel = 1; (function (module) { (function (root, definition) { if (module.exports) { module.exports = definition(); } else { root.log = definition(); } }(loglevel, function () { // Slightly dubious tricks to cut down minimized file size var noop = function() {}; var undefinedType = "undefined"; var isIE = (typeof window !== undefinedType) && (typeof window.navigator !== undefinedType) && ( /Trident\/|MSIE /.test(window.navigator.userAgent) ); var logMethods = [ "trace", "debug", "info", "warn", "error" ]; var _loggersByName = {}; var defaultLogger = null; // Cross-browser bind equivalent that works at least back to IE6 function bindMethod(obj, methodName) { var method = obj[methodName]; if (typeof method.bind === 'function') { return method.bind(obj); } else { try { return Function.prototype.bind.call(method, obj); } catch (e) { // Missing bind shim or IE8 + Modernizr, fallback to wrapping return function() { return Function.prototype.apply.apply(method, [obj, arguments]); }; } } } // Trace() doesn't print the message in IE, so for that case we need to wrap it function traceForIE() { if (console.log) { if (console.log.apply) { console.log.apply(console, arguments); } else { // In old IE, native console methods themselves don't have apply(). Function.prototype.apply.apply(console.log, [console, arguments]); } } if (console.trace) console.trace(); } // Build the best logging method possible for this env // Wherever possible we want to bind, not wrap, to preserve stack traces function realMethod(methodName) { if (methodName === 'debug') { methodName = 'log'; } if (typeof console === undefinedType) { return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives } else if (methodName === 'trace' && isIE) { return traceForIE; } else if (console[methodName] !== undefined) { return bindMethod(console, methodName); } else if (console.log !== undefined) { return bindMethod(console, 'log'); } else { return noop; } } // These private functions always need `this` to be set properly function replaceLoggingMethods() { /*jshint validthis:true */ var level = this.getLevel(); // Replace the actual methods. for (var i = 0; i < logMethods.length; i++) { var methodName = logMethods[i]; this[methodName] = (i < level) ? noop : this.methodFactory(methodName, level, this.name); } // Define log.log as an alias for log.debug this.log = this.debug; // Return any important warnings. if (typeof console === undefinedType && level < this.levels.SILENT) { return "No console available for logging"; } } // In old IE versions, the console isn't present until you first open it. // We build realMethod() replacements here that regenerate logging methods function enableLoggingWhenConsoleArrives(methodName) { return function () { if (typeof console !== undefinedType) { replaceLoggingMethods.call(this); this[methodName].apply(this, arguments); } }; } // By default, we use closely bound real methods wherever possible, and // otherwise we wait for a console to appear, and then try again. function defaultMethodFactory(methodName, _level, _loggerName) { /*jshint validthis:true */ return realMethod(methodName) || enableLoggingWhenConsoleArrives.apply(this, arguments); } function Logger(name, factory) { // Private instance variables. var self = this; /** * The level inherited from a parent logger (or a global default). We * cache this here rather than delegating to the parent so that it stays * in sync with the actual logging methods that we have installed (the * parent could change levels but we might not have rebuilt the loggers * in this child yet). * @type {number} */ var inheritedLevel; /** * The default level for this logger, if any. If set, this overrides * `inheritedLevel`. * @type {number|null} */ var defaultLevel; /** * A user-specific level for this logger. If set, this overrides * `defaultLevel`. * @type {number|null} */ var userLevel; var storageKey = "loglevel"; if (typeof name === "string") { storageKey += ":" + name; } else if (typeof name === "symbol") { storageKey = undefined; } function persistLevelIfPossible(levelNum) { var levelName = (logMethods[levelNum] || 'silent').toUpperCase(); if (typeof window === undefinedType || !storageKey) return; // Use localStorage if available try { window.localStorage[storageKey] = levelName; return; } catch (ignore) {} // Use session cookie as fallback try { window.document.cookie = encodeURIComponent(storageKey) + "=" + levelName + ";"; } catch (ignore) {} } function getPersistedLevel() { var storedLevel; if (typeof window === undefinedType || !storageKey) return; try { storedLevel = window.localStorage[storageKey]; } catch (ignore) {} // Fallback to cookies if local storage gives us nothing if (typeof storedLevel === undefinedType) { try { var cookie = window.document.cookie; var cookieName = encodeURIComponent(storageKey); var location = cookie.indexOf(cookieName + "="); if (location !== -1) { storedLevel = /^([^;]+)/.exec( cookie.slice(location + cookieName.length + 1) )[1]; } } catch (ignore) {} } // If the stored level is not valid, treat it as if nothing was stored. if (self.levels[storedLevel] === undefined) { storedLevel = undefined; } return storedLevel; } function clearPersistedLevel() { if (typeof window === undefinedType || !storageKey) return; // Use localStorage if available try { window.localStorage.removeItem(storageKey); } catch (ignore) {} // Use session cookie as fallback try { window.document.cookie = encodeURIComponent(storageKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; } catch (ignore) {} } function normalizeLevel(input) { var level = input; if (typeof level === "string" && self.levels[level.toUpperCase()] !== undefined) { level = self.levels[level.toUpperCase()]; } if (typeof level === "number" && level >= 0 && level <= self.levels.SILENT) { return level; } else { throw new TypeError("log.setLevel() called with invalid level: " + input); } } /* * * Public logger API - see https://github.com/pimterry/loglevel for details * */ self.name = name; self.levels = { "TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, "ERROR": 4, "SILENT": 5}; self.methodFactory = factory || defaultMethodFactory; self.getLevel = function () { if (userLevel != null) { return userLevel; } else if (defaultLevel != null) { return defaultLevel; } else { return inheritedLevel; } }; self.setLevel = function (level, persist) { userLevel = normalizeLevel(level); if (persist !== false) { // defaults to true persistLevelIfPossible(userLevel); } // NOTE: in v2, this should call rebuild(), which updates children. return replaceLoggingMethods.call(self); }; self.setDefaultLevel = function (level) { defaultLevel = normalizeLevel(level); if (!getPersistedLevel()) { self.setLevel(level, false); } }; self.resetLevel = function () { userLevel = null; clearPersistedLevel(); replaceLoggingMethods.call(self); }; self.enableAll = function(persist) { self.setLevel(self.levels.TRACE, persist); }; self.disableAll = function(persist) { self.setLevel(self.levels.SILENT, persist); }; self.rebuild = function () { if (defaultLogger !== self) { inheritedLevel = normalizeLevel(defaultLogger.getLevel()); } replaceLoggingMethods.call(self); if (defaultLogger === self) { for (var childName in _loggersByName) { _loggersByName[childName].rebuild(); } } }; // Initialize all the internal levels. inheritedLevel = normalizeLevel( defaultLogger ? defaultLogger.getLevel() : "WARN" ); var initialLevel = getPersistedLevel(); if (initialLevel != null) { userLevel = normalizeLevel(initialLevel); } replaceLoggingMethods.call(self); } /* * * Top-level API * */ defaultLogger = new Logger(); defaultLogger.getLogger = function getLogger(name) { if ((typeof name !== "symbol" && typeof name !== "string") || name === "") { throw new TypeError("You must supply a name when creating a logger."); } var logger = _loggersByName[name]; if (!logger) { logger = _loggersByName[name] = new Logger( name, defaultLogger.methodFactory ); } return logger; }; // Grab the current global log variable in case of overwrite var _log = (typeof window !== undefinedType) ? window.log : undefined; defaultLogger.noConflict = function() { if (typeof window !== undefinedType && window.log === defaultLogger) { window.log = _log; } return defaultLogger; }; defaultLogger.getLoggers = function getLoggers() { return _loggersByName; }; // ES6 default export, for compatibility defaultLogger['default'] = defaultLogger; return defaultLogger; })); } (loglevel$1)); return loglevel$1.exports; } var loglevelExports = requireLoglevel(); loglevelExports.setDefaultLevel(loglevelExports.levels.WARN); const logging = loglevelExports.noConflict(); /** * A service for handling messaging between different workers and instances. * It provides functionality to send messages, handle responses, and manage workers. */ class MessagingService { static instance = null; static instancePromise = null; workerThreads = undefined; messenger; messageReceivedCallback = (message) => { }; workers = new Map(); workerListeners = new Map(); responseHandlers = new Map(); /** * Creates an instance of the MessagingService. * @param {Messenger} messenger The messenger identifier for this instance. * @param {Function} messageReceivedCallback Callback function to handle received messages. */ constructor(messenger) { this.messenger = messenger; } static async getInstance() { if (!MessagingService.instance) { MessagingService.instancePromise = new Promise(async (resolve, reject) => { try { if (typeof process !== 'undefined' && process.versions && process.versions.node) { try { const workerThreadsModule = await import('worker_threads'); const { isMainThread, workerData } = workerThreadsModule; let messenger = '/'; if (!isMainThread) { messenger = workerData.name; } MessagingService.instance = new MessagingService(messenger); MessagingService.instance.workerThreads = workerThreadsModule; if (!isMainThread) { MessagingService.instance.setupWorkerThreadListener(); } } catch (err) { logging.error(MessagingService.instance?.getName(), 'failed to import worker_threads.'); throw err; } logging.info(MessagingService.instance.getName(), 'running in node.'); } else if (typeof self !== 'undefined') { let messenger = '/'; if (typeof window === 'undefined') { messenger = self.name; } MessagingService.instance = new MessagingService(messenger); if (typeof window === 'undefined') { MessagingService.instance.setupWebWorkerListener(); } logging.info(MessagingService.instance.getName(), 'running in browser.'); } else { logging.error(MessagingService.instance?.getName(), 'running in an unknown worker environment.'); reject(new Error(`${MessagingService.instance?.getName()} running in an unknown worker environment.`)); } resolve(MessagingService.instance); } catch (err) { logging.error(MessagingService.instance?.getName(), 'error creating messaging service instance:', err); reject(err); } }); } return MessagingService.instancePromise; } setupWorkerThreadListener() { if (this.workerThreads) { if (!this.workerThreads.isMainThread) { this.workerThreads.parentPort?.on('message', (message) => { this.handleMessage(message); }); logging.debug(this.getName(), 'attached parent message listener.'); } } } setupWebWorkerListener() { if (typeof self !== 'undefined' && self.addEventListener) { self.addEventListener('message', (event) => { this.handleMessage(event.data); }); logging.debug(this.getName(), 'attached parent message listener.'); } } /** * Adds a worker to the service, associating it with the given messenger. * @param {Messenger} messenger The messenger identifier for the worker. * @param {Worker} worker The worker to be added. */ addWorker(name, worker) { if (!name || !worker) { logging.error(this.getName(), 'invalid name:', name, 'or worker:', worker); return; } const workerKey = this.getWorkerKey(name); if (this.workerThreads) { const workerListener = (message) => { logging.debug(this.messenger, 'message received from', name, message); this.handleMessage(message); }; worker.on('message', workerListener); this.workerListeners.set(workerKey, workerListener); logging.debug(this.getName(), 'attached message listener to', name); } else { const workerListener = (event) => { logging.debug(this.getName(), 'message received from', name, event.data); this.handleMessage(event.data); }; worker.addEventListener('message', workerListener); this.workerListeners.set(workerKey, workerListener); logging.debug(this.getName(), 'attached message listener', name); } this.workers.set(workerKey, worker); logging.info(this.getName(), 'added', name); } /** * Removes a worker from the service and cleans up associated resources. * @param {Worker} worker The worker to be removed. */ removeWorker(name) { const workerKey = this.getWorkerKey(name); const worker = this.workers.get(workerKey); const workerListener = this.workerListeners.get(workerKey); if (workerListener) { if (this.workerThreads) { worker.off('message', workerListener); logging.debug(this.getName(), 'removed message listener for', name); } else { worker.removeEventListener('message', workerListener); logging.debug(this.getName(), 'removed message listener for', name); } this.workerListeners.delete(workerKey); this.workers.delete(workerKey); logging.info(this.getName(), 'removed', name); } else { logging.warn(this.getName(), 'worker listener not found for', name); } } /** * Cleans up all workers, listeners, and response handlers. */ cleanUp() { this.workers.forEach((worker, key) => this.removeWorker(key)); this.workerListeners.clear(); this.responseHandlers.clear(); this.messageReceivedCallback = () => { }; } /** * Sends a message to one or more destinations (workers or upstream). * @param {GeneralMessage<T>} message The message to be sent. * @param {Worker} [worker] Optionally specify a specific worker to send the message to. * @returns {Promise<ResponseMessage<T> | null>} A promise that resolves with the response message, or null if no response is expected. */ async sendMessage(message, worker) { logging.debug(this.getName(), 'send message:', message); message.source = this.messenger; message.id = uuid.v4(); const destinations = []; if (worker) { logging.debug(this.getName(), 'sending message directly to worker:', message.destination); destinations.push(worker.postMessage.bind(worker)); } else if (messengerIsUpstream(this.messenger, message.destination)) { if (this.workerThreads) { if (this.workerThreads.parentPort) { logging.debug(this.getName(), 'sending message upstream using parentPort:', message.destination); destinations.push(this.workerThreads.parentPort.postMessage.bind(this.workerThreads.parentPort)); } else { logging.error(this.getName(), 'no parentPort found to send message upstream to:', message.destination); } } else if (typeof self !== 'undefined' && self) { logging.debug(this.getName(), 'sending message upstream using self.postMessage to:', message.destination); destinations.push(self.postMessage.bind(self)); } else { logging.error(this.getName(), 'no postMessage available to send message upstream to:', message.destination); } } else { const here = messengerAsArray(this.messenger); const there = messengerAsArray(message.destination); const nextHop = there.slice(0, here.length + 1); this.workers.forEach((worker, key) => { // TODO: Broadcast Message Handling if (message.broadcast) { logging.debug(this.getName(), 'broadcasting message downstream to:', key); destinations.push(worker.postMessage.bind(worker)); } else { if (messengersAreEqual(message.destination, key)) { logging.debug(this.getName(), 'sending message downstream directly to:', key); destinations.push(worker.postMessage.bind(worker)); } else if (messengersAreEqual(nextHop, key)) { logging.debug(this.getName(), 'sending message downstream indirectly through:', key); destinations.push(worker.postMessage.bind(worker)); } } }); } if (destinations.length > 0) { const sendMessagePromises = destinations.map((destination) => { // If the message is a request, setup the response handle if (message.type === exports.MessageType.REQUEST) { return new Promise((resolve, reject) => { const responseHandler = (responseMessage) => { // Resolve the promise with the correct response message if (responseMessage.requestId === message.id) { this.responseHandlers.delete(responseMessage.requestId); resolve(responseMessage); } }; this.responseHandlers.set(message.id, responseHandler); logging.debug(this.getName(), 'added response handler for request:', message.id); destination(message); }).then((responseMessage) => { logging.debug(this.getName(), 'returning response for:', responseMessage.requestId); return responseMessage; }); } else { logging.debug(this.getName(), 'sending non-request message'); // If it's not a request, just send the message without expecting a response destination(message); return null; } }); logging.debug(this.getName(), 'waiting for response'); const responses = await Promise.all(sendMessagePromises); logging.debug(this.getName(), 'finished waiting for response'); return responses.find((response) => response !== null) || null; } else { logging.error(this.getName(), 'no worker found to send message to:', message); } return null; } /** * Handles an incoming message. * @param {GeneralMessage<any>} message The message to handle. */ handleMessage(message) { // TODO: Broadcast Message Handling if (message.broadcast || messengersAreEqual(message.destination, this.messenger)) { if (message.type === exports.MessageType.RESPONSE) { const responseMessage = message; const responseHandler = this.responseHandlers.get(responseMessage.requestId); if (responseHandler) { responseHandler(responseMessage); } else { logging.error(this.getName(), 'no response handler for request:', responseMessage.requestId); } this.responseHandlers.delete(responseMessage.requestId); logging.debug(this.getName(), 'deleted response handler for request:', responseMessage.requestId); } else { logging.debug(this.getName(), 'sending non-MessageWorks message to message received callback'); this.messageReceivedCallback(message); } } else { logging.debug(this.getName(), 'forwarding message'); this.forwardMessage(message); } } /** * Forwards a message to its correct destination (upstream or downstream). * @param {GeneralMessage<any>} message The message to forward. */ forwardMessage(message) { if (messengerIsUpstream(this.messenger, message.destination)) { logging.debug(this.getName(), 'forwarding message upstream'); this.forwardUpstream(message); } else if (messengersAreEqual(this.messenger, message.destination)) { logging.debug(this.getName(), 'forwarding message to message received callback'); this.messageReceivedCallback(message); } else { logging.debug(this.getName(), 'forwarding message downstream'); this.forwardDownstream(message); } } /** * Forwards a message upstream (to the parent or higher level). * @param {GeneralMessage<any>} message The message to forward upstream. */ forwardUpstream(message) { if (this.workerThreads) { if (this.workerThreads.parentPort) { logging.debug(this.getName(), 'forwarding upstream using parentPort.postMessage'); this.workerThreads.parentPort?.postMessage(message); } else { logging.error(this.getName(), 'no upstream parentPort to forward message to'); } } else { logging.debug(this.getName(), 'forwarding upstream using self.postMessage'); self.postMessage(message); } } /** * Forwards a message downstream (to the worker or lower level). * @param {GeneralMessage<any>} message The message to forward downstream. */ forwardDownstream(message) { const here = messengerAsArray(this.messenger); // [w] = 1 vs [w,s,i] const next = messengerAsArray(message.destination).slice(0, here.length + 1); this.workers.forEach((worker, key) => { if (messengersAreEqual(message.destination, key)) { logging.debug(this.getName(), 'forwarding downstream directly to:', key); worker.postMessage(message); } else if (messengersAreEqual(next, key)) { logging.debug(this.getName(), 'forwarding downstream indirectly through:', key); worker.postMessage(message); } }); } getWorkerKey(name) { const workerMessenger = messengerAsArray(this.messenger); workerMessenger.push(name); return messengerAsString(workerMessenger); } getName() { return messengerAsString(this.messenger); } } /** * Represents a general message in the messaging system. * This class can be extended to create more specific types of messages (e.g., start, stop, status). * * @template T The type of data that this message may contain. Can be any type or undefined if no data is provided. */ class GeneralMessage { /** * The unique identifier for this message. This will be set automatically in MessagingService.sendMessage(). * @type {any} */ id; /** * The name of the message, typically used to identify the message type or purpose (e.g. start-process, stop-process). * @type {string} */ name; /** * The type of the message (e.g., GENERAL, REQUEST, RESPONSE). Default is GENERAL. * @type {MessageType} */ type = exports.MessageType.GENERAL; /** * A flag indicating whether the message is a broadcast message. * @type {boolean} */ broadcast = false; /** * The source of the message, usually represented as a path or address. This will be set automatically by MessagingService.sendMessage(). * @type {Messenger} */ source = []; /** * The destination of the message. This can be in '/system/middleware/destination' or ['system', 'middleware', 'destination'] format. * @type {Messenger} */ destination; /** * Optional data associated with the message. * @type {T | undefined} */ data; /** * Creates an instance of the GeneralMessage. * * @param name The name of the message. * @param destination The destination of the message (can be in string or string[] format). * @param data Optional data that the message may contain. */ constructor(name, destination, data) { this.name = name; this.destination = destination; this.data = data; } } /** * Represents a request message in the messaging system. * This class extends the GeneralMessage class and overrides the message type to `REQUEST`. * * @template T The type of data this request message may contain. This is typically used for passing data in the request. */ class RequestMessage extends GeneralMessage { /** * Creates an instance of the RequestMessage. * * This constructor sets the message type to `REQUEST` and passes the name, destination, and optional data * to the parent class `GeneralMessage` constructor. * * @param name The name of the request message, typically identifying the purpose of the request. * @param destination The destination(s) for the request, typically an address or a set of addresses. * @param data Optional data that the request message may contain. */ constructor(name, destination, data) { super(name, destination, data); this.type = exports.MessageType.REQUEST; } } /** * Represents a response message in the messaging system. * This class extends the GeneralMessage class and overrides the message type to `RESPONSE`. * * It is typically used as a reply to a `RequestMessage`, containing the result or information * in response to the request. This class includes a `requestId` that references the original * request message to correlate the response. * * @template T The type of data this response message may contain. This is typically used for passing data * related to the response (such as success, failure, or result data). */ class ResponseMessage extends GeneralMessage { /** * The ID of the original request this response is related to. * This property is used to link the response back to the originating request message. * * @protected */ requestId; /** * Creates an instance of the ResponseMessage. * * This constructor sets the message type to `RESPONSE`, assigns the `requestId` from the incoming request, * and passes the source of the request (to be used as the destination in the response) and optional data * to the parent class `GeneralMessage`. * * @param name The name of the response message, typically identifying the purpose of the response. * @param request The original request message to which this is responding. The `requestId` and `source` * are extracted from this request. * @param data Optional data that the response message may contain (e.g., result, success/failure data). */ constructor(name, request, data) { super(name, request.source, data); this.type = exports.MessageType.RESPONSE; this.requestId = request.id; } } exports.GeneralMessage = GeneralMessage; exports.MessagingService = MessagingService; exports.RequestMessage = RequestMessage; exports.ResponseMessage = ResponseMessage; exports.messengerAsArray = messengerAsArray; exports.messengerAsString = messengerAsString; exports.messengerIsUpstream = messengerIsUpstream; exports.messengersAreEqual = messengersAreEqual; exports.normalizeMessenger = normalizeMessenger; //# sourceMappingURL=bundle.cjs.js.map