@noxfly/noxus 1.1.10 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,236 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+ var __defProp = Object.defineProperty;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
10
+
11
+ // src/request.ts
12
+ import "reflect-metadata";
13
+
14
+ // src/DI/app-injector.ts
15
+ import "reflect-metadata";
16
+
17
+ // src/exceptions.ts
18
+ var _ResponseException = class _ResponseException extends Error {
19
+ constructor(statusOrMessage, message) {
20
+ let statusCode;
21
+ if (typeof statusOrMessage === "number") {
22
+ statusCode = statusOrMessage;
23
+ } else if (typeof statusOrMessage === "string") {
24
+ message = statusOrMessage;
25
+ }
26
+ super(message ?? "");
27
+ __publicField(this, "status", 0);
28
+ if (statusCode !== void 0) {
29
+ this.status = statusCode;
30
+ }
31
+ this.name = this.constructor.name.replace(/([A-Z])/g, " $1");
32
+ }
33
+ };
34
+ __name(_ResponseException, "ResponseException");
35
+ var ResponseException = _ResponseException;
36
+ var _InternalServerException = class _InternalServerException extends ResponseException {
37
+ constructor() {
38
+ super(...arguments);
39
+ __publicField(this, "status", 500);
40
+ }
41
+ };
42
+ __name(_InternalServerException, "InternalServerException");
43
+ var InternalServerException = _InternalServerException;
44
+
45
+ // src/DI/app-injector.ts
46
+ var _AppInjector = class _AppInjector {
47
+ constructor(name = null) {
48
+ __publicField(this, "name");
49
+ __publicField(this, "bindings", /* @__PURE__ */ new Map());
50
+ __publicField(this, "singletons", /* @__PURE__ */ new Map());
51
+ __publicField(this, "scoped", /* @__PURE__ */ new Map());
52
+ this.name = name;
53
+ }
54
+ /**
55
+ * Typically used to create a dependency injection scope
56
+ * at the "scope" level (i.e., per-request lifetime).
57
+ *
58
+ * SHOULD NOT BE USED by anything else than the framework itself.
59
+ */
60
+ createScope() {
61
+ const scope = new _AppInjector();
62
+ scope.bindings = this.bindings;
63
+ scope.singletons = this.singletons;
64
+ return scope;
65
+ }
66
+ /**
67
+ * Called when resolving a dependency,
68
+ * i.e., retrieving the instance of a given class.
69
+ */
70
+ resolve(target) {
71
+ const binding = this.bindings.get(target);
72
+ if (!binding) throw new InternalServerException(`Failed to resolve a dependency injection : No binding for type ${target.name}.
73
+ Did you forget to use @Injectable() decorator ?`);
74
+ switch (binding.lifetime) {
75
+ case "transient":
76
+ return this.instantiate(binding.implementation);
77
+ case "scope": {
78
+ if (this.scoped.has(target)) {
79
+ return this.scoped.get(target);
80
+ }
81
+ const instance = this.instantiate(binding.implementation);
82
+ this.scoped.set(target, instance);
83
+ return instance;
84
+ }
85
+ case "singleton": {
86
+ if (binding.instance === void 0 && this.name === "root") {
87
+ binding.instance = this.instantiate(binding.implementation);
88
+ this.singletons.set(target, binding.instance);
89
+ }
90
+ return binding.instance;
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ *
96
+ */
97
+ instantiate(target) {
98
+ const paramTypes = Reflect.getMetadata("design:paramtypes", target) || [];
99
+ const params = paramTypes.map((p) => this.resolve(p));
100
+ return new target(...params);
101
+ }
102
+ };
103
+ __name(_AppInjector, "AppInjector");
104
+ var AppInjector = _AppInjector;
105
+ var RootInjector = new AppInjector("root");
106
+
107
+ // src/request.ts
108
+ var _Request = class _Request {
109
+ constructor(event, id, method, path, body) {
110
+ __publicField(this, "event");
111
+ __publicField(this, "id");
112
+ __publicField(this, "method");
113
+ __publicField(this, "path");
114
+ __publicField(this, "body");
115
+ __publicField(this, "context", RootInjector.createScope());
116
+ __publicField(this, "params", {});
117
+ this.event = event;
118
+ this.id = id;
119
+ this.method = method;
120
+ this.path = path;
121
+ this.body = body;
122
+ this.path = path.replace(/^\/|\/$/g, "");
123
+ }
124
+ };
125
+ __name(_Request, "Request");
126
+ var Request = _Request;
127
+ var RENDERER_EVENT_TYPE = "noxus:event";
128
+ function createRendererEventMessage(event, payload) {
129
+ return {
130
+ type: RENDERER_EVENT_TYPE,
131
+ event,
132
+ payload
133
+ };
134
+ }
135
+ __name(createRendererEventMessage, "createRendererEventMessage");
136
+ function isRendererEventMessage(value) {
137
+ if (value === null || typeof value !== "object") {
138
+ return false;
139
+ }
140
+ const possibleMessage = value;
141
+ return possibleMessage.type === RENDERER_EVENT_TYPE && typeof possibleMessage.event === "string";
142
+ }
143
+ __name(isRendererEventMessage, "isRendererEventMessage");
144
+
145
+ // src/renderer-events.ts
146
+ var _RendererEventRegistry = class _RendererEventRegistry {
147
+ constructor() {
148
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
149
+ }
150
+ /**
151
+ *
152
+ */
153
+ subscribe(eventName, handler) {
154
+ const normalizedEventName = eventName.trim();
155
+ if (normalizedEventName.length === 0) {
156
+ throw new Error("Renderer event name must be a non-empty string.");
157
+ }
158
+ const handlers = this.listeners.get(normalizedEventName) ?? /* @__PURE__ */ new Set();
159
+ handlers.add(handler);
160
+ this.listeners.set(normalizedEventName, handlers);
161
+ return {
162
+ unsubscribe: /* @__PURE__ */ __name(() => this.unsubscribe(normalizedEventName, handler), "unsubscribe")
163
+ };
164
+ }
165
+ /**
166
+ *
167
+ */
168
+ unsubscribe(eventName, handler) {
169
+ const handlers = this.listeners.get(eventName);
170
+ if (!handlers) {
171
+ return;
172
+ }
173
+ handlers.delete(handler);
174
+ if (handlers.size === 0) {
175
+ this.listeners.delete(eventName);
176
+ }
177
+ }
178
+ /**
179
+ *
180
+ */
181
+ clear(eventName) {
182
+ if (eventName) {
183
+ this.listeners.delete(eventName);
184
+ return;
185
+ }
186
+ this.listeners.clear();
187
+ }
188
+ /**
189
+ *
190
+ */
191
+ dispatch(message) {
192
+ const handlers = this.listeners.get(message.event);
193
+ if (!handlers || handlers.size === 0) {
194
+ return;
195
+ }
196
+ handlers.forEach((handler) => {
197
+ try {
198
+ handler(message.payload);
199
+ } catch (error) {
200
+ console.error(`[Noxus] Renderer event handler for "${message.event}" threw an error.`, error);
201
+ }
202
+ });
203
+ }
204
+ /**
205
+ *
206
+ */
207
+ tryDispatchFromMessageEvent(event) {
208
+ if (!isRendererEventMessage(event.data)) {
209
+ return false;
210
+ }
211
+ this.dispatch(event.data);
212
+ return true;
213
+ }
214
+ /**
215
+ *
216
+ */
217
+ hasHandlers(eventName) {
218
+ const handlers = this.listeners.get(eventName);
219
+ return !!handlers && handlers.size > 0;
220
+ }
221
+ };
222
+ __name(_RendererEventRegistry, "RendererEventRegistry");
223
+ var RendererEventRegistry = _RendererEventRegistry;
224
+ export {
225
+ RENDERER_EVENT_TYPE,
226
+ RendererEventRegistry,
227
+ Request,
228
+ createRendererEventMessage,
229
+ isRendererEventMessage
230
+ };
231
+ /**
232
+ * @copyright 2025 NoxFly
233
+ * @license MIT
234
+ * @author NoxFly
235
+ */
236
+ //# sourceMappingURL=renderer.mjs.map
package/package.json CHANGED
@@ -1,50 +1,75 @@
1
1
  {
2
- "name": "@noxfly/noxus",
3
- "version": "1.1.10",
4
- "main": "dist/noxus.js",
5
- "types": "dist/noxus.d.ts",
6
- "scripts": {
7
- "build": "tsup",
8
- "prepublishOnly": "npm run build",
9
- "prepush": "npm run build",
10
- "postbuild": "node scripts/postbuild.js"
11
- },
12
- "keywords": [
13
- "noxus",
14
- "nox",
15
- "nodejs",
16
- "typescript",
17
- "framework",
18
- "web framework",
19
- "electron"
20
- ],
21
- "author": "NoxFly",
22
- "license": "MIT",
23
- "description": "Simulate lightweight HTTP-like requests between renderer and main process in Electron applications with MessagePort, with structured and modular design.",
24
- "homepage": "https://github.com/NoxFly/noxus",
25
- "repository": {
26
- "type": "git",
27
- "url": "https://github.com/NoxFly/noxus.git"
28
- },
29
- "engines": {
30
- "node": ">= 20"
31
- },
32
- "devDependencies": {
33
- "@stylistic/eslint-plugin": "^5.1.0",
34
- "@swc/core": "^1.12.14",
35
- "@typescript-eslint/eslint-plugin": "^8.36.0",
36
- "@typescript-eslint/parser": "^8.36.0",
37
- "electron": "^37.2.1",
38
- "eslint": "^9.31.0",
39
- "eslint-plugin-import": "^2.32.0",
40
- "eslint-plugin-jsdoc": "^51.3.4",
41
- "eslint-plugin-prefer-arrow": "^1.2.3",
42
- "tsc-alias": "^1.8.16",
43
- "tsup": "^8.5.0",
44
- "typescript": "^5.8.3",
45
- "typescript-eslint": "^8.36.0"
46
- },
47
- "peerDependencies": {
48
- "reflect-metadata": "^0.2.2"
49
- }
2
+ "name": "@noxfly/noxus",
3
+ "version": "1.2.0",
4
+ "main": "dist/main.js",
5
+ "module": "dist/main.mjs",
6
+ "types": "dist/main.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "browser": {
10
+ "types": "./dist/renderer.d.ts",
11
+ "import": "./dist/renderer.mjs",
12
+ "require": "./dist/renderer.js"
13
+ },
14
+ "default": {
15
+ "types": "./dist/main.d.ts",
16
+ "import": "./dist/main.mjs",
17
+ "require": "./dist/main.js"
18
+ }
19
+ },
20
+ "./renderer": {
21
+ "types": "./dist/renderer.d.ts",
22
+ "import": "./dist/renderer.mjs",
23
+ "require": "./dist/renderer.js"
24
+ },
25
+ "./main": {
26
+ "types": "./dist/main.d.ts",
27
+ "import": "./dist/main.mjs",
28
+ "require": "./dist/main.js"
29
+ }
30
+ },
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "prepublishOnly": "npm run build",
34
+ "prepush": "npm run build",
35
+ "postbuild": "node scripts/postbuild.js"
36
+ },
37
+ "keywords": [
38
+ "noxus",
39
+ "nox",
40
+ "nodejs",
41
+ "typescript",
42
+ "framework",
43
+ "web framework",
44
+ "electron"
45
+ ],
46
+ "author": "NoxFly",
47
+ "license": "MIT",
48
+ "description": "Simulate lightweight HTTP-like requests between renderer and main process in Electron applications with MessagePort, with structured and modular design.",
49
+ "homepage": "https://github.com/NoxFly/noxus",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/NoxFly/noxus.git"
53
+ },
54
+ "engines": {
55
+ "node": ">= 20"
56
+ },
57
+ "devDependencies": {
58
+ "@stylistic/eslint-plugin": "^5.1.0",
59
+ "@swc/core": "^1.12.14",
60
+ "@typescript-eslint/eslint-plugin": "^8.36.0",
61
+ "@typescript-eslint/parser": "^8.36.0",
62
+ "electron": "^37.2.1",
63
+ "eslint": "^9.31.0",
64
+ "eslint-plugin-import": "^2.32.0",
65
+ "eslint-plugin-jsdoc": "^51.3.4",
66
+ "eslint-plugin-prefer-arrow": "^1.2.3",
67
+ "tsc-alias": "^1.8.16",
68
+ "tsup": "^8.5.0",
69
+ "typescript": "^5.8.3",
70
+ "typescript-eslint": "^8.36.0"
71
+ },
72
+ "peerDependencies": {
73
+ "reflect-metadata": "^0.2.2"
74
+ }
50
75
  }
