@navios/di 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +117 -17
  3. package/lib/browser/container/abstract-container.d.mts +112 -0
  4. package/lib/browser/container/abstract-container.d.mts.map +1 -0
  5. package/lib/browser/container/abstract-container.mjs +100 -0
  6. package/lib/browser/container/abstract-container.mjs.map +1 -0
  7. package/lib/browser/container/container.d.mts +100 -0
  8. package/lib/browser/container/container.d.mts.map +1 -0
  9. package/lib/browser/container/container.mjs +424 -0
  10. package/lib/browser/container/container.mjs.map +1 -0
  11. package/lib/browser/container/scoped-container.d.mts +93 -0
  12. package/lib/browser/container/scoped-container.d.mts.map +1 -0
  13. package/lib/browser/container/scoped-container.mjs +119 -0
  14. package/lib/browser/container/scoped-container.mjs.map +1 -0
  15. package/lib/browser/decorators/factory.decorator.d.mts +26 -0
  16. package/lib/browser/decorators/factory.decorator.d.mts.map +1 -0
  17. package/lib/browser/decorators/factory.decorator.mjs +20 -0
  18. package/lib/browser/decorators/factory.decorator.mjs.map +1 -0
  19. package/lib/browser/decorators/injectable.decorator.d.mts +38 -0
  20. package/lib/browser/decorators/injectable.decorator.d.mts.map +1 -0
  21. package/lib/browser/decorators/injectable.decorator.mjs +21 -0
  22. package/lib/browser/decorators/injectable.decorator.mjs.map +1 -0
  23. package/lib/browser/enums/injectable-scope.enum.d.mts +18 -0
  24. package/lib/browser/enums/injectable-scope.enum.d.mts.map +1 -0
  25. package/lib/browser/enums/injectable-scope.enum.mjs +20 -0
  26. package/lib/browser/enums/injectable-scope.enum.mjs.map +1 -0
  27. package/lib/browser/enums/injectable-type.enum.d.mts +8 -0
  28. package/lib/browser/enums/injectable-type.enum.d.mts.map +1 -0
  29. package/lib/browser/enums/injectable-type.enum.mjs +10 -0
  30. package/lib/browser/enums/injectable-type.enum.mjs.map +1 -0
  31. package/lib/browser/errors/di-error.d.mts +43 -0
  32. package/lib/browser/errors/di-error.d.mts.map +1 -0
  33. package/lib/browser/errors/di-error.mjs +98 -0
  34. package/lib/browser/errors/di-error.mjs.map +1 -0
  35. package/lib/browser/event-emitter.d.mts +16 -0
  36. package/lib/browser/event-emitter.d.mts.map +1 -0
  37. package/lib/browser/event-emitter.mjs +320 -0
  38. package/lib/browser/event-emitter.mjs.map +1 -0
  39. package/lib/browser/index.d.mts +37 -1508
  40. package/lib/browser/index.mjs +29 -2650
  41. package/lib/browser/interfaces/container.interface.d.mts +59 -0
  42. package/lib/browser/interfaces/container.interface.d.mts.map +1 -0
  43. package/lib/browser/interfaces/factory.interface.d.mts +14 -0
  44. package/lib/browser/interfaces/factory.interface.d.mts.map +1 -0
  45. package/lib/browser/interfaces/on-service-destroy.interface.d.mts +7 -0
  46. package/lib/browser/interfaces/on-service-destroy.interface.d.mts.map +1 -0
  47. package/lib/browser/interfaces/on-service-init.interface.d.mts +7 -0
  48. package/lib/browser/interfaces/on-service-init.interface.d.mts.map +1 -0
  49. package/lib/browser/internal/context/async-local-storage.browser.mjs +20 -0
  50. package/lib/browser/internal/context/async-local-storage.browser.mjs.map +1 -0
  51. package/lib/browser/internal/context/async-local-storage.d.mts +9 -0
  52. package/lib/browser/internal/context/async-local-storage.d.mts.map +1 -0
  53. package/lib/browser/internal/context/async-local-storage.types.d.mts +11 -0
  54. package/lib/browser/internal/context/async-local-storage.types.d.mts.map +1 -0
  55. package/lib/browser/internal/context/factory-context.d.mts +23 -0
  56. package/lib/browser/internal/context/factory-context.d.mts.map +1 -0
  57. package/lib/browser/internal/context/resolution-context.d.mts +43 -0
  58. package/lib/browser/internal/context/resolution-context.d.mts.map +1 -0
  59. package/lib/browser/internal/context/resolution-context.mjs +56 -0
  60. package/lib/browser/internal/context/resolution-context.mjs.map +1 -0
  61. package/lib/browser/internal/context/service-initialization-context.d.mts +48 -0
  62. package/lib/browser/internal/context/service-initialization-context.d.mts.map +1 -0
  63. package/lib/browser/internal/context/sync-local-storage.mjs +53 -0
  64. package/lib/browser/internal/context/sync-local-storage.mjs.map +1 -0
  65. package/lib/browser/internal/core/instance-resolver.d.mts +119 -0
  66. package/lib/browser/internal/core/instance-resolver.d.mts.map +1 -0
  67. package/lib/browser/internal/core/instance-resolver.mjs +306 -0
  68. package/lib/browser/internal/core/instance-resolver.mjs.map +1 -0
  69. package/lib/browser/internal/core/name-resolver.d.mts +52 -0
  70. package/lib/browser/internal/core/name-resolver.d.mts.map +1 -0
  71. package/lib/browser/internal/core/name-resolver.mjs +118 -0
  72. package/lib/browser/internal/core/name-resolver.mjs.map +1 -0
  73. package/lib/browser/internal/core/scope-tracker.d.mts +65 -0
  74. package/lib/browser/internal/core/scope-tracker.d.mts.map +1 -0
  75. package/lib/browser/internal/core/scope-tracker.mjs +120 -0
  76. package/lib/browser/internal/core/scope-tracker.mjs.map +1 -0
  77. package/lib/browser/internal/core/service-initializer.d.mts +44 -0
  78. package/lib/browser/internal/core/service-initializer.d.mts.map +1 -0
  79. package/lib/browser/internal/core/service-initializer.mjs +109 -0
  80. package/lib/browser/internal/core/service-initializer.mjs.map +1 -0
  81. package/lib/browser/internal/core/service-invalidator.d.mts +81 -0
  82. package/lib/browser/internal/core/service-invalidator.d.mts.map +1 -0
  83. package/lib/browser/internal/core/service-invalidator.mjs +142 -0
  84. package/lib/browser/internal/core/service-invalidator.mjs.map +1 -0
  85. package/lib/browser/internal/core/token-resolver.d.mts +54 -0
  86. package/lib/browser/internal/core/token-resolver.d.mts.map +1 -0
  87. package/lib/browser/internal/core/token-resolver.mjs +77 -0
  88. package/lib/browser/internal/core/token-resolver.mjs.map +1 -0
  89. package/lib/browser/internal/holder/holder-storage.interface.d.mts +99 -0
  90. package/lib/browser/internal/holder/holder-storage.interface.d.mts.map +1 -0
  91. package/lib/browser/internal/holder/instance-holder.d.mts +101 -0
  92. package/lib/browser/internal/holder/instance-holder.d.mts.map +1 -0
  93. package/lib/browser/internal/holder/instance-holder.mjs +19 -0
  94. package/lib/browser/internal/holder/instance-holder.mjs.map +1 -0
  95. package/lib/browser/internal/holder/unified-storage.d.mts +53 -0
  96. package/lib/browser/internal/holder/unified-storage.d.mts.map +1 -0
  97. package/lib/browser/internal/holder/unified-storage.mjs +144 -0
  98. package/lib/browser/internal/holder/unified-storage.mjs.map +1 -0
  99. package/lib/browser/internal/lifecycle/circular-detector.d.mts +39 -0
  100. package/lib/browser/internal/lifecycle/circular-detector.d.mts.map +1 -0
  101. package/lib/browser/internal/lifecycle/circular-detector.mjs +55 -0
  102. package/lib/browser/internal/lifecycle/circular-detector.mjs.map +1 -0
  103. package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts +18 -0
  104. package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts.map +1 -0
  105. package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs +43 -0
  106. package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs.map +1 -0
  107. package/lib/browser/internal/stub-factory-class.d.mts +14 -0
  108. package/lib/browser/internal/stub-factory-class.d.mts.map +1 -0
  109. package/lib/browser/internal/stub-factory-class.mjs +18 -0
  110. package/lib/browser/internal/stub-factory-class.mjs.map +1 -0
  111. package/lib/browser/symbols/injectable-token.d.mts +5 -0
  112. package/lib/browser/symbols/injectable-token.d.mts.map +1 -0
  113. package/lib/browser/symbols/injectable-token.mjs +6 -0
  114. package/lib/browser/symbols/injectable-token.mjs.map +1 -0
  115. package/lib/browser/token/injection-token.d.mts +55 -0
  116. package/lib/browser/token/injection-token.d.mts.map +1 -0
  117. package/lib/browser/token/injection-token.mjs +100 -0
  118. package/lib/browser/token/injection-token.mjs.map +1 -0
  119. package/lib/browser/token/registry.d.mts +37 -0
  120. package/lib/browser/token/registry.d.mts.map +1 -0
  121. package/lib/browser/token/registry.mjs +86 -0
  122. package/lib/browser/token/registry.mjs.map +1 -0
  123. package/lib/browser/utils/default-injectors.d.mts +12 -0
  124. package/lib/browser/utils/default-injectors.d.mts.map +1 -0
  125. package/lib/browser/utils/default-injectors.mjs +13 -0
  126. package/lib/browser/utils/default-injectors.mjs.map +1 -0
  127. package/lib/browser/utils/get-injectable-token.d.mts +9 -0
  128. package/lib/browser/utils/get-injectable-token.d.mts.map +1 -0
  129. package/lib/browser/utils/get-injectable-token.mjs +13 -0
  130. package/lib/browser/utils/get-injectable-token.mjs.map +1 -0
  131. package/lib/browser/utils/get-injectors.d.mts +55 -0
  132. package/lib/browser/utils/get-injectors.d.mts.map +1 -0
  133. package/lib/browser/utils/get-injectors.mjs +121 -0
  134. package/lib/browser/utils/get-injectors.mjs.map +1 -0
  135. package/lib/browser/utils/types.d.mts +23 -0
  136. package/lib/browser/utils/types.d.mts.map +1 -0
  137. package/lib/{container-Pb_Y4Z4x.mjs → container-8-z89TyQ.mjs} +1269 -1305
  138. package/lib/container-8-z89TyQ.mjs.map +1 -0
  139. package/lib/{container-BuAutHGg.d.mts → container-CNiqesCL.d.mts} +600 -569
  140. package/lib/container-CNiqesCL.d.mts.map +1 -0
  141. package/lib/{container-DnzgpfBe.cjs → container-CaY2fDuk.cjs} +1287 -1329
  142. package/lib/container-CaY2fDuk.cjs.map +1 -0
  143. package/lib/{container-oGTgX2iX.d.cts → container-D-0Ho3qL.d.cts} +601 -565
  144. package/lib/container-D-0Ho3qL.d.cts.map +1 -0
  145. package/lib/index.cjs +13 -15
  146. package/lib/index.cjs.map +1 -1
  147. package/lib/index.d.cts +58 -223
  148. package/lib/index.d.cts.map +1 -1
  149. package/lib/index.d.mts +62 -222
  150. package/lib/index.d.mts.map +1 -1
  151. package/lib/index.mjs +5 -6
  152. package/lib/index.mjs.map +1 -1
  153. package/lib/testing/index.cjs +569 -311
  154. package/lib/testing/index.cjs.map +1 -1
  155. package/lib/testing/index.d.cts +370 -41
  156. package/lib/testing/index.d.cts.map +1 -1
  157. package/lib/testing/index.d.mts +370 -41
  158. package/lib/testing/index.d.mts.map +1 -1
  159. package/lib/testing/index.mjs +568 -305
  160. package/lib/testing/index.mjs.map +1 -1
  161. package/package.json +2 -1
  162. package/src/__tests__/circular-detector.spec.mts +193 -0
  163. package/src/__tests__/concurrent.spec.mts +368 -0
  164. package/src/__tests__/container.spec.mts +32 -30
  165. package/src/__tests__/di-error.spec.mts +351 -0
  166. package/src/__tests__/e2e.browser.spec.mts +0 -4
  167. package/src/__tests__/e2e.spec.mts +10 -19
  168. package/src/__tests__/event-emitter.spec.mts +232 -109
  169. package/src/__tests__/get-injectors.spec.mts +250 -39
  170. package/src/__tests__/injection-token.spec.mts +293 -349
  171. package/src/__tests__/library-findings.spec.mts +8 -8
  172. package/src/__tests__/registry.spec.mts +358 -210
  173. package/src/__tests__/resolution-context.spec.mts +255 -0
  174. package/src/__tests__/scope-tracker.spec.mts +598 -0
  175. package/src/__tests__/scope-upgrade.spec.mts +808 -0
  176. package/src/__tests__/scoped-container.spec.mts +595 -0
  177. package/src/__tests__/test-container.spec.mts +293 -0
  178. package/src/__tests__/token-resolver.spec.mts +207 -0
  179. package/src/__tests__/unified-storage.spec.mts +535 -0
  180. package/src/__tests__/unit-test-container.spec.mts +405 -0
  181. package/src/__type-tests__/container.spec-d.mts +180 -0
  182. package/src/__type-tests__/factory.spec-d.mts +15 -3
  183. package/src/__type-tests__/inject.spec-d.mts +115 -20
  184. package/src/__type-tests__/injectable.spec-d.mts +69 -52
  185. package/src/__type-tests__/injection-token.spec-d.mts +176 -0
  186. package/src/__type-tests__/scoped-container.spec-d.mts +212 -0
  187. package/src/container/abstract-container.mts +327 -0
  188. package/src/container/container.mts +142 -170
  189. package/src/container/scoped-container.mts +126 -208
  190. package/src/decorators/factory.decorator.mts +16 -11
  191. package/src/decorators/injectable.decorator.mts +20 -16
  192. package/src/enums/index.mts +2 -2
  193. package/src/enums/injectable-scope.enum.mts +1 -0
  194. package/src/enums/injectable-type.enum.mts +1 -0
  195. package/src/errors/di-error.mts +96 -0
  196. package/src/event-emitter.mts +3 -27
  197. package/src/index.mts +6 -153
  198. package/src/interfaces/container.interface.mts +13 -0
  199. package/src/interfaces/factory.interface.mts +1 -1
  200. package/src/interfaces/index.mts +1 -1
  201. package/src/internal/context/async-local-storage.mts +3 -2
  202. package/src/internal/context/async-local-storage.types.mts +1 -0
  203. package/src/internal/context/factory-context.mts +1 -0
  204. package/src/internal/context/index.mts +3 -1
  205. package/src/internal/context/resolution-context.mts +1 -0
  206. package/src/internal/context/service-initialization-context.mts +43 -0
  207. package/src/internal/core/index.mts +5 -4
  208. package/src/internal/core/instance-resolver.mts +461 -292
  209. package/src/internal/core/name-resolver.mts +196 -0
  210. package/src/internal/core/scope-tracker.mts +242 -0
  211. package/src/internal/core/{instantiator.mts → service-initializer.mts} +51 -29
  212. package/src/internal/core/service-invalidator.mts +290 -0
  213. package/src/internal/core/{token-processor.mts → token-resolver.mts} +17 -88
  214. package/src/internal/holder/holder-storage.interface.mts +11 -5
  215. package/src/internal/holder/index.mts +2 -5
  216. package/src/internal/holder/instance-holder.mts +1 -3
  217. package/src/internal/holder/unified-storage.mts +245 -0
  218. package/src/internal/index.mts +2 -1
  219. package/src/internal/lifecycle/circular-detector.mts +1 -0
  220. package/src/internal/lifecycle/index.mts +1 -1
  221. package/src/internal/lifecycle/lifecycle-event-bus.mts +1 -0
  222. package/src/internal/stub-factory-class.mts +16 -0
  223. package/src/symbols/injectable-token.mts +3 -1
  224. package/src/testing/index.mts +2 -0
  225. package/src/testing/test-container.mts +546 -85
  226. package/src/testing/types.mts +117 -0
  227. package/src/testing/unit-test-container.mts +509 -0
  228. package/src/token/injection-token.mts +41 -4
  229. package/src/token/registry.mts +75 -9
  230. package/src/utils/default-injectors.mts +16 -0
  231. package/src/utils/get-injectable-token.mts +2 -3
  232. package/src/utils/get-injectors.mts +26 -15
  233. package/src/utils/index.mts +3 -1
  234. package/src/utils/types.mts +1 -0
  235. package/tsdown.config.mts +11 -1
  236. package/lib/browser/index.d.mts.map +0 -1
  237. package/lib/browser/index.mjs.map +0 -1
  238. package/lib/container-BuAutHGg.d.mts.map +0 -1
  239. package/lib/container-DnzgpfBe.cjs.map +0 -1
  240. package/lib/container-Pb_Y4Z4x.mjs.map +0 -1
  241. package/lib/container-oGTgX2iX.d.cts.map +0 -1
  242. package/src/__tests__/async-local-storage.browser.spec.mts +0 -166
  243. package/src/__tests__/async-local-storage.spec.mts +0 -333
  244. package/src/__tests__/errors.spec.mts +0 -87
  245. package/src/__tests__/factory.spec.mts +0 -137
  246. package/src/__tests__/injectable.spec.mts +0 -246
  247. package/src/__tests__/request-scope.spec.mts +0 -416
  248. package/src/__tests__/service-instantiator.spec.mts +0 -410
  249. package/src/__tests__/service-locator-event-bus.spec.mts +0 -242
  250. package/src/__tests__/service-locator-manager.spec.mts +0 -300
  251. package/src/__tests__/service-locator.spec.mts +0 -966
  252. package/src/__tests__/unified-api.spec.mts +0 -130
  253. package/src/browser.mts +0 -11
  254. package/src/injectors.mts +0 -18
  255. package/src/internal/context/request-context.mts +0 -214
  256. package/src/internal/core/invalidator.mts +0 -437
  257. package/src/internal/core/service-locator.mts +0 -202
  258. package/src/internal/holder/base-holder-manager.mts +0 -238
  259. package/src/internal/holder/holder-manager.mts +0 -85
  260. package/src/internal/holder/request-storage.mts +0 -134
  261. package/src/internal/holder/singleton-storage.mts +0 -105
  262. package/src/testing/README.md +0 -80
  263. package/src/testing/__tests__/test-container.spec.mts +0 -173
