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