@navios/di 0.7.1 → 0.9.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.
Files changed (263) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +117 -17
  3. package/lib/browser/container/abstract-container.d.mts +112 -0
  4. package/lib/browser/container/abstract-container.d.mts.map +1 -0
  5. package/lib/browser/container/abstract-container.mjs +100 -0
  6. package/lib/browser/container/abstract-container.mjs.map +1 -0
  7. package/lib/browser/container/container.d.mts +100 -0
  8. package/lib/browser/container/container.d.mts.map +1 -0
  9. package/lib/browser/container/container.mjs +424 -0
  10. package/lib/browser/container/container.mjs.map +1 -0
  11. package/lib/browser/container/scoped-container.d.mts +93 -0
  12. package/lib/browser/container/scoped-container.d.mts.map +1 -0
  13. package/lib/browser/container/scoped-container.mjs +119 -0
  14. package/lib/browser/container/scoped-container.mjs.map +1 -0
  15. package/lib/browser/decorators/factory.decorator.d.mts +26 -0
  16. package/lib/browser/decorators/factory.decorator.d.mts.map +1 -0
  17. package/lib/browser/decorators/factory.decorator.mjs +20 -0
  18. package/lib/browser/decorators/factory.decorator.mjs.map +1 -0
  19. package/lib/browser/decorators/injectable.decorator.d.mts +38 -0
  20. package/lib/browser/decorators/injectable.decorator.d.mts.map +1 -0
  21. package/lib/browser/decorators/injectable.decorator.mjs +21 -0
  22. package/lib/browser/decorators/injectable.decorator.mjs.map +1 -0
  23. package/lib/browser/enums/injectable-scope.enum.d.mts +18 -0
  24. package/lib/browser/enums/injectable-scope.enum.d.mts.map +1 -0
  25. package/lib/browser/enums/injectable-scope.enum.mjs +20 -0
  26. package/lib/browser/enums/injectable-scope.enum.mjs.map +1 -0
  27. package/lib/browser/enums/injectable-type.enum.d.mts +8 -0
  28. package/lib/browser/enums/injectable-type.enum.d.mts.map +1 -0
  29. package/lib/browser/enums/injectable-type.enum.mjs +10 -0
  30. package/lib/browser/enums/injectable-type.enum.mjs.map +1 -0
  31. package/lib/browser/errors/di-error.d.mts +43 -0
  32. package/lib/browser/errors/di-error.d.mts.map +1 -0
  33. package/lib/browser/errors/di-error.mjs +98 -0
  34. package/lib/browser/errors/di-error.mjs.map +1 -0
  35. package/lib/browser/event-emitter.d.mts +16 -0
  36. package/lib/browser/event-emitter.d.mts.map +1 -0
  37. package/lib/browser/event-emitter.mjs +320 -0
  38. package/lib/browser/event-emitter.mjs.map +1 -0
  39. package/lib/browser/index.d.mts +37 -1508
  40. package/lib/browser/index.mjs +29 -2650
  41. package/lib/browser/interfaces/container.interface.d.mts +59 -0
  42. package/lib/browser/interfaces/container.interface.d.mts.map +1 -0
  43. package/lib/browser/interfaces/factory.interface.d.mts +14 -0
  44. package/lib/browser/interfaces/factory.interface.d.mts.map +1 -0
  45. package/lib/browser/interfaces/on-service-destroy.interface.d.mts +7 -0
  46. package/lib/browser/interfaces/on-service-destroy.interface.d.mts.map +1 -0
  47. package/lib/browser/interfaces/on-service-init.interface.d.mts +7 -0
  48. package/lib/browser/interfaces/on-service-init.interface.d.mts.map +1 -0
  49. package/lib/browser/internal/context/async-local-storage.browser.mjs +20 -0
  50. package/lib/browser/internal/context/async-local-storage.browser.mjs.map +1 -0
  51. package/lib/browser/internal/context/async-local-storage.d.mts +9 -0
  52. package/lib/browser/internal/context/async-local-storage.d.mts.map +1 -0
  53. package/lib/browser/internal/context/async-local-storage.types.d.mts +11 -0
  54. package/lib/browser/internal/context/async-local-storage.types.d.mts.map +1 -0
  55. package/lib/browser/internal/context/factory-context.d.mts +23 -0
  56. package/lib/browser/internal/context/factory-context.d.mts.map +1 -0
  57. package/lib/browser/internal/context/resolution-context.d.mts +43 -0
  58. package/lib/browser/internal/context/resolution-context.d.mts.map +1 -0
  59. package/lib/browser/internal/context/resolution-context.mjs +56 -0
  60. package/lib/browser/internal/context/resolution-context.mjs.map +1 -0
  61. package/lib/browser/internal/context/service-initialization-context.d.mts +48 -0
  62. package/lib/browser/internal/context/service-initialization-context.d.mts.map +1 -0
  63. package/lib/browser/internal/context/sync-local-storage.mjs +53 -0
  64. package/lib/browser/internal/context/sync-local-storage.mjs.map +1 -0
  65. package/lib/browser/internal/core/instance-resolver.d.mts +119 -0
  66. package/lib/browser/internal/core/instance-resolver.d.mts.map +1 -0
  67. package/lib/browser/internal/core/instance-resolver.mjs +306 -0
  68. package/lib/browser/internal/core/instance-resolver.mjs.map +1 -0
  69. package/lib/browser/internal/core/name-resolver.d.mts +52 -0
  70. package/lib/browser/internal/core/name-resolver.d.mts.map +1 -0
  71. package/lib/browser/internal/core/name-resolver.mjs +118 -0
  72. package/lib/browser/internal/core/name-resolver.mjs.map +1 -0
  73. package/lib/browser/internal/core/scope-tracker.d.mts +65 -0
  74. package/lib/browser/internal/core/scope-tracker.d.mts.map +1 -0
  75. package/lib/browser/internal/core/scope-tracker.mjs +120 -0
  76. package/lib/browser/internal/core/scope-tracker.mjs.map +1 -0
  77. package/lib/browser/internal/core/service-initializer.d.mts +44 -0
  78. package/lib/browser/internal/core/service-initializer.d.mts.map +1 -0
  79. package/lib/browser/internal/core/service-initializer.mjs +109 -0
  80. package/lib/browser/internal/core/service-initializer.mjs.map +1 -0
  81. package/lib/browser/internal/core/service-invalidator.d.mts +81 -0
  82. package/lib/browser/internal/core/service-invalidator.d.mts.map +1 -0
  83. package/lib/browser/internal/core/service-invalidator.mjs +142 -0
  84. package/lib/browser/internal/core/service-invalidator.mjs.map +1 -0
  85. package/lib/browser/internal/core/token-resolver.d.mts +54 -0
  86. package/lib/browser/internal/core/token-resolver.d.mts.map +1 -0
  87. package/lib/browser/internal/core/token-resolver.mjs +77 -0
  88. package/lib/browser/internal/core/token-resolver.mjs.map +1 -0
  89. package/lib/browser/internal/holder/holder-storage.interface.d.mts +99 -0
  90. package/lib/browser/internal/holder/holder-storage.interface.d.mts.map +1 -0
  91. package/lib/browser/internal/holder/instance-holder.d.mts +101 -0
  92. package/lib/browser/internal/holder/instance-holder.d.mts.map +1 -0
  93. package/lib/browser/internal/holder/instance-holder.mjs +19 -0
  94. package/lib/browser/internal/holder/instance-holder.mjs.map +1 -0
  95. package/lib/browser/internal/holder/unified-storage.d.mts +53 -0
  96. package/lib/browser/internal/holder/unified-storage.d.mts.map +1 -0
  97. package/lib/browser/internal/holder/unified-storage.mjs +144 -0
  98. package/lib/browser/internal/holder/unified-storage.mjs.map +1 -0
  99. package/lib/browser/internal/lifecycle/circular-detector.d.mts +39 -0
  100. package/lib/browser/internal/lifecycle/circular-detector.d.mts.map +1 -0
  101. package/lib/browser/internal/lifecycle/circular-detector.mjs +55 -0
  102. package/lib/browser/internal/lifecycle/circular-detector.mjs.map +1 -0
  103. package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts +18 -0
  104. package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts.map +1 -0
  105. package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs +43 -0
  106. package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs.map +1 -0
  107. package/lib/browser/internal/stub-factory-class.d.mts +14 -0
  108. package/lib/browser/internal/stub-factory-class.d.mts.map +1 -0
  109. package/lib/browser/internal/stub-factory-class.mjs +18 -0
  110. package/lib/browser/internal/stub-factory-class.mjs.map +1 -0
  111. package/lib/browser/symbols/injectable-token.d.mts +5 -0
  112. package/lib/browser/symbols/injectable-token.d.mts.map +1 -0
  113. package/lib/browser/symbols/injectable-token.mjs +6 -0
  114. package/lib/browser/symbols/injectable-token.mjs.map +1 -0
  115. package/lib/browser/token/injection-token.d.mts +55 -0
  116. package/lib/browser/token/injection-token.d.mts.map +1 -0
  117. package/lib/browser/token/injection-token.mjs +100 -0
  118. package/lib/browser/token/injection-token.mjs.map +1 -0
  119. package/lib/browser/token/registry.d.mts +37 -0
  120. package/lib/browser/token/registry.d.mts.map +1 -0
  121. package/lib/browser/token/registry.mjs +86 -0
  122. package/lib/browser/token/registry.mjs.map +1 -0
  123. package/lib/browser/utils/default-injectors.d.mts +12 -0
  124. package/lib/browser/utils/default-injectors.d.mts.map +1 -0
  125. package/lib/browser/utils/default-injectors.mjs +13 -0
  126. package/lib/browser/utils/default-injectors.mjs.map +1 -0
  127. package/lib/browser/utils/get-injectable-token.d.mts +9 -0
  128. package/lib/browser/utils/get-injectable-token.d.mts.map +1 -0
  129. package/lib/browser/utils/get-injectable-token.mjs +13 -0
  130. package/lib/browser/utils/get-injectable-token.mjs.map +1 -0
  131. package/lib/browser/utils/get-injectors.d.mts +55 -0
  132. package/lib/browser/utils/get-injectors.d.mts.map +1 -0
  133. package/lib/browser/utils/get-injectors.mjs +121 -0
  134. package/lib/browser/utils/get-injectors.mjs.map +1 -0
  135. package/lib/browser/utils/types.d.mts +23 -0
  136. package/lib/browser/utils/types.d.mts.map +1 -0
  137. package/lib/{container-Pb_Y4Z4x.mjs → container-8-z89TyQ.mjs} +1269 -1305
  138. package/lib/container-8-z89TyQ.mjs.map +1 -0
  139. package/lib/{container-BuAutHGg.d.mts → container-CNiqesCL.d.mts} +600 -569
  140. package/lib/container-CNiqesCL.d.mts.map +1 -0
  141. package/lib/{container-DnzgpfBe.cjs → container-CaY2fDuk.cjs} +1287 -1329
  142. package/lib/container-CaY2fDuk.cjs.map +1 -0
  143. package/lib/{container-oGTgX2iX.d.cts → container-D-0Ho3qL.d.cts} +601 -565
  144. package/lib/container-D-0Ho3qL.d.cts.map +1 -0
  145. package/lib/index.cjs +13 -15
  146. package/lib/index.cjs.map +1 -1
  147. package/lib/index.d.cts +58 -223
  148. package/lib/index.d.cts.map +1 -1
  149. package/lib/index.d.mts +62 -222
  150. package/lib/index.d.mts.map +1 -1
  151. package/lib/index.mjs +5 -6
  152. package/lib/index.mjs.map +1 -1
  153. package/lib/testing/index.cjs +569 -311
  154. package/lib/testing/index.cjs.map +1 -1
  155. package/lib/testing/index.d.cts +370 -41
  156. package/lib/testing/index.d.cts.map +1 -1
  157. package/lib/testing/index.d.mts +370 -41
  158. package/lib/testing/index.d.mts.map +1 -1
  159. package/lib/testing/index.mjs +568 -305
  160. package/lib/testing/index.mjs.map +1 -1
  161. package/package.json +2 -1
  162. package/src/__tests__/circular-detector.spec.mts +193 -0
  163. package/src/__tests__/concurrent.spec.mts +368 -0
  164. package/src/__tests__/container.spec.mts +32 -30
  165. package/src/__tests__/di-error.spec.mts +351 -0
  166. package/src/__tests__/e2e.browser.spec.mts +0 -4
  167. package/src/__tests__/e2e.spec.mts +10 -19
  168. package/src/__tests__/event-emitter.spec.mts +232 -109
  169. package/src/__tests__/get-injectors.spec.mts +250 -39
  170. package/src/__tests__/injection-token.spec.mts +293 -349
  171. package/src/__tests__/library-findings.spec.mts +8 -8
  172. package/src/__tests__/registry.spec.mts +358 -210
  173. package/src/__tests__/resolution-context.spec.mts +255 -0
  174. package/src/__tests__/scope-tracker.spec.mts +598 -0
  175. package/src/__tests__/scope-upgrade.spec.mts +808 -0
  176. package/src/__tests__/scoped-container.spec.mts +595 -0
  177. package/src/__tests__/test-container.spec.mts +293 -0
  178. package/src/__tests__/token-resolver.spec.mts +207 -0
  179. package/src/__tests__/unified-storage.spec.mts +535 -0
  180. package/src/__tests__/unit-test-container.spec.mts +405 -0
  181. package/src/__type-tests__/container.spec-d.mts +180 -0
  182. package/src/__type-tests__/factory.spec-d.mts +15 -3
  183. package/src/__type-tests__/inject.spec-d.mts +115 -20
  184. package/src/__type-tests__/injectable.spec-d.mts +69 -52
  185. package/src/__type-tests__/injection-token.spec-d.mts +176 -0
  186. package/src/__type-tests__/scoped-container.spec-d.mts +212 -0
  187. package/src/container/abstract-container.mts +327 -0
  188. package/src/container/container.mts +142 -170
  189. package/src/container/scoped-container.mts +126 -208
  190. package/src/decorators/factory.decorator.mts +16 -11
  191. package/src/decorators/injectable.decorator.mts +20 -16
  192. package/src/enums/index.mts +2 -2
  193. package/src/enums/injectable-scope.enum.mts +1 -0
  194. package/src/enums/injectable-type.enum.mts +1 -0
  195. package/src/errors/di-error.mts +96 -0
  196. package/src/event-emitter.mts +3 -27
  197. package/src/index.mts +6 -153
  198. package/src/interfaces/container.interface.mts +13 -0
  199. package/src/interfaces/factory.interface.mts +1 -1
  200. package/src/interfaces/index.mts +1 -1
  201. package/src/internal/context/async-local-storage.mts +3 -2
  202. package/src/internal/context/async-local-storage.types.mts +1 -0
  203. package/src/internal/context/factory-context.mts +1 -0
  204. package/src/internal/context/index.mts +3 -1
  205. package/src/internal/context/resolution-context.mts +1 -0
  206. package/src/internal/context/service-initialization-context.mts +43 -0
  207. package/src/internal/core/index.mts +5 -4
  208. package/src/internal/core/instance-resolver.mts +461 -292
  209. package/src/internal/core/name-resolver.mts +196 -0
  210. package/src/internal/core/scope-tracker.mts +242 -0
  211. package/src/internal/core/{instantiator.mts → service-initializer.mts} +51 -29
  212. package/src/internal/core/service-invalidator.mts +290 -0
  213. package/src/internal/core/{token-processor.mts → token-resolver.mts} +17 -88
  214. package/src/internal/holder/holder-storage.interface.mts +11 -5
  215. package/src/internal/holder/index.mts +2 -5
  216. package/src/internal/holder/instance-holder.mts +1 -3
  217. package/src/internal/holder/unified-storage.mts +245 -0
  218. package/src/internal/index.mts +2 -1
  219. package/src/internal/lifecycle/circular-detector.mts +1 -0
  220. package/src/internal/lifecycle/index.mts +1 -1
  221. package/src/internal/lifecycle/lifecycle-event-bus.mts +1 -0
  222. package/src/internal/stub-factory-class.mts +16 -0
  223. package/src/symbols/injectable-token.mts +3 -1
  224. package/src/testing/index.mts +2 -0
  225. package/src/testing/test-container.mts +546 -85
  226. package/src/testing/types.mts +117 -0
  227. package/src/testing/unit-test-container.mts +509 -0
  228. package/src/token/injection-token.mts +41 -4
  229. package/src/token/registry.mts +75 -9
  230. package/src/utils/default-injectors.mts +16 -0
  231. package/src/utils/get-injectable-token.mts +2 -3
  232. package/src/utils/get-injectors.mts +26 -15
  233. package/src/utils/index.mts +3 -1
  234. package/src/utils/types.mts +1 -0
  235. package/tsdown.config.mts +11 -1
  236. package/lib/browser/index.d.mts.map +0 -1
  237. package/lib/browser/index.mjs.map +0 -1
  238. package/lib/container-BuAutHGg.d.mts.map +0 -1
  239. package/lib/container-DnzgpfBe.cjs.map +0 -1
  240. package/lib/container-Pb_Y4Z4x.mjs.map +0 -1
  241. package/lib/container-oGTgX2iX.d.cts.map +0 -1
  242. package/src/__tests__/async-local-storage.browser.spec.mts +0 -166
  243. package/src/__tests__/async-local-storage.spec.mts +0 -333
  244. package/src/__tests__/errors.spec.mts +0 -87
  245. package/src/__tests__/factory.spec.mts +0 -137
  246. package/src/__tests__/injectable.spec.mts +0 -246
  247. package/src/__tests__/request-scope.spec.mts +0 -416
  248. package/src/__tests__/service-instantiator.spec.mts +0 -410
  249. package/src/__tests__/service-locator-event-bus.spec.mts +0 -242
  250. package/src/__tests__/service-locator-manager.spec.mts +0 -300
  251. package/src/__tests__/service-locator.spec.mts +0 -966
  252. package/src/__tests__/unified-api.spec.mts +0 -130
  253. package/src/browser.mts +0 -11
  254. package/src/injectors.mts +0 -18
  255. package/src/internal/context/request-context.mts +0 -214
  256. package/src/internal/core/invalidator.mts +0 -437
  257. package/src/internal/core/service-locator.mts +0 -202
  258. package/src/internal/holder/base-holder-manager.mts +0 -238
  259. package/src/internal/holder/holder-manager.mts +0 -85
  260. package/src/internal/holder/request-storage.mts +0 -134
  261. package/src/internal/holder/singleton-storage.mts +0 -105
  262. package/src/testing/README.md +0 -80
  263. package/src/testing/__tests__/test-container.spec.mts +0 -173
