@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
@@ -1,416 +0,0 @@
1
- import { beforeEach, describe, expect, it } from 'vitest'
2
-
3
- import { Container } from '../container/container.mjs'
4
- import { Injectable } from '../decorators/injectable.decorator.mjs'
5
- import { InjectableScope } from '../enums/index.mjs'
6
- import { inject } from '../index.mjs'
7
- import { InjectionToken } from '../token/injection-token.mjs'
8
- import { Registry } from '../token/registry.mjs'
9
- import { ScopedContainer } from '../container/scoped-container.mjs'
10
-
11
- describe('Request Scope', () => {
12
- let container: Container
13
- let registry: Registry
14
-
15
- beforeEach(() => {
16
- registry = new Registry()
17
- container = new Container(registry)
18
- })
19
-
20
- describe('Request-scoped services with ScopedContainer', () => {
21
- it('should create different instances for different requests', async () => {
22
- @Injectable({ registry, scope: InjectableScope.Request })
23
- class RequestService {
24
- public readonly requestId = Math.random().toString(36)
25
- public readonly createdAt = new Date()
26
- }
27
-
28
- // Start first request - get a ScopedContainer
29
- const scoped1 = container.beginRequest('request-1')
30
- const instance1a = await scoped1.get(RequestService)
31
- const instance1b = await scoped1.get(RequestService)
32
-
33
- // Start second request - get another ScopedContainer
34
- const scoped2 = container.beginRequest('request-2')
35
- const instance2a = await scoped2.get(RequestService)
36
- const instance2b = await scoped2.get(RequestService)
37
-
38
- // Within same request, instances should be the same
39
- expect(instance1a).toBe(instance1b)
40
- expect(instance2a).toBe(instance2b)
41
-
42
- // Between different requests, instances should be different
43
- expect(instance1a).not.toBe(instance2a)
44
- expect(instance1a.requestId).not.toBe(instance2a.requestId)
45
-
46
- // Clean up
47
- await scoped1.endRequest()
48
- await scoped2.endRequest()
49
- })
50
-
51
- it('should handle request context lifecycle correctly', async () => {
52
- @Injectable({ registry, scope: InjectableScope.Request })
53
- class RequestService {
54
- public readonly requestId = Math.random().toString(36)
55
- public destroyed = false
56
-
57
- async onServiceDestroy() {
58
- this.destroyed = true
59
- }
60
- }
61
-
62
- // Start request - get a ScopedContainer
63
- const scoped = container.beginRequest('test-request')
64
-
65
- const instance = await scoped.get(RequestService)
66
- expect(instance.destroyed).toBe(false)
67
-
68
- // End request should trigger cleanup
69
- await scoped.endRequest()
70
- expect(instance.destroyed).toBe(true)
71
- })
72
-
73
- it('should support request metadata', async () => {
74
- const requestId = 'test-request'
75
- const metadata = { userId: 'user123', sessionId: 'session456' }
76
-
77
- const scoped = container.beginRequest(requestId, metadata)
78
-
79
- // Verify metadata is accessible from the scoped container
80
- expect(scoped.getRequestId()).toBe(requestId)
81
- expect(scoped.getMetadata('userId')).toBe('user123')
82
- expect(scoped.getMetadata('sessionId')).toBe('session456')
83
-
84
- await scoped.endRequest()
85
- })
86
-
87
- it('should handle pre-prepared instances', async () => {
88
- const token = InjectionToken.create<{ value: string }>('PrePrepared')
89
-
90
- @Injectable({ registry, scope: InjectableScope.Request, token })
91
- class RequestService {
92
- constructor(public readonly value: string = 'default') {}
93
- }
94
-
95
- const scoped = container.beginRequest('test-request')
96
-
97
- // Add a pre-prepared instance
98
- const prePreparedInstance = { value: 'pre-prepared' }
99
- scoped.addInstance(token, prePreparedInstance)
100
-
101
- // Getting the service should return the pre-prepared instance
102
- const instance = await scoped.get(token)
103
- expect(instance).toBe(prePreparedInstance)
104
-
105
- await scoped.endRequest()
106
- })
107
-
108
- it('should throw error when resolving request-scoped service from Container directly', async () => {
109
- @Injectable({ registry, scope: InjectableScope.Request })
110
- class RequestService {
111
- public readonly requestId = Math.random().toString(36)
112
- }
113
-
114
- // Trying to resolve request-scoped service from Container should throw
115
- await expect(container.get(RequestService)).rejects.toThrow(
116
- /Cannot resolve request-scoped service/,
117
- )
118
- })
119
-
120
- it('should throw error when creating duplicate request ID', () => {
121
- container.beginRequest('request-1')
122
-
123
- // Creating another request with the same ID should throw
124
- expect(() => container.beginRequest('request-1')).toThrow(
125
- /Request context "request-1" already exists/,
126
- )
127
- })
128
-
129
- it('should allow reusing request ID after ending the request', async () => {
130
- const scoped1 = container.beginRequest('request-1')
131
- await scoped1.endRequest()
132
-
133
- // Should be able to create a new request with the same ID
134
- const scoped2 = container.beginRequest('request-1')
135
- expect(scoped2).toBeInstanceOf(ScopedContainer)
136
- await scoped2.endRequest()
137
- })
138
- })
139
-
140
- describe('ScopedContainer delegation', () => {
141
- it('should delegate singleton resolution to parent Container', async () => {
142
- @Injectable({ registry })
143
- class SingletonService {
144
- public readonly id = Math.random()
145
- }
146
-
147
- const scoped = container.beginRequest('test-request')
148
-
149
- // Get singleton from scoped container
150
- const instance1 = await scoped.get(SingletonService)
151
-
152
- // Get singleton from main container
153
- const instance2 = await container.get(SingletonService)
154
-
155
- // Should be the same instance
156
- expect(instance1).toBe(instance2)
157
-
158
- await scoped.endRequest()
159
- })
160
-
161
- it('should delegate transient resolution to parent Container', async () => {
162
- @Injectable({ registry, scope: InjectableScope.Transient })
163
- class TransientService {
164
- public readonly id = Math.random()
165
- }
166
-
167
- const scoped = container.beginRequest('test-request')
168
-
169
- // Each get should create a new instance
170
- const instance1 = await scoped.get(TransientService)
171
- const instance2 = await scoped.get(TransientService)
172
-
173
- expect(instance1).not.toBe(instance2)
174
-
175
- await scoped.endRequest()
176
- })
177
-
178
- it('should allow request-scoped services to depend on singletons', async () => {
179
- @Injectable({ registry })
180
- class SingletonService {
181
- public readonly id = Math.random()
182
- }
183
-
184
- @Injectable({ registry, scope: InjectableScope.Request })
185
- class RequestService {
186
- singleton = inject(SingletonService)
187
- public readonly id = Math.random()
188
- }
189
-
190
- const scoped = container.beginRequest('test-request')
191
-
192
- const requestInstance = await scoped.get(RequestService)
193
- const singletonFromRequest = requestInstance.singleton
194
- const singletonDirect = await container.get(SingletonService)
195
-
196
- // The singleton injected into the request service should be the same
197
- // as the one from the main container
198
- expect(singletonFromRequest).toBe(singletonDirect)
199
-
200
- await scoped.endRequest()
201
- })
202
- })
203
-
204
- describe('Request isolation (race condition prevention)', () => {
205
- it('should prevent duplicate initialization during concurrent resolution within same request', async () => {
206
- let initializationCount = 0
207
-
208
- @Injectable({ registry, scope: InjectableScope.Request })
209
- class RequestService {
210
- public readonly instanceId: string
211
-
212
- constructor() {
213
- initializationCount++
214
- this.instanceId = Math.random().toString(36)
215
- }
216
-
217
- async onServiceInit() {
218
- // Simulate async initialization that takes time
219
- await new Promise((resolve) => setTimeout(resolve, 50))
220
- }
221
- }
222
-
223
- const scoped = container.beginRequest('test-request')
224
-
225
- // Fire multiple concurrent resolution requests for the same service
226
- const [instance1, instance2, instance3] = await Promise.all([
227
- scoped.get(RequestService),
228
- scoped.get(RequestService),
229
- scoped.get(RequestService),
230
- ])
231
-
232
- // All instances should be the same (no duplicate initialization)
233
- expect(instance1).toBe(instance2)
234
- expect(instance2).toBe(instance3)
235
- expect(initializationCount).toBe(1) // Only initialized once
236
-
237
- await scoped.endRequest()
238
- })
239
-
240
- it('should return correct instance when waiting for in-progress creation', async () => {
241
- let creationOrder: string[] = []
242
-
243
- @Injectable({ registry, scope: InjectableScope.Request })
244
- class SlowService {
245
- public readonly id: string
246
-
247
- constructor() {
248
- this.id = Math.random().toString(36)
249
- }
250
-
251
- async onServiceInit() {
252
- creationOrder.push('init-start')
253
- // Simulate slow async initialization
254
- await new Promise((resolve) => setTimeout(resolve, 100))
255
- creationOrder.push('init-end')
256
- }
257
- }
258
-
259
- const scoped = container.beginRequest('test-request')
260
-
261
- // Start first resolution (will start creating)
262
- const promise1 = scoped.get(SlowService)
263
- creationOrder.push('promise1-started')
264
-
265
- // Start second resolution while first is still in progress
266
- await new Promise((resolve) => setTimeout(resolve, 10))
267
- creationOrder.push('promise2-starting')
268
- const promise2 = scoped.get(SlowService)
269
-
270
- const [instance1, instance2] = await Promise.all([promise1, promise2])
271
-
272
- // Both should be the same instance
273
- expect(instance1).toBe(instance2)
274
- expect(instance1.id).toBe(instance2.id)
275
-
276
- // Verify the initialization only happened once
277
- expect(creationOrder.filter((x) => x === 'init-start').length).toBe(1)
278
- expect(creationOrder.filter((x) => x === 'init-end').length).toBe(1)
279
-
280
- await scoped.endRequest()
281
- })
282
-
283
- it('should isolate request contexts during concurrent async operations', async () => {
284
- @Injectable({ registry, scope: InjectableScope.Request })
285
- class RequestService {
286
- public readonly requestId: string
287
-
288
- constructor() {
289
- this.requestId = Math.random().toString(36)
290
- }
291
- }
292
-
293
- // Start two requests concurrently
294
- const scoped1 = container.beginRequest('request-1')
295
- const scoped2 = container.beginRequest('request-2')
296
-
297
- // Simulate concurrent async operations
298
- const [instance1, instance2] = await Promise.all([
299
- scoped1.get(RequestService),
300
- scoped2.get(RequestService),
301
- ])
302
-
303
- // Each request should have its own instance
304
- expect(instance1.requestId).not.toBe(instance2.requestId)
305
-
306
- // Verify they're still accessible after concurrent resolution
307
- const instance1Again = await scoped1.get(RequestService)
308
- const instance2Again = await scoped2.get(RequestService)
309
-
310
- expect(instance1).toBe(instance1Again)
311
- expect(instance2).toBe(instance2Again)
312
-
313
- await scoped1.endRequest()
314
- await scoped2.endRequest()
315
- })
316
-
317
- it('should maintain correct context during interleaved async operations', async () => {
318
- @Injectable({ registry, scope: InjectableScope.Request })
319
- class RequestService {
320
- public readonly requestId: string
321
- public value = 0
322
-
323
- constructor() {
324
- this.requestId = Math.random().toString(36)
325
- }
326
-
327
- async asyncOperation(delay: number): Promise<void> {
328
- await new Promise((resolve) => setTimeout(resolve, delay))
329
- this.value++
330
- }
331
- }
332
-
333
- const scoped1 = container.beginRequest('request-1')
334
- const scoped2 = container.beginRequest('request-2')
335
-
336
- const instance1 = await scoped1.get(RequestService)
337
- const instance2 = await scoped2.get(RequestService)
338
-
339
- // Start async operations with different delays
340
- await Promise.all([
341
- instance1.asyncOperation(50),
342
- instance2.asyncOperation(25),
343
- instance1.asyncOperation(10),
344
- instance2.asyncOperation(75),
345
- ])
346
-
347
- // Each instance should have been modified independently
348
- expect(instance1.value).toBe(2)
349
- expect(instance2.value).toBe(2)
350
-
351
- await scoped1.endRequest()
352
- await scoped2.endRequest()
353
- })
354
- })
355
-
356
- describe('ScopedContainer API', () => {
357
- it('should implement IContainer interface', async () => {
358
- const scoped = container.beginRequest('test-request')
359
-
360
- // Check that all IContainer methods exist
361
- expect(typeof scoped.get).toBe('function')
362
- expect(typeof scoped.invalidate).toBe('function')
363
- expect(typeof scoped.isRegistered).toBe('function')
364
- expect(typeof scoped.dispose).toBe('function')
365
- expect(typeof scoped.ready).toBe('function')
366
- expect(typeof scoped.tryGetSync).toBe('function')
367
-
368
- await scoped.endRequest()
369
- })
370
-
371
- it('should track active request IDs in Container', async () => {
372
- expect(container.hasActiveRequest('request-1')).toBe(false)
373
-
374
- const scoped1 = container.beginRequest('request-1')
375
- expect(container.hasActiveRequest('request-1')).toBe(true)
376
-
377
- const scoped2 = container.beginRequest('request-2')
378
- expect(container.hasActiveRequest('request-2')).toBe(true)
379
-
380
- expect(container.getActiveRequestIds().size).toBe(2)
381
-
382
- await scoped1.endRequest()
383
- expect(container.hasActiveRequest('request-1')).toBe(false)
384
- expect(container.hasActiveRequest('request-2')).toBe(true)
385
-
386
- await scoped2.endRequest()
387
- expect(container.getActiveRequestIds().size).toBe(0)
388
- })
389
-
390
- it('should return parent Container from ScopedContainer', async () => {
391
- const scoped = container.beginRequest('test-request')
392
- expect(scoped.getParent()).toBe(container)
393
- await scoped.endRequest()
394
- })
395
-
396
- it('dispose() should be an alias for endRequest()', async () => {
397
- @Injectable({ registry, scope: InjectableScope.Request })
398
- class RequestService {
399
- public destroyed = false
400
-
401
- async onServiceDestroy() {
402
- this.destroyed = true
403
- }
404
- }
405
-
406
- const scoped = container.beginRequest('test-request')
407
- const instance = await scoped.get(RequestService)
408
-
409
- // Use dispose() instead of endRequest()
410
- await scoped.dispose()
411
-
412
- expect(instance.destroyed).toBe(true)
413
- expect(container.hasActiveRequest('test-request')).toBe(false)
414
- })
415
- })
416
- })