@@ -0,0 +1,212 @@
1
+ import { assertType, describe, test } from 'vitest'
2
+ import { z } from 'zod/v4'
3
+
4
+ import type { Factorable } from '../interfaces/index.mjs'
5
+
6
+ import { Container } from '../container/container.mjs'
7
+ import { Factory, Injectable } from '../decorators/index.mjs'
8
+ import { InjectionToken } from '../token/injection-token.mjs'
9
+
10
+ interface FooService {
11
+ makeFoo(): string
12
+ }
13
+
14
+ const simpleObjectSchema = z.object({
15
+ foo: z.string(),
16
+ })
17
+ const simpleOptionalObjectSchema = z
18
+ .object({
19
+ foo: z.string(),
20
+ })
21
+ .optional()
22
+
23
+ const typelessObjectToken = InjectionToken.create(
24
+ Symbol.for('Typeless object token'),
25
+ simpleObjectSchema,
26
+ )
27
+ const typelessOptionalObjectToken = InjectionToken.create(
28
+ Symbol.for('Typeless optional object token'),
29
+ simpleOptionalObjectSchema,
30
+ )
31
+
32
+ const typedObjectToken = InjectionToken.create<
33
+ FooService,
34
+ typeof simpleObjectSchema
35
+ >(Symbol.for('Typed object token'), simpleObjectSchema)
36
+ const typedOptionalObjectToken = InjectionToken.create<
37
+ FooService,
38
+ typeof simpleOptionalObjectSchema
39
+ >(Symbol.for('Typed optional object token'), simpleOptionalObjectSchema)
40
+
41
+ const typedToken = InjectionToken.create<FooService>(Symbol.for('Typed token'))
42
+
43
+ describe('ScopedContainer.get', () => {
44
+ describe('#1 Classes', () => {
45
+ test('simple class', async () => {
46
+ @Injectable()
47
+ class Foo {
48
+ makeFoo() {
49
+ return 'foo'
50
+ }
51
+ }
52
+
53
+ const container = new Container()
54
+ const scopedContainer = container.beginRequest('req-1')
55
+ assertType<Foo>(await scopedContainer.get(Foo))
56
+ })
57
+
58
+ test('class with required argument', async () => {
59
+ @Injectable({
60
+ schema: simpleObjectSchema,
61
+ })
62
+ class Foo {
63
+ constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
64
+ }
65
+
66
+ const container = new Container()
67
+ const scopedContainer = container.beginRequest('req-1')
68
+ assertType<Foo>(await scopedContainer.get(Foo, { foo: 'bar' }))
69
+ })
70
+
71
+ test('should fail if not compatible', async () => {
72
+ @Injectable({
73
+ schema: simpleObjectSchema,
74
+ })
75
+ class Foo {
76
+ constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
77
+ }
78
+
79
+ const container = new Container()
80
+ const scopedContainer = container.beginRequest('req-1')
81
+ // @ts-expect-error Should fail if not compatible
82
+ await scopedContainer.get(Foo, { test: 'bar' })
83
+ })
84
+
85
+ test('factory class returns unwrapped type', async () => {
86
+ @Factory()
87
+ class FooFactory implements Factorable<string> {
88
+ create() {
89
+ return 'created'
90
+ }
91
+ }
92
+
93
+ const container = new Container()
94
+ const scopedContainer = container.beginRequest('req-1')
95
+ // When getting a Factorable class, we get the created type, not the factory
96
+ assertType<string>(await scopedContainer.get(FooFactory))
97
+ })
98
+ })
99
+
100
+ test('#2 Token with required Schema', async () => {
101
+ const container = new Container()
102
+ const scopedContainer = container.beginRequest('req-1')
103
+
104
+ const result = await scopedContainer.get(typelessObjectToken, {
105
+ foo: 'bar',
106
+ })
107
+ assertType<unknown>(result)
108
+
109
+ const result2 = await scopedContainer.get(typedObjectToken, { foo: 'bar' })
110
+ assertType<FooService>(result2)
111
+
112
+ // @ts-expect-error We show error when we pass the wrong type
113
+ await scopedContainer.get(typedObjectToken, undefined)
114
+ })
115
+
116
+ test('#3 Token with optional Schema', async () => {
117
+ const container = new Container()
118
+ const scopedContainer = container.beginRequest('req-1')
119
+
120
+ const result = await scopedContainer.get(typelessOptionalObjectToken)
121
+ assertType<unknown>(result)
122
+
123
+ const result2 = await scopedContainer.get(typedOptionalObjectToken)
124
+ assertType<FooService>(result2)
125
+
126
+ const result3 = await scopedContainer.get(typedObjectToken)
127
+ // Special case when we pass the token without args
128
+ // We can only return an error string
129
+ assertType<'Error: Your token requires args: foo'>(result3)
130
+ })
131
+
132
+ test('#4 Token with no Schema', async () => {
133
+ const container = new Container()
134
+ const scopedContainer = container.beginRequest('req-1')
135
+
136
+ const result = await scopedContainer.get(typedToken)
137
+ assertType<FooService>(result)
138
+ })
139
+
140
+ test('#5 BoundInjectionToken', async () => {
141
+ const container = new Container()
142
+ const scopedContainer = container.beginRequest('req-1')
143
+
144
+ const boundToken = InjectionToken.bound(typedObjectToken, { foo: 'bar' })
145
+ const result = await scopedContainer.get(boundToken)
146
+ assertType<FooService>(result)
147
+ })
148
+
149
+ test('#6 FactoryInjectionToken', async () => {
150
+ const container = new Container()
151
+ const scopedContainer = container.beginRequest('req-1')
152
+
153
+ const factoryToken = InjectionToken.factory(typedObjectToken, async () => ({
154
+ foo: 'bar',
155
+ }))
156
+ const result = await scopedContainer.get(factoryToken)
157
+ assertType<FooService>(result)
158
+ })
159
+ })
160
+
161
+ describe('ScopedContainer methods', () => {
162
+ test('getRequestId returns string', () => {
163
+ const container = new Container()
164
+ const scopedContainer = container.beginRequest('req-1')
165
+ assertType<string>(scopedContainer.getRequestId())
166
+ })
167
+
168
+ test('getParent returns Container', () => {
169
+ const container = new Container()
170
+ const scopedContainer = container.beginRequest('req-1')
171
+ assertType<Container>(scopedContainer.getParent())
172
+ })
173
+
174
+ test('getMetadata returns any', () => {
175
+ const container = new Container()
176
+ const scopedContainer = container.beginRequest('req-1', { userId: '123' })
177
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
178
+ assertType<any | undefined>(scopedContainer.getMetadata('userId'))
179
+ })
180
+
181
+ test('setMetadata accepts any value', () => {
182
+ const container = new Container()
183
+ const scopedContainer = container.beginRequest('req-1')
184
+ scopedContainer.setMetadata('userId', '123')
185
+ scopedContainer.setMetadata('count', 42)
186
+ scopedContainer.setMetadata('data', { nested: true })
187
+ })
188
+
189
+ test('dispose returns Promise<void>', async () => {
190
+ const container = new Container()
191
+ const scopedContainer = container.beginRequest('req-1')
192
+ assertType<Promise<void>>(scopedContainer.dispose())
193
+ })
194
+
195
+ test('endRequest returns Promise<void>', async () => {
196
+ const container = new Container()
197
+ const scopedContainer = container.beginRequest('req-1')
198
+ assertType<Promise<void>>(scopedContainer.endRequest())
199
+ })
200
+
201
+ test('invalidate returns Promise<void>', async () => {
202
+ const container = new Container()
203
+ const scopedContainer = container.beginRequest('req-1')
204
+ assertType<Promise<void>>(scopedContainer.invalidate({}))
205
+ })
206
+
207
+ test('requestId property is string', () => {
208
+ const container = new Container()
209
+ const scopedContainer = container.beginRequest('req-1')
210
+ assertType<string>(scopedContainer.requestId)
211
+ })
212
+ })
@@ -0,0 +1,327 @@
1
+ import type { z, ZodType } from 'zod/v4'
2
+
3
+ import type { IContainer } from '../interfaces/container.interface.mjs'
4
+ import type { Factorable } from '../interfaces/factory.interface.mjs'
5
+ import type { NameResolver } from '../internal/core/name-resolver.mjs'
6
+ import type { ServiceInvalidator } from '../internal/core/service-invalidator.mjs'
7
+ import type { TokenResolver } from '../internal/core/token-resolver.mjs'
8
+ import type {
9
+ ClassType,
10
+ ClassTypeWithArgument,
11
+ InjectionTokenSchemaType,
12
+ } from '../token/injection-token.mjs'
13
+ import type { Registry } from '../token/registry.mjs'
14
+ import type { Join, UnionToArray } from '../utils/types.mjs'
15
+
16
+ import { InjectableScope, InjectableType } from '../enums/index.mjs'
17
+ import { DIError, DIErrorCode } from '../errors/index.mjs'
18
+ import { InstanceStatus } from '../internal/holder/instance-holder.mjs'
19
+ import { UnifiedStorage } from '../internal/holder/unified-storage.mjs'
20
+ import { StubFactoryClass } from '../internal/index.mjs'
21
+ import {
22
+ BoundInjectionToken,
23
+ FactoryInjectionToken,
24
+ InjectionToken,
25
+ } from '../token/injection-token.mjs'
26
+
27
+ /**
28
+ * Abstract base class for dependency injection containers.
29
+ *
30
+ * Provides shared implementation for common container operations.
31
+ * Both Container and ScopedContainer extend this class.
32
+ */
33
+ export abstract class AbstractContainer implements IContainer {
34
+ /**
35
+ * The default scope used when adding instances without explicit registration.
36
+ */
37
+ protected abstract readonly defaultScope: InjectableScope
38
+
39
+ /**
40
+ * The request ID for scoped containers, undefined for root container.
41
+ */
42
+ protected abstract readonly requestId: string | undefined
43
+
44
+ // ============================================================================
45
+ // ABSTRACT METHODS - Must be implemented by subclasses
46
+ // ============================================================================
47
+
48
+ /**
49
+ * Gets the storage for this container.
50
+ */
51
+ abstract getStorage(): UnifiedStorage
52
+
53
+ /**
54
+ * Gets the registry for this container.
55
+ */
56
+ protected abstract getRegistry(): Registry
57
+
58
+ /**
59
+ * Gets the token resolver.
60
+ */
61
+ protected abstract getTokenResolver(): TokenResolver
62
+
63
+ /**
64
+ * Gets the name resolver.
65
+ */
66
+ protected abstract getNameResolver(): NameResolver
67
+
68
+ /**
69
+ * Gets the service invalidator.
70
+ */
71
+ protected abstract getServiceInvalidator(): ServiceInvalidator
72
+
73
+ /**
74
+ * Gets an instance from the container.
75
+ */
76
+ // #1 Simple class
77
+ abstract get<T extends ClassType>(
78
+ token: T,
79
+ ): InstanceType<T> extends Factorable<infer R>
80
+ ? Promise<R>
81
+ : Promise<InstanceType<T>>
82
+ // #1.1 Simple class with args
83
+ abstract get<T extends ClassTypeWithArgument<R>, R>(
84
+ token: T,
85
+ args: R,
86
+ ): Promise<InstanceType<T>>
87
+ // #2 Token with required Schema
88
+ abstract get<T, S extends InjectionTokenSchemaType>(
89
+ token: InjectionToken<T, S>,
90
+ args: z.input<S>,
91
+ ): Promise<T>
92
+ // #3 Token with optional Schema
93
+ abstract get<T, S extends InjectionTokenSchemaType, R extends boolean>(
94
+ token: InjectionToken<T, S, R>,
95
+ ): R extends false
96
+ ? Promise<T>
97
+ : S extends ZodType<infer Type>
98
+ ? `Error: Your token requires args: ${Join<
99
+ UnionToArray<keyof Type>,
100
+ ', '
101
+ >}`
102
+ : 'Error: Your token requires args'
103
+ // #4 Token with no Schema
104
+ abstract get<T>(token: InjectionToken<T, undefined>): Promise<T>
105
+ abstract get<T>(token: BoundInjectionToken<T, any>): Promise<T>
106
+ abstract get<T>(token: FactoryInjectionToken<T, any>): Promise<T>
107
+
108
+ /**
109
+ * Invalidates a service and its dependencies.
110
+ */
111
+ abstract invalidate(service: unknown): Promise<void>
112
+
113
+ /**
114
+ * Disposes the container and cleans up all resources.
115
+ */
116
+ abstract dispose(): Promise<void>
117
+
118
+ // ============================================================================
119
+ // SHARED IMPLEMENTATIONS
120
+ // ============================================================================
121
+
122
+ /**
123
+ * Calculates the instance name for a given token and optional arguments.
124
+ *
125
+ * @internal
126
+ * @param token The class type, InjectionToken, BoundInjectionToken, or FactoryInjectionToken
127
+ * @param args Optional arguments (ignored for BoundInjectionToken which uses its bound value)
128
+ * @returns The calculated instance name string, or null if the token is a FactoryInjectionToken that is not yet resolved
129
+ */
130
+ calculateInstanceName(
131
+ token:
132
+ | ClassType
133
+ | InjectionToken<any, any>
134
+ | BoundInjectionToken<any, any>
135
+ | FactoryInjectionToken<any, any>,
136
+ args?: unknown,
137
+ ): string | null {
138
+ const tokenResolver = this.getTokenResolver()
139
+
140
+ // Use validateAndResolveTokenArgs to handle token normalization and arg resolution
141
+ const [err, { actualToken, validatedArgs }] =
142
+ tokenResolver.validateAndResolveTokenArgs(token, args)
143
+
144
+ if (err) {
145
+ // Return null if factory token is not resolved
146
+ if (
147
+ err instanceof DIError &&
148
+ err.code === DIErrorCode.FactoryTokenNotResolved
149
+ ) {
150
+ return null
151
+ }
152
+
153
+ // Return null if validation fails (can't calculate name with invalid args)
154
+ if (
155
+ err instanceof DIError &&
156
+ err.code === DIErrorCode.TokenValidationError
157
+ ) {
158
+ return null
159
+ }
160
+ }
161
+
162
+ // Get the real token for registry lookup to determine scope
163
+ const realToken = this.getTokenResolver().getRealToken(actualToken)
164
+
165
+ const registry = this.getRegistry()
166
+
167
+ // Get scope from registry, or use default scope if not registered
168
+ const scope = registry.has(realToken)
169
+ ? registry.get(realToken).scope
170
+ : this.defaultScope
171
+
172
+ // Generate instance name using the name resolver with actual token and validated args
173
+ return this.getNameResolver().generateInstanceName(
174
+ actualToken,
175
+ validatedArgs,
176
+ scope === InjectableScope.Request ? this.requestId : undefined,
177
+ scope,
178
+ )
179
+ }
180
+
181
+ /**
182
+ * Checks if a service is registered in the container.
183
+ */
184
+ isRegistered(token: any): boolean {
185
+ const realToken = this.getTokenResolver().getRegistryToken(token)
186
+ return this.getRegistry().has(realToken)
187
+ }
188
+
189
+ /**
190
+ * Waits for all pending operations to complete.
191
+ */
192
+ async ready(): Promise<void> {
193
+ await this.getServiceInvalidator().readyWithStorage(this.getStorage())
194
+ }
195
+
196
+ /**
197
+ * @internal
198
+ * Attempts to get an instance synchronously if it already exists.
199
+ */
200
+ tryGetSync<T>(token: any, args?: any): T | null {
201
+ return this.tryGetSyncFromStorage(
202
+ token,
203
+ args,
204
+ this.getStorage(),
205
+ this.requestId,
206
+ )
207
+ }
208
+
209
+ /**
210
+ * @internal
211
+ * Internal method for getting instances synchronously with configurable storage.
212
+ */
213
+ protected tryGetSyncFromStorage<T>(
214
+ token: any,
215
+ args: any,
216
+ storage: UnifiedStorage,
217
+ requestId?: string,
218
+ ): T | null {
219
+ const tokenResolver = this.getTokenResolver()
220
+ const realToken = tokenResolver.getRegistryToken(token)
221
+ const registry = this.getRegistry()
222
+ const scope = registry.has(realToken)
223
+ ? registry.get(realToken).scope
224
+ : InjectableScope.Singleton
225
+
226
+ try {
227
+ const instanceName = this.getNameResolver().generateInstanceName(
228
+ tokenResolver.normalizeToken(token),
229
+ args,
230
+ requestId,
231
+ scope,
232
+ )
233
+
234
+ const result = storage.get(instanceName)
235
+ if (result && result[0] === undefined && result[1]) {
236
+ const holder = result[1]
237
+ if (holder.status === InstanceStatus.Created) {
238
+ return holder.instance as T
239
+ }
240
+ }
241
+ } catch {
242
+ // Ignore error
243
+ }
244
+
245
+ return null
246
+ }
247
+
248
+ /**
249
+ * Adds an instance to the container.
250
+ * Accepts class types, InjectionTokens, and BoundInjectionTokens.
251
+ * Rejects InjectionTokens with required schemas (use BoundInjectionToken instead).
252
+ *
253
+ * @param token The class type, InjectionToken, or BoundInjectionToken to register the instance for
254
+ * @param instance The instance to store
255
+ */
256
+ addInstance<T>(
257
+ token: ClassType | InjectionToken<T, any> | BoundInjectionToken<T, any>,
258
+ instance: T,
259
+ ): void {
260
+ this.addInstanceToStorage(
261
+ token,
262
+ instance,
263
+ this.getStorage(),
264
+ this.defaultScope,
265
+ this.requestId,
266
+ )
267
+ }
268
+
269
+ /**
270
+ * @internal
271
+ * Internal method for adding instances with configurable scope and storage.
272
+ */
273
+ protected addInstanceToStorage<T>(
274
+ token: ClassType | InjectionToken<T, any> | BoundInjectionToken<T, any>,
275
+ instance: T,
276
+ storage: UnifiedStorage,
277
+ scope: InjectableScope,
278
+ requestId?: string,
279
+ ): void {
280
+ // Check if token is an InjectionToken with required schema
281
+ // BoundInjectionToken is allowed (it already has a value bound)
282
+ if (token instanceof InjectionToken) {
283
+ // Check if schema exists and is required (not optional)
284
+ if (token.schema) {
285
+ const schemaType = (token.schema as ZodType)?.def?.type
286
+ if (schemaType !== 'optional') {
287
+ throw DIError.tokenSchemaRequiredError(token.name)
288
+ }
289
+ }
290
+ }
291
+
292
+ const tokenResolver = this.getTokenResolver()
293
+ const registry = this.getRegistry()
294
+
295
+ // Normalize the token
296
+ const normalizedToken = tokenResolver.normalizeToken(token)
297
+ const realToken = tokenResolver.getRegistryToken(token)
298
+
299
+ // If it's a class type and not registered, register it with the given scope
300
+ if (typeof token === 'function' && !registry.has(realToken)) {
301
+ registry.set(realToken, scope, token, InjectableType.Class)
302
+ } else if (!registry.has(realToken)) {
303
+ // Set a stub factory class to avoid errors when getting instances of unregistered factory tokens
304
+ registry.set(
305
+ realToken,
306
+ scope,
307
+ StubFactoryClass,
308
+ InjectableType.Class,
309
+ // Lowest priority to avoid conflicts with other registered tokens
310
+ -1,
311
+ )
312
+ }
313
+
314
+ // Generate instance name with the given scope
315
+ const instanceName = this.getNameResolver().generateInstanceName(
316
+ normalizedToken,
317
+ normalizedToken instanceof BoundInjectionToken
318
+ ? normalizedToken.value
319
+ : undefined,
320
+ requestId,
321
+ scope,
322
+ )
323
+
324
+ // Store the instance
325
+ storage.storeInstance(instanceName, instance)
326
+ }
327
+ }