"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Emitter: () => Emitter, EventEmitter: () => EventEmitter, EventsDomain: () => eventsDomain, default: () => src_default, eventsServer: () => eventsServer, getEventsServer: () => getEventsServer }); module.exports = __toCommonJS(src_exports); // src/domain.ts var import_core2 = require("@palmares/core"); // src/app/index.ts var import_core = require("@palmares/core"); // src/app/utils.ts async function loadEvents(server, domains) { const promises = domains.map(async (eventsDomain2) => { const events = await eventsDomain2.getEvents(); const eventsEntries = Object.entries(events); await Promise.all(eventsEntries.map(async ([eventName, eventHandlerOrObject]) => { let isWithResult = true; let eventHandler; if (typeof eventHandlerOrObject !== "function") { isWithResult = eventHandlerOrObject.withResult; eventHandler = eventHandlerOrObject.handler.bind(eventHandlerOrObject.handler); } else { eventHandler = eventHandlerOrObject.bind(eventHandlerOrObject); } if (isWithResult) await server.addEventListener(eventName, eventHandler); else await server.addEventListenerWithoutResult(eventName, eventHandler); })); }); await Promise.all(promises); } __name(loadEvents, "loadEvents"); // src/logging.ts var import_logging = require("@palmares/logging"); var eventsLogger = new import_logging.Logger({ domainName: "@palmares/events" }, { APP_START_EVENTS_SERVER: { category: "info", handler: /* @__PURE__ */ __name(({ appName }) => `${appName} is running an events server and will only listen for events. Press Ctrl+C to quit.`, "handler") } }); // src/events/exceptions.ts var NoLayerError = class _NoLayerError extends Error { static { __name(this, "NoLayerError"); } constructor() { super("Your emitter does not have a layer. You should add a layer before trying to emit an event to the layer"); this.name = _NoLayerError.name; } }; // src/utils/uuid.ts function uuid() { let date = (/* @__PURE__ */ new Date()).getTime(); const browserOrNodePerformance = globalThis.performance; let performanceDate = ( // eslint-disable-next-line ts/no-unnecessary-condition browserOrNodePerformance && browserOrNodePerformance.now && browserOrNodePerformance.now() * 1e3 || 0 ); return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (character) => { let randomNumber = Math.random() * 16; if (date > 0) { randomNumber = (date + randomNumber) % 16 | 0; date = Math.floor(date / 16); } else { randomNumber = (performanceDate + randomNumber) % 16 | 0; performanceDate = Math.floor(performanceDate / 16); } return (character === "x" ? randomNumber : randomNumber & 3 | 8).toString(16); }); } __name(uuid, "uuid"); // src/events/index.ts var EventEmitter = class { static { __name(this, "EventEmitter"); } $$type = "$PEventEmitter"; emitter; layer; resultsEventName; #channels; #unsubscribeByChannel = {}; #pendingHandlerIdForResultKey = /* @__PURE__ */ new Map(); #pendingResults = {}; #pingTimeout = 1e3; #resultsTimeout = 5e3; #delimiter = "."; #wildcards = false; #groupByKeys = {}; #groups = {}; /** * Factory method for the building the emitter, we need this because we need to add results listener and layer * listeners to the function and both operations are async. * * Be aware that you need to pass the emitter, the constructor, and not the instance, you can pass the parameters * of the emitter inside of options: { customParams: } * * @param emitter - The emitter constructor so we build it inside here or a default export by using * `import('./my-custom-emitter.ts')` * @param options - Custom options for the emitter, on here you can pass a layer instance, wildcards options and * customize the timeout for the results to be retrieved. * * @returns - This is a factory method so we create a new EventEmitter instance. */ static async new(emitter, options) { const { emitterParams, ...optionsForConstructor } = options || {}; if (emitter instanceof Promise) emitter = (await emitter).default; const emitterCustomParams = emitterParams || []; const emitterInstance = await emitter.new(...emitterCustomParams); const eventEmitterInstance = new this(emitterInstance, optionsForConstructor); eventEmitterInstance.resultsEventName = `resultsOfEmitter-${uuid()}`; await eventEmitterInstance.addRawEventListenerWithoutResult(eventEmitterInstance.resultsEventName, eventEmitterInstance.resultsListener.bind(eventEmitterInstance)); if (options?.layer?.use) { eventEmitterInstance.layer = await Promise.resolve(options.layer.use); await eventEmitterInstance.addChannelListeners(options.layer.channels || [ "all" ]); } return eventEmitterInstance; } constructor(emitterInstance, options) { this.emitter = emitterInstance; if (options?.wildcards?.delimiter) this.#delimiter = options.wildcards.delimiter; if (options?.wildcards?.use) this.#wildcards = options.wildcards.use; if (typeof options?.results?.pingTimeout === "number") this.#pingTimeout = options.results.pingTimeout; if (typeof options?.results?.timeout === "number") this.#resultsTimeout = options.results.timeout; if (options?.layer?.channels) this.#channels = options.layer.channels; } get hasLayer() { return this.layer?.["$$type"] === "$PEventEmitter"; } get channels() { return Array.isArray(this.#channels) ? [ ...this.#channels ] : []; } /** * This is responsible fo retrieving the response of the emitted event, when the event * finishes processing it'll send a response to this function (this is handler for a specific * event inside of the event emitter). */ resultsListener(handlerId, resultId, _, result) { const hasPendingResultsForId = ( // eslint-disable-next-line ts/no-unnecessary-condition typeof this.#pendingResults[resultId] === "object" && this.#pendingResults[resultId] !== void 0 ); if (hasPendingResultsForId) this.#pendingResults[resultId][handlerId] = result; } /** * Adds the event listener without the wildcards, self explanatory, so we will * not append the groupId to stuff like '*' or '**' or 'create.*' * * We just append the groupId to the key and that's it. It shouldn't be called directly * because there is no other reason for this instead of organizing the callback in the right keys. * * @param handlerGroupId - As explained, the groupId is the actual event that will be fired in the actual emitter. * @param handlerId - The id of the handler so we can retrieve the function quickly. * @param key - The actual key that the `handlerGroupId` refers to. * @param callback - The function (wrapped or not) that will be fired when we emit an event. */ #addListenerWithoutWildcards(handlerGroupId, handlerId, key, callback) { if (key in this.#groupByKeys === false) this.#groupByKeys[key] = /* @__PURE__ */ new Set([ handlerGroupId ]); if (this.#groups[handlerGroupId]) this.#groups[handlerGroupId].listeners[handlerId] = callback; else { this.#groups[handlerGroupId] = { listeners: { [handlerId]: callback }, keys: /* @__PURE__ */ new Set([ key ]) }; } } /** * This will add the listener with the wildcards, what we do in order to be able to save the list with the * wildcards is that we save all of the events tied to the same group. * * Think that we are appending the event with the keyword: `create.users`. The group id refers to all of the * handlers that will listen for this particular event. * * What we do is that we append the same groupId to multiple keys like for example: * * `create.user`: new Set(`group-h123-huasd1-123890-1023098`), * `create.*`: new Set(`group-h123-huasd1-123890-1023098`), * `create.**`: new Set(`group-h123-huasd1-123890-1023098`), * `**`: new Set(`group-h123-huasd1-123890-1023098`) * * @param handlerGroupId - The groupId is the actual event that will be fired in the actual emitter. * @param handlerId - The id of the handler that is the actual function that will fire the event. * @param key - This will be like `create.user` in the example above. * @param callback - The function appended to the handlerId. */ #addListenerWithWildcards(handlerGroupId, handlerId, key, callback) { const splittedKey = key.split(this.#delimiter); const allKeysOfKey = [ "**" ]; if (this.#groupByKeys["**"]) this.#groupByKeys["**"].add(handlerGroupId); else this.#groupByKeys["**"] = /* @__PURE__ */ new Set([ handlerGroupId ]); let appendedKey = ""; for (let i = 0; i < splittedKey.length; i++) { const isLastKey = i === splittedKey.length - 1; const eachKey = splittedKey[i]; const newAppendedKey = `${appendedKey}${i > 0 ? this.#delimiter : ""}${eachKey}`; if (isLastKey) { const wildCardLastKey = `${appendedKey}${i > 0 ? this.#delimiter : ""}*`; if (this.#groupByKeys[wildCardLastKey]) this.#groupByKeys[wildCardLastKey].add(handlerGroupId); else this.#groupByKeys[wildCardLastKey] = /* @__PURE__ */ new Set([ handlerGroupId ]); const completeKey = newAppendedKey; if (this.#groupByKeys[completeKey]) this.#groupByKeys[completeKey].add(handlerGroupId); else this.#groupByKeys[completeKey] = /* @__PURE__ */ new Set([ handlerGroupId ]); allKeysOfKey.push(wildCardLastKey, completeKey); } else { const deepNestedKey = `${newAppendedKey}${this.#delimiter}**`; if (this.#groupByKeys[deepNestedKey]) this.#groupByKeys[deepNestedKey].add(handlerGroupId); else this.#groupByKeys[deepNestedKey] = /* @__PURE__ */ new Set([ handlerGroupId ]); allKeysOfKey.push(deepNestedKey); } appendedKey = newAppendedKey; } if (this.#groups[handlerGroupId]) this.#groups[handlerGroupId].listeners[handlerId] = callback; else { this.#groups[handlerGroupId] = { listeners: { [handlerId]: callback }, keys: new Set(allKeysOfKey) }; } } /** * This will prevent that we call the same function for the same handler twice, it'll also nicely organize the data. * stuff like `resultsEventName`, `resultKey` and `channelLayer` should not be passed to the user defined callback. * It's for internal usage only, so you see that if `isResultWrapped` is false we will just pass the data to the * callback and nothing else. * * @param handlerId - The id of the handler that is being called. * @param callback - The function (wrapped or not) that will be called when we fire the event. * @param isResultWrapped - Boolean value, if it's result wrapped we will send the `resultsEventName`, * `resultKey`, `channelLayer` to the callback, otherwise we will just send the data. We try to not send unnecessary * data to the callback, all of the values should be used by the handler itself. * * @returns - Return the callback wrapped, so we can notify the class that we are working on a result for a particular * resultKey, this is nice so if the system receives multiple requests it won't do nothing. */ // eslint-disable-next-line ts/require-await async #wrapInPendingHandlerToPreventMultipleCalls(handlerId, callback, isResultWrapped = true) { const preventMultipleCallsWrappedCallback = /* @__PURE__ */ __name(async (resultsEventName, resultKey, channelLayer, ...data) => { const isThisHandlerAlreadyWorkingForAResponse = this.#pendingHandlerIdForResultKey.has(handlerId) && this.#pendingHandlerIdForResultKey.get(handlerId) === resultKey; if (isThisHandlerAlreadyWorkingForAResponse === false) { this.#pendingHandlerIdForResultKey.set(handlerId, resultKey); if (isResultWrapped) await Promise.resolve(callback(resultsEventName, resultKey, channelLayer, ...data)); else await Promise.resolve(callback(...data)); this.#pendingHandlerIdForResultKey.delete(handlerId); } }, "preventMultipleCallsWrappedCallback"); return preventMultipleCallsWrappedCallback.bind(this); } /** * This works similar to a promise in javascript, what we do is that we send the emitter * that we are working on a response. * * When we work on a response we notify with `pending`, after the result is finished we notify * the result with `completed`. This is why we use the `resultsEventName` and `resultKey` for. * This way we are able to notify the emitter that we are working on a response for it. * * @param handlerId - We need the handlerId so we know that exactly that this handler that is * working on a response. */ async #wrapInResultCallback(handlerId, callback) { const resultWrappedCallback = /* @__PURE__ */ __name(async (resultsEventName, resultKey, channelLayer, ...data) => { this.emitResult(resultsEventName, handlerId, resultKey, channelLayer, { status: "pending" }); try { const result = await Promise.resolve(callback(...data)); this.emitResult(resultsEventName, handlerId, resultKey, channelLayer, { status: "completed", result }); } catch (e) { this.emitResult(resultsEventName, handlerId, resultKey, channelLayer, { status: "failed" }); throw e; } }, "resultWrappedCallback"); return this.#wrapInPendingHandlerToPreventMultipleCalls(handlerId, resultWrappedCallback.bind(this), true); } /** * This will subscribe a listener (function) to an specific event (key). When this key is emitted, either from * a channel or from the emitter itself, the listener (function) will be called. * * Returning a value from the function will emit a result back to the caller. * * IMPORTANT: The data received and the return value must be JSON serializable values. This means you cannot expect * to receive a callback or function in your listener. As well as this, you can't return a function, can't return * a class. It needs to be JSON serializable. * * @param key - The key that will be used to emit the event. * @param callback - The function that will be called when the event is emitted. * * @returns - A unsubscribe function that if called, will remove the listener from the emitter. */ async addEventListener(key, callback) { return this.#addEventListenerWithOptions(key, { useResult: true, wildcards: this.#wildcards, usePreventMultipleCalls: true }, callback); } /** * This method will subscribe a listener that will not emit a result back to the caller. So it might * be useful for listeners where performance does matter and needs to be taken aware of. * * @param key - The key that will be used to emit the event. * @param callback - The function that will be called when the event is emitted. * * @returns - A unsubscribe function that if called, will remove the listener from the emitter. */ async addEventListenerWithoutResult(key, callback) { return this.#addEventListenerWithOptions(key, { useResult: false, wildcards: this.#wildcards, usePreventMultipleCalls: true }, callback); } /** * [INTERNAL] This will subscribe a listener (function) to an specific event (key) without worrying about the result. * This is mostly used for internal usage, we do not need to wrap the `results` listener and * `layerListener` to send the results. Actually if we did this we might would end up in a loop. * * So in other words, this adds the key and the listener `raw`, so not wrapped in anything and without * the wildcards. * * @param key - The key that will be used to emit the event. * @param callback - The function that will be called when the event is emitted. * * @returns - Returns the unsubscribe function that should be called to unsubscribe the listener. */ async addRawEventListenerWithoutResult(key, callback) { return this.#addEventListenerWithOptions(key, { useResult: false, wildcards: false, usePreventMultipleCalls: false }, callback); } /** * Adds the event listeners with custom options, as you see this function is 100% private, we do not want to * expose this to the user because we want to keep the api as simple as possible, and this might bring more * confusion than making it simple. We created this function so we can reuse it if you are adding an event * listener with the `addEventListener` or with the `addRawEventListenerWithoutResult`. * * @param key - The key that will be used to fire the event. * @param options - The options that will be used to fire the event. * @param options.useResult - If the event listener will be wrapped with the result emitter. * @param options.wildcards - If the event listener accepts wildcards or not. * @param callback - The function (not wrapped) that will be fired when we emit an event. * * @returns - Returns the unsubscribe function that should be called to unsubscribe the listener. */ async #addEventListenerWithOptions(key, options, callback) { const handlerGroupId = key in this.#groupByKeys ? [ ...this.#groupByKeys[key].values() ][0] : `group-${uuid()}`; const handlerId = `handler-${uuid()}`; if (options.useResult) callback = await this.#wrapInResultCallback(handlerId, callback); else if (options.usePreventMultipleCalls) { callback = await this.#wrapInPendingHandlerToPreventMultipleCalls(handlerId, callback, false); } if (options.wildcards) { this.#addListenerWithWildcards(handlerGroupId, handlerId, key, callback); } else { this.#addListenerWithoutWildcards(handlerGroupId, handlerId, key, callback); } await this.emitter.addEventListener(handlerGroupId, key, callback); return this.#unsubscribe({ handlerGroupId, key, handlerId }); } /** * This will either unsubscribe all listeners or all of the listeners of a specific key. We pass an object here * to prevent undesired behavior, if for some reason key is undefined we will not remove all of the listeners you need * to explicitly define the key that you want to remove. * * @param options - The options of the listeners we want to remove. * @param options.key - The key that you want to remove from the emitter. */ async unsubscribeAll(options) { const isKeyDefined = typeof options?.key === "string"; const isToRemoveAll = options === void 0; const doesKeyExists = isKeyDefined && options.key in this.#groupByKeys; if (doesKeyExists) { const groupIds = this.#groupByKeys[options.key]; const promises = []; for (const groupId of groupIds) { promises.push((async () => { const groupKeysAndListeners = this.#groups[groupId]; if (groupKeysAndListeners) { const listeners = Object.values(groupKeysAndListeners.listeners); const listenerRemovalPromises = listeners.map(async (listener) => { await this.emitter.removeEventListener(groupId, options.key, listener); }); const keysToRemove = groupKeysAndListeners.keys.values(); for (const keyBeingRemoved of keysToRemove) { if (this.#groupByKeys[keyBeingRemoved].size === 1) delete this.#groupByKeys[keyBeingRemoved]; else this.#groupByKeys[keyBeingRemoved].delete(groupId); } await Promise.all(listenerRemovalPromises); delete this.#groups[groupId]; } })()); } await Promise.all(promises); } else if (isToRemoveAll) { if (this.#wildcards) await this.unsubscribeAll({ key: "**" }); else { const promises = []; const keysToRemove = Object.keys(this.#groupByKeys); for (const key of keysToRemove) { if (this.resultsEventName !== key) promises.push(this.unsubscribeAll({ key })); } await Promise.all(promises); } } } /** * Unsubscribes this emitter from a specific channel inside of the layer. If it doesn't exist it will do nothing. * * @param channel - The channel that you want to unsubscribe from. */ async unsubscribeFromChannel(channel) { if (channel in this.#unsubscribeByChannel) await this.#unsubscribeByChannel[channel](); } /** * When unsubscribing we need to remove the listener from the groups array. This is exactly what this do. * We remove all of the keys from the groupKeys. Also if the #groupByKeys becomes empty we make sure to remove * it from the #groupByKeys object. * * @param handlerGroupId - The group id that we want to remove from the #groups and #groupByKeys. */ #unsubscribeGroup(handlerGroupId) { for (const keyToRemoveIdFrom of this.#groups[handlerGroupId].keys) { this.#groupByKeys[keyToRemoveIdFrom].delete(handlerGroupId); const isHandlerByKeyEmpty = this.#groupByKeys[keyToRemoveIdFrom].size === 0; if (isHandlerByKeyEmpty) delete this.#groupByKeys[keyToRemoveIdFrom]; } delete this.#groups[handlerGroupId]; } /** * This function is called when we append a new listener using the `addEventListeners` functions. * * @example * ```ts * const unsubscribe = await emitter.addEventListener('customEventName', () => { console.log('hello world') }); * * await unsubscribe(); * ``` * * You see that on addEventListener what we return a function to unsubscribe the listener. The unsubscribe function * is returned from this method. * * To unsubscribe the listener we need to remove it from the emitter and from the #groups and #groupByKeys objects. * But all of this logic is handled here. You see that we bind the function we return to the `this` context of the * emitter instance. So that the `this` context will always be the emitter instance. * * @param handlerGroupId - The group id that we want to remove from the emitter (remember, groupIds are like the * 'eventName') * @param key - The original key that we used to add the listener. * @param handlerId - The id of the handler that we want to remove from the emitter. */ // eslint-disable-next-line ts/require-await async #unsubscribe({ handlerGroupId, key, handlerId }) { const unsubscribeHandlerFunction = /* @__PURE__ */ __name(async () => { const doesGroupStillExists = handlerGroupId in this.#groups; const doesHandlerStillExists = doesGroupStillExists && handlerId in this.#groups[handlerGroupId].listeners; const isLastListenerFromGroup = doesHandlerStillExists && Object.keys(this.#groups[handlerGroupId].listeners).length === 1; if (doesHandlerStillExists) { const listener = this.#groups[handlerGroupId].listeners[handlerId]; await this.emitter.removeEventListener(handlerGroupId, key, listener); delete this.#groups[handlerGroupId].listeners[handlerId]; if (isLastListenerFromGroup) this.#unsubscribeGroup(handlerGroupId); } }, "unsubscribeHandlerFunction"); return unsubscribeHandlerFunction.bind(this); } #getOriginalKeyFromGroup(key, groupId) { if (groupId in this.#groups) { const groupKeys = this.#groups[groupId].keys; let originalKey; for (originalKey of groupKeys) ; return originalKey; } return key; } /** * Emits the event to the `this.emitter.emit` * * @param resultsEventName - this is the handler you will call with the result, it'll * it's just one for every emitter, so each emitter instance define it's own resultsEventName * @param resultKey - This is the key of the result, when you all `.emit()` we will create a key * meaning that we will populate the contents of this key with the results. */ // eslint-disable-next-line ts/require-await async emitEventToEmitter(key, resultsEventName, resultKey, channelLayer, ...data) { const groupIdsToEmitEventTo = (this.#groupByKeys[key] || /* @__PURE__ */ new Set()).values(); for (const groupId of groupIdsToEmitEventTo) { const groupListenersIds = Object.keys(this.#groups[groupId].listeners || {}); const areAllListenersBeingHandled = groupListenersIds.every((handlerId) => this.#pendingHandlerIdForResultKey.get(handlerId) === resultKey); if (areAllListenersBeingHandled === false) { const originalKey = this.#getOriginalKeyFromGroup(key, groupId); this.emitter.emit(groupId, originalKey, resultsEventName, resultKey, channelLayer, ...data); } } } /** * This is responsible to wait for the result of the fired event. First we fire the event, then we will iterate * over and over again over `this.#pendingResults` until we receive the result of the event that we fired. * * We will loop until we receive the result of the event, or we can timeout with both `#resultsTimeout` or * `#pingTimeout`. The first one is how long we will wait to retrieve a response. The second one is how long we will * wait until we receive that a listener is working on a response. The second one is useful when the event fired does * not have any handlers. * * @param resultKey - In other words, the key of the event that was fired. But generally speaking this is the id * that we will append the results to. * * @returns - A Promise that resolves to an array of the results of the event that was fired. */ async #fetchResultForEmit(resultKey) { return new Promise((resolve, reject) => { function keepAlive(startTimer) { try { const hasReachedTimeout = Date.now() - startTimer > this.#resultsTimeout; const hasResultForKey = Object.keys(this.#pendingResults[resultKey]).length > 0; const resultsAsArray = Object.values(this.#pendingResults[resultKey] || {}); const allResultsConcluded = hasResultForKey && resultsAsArray.every(({ status }) => status !== "pending"); const isPingTimeoutPassed = Date.now() - startTimer > this.#pingTimeout; const hasReachedPingTimeout = hasResultForKey === false && isPingTimeoutPassed; if (allResultsConcluded && isPingTimeoutPassed || hasReachedTimeout) { delete this.#pendingResults[resultKey]; return resolve(resultsAsArray.filter(({ status }) => status === "completed").map(({ result }) => result)); } else if (hasReachedPingTimeout) { return resolve([]); } else setTimeout(() => keepAlive.bind(this)(startTimer), 0); } catch (e) { reject(e); } } __name(keepAlive, "keepAlive"); keepAlive.bind(this)(Date.now()); }); } /** * Emits some data to a channel, a channel is something that should be defined in the layer, This will fire the event * in the layer calling all subscribed listeners. By doing this you can call the `emit` method on multiple machines * inside of the server. * * @param channel - The channel to emit the event to. * @param key - The key to send events to. * @param data - The data to send over to the listeners. (IT SHOULD BE JSON SERIALIZABLE) * * @return - A promise that will wait for a return of the emitters. */ async emitToChannel(channels, key, ...data) { const resultKey = `emittedToChannelResultKey-${uuid()}`; this.#pendingResults[resultKey] = {}; if (this.layer) { const channelsAsArray = Array.isArray(channels) ? channels : [ channels ]; const filteredChannels = channelsAsArray.filter((channel) => this.#channels.includes(channel)); for (const channel of filteredChannels) { this.layer.emitEventToEmitter(channel, this.resultsEventName, resultKey, channel, { key, data }); } return this.#fetchResultForEmit(resultKey); } else { throw new NoLayerError(); } } /** * When we emit the event we will return a promise, this promise will wait * for the results of the listeners to be sent back to the application. With this * we are able to retrieve the results of the connected listeners. * * @param key - The key to send events to. * @param data - The data to send over to the listeners. (IT SHOULD BE JSON SERIALIZABLE) * * @return - A promise that will wait for a return of the emitters. */ async emit(key, ...data) { const resultKey = `emittedResultKey-${uuid()}`; this.#pendingResults[resultKey] = {}; this.emitEventToEmitter(key, this.resultsEventName, resultKey, null, ...data); return this.#fetchResultForEmit(resultKey); } /** * Function that is used to emit the result back to the caller this way we can distribute the callers * and send the response back to the caller as it was on the same system / machine. * * If it is in an emitter we will send this response through the layer, otherwise it'll send normally * in the event emitter. * * @param resultsEventName - The results event name is a listener that exists for all emitters inside * of the application. This is a unique ID. Each EventEmitter instance has it's own. So what we do is that * we send this over the network and guarantee that only who sent the event will receive this response back. * @param handlerId - An id of the function (listener) that is working for the result of this event. * @param resultKey - The id of the result. When you emit an event it'll generate a key. This is the key * that we use to guarantee that the value received is from this event. * @param channelLayer - Only needed when using `layers`, but this is the channel that we should broadcast * this response to. For example if we emitted the event for every listener in the `users` channel, we should * guarantee that we are sending the response back to the `users` channel so that the `resultEventName` can * catch this value and do something with it. * @param data - This is the actual data that you are sending over the network, generally speaking it'll be, * most of the times, an array since we are spreading it over. */ emitResult(resultsEventName, handlerId, resultKey, channelLayer, ...data) { if (this.layer && channelLayer !== null) { this.layer.emitEventToEmitter(channelLayer, handlerId, resultKey, channelLayer, { key: resultsEventName, data }); } else if (channelLayer === null) { this.emitEventToEmitter(resultsEventName, handlerId, resultKey, null, ...data); } } /** * This is used to append listener functions to the layer, you see that the layer calls the emitter. * * So what we are doing is: We are appending the callback to the layer, from the emitter, when this function * is called we will call the `this.emitEventToEmitter` from the emitter itself AND NOT the layer. * * @returns - Returns the callback that will be called when we fire the emitter. */ // eslint-disable-next-line ts/require-await async #getLayerListener() { function layerListener(resultsEventName, resultKey, channel, data) { this.emitEventToEmitter(data.key, resultsEventName, resultKey, channel, ...data.data || []); } __name(layerListener, "layerListener"); return layerListener.bind(this); } /** * Appends the listeners to the layer, this way we will be able to connect two different emitters together. * Those 2 different emitters might be on the same machine or a completely different machine (if we are using * RedisEmitter) * * @param channels - The channels that your emitter will listen to. This means that when we receive an event on * a specific channel and this emitter has handlers for this event, we will emit the event. */ async addChannelListeners(channels) { const promises = channels.map(async (channel) => { if (this.layer) { const unsubscribe = await this.layer.addRawEventListenerWithoutResult(channel, await this.#getLayerListener()); this.#unsubscribeByChannel[channel] = unsubscribe; } }); await Promise.all(promises); } }; // src/server.ts var EventsServer = class extends EventEmitter { static { __name(this, "EventsServer"); } #interval; #addEventListenerPromises = []; /** * This is used for appending the promise of adding the event listener to an array so when we initialize the server * we can wait for all the callbacks to be appended and listen for events. */ addEventListener(...params) { const addEventListenerPromise = super.addEventListener(...params); this.#addEventListenerPromises.push(addEventListenerPromise); return addEventListenerPromise; } /** * This is used for appending the promise of adding the event listener to an array so when we initialize the server * we can wait for all the callbacks to be appended and listen for events. */ addEventListenerWithoutResult(...params) { const addEventListenerPromise = super.addEventListenerWithoutResult(...params); this.#addEventListenerPromises.push(addEventListenerPromise); return addEventListenerPromise; } /** * Listens for a request and keeps the application running, see here: * https://stackoverflow.com/questions/23622051/how-to-forcibly-keep-a-node-js-process-from-terminating/47456805#47456805 * * @param callback - Receives a function that should be called prior to starting the server. */ async listen(callback) { await Promise.all(this.#addEventListenerPromises); this.#addEventListenerPromises = []; callback(); if (setInterval) { this.#interval = setInterval(() => { return; }, 1 << 30); } } /** * If an interval had been created then we use this function to remove it so the connection is not kept alive. * * Also we use this so we can unsubscribe everything from the layer and unsubscribe all of it's listeners. */ async close() { const promises = []; if (this.#interval) clearInterval(this.#interval); if (this.layer) promises.push(this.layer.unsubscribeAll()); promises.push(this.unsubscribeAll()); await Promise.all(promises); } }; async function eventsServer(emitter, options) { return EventsServer.new(emitter, options); } __name(eventsServer, "eventsServer"); function setEventsServer(server) { globalThis.$PRunningEventsServer = server; } __name(setEventsServer, "setEventsServer"); function getEventsServer() { return globalThis.$PRunningEventsServer; } __name(getEventsServer, "getEventsServer"); // src/app/index.ts var cachedSettings = void 0; var eventsAppServer = (0, import_core.appServer)({ load: /* @__PURE__ */ __name(async ({ settings, domains }) => { const settingsAsEventsSettingsType = settings; cachedSettings = settingsAsEventsSettingsType; const server = await eventsServer(settingsAsEventsSettingsType.EVENTS_EMITTER, settingsAsEventsSettingsType.EVENTS_OPTIONS); await loadEvents(server, domains); setEventsServer(server); }, "load"), // eslint-disable-next-line ts/require-await close: /* @__PURE__ */ __name(async () => { const server = getEventsServer(); if (server) server.close(); }, "close"), start: /* @__PURE__ */ __name(async (configureCleanup) => { const server = getEventsServer(); await Promise.all([ configureCleanup(), server.listen(() => { if (cachedSettings) eventsLogger.logMessage("APP_START_EVENTS_SERVER", { appName: cachedSettings.APP_NAME || "@palmares/events_server" }); }) ]); }, "start") }); // src/domain.ts var eventsDomainModifier = (0, import_core2.domain)("@palmares/events", "", {}); var eventsDomain = (0, import_core2.domain)("@palmares/events", "", { commands: { eventsServer: { description: "Run the application in events server mode", positionalArgs: void 0, keywordArgs: void 0, handler: /* @__PURE__ */ __name(() => { return eventsAppServer; }, "handler") } } }); // src/emitter/exceptions.ts var NotImplementedServerException = class _NotImplementedServerException extends Error { static { __name(this, "NotImplementedServerException"); } constructor(serverName, methodName) { super(`Method '${methodName}' was not implemented in '${serverName}' and it should be implemented in order to fully work.`); this.name = _NotImplementedServerException.name; } }; // src/emitter/index.ts var Emitter = class { static { __name(this, "Emitter"); } // eslint-disable-next-line ts/require-await static async new(...args) { throw new NotImplementedServerException(this.name, "new"); } // eslint-disable-next-line ts/require-await async addEventListener(groupId, eventName, callback) { throw new NotImplementedServerException(this.constructor.name, "addEventListener"); } // eslint-disable-next-line ts/require-await async removeEventListener(groupId, eventName, callback) { throw new NotImplementedServerException(this.constructor.name, "unsubscribe"); } // eslint-disable-next-line ts/require-await async emit(groupId, eventName, ...data) { throw new NotImplementedServerException(this.constructor.name, "emit"); } }; // src/index.ts var src_default = eventsDomain; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Emitter, EventEmitter, EventsDomain, eventsServer, getEventsServer });