@navios/di 0.8.0 → 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 (264) hide show
  1. package/CHANGELOG.md +87 -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 -1558
  40. package/lib/browser/index.mjs +29 -2749
  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-DAKOvAgr.mjs → container-8-z89TyQ.mjs} +1325 -1462
  138. package/lib/container-8-z89TyQ.mjs.map +1 -0
  139. package/lib/{container-Bp1W-pWJ.d.mts → container-CNiqesCL.d.mts} +598 -617
  140. package/lib/container-CNiqesCL.d.mts.map +1 -0
  141. package/lib/{container-DENMeJ87.cjs → container-CaY2fDuk.cjs} +1369 -1512
  142. package/lib/container-CaY2fDuk.cjs.map +1 -0
  143. package/lib/{container-YPwvmlK2.d.cts → container-D-0Ho3qL.d.cts} +598 -612
  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 +460 -302
  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-resolver.mts +122 -0
  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-Bp1W-pWJ.d.mts.map +0 -1
  239. package/lib/container-DAKOvAgr.mjs.map +0 -1
  240. package/lib/container-DENMeJ87.cjs.map +0 -1
  241. package/lib/container-YPwvmlK2.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 -225
  256. package/src/internal/core/invalidator.mts +0 -437
  257. package/src/internal/core/service-locator.mts +0 -202
  258. package/src/internal/core/token-processor.mts +0 -252
  259. package/src/internal/holder/base-holder-manager.mts +0 -334
  260. package/src/internal/holder/holder-manager.mts +0 -85
  261. package/src/internal/holder/request-storage.mts +0 -127
  262. package/src/internal/holder/singleton-storage.mts +0 -92
  263. package/src/testing/README.md +0 -80
  264. package/src/testing/__tests__/test-container.spec.mts +0 -173
