@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,966 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest'
2
- import { z } from 'zod/v4'
3
-
4
- import type { OnServiceDestroy } from '../index.mjs'
5
-
6
- import { Container } from '../container/container.mjs'
7
- import { Injectable } from '../decorators/injectable.decorator.mjs'
8
- import { InjectableScope } from '../enums/index.mjs'
9
- import { getInjectableToken } from '../index.mjs'
10
- import { asyncInject, inject } from '../injectors.mjs'
11
- import { ServiceLocator } from '../internal/core/service-locator.mjs'
12
- import { InjectionToken } from '../token/injection-token.mjs'
13
- import { globalRegistry } from '../token/registry.mjs'
14
-
15
- describe('ServiceLocator', () => {
16
- describe('getInstanceIdentifier', () => {
17
- it('should be possible to simple token', () => {
18
- const serviceLocator = new ServiceLocator()
19
- const token = InjectionToken.create('test')
20
- const identifier = serviceLocator.getInstanceIdentifier(token)
21
- expect(identifier).toBe(`test(${token.id})`)
22
- })
23
-
24
- it('should be possible to bound token', () => {
25
- const serviceLocator = new ServiceLocator()
26
- const token = InjectionToken.create('test')
27
- const identifier = serviceLocator.getInstanceIdentifier(
28
- InjectionToken.bound(token, {
29
- test: 'test',
30
- }),
31
- )
32
- expect(identifier).toBe(`test(${token.id}):test=test`)
33
- })
34
-
35
- it('should be possible to bound token with function', () => {
36
- const serviceLocator = new ServiceLocator()
37
- const token = InjectionToken.create('test')
38
- const identifier = serviceLocator.getInstanceIdentifier(
39
- InjectionToken.bound(token, { test: () => 'test' }),
40
- )
41
- expect(identifier).toBe(`test(${token.id}):test=fn_test(0)`)
42
- })
43
- })
44
-
45
- describe('clearAll', () => {
46
- let container: Container
47
- let mockLogger: Console
48
-
49
- beforeEach(() => {
50
- mockLogger = {
51
- log: vi.fn(),
52
- warn: vi.fn(),
53
- error: vi.fn(),
54
- debug: vi.fn(),
55
- trace: vi.fn(),
56
- } as any
57
-
58
- container = new Container(globalRegistry, mockLogger)
59
- })
60
-
61
- it('should clear all services gracefully', async () => {
62
- const serviceLocator = container.getServiceLocator()
63
-
64
- // Create Injectable services
65
- @Injectable({ scope: InjectableScope.Singleton })
66
- class ServiceA {
67
- name = 'ServiceA'
68
- }
69
-
70
- @Injectable({ scope: InjectableScope.Singleton })
71
- class ServiceB {
72
- name = 'ServiceB'
73
- }
74
-
75
- @Injectable({ scope: InjectableScope.Singleton })
76
- class ServiceC {
77
- name = 'ServiceC'
78
- }
79
-
80
- // Create instances using container
81
- await container.get(ServiceA)
82
- await container.get(ServiceB)
83
- await container.get(ServiceC)
84
-
85
- // Verify services exist (container also registers itself as +1)
86
- expect(serviceLocator.getManager().size()).toBe(4)
87
-
88
- // Clear all services
89
- await serviceLocator.clearAll()
90
-
91
- // Verify all services are cleared
92
- expect(serviceLocator.getManager().size()).toBe(0)
93
- expect(mockLogger.log).toHaveBeenCalledWith(
94
- '[Invalidator] Graceful clearing completed',
95
- )
96
- })
97
-
98
- it('should handle empty service locator', async () => {
99
- const serviceLocator = new ServiceLocator(globalRegistry, mockLogger)
100
- await serviceLocator.clearAll()
101
-
102
- expect(mockLogger.log).toHaveBeenCalledWith(
103
- '[Invalidator] No services to clear',
104
- )
105
- })
106
-
107
- it('should clear service from a request context', async () => {
108
- const serviceLocator = container.getServiceLocator()
109
-
110
- @Injectable({ scope: InjectableScope.Singleton })
111
- class ServiceA {
112
- name = 'ServiceA'
113
- }
114
-
115
- @Injectable({ scope: InjectableScope.Request })
116
- class ServiceB {
117
- serviceA = inject(ServiceA)
118
- name = 'ServiceB'
119
- }
120
-
121
- const requestId = 'test-request'
122
- const scoped = container.beginRequest(requestId)
123
- const serviceB = await scoped.get(ServiceB)
124
- expect(serviceB).toBeDefined()
125
-
126
- await scoped.invalidate(await container.get(ServiceA))
127
- // Container itself remains
128
- expect(serviceLocator.getManager().size()).toBe(1)
129
- await serviceLocator.clearAll()
130
- await scoped.endRequest()
131
- })
132
-
133
- it('should clear request contexts via ScopedContainer', async () => {
134
- // Create a request context
135
- const requestId = 'test-request'
136
- const scoped = container.beginRequest(requestId)
137
-
138
- // Create Injectable service with request scope
139
- @Injectable({ scope: InjectableScope.Request })
140
- class TestService {
141
- name = 'TestService'
142
- }
143
-
144
- await scoped.get(TestService)
145
-
146
- // Verify request context exists
147
- expect(container.hasActiveRequest(requestId)).toBe(true)
148
-
149
- // End request to clean up
150
- await scoped.endRequest()
151
-
152
- // Verify request context is cleared
153
- expect(container.hasActiveRequest(requestId)).toBe(false)
154
- })
155
-
156
- it('should track active request contexts', async () => {
157
- // Create a request context
158
- const requestId = 'test-request'
159
- const scoped = container.beginRequest(requestId)
160
-
161
- // Verify request context exists
162
- expect(container.hasActiveRequest(requestId)).toBe(true)
163
-
164
- // End request
165
- await scoped.endRequest()
166
-
167
- // Verify request context is gone
168
- expect(container.hasActiveRequest(requestId)).toBe(false)
169
- })
170
-
171
- it('should handle services with dependencies correctly', async () => {
172
- const serviceLocator = container.getServiceLocator()
173
-
174
- // Create Injectable services
175
- @Injectable({ scope: InjectableScope.Singleton })
176
- class ServiceA {
177
- name = 'ServiceA'
178
- }
179
-
180
- @Injectable({ scope: InjectableScope.Singleton })
181
- class ServiceB {
182
- serviceA = inject(ServiceA)
183
- name = 'ServiceB'
184
- }
185
-
186
- // Create instances using container
187
- await container.get(ServiceB)
188
-
189
- // Clear all services
190
- await serviceLocator.clearAll()
191
-
192
- // Verify all services are cleared
193
- expect(serviceLocator.getManager().size()).toBe(0)
194
- })
195
-
196
- it('should respect maxRounds option', async () => {
197
- const serviceLocator = container.getServiceLocator()
198
-
199
- // Create Injectable service
200
- @Injectable({ scope: InjectableScope.Singleton })
201
- class TestService {
202
- name = 'TestService'
203
- }
204
-
205
- await container.get(TestService)
206
-
207
- // Clear with a very low maxRounds to test the limit
208
- await serviceLocator.clearAll({ maxRounds: 1 })
209
-
210
- // Should still clear the service
211
- expect(serviceLocator.getManager().size()).toBe(0)
212
- })
213
-
214
- it('should clear services with dependencies in correct order', async () => {
215
- const serviceLocator = container.getServiceLocator()
216
-
217
- // Create services with dependencies
218
- @Injectable({ scope: InjectableScope.Singleton })
219
- class DatabaseService {
220
- name = 'DatabaseService'
221
- }
222
-
223
- @Injectable({ scope: InjectableScope.Singleton })
224
- class UserService {
225
- public database = inject(DatabaseService)
226
- name = 'UserService'
227
- }
228
-
229
- @Injectable({ scope: InjectableScope.Singleton })
230
- class AuthService {
231
- public userService = inject(UserService)
232
- name = 'AuthService'
233
- }
234
-
235
- // Create instances (this will establish dependencies)
236
- await container.get(AuthService)
237
- await container.get(UserService)
238
- await container.get(DatabaseService)
239
-
240
- // Verify services exist (container also registers itself as +1)
241
- expect(serviceLocator.getManager().size()).toBe(4)
242
-
243
- // Clear all services - should clear in dependency order
244
- await serviceLocator.clearAll()
245
-
246
- // Verify all services are cleared
247
- expect(serviceLocator.getManager().size()).toBe(0)
248
- })
249
-
250
- it('should handle services with destroy listeners', async () => {
251
- const serviceLocator = container.getServiceLocator()
252
-
253
- let destroyCalled = false
254
- @Injectable({ scope: InjectableScope.Singleton })
255
- class TestService implements OnServiceDestroy {
256
- name = 'TestService'
257
-
258
- constructor() {
259
- // Simulate a service that needs cleanup
260
- }
261
-
262
- async onServiceDestroy() {
263
- destroyCalled = true
264
- }
265
- }
266
-
267
- await container.get(TestService)
268
-
269
- // Clear all services
270
- await serviceLocator.clearAll()
271
-
272
- // Verify all services are cleared
273
- expect(serviceLocator.getManager().size()).toBe(0)
274
- expect(destroyCalled).toBe(true)
275
- })
276
- })
277
-
278
- describe('Mixed Scope Services', () => {
279
- let container: Container
280
- let mockLogger: Console
281
-
282
- beforeEach(() => {
283
- mockLogger = {
284
- log: vi.fn(),
285
- warn: vi.fn(),
286
- error: vi.fn(),
287
- debug: vi.fn(),
288
- trace: vi.fn(),
289
- } as any
290
-
291
- container = new Container(globalRegistry, mockLogger)
292
- })
293
-
294
- describe('Services with dependencies across different scopes', () => {
295
- it('should handle Singleton service depending on Transient service', async () => {
296
- // Create Transient service
297
- @Injectable({ scope: InjectableScope.Transient })
298
- class TransientService {
299
- id = Math.random().toString(36).substr(2, 9)
300
- name = 'TransientService'
301
- }
302
-
303
- // Create Singleton service that depends on Transient service
304
- @Injectable({ scope: InjectableScope.Singleton })
305
- class SingletonService {
306
- transientService = asyncInject(TransientService)
307
- name = 'SingletonService'
308
- }
309
-
310
- // Get instances
311
- const singleton1 = await container.get(SingletonService)
312
- const singleton2 = await container.get(SingletonService)
313
-
314
- expect(singleton1).toBe(singleton2) // Same singleton instance
315
-
316
- // Get the actual transient service instances (asyncInject returns Promises)
317
- const transient1 = await singleton1.transientService
318
- const transient2 = await singleton2.transientService
319
-
320
- // Note: Since Singleton is created once, both references point to the same Transient instance
321
- // This is expected behavior - the Transient service is created once during Singleton instantiation
322
- expect(transient1).toBe(transient2) // Same transient instance (created during singleton instantiation)
323
- })
324
-
325
- it('should handle Request service depending on Singleton service', async () => {
326
- // Create Singleton service
327
- @Injectable({ scope: InjectableScope.Singleton })
328
- class SingletonService {
329
- id = Math.random().toString(36).substr(2, 9)
330
- name = 'SingletonService'
331
- }
332
-
333
- // Create Request service that depends on Singleton service
334
- @Injectable({ scope: InjectableScope.Request })
335
- class RequestService {
336
- singletonService = inject(SingletonService)
337
- name = 'RequestService'
338
- }
339
-
340
- // Begin request context
341
- const scoped1 = container.beginRequest('test-request-1')
342
-
343
- // Get instances within the same request
344
- const request1 = await scoped1.get(RequestService)
345
- const request2 = await scoped1.get(RequestService)
346
-
347
- expect(request1).toBe(request2) // Same request-scoped instance
348
- expect(request1.singletonService).toBe(request2.singletonService) // Same singleton instance
349
-
350
- // End request and start new one
351
- await scoped1.endRequest()
352
- const scoped2 = container.beginRequest('test-request-2')
353
-
354
- // Get instance in new request
355
- const request3 = await scoped2.get(RequestService)
356
-
357
- expect(request1).not.toBe(request3) // Different request-scoped instances
358
- expect(request1.singletonService).toBe(request3.singletonService) // Same singleton instance
359
-
360
- await scoped2.endRequest()
361
- })
362
-
363
- it('should handle Transient service depending on Request service', async () => {
364
- // Create Request service
365
- @Injectable({ scope: InjectableScope.Request })
366
- class RequestService {
367
- id = Math.random().toString(36).substr(2, 9)
368
- name = 'RequestService'
369
- }
370
-
371
- // Create Transient service that depends on Request service
372
- @Injectable({ scope: InjectableScope.Transient })
373
- class TransientService {
374
- requestService = inject(RequestService)
375
- name = 'TransientService'
376
- }
377
-
378
- // Begin request context
379
- const scoped = container.beginRequest('test-request')
380
-
381
- // Get multiple transient instances
382
- const transient1 = await scoped.get(TransientService)
383
- const transient2 = await scoped.get(TransientService)
384
-
385
- expect(transient1).not.toBe(transient2) // Different transient instances
386
-
387
- // Get the actual request service instances
388
- const requestService1 = transient1.requestService
389
- const requestService2 = transient2.requestService
390
- expect(requestService1).toBe(requestService2) // Same request-scoped instance
391
-
392
- await scoped.endRequest()
393
- })
394
-
395
- it('should handle complex dependency chain across all scopes', async () => {
396
- // Create services with different scopes
397
- @Injectable({ scope: InjectableScope.Singleton })
398
- class DatabaseService {
399
- id = Math.random().toString(36).substr(2, 9)
400
- name = 'DatabaseService'
401
- }
402
-
403
- @Injectable({ scope: InjectableScope.Request })
404
- class UserSessionService {
405
- database = inject(DatabaseService)
406
- id = Math.random().toString(36).substr(2, 9)
407
- name = 'UserSessionService'
408
- }
409
-
410
- @Injectable({ scope: InjectableScope.Transient })
411
- class UserActionService {
412
- session = inject(UserSessionService)
413
- database = inject(DatabaseService)
414
- id = Math.random().toString(36).substr(2, 9)
415
- name = 'UserActionService'
416
- }
417
-
418
- @Injectable({ scope: InjectableScope.Singleton })
419
- class UserManagerService {
420
- database = inject(DatabaseService)
421
- name = 'UserManagerService'
422
- }
423
-
424
- // Begin request context
425
- const scoped = container.beginRequest('complex-request')
426
-
427
- // Get instances
428
- const action1 = await scoped.get(UserActionService)
429
- const action2 = await scoped.get(UserActionService)
430
- const manager = await container.get(UserManagerService)
431
-
432
- // Verify instances are created
433
- expect(action1).toBeDefined()
434
- expect(action2).toBeDefined()
435
- expect(manager).toBeDefined()
436
-
437
- // Verify scope behavior - check if dependencies are properly injected
438
- expect(action1).not.toBe(action2) // Different transient instances
439
-
440
- // Get the actual dependency instances
441
- const action1Database = action1.database
442
- const action2Database = action2.database
443
- const action1Session = action1.session
444
- const action2Session = action2.session
445
-
446
- expect(action1Database).toBe(action2Database) // Same singleton instance
447
- expect(action1Database).toBe(manager.database) // Same singleton instance
448
-
449
- // Check if session dependency is properly injected
450
- expect(action1Session).toBe(action2Session) // Same request-scoped instance
451
- expect(action1Session.database).toBe(action1Database) // Same singleton instance
452
-
453
- // End request and start new one
454
- await scoped.endRequest()
455
- const scoped2 = container.beginRequest('complex-request-2')
456
-
457
- // Get instances in new request
458
- const action3 = await scoped2.get(UserActionService)
459
- const manager2 = await container.get(UserManagerService)
460
-
461
- // Verify scope behavior across requests
462
- expect(action1).not.toBe(action3) // Different transient instances
463
-
464
- // Get the actual dependency instances for the new request
465
- const action3Database = action3.database
466
- const action3Session = action3.session
467
-
468
- expect(action1Database).toBe(action3Database) // Same singleton instance
469
- expect(manager).toBe(manager2) // Same singleton instance
470
-
471
- // Check if session dependency is properly injected in new request
472
- expect(action1Session).not.toBe(action3Session) // Different request-scoped instances
473
-
474
- await scoped2.endRequest()
475
- })
476
- })
477
-
478
- describe('Instance sharing and isolation', () => {
479
- it('should isolate Request-scoped instances between different requests', async () => {
480
- @Injectable({ scope: InjectableScope.Request })
481
- class RequestService {
482
- id = Math.random().toString(36).substr(2, 9)
483
- name = 'RequestService'
484
- }
485
-
486
- // First request
487
- const scoped1 = container.beginRequest('request-1')
488
- const service1 = await scoped1.get(RequestService)
489
- await scoped1.endRequest()
490
-
491
- // Second request
492
- const scoped2 = container.beginRequest('request-2')
493
- const service2 = await scoped2.get(RequestService)
494
- await scoped2.endRequest()
495
-
496
- expect(service1).not.toBe(service2) // Different instances
497
- expect(service1.id).not.toBe(service2.id) // Different IDs
498
- })
499
-
500
- it('should share Singleton instances across requests', async () => {
501
- @Injectable({ scope: InjectableScope.Singleton })
502
- class SingletonService {
503
- id = Math.random().toString(36).substr(2, 9)
504
- name = 'SingletonService'
505
- }
506
-
507
- // First request
508
- const scoped1 = container.beginRequest('request-1')
509
- const service1 = await scoped1.get(SingletonService)
510
- await scoped1.endRequest()
511
-
512
- // Second request
513
- const scoped2 = container.beginRequest('request-2')
514
- const service2 = await scoped2.get(SingletonService)
515
- await scoped2.endRequest()
516
-
517
- expect(service1).toBe(service2) // Same instance
518
- expect(service1.id).toBe(service2.id) // Same ID
519
- })
520
-
521
- it('should create new Transient instances every time', async () => {
522
- @Injectable({ scope: InjectableScope.Transient })
523
- class TransientService {
524
- id = Math.random().toString(36).substr(2, 9)
525
- name = 'TransientService'
526
- }
527
-
528
- const service1 = await container.get(TransientService)
529
- const service2 = await container.get(TransientService)
530
- const service3 = await container.get(TransientService)
531
-
532
- expect(service1).not.toBe(service2) // Different instances
533
- expect(service1).not.toBe(service3) // Different instances
534
- expect(service2).not.toBe(service3) // Different instances
535
- expect(service1.id).not.toBe(service2.id) // Different IDs
536
- expect(service1.id).not.toBe(service3.id) // Different IDs
537
- expect(service2.id).not.toBe(service3.id) // Different IDs
538
- })
539
- })
540
-
541
- describe('Request context management with mixed scopes', () => {
542
- it('should properly clean up Request-scoped instances when ending request', async () => {
543
- @Injectable({ scope: InjectableScope.Request })
544
- class RequestService {
545
- id = Math.random().toString(36).substr(2, 9)
546
- name = 'RequestService'
547
- }
548
-
549
- @Injectable({ scope: InjectableScope.Singleton })
550
- class SingletonService {
551
- id = Math.random().toString(36).substr(2, 9)
552
- name = 'SingletonService'
553
- }
554
-
555
- const scoped = container.beginRequest('cleanup-test')
556
-
557
- // Create instances
558
- const _requestService = await scoped.get(RequestService)
559
- const singletonService = await scoped.get(SingletonService)
560
-
561
- // Verify request context exists
562
- expect(container.hasActiveRequest('cleanup-test')).toBe(true)
563
-
564
- // End request
565
- await scoped.endRequest()
566
-
567
- // Verify request context is cleared
568
- expect(container.hasActiveRequest('cleanup-test')).toBe(false)
569
-
570
- // Singleton should still be available
571
- const singletonService2 = await container.get(SingletonService)
572
- expect(singletonService).toBe(singletonService2) // Same singleton instance
573
-
574
- // Request service should not be available (no current request context)
575
- await expect(container.get(RequestService)).rejects.toThrow(
576
- /Cannot resolve request-scoped service/,
577
- )
578
- })
579
-
580
- it('should handle parallel request contexts with mixed scopes', async () => {
581
- @Injectable({ scope: InjectableScope.Request })
582
- class RequestService {
583
- id = Math.random().toString(36).substr(2, 9)
584
- name = 'RequestService'
585
- }
586
-
587
- @Injectable({ scope: InjectableScope.Singleton })
588
- class SingletonService {
589
- id = Math.random().toString(36).substr(2, 9)
590
- name = 'SingletonService'
591
- }
592
-
593
- // First request
594
- const scoped1 = container.beginRequest('outer-request')
595
- const requestService1 = await scoped1.get(RequestService)
596
- const singletonService1 = await scoped1.get(SingletonService)
597
-
598
- // Second request (parallel)
599
- const scoped2 = container.beginRequest('inner-request')
600
- const requestService2 = await scoped2.get(RequestService)
601
- const singletonService2 = await scoped2.get(SingletonService)
602
-
603
- // Verify instances
604
- expect(requestService1).not.toBe(requestService2) // Different request instances
605
- expect(singletonService1).toBe(singletonService2) // Same singleton instance
606
-
607
- // End both requests
608
- await scoped2.endRequest()
609
- await scoped1.endRequest()
610
-
611
- // Verify no active contexts
612
- expect(container.hasActiveRequest('outer-request')).toBe(false)
613
- expect(container.hasActiveRequest('inner-request')).toBe(false)
614
- })
615
-
616
- it('should handle concurrent requests with mixed scopes', async () => {
617
- @Injectable({ scope: InjectableScope.Request })
618
- class RequestService {
619
- id = Math.random().toString(36).substr(2, 9)
620
- name = 'RequestService'
621
- }
622
-
623
- @Injectable({ scope: InjectableScope.Singleton })
624
- class SingletonService {
625
- id = Math.random().toString(36).substr(2, 9)
626
- name = 'SingletonService'
627
- }
628
-
629
- // Start multiple requests sequentially
630
- const requestIds = ['req-1', 'req-2', 'req-3']
631
- const results = []
632
-
633
- for (const requestId of requestIds) {
634
- const scoped = container.beginRequest(requestId)
635
- const requestService = await scoped.get(RequestService)
636
- const singletonService = await scoped.get(SingletonService)
637
- await scoped.endRequest()
638
- results.push({ requestService, singletonService, requestId })
639
- }
640
-
641
- // Verify all requests completed successfully
642
- results.forEach(({ requestService, singletonService }) => {
643
- expect(requestService).toBeDefined()
644
- expect(singletonService).toBeDefined()
645
- })
646
-
647
- // Verify request services are different
648
- expect(results[0].requestService).not.toBe(results[1].requestService)
649
- expect(results[0].requestService).not.toBe(results[2].requestService)
650
- expect(results[1].requestService).not.toBe(results[2].requestService)
651
-
652
- // Verify singleton services are the same
653
- expect(results[0].singletonService).toBe(results[1].singletonService)
654
- expect(results[0].singletonService).toBe(results[2].singletonService)
655
- expect(results[1].singletonService).toBe(results[2].singletonService)
656
- })
657
- })
658
-
659
- describe('Error handling with mixed scopes', () => {
660
- it('should handle Request-scoped service without request context', async () => {
661
- @Injectable({ scope: InjectableScope.Request })
662
- class RequestService {
663
- name = 'RequestService'
664
- }
665
-
666
- // Try to get Request-scoped service without request context
667
- await expect(container.get(RequestService)).rejects.toThrow(
668
- /Cannot resolve request-scoped service/,
669
- )
670
- })
671
-
672
- it('should handle service instantiation errors in mixed scope scenario', async () => {
673
- @Injectable({ scope: InjectableScope.Singleton })
674
- class SingletonService {
675
- constructor() {
676
- throw new Error('Singleton creation failed')
677
- }
678
- name = 'SingletonService'
679
- }
680
-
681
- @Injectable({ scope: InjectableScope.Request })
682
- class RequestService {
683
- singleton = inject(SingletonService)
684
- name = 'RequestService'
685
- }
686
-
687
- const scoped = container.beginRequest('error-test')
688
-
689
- // Try to get Request service that depends on failing Singleton
690
- await expect(scoped.get(RequestService)).rejects.toThrow(
691
- 'Singleton creation failed',
692
- )
693
-
694
- await scoped.endRequest()
695
- })
696
- })
697
- })
698
-
699
- describe('Injectable with Schema', () => {
700
- let container: Container
701
-
702
- beforeEach(() => {
703
- container = new Container(globalRegistry)
704
- })
705
-
706
- it('should work with simple schema definition', async () => {
707
- const configSchema = z.object({
708
- host: z.string(),
709
- port: z.number(),
710
- })
711
-
712
- @Injectable({ schema: configSchema })
713
- class DatabaseConfig {
714
- constructor(public readonly config: z.output<typeof configSchema>) {}
715
-
716
- getConnectionString() {
717
- return `${this.config.host}:${this.config.port}`
718
- }
719
- }
720
-
721
- const token = getInjectableToken(DatabaseConfig)
722
- const instance = await container.get(
723
- InjectionToken.bound(token, {
724
- host: 'localhost',
725
- port: 5432,
726
- }),
727
- )
728
-
729
- expect(instance).toBeInstanceOf(DatabaseConfig)
730
- // @ts-expect-error - instance is of type DatabaseConfig
731
- expect(instance.config).toEqual({ host: 'localhost', port: 5432 })
732
- // @ts-expect-error - instance is of type DatabaseConfig
733
- expect(instance.getConnectionString()).toBe('localhost:5432')
734
- })
735
-
736
- it('should work with schema and singleton scope', async () => {
737
- const apiSchema = z.object({
738
- apiKey: z.string(),
739
- baseUrl: z.string(),
740
- })
741
-
742
- @Injectable({ schema: apiSchema, scope: InjectableScope.Singleton })
743
- class ApiClient {
744
- constructor(public readonly config: z.output<typeof apiSchema>) {}
745
-
746
- getApiKey() {
747
- return this.config.apiKey
748
- }
749
- }
750
-
751
- const instance1 = await container.get(ApiClient, {
752
- apiKey: 'secret-key',
753
- baseUrl: 'https://api.example.com',
754
- })
755
- const instance2 = await container.get(ApiClient, {
756
- apiKey: 'secret-key',
757
- baseUrl: 'https://api.example.com',
758
- })
759
-
760
- expect(instance1).toBe(instance2) // Same singleton instance
761
- expect(instance1.getApiKey()).toBe('secret-key')
762
- })
763
-
764
- it('should work with schema and transient scope', async () => {
765
- const loggerSchema = z.object({
766
- level: z.enum(['debug', 'info', 'warn', 'error']),
767
- prefix: z.string(),
768
- })
769
-
770
- @Injectable({ schema: loggerSchema, scope: InjectableScope.Transient })
771
- class Logger {
772
- constructor(public readonly config: z.output<typeof loggerSchema>) {}
773
-
774
- log(message: string) {
775
- return `[${this.config.prefix}] ${message}`
776
- }
777
- }
778
-
779
- const instance1 = await container.get(Logger, {
780
- level: 'info' as const,
781
- prefix: 'APP',
782
- })
783
- const instance2 = await container.get(Logger, {
784
- level: 'info' as const,
785
- prefix: 'APP',
786
- })
787
-
788
- expect(instance1).not.toBe(instance2) // Different transient instances
789
- expect(instance1.log('test')).toBe('[APP] test')
790
- expect(instance2.log('test')).toBe('[APP] test')
791
- })
792
-
793
- it('should work with schema and dependency injection', async () => {
794
- const dbConfigSchema = z.object({
795
- connectionString: z.string(),
796
- })
797
-
798
- @Injectable({ schema: dbConfigSchema })
799
- class DatabaseConfig {
800
- constructor(public readonly config: z.output<typeof dbConfigSchema>) {}
801
- }
802
-
803
- @Injectable()
804
- class DatabaseService {
805
- private dbConfig = inject(DatabaseConfig, {
806
- connectionString: 'postgres://localhost:5432/mydb',
807
- })
808
-
809
- getConnectionString() {
810
- return this.dbConfig.config.connectionString
811
- }
812
- }
813
-
814
- const instance = await container.get(DatabaseService)
815
-
816
- expect(instance).toBeInstanceOf(DatabaseService)
817
- expect(instance.getConnectionString()).toBe(
818
- 'postgres://localhost:5432/mydb',
819
- )
820
- })
821
-
822
- it('should work with schema and async dependency injection', async () => {
823
- const cacheConfigSchema = z.object({
824
- ttl: z.number(),
825
- maxSize: z.number(),
826
- })
827
-
828
- @Injectable({ schema: cacheConfigSchema })
829
- class CacheConfig {
830
- constructor(
831
- public readonly config: z.output<typeof cacheConfigSchema>,
832
- ) {}
833
- }
834
-
835
- @Injectable()
836
- class CacheService {
837
- private cacheConfig = asyncInject(CacheConfig, {
838
- ttl: 3600,
839
- maxSize: 1000,
840
- })
841
-
842
- async getConfig() {
843
- const config = await this.cacheConfig
844
- return config.config
845
- }
846
- }
847
-
848
- const instance = await container.get(CacheService)
849
-
850
- expect(instance).toBeInstanceOf(CacheService)
851
- const config = await instance.getConfig()
852
- expect(config).toEqual({ ttl: 3600, maxSize: 1000 })
853
- })
854
-
855
- it('should validate schema when using bound tokens', async () => {
856
- const strictSchema = z.object({
857
- required: z.string(),
858
- optional: z.number().optional(),
859
- })
860
-
861
- @Injectable({ schema: strictSchema })
862
- class StrictService {
863
- constructor(public readonly config: z.output<typeof strictSchema>) {}
864
- }
865
-
866
- // Valid configuration
867
- const instance1 = await container.get(StrictService, {
868
- required: 'value',
869
- optional: 42,
870
- })
871
-
872
- expect(instance1).toBeInstanceOf(StrictService)
873
- expect(instance1.config).toEqual({ required: 'value', optional: 42 })
874
-
875
- // Valid with optional field missing
876
- const instance2 = await container.get(StrictService, {
877
- required: 'another value',
878
- })
879
-
880
- expect(instance2).toBeInstanceOf(StrictService)
881
- expect(instance2.config).toEqual({ required: 'another value' })
882
- })
883
-
884
- it('should work with complex nested schemas', async () => {
885
- const nestedSchema = z.object({
886
- database: z.object({
887
- host: z.string(),
888
- port: z.number(),
889
- credentials: z.object({
890
- username: z.string(),
891
- password: z.string(),
892
- }),
893
- }),
894
- cache: z.object({
895
- enabled: z.boolean(),
896
- ttl: z.number(),
897
- }),
898
- })
899
-
900
- @Injectable({ schema: nestedSchema })
901
- class AppConfig {
902
- constructor(public readonly config: z.output<typeof nestedSchema>) {}
903
-
904
- getDatabaseHost() {
905
- return this.config.database.host
906
- }
907
-
908
- isCacheEnabled() {
909
- return this.config.cache.enabled
910
- }
911
- }
912
-
913
- const instance = await container.get(AppConfig, {
914
- database: {
915
- host: 'db.example.com',
916
- port: 5432,
917
- credentials: {
918
- username: 'admin',
919
- password: 'secret',
920
- },
921
- },
922
- cache: {
923
- enabled: true,
924
- ttl: 300,
925
- },
926
- })
927
-
928
- expect(instance).toBeInstanceOf(AppConfig)
929
- expect(instance.getDatabaseHost()).toBe('db.example.com')
930
- expect(instance.isCacheEnabled()).toBe(true)
931
- })
932
-
933
- it('should work with schema in request-scoped services', async () => {
934
- const userContextSchema = z.object({
935
- userId: z.string(),
936
- sessionId: z.string(),
937
- })
938
-
939
- @Injectable({
940
- schema: userContextSchema,
941
- scope: InjectableScope.Request,
942
- })
943
- class UserContext {
944
- constructor(
945
- public readonly context: z.output<typeof userContextSchema>,
946
- ) {}
947
-
948
- getUserId() {
949
- return this.context.userId
950
- }
951
- }
952
-
953
- const scoped = container.beginRequest('test-request')
954
-
955
- const instance = await scoped.get(UserContext, {
956
- userId: 'user-123',
957
- sessionId: 'session-456',
958
- })
959
-
960
- expect(instance).toBeInstanceOf(UserContext)
961
- expect(instance.getUserId()).toBe('user-123')
962
-
963
- await scoped.endRequest()
964
- })
965
- })
966
- })