@codefast/di 0.5.0-canary.5 → 0.5.0-canary.7
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 +65 -0
- package/README.md +73 -496
- package/dist/binding.d.ts +211 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +46 -0
- package/dist/binding.js.map +1 -0
- package/dist/{constructor-type.d.mts → constructor-type.d.ts} +3 -5
- package/dist/constructor-type.d.ts.map +1 -0
- package/dist/constructor-type.js +2 -0
- package/dist/constructor-type.js.map +1 -0
- package/dist/container/binding-builders.d.ts +35 -0
- package/dist/container/binding-builders.d.ts.map +1 -0
- package/dist/container/binding-builders.js +247 -0
- package/dist/container/binding-builders.js.map +1 -0
- package/dist/container/container.d.ts +58 -0
- package/dist/container/container.d.ts.map +1 -0
- package/dist/container/container.js +641 -0
- package/dist/container/container.js.map +1 -0
- package/dist/decorators/inject.d.ts +57 -0
- package/dist/decorators/inject.d.ts.map +1 -0
- package/dist/decorators/inject.js +145 -0
- package/dist/decorators/inject.js.map +1 -0
- package/dist/decorators/injectable.d.ts +29 -0
- package/dist/decorators/injectable.d.ts.map +1 -0
- package/dist/decorators/injectable.js +53 -0
- package/dist/decorators/injectable.js.map +1 -0
- package/dist/decorators/lifecycle-decorators.d.ts +9 -0
- package/dist/decorators/lifecycle-decorators.d.ts.map +1 -0
- package/dist/decorators/lifecycle-decorators.js +40 -0
- package/dist/decorators/lifecycle-decorators.js.map +1 -0
- package/dist/errors.d.ts +150 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +197 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection/dependency-graph.d.ts +40 -0
- package/dist/introspection/dependency-graph.d.ts.map +1 -0
- package/dist/introspection/dependency-graph.js +66 -0
- package/dist/introspection/dependency-graph.js.map +1 -0
- package/dist/introspection/graph-adapters/cytoscape.d.ts +33 -0
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/cytoscape.js +30 -0
- package/dist/introspection/graph-adapters/cytoscape.js.map +1 -0
- package/dist/introspection/graph-adapters/dot.d.ts +6 -0
- package/dist/introspection/graph-adapters/dot.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/dot.js +18 -0
- package/dist/introspection/graph-adapters/dot.js.map +1 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts +38 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/reactflow.js +33 -0
- package/dist/introspection/graph-adapters/reactflow.js.map +1 -0
- package/dist/introspection/inspector.d.ts +39 -0
- package/dist/introspection/inspector.d.ts.map +1 -0
- package/dist/introspection/inspector.js +82 -0
- package/dist/introspection/inspector.js.map +1 -0
- package/dist/metadata/{metadata-keys.d.mts → metadata-keys.d.ts} +5 -7
- package/dist/metadata/metadata-keys.d.ts.map +1 -0
- package/dist/metadata/metadata-keys.js +25 -0
- package/dist/metadata/metadata-keys.js.map +1 -0
- package/dist/metadata/metadata-reader-token.d.ts +7 -0
- package/dist/metadata/metadata-reader-token.d.ts.map +1 -0
- package/dist/metadata/metadata-reader-token.js +6 -0
- package/dist/metadata/metadata-reader-token.js.map +1 -0
- package/dist/metadata/metadata-types.d.ts +48 -0
- package/dist/metadata/metadata-types.d.ts.map +1 -0
- package/dist/metadata/metadata-types.js +2 -0
- package/dist/metadata/metadata-types.js.map +1 -0
- package/dist/metadata/symbol-metadata-reader.d.ts +20 -0
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -0
- package/dist/metadata/symbol-metadata-reader.js +34 -0
- package/dist/metadata/symbol-metadata-reader.js.map +1 -0
- package/dist/module.d.ts +67 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +54 -0
- package/dist/module.js.map +1 -0
- package/dist/registry.d.ts +33 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +249 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolution/binding-scope.d.ts +10 -0
- package/dist/resolution/binding-scope.d.ts.map +1 -0
- package/dist/resolution/binding-scope.js +24 -0
- package/dist/resolution/binding-scope.js.map +1 -0
- package/dist/resolution/binding-select.d.ts +16 -0
- package/dist/resolution/binding-select.d.ts.map +1 -0
- package/dist/resolution/binding-select.js +143 -0
- package/dist/resolution/binding-select.js.map +1 -0
- package/dist/resolution/constraints.d.ts +51 -0
- package/dist/resolution/constraints.d.ts.map +1 -0
- package/dist/resolution/constraints.js +88 -0
- package/dist/resolution/constraints.js.map +1 -0
- package/dist/resolution/environment.d.ts +47 -0
- package/dist/resolution/environment.d.ts.map +1 -0
- package/dist/resolution/environment.js +100 -0
- package/dist/resolution/environment.js.map +1 -0
- package/dist/resolution/instantiation-plan.d.ts +67 -0
- package/dist/resolution/instantiation-plan.d.ts.map +1 -0
- package/dist/resolution/instantiation-plan.js +163 -0
- package/dist/resolution/instantiation-plan.js.map +1 -0
- package/dist/resolution/lifecycle.d.ts +21 -0
- package/dist/resolution/lifecycle.d.ts.map +1 -0
- package/dist/resolution/lifecycle.js +178 -0
- package/dist/resolution/lifecycle.js.map +1 -0
- package/dist/resolution/resolution-path.d.ts +31 -0
- package/dist/resolution/resolution-path.d.ts.map +1 -0
- package/dist/resolution/resolution-path.js +53 -0
- package/dist/resolution/resolution-path.js.map +1 -0
- package/dist/resolution/resolve-options.d.ts +19 -0
- package/dist/resolution/resolve-options.d.ts.map +1 -0
- package/dist/resolution/resolve-options.js +32 -0
- package/dist/resolution/resolve-options.js.map +1 -0
- package/dist/resolution/resolver.d.ts +35 -0
- package/dist/resolution/resolver.d.ts.map +1 -0
- package/dist/resolution/resolver.js +1320 -0
- package/dist/resolution/resolver.js.map +1 -0
- package/dist/resolution/scope.d.ts +32 -0
- package/dist/resolution/scope.d.ts.map +1 -0
- package/dist/resolution/scope.js +76 -0
- package/dist/resolution/scope.js.map +1 -0
- package/dist/token.d.ts +23 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +22 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +143 -120
- package/src/binding.ts +38 -22
- package/src/container/binding-builders.ts +397 -0
- package/src/container/container.ts +838 -0
- package/src/decorators/inject.ts +16 -3
- package/src/decorators/injectable.ts +3 -3
- package/src/index.ts +14 -7
- package/src/{dependency-graph.ts → introspection/dependency-graph.ts} +1 -1
- package/src/{graph-adapters → introspection/graph-adapters}/cytoscape.ts +1 -1
- package/src/{graph-adapters → introspection/graph-adapters}/dot.ts +1 -1
- package/src/{graph-adapters → introspection/graph-adapters}/reactflow.ts +13 -2
- package/src/{inspector.ts → introspection/inspector.ts} +26 -21
- package/src/metadata/symbol-metadata-reader.ts +4 -4
- package/src/module.ts +14 -6
- package/src/registry.ts +115 -61
- package/src/{binding-select.ts → resolution/binding-select.ts} +16 -3
- package/src/{environment.ts → resolution/environment.ts} +35 -35
- package/src/resolution/instantiation-plan.ts +240 -0
- package/src/{lifecycle.ts → resolution/lifecycle.ts} +20 -13
- package/src/resolution/resolution-path.ts +77 -0
- package/src/resolution/resolver.ts +1755 -0
- package/src/{scope.ts → resolution/scope.ts} +35 -18
- package/src/token.ts +0 -3
- package/dist/binding-scope.d.mts +0 -13
- package/dist/binding-scope.mjs +0 -21
- package/dist/binding-select.d.mts +0 -19
- package/dist/binding-select.mjs +0 -72
- package/dist/binding.d.mts +0 -191
- package/dist/binding.mjs +0 -36
- package/dist/constraints.d.mts +0 -55
- package/dist/constraints.mjs +0 -87
- package/dist/constructor-type.mjs +0 -1
- package/dist/container.d.mts +0 -62
- package/dist/container.mjs +0 -741
- package/dist/decorators/inject.d.mts +0 -49
- package/dist/decorators/inject.mjs +0 -136
- package/dist/decorators/injectable.d.mts +0 -32
- package/dist/decorators/injectable.mjs +0 -57
- package/dist/decorators/lifecycle-decorators.d.mts +0 -11
- package/dist/decorators/lifecycle-decorators.mjs +0 -38
- package/dist/dependency-graph.d.mts +0 -43
- package/dist/dependency-graph.mjs +0 -63
- package/dist/environment.d.mts +0 -55
- package/dist/environment.mjs +0 -98
- package/dist/errors.d.mts +0 -153
- package/dist/errors.mjs +0 -197
- package/dist/graph-adapters/cytoscape.d.mts +0 -36
- package/dist/graph-adapters/cytoscape.mjs +0 -26
- package/dist/graph-adapters/dot.d.mts +0 -9
- package/dist/graph-adapters/dot.mjs +0 -20
- package/dist/graph-adapters/reactflow.d.mts +0 -41
- package/dist/graph-adapters/reactflow.mjs +0 -29
- package/dist/index.d.mts +0 -18
- package/dist/index.mjs +0 -12
- package/dist/inspector.d.mts +0 -48
- package/dist/inspector.mjs +0 -71
- package/dist/lifecycle.d.mts +0 -25
- package/dist/lifecycle.mjs +0 -118
- package/dist/metadata/metadata-keys.mjs +0 -27
- package/dist/metadata/metadata-reader-token.d.mts +0 -10
- package/dist/metadata/metadata-reader-token.mjs +0 -8
- package/dist/metadata/metadata-types.d.mts +0 -51
- package/dist/metadata/metadata-types.mjs +0 -1
- package/dist/metadata/symbol-metadata-reader.d.mts +0 -23
- package/dist/metadata/symbol-metadata-reader.mjs +0 -29
- package/dist/module.d.mts +0 -62
- package/dist/module.mjs +0 -42
- package/dist/registry.d.mts +0 -45
- package/dist/registry.mjs +0 -163
- package/dist/resolve-options.d.mts +0 -22
- package/dist/resolve-options.mjs +0 -28
- package/dist/resolver.d.mts +0 -94
- package/dist/resolver.mjs +0 -767
- package/dist/scope.d.mts +0 -28
- package/dist/scope.mjs +0 -58
- package/dist/token.d.mts +0 -25
- package/dist/token.mjs +0 -22
- package/dist/types.d.mts +0 -92
- package/dist/types.mjs +0 -1
- package/src/container.ts +0 -1141
- package/src/resolver.ts +0 -1626
- /package/src/{binding-scope.ts → resolution/binding-scope.ts} +0 -0
- /package/src/{constraints.ts → resolution/constraints.ts} +0 -0
- /package/src/{resolve-options.ts → resolution/resolve-options.ts} +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiler for the resolver's Dagger-style instantiation plans: a transient
|
|
3
|
+
* class or resolved-factory binding whose dependency subgraph is pure static
|
|
4
|
+
* (class/constant/cached-singleton deps, no activation hooks or postConstruct)
|
|
5
|
+
* compiles once into a nested-constructor/factory closure, cycle-checked at
|
|
6
|
+
* compile time.
|
|
7
|
+
*
|
|
8
|
+
* Compilation is cold-path — it runs once per (binding, cache version). The
|
|
9
|
+
* closures it returns ARE the hot path and touch nothing but their captures.
|
|
10
|
+
* Anything dynamic refuses to compile so the runtime cycle guard stays in
|
|
11
|
+
* charge and error semantics never change.
|
|
12
|
+
*/
|
|
13
|
+
import type { Binding } from "#/binding";
|
|
14
|
+
import type { ConstructorInvocation } from "#/constructor-type";
|
|
15
|
+
import type { InjectionDescriptor } from "#/decorators/inject";
|
|
16
|
+
import { AsyncResolutionError } from "#/errors";
|
|
17
|
+
import type { ConstructorMetadata } from "#/metadata/metadata-types";
|
|
18
|
+
import { injectionSlotToResolveOptions } from "#/resolution/resolve-options";
|
|
19
|
+
import type { ScopeManager } from "#/resolution/scope";
|
|
20
|
+
import { SINGLETON_MISS } from "#/resolution/scope";
|
|
21
|
+
import type { Token } from "#/token";
|
|
22
|
+
import { tokenName } from "#/token";
|
|
23
|
+
import type { BindingScope, Constructor } from "#/types";
|
|
24
|
+
|
|
25
|
+
// Bail out of pathological graphs — the runtime path handles them correctly.
|
|
26
|
+
const PLAN_DEPTH_LIMIT = 32;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compilation asked to retry later (class lifecycle metadata not discovered yet).
|
|
30
|
+
*
|
|
31
|
+
* @since 0.5.0-canary.7
|
|
32
|
+
*/
|
|
33
|
+
export const PLAN_RETRY: unique symbol = Symbol("di:plan-retry");
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A compiled plan, `null` for "not plannable under the current cache versions",
|
|
37
|
+
* or {@link PLAN_RETRY} when a first runtime resolve must discover metadata first.
|
|
38
|
+
*
|
|
39
|
+
* @since 0.5.0-canary.7
|
|
40
|
+
*/
|
|
41
|
+
export type InstantiationPlanCompileResult = (() => unknown) | null | typeof PLAN_RETRY;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A dependency's terminal binding plus the scope cache of the resolver that owns it.
|
|
45
|
+
*
|
|
46
|
+
* @since 0.5.0-canary.7
|
|
47
|
+
*/
|
|
48
|
+
export interface InstantiationPlanDependencyEntry {
|
|
49
|
+
readonly binding: Binding;
|
|
50
|
+
readonly ownerScope: ScopeManager;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Everything the compiler needs from its resolver, expressed as behavior so the
|
|
55
|
+
* compiler stays independently testable and free of resolver internals.
|
|
56
|
+
*
|
|
57
|
+
* @since 0.5.0-canary.7
|
|
58
|
+
*/
|
|
59
|
+
export interface InstantiationPlanHost {
|
|
60
|
+
hasActivationHandlers(token: Token<unknown> | Constructor): boolean;
|
|
61
|
+
/** Cached postConstruct presence — `undefined` until a runtime resolve discovers it. */
|
|
62
|
+
knownPostConstruct(target: Constructor): boolean | undefined;
|
|
63
|
+
needsActiveContainer(target: Constructor): boolean;
|
|
64
|
+
getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
|
|
65
|
+
/** Options-less lookup with alias hops folded; `null` when the fast lane can't answer. */
|
|
66
|
+
lookupDependencyEntry(token: Token<unknown> | Constructor): InstantiationPlanDependencyEntry | null;
|
|
67
|
+
/** Full runtime resolve — used by singleton thunks for the first materialization. */
|
|
68
|
+
resolveFallback(token: Token<unknown> | Constructor): unknown;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @since 0.3.16-canary.1
|
|
73
|
+
*/
|
|
74
|
+
export class InstantiationPlanCompiler {
|
|
75
|
+
readonly #host: InstantiationPlanHost;
|
|
76
|
+
|
|
77
|
+
constructor(host: InstantiationPlanHost) {
|
|
78
|
+
this.#host = host;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
compile(binding: Binding & { kind: "class" | "resolved" }): InstantiationPlanCompileResult {
|
|
82
|
+
return binding.kind === "class"
|
|
83
|
+
? this.#compileClassPlan(binding, new Set(), 0)
|
|
84
|
+
: this.#compileResolvedPlan(binding, new Set(), 0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// A resolved binding declares its deps as explicit descriptors — same rules as
|
|
88
|
+
// class params, with the factory call (and its sync-only check) in place of `new`.
|
|
89
|
+
#compileResolvedPlan(
|
|
90
|
+
binding: Binding & { kind: "resolved" },
|
|
91
|
+
compileStack: Set<Binding["id"]>,
|
|
92
|
+
depth: number,
|
|
93
|
+
): InstantiationPlanCompileResult {
|
|
94
|
+
if (depth > PLAN_DEPTH_LIMIT || compileStack.has(binding.id)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding.token)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const factory = binding.factory;
|
|
101
|
+
const tokenDisplayName = tokenName(binding.token);
|
|
102
|
+
const depThunks = new Array<() => unknown>(binding.deps.length);
|
|
103
|
+
compileStack.add(binding.id);
|
|
104
|
+
try {
|
|
105
|
+
for (let index = 0; index < binding.deps.length; index += 1) {
|
|
106
|
+
const thunk = this.#compileDescriptorThunk(binding.deps[index]!, compileStack, depth);
|
|
107
|
+
if (thunk === null || thunk === PLAN_RETRY) {
|
|
108
|
+
return thunk;
|
|
109
|
+
}
|
|
110
|
+
depThunks[index] = thunk;
|
|
111
|
+
}
|
|
112
|
+
} finally {
|
|
113
|
+
compileStack.delete(binding.id);
|
|
114
|
+
}
|
|
115
|
+
return () => {
|
|
116
|
+
const factoryResult = factory(...depThunks.map((thunk) => thunk()));
|
|
117
|
+
if (factoryResult instanceof Promise) {
|
|
118
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
119
|
+
}
|
|
120
|
+
return factoryResult;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#compileDescriptorThunk(
|
|
125
|
+
descriptor: InjectionDescriptor,
|
|
126
|
+
compileStack: Set<Binding["id"]>,
|
|
127
|
+
depth: number,
|
|
128
|
+
): InstantiationPlanCompileResult {
|
|
129
|
+
if (descriptor.multi || descriptor.optional || injectionSlotToResolveOptions(descriptor) !== undefined) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const entry = this.#host.lookupDependencyEntry(descriptor.token as Token<unknown> | Constructor);
|
|
133
|
+
if (entry === null) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return this.#compileDepThunk(entry, compileStack, depth);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#compileClassPlan(
|
|
140
|
+
binding: Binding & { kind: "class" },
|
|
141
|
+
compileStack: Set<Binding["id"]>,
|
|
142
|
+
depth: number,
|
|
143
|
+
): InstantiationPlanCompileResult {
|
|
144
|
+
if (depth > PLAN_DEPTH_LIMIT || compileStack.has(binding.id)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding.token)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const target = binding.target;
|
|
151
|
+
const hasPostConstruct = this.#host.knownPostConstruct(target);
|
|
152
|
+
if (hasPostConstruct === undefined) {
|
|
153
|
+
return PLAN_RETRY;
|
|
154
|
+
}
|
|
155
|
+
if (hasPostConstruct || this.#host.needsActiveContainer(target)) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const invokable = target as ConstructorInvocation;
|
|
159
|
+
const meta = this.#host.getConstructorMetadata(target);
|
|
160
|
+
if (meta === undefined) {
|
|
161
|
+
// Metadata-less classes with required params throw on the runtime path — keep them there.
|
|
162
|
+
return target.length === 0 ? () => new invokable() : null;
|
|
163
|
+
}
|
|
164
|
+
const params = meta.params;
|
|
165
|
+
if (params.length === 0) {
|
|
166
|
+
return () => new invokable();
|
|
167
|
+
}
|
|
168
|
+
const depThunks = new Array<() => unknown>(params.length);
|
|
169
|
+
compileStack.add(binding.id);
|
|
170
|
+
try {
|
|
171
|
+
for (let index = 0; index < params.length; index += 1) {
|
|
172
|
+
const param = params[index]!;
|
|
173
|
+
if (param.multi || param.optional || injectionSlotToResolveOptions(param) !== undefined) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const entry = this.#host.lookupDependencyEntry(param.token);
|
|
177
|
+
if (entry === null) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const thunk = this.#compileDepThunk(entry, compileStack, depth);
|
|
181
|
+
if (thunk === null || thunk === PLAN_RETRY) {
|
|
182
|
+
return thunk;
|
|
183
|
+
}
|
|
184
|
+
depThunks[index] = thunk;
|
|
185
|
+
}
|
|
186
|
+
} finally {
|
|
187
|
+
compileStack.delete(binding.id);
|
|
188
|
+
}
|
|
189
|
+
switch (depThunks.length) {
|
|
190
|
+
case 1: {
|
|
191
|
+
const dep0 = depThunks[0]!;
|
|
192
|
+
return () => new invokable(dep0());
|
|
193
|
+
}
|
|
194
|
+
case 2: {
|
|
195
|
+
const dep0 = depThunks[0]!;
|
|
196
|
+
const dep1 = depThunks[1]!;
|
|
197
|
+
return () => new invokable(dep0(), dep1());
|
|
198
|
+
}
|
|
199
|
+
case 3: {
|
|
200
|
+
const dep0 = depThunks[0]!;
|
|
201
|
+
const dep1 = depThunks[1]!;
|
|
202
|
+
const dep2 = depThunks[2]!;
|
|
203
|
+
return () => new invokable(dep0(), dep1(), dep2());
|
|
204
|
+
}
|
|
205
|
+
default:
|
|
206
|
+
return () => new invokable(...depThunks.map((thunk) => thunk()));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#compileDepThunk(
|
|
211
|
+
entry: InstantiationPlanDependencyEntry,
|
|
212
|
+
compileStack: Set<Binding["id"]>,
|
|
213
|
+
depth: number,
|
|
214
|
+
): InstantiationPlanCompileResult {
|
|
215
|
+
const { binding, ownerScope } = entry;
|
|
216
|
+
if (binding.kind === "constant") {
|
|
217
|
+
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding.token)) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const value = binding.value;
|
|
221
|
+
return () => value;
|
|
222
|
+
}
|
|
223
|
+
const scope = (binding as Binding & { scope: BindingScope }).scope ?? "transient";
|
|
224
|
+
if (scope === "singleton") {
|
|
225
|
+
// Cached-singleton read with a full-resolve fallback for the first materialization.
|
|
226
|
+
const host = this.#host;
|
|
227
|
+
const bindingId = binding.id;
|
|
228
|
+
const singletonToken = binding.token;
|
|
229
|
+
return () => {
|
|
230
|
+
const cachedSingleton = ownerScope.peekSingleton(bindingId);
|
|
231
|
+
return cachedSingleton === SINGLETON_MISS ? host.resolveFallback(singletonToken) : cachedSingleton;
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (scope === "transient" && binding.kind === "class") {
|
|
235
|
+
return this.#compileClassPlan(binding as Binding & { kind: "class" }, compileStack, depth + 1);
|
|
236
|
+
}
|
|
237
|
+
// Dynamic/resolved/scoped deps keep the runtime path (and its cycle guard).
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -10,34 +10,41 @@ import type { ActivationHandler, Constructor, DeactivationHandler, ResolutionCon
|
|
|
10
10
|
*/
|
|
11
11
|
export class LifecycleManager {
|
|
12
12
|
// Container-level activation/deactivation hooks per token
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
readonly #activationHooks = new Map<Token<unknown> | Constructor, Array<ActivationHandler<unknown>>>();
|
|
14
|
+
readonly #deactivationHooks = new Map<Token<unknown> | Constructor, Array<DeactivationHandler<unknown>>>();
|
|
15
|
+
#activationVersion = 0;
|
|
16
16
|
|
|
17
17
|
registerActivation<const Value>(token: Token<Value> | Constructor<Value>, handler: ActivationHandler<Value>): void {
|
|
18
|
-
this
|
|
18
|
+
this.#activationVersion += 1;
|
|
19
19
|
// ✓ TS6.0: Map.getOrInsert (ES2025)
|
|
20
|
-
const list = this.
|
|
20
|
+
const list = this.#activationHooks.getOrInsert(token as Token<unknown> | Constructor, []);
|
|
21
21
|
list.push(handler as ActivationHandler<unknown>);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
hasActivationHandlers<const Value>(token: Token<Value> | Constructor<Value>): boolean {
|
|
25
|
-
if (this.
|
|
25
|
+
if (this.#activationHooks.size === 0) {
|
|
26
26
|
return false;
|
|
27
27
|
}
|
|
28
|
-
const list = this.
|
|
28
|
+
const list = this.#activationHooks.get(token as Token<unknown> | Constructor);
|
|
29
29
|
return list !== undefined && list.length > 0;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
get activationVersion(): number {
|
|
33
|
-
return this
|
|
33
|
+
return this.#activationVersion;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Container-level activation handlers for a token — hot-path accessor, no copies. */
|
|
37
|
+
activationHandlersFor<const Value>(
|
|
38
|
+
token: Token<Value> | Constructor<Value>,
|
|
39
|
+
): ReadonlyArray<ActivationHandler<unknown>> | undefined {
|
|
40
|
+
return this.#activationHooks.get(token as Token<unknown> | Constructor);
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
registerDeactivation<const Value>(
|
|
37
44
|
token: Token<Value> | Constructor<Value>,
|
|
38
45
|
handler: DeactivationHandler<Value>,
|
|
39
46
|
): void {
|
|
40
|
-
const list = this.
|
|
47
|
+
const list = this.#deactivationHooks.getOrInsert(token as Token<unknown> | Constructor, []);
|
|
41
48
|
list.push(handler as DeactivationHandler<unknown>);
|
|
42
49
|
}
|
|
43
50
|
|
|
@@ -72,7 +79,7 @@ export class LifecycleManager {
|
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
// 3. container-level onActivation
|
|
75
|
-
const containerHooks = this.
|
|
82
|
+
const containerHooks = this.#activationHooks.get(binding.token as Token<unknown> | Constructor);
|
|
76
83
|
if (containerHooks !== undefined) {
|
|
77
84
|
for (const hook of containerHooks) {
|
|
78
85
|
const activationResult = hook(resolutionContext, activatedInstance);
|
|
@@ -118,7 +125,7 @@ export class LifecycleManager {
|
|
|
118
125
|
|
|
119
126
|
// 3. container-level onActivation (must be sync)
|
|
120
127
|
const tokenDisplayName = tokenName(binding.token);
|
|
121
|
-
const containerHooks = this.
|
|
128
|
+
const containerHooks = this.#activationHooks.get(binding.token as Token<unknown> | Constructor);
|
|
122
129
|
if (containerHooks !== undefined) {
|
|
123
130
|
for (const hook of containerHooks) {
|
|
124
131
|
const activationResult = hook(resolutionContext, activatedInstance);
|
|
@@ -140,7 +147,7 @@ export class LifecycleManager {
|
|
|
140
147
|
const tokenKey = binding.token as Token<unknown> | Constructor;
|
|
141
148
|
|
|
142
149
|
// 1. container-level onDeactivation
|
|
143
|
-
const containerHooks = this.
|
|
150
|
+
const containerHooks = this.#deactivationHooks.get(tokenKey);
|
|
144
151
|
if (containerHooks !== undefined) {
|
|
145
152
|
for (const hook of containerHooks) {
|
|
146
153
|
const hookResult = hook(instance);
|
|
@@ -180,7 +187,7 @@ export class LifecycleManager {
|
|
|
180
187
|
const tokenKey = binding.token as Token<unknown> | Constructor;
|
|
181
188
|
|
|
182
189
|
// 1. container-level onDeactivation
|
|
183
|
-
const containerHooks = this.
|
|
190
|
+
const containerHooks = this.#deactivationHooks.get(tokenKey);
|
|
184
191
|
if (containerHooks !== undefined) {
|
|
185
192
|
for (const hook of containerHooks) {
|
|
186
193
|
const hookResult = hook(instance);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cycle-detection bookkeeping shared by every resolution path.
|
|
3
|
+
*
|
|
4
|
+
* The resolution path is a plain string array for cheap push/pop; past
|
|
5
|
+
* RESOLUTION_SET_THRESHOLD entries an O(1) membership Set is attached to the
|
|
6
|
+
* array itself (symbol-keyed) so very deep graphs keep bounded cycle checks.
|
|
7
|
+
*/
|
|
8
|
+
import { CircularDependencyError } from "#/errors";
|
|
9
|
+
|
|
10
|
+
const RESOLUTION_SET_KEY: unique symbol = Symbol("di:resolution-set");
|
|
11
|
+
/**
|
|
12
|
+
* Where the cycle check switches from a linear `Array.includes` scan to an attached Set.
|
|
13
|
+
*
|
|
14
|
+
* @remarks Re-measured on Node 26 / M3 Max over an async transient chain (ns/op at depth
|
|
15
|
+
* 16 / 32 / 64 / 128): a threshold of 128 gives 1275 / 3641 / 9645 / 26082, of 32 gives
|
|
16
|
+
* 1202 / 3285 / 7735 / 16837, of 16 gives 1299 / 3694 / 7449 / 15625. Switching at 32 wins
|
|
17
|
+
* the shallow-to-mid depths real graphs actually have while staying close to the best deep
|
|
18
|
+
* numbers; the previous value of 128 was the worst of the three almost everywhere.
|
|
19
|
+
*
|
|
20
|
+
* @since 0.5.0-canary.7
|
|
21
|
+
*/
|
|
22
|
+
export const RESOLUTION_SET_THRESHOLD = 32;
|
|
23
|
+
type ResolutionPathWithSet = Array<string> & { [RESOLUTION_SET_KEY]?: Set<string> };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Shared cycle guard for every transient resolution path: attaches the O(1) membership
|
|
27
|
+
* Set to the path array (lazily past RESOLUTION_SET_THRESHOLD, eagerly when `forceSet`),
|
|
28
|
+
* throws on a repeated token, then marks the token on both structures.
|
|
29
|
+
*
|
|
30
|
+
* Callers unmark with `resolutionPath.pop()` + `set?.delete(name)` on unwind.
|
|
31
|
+
*/
|
|
32
|
+
export function enterResolutionPath(
|
|
33
|
+
resolutionPath: Array<string>,
|
|
34
|
+
tokenDisplayName: string,
|
|
35
|
+
forceSet: true,
|
|
36
|
+
): Set<string>;
|
|
37
|
+
export function enterResolutionPath(
|
|
38
|
+
resolutionPath: Array<string>,
|
|
39
|
+
tokenDisplayName: string,
|
|
40
|
+
forceSet: boolean,
|
|
41
|
+
): Set<string> | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* @since 0.5.0-canary.7
|
|
44
|
+
*/
|
|
45
|
+
export function enterResolutionPath(
|
|
46
|
+
resolutionPath: Array<string>,
|
|
47
|
+
tokenDisplayName: string,
|
|
48
|
+
forceSet: boolean,
|
|
49
|
+
): Set<string> | undefined {
|
|
50
|
+
const pathWithSet = resolutionPath as ResolutionPathWithSet;
|
|
51
|
+
let resolutionSet = pathWithSet[RESOLUTION_SET_KEY];
|
|
52
|
+
if (resolutionSet === undefined && (forceSet || resolutionPath.length >= RESOLUTION_SET_THRESHOLD)) {
|
|
53
|
+
resolutionSet = new Set<string>(resolutionPath);
|
|
54
|
+
pathWithSet[RESOLUTION_SET_KEY] = resolutionSet;
|
|
55
|
+
}
|
|
56
|
+
if (resolutionSet === undefined ? resolutionPath.includes(tokenDisplayName) : resolutionSet.has(tokenDisplayName)) {
|
|
57
|
+
throw new CircularDependencyError([...resolutionPath, tokenDisplayName]);
|
|
58
|
+
}
|
|
59
|
+
resolutionPath.push(tokenDisplayName);
|
|
60
|
+
resolutionSet?.add(tokenDisplayName);
|
|
61
|
+
return resolutionSet;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Unwinds the innermost `enterResolutionPath` entry, keeping the attached membership Set in
|
|
66
|
+
* sync. Callers that already hold the entry's name can pop and delete directly; this exists for
|
|
67
|
+
* unwind paths that only have the path array — notably the async chain's shared settle callback,
|
|
68
|
+
* which serves every level and therefore cannot capture a per-level name.
|
|
69
|
+
*
|
|
70
|
+
* @since 0.5.0-canary.7
|
|
71
|
+
*/
|
|
72
|
+
export function exitResolutionPath(resolutionPath: Array<string>): void {
|
|
73
|
+
const tokenDisplayName = resolutionPath.pop();
|
|
74
|
+
if (tokenDisplayName !== undefined) {
|
|
75
|
+
(resolutionPath as ResolutionPathWithSet)[RESOLUTION_SET_KEY]?.delete(tokenDisplayName);
|
|
76
|
+
}
|
|
77
|
+
}
|