@@ -0,0 +1,368 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from 'vitest'
2
+
3
+ import { Container } from '../container/container.mjs'
4
+ import { Injectable } from '../decorators/index.mjs'
5
+ import { InjectableScope } from '../enums/index.mjs'
6
+ import { inject, asyncInject } from '../utils/index.mjs'
7
+
8
+ describe('Concurrent Operations', () => {
9
+ let container: Container
10
+
11
+ beforeEach(() => {
12
+ container = new Container()
13
+ })
14
+
15
+ afterEach(async () => {
16
+ await container.dispose()
17
+ })
18
+
19
+ describe('concurrent singleton resolution', () => {
20
+ it('should return same instance for concurrent requests', async () => {
21
+ let initCount = 0
22
+
23
+ @Injectable()
24
+ class SlowService {
25
+ id: number
26
+
27
+ constructor() {
28
+ initCount++
29
+ this.id = Math.random()
30
+ }
31
+
32
+ async onServiceInit() {
33
+ await new Promise((resolve) => setTimeout(resolve, 50))
34
+ }
35
+ }
36
+
37
+ // Start multiple concurrent resolutions
38
+ const promises = Array.from({ length: 10 }, () =>
39
+ container.get(SlowService),
40
+ )
41
+
42
+ const instances = await Promise.all(promises)
43
+
44
+ // All instances should be the same
45
+ const firstInstance = instances[0]
46
+ expect(instances.every((i) => i === firstInstance)).toBe(true)
47
+
48
+ // Should only have initialized once
49
+ expect(initCount).toBe(1)
50
+ })
51
+
52
+ it('should handle race conditions without creating duplicate instances', async () => {
53
+ let creationCount = 0
54
+
55
+ @Injectable()
56
+ class RaceService {
57
+ createdAt = creationCount++
58
+ }
59
+
60
+ // Fire many concurrent requests
61
+ const promises = Array.from({ length: 100 }, () =>
62
+ container.get(RaceService),
63
+ )
64
+
65
+ const instances = await Promise.all(promises)
66
+
67
+ // All should be the same instance
68
+ const first = instances[0]
69
+ expect(instances.every((i) => i === first)).toBe(true)
70
+ expect(creationCount).toBe(1)
71
+ })
72
+ })
73
+
74
+ describe('concurrent transient resolution', () => {
75
+ it('should create new instance for each concurrent request', async () => {
76
+ let instanceCount = 0
77
+
78
+ @Injectable({ scope: InjectableScope.Transient })
79
+ class TransientService {
80
+ id = ++instanceCount
81
+ }
82
+
83
+ const promises = Array.from({ length: 10 }, () =>
84
+ container.get(TransientService),
85
+ )
86
+
87
+ const instances = await Promise.all(promises)
88
+
89
+ // All instances should be different
90
+ const ids = instances.map((i) => i.id)
91
+ const uniqueIds = new Set(ids)
92
+ expect(uniqueIds.size).toBe(10)
93
+ })
94
+ })
95
+
96
+ describe('concurrent request-scoped resolution', () => {
97
+ it('should isolate instances per ScopedContainer', async () => {
98
+ @Injectable({ scope: InjectableScope.Request })
99
+ class RequestService {
100
+ id = Math.random()
101
+ }
102
+
103
+ // Create multiple scoped containers concurrently
104
+ const scopedContainers = Array.from({ length: 5 }, (_, i) =>
105
+ container.beginRequest(`request-${i}`),
106
+ )
107
+
108
+ const promises = scopedContainers.map((sc) => sc.get(RequestService))
109
+ const instances = await Promise.all(promises)
110
+
111
+ // Each scoped container should have its own instance
112
+ const uniqueInstances = new Set(instances)
113
+ expect(uniqueInstances.size).toBe(5)
114
+
115
+ // Clean up
116
+ await Promise.all(scopedContainers.map((sc) => sc.endRequest()))
117
+ })
118
+
119
+ it('should return same instance within same ScopedContainer', async () => {
120
+ @Injectable({ scope: InjectableScope.Request })
121
+ class RequestService {
122
+ id = Math.random()
123
+ }
124
+
125
+ const scopedContainer = container.beginRequest('test-request')
126
+
127
+ // Multiple concurrent requests within same scoped container
128
+ const promises = Array.from({ length: 10 }, () =>
129
+ scopedContainer.get(RequestService),
130
+ )
131
+
132
+ const instances = await Promise.all(promises)
133
+
134
+ // All should be the same instance
135
+ const first = instances[0]
136
+ expect(instances.every((i) => i === first)).toBe(true)
137
+
138
+ await scopedContainer.endRequest()
139
+ })
140
+ })
141
+
142
+ describe('concurrent resolution with dependencies', () => {
143
+ it('should correctly resolve dependency graph concurrently', async () => {
144
+ @Injectable()
145
+ class ServiceA {
146
+ id = Math.random()
147
+ }
148
+
149
+ @Injectable()
150
+ class ServiceB {
151
+ private a = inject(ServiceA)
152
+ id = Math.random()
153
+
154
+ getAId() {
155
+ return this.a.id
156
+ }
157
+ }
158
+
159
+ @Injectable()
160
+ class ServiceC {
161
+ private a = inject(ServiceA)
162
+ private b = inject(ServiceB)
163
+
164
+ getIds() {
165
+ return { a: this.a.id, b: this.b.id }
166
+ }
167
+ }
168
+
169
+ // Resolve ServiceC concurrently multiple times
170
+ const promises = Array.from({ length: 20 }, () =>
171
+ container.get(ServiceC),
172
+ )
173
+
174
+ const instances = await Promise.all(promises)
175
+
176
+ // All should be the same instance (singleton)
177
+ const first = instances[0]
178
+ expect(instances.every((i) => i === first)).toBe(true)
179
+
180
+ // All should reference the same A and B (verified by id)
181
+ const ids = first.getIds()
182
+ // ServiceB should reference the same ServiceA
183
+ expect(first.getIds().a).toBe(ids.a)
184
+ })
185
+
186
+ it('should handle async dependencies concurrently', async () => {
187
+ @Injectable()
188
+ class AsyncDependency {
189
+ value = 'async-dep'
190
+
191
+ async onServiceInit() {
192
+ await new Promise((resolve) => setTimeout(resolve, 30))
193
+ }
194
+ }
195
+
196
+ @Injectable()
197
+ class DependentService {
198
+ private dep = asyncInject(AsyncDependency)
199
+
200
+ async getValue() {
201
+ const d = await this.dep
202
+ return d.value
203
+ }
204
+ }
205
+
206
+ const promises = Array.from({ length: 5 }, () =>
207
+ container.get(DependentService),
208
+ )
209
+
210
+ const instances = await Promise.all(promises)
211
+
212
+ // All should be the same instance
213
+ expect(instances.every((i) => i === instances[0])).toBe(true)
214
+
215
+ // Should work correctly
216
+ const value = await instances[0].getValue()
217
+ expect(value).toBe('async-dep')
218
+ })
219
+ })
220
+
221
+ describe('concurrent disposal', () => {
222
+ it('should handle invalidation gracefully', async () => {
223
+ @Injectable()
224
+ class DisposableService {
225
+ value = 'active'
226
+ }
227
+
228
+ const instance = await container.get(DisposableService)
229
+ expect(instance.value).toBe('active')
230
+
231
+ // invalidate takes the instance, not the class
232
+ await container.invalidate(instance)
233
+
234
+ // After invalidation, getting a new instance should work
235
+ const newInstance = await container.get(DisposableService)
236
+ expect(newInstance).not.toBe(instance)
237
+ })
238
+ })
239
+
240
+ describe('concurrent resolution during initialization', () => {
241
+ it('should wait for service to complete initialization', async () => {
242
+ let initCompleted = false
243
+
244
+ @Injectable()
245
+ class SlowInitService {
246
+ ready = false
247
+
248
+ async onServiceInit() {
249
+ await new Promise((resolve) => setTimeout(resolve, 100))
250
+ this.ready = true
251
+ initCompleted = true
252
+ }
253
+ }
254
+
255
+ // Start resolution
256
+ const firstPromise = container.get(SlowInitService)
257
+
258
+ // Try to get it again before first resolution completes
259
+ await new Promise((resolve) => setTimeout(resolve, 20))
260
+ const secondPromise = container.get(SlowInitService)
261
+
262
+ const [first, second] = await Promise.all([firstPromise, secondPromise])
263
+
264
+ // Both should be the same instance and fully initialized
265
+ expect(first).toBe(second)
266
+ expect(first.ready).toBe(true)
267
+ expect(initCompleted).toBe(true)
268
+ })
269
+ })
270
+
271
+ describe('concurrent mixed scope resolution', () => {
272
+ it('should correctly handle mixed scopes concurrently', async () => {
273
+ @Injectable()
274
+ class SingletonService {
275
+ id = Math.random()
276
+ }
277
+
278
+ @Injectable({ scope: InjectableScope.Transient })
279
+ class TransientService {
280
+ private singleton = inject(SingletonService)
281
+ id = Math.random()
282
+
283
+ getSingletonId() {
284
+ return this.singleton.id
285
+ }
286
+ }
287
+
288
+ // Get transient services concurrently
289
+ const promises = Array.from({ length: 10 }, () =>
290
+ container.get(TransientService),
291
+ )
292
+
293
+ const instances = await Promise.all(promises)
294
+
295
+ // All transients should be different
296
+ const transientIds = new Set(instances.map((i) => i.id))
297
+ expect(transientIds.size).toBe(10)
298
+
299
+ // But they should all share the same singleton
300
+ const singletonId = instances[0].getSingletonId()
301
+ expect(instances.every((i) => i.getSingletonId() === singletonId)).toBe(
302
+ true,
303
+ )
304
+ })
305
+ })
306
+
307
+ describe('stress test', () => {
308
+ it('should handle high concurrency without errors', async () => {
309
+ @Injectable()
310
+ class StressService {
311
+ value = 'stress'
312
+ }
313
+
314
+ // Fire 200 concurrent requests
315
+ const promises = Array.from({ length: 200 }, () =>
316
+ container.get(StressService),
317
+ )
318
+
319
+ const instances = await Promise.all(promises)
320
+
321
+ // All should resolve successfully to the same instance
322
+ expect(instances.every((i) => i === instances[0])).toBe(true)
323
+ expect(instances[0].value).toBe('stress')
324
+ })
325
+
326
+ it('should handle rapid get/invalidate cycles', async () => {
327
+ @Injectable()
328
+ class CycleService {
329
+ value = Math.random()
330
+ }
331
+
332
+ // Get initial instance
333
+ const first = await container.get(CycleService)
334
+ const firstValue = first.value
335
+
336
+ // Invalidate takes instance, not class
337
+ await container.invalidate(first)
338
+ const second = await container.get(CycleService)
339
+
340
+ // New instance should be different
341
+ expect(second).not.toBe(first)
342
+ expect(second.value).not.toBe(firstValue)
343
+ })
344
+ })
345
+
346
+ describe('concurrent scoped container operations', () => {
347
+ it('should handle multiple scoped containers created and disposed concurrently', async () => {
348
+ @Injectable({ scope: InjectableScope.Request })
349
+ class RequestService {
350
+ id = Math.random()
351
+ }
352
+
353
+ // Create, use, and dispose many scoped containers sequentially
354
+ // (concurrent beginRequest with same IDs would throw)
355
+ const ids: number[] = []
356
+ for (let i = 0; i < 10; i++) {
357
+ const scoped = container.beginRequest(`request-${i}`)
358
+ const service = await scoped.get(RequestService)
359
+ ids.push(service.id)
360
+ await scoped.endRequest()
361
+ }
362
+
363
+ // All should have unique IDs
364
+ const uniqueIds = new Set(ids)
365
+ expect(uniqueIds.size).toBe(10)
366
+ })
367
+ })
368
+ })
@@ -2,26 +2,32 @@
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest'
3
3
  import { z } from 'zod/v4'
