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