@@ -1,10 +1,11 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
- const frameworkName = 'noxus';
4
+ function uniqueDocBlocks(filepath) {
5
+ if(!fs.existsSync(filepath)) {
6
+ return;
7
+ }
5
8
 
6
- function removeDuplicateCopyrights(filename) {
7
- const filepath = path.join(__dirname, '../dist/' + filename);
8
9
  const content = fs.readFileSync(filepath, 'utf8');
9
10
 
10
11
  const reg = /\/\*\*[\t ]*\n(?: \*.*\n)*? \* *@copyright.*\n(?: \*.*\n)*? \*\/\n?/gm;
@@ -21,6 +22,10 @@ function removeDuplicateCopyrights(filename) {
21
22
  fs.writeFileSync(filepath, deduped);
22
23
  }
23
24
 
25
+ const distDir = path.join(__dirname, '../dist');
24
26
 
25
- removeDuplicateCopyrights(`${frameworkName}.d.mts`);
26
- removeDuplicateCopyrights(`${frameworkName}.d.ts`);
27
+ for(const filename of fs.readdirSync(distDir)) {
28
+ if(filename.endsWith('.d.ts') || filename.endsWith('.d.mts')) {
29
+ uniqueDocBlocks(path.join(distDir, filename));
30
+ }
31
+ }
package/src/app.ts CHANGED
@@ -9,6 +9,7 @@ import { Injectable } from "src/decorators/injectable.decorator";
9
9
  import { IMiddleware } from "src/decorators/middleware.decorator";
10
10
  import { inject } from "src/DI/app-injector";
11
11
  import { IRequest, IResponse, Request } from "src/request";
12
+ import { NoxSocket } from "src/socket";
12
13
  import { Router } from "src/router";
13
14
  import { Logger } from "src/utils/logger";
14
15
  import { Type } from "src/utils/types";
@@ -30,11 +31,37 @@ export interface IApp {
30
31
  */
31
32
  @Injectable('singleton')
32
33
  export class NoxApp {
33
- private readonly messagePorts = new Map<number, Electron.MessageChannelMain>();
34
34
  private app: IApp | undefined;
35
+ private readonly onRendererMessage = async (event: Electron.MessageEvent): Promise<void> => {
36
+ const { senderId, requestId, path, method, body }: IRequest = event.data;
37
+
38
+ const channel = this.socket.get(senderId);
39
+
40
+ if(!channel) {
41
+ Logger.error(`No message channel found for sender ID: ${senderId}`);
42
+ return;
43
+ }
44
+
45
+ try {
46
+ const request = new Request(event, requestId, method, path, body);
47
+ const response = await this.router.handle(request);
48
+ channel.port1.postMessage(response);
49
+ }
50
+ catch(err: any) {
51
+ const response: IResponse = {
52
+ requestId,
53
+ status: 500,
54
+ body: null,
55
+ error: err.message || 'Internal Server Error',
56
+ };
57
+
58
+ channel.port1.postMessage(response);
59
+ }
60
+ };
35
61
 
36
62
  constructor(
37
63
  private readonly router: Router,
64
+ private readonly socket: NoxSocket,
38
65
  ) {}
39
66
 
40
67
  /**
@@ -62,47 +89,18 @@ export class NoxApp {
62
89
  private giveTheRendererAPort(event: Electron.IpcMainInvokeEvent): void {
63
90
  const senderId = event.sender.id;
64
91
 
65
- if(this.messagePorts.has(senderId)) {
92
+ if(this.socket.get(senderId)) {
66
93
  this.shutdownChannel(senderId);
67
94
  }
68
95
 
69
96
  const channel = new MessageChannelMain();
70
- this.messagePorts.set(senderId, channel);
71
97
 
72
- channel.port1.on('message', this.onRendererMessage.bind(this));
98
+ channel.port1.on('message', this.onRendererMessage);
73
99
  channel.port1.start();
74
100
 
75
- event.sender.postMessage('port', { senderId }, [channel.port2]);
76
- }
77
-
78
- /**
79
- * Electron specific message handling.
80
- * Replaces HTTP calls by using Electron's IPC mechanism.
81
- */
82
- private async onRendererMessage(event: Electron.MessageEvent): Promise<void> {
83
- const { senderId, requestId, path, method, body }: IRequest = event.data;
84
-
85
- const channel = this.messagePorts.get(senderId);
86
-
87
- if(!channel) {
88
- Logger.error(`No message channel found for sender ID: ${senderId}`);
89
- return;
90
- }
91
- try {
92
- const request = new Request(event, requestId, method, path, body);
93
- const response = await this.router.handle(request);
94
- channel.port1.postMessage(response);
95
- }
96
- catch(err: any) {
97
- const response: IResponse = {
98
- requestId,
99
- status: 500,
100
- body: null,
101
- error: err.message || 'Internal Server Error',
102
- };
101
+ this.socket.register(senderId, channel);
103
102
 
104
- channel.port1.postMessage(response);
105
- }
103
+ event.sender.postMessage('port', { senderId }, [channel.port2]);
106
104
  }
107
105
 
108
106
  /**
@@ -122,18 +120,18 @@ export class NoxApp {
122
120
  * @param remove - Whether to remove the channel from the messagePorts map.
123
121
  */
124
122
  private shutdownChannel(channelSenderId: number): void {
125
- const channel = this.messagePorts.get(channelSenderId);
123
+ const channel = this.socket.get(channelSenderId);
126
124
 
127
125
  if(!channel) {
128
126
  Logger.warn(`No message channel found for sender ID: ${channelSenderId}`);
129
127
  return;
130
128
  }
131
129
 
132
- channel.port1.off('message', this.onRendererMessage.bind(this));
130
+ channel.port1.off('message', this.onRendererMessage);
133
131
  channel.port1.close();
134
132
  channel.port2.close();
135
133
 
136
- this.messagePorts.delete(channelSenderId);
134
+ this.socket.unregister(channelSenderId);
137
135
  }
138
136
 
139
137
  /**
@@ -141,11 +139,9 @@ export class NoxApp {
141
139
  * This method is called when all windows are closed, and it cleans up the message channels
142
140
  */
143
141
  private async onAllWindowsClosed(): Promise<void> {
144
- this.messagePorts.forEach((channel, senderId) => {
142
+ for(const senderId of this.socket.getSenderIds()) {
145
143
  this.shutdownChannel(senderId);
146
- });
147
-
148
- this.messagePorts.clear();
144
+ }
149
145
 
150
146
  Logger.info('All windows closed, shutting down application...');
151
147
  await this.app?.dispose();
package/src/index.ts CHANGED
@@ -4,17 +4,5 @@
4
4
  * @author NoxFly
5
5
  */
6
6
 
7
- export * from './DI/app-injector';
8
- export * from './router';
9
- export * from './app';
10
- export * from './bootstrap';
11
- export * from './exceptions';
12
- export * from './decorators/middleware.decorator';
13
- export * from './decorators/guards.decorator';
14
- export * from './decorators/controller.decorator';
15
- export * from './decorators/injectable.decorator';
16
- export * from './decorators/method.decorator';
17
- export * from './decorators/module.decorator';
18
- export * from './utils/logger';
19
- export * from './utils/types';
20
7
  export * from './request';
8
+ export * from './renderer-events';
package/src/main.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
7
+ /**
8
+ * Entry point for Electron main-process consumers.
9
+ */
10
+ export * from './DI/app-injector';
11
+ export * from './router';
12
+ export * from './app';
13
+ export * from './bootstrap';
14
+ export * from './exceptions';
15
+ export * from './decorators/middleware.decorator';
16
+ export * from './decorators/guards.decorator';
17
+ export * from './decorators/controller.decorator';
18
+ export * from './decorators/injectable.decorator';
19
+ export * from './decorators/method.decorator';
20
+ export * from './decorators/module.decorator';
21
+ export * from './utils/logger';
22
+ export * from './utils/types';
23
+ export * from './request';
24
+ export * from './renderer-events';
25
+ export * from './socket';
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
7
+ /**
8
+ * Lightweight event registry to help renderer processes subscribe to
9
+ * push messages sent by the main process through Noxus.
10
+ */
11
+ import { IRendererEventMessage, isRendererEventMessage } from 'src/request';
12
+
13
+ export type RendererEventHandler<TPayload = unknown> = (payload: TPayload) => void;
14
+
15
+ export interface RendererEventSubscription {
16
+ unsubscribe(): void;
17
+ }
18
+
19
+ export class RendererEventRegistry {
20
+ private readonly listeners = new Map<string, Set<RendererEventHandler>>();
21
+
22
+ /**
23
+ *
24
+ */
25
+ public subscribe<TPayload>(eventName: string, handler: RendererEventHandler<TPayload>): RendererEventSubscription {
26
+ const normalizedEventName = eventName.trim();
27
+
28
+ if(normalizedEventName.length === 0) {
29
+ throw new Error('Renderer event name must be a non-empty string.');
30
+ }
31
+
32
+ const handlers = this.listeners.get(normalizedEventName) ?? new Set<RendererEventHandler>();
33
+
34
+ handlers.add(handler as RendererEventHandler);
35
+ this.listeners.set(normalizedEventName, handlers);
36
+
37
+ return {
38
+ unsubscribe: () => this.unsubscribe(normalizedEventName, handler as RendererEventHandler),
39
+ };
40
+ }
41
+
42
+ /**
43
+ *
44
+ */
45
+ public unsubscribe<TPayload>(eventName: string, handler: RendererEventHandler<TPayload>): void {
46
+ const handlers = this.listeners.get(eventName);
47
+
48
+ if(!handlers) {
49
+ return;
50
+ }
51
+
52
+ handlers.delete(handler as RendererEventHandler);
53
+
54
+ if(handlers.size === 0) {
55
+ this.listeners.delete(eventName);
56
+ }
57
+ }
58
+
59
+ /**
60
+ *
61
+ */
62
+ public clear(eventName?: string): void {
63
+ if(eventName) {
64
+ this.listeners.delete(eventName);
65
+ return;
66
+ }
67
+
68
+ this.listeners.clear();
69
+ }
70
+
71
+ /**
72
+ *
73
+ */
74
+ public dispatch<TPayload>(message: IRendererEventMessage<TPayload>): void {
75
+ const handlers = this.listeners.get(message.event);
76
+
77
+ if(!handlers || handlers.size === 0) {
78
+ return;
79
+ }
80
+
81
+ handlers.forEach((handler) => {
82
+ try {
83
+ handler(message.payload as TPayload);
84
+ }
85
+ catch(error) {
86
+ console.error(`[Noxus] Renderer event handler for "${message.event}" threw an error.`, error);
87
+ }
88
+ });
89
+ }
90
+
91
+ /**
92
+ *
93
+ */
94
+ public tryDispatchFromMessageEvent(event: MessageEvent): boolean {
95
+ if(!isRendererEventMessage(event.data)) {
96
+ return false;
97
+ }
98
+
99
+ this.dispatch(event.data);
100
+ return true;
101
+ }
102
+
103
+ /**
104
+ *
105
+ */
106
+ public hasHandlers(eventName: string): boolean {
107
+ const handlers = this.listeners.get(eventName);
108
+ return !!handlers && handlers.size > 0;
109
+ }
110
+ }