@navios/di 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +117 -17
  3. package/lib/browser/container/abstract-container.d.mts +112 -0
  4. package/lib/browser/container/abstract-container.d.mts.map +1 -0
  5. package/lib/browser/container/abstract-container.mjs +100 -0
  6. package/lib/browser/container/abstract-container.mjs.map +1 -0
  7. package/lib/browser/container/container.d.mts +100 -0
  8. package/lib/browser/container/container.d.mts.map +1 -0
  9. package/lib/browser/container/container.mjs +424 -0
  10. package/lib/browser/container/container.mjs.map +1 -0
  11. package/lib/browser/container/scoped-container.d.mts +93 -0
  12. package/lib/browser/container/scoped-container.d.mts.map +1 -0
  13. package/lib/browser/container/scoped-container.mjs +119 -0
  14. package/lib/browser/container/scoped-container.mjs.map +1 -0
  15. package/lib/browser/decorators/factory.decorator.d.mts +26 -0
  16. package/lib/browser/decorators/factory.decorator.d.mts.map +1 -0
  17. package/lib/browser/decorators/factory.decorator.mjs +20 -0
  18. package/lib/browser/decorators/factory.decorator.mjs.map +1 -0
  19. package/lib/browser/decorators/injectable.decorator.d.mts +38 -0
  20. package/lib/browser/decorators/injectable.decorator.d.mts.map +1 -0
  21. package/lib/browser/decorators/injectable.decorator.mjs +21 -0
  22. package/lib/browser/decorators/injectable.decorator.mjs.map +1 -0
  23. package/lib/browser/enums/injectable-scope.enum.d.mts +18 -0
  24. package/lib/browser/enums/injectable-scope.enum.d.mts.map +1 -0
  25. package/lib/browser/enums/injectable-scope.enum.mjs +20 -0
  26. package/lib/browser/enums/injectable-scope.enum.mjs.map +1 -0
  27. package/lib/browser/enums/injectable-type.enum.d.mts +8 -0
  28. package/lib/browser/enums/injectable-type.enum.d.mts.map +1 -0
  29. package/lib/browser/enums/injectable-type.enum.mjs +10 -0
  30. package/lib/browser/enums/injectable-type.enum.mjs.map +1 -0
  31. package/lib/browser/errors/di-error.d.mts +43 -0
  32. package/lib/browser/errors/di-error.d.mts.map +1 -0
  33. package/lib/browser/errors/di-error.mjs +98 -0
  34. package/lib/browser/errors/di-error.mjs.map +1 -0
  35. package/lib/browser/event-emitter.d.mts +16 -0
  36. package/lib/browser/event-emitter.d.mts.map +1 -0
  37. package/lib/browser/event-emitter.mjs +320 -0
  38. package/lib/browser/event-emitter.mjs.map +1 -0
  39. package/lib/browser/index.d.mts +37 -1558
  40. package/lib/browser/index.mjs +29 -2749
  41. package/lib/browser/interfaces/container.interface.d.mts +59 -0
  42. package/lib/browser/interfaces/container.interface.d.mts.map +1 -0
  43. package/lib/browser/interfaces/factory.interface.d.mts +14 -0
  44. package/lib/browser/interfaces/factory.interface.d.mts.map +1 -0
  45. package/lib/browser/interfaces/on-service-destroy.interface.d.mts +7 -0
  46. package/lib/browser/interfaces/on-service-destroy.interface.d.mts.map +1 -0
  47. package/lib/browser/interfaces/on-service-init.interface.d.mts +7 -0
  48. package/lib/browser/interfaces/on-service-init.interface.d.mts.map +1 -0
  49. package/lib/browser/internal/context/async-local-storage.browser.mjs +20 -0
  50. package/lib/browser/internal/context/async-local-storage.browser.mjs.map +1 -0
  51. package/lib/browser/internal/context/async-local-storage.d.mts +9 -0
  52. package/lib/browser/internal/context/async-local-storage.d.mts.map +1 -0
  53. package/lib/browser/internal/context/async-local-storage.types.d.mts +11 -0
  54. package/lib/browser/internal/context/async-local-storage.types.d.mts.map +1 -0
  55. package/lib/browser/internal/context/factory-context.d.mts +23 -0
  56. package/lib/browser/internal/context/factory-context.d.mts.map +1 -0
  57. package/lib/browser/internal/context/resolution-context.d.mts +43 -0
  58. package/lib/browser/internal/context/resolution-context.d.mts.map +1 -0
  59. package/lib/browser/internal/context/resolution-context.mjs +56 -0
  60. package/lib/browser/internal/context/resolution-context.mjs.map +1 -0
  61. package/lib/browser/internal/context/service-initialization-context.d.mts +48 -0
  62. package/lib/browser/internal/context/service-initialization-context.d.mts.map +1 -0
  63. package/lib/browser/internal/context/sync-local-storage.mjs +53 -0
  64. package/lib/browser/internal/context/sync-local-storage.mjs.map +1 -0
  65. package/lib/browser/internal/core/instance-resolver.d.mts +119 -0
  66. package/lib/browser/internal/core/instance-resolver.d.mts.map +1 -0
  67. package/lib/browser/internal/core/instance-resolver.mjs +306 -0
  68. package/lib/browser/internal/core/instance-resolver.mjs.map +1 -0
  69. package/lib/browser/internal/core/name-resolver.d.mts +52 -0
  70. package/lib/browser/internal/core/name-resolver.d.mts.map +1 -0
  71. package/lib/browser/internal/core/name-resolver.mjs +118 -0
  72. package/lib/browser/internal/core/name-resolver.mjs.map +1 -0
  73. package/lib/browser/internal/core/scope-tracker.d.mts +65 -0
  74. package/lib/browser/internal/core/scope-tracker.d.mts.map +1 -0
  75. package/lib/browser/internal/core/scope-tracker.mjs +120 -0
  76. package/lib/browser/internal/core/scope-tracker.mjs.map +1 -0
  77. package/lib/browser/internal/core/service-initializer.d.mts +44 -0
  78. package/lib/browser/internal/core/service-initializer.d.mts.map +1 -0
  79. package/lib/browser/internal/core/service-initializer.mjs +109 -0
  80. package/lib/browser/internal/core/service-initializer.mjs.map +1 -0
  81. package/lib/browser/internal/core/service-invalidator.d.mts +81 -0
  82. package/lib/browser/internal/core/service-invalidator.d.mts.map +1 -0
  83. package/lib/browser/internal/core/service-invalidator.mjs +142 -0
  84. package/lib/browser/internal/core/service-invalidator.mjs.map +1 -0
  85. package/lib/browser/internal/core/token-resolver.d.mts +54 -0
  86. package/lib/browser/internal/core/token-resolver.d.mts.map +1 -0
  87. package/lib/browser/internal/core/token-resolver.mjs +77 -0
  88. package/lib/browser/internal/core/token-resolver.mjs.map +1 -0
  89. package/lib/browser/internal/holder/holder-storage.interface.d.mts +99 -0
  90. package/lib/browser/internal/holder/holder-storage.interface.d.mts.map +1 -0
  91. package/lib/browser/internal/holder/instance-holder.d.mts +101 -0
  92. package/lib/browser/internal/holder/instance-holder.d.mts.map +1 -0
  93. package/lib/browser/internal/holder/instance-holder.mjs +19 -0
  94. package/lib/browser/internal/holder/instance-holder.mjs.map +1 -0
  95. package/lib/browser/internal/holder/unified-storage.d.mts +53 -0
  96. package/lib/browser/internal/holder/unified-storage.d.mts.map +1 -0
  97. package/lib/browser/internal/holder/unified-storage.mjs +144 -0
  98. package/lib/browser/internal/holder/unified-storage.mjs.map +1 -0
  99. package/lib/browser/internal/lifecycle/circular-detector.d.mts +39 -0
  100. package/lib/browser/internal/lifecycle/circular-detector.d.mts.map +1 -0
  101. package/lib/browser/internal/lifecycle/circular-detector.mjs +55 -0
  102. package/lib/browser/internal/lifecycle/circular-detector.mjs.map +1 -0
  103. package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts +18 -0
  104. package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts.map +1 -0
  105. package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs +43 -0
  106. package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs.map +1 -0
  107. package/lib/browser/internal/stub-factory-class.d.mts +14 -0
  108. package/lib/browser/internal/stub-factory-class.d.mts.map +1 -0
  109. package/lib/browser/internal/stub-factory-class.mjs +18 -0
  110. package/lib/browser/internal/stub-factory-class.mjs.map +1 -0
  111. package/lib/browser/symbols/injectable-token.d.mts +5 -0
  112. package/lib/browser/symbols/injectable-token.d.mts.map +1 -0
  113. package/lib/browser/symbols/injectable-token.mjs +6 -0
  114. package/lib/browser/symbols/injectable-token.mjs.map +1 -0
  115. package/lib/browser/token/injection-token.d.mts +55 -0
  116. package/lib/browser/token/injection-token.d.mts.map +1 -0
  117. package/lib/browser/token/injection-token.mjs +100 -0
  118. package/lib/browser/token/injection-token.mjs.map +1 -0
  119. package/lib/browser/token/registry.d.mts +37 -0
  120. package/lib/browser/token/registry.d.mts.map +1 -0
  121. package/lib/browser/token/registry.mjs +86 -0
  122. package/lib/browser/token/registry.mjs.map +1 -0
  123. package/lib/browser/utils/default-injectors.d.mts +12 -0
  124. package/lib/browser/utils/default-injectors.d.mts.map +1 -0
  125. package/lib/browser/utils/default-injectors.mjs +13 -0
  126. package/lib/browser/utils/default-injectors.mjs.map +1 -0
  127. package/lib/browser/utils/get-injectable-token.d.mts +9 -0
  128. package/lib/browser/utils/get-injectable-token.d.mts.map +1 -0
  129. package/lib/browser/utils/get-injectable-token.mjs +13 -0
  130. package/lib/browser/utils/get-injectable-token.mjs.map +1 -0
  131. package/lib/browser/utils/get-injectors.d.mts +55 -0
  132. package/lib/browser/utils/get-injectors.d.mts.map +1 -0
  133. package/lib/browser/utils/get-injectors.mjs +121 -0
  134. package/lib/browser/utils/get-injectors.mjs.map +1 -0
  135. package/lib/browser/utils/types.d.mts +23 -0
  136. package/lib/browser/utils/types.d.mts.map +1 -0
  137. package/lib/{container-DAKOvAgr.mjs → container-8-z89TyQ.mjs} +1325 -1462
  138. package/lib/container-8-z89TyQ.mjs.map +1 -0
  139. package/lib/{container-Bp1W-pWJ.d.mts → container-CNiqesCL.d.mts} +598 -617
  140. package/lib/container-CNiqesCL.d.mts.map +1 -0
  141. package/lib/{container-DENMeJ87.cjs → container-CaY2fDuk.cjs} +1369 -1512
  142. package/lib/container-CaY2fDuk.cjs.map +1 -0
  143. package/lib/{container-YPwvmlK2.d.cts → container-D-0Ho3qL.d.cts} +598 -612
  144. package/lib/container-D-0Ho3qL.d.cts.map +1 -0
  145. package/lib/index.cjs +13 -15
  146. package/lib/index.cjs.map +1 -1
  147. package/lib/index.d.cts +58 -223
  148. package/lib/index.d.cts.map +1 -1
  149. package/lib/index.d.mts +62 -222
  150. package/lib/index.d.mts.map +1 -1
  151. package/lib/index.mjs +5 -6
  152. package/lib/index.mjs.map +1 -1
  153. package/lib/testing/index.cjs +569 -311
  154. package/lib/testing/index.cjs.map +1 -1
  155. package/lib/testing/index.d.cts +370 -41
  156. package/lib/testing/index.d.cts.map +1 -1
  157. package/lib/testing/index.d.mts +370 -41
  158. package/lib/testing/index.d.mts.map +1 -1
  159. package/lib/testing/index.mjs +568 -305
  160. package/lib/testing/index.mjs.map +1 -1
  161. package/package.json +2 -1
  162. package/src/__tests__/circular-detector.spec.mts +193 -0
  163. package/src/__tests__/concurrent.spec.mts +368 -0
  164. package/src/__tests__/container.spec.mts +32 -30
  165. package/src/__tests__/di-error.spec.mts +351 -0
  166. package/src/__tests__/e2e.browser.spec.mts +0 -4
  167. package/src/__tests__/e2e.spec.mts +10 -19
  168. package/src/__tests__/event-emitter.spec.mts +232 -109
  169. package/src/__tests__/get-injectors.spec.mts +250 -39
  170. package/src/__tests__/injection-token.spec.mts +293 -349
  171. package/src/__tests__/library-findings.spec.mts +8 -8
  172. package/src/__tests__/registry.spec.mts +358 -210
  173. package/src/__tests__/resolution-context.spec.mts +255 -0
  174. package/src/__tests__/scope-tracker.spec.mts +598 -0
  175. package/src/__tests__/scope-upgrade.spec.mts +808 -0
  176. package/src/__tests__/scoped-container.spec.mts +595 -0
  177. package/src/__tests__/test-container.spec.mts +293 -0
  178. package/src/__tests__/token-resolver.spec.mts +207 -0
  179. package/src/__tests__/unified-storage.spec.mts +535 -0
  180. package/src/__tests__/unit-test-container.spec.mts +405 -0
  181. package/src/__type-tests__/container.spec-d.mts +180 -0
  182. package/src/__type-tests__/factory.spec-d.mts +15 -3
  183. package/src/__type-tests__/inject.spec-d.mts +115 -20
  184. package/src/__type-tests__/injectable.spec-d.mts +69 -52
  185. package/src/__type-tests__/injection-token.spec-d.mts +176 -0
  186. package/src/__type-tests__/scoped-container.spec-d.mts +212 -0
  187. package/src/container/abstract-container.mts +327 -0
  188. package/src/container/container.mts +142 -170
  189. package/src/container/scoped-container.mts +126 -208
  190. package/src/decorators/factory.decorator.mts +16 -11
  191. package/src/decorators/injectable.decorator.mts +20 -16
  192. package/src/enums/index.mts +2 -2
  193. package/src/enums/injectable-scope.enum.mts +1 -0
  194. package/src/enums/injectable-type.enum.mts +1 -0
  195. package/src/errors/di-error.mts +96 -0
  196. package/src/event-emitter.mts +3 -27
  197. package/src/index.mts +6 -153
  198. package/src/interfaces/container.interface.mts +13 -0
  199. package/src/interfaces/factory.interface.mts +1 -1
  200. package/src/interfaces/index.mts +1 -1
  201. package/src/internal/context/async-local-storage.mts +3 -2
  202. package/src/internal/context/async-local-storage.types.mts +1 -0
  203. package/src/internal/context/factory-context.mts +1 -0
  204. package/src/internal/context/index.mts +3 -1
  205. package/src/internal/context/resolution-context.mts +1 -0
  206. package/src/internal/context/service-initialization-context.mts +43 -0
  207. package/src/internal/core/index.mts +5 -4
  208. package/src/internal/core/instance-resolver.mts +460 -302
  209. package/src/internal/core/name-resolver.mts +196 -0
  210. package/src/internal/core/scope-tracker.mts +242 -0
  211. package/src/internal/core/{instantiator.mts → service-initializer.mts} +51 -29
  212. package/src/internal/core/service-invalidator.mts +290 -0
  213. package/src/internal/core/token-resolver.mts +122 -0
  214. package/src/internal/holder/holder-storage.interface.mts +11 -5
  215. package/src/internal/holder/index.mts +2 -5
  216. package/src/internal/holder/instance-holder.mts +1 -3
  217. package/src/internal/holder/unified-storage.mts +245 -0
  218. package/src/internal/index.mts +2 -1
  219. package/src/internal/lifecycle/circular-detector.mts +1 -0
  220. package/src/internal/lifecycle/index.mts +1 -1
  221. package/src/internal/lifecycle/lifecycle-event-bus.mts +1 -0
  222. package/src/internal/stub-factory-class.mts +16 -0
  223. package/src/symbols/injectable-token.mts +3 -1
  224. package/src/testing/index.mts +2 -0
  225. package/src/testing/test-container.mts +546 -85
  226. package/src/testing/types.mts +117 -0
  227. package/src/testing/unit-test-container.mts +509 -0
  228. package/src/token/injection-token.mts +41 -4
  229. package/src/token/registry.mts +75 -9
  230. package/src/utils/default-injectors.mts +16 -0
  231. package/src/utils/get-injectable-token.mts +2 -3
  232. package/src/utils/get-injectors.mts +26 -15
  233. package/src/utils/index.mts +3 -1
  234. package/src/utils/types.mts +1 -0
  235. package/tsdown.config.mts +11 -1
  236. package/lib/browser/index.d.mts.map +0 -1
  237. package/lib/browser/index.mjs.map +0 -1
  238. package/lib/container-Bp1W-pWJ.d.mts.map +0 -1
  239. package/lib/container-DAKOvAgr.mjs.map +0 -1
  240. package/lib/container-DENMeJ87.cjs.map +0 -1
  241. package/lib/container-YPwvmlK2.d.cts.map +0 -1
  242. package/src/__tests__/async-local-storage.browser.spec.mts +0 -166
  243. package/src/__tests__/async-local-storage.spec.mts +0 -333
  244. package/src/__tests__/errors.spec.mts +0 -87
  245. package/src/__tests__/factory.spec.mts +0 -137
  246. package/src/__tests__/injectable.spec.mts +0 -246
  247. package/src/__tests__/request-scope.spec.mts +0 -416
  248. package/src/__tests__/service-instantiator.spec.mts +0 -410
  249. package/src/__tests__/service-locator-event-bus.spec.mts +0 -242
  250. package/src/__tests__/service-locator-manager.spec.mts +0 -300
  251. package/src/__tests__/service-locator.spec.mts +0 -966
  252. package/src/__tests__/unified-api.spec.mts +0 -130
  253. package/src/browser.mts +0 -11
  254. package/src/injectors.mts +0 -18
  255. package/src/internal/context/request-context.mts +0 -225
  256. package/src/internal/core/invalidator.mts +0 -437
  257. package/src/internal/core/service-locator.mts +0 -202
  258. package/src/internal/core/token-processor.mts +0 -252
  259. package/src/internal/holder/base-holder-manager.mts +0 -334
  260. package/src/internal/holder/holder-manager.mts +0 -85
  261. package/src/internal/holder/request-storage.mts +0 -127
  262. package/src/internal/holder/singleton-storage.mts +0 -92
  263. package/src/testing/README.md +0 -80
  264. package/src/testing/__tests__/test-container.spec.mts +0 -173
