@codefast/di 0.9.0 → 0.10.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 +165 -0
- package/README.md +6 -5
- package/dist/ambient/active-container.d.ts +14 -4
- package/dist/ambient/active-container.js +22 -1
- package/dist/container/binding-builders.d.ts +35 -13
- package/dist/container/binding-builders.js +174 -97
- package/dist/container/container.d.ts +10 -10
- package/dist/container/container.js +53 -36
- package/dist/core/binding-scope.d.ts +2 -2
- package/dist/core/binding.d.ts +42 -49
- package/dist/core/binding.js +12 -38
- package/dist/core/constraint-requirement.d.ts +1 -1
- package/dist/core/module.d.ts +3 -3
- package/dist/core/registry.d.ts +52 -9
- package/dist/core/registry.js +376 -162
- package/dist/core/state-epoch.d.ts +16 -0
- package/dist/core/state-epoch.js +21 -0
- package/dist/core/token.d.ts +1 -1
- package/dist/core/types.d.ts +12 -9
- package/dist/decorators/inject.d.ts +3 -3
- package/dist/decorators/inject.js +5 -5
- package/dist/decorators/injectable.d.ts +2 -2
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/lifecycle-decorators.js +2 -2
- package/dist/errors/diagnostics.d.ts +2 -0
- package/dist/errors/errors.d.ts +29 -3
- package/dist/errors/errors.js +34 -2
- package/dist/index.d.ts +35 -35
- package/dist/index.js +19 -19
- package/dist/injection/descriptor.d.ts +9 -7
- package/dist/injection/descriptor.js +3 -1
- package/dist/injection/resolve-options.d.ts +9 -3
- package/dist/injection/resolve-options.js +17 -1
- package/dist/introspection/dependency-graph.d.ts +3 -3
- package/dist/introspection/dependency-graph.js +15 -10
- package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts +1 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts +1 -1
- package/dist/introspection/graph-adapters/reactflow.d.ts +1 -1
- package/dist/introspection/inspector.d.ts +7 -5
- package/dist/introspection/inspector.js +13 -27
- package/dist/lifecycle/lifecycle-manager.d.ts +4 -4
- package/dist/lifecycle/lifecycle-manager.js +13 -11
- package/dist/lifecycle/scope-manager.d.ts +2 -2
- package/dist/lifecycle/scope-manager.js +4 -4
- package/dist/metadata/metadata-reader-token.d.ts +2 -2
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/metadata/metadata-types.d.ts +3 -3
- package/dist/metadata/symbol-metadata-reader.d.ts +3 -3
- package/dist/metadata/symbol-metadata-reader.js +1 -1
- package/dist/metadata/verifying-metadata-reader.d.ts +1 -1
- package/dist/metadata/verifying-metadata-reader.js +2 -2
- package/dist/resolution/cache/activation-need.d.ts +6 -4
- package/dist/resolution/cache/activation-need.js +13 -6
- package/dist/resolution/cache/binding-lookup-cache.d.ts +39 -6
- package/dist/resolution/cache/binding-lookup-cache.js +97 -13
- package/dist/resolution/cache/class-introspector.d.ts +6 -6
- package/dist/resolution/cache/class-introspector.js +82 -69
- package/dist/resolution/context.d.ts +11 -11
- package/dist/resolution/context.js +1 -1
- package/dist/resolution/path/resolution-path.d.ts +1 -1
- package/dist/resolution/path/resolution-path.js +1 -1
- package/dist/resolution/plan/instantiation-plan.d.ts +16 -4
- package/dist/resolution/plan/instantiation-plan.js +160 -69
- package/dist/resolution/plan/plan-codegen.d.ts +100 -0
- package/dist/resolution/plan/plan-codegen.js +185 -0
- package/dist/resolution/resolver.d.ts +26 -16
- package/dist/resolution/resolver.js +401 -147
- package/dist/resolution/select/binding-select.d.ts +6 -5
- package/dist/resolution/select/binding-select.js +17 -11
- package/dist/resolution/select/constraints.d.ts +3 -3
- package/dist/resolution/select/constraints.js +4 -4
- package/package.json +11 -3
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { clearBindingFrame,
|
|
2
|
-
import { mergingConstraintRequirements } from "
|
|
3
|
-
import { slotName } from "
|
|
4
|
-
import { tokenName } from "
|
|
5
|
-
import { ChainNotRegisteredError, SelfBindingRequiresClassError } from "
|
|
6
|
-
import { normalizeToDescriptor } from "
|
|
1
|
+
import { clearBindingFrame, createBindingSlot, DEFAULT_BINDING_SLOT, generateBindingId, NO_INSTANCE, } from "#core/binding";
|
|
2
|
+
import { mergingConstraintRequirements } from "#core/constraint-requirement";
|
|
3
|
+
import { slotName } from "#core/tag";
|
|
4
|
+
import { tokenName } from "#core/token";
|
|
5
|
+
import { ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, SelfBindingRequiresClassError, } from "#errors/errors";
|
|
6
|
+
import { normalizeToDescriptor } from "#injection/descriptor";
|
|
7
7
|
/** One criterion per key: re-tagging the same key replaces it rather than asking for both values. */
|
|
8
8
|
function updateSlotTag(slot, criterion) {
|
|
9
9
|
const tags = [...slot.tags];
|
|
@@ -16,139 +16,211 @@ function updateSlotTag(slot, criterion) {
|
|
|
16
16
|
}
|
|
17
17
|
return createBindingSlot(tags);
|
|
18
18
|
}
|
|
19
|
-
/** Record a module's binding id, dropping the id the chain re-slotted away from. */
|
|
20
|
-
function trackBindingForModule(ids, id, previousId) {
|
|
21
|
-
if (previousId !== undefined) {
|
|
22
|
-
const previousIndex = ids.indexOf(previousId);
|
|
23
|
-
if (previousIndex !== -1) {
|
|
24
|
-
ids.splice(previousIndex, 1);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
ids.push(id);
|
|
28
|
-
}
|
|
29
19
|
// ── BindingChain ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
30
20
|
/**
|
|
31
|
-
* The one builder behind `bind()` and every `to*()` return type
|
|
32
|
-
*
|
|
33
|
-
*
|
|
21
|
+
* The one builder behind `bind()` and every `to*()` return type — and the binding it registers.
|
|
22
|
+
*
|
|
23
|
+
* @remarks The binding fields come first, in the order every binding shares, and the chain's own
|
|
24
|
+
* bookkeeping follows as private fields; `to*()` fills the fields in place and hands this object to
|
|
25
|
+
* the registry, so a plain bind is one allocation. Refinements write the registered object: a scope
|
|
26
|
+
* or hook in place, a slot or predicate through the registry so its indexes follow.
|
|
34
27
|
*
|
|
35
28
|
* @since 0.5.0-canary.8
|
|
36
29
|
*/
|
|
37
30
|
export class BindingChain {
|
|
38
|
-
//
|
|
39
|
-
|
|
31
|
+
// The binding. Every field, every kind, written in this order and nowhere else: one hidden class.
|
|
32
|
+
kind = "constant";
|
|
33
|
+
identifier = generateBindingId();
|
|
34
|
+
inFlight = false;
|
|
35
|
+
frame = undefined;
|
|
36
|
+
instance = NO_INSTANCE;
|
|
37
|
+
token;
|
|
38
|
+
slot = DEFAULT_BINDING_SLOT;
|
|
39
|
+
predicate = undefined;
|
|
40
|
+
isMany = false;
|
|
41
|
+
scope = "singleton";
|
|
42
|
+
target = undefined;
|
|
43
|
+
factory = undefined;
|
|
44
|
+
deps = undefined;
|
|
45
|
+
value = undefined;
|
|
46
|
+
activationHook = undefined;
|
|
47
|
+
deactivationHook = undefined;
|
|
48
|
+
// The chain. Set by the first `to*()`, which is the only one a chain accepts.
|
|
49
|
+
#isRegistered = false;
|
|
40
50
|
// Allocated only by a chain that actually displaces something — most never do.
|
|
41
51
|
#displacedByChain;
|
|
42
52
|
// Registry version after this chain's last write — a mismatch means someone else wrote in between.
|
|
43
53
|
#versionAfterLastWrite = -1;
|
|
44
|
-
#token;
|
|
45
54
|
#registration;
|
|
46
55
|
constructor(token, registration) {
|
|
47
|
-
this
|
|
56
|
+
this.token = token;
|
|
48
57
|
this.#registration = registration;
|
|
49
58
|
}
|
|
50
|
-
/**
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
/** This object as the registry and the resolver see it: the value type erased once, here. */
|
|
60
|
+
get #binding() {
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
/** Loud failure for a refinement before `to*()`. */
|
|
64
|
+
#requireRegistered() {
|
|
65
|
+
if (!this.#isRegistered) {
|
|
66
|
+
throw new ChainNotRegisteredError(tokenName(this.token));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Loud failure for a second `to*()`, raised before the first one's fields can be overwritten. */
|
|
70
|
+
#requireUnregistered() {
|
|
71
|
+
if (this.#isRegistered) {
|
|
72
|
+
throw new ChainAlreadyRegisteredError(tokenName(this.token));
|
|
54
73
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.#
|
|
61
|
-
this.#binding = createBinding(partial, this.#token, DEFAULT_BINDING_SLOT, undefined);
|
|
62
|
-
this.#commit(this.#binding, undefined);
|
|
74
|
+
}
|
|
75
|
+
#register(kind, scope) {
|
|
76
|
+
this.kind = kind;
|
|
77
|
+
this.scope = scope;
|
|
78
|
+
this.#isRegistered = true;
|
|
79
|
+
this.#commit(undefined);
|
|
63
80
|
return this;
|
|
64
81
|
}
|
|
65
82
|
// ── Registration ───────────────────────────────────────────────────────────────────────────────────────────────────
|
|
66
83
|
to(type) {
|
|
67
|
-
|
|
84
|
+
this.#requireUnregistered();
|
|
85
|
+
this.target = type;
|
|
86
|
+
return this.#register("class", "transient");
|
|
68
87
|
}
|
|
69
88
|
toSelf() {
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
this.#requireUnregistered();
|
|
90
|
+
if (typeof this.token !== "function") {
|
|
91
|
+
throw new SelfBindingRequiresClassError(tokenName(this.token));
|
|
72
92
|
}
|
|
73
|
-
|
|
93
|
+
this.target = this.token;
|
|
94
|
+
return this.#register("class", "transient");
|
|
74
95
|
}
|
|
75
96
|
toConstantValue(value) {
|
|
76
|
-
|
|
97
|
+
this.#requireUnregistered();
|
|
98
|
+
this.value = value;
|
|
99
|
+
return this.#register("constant", "singleton");
|
|
77
100
|
}
|
|
78
101
|
toDynamic(factory) {
|
|
79
|
-
|
|
102
|
+
this.#requireUnregistered();
|
|
103
|
+
this.factory = factory;
|
|
104
|
+
return this.#register("dynamic", "transient");
|
|
80
105
|
}
|
|
81
106
|
toDynamicAsync(factory) {
|
|
82
|
-
|
|
107
|
+
this.#requireUnregistered();
|
|
108
|
+
this.factory = factory;
|
|
109
|
+
return this.#register("dynamic-async", "transient");
|
|
83
110
|
}
|
|
84
111
|
toResolved(factory, deps) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
scope: "transient",
|
|
90
|
-
});
|
|
112
|
+
this.#requireUnregistered();
|
|
113
|
+
this.factory = factory;
|
|
114
|
+
this.deps = deps.map((dependency) => normalizeToDescriptor(dependency));
|
|
115
|
+
return this.#register("resolved", "transient");
|
|
91
116
|
}
|
|
92
117
|
toResolvedAsync(factory, deps) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
scope: "transient",
|
|
98
|
-
});
|
|
118
|
+
this.#requireUnregistered();
|
|
119
|
+
this.factory = factory;
|
|
120
|
+
this.deps = deps.map((dependency) => normalizeToDescriptor(dependency));
|
|
121
|
+
return this.#register("resolved-async", "transient");
|
|
99
122
|
}
|
|
100
123
|
toAlias(target) {
|
|
101
|
-
|
|
124
|
+
this.#requireUnregistered();
|
|
125
|
+
this.target = target;
|
|
126
|
+
return this.#register("alias", "transient");
|
|
102
127
|
}
|
|
103
128
|
// ── Refinement ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Whether this chain provably still owns the registry's last write with nothing parked.
|
|
131
|
+
*
|
|
132
|
+
* @remarks When it does, a refinement rewrites the live binding in place; otherwise `#reslot`
|
|
133
|
+
* re-checks liveness and restores what the new shape frees.
|
|
134
|
+
*/
|
|
135
|
+
get #isProvablyLive() {
|
|
136
|
+
return this.#registration.registry.version === this.#versionAfterLastWrite && this.#displacedByChain === undefined;
|
|
137
|
+
}
|
|
138
|
+
/** Stamps this chain's last-write version after a mutation it made in place. */
|
|
139
|
+
#recordWrite() {
|
|
140
|
+
this.#versionAfterLastWrite = this.#registration.registry.version;
|
|
141
|
+
}
|
|
142
|
+
/** Bumps the registry for an index-neutral mutation (scope, hook) and records this chain's write. */
|
|
143
|
+
#touchAndRecordWrite() {
|
|
144
|
+
this.#registration.registry.touch();
|
|
145
|
+
this.#recordWrite();
|
|
146
|
+
}
|
|
147
|
+
// Slot and predicate are what the registry indexes on, so a re-slot takes the binding out of the
|
|
148
|
+
// registry, rewrites the two fields while it is out, and registers it again — same object, same id.
|
|
106
149
|
#reslot(slot, predicate) {
|
|
107
|
-
|
|
108
|
-
this.#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
150
|
+
this.#requireRegistered();
|
|
151
|
+
this.#commit(() => {
|
|
152
|
+
this.slot = slot;
|
|
153
|
+
this.predicate = predicate;
|
|
154
|
+
// The frame reports the slot, so a resolve before this call memoized the previous one.
|
|
155
|
+
clearBindingFrame(this.#binding);
|
|
156
|
+
});
|
|
112
157
|
return this;
|
|
113
158
|
}
|
|
114
159
|
#withScope(scope) {
|
|
115
|
-
|
|
116
|
-
if (
|
|
160
|
+
this.#requireRegistered();
|
|
161
|
+
if (this.scope !== scope) {
|
|
117
162
|
// An instance cached under the old scope must not survive the change — a later flip back
|
|
118
163
|
// to that scope would resurrect it.
|
|
119
|
-
this.#registration.scope.deleteSingleton(binding);
|
|
120
|
-
this.#registration.scope.deleteScoped(
|
|
121
|
-
|
|
164
|
+
this.#registration.scope.deleteSingleton(this.#binding);
|
|
165
|
+
this.#registration.scope.deleteScoped(this.identifier);
|
|
166
|
+
this.scope = scope;
|
|
122
167
|
}
|
|
123
168
|
// The frame reports the scope, so a resolve before this call memoized the previous one.
|
|
124
|
-
clearBindingFrame(binding);
|
|
125
|
-
this.#
|
|
126
|
-
this.#versionAfterLastWrite = this.#registration.registry.version;
|
|
169
|
+
clearBindingFrame(this.#binding);
|
|
170
|
+
this.#touchAndRecordWrite();
|
|
127
171
|
return this;
|
|
128
172
|
}
|
|
129
173
|
// SPEC calls a candidate a binding that passes *all* of a chain's predicates, and the chain type
|
|
130
174
|
// reads as refinement, so a second `when()` narrows rather than replaces.
|
|
131
175
|
when(predicate) {
|
|
132
|
-
|
|
133
|
-
const previous =
|
|
134
|
-
if (previous === undefined) {
|
|
135
|
-
return this.#reslot(binding.slot, predicate);
|
|
136
|
-
}
|
|
176
|
+
this.#requireRegistered();
|
|
177
|
+
const previous = this.predicate;
|
|
137
178
|
// The composite carries both sides' requirements, so validate() still sees them.
|
|
138
|
-
const
|
|
139
|
-
|
|
179
|
+
const narrowed = previous === undefined
|
|
180
|
+
? predicate
|
|
181
|
+
: mergingConstraintRequirements((ctx) => previous(ctx) && predicate(ctx), previous, predicate);
|
|
182
|
+
// The slot is unchanged, so a provably-live binding just takes the predicate in place.
|
|
183
|
+
if (this.#isProvablyLive) {
|
|
184
|
+
this.#registration.registry.setPredicate(this.#binding, narrowed);
|
|
185
|
+
this.#recordWrite();
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
return this.#reslot(this.slot, narrowed);
|
|
140
189
|
}
|
|
141
190
|
whenNamed(name) {
|
|
142
191
|
return this.whenTagged(slotName.of(name));
|
|
143
192
|
}
|
|
144
193
|
whenTagged(criterion) {
|
|
145
|
-
|
|
146
|
-
|
|
194
|
+
this.#requireRegistered();
|
|
195
|
+
if (this.isMany) {
|
|
196
|
+
throw new ManyBindingSlotError(tokenName(this.token));
|
|
197
|
+
}
|
|
198
|
+
return this.#reslot(updateSlotTag(this.slot, criterion), this.predicate);
|
|
199
|
+
}
|
|
200
|
+
many() {
|
|
201
|
+
this.#requireRegistered();
|
|
202
|
+
if (this.slot.tags.length !== 0) {
|
|
203
|
+
throw new ManyBindingSlotError(tokenName(this.token));
|
|
204
|
+
}
|
|
205
|
+
if (this.isMany) {
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
// A provably-live binding takes membership in place; the registry only moves it out of the lone
|
|
209
|
+
// map. Otherwise `#commit` re-checks liveness and restores what the freed slot lets back in.
|
|
210
|
+
if (this.#isProvablyLive) {
|
|
211
|
+
this.#registration.registry.setMany(this.#binding);
|
|
212
|
+
this.#recordWrite();
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
this.#commit(() => {
|
|
216
|
+
this.isMany = true;
|
|
217
|
+
});
|
|
218
|
+
return this;
|
|
147
219
|
}
|
|
148
220
|
whenDefault() {
|
|
149
221
|
// The default slot is what a fresh registration already has, so there is nothing to re-slot —
|
|
150
222
|
// but an unregistered chain must fail here exactly as it does in every other refinement.
|
|
151
|
-
this.#
|
|
223
|
+
this.#requireRegistered();
|
|
152
224
|
return this;
|
|
153
225
|
}
|
|
154
226
|
singleton() {
|
|
@@ -161,40 +233,40 @@ export class BindingChain {
|
|
|
161
233
|
return this.#withScope("scoped");
|
|
162
234
|
}
|
|
163
235
|
onActivation(fn) {
|
|
164
|
-
|
|
165
|
-
this
|
|
166
|
-
this.#
|
|
236
|
+
this.#requireRegistered();
|
|
237
|
+
this.activationHook = fn;
|
|
238
|
+
this.#touchAndRecordWrite();
|
|
167
239
|
return this;
|
|
168
240
|
}
|
|
169
241
|
onDeactivation(fn) {
|
|
170
|
-
|
|
171
|
-
this
|
|
172
|
-
this.#
|
|
242
|
+
this.#requireRegistered();
|
|
243
|
+
this.deactivationHook = fn;
|
|
244
|
+
this.#touchAndRecordWrite();
|
|
173
245
|
return this;
|
|
174
246
|
}
|
|
175
247
|
id() {
|
|
176
|
-
|
|
248
|
+
this.#requireRegistered();
|
|
249
|
+
return this.identifier;
|
|
177
250
|
}
|
|
178
251
|
// ── Registry ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
179
252
|
/**
|
|
180
|
-
* Registers
|
|
253
|
+
* Registers this binding; with `rewrite` given, first takes the live binding out, rewrites it, and
|
|
254
|
+
* registers it again.
|
|
181
255
|
*
|
|
182
256
|
* @remarks A `when*()` that follows `to*()` re-slots an already-live binding and can displace one
|
|
183
257
|
* the final shape would never conflict with. Those stay parked in `#displacedByChain` until the
|
|
184
258
|
* chain settles, then get restored.
|
|
185
259
|
*/
|
|
186
|
-
#commit(
|
|
260
|
+
#commit(rewrite) {
|
|
187
261
|
const { registry, moduleBindingIds } = this.#registration;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const registered = binding;
|
|
191
|
-
if (previousId !== undefined) {
|
|
262
|
+
const registered = this.#binding;
|
|
263
|
+
if (rewrite !== undefined) {
|
|
192
264
|
// A registry someone else wrote since this chain's last write invalidates the parked
|
|
193
265
|
// snapshot: restoring it could undo an unbind or shadow a newer binding.
|
|
194
266
|
if (registry.version !== this.#versionAfterLastWrite) {
|
|
195
267
|
this.#displacedByChain = undefined;
|
|
196
268
|
}
|
|
197
|
-
if (registry.
|
|
269
|
+
if (!registry.reslot(registered, rewrite)) {
|
|
198
270
|
// The chain's binding is no longer live (unbound or displaced) — a refinement must not
|
|
199
271
|
// resurrect it, so the chain goes inert against the registry.
|
|
200
272
|
this.#displacedByChain = undefined;
|
|
@@ -202,15 +274,20 @@ export class BindingChain {
|
|
|
202
274
|
return;
|
|
203
275
|
}
|
|
204
276
|
}
|
|
277
|
+
else {
|
|
278
|
+
// Each `to*()` starts its own registration, so anything a previous one displaced is not this
|
|
279
|
+
// registration's to restore.
|
|
280
|
+
this.#displacedByChain = undefined;
|
|
281
|
+
}
|
|
205
282
|
const displaced = registry.add(registered);
|
|
206
283
|
if (displaced !== undefined) {
|
|
207
284
|
(this.#displacedByChain ??= []).push(displaced);
|
|
208
285
|
}
|
|
209
|
-
if (
|
|
286
|
+
if (rewrite !== undefined && this.#displacedByChain !== undefined) {
|
|
210
287
|
this.#restoreNonConflicting(this.#displacedByChain);
|
|
211
288
|
}
|
|
212
|
-
if (moduleBindingIds !== undefined) {
|
|
213
|
-
|
|
289
|
+
if (rewrite === undefined && moduleBindingIds !== undefined) {
|
|
290
|
+
moduleBindingIds.push(this.identifier);
|
|
214
291
|
}
|
|
215
292
|
this.#versionAfterLastWrite = registry.version;
|
|
216
293
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { BindToBuilder } from "
|
|
2
|
-
import type { AsyncModule, SyncModule } from "
|
|
3
|
-
import type { Token } from "
|
|
4
|
-
import type { ActivationHandler, BindingIdentifier, Constructor, DeactivationHandler, ResolveOptions } from "
|
|
5
|
-
import type { AutoRegisterRegistry } from "
|
|
6
|
-
import type { ContainerGraphJson, GraphOptions } from "
|
|
7
|
-
import type { BindingSnapshot, ContainerSnapshot } from "
|
|
8
|
-
import type { MetadataReader } from "
|
|
1
|
+
import type { BindToBuilder } from "#core/binding";
|
|
2
|
+
import type { AsyncModule, SyncModule } from "#core/module";
|
|
3
|
+
import type { Token } from "#core/token";
|
|
4
|
+
import type { ActivationHandler, BindingIdentifier, Constructor, DeactivationHandler, ResolveOptions } from "#core/types";
|
|
5
|
+
import type { AutoRegisterRegistry } from "#decorators/injectable";
|
|
6
|
+
import type { ContainerGraphJson, GraphOptions } from "#introspection/dependency-graph";
|
|
7
|
+
import type { BindingSnapshot, ContainerSnapshot } from "#introspection/inspector";
|
|
8
|
+
import type { MetadataReader } from "#metadata/metadata-types";
|
|
9
9
|
/**
|
|
10
10
|
* The public surface: binding, resolving, modules, lifecycle hooks, child scopes, and disposal.
|
|
11
11
|
*
|
|
@@ -30,8 +30,8 @@ export interface Container {
|
|
|
30
30
|
resolveAsync<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Promise<Value>;
|
|
31
31
|
resolveOptional<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Value | undefined;
|
|
32
32
|
resolveOptionalAsync<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Promise<Value | undefined>;
|
|
33
|
-
resolveAll<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>):
|
|
34
|
-
resolveAllAsync<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Promise<
|
|
33
|
+
resolveAll<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): ReadonlyArray<Value>;
|
|
34
|
+
resolveAllAsync<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Promise<ReadonlyArray<Value>>;
|
|
35
35
|
createChild(): Container;
|
|
36
36
|
dispose(): Promise<void>;
|
|
37
37
|
[Symbol.asyncDispose](): Promise<void>;
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { BindingChain } from "
|
|
2
|
-
import { NO_INSTANCE } from "
|
|
3
|
-
import { effectiveBindingScope } from "
|
|
4
|
-
import { constraintRequirementsOf } from "
|
|
5
|
-
import { getOrInsert, getOrInsertComputed } from "
|
|
6
|
-
import { isSyncModule, MODULE_SETUP } from "
|
|
7
|
-
import { BindingRegistry } from "
|
|
8
|
-
import { tokenName } from "
|
|
9
|
-
import { RESOLUTION_DIAGNOSTICS } from "
|
|
10
|
-
import { AsyncModuleLoadError, CircularDependencyError, DisposedContainerError, InternalError, RebindUnboundTokenError, ScopeViolationError, SyncDisposalNotSupportedError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "
|
|
11
|
-
import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "
|
|
12
|
-
import { buildDependencyGraph } from "
|
|
13
|
-
import { Inspector } from "
|
|
14
|
-
import { LifecycleManager } from "
|
|
15
|
-
import { ScopeManager } from "
|
|
16
|
-
import { MetadataReaderToken } from "
|
|
17
|
-
import { defaultMetadataReader } from "
|
|
18
|
-
import { verifyingMetadataReader } from "
|
|
19
|
-
import { ROOT_BRANCH } from "
|
|
20
|
-
import { DependencyResolver } from "
|
|
1
|
+
import { BindingChain } from "#container/binding-builders";
|
|
2
|
+
import { NO_INSTANCE } from "#core/binding";
|
|
3
|
+
import { effectiveBindingScope } from "#core/binding-scope";
|
|
4
|
+
import { constraintRequirementsOf } from "#core/constraint-requirement";
|
|
5
|
+
import { getOrInsert, getOrInsertComputed } from "#core/map-upsert";
|
|
6
|
+
import { isSyncModule, MODULE_SETUP } from "#core/module";
|
|
7
|
+
import { BindingRegistry } from "#core/registry";
|
|
8
|
+
import { tokenName } from "#core/token";
|
|
9
|
+
import { RESOLUTION_DIAGNOSTICS } from "#errors/diagnostics";
|
|
10
|
+
import { AsyncModuleLoadError, CircularDependencyError, DisposedContainerError, InternalError, RebindUnboundTokenError, ScopeViolationError, SyncDisposalNotSupportedError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors/errors";
|
|
11
|
+
import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#injection/resolve-options";
|
|
12
|
+
import { buildDependencyGraph } from "#introspection/dependency-graph";
|
|
13
|
+
import { Inspector } from "#introspection/inspector";
|
|
14
|
+
import { LifecycleManager } from "#lifecycle/lifecycle-manager";
|
|
15
|
+
import { ScopeManager } from "#lifecycle/scope-manager";
|
|
16
|
+
import { MetadataReaderToken } from "#metadata/metadata-reader-token";
|
|
17
|
+
import { defaultMetadataReader } from "#metadata/symbol-metadata-reader";
|
|
18
|
+
import { verifyingMetadataReader } from "#metadata/verifying-metadata-reader";
|
|
19
|
+
import { ROOT_BRANCH } from "#resolution/path/resolution-path";
|
|
20
|
+
import { DependencyResolver } from "#resolution/resolver";
|
|
21
21
|
/** Whether a requirement's name is declared — on its token when it names one, on any token otherwise. */
|
|
22
22
|
function isSlotNameDeclared(declared, requirement) {
|
|
23
23
|
if (requirement.tokenName !== undefined) {
|
|
@@ -45,6 +45,7 @@ const APPLY_BINDING_SCOPE = {
|
|
|
45
45
|
builder.transient();
|
|
46
46
|
},
|
|
47
47
|
};
|
|
48
|
+
const NO_DEACTIVATION_PAIRS = Object.freeze([]);
|
|
48
49
|
// ── DefaultContainer ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
49
50
|
class DefaultContainer {
|
|
50
51
|
#disposed = false;
|
|
@@ -74,6 +75,7 @@ class DefaultContainer {
|
|
|
74
75
|
return (this.#inspector ??= new Inspector(this.#registry, this.#scope, this.#parent !== undefined, () => this.#disposed));
|
|
75
76
|
}
|
|
76
77
|
[RESOLUTION_DIAGNOSTICS]() {
|
|
78
|
+
const resolverCaches = this.#resolver.describeCaches();
|
|
77
79
|
const builtSubsystems = [];
|
|
78
80
|
if (this.#inspector !== undefined) {
|
|
79
81
|
builtSubsystems.push("container.inspector");
|
|
@@ -81,6 +83,12 @@ class DefaultContainer {
|
|
|
81
83
|
if (this.#moduleRefs !== undefined || this.#moduleBindingIds !== undefined) {
|
|
82
84
|
builtSubsystems.push("container.moduleTables");
|
|
83
85
|
}
|
|
86
|
+
if (this.#registry.isRecordMapBuilt) {
|
|
87
|
+
builtSubsystems.push("registry.records");
|
|
88
|
+
}
|
|
89
|
+
if (this.#registry.isIdIndexBuilt) {
|
|
90
|
+
builtSubsystems.push("registry.idIndex");
|
|
91
|
+
}
|
|
84
92
|
if (this.#registry.isTaggedIndexBuilt) {
|
|
85
93
|
builtSubsystems.push("registry.taggedIndex");
|
|
86
94
|
}
|
|
@@ -90,7 +98,8 @@ class DefaultContainer {
|
|
|
90
98
|
if (this.#lifecycle.isActivationTableBuilt) {
|
|
91
99
|
builtSubsystems.push("lifecycle.activationHooks");
|
|
92
100
|
}
|
|
93
|
-
|
|
101
|
+
builtSubsystems.push(...resolverCaches.builtSubsystems);
|
|
102
|
+
return { ...resolverCaches, scopedInstanceCount: this.#scope.scopedCount, builtSubsystems };
|
|
94
103
|
}
|
|
95
104
|
#initResolver(configuredReader) {
|
|
96
105
|
const parent = this.#parent;
|
|
@@ -100,7 +109,7 @@ class DefaultContainer {
|
|
|
100
109
|
}
|
|
101
110
|
/** What a container being constructed under this one inherits: a reader bound here, else this one's. */
|
|
102
111
|
#readerForChild() {
|
|
103
|
-
if (this.#registry.
|
|
112
|
+
if (this.#registry.has(MetadataReaderToken)) {
|
|
104
113
|
try {
|
|
105
114
|
return this.#resolver.resolve(MetadataReaderToken, undefined, []);
|
|
106
115
|
}
|
|
@@ -144,9 +153,9 @@ class DefaultContainer {
|
|
|
144
153
|
}
|
|
145
154
|
/** Remove bindings from registry + scope and collect [binding, instance] pairs for deactivation. */
|
|
146
155
|
#collectDeactivationPairs(tokenOrId) {
|
|
147
|
-
if (typeof tokenOrId === "
|
|
156
|
+
if (typeof tokenOrId === "number") {
|
|
148
157
|
const binding = this.#registry.removeById(tokenOrId);
|
|
149
|
-
return binding === undefined ?
|
|
158
|
+
return binding === undefined ? NO_DEACTIVATION_PAIRS : this.#drainSingletons([binding]);
|
|
150
159
|
}
|
|
151
160
|
// Dropping the whole token in one pass: removing each binding by id instead would re-scan and
|
|
152
161
|
// re-index the token's binding list once per binding.
|
|
@@ -154,18 +163,20 @@ class DefaultContainer {
|
|
|
154
163
|
}
|
|
155
164
|
/** Drain scope entries for already-removed bindings, and pair each one that still owes a deactivation. */
|
|
156
165
|
#drainSingletons(bindings) {
|
|
157
|
-
|
|
166
|
+
// Allocated by the first pair owed: an unbind or rebind of a binding nothing ever cached — the
|
|
167
|
+
// hot-swap shape — owes no deactivation and hands the shared empty list back.
|
|
168
|
+
let pairs;
|
|
158
169
|
for (const binding of bindings) {
|
|
159
170
|
if (binding.instance !== NO_INSTANCE) {
|
|
160
|
-
pairs.push([binding, binding.instance]);
|
|
171
|
+
(pairs ??= []).push([binding, binding.instance]);
|
|
161
172
|
this.#scope.deleteSingleton(binding);
|
|
162
173
|
}
|
|
163
174
|
else if (this.#owesConstantDeactivation(binding)) {
|
|
164
|
-
pairs.push([binding, binding.value]);
|
|
175
|
+
(pairs ??= []).push([binding, binding.value]);
|
|
165
176
|
}
|
|
166
|
-
this.#scope.deleteScoped(binding.
|
|
177
|
+
this.#scope.deleteScoped(binding.identifier);
|
|
167
178
|
}
|
|
168
|
-
return pairs;
|
|
179
|
+
return pairs ?? NO_DEACTIVATION_PAIRS;
|
|
169
180
|
}
|
|
170
181
|
/**
|
|
171
182
|
* Whether a constant still owes its deactivation.
|
|
@@ -176,7 +187,7 @@ class DefaultContainer {
|
|
|
176
187
|
*/
|
|
177
188
|
#owesConstantDeactivation(binding) {
|
|
178
189
|
return (binding.kind === "constant" &&
|
|
179
|
-
(binding.
|
|
190
|
+
(binding.deactivationHook !== undefined || this.#lifecycle.hasDeactivationHandlers(binding.token)));
|
|
180
191
|
}
|
|
181
192
|
/** Runs every pair's deactivation even when one throws, then reports what threw. */
|
|
182
193
|
#deactivatePairsSync(pairs) {
|
|
@@ -445,12 +456,18 @@ class DefaultContainer {
|
|
|
445
456
|
resolveAll(token, options) {
|
|
446
457
|
this.#assertNotDisposed();
|
|
447
458
|
const rootStack = this.#resolver.rootStack;
|
|
459
|
+
if (options === undefined && rootStack.length === 0) {
|
|
460
|
+
return this.#resolver.resolveRootCollection(token);
|
|
461
|
+
}
|
|
448
462
|
return rootStack.length === 0
|
|
449
463
|
? this.#resolver.resolveAll(token, options, rootStack)
|
|
450
464
|
: this.#resolver.resolveAll(token, options, []);
|
|
451
465
|
}
|
|
452
466
|
resolveAllAsync(token, options) {
|
|
453
467
|
this.#assertNotDisposed();
|
|
468
|
+
if (options === undefined) {
|
|
469
|
+
return this.#resolver.resolveRootCollectionAsync(token);
|
|
470
|
+
}
|
|
454
471
|
return this.#resolver.resolveAllAsync(token, options, [], ROOT_BRANCH);
|
|
455
472
|
}
|
|
456
473
|
// ── Child ──────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
@@ -523,7 +540,7 @@ class DefaultContainer {
|
|
|
523
540
|
// A constant is already its own instance, so only an activation hook gives the warm-up
|
|
524
541
|
// something to run — and a container-level hook counts as one just as a per-binding hook does.
|
|
525
542
|
if (binding.kind === "constant" &&
|
|
526
|
-
binding.
|
|
543
|
+
binding.activationHook === undefined &&
|
|
527
544
|
!this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
528
545
|
continue;
|
|
529
546
|
}
|
|
@@ -607,11 +624,11 @@ class DefaultContainer {
|
|
|
607
624
|
#validateSingletonBindingGraph(root, reader) {
|
|
608
625
|
const rootName = tokenName(root.token);
|
|
609
626
|
const dfs = (current, pathNames, pathBindingIds) => {
|
|
610
|
-
if (pathBindingIds.has(current.
|
|
627
|
+
if (pathBindingIds.has(current.identifier)) {
|
|
611
628
|
return;
|
|
612
629
|
}
|
|
613
630
|
const extendedPathIds = new Set(pathBindingIds);
|
|
614
|
-
extendedPathIds.add(current.
|
|
631
|
+
extendedPathIds.add(current.identifier);
|
|
615
632
|
for (const edge of this.#collectStaticDependencyEdges(current, reader)) {
|
|
616
633
|
const { terminal, depTokenName } = edge;
|
|
617
634
|
const depScope = this.#validationScopeFromTerminal(terminal);
|
|
@@ -649,10 +666,10 @@ class DefaultContainer {
|
|
|
649
666
|
const seenAliasIds = new Set();
|
|
650
667
|
let current = binding;
|
|
651
668
|
while (current !== undefined && current.kind === "alias") {
|
|
652
|
-
if (seenAliasIds.has(current.
|
|
669
|
+
if (seenAliasIds.has(current.identifier)) {
|
|
653
670
|
throw new CircularDependencyError(cyclePath);
|
|
654
671
|
}
|
|
655
|
-
seenAliasIds.add(current.
|
|
672
|
+
seenAliasIds.add(current.identifier);
|
|
656
673
|
cyclePath.push(tokenName(current.token));
|
|
657
674
|
const nextToken = current.target;
|
|
658
675
|
const next = this.#resolver.peekBindingForValidate(nextToken, options);
|
|
@@ -701,7 +718,7 @@ class DefaultContainer {
|
|
|
701
718
|
// ── Introspection ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
702
719
|
has(token, options) {
|
|
703
720
|
this.#assertNotDisposed();
|
|
704
|
-
return this.#getInspector().
|
|
721
|
+
return this.#getInspector().hasOwn(token, options) || (this.#parent?.has(token, options) ?? false);
|
|
705
722
|
}
|
|
706
723
|
hasOwn(token, options) {
|
|
707
724
|
this.#assertNotDisposed();
|