@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,595 @@
1
+ /**
2
+ * Comprehensive tests for ScopedContainer
3
+ *
4
+ * These tests cover:
5
+ * 1. Basic ScopedContainer functionality
6
+ * 2. Request-scoped service resolution
7
+ * 3. addInstance method with various token types
8
+ * 4. Error handling and validation
9
+ * 5. Integration with parent Container
10
+ * 6. Disposal and cleanup
11
+ */
12
+
13
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
14
+ import { z } from 'zod/v4'
15
+
16
+ import { Container } from '../container/container.mjs'
17
+ import { Injectable } from '../decorators/injectable.decorator.mjs'
18
+ import { InjectableScope } from '../enums/index.mjs'
19
+ import { DIError, DIErrorCode } from '../errors/di-error.mjs'
20
+ import { InjectionToken } from '../token/injection-token.mjs'
21
+ import { Registry } from '../token/registry.mjs'
22
+ import { getInjectors } from '../utils/get-injectors.mjs'
23
+
24
+ // ============================================================================
25
+ // TEST UTILITIES
26
+ // ============================================================================
27
+
28
+ function createTestSetup() {
29
+ const registry = new Registry()
30
+ const injectors = getInjectors()
31
+ const container = new Container(registry, null, injectors)
32
+
33
+ return { registry, injectors, container }
34
+ }
35
+
36
+ // ============================================================================
37
+ // SECTION 1: BASIC FUNCTIONALITY
38
+ // ============================================================================
39
+
40
+ describe('ScopedContainer: Basic Functionality', () => {
41
+ let registry: Registry
42
+ let container: Container
43
+
44
+ beforeEach(() => {
45
+ const setup = createTestSetup()
46
+ registry = setup.registry
47
+ container = setup.container
48
+ })
49
+
50
+ afterEach(async () => {
51
+ await container.dispose()
52
+ })
53
+
54
+ describe('Container creation and lifecycle', () => {
55
+ it('should create a scoped container with request ID', () => {
56
+ const scoped = container.beginRequest('test-request-1')
57
+ expect(scoped).toBeDefined()
58
+ expect(scoped.getRequestId()).toBe('test-request-1')
59
+ expect(scoped.getParent()).toBe(container)
60
+ })
61
+
62
+ it('should track active request IDs', async () => {
63
+ const scoped1 = container.beginRequest('request-1')
64
+ const scoped2 = container.beginRequest('request-2')
65
+
66
+ expect(container.hasActiveRequest('request-1')).toBe(true)
67
+ expect(container.hasActiveRequest('request-2')).toBe(true)
68
+ expect(container.getActiveRequestIds().size).toBe(2)
69
+
70
+ await scoped1.endRequest()
71
+ expect(container.hasActiveRequest('request-1')).toBe(false)
72
+ expect(container.hasActiveRequest('request-2')).toBe(true)
73
+
74
+ await scoped2.endRequest()
75
+ expect(container.getActiveRequestIds().size).toBe(0)
76
+ })
77
+
78
+ it('should prevent duplicate request IDs', () => {
79
+ container.beginRequest('duplicate-id')
80
+ expect(() => container.beginRequest('duplicate-id')).toThrow()
81
+ })
82
+
83
+ it('should support metadata', () => {
84
+ const metadata = { userId: '123', sessionId: 'abc' }
85
+ const scoped = container.beginRequest('request-1', metadata)
86
+
87
+ expect(scoped.getMetadata('userId')).toBe('123')
88
+ expect(scoped.getMetadata('sessionId')).toBe('abc')
89
+ expect(scoped.getMetadata('nonExistent')).toBeUndefined()
90
+
91
+ scoped.setMetadata('newKey', 'newValue')
92
+ expect(scoped.getMetadata('newKey')).toBe('newValue')
93
+ })
94
+ })
95
+
96
+ describe('Request-scoped service resolution', () => {
97
+ it('should resolve request-scoped services from scoped container', async () => {
98
+ let instanceCount = 0
99
+
100
+ @Injectable({ scope: InjectableScope.Request, registry })
101
+ class RequestService {
102
+ id = ++instanceCount
103
+ }
104
+
105
+ const scoped = container.beginRequest('request-1')
106
+ const instance1 = await scoped.get(RequestService)
107
+ const instance2 = await scoped.get(RequestService)
108
+
109
+ expect(instance1).toBeInstanceOf(RequestService)
110
+ expect(instance1).toBe(instance2) // Same instance within request
111
+ expect(instance1.id).toBe(1)
112
+
113
+ await scoped.endRequest()
114
+ })
115
+
116
+ it('should create different instances for different requests', async () => {
117
+ let instanceCount = 0
118
+
119
+ @Injectable({ scope: InjectableScope.Request, registry })
120
+ class RequestService {
121
+ id = ++instanceCount
122
+ }
123
+
124
+ const scoped1 = container.beginRequest('request-1')
125
+ const instance1 = await scoped1.get(RequestService)
126
+
127
+ const scoped2 = container.beginRequest('request-2')
128
+ const instance2 = await scoped2.get(RequestService)
129
+
130
+ expect(instance1).not.toBe(instance2)
131
+ expect(instance1.id).toBe(1)
132
+ expect(instance2.id).toBe(2)
133
+
134
+ await scoped1.endRequest()
135
+ await scoped2.endRequest()
136
+ })
137
+
138
+ it('should delegate singleton services to parent', async () => {
139
+ @Injectable({ scope: InjectableScope.Singleton, registry })
140
+ class SingletonService {
141
+ id = Math.random()
142
+ }
143
+
144
+ const scoped = container.beginRequest('request-1')
145
+ const instance1 = await scoped.get(SingletonService)
146
+ const instance2 = await container.get(SingletonService)
147
+
148
+ expect(instance1).toBe(instance2) // Same singleton instance
149
+
150
+ await scoped.endRequest()
151
+ })
152
+
153
+ it('should delegate transient services to parent', async () => {
154
+ @Injectable({ scope: InjectableScope.Transient, registry })
155
+ class TransientService {
156
+ id = Math.random()
157
+ }
158
+
159
+ const scoped = container.beginRequest('request-1')
160
+ const instance1 = await scoped.get(TransientService)
161
+ const instance2 = await scoped.get(TransientService)
162
+
163
+ expect(instance1).not.toBe(instance2) // Different instances
164
+
165
+ await scoped.endRequest()
166
+ })
167
+ })
168
+
169
+ describe('Disposal and cleanup', () => {
170
+ it('should clean up request-scoped services on endRequest', async () => {
171
+ let destroyCount = 0
172
+
173
+ @Injectable({ scope: InjectableScope.Request, registry })
174
+ class RequestService {
175
+ onServiceDestroy() {
176
+ destroyCount++
177
+ }
178
+ }
179
+
180
+ const scoped = container.beginRequest('request-1')
181
+ await scoped.get(RequestService)
182
+ await scoped.endRequest()
183
+
184
+ expect(destroyCount).toBe(1)
185
+ })
186
+
187
+ it('should prevent operations after disposal', async () => {
188
+ const scoped = container.beginRequest('request-1')
189
+ await scoped.endRequest()
190
+
191
+ await expect(scoped.get(Container)).rejects.toThrow(
192
+ 'ScopedContainer has been disposed',
193
+ )
194
+ })
195
+
196
+ it('should support dispose alias', async () => {
197
+ const scoped = container.beginRequest('request-1')
198
+ await scoped.dispose()
199
+
200
+ await expect(scoped.get(Container)).rejects.toThrow(
201
+ 'ScopedContainer has been disposed',
202
+ )
203
+ })
204
+ })
205
+ })
206
+
207
+ // ============================================================================
208
+ // SECTION 2: addInstance METHOD TESTS
209
+ // ============================================================================
210
+
211
+ describe('ScopedContainer: addInstance Method', () => {
212
+ let registry: Registry
213
+ let container: Container
214
+
215
+ beforeEach(() => {
216
+ const setup = createTestSetup()
217
+ registry = setup.registry
218
+ container = setup.container
219
+ })
220
+
221
+ afterEach(async () => {
222
+ await container.dispose()
223
+ })
224
+
225
+ describe('addInstance with class types', () => {
226
+ it('should throw an error for unregistered class type', () => {
227
+ class UnregisteredService {
228
+ value = 'test'
229
+ }
230
+
231
+ const scoped = container.beginRequest('request-1')
232
+ const instance = new UnregisteredService()
233
+ expect(() => scoped.addInstance(UnregisteredService, instance)).toThrow(
234
+ DIError,
235
+ )
236
+
237
+ scoped.endRequest()
238
+ })
239
+
240
+ it('should add instance for registered class type', async () => {
241
+ @Injectable({ scope: InjectableScope.Request, registry })
242
+ class RegisteredService {
243
+ value = 'default'
244
+ }
245
+
246
+ const scoped = container.beginRequest('request-1')
247
+ const customInstance = new RegisteredService()
248
+ customInstance.value = 'custom'
249
+
250
+ scoped.addInstance(RegisteredService, customInstance)
251
+
252
+ // Should retrieve the added instance, not create a new one
253
+ const retrieved = await scoped.get(RegisteredService)
254
+ expect(retrieved).toBe(customInstance)
255
+ expect(retrieved.value).toBe('custom')
256
+
257
+ scoped.endRequest()
258
+ })
259
+ })
260
+
261
+ describe('addInstance with InjectionToken (no schema)', () => {
262
+ it('should add instance for InjectionToken without schema', async () => {
263
+ interface TestService {
264
+ value: string
265
+ }
266
+
267
+ const token = InjectionToken.create<TestService>('TestService')
268
+ const instance: TestService = { value: 'test' }
269
+
270
+ const scoped = container.beginRequest('request-1')
271
+ scoped.addInstance(token, instance)
272
+
273
+ const retrieved = await scoped.get(token)
274
+ expect(retrieved).toBe(instance)
275
+ expect(retrieved.value).toBe('test')
276
+
277
+ scoped.endRequest()
278
+ })
279
+
280
+ it('should add instance for InjectionToken with optional schema', async () => {
281
+ interface TestService {
282
+ value: string
283
+ }
284
+
285
+ const optionalSchema = z
286
+ .object({
287
+ name: z.string(),
288
+ })
289
+ .optional()
290
+
291
+ const token = InjectionToken.create<TestService, typeof optionalSchema>(
292
+ 'TestService',
293
+ optionalSchema,
294
+ )
295
+ const instance: TestService = { value: 'test' }
296
+
297
+ const scoped = container.beginRequest('request-1')
298
+ scoped.addInstance(token, instance)
299
+
300
+ const retrieved = await scoped.get(token)
301
+ expect(retrieved).toBe(instance)
302
+
303
+ scoped.endRequest()
304
+ })
305
+
306
+ it('should reject InjectionToken with required schema', () => {
307
+ interface TestService {
308
+ value: string
309
+ }
310
+
311
+ const requiredSchema = z.object({
312
+ name: z.string(),
313
+ age: z.number(),
314
+ })
315
+
316
+ const token = InjectionToken.create<TestService, typeof requiredSchema>(
317
+ 'TestService',
318
+ requiredSchema,
319
+ )
320
+ const instance: TestService = { value: 'test' }
321
+
322
+ const scoped = container.beginRequest('request-1')
323
+
324
+ expect(() => {
325
+ scoped.addInstance(token, instance)
326
+ }).toThrow(DIError)
327
+
328
+ const error = (() => {
329
+ try {
330
+ scoped.addInstance(token, instance)
331
+ } catch (e) {
332
+ return e
333
+ }
334
+ })() as DIError
335
+
336
+ expect(error.code).toBe(DIErrorCode.TokenSchemaRequiredError)
337
+ expect(error.message).toContain('requires schema arguments')
338
+
339
+ scoped.endRequest()
340
+ })
341
+ })
342
+
343
+ describe('addInstance with BoundInjectionToken', () => {
344
+ it('should add instance for BoundInjectionToken with required schema', async () => {
345
+ interface TestService {
346
+ value: string
347
+ }
348
+
349
+ const requiredSchema = z.object({
350
+ name: z.string(),
351
+ age: z.number(),
352
+ })
353
+
354
+ const token = InjectionToken.create<TestService, typeof requiredSchema>(
355
+ 'TestService',
356
+ requiredSchema,
357
+ )
358
+
359
+ const boundToken = InjectionToken.bound(token, {
360
+ name: 'John',
361
+ age: 30,
362
+ })
363
+
364
+ const instance: TestService = { value: 'test' }
365
+
366
+ const scoped = container.beginRequest('request-1')
367
+ scoped.addInstance(boundToken, instance)
368
+ console.log(scoped.getStorage().getAllNames())
369
+
370
+ const retrieved = await scoped.get(boundToken)
371
+ expect(retrieved).toBe(instance)
372
+ expect(retrieved.value).toBe('test')
373
+
374
+ scoped.endRequest()
375
+ })
376
+
377
+ it('should use bound value for instance name generation', async () => {
378
+ interface TestService {
379
+ value: string
380
+ }
381
+
382
+ const schema = z.object({
383
+ id: z.string(),
384
+ })
385
+
386
+ const token = InjectionToken.create<TestService, typeof schema>(
387
+ 'TestService',
388
+ schema,
389
+ )
390
+
391
+ const boundToken1 = InjectionToken.bound(token, { id: '1' })
392
+ const boundToken2 = InjectionToken.bound(token, { id: '2' })
393
+
394
+ const instance1: TestService = { value: 'instance1' }
395
+ const instance2: TestService = { value: 'instance2' }
396
+
397
+ const scoped = container.beginRequest('request-1')
398
+ scoped.addInstance(boundToken1, instance1)
399
+ scoped.addInstance(boundToken2, instance2)
400
+
401
+ const retrieved1 = await scoped.get(boundToken1)
402
+ const retrieved2 = await scoped.get(boundToken2)
403
+
404
+ expect(retrieved1).toBe(instance1)
405
+ expect(retrieved2).toBe(instance2)
406
+ expect(retrieved1.value).toBe('instance1')
407
+ expect(retrieved2.value).toBe('instance2')
408
+
409
+ scoped.endRequest()
410
+ })
411
+ })
412
+
413
+ describe('addInstance error cases', () => {
414
+ it('should reject addInstance on disposed container', async () => {
415
+ class TestService {
416
+ value = 'test'
417
+ }
418
+
419
+ const scoped = container.beginRequest('request-1')
420
+ await scoped.endRequest()
421
+
422
+ const instance = new TestService()
423
+ expect(() => {
424
+ scoped.addInstance(TestService, instance)
425
+ }).toThrow('ScopedContainer has been disposed')
426
+ })
427
+
428
+ it('should handle multiple addInstance calls for same token', async () => {
429
+ @Injectable({ scope: InjectableScope.Request, registry })
430
+ class TestService {
431
+ value = 'test'
432
+ }
433
+
434
+ const scoped = container.beginRequest('request-1')
435
+ const instance1 = new TestService()
436
+ instance1.value = 'first'
437
+ scoped.addInstance(TestService, instance1)
438
+
439
+ // Second addInstance should overwrite (or throw if storage prevents it)
440
+ const instance2 = new TestService()
441
+ instance2.value = 'second'
442
+
443
+ // Storage.storeInstance throws if instance already exists
444
+ expect(() => {
445
+ scoped.addInstance(TestService, instance2)
446
+ }).toThrow()
447
+
448
+ scoped.endRequest()
449
+ })
450
+ })
451
+
452
+ describe('addInstance integration with get', () => {
453
+ it('should retrieve added instance via get method', async () => {
454
+ @Injectable({ scope: InjectableScope.Request, registry })
455
+ class TestService {
456
+ value = 'custom'
457
+ }
458
+
459
+ const scoped = container.beginRequest('request-1')
460
+ const instance = new TestService()
461
+ scoped.addInstance(TestService, instance)
462
+
463
+ const retrieved = await scoped.get(TestService)
464
+ expect(retrieved).toBe(instance)
465
+ expect(retrieved.value).toBe('custom')
466
+
467
+ scoped.endRequest()
468
+ })
469
+ })
470
+ })
471
+
472
+ // ============================================================================
473
+ // SECTION 3: COMPLEX SCENARIOS
474
+ // ============================================================================
475
+
476
+ describe('ScopedContainer: Complex Scenarios', () => {
477
+ let registry: Registry
478
+ let container: Container
479
+ let injectors: ReturnType<typeof getInjectors>
480
+
481
+ beforeEach(() => {
482
+ const setup = createTestSetup()
483
+ registry = setup.registry
484
+ container = setup.container
485
+ injectors = setup.injectors
486
+ })
487
+
488
+ afterEach(async () => {
489
+ await container.dispose()
490
+ })
491
+
492
+ describe('Mixed scopes with addInstance', () => {
493
+ it('should handle singleton depending on request-scoped added instance', async () => {
494
+ @Injectable({ scope: InjectableScope.Singleton, registry })
495
+ class SingletonService {
496
+ public requestService = injectors.inject(RequestService)
497
+ }
498
+
499
+ @Injectable({ scope: InjectableScope.Request, registry })
500
+ class RequestService {
501
+ value = 'request'
502
+ }
503
+
504
+ const scoped = container.beginRequest('request-1')
505
+ const requestInstance = new RequestService()
506
+ scoped.addInstance(RequestService, requestInstance)
507
+
508
+ // Singleton should be able to get the request-scoped instance
509
+ const singleton = await scoped.get(SingletonService)
510
+ expect(singleton.requestService).toBe(requestInstance)
511
+
512
+ scoped.endRequest()
513
+ })
514
+
515
+ it('should handle multiple addInstance calls with different token types', async () => {
516
+ @Injectable({ registry, scope: InjectableScope.Request })
517
+ class ClassService {
518
+ value = 'class'
519
+ }
520
+
521
+ interface TokenService {
522
+ value: string
523
+ }
524
+ const token = InjectionToken.create<TokenService>('TokenService')
525
+
526
+ const schema = z.object({ id: z.string() })
527
+ interface BoundService {
528
+ value: string
529
+ }
530
+ const boundToken = InjectionToken.bound(
531
+ InjectionToken.create<BoundService, typeof schema>(
532
+ 'BoundService',
533
+ schema,
534
+ ),
535
+ { id: '123' },
536
+ )
537
+
538
+ const scoped = container.beginRequest('request-1')
539
+
540
+ const classInstance = new ClassService()
541
+ scoped.addInstance(ClassService, classInstance)
542
+
543
+ const tokenInstance: TokenService = { value: 'token' }
544
+ scoped.addInstance(token, tokenInstance)
545
+
546
+ const boundInstance: BoundService = { value: 'bound' }
547
+ scoped.addInstance(boundToken, boundInstance)
548
+
549
+ expect(await scoped.get(ClassService)).toBe(classInstance)
550
+ expect(await scoped.get(token)).toBe(tokenInstance)
551
+ expect(await scoped.get(boundToken)).toBe(boundInstance)
552
+
553
+ scoped.endRequest()
554
+ })
555
+ })
556
+
557
+ describe('Invalidation with addInstance', () => {
558
+ it('should invalidate added instances', async () => {
559
+ let destroyCount = 0
560
+
561
+ @Injectable({ scope: InjectableScope.Request, registry })
562
+ class TestService {
563
+ onServiceDestroy() {
564
+ destroyCount++
565
+ }
566
+ }
567
+
568
+ const scoped = container.beginRequest('request-1')
569
+ const instance = new TestService()
570
+ scoped.addInstance(TestService, instance)
571
+
572
+ await scoped.invalidate(instance)
573
+ expect(destroyCount).toBe(1)
574
+
575
+ scoped.endRequest()
576
+ })
577
+ })
578
+
579
+ describe('Ready state with addInstance', () => {
580
+ it('should wait for ready state after adding instances', async () => {
581
+ @Injectable({ scope: InjectableScope.Request, registry })
582
+ class TestService {
583
+ value = 'test'
584
+ }
585
+
586
+ const scoped = container.beginRequest('request-1')
587
+ const instance = new TestService()
588
+ scoped.addInstance(TestService, instance)
589
+
590
+ await scoped.ready() // Should complete without hanging
591
+
592
+ scoped.endRequest()
593
+ })
594
+ })
595
+ })