@codefast/di 0.8.0 → 0.8.1
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 +13 -0
- package/README.md +242 -159
- package/dist/ambient/active-container.d.ts +1 -2
- package/dist/ambient/active-container.js +1 -2
- package/dist/container/binding-builders.d.ts +1 -2
- package/dist/container/binding-builders.js +1 -2
- package/dist/container/container.d.ts +1 -2
- package/dist/container/container.js +1 -2
- package/dist/core/binding-scope.d.ts +1 -2
- package/dist/core/binding-scope.js +1 -2
- package/dist/core/binding.d.ts +1 -2
- package/dist/core/binding.js +1 -2
- package/dist/core/constraint-requirement.d.ts +1 -2
- package/dist/core/constraint-requirement.js +1 -2
- package/dist/core/constructor-type.d.ts +1 -2
- package/dist/core/constructor-type.js +1 -2
- package/dist/core/map-upsert.d.ts +1 -2
- package/dist/core/map-upsert.js +1 -2
- package/dist/core/module.d.ts +1 -2
- package/dist/core/module.js +1 -2
- package/dist/core/registry.d.ts +1 -2
- package/dist/core/registry.js +1 -2
- package/dist/core/tag.d.ts +1 -2
- package/dist/core/tag.js +1 -2
- package/dist/core/token.d.ts +1 -2
- package/dist/core/token.js +1 -2
- package/dist/core/types.d.ts +1 -2
- package/dist/core/types.js +1 -2
- package/dist/decorators/inject.d.ts +1 -2
- package/dist/decorators/inject.js +1 -2
- package/dist/decorators/injectable.d.ts +1 -2
- package/dist/decorators/injectable.js +1 -2
- package/dist/decorators/lifecycle-decorators.d.ts +1 -2
- package/dist/decorators/lifecycle-decorators.js +1 -2
- package/dist/errors/diagnostics.d.ts +1 -2
- package/dist/errors/diagnostics.js +1 -2
- package/dist/errors/errors.d.ts +1 -2
- package/dist/errors/errors.js +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/injection/descriptor.d.ts +1 -2
- package/dist/injection/descriptor.js +1 -2
- package/dist/injection/resolve-options.d.ts +1 -2
- package/dist/injection/resolve-options.js +1 -2
- package/dist/introspection/dependency-graph.d.ts +1 -2
- package/dist/introspection/dependency-graph.js +1 -2
- package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -2
- package/dist/introspection/graph-adapters/cytoscape.js +1 -2
- package/dist/introspection/graph-adapters/dot.d.ts +1 -2
- package/dist/introspection/graph-adapters/dot.js +1 -2
- package/dist/introspection/graph-adapters/mermaid.d.ts +1 -2
- package/dist/introspection/graph-adapters/mermaid.js +1 -2
- package/dist/introspection/graph-adapters/reactflow.d.ts +1 -2
- package/dist/introspection/graph-adapters/reactflow.js +1 -2
- package/dist/introspection/inspector.d.ts +1 -2
- package/dist/introspection/inspector.js +1 -2
- package/dist/lifecycle/lifecycle-manager.d.ts +1 -2
- package/dist/lifecycle/lifecycle-manager.js +1 -2
- package/dist/lifecycle/scope-manager.d.ts +1 -2
- package/dist/lifecycle/scope-manager.js +1 -2
- package/dist/metadata/metadata-keys.d.ts +1 -2
- package/dist/metadata/metadata-keys.js +1 -2
- package/dist/metadata/metadata-reader-token.d.ts +1 -2
- package/dist/metadata/metadata-reader-token.js +1 -2
- package/dist/metadata/metadata-types.d.ts +1 -2
- package/dist/metadata/metadata-types.js +1 -2
- package/dist/metadata/symbol-metadata-reader.d.ts +1 -2
- package/dist/metadata/symbol-metadata-reader.js +1 -2
- package/dist/metadata/verifying-metadata-reader.d.ts +1 -2
- package/dist/metadata/verifying-metadata-reader.js +1 -2
- package/dist/resolution/cache/activation-need.d.ts +1 -2
- package/dist/resolution/cache/activation-need.js +1 -2
- package/dist/resolution/cache/binding-lookup-cache.d.ts +1 -2
- package/dist/resolution/cache/binding-lookup-cache.js +1 -2
- package/dist/resolution/cache/class-introspector.d.ts +1 -2
- package/dist/resolution/cache/class-introspector.js +1 -2
- package/dist/resolution/context.d.ts +1 -2
- package/dist/resolution/context.js +1 -2
- package/dist/resolution/path/resolution-path.d.ts +1 -2
- package/dist/resolution/path/resolution-path.js +1 -2
- package/dist/resolution/plan/instantiation-plan.d.ts +1 -2
- package/dist/resolution/plan/instantiation-plan.js +1 -2
- package/dist/resolution/resolver.d.ts +1 -2
- package/dist/resolution/resolver.js +1 -2
- package/dist/resolution/select/binding-select.d.ts +1 -2
- package/dist/resolution/select/binding-select.js +1 -2
- package/dist/resolution/select/constraints.d.ts +1 -2
- package/dist/resolution/select/constraints.js +1 -2
- package/package.json +6 -58
- package/dist/ambient/active-container.d.ts.map +0 -1
- package/dist/ambient/active-container.js.map +0 -1
- package/dist/container/binding-builders.d.ts.map +0 -1
- package/dist/container/binding-builders.js.map +0 -1
- package/dist/container/container.d.ts.map +0 -1
- package/dist/container/container.js.map +0 -1
- package/dist/core/binding-scope.d.ts.map +0 -1
- package/dist/core/binding-scope.js.map +0 -1
- package/dist/core/binding.d.ts.map +0 -1
- package/dist/core/binding.js.map +0 -1
- package/dist/core/constraint-requirement.d.ts.map +0 -1
- package/dist/core/constraint-requirement.js.map +0 -1
- package/dist/core/constructor-type.d.ts.map +0 -1
- package/dist/core/constructor-type.js.map +0 -1
- package/dist/core/map-upsert.d.ts.map +0 -1
- package/dist/core/map-upsert.js.map +0 -1
- package/dist/core/module.d.ts.map +0 -1
- package/dist/core/module.js.map +0 -1
- package/dist/core/registry.d.ts.map +0 -1
- package/dist/core/registry.js.map +0 -1
- package/dist/core/tag.d.ts.map +0 -1
- package/dist/core/tag.js.map +0 -1
- package/dist/core/token.d.ts.map +0 -1
- package/dist/core/token.js.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js.map +0 -1
- package/dist/decorators/inject.d.ts.map +0 -1
- package/dist/decorators/inject.js.map +0 -1
- package/dist/decorators/injectable.d.ts.map +0 -1
- package/dist/decorators/injectable.js.map +0 -1
- package/dist/decorators/lifecycle-decorators.d.ts.map +0 -1
- package/dist/decorators/lifecycle-decorators.js.map +0 -1
- package/dist/errors/diagnostics.d.ts.map +0 -1
- package/dist/errors/diagnostics.js.map +0 -1
- package/dist/errors/errors.d.ts.map +0 -1
- package/dist/errors/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/injection/descriptor.d.ts.map +0 -1
- package/dist/injection/descriptor.js.map +0 -1
- package/dist/injection/resolve-options.d.ts.map +0 -1
- package/dist/injection/resolve-options.js.map +0 -1
- package/dist/introspection/dependency-graph.d.ts.map +0 -1
- package/dist/introspection/dependency-graph.js.map +0 -1
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +0 -1
- package/dist/introspection/graph-adapters/cytoscape.js.map +0 -1
- package/dist/introspection/graph-adapters/dot.d.ts.map +0 -1
- package/dist/introspection/graph-adapters/dot.js.map +0 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts.map +0 -1
- package/dist/introspection/graph-adapters/mermaid.js.map +0 -1
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +0 -1
- package/dist/introspection/graph-adapters/reactflow.js.map +0 -1
- package/dist/introspection/inspector.d.ts.map +0 -1
- package/dist/introspection/inspector.js.map +0 -1
- package/dist/lifecycle/lifecycle-manager.d.ts.map +0 -1
- package/dist/lifecycle/lifecycle-manager.js.map +0 -1
- package/dist/lifecycle/scope-manager.d.ts.map +0 -1
- package/dist/lifecycle/scope-manager.js.map +0 -1
- package/dist/metadata/metadata-keys.d.ts.map +0 -1
- package/dist/metadata/metadata-keys.js.map +0 -1
- package/dist/metadata/metadata-reader-token.d.ts.map +0 -1
- package/dist/metadata/metadata-reader-token.js.map +0 -1
- package/dist/metadata/metadata-types.d.ts.map +0 -1
- package/dist/metadata/metadata-types.js.map +0 -1
- package/dist/metadata/symbol-metadata-reader.d.ts.map +0 -1
- package/dist/metadata/symbol-metadata-reader.js.map +0 -1
- package/dist/metadata/verifying-metadata-reader.d.ts.map +0 -1
- package/dist/metadata/verifying-metadata-reader.js.map +0 -1
- package/dist/resolution/cache/activation-need.d.ts.map +0 -1
- package/dist/resolution/cache/activation-need.js.map +0 -1
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +0 -1
- package/dist/resolution/cache/binding-lookup-cache.js.map +0 -1
- package/dist/resolution/cache/class-introspector.d.ts.map +0 -1
- package/dist/resolution/cache/class-introspector.js.map +0 -1
- package/dist/resolution/context.d.ts.map +0 -1
- package/dist/resolution/context.js.map +0 -1
- package/dist/resolution/path/resolution-path.d.ts.map +0 -1
- package/dist/resolution/path/resolution-path.js.map +0 -1
- package/dist/resolution/plan/instantiation-plan.d.ts.map +0 -1
- package/dist/resolution/plan/instantiation-plan.js.map +0 -1
- package/dist/resolution/resolver.d.ts.map +0 -1
- package/dist/resolution/resolver.js.map +0 -1
- package/dist/resolution/select/binding-select.d.ts.map +0 -1
- package/dist/resolution/select/binding-select.js.map +0 -1
- package/dist/resolution/select/constraints.d.ts.map +0 -1
- package/dist/resolution/select/constraints.js.map +0 -1
- package/src/ambient/active-container.ts +0 -69
- package/src/container/binding-builders.ts +0 -337
- package/src/container/container.ts +0 -972
- package/src/core/binding-scope.ts +0 -15
- package/src/core/binding.ts +0 -505
- package/src/core/constraint-requirement.ts +0 -94
- package/src/core/constructor-type.ts +0 -18
- package/src/core/map-upsert.ts +0 -53
- package/src/core/module.ts +0 -117
- package/src/core/registry.ts +0 -319
- package/src/core/tag.ts +0 -212
- package/src/core/token.ts +0 -31
- package/src/core/types.ts +0 -149
- package/src/decorators/inject.ts +0 -101
- package/src/decorators/injectable.ts +0 -134
- package/src/decorators/lifecycle-decorators.ts +0 -43
- package/src/errors/diagnostics.ts +0 -45
- package/src/errors/errors.ts +0 -499
- package/src/index.ts +0 -139
- package/src/injection/descriptor.ts +0 -218
- package/src/injection/resolve-options.ts +0 -201
- package/src/introspection/dependency-graph.ts +0 -296
- package/src/introspection/graph-adapters/cytoscape.ts +0 -78
- package/src/introspection/graph-adapters/dot.ts +0 -32
- package/src/introspection/graph-adapters/mermaid.ts +0 -61
- package/src/introspection/graph-adapters/reactflow.ts +0 -84
- package/src/introspection/inspector.ts +0 -134
- package/src/lifecycle/lifecycle-manager.ts +0 -272
- package/src/lifecycle/scope-manager.ts +0 -158
- package/src/metadata/metadata-keys.ts +0 -28
- package/src/metadata/metadata-reader-token.ts +0 -10
- package/src/metadata/metadata-types.ts +0 -57
- package/src/metadata/symbol-metadata-reader.ts +0 -126
- package/src/metadata/verifying-metadata-reader.ts +0 -41
- package/src/resolution/cache/activation-need.ts +0 -87
- package/src/resolution/cache/binding-lookup-cache.ts +0 -169
- package/src/resolution/cache/class-introspector.ts +0 -256
- package/src/resolution/context.ts +0 -331
- package/src/resolution/path/resolution-path.ts +0 -167
- package/src/resolution/plan/instantiation-plan.ts +0 -592
- package/src/resolution/resolver.ts +0 -1654
- package/src/resolution/select/binding-select.ts +0 -207
- package/src/resolution/select/constraints.ts +0 -146
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import type { Binding } from "#/core/binding";
|
|
2
|
-
import { getOrInsert } from "#/core/map-upsert";
|
|
3
|
-
import type { Token } from "#/core/token";
|
|
4
|
-
import { tokenName } from "#/core/token";
|
|
5
|
-
import type {
|
|
6
|
-
ActivationHandler,
|
|
7
|
-
Constructor,
|
|
8
|
-
DeactivationHandler,
|
|
9
|
-
DependencyKey,
|
|
10
|
-
ResolutionContext,
|
|
11
|
-
} from "#/core/types";
|
|
12
|
-
import { AsyncActivationError, AsyncDeactivationError, InvalidMetadataError } from "#/errors/errors";
|
|
13
|
-
import type { MetadataReader } from "#/metadata/metadata-types";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* One container's registry of container-level activation and deactivation hooks, keyed by token.
|
|
17
|
-
*
|
|
18
|
-
* @since 0.3.16-canary.0
|
|
19
|
-
*/
|
|
20
|
-
export class LifecycleManager {
|
|
21
|
-
// Container-level activation/deactivation hooks per token — most containers register none, so
|
|
22
|
-
// both tables stay unallocated until the first hook arrives.
|
|
23
|
-
#activationHooks: Map<DependencyKey, Array<ActivationHandler<unknown>>> | undefined;
|
|
24
|
-
#deactivationHooks: Map<DependencyKey, Array<DeactivationHandler<unknown>>> | undefined;
|
|
25
|
-
#activationVersion = 0;
|
|
26
|
-
|
|
27
|
-
// One-entry cache in front of the map: a resolve loop asks about the same token over and over,
|
|
28
|
-
// and registration is the only thing that can change the answer.
|
|
29
|
-
#cachedToken: Token<unknown> | Constructor | undefined;
|
|
30
|
-
#cachedHooks: Array<ActivationHandler<unknown>> | undefined;
|
|
31
|
-
|
|
32
|
-
registerActivation<Value>(token: Token<Value> | Constructor<Value>, handler: ActivationHandler<Value>): void {
|
|
33
|
-
this.#activationVersion += 1;
|
|
34
|
-
this.#cachedToken = undefined;
|
|
35
|
-
this.#cachedHooks = undefined;
|
|
36
|
-
this.#activationHooks ??= new Map();
|
|
37
|
-
const list = getOrInsert(this.#activationHooks, token, []);
|
|
38
|
-
list.push(handler as ActivationHandler<unknown>);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
hasActivationHandlers<Value>(token: Token<Value> | Constructor<Value>): boolean {
|
|
42
|
-
const list = this.activationHandlersFor(token);
|
|
43
|
-
return list !== undefined && list.length > 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get activationVersion(): number {
|
|
47
|
-
return this.#activationVersion;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Container-level activation handlers for a token — hot-path accessor, no copies. */
|
|
51
|
-
activationHandlersFor<Value>(
|
|
52
|
-
token: Token<Value> | Constructor<Value>,
|
|
53
|
-
): ReadonlyArray<ActivationHandler<unknown>> | undefined {
|
|
54
|
-
const hooks = this.#activationHooks;
|
|
55
|
-
if (hooks === undefined) {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
const key: DependencyKey = token;
|
|
59
|
-
if (key === this.#cachedToken) {
|
|
60
|
-
return this.#cachedHooks;
|
|
61
|
-
}
|
|
62
|
-
const list = hooks.get(key);
|
|
63
|
-
this.#cachedToken = key;
|
|
64
|
-
this.#cachedHooks = list;
|
|
65
|
-
return list;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
registerDeactivation<Value>(token: Token<Value> | Constructor<Value>, handler: DeactivationHandler<Value>): void {
|
|
69
|
-
this.#deactivationHooks ??= new Map();
|
|
70
|
-
const list = getOrInsert(this.#deactivationHooks, token, []);
|
|
71
|
-
list.push(handler as DeactivationHandler<unknown>);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Asked only while tearing down, so it reads the map directly rather than caching like activation. */
|
|
75
|
-
hasDeactivationHandlers<Value>(token: Token<Value> | Constructor<Value>): boolean {
|
|
76
|
-
const list = this.#deactivationHooks?.get(token);
|
|
77
|
-
return list !== undefined && list.length > 0;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Every token carrying a container-level hook, paired with the phase that registered it. */
|
|
81
|
-
hookedTokens(): Array<[DependencyKey, "onActivation" | "onDeactivation"]> {
|
|
82
|
-
const hooked: Array<[DependencyKey, "onActivation" | "onDeactivation"]> = [];
|
|
83
|
-
for (const key of this.#activationHooks?.keys() ?? []) {
|
|
84
|
-
hooked.push([key, "onActivation"]);
|
|
85
|
-
}
|
|
86
|
-
for (const key of this.#deactivationHooks?.keys() ?? []) {
|
|
87
|
-
hooked.push([key, "onDeactivation"]);
|
|
88
|
-
}
|
|
89
|
-
return hooked;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async runActivation<Value>(
|
|
93
|
-
resolutionContext: ResolutionContext,
|
|
94
|
-
binding: Binding<Value>,
|
|
95
|
-
instance: Value,
|
|
96
|
-
metadataReader: MetadataReader,
|
|
97
|
-
): Promise<Value> {
|
|
98
|
-
let activatedInstance: Value = instance;
|
|
99
|
-
|
|
100
|
-
// 1. @postConstruct() — after TC39 construction (constructor + accessor addInitializer callbacks)
|
|
101
|
-
for (const methodName of lifecycleMethods(binding, metadataReader, "postConstruct")) {
|
|
102
|
-
const hookResult = callHook(activatedInstance, methodName);
|
|
103
|
-
if (hookResult instanceof Promise) {
|
|
104
|
-
await hookResult;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 2. per-binding onActivation
|
|
109
|
-
if (binding.kind !== "alias" && binding.onActivation !== undefined) {
|
|
110
|
-
const activationResult = binding.onActivation(resolutionContext, activatedInstance);
|
|
111
|
-
activatedInstance = activationResult instanceof Promise ? await activationResult : activationResult;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// 3. container-level onActivation
|
|
115
|
-
const containerHooks = this.#activationHooks?.get(binding.token);
|
|
116
|
-
if (containerHooks !== undefined) {
|
|
117
|
-
for (const hook of containerHooks) {
|
|
118
|
-
const activationResult = hook(resolutionContext, activatedInstance);
|
|
119
|
-
activatedInstance = (activationResult instanceof Promise ? await activationResult : activationResult) as Value;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return activatedInstance;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
runActivationSync<Value>(
|
|
127
|
-
resolutionContext: ResolutionContext,
|
|
128
|
-
binding: Binding<Value>,
|
|
129
|
-
instance: Value,
|
|
130
|
-
metadataReader: MetadataReader,
|
|
131
|
-
): Value {
|
|
132
|
-
let activatedInstance: Value = instance;
|
|
133
|
-
|
|
134
|
-
// 1. @postConstruct() — must be sync (instance fully constructed per TC39 order)
|
|
135
|
-
for (const methodName of lifecycleMethods(binding, metadataReader, "postConstruct")) {
|
|
136
|
-
if (callHook(activatedInstance, methodName) instanceof Promise) {
|
|
137
|
-
throw new AsyncActivationError(tokenName(binding.token), "postConstruct", methodName);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 2. per-binding onActivation (must be sync)
|
|
142
|
-
if (binding.kind !== "alias" && binding.onActivation !== undefined) {
|
|
143
|
-
const activationResult = binding.onActivation(resolutionContext, activatedInstance);
|
|
144
|
-
if (activationResult instanceof Promise) {
|
|
145
|
-
throw new AsyncActivationError(tokenName(binding.token), "onActivation");
|
|
146
|
-
}
|
|
147
|
-
activatedInstance = activationResult;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 3. container-level onActivation (must be sync)
|
|
151
|
-
const tokenDisplayName = tokenName(binding.token);
|
|
152
|
-
const containerHooks = this.#activationHooks?.get(binding.token);
|
|
153
|
-
if (containerHooks !== undefined) {
|
|
154
|
-
for (const hook of containerHooks) {
|
|
155
|
-
const activationResult = hook(resolutionContext, activatedInstance);
|
|
156
|
-
if (activationResult instanceof Promise) {
|
|
157
|
-
throw new AsyncActivationError(tokenDisplayName, "onActivation");
|
|
158
|
-
}
|
|
159
|
-
activatedInstance = activationResult as Value;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return activatedInstance;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async runDeactivation<Value>(
|
|
167
|
-
binding: Binding<Value>,
|
|
168
|
-
instance: Value,
|
|
169
|
-
metadataReader: MetadataReader,
|
|
170
|
-
): Promise<void> {
|
|
171
|
-
const tokenKey: DependencyKey = binding.token;
|
|
172
|
-
|
|
173
|
-
// 1. container-level onDeactivation
|
|
174
|
-
const containerHooks = this.#deactivationHooks?.get(tokenKey);
|
|
175
|
-
if (containerHooks !== undefined) {
|
|
176
|
-
for (const hook of containerHooks) {
|
|
177
|
-
const hookResult = hook(instance);
|
|
178
|
-
if (hookResult instanceof Promise) {
|
|
179
|
-
await hookResult;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 2. per-binding onDeactivation
|
|
185
|
-
if (binding.kind !== "alias" && binding.onDeactivation !== undefined) {
|
|
186
|
-
const hookResult = binding.onDeactivation(instance);
|
|
187
|
-
if (hookResult instanceof Promise) {
|
|
188
|
-
await hookResult;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 3. @preDestroy() — all methods in declaration order
|
|
193
|
-
for (const methodName of lifecycleMethods(binding, metadataReader, "preDestroy")) {
|
|
194
|
-
const hookResult = callHook(instance, methodName);
|
|
195
|
-
if (hookResult instanceof Promise) {
|
|
196
|
-
await hookResult;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
runDeactivationSync<Value>(binding: Binding<Value>, instance: Value, metadataReader: MetadataReader): void {
|
|
202
|
-
const tokenDisplayName = tokenName(binding.token);
|
|
203
|
-
const tokenKey: DependencyKey = binding.token;
|
|
204
|
-
|
|
205
|
-
// 1. container-level onDeactivation
|
|
206
|
-
const containerHooks = this.#deactivationHooks?.get(tokenKey);
|
|
207
|
-
if (containerHooks !== undefined) {
|
|
208
|
-
for (const hook of containerHooks) {
|
|
209
|
-
const hookResult = hook(instance);
|
|
210
|
-
if (hookResult instanceof Promise) {
|
|
211
|
-
throw new AsyncDeactivationError(tokenDisplayName);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 2. per-binding onDeactivation
|
|
217
|
-
if (binding.kind !== "alias" && binding.onDeactivation !== undefined) {
|
|
218
|
-
const hookResult = binding.onDeactivation(instance);
|
|
219
|
-
if (hookResult instanceof Promise) {
|
|
220
|
-
throw new AsyncDeactivationError(tokenDisplayName);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// 3. @preDestroy()
|
|
225
|
-
for (const methodName of lifecycleMethods(binding, metadataReader, "preDestroy")) {
|
|
226
|
-
if (callHook(instance, methodName) instanceof Promise) {
|
|
227
|
-
throw new AsyncDeactivationError(tokenDisplayName);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/** Whether the deferred activation-hook table has had to be built. */
|
|
233
|
-
get isActivationTableBuilt(): boolean {
|
|
234
|
-
return this.#activationHooks !== undefined;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const NO_METHODS: ReadonlyArray<string> = [];
|
|
239
|
-
|
|
240
|
-
/** The `@postConstruct` / `@preDestroy` methods a binding declares — only a class can declare any. */
|
|
241
|
-
function lifecycleMethods<Value>(
|
|
242
|
-
binding: Binding<Value>,
|
|
243
|
-
metadataReader: MetadataReader,
|
|
244
|
-
phase: "postConstruct" | "preDestroy",
|
|
245
|
-
): ReadonlyArray<string> {
|
|
246
|
-
if (binding.kind !== "class") {
|
|
247
|
-
return NO_METHODS;
|
|
248
|
-
}
|
|
249
|
-
return metadataReader.getLifecycleMetadata(binding.target)?.[phase] ?? NO_METHODS;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Invokes a lifecycle hook by name.
|
|
254
|
-
*
|
|
255
|
-
* @remarks A name that is not a method can only come from a {@link MetadataReader} that answered
|
|
256
|
-
* wrongly, so it is reported rather than skipped — a hook that never runs is the failure a caller
|
|
257
|
-
* cannot see. A non-object instance has no hooks to run and is not an error.
|
|
258
|
-
*/
|
|
259
|
-
function callHook(instance: unknown, methodName: string): unknown {
|
|
260
|
-
if (typeof instance !== "object" || instance === null) {
|
|
261
|
-
return undefined;
|
|
262
|
-
}
|
|
263
|
-
const method: unknown = Reflect.get(instance, methodName);
|
|
264
|
-
if (typeof method !== "function") {
|
|
265
|
-
// The class is derived here rather than passed in, so the happy path carries no extra argument.
|
|
266
|
-
const constructor: unknown = Reflect.get(instance, "constructor");
|
|
267
|
-
const className = typeof constructor === "function" && constructor.name !== "" ? constructor.name : "(anonymous)";
|
|
268
|
-
throw new InvalidMetadataError(className, `lifecycle method '${methodName}' is not a method on the instance`);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return method.call(instance);
|
|
272
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import type { Binding } from "#/core/binding";
|
|
2
|
-
import { NO_INSTANCE } from "#/core/binding";
|
|
3
|
-
import { tokenName } from "#/core/token";
|
|
4
|
-
import type { BindingIdentifier } from "#/core/types";
|
|
5
|
-
import { MissingScopeContextError } from "#/errors/errors";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* One container's instance caches — singletons, in-flight async creations, and the scoped cache.
|
|
9
|
-
*
|
|
10
|
-
* @since 0.3.16-canary.0
|
|
11
|
-
*/
|
|
12
|
-
export class ScopeManager {
|
|
13
|
-
// Instances live on their binding; this list only lets disposal and `inspect()` enumerate them.
|
|
14
|
-
#singletonBindings: Array<Binding<unknown>> | undefined;
|
|
15
|
-
// In-flight promises for async singleton creation — only an async resolve ever needs it.
|
|
16
|
-
#inflight: Map<BindingIdentifier, Promise<unknown>> | undefined;
|
|
17
|
-
// Scoped cache — only a child container resolving a `scoped` binding ever needs it.
|
|
18
|
-
#scoped: Map<BindingIdentifier, unknown> | undefined;
|
|
19
|
-
// Set once by the owning container's dispose — refuses new materializations into torn-down state.
|
|
20
|
-
#closed = false;
|
|
21
|
-
|
|
22
|
-
readonly isChild: boolean;
|
|
23
|
-
|
|
24
|
-
constructor(isChild = false) {
|
|
25
|
-
this.isChild = isChild;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
get isClosed(): boolean {
|
|
29
|
-
return this.#closed;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
markClosed(): void {
|
|
33
|
-
this.#closed = true;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Awaits every in-flight async materialization, so teardown deactivates what they produce. */
|
|
37
|
-
async settleInflight(): Promise<void> {
|
|
38
|
-
let previousSize = -1;
|
|
39
|
-
while (this.#inflight !== undefined && this.#inflight.size > 0 && this.#inflight.size !== previousSize) {
|
|
40
|
-
previousSize = this.#inflight.size;
|
|
41
|
-
await Promise.allSettled(this.#inflight.values());
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
setSingleton<Value>(binding: Binding<Value>, instance: unknown): void {
|
|
46
|
-
if (binding.instance === NO_INSTANCE) {
|
|
47
|
-
(this.#singletonBindings ??= []).push(binding as Binding<unknown>);
|
|
48
|
-
}
|
|
49
|
-
binding.instance = instance;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Every binding in this container holding a cached singleton. */
|
|
53
|
-
cachedSingletons(): ReadonlyArray<Binding<unknown>> {
|
|
54
|
-
return this.#singletonBindings ?? EMPTY_BINDINGS;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
deleteSingleton<Value>(binding: Binding<Value>): boolean {
|
|
58
|
-
if (binding.instance === NO_INSTANCE) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
binding.instance = NO_INSTANCE;
|
|
62
|
-
const tracked = this.#singletonBindings;
|
|
63
|
-
if (tracked !== undefined) {
|
|
64
|
-
const index = tracked.indexOf(binding as Binding<unknown>);
|
|
65
|
-
if (index !== -1) {
|
|
66
|
-
tracked.splice(index, 1);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Swaps a re-slotted binding's tracked entry, so teardown pairs the instance with the live object. */
|
|
73
|
-
replaceSingleton(previous: Binding, next: Binding): void {
|
|
74
|
-
const tracked = this.#singletonBindings;
|
|
75
|
-
if (tracked === undefined) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const index = tracked.indexOf(previous as Binding<unknown>);
|
|
79
|
-
if (index !== -1) {
|
|
80
|
-
tracked[index] = next as Binding<unknown>;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
getInflight(id: BindingIdentifier): Promise<unknown> | undefined {
|
|
85
|
-
return this.#inflight?.get(id);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
setInflight(id: BindingIdentifier, promise: Promise<unknown>): void {
|
|
89
|
-
(this.#inflight ??= new Map<BindingIdentifier, Promise<unknown>>()).set(id, promise);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
clearInflight(id: BindingIdentifier): void {
|
|
93
|
-
this.#inflight?.delete(id);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* The cached scoped instance, or {@link SCOPED_MISS}.
|
|
98
|
-
*
|
|
99
|
-
* @remarks One map read answers both existence and value; a cached `undefined` is the only
|
|
100
|
-
* shape that pays for a second, and it is the rare one.
|
|
101
|
-
*/
|
|
102
|
-
readScoped(id: BindingIdentifier): unknown {
|
|
103
|
-
const scoped = this.#scoped;
|
|
104
|
-
if (scoped === undefined) {
|
|
105
|
-
return SCOPED_MISS;
|
|
106
|
-
}
|
|
107
|
-
const cached = scoped.get(id);
|
|
108
|
-
if (cached !== undefined) {
|
|
109
|
-
return cached;
|
|
110
|
-
}
|
|
111
|
-
return scoped.has(id) ? undefined : SCOPED_MISS;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Takes the binding rather than its id, so a failure here can name the token — as `setSingleton` does. */
|
|
115
|
-
setScoped(binding: Binding, instance: unknown): void {
|
|
116
|
-
if (!this.isChild) {
|
|
117
|
-
throw new MissingScopeContextError(tokenName(binding.token));
|
|
118
|
-
}
|
|
119
|
-
(this.#scoped ??= new Map<BindingIdentifier, unknown>()).set(binding.id, instance);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Releases a removed binding's scoped instance. A scoped instance has no deactivation. */
|
|
123
|
-
deleteScoped(id: BindingIdentifier): void {
|
|
124
|
-
this.#scoped?.delete(id);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** Scoped instances currently cached — a structural count for diagnostics. */
|
|
128
|
-
get scopedCount(): number {
|
|
129
|
-
return this.#scoped?.size ?? 0;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
clearAll(): void {
|
|
133
|
-
const tracked = this.#singletonBindings;
|
|
134
|
-
if (tracked !== undefined) {
|
|
135
|
-
for (const binding of tracked) {
|
|
136
|
-
binding.instance = NO_INSTANCE;
|
|
137
|
-
}
|
|
138
|
-
tracked.length = 0;
|
|
139
|
-
}
|
|
140
|
-
this.#inflight?.clear();
|
|
141
|
-
this.#scoped?.clear();
|
|
142
|
-
}
|
|
143
|
-
/** Whether the deferred scoped-instance cache has had to be built. */
|
|
144
|
-
get isScopedCacheBuilt(): boolean {
|
|
145
|
-
return this.#scoped !== undefined;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const EMPTY_BINDINGS: ReadonlyArray<Binding<unknown>> = [];
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Absent scoped entry — distinguishes it from a cached `undefined`.
|
|
153
|
-
*
|
|
154
|
-
* @remarks A `unique symbol`, so no value a caller could cache can ever equal it.
|
|
155
|
-
*
|
|
156
|
-
* @since 0.6.0
|
|
157
|
-
*/
|
|
158
|
-
export const SCOPED_MISS: unique symbol = Symbol("di:scoped-miss");
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The metadata key `@injectable` writes a class's constructor dependencies under.
|
|
3
|
-
*
|
|
4
|
-
* @since 0.3.16-canary.0
|
|
5
|
-
*/
|
|
6
|
-
export const INJECTABLE_KEY: unique symbol = Symbol("di:injectable");
|
|
7
|
-
/**
|
|
8
|
-
* The metadata key `@postConstruct` and `@preDestroy` record method names under.
|
|
9
|
-
*
|
|
10
|
-
* @since 0.3.16-canary.0
|
|
11
|
-
*/
|
|
12
|
-
export const LIFECYCLE_KEY: unique symbol = Symbol("di:lifecycle");
|
|
13
|
-
/**
|
|
14
|
-
* The metadata key `@inject` accessors record their injection descriptors under.
|
|
15
|
-
*
|
|
16
|
-
* @since 0.3.16-canary.0
|
|
17
|
-
*/
|
|
18
|
-
export const INJECT_ACCESSOR_KEY: unique symbol = Symbol("di:inject-accessor");
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The symbol TC39 Stage 3 decorator transforms store class metadata under.
|
|
22
|
-
*
|
|
23
|
-
* @remarks Falls back to the global-registry symbol, which is what Babel and esbuild emit until
|
|
24
|
-
* a runtime ships `Symbol.metadata` natively.
|
|
25
|
-
*
|
|
26
|
-
* @since 0.3.16-canary.0
|
|
27
|
-
*/
|
|
28
|
-
export const METADATA_SYMBOL: symbol = Symbol.metadata ?? Symbol.for("Symbol.metadata");
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { token } from "#/core/token";
|
|
2
|
-
import type { Token } from "#/core/token";
|
|
3
|
-
import type { MetadataReader } from "#/metadata/metadata-types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The token a custom `MetadataReader` is bound to for a container to pick up.
|
|
7
|
-
*
|
|
8
|
-
* @since 0.3.16-canary.0
|
|
9
|
-
*/
|
|
10
|
-
export const MetadataReaderToken: Token<MetadataReader> = token<MetadataReader>("MetadataReader");
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { Constructor } from "#/core/types";
|
|
2
|
-
import type { InjectionDescriptor } from "#/injection/descriptor";
|
|
3
|
-
import type { DependencySlot } from "#/injection/resolve-options";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* One constructor parameter's declaration.
|
|
7
|
-
*
|
|
8
|
-
* @remarks Extends {@link DependencySlot} so the two dependency sources stay literally one shape
|
|
9
|
-
* rather than two that happen to match; `index` is the only thing a parameter adds.
|
|
10
|
-
*
|
|
11
|
-
* @since 0.3.16-canary.0
|
|
12
|
-
*/
|
|
13
|
-
export interface ParamMetadata extends DependencySlot {
|
|
14
|
-
readonly index: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A class's declared constructor dependencies, as a `MetadataReader` reports them.
|
|
19
|
-
*
|
|
20
|
-
* @since 0.3.16-canary.0
|
|
21
|
-
*/
|
|
22
|
-
export interface ConstructorMetadata {
|
|
23
|
-
readonly params: ReadonlyArray<ParamMetadata>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The `postConstruct` and `preDestroy` method names a class declares.
|
|
28
|
-
*
|
|
29
|
-
* @since 0.3.16-canary.0
|
|
30
|
-
*/
|
|
31
|
-
export interface LifecycleMetadata {
|
|
32
|
-
readonly postConstruct: ReadonlyArray<string>;
|
|
33
|
-
readonly preDestroy: ReadonlyArray<string>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Mutable buckets used while aggregating decorator metadata (same keys as {@link LifecycleMetadata}).
|
|
38
|
-
*
|
|
39
|
-
* @since 0.3.16-canary.0
|
|
40
|
-
*/
|
|
41
|
-
export interface MutableLifecycleMetadata {
|
|
42
|
-
postConstruct: Array<string>;
|
|
43
|
-
preDestroy: Array<string>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* The port a container reads class metadata through — constructor deps, lifecycle, and accessors.
|
|
48
|
-
*
|
|
49
|
-
* @since 0.3.16-canary.0
|
|
50
|
-
*/
|
|
51
|
-
export interface MetadataReader {
|
|
52
|
-
getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
|
|
53
|
-
getLifecycleMetadata(target: Constructor): LifecycleMetadata | undefined;
|
|
54
|
-
getAccessorMetadata?(
|
|
55
|
-
target: Constructor,
|
|
56
|
-
): ReadonlyArray<{ readonly key: string | symbol; readonly descriptor: InjectionDescriptor }> | undefined;
|
|
57
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import type { Constructor } from "#/core/types";
|
|
2
|
-
import type { InjectionDescriptor } from "#/injection/descriptor";
|
|
3
|
-
import { INJECT_ACCESSOR_KEY, INJECTABLE_KEY, LIFECYCLE_KEY, METADATA_SYMBOL } from "#/metadata/metadata-keys";
|
|
4
|
-
import type { ConstructorMetadata, LifecycleMetadata, MetadataReader } from "#/metadata/metadata-types";
|
|
5
|
-
|
|
6
|
-
type AccessorEntry = { readonly key: string | symbol; readonly descriptor: InjectionDescriptor };
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* The default `MetadataReader`, reading decorator metadata stored under `Symbol.metadata`.
|
|
10
|
-
*
|
|
11
|
-
* @since 0.3.16-canary.0
|
|
12
|
-
*/
|
|
13
|
-
export class SymbolMetadataReader implements MetadataReader {
|
|
14
|
-
// Merged chain answers, cached per class: decorator metadata cannot change once a class is defined.
|
|
15
|
-
#lifecycleByClass: WeakMap<Constructor, LifecycleMetadata | null> | undefined;
|
|
16
|
-
#accessorsByClass: WeakMap<Constructor, ReadonlyArray<AccessorEntry> | null> | undefined;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Whatever a decorator stored under `key` on this exact class, narrowed as far as the platform allows.
|
|
20
|
-
*
|
|
21
|
-
* @remarks Own-property only: an inherited `Symbol.metadata` belongs to the base class. Constructor
|
|
22
|
-
* metadata must not be borrowed (each class opts into its deps); the lifecycle and accessor readers
|
|
23
|
-
* walk the base chain themselves, because the platform runs inherited members regardless.
|
|
24
|
-
*/
|
|
25
|
-
#read(target: object, key: string | symbol): unknown {
|
|
26
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, METADATA_SYMBOL);
|
|
27
|
-
if (descriptor === undefined) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
const record: unknown = descriptor.value;
|
|
31
|
-
if (typeof record !== "object" || record === null || !Object.hasOwn(record, key)) {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
return Reflect.get(record, key);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Each class level's own bucket under `key`, base first — the order construction runs them. */
|
|
38
|
-
#ownBucketsBaseFirst(target: Constructor, key: string | symbol): Array<unknown> {
|
|
39
|
-
const buckets: Array<unknown> = [];
|
|
40
|
-
let current: unknown = target;
|
|
41
|
-
while (typeof current === "function" && current !== Function.prototype) {
|
|
42
|
-
const bucket = this.#read(current, key);
|
|
43
|
-
if (bucket !== undefined) {
|
|
44
|
-
buckets.unshift(bucket);
|
|
45
|
-
}
|
|
46
|
-
current = Object.getPrototypeOf(current);
|
|
47
|
-
}
|
|
48
|
-
return buckets;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined {
|
|
52
|
-
return this.#read(target, INJECTABLE_KEY) as ConstructorMetadata | undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Lifecycle hooks aggregated over the base chain — an inherited hook method exists on the instance.
|
|
57
|
-
*
|
|
58
|
-
* @remarks `postConstruct` runs base first (a derived hook may rely on base state); `preDestroy`
|
|
59
|
-
* runs derived first, mirroring teardown. A method name repeated across levels runs once.
|
|
60
|
-
*/
|
|
61
|
-
getLifecycleMetadata(target: Constructor): LifecycleMetadata | undefined {
|
|
62
|
-
const cached = this.#lifecycleByClass?.get(target);
|
|
63
|
-
if (cached !== undefined) {
|
|
64
|
-
return cached ?? undefined;
|
|
65
|
-
}
|
|
66
|
-
const buckets = this.#ownBucketsBaseFirst(target, LIFECYCLE_KEY) as Array<LifecycleMetadata>;
|
|
67
|
-
let merged: LifecycleMetadata | null;
|
|
68
|
-
if (buckets.length === 0) {
|
|
69
|
-
merged = null;
|
|
70
|
-
} else if (buckets.length === 1) {
|
|
71
|
-
merged = buckets[0]!;
|
|
72
|
-
} else {
|
|
73
|
-
merged = {
|
|
74
|
-
postConstruct: mergeUniqueNames(buckets.map((bucket) => bucket.postConstruct)),
|
|
75
|
-
preDestroy: mergeUniqueNames(buckets.map((bucket) => bucket.preDestroy).reverse()),
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
(this.#lifecycleByClass ??= new WeakMap<Constructor, LifecycleMetadata | null>()).set(target, merged);
|
|
79
|
-
return merged ?? undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Accessor injections aggregated over the base chain, base first — the order initializers run.
|
|
84
|
-
*
|
|
85
|
-
* @remarks Not deduplicated: a derived accessor shadowing a base one still runs both initializers.
|
|
86
|
-
*/
|
|
87
|
-
getAccessorMetadata(target: Constructor): ReadonlyArray<AccessorEntry> | undefined {
|
|
88
|
-
const cached = this.#accessorsByClass?.get(target);
|
|
89
|
-
if (cached !== undefined) {
|
|
90
|
-
return cached ?? undefined;
|
|
91
|
-
}
|
|
92
|
-
const buckets = this.#ownBucketsBaseFirst(target, INJECT_ACCESSOR_KEY) as Array<ReadonlyArray<AccessorEntry>>;
|
|
93
|
-
let merged: ReadonlyArray<AccessorEntry> | null;
|
|
94
|
-
if (buckets.length === 0) {
|
|
95
|
-
merged = null;
|
|
96
|
-
} else if (buckets.length === 1) {
|
|
97
|
-
merged = buckets[0]!;
|
|
98
|
-
} else {
|
|
99
|
-
merged = buckets.flat();
|
|
100
|
-
}
|
|
101
|
-
(this.#accessorsByClass ??= new WeakMap<Constructor, ReadonlyArray<AccessorEntry> | null>()).set(target, merged);
|
|
102
|
-
return merged ?? undefined;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Concatenates hook-name lists in the given order, keeping each name's first occurrence. */
|
|
107
|
-
function mergeUniqueNames(lists: Array<ReadonlyArray<string>>): Array<string> {
|
|
108
|
-
const seen = new Set<string>();
|
|
109
|
-
const merged: Array<string> = [];
|
|
110
|
-
for (const list of lists) {
|
|
111
|
-
for (const name of list) {
|
|
112
|
-
if (!seen.has(name)) {
|
|
113
|
-
seen.add(name);
|
|
114
|
-
merged.push(name);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return merged;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* The shared reader a container falls back to when given no custom `MetadataReader`.
|
|
123
|
-
*
|
|
124
|
-
* @since 0.3.16-canary.0
|
|
125
|
-
*/
|
|
126
|
-
export const defaultMetadataReader: SymbolMetadataReader = new SymbolMetadataReader();
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wraps a foreign {@link MetadataReader} so its answers are verified before anything dereferences
|
|
3
|
-
* them.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { MetadataReader } from "#/metadata/metadata-types";
|
|
7
|
-
import { defaultMetadataReader } from "#/metadata/symbol-metadata-reader";
|
|
8
|
-
import {
|
|
9
|
-
verifyAccessorMetadata,
|
|
10
|
-
verifyConstructorMetadata,
|
|
11
|
-
verifyLifecycleMetadata,
|
|
12
|
-
} from "#/resolution/cache/class-introspector";
|
|
13
|
-
|
|
14
|
-
// Wrapping a wrapper would stack a layer per child container, so each one is remembered.
|
|
15
|
-
const verifyingReaders = new WeakSet<MetadataReader>();
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The reader a container should hand its resolver: verified when it came from outside.
|
|
19
|
-
*
|
|
20
|
-
* @remarks The decorator reader writes the metadata it later reads, so there is nothing to check and
|
|
21
|
-
* nothing to pay — a container that supplies no reader of its own is left on the same code path it
|
|
22
|
-
* has always taken. A supplied reader is a claim, and only its callers can be charged for checking.
|
|
23
|
-
*
|
|
24
|
-
* @since 0.6.0
|
|
25
|
-
*/
|
|
26
|
-
export function verifyingMetadataReader(reader: MetadataReader): MetadataReader {
|
|
27
|
-
if (reader === defaultMetadataReader || verifyingReaders.has(reader)) {
|
|
28
|
-
return reader;
|
|
29
|
-
}
|
|
30
|
-
const verifying: MetadataReader = {
|
|
31
|
-
getConstructorMetadata: (target) => verifyConstructorMetadata(reader, target),
|
|
32
|
-
getLifecycleMetadata: (target) => verifyLifecycleMetadata(reader, target),
|
|
33
|
-
...(reader.getAccessorMetadata === undefined
|
|
34
|
-
? {}
|
|
35
|
-
: { getAccessorMetadata: (target) => verifyAccessorMetadata(reader, target) }),
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
verifyingReaders.add(verifying);
|
|
39
|
-
|
|
40
|
-
return verifying;
|
|
41
|
-
}
|