@codefast/di 0.7.1 → 0.8.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 +27 -0
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +4 -5
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +3 -4
- package/dist/container/container.js.map +1 -1
- package/dist/core/binding.d.ts +15 -2
- package/dist/core/binding.d.ts.map +1 -1
- package/dist/core/binding.js +29 -4
- package/dist/core/binding.js.map +1 -1
- package/dist/core/registry.d.ts +0 -3
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +14 -61
- package/dist/core/registry.js.map +1 -1
- package/dist/core/tag.d.ts +26 -0
- package/dist/core/tag.d.ts.map +1 -1
- package/dist/core/tag.js +54 -11
- package/dist/core/tag.js.map +1 -1
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +28 -3
- package/dist/errors/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/injection/resolve-options.d.ts +17 -12
- package/dist/injection/resolve-options.d.ts.map +1 -1
- package/dist/injection/resolve-options.js +69 -15
- package/dist/injection/resolve-options.js.map +1 -1
- package/dist/introspection/dependency-graph.d.ts.map +1 -1
- package/dist/introspection/dependency-graph.js +22 -9
- package/dist/introspection/dependency-graph.js.map +1 -1
- package/dist/resolution/cache/binding-lookup-cache.d.ts +1 -3
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -1
- package/dist/resolution/cache/binding-lookup-cache.js +1 -38
- package/dist/resolution/cache/binding-lookup-cache.js.map +1 -1
- package/dist/resolution/plan/instantiation-plan.d.ts +4 -12
- package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -1
- package/dist/resolution/plan/instantiation-plan.js +9 -27
- package/dist/resolution/plan/instantiation-plan.js.map +1 -1
- package/dist/resolution/resolver.d.ts +12 -5
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +106 -141
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/select/binding-select.d.ts +4 -5
- package/dist/resolution/select/binding-select.d.ts.map +1 -1
- package/dist/resolution/select/binding-select.js +18 -20
- package/dist/resolution/select/binding-select.js.map +1 -1
- package/package.json +2 -2
- package/src/container/binding-builders.ts +10 -5
- package/src/container/container.ts +3 -4
- package/src/core/binding.ts +33 -5
- package/src/core/registry.ts +14 -66
- package/src/core/tag.ts +71 -17
- package/src/errors/errors.ts +28 -5
- package/src/index.ts +1 -1
- package/src/injection/resolve-options.ts +75 -18
- package/src/introspection/dependency-graph.ts +23 -9
- package/src/resolution/cache/binding-lookup-cache.ts +1 -41
- package/src/resolution/plan/instantiation-plan.ts +13 -38
- package/src/resolution/resolver.ts +120 -141
- package/src/resolution/select/binding-select.ts +18 -21
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NO_INSTANCE } from "#/core/binding";
|
|
2
|
-
import { NO_TAG_KEYS } from "#/core/tag";
|
|
2
|
+
import { NO_TAG_KEYS, slotNameCriterionOf } from "#/core/tag";
|
|
3
3
|
import { tokenName } from "#/core/token";
|
|
4
4
|
import { AsyncActivationError, AsyncResolutionError, CircularDependencyError, DisposedContainerError, InternalError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, TokenNotBoundError, } from "#/errors/errors";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveOptionsForSlot, singleCriterionForSlot, singleCriterionOnlyOf } from "#/injection/resolve-options";
|
|
6
6
|
import { SCOPED_MISS } from "#/lifecycle/scope-manager";
|
|
7
7
|
import { ActivationNeedCache } from "#/resolution/cache/activation-need";
|
|
8
8
|
import { BindingLookupCache } from "#/resolution/cache/binding-lookup-cache";
|
|
@@ -96,49 +96,47 @@ export class DependencyResolver {
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
// ── Binding lookup ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
99
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Finds the binding a request selects in this container, walking up to the parent on a miss.
|
|
101
|
+
*
|
|
102
|
+
* @remarks `singleCriterion` is the request's lone criterion, folded once by the caller so alias
|
|
103
|
+
* hops and parent walks do not re-fold it — `undefined` when the request carries none or several.
|
|
104
|
+
*/
|
|
105
|
+
#findBinding(token, options, resolutionStack, singleCriterion) {
|
|
100
106
|
if (options === undefined) {
|
|
101
107
|
const fastDefaultBinding = this.#registry.getFastDefault(token);
|
|
102
108
|
if (fastDefaultBinding !== undefined) {
|
|
103
109
|
return { binding: fastDefaultBinding, owner: this };
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
|
-
else if (
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
return { binding:
|
|
112
|
+
else if (singleCriterion !== undefined) {
|
|
113
|
+
const indexed = this.#registry.getSimpleTagged(token, singleCriterion);
|
|
114
|
+
if (indexed !== undefined && this.#satisfiesPredicate(indexed, options, resolutionStack)) {
|
|
115
|
+
return { binding: indexed, owner: this };
|
|
110
116
|
}
|
|
111
117
|
}
|
|
112
|
-
else
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// small list pays one length read and nothing else.
|
|
124
|
-
this.#registry.getAll(token).length > MULTI_TAG_INDEX_THRESHOLD &&
|
|
125
|
-
requestedTagKeyMask(options) !== NO_TAG_KEYS) {
|
|
126
|
-
// A name-less multi-tag request matches only name-less tagged slots, and every such slot
|
|
127
|
-
// lives in one of the two tag indexes — so their union is the whole candidate set and the
|
|
128
|
-
// token's full list never needs scanning. Selection still owns predicates and specificity.
|
|
129
|
-
const selected = this.#selectMultiTagged(token, options, resolutionStack);
|
|
130
|
-
if (selected !== undefined) {
|
|
131
|
-
return { binding: selected, owner: this };
|
|
132
|
-
}
|
|
133
|
-
return this.#parent === undefined ? undefined : this.#parent.#findBinding(token, options, resolutionStack);
|
|
118
|
+
else if (
|
|
119
|
+
// A threshold switches the data structure, never the semantics: under it the generic scan
|
|
120
|
+
// below beats walking the indexes, and both paths answer identically. Sized first, so a
|
|
121
|
+
// small list pays one length read and nothing else.
|
|
122
|
+
this.#registry.getAll(token).length > MULTI_TAG_INDEX_THRESHOLD &&
|
|
123
|
+
requestedTagKeyMask(options) !== NO_TAG_KEYS) {
|
|
124
|
+
// A multi-criterion request matches only slots whose every criterion it carries, and every
|
|
125
|
+
// such slot is in the two tag indexes — their union is the whole candidate set, unscanned.
|
|
126
|
+
const selected = this.#selectMultiTagged(token, options, resolutionStack);
|
|
127
|
+
if (selected !== undefined) {
|
|
128
|
+
return { binding: selected, owner: this };
|
|
134
129
|
}
|
|
130
|
+
return this.#parent === undefined
|
|
131
|
+
? undefined
|
|
132
|
+
: this.#parent.#findBinding(token, options, resolutionStack, singleCriterion);
|
|
135
133
|
}
|
|
136
134
|
const bindings = this.#registry.getAll(token);
|
|
137
135
|
if (bindings.length > 0) {
|
|
138
136
|
// A lone candidate is its own selection: matching it is the whole decision, with no
|
|
139
137
|
// specificity to weigh and no ambiguity to report.
|
|
140
138
|
const selected = bindings.length === 1
|
|
141
|
-
? this.#
|
|
139
|
+
? matchesSlot(bindings[0].slot, options) && this.#satisfiesPredicate(bindings[0], options, resolutionStack)
|
|
142
140
|
? bindings[0]
|
|
143
141
|
: undefined
|
|
144
142
|
: selectBinding(bindings, options, this.#makeConstraintContext(resolutionStack, options), tokenName(token));
|
|
@@ -147,7 +145,7 @@ export class DependencyResolver {
|
|
|
147
145
|
}
|
|
148
146
|
}
|
|
149
147
|
if (this.#parent !== undefined) {
|
|
150
|
-
return this.#parent.#findBinding(token, options, resolutionStack);
|
|
148
|
+
return this.#parent.#findBinding(token, options, resolutionStack, singleCriterion);
|
|
151
149
|
}
|
|
152
150
|
return undefined;
|
|
153
151
|
}
|
|
@@ -157,10 +155,12 @@ export class DependencyResolver {
|
|
|
157
155
|
* @remarks Alias hops are followed iteratively with exact cycle detection — a revisited alias
|
|
158
156
|
* token raises {@link CircularDependencyError} instead of overflowing the call stack.
|
|
159
157
|
*/
|
|
160
|
-
#requireBinding(token, options, resolutionStack) {
|
|
158
|
+
#requireBinding(token, options, resolutionStack, precomputedCriterion) {
|
|
159
|
+
// `null` is a caller's "folded: none" — only an absent precomputation re-folds.
|
|
160
|
+
const singleCriterion = precomputedCriterion === undefined ? singleCriterionOnlyOf(options) : (precomputedCriterion ?? undefined);
|
|
161
161
|
let currentToken = token;
|
|
162
162
|
let visitedAliasTokens;
|
|
163
|
-
let found = this.#findBinding(currentToken, options, resolutionStack);
|
|
163
|
+
let found = this.#findBinding(currentToken, options, resolutionStack, singleCriterion);
|
|
164
164
|
while (found !== undefined && found.binding.kind === "alias") {
|
|
165
165
|
const target = found.binding.target;
|
|
166
166
|
visitedAliasTokens ??= new Set([currentToken]);
|
|
@@ -169,7 +169,7 @@ export class DependencyResolver {
|
|
|
169
169
|
}
|
|
170
170
|
visitedAliasTokens.add(target);
|
|
171
171
|
currentToken = target;
|
|
172
|
-
found = this.#findBinding(currentToken, options, resolutionStack);
|
|
172
|
+
found = this.#findBinding(currentToken, options, resolutionStack, singleCriterion);
|
|
173
173
|
}
|
|
174
174
|
if (found === undefined) {
|
|
175
175
|
// Thrown here rather than from a helper: the error captures this stack, and an error path is
|
|
@@ -185,7 +185,7 @@ export class DependencyResolver {
|
|
|
185
185
|
* Binding lookup aligned with `resolve` — used by `Container.validate` without instantiating.
|
|
186
186
|
*/
|
|
187
187
|
peekBindingForValidate(token, options) {
|
|
188
|
-
return this.#findBinding(token, options, []);
|
|
188
|
+
return this.#findBinding(token, options, [], singleCriterionOnlyOf(options));
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
191
|
* Mirrors {@link DependencyResolver.resolveAll} candidate selection only (no instantiation).
|
|
@@ -329,22 +329,14 @@ export class DependencyResolver {
|
|
|
329
329
|
const entry = this.#lookup.defaultEntry(token);
|
|
330
330
|
return entry === null ? null : { binding: entry.binding };
|
|
331
331
|
},
|
|
332
|
-
// Exactly what #findBinding's
|
|
333
|
-
// is the compiler's cue to leave the selection to the runtime.
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
if (
|
|
332
|
+
// Exactly what #findBinding's single-criterion lane accepts, minus the half that reads a path:
|
|
333
|
+
// a predicate is the compiler's cue to leave the selection to the runtime.
|
|
334
|
+
lookupPathIndependentEntry: (token, options) => {
|
|
335
|
+
const singleCriterion = singleCriterionOnlyOf(options);
|
|
336
|
+
if (singleCriterion === undefined) {
|
|
337
337
|
return null;
|
|
338
338
|
}
|
|
339
|
-
|
|
340
|
-
},
|
|
341
|
-
// The named rule verbatim, on the single-tag lane's memo.
|
|
342
|
-
lookupPathIndependentTaggedEntry: (token, options) => {
|
|
343
|
-
const singleTag = singleTagOnlyOf(options);
|
|
344
|
-
if (singleTag === undefined) {
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
347
|
-
const entry = this.#lookup.taggedEntry(token, singleTag);
|
|
339
|
+
const entry = this.#lookup.taggedEntry(token, singleCriterion);
|
|
348
340
|
if (entry === null || entry.binding.predicate !== undefined || !matchesSlot(entry.binding.slot, options)) {
|
|
349
341
|
return null;
|
|
350
342
|
}
|
|
@@ -400,40 +392,28 @@ export class DependencyResolver {
|
|
|
400
392
|
this.#asyncPlanByBindingId.set(binding.id, compiled);
|
|
401
393
|
return compiled;
|
|
402
394
|
}
|
|
403
|
-
resolve(token, options, resolutionStack) {
|
|
404
|
-
//
|
|
405
|
-
// semantics involve no resolution context (constants, cached singletons).
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
// Everything else keeps the full path (context, activation, guards).
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
else if (options !== undefined) {
|
|
420
|
-
// Single-tag fast lane: the named lane's tagged twin, memoizing the chain walk.
|
|
421
|
-
const singleTag = singleTagOnlyOf(options);
|
|
422
|
-
if (singleTag !== undefined) {
|
|
423
|
-
const taggedEntry = this.#lookup.taggedEntry(token, singleTag);
|
|
424
|
-
if (taggedEntry !== null) {
|
|
425
|
-
const taggedBinding = taggedEntry.binding;
|
|
426
|
-
if (taggedEntry.owner.#isPlainConstant(taggedBinding)) {
|
|
427
|
-
return taggedBinding.value;
|
|
395
|
+
resolve(token, options, resolutionStack, precomputedCriterion) {
|
|
396
|
+
// Single-criterion fast lane (a lone name folds here too): memoized lookup, dispatching just
|
|
397
|
+
// the shapes whose semantics involve no resolution context (constants, cached singletons).
|
|
398
|
+
let singleCriterion;
|
|
399
|
+
if (options !== undefined) {
|
|
400
|
+
singleCriterion =
|
|
401
|
+
precomputedCriterion === undefined ? singleCriterionOnlyOf(options) : (precomputedCriterion ?? undefined);
|
|
402
|
+
if (singleCriterion !== undefined) {
|
|
403
|
+
const indexedEntry = this.#lookup.taggedEntry(token, singleCriterion);
|
|
404
|
+
if (indexedEntry !== null) {
|
|
405
|
+
const indexedBinding = indexedEntry.binding;
|
|
406
|
+
if (indexedEntry.owner.#isPlainConstant(indexedBinding)) {
|
|
407
|
+
return indexedBinding.value;
|
|
428
408
|
}
|
|
429
|
-
if (
|
|
430
|
-
return
|
|
409
|
+
if (indexedBinding.scope === "singleton" && indexedBinding.instance !== NO_INSTANCE) {
|
|
410
|
+
return indexedBinding.instance;
|
|
431
411
|
}
|
|
432
412
|
// Everything else keeps the full path (context, activation, guards).
|
|
433
413
|
}
|
|
434
414
|
}
|
|
435
415
|
}
|
|
436
|
-
const { binding, owner } = this.#requireBinding(token, options, resolutionStack);
|
|
416
|
+
const { binding, owner } = this.#requireBinding(token, options, resolutionStack, singleCriterion ?? null);
|
|
437
417
|
// A singleton owned by a parent resolver is resolved there, so the parent caches it.
|
|
438
418
|
if (binding.scope === "singleton" && owner !== this) {
|
|
439
419
|
return owner.#resolveBinding(binding, options, resolutionStack, owner);
|
|
@@ -584,15 +564,15 @@ export class DependencyResolver {
|
|
|
584
564
|
return this.resolveAll(dep.token, options, resolutionStack);
|
|
585
565
|
}
|
|
586
566
|
if (dep.optional) {
|
|
587
|
-
return this.resolveOptional(dep.token, options, resolutionStack);
|
|
567
|
+
return this.resolveOptional(dep.token, options, resolutionStack, singleCriterionForSlot(dep));
|
|
588
568
|
}
|
|
589
569
|
if (options === undefined) {
|
|
590
570
|
return this.resolveFromContext(dep.token, resolutionStack);
|
|
591
571
|
}
|
|
592
|
-
return this.resolve(dep.token, options, resolutionStack);
|
|
572
|
+
return this.resolve(dep.token, options, resolutionStack, singleCriterionForSlot(dep));
|
|
593
573
|
}
|
|
594
|
-
resolveOptional(token, options, resolutionStack) {
|
|
595
|
-
const entry = this.#findBinding(token, options, resolutionStack);
|
|
574
|
+
resolveOptional(token, options, resolutionStack, precomputedCriterion) {
|
|
575
|
+
const entry = this.#findBinding(token, options, resolutionStack, precomputedCriterion === undefined ? singleCriterionOnlyOf(options) : (precomputedCriterion ?? undefined));
|
|
596
576
|
if (entry === undefined) {
|
|
597
577
|
return undefined;
|
|
598
578
|
}
|
|
@@ -679,8 +659,17 @@ export class DependencyResolver {
|
|
|
679
659
|
}
|
|
680
660
|
return this.#resolveBindingAsync(binding, undefined, resolutionStack, branchDepth, owner);
|
|
681
661
|
}
|
|
682
|
-
|
|
683
|
-
|
|
662
|
+
/**
|
|
663
|
+
* Instantiates one owned binding directly, bypassing selection.
|
|
664
|
+
*
|
|
665
|
+
* @remarks Warm-up must build the binding it inspected: re-selecting by the slot's own criteria
|
|
666
|
+
* could pick a different candidate whose criteria are a subset of them.
|
|
667
|
+
*/
|
|
668
|
+
warmBindingAsync(binding, options) {
|
|
669
|
+
return this.#resolveBindingAsync(binding, options, [], ROOT_BRANCH, this);
|
|
670
|
+
}
|
|
671
|
+
async resolveAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH, precomputedCriterion) {
|
|
672
|
+
const { binding, owner } = this.#requireBinding(token, options, resolutionStack, precomputedCriterion);
|
|
684
673
|
if (binding.scope === "singleton" && owner !== this) {
|
|
685
674
|
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
686
675
|
}
|
|
@@ -838,15 +827,15 @@ export class DependencyResolver {
|
|
|
838
827
|
return this.resolveAllAsync(dep.token, options, resolutionStack, branchDepth);
|
|
839
828
|
}
|
|
840
829
|
if (dep.optional) {
|
|
841
|
-
return this.resolveOptionalAsync(dep.token, options, resolutionStack, branchDepth);
|
|
830
|
+
return this.resolveOptionalAsync(dep.token, options, resolutionStack, branchDepth, singleCriterionForSlot(dep));
|
|
842
831
|
}
|
|
843
832
|
if (options === undefined) {
|
|
844
833
|
return this.resolveAsyncFromContext(dep.token, resolutionStack, branchDepth);
|
|
845
834
|
}
|
|
846
|
-
return this.resolveAsync(dep.token, options, resolutionStack, branchDepth);
|
|
835
|
+
return this.resolveAsync(dep.token, options, resolutionStack, branchDepth, singleCriterionForSlot(dep));
|
|
847
836
|
}
|
|
848
|
-
async resolveOptionalAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
|
|
849
|
-
const entry = this.#findBinding(token, options, resolutionStack);
|
|
837
|
+
async resolveOptionalAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH, precomputedCriterion) {
|
|
838
|
+
const entry = this.#findBinding(token, options, resolutionStack, precomputedCriterion === undefined ? singleCriterionOnlyOf(options) : (precomputedCriterion ?? undefined));
|
|
850
839
|
if (entry === undefined) {
|
|
851
840
|
return undefined;
|
|
852
841
|
}
|
|
@@ -883,56 +872,34 @@ export class DependencyResolver {
|
|
|
883
872
|
}
|
|
884
873
|
return result;
|
|
885
874
|
}
|
|
886
|
-
/** Every binding the chain's name indexes hold for one name, nearest container first. */
|
|
887
|
-
#namedBindingsFromChain(token, name) {
|
|
888
|
-
// A name resolves to at most one binding per registry, so a root container's answer is built
|
|
889
|
-
// whole rather than grown — the list is sized at its allocation.
|
|
890
|
-
const ownBinding = this.#registry.getSimpleNamed(token, name);
|
|
891
|
-
if (this.#parent === undefined) {
|
|
892
|
-
return ownBinding === undefined ? [] : [ownBinding];
|
|
893
|
-
}
|
|
894
|
-
const result = ownBinding === undefined ? [] : [ownBinding];
|
|
895
|
-
for (let current = this.#parent; current !== undefined; current = current.#parent) {
|
|
896
|
-
const binding = current.#registry.getSimpleNamed(token, name);
|
|
897
|
-
if (binding !== undefined) {
|
|
898
|
-
result.push(binding);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
return result;
|
|
902
|
-
}
|
|
903
875
|
/**
|
|
904
|
-
* The candidates
|
|
876
|
+
* The candidates the index can name outright, or `null` when the request needs full selection.
|
|
905
877
|
*
|
|
906
|
-
* @remarks Kept off `#candidateBindings` so that method stays the size it was: a request
|
|
907
|
-
* index
|
|
878
|
+
* @remarks Kept off `#candidateBindings` so that method stays the size it was: a request the
|
|
879
|
+
* index cannot serve must not pay for the shape it does.
|
|
908
880
|
* An index has matched the slot already, but a hit may still carry a predicate, and evaluating
|
|
909
881
|
* that is the selection path's job.
|
|
910
882
|
*/
|
|
911
883
|
#indexedCandidates(token, options, resolutionStack) {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
return anyPredicate(named)
|
|
915
|
-
? selectAllBindings(named, options, this.#makeConstraintContext(resolutionStack, options))
|
|
916
|
-
: named;
|
|
917
|
-
}
|
|
918
|
-
const singleTag = singleTagOnlyOf(options);
|
|
919
|
-
if (singleTag === undefined) {
|
|
884
|
+
const singleCriterion = singleCriterionOnlyOf(options);
|
|
885
|
+
if (singleCriterion === undefined) {
|
|
920
886
|
return null;
|
|
921
887
|
}
|
|
922
|
-
const
|
|
923
|
-
return anyPredicate(
|
|
924
|
-
? selectAllBindings(
|
|
925
|
-
:
|
|
888
|
+
const indexed = this.#taggedBindingsFromChain(token, singleCriterion);
|
|
889
|
+
return anyPredicate(indexed)
|
|
890
|
+
? selectAllBindings(indexed, options, this.#makeConstraintContext(resolutionStack, options))
|
|
891
|
+
: indexed;
|
|
926
892
|
}
|
|
927
893
|
/**
|
|
928
|
-
* Every binding the chain's
|
|
894
|
+
* Every binding the chain's criterion indexes hold for one criterion, nearest container first.
|
|
929
895
|
*
|
|
930
|
-
* @remarks A request for one
|
|
931
|
-
* is the whole candidate set rather than a prefilter — a
|
|
896
|
+
* @remarks A request for exactly one criterion matches exactly the bindings the index keys, so
|
|
897
|
+
* this is the whole candidate set rather than a prefilter — a multi-criterion slot cannot satisfy
|
|
898
|
+
* it.
|
|
932
899
|
*/
|
|
933
900
|
#taggedBindingsFromChain(token, tag) {
|
|
934
|
-
// A
|
|
935
|
-
// rather than grown — the
|
|
901
|
+
// A criterion matches at most one binding per registry, so a root container's answer is built
|
|
902
|
+
// whole rather than grown — the list is sized at its allocation.
|
|
936
903
|
const ownBinding = this.#registry.getSimpleTagged(token, tag);
|
|
937
904
|
if (this.#parent === undefined) {
|
|
938
905
|
return ownBinding === undefined ? [] : [ownBinding];
|
|
@@ -979,14 +946,19 @@ export class DependencyResolver {
|
|
|
979
946
|
}
|
|
980
947
|
return buildConstraintContext(resolutionStack, options);
|
|
981
948
|
}
|
|
982
|
-
/** Selection for a
|
|
949
|
+
/** Selection for a multi-criterion request, over the union of the two tag indexes. */
|
|
983
950
|
#selectMultiTagged(token, options, resolutionStack) {
|
|
984
951
|
const candidates = [];
|
|
985
|
-
|
|
952
|
+
const gathered = [];
|
|
953
|
+
if (options.name !== undefined) {
|
|
954
|
+
// Read, not minted: an unminted name has no criterion, so no slot can carry it.
|
|
955
|
+
this.#gatherTagCandidates(token, slotNameCriterionOf(options.name), candidates, gathered);
|
|
956
|
+
}
|
|
957
|
+
this.#gatherTagCandidates(token, options.tag, candidates, gathered);
|
|
986
958
|
const listed = options.tags;
|
|
987
959
|
if (listed !== undefined) {
|
|
988
960
|
for (let index = 0; index < listed.length; index += 1) {
|
|
989
|
-
this.#gatherTagCandidates(token, listed[index], candidates);
|
|
961
|
+
this.#gatherTagCandidates(token, listed[index], candidates, gathered);
|
|
990
962
|
}
|
|
991
963
|
}
|
|
992
964
|
if (candidates.length === 0) {
|
|
@@ -995,31 +967,24 @@ export class DependencyResolver {
|
|
|
995
967
|
return selectBinding(candidates, options, this.#makeConstraintContext(resolutionStack, options), tokenName(token));
|
|
996
968
|
}
|
|
997
969
|
/** One request criterion's candidates: its exact single-tag binding, plus its first-tag bucket. */
|
|
998
|
-
#gatherTagCandidates(token, criterion, out) {
|
|
999
|
-
|
|
970
|
+
#gatherTagCandidates(token, criterion, out, gathered) {
|
|
971
|
+
// Distinct criteria never share a binding — a slot lives in exactly one bucket — so deduping
|
|
972
|
+
// by criterion covers a request repeating one across its spellings, without scanning `out`.
|
|
973
|
+
if (criterion === undefined || gathered.includes(criterion)) {
|
|
1000
974
|
return;
|
|
1001
975
|
}
|
|
1002
|
-
|
|
976
|
+
gathered.push(criterion);
|
|
1003
977
|
const single = this.#registry.getSimpleTagged(token, criterion);
|
|
1004
|
-
if (single !== undefined
|
|
978
|
+
if (single !== undefined) {
|
|
1005
979
|
out.push(single);
|
|
1006
980
|
}
|
|
1007
981
|
const bucket = this.#registry.getMultiTagged(token, criterion);
|
|
1008
982
|
if (bucket !== undefined) {
|
|
1009
983
|
for (let index = 0; index < bucket.length; index += 1) {
|
|
1010
|
-
|
|
1011
|
-
if (!out.includes(candidate)) {
|
|
1012
|
-
out.push(candidate);
|
|
1013
|
-
}
|
|
984
|
+
out.push(bucket[index]);
|
|
1014
985
|
}
|
|
1015
986
|
}
|
|
1016
987
|
}
|
|
1017
|
-
#matchesBindingFast(binding, options, resolutionStack) {
|
|
1018
|
-
if (!matchesSlot(binding.slot, options)) {
|
|
1019
|
-
return false;
|
|
1020
|
-
}
|
|
1021
|
-
return this.#satisfiesPredicate(binding, options, resolutionStack);
|
|
1022
|
-
}
|
|
1023
988
|
/** The predicate half of a match, for a lane whose index has already settled the slot. */
|
|
1024
989
|
#satisfiesPredicate(binding, options, resolutionStack) {
|
|
1025
990
|
const predicate = binding.predicate;
|