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
JavaScript
'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