@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
|
@@ -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
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 = [
|
|
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
|
-
|
|
68
|
-
await this.
|
|
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
|
|
110
|
-
|
|
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
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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: !
|
|
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
|
|
159
|
-
|
|
160
|
-
|
|
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: !
|
|
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
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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: !
|
|
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
|
|
216
|
-
|
|
217
|
-
|
|
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: !
|
|
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
|
|
243
|
-
|
|
244
|
-
|
|
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(
|
|
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:
|
|
818
|
+
target: dependencyToken,
|
|
255
819
|
metadata: {
|
|
256
|
-
unreached: !
|
|
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
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
859
|
+
globalExports.push(...globalModule.exports);
|
|
860
|
+
}
|
|
861
|
+
if (this.exportsReach(globalExports, dependencyToken)) {
|
|
862
|
+
return true;
|
|
272
863
|
}
|
|
273
|
-
//
|
|
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
|
-
//
|
|
279
|
-
const containerImports =
|
|
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 =
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
}
|