@nexus-ioc/core 0.6.2 → 0.7.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 (220) hide show
  1. package/README.md +731 -1
  2. package/dist/cjs/browser.js +2 -2
  3. package/dist/cjs/browser.js.map +1 -1
  4. package/dist/cjs/core/child-module.js +32 -0
  5. package/dist/cjs/core/child-module.js.map +1 -0
  6. package/dist/cjs/core/graph/analyze-lazy-module.js +54 -0
  7. package/dist/cjs/core/graph/analyze-lazy-module.js.map +1 -0
  8. package/dist/cjs/core/graph/analyze-module.js +11 -0
  9. package/dist/cjs/core/graph/analyze-module.js.map +1 -1
  10. package/dist/cjs/core/graph/module-graph.js +685 -70
  11. package/dist/cjs/core/graph/module-graph.js.map +1 -1
  12. package/dist/cjs/core/injection/injection-context.js +29 -0
  13. package/dist/cjs/core/injection/injection-context.js.map +1 -0
  14. package/dist/cjs/core/lazy-module-loader.js +41 -0
  15. package/dist/cjs/core/lazy-module-loader.js.map +1 -0
  16. package/dist/cjs/core/module-ref.js +84 -0
  17. package/dist/cjs/core/module-ref.js.map +1 -0
  18. package/dist/cjs/core/modules/container.js +305 -17
  19. package/dist/cjs/core/modules/container.js.map +1 -1
  20. package/dist/cjs/core/modules/module-container-factory.js +2 -2
  21. package/dist/cjs/core/modules/module-container-factory.js.map +1 -1
  22. package/dist/cjs/core/modules/module-container.js +54 -16
  23. package/dist/cjs/core/modules/module-container.js.map +1 -1
  24. package/dist/cjs/core/modules/module-token-factory.js.map +1 -1
  25. package/dist/cjs/core/modules/modules-container.js +43 -3
  26. package/dist/cjs/core/modules/modules-container.js.map +1 -1
  27. package/dist/cjs/core/nexus-applications.browser.js +57 -15
  28. package/dist/cjs/core/nexus-applications.browser.js.map +1 -1
  29. package/dist/cjs/core/nexus-applications.js +440 -82
  30. package/dist/cjs/core/nexus-applications.js.map +1 -1
  31. package/dist/cjs/core/nexus-applications.server.js +57 -15
  32. package/dist/cjs/core/nexus-applications.server.js.map +1 -1
  33. package/dist/cjs/core/resolver/providers-container.js.map +1 -1
  34. package/dist/cjs/core/resolver/resolution-context.js +62 -0
  35. package/dist/cjs/core/resolver/resolution-context.js.map +1 -0
  36. package/dist/cjs/core/resolver/resolver.js +523 -99
  37. package/dist/cjs/core/resolver/resolver.js.map +1 -1
  38. package/dist/cjs/decorators/inject.js +6 -3
  39. package/dist/cjs/decorators/inject.js.map +1 -1
  40. package/dist/cjs/decorators/injectable.js +0 -7
  41. package/dist/cjs/decorators/injectable.js.map +1 -1
  42. package/dist/cjs/decorators/{nsModule.js → module.js} +9 -7
  43. package/dist/cjs/decorators/module.js.map +1 -0
  44. package/dist/cjs/errors/bootstrap-error.js +47 -0
  45. package/dist/cjs/errors/bootstrap-error.js.map +1 -0
  46. package/dist/cjs/errors/child-errors.js +21 -0
  47. package/dist/cjs/errors/child-errors.js.map +1 -0
  48. package/dist/cjs/errors/index.js +24 -0
  49. package/dist/cjs/errors/index.js.map +1 -0
  50. package/dist/cjs/errors/injection-errors.js +25 -0
  51. package/dist/cjs/errors/injection-errors.js.map +1 -0
  52. package/dist/cjs/errors/lazy-module-errors.js +26 -0
  53. package/dist/cjs/errors/lazy-module-errors.js.map +1 -0
  54. package/dist/cjs/errors/provider-errors.js +95 -0
  55. package/dist/cjs/errors/provider-errors.js.map +1 -0
  56. package/dist/cjs/index.js +30 -6
  57. package/dist/cjs/index.js.map +1 -1
  58. package/dist/cjs/interfaces/index.js +1 -0
  59. package/dist/cjs/interfaces/index.js.map +1 -1
  60. package/dist/cjs/interfaces/modules/index.js +1 -0
  61. package/dist/cjs/interfaces/modules/index.js.map +1 -1
  62. package/dist/cjs/interfaces/modules/module-graph.interface.js +2 -0
  63. package/dist/cjs/interfaces/modules/module-graph.interface.js.map +1 -1
  64. package/dist/cjs/interfaces/modules/parent-link.interface.js +3 -0
  65. package/dist/cjs/interfaces/modules/parent-link.interface.js.map +1 -0
  66. package/dist/cjs/interfaces/on-module-destroy.interface.js +3 -0
  67. package/dist/cjs/interfaces/on-module-destroy.interface.js.map +1 -0
  68. package/dist/cjs/interfaces/scope.interface.js +2 -55
  69. package/dist/cjs/interfaces/scope.interface.js.map +1 -1
  70. package/dist/cjs/interfaces/validate-module-keys.js +2 -0
  71. package/dist/cjs/interfaces/validate-module-keys.js.map +1 -1
  72. package/dist/cjs/internal.js +9 -0
  73. package/dist/cjs/internal.js.map +1 -0
  74. package/dist/cjs/server.js +2 -2
  75. package/dist/cjs/server.js.map +1 -1
  76. package/dist/cjs/utils/forward-ref.js +14 -0
  77. package/dist/cjs/utils/forward-ref.js.map +1 -0
  78. package/dist/cjs/utils/helpers.js +14 -4
  79. package/dist/cjs/utils/helpers.js.map +1 -1
  80. package/dist/cjs/utils/lazy-module.js +33 -0
  81. package/dist/cjs/utils/lazy-module.js.map +1 -0
  82. package/dist/esm/browser.js +1 -1
  83. package/dist/esm/browser.js.map +1 -1
  84. package/dist/esm/core/child-module.js +29 -0
  85. package/dist/esm/core/child-module.js.map +1 -0
  86. package/dist/esm/core/graph/analyze-lazy-module.js +50 -0
  87. package/dist/esm/core/graph/analyze-lazy-module.js.map +1 -0
  88. package/dist/esm/core/graph/analyze-module.js +11 -0
  89. package/dist/esm/core/graph/analyze-module.js.map +1 -1
  90. package/dist/esm/core/graph/module-graph.js +686 -71
  91. package/dist/esm/core/graph/module-graph.js.map +1 -1
  92. package/dist/esm/core/injection/injection-context.js +25 -0
  93. package/dist/esm/core/injection/injection-context.js.map +1 -0
  94. package/dist/esm/core/lazy-module-loader.js +36 -0
  95. package/dist/esm/core/lazy-module-loader.js.map +1 -0
  96. package/dist/esm/core/module-ref.js +80 -0
  97. package/dist/esm/core/module-ref.js.map +1 -0
  98. package/dist/esm/core/modules/container.js +305 -17
  99. package/dist/esm/core/modules/container.js.map +1 -1
  100. package/dist/esm/core/modules/module-container-factory.js +2 -2
  101. package/dist/esm/core/modules/module-container-factory.js.map +1 -1
  102. package/dist/esm/core/modules/module-container.js +55 -17
  103. package/dist/esm/core/modules/module-container.js.map +1 -1
  104. package/dist/esm/core/modules/module-token-factory.js.map +1 -1
  105. package/dist/esm/core/modules/modules-container.js +43 -3
  106. package/dist/esm/core/modules/modules-container.js.map +1 -1
  107. package/dist/esm/core/nexus-applications.browser.js +55 -13
  108. package/dist/esm/core/nexus-applications.browser.js.map +1 -1
  109. package/dist/esm/core/nexus-applications.js +439 -81
  110. package/dist/esm/core/nexus-applications.js.map +1 -1
  111. package/dist/esm/core/nexus-applications.server.js +55 -13
  112. package/dist/esm/core/nexus-applications.server.js.map +1 -1
  113. package/dist/esm/core/resolver/providers-container.js.map +1 -1
  114. package/dist/esm/core/resolver/resolution-context.js +58 -0
  115. package/dist/esm/core/resolver/resolution-context.js.map +1 -0
  116. package/dist/esm/core/resolver/resolver.js +524 -100
  117. package/dist/esm/core/resolver/resolver.js.map +1 -1
  118. package/dist/esm/decorators/inject.js +6 -3
  119. package/dist/esm/decorators/inject.js.map +1 -1
  120. package/dist/esm/decorators/injectable.js +0 -7
  121. package/dist/esm/decorators/injectable.js.map +1 -1
  122. package/dist/esm/decorators/{nsModule.js → module.js} +8 -6
  123. package/dist/esm/decorators/module.js.map +1 -0
  124. package/dist/esm/errors/bootstrap-error.js +42 -0
  125. package/dist/esm/errors/bootstrap-error.js.map +1 -0
  126. package/dist/esm/errors/child-errors.js +17 -0
  127. package/dist/esm/errors/child-errors.js.map +1 -0
  128. package/dist/esm/errors/index.js +6 -0
  129. package/dist/esm/errors/index.js.map +1 -0
  130. package/dist/esm/errors/injection-errors.js +20 -0
  131. package/dist/esm/errors/injection-errors.js.map +1 -0
  132. package/dist/esm/errors/lazy-module-errors.js +21 -0
  133. package/dist/esm/errors/lazy-module-errors.js.map +1 -0
  134. package/dist/esm/errors/provider-errors.js +84 -0
  135. package/dist/esm/errors/provider-errors.js.map +1 -0
  136. package/dist/esm/index.js +9 -4
  137. package/dist/esm/index.js.map +1 -1
  138. package/dist/esm/interfaces/index.js +1 -0
  139. package/dist/esm/interfaces/index.js.map +1 -1
  140. package/dist/esm/interfaces/modules/index.js +1 -0
  141. package/dist/esm/interfaces/modules/index.js.map +1 -1
  142. package/dist/esm/interfaces/modules/module-graph.interface.js +2 -0
  143. package/dist/esm/interfaces/modules/module-graph.interface.js.map +1 -1
  144. package/dist/esm/interfaces/modules/parent-link.interface.js +2 -0
  145. package/dist/esm/interfaces/modules/parent-link.interface.js.map +1 -0
  146. package/dist/esm/interfaces/on-module-destroy.interface.js +2 -0
  147. package/dist/esm/interfaces/on-module-destroy.interface.js.map +1 -0
  148. package/dist/esm/interfaces/scope.interface.js +1 -55
  149. package/dist/esm/interfaces/scope.interface.js.map +1 -1
  150. package/dist/esm/interfaces/validate-module-keys.js +2 -0
  151. package/dist/esm/interfaces/validate-module-keys.js.map +1 -1
  152. package/dist/esm/internal.js +3 -0
  153. package/dist/esm/internal.js.map +1 -0
  154. package/dist/esm/server.js +1 -1
  155. package/dist/esm/server.js.map +1 -1
  156. package/dist/esm/utils/forward-ref.js +10 -0
  157. package/dist/esm/utils/forward-ref.js.map +1 -0
  158. package/dist/esm/utils/helpers.js +14 -4
  159. package/dist/esm/utils/helpers.js.map +1 -1
  160. package/dist/esm/utils/lazy-module.js +29 -0
  161. package/dist/esm/utils/lazy-module.js.map +1 -0
  162. package/dist/types/browser.d.ts +1 -1
  163. package/dist/types/core/child-module.d.ts +8 -0
  164. package/dist/types/core/graph/analyze-lazy-module.d.ts +28 -0
  165. package/dist/types/core/graph/analyze-module.d.ts +4 -2
  166. package/dist/types/core/graph/analyze-provider.d.ts +4 -4
  167. package/dist/types/core/graph/module-graph.d.ts +200 -7
  168. package/dist/types/core/graph/providers/analyze-class-provider.d.ts +1 -1
  169. package/dist/types/core/graph/providers/analyze-factory-provider.d.ts +2 -2
  170. package/dist/types/core/graph/providers/analyze-value-provider.d.ts +1 -1
  171. package/dist/types/core/injection/injection-context.d.ts +29 -0
  172. package/dist/types/core/lazy-module-loader.d.ts +17 -0
  173. package/dist/types/core/module-ref.d.ts +34 -0
  174. package/dist/types/core/modules/container.d.ts +134 -18
  175. package/dist/types/core/modules/module-container-factory.d.ts +3 -3
  176. package/dist/types/core/modules/module-container.d.ts +44 -5
  177. package/dist/types/core/modules/module-token-factory.d.ts +2 -2
  178. package/dist/types/core/modules/modules-container.d.ts +23 -4
  179. package/dist/types/core/nexus-applications.browser.d.ts +30 -8
  180. package/dist/types/core/nexus-applications.d.ts +219 -55
  181. package/dist/types/core/nexus-applications.server.d.ts +30 -8
  182. package/dist/types/core/resolver/providers-container.d.ts +2 -2
  183. package/dist/types/core/resolver/resolution-context.d.ts +22 -0
  184. package/dist/types/core/resolver/resolver.d.ts +122 -4
  185. package/dist/types/decorators/inject.d.ts +9 -5
  186. package/dist/types/decorators/injectable.d.ts +0 -7
  187. package/dist/types/decorators/{nsModule.d.ts → module.d.ts} +6 -5
  188. package/dist/types/errors/bootstrap-error.d.ts +6 -0
  189. package/dist/types/errors/child-errors.d.ts +10 -0
  190. package/dist/types/errors/index.d.ts +5 -0
  191. package/dist/types/errors/injection-errors.d.ts +11 -0
  192. package/dist/types/errors/lazy-module-errors.d.ts +12 -0
  193. package/dist/types/errors/provider-errors.d.ts +47 -0
  194. package/dist/types/index.d.ts +13 -4
  195. package/dist/types/interfaces/abstract.interface.d.ts +1 -3
  196. package/dist/types/interfaces/dynamic-module.interface.d.ts +7 -5
  197. package/dist/types/interfaces/index.d.ts +1 -0
  198. package/dist/types/interfaces/injection-token.interface.d.ts +1 -3
  199. package/dist/types/interfaces/module-metadata.interface.d.ts +12 -4
  200. package/dist/types/interfaces/module-types.interface.d.ts +21 -24
  201. package/dist/types/interfaces/modules/container.interface.d.ts +10 -4
  202. package/dist/types/interfaces/modules/index.d.ts +1 -0
  203. package/dist/types/interfaces/modules/module-container-factory.interface.d.ts +2 -2
  204. package/dist/types/interfaces/modules/module-graph.interface.d.ts +85 -12
  205. package/dist/types/interfaces/modules/module-token-factory.interface.d.ts +2 -2
  206. package/dist/types/interfaces/modules/modules-container.interface.d.ts +5 -4
  207. package/dist/types/interfaces/modules/parent-link.interface.d.ts +12 -0
  208. package/dist/types/interfaces/nexus-application.interface.d.ts +49 -6
  209. package/dist/types/interfaces/on-module-destroy.interface.d.ts +3 -0
  210. package/dist/types/interfaces/plugins/scanner-plugin.interface.d.ts +1 -1
  211. package/dist/types/interfaces/scope.interface.d.ts +1 -54
  212. package/dist/types/interfaces/validate-module-keys.d.ts +1 -0
  213. package/dist/types/internal.d.ts +3 -0
  214. package/dist/types/server.d.ts +1 -1
  215. package/dist/types/utils/forward-ref.d.ts +5 -0
  216. package/dist/types/utils/helpers.d.ts +8 -8
  217. package/dist/types/utils/lazy-module.d.ts +20 -0
  218. package/package.json +21 -4
  219. package/dist/cjs/decorators/nsModule.js.map +0 -1
  220. package/dist/esm/decorators/nsModule.js.map +0 -1