@@ -0,0 +1,196 @@
1
+ import type { InjectionTokenType } from '../../token/injection-token.mjs'
2
+
3
+ import { InjectableScope } from '../../enums/index.mjs'
4
+
5
+ /**
6
+ * Simple LRU cache for instance name generation.
7
+ * Uses a Map which maintains insertion order for efficient LRU eviction.
8
+ */
9
+ class InstanceNameCache {
10
+ private readonly cache = new Map<string, string>()
11
+ private readonly maxSize: number
12
+
13
+ constructor(maxSize = 1000) {
14
+ this.maxSize = maxSize
15
+ }
16
+
17
+ get(key: string): string | undefined {
18
+ const value = this.cache.get(key)
19
+ if (value !== undefined) {
20
+ // Move to end (most recently used)
21
+ this.cache.delete(key)
22
+ this.cache.set(key, value)
23
+ }
24
+ return value
25
+ }
26
+
27
+ set(key: string, value: string): void {
28
+ if (this.cache.has(key)) {
29
+ this.cache.delete(key)
30
+ } else if (this.cache.size >= this.maxSize) {
31
+ // Remove least recently used (first item)
32
+ const firstKey = this.cache.keys().next().value
33
+ if (firstKey !== undefined) {
34
+ this.cache.delete(firstKey)
35
+ }
36
+ }
37
+ this.cache.set(key, value)
38
+ }
39
+
40
+ clear(): void {
41
+ this.cache.clear()
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Simple hash function for deterministic hashing of arguments
47
+ */
48
+ function hashArgs(args: any): string {
49
+ const str = JSON.stringify(args, Object.keys(args || {}).sort())
50
+ let hash = 0
51
+ for (let i = 0; i < str.length; i++) {
52
+ const char = str.charCodeAt(i)
53
+ hash = (hash << 5) - hash + char
54
+ hash = hash & hash // Convert to 32-bit integer
55
+ }
56
+ return Math.abs(hash).toString(36)
57
+ }
58
+
59
+ /**
60
+ * Handles instance name generation with support for requestId and scope.
61
+ *
62
+ * Generates unique instance identifiers based on token, arguments, and scope.
63
+ * Request-scoped services MUST include requestId in their name for proper isolation.
64
+ */
65
+ export class NameResolver {
66
+ private readonly instanceNameCache = new InstanceNameCache()
67
+
68
+ constructor(private readonly logger: Console | null = null) {}
69
+
70
+ /**
71
+ * Generates a unique instance name based on token, arguments, requestId, and scope.
72
+ *
73
+ * Name formats:
74
+ * - Singleton/Transient without args: `${tokenId}`
75
+ * - Singleton/Transient with args: `${tokenId}:${argsHash}`
76
+ * - Request without args: `${tokenId}:requestId=${requestId}`
77
+ * - Request with args: `${tokenId}:requestId=${requestId}:${argsHash}`
78
+ *
79
+ * @param token The injection token
80
+ * @param args Optional arguments
81
+ * @param requestId Optional request ID (required for request-scoped services)
82
+ * @param scope Optional scope (used to determine if requestId should be included)
83
+ * @returns The generated instance name
84
+ */
85
+ generateInstanceName(
86
+ token: InjectionTokenType,
87
+ args?: any,
88
+ requestId?: string,
89
+ scope?: InjectableScope,
90
+ ): string {
91
+ const tokenStr = token.toString()
92
+ const isRequest = scope === InjectableScope.Request
93
+
94
+ // For request-scoped services, requestId is required
95
+ if (isRequest && !requestId) {
96
+ throw new Error(
97
+ `[NameResolver] requestId is required for request-scoped services`,
98
+ )
99
+ }
100
+
101
+ // Build cache key
102
+ const cacheKey = `${tokenStr}:${scope}:${requestId || ''}:${args ? JSON.stringify(args) : ''}`
103
+
104
+ // Check cache first
105
+ const cached = this.instanceNameCache.get(cacheKey)
106
+ if (cached !== undefined) {
107
+ return cached
108
+ }
109
+
110
+ // Generate the instance name
111
+ let result = tokenStr
112
+
113
+ // Add requestId for request-scoped services
114
+ if (isRequest && requestId) {
115
+ result = `${result}:requestId=${requestId}`
116
+ }
117
+
118
+ // Add args hash if args are provided
119
+ if (args) {
120
+ const argsHash = hashArgs(args)
121
+ result = `${result}:${argsHash}`
122
+ }
123
+
124
+ // Cache the result
125
+ this.instanceNameCache.set(cacheKey, result)
126
+
127
+ return result
128
+ }
129
+
130
+ /**
131
+ * Upgrades an existing instance name to include requestId.
132
+ * Preserves any args hash that might already be in the name.
133
+ *
134
+ * Examples:
135
+ * - `TokenName` → `TokenName:requestId=req-123`
136
+ * - `TokenName:abc123` → `TokenName:requestId=req-123:abc123`
137
+ *
138
+ * @param existingName The existing instance name (without requestId)
139
+ * @param requestId The request ID to add
140
+ * @returns The upgraded instance name with requestId
141
+ */
142
+ upgradeInstanceNameToRequest(
143
+ existingName: string,
144
+ requestId: string,
145
+ ): string {
146
+ // Check if requestId is already in the name
147
+ if (existingName.includes(`:requestId=${requestId}`)) {
148
+ return existingName
149
+ }
150
+
151
+ // Find where to insert requestId
152
+ // Format: TokenName or TokenName:argsHash
153
+ // We want: TokenName:requestId=req-123 or TokenName:requestId=req-123:argsHash
154
+
155
+ // Check if there's an args hash (starts after first colon, but not requestId=)
156
+ const requestIdPattern = /:requestId=/
157
+ const hasRequestId = requestIdPattern.test(existingName)
158
+
159
+ if (hasRequestId) {
160
+ // Already has a requestId, don't upgrade
161
+ return existingName
162
+ }
163
+
164
+ // Find the token part (everything before first colon, or entire string if no colon)
165
+ const colonIndex = existingName.indexOf(':')
166
+ if (colonIndex === -1) {
167
+ // No colon, just token name: TokenName → TokenName:requestId=req-123
168
+ return `${existingName}:requestId=${requestId}`
169
+ }
170
+
171
+ // Has colon, means there's an args hash: TokenName:abc123 → TokenName:requestId=req-123:abc123
172
+ const tokenPart = existingName.substring(0, colonIndex)
173
+ const argsPart = existingName.substring(colonIndex + 1)
174
+
175
+ // Check if argsPart looks like an args hash (not requestId=)
176
+ if (argsPart.startsWith('requestId=')) {
177
+ // Already has requestId, return as is
178
+ return existingName
179
+ }
180
+
181
+ return `${tokenPart}:requestId=${requestId}:${argsPart}`
182
+ }
183
+
184
+ /**
185
+ * Formats a single argument value for instance name generation.
186
+ */
187
+ formatArgValue(value: any): string {
188
+ if (typeof value === 'function') {
189
+ return `fn_${value.name}(${value.length})`
190
+ }
191
+ if (typeof value === 'symbol') {
192
+ return value.toString()
193
+ }
194
+ return JSON.stringify(value).slice(0, 40)
195
+ }
196
+ }
@@ -0,0 +1,242 @@
1
+ import type { InjectableScope } from '../../enums/index.mjs'
2
+ import type { InjectionToken } from '../../token/injection-token.mjs'
3
+ import type { IHolderStorage } from '../holder/holder-storage.interface.mjs'
4
+
5
+ import { InjectableScope as Scope } from '../../enums/index.mjs'
6
+ import { DIError } from '../../errors/index.mjs'
7
+ import { InstanceStatus } from '../holder/instance-holder.mjs'
8
+ import { Registry } from '../../token/registry.mjs'
9
+ import { NameResolver } from './name-resolver.mjs'
10
+
11
+ /**
12
+ * Component for tracking and handling scope upgrades.
13
+ *
14
+ * Detects when a Singleton service needs to be upgraded to Request scope
15
+ * and coordinates the scope upgrade process atomically.
16
+ */
17
+ export class ScopeTracker {
18
+ constructor(
19
+ private readonly registry: Registry,
20
+ private readonly nameResolver: NameResolver,
21
+ private readonly logger: Console | null = null,
22
+ ) {}
23
+
24
+ /**
25
+ * Checks if a dependency requires scope upgrade and performs it if needed.
26
+ * Called during service resolution when a dependency is resolved.
27
+ *
28
+ * @param currentServiceName - Name of the service being created
29
+ * @param currentServiceScope - Current scope of the service being created
30
+ * @param dependencyName - Name of the dependency being resolved
31
+ * @param dependencyScope - Scope of the dependency
32
+ * @param dependencyToken - Token of the dependency
33
+ * @param singletonStorage - Singleton storage instance
34
+ * @param requestStorage - Request storage instance (if in request context)
35
+ * @param requestId - Request ID (if in request context)
36
+ * @returns [needsUpgrade: boolean, newName?: string] - whether upgrade occurred and new name
37
+ */
38
+ checkAndUpgradeScope(
39
+ currentServiceName: string,
40
+ currentServiceScope: InjectableScope,
41
+ dependencyName: string,
42
+ dependencyScope: InjectableScope,
43
+ dependencyToken: InjectionToken<any, any>,
44
+ singletonStorage: IHolderStorage,
45
+ requestStorage?: IHolderStorage,
46
+ requestId?: string,
47
+ ): [boolean, string?] {
48
+ // Only upgrade if current service is Singleton and dependency is Request
49
+ if (
50
+ currentServiceScope !== Scope.Singleton ||
51
+ dependencyScope !== Scope.Request
52
+ ) {
53
+ return [false]
54
+ }
55
+
56
+ // Need request storage and requestId for upgrade
57
+ if (!requestStorage || !requestId) {
58
+ this.logger?.warn(
59
+ `[ScopeTracker] Cannot upgrade scope for ${currentServiceName}: missing requestStorage or requestId`,
60
+ )
61
+ return [false]
62
+ }
63
+
64
+ // Perform the upgrade
65
+ this.logger?.log(
66
+ `[ScopeTracker] Upgrading ${currentServiceName} from Singleton to Request scope`,
67
+ )
68
+
69
+ try {
70
+ const [success, newName] = this.upgradeScopeToRequestSync(
71
+ currentServiceName,
72
+ dependencyToken,
73
+ singletonStorage,
74
+ requestStorage,
75
+ requestId,
76
+ )
77
+
78
+ if (success && newName) {
79
+ return [true, newName]
80
+ }
81
+ } catch (error) {
82
+ this.logger?.error(
83
+ `[ScopeTracker] Error upgrading scope for ${currentServiceName}:`,
84
+ error,
85
+ )
86
+ }
87
+
88
+ return [false]
89
+ }
90
+
91
+ /**
92
+ * Performs the actual scope upgrade from Singleton to Request.
93
+ * This is the core migration logic.
94
+ *
95
+ * @param serviceName - Current service name (without requestId)
96
+ * @param token - Service injection token
97
+ * @param singletonStorage - Source storage
98
+ * @param requestStorage - Target storage
99
+ * @param requestId - Request ID to include in new name
100
+ * @returns [success: boolean, newName?: string, error?: DIError]
101
+ */
102
+ async upgradeScopeToRequest(
103
+ serviceName: string,
104
+ token: InjectionToken<any, any>,
105
+ singletonStorage: IHolderStorage,
106
+ requestStorage: IHolderStorage,
107
+ requestId: string,
108
+ ): Promise<[boolean, string?, DIError?]> {
109
+ try {
110
+ const [success, newName] = this.upgradeScopeToRequestSync(
111
+ serviceName,
112
+ token,
113
+ singletonStorage,
114
+ requestStorage,
115
+ requestId,
116
+ )
117
+
118
+ if (success && newName) {
119
+ return [true, newName]
120
+ }
121
+ return [
122
+ false,
123
+ undefined,
124
+ DIError.storageError(
125
+ 'Scope upgrade failed',
126
+ 'upgradeScopeToRequest',
127
+ serviceName,
128
+ ),
129
+ ]
130
+ } catch (error) {
131
+ return [
132
+ false,
133
+ undefined,
134
+ error instanceof DIError ? error : DIError.unknown(error as Error),
135
+ ]
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Synchronous part of scope upgrade - handles immediate updates.
141
+ * Async operations (like waiting for holder creation) should be done separately.
142
+ */
143
+ private upgradeScopeToRequestSync(
144
+ serviceName: string,
145
+ token: InjectionToken<any, any>,
146
+ singletonStorage: IHolderStorage,
147
+ requestStorage: IHolderStorage,
148
+ requestId: string,
149
+ ): [boolean, string?] {
150
+ // 1. Upgrade existing instance name to include requestId
151
+ // This preserves any args hash that might be in the original name
152
+ const newName = this.nameResolver.upgradeInstanceNameToRequest(
153
+ serviceName,
154
+ requestId,
155
+ )
156
+
157
+ // 2. Update Registry scope to Request (synchronous)
158
+ const updated = this.registry.updateScope(token, Scope.Request)
159
+ if (!updated) {
160
+ this.logger?.warn(
161
+ `[ScopeTracker] Could not update scope in registry for ${serviceName}`,
162
+ )
163
+ return [false]
164
+ }
165
+
166
+ // 3. Check if holder exists in singleton storage
167
+ const holderResult = singletonStorage.get(serviceName)
168
+ if (holderResult === null) {
169
+ // No holder exists yet - just update registry, future resolutions will use request storage
170
+ return [true, newName]
171
+ }
172
+
173
+ const [error, holder] = holderResult
174
+ if (error) {
175
+ this.logger?.warn(
176
+ `[ScopeTracker] Holder for ${serviceName} is in error state: ${error.message}`,
177
+ )
178
+ return [false]
179
+ }
180
+
181
+ if (!holder) {
182
+ return [false]
183
+ }
184
+
185
+ // 4. If holder is in "Creating" state, we need to wait for it before migrating
186
+ // For now, we'll update the name and move it, but the caller should wait for creation
187
+ if (holder.status === InstanceStatus.Creating) {
188
+ // Update holder name
189
+ holder.name = newName
190
+ // Move to request storage
191
+ requestStorage.set(newName, holder)
192
+ // Remove from singleton storage
193
+ singletonStorage.delete(serviceName)
194
+ // Update parent dependencies
195
+ this.updateParentDependencies(
196
+ serviceName,
197
+ newName,
198
+ singletonStorage,
199
+ requestStorage,
200
+ )
201
+ return [true, newName]
202
+ }
203
+
204
+ // 5. Move holder from singleton to request storage
205
+ holder.name = newName
206
+ requestStorage.set(newName, holder)
207
+ singletonStorage.delete(serviceName)
208
+
209
+ // 6. Update all parent dependencies
210
+ this.updateParentDependencies(
211
+ serviceName,
212
+ newName,
213
+ singletonStorage,
214
+ requestStorage,
215
+ )
216
+
217
+ return [true, newName]
218
+ }
219
+
220
+ /**
221
+ * Updates all parent dependencies to reference the new service name.
222
+ *
223
+ * @param oldName - Original service name
224
+ * @param newName - New service name with requestId
225
+ * @param singletonStorage - Singleton storage to check
226
+ * @param requestStorage - Request storage to check
227
+ */
228
+ updateParentDependencies(
229
+ oldName: string,
230
+ newName: string,
231
+ singletonStorage: IHolderStorage,
232
+ requestStorage?: IHolderStorage,
233
+ ): void {
234
+ // Update dependencies in singleton storage
235
+ singletonStorage.updateDependencyReference(oldName, newName)
236
+
237
+ // Update dependencies in request storage if provided
238
+ if (requestStorage) {
239
+ requestStorage.updateDependencyReference(oldName, newName)
240
+ }
241
+ }
242
+ }
@@ -1,6 +1,6 @@
1
1
  import type { FactoryRecord } from '../../token/registry.mjs'
2
- import type { Injectors } from '../../utils/get-injectors.mjs'
3
- import type { FactoryContext } from '../context/factory-context.mjs'
2
+ import type { Injectors } from '../../utils/index.mjs'
3
+ import type { ServiceInitializationContext } from '../context/service-initialization-context.mjs'
4
4
 
5
5
  import { InjectableType } from '../../enums/index.mjs'
6
6
  import { DIError } from '../../errors/index.mjs'
@@ -9,10 +9,9 @@ import { DIError } from '../../errors/index.mjs'
9
9
  * Creates service instances from registry records.
10
10
  *
11
11
  * Handles both class-based (@Injectable) and factory-based (@Factory) services,
12
- * managing the instantiation lifecycle including sync initialization retries
13
- * and lifecycle hook invocation (onServiceInit, onServiceDestroy).
12
+ * managing the instantiation lifecycle including lifecycle hook invocation.
14
13
  */
15
- export class Instantiator {
14
+ export class ServiceInitializer {
16
15
  constructor(private readonly injectors: Injectors) {}
17
16
 
18
17
  /**
@@ -23,7 +22,7 @@ export class Instantiator {
23
22
  * @returns Promise resolving to [undefined, instance] or [error]
24
23
  */
25
24
  async instantiateService<T>(
26
- ctx: FactoryContext,
25
+ ctx: ServiceInitializationContext,
27
26
  record: FactoryRecord<T, any>,
28
27
  args: any = undefined,
29
28
  ): Promise<[undefined, T] | [DIError]> {
@@ -35,11 +34,15 @@ export class Instantiator {
35
34
  return this.instantiateFactory(ctx, record, args)
36
35
  default:
37
36
  throw DIError.unknown(
38
- `[Instantiator] Unknown service type: ${record.type}`,
37
+ `[ServiceInitializer] Unknown service type: ${record.type}`,
39
38
  )
40
39
  }
41
40
  } catch (error) {
42
- return [error instanceof DIError ? error : DIError.unknown(String(error))]
41
+ return [
42
+ error instanceof DIError
43
+ ? error
44
+ : DIError.initializationError(record.target.name, error as Error),
45
+ ]
43
46
  }
44
47
  }
45
48
 
@@ -51,13 +54,15 @@ export class Instantiator {
51
54
  * @returns Promise resolving to [undefined, instance] or [error]
52
55
  */
53
56
  private async instantiateClass<T>(
54
- ctx: FactoryContext,
57
+ ctx: ServiceInitializationContext,
55
58
  record: FactoryRecord<T, any>,
56
59
  args: any,
57
60
  ): Promise<[undefined, T] | [DIError]> {
58
61
  try {
59
62
  const tryLoad = this.injectors.wrapSyncInit(() => {
60
- const original = this.injectors.provideFactoryContext(ctx)
63
+ const original = this.injectors.provideFactoryContext(
64
+ ctx as ServiceInitializationContext,
65
+ )
61
66
  let result = new record.target(...(args ? [args] : []))
62
67
  this.injectors.provideFactoryContext(original)
63
68
  return result
@@ -67,8 +72,9 @@ export class Instantiator {
67
72
  if (promises.length > 0) {
68
73
  const results = await Promise.allSettled(promises)
69
74
  if (results.some((result) => result.status === 'rejected')) {
70
- throw DIError.unknown(
71
- `[Instantiator] Service ${record.target.name} cannot be instantiated.`,
75
+ throw DIError.initializationError(
76
+ record.target.name,
77
+ new Error('Service cannot be instantiated'),
72
78
  )
73
79
  }
74
80
  const newRes = tryLoad(injectState)
@@ -77,13 +83,16 @@ export class Instantiator {
77
83
  }
78
84
 
79
85
  if (promises.length > 0) {
80
- console.error(`[Instantiator] ${record.target.name} has problem with it's definition.
86
+ console.error(
87
+ `[ServiceInitializer] ${record.target.name} has problem with it's definition.
81
88
 
82
- One or more of the dependencies are registered as a InjectableScope.Instance and are used with inject.
89
+ One or more of the dependencies are registered as a InjectableScope.Transient and are used with inject.
83
90
 
84
- Please use inject asyncInject of inject to load those dependencies.`)
85
- throw DIError.unknown(
86
- `[Instantiator] Service ${record.target.name} cannot be instantiated.`,
91
+ Please use asyncInject instead of inject to load those dependencies.`,
92
+ )
93
+ throw DIError.initializationError(
94
+ record.target.name,
95
+ new Error('Service cannot be instantiated'),
87
96
  )
88
97
  }
89
98
 
@@ -99,7 +108,11 @@ export class Instantiator {
99
108
 
100
109
  return [undefined, instance]
101
110
  } catch (error) {
102
- return [error instanceof DIError ? error : DIError.unknown(String(error))]
111
+ return [
112
+ error instanceof DIError
113
+ ? error
114
+ : DIError.initializationError(record.target.name, error as Error),
115
+ ]
103
116
  }
104
117
  }
105
118
 
@@ -111,7 +124,7 @@ export class Instantiator {
111
124
  * @returns Promise resolving to [undefined, instance] or [error]
112
125
  */
113
126
  private async instantiateFactory<T>(
114
- ctx: FactoryContext,
127
+ ctx: ServiceInitializationContext,
115
128
  record: FactoryRecord<T, any>,
116
129
  args: any,
117
130
  ): Promise<[undefined, T] | [DIError]> {
@@ -127,8 +140,9 @@ export class Instantiator {
127
140
  if (promises.length > 0) {
128
141
  const results = await Promise.allSettled(promises)
129
142
  if (results.some((result) => result.status === 'rejected')) {
130
- throw DIError.unknown(
131
- `[Instantiator] Service ${record.target.name} cannot be instantiated.`,
143
+ throw DIError.initializationError(
144
+ record.target.name,
145
+ new Error('Service cannot be instantiated'),
132
146
  )
133
147
  }
134
148
  const newRes = tryLoad(injectState)
@@ -137,26 +151,34 @@ export class Instantiator {
137
151
  }
138
152
 
139
153
  if (promises.length > 0) {
140
- console.error(`[Instantiator] ${record.target.name} has problem with it's definition.
154
+ console.error(
155
+ `[ServiceInitializer] ${record.target.name} has problem with it's definition.
141
156
 
142
- One or more of the dependencies are registered as a InjectableScope.Instance and are used with inject.
157
+ One or more of the dependencies are registered as a InjectableScope.Transient and are used with inject.
143
158
 
144
- Please use asyncInject instead of inject to load those dependencies.`)
145
- throw DIError.unknown(
146
- `[Instantiator] Service ${record.target.name} cannot be instantiated.`,
159
+ Please use asyncInject instead of inject to load those dependencies.`,
160
+ )
161
+ throw DIError.initializationError(
162
+ record.target.name,
163
+ new Error('Service cannot be instantiated'),
147
164
  )
148
165
  }
149
166
 
150
167
  if (typeof builder.create !== 'function') {
151
- throw DIError.unknown(
152
- `[Instantiator] Factory ${record.target.name} does not implement the create method.`,
168
+ throw DIError.initializationError(
169
+ record.target.name,
170
+ new Error('Factory does not implement the create method'),
153
171
  )
154
172
  }
155
173
 
156
174
  const instance = await builder.create(ctx, args)
157
175
  return [undefined, instance]
158
176
  } catch (error) {
159
- return [error instanceof DIError ? error : DIError.unknown(String(error))]
177
+ return [
178
+ error instanceof DIError
179
+ ? error
180
+ : DIError.initializationError(record.target.name, error as Error),
181
+ ]
160
182
  }
161
183
  }
162
184
  }