najm-event 0.1.1

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.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # najm-events
2
+
3
+ Optional events (event bus) plugin for Najm framework with decorator-based event handling and async event emission.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun install najm-events
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { On, Events } from 'najm-events';
15
+
16
+ // Listen to events with the @On decorator
17
+ @Service()
18
+ class UserService {
19
+ @On('user.created')
20
+ async handleUserCreated(data: any) {
21
+ console.log('User created:', data);
22
+ }
23
+ }
24
+
25
+ // Inject event methods using @Events decorator
26
+ @Controller('/api/users')
27
+ class UserController {
28
+ @Events() private events!: any;
29
+
30
+ @Post('/create')
31
+ async createUser(@Body() userData: any) {
32
+ // Create user logic here
33
+ const user = await this.userService.create(userData);
34
+
35
+ // Emit event
36
+ this.events.emit('user.created', user);
37
+
38
+ return user;
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Features
44
+
45
+ - Decorator-based event handling with `@On()` and `@Events()`
46
+ - Async event emission with error handling
47
+ - Integration with Najm's DI system
48
+ - Event statistics and management
@@ -0,0 +1,68 @@
1
+ import { InjectionDefinition, Constructor } from 'diject';
2
+ import * as najm_core from 'najm-core';
3
+ import { Constructor as Constructor$1 } from 'najm-core';
4
+ import * as mitt from 'mitt';
5
+ import { Handler } from 'mitt';
6
+
7
+ interface EventEmitter {
8
+ emit<T = any>(event: string, data?: T): void;
9
+ emitAsync<T = any>(event: string, data?: T): Promise<void>;
10
+ on<T = any>(event: string, handler: (data: T) => void): void;
11
+ off<T = any>(event: string, handler?: (data: T) => void): void;
12
+ once<T = any>(event: string, handler: (data: T) => void): void;
13
+ hasListeners(event: string): boolean;
14
+ listenerCount(event: string): number;
15
+ eventNames(): string[];
16
+ all(): any;
17
+ }
18
+ interface EventRegistration extends InjectionDefinition {
19
+ type: 'event';
20
+ target: Constructor;
21
+ eventName: string;
22
+ methodName: string;
23
+ }
24
+
25
+ declare const EVENTS_META: unique symbol;
26
+ declare const EVENT_CONFIG: unique symbol;
27
+
28
+ declare function Events(): any;
29
+ declare function On(eventName: string): MethodDecorator;
30
+ declare const getEventListeners: (t: any) => any;
31
+
32
+ declare const events: () => najm_core.NajmPlugin;
33
+
34
+ declare class EventService {
35
+ private container;
36
+ private scanner;
37
+ private log;
38
+ private emitter;
39
+ private eventHandlers;
40
+ scan(): Promise<void>;
41
+ private setupEventsGetter;
42
+ configure(): Promise<void>;
43
+ private registerInjectors;
44
+ activate(): Promise<void>;
45
+ private registerEventHandler;
46
+ onReady(): Promise<void>;
47
+ emit<T = any>(event: string, data?: T): void;
48
+ emitAsync<T = any>(event: string, data?: T): Promise<void>;
49
+ on<T = any>(event: string, handler: Handler<T>): void;
50
+ off<T = any>(event: string, handler?: Handler<T>): void;
51
+ once<T = any>(event: string, handler: Handler<T>): void;
52
+ hasListeners(event: string): boolean;
53
+ listenerCount(event: string): number;
54
+ eventNames(): string[];
55
+ all(): mitt.EventHandlerMap<any>;
56
+ registerProviderEvents(provider: Constructor$1): Promise<void>;
57
+ unregisterProviderEvents(provider: Constructor$1): void;
58
+ clear(): void;
59
+ getStats(): {
60
+ totalProviders: number;
61
+ totalHandlers: number;
62
+ providers: string[];
63
+ events: string[];
64
+ eventCounts: Record<string, number>;
65
+ };
66
+ }
67
+
68
+ export { EVENTS_META, EVENT_CONFIG, type EventEmitter, type EventRegistration, EventService, Events, On, events, getEventListeners };
package/dist/index.mjs ADDED
@@ -0,0 +1,315 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/tokens.ts
5
+ var EVENTS_META = /* @__PURE__ */ Symbol("events");
6
+ var EVENT_CONFIG = /* @__PURE__ */ Symbol("event:config");
7
+
8
+ // src/decorator.ts
9
+ import { INJECT_PROPS, MetaHelper } from "najm-core";
10
+ function Events() {
11
+ return (target, propertyKey) => {
12
+ MetaHelper.append(INJECT_PROPS, {
13
+ propertyKey,
14
+ token: "EventsMethods"
15
+ }, target.constructor);
16
+ };
17
+ }
18
+ __name(Events, "Events");
19
+ function On(eventName) {
20
+ return (target, method) => {
21
+ MetaHelper.append(EVENTS_META, { eventName, methodName: method }, target.constructor);
22
+ };
23
+ }
24
+ __name(On, "On");
25
+ var getEventListeners = /* @__PURE__ */ __name((t) => MetaHelper.get(EVENTS_META, t) ?? [], "getEventListeners");
26
+
27
+ // src/EventPlugin.ts
28
+ import { plugin } from "najm-core";
29
+
30
+ // src/EventService.ts
31
+ import mitt from "mitt";
32
+ import { LoggerService, Scan, ScannerService, ScanType, INJECTION_TYPES } from "najm-core";
33
+ import { Meta, Container, Inject, getPropertyInjections, DI, Service } from "najm-core";
34
+ var __decorate = function(decorators, target, key, desc) {
35
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
36
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
37
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
38
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
39
+ };
40
+ var __metadata = function(k, v) {
41
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
42
+ };
43
+ var _a;
44
+ var _b;
45
+ var _c;
46
+ var EventService = class EventService2 {
47
+ static {
48
+ __name(this, "EventService");
49
+ }
50
+ container;
51
+ scanner;
52
+ log;
53
+ emitter;
54
+ eventHandlers = /* @__PURE__ */ new Map();
55
+ // ============================================================================
56
+ // LIFECYCLE: SCAN
57
+ // ============================================================================
58
+ async scan() {
59
+ this.emitter = mitt();
60
+ this.scanner.scan(ScanType.APP, {
61
+ onClass: /* @__PURE__ */ __name((provider) => {
62
+ const listeners = getEventListeners(provider);
63
+ for (const { eventName, methodName } of listeners) {
64
+ this.container.setInjection({
65
+ type: INJECTION_TYPES.EVENT,
66
+ target: provider,
67
+ eventName,
68
+ methodName
69
+ });
70
+ }
71
+ const propInjections = getPropertyInjections(provider);
72
+ const eventsInjections = propInjections.filter((inj) => inj.token === "EventsMethods");
73
+ for (const injection of eventsInjections) {
74
+ this.setupEventsGetter(provider, injection.propertyKey);
75
+ }
76
+ }, "onClass")
77
+ });
78
+ }
79
+ setupEventsGetter(provider, propertyKey) {
80
+ const eventService = this;
81
+ const createEventsMethods = /* @__PURE__ */ __name(() => ({
82
+ emit: eventService.emit.bind(eventService),
83
+ emitAsync: eventService.emitAsync.bind(eventService),
84
+ on: eventService.on.bind(eventService),
85
+ off: eventService.off.bind(eventService),
86
+ once: eventService.once.bind(eventService),
87
+ hasListeners: eventService.hasListeners.bind(eventService),
88
+ listenerCount: eventService.listenerCount.bind(eventService),
89
+ eventNames: eventService.eventNames.bind(eventService),
90
+ all: eventService.all.bind(eventService)
91
+ }), "createEventsMethods");
92
+ const instanceCache = /* @__PURE__ */ new WeakMap();
93
+ Object.defineProperty(provider.prototype, propertyKey, {
94
+ configurable: true,
95
+ enumerable: true,
96
+ get() {
97
+ let methods = instanceCache.get(this);
98
+ if (!methods) {
99
+ methods = createEventsMethods();
100
+ instanceCache.set(this, methods);
101
+ }
102
+ return methods;
103
+ },
104
+ set(value) {
105
+ Object.defineProperty(this, propertyKey, {
106
+ configurable: true,
107
+ enumerable: true,
108
+ writable: true,
109
+ value
110
+ });
111
+ }
112
+ });
113
+ }
114
+ // ============================================================================
115
+ // LIFECYCLE: CONFIGURE
116
+ // ============================================================================
117
+ async configure() {
118
+ this.registerInjectors();
119
+ }
120
+ registerInjectors() {
121
+ this.container.use({
122
+ name: "Events",
123
+ global: true,
124
+ inject: /* @__PURE__ */ __name((instance) => {
125
+ instance.events = this;
126
+ }, "inject")
127
+ });
128
+ this.container.use({
129
+ name: "EventsMethods",
130
+ global: true,
131
+ inject: /* @__PURE__ */ __name((instance, _ctor, _registry, injections) => {
132
+ if (!injections)
133
+ return;
134
+ const eventsInjections = injections.filter((inj) => inj.token === "EventsMethods");
135
+ for (const injection of eventsInjections) {
136
+ const { propertyKey } = injection;
137
+ instance[propertyKey] = {
138
+ emit: this.emit.bind(this),
139
+ emitAsync: this.emitAsync.bind(this),
140
+ on: this.on.bind(this),
141
+ off: this.off.bind(this),
142
+ once: this.once.bind(this),
143
+ hasListeners: this.hasListeners.bind(this),
144
+ listenerCount: this.listenerCount.bind(this),
145
+ eventNames: this.eventNames.bind(this),
146
+ all: this.all.bind(this)
147
+ };
148
+ }
149
+ }, "inject")
150
+ });
151
+ }
152
+ // ============================================================================
153
+ // LIFECYCLE: ACTIVATE
154
+ // ============================================================================
155
+ async activate() {
156
+ const registrations = this.container.getInjections(INJECTION_TYPES.EVENT);
157
+ const providersWithHandlers = new Set(registrations.map((r) => r.target));
158
+ for (const provider of providersWithHandlers) {
159
+ await this.container.resolve(provider);
160
+ }
161
+ for (const registration of registrations) {
162
+ await this.registerEventHandler(registration);
163
+ }
164
+ }
165
+ async registerEventHandler(registration) {
166
+ const { target, eventName, methodName } = registration;
167
+ if (!this.eventHandlers.has(target)) {
168
+ this.eventHandlers.set(target, []);
169
+ }
170
+ const instance = await this.container.resolve(target);
171
+ const handler = /* @__PURE__ */ __name(async (data) => {
172
+ return instance[methodName]?.(data);
173
+ }, "handler");
174
+ this.on(eventName, handler);
175
+ this.eventHandlers.get(target).push({ eventName, handler });
176
+ }
177
+ // ============================================================================
178
+ // LIFECYCLE: READY
179
+ // ============================================================================
180
+ async onReady() {
181
+ const count = this.container.getInjections(INJECTION_TYPES.EVENT).length;
182
+ this.log.info(`Event plugin ready: ${count} handler(s) registered`);
183
+ }
184
+ // ============================================================================
185
+ // EVENT EMITTER API
186
+ // ============================================================================
187
+ emit(event, data) {
188
+ this.emitter.emit(event, data);
189
+ }
190
+ async emitAsync(event, data) {
191
+ const handlers = this.emitter.all.get(event);
192
+ if (!handlers || handlers.size === 0)
193
+ return;
194
+ const results = await Promise.allSettled([...handlers].map((h) => Promise.resolve(h(data))));
195
+ const errors = results.filter((r) => r.status === "rejected").map((r) => r.reason);
196
+ if (errors.length > 0) {
197
+ const errorMessage = `${errors.length} handler(s) failed for event "${event}"`;
198
+ this.log.error(errorMessage, errors[0], { event, failedHandlers: errors.length });
199
+ throw new AggregateError(errors, errorMessage);
200
+ }
201
+ }
202
+ on(event, handler) {
203
+ this.emitter.on(event, handler);
204
+ }
205
+ off(event, handler) {
206
+ if (handler) {
207
+ this.emitter.off(event, handler);
208
+ } else {
209
+ this.emitter.all.delete(event);
210
+ }
211
+ }
212
+ once(event, handler) {
213
+ const onceHandler = /* @__PURE__ */ __name((data) => {
214
+ this.off(event, onceHandler);
215
+ handler(data);
216
+ }, "onceHandler");
217
+ this.on(event, onceHandler);
218
+ }
219
+ hasListeners(event) {
220
+ const handlers = this.emitter.all.get(event);
221
+ return handlers ? handlers.length > 0 : false;
222
+ }
223
+ listenerCount(event) {
224
+ const handlers = this.emitter.all.get(event);
225
+ return handlers ? handlers.length : 0;
226
+ }
227
+ eventNames() {
228
+ return Array.from(this.emitter.all.keys());
229
+ }
230
+ all() {
231
+ return this.emitter.all;
232
+ }
233
+ // ============================================================================
234
+ // DYNAMIC REGISTRATION
235
+ // ============================================================================
236
+ async registerProviderEvents(provider) {
237
+ if (this.eventHandlers.has(provider))
238
+ return;
239
+ const listeners = getEventListeners(provider);
240
+ if (listeners.length === 0)
241
+ return;
242
+ const instance = await this.container.resolve(provider);
243
+ const handlers = [];
244
+ for (const { eventName, methodName } of listeners) {
245
+ const handler = /* @__PURE__ */ __name(async (data) => {
246
+ return instance[methodName]?.(data);
247
+ }, "handler");
248
+ this.on(eventName, handler);
249
+ handlers.push({ eventName, handler });
250
+ }
251
+ this.eventHandlers.set(provider, handlers);
252
+ }
253
+ unregisterProviderEvents(provider) {
254
+ const handlers = this.eventHandlers.get(provider);
255
+ if (!handlers)
256
+ return;
257
+ for (const { eventName, handler } of handlers) {
258
+ this.emitter.off(eventName, handler);
259
+ }
260
+ this.eventHandlers.delete(provider);
261
+ }
262
+ // ============================================================================
263
+ // UTILITIES
264
+ // ============================================================================
265
+ clear() {
266
+ this.eventHandlers.clear();
267
+ this.emitter.all.clear();
268
+ }
269
+ getStats() {
270
+ const events2 = this.eventNames();
271
+ const eventCounts = {};
272
+ for (const event of events2) {
273
+ const count = this.listenerCount(event);
274
+ if (count > 0) {
275
+ eventCounts[event] = count;
276
+ }
277
+ }
278
+ const totalHandlers = Array.from(this.eventHandlers.values()).reduce((sum, handlers) => sum + handlers.length, 0);
279
+ return {
280
+ totalProviders: this.eventHandlers.size,
281
+ totalHandlers,
282
+ providers: Array.from(this.eventHandlers.keys()).map((p) => p.name),
283
+ events: events2,
284
+ eventCounts
285
+ };
286
+ }
287
+ };
288
+ __decorate([
289
+ DI(),
290
+ __metadata("design:type", typeof (_a = typeof Container !== "undefined" && Container) === "function" ? _a : Object)
291
+ ], EventService.prototype, "container", void 0);
292
+ __decorate([
293
+ Scan(),
294
+ __metadata("design:type", typeof (_b = typeof ScannerService !== "undefined" && ScannerService) === "function" ? _b : Object)
295
+ ], EventService.prototype, "scanner", void 0);
296
+ __decorate([
297
+ Inject(LoggerService),
298
+ __metadata("design:type", typeof (_c = typeof LoggerService !== "undefined" && LoggerService) === "function" ? _c : Object)
299
+ ], EventService.prototype, "log", void 0);
300
+ EventService = __decorate([
301
+ Service(),
302
+ Meta({ layer: "plugin" })
303
+ ], EventService);
304
+
305
+ // src/EventPlugin.ts
306
+ var events = /* @__PURE__ */ __name(() => plugin("events").services(EventService).build(), "events");
307
+ export {
308
+ EVENTS_META,
309
+ EVENT_CONFIG,
310
+ EventService,
311
+ Events,
312
+ On,
313
+ events,
314
+ getEventListeners
315
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "najm-event",
3
+ "version": "0.1.1",
4
+ "description": "Event .",
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "bun": "./src/index.ts",
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.mjs",
16
+ "default": "./dist/index.mjs"
17
+ },
18
+ "./*": {
19
+ "bun": "./src/*.ts",
20
+ "types": "./src/*.ts",
21
+ "import": "./src/*.ts",
22
+ "default": "./src/*.ts"
23
+ }
24
+ },
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "test": "bun test",
28
+ "test:watch": "bun test --watch",
29
+ "start": "bun --watch src/main.ts",
30
+ "clean": "rimraf dist tsconfig.tsbuildinfo"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.7.3",
34
+ "rimraf": "^6.0.1"
35
+ },
36
+ "dependencies": {
37
+ "mitt": "^3.0.1"
38
+ },
39
+ "peerDependencies": {
40
+ "najm-core": "^0.1.1",
41
+ "reflect-metadata": "^0.2.0"
42
+ }
43
+ }