@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
@@ -1,22 +1,15 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- /* eslint-disable @typescript-eslint/no-empty-object-type */
3
- import type { z, ZodObject, ZodOptional } from 'zod/v4'
4
-
5
2
  import type { ScopedContainer } from '../../container/scoped-container.mjs'
6
3
  import type { IContainer } from '../../interfaces/container.interface.mjs'
7
4
  import type {
8
5
  AnyInjectableType,
9
- InjectionTokenSchemaType,
10
6
  InjectionTokenType,
11
7
  } from '../../token/injection-token.mjs'
12
8
  import type { Registry } from '../../token/registry.mjs'
13
- import type { FactoryContext } from '../context/factory-context.mjs'
14
- import type { HolderManager } from '../holder/holder-manager.mjs'
9
+ import type { ServiceInitializationContext } from '../context/service-initialization-context.mjs'
15
10
  import type { IHolderStorage } from '../holder/holder-storage.interface.mjs'
16
11
  import type { InstanceHolder } from '../holder/instance-holder.mjs'
17
- import type { Instantiator } from './instantiator.mjs'
18
- import type { ServiceLocator } from './service-locator.mjs'
19
- import type { TokenProcessor } from './token-processor.mjs'
12
+ import type { LifecycleEventBus } from '../lifecycle/lifecycle-event-bus.mjs'
20
13
 
21
14
  import { InjectableScope } from '../../enums/index.mjs'
22
15
  import { DIError, DIErrorCode } from '../../errors/index.mjs'
@@ -29,29 +22,33 @@ import {
29
22
  getCurrentResolutionContext,
30
23
  withResolutionContext,
31
24
  } from '../context/resolution-context.mjs'
32
- import { BaseHolderManager } from '../holder/base-holder-manager.mjs'
33
25
  import { InstanceStatus } from '../holder/instance-holder.mjs'
34
- import { SingletonStorage } from '../holder/singleton-storage.mjs'
26
+ import { CircularDetector } from '../lifecycle/circular-detector.mjs'
27
+ import { NameResolver } from './name-resolver.mjs'
28
+ import { ScopeTracker } from './scope-tracker.mjs'
29
+ import { ServiceInitializer } from './service-initializer.mjs'
30
+ import { ServiceInvalidator } from './service-invalidator.mjs'
31
+ import { TokenResolver } from './token-resolver.mjs'
35
32
 
36
33
  /**
37
34
  * Resolves instances from tokens, handling caching, creation, and scope rules.
38
35
  *
39
- * Uses the Storage Strategy pattern for unified singleton/request-scoped handling.
40
- * Coordinates with Instantiator for actual service creation.
36
+ * Uses unified storage for both singleton and request-scoped services.
37
+ * Coordinates with ServiceInitializer for actual service creation.
38
+ * Integrates ScopeTracker for automatic scope upgrades.
41
39
  */
42
40
  export class InstanceResolver {
43
- private readonly singletonStorage: IHolderStorage
44
-
45
41
  constructor(
46
42
  private readonly registry: Registry,
47
- private readonly manager: HolderManager,
48
- private readonly instantiator: Instantiator,
49
- private readonly tokenProcessor: TokenProcessor,
43
+ private readonly storage: IHolderStorage,
44
+ private readonly serviceInitializer: ServiceInitializer,
45
+ private readonly tokenResolver: TokenResolver,
46
+ private readonly nameResolver: NameResolver,
47
+ private readonly scopeTracker: ScopeTracker,
48
+ private readonly serviceInvalidator: ServiceInvalidator,
49
+ private readonly eventBus: LifecycleEventBus,
50
50
  private readonly logger: Console | null = null,
51
- private readonly serviceLocator: ServiceLocator,
52
- ) {
53
- this.singletonStorage = new SingletonStorage(manager)
54
- }
51
+ ) {}
55
52
 
56
53
  // ============================================================================
57
54
  // PUBLIC RESOLUTION METHODS
