@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,535 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest'
2
+
3
+ import type { InstanceHolder } from '../internal/holder/instance-holder.mjs'
4
+
5
+ import { InjectableScope, InjectableType } from '../enums/index.mjs'
6
+ import { DIError, DIErrorCode } from '../errors/index.mjs'
7
+ import { InstanceStatus } from '../internal/holder/instance-holder.mjs'
8
+ import { UnifiedStorage } from '../internal/holder/unified-storage.mjs'
9
+
10
+ describe('UnifiedStorage', () => {
11
+ let storage: UnifiedStorage
12
+
13
+ beforeEach(() => {
14
+ storage = new UnifiedStorage(InjectableScope.Singleton)
15
+ })
16
+
17
+ describe('constructor', () => {
18
+ it('should create storage with default Singleton scope', () => {
19
+ const defaultStorage = new UnifiedStorage()
20
+ expect(defaultStorage.scope).toBe(InjectableScope.Singleton)
21
+ })
22
+
23
+ it('should create storage with specified scope', () => {
24
+ const requestStorage = new UnifiedStorage(InjectableScope.Request)
25
+ expect(requestStorage.scope).toBe(InjectableScope.Request)
26
+ })
27
+ })
28
+
29
+ describe('get', () => {
30
+ it('should return null for non-existent instance', () => {
31
+ const result = storage.get('non-existent')
32
+ expect(result).toBeNull()
33
+ })
34
+
35
+ it('should return holder for existing instance in Created state', () => {
36
+ const holder: InstanceHolder = {
37
+ status: InstanceStatus.Created,
38
+ name: 'test',
39
+ instance: { value: 'test' },
40
+ creationPromise: null,
41
+ destroyPromise: null,
42
+ type: InjectableType.Class,
43
+ scope: InjectableScope.Singleton,
44
+ deps: new Set(),
45
+ destroyListeners: [],
46
+ createdAt: Date.now(),
47
+ waitingFor: new Set(),
48
+ }
49
+ storage.set('test', holder)
50
+
51
+ const result = storage.get('test')
52
+ expect(result).not.toBeNull()
53
+ expect(result![0]).toBeUndefined()
54
+ expect(result![1]).toBe(holder)
55
+ })
56
+
57
+ it('should return holder for instance in Creating state', () => {
58
+ const holder: InstanceHolder = {
59
+ status: InstanceStatus.Creating,
60
+ name: 'test',
61
+ instance: null,
62
+ creationPromise: Promise.resolve([undefined, {}]) as any,
63
+ destroyPromise: null,
64
+ type: InjectableType.Class,
65
+ scope: InjectableScope.Singleton,
66
+ deps: new Set(),
67
+ destroyListeners: [],
68
+ createdAt: Date.now(),
69
+ waitingFor: new Set(),
70
+ }
71
+ storage.set('test', holder)
72
+
73
+ const result = storage.get('test')
74
+ expect(result).not.toBeNull()
75
+ expect(result![0]).toBeUndefined()
76
+ expect(result![1]).toBe(holder)
77
+ })
78
+
79
+ it('should return error for instance in Destroying state', () => {
80
+ const holder: InstanceHolder = {
81
+ status: InstanceStatus.Destroying,
82
+ name: 'test',
83
+ instance: null,
84
+ creationPromise: null,
85
+ destroyPromise: Promise.resolve(),
86
+ type: InjectableType.Class,
87
+ scope: InjectableScope.Singleton,
88
+ deps: new Set(),
89
+ destroyListeners: [],
90
+ createdAt: Date.now(),
91
+ waitingFor: new Set(),
92
+ }
93
+ storage.set('test', holder)
94
+
95
+ const result = storage.get('test')
96
+ expect(result).not.toBeNull()
97
+ expect(result![0]).toBeInstanceOf(DIError)
98
+ expect(result![0]?.code).toBe(DIErrorCode.InstanceDestroying)
99
+ })
100
+
101
+ it('should return error for instance in Error state', () => {
102
+ const originalError = DIError.initializationError('test', 'Failed')
103
+ const holder: InstanceHolder = {
104
+ status: InstanceStatus.Error,
105
+ name: 'test',
106
+ instance: originalError,
107
+ creationPromise: null,
108
+ destroyPromise: null,
109
+ type: InjectableType.Class,
110
+ scope: InjectableScope.Singleton,
111
+ deps: new Set(),
112
+ destroyListeners: [],
113
+ createdAt: Date.now(),
114
+ waitingFor: new Set(),
115
+ }
116
+ storage.set('test', holder)
117
+
118
+ const result = storage.get('test')
119
+ expect(result).not.toBeNull()
120
+ expect(result![0]).toBe(originalError)
121
+ })
122
+ })
123
+
124
+ describe('set', () => {
125
+ it('should store holder', () => {
126
+ const holder: InstanceHolder = {
127
+ status: InstanceStatus.Created,
128
+ name: 'test',
129
+ instance: {},
130
+ creationPromise: null,
131
+ destroyPromise: null,
132
+ type: InjectableType.Class,
133
+ scope: InjectableScope.Singleton,
134
+ deps: new Set(),
135
+ destroyListeners: [],
136
+ createdAt: Date.now(),
137
+ waitingFor: new Set(),
138
+ }
139
+ storage.set('test', holder)
140
+
141
+ const result = storage.get('test')
142
+ expect(result![1]).toBe(holder)
143
+ })
144
+
145
+ it('should register dependencies in reverse index', () => {
146
+ const holder: InstanceHolder = {
147
+ status: InstanceStatus.Created,
148
+ name: 'child',
149
+ instance: {},
150
+ creationPromise: null,
151
+ destroyPromise: null,
152
+ type: InjectableType.Class,
153
+ scope: InjectableScope.Singleton,
154
+ deps: new Set(['parent1', 'parent2']),
155
+ destroyListeners: [],
156
+ createdAt: Date.now(),
157
+ waitingFor: new Set(),
158
+ }
159
+ storage.set('child', holder)
160
+
161
+ // findDependents should now return the child for each parent
162
+ expect(storage.findDependents('parent1')).toContain('child')
163
+ expect(storage.findDependents('parent2')).toContain('child')
164
+ })
165
+ })
166
+
167
+ describe('delete', () => {
168
+ it('should delete existing holder', () => {
169
+ const holder: InstanceHolder = {
170
+ status: InstanceStatus.Created,
171
+ name: 'test',
172
+ instance: {},
173
+ creationPromise: null,
174
+ destroyPromise: null,
175
+ type: InjectableType.Class,
176
+ scope: InjectableScope.Singleton,
177
+ deps: new Set(),
178
+ destroyListeners: [],
179
+ createdAt: Date.now(),
180
+ waitingFor: new Set(),
181
+ }
182
+ storage.set('test', holder)
183
+
184
+ const result = storage.delete('test')
185
+ expect(result).toBe(true)
186
+ expect(storage.get('test')).toBeNull()
187
+ })
188
+
189
+ it('should return false for non-existent holder', () => {
190
+ const result = storage.delete('non-existent')
191
+ expect(result).toBe(false)
192
+ })
193
+
194
+ it('should remove from reverse dependency index', () => {
195
+ const holder: InstanceHolder = {
196
+ status: InstanceStatus.Created,
197
+ name: 'child',
198
+ instance: {},
199
+ creationPromise: null,
200
+ destroyPromise: null,
201
+ type: InjectableType.Class,
202
+ scope: InjectableScope.Singleton,
203
+ deps: new Set(['parent']),
204
+ destroyListeners: [],
205
+ createdAt: Date.now(),
206
+ waitingFor: new Set(),
207
+ }
208
+ storage.set('child', holder)
209
+ expect(storage.findDependents('parent')).toContain('child')
210
+
211
+ storage.delete('child')
212
+ expect(storage.findDependents('parent')).not.toContain('child')
213
+ })
214
+ })
215
+
216
+ describe('createHolder', () => {
217
+ it('should create holder in Creating state', () => {
218
+ const [deferred, holder] = storage.createHolder<{ value: string }>(
219
+ 'test',
220
+ InjectableType.Class,
221
+ new Set(),
222
+ )
223
+
224
+ expect(holder.status).toBe(InstanceStatus.Creating)
225
+ expect(holder.name).toBe('test')
226
+ expect(holder.instance).toBeNull()
227
+ expect(holder.creationPromise).toBe(deferred.promise)
228
+ expect(holder.type).toBe(InjectableType.Class)
229
+ expect(holder.scope).toBe(InjectableScope.Singleton)
230
+ })
231
+
232
+ it('should return working deferred promise', async () => {
233
+ const [deferred, holder] = storage.createHolder<{ value: string }>(
234
+ 'test',
235
+ InjectableType.Class,
236
+ new Set(),
237
+ )
238
+
239
+ const testValue = { value: 'resolved' }
240
+ deferred.resolve([undefined, testValue])
241
+
242
+ const result = await holder.creationPromise
243
+ expect(result).toEqual([undefined, testValue])
244
+ })
245
+
246
+ it('should use storage scope for holder', () => {
247
+ const requestStorage = new UnifiedStorage(InjectableScope.Request)
248
+ const [, holder] = requestStorage.createHolder(
249
+ 'test',
250
+ InjectableType.Class,
251
+ new Set(),
252
+ )
253
+
254
+ expect(holder.scope).toBe(InjectableScope.Request)
255
+ })
256
+
257
+ it('should include provided dependencies', () => {
258
+ const deps = new Set(['dep1', 'dep2'])
259
+ const [, holder] = storage.createHolder(
260
+ 'test',
261
+ InjectableType.Class,
262
+ deps,
263
+ )
264
+
265
+ expect(holder.deps).toBe(deps)
266
+ })
267
+ })
268
+
269
+ describe('storeInstance', () => {
270
+ it('should store instance directly in Created state', () => {
271
+ const instance = { value: 'test' }
272
+ storage.storeInstance('test', instance)
273
+
274
+ const result = storage.get('test')
275
+ expect(result![1]?.status).toBe(InstanceStatus.Created)
276
+ expect(result![1]?.instance).toBe(instance)
277
+ })
278
+
279
+ it('should throw error if instance already exists', () => {
280
+ storage.storeInstance('test', { value: 'first' })
281
+
282
+ expect(() => {
283
+ storage.storeInstance('test', { value: 'second' })
284
+ }).toThrow(DIError)
285
+ })
286
+
287
+ it('should register onServiceDestroy listener if present', () => {
288
+ const destroyFn = () => {}
289
+ const instance = { onServiceDestroy: destroyFn }
290
+ storage.storeInstance('test', instance)
291
+
292
+ const result = storage.get('test')
293
+ expect(result![1]?.destroyListeners).toContain(destroyFn)
294
+ })
295
+
296
+ it('should not add destroy listener for non-object values', () => {
297
+ storage.storeInstance('test', 'string-value')
298
+
299
+ const result = storage.get('test')
300
+ expect(result![1]?.destroyListeners).toHaveLength(0)
301
+ })
302
+ })
303
+
304
+ describe('handles', () => {
305
+ it('should return true for matching scope', () => {
306
+ expect(storage.handles(InjectableScope.Singleton)).toBe(true)
307
+ })
308
+
309
+ it('should return false for non-matching scope', () => {
310
+ expect(storage.handles(InjectableScope.Request)).toBe(false)
311
+ expect(storage.handles(InjectableScope.Transient)).toBe(false)
312
+ })
313
+ })
314
+
315
+ describe('getAllNames', () => {
316
+ it('should return empty array for empty storage', () => {
317
+ expect(storage.getAllNames()).toEqual([])
318
+ })
319
+
320
+ it('should return all instance names', () => {
321
+ storage.storeInstance('a', {})
322
+ storage.storeInstance('b', {})
323
+ storage.storeInstance('c', {})
324
+
325
+ const names = storage.getAllNames()
326
+ expect(names).toHaveLength(3)
327
+ expect(names).toContain('a')
328
+ expect(names).toContain('b')
329
+ expect(names).toContain('c')
330
+ })
331
+ })
332
+
333
+ describe('forEach', () => {
334
+ it('should iterate over all holders', () => {
335
+ storage.storeInstance('a', { id: 1 })
336
+ storage.storeInstance('b', { id: 2 })
337
+
338
+ const visited: string[] = []
339
+ storage.forEach((name) => {
340
+ visited.push(name)
341
+ })
342
+
343
+ expect(visited).toHaveLength(2)
344
+ expect(visited).toContain('a')
345
+ expect(visited).toContain('b')
346
+ })
347
+
348
+ it('should not call callback for empty storage', () => {
349
+ const callback = () => {
350
+ throw new Error('Should not be called')
351
+ }
352
+ storage.forEach(callback)
353
+ })
354
+ })
355
+
356
+ describe('findByInstance', () => {
357
+ it('should find holder by instance reference', () => {
358
+ const instance = { value: 'test' }
359
+ storage.storeInstance('test', instance)
360
+
361
+ const holder = storage.findByInstance(instance)
362
+ expect(holder).not.toBeNull()
363
+ expect(holder?.name).toBe('test')
364
+ })
365
+
366
+ it('should return null for non-existent instance', () => {
367
+ const holder = storage.findByInstance({ value: 'not-stored' })
368
+ expect(holder).toBeNull()
369
+ })
370
+
371
+ it('should match by reference, not value', () => {
372
+ const instance = { value: 'test' }
373
+ storage.storeInstance('test', instance)
374
+
375
+ // Different object with same value
376
+ const holder = storage.findByInstance({ value: 'test' })
377
+ expect(holder).toBeNull()
378
+ })
379
+ })
380
+
381
+ describe('findDependents', () => {
382
+ it('should return empty array for instance with no dependents', () => {
383
+ storage.storeInstance('orphan', {})
384
+ expect(storage.findDependents('orphan')).toEqual([])
385
+ })
386
+
387
+ it('should return dependents for instance', () => {
388
+ // Create parent
389
+ storage.storeInstance('parent', {})
390
+
391
+ // Create children that depend on parent
392
+ const child1Holder: InstanceHolder = {
393
+ status: InstanceStatus.Created,
394
+ name: 'child1',
395
+ instance: {},
396
+ creationPromise: null,
397
+ destroyPromise: null,
398
+ type: InjectableType.Class,
399
+ scope: InjectableScope.Singleton,
400
+ deps: new Set(['parent']),
401
+ destroyListeners: [],
402
+ createdAt: Date.now(),
403
+ waitingFor: new Set(),
404
+ }
405
+ const child2Holder: InstanceHolder = {
406
+ status: InstanceStatus.Created,
407
+ name: 'child2',
408
+ instance: {},
409
+ creationPromise: null,
410
+ destroyPromise: null,
411
+ type: InjectableType.Class,
412
+ scope: InjectableScope.Singleton,
413
+ deps: new Set(['parent']),
414
+ destroyListeners: [],
415
+ createdAt: Date.now(),
416
+ waitingFor: new Set(),
417
+ }
418
+
419
+ storage.set('child1', child1Holder)
420
+ storage.set('child2', child2Holder)
421
+
422
+ const dependents = storage.findDependents('parent')
423
+ expect(dependents).toHaveLength(2)
424
+ expect(dependents).toContain('child1')
425
+ expect(dependents).toContain('child2')
426
+ })
427
+ })
428
+
429
+ describe('updateDependencyReference', () => {
430
+ it('should update dependency references in holder deps', () => {
431
+ // Create a holder that depends on 'oldName'
432
+ const holder: InstanceHolder = {
433
+ status: InstanceStatus.Created,
434
+ name: 'dependent',
435
+ instance: {},
436
+ creationPromise: null,
437
+ destroyPromise: null,
438
+ type: InjectableType.Class,
439
+ scope: InjectableScope.Singleton,
440
+ deps: new Set(['oldName']),
441
+ destroyListeners: [],
442
+ createdAt: Date.now(),
443
+ waitingFor: new Set(),
444
+ }
445
+ storage.set('dependent', holder)
446
+
447
+ storage.updateDependencyReference('oldName', 'newName')
448
+
449
+ // Check that deps was updated
450
+ expect(holder.deps.has('oldName')).toBe(false)
451
+ expect(holder.deps.has('newName')).toBe(true)
452
+ })
453
+
454
+ it('should update reverse dependency index', () => {
455
+ // Create a holder that depends on 'oldName'
456
+ const holder: InstanceHolder = {
457
+ status: InstanceStatus.Created,
458
+ name: 'dependent',
459
+ instance: {},
460
+ creationPromise: null,
461
+ destroyPromise: null,
462
+ type: InjectableType.Class,
463
+ scope: InjectableScope.Singleton,
464
+ deps: new Set(['oldName']),
465
+ destroyListeners: [],
466
+ createdAt: Date.now(),
467
+ waitingFor: new Set(),
468
+ }
469
+ storage.set('dependent', holder)
470
+
471
+ // Verify initial state
472
+ expect(storage.findDependents('oldName')).toContain('dependent')
473
+ expect(storage.findDependents('newName')).not.toContain('dependent')
474
+
475
+ storage.updateDependencyReference('oldName', 'newName')
476
+
477
+ // Verify updated state
478
+ expect(storage.findDependents('oldName')).not.toContain('dependent')
479
+ expect(storage.findDependents('newName')).toContain('dependent')
480
+ })
481
+
482
+ it('should handle multiple dependents', () => {
483
+ const holder1: InstanceHolder = {
484
+ status: InstanceStatus.Created,
485
+ name: 'dep1',
486
+ instance: {},
487
+ creationPromise: null,
488
+ destroyPromise: null,
489
+ type: InjectableType.Class,
490
+ scope: InjectableScope.Singleton,
491
+ deps: new Set(['oldName']),
492
+ destroyListeners: [],
493
+ createdAt: Date.now(),
494
+ waitingFor: new Set(),
495
+ }
496
+ const holder2: InstanceHolder = {
497
+ status: InstanceStatus.Created,
498
+ name: 'dep2',
499
+ instance: {},
500
+ creationPromise: null,
501
+ destroyPromise: null,
502
+ type: InjectableType.Class,
503
+ scope: InjectableScope.Singleton,
504
+ deps: new Set(['oldName']),
505
+ destroyListeners: [],
506
+ createdAt: Date.now(),
507
+ waitingFor: new Set(),
508
+ }
509
+
510
+ storage.set('dep1', holder1)
511
+ storage.set('dep2', holder2)
512
+
513
+ storage.updateDependencyReference('oldName', 'newName')
514
+
515
+ expect(storage.findDependents('newName')).toContain('dep1')
516
+ expect(storage.findDependents('newName')).toContain('dep2')
517
+ })
518
+ })
519
+
520
+ describe('scope isolation', () => {
521
+ it('should maintain separate instances per storage', () => {
522
+ const singletonStorage = new UnifiedStorage(InjectableScope.Singleton)
523
+ const requestStorage = new UnifiedStorage(InjectableScope.Request)
524
+
525
+ singletonStorage.storeInstance('service', { scope: 'singleton' })
526
+ requestStorage.storeInstance('service', { scope: 'request' })
527
+
528
+ const singletonResult = singletonStorage.get('service')
529
+ const requestResult = requestStorage.get('service')
530
+
531
+ expect(singletonResult![1]?.instance).toEqual({ scope: 'singleton' })
532
+ expect(requestResult![1]?.instance).toEqual({ scope: 'request' })
533
+ })
534
+ })
535
+ })