@@ -1,163 +1,286 @@
1
- import { describe, expect, it, vi } from 'vitest'
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
2
 
3
+ import { Container } from '../container/container.mjs'
3
4
  import { EventEmitter } from '../event-emitter.mjs'
4
5
 
5
6
  describe('EventEmitter', () => {
6
- it('should create an instance', () => {
7
- const emitter = new EventEmitter()
8
- expect(emitter).toBeDefined()
7
+ let container: Container
8
+
9
+ beforeEach(() => {
10
+ container = new Container()
9
11
  })
10
12
 
11
- it('should add and trigger event listeners', async () => {
12
- const emitter = new EventEmitter<{ test: [string] }>()
13
- const mockListener = vi.fn()
13
+ afterEach(async () => {
14
+ await container.dispose()
15
+ })
14
16
 
15
- emitter.on('test', mockListener)
16
- await emitter.emit('test', 'hello')
17
+ describe('basic operations', () => {
18
+ it('should be injectable as transient', async () => {
19
+ const emitter1 = await container.get(EventEmitter)
20
+ const emitter2 = await container.get(EventEmitter)
17
21
 
18
- expect(mockListener).toHaveBeenCalledWith('hello')
19
- expect(mockListener).toHaveBeenCalledTimes(1)
20
- emitter.off('test', mockListener)
22
+ expect(emitter1).toBeInstanceOf(EventEmitter)
23
+ expect(emitter2).toBeInstanceOf(EventEmitter)
24
+ expect(emitter1).not.toBe(emitter2)
25
+ })
21
26
  })
22
27
 
23
- it('should support multiple listeners for the same event', async () => {
24
- const emitter = new EventEmitter<{ test: [string] }>()
25
- const mockListener1 = vi.fn()
26
- const mockListener2 = vi.fn()
28
+ describe('on', () => {
29
+ it('should register event listener', async () => {
30
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
31
+ const listener = vi.fn()
27
32
 
28
- emitter.on('test', mockListener1)
29
- emitter.on('test', mockListener2)
30
- await emitter.emit('test', 'hello')
33
+ emitter.on('test', listener)
34
+ await emitter.emit('test')
31
35
 
32
- expect(mockListener1).toHaveBeenCalledWith('hello')
33
- expect(mockListener2).toHaveBeenCalledWith('hello')
34
- })
36
+ expect(listener).toHaveBeenCalledTimes(1)
37
+ })
35
38
 
36
- it('should return unsubscribe function from on()', async () => {
37
- const emitter = new EventEmitter<{ test: [string] }>()
38
- const mockListener = vi.fn()
39
+ it('should return unsubscribe function', async () => {
40
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
41
+ const listener = vi.fn()
39
42
 
40
- const unsubscribe = emitter.on('test', mockListener)
41
- await emitter.emit('test', 'hello1')
43
+ const unsubscribe = emitter.on('test', listener)
44
+ unsubscribe()
42
45
 
43
- unsubscribe()
44
- await emitter.emit('test', 'hello2')
46
+ await emitter.emit('test')
47
+ expect(listener).not.toHaveBeenCalled()
48
+ })
45
49
 
46
- expect(mockListener).toHaveBeenCalledTimes(1)
47
- expect(mockListener).toHaveBeenCalledWith('hello1')
48
- })
50
+ it('should support multiple listeners for same event', async () => {
51
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
52
+ const listener1 = vi.fn()
53
+ const listener2 = vi.fn()
49
54
 
50
- it('should support off() method to remove listeners', async () => {
51
- const emitter = new EventEmitter<{ test: [string] }>()
52
- const mockListener = vi.fn()
55
+ emitter.on('test', listener1)
56
+ emitter.on('test', listener2)
57
+ await emitter.emit('test')
53
58
 
54
- emitter.on('test', mockListener)
55
- await emitter.emit('test', 'hello1')
59
+ expect(listener1).toHaveBeenCalledTimes(1)
60
+ expect(listener2).toHaveBeenCalledTimes(1)
61
+ })
56
62
 
57
- emitter.off('test', mockListener)
58
- await emitter.emit('test', 'hello2')
63
+ it('should support different event types', async () => {
64
+ const emitter = await container.get(
65
+ EventEmitter<{ eventA: []; eventB: [] }>,
66
+ )
67
+ const listenerA = vi.fn()
68
+ const listenerB = vi.fn()
59
69
 
60
- expect(mockListener).toHaveBeenCalledTimes(1)
61
- expect(mockListener).toHaveBeenCalledWith('hello1')
62
- })
70
+ emitter.on('eventA', listenerA)
71
+ emitter.on('eventB', listenerB)
63
72
 
64
- it('should handle off() for non-existent event', () => {
65
- const emitter = new EventEmitter<{ test: [string] }>()
66
- const mockListener = vi.fn()
73
+ await emitter.emit('eventA')
67
74
 
68
- expect(() => {
69
- emitter.off('test', mockListener)
70
- }).not.toThrow()
75
+ expect(listenerA).toHaveBeenCalledTimes(1)
76
+ expect(listenerB).not.toHaveBeenCalled()
77
+ })
71
78
  })
72
79
 
73
- it('should clean up empty listener sets after removing all listeners', async () => {
74
- const emitter = new EventEmitter<{ test: [string] }>()
75
- const mockListener1 = vi.fn()
76
- const mockListener2 = vi.fn()
80
+ describe('off', () => {
81
+ it('should remove specific listener', async () => {
82
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
83
+ const listener1 = vi.fn()
84
+ const listener2 = vi.fn()
77
85
 
78
- emitter.on('test', mockListener1)
79
- emitter.on('test', mockListener2)
86
+ emitter.on('test', listener1)
87
+ emitter.on('test', listener2)
88
+ emitter.off('test', listener1)
80
89
 
81
- emitter.off('test', mockListener1)
82
- emitter.off('test', mockListener2)
90
+ await emitter.emit('test')
83
91
 
84
- // After removing all listeners, emitting should not call anything
85
- await emitter.emit('test', 'hello')
92
+ expect(listener1).not.toHaveBeenCalled()
93
+ expect(listener2).toHaveBeenCalledTimes(1)
94
+ })
86
95
 
87
- expect(mockListener1).not.toHaveBeenCalled()
88
- expect(mockListener2).not.toHaveBeenCalled()
89
- })
96
+ it('should handle removing non-existent listener gracefully', async () => {
97
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
98
+ const listener = vi.fn()
90
99
 
91
- it('should support once() method for one-time listeners', async () => {
92
- const emitter = new EventEmitter<{ test: [string] }>()
93
- const mockListener = vi.fn()
100
+ // Should not throw
101
+ emitter.off('test', listener)
102
+ })
94
103
 
95
- emitter.once('test', mockListener)
96
- await emitter.emit('test', 'hello1')
97
- await emitter.emit('test', 'hello2')
104
+ it('should handle removing from non-existent event gracefully', async () => {
105
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
98
106
 
99
- expect(mockListener).toHaveBeenCalledTimes(1)
100
- expect(mockListener).toHaveBeenCalledWith('hello1')
101
- })
107
+ // Should not throw
108
+ emitter.off('test', () => {})
109
+ })
102
110
 
103
- it('should return unsubscribe function from once()', async () => {
104
- const emitter = new EventEmitter<{ test: [string] }>()
105
- const mockListener = vi.fn()
111
+ it('should clean up empty event sets', async () => {
112
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
113
+ const listener = vi.fn()
106
114
 
107
- const unsubscribe = emitter.once('test', mockListener)
108
- unsubscribe() // Remove before emitting
109
- await emitter.emit('test', 'hello')
115
+ emitter.on('test', listener)
116
+ emitter.off('test', listener)
110
117
 
111
- expect(mockListener).not.toHaveBeenCalled()
118
+ // After removing the last listener, the event should be cleaned up
119
+ // We can verify this by checking that emit doesn't call anything
120
+ await emitter.emit('test')
121
+ expect(listener).not.toHaveBeenCalled()
122
+ })
112
123
  })
113
124
 
114
- it('should handle emit() for non-existent event', async () => {
115
- const emitter = new EventEmitter<{ test: [string] }>()
125
+ describe('once', () => {
126
+ it('should only trigger listener once', async () => {
127
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
128
+ const listener = vi.fn()
116
129
 
117
- const result = await emitter.emit('test', 'hello')
118
- expect(result).toBeUndefined()
119
- })
130
+ emitter.once('test', listener)
120
131
 
121
- it('should handle multiple arguments in events', async () => {
122
- const emitter = new EventEmitter<{ test: [string, number, boolean] }>()
123
- const mockListener = vi.fn()
132
+ await emitter.emit('test')
133
+ await emitter.emit('test')
134
+ await emitter.emit('test')
124
135
 
125
- emitter.on('test', mockListener)
126
- await emitter.emit('test', 'hello', 42, true)
136
+ expect(listener).toHaveBeenCalledTimes(1)
137
+ })
127
138
 
128
- expect(mockListener).toHaveBeenCalledWith('hello', 42, true)
139
+ it('should return unsubscribe function', async () => {
140
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
141
+ const listener = vi.fn()
142
+
143
+ const unsubscribe = emitter.once('test', listener)
144
+ unsubscribe()
145
+
146
+ await emitter.emit('test')
147
+ expect(listener).not.toHaveBeenCalled()
148
+ })
129
149
  })
130
150
 
131
- it('should handle async listeners', async () => {
132
- const emitter = new EventEmitter<{ test: [string] }>()
133
- const mockAsyncListener = vi.fn().mockResolvedValue('async result')
151
+ describe('emit', () => {
152
+ it('should pass arguments to listeners', async () => {
153
+ const emitter = await container.get(
154
+ EventEmitter<{ test: [string, number] }>,
155
+ )
156
+ const listener = vi.fn()
157
+
158
+ emitter.on('test', listener)
159
+ await emitter.emit('test', 'hello', 42)
160
+
161
+ expect(listener).toHaveBeenCalledWith('hello', 42)
162
+ })
163
+
164
+ it('should return undefined for non-existent event', async () => {
165
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
166
+
167
+ const result = await emitter.emit('test')
168
+ expect(result).toBeUndefined()
169
+ })
170
+
171
+ it('should wait for async listeners', async () => {
172
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
173
+ let completed = false
134
174
 
135
- emitter.on('test', mockAsyncListener)
136
- const results = await emitter.emit('test', 'hello')
175
+ emitter.on('test', async () => {
176
+ await new Promise((resolve) => setTimeout(resolve, 10))
177
+ completed = true
178
+ })
137
179
 
138
- expect(mockAsyncListener).toHaveBeenCalledWith('hello')
139
- expect(results).toEqual(['async result'])
180
+ await emitter.emit('test')
181
+ expect(completed).toBe(true)
182
+ })
183
+
184
+ it('should execute all listeners concurrently', async () => {
185
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
186
+ const order: number[] = []
187
+
188
+ emitter.on('test', async () => {
189
+ await new Promise((resolve) => setTimeout(resolve, 30))
190
+ order.push(1)
191
+ })
192
+
193
+ emitter.on('test', async () => {
194
+ await new Promise((resolve) => setTimeout(resolve, 10))
195
+ order.push(2)
196
+ })
197
+
198
+ emitter.on('test', async () => {
199
+ order.push(3)
200
+ })
201
+
202
+ await emitter.emit('test')
203
+
204
+ // All should complete, but order depends on timing
205
+ expect(order).toHaveLength(3)
206
+ // The sync one should complete first
207
+ expect(order[0]).toBe(3)
208
+ })
209
+
210
+ it('should return array of results from Promise.all', async () => {
211
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
212
+
213
+ // @ts-expect-error - not a documented feature
214
+ emitter.on('test', async () => 'result1')
215
+ // @ts-expect-error - not a documented feature
216
+ emitter.on('test', async () => 'result2')
217
+
218
+ const results = await emitter.emit('test')
219
+ expect(results).toEqual(['result1', 'result2'])
220
+ })
221
+ })
222
+
223
+ describe('type safety', () => {
224
+ it('should enforce correct event argument types', async () => {
225
+ type Events = {
226
+ userCreated: [string, number]
227
+ orderPlaced: [{ id: string; total: number }]
228
+ empty: []
229
+ }
230
+
231
+ const emitter = await container.get(EventEmitter<Events>)
232
+ const userListener = vi.fn()
233
+ const orderListener = vi.fn()
234
+ const emptyListener = vi.fn()
235
+
236
+ emitter.on('userCreated', userListener)
237
+ emitter.on('orderPlaced', orderListener)
238
+ emitter.on('empty', emptyListener)
239
+
240
+ await emitter.emit('userCreated', 'user123', 25)
241
+ await emitter.emit('orderPlaced', { id: 'order1', total: 100 })
242
+ await emitter.emit('empty')
243
+
244
+ expect(userListener).toHaveBeenCalledWith('user123', 25)
245
+ expect(orderListener).toHaveBeenCalledWith({ id: 'order1', total: 100 })
246
+ expect(emptyListener).toHaveBeenCalledWith()
247
+ })
140
248
  })
141
249
 
142
- it('should handle multiple async listeners and return all results', async () => {
143
- const emitter = new EventEmitter<{ test: [string] }>()
144
- const mockAsyncListener1 = vi.fn().mockResolvedValue('result1')
145
- const mockAsyncListener2 = vi.fn().mockResolvedValue('result2')
250
+ describe('error handling', () => {
251
+ it('should propagate errors from sync listeners', async () => {
252
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
253
+
254
+ emitter.on('test', () => {
255
+ throw new Error('Sync error')
256
+ })
257
+
258
+ await expect(emitter.emit('test')).rejects.toThrow('Sync error')
259
+ })
260
+
261
+ it('should propagate errors from async listeners', async () => {
262
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
146
263
 
147
- emitter.on('test', mockAsyncListener1)
148
- emitter.on('test', mockAsyncListener2)
149
- const results = await emitter.emit('test', 'hello')
264
+ emitter.on('test', async () => {
265
+ throw new Error('Async error')
266
+ })
150
267
 
151
- expect(results).toEqual(['result1', 'result2'])
268
+ await expect(emitter.emit('test')).rejects.toThrow('Async error')
269
+ })
152
270
  })
153
271
 
154
- it('should work with no-argument events', async () => {
155
- const emitter = new EventEmitter<{ test: [] }>()
156
- const mockListener = vi.fn()
272
+ describe('memory management', () => {
273
+ it('should allow garbage collection after unsubscribe', async () => {
274
+ const emitter = await container.get(EventEmitter<{ test: [] }>)
157
275
 
158
- emitter.on('test', mockListener)
159
- await emitter.emit('test')
276
+ // Add and remove many listeners
277
+ for (let i = 0; i < 100; i++) {
278
+ const unsubscribe = emitter.on('test', () => {})
279
+ unsubscribe()
280
+ }
160
281
 
161
- expect(mockListener).toHaveBeenCalledWith()
282
+ // The event should be cleaned up (empty listeners set removed)
283
+ await emitter.emit('test')
284
+ })
162
285
  })
163
286
  })