@@ -63,24 +60,31 @@ export class InstanceResolver {
63
60
  *
64
61
  * @param token The injection token
65
62
  * @param args Optional arguments
66
- * @param contextContainer The container to use for creating FactoryContext
63
+ * @param contextContainer The container to use for creating context
64
+ * @param requestStorage Optional request storage (for scope upgrades)
65
+ * @param requestId Optional request ID (for scope upgrades)
67
66
  */
68
67
  async resolveInstance(
69
68
  token: AnyInjectableType,
70
69
  args: any,
71
70
  contextContainer: IContainer,
71
+ requestStorage?: IHolderStorage,
72
+ requestId?: string,
72
73
  ): Promise<[undefined, any] | [DIError]> {
73
74
  return this.resolveWithStorage(
74
75
  token,
75
76
  args,
76
77
  contextContainer,
77
- this.singletonStorage,
78
+ this.storage,
79
+ undefined,
80
+ requestStorage,
81
+ requestId,
78
82
  )
79
83
  }
80
84
 
81
85
  /**
82
86
  * Resolves a request-scoped instance for a ScopedContainer.
83
- * The service will be stored in the ScopedContainer's request context.
87
+ * The service will be stored in the ScopedContainer's request storage.
84
88
  *
85
89
  * @param token The injection token
86
90
  * @param args Optional arguments
@@ -91,13 +95,14 @@ export class InstanceResolver {
91
95
  args: any,
92
96
  scopedContainer: ScopedContainer,
93
97
  ): Promise<[undefined, any] | [DIError]> {
94
- // Use the cached storage from ScopedContainer
95
98
  return this.resolveWithStorage(
96
99
  token,
97
100
  args,
101
+ scopedContainer.getParent(),
102
+ scopedContainer.getParent().getStorage(),
98
103
  scopedContainer,
99
- scopedContainer.getHolderStorage(),
100
- scopedContainer,
104
+ scopedContainer.getStorage(),
105
+ scopedContainer.getRequestId(),
101
106
  )
102
107
  }
103
108
 
@@ -114,9 +119,11 @@ export class InstanceResolver {
114
119
  *
115
120
  * @param token The injection token
116
121
  * @param args Optional arguments
117
- * @param contextContainer The container for FactoryContext
122
+ * @param contextContainer The container for context
118
123
  * @param storage The storage strategy to use
119
124
  * @param scopedContainer Optional scoped container for request-scoped services
125
+ * @param requestStorage Optional request storage (for scope upgrades)
126
+ * @param requestId Optional request ID (for scope upgrades)
120
127
  */
121
128
  private async resolveWithStorage(
122
129
  token: AnyInjectableType,
@@ -124,28 +131,56 @@ export class InstanceResolver {
124
131
  contextContainer: IContainer,
125
132
  storage: IHolderStorage,
126
133
  scopedContainer?: ScopedContainer,
134
+ requestStorage?: IHolderStorage,
135
+ requestId?: string,
127
136
  ): Promise<[undefined, any] | [DIError]> {
128
137
  // Step 1: Resolve token and prepare instance name
129
138
  const [err, data] = await this.resolveTokenAndPrepareInstanceName(
130
139
  token,
131
140
  args,
132
141
  contextContainer,
142
+ requestId,
143
+ scopedContainer,
133
144
  )
134
145
  if (err) {
135
146
  return [err]
136
147
  }
137
148
 
138
- const { instanceName, validatedArgs, realToken } = data!
149
+ const { instanceName, validatedArgs, realToken, scope } = data!
139
150
 
140
151
  // Step 2: Check for existing holder SYNCHRONOUSLY (no await between check and store)
141
152
  // This is critical for preventing race conditions with concurrent resolution
142
- const getResult = storage.get(instanceName)
153
+ const getResult =
154
+ storage.get(instanceName) ?? requestStorage?.get(instanceName) ?? null
155
+
156
+ // Create getHolder function for circular dependency detection
157
+ const getHolder = (name: string): InstanceHolder | undefined => {
158
+ // Check both storages
159
+ const result = storage.get(name)
160
+ if (result && result[0] === undefined && result[1]) {
161
+ return result[1]
162
+ }
163
+ if (requestStorage) {
164
+ const reqResult = requestStorage.get(name)
165
+ if (reqResult && reqResult[0] === undefined && reqResult[1]) {
166
+ return reqResult[1]
167
+ }
168
+ }
169
+ return undefined
170
+ }
143
171
 
144
172
  if (getResult !== null) {
145
173
  const [error, holder] = getResult
146
174
  if (!error && holder) {
147
175
  // Found existing holder - wait for it to be ready
148
- const readyResult = await this.waitForInstanceReady(holder)
176
+ // Try to get waiterHolder from resolution context if available
177
+ const resolutionCtx = getCurrentResolutionContext()
178
+ const waiterHolder = resolutionCtx?.waiterHolder
179
+ const readyResult = await this.waitForInstanceReady(
180
+ holder,
181
+ waiterHolder,
182
+ getHolder,
183
+ )
149
184
  if (readyResult[0]) {
150
185
  return [readyResult[0]]
151
186
  }
@@ -174,6 +209,9 @@ export class InstanceResolver {
174
209
  contextContainer,
175
210
  storage,
176
211
  scopedContainer,
212
+ requestStorage,
213
+ requestId,
214
+ scope,
177
215
  )
178
216
  if (createError) {
179
217
  return [createError]
@@ -182,6 +220,81 @@ export class InstanceResolver {
182
220
  return [undefined, holder!.instance]
183
221
  }
184
222
 
223
+ /**
224
+ * Internal method to resolve token args and create instance name.
225
+ * Handles factory token resolution and validation.
226
+ */
227
+ private async resolveTokenAndPrepareInstanceName(
228
+ token: AnyInjectableType,
229
+ args: any,
230
+ contextContainer: IContainer,
231
+ requestId?: string,
232
+ scopedContainer?: ScopedContainer,
233
+ ): Promise<
234
+ | [
235
+ undefined,
236
+ {
237
+ instanceName: string
238
+ validatedArgs: any
239
+ actualToken: InjectionTokenType
240
+ realToken: InjectionToken<any, any>
241
+ scope: InjectableScope
242
+ },
243
+ ]
244
+ | [DIError]
245
+ > {
246
+ const [err, { actualToken, validatedArgs }] =
247
+ this.tokenResolver.validateAndResolveTokenArgs(token, args)
248
+ if (
249
+ err instanceof DIError &&
250
+ err.code === DIErrorCode.TokenValidationError
251
+ ) {
252
+ return [err]
253
+ } else if (
254
+ err instanceof DIError &&
255
+ err.code === DIErrorCode.FactoryTokenNotResolved &&
256
+ actualToken instanceof FactoryInjectionToken
257
+ ) {
258
+ this.logger?.log(
259
+ `[InstanceResolver]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`,
260
+ )
261
+ // Create a simple factory context for resolving the factory token
262
+ const factoryCtx = {
263
+ inject: async (t: any, a?: any) =>
264
+ (scopedContainer ?? contextContainer).get(t, a),
265
+ container: scopedContainer ?? contextContainer,
266
+ addDestroyListener: () => {},
267
+ }
268
+ await actualToken.resolve(factoryCtx as any)
269
+ return this.resolveTokenAndPrepareInstanceName(
270
+ token,
271
+ undefined,
272
+ contextContainer,
273
+ requestId,
274
+ scopedContainer,
275
+ )
276
+ }
277
+
278
+ // Get the real token for registry lookup
279
+ const realToken = this.tokenResolver.getRealToken(actualToken)
280
+ // Get scope from registry
281
+ const record = this.registry.get(realToken)
282
+ const scope = record.scope
283
+
284
+ // Generate instance name with requestId if needed
285
+ const instanceName = this.nameResolver.generateInstanceName(
286
+ actualToken,
287
+ validatedArgs,
288
+ requestId,
289
+ scope,
290
+ )
291
+
292
+ return [
293
+ undefined,
294
+ { instanceName, validatedArgs, actualToken, realToken, scope },
295
+ ]
296
+ }
297
+
185
298
  /**
186
299
  * Handles storage error states (destroying, error, etc.).
187
300
  * Returns a result if handled, null if should proceed with creation.
@@ -204,7 +317,18 @@ export class InstanceResolver {
204
317
  // Re-check after destruction
205
318
  const newResult = storage.get(instanceName)
206
319
  if (newResult !== null && !newResult[0]) {
207
- const readyResult = await this.waitForInstanceReady(newResult[1]!)
320
+ // Create getHolder for circular dependency detection
321
+ const getHolder = (name: string): InstanceHolder | undefined => {
322
+ const result = storage.get(name)
323
+ return result && result[0] === undefined && result[1]
324
+ ? result[1]
325
+ : undefined
326
+ }
327
+ const readyResult = await this.waitForInstanceReady(
328
+ newResult[1]!,
329
+ undefined,
330
+ getHolder,
331
+ )
208
332
  if (readyResult[0]) {
209
333
  return [readyResult[0]]
210
334
  }
@@ -237,6 +361,9 @@ export class InstanceResolver {
237
361
  contextContainer: IContainer,
238
362
  storage: IHolderStorage,
239
363
  scopedContainer?: ScopedContainer,
364
+ requestStorage?: IHolderStorage,
365
+ requestId?: string,
366
+ scope?: InjectableScope,
240
367
  ): Promise<[undefined, InstanceHolder<Instance>] | [DIError]> {
241
368
  this.logger?.log(
242
369
  `[InstanceResolver]#createAndStoreInstance() Creating instance for ${instanceName}`,
@@ -246,64 +373,117 @@ export class InstanceResolver {
246
373
  return [DIError.factoryNotFound(realToken.name.toString())]
247
374
  }
248
375
 
249
- const ctx = this.createFactoryContext(contextContainer)
250
376
  const record = this.registry.get<Instance, any>(realToken)
251
- const { scope, type } = record
377
+ const { type, scope: recordScope } = record
378
+ const serviceScope = scope || recordScope
252
379
 
253
380
  // For transient services, don't use storage locking - create directly
254
- if (scope === InjectableScope.Transient) {
255
- return this.createTransientInstance(instanceName, record, args, ctx)
381
+ if (serviceScope === InjectableScope.Transient) {
382
+ return this.createTransientInstance(
383
+ instanceName,
384
+ record,
385
+ args,
386
+ contextContainer,
387
+ scopedContainer,
388
+ requestStorage,
389
+ requestId,
390
+ )
391
+ }
392
+ if (serviceScope === InjectableScope.Request && !requestStorage) {
393
+ return [
394
+ DIError.initializationError(
395
+ `Request storage is required for request-scoped services`,
396
+ instanceName,
397
+ ),
398
+ ]
256
399
  }
257
400
 
258
- // Create holder in "Creating" state using registry scope, not storage scope
259
- const [deferred, holder] = this.manager.createCreatingHolder<Instance>(
401
+ let storageToUse: IHolderStorage
402
+ if (serviceScope === InjectableScope.Request) {
403
+ storageToUse = requestStorage!
404
+ } else {
405
+ storageToUse = storage
406
+ }
407
+
408
+ // Create holder in "Creating" state
409
+ const [deferred, holder] = storageToUse.createHolder<Instance>(
260
410
  instanceName,
261
411
  type,
262
- scope,
263
- ctx.deps,
412
+ new Set(),
264
413
  )
265
-
266
414
  // Store holder immediately (for lock mechanism)
267
- storage.set(instanceName, holder)
415
+ storageToUse.set(instanceName, holder)
416
+
417
+ // Create context for service initialization
418
+ const ctx = this.createServiceInitializationContext(
419
+ scopedContainer ?? contextContainer,
420
+ instanceName,
421
+ serviceScope,
422
+ holder.deps,
423
+ realToken,
424
+ requestStorage,
425
+ requestId,
426
+ )
268
427
 
269
- // Create a getHolder function that looks up holders from both the manager and storage
428
+ holder.destroyListeners = ctx.getDestroyListeners()
429
+
430
+ // Create getHolder function for resolution context
270
431
  const getHolder = (name: string): InstanceHolder | undefined => {
271
- // First check the storage (which might be request-scoped)
272
- const storageResult = storage.get(name)
273
- if (storageResult !== null) {
274
- const [, storageHolder] = storageResult
275
- if (storageHolder) return storageHolder
432
+ // Check both storages
433
+ const result = storage.get(name)
434
+ if (result && result[0] === undefined && result[1]) {
435
+ return result[1]
436
+ }
437
+ if (requestStorage) {
438
+ const reqResult = requestStorage.get(name)
439
+ if (reqResult && reqResult[0] === undefined && reqResult[1]) {
440
+ return reqResult[1]
441
+ }
276
442
  }
277
- // Fall back to the singleton manager
278
- const [, managerHolder] = this.manager.get(name)
279
- return managerHolder
443
+ return undefined
280
444
  }
281
445
 
282
- // Start async instantiation within the resolution context
283
- // This allows circular dependency detection to track the waiter
446
+ // Start async instantiation within resolution context for circular dependency detection
284
447
  withResolutionContext(holder, getHolder, () => {
285
- this.instantiator
448
+ this.serviceInitializer
286
449
  .instantiateService(ctx, record, args)
287
450
  .then(async (result: [undefined, Instance] | [DIError]) => {
288
451
  const [error, instance] =
289
452
  result.length === 2 ? result : [result[0], undefined]
453
+ const newScope = record.scope
454
+ const newName = this.nameResolver.generateInstanceName(
455
+ realToken,
456
+ args,
457
+ requestId,
458
+ newScope,
459
+ )
290
460
  await this.handleInstantiationResult(
291
- instanceName,
461
+ newName,
292
462
  holder,
293
463
  ctx,
294
464
  deferred,
295
- scope,
465
+ newScope,
296
466
  error,
297
467
  instance,
298
468
  scopedContainer,
469
+ requestStorage,
470
+ requestId,
299
471
  )
300
472
  })
301
473
  .catch(async (error: Error) => {
474
+ const newScope = record.scope
475
+ const newName = this.nameResolver.generateInstanceName(
476
+ realToken,
477
+ args,
478
+ requestId,
479
+ newScope,
480
+ )
481
+
302
482
  await this.handleInstantiationError(
303
- instanceName,
483
+ newName,
304
484
  holder,
305
485
  deferred,
306
- scope,
486
+ newScope,
307
487
  error,
308
488
  )
309
489
  })
@@ -316,7 +496,10 @@ export class InstanceResolver {
316
496
  })
317
497
 
318
498
  // Wait for instance to be ready
319
- return this.waitForInstanceReady(holder)
499
+ // Use resolution context to get waiterHolder if available
500
+ const resolutionCtx = getCurrentResolutionContext()
501
+ const waiterHolder = resolutionCtx?.waiterHolder
502
+ return this.waitForInstanceReady(holder, waiterHolder, getHolder)
320
503
  }
321
504
 
322
505
  /**
@@ -327,49 +510,38 @@ export class InstanceResolver {
327
510
  instanceName: string,
328
511
  record: any,
329
512
  args: any,
330
- ctx: FactoryContext & {
331
- deps: Set<string>
332
- getDestroyListeners: () => (() => void)[]
333
- },
513
+ contextContainer: IContainer,
514
+ scopedContainer?: ScopedContainer,
515
+ requestStorage?: IHolderStorage,
516
+ requestId?: string,
334
517
  ): Promise<[undefined, InstanceHolder<Instance>] | [DIError]> {
335
518
  this.logger?.log(
336
519
  `[InstanceResolver]#createTransientInstance() Creating transient instance for ${instanceName}`,
337
520
  )
338
521
 
339
522
  // Create a temporary holder for resolution context (transient instances can still have deps)
340
- const tempHolder: InstanceHolder<Instance> = {
341
- status: InstanceStatus.Creating,
342
- name: instanceName,
343
- instance: null,
344
- creationPromise: null,
345
- destroyPromise: null,
346
- type: record.type,
347
- scope: InjectableScope.Transient,
348
- deps: ctx.deps,
349
- destroyListeners: [],
350
- createdAt: Date.now(),
351
- waitingFor: new Set(),
352
- }
353
-
354
- // Create a getHolder function for resolution context
355
- const getHolder = (name: string): InstanceHolder | undefined => {
356
- const [, managerHolder] = this.manager.get(name)
357
- return managerHolder
358
- }
523
+ const ctx = this.createServiceInitializationContext(
524
+ scopedContainer ?? contextContainer,
525
+ instanceName,
526
+ InjectableScope.Transient,
527
+ new Set(),
528
+ record.originalToken,
529
+ requestStorage,
530
+ requestId,
531
+ )
359
532
 
360
- // Run instantiation within resolution context for cycle detection
361
- const [error, instance] = await withResolutionContext(
362
- tempHolder,
363
- getHolder,
364
- () => this.instantiator.instantiateService(ctx, record, args),
533
+ const [error, instance] = await this.serviceInitializer.instantiateService(
534
+ ctx,
535
+ record,
536
+ args,
365
537
  )
366
538
 
367
539
  if (error) {
368
- return [error as DIError]
540
+ return [error]
369
541
  }
370
542
 
371
- // Create a holder for the transient instance (not stored, just for return consistency)
372
- const holder: InstanceHolder<Instance> = {
543
+ // Create a temporary holder for the result
544
+ const tempHolder: InstanceHolder<Instance> = {
373
545
  status: InstanceStatus.Created,
374
546
  name: instanceName,
375
547
  instance: instance as Instance,
@@ -377,150 +549,85 @@ export class InstanceResolver {
377
549
  destroyPromise: null,
378
550
  type: record.type,
379
551
  scope: InjectableScope.Transient,
380
- deps: ctx.deps,
552
+ deps: ctx.dependencies,
381
553
  destroyListeners: ctx.getDestroyListeners(),
382
554
  createdAt: Date.now(),
383
555
  waitingFor: new Set(),
384
556
  }
385
557
 
386
- return [undefined, holder]
558
+ return [undefined, tempHolder]
387
559
  }
388
560
 
389
561
  /**
390
- * Gets a synchronous instance (for sync operations).
562
+ * Handles successful service instantiation.
391
563
  */
392
- getSyncInstance<
393
- Instance,
394
- Schema extends InjectionTokenSchemaType | undefined,
395
- >(
396
- token: AnyInjectableType,
397
- args: Schema extends ZodObject
398
- ? z.input<Schema>
399
- : Schema extends ZodOptional<ZodObject>
400
- ? z.input<Schema> | undefined
401
- : undefined,
402
- contextContainer: IContainer,
403
- ): Instance | null {
404
- const [err, { actualToken, validatedArgs }] =
405
- this.tokenProcessor.validateAndResolveTokenArgs(token, args)
406
- if (err) {
407
- return null
408
- }
409
- const instanceName = this.tokenProcessor.generateInstanceName(
410
- actualToken,
411
- validatedArgs,
412
- )
564
+ private async handleInstantiationSuccess(
565
+ instanceName: string,
566
+ holder: InstanceHolder<any>,
567
+ ctx: ServiceInitializationContext,
568
+ deferred: any,
569
+ instance: any,
570
+ _scopedContainer?: ScopedContainer,
571
+ requestStorage?: IHolderStorage,
572
+ _requestId?: string,
573
+ ): Promise<void> {
574
+ holder.instance = instance
575
+ holder.status = InstanceStatus.Created
413
576
 
414
- // Check if this is a ScopedContainer and the service is request-scoped
415
- if ('getRequestInstance' in contextContainer) {
416
- const scopedContainer = contextContainer as ScopedContainer
417
- const requestHolder = scopedContainer.getRequestInstance(instanceName)
418
- if (requestHolder) {
419
- return requestHolder.instance as Instance
420
- }
421
- }
577
+ // Set up dependency subscriptions for event-based invalidation
578
+ // Determine which storage to use for subscriptions
579
+ const storageForSubscriptions = requestStorage || this.storage
422
580
 
423
- // Try singleton manager
424
- const [error, holder] = this.manager.get(instanceName)
425
- if (error) {
426
- return null
427
- }
428
- return holder.instance as Instance
429
- }
430
-
431
- /**
432
- * Internal method to resolve token args and create instance name.
433
- * Handles factory token resolution and validation.
434
- */
435
- private async resolveTokenAndPrepareInstanceName(
436
- token: AnyInjectableType,
437
- args: any,
438
- contextContainer: IContainer,
439
- ): Promise<
440
- | [
441
- undefined,
442
- {
443
- instanceName: string
444
- validatedArgs: any
445
- actualToken: InjectionTokenType
446
- realToken: InjectionToken<any, any>
447
- },
448
- ]
449
- | [DIError]
450
- > {
451
- const [err, { actualToken, validatedArgs }] =
452
- this.tokenProcessor.validateAndResolveTokenArgs(token, args)
453
- if (err instanceof DIError && err.code === DIErrorCode.UnknownError) {
454
- return [err]
455
- } else if (
456
- err instanceof DIError &&
457
- err.code === DIErrorCode.FactoryTokenNotResolved &&
458
- actualToken instanceof FactoryInjectionToken
459
- ) {
460
- this.logger?.log(
461
- `[InstanceResolver]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`,
462
- )
463
- await actualToken.resolve(this.createFactoryContext(contextContainer))
464
- return this.resolveTokenAndPrepareInstanceName(
465
- token,
466
- undefined,
467
- contextContainer,
581
+ // Set up subscriptions via ServiceInvalidator
582
+ if (ctx.dependencies.size > 0) {
583
+ this.serviceInvalidator.setupDependencySubscriptions(
584
+ instanceName,
585
+ ctx.dependencies,
586
+ storageForSubscriptions,
587
+ holder,
468
588
  )
469
589
  }
470
- const instanceName = this.tokenProcessor.generateInstanceName(
471
- actualToken,
472
- validatedArgs,
590
+
591
+ this.logger?.log(
592
+ `[InstanceResolver] Instance ${instanceName} created successfully`,
473
593
  )
474
- // Determine the real token (the actual InjectionToken that will be used for resolution)
475
- const realToken =
476
- actualToken instanceof BoundInjectionToken ||
477
- actualToken instanceof FactoryInjectionToken
478
- ? actualToken.token
479
- : actualToken
480
- return [undefined, { instanceName, validatedArgs, actualToken, realToken }]
594
+ deferred.resolve([undefined, instance])
481
595
  }
482
596
 
483
- // ============================================================================
484
- // INSTANTIATION HANDLERS
485
- // ============================================================================
486
-
487
597
  /**
488
- * Waits for an instance holder to be ready and returns the appropriate result.
489
- * Uses the shared utility from BaseHolderManager.
490
- * Passes the current resolution context for circular dependency detection.
598
+ * Handles service instantiation errors.
491
599
  */
492
- private waitForInstanceReady<T>(
493
- holder: InstanceHolder<T>,
494
- ): Promise<[undefined, InstanceHolder<T>] | [DIError]> {
495
- // Get the current resolution context (if we're inside an instantiation)
496
- const ctx = getCurrentResolutionContext()
497
-
498
- return BaseHolderManager.waitForHolderReady(
499
- holder,
500
- ctx?.waiterHolder,
501
- ctx?.getHolder,
600
+ private async handleInstantiationError(
601
+ instanceName: string,
602
+ holder: InstanceHolder<any>,
603
+ deferred: any,
604
+ scope: InjectableScope,
605
+ error: any,
606
+ ): Promise<void> {
607
+ holder.status = InstanceStatus.Error
608
+ holder.instance = error instanceof DIError ? error : DIError.unknown(error)
609
+ this.logger?.error(
610
+ `[InstanceResolver] Instance ${instanceName} creation failed:`,
611
+ error,
502
612
  )
613
+ deferred.reject(error instanceof DIError ? error : DIError.unknown(error))
503
614
  }
504
615
 
505
616
  /**
506
- * Handles the result of service instantiation.
617
+ * Handles instantiation result (success or error).
507
618
  */
508
619
  private async handleInstantiationResult(
509
620
  instanceName: string,
510
621
  holder: InstanceHolder<any>,
511
- ctx: FactoryContext & {
512
- deps: Set<string>
513
- getDestroyListeners: () => (() => void)[]
514
- },
622
+ ctx: ServiceInitializationContext,
515
623
  deferred: any,
516
624
  scope: InjectableScope,
517
625
  error: any,
518
626
  instance: any,
519
627
  scopedContainer?: ScopedContainer,
628
+ requestStorage?: IHolderStorage,
629
+ requestId?: string,
520
630
  ): Promise<void> {
521
- holder.destroyListeners = ctx.getDestroyListeners()
522
- holder.creationPromise = null
523
-
524
631
  if (error) {
525
632
  await this.handleInstantiationError(
526
633
  instanceName,
@@ -537,112 +644,174 @@ export class InstanceResolver {
537
644
  deferred,
538
645
  instance,
539
646
  scopedContainer,
647
+ requestStorage,
648
+ requestId,
540
649
  )
541
650
  }
542
651
  }
543
652
 
544
653
  /**
545
- * Handles successful service instantiation.
654
+ * Waits for an instance holder to be ready and returns the appropriate result.
655
+ *
656
+ * @param holder The holder to wait for
657
+ * @param waiterHolder Optional holder that is doing the waiting (for circular dependency detection)
658
+ * @param getHolder Optional function to retrieve holders by name (required if waiterHolder is provided)
546
659
  */
547
- private async handleInstantiationSuccess(
548
- instanceName: string,
549
- holder: InstanceHolder<any>,
550
- ctx: FactoryContext & {
551
- deps: Set<string>
552
- getDestroyListeners: () => (() => void)[]
553
- },
554
- deferred: any,
555
- instance: any,
556
- scopedContainer?: ScopedContainer,
557
- ): Promise<void> {
558
- holder.instance = instance
559
- holder.status = InstanceStatus.Created
660
+ private async waitForInstanceReady<T>(
661
+ holder: InstanceHolder<T>,
662
+ waiterHolder?: InstanceHolder,
663
+ getHolder?: (name: string) => InstanceHolder | undefined,
664
+ ): Promise<[undefined, InstanceHolder<T>] | [DIError]> {
665
+ switch (holder.status) {
666
+ case InstanceStatus.Creating: {
667
+ // Check for circular dependency before waiting
668
+ if (waiterHolder && getHolder) {
669
+ const cycle = CircularDetector.detectCycle(
670
+ waiterHolder.name,
671
+ holder.name,
672
+ getHolder,
673
+ )
674
+ if (cycle) {
675
+ return [DIError.circularDependency(cycle)]
676
+ }
560
677
 
561
- // Set up dependency invalidation listeners
562
- if (ctx.deps.size > 0) {
563
- ctx.deps.forEach((dependency: string) => {
564
- holder.destroyListeners.push(
565
- this.serviceLocator.getEventBus().on(dependency, 'destroy', () => {
566
- this.logger?.log(
567
- `[InstanceResolver] Dependency ${dependency} destroyed, invalidating ${instanceName}`,
568
- )
569
- this.serviceLocator.getInvalidator().invalidate(instanceName)
570
- }),
571
- )
678
+ if (process.env.NODE_ENV !== 'production') {
679
+ // Track the waiting relationship
680
+ waiterHolder.waitingFor.add(holder.name)
681
+ }
682
+ }
572
683
 
573
- // For request-scoped services, also listen with prefixed event name
574
- if (scopedContainer) {
575
- const prefixedDependency =
576
- scopedContainer.getPrefixedEventName(dependency)
577
- holder.destroyListeners.push(
578
- this.serviceLocator
579
- .getEventBus()
580
- .on(prefixedDependency, 'destroy', () => {
581
- this.logger?.log(
582
- `[InstanceResolver] Request-scoped dependency ${dependency} destroyed, invalidating ${instanceName}`,
583
- )
584
- // For request-scoped, we need to invalidate within the scoped container
585
- scopedContainer.invalidate(instance)
586
- }),
587
- )
684
+ try {
685
+ await holder.creationPromise
686
+ } finally {
687
+ if (process.env.NODE_ENV !== 'production') {
688
+ // Clean up the waiting relationship
689
+ if (waiterHolder) {
690
+ waiterHolder.waitingFor.delete(holder.name)
691
+ }
692
+ }
588
693
  }
589
- })
590
- }
591
694
 
592
- // Note: Event emission would need access to the event bus
593
- this.logger?.log(
594
- `[InstanceResolver] Instance ${instanceName} created successfully`,
595
- )
596
- deferred.resolve([undefined, instance])
597
- }
695
+ // Recursively check after creation completes
696
+ return this.waitForInstanceReady(holder, waiterHolder, getHolder)
697
+ }
598
698
 
599
- /**
600
- * Handles service instantiation errors.
601
- */
602
- private async handleInstantiationError(
603
- instanceName: string,
604
- holder: InstanceHolder<any>,
605
- deferred: any,
606
- scope: InjectableScope,
607
- error: any,
608
- ): Promise<void> {
609
- this.logger?.error(
610
- `[InstanceResolver] Error creating instance for ${instanceName}`,
611
- error,
612
- )
699
+ case InstanceStatus.Destroying:
700
+ return [DIError.instanceDestroying(holder.name)]
613
701
 
614
- holder.status = InstanceStatus.Error
615
- holder.instance = error
616
- holder.creationPromise = null
702
+ case InstanceStatus.Error:
703
+ return [holder.instance as unknown as DIError]
617
704
 
618
- if (scope === InjectableScope.Singleton) {
619
- this.logger?.log(
620
- `[InstanceResolver] Singleton ${instanceName} failed, will be invalidated`,
621
- )
622
- // Fire-and-forget invalidation - don't await as it could cause deadlocks
623
- // Suppress any potential rejections since the primary error is already handled
624
- this.serviceLocator
625
- .getInvalidator()
626
- .invalidate(instanceName)
627
- .catch(() => {
628
- // Suppress - primary error is communicated via deferred.reject()
629
- })
630
- }
705
+ case InstanceStatus.Created:
706
+ return [undefined, holder]
631
707
 
632
- deferred.reject(error)
708
+ default:
709
+ // @ts-expect-error Maybe we will use this in the future
710
+ return [DIError.instanceNotFound(holder?.name ?? 'unknown')]
711
+ }
633
712
  }
634
713
 
635
- // ============================================================================
636
- // FACTORY CONTEXT
637
- // ============================================================================
638
-
639
714
  /**
640
- * Creates a factory context for dependency injection during service instantiation.
715
+ * Creates a ServiceInitializationContext for service instantiation.
641
716
  */
642
- private createFactoryContext(contextContainer: IContainer): FactoryContext & {
643
- getDestroyListeners: () => (() => void)[]
644
- deps: Set<string>
645
- } {
646
- return this.tokenProcessor.createFactoryContext(contextContainer)
717
+ private createServiceInitializationContext(
718
+ container: IContainer,
719
+ serviceName: string,
720
+ scope: InjectableScope,
721
+ deps: Set<string>,
722
+ serviceToken: InjectionToken<any, any>,
723
+ requestStorage?: IHolderStorage,
724
+ requestId?: string,
725
+ ): ServiceInitializationContext {
726
+ const destroyListeners: Array<() => void> = []
727
+
728
+ return {
729
+ inject: async (token: any, args?: any) => {
730
+ // Track dependency and check for scope upgrade
731
+ const actualToken =
732
+ typeof token === 'function'
733
+ ? this.tokenResolver.normalizeToken(token)
734
+ : token
735
+ const realToken = this.tokenResolver.getRealToken(actualToken)
736
+ const depRecord = this.registry.get(realToken)
737
+ const depScope = depRecord.scope
738
+
739
+ // Generate dependency name - if dependency is Request-scoped and we have requestId, use it
740
+ const dependencyRequestId =
741
+ depScope === InjectableScope.Request ? requestId : undefined
742
+ const finalDepName = this.nameResolver.generateInstanceName(
743
+ actualToken,
744
+ args,
745
+ dependencyRequestId,
746
+ depScope,
747
+ )
748
+
749
+ // Check if current service needs scope upgrade
750
+ // If current service is Singleton and dependency is Request, upgrade current service
751
+ if (
752
+ scope === InjectableScope.Singleton &&
753
+ depScope === InjectableScope.Request &&
754
+ requestStorage &&
755
+ requestId
756
+ ) {
757
+ // Check and perform scope upgrade for current service
758
+ // Use the dependency name with requestId for the check
759
+ const [needsUpgrade, newServiceName] =
760
+ this.scopeTracker.checkAndUpgradeScope(
761
+ serviceName,
762
+ scope,
763
+ finalDepName,
764
+ depScope,
765
+ serviceToken,
766
+ this.storage,
767
+ requestStorage,
768
+ requestId,
769
+ )
770
+
771
+ if (needsUpgrade && newServiceName) {
772
+ // Service was upgraded - update the service name in context
773
+ // The holder will be moved to request storage by ScopeTracker
774
+ // For now, we continue with the current resolution
775
+ // Future resolutions will use the new name
776
+ }
777
+ }
778
+
779
+ // Track dependency
780
+ deps.add(finalDepName)
781
+
782
+ // Resolve dependency
783
+ // Resolution context is automatically used by the injectors system for circular dependency detection
784
+ return container.get(token, args)
785
+ },
786
+ container,
787
+ addDestroyListener: (listener: () => void) => {
788
+ destroyListeners.push(listener)
789
+ },
790
+ getDestroyListeners: () => destroyListeners,
791
+ serviceName,
792
+ dependencies: deps,
793
+ scope,
794
+ trackDependency: (name: string, depScope: InjectableScope) => {
795
+ deps.add(name)
796
+ // Check for scope upgrade
797
+ if (
798
+ scope === InjectableScope.Singleton &&
799
+ depScope === InjectableScope.Request &&
800
+ requestStorage &&
801
+ requestId
802
+ ) {
803
+ this.scopeTracker.checkAndUpgradeScope(
804
+ serviceName,
805
+ scope,
806
+ name,
807
+ depScope,
808
+ serviceToken,
809
+ this.storage,
810
+ requestStorage,
811
+ requestId,
812
+ )
813
+ }
814
+ },
815
+ }
647
816
  }
648
817
  }