@codefast/di 0.11.0 → 0.13.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 +151 -0
- package/README.md +128 -31
- package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +21 -28
- package/dist/container/{binding-builders.js → binding-chain.js} +53 -163
- package/dist/container/container.d.ts +11 -3
- package/dist/container/container.js +176 -102
- package/dist/core/binding-builders.d.ts +117 -0
- package/dist/core/binding-declaration.d.ts +9 -2
- package/dist/core/binding-declaration.js +5 -4
- package/dist/core/binding.d.ts +21 -149
- package/dist/core/binding.js +15 -25
- package/dist/core/module.d.ts +1 -1
- package/dist/core/registry.d.ts +9 -24
- package/dist/core/registry.js +30 -155
- package/dist/core/tag.js +1 -1
- package/dist/decorators/inject.d.ts +3 -1
- package/dist/decorators/inject.js +5 -5
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/lifecycle.d.ts +20 -0
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +7 -3
- package/dist/decorators/{decorator-metadata.js → metadata-record.js} +1 -1
- package/dist/{errors/errors.d.ts → errors.d.ts} +29 -19
- package/dist/{errors/errors.js → errors.js} +43 -25
- package/dist/index.d.ts +12 -10
- package/dist/index.js +6 -6
- package/dist/injection/descriptor.d.ts +1 -1
- package/dist/introspection/dependency-graph.d.ts +1 -1
- package/dist/introspection/dependency-graph.js +3 -4
- package/dist/introspection/explanation.d.ts +101 -0
- package/dist/introspection/explanation.js +169 -0
- package/dist/introspection/inspector.d.ts +9 -2
- package/dist/introspection/inspector.js +25 -20
- package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +11 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +51 -28
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +23 -6
- package/dist/lifecycle/{scope-manager.js → scopes.js} +93 -20
- package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +1 -1
- package/dist/metadata/{metadata-keys.js → keys.js} +1 -1
- package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
- package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
- package/dist/metadata/types.js +1 -0
- package/dist/metadata/verify.d.ts +43 -0
- package/dist/metadata/verify.js +133 -0
- package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +1 -1
- package/dist/metadata/{verifying-metadata-reader.js → verifying-reader.js} +2 -2
- package/dist/resolution/cache/activation-need.d.ts +1 -1
- package/dist/resolution/cache/activation-need.js +5 -1
- package/dist/resolution/cache/class-introspector.d.ts +27 -36
- package/dist/resolution/cache/class-introspector.js +61 -159
- package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -2
- package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +40 -8
- package/dist/resolution/context.d.ts +1 -1
- package/dist/resolution/context.js +1 -1
- package/dist/resolution/{path/resolution-path.d.ts → path.d.ts} +12 -2
- package/dist/resolution/{path/resolution-path.js → path.js} +23 -3
- package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +2 -4
- package/dist/resolution/plan/{plan-codegen.js → codegen.js} +2 -4
- package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
- package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +4 -4
- package/dist/resolution/resolver.d.ts +19 -7
- package/dist/resolution/resolver.js +343 -125
- package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
- package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
- package/dist/resolution/select/constraints.js +1 -1
- package/package.json +80 -72
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- package/dist/decorators/lifecycle-decorators.d.ts +0 -14
- /package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +0 -0
- /package/dist/{ambient/active-container.js → ambient-container.js} +0 -0
- /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
- /package/dist/decorators/{decorator-metadata.d.ts → metadata-record.d.ts} +0 -0
- /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
- /package/dist/injection/{resolve-options.js → dependency-slot.js} +0 -0
- /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
- /package/dist/{errors → introspection}/diagnostics.js +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
|
@@ -48,7 +48,11 @@ export class ActivationNeedCache {
|
|
|
48
48
|
* conservative `true`.
|
|
49
49
|
*/
|
|
50
50
|
refreshAfterFirstInstantiation(binding, needsActivation) {
|
|
51
|
-
|
|
51
|
+
// A class binding answers `false` only once its lifecycle metadata is known, so only a conservative
|
|
52
|
+
// `true` can still settle, and a `false` needs no second look at the class.
|
|
53
|
+
if (!needsActivation ||
|
|
54
|
+
binding.kind !== "class" ||
|
|
55
|
+
this.#classes.knownPostConstruct(binding.target) !== undefined) {
|
|
52
56
|
return needsActivation;
|
|
53
57
|
}
|
|
54
58
|
this.#classes.discoverPostConstruct(binding.target);
|
|
@@ -3,57 +3,44 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @remarks Metadata cannot change once a class is defined, so nothing here needs version stamping.
|
|
5
5
|
*/
|
|
6
|
-
import type { AmbientResolution } from "#ambient
|
|
6
|
+
import type { AmbientResolution } from "#ambient-container";
|
|
7
7
|
import type { Container } from "#container/container";
|
|
8
8
|
import type { Constructor } from "#core/types";
|
|
9
|
-
import type {
|
|
10
|
-
import type { ConstructorMetadata, LifecycleMetadata, MetadataReader } from "#metadata/metadata-types";
|
|
9
|
+
import type { ConstructorMetadata, MetadataReader, ParamMetadata } from "#metadata/types";
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* What one reader has answered about one class so far, each field unknown until first asked.
|
|
13
12
|
*
|
|
14
|
-
* @remarks
|
|
15
|
-
*
|
|
16
|
-
* that repeatedly.
|
|
13
|
+
* @remarks One record per class, so a cold resolve looks the class up once and hands the record down
|
|
14
|
+
* rather than asking a map per fact.
|
|
17
15
|
*
|
|
18
|
-
* @since 0.
|
|
16
|
+
* @since 0.12.0
|
|
19
17
|
*/
|
|
20
|
-
export
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
* @since 0.6.0
|
|
31
|
-
*/
|
|
32
|
-
export declare function assertConstructorMetadata(metadata: unknown, target: Constructor): ConstructorMetadata | undefined;
|
|
33
|
-
/**
|
|
34
|
-
* A reader's lifecycle metadata for a class, verified the first time this process asks.
|
|
35
|
-
*
|
|
36
|
-
* @since 0.6.0
|
|
37
|
-
*/
|
|
38
|
-
export declare function verifyLifecycleMetadata(reader: MetadataReader, target: Constructor): LifecycleMetadata | undefined;
|
|
39
|
-
/**
|
|
40
|
-
* A reader's accessor metadata for a class, verified the first time this process asks.
|
|
41
|
-
*
|
|
42
|
-
* @since 0.6.0
|
|
43
|
-
*/
|
|
44
|
-
export declare function verifyAccessorMetadata(reader: MetadataReader, target: Constructor): ReadonlyArray<{
|
|
45
|
-
readonly key: string | symbol;
|
|
46
|
-
readonly descriptor: InjectionDescriptor;
|
|
47
|
-
}> | undefined;
|
|
18
|
+
export interface ClassFacts {
|
|
19
|
+
/** The reader's constructor metadata, `null` once it answered that it knows nothing about the class. */
|
|
20
|
+
constructorMetadata: ConstructorMetadata | null | undefined;
|
|
21
|
+
/** Unknown until the first instantiation reads lifecycle metadata; callers read unknown as "it does". */
|
|
22
|
+
hasPostConstruct: boolean | undefined;
|
|
23
|
+
needsActiveContainer: boolean | undefined;
|
|
24
|
+
/** The parameters an instantiation injects, settled by the first one that succeeds. */
|
|
25
|
+
params: ReadonlyArray<ParamMetadata> | undefined;
|
|
26
|
+
}
|
|
48
27
|
/**
|
|
49
28
|
* A per-class cache of constructor metadata and the activation facts derived from it.
|
|
50
29
|
*
|
|
30
|
+
* @remarks A child inheriting its parent's reader takes the parent's facts too, and resolves a class
|
|
31
|
+
* the parent already met without reading its metadata again; a root finds them by reader on its first
|
|
32
|
+
* question.
|
|
33
|
+
*
|
|
51
34
|
* @since 0.5.0-canary.8
|
|
52
35
|
*/
|
|
53
36
|
export declare class ClassIntrospector {
|
|
54
37
|
#private;
|
|
55
38
|
constructor(reader: MetadataReader, container: Container, inherited: ClassIntrospector | undefined);
|
|
39
|
+
/** The record of what this reader has answered about a class, allocated on the class's first question. */
|
|
40
|
+
facts(target: Constructor): ClassFacts;
|
|
56
41
|
constructorMetadata(target: Constructor): ConstructorMetadata | undefined;
|
|
42
|
+
/** The constructor metadata of a class whose facts the caller already holds. */
|
|
43
|
+
constructorMetadataOf(target: Constructor, facts: ClassFacts): ConstructorMetadata | undefined;
|
|
57
44
|
/**
|
|
58
45
|
* The nearest ancestor's own constructor metadata, for a subclass that declares none of its own.
|
|
59
46
|
*
|
|
@@ -74,5 +61,9 @@ export declare class ClassIntrospector {
|
|
|
74
61
|
discoverPostConstruct(target: Constructor): void;
|
|
75
62
|
/** True when the class has accessor injection, which reads the container during construction. */
|
|
76
63
|
needsActiveContainer(target: Constructor): boolean;
|
|
64
|
+
/** Whether a class whose facts the caller already holds reads the container during construction. */
|
|
65
|
+
needsActiveContainerOf(target: Constructor, facts: ClassFacts): boolean;
|
|
77
66
|
instantiate(target: Constructor, deps: Array<unknown>, resolution?: AmbientResolution): unknown;
|
|
67
|
+
/** Builds an instance whose accessor need the caller already knows; an empty `deps` is a call with no arguments. */
|
|
68
|
+
construct(target: Constructor, deps: ReadonlyArray<unknown>, needsActiveContainer: boolean, resolution: AmbientResolution | undefined): unknown;
|
|
78
69
|
}
|
|
@@ -3,175 +3,69 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @remarks Metadata cannot change once a class is defined, so nothing here needs version stamping.
|
|
5
5
|
*/
|
|
6
|
-
import { constructWithAmbientResolution, runWithAmbientResolution } from "#ambient
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return cache.get(reader)?.has(target) ?? false;
|
|
15
|
-
}
|
|
16
|
-
function markVerified(cache, reader, target) {
|
|
17
|
-
let verified = cache.get(reader);
|
|
18
|
-
if (verified === undefined) {
|
|
19
|
-
verified = new WeakSet();
|
|
20
|
-
cache.set(reader, verified);
|
|
21
|
-
}
|
|
22
|
-
verified.add(target);
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Runs `validate` against a reader's metadata the first time this process sees the pair, then remembers it.
|
|
26
|
-
*
|
|
27
|
-
* @remarks An absent metadata answer needs no check; a verified pair is returned untouched, so the
|
|
28
|
-
* shape assertion runs once per `(reader, target)` rather than per container.
|
|
29
|
-
*/
|
|
30
|
-
function verifyOnce(cache, reader, target, metadata, validate) {
|
|
31
|
-
if (metadata === undefined || isVerified(cache, reader, target)) {
|
|
32
|
-
return metadata;
|
|
33
|
-
}
|
|
34
|
-
validate(metadata);
|
|
35
|
-
markVerified(cache, reader, target);
|
|
36
|
-
return metadata;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* A reader's constructor metadata for a class, verified the first time this process asks.
|
|
40
|
-
*
|
|
41
|
-
* @remarks Metadata cannot change once a class is defined, so re-checking per container would charge
|
|
42
|
-
* every fresh container for a fact already established — a per-request child or a cold boot pays
|
|
43
|
-
* that repeatedly.
|
|
44
|
-
*
|
|
45
|
-
* @since 0.6.0
|
|
46
|
-
*/
|
|
47
|
-
export function verifyConstructorMetadata(reader, target) {
|
|
48
|
-
return verifyOnce(verifiedTargets, reader, target, reader.getConstructorMetadata(target), (metadata) => {
|
|
49
|
-
assertConstructorMetadata(metadata, target);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Verifies what a reader claims about a class, since a `MetadataReader` is a public seam.
|
|
54
|
-
*
|
|
55
|
-
* @remarks Every path that reads constructor metadata comes through here, so a reader that answers
|
|
56
|
-
* wrongly is a named error at the class it described rather than a `TypeError` raised later inside a
|
|
57
|
-
* resolve. Only what a consumer dereferences is checked: `params` and each entry's `token`.
|
|
58
|
-
* `optional`/`multi` degrade to falsy without crashing, and `index` is decorative — dependencies are
|
|
59
|
-
* consumed positionally.
|
|
60
|
-
*
|
|
61
|
-
* @since 0.6.0
|
|
62
|
-
*/
|
|
63
|
-
export function assertConstructorMetadata(metadata, target) {
|
|
64
|
-
if (metadata === undefined) {
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
if (typeof metadata !== "object" || metadata === null) {
|
|
68
|
-
throw new InvalidMetadataError(target.name, `constructor metadata: expected an object, received ${typeof metadata}`);
|
|
69
|
-
}
|
|
70
|
-
const params = Reflect.get(metadata, "params");
|
|
71
|
-
if (!Array.isArray(params)) {
|
|
72
|
-
throw new InvalidMetadataError(target.name, "constructor metadata: params is not an array");
|
|
73
|
-
}
|
|
74
|
-
for (const [position, param] of params.entries()) {
|
|
75
|
-
if (typeof param !== "object" || param === null) {
|
|
76
|
-
throw new InvalidMetadataError(target.name, `constructor metadata: params[${String(position)}] is not an object`);
|
|
77
|
-
}
|
|
78
|
-
const dependency = Reflect.get(param, "token");
|
|
79
|
-
if (typeof dependency !== "object" && typeof dependency !== "function") {
|
|
80
|
-
throw new InvalidMetadataError(target.name, `constructor metadata: params[${String(position)}].token is not a token or a class`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return metadata;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* A reader's lifecycle metadata for a class, verified the first time this process asks.
|
|
87
|
-
*
|
|
88
|
-
* @since 0.6.0
|
|
89
|
-
*/
|
|
90
|
-
export function verifyLifecycleMetadata(reader, target) {
|
|
91
|
-
return verifyOnce(verifiedLifecycleTargets, reader, target, reader.getLifecycleMetadata(target), (metadata) => {
|
|
92
|
-
if (typeof metadata !== "object" || metadata === null) {
|
|
93
|
-
throw new InvalidMetadataError(target.name, `lifecycle metadata: expected an object, received ${typeof metadata}`);
|
|
94
|
-
}
|
|
95
|
-
for (const phase of ["postConstruct", "preDestroy"]) {
|
|
96
|
-
const methods = Reflect.get(metadata, phase);
|
|
97
|
-
if (methods === undefined) {
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (!Array.isArray(methods)) {
|
|
101
|
-
throw new InvalidMetadataError(target.name, `lifecycle metadata: ${phase} is not an array`);
|
|
102
|
-
}
|
|
103
|
-
for (const [position, name] of methods.entries()) {
|
|
104
|
-
if (typeof name !== "string") {
|
|
105
|
-
throw new InvalidMetadataError(target.name, `lifecycle metadata: ${phase}[${String(position)}] is not a string`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* A reader's accessor metadata for a class, verified the first time this process asks.
|
|
113
|
-
*
|
|
114
|
-
* @since 0.6.0
|
|
115
|
-
*/
|
|
116
|
-
export function verifyAccessorMetadata(reader, target) {
|
|
117
|
-
return verifyOnce(verifiedAccessorTargets, reader, target, reader.getAccessorMetadata?.(target), (metadata) => {
|
|
118
|
-
if (!Array.isArray(metadata)) {
|
|
119
|
-
throw new InvalidMetadataError(target.name, "accessor metadata: expected an array");
|
|
120
|
-
}
|
|
121
|
-
for (const [position, entry] of metadata.entries()) {
|
|
122
|
-
if (typeof entry !== "object" || entry === null) {
|
|
123
|
-
throw new InvalidMetadataError(target.name, `accessor metadata: [${String(position)}] is not an object`);
|
|
124
|
-
}
|
|
125
|
-
const key = Reflect.get(entry, "key");
|
|
126
|
-
if (typeof key !== "string" && typeof key !== "symbol") {
|
|
127
|
-
throw new InvalidMetadataError(target.name, `accessor metadata: [${String(position)}].key is not a string or symbol`);
|
|
128
|
-
}
|
|
129
|
-
const descriptor = Reflect.get(entry, "descriptor");
|
|
130
|
-
if (typeof descriptor !== "object" || descriptor === null) {
|
|
131
|
-
throw new InvalidMetadataError(target.name, `accessor metadata: [${String(position)}].descriptor is not an object`);
|
|
132
|
-
}
|
|
133
|
-
const dependency = Reflect.get(descriptor, "token");
|
|
134
|
-
if (typeof dependency !== "object" && typeof dependency !== "function") {
|
|
135
|
-
throw new InvalidMetadataError(target.name, `accessor metadata: [${String(position)}].descriptor.token is not a token or a class`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
const cachesByReader = new WeakMap();
|
|
141
|
-
function cachesFor(reader) {
|
|
142
|
-
let caches = cachesByReader.get(reader);
|
|
143
|
-
if (caches === undefined) {
|
|
144
|
-
caches = { constructorMetadata: undefined, hasPostConstruct: undefined, needsActiveContainer: undefined };
|
|
145
|
-
cachesByReader.set(reader, caches);
|
|
6
|
+
import { constructWithAmbientResolution, runWithAmbientResolution } from "#ambient-container";
|
|
7
|
+
// A reader answers from the class alone, so its facts are shared by every container reading through it.
|
|
8
|
+
const factsByReader = new WeakMap();
|
|
9
|
+
function factsFor(reader) {
|
|
10
|
+
let byClass = factsByReader.get(reader);
|
|
11
|
+
if (byClass === undefined) {
|
|
12
|
+
byClass = new WeakMap();
|
|
13
|
+
factsByReader.set(reader, byClass);
|
|
146
14
|
}
|
|
147
|
-
return
|
|
15
|
+
return byClass;
|
|
148
16
|
}
|
|
149
17
|
/**
|
|
150
18
|
* A per-class cache of constructor metadata and the activation facts derived from it.
|
|
151
19
|
*
|
|
20
|
+
* @remarks A child inheriting its parent's reader takes the parent's facts too, and resolves a class
|
|
21
|
+
* the parent already met without reading its metadata again; a root finds them by reader on its first
|
|
22
|
+
* question.
|
|
23
|
+
*
|
|
152
24
|
* @since 0.5.0-canary.8
|
|
153
25
|
*/
|
|
154
26
|
export class ClassIntrospector {
|
|
155
|
-
#
|
|
27
|
+
#byClass;
|
|
28
|
+
// The class asked about last and its record: a cold resolve asks about one class several times in a row.
|
|
29
|
+
#lastTarget;
|
|
30
|
+
#lastFacts;
|
|
156
31
|
#reader;
|
|
157
32
|
#container;
|
|
158
33
|
constructor(reader, container, inherited) {
|
|
159
|
-
this.#
|
|
34
|
+
this.#byClass = inherited !== undefined && inherited.#reader === reader ? inherited.#byClass : undefined;
|
|
160
35
|
this.#reader = reader;
|
|
161
36
|
this.#container = container;
|
|
162
37
|
}
|
|
163
|
-
|
|
164
|
-
|
|
38
|
+
/** The record of what this reader has answered about a class, allocated on the class's first question. */
|
|
39
|
+
facts(target) {
|
|
40
|
+
if (target === this.#lastTarget) {
|
|
41
|
+
return this.#lastFacts;
|
|
42
|
+
}
|
|
43
|
+
const byClass = (this.#byClass ??= factsFor(this.#reader));
|
|
44
|
+
let facts = byClass.get(target);
|
|
45
|
+
if (facts === undefined) {
|
|
46
|
+
facts = {
|
|
47
|
+
constructorMetadata: undefined,
|
|
48
|
+
hasPostConstruct: undefined,
|
|
49
|
+
needsActiveContainer: undefined,
|
|
50
|
+
params: undefined,
|
|
51
|
+
};
|
|
52
|
+
byClass.set(target, facts);
|
|
53
|
+
}
|
|
54
|
+
this.#lastTarget = target;
|
|
55
|
+
this.#lastFacts = facts;
|
|
56
|
+
return facts;
|
|
165
57
|
}
|
|
166
58
|
constructorMetadata(target) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
59
|
+
return this.constructorMetadataOf(target, this.facts(target));
|
|
60
|
+
}
|
|
61
|
+
/** The constructor metadata of a class whose facts the caller already holds. */
|
|
62
|
+
constructorMetadataOf(target, facts) {
|
|
63
|
+
let metadata = facts.constructorMetadata;
|
|
64
|
+
if (metadata === undefined) {
|
|
65
|
+
metadata = this.#reader.getConstructorMetadata(target) ?? null;
|
|
66
|
+
facts.constructorMetadata = metadata;
|
|
171
67
|
}
|
|
172
|
-
|
|
173
|
-
(caches.constructorMetadata ??= new WeakMap()).set(target, metadata ?? null);
|
|
174
|
-
return metadata;
|
|
68
|
+
return metadata === null ? undefined : metadata;
|
|
175
69
|
}
|
|
176
70
|
/**
|
|
177
71
|
* The nearest ancestor's own constructor metadata, for a subclass that declares none of its own.
|
|
@@ -197,26 +91,34 @@ export class ClassIntrospector {
|
|
|
197
91
|
* @remarks Callers treat unknown as "assume it does", so the first activation settles it.
|
|
198
92
|
*/
|
|
199
93
|
knownPostConstruct(target) {
|
|
200
|
-
return this
|
|
94
|
+
return this.facts(target).hasPostConstruct;
|
|
201
95
|
}
|
|
202
96
|
discoverPostConstruct(target) {
|
|
203
97
|
const lifecycle = this.#reader.getLifecycleMetadata(target);
|
|
204
|
-
|
|
98
|
+
this.facts(target).hasPostConstruct =
|
|
99
|
+
lifecycle !== undefined && lifecycle.postConstruct !== undefined && lifecycle.postConstruct.length > 0;
|
|
205
100
|
}
|
|
206
101
|
/** True when the class has accessor injection, which reads the container during construction. */
|
|
207
102
|
needsActiveContainer(target) {
|
|
208
|
-
|
|
209
|
-
|
|
103
|
+
return this.needsActiveContainerOf(target, this.facts(target));
|
|
104
|
+
}
|
|
105
|
+
/** Whether a class whose facts the caller already holds reads the container during construction. */
|
|
106
|
+
needsActiveContainerOf(target, facts) {
|
|
107
|
+
let needsActiveContainer = facts.needsActiveContainer;
|
|
210
108
|
if (needsActiveContainer === undefined) {
|
|
211
109
|
needsActiveContainer = (this.#reader.getAccessorMetadata?.(target)?.length ?? 0) > 0;
|
|
212
|
-
|
|
110
|
+
facts.needsActiveContainer = needsActiveContainer;
|
|
213
111
|
}
|
|
214
112
|
return needsActiveContainer;
|
|
215
113
|
}
|
|
216
114
|
instantiate(target, deps, resolution) {
|
|
115
|
+
return this.construct(target, deps, this.needsActiveContainer(target), resolution);
|
|
116
|
+
}
|
|
117
|
+
/** Builds an instance whose accessor need the caller already knows; an empty `deps` is a call with no arguments. */
|
|
118
|
+
construct(target, deps, needsActiveContainer, resolution) {
|
|
217
119
|
const invokable = target;
|
|
218
|
-
if (!
|
|
219
|
-
return new invokable(...deps);
|
|
120
|
+
if (!needsActiveContainer) {
|
|
121
|
+
return deps.length === 0 ? new invokable() : new invokable(...deps);
|
|
220
122
|
}
|
|
221
123
|
if (resolution === undefined) {
|
|
222
124
|
return runWithAmbientResolution(this.#container, undefined, () => new invokable(...deps));
|
|
@@ -25,7 +25,8 @@ export interface DefaultLookupEntry<Owner> {
|
|
|
25
25
|
*
|
|
26
26
|
* @remarks `values` is kept only while every member is a hook-free constant or a hook-free singleton
|
|
27
27
|
* whose instance is cached, and `activationVersion` is the chain's activation version that promise was
|
|
28
|
-
* made under.
|
|
28
|
+
* made under. It is settled on the read that repeats the first, which `readBefore` records, so a
|
|
29
|
+
* container read once never builds it.
|
|
29
30
|
*
|
|
30
31
|
* @since 0.10.0
|
|
31
32
|
*/
|
|
@@ -33,6 +34,7 @@ export interface CollectionEntry {
|
|
|
33
34
|
readonly candidates: ReadonlyArray<Binding>;
|
|
34
35
|
values: ReadonlyArray<unknown> | undefined;
|
|
35
36
|
activationVersion: number;
|
|
37
|
+
readBefore: boolean;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* A version-stamped cache of binding lookups by token and criterion across the container chain.
|
|
@@ -65,6 +67,9 @@ export declare class BindingLookupCache<Owner> {
|
|
|
65
67
|
namedTaggedEntry(token: Token<unknown> | Constructor, nameCriterion: BindingTag, tag: BindingTag): DefaultLookupEntry<Owner> | null;
|
|
66
68
|
/** The memoized root-level collection for a token, or `undefined` once the chain changed since it was stored. */
|
|
67
69
|
collection(token: Token<unknown> | Constructor): CollectionEntry | undefined;
|
|
68
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Stores a root-level collection under the chain version the last `collection()` read stamped, so a
|
|
72
|
+
* predicate that binds during the gather leaves the entry stale rather than current.
|
|
73
|
+
*/
|
|
69
74
|
rememberCollection(token: Token<unknown> | Constructor, entry: CollectionEntry): void;
|
|
70
75
|
}
|
|
@@ -29,9 +29,9 @@ export class BindingLookupCache {
|
|
|
29
29
|
#lastTagToken;
|
|
30
30
|
#lastTag;
|
|
31
31
|
#lastTaggedEntry = null;
|
|
32
|
-
// Root-level collections
|
|
32
|
+
// Root-level collections, stamped with the chain version like the memos above. Deferred into one object,
|
|
33
|
+
// because every per-request child builds this cache and almost none reads a collection at the root.
|
|
33
34
|
#collections;
|
|
34
|
-
#collectionsVersion = -1;
|
|
35
35
|
#registry;
|
|
36
36
|
#owner;
|
|
37
37
|
#parent;
|
|
@@ -171,16 +171,48 @@ export class BindingLookupCache {
|
|
|
171
171
|
/** The memoized root-level collection for a token, or `undefined` once the chain changed since it was stored. */
|
|
172
172
|
collection(token) {
|
|
173
173
|
const version = this.chainVersion();
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this.#
|
|
174
|
+
const memo = this.#collections;
|
|
175
|
+
if (memo === undefined) {
|
|
176
|
+
this.#collections = { version, lastToken: undefined, lastEntry: undefined, byToken: undefined };
|
|
177
177
|
return undefined;
|
|
178
178
|
}
|
|
179
|
-
|
|
179
|
+
if (version !== memo.version) {
|
|
180
|
+
memo.version = version;
|
|
181
|
+
memo.byToken?.clear();
|
|
182
|
+
// The rebuild that follows can throw, so the entry in front must not outlive the generation.
|
|
183
|
+
memo.lastToken = undefined;
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
if (token === memo.lastToken) {
|
|
187
|
+
return memo.lastEntry;
|
|
188
|
+
}
|
|
189
|
+
const entry = memo.byToken?.get(token);
|
|
190
|
+
if (entry !== undefined) {
|
|
191
|
+
memo.lastToken = token;
|
|
192
|
+
memo.lastEntry = entry;
|
|
193
|
+
}
|
|
194
|
+
return entry;
|
|
180
195
|
}
|
|
181
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Stores a root-level collection under the chain version the last `collection()` read stamped, so a
|
|
198
|
+
* predicate that binds during the gather leaves the entry stale rather than current.
|
|
199
|
+
*/
|
|
182
200
|
rememberCollection(token, entry) {
|
|
183
|
-
|
|
201
|
+
const memo = this.#collections;
|
|
202
|
+
if (memo === undefined) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (memo.byToken !== undefined) {
|
|
206
|
+
memo.byToken.set(token, entry);
|
|
207
|
+
}
|
|
208
|
+
else if (memo.lastToken !== undefined && memo.lastEntry !== undefined) {
|
|
209
|
+
memo.byToken = new Map([
|
|
210
|
+
[memo.lastToken, memo.lastEntry],
|
|
211
|
+
[token, entry],
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
memo.lastToken = token;
|
|
215
|
+
memo.lastEntry = entry;
|
|
184
216
|
}
|
|
185
217
|
/**
|
|
186
218
|
* Follows alias hops to the terminal entry, or declines with `null` on a cycle, which the full
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The `ResolutionContext` a factory is handed, and the callbacks the resolver answers it with. */
|
|
2
2
|
import type { Token } from "#core/token";
|
|
3
3
|
import type { ConstraintContext, Constructor, ResolutionFrame, ResolutionContext, ResolveOptions } from "#core/types";
|
|
4
|
-
import type { BranchDepth, OwnedBranchStack } from "#resolution/path
|
|
4
|
+
import type { BranchDepth, OwnedBranchStack } from "#resolution/path";
|
|
5
5
|
/**
|
|
6
6
|
* The engine surface a resolution context calls back into to resolve further dependencies.
|
|
7
7
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bindingsOf, enterSeededPath, leaveSeededPath, UNOWNED_BRANCH } from "#resolution/path
|
|
1
|
+
import { bindingsOf, enterSeededPath, leaveSeededPath, UNOWNED_BRANCH } from "#resolution/path";
|
|
2
2
|
/**
|
|
3
3
|
* The `ResolutionContext` handed to factories and hooks, backed by the engine's callbacks.
|
|
4
4
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Binding } from "#core/binding";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Token } from "#core/token";
|
|
3
|
+
import type { Constructor, ResolutionFrame } from "#core/types";
|
|
3
4
|
/**
|
|
4
5
|
* Creates the resolution-stack frame for one in-flight resolve of a binding.
|
|
5
6
|
*
|
|
@@ -17,11 +18,20 @@ export declare function buildResolutionFrame(binding: Binding, tokenName: string
|
|
|
17
18
|
*/
|
|
18
19
|
export declare function bindingsOf(frames: ReadonlyArray<ResolutionFrame>): Array<Binding>;
|
|
19
20
|
/**
|
|
20
|
-
* The token names along a stack plus the
|
|
21
|
+
* The token names along a stack plus the one ending the path, a cycle's closing frame or a miss — built only to throw.
|
|
21
22
|
*
|
|
22
23
|
* @since 0.6.0
|
|
23
24
|
*/
|
|
24
25
|
export declare function cycleNamesOf(resolutionStack: ReadonlyArray<ResolutionFrame>, closingName: string): Array<string>;
|
|
26
|
+
/**
|
|
27
|
+
* The token names from the outermost request to a lookup that selected nothing, its alias hops included.
|
|
28
|
+
*
|
|
29
|
+
* @remarks `aliasHops` is the set an alias walk keeps, which starts at `requested` — the name the path already
|
|
30
|
+
* ends on.
|
|
31
|
+
*
|
|
32
|
+
* @since 0.13.0
|
|
33
|
+
*/
|
|
34
|
+
export declare function missPathOf(resolutionStack: ReadonlyArray<ResolutionFrame>, requested: Token<unknown> | Constructor, aliasHops: ReadonlySet<Token<unknown> | Constructor> | undefined): Array<string>;
|
|
25
35
|
/**
|
|
26
36
|
* Marks a level in flight on a synchronous path and pushes its frame, throwing if its binding already is.
|
|
27
37
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { tokenName } from "#core/token";
|
|
2
|
+
/** The resolution path: cycle bookkeeping carried on the stack itself, and the names an error prints. */
|
|
3
|
+
import { CircularDependencyError } from "#errors";
|
|
3
4
|
/** The key under which a resolver-built frame carries its binding, for marking a seeded path. */
|
|
4
5
|
const FRAME_BINDING = Symbol("di:frame-binding");
|
|
5
6
|
/**
|
|
@@ -38,7 +39,7 @@ export function bindingsOf(frames) {
|
|
|
38
39
|
return bindings;
|
|
39
40
|
}
|
|
40
41
|
/**
|
|
41
|
-
* The token names along a stack plus the
|
|
42
|
+
* The token names along a stack plus the one ending the path, a cycle's closing frame or a miss — built only to throw.
|
|
42
43
|
*
|
|
43
44
|
* @since 0.6.0
|
|
44
45
|
*/
|
|
@@ -50,6 +51,25 @@ export function cycleNamesOf(resolutionStack, closingName) {
|
|
|
50
51
|
names[resolutionStack.length] = closingName;
|
|
51
52
|
return names;
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The token names from the outermost request to a lookup that selected nothing, its alias hops included.
|
|
56
|
+
*
|
|
57
|
+
* @remarks `aliasHops` is the set an alias walk keeps, which starts at `requested` — the name the path already
|
|
58
|
+
* ends on.
|
|
59
|
+
*
|
|
60
|
+
* @since 0.13.0
|
|
61
|
+
*/
|
|
62
|
+
export function missPathOf(resolutionStack, requested, aliasHops) {
|
|
63
|
+
const path = cycleNamesOf(resolutionStack, tokenName(requested));
|
|
64
|
+
if (aliasHops !== undefined) {
|
|
65
|
+
for (const hop of aliasHops) {
|
|
66
|
+
if (hop !== requested) {
|
|
67
|
+
path.push(tokenName(hop));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return path;
|
|
72
|
+
}
|
|
53
73
|
/**
|
|
54
74
|
* Marks a level in flight on a synchronous path and pushes its frame, throwing if its binding already is.
|
|
55
75
|
*
|
|
@@ -6,10 +6,8 @@ import type { ConstructorInvocation } from "#core/constructor-type";
|
|
|
6
6
|
/**
|
|
7
7
|
* The number of runs a plan's closure makes before the plan is generated as its own function.
|
|
8
8
|
*
|
|
9
|
-
* @remarks A measured policy
|
|
10
|
-
*
|
|
11
|
-
* over runs in the thousands. Below it a plan stays a closure, which is all a cold container or a
|
|
12
|
-
* per-request child ever runs.
|
|
9
|
+
* @remarks A measured policy: the generated function costs a compile and a warm-up, so it repays only over
|
|
10
|
+
* many runs of one plan in one container, and a cold container or a per-request child keeps the closure.
|
|
13
11
|
*
|
|
14
12
|
* @since 0.10.0
|
|
15
13
|
*/
|
|
@@ -3,10 +3,8 @@ import { settleInOrder } from "#resolution/async-fan-out";
|
|
|
3
3
|
/**
|
|
4
4
|
* The number of runs a plan's closure makes before the plan is generated as its own function.
|
|
5
5
|
*
|
|
6
|
-
* @remarks A measured policy
|
|
7
|
-
*
|
|
8
|
-
* over runs in the thousands. Below it a plan stays a closure, which is all a cold container or a
|
|
9
|
-
* per-request child ever runs.
|
|
6
|
+
* @remarks A measured policy: the generated function costs a compile and a warm-up, so it repays only over
|
|
7
|
+
* many runs of one plan in one container, and a cold container or a per-request child keeps the closure.
|
|
10
8
|
*
|
|
11
9
|
* @since 0.10.0
|
|
12
10
|
*/
|
|
@@ -8,7 +8,7 @@ import type { Binding } from "#core/binding";
|
|
|
8
8
|
import type { ConstructorInvocation } from "#core/constructor-type";
|
|
9
9
|
import type { Token } from "#core/token";
|
|
10
10
|
import type { Constructor, ResolutionFrame, ResolveOptions } from "#core/types";
|
|
11
|
-
import type { ConstructorMetadata } from "#metadata/
|
|
11
|
+
import type { ConstructorMetadata } from "#metadata/types";
|
|
12
12
|
/**
|
|
13
13
|
* Compilation asked to retry later (class lifecycle metadata not discovered yet).
|
|
14
14
|
*
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { NO_INSTANCE } from "#core/binding";
|
|
2
2
|
import { tokenName } from "#core/token";
|
|
3
|
-
import { AsyncResolutionError } from "#errors
|
|
4
|
-
import { injectionSlotToResolveOptions } from "#injection/
|
|
3
|
+
import { AsyncResolutionError } from "#errors";
|
|
4
|
+
import { injectionSlotToResolveOptions } from "#injection/dependency-slot";
|
|
5
5
|
import { settleInOrder } from "#resolution/async-fan-out";
|
|
6
|
-
import { enterSeededPath, leaveSeededPath } from "#resolution/path
|
|
7
|
-
import { generateAsyncPlan, generatePlan, isPlanCodegenAvailable, PLAN_CODEGEN_THRESHOLD, } from "#resolution/plan/
|
|
6
|
+
import { enterSeededPath, leaveSeededPath } from "#resolution/path";
|
|
7
|
+
import { generateAsyncPlan, generatePlan, isPlanCodegenAvailable, PLAN_CODEGEN_THRESHOLD, } from "#resolution/plan/codegen";
|
|
8
8
|
/**
|
|
9
9
|
* Compilation asked to retry later (class lifecycle metadata not discovered yet).
|
|
10
10
|
*
|