@@ -1,163 +1,587 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Resolver = void 0;
4
+ const injection_errors_1 = require("../../errors/injection-errors");
5
+ const provider_errors_1 = require("../../errors/provider-errors");
4
6
  const interfaces_1 = require("../../interfaces");
5
7
  const helpers_1 = require("../../utils/helpers");
8
+ const injection_context_1 = require("../injection/injection-context");
6
9
  const providers_container_1 = require("./providers-container");
10
+ const resolution_context_1 = require("./resolution-context");
7
11
  function isCircularDependencyFn(fn) {
8
12
  // @ts-expect-error
9
13
  return !!(typeof fn === "function" && fn.dependencyName);
10
14
  }
15
+ function isThenable(value) {
16
+ return (value !== null &&
17
+ (typeof value === "object" || typeof value === "function") &&
18
+ typeof value.then === "function");
19
+ }
11
20
  class Resolver {
12
- constructor(graph) {
21
+ /**
22
+ * @param link - The parent chain of a child container. Edges marked
23
+ * `external` resolve through it; their results are never cached here.
24
+ */
25
+ constructor(graph, link = null) {
13
26
  this.graph = graph;
27
+ this.link = link;
14
28
  this.providersContainer = new providers_container_1.ProvidersContainer();
29
+ this.initializationOrder = [];
30
+ /** `lateBoundVisible` results, valid while the graph version matches. */
31
+ this.lateBoundMemo = new WeakMap();
32
+ /**
33
+ * Singletons created by inject() whose onModuleInit (or the wait on
34
+ * inits queued before them) has not settled yet. Other resolution trees
35
+ * that hit such a cached singleton wait on this promise, so they never
36
+ * see it half-initialised or keep it after a failed init evicted it.
37
+ */
38
+ this.inFlightInits = new Map();
39
+ /**
40
+ * Singletons that resolveProvider is creating (construction through
41
+ * onModuleInit), not yet in the cache. Concurrent callers await the same
42
+ * promise, so each singleton is created once. An entry is removed when
43
+ * its creation settles: after the commit on success, so a token is always
44
+ * in the cache or here; on failure, so the next resolve retries.
45
+ */
46
+ this.inFlightCreations = new Map();
47
+ /**
48
+ * Set once close() starts. From then on nothing new is created or
49
+ * cached: creations still in flight are destroyed when they finish
50
+ * (close() does not wait for them, see commitCreation), and inject()
51
+ * in a resolution tree that outlived close() throws instead of creating.
52
+ */
53
+ this.closed = false;
54
+ }
55
+ /**
56
+ * The link that satisfies an external edge. The graph marks an edge
57
+ * external only when a link can provide its target, so a missing link
58
+ * is an internal error, reported as not found rather than `undefined`.
59
+ */
60
+ linkFor(edge) {
61
+ if (!this.link) {
62
+ throw new provider_errors_1.ProviderNotFoundError(edge.target, edge.source);
63
+ }
64
+ return this.link;
65
+ }
66
+ /** Cached singleton lookup; never creates. Uses has() so falsy values count. */
67
+ getInstantiated(token) {
68
+ // A singleton inject() created is cached before its async init settles;
69
+ // like the async path, it counts as instantiated only after the init.
70
+ if (!this.providersContainer.has(token) || this.inFlightInits.has(token)) {
71
+ return { found: false };
72
+ }
73
+ return { found: true, instance: this.providersContainer.get(token) };
74
+ }
75
+ /**
76
+ * Marks the resolver closed without destroying anything; used when the
77
+ * container was closed before this resolver existed.
78
+ */
79
+ markClosed() {
80
+ this.closed = true;
81
+ // Every creation still in flight is now stale (see commitCreation).
82
+ this.inFlightCreations.clear();
83
+ }
84
+ async close() {
85
+ // Before the first await: a creation that finishes while the hooks
86
+ // below run must not be cached behind the destroy pass.
87
+ this.markClosed();
88
+ const errors = [];
89
+ for (const token of [...this.initializationOrder].reverse()) {
90
+ const instance = this.providersContainer.get(token);
91
+ if (instance?.onModuleDestroy) {
92
+ try {
93
+ await instance.onModuleDestroy();
94
+ }
95
+ catch (error) {
96
+ errors.push(error);
97
+ }
98
+ }
99
+ }
100
+ this.providersContainer.clear();
101
+ this.initializationOrder.length = 0;
102
+ this.inFlightInits.clear();
103
+ this.inFlightCreations.clear();
104
+ if (errors.length > 0) {
105
+ throw new AggregateError(errors, "One or more onModuleDestroy hooks failed");
106
+ }
107
+ }
108
+ /**
109
+ * Destroys exactly the given singletons: `onModuleDestroy` in reverse
110
+ * initialization order, then removed from the cache and from
111
+ * `initializationOrder`. A token that was never instantiated (never
112
+ * resolved, or `Transient`/non-singleton scope) is silently skipped.
113
+ * Like `close()`, every hook runs even if some throw, the cleanup always
114
+ * completes, and the failures are thrown afterwards as one
115
+ * `AggregateError`.
116
+ */
117
+ async destroy(tokens) {
118
+ const dead = new Set(tokens);
119
+ // Before the first await: a creation of a dead token still in flight
120
+ // is stale from now on and is destroyed, not cached, when it finishes.
121
+ for (const token of dead) {
122
+ this.inFlightInits.delete(token);
123
+ this.inFlightCreations.delete(token);
124
+ }
125
+ const orderedDead = this.initializationOrder.filter((token) => dead.has(token));
126
+ const errors = [];
127
+ for (const token of [...orderedDead].reverse()) {
128
+ const instance = this.providersContainer.get(token);
129
+ if (instance?.onModuleDestroy) {
130
+ try {
131
+ await instance.onModuleDestroy();
132
+ }
133
+ catch (error) {
134
+ errors.push(error);
135
+ }
136
+ }
137
+ this.providersContainer.delete(token);
138
+ }
139
+ const survivors = this.initializationOrder.filter((token) => !dead.has(token));
140
+ this.initializationOrder.length = 0;
141
+ this.initializationOrder.push(...survivors);
142
+ if (errors.length > 0) {
143
+ throw new AggregateError(errors, "One or more onModuleDestroy hooks failed");
144
+ }
15
145
  }
16
- async resolveProvider(token, resolveCache = new providers_container_1.ProvidersContainer(), isCircularDependency = false) {
146
+ async resolveProvider(token, ctx = new resolution_context_1.ResolutionContext(), isCircularDependency = false) {
147
+ if (this.closed) {
148
+ throw new provider_errors_1.ContainerClosedError();
149
+ }
17
150
  const node = this.graph.getNode(token);
18
151
  if (!node) {
19
152
  return undefined;
20
153
  }
154
+ // Module and LAZY placeholder nodes share the token space with providers
155
+ // but carry no instance, so they resolve to undefined instead of failing.
156
+ if (node.type !== interfaces_1.NodeTypeEnum.PROVIDER) {
157
+ return undefined;
158
+ }
21
159
  // Get the scope of the provider
22
160
  const scope = node.scope;
23
161
  // For Transient scope, always create a new instance (no caching at all)
24
162
  if (scope === interfaces_1.Scope.Transient) {
25
- const [instance] = await this.createInstance(node, resolveCache, isCircularDependency);
26
- return instance;
163
+ return (await this.createInstance(node, ctx, isCircularDependency));
27
164
  }
28
165
  // For Singleton scope, check global cache first
29
166
  if (scope === interfaces_1.Scope.Singleton && this.providersContainer.has(token)) {
30
- return this.providersContainer.get(token);
167
+ const cached = this.providersContainer.get(token);
168
+ const inFlight = this.inFlightInits.get(token);
169
+ if (inFlight) {
170
+ // Created by inject() in another tree; its init is still running.
171
+ // On failure it was evicted, so the error propagates.
172
+ await inFlight;
173
+ }
174
+ return cached;
175
+ }
176
+ const inFlight = this.inFlightCreations.get(token);
177
+ if (inFlight) {
178
+ // Defensive: this tree is creating the token itself, so awaiting its
179
+ // own creation would never settle. Static cycles are marked circular
180
+ // by the graph and never get here.
181
+ if (ctx.isConstructing(token)) {
182
+ throw new injection_errors_1.CircularInjectionError([...ctx.path, token]);
183
+ }
184
+ return (await inFlight);
31
185
  }
32
- // For Request and Singleton scopes, check resolve cache (within current resolution tree)
33
- if (resolveCache.has(token)) {
34
- return resolveCache.get(token);
186
+ const creation = this.createInstance(node, ctx, isCircularDependency).then((instance) => this.commitCreation(node, creation, instance));
187
+ this.inFlightCreations.set(token, creation);
188
+ const clear = () => {
189
+ // close()/destroy() may have dropped it, or a retry replaced it.
190
+ if (this.inFlightCreations.get(token) === creation) {
191
+ this.inFlightCreations.delete(token);
192
+ }
193
+ };
194
+ creation.then(clear, clear);
195
+ return (await creation);
196
+ }
197
+ /**
198
+ * Caches what `creation` produced, unless the creation went stale while
199
+ * it ran: close() or destroy() (an unload) dropped its in-flight entry.
200
+ * A stale instance is never cached. Its onModuleDestroy is called (best
201
+ * effort: a failure there is ignored, since the caller already gets the
202
+ * rejection below and nothing else holds the instance), and the caller
203
+ * gets ContainerClosedError after a close, ProviderUnloadedError after
204
+ * an unload.
205
+ */
206
+ async commitCreation(node, creation, instance) {
207
+ if (this.inFlightCreations.get(node.id) === creation) {
208
+ this.commitInstance(node, instance);
209
+ return instance;
35
210
  }
36
- const [instance, saveInCache] = await this.createInstance(node, resolveCache, isCircularDependency);
37
- // Add to resolve cache for current resolution context (for Request and Singleton)
38
- // This ensures same instance is used within a single dependency resolution tree
39
- if (scope === interfaces_1.Scope.Singleton || scope === interfaces_1.Scope.Request) {
40
- resolveCache.set(token, instance);
211
+ const stale = instance;
212
+ try {
213
+ await stale?.onModuleDestroy?.();
41
214
  }
42
- // Only save to global cache for Singleton scope
43
- if (saveInCache) {
44
- this.providersContainer.set(token, instance);
215
+ catch {
216
+ // Best effort; see above.
217
+ }
218
+ throw this.closed
219
+ ? new provider_errors_1.ContainerClosedError()
220
+ : new provider_errors_1.ProviderUnloadedError(node.id);
221
+ }
222
+ /**
223
+ * Caching rules shared by the async and sync paths: Singletons go into the
224
+ * global cache and the destroy order. Nothing else is cached.
225
+ */
226
+ commitInstance(node, instance) {
227
+ if (node.scope !== interfaces_1.Scope.Singleton) {
228
+ return;
229
+ }
230
+ // First writer wins if concurrent creations race for the same token.
231
+ if (!this.providersContainer.has(node.id)) {
232
+ this.providersContainer.set(node.id, instance);
233
+ this.initializationOrder.push(node.id);
234
+ }
235
+ }
236
+ /** Undoes commitInstance for `instance`, if it is still the cached one. */
237
+ evictInstance(node, instance) {
238
+ const token = node.id;
239
+ if (this.providersContainer.has(token) &&
240
+ this.providersContainer.get(token) === instance) {
241
+ this.providersContainer.delete(token);
242
+ const index = this.initializationOrder.lastIndexOf(token);
243
+ if (index !== -1) {
244
+ this.initializationOrder.splice(index, 1);
245
+ }
246
+ }
247
+ }
248
+ async createInstance(node, ctx, isCircularDependency = false) {
249
+ ctx.enter(node.id);
250
+ try {
251
+ return await this.createInstanceInContext(node, ctx, isCircularDependency);
252
+ }
253
+ finally {
254
+ ctx.leave(node.id);
45
255
  }
46
- return instance;
47
256
  }
48
- async createInstance(node, resolveCache, isCircularDependency = false) {
257
+ async createInstanceInContext(node, ctx, isCircularDependency) {
49
258
  const provider = node.metatype;
50
- const dependencies = this.graph
51
- .getEdge(node.id)
52
- .filter((edge) => edge.type === interfaces_1.EdgeTypeEnum.DEPENDENCY &&
53
- edge.metadata.inject === "constructor" &&
54
- edge.metadata.unreached === false)
55
- .map((edge) => {
259
+ const dependencyEdges = this.constructorEdges(node).filter((edge) => edge.metadata.unreached === false);
260
+ const resolvedDependencies = [];
261
+ for (const edge of dependencyEdges) {
262
+ // An external target lives in the parent chain. Branch before any
263
+ // local lookup: the child graph may have no node for it (which would
264
+ // read as undefined) or a hidden one the requester must not get.
265
+ if (edge.metadata.external) {
266
+ resolvedDependencies.push(await this.linkFor(edge).resolve(edge.target));
267
+ continue;
268
+ }
269
+ if (edge.metadata.lateBound && !this.lateBoundVisible(edge, node)) {
270
+ resolvedDependencies.push(undefined);
271
+ continue;
272
+ }
56
273
  if (edge.metadata.isCircular) {
57
- const circularResolver = () => new Proxy({}, {
58
- get: (_, prop) => {
59
- const instance = resolveCache.get(edge.target);
60
- return instance?.[prop];
61
- },
62
- });
63
- circularResolver.dependencyName = edge.target;
64
- return circularResolver;
65
- }
66
- return edge.target;
67
- });
68
- const depsChunks = dependencies.reduce((prev, depToken) => {
69
- if (isCircularDependencyFn(depToken)) {
70
- prev[0].push(depToken);
71
- prev[1].push(null);
72
- return prev;
73
- }
74
- prev[0].push(null);
75
- prev[1].push(depToken);
76
- return prev;
77
- }, [[], []]);
78
- // biome-ignore lint/suspicious/noExplicitAny: dependency resolution
79
- let resolvedDependencies = [];
80
- for (const depToken of dependencies) {
81
- if (isCircularDependencyFn(depToken)) {
82
- if (isCircularDependency) {
83
- resolvedDependencies.push(depToken);
274
+ // A circular target that another tree is creating gets the proxy
275
+ // too: awaiting it could deadlock, since that tree may be waiting
276
+ // on this one through the same cycle.
277
+ if (isCircularDependency ||
278
+ (this.inFlightCreations.has(edge.target) &&
279
+ !this.providersContainer.has(edge.target))) {
280
+ const circularResolver = () => new Proxy({}, {
281
+ get: (_, prop) => {
282
+ const instance = this.providersContainer.get(edge.target);
283
+ return instance?.[prop];
284
+ },
285
+ });
286
+ circularResolver.dependencyName = edge.target;
287
+ resolvedDependencies.push(circularResolver);
84
288
  continue;
85
289
  }
86
- const instance = await this.resolveProvider(
87
- // @ts-expect-error
88
- depToken.dependencyName, resolveCache, true);
89
- resolvedDependencies.push(instance);
290
+ resolvedDependencies.push(await this.resolveProvider(edge.target, ctx, true));
90
291
  continue;
91
292
  }
92
- const instance = await this.resolveProvider(depToken, resolveCache);
93
- resolvedDependencies.push(instance);
293
+ resolvedDependencies.push(await this.resolveProvider(edge.target, ctx));
94
294
  }
95
- resolvedDependencies = resolvedDependencies.map((dependency, index) => {
96
- if (depsChunks[0]?.[index]) {
97
- return depsChunks[0]?.[index];
98
- }
99
- return dependency;
100
- });
295
+ const deps = resolvedDependencies.map((dep) => isCircularDependencyFn(dep)
296
+ ? dep()
297
+ : dep);
101
298
  // biome-ignore lint/suspicious/noExplicitAny: instance creation
102
299
  let instance;
103
- let saveInCache = true;
104
- const deps = resolvedDependencies.map((depToken) => isCircularDependencyFn(depToken) ? depToken() : depToken);
300
+ const injectionContext = this.contextFor(node, ctx);
105
301
  if ((0, helpers_1.isClassProvider)(provider)) {
106
- instance = new provider.useClass(...deps);
107
- await this.injectPropertyDependencies(instance, node, resolveCache);
108
- // Determine caching strategy based on scope
109
- const scope = node.scope;
110
- saveInCache = scope === interfaces_1.Scope.Singleton;
302
+ instance = (0, injection_context_1.withInjectionContext)(injectionContext, () => new provider.useClass(...deps));
303
+ await this.injectPropertyDependencies(instance, node, ctx);
111
304
  }
112
305
  else if ((0, helpers_1.isValueProvider)(provider)) {
113
306
  instance = provider.useValue;
114
- // Value providers are always singleton
115
- saveInCache = true;
116
307
  }
117
308
  else if ((0, helpers_1.isFactoryProvider)(provider)) {
118
- instance = await provider.useFactory(...deps);
119
- // Check scope for factory providers
120
- const scope = node.scope;
121
- saveInCache = scope === interfaces_1.Scope.Singleton;
309
+ instance = this.callFactory(node, provider, deps, injectionContext);
122
310
  }
123
311
  else {
124
- instance = new provider(...deps);
125
- await this.injectPropertyDependencies(instance, node, resolveCache);
126
- // Check scope for function providers
127
- const scope = node.scope;
128
- saveInCache = scope === interfaces_1.Scope.Singleton;
312
+ instance = (0, injection_context_1.withInjectionContext)(injectionContext, () => new provider(...deps));
313
+ await this.injectPropertyDependencies(instance, node, ctx);
129
314
  }
130
- // Call lifecycle hook with error handling
315
+ // onModuleInit of providers created via inject() must finish before
316
+ // this instance's own onModuleInit runs.
317
+ await ctx.drainPendingInits();
131
318
  if (instance?.onModuleInit) {
132
319
  try {
133
320
  await instance.onModuleInit();
134
321
  }
135
322
  catch (error) {
136
- // Re-throw with additional context about which provider failed
137
- const providerName = typeof provider === "function"
138
- ? provider.name
139
- : provider.provide?.toString() ||
140
- "Unknown";
141
- const errorMessage = `Failed to initialize provider "${providerName}": ${error instanceof Error ? error.message : String(error)}`;
142
- const wrappedError = new Error(errorMessage);
143
- // Preserve original error stack if available
144
- if (error instanceof Error && error.stack) {
145
- wrappedError.stack = `${wrappedError.stack}\nCaused by: ${error.stack}`;
146
- }
147
- throw wrappedError;
323
+ throw this.wrapInitError(provider, error);
324
+ }
325
+ }
326
+ return instance;
327
+ }
328
+ /**
329
+ * inject() entry point: resolves `token` synchronously for `requester`.
330
+ * The token must be visible from the requester's module, the same rule
331
+ * as `@Inject`.
332
+ */
333
+ resolveSync(token, ctx, requester, options) {
334
+ const node = this.graph.getNode(token);
335
+ const visible = node?.type === interfaces_1.NodeTypeEnum.PROVIDER &&
336
+ this.graph.isProviderVisible(requester.moduleContainer, token);
337
+ if (!visible) {
338
+ // Not visible locally: the parent chain may provide it. `link.get`
339
+ // returns only an existing parent singleton (never creates one) and
340
+ // throws for a parent Transient, a not-yet-created singleton or a
341
+ // closed parent, even with `optional`. The dependency crosses
342
+ // containers, so no runtime edge is recorded and nothing is cached.
343
+ if (this.link?.canProvide(token)) {
344
+ return this.link.get(token);
345
+ }
346
+ if (options.optional) {
347
+ return undefined;
348
+ }
349
+ throw new provider_errors_1.ProviderNotFoundError(token, requester.id);
350
+ }
351
+ const instance = this.resolveProviderSync(node, ctx);
352
+ // Idempotent; covers cached and freshly created instances alike.
353
+ this.graph.addRuntimeDependency(requester.id, node.id);
354
+ return instance;
355
+ }
356
+ /**
357
+ * Synchronous counterpart of resolveProvider for a provider node whose
358
+ * reachability was already established (visibility check or graph edge).
359
+ */
360
+ resolveProviderSync(node, ctx) {
361
+ const token = node.id;
362
+ if (ctx.isConstructing(token)) {
363
+ throw new injection_errors_1.CircularInjectionError([...ctx.path, token]);
364
+ }
365
+ if (node.scope === interfaces_1.Scope.Singleton && this.providersContainer.has(token)) {
366
+ const inFlight = this.inFlightInits.get(token);
367
+ if (inFlight) {
368
+ // The requester's own init and eviction chain wait on it.
369
+ ctx.addPendingInit(inFlight);
370
+ }
371
+ return this.providersContainer.get(token);
372
+ }
373
+ if (node.scope === interfaces_1.Scope.Singleton && this.inFlightCreations.has(token)) {
374
+ // resolveProvider is creating it in another tree. This path is
375
+ // synchronous and cannot wait, and creating it here would make a
376
+ // second instance.
377
+ throw new provider_errors_1.ProviderNotInstantiatedError(token, "creation-in-flight");
378
+ }
379
+ return this.createInstanceSync(node, ctx);
380
+ }
381
+ /** Resolves a static @Inject edge of `requester` on the synchronous path. */
382
+ resolveEdgeSync(edge, requester, ctx) {
383
+ // External first, before any node lookup (see createInstanceInContext).
384
+ if (edge.metadata.external) {
385
+ return this.linkFor(edge).get(edge.target);
386
+ }
387
+ if (edge.metadata.isCircular) {
388
+ throw new injection_errors_1.CircularInjectionError([...ctx.path, edge.target]);
389
+ }
390
+ // Unreached edges resolve to undefined, as on the async path. Reached
391
+ // edges were checked by the graph, so there is no second visibility
392
+ // check, except for a late-bound one (see lateBoundVisible); a missing
393
+ // (@Optional) node resolves to undefined.
394
+ if (edge.metadata.unreached) {
395
+ return undefined;
396
+ }
397
+ if (edge.metadata.lateBound && !this.lateBoundVisible(edge, requester)) {
398
+ return undefined;
399
+ }
400
+ const target = this.graph.getNode(edge.target);
401
+ if (!target || target.type !== interfaces_1.NodeTypeEnum.PROVIDER) {
402
+ return undefined;
403
+ }
404
+ return this.resolveProviderSync(target, ctx);
405
+ }
406
+ /**
407
+ * Synchronous mirror of createInstance + resolveProvider caching, used
408
+ * when inject() reaches a provider that does not exist yet.
409
+ */
410
+ createInstanceSync(node, ctx) {
411
+ // A resolution tree that outlived close() (it was awaiting something
412
+ // when close() ran) must not create and cache anything: the destroy
413
+ // pass is over. Refused before construction, so there is nothing to
414
+ // destroy. No unload counterpart is needed: this path always reads
415
+ // the live graph, so it never creates an instance of an unloaded node.
416
+ if (this.closed) {
417
+ throw new provider_errors_1.ContainerClosedError();
418
+ }
419
+ ctx.enter(node.id);
420
+ try {
421
+ const provider = node.metatype;
422
+ const deps = this.constructorEdges(node).map((edge) => this.resolveEdgeSync(edge, node, ctx));
423
+ const injectionContext = this.contextFor(node, ctx);
424
+ // biome-ignore lint/suspicious/noExplicitAny: instance creation
425
+ let instance;
426
+ if ((0, helpers_1.isValueProvider)(provider)) {
427
+ instance = provider.useValue;
428
+ }
429
+ else if ((0, helpers_1.isFactoryProvider)(provider)) {
430
+ instance = this.callFactory(node, provider, deps, injectionContext);
431
+ }
432
+ else {
433
+ const Class = (0, helpers_1.isClassProvider)(provider)
434
+ ? provider.useClass
435
+ : provider;
436
+ instance = (0, injection_context_1.withInjectionContext)(injectionContext, () => new Class(...deps));
437
+ this.injectPropertyDependenciesSync(instance, node, ctx);
438
+ }
439
+ // onModuleInit is not part of creation: it runs with no injection
440
+ // context, so inject() there throws InjectionContextError.
441
+ const runInit = () => (0, injection_context_1.withInjectionContext)(null, () => this.callInitSync(provider, instance));
442
+ // Conservatively wait on every init queued before this instance (it
443
+ // may hold some of those instances, created here or via cache hits),
444
+ // even without its own onModuleInit, so a failure there evicts this
445
+ // instance too. No deadlock: each entry only waits on earlier ones.
446
+ const earlier = [...ctx.pendingInits];
447
+ let pendingInit;
448
+ if (earlier.length > 0) {
449
+ const afterEarlier = Promise.all(earlier);
450
+ pendingInit = instance?.onModuleInit
451
+ ? afterEarlier.then(runInit)
452
+ : afterEarlier;
148
453
  }
454
+ else if (instance?.onModuleInit) {
455
+ pendingInit = runInit();
456
+ }
457
+ this.commitInstance(node, instance);
458
+ if (pendingInit) {
459
+ // A failed init (own or earlier) must not leave a broken cached
460
+ // instance. The eviction is chained here, not in a drain, so it
461
+ // also happens when the requester's constructor throws later.
462
+ const settled = pendingInit.catch((error) => {
463
+ this.evictInstance(node, instance);
464
+ throw error;
465
+ });
466
+ ctx.addPendingInit(settled);
467
+ this.trackInFlightInit(node.id, instance, settled);
468
+ }
469
+ return instance;
470
+ }
471
+ finally {
472
+ ctx.leave(node.id);
473
+ }
474
+ }
475
+ /** Runs a factory; factories must be synchronous. */
476
+ callFactory(node, provider, deps, injectionContext) {
477
+ const result = (0, injection_context_1.withInjectionContext)(injectionContext, () => provider.useFactory(...deps));
478
+ if (isThenable(result)) {
479
+ // Never leave a rejected promise unhandled. A foreign thenable's
480
+ // then() is user code with unknown effects, so it is not called.
481
+ if (result instanceof Promise) {
482
+ result.catch(() => { });
483
+ }
484
+ throw new provider_errors_1.AsyncFactoryError(node.id);
485
+ }
486
+ return result;
487
+ }
488
+ /** Registers `init` as the in-flight init of a just-committed singleton. */
489
+ trackInFlightInit(token, instance, init) {
490
+ if (!this.providersContainer.has(token) ||
491
+ this.providersContainer.get(token) !== instance) {
492
+ return; // not the global singleton (lost a race)
493
+ }
494
+ this.inFlightInits.set(token, init);
495
+ const clear = () => {
496
+ if (this.inFlightInits.get(token) === init) {
497
+ this.inFlightInits.delete(token);
498
+ }
499
+ };
500
+ init.then(clear, clear);
501
+ }
502
+ /**
503
+ * Calls onModuleInit synchronously. A synchronous throw is wrapped and
504
+ * rethrown; an async result is returned with its rejection wrapped.
505
+ */
506
+ callInitSync(provider, instance) {
507
+ let result;
508
+ try {
509
+ result = instance.onModuleInit();
149
510
  }
150
- return [instance, saveInCache];
511
+ catch (error) {
512
+ throw this.wrapInitError(provider, error);
513
+ }
514
+ if (isThenable(result)) {
515
+ return Promise.resolve(result).catch((error) => {
516
+ throw this.wrapInitError(provider, error);
517
+ });
518
+ }
519
+ return undefined;
520
+ }
521
+ wrapInitError(provider, error) {
522
+ const providerName = typeof provider === "function"
523
+ ? provider.name
524
+ : (provider.provide?.toString() ??
525
+ "Unknown");
526
+ const errorMessage = `Failed to initialize provider "${providerName}": ${error instanceof Error ? error.message : String(error)}`;
527
+ const wrappedError = new Error(errorMessage);
528
+ // Assigned rather than passed as ErrorOptions: the build targets ES2021.
529
+ wrappedError.cause = error;
530
+ if (error instanceof Error && error.stack) {
531
+ wrappedError.stack = `${wrappedError.stack}\nCaused by: ${error.stack}`;
532
+ }
533
+ return wrappedError;
534
+ }
535
+ contextFor(node, ctx) {
536
+ return {
537
+ inject: (token, options) => this.resolveSync(token, ctx, node, options),
538
+ };
539
+ }
540
+ /**
541
+ * Whether a late-bound edge's target may be injected into `requester`:
542
+ * it exists now and is visible from the requester's module. The graph
543
+ * records the referrer (keeping the target's module alive) under the
544
+ * same rule, so injection and unload bookkeeping agree.
545
+ */
546
+ lateBoundVisible(edge, requester) {
547
+ const version = this.graph.version;
548
+ const memo = this.lateBoundMemo.get(edge);
549
+ if (memo?.version === version) {
550
+ return memo.visible;
551
+ }
552
+ const visible = this.graph.getNode(edge.target)?.type === interfaces_1.NodeTypeEnum.PROVIDER &&
553
+ this.graph.isProviderVisible(requester.moduleContainer, edge.target);
554
+ this.lateBoundMemo.set(edge, { version, visible });
555
+ return visible;
151
556
  }
152
- async injectPropertyDependencies(instance, node, resolveCache) {
153
- const dependencies = this.graph
557
+ constructorEdges(node) {
558
+ return this.graph
559
+ .getEdge(node.id)
560
+ .filter((edge) => edge.type === interfaces_1.EdgeTypeEnum.DEPENDENCY &&
561
+ edge.metadata.inject === "constructor");
562
+ }
563
+ propertyEdges(node) {
564
+ return this.graph
154
565
  .getEdge(node.id)
155
566
  .filter((edge) => edge.type === interfaces_1.EdgeTypeEnum.DEPENDENCY &&
156
567
  edge.metadata.inject === "property" &&
157
568
  edge.metadata.unreached === false &&
158
569
  edge.metadata.isCircular === false);
159
- for (const edge of dependencies) {
160
- instance[edge.metadata.key] = await this.resolveProvider(edge.target, resolveCache);
570
+ }
571
+ injectPropertyDependenciesSync(instance, node, ctx) {
572
+ for (const edge of this.propertyEdges(node)) {
573
+ instance[edge.metadata.key] = this.resolveEdgeSync(edge, node, ctx);
574
+ }
575
+ }
576
+ async injectPropertyDependencies(instance, node, ctx) {
577
+ for (const edge of this.propertyEdges(node)) {
578
+ // External first, before resolveProvider's node lookup.
579
+ instance[edge.metadata.key] = edge.metadata.external
580
+ ? await this.linkFor(edge).resolve(edge.target)
581
+ : edge.metadata.lateBound &&
582
+ !this.lateBoundVisible(edge, node)
583
+ ? undefined
584
+ : await this.resolveProvider(edge.target, ctx);
161
585
  }
162
586
  }
163
587
  }