@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
@@ -2,29 +2,62 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ModuleGraph = void 0;
4
4
  const interfaces_1 = require("../../interfaces");
5
+ const forward_ref_1 = require("../../utils/forward-ref");
5
6
  const helpers_1 = require("../../utils/helpers");
7
+ const lazy_module_1 = require("../../utils/lazy-module");
8
+ const analyze_lazy_module_1 = require("./analyze-lazy-module");
6
9
  const analyze_module_1 = require("./analyze-module");
7
10
  const helpers_2 = require("./helpers");
8
11
  const provider_factory_1 = require("./providers/provider-factory");
9
12
  /**
10
- * Helper function to safely convert InjectionToken to string for error messages
13
+ * Helper function to safely convert ProviderToken to string for error messages
11
14
  */
12
15
  function tokenToString(token) {
13
16
  return typeof token === "function" ? token.name : String(token);
14
17
  }
15
- /**
16
- * Type guard to check if a node is an AnalyzeProvider
17
- */
18
- function isProviderNode(entry) {
19
- return entry[1].type === interfaces_1.NodeTypeEnum.PROVIDER;
20
- }
21
18
  class ModuleGraph {
22
- constructor(_root) {
19
+ constructor(_root, _internalRoots = [], options = {}) {
23
20
  this._root = _root;
21
+ this._internalRoots = _internalRoots;
22
+ this.options = options;
24
23
  this._nodes = new Map();
25
24
  this._edges = new Map();
26
25
  this._globalModules = new Map();
26
+ /** Module token -> its own declared provider tokens. */
27
+ this._moduleProviders = new Map();
28
+ /**
29
+ * Module token -> the other module tokens that need it alive: every
30
+ * module that imports it, plus every module owning a provider that
31
+ * depends (via a resolved `DEPENDENCY` edge, including one resolved
32
+ * through a `@Global()` module) on one of its providers.
33
+ */
34
+ this._moduleReferrers = new Map();
35
+ /**
36
+ * Late-bound edge index: target token -> source provider token -> whether
37
+ * the edge is bound. A `lateBound` edge is an `@Optional()` static edge
38
+ * whose target was not visible from the source's module when it was
39
+ * compiled (it may not have existed yet). The resolver injects its target
40
+ * only while the target exists and is visible. "Bound" means that held
41
+ * when a segment added the target (or a global module exposing it), so
42
+ * the source's module was recorded as a referrer of the target's module;
43
+ * only a bound edge counts in `moduleDependencies`, which keeps the
44
+ * referrer purge symmetric with the recording.
45
+ */
46
+ this._lateBound = new Map();
47
+ /**
48
+ * Child declaration index: the child's module class (or the `LazyModule.id`
49
+ * symbol of a lazy ref) -> the token of the one module declaring it.
50
+ */
51
+ this._childDeclarations = new Map();
27
52
  this._errors = [];
53
+ /**
54
+ * While a segment compiles, its errors are collected here instead of in
55
+ * `_errors`, so they are never mixed with (or mistaken for) errors of the
56
+ * eager graph or of another segment.
57
+ */
58
+ this._errorSink = null;
59
+ /** See `version`. */
60
+ this._version = 0;
28
61
  }
29
62
  get nodes() {
30
63
  return this._nodes;
@@ -35,11 +68,389 @@ class ModuleGraph {
35
68
  get errors() {
36
69
  return this._errors;
37
70
  }
71
+ get rootToken() {
72
+ return this._root.token;
73
+ }
74
+ get internalRootTokens() {
75
+ return this._internalRoots.map((root) => root.token);
76
+ }
77
+ /**
78
+ * Bumped whenever `compile`, `compileSegment` or `unloadSegment` may have
79
+ * changed which providers exist or which modules see them. A cache of
80
+ * `isProviderVisible` results is valid while this number is unchanged.
81
+ */
82
+ get version() {
83
+ return this._version;
84
+ }
85
+ /** Records a graph error in the active sink, or in the shared list. */
86
+ pushError(error) {
87
+ (this._errorSink ?? this._errors).push(error);
88
+ }
89
+ /**
90
+ * Compiles the eager graph. Internal roots are added before the user root so
91
+ * `_globalModules` already contains the internal modules when user providers
92
+ * are checked by `isProviderExported`.
93
+ */
38
94
  async compile() {
39
- await this.addModules();
40
- await this.addDependencies();
41
- await this.detectCircularDependencies();
42
- await this.detectCircularImports();
95
+ const moduleTokens = [];
96
+ const providerTokens = [];
97
+ for (const root of [...this._internalRoots, this._root]) {
98
+ const added = await this.addModules(root, false);
99
+ moduleTokens.push(...added.moduleTokens);
100
+ providerTokens.push(...added.providerTokens);
101
+ }
102
+ await this.addDependencies(providerTokens);
103
+ await this.detectCircularDependencies(providerTokens);
104
+ await this.detectCircularImports(moduleTokens);
105
+ this._version++;
106
+ }
107
+ /**
108
+ * Compiles the part of the graph reachable from `root` that is not already
109
+ * registered, attributing it to the `lazyModule` placeholder node.
110
+ *
111
+ * The pass is atomic: if it produces any error the segment is rolled back
112
+ * (every node, edge, global registration and LAZY placeholder it created is
113
+ * removed) and the placeholder stays unloaded. On success the placeholder is
114
+ * marked loaded. A placeholder that already existed before the segment
115
+ * started is never removed.
116
+ *
117
+ * The segment's errors are collected in a segment-local sink and never
118
+ * reach `this.errors`, so they cannot be attributed to the eager graph or
119
+ * to another segment. The caller (`Container.load`) runs one segment at a
120
+ * time, which is what makes the sink and the graph mutations safe under
121
+ * concurrent loads of different refs.
122
+ */
123
+ async compileSegment(root, lazyModule) {
124
+ const createdPlaceholder = !this._nodes.has(lazyModule.id);
125
+ if (createdPlaceholder) {
126
+ this.addNode(lazyModule.id, new analyze_lazy_module_1.AnalyzeLazyModule(lazyModule));
127
+ }
128
+ const errors = [];
129
+ const previousSink = this._errorSink;
130
+ this._errorSink = errors;
131
+ try {
132
+ const added = await this.addModules(root, true);
133
+ await this.addDependencies(added.providerTokens);
134
+ await this.detectCircularDependencies(added.providerTokens);
135
+ await this.detectCircularImports(added.moduleTokens);
136
+ if (errors.length > 0) {
137
+ this.removeTokens([
138
+ ...added.moduleTokens,
139
+ ...added.providerTokens,
140
+ ...added.lazyTokens,
141
+ ...(createdPlaceholder ? [lazyModule.id] : []),
142
+ ]);
143
+ }
144
+ else {
145
+ this.bindLateEdges(added.moduleTokens, added.providerTokens);
146
+ this._nodes.get(lazyModule.id).markLoaded(root.token);
147
+ }
148
+ return {
149
+ lazyModule,
150
+ moduleContainer: root,
151
+ moduleTokens: added.moduleTokens,
152
+ providerTokens: added.providerTokens,
153
+ errors,
154
+ };
155
+ }
156
+ finally {
157
+ this._errorSink = previousSink;
158
+ // Even a rolled-back segment was briefly part of the graph.
159
+ this._version++;
160
+ }
161
+ }
162
+ /**
163
+ * Binds the late-bound edges (see `_lateBound`) whose target this
164
+ * segment made visible to the edge's source, recording the referrer the
165
+ * resolver's injection now implies. The same visibility check as the
166
+ * resolver's, so bookkeeping and injection always agree.
167
+ *
168
+ * Only two things can make a late-bound target visible to an existing
169
+ * source: the target's node being added (looked up per added provider)
170
+ * or a new `@Global()` module exposing it (compiled modules' imports
171
+ * never change). Only a segment adding a global module rechecks every
172
+ * indexed target; the index holds only late-bound edges, so it is small.
173
+ *
174
+ * Once bound, an edge keeps the target's module pinned for the consumer
175
+ * module's lifetime, even after the global module that exposed it
176
+ * unloads. This is deliberately safe: instances created meanwhile may
177
+ * still hold the target.
178
+ */
179
+ bindLateEdges(addedModuleTokens, addedProviderTokens) {
180
+ if (this._lateBound.size === 0) {
181
+ return;
182
+ }
183
+ const addsGlobal = addedModuleTokens.some((token) => this._globalModules.has(token));
184
+ const targets = addsGlobal
185
+ ? [...this._lateBound.keys()]
186
+ : addedProviderTokens.filter((token) => this._lateBound.has(token));
187
+ for (const target of targets) {
188
+ if (this._nodes.get(target)?.type !== interfaces_1.NodeTypeEnum.PROVIDER) {
189
+ continue;
190
+ }
191
+ const sources = this._lateBound.get(target);
192
+ for (const [source, bound] of sources) {
193
+ const consumer = this._nodes.get(source);
194
+ if (bound || consumer?.type !== interfaces_1.NodeTypeEnum.PROVIDER) {
195
+ continue;
196
+ }
197
+ if (this.isProviderVisible(consumer.moduleContainer, target)) {
198
+ sources.set(source, true);
199
+ this.recordCrossModuleReferrer(source, target, true);
200
+ }
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Drops removed sources from the late-bound index and unbinds edges
206
+ * whose target was removed (a later load of the target binds them
207
+ * again). Runs after `purgeModuleIndexes`, which reads the bound state.
208
+ */
209
+ purgeLateBound(tokens) {
210
+ if (this._lateBound.size === 0) {
211
+ return;
212
+ }
213
+ const removed = new Set(tokens);
214
+ for (const [target, sources] of this._lateBound) {
215
+ for (const [source, bound] of sources) {
216
+ if (removed.has(source)) {
217
+ sources.delete(source);
218
+ }
219
+ else if (bound && removed.has(target)) {
220
+ sources.set(source, false);
221
+ }
222
+ }
223
+ if (sources.size === 0) {
224
+ this._lateBound.delete(target);
225
+ }
226
+ }
227
+ }
228
+ /**
229
+ * Rolls back a segment that failed validation: removes every node, edge
230
+ * and global registration it created (module, provider and LAZY
231
+ * placeholder tokens are all passed in together). Also purges
232
+ * `_moduleProviders`/`_moduleReferrers` index entries for `tokens` via
233
+ * `purgeModuleIndexes` — without this, a referrer entry recorded by
234
+ * `addModuleImports` before the rollback (pointing at a module token
235
+ * that no longer exists) would survive and could later be mistaken by
236
+ * `rescueCandidates` for a live outside referrer of a module reused by a
237
+ * different, successful segment.
238
+ */
239
+ removeTokens(tokens) {
240
+ this.purgeModuleIndexes(tokens);
241
+ this.purgeLateBound(tokens);
242
+ for (const token of tokens) {
243
+ this._nodes.delete(token);
244
+ this._edges.delete(token);
245
+ this._globalModules.delete(token);
246
+ }
247
+ }
248
+ /**
249
+ * Unloads a loaded lazy segment. Scoped to the segment's own subgraph,
250
+ * not the whole live graph: collects everything reachable from the
251
+ * segment's root (pruned at any module in `protectedRoots` — the eager
252
+ * root, internal roots, and every *other* currently loaded segment's
253
+ * root), then rescues back anything a survivor still needs. Returns the
254
+ * modules and providers that were actually destroyed; a no-op (empty
255
+ * result) if the ref is not currently loaded.
256
+ */
257
+ unloadSegment(lazyModule, protectedRoots) {
258
+ const placeholder = this._nodes.get(lazyModule.id);
259
+ if (!placeholder ||
260
+ !placeholder.loaded ||
261
+ placeholder.moduleToken === null) {
262
+ return { destroyedModules: [], destroyedProviders: [] };
263
+ }
264
+ const candidates = this.collectCandidates(placeholder.moduleToken, protectedRoots);
265
+ const alive = this.rescueCandidates(candidates);
266
+ const destroyedModules = [];
267
+ const destroyedProviders = [];
268
+ for (const moduleToken of candidates) {
269
+ if (alive.has(moduleToken)) {
270
+ continue;
271
+ }
272
+ destroyedModules.push(moduleToken);
273
+ for (const providerToken of this._moduleProviders.get(moduleToken) ??
274
+ []) {
275
+ destroyedProviders.push(providerToken);
276
+ }
277
+ }
278
+ this.removeModuleTokens(destroyedModules, destroyedProviders);
279
+ placeholder.markUnloaded();
280
+ this._version++;
281
+ return { destroyedModules, destroyedProviders };
282
+ }
283
+ /** A module's own `IMPORT` targets, plus the declaring modules of
284
+ * whatever its own providers depend on in another module. Shared by
285
+ * candidate collection, rescue propagation and teardown cleanup. */
286
+ moduleDependencies(moduleToken) {
287
+ const dependencies = [];
288
+ for (const edge of this.getEdge(moduleToken)) {
289
+ if (edge.type === interfaces_1.EdgeTypeEnum.IMPORT) {
290
+ dependencies.push(edge.source);
291
+ }
292
+ }
293
+ for (const providerToken of this._moduleProviders.get(moduleToken) ?? []) {
294
+ for (const edge of this.getEdge(providerToken)) {
295
+ if (edge.type !== interfaces_1.EdgeTypeEnum.DEPENDENCY ||
296
+ edge.metadata.unreached ||
297
+ edge.metadata.external ||
298
+ (edge.metadata.lateBound &&
299
+ this._lateBound.get(edge.target)?.get(providerToken) !== true)) {
300
+ continue;
301
+ }
302
+ const dependency = this._nodes.get(edge.target);
303
+ if (dependency?.type === interfaces_1.NodeTypeEnum.PROVIDER) {
304
+ dependencies.push(dependency.moduleContainer.token);
305
+ }
306
+ }
307
+ }
308
+ return dependencies;
309
+ }
310
+ collectCandidates(rootToken, protectedRoots) {
311
+ const candidates = new Set();
312
+ const queue = [rootToken];
313
+ while (queue.length) {
314
+ const moduleToken = queue.shift();
315
+ if (protectedRoots.has(moduleToken) || candidates.has(moduleToken)) {
316
+ continue;
317
+ }
318
+ candidates.add(moduleToken);
319
+ queue.push(...this.moduleDependencies(moduleToken));
320
+ }
321
+ return candidates;
322
+ }
323
+ /**
324
+ * A candidate survives if something outside the candidate set still
325
+ * needs it (a "seed"), or if it is only reachable through a candidate
326
+ * that survives. A plain per-node check for the first case alone is not
327
+ * enough — see the "rescues a module only reachable through a surviving
328
+ * sibling candidate" test.
329
+ */
330
+ rescueCandidates(candidates) {
331
+ const alive = new Set();
332
+ const queue = [];
333
+ for (const moduleToken of candidates) {
334
+ const referrers = this._moduleReferrers.get(moduleToken);
335
+ const hasOutsideReferrer = referrers &&
336
+ [...referrers].some((referrer) => !candidates.has(referrer));
337
+ if (hasOutsideReferrer) {
338
+ queue.push(moduleToken);
339
+ }
340
+ }
341
+ while (queue.length) {
342
+ const moduleToken = queue.shift();
343
+ if (alive.has(moduleToken)) {
344
+ continue;
345
+ }
346
+ alive.add(moduleToken);
347
+ queue.push(...this.moduleDependencies(moduleToken).filter((token) => candidates.has(token)));
348
+ }
349
+ return alive;
350
+ }
351
+ removeModuleTokens(moduleTokens, providerTokens) {
352
+ // Drop stale referrer entries, and the modules' own index entries,
353
+ // before deleting the edges they were derived from, so a module that
354
+ // stays alive never keeps a phantom referrer from a module that no
355
+ // longer exists.
356
+ //
357
+ // Note: this only removes edges from the maps keyed by the destroyed
358
+ // tokens themselves, not from every place a destroyed token might
359
+ // still appear as an edge *target* elsewhere — e.g. an
360
+ // `unreached: true` DEPENDENCY edge left on a surviving provider, or
361
+ // a LAZY placeholder edge whose declaring module was destroyed.
362
+ // That's safe for resolution (`Resolver.resolveProvider` already
363
+ // returns `undefined` for a missing node), but graph-introspection
364
+ // consumers (like `graph-analyzer`) can still see these as orphaned
365
+ // edge references.
366
+ this.purgeModuleIndexes(moduleTokens);
367
+ this.purgeLateBound(providerTokens);
368
+ // Defensive: a runtime (inject()) edge also records its source's module
369
+ // as a referrer of the target's module, so a provider that survives
370
+ // this unload should never hold one to a destroyed provider. Should it
371
+ // happen anyway, drop the edge; it is re-discovered on the next creation.
372
+ const destroyedProviders = new Set(providerTokens);
373
+ for (const [source, edges] of this._edges) {
374
+ if (destroyedProviders.has(source)) {
375
+ continue;
376
+ }
377
+ if (edges.some((e) => e.metadata.inject === "runtime" && destroyedProviders.has(e.target))) {
378
+ this._edges.set(source, edges.filter((e) => !(e.metadata.inject === "runtime" &&
379
+ destroyedProviders.has(e.target))));
380
+ }
381
+ }
382
+ for (const providerToken of providerTokens) {
383
+ this._nodes.delete(providerToken);
384
+ this._edges.delete(providerToken);
385
+ }
386
+ for (const moduleToken of moduleTokens) {
387
+ this._nodes.delete(moduleToken);
388
+ this._edges.delete(moduleToken);
389
+ this._globalModules.delete(moduleToken);
390
+ }
391
+ }
392
+ /**
393
+ * Purges `_moduleProviders`/`_moduleReferrers` index entries for
394
+ * `tokens`, and removes each of them from any surviving module's
395
+ * referrer set. Shared by `removeModuleTokens` (destroying a live
396
+ * segment) and `removeTokens` (rolling back a failed one).
397
+ *
398
+ * `tokens` may freely mix module tokens with provider or LAZY
399
+ * placeholder tokens: both indexes are keyed by module tokens only, so a
400
+ * lookup or delete with a non-module key is a harmless no-op.
401
+ *
402
+ * Must run before the tokens' own `_edges` entries (and, for a removed
403
+ * module, its `_moduleProviders` entry) are deleted — `moduleDependencies`
404
+ * reads both to determine what each token itself depended on.
405
+ */
406
+ purgeModuleIndexes(tokens) {
407
+ for (const token of tokens) {
408
+ for (const dependencyModule of this.moduleDependencies(token)) {
409
+ this._moduleReferrers.get(dependencyModule)?.delete(token);
410
+ }
411
+ }
412
+ const purged = new Set(tokens);
413
+ for (const [child, declarer] of this._childDeclarations) {
414
+ if (purged.has(declarer)) {
415
+ this._childDeclarations.delete(child);
416
+ }
417
+ }
418
+ for (const token of tokens) {
419
+ this._moduleProviders.delete(token);
420
+ this._moduleReferrers.delete(token);
421
+ }
422
+ }
423
+ addRuntimeDependency(source, target) {
424
+ // inject() only gets here for a visible target. Recorded even when an
425
+ // edge already exists: a static late-bound edge to the same target
426
+ // (an @Optional one compiled while the target was not visible) may
427
+ // not be bound yet, and then recorded no referrer. Idempotent.
428
+ this.recordCrossModuleReferrer(source, target, true);
429
+ const exists = this.getEdge(source).some((edge) => edge.type === interfaces_1.EdgeTypeEnum.DEPENDENCY && edge.target === target);
430
+ if (exists) {
431
+ // Bind it, so moduleDependencies sees it and the purge drops the
432
+ // referrer just recorded when either side unloads.
433
+ const sources = this._lateBound.get(target);
434
+ if (sources?.has(source)) {
435
+ sources.set(source, true);
436
+ }
437
+ return;
438
+ }
439
+ this.addEdge(source, {
440
+ type: interfaces_1.EdgeTypeEnum.DEPENDENCY,
441
+ source,
442
+ target,
443
+ metadata: { inject: "runtime", unreached: false, isCircular: false },
444
+ });
445
+ }
446
+ findChildDeclaration(child) {
447
+ const key = (0, lazy_module_1.isLazyModule)(child) ? child.id : child;
448
+ const declarer = this._childDeclarations.get(key);
449
+ if (declarer === undefined) {
450
+ return undefined;
451
+ }
452
+ const node = this._nodes.get(declarer);
453
+ return node instanceof analyze_module_1.AnalyzeModule ? node.moduleContainer : undefined;
43
454
  }
44
455
  getNode(token) {
45
456
  return this._nodes.get(token);
@@ -54,43 +465,121 @@ class ModuleGraph {
54
465
  return [...this._nodes.values()];
55
466
  }
56
467
  // modules analyze
57
- async addModules() {
468
+ async addModules(root, strictTokens) {
469
+ const moduleTokens = [];
470
+ const providerTokens = [];
471
+ const lazyTokens = [];
58
472
  const visited = new Set();
59
- const imports = [this._root];
473
+ const imports = [root];
60
474
  while (imports.length) {
61
475
  const importModule = imports.shift();
62
476
  if (!importModule || visited.has(importModule.token)) {
63
477
  continue;
64
478
  }
479
+ visited.add(importModule.token);
480
+ if (this._nodes.has(importModule.token)) {
481
+ // Already part of the graph (eager module or an earlier segment).
482
+ continue;
483
+ }
65
484
  const analyzeModule = new analyze_module_1.AnalyzeModule(importModule);
66
485
  await this.addModule(analyzeModule);
67
- await this.addModuleImports(analyzeModule);
68
- await this.addModuleProviders(analyzeModule);
486
+ moduleTokens.push(analyzeModule.id);
487
+ lazyTokens.push(...(await this.addModuleImports(analyzeModule)));
488
+ providerTokens.push(...(await this.addModuleProviders(analyzeModule, strictTokens)));
69
489
  imports.push(...(await analyzeModule.imports));
70
- visited.add(analyzeModule.id);
71
490
  }
491
+ return { moduleTokens, providerTokens, lazyTokens };
72
492
  }
73
493
  async addModule(analyzeModule) {
74
494
  this.addNode(analyzeModule.id, analyzeModule);
495
+ this.indexChildren(analyzeModule);
75
496
  if (analyzeModule.isGlobal) {
76
497
  this._globalModules.set(analyzeModule.id, analyzeModule.moduleContainer);
77
498
  }
78
499
  }
500
+ /**
501
+ * Records the module's declared children. Children are never compiled into
502
+ * this graph; only the declaration is indexed. A child declared by a second
503
+ * module is a graph error and keeps its first declarer.
504
+ */
505
+ indexChildren(analyzeModule) {
506
+ for (const child of analyzeModule.moduleContainer.children) {
507
+ const key = (0, lazy_module_1.isLazyModule)(child) ? child.id : child;
508
+ const existing = this._childDeclarations.get(key);
509
+ if (existing === undefined) {
510
+ this._childDeclarations.set(key, analyzeModule.id);
511
+ }
512
+ else if (existing !== analyzeModule.id) {
513
+ // A second declarer; a module meeting its own entry is only
514
+ // listing the child twice, which is not an error.
515
+ this.pushError({
516
+ type: "CHILD_DECLARED_TWICE",
517
+ child: child.name,
518
+ modules: [
519
+ this.moduleLabel(existing),
520
+ this.moduleLabel(analyzeModule.id),
521
+ ],
522
+ });
523
+ }
524
+ }
525
+ }
526
+ moduleLabel(moduleToken) {
527
+ const node = this._nodes.get(moduleToken);
528
+ if (node instanceof analyze_module_1.AnalyzeModule) {
529
+ return (0, helpers_1.getModuleLabel)(node.moduleContainer.metatype);
530
+ }
531
+ return moduleToken;
532
+ }
533
+ /**
534
+ * @returns the ids of the LAZY placeholder nodes this call created, so a
535
+ * rolled back segment can remove them again. Placeholders that already
536
+ * existed are not reported and must never be removed.
537
+ */
79
538
  async addModuleImports(analyzeModule) {
539
+ const createdLazyIds = [];
80
540
  const imports = await analyzeModule.edges;
81
541
  for (const importEdge of imports) {
82
542
  this.addEdge(analyzeModule.id, importEdge);
543
+ this.addModuleReferrer(importEdge.source, analyzeModule.id);
544
+ }
545
+ for (const lazyModule of analyzeModule.lazyImports) {
546
+ if (!this._nodes.has(lazyModule.id)) {
547
+ this.addNode(lazyModule.id, new analyze_lazy_module_1.AnalyzeLazyModule(lazyModule));
548
+ createdLazyIds.push(lazyModule.id);
549
+ }
550
+ }
551
+ for (const lazyEdge of analyzeModule.lazyEdges) {
552
+ this.addEdge(analyzeModule.id, lazyEdge);
83
553
  }
554
+ return createdLazyIds;
84
555
  }
85
- async addModuleProviders(analyzeModule) {
556
+ async addModuleProviders(analyzeModule, strictTokens) {
557
+ const added = [];
86
558
  for (const provider of analyzeModule.providers) {
87
559
  const analyzeProvider = (0, provider_factory_1.ProviderFactory)(provider, analyzeModule.moduleContainer);
88
560
  if (analyzeProvider === null) {
89
561
  continue;
90
562
  }
563
+ const existing = this._nodes.get(analyzeProvider.id);
564
+ if (strictTokens &&
565
+ existing &&
566
+ existing.type === interfaces_1.NodeTypeEnum.PROVIDER &&
567
+ existing.moduleContainer.token !==
568
+ analyzeModule.moduleContainer.token) {
569
+ this.pushError({
570
+ type: "PROVIDER_TOKEN_CONFLICT",
571
+ token: analyzeProvider.label,
572
+ module: analyzeModule.label,
573
+ existingModule: (0, helpers_1.getModuleLabel)(existing.moduleContainer.metatype),
574
+ });
575
+ continue;
576
+ }
91
577
  this.addNode(analyzeProvider.id, analyzeProvider);
92
578
  this.addEdge(analyzeModule.id, analyzeProvider.edge);
579
+ this.addModuleProvider(analyzeModule.id, analyzeProvider.id);
580
+ added.push(analyzeProvider.id);
93
581
  }
582
+ return added;
94
583
  }
95
584
  // modules analyze
96
585
  // graph helpers
@@ -102,13 +591,68 @@ class ModuleGraph {
102
591
  addNode(token, node) {
103
592
  this._nodes.set(token, node);
104
593
  }
594
+ addModuleProvider(moduleToken, providerToken) {
595
+ const providers = this._moduleProviders.get(moduleToken) ?? new Set();
596
+ providers.add(providerToken);
597
+ this._moduleProviders.set(moduleToken, providers);
598
+ }
599
+ addModuleReferrer(dependencyModule, referrerModule) {
600
+ const referrers = this._moduleReferrers.get(dependencyModule) ?? new Set();
601
+ referrers.add(referrerModule);
602
+ this._moduleReferrers.set(dependencyModule, referrers);
603
+ }
604
+ /**
605
+ * Bookkeeping for a static `DEPENDENCY` edge just added: a late-bound
606
+ * edge is indexed (its referrer is recorded by `bindLateEdges` once the
607
+ * target is visible), any other edge records its referrer now.
608
+ */
609
+ recordStaticDependency(source, target, countsAsReferrer, lateBound) {
610
+ if (!lateBound) {
611
+ this.recordCrossModuleReferrer(source, target, countsAsReferrer);
612
+ return;
613
+ }
614
+ const sources = this._lateBound.get(target) ?? new Map();
615
+ if (!sources.has(source)) {
616
+ sources.set(source, false);
617
+ }
618
+ this._lateBound.set(target, sources);
619
+ }
620
+ /**
621
+ * Records that `consumerToken` (a provider) keeps `dependencyToken`'s
622
+ * declaring module alive, when the two providers are declared in
623
+ * different modules. Called after every `DEPENDENCY` edge is added,
624
+ * regardless of how the dependency was resolved (own module, import, or
625
+ * `@Global()` fallback) — the edge already reflects that, so this needs
626
+ * no help from `isProviderExported`.
627
+ *
628
+ * @param countsAsReferrer - False for an edge that keeps nothing in this
629
+ * graph alive: an unreached dependency, or an external one (resolved
630
+ * through the parent link, so its target lives in another container).
631
+ */
632
+ recordCrossModuleReferrer(consumerToken, dependencyToken, countsAsReferrer) {
633
+ if (!countsAsReferrer) {
634
+ return;
635
+ }
636
+ const consumer = this._nodes.get(consumerToken);
637
+ const dependency = this._nodes.get(dependencyToken);
638
+ if (consumer?.type !== interfaces_1.NodeTypeEnum.PROVIDER ||
639
+ dependency?.type !== interfaces_1.NodeTypeEnum.PROVIDER) {
640
+ return;
641
+ }
642
+ const consumerModule = consumer.moduleContainer.token;
643
+ const dependencyModule = dependency.moduleContainer
644
+ .token;
645
+ if (consumerModule !== dependencyModule) {
646
+ this.addModuleReferrer(dependencyModule, consumerModule);
647
+ }
648
+ }
105
649
  // graph helpers
106
650
  // providers dependencies
107
- async addDependencies() {
651
+ async addDependencies(providerTokens) {
108
652
  const visited = new Set();
109
- const providerNodes = [...this.nodes].filter(isProviderNode);
110
- for (const [token, node] of providerNodes) {
111
- if (visited.has(token)) {
653
+ for (const token of providerTokens) {
654
+ const node = this._nodes.get(token);
655
+ if (!node || node.type !== interfaces_1.NodeTypeEnum.PROVIDER || visited.has(token)) {
112
656
  continue;
113
657
  }
114
658
  if ((0, helpers_2.isAnalyzeFactoryProvider)(node)) {
@@ -127,11 +671,13 @@ class ModuleGraph {
127
671
  const constructorDependencies = this.getConstructorDependencies(node.metatype);
128
672
  const optionalDependency = this.getOptionalConstructorDependencies(node.metatype);
129
673
  for (const [index, dependency] of constructorDependencies.entries()) {
130
- const dependencyToken = (0, helpers_1.getDependencyToken)(dependency.param);
131
- const isExported = optionalDependency.includes(index) ||
132
- (await this.isProviderExported(node.moduleContainer, dependencyToken));
133
- if (!isExported) {
134
- this.errors.push({
674
+ const isLazy = (0, forward_ref_1.isForwardRef)(dependency.param);
675
+ const dependencyToken = isLazy
676
+ ? dependency.param.forwardRef()
677
+ : (0, helpers_1.getDependencyToken)(dependency.param);
678
+ const { reachable, external, lateBound } = await this.reachDependency(node.moduleContainer, dependencyToken, optionalDependency.includes(index));
679
+ if (!reachable) {
680
+ this.pushError({
135
681
  type: "UNREACHED_DEP_CONSTRUCTOR",
136
682
  token: node.label,
137
683
  dependency: tokenToString(dependencyToken),
@@ -143,22 +689,25 @@ class ModuleGraph {
143
689
  source: token,
144
690
  target: dependencyToken,
145
691
  metadata: {
146
- unreached: !isExported,
692
+ unreached: !reachable,
693
+ ...(external && { external: true }),
694
+ ...(lateBound && { lateBound: true }),
147
695
  isCircular: false,
696
+ lazy: isLazy,
148
697
  index: dependency.index,
149
698
  inject: "constructor",
150
699
  },
151
700
  };
152
701
  this.addEdge(token, newEdge);
702
+ this.recordStaticDependency(token, dependencyToken, reachable && !external, lateBound);
153
703
  }
154
704
  const propertiesDependencies = this.getPropertiesDependencies(node.metatype);
155
705
  const optionalProperties = this.getOptionalPropertyDependencies(node.metatype);
156
706
  for (const dependency of propertiesDependencies) {
157
707
  const dependencyToken = (0, helpers_1.getDependencyToken)(dependency.type);
158
- const isExported = optionalProperties.includes(dependency.key) ||
159
- (await this.isProviderExported(node.moduleContainer, dependencyToken));
160
- if (!isExported) {
161
- this.errors.push({
708
+ const { reachable, external, lateBound } = await this.reachDependency(node.moduleContainer, dependencyToken, optionalProperties.includes(dependency.key));
709
+ if (!reachable) {
710
+ this.pushError({
162
711
  type: "UNREACHED_DEP_PROPERTY",
163
712
  token: node.label,
164
713
  dependency: tokenToString(dependencyToken),
@@ -170,13 +719,16 @@ class ModuleGraph {
170
719
  source: token,
171
720
  target: dependencyToken,
172
721
  metadata: {
173
- unreached: !isExported,
722
+ unreached: !reachable,
723
+ ...(external && { external: true }),
724
+ ...(lateBound && { lateBound: true }),
174
725
  isCircular: false,
175
726
  key: dependency.key,
176
727
  inject: "property",
177
728
  },
178
729
  };
179
730
  this.addEdge(token, newEdge);
731
+ this.recordStaticDependency(token, dependencyToken, reachable && !external, lateBound);
180
732
  }
181
733
  }
182
734
  async addClassProviderDependency(token, node) {
@@ -184,11 +736,13 @@ class ModuleGraph {
184
736
  const constructorDependencies = this.getConstructorDependencies(Class);
185
737
  const optionalDependency = this.getOptionalConstructorDependencies(Class);
186
738
  for (const [index, dependency] of constructorDependencies.entries()) {
187
- const dependencyToken = (0, helpers_1.getDependencyToken)(dependency.param);
188
- const isExported = optionalDependency.includes(index) ||
189
- (await this.isProviderExported(node.moduleContainer, dependencyToken));
190
- if (!isExported) {
191
- this.errors.push({
739
+ const isLazy = (0, forward_ref_1.isForwardRef)(dependency.param);
740
+ const dependencyToken = isLazy
741
+ ? dependency.param.forwardRef()
742
+ : (0, helpers_1.getDependencyToken)(dependency.param);
743
+ const { reachable, external, lateBound } = await this.reachDependency(node.moduleContainer, dependencyToken, optionalDependency.includes(index));
744
+ if (!reachable) {
745
+ this.pushError({
192
746
  type: "UNREACHED_DEP_CONSTRUCTOR",
193
747
  token: node.label,
194
748
  dependency: tokenToString(dependencyToken),
@@ -200,22 +754,25 @@ class ModuleGraph {
200
754
  source: token,
201
755
  target: dependencyToken,
202
756
  metadata: {
203
- unreached: !isExported,
757
+ unreached: !reachable,
758
+ ...(external && { external: true }),
759
+ ...(lateBound && { lateBound: true }),
204
760
  isCircular: false,
761
+ lazy: isLazy,
205
762
  index: dependency.index,
206
763
  inject: "constructor",
207
764
  },
208
765
  };
209
766
  this.addEdge(token, newEdge);
767
+ this.recordStaticDependency(token, dependencyToken, reachable && !external, lateBound);
210
768
  }
211
769
  const propertiesDependencies = this.getPropertiesDependencies(Class);
212
770
  const optionalProperty = this.getOptionalPropertyDependencies(Class);
213
771
  for (const dependency of propertiesDependencies) {
214
772
  const dependencyToken = (0, helpers_1.getDependencyToken)(dependency.type);
215
- const isExported = optionalProperty.includes(dependency.key) ||
216
- (await this.isProviderExported(node.moduleContainer, dependencyToken));
217
- if (!isExported) {
218
- this.errors.push({
773
+ const { reachable, external, lateBound } = await this.reachDependency(node.moduleContainer, dependencyToken, optionalProperty.includes(dependency.key));
774
+ if (!reachable) {
775
+ this.pushError({
219
776
  type: "UNREACHED_DEP_PROPERTY",
220
777
  token: node.label,
221
778
  dependency: tokenToString(dependencyToken),
@@ -227,58 +784,101 @@ class ModuleGraph {
227
784
  source: token,
228
785
  target: dependencyToken,
229
786
  metadata: {
230
- unreached: !isExported,
787
+ unreached: !reachable,
788
+ ...(external && { external: true }),
789
+ ...(lateBound && { lateBound: true }),
231
790
  isCircular: false,
232
791
  key: dependency.key,
233
792
  inject: "property",
234
793
  },
235
794
  });
795
+ this.recordStaticDependency(token, dependencyToken, reachable && !external, lateBound);
236
796
  }
237
797
  }
238
798
  async addFactoryProviderDependency(token, node) {
239
799
  const dependencies = node.inject || [];
240
800
  let index = 0;
241
801
  for (const dependency of dependencies) {
242
- const isExported = await this.isProviderExported(node.moduleContainer, dependency);
243
- if (!isExported) {
244
- this.errors.push({
802
+ const isLazy = (0, forward_ref_1.isForwardRef)(dependency);
803
+ const dependencyToken = isLazy
804
+ ? dependency.forwardRef()
805
+ : dependency;
806
+ const { reachable, external, lateBound } = await this.reachDependency(node.moduleContainer, dependencyToken, false);
807
+ if (!reachable) {
808
+ this.pushError({
245
809
  type: "UNREACHED_DEP_FACTORY",
246
810
  token: node.label,
247
- dependency: tokenToString(dependency),
811
+ dependency: tokenToString(dependencyToken),
248
812
  key: index,
249
813
  });
250
814
  }
251
815
  const factoryDependency = {
252
816
  type: interfaces_1.EdgeTypeEnum.DEPENDENCY,
253
817
  source: token,
254
- target: dependency,
818
+ target: dependencyToken,
255
819
  metadata: {
256
- unreached: !isExported,
820
+ unreached: !reachable,
821
+ ...(external && { external: true }),
822
+ ...(lateBound && { lateBound: true }),
257
823
  isCircular: false,
824
+ lazy: isLazy,
258
825
  index: index++,
259
826
  inject: "constructor",
260
827
  },
261
828
  };
262
829
  this.addEdge(token, factoryDependency);
830
+ this.recordStaticDependency(token, dependencyToken, reachable && !external, lateBound);
263
831
  }
264
832
  }
833
+ /**
834
+ * How a dependency of a provider declared in `moduleContainer` is
835
+ * satisfied: locally (own module, imports, globals), by the parent
836
+ * container chain, or not at all. Optional dependencies count as
837
+ * satisfied; a parent that can provide one is still preferred. An
838
+ * optional dependency satisfied only by being optional is `lateBound`:
839
+ * the resolver injects it only once it exists and is visible.
840
+ */
841
+ async reachDependency(moduleContainer, token, optional) {
842
+ if (await this.isProviderExported(moduleContainer, token)) {
843
+ return { reachable: true, external: false, lateBound: false };
844
+ }
845
+ if (this.options.externalLookup?.(token)) {
846
+ return { reachable: true, external: true, lateBound: false };
847
+ }
848
+ return { reachable: optional, external: false, lateBound: optional };
849
+ }
265
850
  // providers dependencies
266
851
  async isProviderExported(moduleContainer, dependencyToken) {
267
- // check globals
852
+ await moduleContainer.imports;
853
+ return this.isProviderVisible(moduleContainer, dependencyToken);
854
+ }
855
+ isProviderVisible(moduleContainer, dependencyToken) {
856
+ // globals, including what they re-export through a module class
857
+ const globalExports = [];
268
858
  for (const globalModule of this._globalModules.values()) {
269
- if (globalModule.exports.some((provider) => provider === dependencyToken)) {
270
- return true;
271
- }
859
+ globalExports.push(...globalModule.exports);
860
+ }
861
+ if (this.exportsReach(globalExports, dependencyToken)) {
862
+ return true;
272
863
  }
273
- // check globals
274
- // check internals
864
+ // own providers
275
865
  if (moduleContainer.providers.some((provider) => (0, helpers_1.getProviderToken)(provider) === dependencyToken)) {
276
866
  return true;
277
867
  }
278
- // externals
279
- const containerImports = await moduleContainer.imports;
868
+ // imports
869
+ const containerImports = moduleContainer.resolvedImports;
870
+ if (!containerImports) {
871
+ throw new Error("Module imports are not resolved yet");
872
+ }
873
+ return this.exportsReach(containerImports.flatMap((imported) => imported.exports), dependencyToken);
874
+ }
875
+ /**
876
+ * Whether `dependencyToken` is among `exports`, following module-class
877
+ * re-exports through the modules' own exports.
878
+ */
879
+ exportsReach(exports, dependencyToken) {
280
880
  const visitedModules = new Set();
281
- const queue = containerImports.flatMap((firstLevelContainer) => firstLevelContainer.exports);
881
+ const queue = [...exports];
282
882
  while (queue.length) {
283
883
  const currentExportToken = queue.shift();
284
884
  if (!currentExportToken || visitedModules.has(currentExportToken)) {
@@ -301,14 +901,13 @@ class ModuleGraph {
301
901
  }
302
902
  return false;
303
903
  }
304
- async detectCircularDependencies() {
904
+ async detectCircularDependencies(startTokens) {
305
905
  const visit = (nodeId, path, visited, stack) => {
306
906
  if (stack.has(nodeId)) {
307
907
  const cyclePath = path.map((node, index) => [node, path[index + 1] || nodeId]);
308
908
  const from = cyclePath[0];
309
909
  const to = cyclePath[cyclePath.length - 1];
310
910
  if (from[0] === to[1]) {
311
- const _edges = this._edges.get(nodeId);
312
911
  for (const [from, to] of cyclePath) {
313
912
  const edges = this._edges.get(from);
314
913
  if (edges) {
@@ -318,10 +917,20 @@ class ModuleGraph {
318
917
  }
319
918
  }
320
919
  }
321
- this.errors.push({
322
- type: "CD_PROVIDERS",
323
- path: cyclePath,
920
+ // Only push CD_PROVIDERS if NO edge in cycle has forwardRef (lazy)
921
+ const hasForwardRef = cyclePath.some(([from, to]) => {
922
+ const edges = this._edges.get(from);
923
+ if (!edges)
924
+ return false;
925
+ const edge = edges.find((e) => e.target === to && e.type === interfaces_1.EdgeTypeEnum.DEPENDENCY);
926
+ return edge?.metadata.lazy === true;
324
927
  });
928
+ if (!hasForwardRef) {
929
+ this.pushError({
930
+ type: "CD_PROVIDERS",
931
+ path: cyclePath,
932
+ });
933
+ }
325
934
  }
326
935
  return true;
327
936
  }
@@ -331,20 +940,27 @@ class ModuleGraph {
331
940
  visited.add(nodeId);
332
941
  stack.add(nodeId);
333
942
  path.push(nodeId);
943
+ // Runtime (inject()) edges are followed too. That is safe: one is
944
+ // recorded only after inject() returned its target, and a cycle
945
+ // through inject() throws CircularInjectionError during creation
946
+ // before the edge is recorded. So runtime edges only mirror
947
+ // dependencies that were actually created without a cycle.
334
948
  const edges = this.getEdge(nodeId) || [];
335
949
  for (const edge of edges) {
950
+ // An external edge ends here: its target lives in the parent chain.
951
+ if (edge.metadata.external)
952
+ continue;
336
953
  visit(edge.target, path, visited, stack);
337
954
  }
338
955
  stack.delete(nodeId);
339
956
  path.pop();
340
957
  return false;
341
958
  };
342
- const providers = [...this._nodes].filter(([_, node]) => node.type === interfaces_1.NodeTypeEnum.PROVIDER);
343
- for (const [nodeId, _] of providers) {
959
+ for (const nodeId of new Set(startTokens)) {
344
960
  visit(nodeId, [], new Set(), new Set());
345
961
  }
346
962
  }
347
- async detectCircularImports() {
963
+ async detectCircularImports(startTokens) {
348
964
  const visit = (nodeId, path, visited, stack) => {
349
965
  if (stack.has(nodeId)) {
350
966
  const cycleStartIndex = path.indexOf(nodeId);
@@ -356,7 +972,7 @@ class ModuleGraph {
356
972
  if (edges) {
357
973
  const edge = edges.find((e) => e.source === to);
358
974
  if (edge && edge.type === interfaces_1.EdgeTypeEnum.IMPORT) {
359
- this.errors.push({
975
+ this.pushError({
360
976
  type: "CD_IMPORTS",
361
977
  path: cyclePath
362
978
  .map((token) => this.getNode(token))
@@ -384,8 +1000,7 @@ class ModuleGraph {
384
1000
  stack.delete(nodeId);
385
1001
  path.pop();
386
1002
  };
387
- const modules = [...this._nodes].filter(([_, node]) => node.type === interfaces_1.NodeTypeEnum.MODULE);
388
- for (const [nodeId, _] of modules) {
1003
+ for (const nodeId of new Set(startTokens)) {
389
1004
  visit(nodeId, [], new Set(), new Set());
390
1005
  }
391
1006
  }