4
4
 
5
- import type {
6
- Factorable,
7
- FactorableWithArgs,
8
- } from '../interfaces/factory.interface.mjs'
9
- import type { FactoryContext } from '../internal/context/factory-context.mjs'
10
-
11
- import { Container } from '../container/container.mjs'
12
- import { Factory } from '../decorators/factory.decorator.mjs'
13
- import { Injectable } from '../decorators/injectable.decorator.mjs'
14
- import { InjectableScope } from '../enums/injectable-scope.enum.mjs'
15
- import { getInjectors } from '../index.mjs'
16
- import { asyncInject, inject } from '../injectors.mjs'
17
- import { ServiceLocator } from '../internal/core/service-locator.mjs'
18
- import { InjectionToken } from '../token/injection-token.mjs'
19
- import { Registry } from '../token/registry.mjs'
5
+ import type { Factorable, FactorableWithArgs } from '../index.mjs'
6
+ import type { ServiceInitializationContext } from '../internal/context/service-initialization-context.mjs'
7
+
8
+ import {
9
+ asyncInject,
10
+ Container,
11
+ Factory,
12
+ getInjectableToken,
13
+ getInjectors,
14
+ inject,
15
+ Injectable,
16
+ InjectableScope,
17
+ InjectionToken,
18
+ Registry,
19
+ } from '../index.mjs'
20
20
 
