@codefast/di 0.8.1 → 0.10.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/CHANGELOG.md +164 -0
- package/LICENSE +1 -1
- package/README.md +29 -20
- package/dist/ambient/active-container.d.ts +13 -0
- package/dist/ambient/active-container.js +24 -0
- package/dist/container/binding-builders.d.ts +42 -20
- package/dist/container/binding-builders.js +155 -87
- package/dist/container/container.d.ts +10 -10
- package/dist/container/container.js +55 -23
- package/dist/core/binding.d.ts +55 -57
- package/dist/core/binding.js +11 -37
- package/dist/core/constraint-requirement.d.ts +19 -4
- package/dist/core/constraint-requirement.js +19 -9
- package/dist/core/registry.d.ts +47 -4
- package/dist/core/registry.js +361 -159
- package/dist/core/state-epoch.d.ts +16 -0
- package/dist/core/state-epoch.js +21 -0
- package/dist/core/tag.d.ts +2 -2
- package/dist/core/tag.js +2 -2
- package/dist/core/token.d.ts +24 -4
- package/dist/core/token.js +1 -1
- package/dist/core/types.d.ts +15 -6
- package/dist/decorators/inject.d.ts +1 -1
- package/dist/decorators/inject.js +6 -4
- package/dist/errors/diagnostics.d.ts +2 -0
- package/dist/errors/errors.d.ts +33 -1
- package/dist/errors/errors.js +44 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/injection/descriptor.d.ts +16 -8
- package/dist/injection/descriptor.js +3 -1
- package/dist/injection/resolve-options.d.ts +6 -0
- package/dist/injection/resolve-options.js +16 -0
- package/dist/introspection/dependency-graph.js +10 -5
- package/dist/introspection/inspector.d.ts +3 -1
- package/dist/introspection/inspector.js +10 -24
- package/dist/lifecycle/lifecycle-manager.js +10 -8
- package/dist/lifecycle/scope-manager.js +1 -1
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/resolution/cache/activation-need.d.ts +2 -0
- package/dist/resolution/cache/activation-need.js +13 -6
- package/dist/resolution/cache/binding-lookup-cache.d.ts +34 -1
- package/dist/resolution/cache/binding-lookup-cache.js +96 -12
- package/dist/resolution/cache/class-introspector.d.ts +1 -1
- package/dist/resolution/cache/class-introspector.js +28 -14
- package/dist/resolution/context.d.ts +8 -8
- package/dist/resolution/plan/instantiation-plan.d.ts +12 -0
- package/dist/resolution/plan/instantiation-plan.js +156 -65
- package/dist/resolution/plan/plan-codegen.d.ts +100 -0
- package/dist/resolution/plan/plan-codegen.js +185 -0
- package/dist/resolution/resolver.d.ts +14 -4
- package/dist/resolution/resolver.js +375 -134
- package/dist/resolution/select/binding-select.js +12 -7
- package/dist/resolution/select/constraints.d.ts +7 -4
- package/dist/resolution/select/constraints.js +34 -13
- package/package.json +14 -40
|
@@ -16,6 +16,8 @@ import type { ClassIntrospector } from "#/resolution/cache/class-introspector";
|
|
|
16
16
|
export declare class ActivationNeedCache {
|
|
17
17
|
#private;
|
|
18
18
|
constructor(lifecycle: LifecycleManager, classes: ClassIntrospector, registry: BindingRegistry);
|
|
19
|
+
/** Whether an answer the early returns could not give has had to allocate the memo. */
|
|
20
|
+
get isMemoBuilt(): boolean;
|
|
19
21
|
needsActivation<Value>(binding: Binding<Value>): boolean;
|
|
20
22
|
/**
|
|
21
23
|
* Settles a class binding's answer once its lifecycle metadata has actually been read, which
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* @since 0.5.0-canary.8
|
|
5
5
|
*/
|
|
6
6
|
export class ActivationNeedCache {
|
|
7
|
-
|
|
7
|
+
// Allocated by the first answer the early returns cannot give, so a hook-free container that
|
|
8
|
+
// resolves no class or alias never pays for it.
|
|
9
|
+
#needByBindingId;
|
|
8
10
|
#version = -1;
|
|
9
11
|
#lifecycle;
|
|
10
12
|
#classes;
|
|
@@ -14,10 +16,14 @@ export class ActivationNeedCache {
|
|
|
14
16
|
this.#classes = classes;
|
|
15
17
|
this.#registry = registry;
|
|
16
18
|
}
|
|
19
|
+
/** Whether an answer the early returns could not give has had to allocate the memo. */
|
|
20
|
+
get isMemoBuilt() {
|
|
21
|
+
return this.#needByBindingId !== undefined;
|
|
22
|
+
}
|
|
17
23
|
needsActivation(binding) {
|
|
18
24
|
// The chain writes a binding's own hook in place with no version anything here can see, so it
|
|
19
25
|
// is read fresh on every call; the memo covers only container hooks and lifecycle metadata.
|
|
20
|
-
if (binding.kind !== "alias" && binding.
|
|
26
|
+
if (binding.kind !== "alias" && binding.activationHook !== undefined) {
|
|
21
27
|
return true;
|
|
22
28
|
}
|
|
23
29
|
const lifecycleVersion = this.#lifecycle.activationVersion;
|
|
@@ -29,15 +35,16 @@ export class ActivationNeedCache {
|
|
|
29
35
|
// The registry version evicts entries for binding ids a rebind has retired.
|
|
30
36
|
const version = lifecycleVersion + this.#registry.version;
|
|
31
37
|
if (this.#version !== version) {
|
|
32
|
-
this.#needByBindingId
|
|
38
|
+
this.#needByBindingId?.clear();
|
|
33
39
|
this.#version = version;
|
|
34
40
|
}
|
|
35
|
-
const
|
|
41
|
+
const memo = (this.#needByBindingId ??= new Map());
|
|
42
|
+
const cached = memo.get(binding.identifier);
|
|
36
43
|
if (cached !== undefined) {
|
|
37
44
|
return cached;
|
|
38
45
|
}
|
|
39
46
|
const needsActivation = binding.kind === "class" ? this.#classNeedsActivation(binding) : this.#nonClassNeedsActivation(binding);
|
|
40
|
-
|
|
47
|
+
memo.set(binding.identifier, needsActivation);
|
|
41
48
|
return needsActivation;
|
|
42
49
|
}
|
|
43
50
|
/**
|
|
@@ -52,7 +59,7 @@ export class ActivationNeedCache {
|
|
|
52
59
|
return needsActivation;
|
|
53
60
|
}
|
|
54
61
|
this.#classes.discoverPostConstruct(binding.target);
|
|
55
|
-
this.#needByBindingId
|
|
62
|
+
this.#needByBindingId?.delete(binding.identifier);
|
|
56
63
|
return this.needsActivation(binding);
|
|
57
64
|
}
|
|
58
65
|
// Own hooks are answered before the memo, so both computations cover the memoizable rest only.
|
|
@@ -20,6 +20,20 @@ export interface DefaultLookupEntry<Owner> {
|
|
|
20
20
|
readonly binding: Binding;
|
|
21
21
|
readonly owner: Owner;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* A root-level collection read, memoized until any registry in the chain changes.
|
|
25
|
+
*
|
|
26
|
+
* @remarks `values` is kept only while every member is a hook-free constant or a hook-free singleton
|
|
27
|
+
* whose instance is cached, and `activationVersion` is the chain's activation version that promise was
|
|
28
|
+
* made under.
|
|
29
|
+
*
|
|
30
|
+
* @since 0.10.0
|
|
31
|
+
*/
|
|
32
|
+
export interface CollectionEntry {
|
|
33
|
+
readonly candidates: ReadonlyArray<Binding>;
|
|
34
|
+
values: ReadonlyArray<unknown> | undefined;
|
|
35
|
+
activationVersion: number;
|
|
36
|
+
}
|
|
23
37
|
/**
|
|
24
38
|
* Alias folding gives up past this many hops and defers to the full resolve loop, whose
|
|
25
39
|
* Set-based traversal detects genuine cycles exactly rather than by an arbitrary cap.
|
|
@@ -35,10 +49,29 @@ export declare const ALIAS_HOP_LIMIT = 32;
|
|
|
35
49
|
export declare class BindingLookupCache<Owner> {
|
|
36
50
|
#private;
|
|
37
51
|
constructor(registry: BindingRegistry, owner: Owner, parent: BindingLookupCache<Owner> | undefined);
|
|
38
|
-
/**
|
|
52
|
+
/** Whether a second distinct token or tag has had to allocate a memo map behind the one-entry fronts. */
|
|
53
|
+
get isMemoBuilt(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Summed registry versions of this cache's whole chain — the memo stamp.
|
|
56
|
+
*
|
|
57
|
+
* @remarks Re-summed only when the process-wide state epoch has moved since the last sum: no
|
|
58
|
+
* registry anywhere changed in between, so no registry in this chain did either.
|
|
59
|
+
*/
|
|
39
60
|
chainVersion(): number;
|
|
40
61
|
/** `null` when the token's shape needs the full selection path. */
|
|
41
62
|
defaultEntry(token: Token<unknown> | Constructor): DefaultLookupEntry<Owner> | null;
|
|
42
63
|
/** `null` when the tag's shape needs the full selection path. */
|
|
43
64
|
taggedEntry(token: Token<unknown> | Constructor, tag: BindingTag): DefaultLookupEntry<Owner> | null;
|
|
65
|
+
/**
|
|
66
|
+
* The memoized entry for a request carrying a name and one tag, or `null` when the answer is not this lane's.
|
|
67
|
+
*
|
|
68
|
+
* @remarks Same contract as the one-criterion memo: a predicate needs a live context and an alias
|
|
69
|
+
* carries options through the full path, so both decline; a registry that holds the token without the
|
|
70
|
+
* exact slot declines too, leaving the parent walk to the full lookup.
|
|
71
|
+
*/
|
|
72
|
+
namedTaggedEntry(token: Token<unknown> | Constructor, nameCriterion: BindingTag, tag: BindingTag): DefaultLookupEntry<Owner> | null;
|
|
73
|
+
/** The memoized root-level collection for a token, or `undefined` once the chain changed since it was stored. */
|
|
74
|
+
collection(token: Token<unknown> | Constructor): CollectionEntry | undefined;
|
|
75
|
+
/** Stores a root-level collection under the chain version the last `collection()` read stamped. */
|
|
76
|
+
rememberCollection(token: Token<unknown> | Constructor, entry: CollectionEntry): void;
|
|
44
77
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getOrInsertComputed } from "#/core/map-upsert";
|
|
2
|
+
import { stateEpoch } from "#/core/state-epoch";
|
|
2
3
|
/**
|
|
3
4
|
* Alias folding gives up past this many hops and defers to the full resolve loop, whose
|
|
4
5
|
* Set-based traversal detects genuine cycles exactly rather than by an arbitrary cap.
|
|
@@ -7,27 +8,37 @@ import { getOrInsertComputed } from "#/core/map-upsert";
|
|
|
7
8
|
*/
|
|
8
9
|
export const ALIAS_HOP_LIMIT = 32;
|
|
9
10
|
const newTagToEntryMap = () => new Map();
|
|
11
|
+
const newNameToTagMap = () => new Map();
|
|
10
12
|
/**
|
|
11
13
|
* A version-stamped cache of binding lookups by token and criterion across the container chain.
|
|
12
14
|
*
|
|
13
15
|
* @since 0.5.0-canary.9
|
|
14
16
|
*/
|
|
15
17
|
export class BindingLookupCache {
|
|
16
|
-
#byToken
|
|
18
|
+
#byToken;
|
|
17
19
|
#version = -1;
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
// The last chain sum and the epoch it was taken at.
|
|
21
|
+
#chainVersion = -1;
|
|
22
|
+
#chainEpoch = -1;
|
|
23
|
+
// One entry in front of the map, and the map is not written until a second distinct token appears
|
|
24
|
+
// in one generation: the two shapes that reach here — an alias, and a token owned by a parent —
|
|
25
|
+
// are both resolved in a loop over the same token. `null` is a real answer, so absence is tracked
|
|
26
|
+
// by the token slot rather than by the entry.
|
|
21
27
|
#lastToken;
|
|
22
28
|
#lastEntry = null;
|
|
23
|
-
#byTokenAndTag
|
|
29
|
+
#byTokenAndTag;
|
|
24
30
|
#taggedVersion = -1;
|
|
31
|
+
#byTokenNameAndTag;
|
|
32
|
+
#pairVersion = -1;
|
|
25
33
|
// One entry in front of the tag map, and the map is not written until a second distinct request
|
|
26
34
|
// shape appears: a per-request child usually asks one (token, tag) once, and the inner-map
|
|
27
35
|
// allocation was that shape's whole regression when this memo landed.
|
|
28
36
|
#lastTagToken;
|
|
29
37
|
#lastTag;
|
|
30
38
|
#lastTaggedEntry = null;
|
|
39
|
+
// Root-level collections by token, stamped with the chain version like the two memos above.
|
|
40
|
+
#collections;
|
|
41
|
+
#collectionsVersion = -1;
|
|
31
42
|
#registry;
|
|
32
43
|
#owner;
|
|
33
44
|
#parent;
|
|
@@ -36,29 +47,56 @@ export class BindingLookupCache {
|
|
|
36
47
|
this.#owner = owner;
|
|
37
48
|
this.#parent = parent;
|
|
38
49
|
}
|
|
39
|
-
/**
|
|
50
|
+
/** Whether a second distinct token or tag has had to allocate a memo map behind the one-entry fronts. */
|
|
51
|
+
get isMemoBuilt() {
|
|
52
|
+
return this.#byToken !== undefined || this.#byTokenAndTag !== undefined || this.#byTokenNameAndTag !== undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Summed registry versions of this cache's whole chain — the memo stamp.
|
|
56
|
+
*
|
|
57
|
+
* @remarks Re-summed only when the process-wide state epoch has moved since the last sum: no
|
|
58
|
+
* registry anywhere changed in between, so no registry in this chain did either.
|
|
59
|
+
*/
|
|
40
60
|
chainVersion() {
|
|
61
|
+
// A root's sum is its own version: one field read, cheaper than the memo it would stamp.
|
|
62
|
+
if (this.#parent === undefined) {
|
|
63
|
+
return this.#registry.version;
|
|
64
|
+
}
|
|
65
|
+
const epoch = stateEpoch();
|
|
66
|
+
if (epoch === this.#chainEpoch) {
|
|
67
|
+
return this.#chainVersion;
|
|
68
|
+
}
|
|
41
69
|
let version = this.#registry.version;
|
|
42
70
|
for (let cache = this.#parent; cache !== undefined; cache = cache.#parent) {
|
|
43
71
|
version += cache.#registry.version;
|
|
44
72
|
}
|
|
73
|
+
this.#chainEpoch = epoch;
|
|
74
|
+
this.#chainVersion = version;
|
|
45
75
|
return version;
|
|
46
76
|
}
|
|
47
77
|
/** `null` when the token's shape needs the full selection path. */
|
|
48
78
|
defaultEntry(token) {
|
|
49
79
|
const version = this.chainVersion();
|
|
50
80
|
if (version !== this.#version) {
|
|
51
|
-
this.#byToken
|
|
81
|
+
this.#byToken?.clear();
|
|
52
82
|
this.#version = version;
|
|
53
83
|
this.#lastToken = undefined;
|
|
54
84
|
}
|
|
55
85
|
else if (token === this.#lastToken) {
|
|
56
86
|
return this.#lastEntry;
|
|
57
87
|
}
|
|
58
|
-
let entry
|
|
59
|
-
if (
|
|
88
|
+
let entry;
|
|
89
|
+
if (this.#lastToken === undefined) {
|
|
90
|
+
// First token this cache generation sees: answer from the walk and defer the map entirely.
|
|
60
91
|
entry = this.#foldAliases(token);
|
|
61
|
-
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const byToken = (this.#byToken ??= new Map());
|
|
95
|
+
entry = byToken.get(token);
|
|
96
|
+
if (entry === undefined) {
|
|
97
|
+
entry = this.#foldAliases(token);
|
|
98
|
+
byToken.set(token, entry);
|
|
99
|
+
}
|
|
62
100
|
}
|
|
63
101
|
this.#lastToken = token;
|
|
64
102
|
this.#lastEntry = entry;
|
|
@@ -68,7 +106,7 @@ export class BindingLookupCache {
|
|
|
68
106
|
taggedEntry(token, tag) {
|
|
69
107
|
const version = this.chainVersion();
|
|
70
108
|
if (version !== this.#taggedVersion) {
|
|
71
|
-
this.#byTokenAndTag
|
|
109
|
+
this.#byTokenAndTag?.clear();
|
|
72
110
|
this.#taggedVersion = version;
|
|
73
111
|
this.#lastTagToken = undefined;
|
|
74
112
|
this.#lastTag = undefined;
|
|
@@ -84,7 +122,7 @@ export class BindingLookupCache {
|
|
|
84
122
|
else {
|
|
85
123
|
// Keyed by the criterion object itself: criteria are interned, so identity is the slot
|
|
86
124
|
// contract's own `Object.is` — the same exactness the registry's tagged index relies on.
|
|
87
|
-
const byTag = getOrInsertComputed(this.#byTokenAndTag, token, newTagToEntryMap);
|
|
125
|
+
const byTag = getOrInsertComputed((this.#byTokenAndTag ??= new Map()), token, newTagToEntryMap);
|
|
88
126
|
entry = byTag.get(tag);
|
|
89
127
|
if (entry === undefined) {
|
|
90
128
|
entry = this.#findTaggedInChain(token, tag);
|
|
@@ -96,6 +134,52 @@ export class BindingLookupCache {
|
|
|
96
134
|
this.#lastTaggedEntry = entry;
|
|
97
135
|
return entry;
|
|
98
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* The memoized entry for a request carrying a name and one tag, or `null` when the answer is not this lane's.
|
|
139
|
+
*
|
|
140
|
+
* @remarks Same contract as the one-criterion memo: a predicate needs a live context and an alias
|
|
141
|
+
* carries options through the full path, so both decline; a registry that holds the token without the
|
|
142
|
+
* exact slot declines too, leaving the parent walk to the full lookup.
|
|
143
|
+
*/
|
|
144
|
+
namedTaggedEntry(token, nameCriterion, tag) {
|
|
145
|
+
const version = this.chainVersion();
|
|
146
|
+
if (version !== this.#pairVersion) {
|
|
147
|
+
this.#byTokenNameAndTag?.clear();
|
|
148
|
+
this.#pairVersion = version;
|
|
149
|
+
}
|
|
150
|
+
const byName = getOrInsertComputed((this.#byTokenNameAndTag ??= new Map()), token, newNameToTagMap);
|
|
151
|
+
const byTag = getOrInsertComputed(byName, nameCriterion, newTagToEntryMap);
|
|
152
|
+
let entry = byTag.get(tag);
|
|
153
|
+
if (entry === undefined) {
|
|
154
|
+
entry = this.#findPairInChain(token, nameCriterion, tag);
|
|
155
|
+
byTag.set(tag, entry);
|
|
156
|
+
}
|
|
157
|
+
return entry;
|
|
158
|
+
}
|
|
159
|
+
#findPairInChain(token, nameCriterion, tag) {
|
|
160
|
+
const found = this.#registry.getPairTagged(token, nameCriterion, tag);
|
|
161
|
+
if (found !== undefined) {
|
|
162
|
+
return found.predicate !== undefined || found.kind === "alias" ? null : { binding: found, owner: this.#owner };
|
|
163
|
+
}
|
|
164
|
+
if (this.#registry.has(token)) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
return this.#parent === undefined ? null : this.#parent.#findPairInChain(token, nameCriterion, tag);
|
|
168
|
+
}
|
|
169
|
+
/** The memoized root-level collection for a token, or `undefined` once the chain changed since it was stored. */
|
|
170
|
+
collection(token) {
|
|
171
|
+
const version = this.chainVersion();
|
|
172
|
+
if (version !== this.#collectionsVersion) {
|
|
173
|
+
this.#collections?.clear();
|
|
174
|
+
this.#collectionsVersion = version;
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
return this.#collections?.get(token);
|
|
178
|
+
}
|
|
179
|
+
/** Stores a root-level collection under the chain version the last `collection()` read stamped. */
|
|
180
|
+
rememberCollection(token, entry) {
|
|
181
|
+
(this.#collections ??= new Map()).set(token, entry);
|
|
182
|
+
}
|
|
99
183
|
#foldAliases(token) {
|
|
100
184
|
let current = token;
|
|
101
185
|
for (let hop = 0; hop < ALIAS_HOP_LIMIT; hop += 1) {
|
|
@@ -52,7 +52,7 @@ export declare function verifyAccessorMetadata(reader: MetadataReader, target: C
|
|
|
52
52
|
*/
|
|
53
53
|
export declare class ClassIntrospector {
|
|
54
54
|
#private;
|
|
55
|
-
constructor(reader: MetadataReader, container: Container);
|
|
55
|
+
constructor(reader: MetadataReader, container: Container, inherited: ClassIntrospector | undefined);
|
|
56
56
|
constructorMetadata(target: Constructor): ConstructorMetadata | undefined;
|
|
57
57
|
/**
|
|
58
58
|
* Whether the class has a `@postConstruct` hook, or `undefined` until {@link discoverPostConstruct}.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @remarks Metadata cannot change once a class is defined, so nothing here needs version stamping.
|
|
5
5
|
*/
|
|
6
|
-
import { runWithAmbientResolution } from "#/ambient/active-container";
|
|
6
|
+
import { constructWithAmbientResolution, runWithAmbientResolution } from "#/ambient/active-container";
|
|
7
7
|
import { InvalidMetadataError } from "#/errors/errors";
|
|
8
8
|
// Verified pairs, not verified classes: two readers may disagree about the same class, and a reader
|
|
9
9
|
// that goes out of scope takes its record with it.
|
|
@@ -138,30 +138,40 @@ export function verifyAccessorMetadata(reader, target) {
|
|
|
138
138
|
markVerified(verifiedAccessorTargets, reader, target);
|
|
139
139
|
return metadata;
|
|
140
140
|
}
|
|
141
|
+
const cachesByReader = new WeakMap();
|
|
142
|
+
function cachesFor(reader) {
|
|
143
|
+
let caches = cachesByReader.get(reader);
|
|
144
|
+
if (caches === undefined) {
|
|
145
|
+
caches = { constructorMetadata: undefined, hasPostConstruct: undefined, needsActiveContainer: undefined };
|
|
146
|
+
cachesByReader.set(reader, caches);
|
|
147
|
+
}
|
|
148
|
+
return caches;
|
|
149
|
+
}
|
|
141
150
|
/**
|
|
142
151
|
* A per-class cache of constructor metadata and the activation facts derived from it.
|
|
143
152
|
*
|
|
144
153
|
* @since 0.5.0-canary.8
|
|
145
154
|
*/
|
|
146
155
|
export class ClassIntrospector {
|
|
147
|
-
|
|
148
|
-
// to constants, factories or aliases never introspects one.
|
|
149
|
-
#constructorMetadata;
|
|
150
|
-
#hasPostConstruct;
|
|
151
|
-
#needsActiveContainer;
|
|
156
|
+
#caches;
|
|
152
157
|
#reader;
|
|
153
158
|
#container;
|
|
154
|
-
constructor(reader, container) {
|
|
159
|
+
constructor(reader, container, inherited) {
|
|
160
|
+
this.#caches = inherited !== undefined && inherited.#reader === reader ? inherited.#caches : undefined;
|
|
155
161
|
this.#reader = reader;
|
|
156
162
|
this.#container = container;
|
|
157
163
|
}
|
|
164
|
+
#shared() {
|
|
165
|
+
return (this.#caches ??= cachesFor(this.#reader));
|
|
166
|
+
}
|
|
158
167
|
constructorMetadata(target) {
|
|
159
|
-
const
|
|
168
|
+
const caches = this.#shared();
|
|
169
|
+
const cached = caches.constructorMetadata?.get(target);
|
|
160
170
|
if (cached !== undefined) {
|
|
161
171
|
return cached === null ? undefined : cached;
|
|
162
172
|
}
|
|
163
173
|
const metadata = this.#reader.getConstructorMetadata(target);
|
|
164
|
-
(
|
|
174
|
+
(caches.constructorMetadata ??= new WeakMap()).set(target, metadata ?? null);
|
|
165
175
|
return metadata;
|
|
166
176
|
}
|
|
167
177
|
/**
|
|
@@ -170,18 +180,19 @@ export class ClassIntrospector {
|
|
|
170
180
|
* @remarks Callers treat unknown as "assume it does", so the first activation settles it.
|
|
171
181
|
*/
|
|
172
182
|
knownPostConstruct(target) {
|
|
173
|
-
return this.#hasPostConstruct?.get(target);
|
|
183
|
+
return this.#shared().hasPostConstruct?.get(target);
|
|
174
184
|
}
|
|
175
185
|
discoverPostConstruct(target) {
|
|
176
186
|
const lifecycle = this.#reader.getLifecycleMetadata(target);
|
|
177
|
-
(this.#hasPostConstruct ??= new WeakMap()).set(target, lifecycle !== undefined && lifecycle.postConstruct !== undefined && lifecycle.postConstruct.length > 0);
|
|
187
|
+
(this.#shared().hasPostConstruct ??= new WeakMap()).set(target, lifecycle !== undefined && lifecycle.postConstruct !== undefined && lifecycle.postConstruct.length > 0);
|
|
178
188
|
}
|
|
179
189
|
/** True when the class has accessor injection, which reads the container during construction. */
|
|
180
190
|
needsActiveContainer(target) {
|
|
181
|
-
|
|
191
|
+
const caches = this.#shared();
|
|
192
|
+
let needsActiveContainer = caches.needsActiveContainer?.get(target);
|
|
182
193
|
if (needsActiveContainer === undefined) {
|
|
183
194
|
needsActiveContainer = (this.#reader.getAccessorMetadata?.(target)?.length ?? 0) > 0;
|
|
184
|
-
(
|
|
195
|
+
(caches.needsActiveContainer ??= new WeakMap()).set(target, needsActiveContainer);
|
|
185
196
|
}
|
|
186
197
|
return needsActiveContainer;
|
|
187
198
|
}
|
|
@@ -190,6 +201,9 @@ export class ClassIntrospector {
|
|
|
190
201
|
if (!this.needsActiveContainer(target)) {
|
|
191
202
|
return new invokable(...deps);
|
|
192
203
|
}
|
|
193
|
-
|
|
204
|
+
if (resolution === undefined) {
|
|
205
|
+
return runWithAmbientResolution(this.#container, undefined, () => new invokable(...deps));
|
|
206
|
+
}
|
|
207
|
+
return constructWithAmbientResolution(this.#container, resolution, invokable, deps);
|
|
194
208
|
}
|
|
195
209
|
}
|
|
@@ -16,8 +16,8 @@ export interface ResolverCallbacks {
|
|
|
16
16
|
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Promise<Value>;
|
|
17
17
|
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Value | undefined;
|
|
18
18
|
resolveOptionalAsync<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Promise<Value | undefined>;
|
|
19
|
-
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>):
|
|
20
|
-
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Promise<
|
|
19
|
+
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): ReadonlyArray<Value>;
|
|
20
|
+
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Promise<ReadonlyArray<Value>>;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* The `ResolutionContext` handed to factories and hooks, backed by the engine's callbacks.
|
|
@@ -33,8 +33,8 @@ export declare class DefaultResolutionContext implements ResolutionContext {
|
|
|
33
33
|
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value>;
|
|
34
34
|
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
|
|
35
35
|
resolveOptionalAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value | undefined>;
|
|
36
|
-
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions):
|
|
37
|
-
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<
|
|
36
|
+
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): ReadonlyArray<Value>;
|
|
37
|
+
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<ReadonlyArray<Value>>;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* One async level's resolution context, which is also its branch of the resolution stack.
|
|
@@ -59,8 +59,8 @@ export declare class AsyncLevelContext implements ResolutionContext {
|
|
|
59
59
|
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value>;
|
|
60
60
|
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
|
|
61
61
|
resolveOptionalAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value | undefined>;
|
|
62
|
-
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions):
|
|
63
|
-
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<
|
|
62
|
+
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): ReadonlyArray<Value>;
|
|
63
|
+
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<ReadonlyArray<Value>>;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* The one context every level of an open synchronous factory cascade shares.
|
|
@@ -78,8 +78,8 @@ export declare class AsyncCascadeContext implements ResolutionContext {
|
|
|
78
78
|
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value>;
|
|
79
79
|
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
|
|
80
80
|
resolveOptionalAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value | undefined>;
|
|
81
|
-
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions):
|
|
82
|
-
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<
|
|
81
|
+
resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): ReadonlyArray<Value>;
|
|
82
|
+
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<ReadonlyArray<Value>>;
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
85
|
* Creates the resolution-stack frame for one in-flight resolve.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* closure must stay callable for it, which is what bounds what may be inlined.
|
|
6
6
|
*/
|
|
7
7
|
import type { Binding } from "#/core/binding";
|
|
8
|
+
import type { ConstructorInvocation } from "#/core/constructor-type";
|
|
8
9
|
import type { Token } from "#/core/token";
|
|
9
10
|
import type { Constructor, ResolutionFrame, ResolveOptions } from "#/core/types";
|
|
10
11
|
import type { ConstructorMetadata } from "#/metadata/metadata-types";
|
|
@@ -41,6 +42,11 @@ export interface InstantiationPlanHost {
|
|
|
41
42
|
/** Cached postConstruct presence — `undefined` until a runtime resolve discovers it. */
|
|
42
43
|
knownPostConstruct(target: Constructor): boolean | undefined;
|
|
43
44
|
needsActiveContainer(target: Constructor): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Constructs a class whose accessors resolve during construction, with the container ambient and
|
|
47
|
+
* the binding's frame on the path so an accessor that cycles back is still caught.
|
|
48
|
+
*/
|
|
49
|
+
constructWithAccessors(binding: Binding, target: ConstructorInvocation, deps: Array<unknown>): unknown;
|
|
44
50
|
getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
|
|
45
51
|
/** Options-less lookup with alias hops folded; `null` when the fast lane can't answer. */
|
|
46
52
|
lookupDependencyEntry(token: Token<unknown> | Constructor): InstantiationPlanDependencyEntry | null;
|
|
@@ -54,6 +60,10 @@ export interface InstantiationPlanHost {
|
|
|
54
60
|
lookupPathIndependentEntry(token: Token<unknown> | Constructor, options: ResolveOptions): InstantiationPlanDependencyEntry | null;
|
|
55
61
|
/** The frame the interpreted path pushes for this binding, so escapes can replay it. */
|
|
56
62
|
getResolutionFrame(binding: Binding): ResolutionFrame;
|
|
63
|
+
/** Swaps a plan the host still holds for its successor; a plan the host has since dropped stays dropped. */
|
|
64
|
+
replacePlan(binding: Binding, current: () => unknown, next: () => unknown): void;
|
|
65
|
+
/** The async counterpart of {@link InstantiationPlanHost.replacePlan}, over the async plan map. */
|
|
66
|
+
replaceAsyncPlan(binding: Binding, current: () => unknown, next: () => unknown): void;
|
|
57
67
|
/** Runtime resolve for an escaped dependency, seeded with the ancestor frames above it. */
|
|
58
68
|
resolveEscaped(token: Token<unknown> | Constructor, options: ResolveOptions | undefined, arity: EscapeArity, resolutionStack: Array<ResolutionFrame>): unknown;
|
|
59
69
|
/** The async counterpart of {@link InstantiationPlanHost.resolveEscaped}, replaying the async dispatch. */
|
|
@@ -74,6 +84,8 @@ export type EscapeArity = "all" | "optional" | "single";
|
|
|
74
84
|
export declare class InstantiationPlanCompiler {
|
|
75
85
|
#private;
|
|
76
86
|
constructor(host: InstantiationPlanHost);
|
|
87
|
+
/** Plans this compiler has generated as functions of their own. */
|
|
88
|
+
get generatedPlanCount(): number;
|
|
77
89
|
compile(binding: Binding & {
|
|
78
90
|
kind: "class" | "resolved";
|
|
79
91
|
}): InstantiationPlanCompileResult;
|