@codefast/di 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +376 -0
- package/README.md +94 -17
- package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +7 -2
- package/dist/{ambient/active-container.js → ambient-container.js} +6 -1
- package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +27 -16
- package/dist/container/binding-chain.js +254 -0
- package/dist/container/container.d.ts +11 -2
- package/dist/container/container.js +293 -73
- package/dist/core/binding-builders.d.ts +114 -0
- package/dist/core/binding-declaration.d.ts +127 -0
- package/dist/core/binding-declaration.js +187 -0
- package/dist/core/binding.d.ts +62 -138
- package/dist/core/binding.js +54 -17
- package/dist/core/module.d.ts +8 -5
- package/dist/core/module.js +17 -3
- package/dist/core/registry.d.ts +17 -28
- package/dist/core/registry.js +92 -133
- package/dist/core/state-epoch.d.ts +18 -1
- package/dist/core/state-epoch.js +17 -0
- package/dist/core/tag.js +1 -1
- package/dist/decorators/inject.js +6 -5
- package/dist/decorators/injectable.js +4 -2
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +9 -3
- package/dist/decorators/metadata-record.d.ts +9 -0
- package/dist/decorators/metadata-record.js +20 -0
- package/dist/{errors/errors.d.ts → errors.d.ts} +84 -9
- package/dist/{errors/errors.js → errors.js} +101 -17
- package/dist/index.d.ts +14 -10
- package/dist/index.js +7 -6
- package/dist/injection/{resolve-options.js → dependency-slot.js} +6 -4
- package/dist/injection/descriptor.d.ts +1 -1
- package/dist/injection/descriptor.js +3 -7
- package/dist/introspection/dependency-graph.d.ts +8 -3
- package/dist/introspection/dependency-graph.js +48 -26
- package/dist/introspection/explanation.d.ts +101 -0
- package/dist/introspection/explanation.js +151 -0
- package/dist/introspection/graph-adapters/reactflow.js +6 -4
- package/dist/introspection/inspector.d.ts +9 -2
- package/dist/introspection/inspector.js +30 -28
- package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +9 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +59 -30
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
- package/dist/lifecycle/{scope-manager.js → scopes.js} +36 -29
- package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
- package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
- package/dist/metadata/types.js +1 -0
- package/dist/metadata/verify.d.ts +43 -0
- package/dist/metadata/verify.js +133 -0
- package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +5 -4
- package/dist/metadata/verifying-reader.js +53 -0
- package/dist/resolution/async-fan-out.d.ts +12 -0
- package/dist/resolution/async-fan-out.js +26 -0
- package/dist/resolution/cache/activation-need.d.ts +1 -2
- package/dist/resolution/cache/activation-need.js +16 -19
- package/dist/resolution/cache/class-introspector.d.ts +38 -36
- package/dist/resolution/cache/class-introspector.js +79 -159
- package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -9
- package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +70 -25
- package/dist/resolution/context.d.ts +16 -24
- package/dist/resolution/context.js +47 -56
- package/dist/resolution/path.d.ts +128 -0
- package/dist/resolution/path.js +167 -0
- package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +5 -4
- package/dist/resolution/plan/{plan-codegen.js → codegen.js} +58 -32
- package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
- package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +64 -24
- package/dist/resolution/resolver.d.ts +11 -12
- package/dist/resolution/resolver.js +573 -356
- package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
- package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
- package/dist/resolution/select/constraints.js +1 -1
- package/package.json +87 -67
- package/dist/container/binding-builders.js +0 -305
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- package/dist/metadata/verifying-metadata-reader.js +0 -31
- package/dist/resolution/path/resolution-path.d.ts +0 -93
- package/dist/resolution/path/resolution-path.js +0 -116
- /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
- /package/dist/decorators/{lifecycle-decorators.d.ts → lifecycle.d.ts} +0 -0
- /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
- /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
- /package/dist/{errors → introspection}/diagnostics.js +0 -0
- /package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +0 -0
- /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
package/dist/core/registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bindingSlotEquals
|
|
1
|
+
import { bindingSlotEquals } from "#core/binding";
|
|
2
2
|
import { getOrInsert } from "#core/map-upsert";
|
|
3
3
|
import { advanceStateEpoch } from "#core/state-epoch";
|
|
4
4
|
const NO_BINDINGS = Object.freeze([]);
|
|
@@ -16,12 +16,22 @@ function createTokenRecord(bindings) {
|
|
|
16
16
|
*
|
|
17
17
|
* @since 0.3.16-canary.0
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* One shared empty map answers every read of a registry nothing was bound into, so a container
|
|
21
|
+
* that only ever resolves through its parent — every per-request child — allocates no map.
|
|
22
|
+
*/
|
|
23
|
+
const EMPTY_LONE = new Map();
|
|
24
|
+
const HELD_CONSTANT = 1;
|
|
25
|
+
const HELD_ALIAS = 2;
|
|
26
|
+
/**
|
|
27
|
+
* @since 0.11.0
|
|
28
|
+
*/
|
|
19
29
|
export class BindingRegistry {
|
|
20
30
|
// Monotonic mutation counter — lets resolvers version-stamp lookup caches across a container chain.
|
|
21
31
|
#version = 0;
|
|
22
32
|
// The common token lives here and nowhere else: exactly one default-slot binding, so the hot read
|
|
23
33
|
// of every resolve is a bare `Map.get` and a plain bind is one map write.
|
|
24
|
-
#lone =
|
|
34
|
+
#lone = EMPTY_LONE;
|
|
25
35
|
// Every other token — several bindings, a tagged slot, a predicate — has a record here, and a
|
|
26
36
|
// token is in exactly one of the two maps. Allocated by the first token that needs a record.
|
|
27
37
|
#records;
|
|
@@ -30,9 +40,9 @@ export class BindingRegistry {
|
|
|
30
40
|
#byId;
|
|
31
41
|
// Set when the first tagged slot lands and never cleared, like the tagged maps it stands for.
|
|
32
42
|
#taggedIndexBuilt = false;
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
#
|
|
43
|
+
// Kinds registered here at least once, never cleared: only the negative answers must be exact, so a
|
|
44
|
+
// container holding no constant skips its teardown sweep and one holding no alias skips its forwarding probe.
|
|
45
|
+
#heldKinds = 0;
|
|
36
46
|
/** Monotonic version — increments on every mutation. */
|
|
37
47
|
get version() {
|
|
38
48
|
return this.#version;
|
|
@@ -45,7 +55,11 @@ export class BindingRegistry {
|
|
|
45
55
|
}
|
|
46
56
|
/** Whether a constant has ever been registered here, and so whether teardown has anything to sweep. */
|
|
47
57
|
get hasHeldConstantBinding() {
|
|
48
|
-
return this.#
|
|
58
|
+
return (this.#heldKinds & HELD_CONSTANT) !== 0;
|
|
59
|
+
}
|
|
60
|
+
/** Whether an alias has ever been registered here, and so whether a candidate list can hold one. */
|
|
61
|
+
get hasHeldAlias() {
|
|
62
|
+
return (this.#heldKinds & HELD_ALIAS) !== 0;
|
|
49
63
|
}
|
|
50
64
|
/** Whether the deferred tagged-slot index has had to be built. */
|
|
51
65
|
get isTaggedIndexBuilt() {
|
|
@@ -75,18 +89,27 @@ export class BindingRegistry {
|
|
|
75
89
|
add(binding) {
|
|
76
90
|
this.#bump();
|
|
77
91
|
if (binding.kind === "constant") {
|
|
78
|
-
this.#
|
|
92
|
+
this.#heldKinds |= HELD_CONSTANT;
|
|
93
|
+
}
|
|
94
|
+
else if (binding.kind === "alias") {
|
|
95
|
+
this.#heldKinds |= HELD_ALIAS;
|
|
79
96
|
}
|
|
80
97
|
const key = binding.token;
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
|
|
98
|
+
const records = this.#records;
|
|
99
|
+
if (records !== undefined) {
|
|
100
|
+
const record = records.get(key);
|
|
101
|
+
if (record !== undefined) {
|
|
102
|
+
return this.#addToRecord(key, record, binding);
|
|
103
|
+
}
|
|
84
104
|
}
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
|
|
105
|
+
const occupant = this.#lone.get(key);
|
|
106
|
+
if (occupant === undefined) {
|
|
107
|
+
// The common bind: a fresh token taking the lone seat, one probe and one write.
|
|
108
|
+
if (this.#byId !== undefined) {
|
|
109
|
+
this.#byId.set(binding.identifier, binding);
|
|
110
|
+
}
|
|
88
111
|
if (isDefaultSlotBinding(binding)) {
|
|
89
|
-
this.#
|
|
112
|
+
this.#setLone(key, binding);
|
|
90
113
|
}
|
|
91
114
|
else {
|
|
92
115
|
this.#createRecord(key, [binding]);
|
|
@@ -95,17 +118,17 @@ export class BindingRegistry {
|
|
|
95
118
|
}
|
|
96
119
|
// Same slot, last wins: the newcomer takes the lone seat and nothing else moves.
|
|
97
120
|
if (isDefaultSlotBinding(binding)) {
|
|
98
|
-
this.#
|
|
121
|
+
this.#setLone(key, binding);
|
|
99
122
|
if (this.#byId !== undefined) {
|
|
100
|
-
this.#byId.delete(
|
|
123
|
+
this.#byId.delete(occupant.identifier);
|
|
101
124
|
this.#byId.set(binding.identifier, binding);
|
|
102
125
|
}
|
|
103
|
-
return
|
|
126
|
+
return occupant;
|
|
104
127
|
}
|
|
105
|
-
// A second shape joins the token, which is what a record is for.
|
|
106
|
-
this.#
|
|
128
|
+
// A second shape joins the token, which is what a record is for; every add is the newest, so it goes last.
|
|
129
|
+
this.#deleteLone(key);
|
|
107
130
|
this.#byId?.set(binding.identifier, binding);
|
|
108
|
-
this.#createRecord(key, [
|
|
131
|
+
this.#createRecord(key, [occupant, binding]);
|
|
109
132
|
return undefined;
|
|
110
133
|
}
|
|
111
134
|
/** Remove all bindings for a token. Returns removed bindings. */
|
|
@@ -113,7 +136,7 @@ export class BindingRegistry {
|
|
|
113
136
|
this.#bump();
|
|
114
137
|
const lone = this.#lone.get(token);
|
|
115
138
|
if (lone !== undefined) {
|
|
116
|
-
this.#
|
|
139
|
+
this.#deleteLone(token);
|
|
117
140
|
this.#byId?.delete(lone.identifier);
|
|
118
141
|
return [lone];
|
|
119
142
|
}
|
|
@@ -141,7 +164,7 @@ export class BindingRegistry {
|
|
|
141
164
|
byId.delete(id);
|
|
142
165
|
const key = binding.token;
|
|
143
166
|
if (this.#lone.get(key)?.identifier === id) {
|
|
144
|
-
this.#
|
|
167
|
+
this.#deleteLone(key);
|
|
145
168
|
return binding;
|
|
146
169
|
}
|
|
147
170
|
const record = this.#records?.get(key);
|
|
@@ -179,14 +202,6 @@ export class BindingRegistry {
|
|
|
179
202
|
getRecorded(token) {
|
|
180
203
|
return this.#records?.get(token)?.bindings ?? NO_BINDINGS;
|
|
181
204
|
}
|
|
182
|
-
/** How many bindings a token holds, without materialising a lone binding's list. */
|
|
183
|
-
countBindings(token) {
|
|
184
|
-
const record = this.#records?.get(token);
|
|
185
|
-
if (record !== undefined) {
|
|
186
|
-
return record.bindings.length;
|
|
187
|
-
}
|
|
188
|
-
return this.#lone.has(token) ? 1 : 0;
|
|
189
|
-
}
|
|
190
205
|
/** Get binding by ID. */
|
|
191
206
|
getById(id) {
|
|
192
207
|
return this.#ensureById().get(id);
|
|
@@ -214,26 +229,11 @@ export class BindingRegistry {
|
|
|
214
229
|
clear() {
|
|
215
230
|
this.#bump();
|
|
216
231
|
const all = this.allBindings();
|
|
217
|
-
this.#
|
|
232
|
+
this.#clearLone();
|
|
218
233
|
this.#records?.clear();
|
|
219
234
|
this.#byId?.clear();
|
|
220
235
|
return all;
|
|
221
236
|
}
|
|
222
|
-
/** Whether a slot-based binding currently occupies `binding`'s slot, so adding it would displace. */
|
|
223
|
-
hasSlotOccupant(binding) {
|
|
224
|
-
if (isPurePredicateBinding(binding)) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
if (this.#lone.has(binding.token)) {
|
|
228
|
-
// The lone seat is the default slot, so only a default-slot newcomer collides with it.
|
|
229
|
-
return binding.slot.tags.length === 0;
|
|
230
|
-
}
|
|
231
|
-
const record = this.#records?.get(binding.token);
|
|
232
|
-
if (record === undefined) {
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
return this.#slotOccupant(record, binding.slot) !== undefined;
|
|
236
|
-
}
|
|
237
237
|
/**
|
|
238
238
|
* The binding indexed under one criterion.
|
|
239
239
|
*
|
|
@@ -260,99 +260,42 @@ export class BindingRegistry {
|
|
|
260
260
|
getMultiTagged(token, criterion) {
|
|
261
261
|
return this.#records?.get(token)?.multi?.get(criterion);
|
|
262
262
|
}
|
|
263
|
-
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
264
|
-
getFastDefault(token) {
|
|
265
|
-
return this.#lone.get(token);
|
|
266
|
-
}
|
|
267
263
|
/**
|
|
268
|
-
*
|
|
269
|
-
* whether it was live; the caller registers it again with `add`.
|
|
264
|
+
* Whether any binding for the token carries a `when()` predicate.
|
|
270
265
|
*
|
|
271
|
-
* @remarks
|
|
272
|
-
*
|
|
273
|
-
*
|
|
266
|
+
* @remarks Only a token that keeps a record can hold a predicate — a lone binding is default-slot
|
|
267
|
+
* with none — so an index fast lane consults this to decline to full selection when the
|
|
268
|
+
* more-specific rule's predicate step could apply.
|
|
274
269
|
*/
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
const record = this.#records?.get(key);
|
|
283
|
-
const index = record === undefined ? -1 : record.bindings.indexOf(binding);
|
|
284
|
-
if (record === undefined || index === -1) {
|
|
285
|
-
return false;
|
|
270
|
+
hasPredicateCandidate(token) {
|
|
271
|
+
const bindings = this.getRecorded(token);
|
|
272
|
+
for (let index = 0; index < bindings.length; index += 1) {
|
|
273
|
+
if (bindings[index].predicate !== undefined) {
|
|
274
|
+
return true;
|
|
286
275
|
}
|
|
287
|
-
this.#bump();
|
|
288
|
-
// Replaced, never spliced: a walk holding the current array must not lose its place.
|
|
289
|
-
record.bindings = record.bindings.toSpliced(index, 1);
|
|
290
|
-
this.#deindexSlot(record, binding);
|
|
291
|
-
this.#settle(key, record);
|
|
292
276
|
}
|
|
293
|
-
|
|
294
|
-
return true;
|
|
277
|
+
return false;
|
|
295
278
|
}
|
|
296
|
-
/**
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
*/
|
|
300
|
-
setMany(binding) {
|
|
301
|
-
this.#bump();
|
|
302
|
-
// Set before any (re)indexing so `#indexSlot` sees a member and leaves it out of every slot.
|
|
303
|
-
writableMembership(binding).isMany = true;
|
|
304
|
-
if (this.#promoteLoneToRecord(binding.token, binding)) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
// A default occupant that becomes a member frees its slot, so drop the stale index entry. A
|
|
308
|
-
// member cannot collapse a record back to lone, so this path does not settle.
|
|
309
|
-
this.#clearDefaultOccupant(binding.token, binding);
|
|
279
|
+
/** The binding holding a token's default slot — lone or recorded — or `undefined` when none does. */
|
|
280
|
+
getDefaultSlotBinding(token) {
|
|
281
|
+
return this.#lone.get(token) ?? this.#records?.get(token)?.defaultOccupant;
|
|
310
282
|
}
|
|
311
283
|
/**
|
|
312
|
-
*
|
|
284
|
+
* The token's default-slot alias, which forwards a request's criteria to its target, or `undefined`.
|
|
313
285
|
*
|
|
314
|
-
* @remarks
|
|
315
|
-
*
|
|
316
|
-
* binding moves to a record because the lone map holds default-slot bindings with no predicate.
|
|
286
|
+
* @remarks A registry that never held an alias answers without probing, so a request missing every slot of a
|
|
287
|
+
* token pays for forwarding only where forwarding can happen.
|
|
317
288
|
*/
|
|
318
|
-
|
|
319
|
-
this.#
|
|
320
|
-
|
|
321
|
-
writablePredicate(binding).predicate = predicate;
|
|
322
|
-
if (this.#promoteLoneToRecord(binding.token, binding)) {
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
// The binding may have vacated the default slot, and a narrowed record can collapse back to lone.
|
|
326
|
-
const record = this.#clearDefaultOccupant(binding.token, binding);
|
|
327
|
-
if (record !== undefined) {
|
|
328
|
-
this.#settle(binding.token, record);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Moves a binding still holding the lone seat into a fresh one-binding record, returning whether it did.
|
|
333
|
-
*
|
|
334
|
-
* @remarks Its field (`isMany` or `predicate`) is written before this call, so the founding
|
|
335
|
-
* `#indexSlot` files it under the slot it now holds.
|
|
336
|
-
*/
|
|
337
|
-
#promoteLoneToRecord(key, binding) {
|
|
338
|
-
if (this.#lone.get(key) !== binding) {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
this.#lone.delete(key);
|
|
342
|
-
this.#createRecord(key, [binding]);
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
/** Clears the default-slot index entry a binding has vacated, returning its record if one exists. */
|
|
346
|
-
#clearDefaultOccupant(key, binding) {
|
|
347
|
-
const record = this.#records?.get(key);
|
|
348
|
-
if (record?.defaultOccupant === binding) {
|
|
349
|
-
record.defaultOccupant = undefined;
|
|
289
|
+
defaultSlotAlias(token) {
|
|
290
|
+
if ((this.#heldKinds & HELD_ALIAS) === 0) {
|
|
291
|
+
return undefined;
|
|
350
292
|
}
|
|
351
|
-
|
|
293
|
+
const occupant = this.getDefaultSlotBinding(token);
|
|
294
|
+
return occupant !== undefined && occupant.kind === "alias" ? occupant : undefined;
|
|
352
295
|
}
|
|
353
|
-
/**
|
|
354
|
-
|
|
355
|
-
return this.
|
|
296
|
+
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
297
|
+
getFastDefault(token) {
|
|
298
|
+
return this.#lone.get(token);
|
|
356
299
|
}
|
|
357
300
|
#createRecord(key, bindings) {
|
|
358
301
|
const record = createTokenRecord(bindings);
|
|
@@ -388,12 +331,10 @@ export class BindingRegistry {
|
|
|
388
331
|
}
|
|
389
332
|
// A selection may be walking this list inside a `when()` predicate. An append past the length it
|
|
390
333
|
// read cannot shift it, so it lands in place; a displacement replaces the array instead.
|
|
391
|
-
if (displacedBinding
|
|
392
|
-
record.bindings.
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
record.bindings = [...record.bindings.filter((candidate) => candidate !== displacedBinding), binding];
|
|
334
|
+
if (displacedBinding !== undefined) {
|
|
335
|
+
record.bindings = record.bindings.filter((candidate) => candidate !== displacedBinding);
|
|
396
336
|
}
|
|
337
|
+
record.bindings.push(binding);
|
|
397
338
|
this.#byId?.set(binding.identifier, binding);
|
|
398
339
|
this.#indexSlot(record, binding);
|
|
399
340
|
this.#settle(key, record);
|
|
@@ -407,9 +348,23 @@ export class BindingRegistry {
|
|
|
407
348
|
}
|
|
408
349
|
else if (bindings.length === 1 && isDefaultSlotBinding(bindings[0])) {
|
|
409
350
|
this.#records.delete(key);
|
|
410
|
-
this.#
|
|
351
|
+
this.#setLone(key, bindings[0]);
|
|
411
352
|
}
|
|
412
353
|
}
|
|
354
|
+
// Every write to the lone map goes through these three, so a cache of its entries has one place to follow them.
|
|
355
|
+
#setLone(key, binding) {
|
|
356
|
+
let lone = this.#lone;
|
|
357
|
+
if (lone === EMPTY_LONE) {
|
|
358
|
+
lone = this.#lone = new Map();
|
|
359
|
+
}
|
|
360
|
+
lone.set(key, binding);
|
|
361
|
+
}
|
|
362
|
+
#deleteLone(key) {
|
|
363
|
+
this.#lone.delete(key);
|
|
364
|
+
}
|
|
365
|
+
#clearLone() {
|
|
366
|
+
this.#lone.clear();
|
|
367
|
+
}
|
|
413
368
|
#ensureById() {
|
|
414
369
|
if (this.#byId === undefined) {
|
|
415
370
|
const byId = new Map();
|
|
@@ -474,6 +429,10 @@ export class BindingRegistry {
|
|
|
474
429
|
}
|
|
475
430
|
}
|
|
476
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Places a binding in its record by registration order: a fresh registration appends, and a binding a
|
|
434
|
+
* chain took out and put back returns to its place.
|
|
435
|
+
*/
|
|
477
436
|
/** A binding nothing has to be matched against: the default slot, no predicate, not a collection member. */
|
|
478
437
|
function isDefaultSlotBinding(binding) {
|
|
479
438
|
return binding.slot.tags.length === 0 && binding.predicate === undefined && !binding.isMany;
|
|
@@ -13,4 +13,21 @@ export declare function stateEpoch(): number;
|
|
|
13
13
|
*
|
|
14
14
|
* @since 0.10.0
|
|
15
15
|
*/
|
|
16
|
-
export declare function advanceStateEpoch(): void;
|
|
16
|
+
export declare function advanceStateEpoch(): void;
|
|
17
|
+
/**
|
|
18
|
+
* The container-disposal counter, held in a cell so a hot reader dereferences a field rather than
|
|
19
|
+
* paying a cross-module call — apart from {@link stateEpoch} so a per-request child's `dispose()`
|
|
20
|
+
* does not invalidate the chain-version memo that keeps deep resolves cheap. A child compares it to
|
|
21
|
+
* learn whether an ancestor was disposed, re-walking the chain only after a disposal somewhere.
|
|
22
|
+
*
|
|
23
|
+
* @since 0.11.0
|
|
24
|
+
*/
|
|
25
|
+
export declare const disposeEpochRef: {
|
|
26
|
+
value: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Advances the dispose epoch; a container's teardown calls it so descendants re-check the chain.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.11.0
|
|
32
|
+
*/
|
|
33
|
+
export declare function advanceDisposeEpoch(): void;
|
package/dist/core/state-epoch.js
CHANGED
|
@@ -18,4 +18,21 @@ export function stateEpoch() {
|
|
|
18
18
|
*/
|
|
19
19
|
export function advanceStateEpoch() {
|
|
20
20
|
epoch += 1;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The container-disposal counter, held in a cell so a hot reader dereferences a field rather than
|
|
24
|
+
* paying a cross-module call — apart from {@link stateEpoch} so a per-request child's `dispose()`
|
|
25
|
+
* does not invalidate the chain-version memo that keeps deep resolves cheap. A child compares it to
|
|
26
|
+
* learn whether an ancestor was disposed, re-walking the chain only after a disposal somewhere.
|
|
27
|
+
*
|
|
28
|
+
* @since 0.11.0
|
|
29
|
+
*/
|
|
30
|
+
export const disposeEpochRef = { value: 0 };
|
|
31
|
+
/**
|
|
32
|
+
* Advances the dispose epoch; a container's teardown calls it so descendants re-check the chain.
|
|
33
|
+
*
|
|
34
|
+
* @since 0.11.0
|
|
35
|
+
*/
|
|
36
|
+
export function advanceDisposeEpoch() {
|
|
37
|
+
disposeEpochRef.value += 1;
|
|
21
38
|
}
|
package/dist/core/tag.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Tag keys and the interned pairs they mint — the one way a slot criterion is built. */
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* The bits a tag-key mask holds: the width of a 32-bit integer.
|
|
4
4
|
*
|
|
5
5
|
* @remarks Ids past this wrap, so two keys can share a bit. The mask is a prefilter only — a shared
|
|
6
6
|
* bit costs a false positive that the identity comparison then rejects, never a false negative.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { getActiveContainer, getAmbientResolution } from "#ambient
|
|
2
|
-
import {
|
|
1
|
+
import { getActiveContainer, getAmbientResolution } from "#ambient-container";
|
|
2
|
+
import { decoratorMetadataOf } from "#decorators/metadata-record";
|
|
3
|
+
import { MissingContainerContextError, StaticMemberDecoratorError } from "#errors";
|
|
4
|
+
import { injectionSlotToResolveOptions } from "#injection/dependency-slot";
|
|
3
5
|
import { buildInjectionDescriptor } from "#injection/descriptor";
|
|
4
|
-
import {
|
|
5
|
-
import { INJECT_ACCESSOR_KEY } from "#metadata/metadata-keys";
|
|
6
|
+
import { INJECT_ACCESSOR_KEY } from "#metadata/keys";
|
|
6
7
|
/**
|
|
7
8
|
* The name of the class being constructed, or `undefined` when there is none to report.
|
|
8
9
|
*
|
|
@@ -36,7 +37,7 @@ export function inject(token, options) {
|
|
|
36
37
|
if (context.static) {
|
|
37
38
|
throw new StaticMemberDecoratorError("inject", String(context.name));
|
|
38
39
|
}
|
|
39
|
-
const meta = context
|
|
40
|
+
const meta = decoratorMetadataOf(context, "inject");
|
|
40
41
|
// Own bucket only: the metadata record inherits the base class's, and pushing into an inherited
|
|
41
42
|
// array would register this accessor on the base class instead.
|
|
42
43
|
if (!Object.hasOwn(meta, INJECT_ACCESSOR_KEY) || !Array.isArray(meta[INJECT_ACCESSOR_KEY])) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { decoratorMetadataOf } from "#decorators/metadata-record";
|
|
1
2
|
import { normalizeToDescriptor } from "#injection/descriptor";
|
|
2
|
-
import { INJECTABLE_KEY } from "#metadata/
|
|
3
|
+
import { INJECTABLE_KEY } from "#metadata/keys";
|
|
3
4
|
/**
|
|
4
5
|
* Creates an empty auto-register registry.
|
|
5
6
|
*
|
|
@@ -23,6 +24,7 @@ export function createAutoRegisterRegistry() {
|
|
|
23
24
|
*/
|
|
24
25
|
export function injectable(deps, options) {
|
|
25
26
|
return function (target, context) {
|
|
27
|
+
const meta = decoratorMetadataOf(context, "injectable");
|
|
26
28
|
const parameterMetadataList = (deps ?? []).map((dependency, index) => {
|
|
27
29
|
const descriptor = normalizeToDescriptor(dependency);
|
|
28
30
|
const baseParameterMetadata = {
|
|
@@ -44,7 +46,7 @@ export function injectable(deps, options) {
|
|
|
44
46
|
});
|
|
45
47
|
// Field decorators run before the class decorator — accessor @inject entries are
|
|
46
48
|
// already on context.metadata by the time this runs.
|
|
47
|
-
|
|
49
|
+
meta[INJECTABLE_KEY] = {
|
|
48
50
|
params: parameterMetadataList,
|
|
49
51
|
};
|
|
50
52
|
if (options?.autoRegister !== undefined) {
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { decoratorMetadataOf } from "#decorators/metadata-record";
|
|
2
|
+
import { StaticMemberDecoratorError, SymbolKeyedLifecycleError } from "#errors";
|
|
3
|
+
import { LIFECYCLE_KEY } from "#metadata/keys";
|
|
3
4
|
/** Records the decorated method under one lifecycle phase; both decorators differ only in that phase. */
|
|
4
5
|
function recordLifecycleMethod(phase) {
|
|
5
6
|
return function (target, context) {
|
|
6
7
|
if (context.static) {
|
|
7
8
|
throw new StaticMemberDecoratorError(phase, String(context.name));
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
+
// The lifecycle reader keys methods by their string name, so a symbol-keyed method can never be
|
|
11
|
+
// found again — fail here, where the declaration is, rather than at resolve.
|
|
12
|
+
if (typeof context.name === "symbol") {
|
|
13
|
+
throw new SymbolKeyedLifecycleError(phase, String(context.name));
|
|
14
|
+
}
|
|
15
|
+
const meta = decoratorMetadataOf(context, phase);
|
|
10
16
|
// Own bucket only: `context.metadata` inherits the base class's record, and writing through an
|
|
11
17
|
// inherited bucket would register this hook on the base class instead.
|
|
12
18
|
if (!Object.hasOwn(meta, LIFECYCLE_KEY)) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the metadata record a decorator context carries.
|
|
3
|
+
*
|
|
4
|
+
* @throws `MissingDecoratorMetadataError` when the transpiler handed none, which is how a runtime
|
|
5
|
+
* without `Symbol.metadata` shows up.
|
|
6
|
+
*
|
|
7
|
+
* @since 0.11.0
|
|
8
|
+
*/
|
|
9
|
+
export declare function decoratorMetadataOf(context: DecoratorContext, decoratorName: string): Record<string | symbol, unknown>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The metadata record a decorator writes into, checked once for every decorator the library ships.
|
|
3
|
+
*/
|
|
4
|
+
import { MissingDecoratorMetadataError } from "#errors";
|
|
5
|
+
/**
|
|
6
|
+
* Returns the metadata record a decorator context carries.
|
|
7
|
+
*
|
|
8
|
+
* @throws `MissingDecoratorMetadataError` when the transpiler handed none, which is how a runtime
|
|
9
|
+
* without `Symbol.metadata` shows up.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.11.0
|
|
12
|
+
*/
|
|
13
|
+
export function decoratorMetadataOf(context, decoratorName) {
|
|
14
|
+
// Widened, not asserted: the lib types promise a record that a runtime without `Symbol.metadata` never hands over.
|
|
15
|
+
const metadata = context.metadata;
|
|
16
|
+
if (metadata === undefined || metadata === null) {
|
|
17
|
+
throw new MissingDecoratorMetadataError(decoratorName);
|
|
18
|
+
}
|
|
19
|
+
return metadata;
|
|
20
|
+
}
|