@dangao/bun-server 1.7.1 → 1.8.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.
Files changed (159) hide show
  1. package/README.md +129 -21
  2. package/dist/di/decorators.d.ts +37 -0
  3. package/dist/di/decorators.d.ts.map +1 -1
  4. package/dist/di/index.d.ts +1 -1
  5. package/dist/di/index.d.ts.map +1 -1
  6. package/dist/di/module-registry.d.ts +17 -0
  7. package/dist/di/module-registry.d.ts.map +1 -1
  8. package/dist/events/decorators.d.ts +52 -0
  9. package/dist/events/decorators.d.ts.map +1 -0
  10. package/dist/events/event-module.d.ts +97 -0
  11. package/dist/events/event-module.d.ts.map +1 -0
  12. package/dist/events/index.d.ts +5 -0
  13. package/dist/events/index.d.ts.map +1 -0
  14. package/dist/events/service.d.ts +76 -0
  15. package/dist/events/service.d.ts.map +1 -0
  16. package/dist/events/types.d.ts +184 -0
  17. package/dist/events/types.d.ts.map +1 -0
  18. package/dist/index.d.ts +5 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1511 -11
  21. package/dist/security/filter.d.ts +23 -0
  22. package/dist/security/filter.d.ts.map +1 -1
  23. package/dist/security/guards/builtin/auth-guard.d.ts +44 -0
  24. package/dist/security/guards/builtin/auth-guard.d.ts.map +1 -0
  25. package/dist/security/guards/builtin/index.d.ts +3 -0
  26. package/dist/security/guards/builtin/index.d.ts.map +1 -0
  27. package/dist/security/guards/builtin/roles-guard.d.ts +66 -0
  28. package/dist/security/guards/builtin/roles-guard.d.ts.map +1 -0
  29. package/dist/security/guards/decorators.d.ts +50 -0
  30. package/dist/security/guards/decorators.d.ts.map +1 -0
  31. package/dist/security/guards/execution-context.d.ts +56 -0
  32. package/dist/security/guards/execution-context.d.ts.map +1 -0
  33. package/dist/security/guards/guard-registry.d.ts +67 -0
  34. package/dist/security/guards/guard-registry.d.ts.map +1 -0
  35. package/dist/security/guards/index.d.ts +7 -0
  36. package/dist/security/guards/index.d.ts.map +1 -0
  37. package/dist/security/guards/reflector.d.ts +57 -0
  38. package/dist/security/guards/reflector.d.ts.map +1 -0
  39. package/dist/security/guards/types.d.ts +126 -0
  40. package/dist/security/guards/types.d.ts.map +1 -0
  41. package/dist/security/index.d.ts +1 -0
  42. package/dist/security/index.d.ts.map +1 -1
  43. package/dist/security/security-module.d.ts +20 -0
  44. package/dist/security/security-module.d.ts.map +1 -1
  45. package/dist/validation/class-validator.d.ts +108 -0
  46. package/dist/validation/class-validator.d.ts.map +1 -0
  47. package/dist/validation/custom-validator.d.ts +130 -0
  48. package/dist/validation/custom-validator.d.ts.map +1 -0
  49. package/dist/validation/errors.d.ts +22 -2
  50. package/dist/validation/errors.d.ts.map +1 -1
  51. package/dist/validation/index.d.ts +7 -1
  52. package/dist/validation/index.d.ts.map +1 -1
  53. package/dist/validation/rules/array.d.ts +33 -0
  54. package/dist/validation/rules/array.d.ts.map +1 -0
  55. package/dist/validation/rules/common.d.ts +90 -0
  56. package/dist/validation/rules/common.d.ts.map +1 -0
  57. package/dist/validation/rules/conditional.d.ts +30 -0
  58. package/dist/validation/rules/conditional.d.ts.map +1 -0
  59. package/dist/validation/rules/index.d.ts +5 -0
  60. package/dist/validation/rules/index.d.ts.map +1 -0
  61. package/dist/validation/rules/object.d.ts +30 -0
  62. package/dist/validation/rules/object.d.ts.map +1 -0
  63. package/dist/validation/types.d.ts +52 -1
  64. package/dist/validation/types.d.ts.map +1 -1
  65. package/docs/events.md +494 -0
  66. package/docs/guards.md +376 -0
  67. package/docs/guide.md +309 -1
  68. package/docs/request-lifecycle.md +444 -0
  69. package/docs/validation.md +407 -0
  70. package/docs/zh/events.md +494 -0
  71. package/docs/zh/guards.md +376 -0
  72. package/docs/zh/guide.md +309 -1
  73. package/docs/zh/request-lifecycle.md +444 -0
  74. package/docs/zh/validation.md +407 -0
  75. package/package.json +1 -1
  76. package/src/di/decorators.ts +46 -0
  77. package/src/di/index.ts +10 -1
  78. package/src/di/module-registry.ts +39 -0
  79. package/src/events/decorators.ts +103 -0
  80. package/src/events/event-module.ts +272 -0
  81. package/src/events/index.ts +32 -0
  82. package/src/events/service.ts +352 -0
  83. package/src/events/types.ts +223 -0
  84. package/src/index.ts +133 -1
  85. package/src/security/filter.ts +88 -8
  86. package/src/security/guards/builtin/auth-guard.ts +68 -0
  87. package/src/security/guards/builtin/index.ts +3 -0
  88. package/src/security/guards/builtin/roles-guard.ts +165 -0
  89. package/src/security/guards/decorators.ts +124 -0
  90. package/src/security/guards/execution-context.ts +152 -0
  91. package/src/security/guards/guard-registry.ts +164 -0
  92. package/src/security/guards/index.ts +7 -0
  93. package/src/security/guards/reflector.ts +99 -0
  94. package/src/security/guards/types.ts +144 -0
  95. package/src/security/index.ts +1 -0
  96. package/src/security/security-module.ts +72 -2
  97. package/src/validation/class-validator.ts +322 -0
  98. package/src/validation/custom-validator.ts +289 -0
  99. package/src/validation/errors.ts +50 -2
  100. package/src/validation/index.ts +103 -1
  101. package/src/validation/rules/array.ts +118 -0
  102. package/src/validation/rules/common.ts +286 -0
  103. package/src/validation/rules/conditional.ts +52 -0
  104. package/src/validation/rules/index.ts +51 -0
  105. package/src/validation/rules/object.ts +86 -0
  106. package/src/validation/types.ts +61 -1
  107. package/tests/auth/auth-decorators.test.ts +241 -0
  108. package/tests/auth/oauth2-service.test.ts +318 -0
  109. package/tests/cache/cache-decorators-extended.test.ts +272 -0
  110. package/tests/cache/cache-interceptors.test.ts +534 -0
  111. package/tests/cache/cache-service-proxy.test.ts +246 -0
  112. package/tests/cache/memory-cache-store.test.ts +155 -0
  113. package/tests/cache/redis-cache-store.test.ts +199 -0
  114. package/tests/config/config-center-integration.test.ts +334 -0
  115. package/tests/config/config-module-extended.test.ts +165 -0
  116. package/tests/controller/param-binder.test.ts +333 -0
  117. package/tests/di/global-module.test.ts +487 -0
  118. package/tests/error/error-handler.test.ts +166 -57
  119. package/tests/error/i18n-extended.test.ts +105 -0
  120. package/tests/events/event-decorators.test.ts +173 -0
  121. package/tests/events/event-emitter.test.ts +373 -0
  122. package/tests/events/event-listener-scanner.test.ts +114 -0
  123. package/tests/events/event-module.test.ts +204 -0
  124. package/tests/extensions/logger-module.test.ts +158 -0
  125. package/tests/files/file-storage.test.ts +136 -0
  126. package/tests/interceptor/base-interceptor.test.ts +605 -0
  127. package/tests/interceptor/builtin/cache-interceptor.test.ts +233 -86
  128. package/tests/interceptor/builtin/log-interceptor.test.ts +469 -0
  129. package/tests/interceptor/builtin/permission-interceptor.test.ts +219 -120
  130. package/tests/interceptor/interceptor-chain.test.ts +241 -189
  131. package/tests/interceptor/interceptor-metadata.test.ts +221 -0
  132. package/tests/microservice/circuit-breaker.test.ts +221 -0
  133. package/tests/microservice/service-client-decorators.test.ts +86 -0
  134. package/tests/microservice/service-client-interceptors.test.ts +274 -0
  135. package/tests/microservice/service-registry-decorators.test.ts +147 -0
  136. package/tests/microservice/tracer.test.ts +213 -0
  137. package/tests/microservice/tracing-collectors.test.ts +168 -0
  138. package/tests/middleware/builtin/middleware-builtin-extended.test.ts +237 -0
  139. package/tests/middleware/builtin/rate-limit.test.ts +257 -0
  140. package/tests/middleware/middleware-decorators.test.ts +222 -0
  141. package/tests/middleware/middleware-pipeline.test.ts +160 -0
  142. package/tests/queue/queue-decorators.test.ts +139 -0
  143. package/tests/queue/queue-service.test.ts +191 -0
  144. package/tests/request/body-parser-extended.test.ts +291 -0
  145. package/tests/request/request-wrapper.test.ts +319 -0
  146. package/tests/router/router-decorators.test.ts +260 -0
  147. package/tests/router/router-extended.test.ts +298 -0
  148. package/tests/security/guards/guards-integration.test.ts +371 -0
  149. package/tests/security/guards/guards.test.ts +775 -0
  150. package/tests/security/guards/reflector.test.ts +188 -0
  151. package/tests/security/security-filter.test.ts +182 -0
  152. package/tests/security/security-module-extended.test.ts +133 -0
  153. package/tests/security/security-module.test.ts +2 -2
  154. package/tests/session/memory-session-store.test.ts +172 -0
  155. package/tests/session/session-decorators.test.ts +163 -0
  156. package/tests/swagger/ui.test.ts +212 -0
  157. package/tests/validation/class-validator.test.ts +349 -0
  158. package/tests/validation/custom-validator.test.ts +335 -0
  159. package/tests/validation/rules.test.ts +543 -0