21
21
  describe('Container', () => {
22
22
  let container: Container
23
23
  let registry: Registry
24
- let mockLogger: Console
24
+ let mockLogger: Console & {
25
+ log: ReturnType<typeof vi.fn>
26
+ error: ReturnType<typeof vi.fn>
27
+ warn: ReturnType<typeof vi.fn>
28
+ info: ReturnType<typeof vi.fn>
29
+ debug: ReturnType<typeof vi.fn>
30
+ }
25
31
 
26
32
  beforeEach(() => {
27
33
  registry = new Registry()
@@ -41,21 +47,10 @@ describe('Container', () => {
41
47
  expect(defaultContainer).toBeInstanceOf(Container)
42
48
  })
43
49
 
44
- it('should create container with custom registry and logger', () => {
45
- expect(container).toBeInstanceOf(Container)
46
- expect(container.getServiceLocator()).toBeInstanceOf(ServiceLocator)
47
- })
48
-
49
50
  it('should register itself in the container', async () => {
50
51
  const selfInstance = await container.get(Container)
51
52
  expect(selfInstance).toBe(container)
52
53
  })
53
-
54
- it('should return the same ServiceLocator instance', () => {
55
- const serviceLocator1 = container.getServiceLocator()
56
- const serviceLocator2 = container.getServiceLocator()
57
- expect(serviceLocator1).toBe(serviceLocator2)
58
- })
59
54
  })
60
55
 
61
56
  describe('Injectable decorator scenarios', () => {
@@ -283,7 +278,7 @@ describe('Container', () => {
283
278
  it('should work with factory using context', async () => {
284
279
  @Factory({ registry })
285
280
  class ContextFactory implements Factorable<TestService> {
286
- async create(ctx: FactoryContext) {
281
+ async create(ctx: ServiceInitializationContext) {
287
282
  const container = await ctx.inject(Container)
288
283
  return new TestService(container)
289
284
  }
@@ -642,6 +637,12 @@ describe('Container', () => {
642
637
  @Injectable({ registry })
643
638
  class TestService {}
644
639
 
640
+ try {
641
+ await container.get(ErrorFactory)
642
+ } catch (error) {
643
+ console.log(error)
644
+ }
645
+
645
646
  await expect(container.get(ErrorFactory)).rejects.toThrow('Factory error')
646
647
  })
647
648
 
@@ -959,11 +960,12 @@ describe('Container', () => {
959
960
 
960
961
  // Create 100 services
961
962
  for (let i = 0; i < 100; i++) {
962
- @Injectable({ registry })
963
+ const token = InjectionToken.create<TestService>(`TestService${i}`)
964
+ @Injectable({ token, registry })
963
965
  class TestService {
964
966
  public id = i
965
967
  }
966
- services.push(TestService)
968
+ services.push(token)
967
969
  }
968
970
 
969
971
  // Get all services