@codefast/di 0.5.0-canary.7 → 0.5.0-canary.8
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 +68 -0
- package/README.md +3 -1
- package/dist/binding.d.ts +47 -5
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +44 -0
- package/dist/binding.js.map +1 -1
- package/dist/constructor-type.d.ts +4 -5
- package/dist/constructor-type.d.ts.map +1 -1
- package/dist/container/binding-builders.d.ts +32 -11
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +140 -191
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +75 -92
- package/dist/container/container.js.map +1 -1
- package/dist/decorators/inject.d.ts +2 -4
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js.map +1 -1
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/introspection/inspector.js +1 -1
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/metadata/metadata-keys.d.ts +3 -6
- package/dist/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/metadata/metadata-keys.js +3 -6
- package/dist/metadata/metadata-keys.js.map +1 -1
- package/dist/registry.d.ts +14 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +35 -45
- package/dist/registry.js.map +1 -1
- package/dist/resolution/activation-need.d.ts +25 -0
- package/dist/resolution/activation-need.d.ts.map +1 -0
- package/dist/resolution/activation-need.js +64 -0
- package/dist/resolution/activation-need.js.map +1 -0
- package/dist/resolution/binding-lookup-cache.d.ts +41 -0
- package/dist/resolution/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/binding-lookup-cache.js +102 -0
- package/dist/resolution/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/binding-select.js +1 -4
- package/dist/resolution/binding-select.js.map +1 -1
- package/dist/resolution/class-introspector.d.ts +27 -0
- package/dist/resolution/class-introspector.d.ts.map +1 -0
- package/dist/resolution/class-introspector.js +60 -0
- package/dist/resolution/class-introspector.js.map +1 -0
- package/dist/resolution/diagnostics.d.ts +41 -0
- package/dist/resolution/diagnostics.d.ts.map +1 -0
- package/dist/resolution/diagnostics.js +18 -0
- package/dist/resolution/diagnostics.js.map +1 -0
- package/dist/resolution/environment.d.ts +22 -1
- package/dist/resolution/environment.d.ts.map +1 -1
- package/dist/resolution/environment.js +27 -1
- package/dist/resolution/environment.js.map +1 -1
- package/dist/resolution/instantiation-plan.d.ts +15 -15
- package/dist/resolution/instantiation-plan.d.ts.map +1 -1
- package/dist/resolution/instantiation-plan.js +68 -48
- package/dist/resolution/instantiation-plan.js.map +1 -1
- package/dist/resolution/lifecycle.d.ts +2 -0
- package/dist/resolution/lifecycle.d.ts.map +1 -1
- package/dist/resolution/lifecycle.js +16 -11
- package/dist/resolution/lifecycle.js.map +1 -1
- package/dist/resolution/resolution-path.d.ts +4 -13
- package/dist/resolution/resolution-path.d.ts.map +1 -1
- package/dist/resolution/resolution-path.js +3 -16
- package/dist/resolution/resolution-path.js.map +1 -1
- package/dist/resolution/resolver.d.ts +7 -2
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +116 -328
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/scope.d.ts +7 -15
- package/dist/resolution/scope.d.ts.map +1 -1
- package/dist/resolution/scope.js +48 -44
- package/dist/resolution/scope.js.map +1 -1
- package/package.json +7 -97
- package/src/binding.ts +104 -5
- package/src/constructor-type.ts +4 -5
- package/src/container/binding-builders.ts +180 -283
- package/src/container/container.ts +90 -103
- package/src/decorators/inject.ts +3 -5
- package/src/errors.ts +21 -0
- package/src/index.ts +1 -0
- package/src/introspection/inspector.ts +1 -1
- package/src/metadata/metadata-keys.ts +3 -6
- package/src/registry.ts +38 -59
- package/src/resolution/activation-need.ts +81 -0
- package/src/resolution/binding-lookup-cache.ts +132 -0
- package/src/resolution/binding-select.ts +1 -4
- package/src/resolution/class-introspector.ts +74 -0
- package/src/resolution/diagnostics.ts +43 -0
- package/src/resolution/environment.ts +31 -1
- package/src/resolution/instantiation-plan.ts +113 -61
- package/src/resolution/lifecycle.ts +16 -11
- package/src/resolution/resolution-path.ts +5 -20
- package/src/resolution/resolver.ts +142 -371
- package/src/resolution/scope.ts +50 -49
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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.
|
|
2
|
+
* Compiles a transient class or resolved-factory binding into a nested-constructor closure.
|
|
7
3
|
*
|
|
8
|
-
*
|
|
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.
|
|
4
|
+
* @see `ARCHITECTURE.md` — the escape contract every dependency the compiler cannot inline must honour.
|
|
12
5
|
*/
|
|
13
6
|
import type { Binding } from "#/binding";
|
|
7
|
+
import { NO_INSTANCE } from "#/binding";
|
|
14
8
|
import type { ConstructorInvocation } from "#/constructor-type";
|
|
15
9
|
import type { InjectionDescriptor } from "#/decorators/inject";
|
|
16
10
|
import { AsyncResolutionError } from "#/errors";
|
|
17
11
|
import type { ConstructorMetadata } from "#/metadata/metadata-types";
|
|
18
12
|
import { injectionSlotToResolveOptions } from "#/resolution/resolve-options";
|
|
19
|
-
import type { ScopeManager } from "#/resolution/scope";
|
|
20
|
-
import { SINGLETON_MISS } from "#/resolution/scope";
|
|
21
13
|
import type { Token } from "#/token";
|
|
22
14
|
import { tokenName } from "#/token";
|
|
23
|
-
import type { BindingScope, Constructor } from "#/types";
|
|
15
|
+
import type { BindingScope, Constructor, ResolutionFrame, ResolveOptions } from "#/types";
|
|
24
16
|
|
|
25
|
-
//
|
|
17
|
+
// Past this depth a dependency escapes to the runtime path rather than inlining further —
|
|
18
|
+
// compiled closures nest one JS frame per level, and pathological graphs are the runtime's job.
|
|
26
19
|
const PLAN_DEPTH_LIMIT = 32;
|
|
27
20
|
|
|
28
21
|
/**
|
|
@@ -41,13 +34,20 @@ export const PLAN_RETRY: unique symbol = Symbol("di:plan-retry");
|
|
|
41
34
|
export type InstantiationPlanCompileResult = (() => unknown) | null | typeof PLAN_RETRY;
|
|
42
35
|
|
|
43
36
|
/**
|
|
44
|
-
*
|
|
37
|
+
* What compiling one *dependency* can yield.
|
|
38
|
+
*
|
|
39
|
+
* @remarks No `null`: a dependency escapes rather than failing, so "no plan" is only ever a
|
|
40
|
+
* verdict on a plan's root.
|
|
41
|
+
*/
|
|
42
|
+
type DependencyCompileResult = (() => unknown) | typeof PLAN_RETRY;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A dependency's terminal binding — all a compiled thunk needs.
|
|
45
46
|
*
|
|
46
47
|
* @since 0.5.0-canary.7
|
|
47
48
|
*/
|
|
48
49
|
export interface InstantiationPlanDependencyEntry {
|
|
49
50
|
readonly binding: Binding;
|
|
50
|
-
readonly ownerScope: ScopeManager;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -64,10 +64,26 @@ export interface InstantiationPlanHost {
|
|
|
64
64
|
getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
|
|
65
65
|
/** Options-less lookup with alias hops folded; `null` when the fast lane can't answer. */
|
|
66
66
|
lookupDependencyEntry(token: Token<unknown> | Constructor): InstantiationPlanDependencyEntry | null;
|
|
67
|
-
/**
|
|
68
|
-
|
|
67
|
+
/** The frame the interpreted path pushes for this binding, so escapes can replay it. */
|
|
68
|
+
getResolutionFrame(binding: Binding): ResolutionFrame;
|
|
69
|
+
/** Runtime resolve for an escaped dependency, seeded with the ancestors above it. */
|
|
70
|
+
resolveEscaped(
|
|
71
|
+
token: Token<unknown> | Constructor,
|
|
72
|
+
options: ResolveOptions | undefined,
|
|
73
|
+
arity: EscapeArity,
|
|
74
|
+
resolutionPath: Array<string>,
|
|
75
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
76
|
+
): unknown;
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Which resolve an escaped dependency replays — mirrors how the interpreted path dispatches
|
|
81
|
+
* a constructor param.
|
|
82
|
+
*
|
|
83
|
+
* @since 0.5.0-canary.8
|
|
84
|
+
*/
|
|
85
|
+
export type EscapeArity = "all" | "optional" | "single";
|
|
86
|
+
|
|
71
87
|
/**
|
|
72
88
|
* @since 0.3.16-canary.1
|
|
73
89
|
*/
|
|
@@ -80,8 +96,26 @@ export class InstantiationPlanCompiler {
|
|
|
80
96
|
|
|
81
97
|
compile(binding: Binding & { kind: "class" | "resolved" }): InstantiationPlanCompileResult {
|
|
82
98
|
return binding.kind === "class"
|
|
83
|
-
? this.#compileClassPlan(binding, new Set(), 0)
|
|
84
|
-
: this.#compileResolvedPlan(binding, new Set(), 0);
|
|
99
|
+
? this.#compileClassPlan(binding, new Set(), 0, [])
|
|
100
|
+
: this.#compileResolvedPlan(binding, new Set(), 0, []);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Re-entry into the runtime resolver for a dependency the plan can't see through.
|
|
105
|
+
*
|
|
106
|
+
* The ancestors are fixed at compile time, so the seeds are built once; each call copies
|
|
107
|
+
* them because the resolver pushes and pops on the arrays it is given.
|
|
108
|
+
*/
|
|
109
|
+
#compileEscapeThunk(
|
|
110
|
+
token: Token<unknown> | Constructor,
|
|
111
|
+
ancestors: ReadonlyArray<Binding>,
|
|
112
|
+
arity: EscapeArity = "single",
|
|
113
|
+
options?: ResolveOptions,
|
|
114
|
+
): () => unknown {
|
|
115
|
+
const host = this.#host;
|
|
116
|
+
const frames = ancestors.map((ancestor) => host.getResolutionFrame(ancestor));
|
|
117
|
+
const names = frames.map((frame) => frame.tokenName);
|
|
118
|
+
return () => host.resolveEscaped(token, options, arity, [...names], [...frames]);
|
|
85
119
|
}
|
|
86
120
|
|
|
87
121
|
// A resolved binding declares its deps as explicit descriptors — same rules as
|
|
@@ -90,21 +124,20 @@ export class InstantiationPlanCompiler {
|
|
|
90
124
|
binding: Binding & { kind: "resolved" },
|
|
91
125
|
compileStack: Set<Binding["id"]>,
|
|
92
126
|
depth: number,
|
|
127
|
+
ancestors: ReadonlyArray<Binding>,
|
|
93
128
|
): InstantiationPlanCompileResult {
|
|
94
|
-
if (depth > PLAN_DEPTH_LIMIT || compileStack.has(binding.id)) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
129
|
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding.token)) {
|
|
98
130
|
return null;
|
|
99
131
|
}
|
|
100
132
|
const factory = binding.factory;
|
|
101
133
|
const tokenDisplayName = tokenName(binding.token);
|
|
102
134
|
const depThunks = new Array<() => unknown>(binding.deps.length);
|
|
135
|
+
const depAncestors = [...ancestors, binding];
|
|
103
136
|
compileStack.add(binding.id);
|
|
104
137
|
try {
|
|
105
138
|
for (let index = 0; index < binding.deps.length; index += 1) {
|
|
106
|
-
const thunk = this.#
|
|
107
|
-
if (thunk ===
|
|
139
|
+
const thunk = this.#compileInjectionThunk(binding.deps[index]!, compileStack, depth, depAncestors);
|
|
140
|
+
if (thunk === PLAN_RETRY) {
|
|
108
141
|
return thunk;
|
|
109
142
|
}
|
|
110
143
|
depThunks[index] = thunk;
|
|
@@ -121,29 +154,41 @@ export class InstantiationPlanCompiler {
|
|
|
121
154
|
};
|
|
122
155
|
}
|
|
123
156
|
|
|
124
|
-
|
|
157
|
+
/**
|
|
158
|
+
* One dependency of a plan node — a constructor param or a `toResolved` descriptor.
|
|
159
|
+
*
|
|
160
|
+
* @remarks Anything but a plain required single dependency escapes to the runtime path.
|
|
161
|
+
*/
|
|
162
|
+
#compileInjectionThunk(
|
|
125
163
|
descriptor: InjectionDescriptor,
|
|
126
164
|
compileStack: Set<Binding["id"]>,
|
|
127
165
|
depth: number,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
166
|
+
ancestors: ReadonlyArray<Binding>,
|
|
167
|
+
): DependencyCompileResult {
|
|
168
|
+
const token = descriptor.token as Token<unknown> | Constructor;
|
|
169
|
+
const options = injectionSlotToResolveOptions(descriptor);
|
|
170
|
+
if (descriptor.multi) {
|
|
171
|
+
return this.#compileEscapeThunk(token, ancestors, "all", options);
|
|
172
|
+
}
|
|
173
|
+
if (descriptor.optional) {
|
|
174
|
+
return this.#compileEscapeThunk(token, ancestors, "optional", options);
|
|
131
175
|
}
|
|
132
|
-
|
|
176
|
+
if (options !== undefined) {
|
|
177
|
+
return this.#compileEscapeThunk(token, ancestors, "single", options);
|
|
178
|
+
}
|
|
179
|
+
const entry = this.#host.lookupDependencyEntry(token);
|
|
133
180
|
if (entry === null) {
|
|
134
|
-
return
|
|
181
|
+
return this.#compileEscapeThunk(token, ancestors);
|
|
135
182
|
}
|
|
136
|
-
return this.#compileDepThunk(entry, compileStack, depth);
|
|
183
|
+
return this.#compileDepThunk(entry, compileStack, depth, ancestors);
|
|
137
184
|
}
|
|
138
185
|
|
|
139
186
|
#compileClassPlan(
|
|
140
187
|
binding: Binding & { kind: "class" },
|
|
141
188
|
compileStack: Set<Binding["id"]>,
|
|
142
189
|
depth: number,
|
|
190
|
+
ancestors: ReadonlyArray<Binding>,
|
|
143
191
|
): InstantiationPlanCompileResult {
|
|
144
|
-
if (depth > PLAN_DEPTH_LIMIT || compileStack.has(binding.id)) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
192
|
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding.token)) {
|
|
148
193
|
return null;
|
|
149
194
|
}
|
|
@@ -166,19 +211,12 @@ export class InstantiationPlanCompiler {
|
|
|
166
211
|
return () => new invokable();
|
|
167
212
|
}
|
|
168
213
|
const depThunks = new Array<() => unknown>(params.length);
|
|
214
|
+
const depAncestors = [...ancestors, binding];
|
|
169
215
|
compileStack.add(binding.id);
|
|
170
216
|
try {
|
|
171
217
|
for (let index = 0; index < params.length; index += 1) {
|
|
172
|
-
const
|
|
173
|
-
if (
|
|
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) {
|
|
218
|
+
const thunk = this.#compileInjectionThunk(params[index]!, compileStack, depth, depAncestors);
|
|
219
|
+
if (thunk === PLAN_RETRY) {
|
|
182
220
|
return thunk;
|
|
183
221
|
}
|
|
184
222
|
depThunks[index] = thunk;
|
|
@@ -211,30 +249,44 @@ export class InstantiationPlanCompiler {
|
|
|
211
249
|
entry: InstantiationPlanDependencyEntry,
|
|
212
250
|
compileStack: Set<Binding["id"]>,
|
|
213
251
|
depth: number,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
252
|
+
ancestors: ReadonlyArray<Binding>,
|
|
253
|
+
): DependencyCompileResult {
|
|
254
|
+
const { binding } = entry;
|
|
255
|
+
if (binding.kind === "constant" && binding.onActivation === undefined) {
|
|
256
|
+
if (!this.#host.hasActivationHandlers(binding.token)) {
|
|
257
|
+
const value = binding.value;
|
|
258
|
+
return () => value;
|
|
219
259
|
}
|
|
220
|
-
const value = binding.value;
|
|
221
|
-
return () => value;
|
|
222
260
|
}
|
|
223
261
|
const scope = (binding as Binding & { scope: BindingScope }).scope ?? "transient";
|
|
224
262
|
if (scope === "singleton") {
|
|
225
|
-
// Cached-singleton read
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const
|
|
263
|
+
// Cached-singleton read; the first materialization escapes so it sees the same ancestors
|
|
264
|
+
// (and therefore the same cycle detection) the interpreted path would have built.
|
|
265
|
+
const escape = this.#compileEscapeThunk(binding.token, ancestors);
|
|
266
|
+
const singletonBinding = binding;
|
|
229
267
|
return () => {
|
|
230
|
-
const
|
|
231
|
-
return
|
|
268
|
+
const cached = singletonBinding.instance;
|
|
269
|
+
return cached === NO_INSTANCE ? escape() : cached;
|
|
232
270
|
};
|
|
233
271
|
}
|
|
234
|
-
if (
|
|
235
|
-
|
|
272
|
+
if (
|
|
273
|
+
scope === "transient" &&
|
|
274
|
+
binding.kind === "class" &&
|
|
275
|
+
depth < PLAN_DEPTH_LIMIT &&
|
|
276
|
+
!compileStack.has(binding.id)
|
|
277
|
+
) {
|
|
278
|
+
const inlined = this.#compileClassPlan(
|
|
279
|
+
binding as Binding & { kind: "class" },
|
|
280
|
+
compileStack,
|
|
281
|
+
depth + 1,
|
|
282
|
+
ancestors,
|
|
283
|
+
);
|
|
284
|
+
if (inlined !== null) {
|
|
285
|
+
return inlined;
|
|
286
|
+
}
|
|
236
287
|
}
|
|
237
|
-
//
|
|
238
|
-
|
|
288
|
+
// Anything opaque — a factory, a scoped binding, an activation hook, a class the compiler
|
|
289
|
+
// declined — runs on the runtime path, seeded with this plan's ancestors.
|
|
290
|
+
return this.#compileEscapeThunk(binding.token, ancestors);
|
|
239
291
|
}
|
|
240
292
|
}
|
|
@@ -9,20 +9,21 @@ import type { ActivationHandler, Constructor, DeactivationHandler, ResolutionCon
|
|
|
9
9
|
* @since 0.3.16-canary.0
|
|
10
10
|
*/
|
|
11
11
|
export class LifecycleManager {
|
|
12
|
-
// Container-level activation/deactivation hooks per token
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// Container-level activation/deactivation hooks per token — most containers register none, so
|
|
13
|
+
// both tables stay unallocated until the first hook arrives.
|
|
14
|
+
#activationHooks: Map<Token<unknown> | Constructor, Array<ActivationHandler<unknown>>> | undefined;
|
|
15
|
+
#deactivationHooks: Map<Token<unknown> | Constructor, Array<DeactivationHandler<unknown>>> | undefined;
|
|
15
16
|
#activationVersion = 0;
|
|
16
17
|
|
|
17
18
|
registerActivation<const Value>(token: Token<Value> | Constructor<Value>, handler: ActivationHandler<Value>): void {
|
|
18
19
|
this.#activationVersion += 1;
|
|
19
20
|
// ✓ TS6.0: Map.getOrInsert (ES2025)
|
|
20
|
-
const list = this.#activationHooks.getOrInsert(token as Token<unknown> | Constructor, []);
|
|
21
|
+
const list = (this.#activationHooks ??= new Map()).getOrInsert(token as Token<unknown> | Constructor, []);
|
|
21
22
|
list.push(handler as ActivationHandler<unknown>);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
hasActivationHandlers<const Value>(token: Token<Value> | Constructor<Value>): boolean {
|
|
25
|
-
if (this.#activationHooks
|
|
26
|
+
if (this.#activationHooks === undefined) {
|
|
26
27
|
return false;
|
|
27
28
|
}
|
|
28
29
|
const list = this.#activationHooks.get(token as Token<unknown> | Constructor);
|
|
@@ -37,14 +38,14 @@ export class LifecycleManager {
|
|
|
37
38
|
activationHandlersFor<const Value>(
|
|
38
39
|
token: Token<Value> | Constructor<Value>,
|
|
39
40
|
): ReadonlyArray<ActivationHandler<unknown>> | undefined {
|
|
40
|
-
return this.#activationHooks
|
|
41
|
+
return this.#activationHooks?.get(token as Token<unknown> | Constructor);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
registerDeactivation<const Value>(
|
|
44
45
|
token: Token<Value> | Constructor<Value>,
|
|
45
46
|
handler: DeactivationHandler<Value>,
|
|
46
47
|
): void {
|
|
47
|
-
const list = this.#deactivationHooks.getOrInsert(token as Token<unknown> | Constructor, []);
|
|
48
|
+
const list = (this.#deactivationHooks ??= new Map()).getOrInsert(token as Token<unknown> | Constructor, []);
|
|
48
49
|
list.push(handler as DeactivationHandler<unknown>);
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -79,7 +80,7 @@ export class LifecycleManager {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// 3. container-level onActivation
|
|
82
|
-
const containerHooks = this.#activationHooks
|
|
83
|
+
const containerHooks = this.#activationHooks?.get(binding.token as Token<unknown> | Constructor);
|
|
83
84
|
if (containerHooks !== undefined) {
|
|
84
85
|
for (const hook of containerHooks) {
|
|
85
86
|
const activationResult = hook(resolutionContext, activatedInstance);
|
|
@@ -125,7 +126,7 @@ export class LifecycleManager {
|
|
|
125
126
|
|
|
126
127
|
// 3. container-level onActivation (must be sync)
|
|
127
128
|
const tokenDisplayName = tokenName(binding.token);
|
|
128
|
-
const containerHooks = this.#activationHooks
|
|
129
|
+
const containerHooks = this.#activationHooks?.get(binding.token as Token<unknown> | Constructor);
|
|
129
130
|
if (containerHooks !== undefined) {
|
|
130
131
|
for (const hook of containerHooks) {
|
|
131
132
|
const activationResult = hook(resolutionContext, activatedInstance);
|
|
@@ -147,7 +148,7 @@ export class LifecycleManager {
|
|
|
147
148
|
const tokenKey = binding.token as Token<unknown> | Constructor;
|
|
148
149
|
|
|
149
150
|
// 1. container-level onDeactivation
|
|
150
|
-
const containerHooks = this.#deactivationHooks
|
|
151
|
+
const containerHooks = this.#deactivationHooks?.get(tokenKey);
|
|
151
152
|
if (containerHooks !== undefined) {
|
|
152
153
|
for (const hook of containerHooks) {
|
|
153
154
|
const hookResult = hook(instance);
|
|
@@ -187,7 +188,7 @@ export class LifecycleManager {
|
|
|
187
188
|
const tokenKey = binding.token as Token<unknown> | Constructor;
|
|
188
189
|
|
|
189
190
|
// 1. container-level onDeactivation
|
|
190
|
-
const containerHooks = this.#deactivationHooks
|
|
191
|
+
const containerHooks = this.#deactivationHooks?.get(tokenKey);
|
|
191
192
|
if (containerHooks !== undefined) {
|
|
192
193
|
for (const hook of containerHooks) {
|
|
193
194
|
const hookResult = hook(instance);
|
|
@@ -221,4 +222,8 @@ export class LifecycleManager {
|
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
}
|
|
225
|
+
/** Whether the deferred table behind `#activationHooks` has had to be built. */
|
|
226
|
+
get isBuilt(): boolean {
|
|
227
|
+
return this.#activationHooks !== undefined;
|
|
228
|
+
}
|
|
224
229
|
}
|
|
@@ -1,21 +1,11 @@
|
|
|
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
|
-
*/
|
|
1
|
+
/** Cycle-detection bookkeeping carried on the resolution path itself. */
|
|
8
2
|
import { CircularDependencyError } from "#/errors";
|
|
9
3
|
|
|
10
4
|
const RESOLUTION_SET_KEY: unique symbol = Symbol("di:resolution-set");
|
|
11
5
|
/**
|
|
12
6
|
* Where the cycle check switches from a linear `Array.includes` scan to an attached Set.
|
|
13
7
|
*
|
|
14
|
-
* @
|
|
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.
|
|
8
|
+
* @see `ARCHITECTURE.md` — the depth sweep this value comes from.
|
|
19
9
|
*
|
|
20
10
|
* @since 0.5.0-canary.7
|
|
21
11
|
*/
|
|
@@ -23,11 +13,9 @@ export const RESOLUTION_SET_THRESHOLD = 32;
|
|
|
23
13
|
type ResolutionPathWithSet = Array<string> & { [RESOLUTION_SET_KEY]?: Set<string> };
|
|
24
14
|
|
|
25
15
|
/**
|
|
26
|
-
*
|
|
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.
|
|
16
|
+
* Marks a token as in-flight on this path, throwing if it is already there.
|
|
29
17
|
*
|
|
30
|
-
*
|
|
18
|
+
* @remarks Unmark with `resolutionPath.pop()` plus `set?.delete(name)`, or {@link exitResolutionPath}.
|
|
31
19
|
*/
|
|
32
20
|
export function enterResolutionPath(
|
|
33
21
|
resolutionPath: Array<string>,
|
|
@@ -62,10 +50,7 @@ export function enterResolutionPath(
|
|
|
62
50
|
}
|
|
63
51
|
|
|
64
52
|
/**
|
|
65
|
-
* Unwinds the innermost
|
|
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.
|
|
53
|
+
* Unwinds the innermost {@link enterResolutionPath} entry, for callers that hold only the path.
|
|
69
54
|
*
|
|
70
55
|
* @since 0.5.0-canary.7
|
|
71
56
|
*/
|