@@ -0,0 +1,373 @@
1
+ import { describe, expect, test, beforeEach, mock } from 'bun:test';
2
+ import { EventEmitterService } from '../../src/events/service';
3
+ import type { EventEmitter, EventModuleOptions } from '../../src/events/types';
4
+
5
+ describe('EventEmitterService', () => {
6
+ let emitter: EventEmitter;
7
+
8
+ beforeEach(() => {
9
+ emitter = new EventEmitterService();
10
+ });
11
+
12
+ describe('on() and emit()', () => {
13
+ test('should register and trigger listener', () => {
14
+ const listener = mock(() => {});
15
+ emitter.on('test.event', listener);
16
+ emitter.emit('test.event', { data: 'hello' });
17
+
18
+ expect(listener).toHaveBeenCalledTimes(1);
19
+ expect(listener).toHaveBeenCalledWith({ data: 'hello' });
20
+ });
21
+
22
+ test('should support multiple listeners for same event', () => {
23
+ const listener1 = mock(() => {});
24
+ const listener2 = mock(() => {});
25
+
26
+ emitter.on('test.event', listener1);
27
+ emitter.on('test.event', listener2);
28
+ emitter.emit('test.event', 'payload');
29
+
30
+ expect(listener1).toHaveBeenCalledTimes(1);
31
+ expect(listener2).toHaveBeenCalledTimes(1);
32
+ });
33
+
34
+ test('should not trigger listener for different event', () => {
35
+ const listener = mock(() => {});
36
+ emitter.on('test.event1', listener);
37
+ emitter.emit('test.event2', 'payload');
38
+
39
+ expect(listener).not.toHaveBeenCalled();
40
+ });
41
+
42
+ test('should support Symbol as event name', () => {
43
+ const eventSymbol = Symbol('test.event');
44
+ const listener = mock(() => {});
45
+
46
+ emitter.on(eventSymbol, listener);
47
+ emitter.emit(eventSymbol, { key: 'value' });
48
+
49
+ expect(listener).toHaveBeenCalledTimes(1);
50
+ expect(listener).toHaveBeenCalledWith({ key: 'value' });
51
+ });
52
+
53
+ test('should return unsubscribe function', () => {
54
+ const listener = mock(() => {});
55
+ const unsubscribe = emitter.on('test.event', listener);
56
+
57
+ emitter.emit('test.event', 'first');
58
+ expect(listener).toHaveBeenCalledTimes(1);
59
+
60
+ unsubscribe();
61
+ emitter.emit('test.event', 'second');
62
+ expect(listener).toHaveBeenCalledTimes(1);
63
+ });
64
+ });
65
+
66
+ describe('once()', () => {
67
+ test('should only trigger listener once', () => {
68
+ const listener = mock(() => {});
69
+ emitter.once('test.event', listener);
70
+
71
+ emitter.emit('test.event', 'first');
72
+ emitter.emit('test.event', 'second');
73
+
74
+ expect(listener).toHaveBeenCalledTimes(1);
75
+ expect(listener).toHaveBeenCalledWith('first');
76
+ });
77
+
78
+ test('should return unsubscribe function', () => {
79
+ const listener = mock(() => {});
80
+ const unsubscribe = emitter.once('test.event', listener);
81
+
82
+ unsubscribe();
83
+ emitter.emit('test.event', 'payload');
84
+
85
+ expect(listener).not.toHaveBeenCalled();
86
+ });
87
+ });
88
+
89
+ describe('off()', () => {
90
+ test('should remove specific listener', () => {
91
+ const listener1 = mock(() => {});
92
+ const listener2 = mock(() => {});
93
+
94
+ emitter.on('test.event', listener1);
95
+ emitter.on('test.event', listener2);
96
+ emitter.off('test.event', listener1);
97
+ emitter.emit('test.event', 'payload');
98
+
99
+ expect(listener1).not.toHaveBeenCalled();
100
+ expect(listener2).toHaveBeenCalledTimes(1);
101
+ });
102
+
103
+ test('should do nothing if listener not found', () => {
104
+ const listener = mock(() => {});
105
+ emitter.off('test.event', listener); // 不抛出错误
106
+ });
107
+ });
108
+
109
+ describe('removeAllListeners()', () => {
110
+ test('should remove all listeners for specific event', () => {
111
+ const listener1 = mock(() => {});
112
+ const listener2 = mock(() => {});
113
+
114
+ emitter.on('event1', listener1);
115
+ emitter.on('event2', listener2);
116
+ emitter.removeAllListeners('event1');
117
+
118
+ emitter.emit('event1', 'payload');
119
+ emitter.emit('event2', 'payload');
120
+
121
+ expect(listener1).not.toHaveBeenCalled();
122
+ expect(listener2).toHaveBeenCalledTimes(1);
123
+ });
124
+
125
+ test('should remove all listeners when no event specified', () => {
126
+ const listener1 = mock(() => {});
127
+ const listener2 = mock(() => {});
128
+
129
+ emitter.on('event1', listener1);
130
+ emitter.on('event2', listener2);
131
+ emitter.removeAllListeners();
132
+
133
+ emitter.emit('event1', 'payload');
134
+ emitter.emit('event2', 'payload');
135
+
136
+ expect(listener1).not.toHaveBeenCalled();
137
+ expect(listener2).not.toHaveBeenCalled();
138
+ });
139
+ });
140
+
141
+ describe('listenerCount()', () => {
142
+ test('should return correct listener count', () => {
143
+ expect(emitter.listenerCount('test.event')).toBe(0);
144
+
145
+ emitter.on('test.event', () => {});
146
+ expect(emitter.listenerCount('test.event')).toBe(1);
147
+
148
+ emitter.on('test.event', () => {});
149
+ expect(emitter.listenerCount('test.event')).toBe(2);
150
+ });
151
+ });
152
+
153
+ describe('eventNames()', () => {
154
+ test('should return all registered event names', () => {
155
+ const symbol = Symbol('symbol.event');
156
+ emitter.on('string.event', () => {});
157
+ emitter.on(symbol, () => {});
158
+
159
+ const names = emitter.eventNames();
160
+ expect(names).toContain('string.event');
161
+ expect(names).toContain(symbol);
162
+ expect(names.length).toBe(2);
163
+ });
164
+
165
+ test('should return empty array when no listeners', () => {
166
+ expect(emitter.eventNames()).toEqual([]);
167
+ });
168
+ });
169
+
170
+ describe('priority', () => {
171
+ test('should execute listeners in priority order (high to low)', () => {
172
+ const order: number[] = [];
173
+
174
+ emitter.on('test.event', () => order.push(1), { priority: 1 });
175
+ emitter.on('test.event', () => order.push(3), { priority: 3 });
176
+ emitter.on('test.event', () => order.push(2), { priority: 2 });
177
+
178
+ emitter.emit('test.event', null);
179
+
180
+ expect(order).toEqual([3, 2, 1]);
181
+ });
182
+
183
+ test('should use default priority 0 when not specified', () => {
184
+ const order: number[] = [];
185
+
186
+ emitter.on('test.event', () => order.push(1)); // priority: 0
187
+ emitter.on('test.event', () => order.push(2), { priority: 1 });
188
+ emitter.on('test.event', () => order.push(3), { priority: -1 });
189
+
190
+ emitter.emit('test.event', null);
191
+
192
+ expect(order).toEqual([2, 1, 3]);
193
+ });
194
+ });
195
+
196
+ describe('emitAsync()', () => {
197
+ test('should wait for all async listeners to complete', async () => {
198
+ const results: number[] = [];
199
+
200
+ emitter.on('test.event', async () => {
201
+ await new Promise((resolve) => setTimeout(resolve, 10));
202
+ results.push(1);
203
+ });
204
+
205
+ emitter.on('test.event', async () => {
206
+ await new Promise((resolve) => setTimeout(resolve, 5));
207
+ results.push(2);
208
+ });
209
+
210
+ await emitter.emitAsync('test.event', null);
211
+
212
+ expect(results.length).toBe(2);
213
+ expect(results).toContain(1);
214
+ expect(results).toContain(2);
215
+ });
216
+
217
+ test('should handle sync listeners in emitAsync', async () => {
218
+ const listener = mock(() => {});
219
+ emitter.on('test.event', listener);
220
+
221
+ await emitter.emitAsync('test.event', 'payload');
222
+
223
+ expect(listener).toHaveBeenCalledTimes(1);
224
+ });
225
+
226
+ test('should remove once listeners after emitAsync', async () => {
227
+ const listener = mock(() => {});
228
+ emitter.once('test.event', listener);
229
+
230
+ await emitter.emitAsync('test.event', 'first');
231
+ await emitter.emitAsync('test.event', 'second');
232
+
233
+ expect(listener).toHaveBeenCalledTimes(1);
234
+ });
235
+ });
236
+
237
+ describe('error handling', () => {
238
+ test('should continue execution when listener throws sync error', () => {
239
+ const consoleSpy = mock(() => {});
240
+ const originalError = console.error;
241
+ console.error = consoleSpy;
242
+
243
+ const listener1 = mock(() => {
244
+ throw new Error('Test error');
245
+ });
246
+ const listener2 = mock(() => {});
247
+
248
+ emitter.on('test.event', listener1);
249
+ emitter.on('test.event', listener2);
250
+ emitter.emit('test.event', 'payload');
251
+
252
+ expect(listener1).toHaveBeenCalledTimes(1);
253
+ expect(listener2).toHaveBeenCalledTimes(1);
254
+ expect(consoleSpy).toHaveBeenCalled();
255
+
256
+ console.error = originalError;
257
+ });
258
+
259
+ test('should use custom error handler when provided', () => {
260
+ const errorHandler = mock(() => {});
261
+ const emitterWithErrorHandler = new EventEmitterService({
262
+ onError: errorHandler,
263
+ });
264
+
265
+ emitterWithErrorHandler.on('test.event', () => {
266
+ throw new Error('Custom error');
267
+ });
268
+ emitterWithErrorHandler.emit('test.event', 'payload');
269
+
270
+ expect(errorHandler).toHaveBeenCalledTimes(1);
271
+ expect(errorHandler.mock.calls[0]?.[0]).toBeInstanceOf(Error);
272
+ });
273
+ });
274
+
275
+ describe('maxListeners warning', () => {
276
+ test('should warn when exceeding maxListeners', () => {
277
+ const consoleSpy = mock(() => {});
278
+ const originalWarn = console.warn;
279
+ console.warn = consoleSpy;
280
+
281
+ const emitterWithLimit = new EventEmitterService({ maxListeners: 2 });
282
+
283
+ emitterWithLimit.on('test.event', () => {});
284
+ emitterWithLimit.on('test.event', () => {});
285
+ emitterWithLimit.on('test.event', () => {}); // 超过限制
286
+
287
+ expect(consoleSpy).toHaveBeenCalled();
288
+ expect(consoleSpy.mock.calls[0]?.[0]).toContain('Max listeners');
289
+
290
+ console.warn = originalWarn;
291
+ });
292
+ });
293
+
294
+ describe('globalPrefix', () => {
295
+ test('should add global prefix to event names', () => {
296
+ const emitterWithPrefix = new EventEmitterService({
297
+ globalPrefix: 'app',
298
+ });
299
+ const listener = mock(() => {});
300
+
301
+ emitterWithPrefix.on('user.created', listener);
302
+ emitterWithPrefix.emit('user.created', 'payload');
303
+
304
+ expect(listener).toHaveBeenCalledTimes(1);
305
+ });
306
+ });
307
+ });
308
+
309
+ describe('EventEmitterService wildcard', () => {
310
+ let emitter: EventEmitter;
311
+
312
+ beforeEach(() => {
313
+ emitter = new EventEmitterService({ wildcard: true });
314
+ });
315
+
316
+ describe('single wildcard (*)', () => {
317
+ test('should match single segment', () => {
318
+ const listener = mock(() => {});
319
+ emitter.on('user.*', listener);
320
+
321
+ emitter.emit('user.created', 'payload1');
322
+ emitter.emit('user.updated', 'payload2');
323
+
324
+ expect(listener).toHaveBeenCalledTimes(2);
325
+ });
326
+
327
+ test('should not match multiple segments', () => {
328
+ const listener = mock(() => {});
329
+ emitter.on('user.*', listener);
330
+
331
+ emitter.emit('user.profile.updated', 'payload');
332
+
333
+ expect(listener).not.toHaveBeenCalled();
334
+ });
335
+ });
336
+
337
+ describe('double wildcard (**)', () => {
338
+ test('should match any number of segments', () => {
339
+ const listener = mock(() => {});
340
+ emitter.on('user.**', listener);
341
+
342
+ emitter.emit('user.created', 'payload1');
343
+ emitter.emit('user.profile.updated', 'payload2');
344
+ emitter.emit('user.settings.privacy.changed', 'payload3');
345
+
346
+ expect(listener).toHaveBeenCalledTimes(3);
347
+ });
348
+
349
+ test('should match at end of pattern', () => {
350
+ const listener = mock(() => {});
351
+ emitter.on('app.**', listener);
352
+
353
+ emitter.emit('app.start', 'payload');
354
+
355
+ expect(listener).toHaveBeenCalledTimes(1);
356
+ });
357
+ });
358
+
359
+ describe('mixed patterns', () => {
360
+ test('should handle exact match alongside wildcards', () => {
361
+ const exactListener = mock(() => {});
362
+ const wildcardListener = mock(() => {});
363
+
364
+ emitter.on('user.created', exactListener);
365
+ emitter.on('user.*', wildcardListener);
366
+
367
+ emitter.emit('user.created', 'payload');
368
+
369
+ expect(exactListener).toHaveBeenCalledTimes(1);
370
+ expect(wildcardListener).toHaveBeenCalledTimes(1);
371
+ });
372
+ });
373
+ });
@@ -0,0 +1,114 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { EventListenerScanner, EVENT_LISTENER_SCANNER_TOKEN } from '../../src/events/event-module';
5
+ import { EventEmitterService } from '../../src/events/service';
6
+ import { OnEvent } from '../../src/events/decorators';
7
+ import { EVENT_LISTENER_CLASS_METADATA_KEY } from '../../src/events/types';
8
+ import { Container } from '../../src/di/container';
9
+ import { Injectable } from '../../src/di/decorators';
10
+
11
+ describe('EventListenerScanner', () => {
12
+ let emitter: EventEmitterService;
13
+ let container: Container;
14
+ let scanner: EventListenerScanner;
15
+
16
+ beforeEach(() => {
17
+ emitter = new EventEmitterService();
18
+ container = new Container();
19
+ scanner = new EventListenerScanner(emitter, container);
20
+ });
21
+
22
+ describe('scanAndRegister', () => {
23
+ test('should register listeners from class with OnEvent decorator', () => {
24
+ @Injectable()
25
+ class TestListener {
26
+ public receivedPayload: any = null;
27
+
28
+ @OnEvent('test-event')
29
+ public handleTestEvent(payload: any): void {
30
+ this.receivedPayload = payload;
31
+ }
32
+ }
33
+
34
+ // Mark as event listener class
35
+ Reflect.defineMetadata(EVENT_LISTENER_CLASS_METADATA_KEY, true, TestListener);
36
+
37
+ container.register(TestListener);
38
+ scanner.scanAndRegister([TestListener]);
39
+
40
+ expect(emitter.listenerCount('test-event')).toBe(1);
41
+ });
42
+
43
+ test('should skip non-listener classes', () => {
44
+ class NotAListener {
45
+ public someMethod(): void {}
46
+ }
47
+
48
+ scanner.scanAndRegister([NotAListener]);
49
+
50
+ // No listeners registered
51
+ expect(emitter.listenerCount('some-event')).toBe(0);
52
+ });
53
+
54
+ test('should handle multiple listeners', () => {
55
+ @Injectable()
56
+ class MultiListener {
57
+ @OnEvent('event1')
58
+ public handleEvent1(): void {}
59
+
60
+ @OnEvent('event2')
61
+ public handleEvent2(): void {}
62
+ }
63
+
64
+ Reflect.defineMetadata(EVENT_LISTENER_CLASS_METADATA_KEY, true, MultiListener);
65
+
66
+ container.register(MultiListener);
67
+ scanner.scanAndRegister([MultiListener]);
68
+
69
+ expect(emitter.listenerCount('event1')).toBe(1);
70
+ expect(emitter.listenerCount('event2')).toBe(1);
71
+ });
72
+ });
73
+
74
+ describe('registerListenerClass', () => {
75
+ test('should register single listener class', () => {
76
+ @Injectable()
77
+ class SingleListener {
78
+ @OnEvent('single-event')
79
+ public handle(): void {}
80
+ }
81
+
82
+ Reflect.defineMetadata(EVENT_LISTENER_CLASS_METADATA_KEY, true, SingleListener);
83
+ container.register(SingleListener);
84
+
85
+ scanner.registerListenerClass(SingleListener);
86
+
87
+ expect(emitter.listenerCount('single-event')).toBe(1);
88
+ });
89
+
90
+ test('should skip class without metadata', () => {
91
+ class NoMetadata {
92
+ public handle(): void {}
93
+ }
94
+
95
+ scanner.registerListenerClass(NoMetadata);
96
+
97
+ expect(emitter.eventNames().length).toBe(0);
98
+ });
99
+
100
+ test('should handle class not registered in container', () => {
101
+ @Injectable()
102
+ class UnregisteredListener {
103
+ @OnEvent('unreg-event')
104
+ public handle(): void {}
105
+ }
106
+
107
+ Reflect.defineMetadata(EVENT_LISTENER_CLASS_METADATA_KEY, true, UnregisteredListener);
108
+ // Not registering in container
109
+
110
+ // Should not throw, just warn
111
+ expect(() => scanner.registerListenerClass(UnregisteredListener)).not.toThrow();
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,204 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { EventModule } from '../../src/events/event-module';
5
+ import { EventEmitterService } from '../../src/events/service';
6
+ import { EVENT_EMITTER_TOKEN, EVENT_OPTIONS_TOKEN } from '../../src/events/types';
7
+ import { MODULE_METADATA_KEY } from '../../src/di/module';
8
+
9
+ describe('EventModule', () => {
10
+ beforeEach(() => {
11
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, EventModule);
12
+ });
13
+
14
+ describe('forRoot', () => {
15
+ test('should create module with default options', () => {
16
+ EventModule.forRoot();
17
+
18
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, EventModule);
19
+ expect(metadata.providers).toBeDefined();
20
+ expect(metadata.exports).toContain(EVENT_EMITTER_TOKEN);
21
+ });
22
+
23
+ test('should create module with custom options', () => {
24
+ EventModule.forRoot({
25
+ maxListeners: 50,
26
+ async: true,
27
+ errorHandler: (event, error) => {
28
+ console.error(`Event ${event} error:`, error);
29
+ },
30
+ });
31
+
32
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, EventModule);
33
+ expect(metadata.providers).toBeDefined();
34
+
35
+ // Find options provider
36
+ const optionsProvider = metadata.providers.find(
37
+ (p: any) => p.provide === EVENT_OPTIONS_TOKEN,
38
+ );
39
+ expect(optionsProvider?.useValue?.maxListeners).toBe(50);
40
+ expect(optionsProvider?.useValue?.async).toBe(true);
41
+ });
42
+
43
+ test('should export EventService', () => {
44
+ EventModule.forRoot();
45
+
46
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, EventModule);
47
+ expect(metadata.exports).toContain(EVENT_EMITTER_TOKEN);
48
+ });
49
+ });
50
+ });
51
+
52
+ describe('EventEmitterService', () => {
53
+ let service: EventEmitterService;
54
+
55
+ beforeEach(() => {
56
+ service = new EventEmitterService({ maxListeners: 10 });
57
+ });
58
+
59
+ afterEach(() => {
60
+ service.removeAllListeners();
61
+ });
62
+
63
+ describe('on', () => {
64
+ test('should register listener', () => {
65
+ const handler = () => {};
66
+ service.on('test-event', handler);
67
+
68
+ expect(service.listenerCount('test-event')).toBe(1);
69
+ });
70
+
71
+ test('should return unsubscribe function', () => {
72
+ const handler = () => {};
73
+ const unsubscribe = service.on('test-event', handler);
74
+
75
+ expect(typeof unsubscribe).toBe('function');
76
+ unsubscribe();
77
+ expect(service.listenerCount('test-event')).toBe(0);
78
+ });
79
+
80
+ test('should support multiple listeners', () => {
81
+ service.on('event', () => {});
82
+ service.on('event', () => {});
83
+ service.on('event', () => {});
84
+
85
+ expect(service.listenerCount('event')).toBe(3);
86
+ });
87
+ });
88
+
89
+ describe('once', () => {
90
+ test('should register one-time listener', async () => {
91
+ let callCount = 0;
92
+ service.once('one-time', () => {
93
+ callCount++;
94
+ });
95
+
96
+ await service.emit('one-time');
97
+ await service.emit('one-time');
98
+
99
+ expect(callCount).toBe(1);
100
+ });
101
+ });
102
+
103
+ describe('emit', () => {
104
+ test('should call listeners with payload', () => {
105
+ let receivedData: any;
106
+ service.on('data-event', (data) => {
107
+ receivedData = data;
108
+ });
109
+
110
+ service.emit('data-event', { value: 42 });
111
+
112
+ expect(receivedData).toEqual({ value: 42 });
113
+ });
114
+
115
+ test('should not throw when no listeners', () => {
116
+ // emit returns void, not boolean
117
+ expect(() => service.emit('no-listeners', null)).not.toThrow();
118
+ });
119
+
120
+ test('should call listener when registered', () => {
121
+ let called = false;
122
+ service.on('has-listeners', () => { called = true; });
123
+ service.emit('has-listeners', null);
124
+ expect(called).toBe(true);
125
+ });
126
+
127
+ test('should call multiple listeners in order', () => {
128
+ const order: number[] = [];
129
+ service.on('ordered', () => order.push(1));
130
+ service.on('ordered', () => order.push(2));
131
+ service.on('ordered', () => order.push(3));
132
+
133
+ service.emit('ordered', null);
134
+
135
+ expect(order).toEqual([1, 2, 3]);
136
+ });
137
+ });
138
+
139
+ describe('off', () => {
140
+ test('should remove specific listener', () => {
141
+ const handler = () => {};
142
+ service.on('removable', handler);
143
+ service.off('removable', handler);
144
+
145
+ expect(service.listenerCount('removable')).toBe(0);
146
+ });
147
+
148
+ test('should not affect other listeners', () => {
149
+ const handler1 = () => {};
150
+ const handler2 = () => {};
151
+
152
+ service.on('event', handler1);
153
+ service.on('event', handler2);
154
+ service.off('event', handler1);
155
+
156
+ expect(service.listenerCount('event')).toBe(1);
157
+ });
158
+ });
159
+
160
+ describe('removeAllListeners', () => {
161
+ test('should remove all listeners for event', () => {
162
+ service.on('event', () => {});
163
+ service.on('event', () => {});
164
+ service.removeAllListeners('event');
165
+
166
+ expect(service.listenerCount('event')).toBe(0);
167
+ });
168
+
169
+ test('should remove all listeners when no event specified', () => {
170
+ service.on('event1', () => {});
171
+ service.on('event2', () => {});
172
+ service.removeAllListeners();
173
+
174
+ expect(service.listenerCount('event1')).toBe(0);
175
+ expect(service.listenerCount('event2')).toBe(0);
176
+ });
177
+ });
178
+
179
+ describe('eventNames', () => {
180
+ test('should return all event names', () => {
181
+ service.on('event1', () => {});
182
+ service.on('event2', () => {});
183
+ service.on('event3', () => {});
184
+
185
+ const names = service.eventNames();
186
+ expect(names).toContain('event1');
187
+ expect(names).toContain('event2');
188
+ expect(names).toContain('event3');
189
+ });
190
+ });
191
+
192
+ describe('getListeners', () => {
193
+ test('should return listeners count via listenerCount', () => {
194
+ const handler1 = () => {};
195
+ const handler2 = () => {};
196
+
197
+ service.on('event', handler1);
198
+ service.on('event', handler2);
199
+
200
+ // Use listenerCount method instead
201
+ expect(service.listenerCount('event')).toBe(2);
202
+ });
203
+ });
204
+ });