@codefast/di 0.11.0 → 0.13.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 +151 -0
- package/README.md +128 -31
- package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +21 -28
- package/dist/container/{binding-builders.js → binding-chain.js} +53 -163
- package/dist/container/container.d.ts +11 -3
- package/dist/container/container.js +176 -102
- package/dist/core/binding-builders.d.ts +117 -0
- package/dist/core/binding-declaration.d.ts +9 -2
- package/dist/core/binding-declaration.js +5 -4
- package/dist/core/binding.d.ts +21 -149
- package/dist/core/binding.js +15 -25
- package/dist/core/module.d.ts +1 -1
- package/dist/core/registry.d.ts +9 -24
- package/dist/core/registry.js +30 -155
- package/dist/core/tag.js +1 -1
- package/dist/decorators/inject.d.ts +3 -1
- package/dist/decorators/inject.js +5 -5
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/lifecycle.d.ts +20 -0
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +7 -3
- package/dist/decorators/{decorator-metadata.js → metadata-record.js} +1 -1
- package/dist/{errors/errors.d.ts → errors.d.ts} +29 -19
- package/dist/{errors/errors.js → errors.js} +43 -25
- package/dist/index.d.ts +12 -10
- package/dist/index.js +6 -6
- package/dist/injection/descriptor.d.ts +1 -1
- package/dist/introspection/dependency-graph.d.ts +1 -1
- package/dist/introspection/dependency-graph.js +3 -4
- package/dist/introspection/explanation.d.ts +101 -0
- package/dist/introspection/explanation.js +169 -0
- package/dist/introspection/inspector.d.ts +9 -2
- package/dist/introspection/inspector.js +25 -20
- package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +11 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +51 -28
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +23 -6
- package/dist/lifecycle/{scope-manager.js → scopes.js} +93 -20
- package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +1 -1
- package/dist/metadata/{metadata-keys.js → keys.js} +1 -1
- 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} +1 -1
- package/dist/metadata/{verifying-metadata-reader.js → verifying-reader.js} +2 -2
- package/dist/resolution/cache/activation-need.d.ts +1 -1
- package/dist/resolution/cache/activation-need.js +5 -1
- package/dist/resolution/cache/class-introspector.d.ts +27 -36
- package/dist/resolution/cache/class-introspector.js +61 -159
- package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -2
- package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +40 -8
- package/dist/resolution/context.d.ts +1 -1
- package/dist/resolution/context.js +1 -1
- package/dist/resolution/{path/resolution-path.d.ts → path.d.ts} +12 -2
- package/dist/resolution/{path/resolution-path.js → path.js} +23 -3
- package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +2 -4
- package/dist/resolution/plan/{plan-codegen.js → codegen.js} +2 -4
- package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
- package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +4 -4
- package/dist/resolution/resolver.d.ts +19 -7
- package/dist/resolution/resolver.js +343 -125
- 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 +80 -72
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- package/dist/decorators/lifecycle-decorators.d.ts +0 -14
- /package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +0 -0
- /package/dist/{ambient/active-container.js → ambient-container.js} +0 -0
- /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
- /package/dist/decorators/{decorator-metadata.d.ts → metadata-record.d.ts} +0 -0
- /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
- /package/dist/injection/{resolve-options.js → dependency-slot.js} +0 -0
- /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
- /package/dist/{errors → introspection}/diagnostics.js +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { bindingSlotToString, NO_INSTANCE } from "#core/binding";
|
|
2
2
|
import { slotNameCriterionOf } from "#core/tag";
|
|
3
3
|
import { tokenName } from "#core/token";
|
|
4
|
-
import { AsyncActivationError, AsyncResolutionError, CircularDependencyError, DisposedContainerError, InternalError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, TokenNotBoundError, } from "#errors
|
|
5
|
-
import { loneTagBesideNameOf, resolveOptionsForSlot, singleCriterionForSlot, singleCriterionOnlyOf, } from "#injection/
|
|
6
|
-
import { SCOPED_MISS } from "#lifecycle/
|
|
4
|
+
import { AsyncActivationError, AsyncResolutionError, CircularDependencyError, DisposedContainerError, InternalError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, TokenNotBoundError, } from "#errors";
|
|
5
|
+
import { loneTagBesideNameOf, resolveOptionsForSlot, singleCriterionForSlot, singleCriterionOnlyOf, } from "#injection/dependency-slot";
|
|
6
|
+
import { SCOPED_MISS } from "#lifecycle/scopes";
|
|
7
7
|
import { settleInOrder } from "#resolution/async-fan-out";
|
|
8
8
|
import { ActivationNeedCache } from "#resolution/cache/activation-need";
|
|
9
|
-
import { BindingLookupCache } from "#resolution/cache/binding-lookup-cache";
|
|
10
9
|
import { ClassIntrospector } from "#resolution/cache/class-introspector";
|
|
10
|
+
import { BindingLookupCache } from "#resolution/cache/lookup";
|
|
11
11
|
import { AsyncLevelContext, DefaultConstraintContext, DefaultResolutionContext } from "#resolution/context";
|
|
12
|
-
import { branchDepthOf, buildResolutionFrame, cycleNamesOf, enterSyncPath, extendResolutionBranch, leaveSyncPath, ROOT_BRANCH, UNOWNED_BRANCH, } from "#resolution/path
|
|
13
|
-
import { InstantiationPlanCompiler, PLAN_RETRY } from "#resolution/plan/
|
|
14
|
-
import { matchesSlot, selectAllBindings, selectBinding } from "#resolution/select/
|
|
12
|
+
import { branchDepthOf, buildResolutionFrame, cycleNamesOf, enterSyncPath, extendResolutionBranch, leaveSyncPath, missPathOf, ROOT_BRANCH, UNOWNED_BRANCH, } from "#resolution/path";
|
|
13
|
+
import { InstantiationPlanCompiler, PLAN_RETRY } from "#resolution/plan/compiler";
|
|
14
|
+
import { matchesSlot, selectAllBindings, selectBinding } from "#resolution/select/candidates";
|
|
15
15
|
const EMPTY_FRAME_LIST = [];
|
|
16
16
|
const EMPTY_PARAM_LIST = [];
|
|
17
|
+
// A class with no params is constructed with no arguments, so no dependency array is built for it.
|
|
18
|
+
const NO_ARGUMENTS = [];
|
|
17
19
|
/** Plans compiled so far, with the `null` unplannable marks left out. */
|
|
18
20
|
function countCompiledPlans(plans) {
|
|
19
21
|
let count = 0;
|
|
@@ -190,9 +192,9 @@ export class DependencyResolver {
|
|
|
190
192
|
* can answer still does.
|
|
191
193
|
*/
|
|
192
194
|
#forwardingAliasOrParent(token, options, resolutionStack, singleCriterion) {
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
195
|
-
return { binding:
|
|
195
|
+
const defaultSlotAlias = this.#registry.defaultSlotAlias(token);
|
|
196
|
+
if (defaultSlotAlias !== undefined) {
|
|
197
|
+
return { binding: defaultSlotAlias, owner: this };
|
|
196
198
|
}
|
|
197
199
|
return this.#parent === undefined
|
|
198
200
|
? undefined
|
|
@@ -250,10 +252,11 @@ export class DependencyResolver {
|
|
|
250
252
|
// dominated by that capture. Bindings under the token anywhere in the chain mean the request
|
|
251
253
|
// matched none of them, so a child reports the same miss its parent would.
|
|
252
254
|
const bound = this.#allBindingsFromChain(currentToken);
|
|
255
|
+
const path = missPathOf(resolutionStack, token, visitedAliasTokens);
|
|
253
256
|
if (bound.length > 0) {
|
|
254
|
-
throw new NoMatchingBindingError(tokenName(currentToken), options ?? {}, bound.map((binding) => bindingSlotToString(binding.slot)));
|
|
257
|
+
throw new NoMatchingBindingError(tokenName(currentToken), options ?? {}, bound.map((binding) => bindingSlotToString(binding.slot)), path);
|
|
255
258
|
}
|
|
256
|
-
throw new TokenNotBoundError(tokenName(currentToken));
|
|
259
|
+
throw new TokenNotBoundError(tokenName(currentToken), path);
|
|
257
260
|
}
|
|
258
261
|
return found;
|
|
259
262
|
}
|
|
@@ -293,6 +296,36 @@ export class DependencyResolver {
|
|
|
293
296
|
peekCandidateBindingsForValidate(token, options) {
|
|
294
297
|
return this.#candidateBindings(token, options, []);
|
|
295
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* The error `resolve` raises for a request nothing selects, or `undefined` while a `when()` candidate leaves it open.
|
|
301
|
+
*
|
|
302
|
+
* @remarks A predicate reads the resolution path, which a static walk does not have, so a slot-matching candidate
|
|
303
|
+
* that carries one may still be selected when the request is really made.
|
|
304
|
+
*/
|
|
305
|
+
missForValidate(token, options, path) {
|
|
306
|
+
const bound = this.#allBindingsFromChain(token);
|
|
307
|
+
if (bound.length === 0) {
|
|
308
|
+
return new TokenNotBoundError(tokenName(token), path);
|
|
309
|
+
}
|
|
310
|
+
for (const binding of bound) {
|
|
311
|
+
if (binding.predicate !== undefined && !binding.isMany && matchesSlot(binding.slot, options)) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return new NoMatchingBindingError(tokenName(token), options ?? {}, bound.map((binding) => bindingSlotToString(binding.slot)), path);
|
|
316
|
+
}
|
|
317
|
+
/** The resolver whose chain a binding this one can see resolves its dependencies from, as a singleton does. */
|
|
318
|
+
ownerForValidate(binding) {
|
|
319
|
+
return this.#ownerOf(binding);
|
|
320
|
+
}
|
|
321
|
+
/** Deactivates a scoped instance of a binding this resolver owns, with its hooks and reader, when it owes a teardown. */
|
|
322
|
+
deactivateScoped(binding, instance) {
|
|
323
|
+
const lifecycle = this.#lifecycle;
|
|
324
|
+
const reader = this.#metadataReader;
|
|
325
|
+
return lifecycle.owesDeactivation(binding, reader)
|
|
326
|
+
? lifecycle.runDeactivation(binding, instance, reader)
|
|
327
|
+
: undefined;
|
|
328
|
+
}
|
|
296
329
|
// ── Sync resolve ───────────────────────────────────────────────────────────────────────────────────────────────────
|
|
297
330
|
resolveFromContext(token, resolutionStack) {
|
|
298
331
|
// Hot lane: own-registry fast default. Fall back to the chain-versioned memo
|
|
@@ -514,7 +547,7 @@ export class DependencyResolver {
|
|
|
514
547
|
}
|
|
515
548
|
return this.resolve(token, options, resolutionStack);
|
|
516
549
|
},
|
|
517
|
-
// Dispatches exactly as #
|
|
550
|
+
// Dispatches exactly as #settleDep does, for the async lane's escapes.
|
|
518
551
|
resolveEscapedAsync: (token, options, arity, resolutionStack) => {
|
|
519
552
|
if (arity === "all") {
|
|
520
553
|
return this.resolveAllAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
@@ -645,7 +678,7 @@ export class DependencyResolver {
|
|
|
645
678
|
this.#scope.setSingleton(binding, activated);
|
|
646
679
|
}
|
|
647
680
|
else if (scope === "scoped") {
|
|
648
|
-
this.#scope.setScoped(binding, activated);
|
|
681
|
+
this.#scope.setScoped(binding, activated, owner);
|
|
649
682
|
}
|
|
650
683
|
return activated;
|
|
651
684
|
}
|
|
@@ -691,10 +724,13 @@ export class DependencyResolver {
|
|
|
691
724
|
case "dynamic-async":
|
|
692
725
|
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
693
726
|
case "class": {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
727
|
+
// One lookup of the class's facts serves the params, the accessor need and the construction.
|
|
728
|
+
const introspector = this.#introspector();
|
|
729
|
+
const target = binding.target;
|
|
730
|
+
const facts = introspector.facts(target);
|
|
731
|
+
const params = this.#constructorParams(target, facts);
|
|
732
|
+
const needsActiveContainer = introspector.needsActiveContainerOf(target, facts);
|
|
733
|
+
return introspector.construct(target, params.length === 0 ? NO_ARGUMENTS : this.#resolveDeps(params, resolutionStack), needsActiveContainer, needsActiveContainer ? this.#ambientResolutionFor(resolutionStack) : undefined);
|
|
698
734
|
}
|
|
699
735
|
case "resolved": {
|
|
700
736
|
const deps = this.#resolveDeps(binding.deps, resolutionStack);
|
|
@@ -716,8 +752,12 @@ export class DependencyResolver {
|
|
|
716
752
|
* @remarks A class the metadata reader knows nothing about is constructible only if it declares
|
|
717
753
|
* no parameters; anything else is a missing `@injectable()`.
|
|
718
754
|
*/
|
|
719
|
-
#constructorParams(target) {
|
|
720
|
-
|
|
755
|
+
#constructorParams(target, facts) {
|
|
756
|
+
// Metadata is fixed once a class is defined, so the answer is too; only a throw is left unsettled.
|
|
757
|
+
return (facts.params ??= this.#settleConstructorParams(target, facts));
|
|
758
|
+
}
|
|
759
|
+
#settleConstructorParams(target, facts) {
|
|
760
|
+
const meta = this.#introspector().constructorMetadataOf(target, facts);
|
|
721
761
|
if (meta !== undefined) {
|
|
722
762
|
return meta.params;
|
|
723
763
|
}
|
|
@@ -824,30 +864,35 @@ export class DependencyResolver {
|
|
|
824
864
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
825
865
|
resolved[index] = this.#resolveCandidateSync(candidates[index], undefined, resolutionStack);
|
|
826
866
|
}
|
|
827
|
-
|
|
828
|
-
this.#settleCollectionValues(memo);
|
|
867
|
+
this.#settleOnRepeat(memo);
|
|
829
868
|
return resolved;
|
|
830
869
|
}
|
|
831
|
-
/** The async twin of `resolveRootCollection`: a stable value list
|
|
870
|
+
/** The async twin of `resolveRootCollection`: a stable value list answers at once, candidates fan out as usual. */
|
|
832
871
|
resolveRootCollectionAsync(token) {
|
|
833
872
|
// The async lane appends to its branch and never unwinds, so it works on a stack of its own.
|
|
834
873
|
const resolutionStack = [];
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
874
|
+
try {
|
|
875
|
+
// Selection runs `when()` predicates and follows aliases, either of which may throw.
|
|
876
|
+
const memo = this.#rootCollection(token, resolutionStack);
|
|
877
|
+
if (memo.values !== undefined && memo.activationVersion === this.#chainActivationVersion()) {
|
|
878
|
+
return Promise.resolve(memo.values.slice());
|
|
879
|
+
}
|
|
880
|
+
return asPromise(this.#settleCandidates(memo.candidates, undefined, resolutionStack, UNOWNED_BRANCH, (values) => {
|
|
881
|
+
this.#settleOnRepeat(memo);
|
|
882
|
+
return values;
|
|
883
|
+
}));
|
|
884
|
+
}
|
|
885
|
+
catch (collectionError) {
|
|
886
|
+
return Promise.reject(collectionError);
|
|
838
887
|
}
|
|
839
|
-
return settleInOrder(memo.candidates.map((candidate) => this.#resolveCandidateAsync(candidate, undefined, resolutionStack, UNOWNED_BRANCH)), (values) => {
|
|
840
|
-
this.#settleCollectionValues(memo);
|
|
841
|
-
return values;
|
|
842
|
-
});
|
|
843
888
|
}
|
|
844
889
|
/**
|
|
845
890
|
* The memoized candidate list of a root-level, options-less collection, built on its first read.
|
|
846
891
|
*
|
|
847
892
|
* @remarks Sound because a `when()` predicate is pure over its context and the root context is a
|
|
848
893
|
* constant: the list can only change when a registry in the chain does, which is the version the
|
|
849
|
-
* memo is stamped with.
|
|
850
|
-
* and no activation hook exists anywhere in the chain.
|
|
894
|
+
* memo is stamped with. From the second read on, the value list is kept too while every member is
|
|
895
|
+
* a hook-free constant and no activation hook exists anywhere in the chain.
|
|
851
896
|
*/
|
|
852
897
|
#rootCollection(token, resolutionStack) {
|
|
853
898
|
const memo = this.#lookup.collection(token);
|
|
@@ -855,11 +900,20 @@ export class DependencyResolver {
|
|
|
855
900
|
return memo;
|
|
856
901
|
}
|
|
857
902
|
const candidates = this.#candidateBindings(token, undefined, resolutionStack);
|
|
858
|
-
const entry = { candidates, values: undefined, activationVersion: -1 };
|
|
859
|
-
this.#settleCollectionValues(entry);
|
|
903
|
+
const entry = { candidates, values: undefined, activationVersion: -1, readBefore: false };
|
|
860
904
|
this.#lookup.rememberCollection(token, entry);
|
|
861
905
|
return entry;
|
|
862
906
|
}
|
|
907
|
+
// The first read interprets and the read that repeats it settles the value list, as a plan compiles on repeat;
|
|
908
|
+
// the members either read materialised may have made the list stable for the next one.
|
|
909
|
+
#settleOnRepeat(entry) {
|
|
910
|
+
if (entry.readBefore) {
|
|
911
|
+
this.#settleCollectionValues(entry);
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
entry.readBefore = true;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
863
917
|
/**
|
|
864
918
|
* Fills a collection memo's value list once every member is stable: a hook-free constant, or a
|
|
865
919
|
* hook-free singleton whose instance is cached — anything that changes either bumps a registry
|
|
@@ -894,6 +948,18 @@ export class DependencyResolver {
|
|
|
894
948
|
const selected = selectAllBindings(allBindings, options, this.#makeConstraintContext(resolutionStack, options));
|
|
895
949
|
return this.#withoutDanglingAliases(selected, options, resolutionStack);
|
|
896
950
|
}
|
|
951
|
+
// A chain none of whose registries ever held an alias has none to drop, which spares the scan.
|
|
952
|
+
#chainHeldAlias() {
|
|
953
|
+
if (this.#registry.hasHeldAlias) {
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
956
|
+
for (let current = this.#parent; current !== undefined; current = current.#parent) {
|
|
957
|
+
if (current.#registry.hasHeldAlias) {
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
897
963
|
/**
|
|
898
964
|
* The candidates with any alias whose chain ends nowhere dropped, so a fan-out skips a dangling
|
|
899
965
|
* alias rather than throwing on it.
|
|
@@ -902,6 +968,9 @@ export class DependencyResolver {
|
|
|
902
968
|
* is returned untouched, so nothing is allocated for it.
|
|
903
969
|
*/
|
|
904
970
|
#withoutDanglingAliases(candidates, options, resolutionStack) {
|
|
971
|
+
if (!this.#chainHeldAlias()) {
|
|
972
|
+
return candidates;
|
|
973
|
+
}
|
|
905
974
|
let hasAlias = false;
|
|
906
975
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
907
976
|
if (candidates[index].kind === "alias") {
|
|
@@ -936,35 +1005,55 @@ export class DependencyResolver {
|
|
|
936
1005
|
}
|
|
937
1006
|
return this.#resolveAsyncDefaultEntry(entry.binding, entry.owner, resolutionStack, branchDepth);
|
|
938
1007
|
}
|
|
1008
|
+
// A `#settle*` lane answers with the value when its level completed in this tick and with a promise
|
|
1009
|
+
// only where something had to be awaited, so a singleton or scoped instance built without yielding
|
|
1010
|
+
// is cached before the fan-out that started it moves on to a sibling that may read it synchronously.
|
|
939
1011
|
#resolveAsyncDefaultEntry(binding, owner, resolutionStack, branchDepth) {
|
|
1012
|
+
try {
|
|
1013
|
+
return asPromise(this.#settleDefaultEntry(binding, owner, resolutionStack, branchDepth));
|
|
1014
|
+
}
|
|
1015
|
+
catch (entryError) {
|
|
1016
|
+
// Not `async`: a failure is turned into the rejection the entry point promises.
|
|
1017
|
+
return Promise.reject(entryError);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/** `resolveAsyncFromContext` for a dependency slot: the same lanes, answered in this tick where they can be. */
|
|
1021
|
+
#settleDefault(token, resolutionStack, branchDepth) {
|
|
1022
|
+
const fastBinding = this.#registry.getFastDefault(token);
|
|
1023
|
+
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
1024
|
+
return this.#settleDefaultEntry(fastBinding, this, resolutionStack, branchDepth);
|
|
1025
|
+
}
|
|
1026
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
1027
|
+
if (entry === null) {
|
|
1028
|
+
return this.#settleRequest(token, undefined, resolutionStack, branchDepth);
|
|
1029
|
+
}
|
|
1030
|
+
return this.#settleDefaultEntry(entry.binding, entry.owner, resolutionStack, branchDepth);
|
|
1031
|
+
}
|
|
1032
|
+
#settleDefaultEntry(binding, owner, resolutionStack, branchDepth) {
|
|
940
1033
|
if (owner.#isPlainConstant(binding)) {
|
|
941
|
-
return
|
|
1034
|
+
return binding.value;
|
|
942
1035
|
}
|
|
943
1036
|
const scope = binding.scope;
|
|
944
1037
|
if (scope === "transient") {
|
|
945
1038
|
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") && !owner.#hasAnyActivation(binding)) {
|
|
946
|
-
return this.#
|
|
1039
|
+
return this.#settleTransientFactory(binding, resolutionStack, branchDepth);
|
|
947
1040
|
}
|
|
948
1041
|
}
|
|
949
1042
|
else if (scope === "singleton") {
|
|
950
1043
|
if (binding.instance !== NO_INSTANCE) {
|
|
951
|
-
return
|
|
1044
|
+
return binding.instance;
|
|
952
1045
|
}
|
|
953
1046
|
if (owner !== this) {
|
|
954
|
-
return owner.#
|
|
1047
|
+
return owner.#settleBinding(binding, undefined, resolutionStack, branchDepth, owner);
|
|
955
1048
|
}
|
|
956
1049
|
}
|
|
957
|
-
else
|
|
958
|
-
const cachedScoped = this.#
|
|
1050
|
+
else {
|
|
1051
|
+
const cachedScoped = this.#readScoped(binding);
|
|
959
1052
|
if (cachedScoped !== SCOPED_MISS) {
|
|
960
|
-
return
|
|
1053
|
+
return cachedScoped;
|
|
961
1054
|
}
|
|
962
1055
|
}
|
|
963
|
-
|
|
964
|
-
// Not `#readScoped`: this entry point reports failure as a rejection, never a sync throw.
|
|
965
|
-
return Promise.reject(new MissingScopeContextError(tokenName(binding.token)));
|
|
966
|
-
}
|
|
967
|
-
return this.#resolveBindingAsync(binding, undefined, resolutionStack, branchDepth, owner);
|
|
1056
|
+
return this.#settleBinding(binding, undefined, resolutionStack, branchDepth, owner);
|
|
968
1057
|
}
|
|
969
1058
|
/**
|
|
970
1059
|
* Instantiates one owned binding directly, bypassing selection.
|
|
@@ -973,18 +1062,38 @@ export class DependencyResolver {
|
|
|
973
1062
|
* could pick a different candidate whose criteria are a subset of them.
|
|
974
1063
|
*/
|
|
975
1064
|
warmBindingAsync(binding, options) {
|
|
976
|
-
|
|
1065
|
+
try {
|
|
1066
|
+
return asPromise(this.#settleBinding(binding, options, [], ROOT_BRANCH, this));
|
|
1067
|
+
}
|
|
1068
|
+
catch (bindingError) {
|
|
1069
|
+
return Promise.reject(bindingError);
|
|
1070
|
+
}
|
|
977
1071
|
}
|
|
978
|
-
|
|
1072
|
+
resolveAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH, precomputedCriterion) {
|
|
1073
|
+
try {
|
|
1074
|
+
return asPromise(this.#settleRequest(token, options, resolutionStack, branchDepth, precomputedCriterion));
|
|
1075
|
+
}
|
|
1076
|
+
catch (requestError) {
|
|
1077
|
+
return Promise.reject(requestError);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
/** Selects the binding a request names and settles it on the async lane. */
|
|
1081
|
+
#settleRequest(token, options, resolutionStack, branchDepth, precomputedCriterion) {
|
|
979
1082
|
const path = ownPrefixOf(resolutionStack, branchDepth);
|
|
980
1083
|
const depth = path === resolutionStack ? branchDepth : branchDepthOf(path);
|
|
981
1084
|
const { binding, owner } = this.#requireBinding(token, options, path, precomputedCriterion);
|
|
982
1085
|
if (binding.scope === "singleton" && owner !== this) {
|
|
983
|
-
return owner.#
|
|
1086
|
+
return owner.#settleBinding(binding, options, path, depth, owner);
|
|
984
1087
|
}
|
|
985
|
-
return this.#
|
|
1088
|
+
return this.#settleBinding(binding, options, path, depth, owner);
|
|
986
1089
|
}
|
|
987
|
-
|
|
1090
|
+
/**
|
|
1091
|
+
* Materialises one binding on the async lane: the instance once nothing yielded, else its promise.
|
|
1092
|
+
*
|
|
1093
|
+
* @remarks A singleton or scoped instance built in this tick is cached in this tick, so a sibling
|
|
1094
|
+
* reading it synchronously finds it; only a materialisation still pending is published in flight.
|
|
1095
|
+
*/
|
|
1096
|
+
#settleBinding(binding, options, resolutionStack, branchDepth, owner) {
|
|
988
1097
|
if (owner.#isPlainConstant(binding)) {
|
|
989
1098
|
return binding.value;
|
|
990
1099
|
}
|
|
@@ -996,7 +1105,7 @@ export class DependencyResolver {
|
|
|
996
1105
|
// In-flight dedup: concurrent callers share the first creation.
|
|
997
1106
|
const inflight = this.#scope.getInflight(binding.identifier);
|
|
998
1107
|
if (inflight !== undefined) {
|
|
999
|
-
return inflight;
|
|
1108
|
+
return ownPromiseOf(inflight);
|
|
1000
1109
|
}
|
|
1001
1110
|
if (this.#scope.isClosed) {
|
|
1002
1111
|
throw new DisposedContainerError();
|
|
@@ -1010,7 +1119,7 @@ export class DependencyResolver {
|
|
|
1010
1119
|
// In-flight dedup, scoped flavor: one instance per scope even under concurrency.
|
|
1011
1120
|
const inflight = this.#scope.getInflight(binding.identifier);
|
|
1012
1121
|
if (inflight !== undefined) {
|
|
1013
|
-
return inflight;
|
|
1122
|
+
return ownPromiseOf(inflight);
|
|
1014
1123
|
}
|
|
1015
1124
|
if (this.#scope.isClosed) {
|
|
1016
1125
|
throw new DisposedContainerError();
|
|
@@ -1026,8 +1135,7 @@ export class DependencyResolver {
|
|
|
1026
1135
|
if (branchDepth === ROOT_BRANCH) {
|
|
1027
1136
|
this.#recentRootLevelContext = resolutionCtx;
|
|
1028
1137
|
}
|
|
1029
|
-
|
|
1030
|
-
return dynamicResult instanceof Promise ? await dynamicResult : dynamicResult;
|
|
1138
|
+
return runFactoryPrefix(binding, resolutionCtx, levelStack);
|
|
1031
1139
|
}
|
|
1032
1140
|
const resolutionCtx = needsActivation || requiresResolutionContext(binding)
|
|
1033
1141
|
? new AsyncLevelContext(this, levelStack, options)
|
|
@@ -1035,33 +1143,41 @@ export class DependencyResolver {
|
|
|
1035
1143
|
if (resolutionCtx !== undefined && branchDepth === ROOT_BRANCH) {
|
|
1036
1144
|
this.#recentRootLevelContext = resolutionCtx;
|
|
1037
1145
|
}
|
|
1146
|
+
const activated = this.#settleActivated(binding, resolutionCtx, levelStack, levelDepth, needsActivation, owner);
|
|
1147
|
+
if (scope === "transient") {
|
|
1148
|
+
return activated;
|
|
1149
|
+
}
|
|
1150
|
+
if (isPending(activated)) {
|
|
1151
|
+
return this.#publishInFlight(binding, activated, scope, owner);
|
|
1152
|
+
}
|
|
1038
1153
|
if (scope === "singleton") {
|
|
1039
|
-
|
|
1040
|
-
|
|
1154
|
+
this.#scope.setSingleton(binding, activated);
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
this.#scope.setScoped(binding, activated, owner);
|
|
1158
|
+
}
|
|
1159
|
+
return activated;
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Publishes a pending singleton or scoped materialisation so concurrent callers dedup onto it,
|
|
1163
|
+
* and caches what it settles to; the creator, like every joiner, is handed a promise of its own.
|
|
1164
|
+
*/
|
|
1165
|
+
#publishInFlight(binding, pending, scope, owner) {
|
|
1166
|
+
const published = pending.then((activated) => {
|
|
1167
|
+
if (scope === "singleton") {
|
|
1041
1168
|
this.#scope.setSingleton(binding, activated);
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
this.#scope.setScoped(binding, activated);
|
|
1055
|
-
this.#scope.clearInflight(binding.identifier);
|
|
1056
|
-
return activated;
|
|
1057
|
-
}, (error) => {
|
|
1058
|
-
this.#scope.clearInflight(binding.identifier);
|
|
1059
|
-
throw error;
|
|
1060
|
-
});
|
|
1061
|
-
this.#scope.setInflight(binding.identifier, scopedPromise);
|
|
1062
|
-
return await scopedPromise;
|
|
1063
|
-
}
|
|
1064
|
-
return await this.#instantiateAndActivateAsync(binding, resolutionCtx, levelStack, levelDepth, needsActivation, owner);
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
this.#scope.setScoped(binding, activated, owner);
|
|
1172
|
+
}
|
|
1173
|
+
this.#scope.clearInflight(binding.identifier);
|
|
1174
|
+
return activated;
|
|
1175
|
+
}, (error) => {
|
|
1176
|
+
this.#scope.clearInflight(binding.identifier);
|
|
1177
|
+
throw error;
|
|
1178
|
+
});
|
|
1179
|
+
this.#scope.setInflight(binding.identifier, published);
|
|
1180
|
+
return ownPromiseOf(published);
|
|
1065
1181
|
}
|
|
1066
1182
|
/**
|
|
1067
1183
|
* Settles this resolver's own `postConstruct` answer for a class binding a parent owns.
|
|
@@ -1077,15 +1193,23 @@ export class DependencyResolver {
|
|
|
1077
1193
|
this.#introspector().discoverPostConstruct(binding.target);
|
|
1078
1194
|
}
|
|
1079
1195
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1196
|
+
/** The activated instance: built and activated in this tick when nothing yielded, else its promise. */
|
|
1197
|
+
#settleActivated(binding, ctx, resolutionStack, branchDepth, needsActivation, owner) {
|
|
1198
|
+
const instance = this.#settleInstance(binding, ctx, resolutionStack, branchDepth);
|
|
1199
|
+
if (isPending(instance)) {
|
|
1200
|
+
return instance.then((built) => this.#activateAsync(binding, ctx, built, needsActivation, owner));
|
|
1201
|
+
}
|
|
1202
|
+
return this.#activateAsync(binding, ctx, instance, needsActivation, owner);
|
|
1203
|
+
}
|
|
1204
|
+
#activateAsync(binding, ctx, instance, needsActivation, owner) {
|
|
1082
1205
|
this.#mirrorPostConstructFromOwner(binding, owner);
|
|
1083
1206
|
if (!owner.#activationNeed().refreshAfterFirstInstantiation(binding, needsActivation)) {
|
|
1084
1207
|
return instance;
|
|
1085
1208
|
}
|
|
1086
1209
|
return owner.#lifecycle.runActivation(ctx, binding, instance, owner.#metadataReader);
|
|
1087
1210
|
}
|
|
1088
|
-
|
|
1211
|
+
/** The instance a binding builds: in this tick when every dependency settled in it, else its promise. */
|
|
1212
|
+
#settleInstance(binding, ctx, resolutionStack, branchDepth) {
|
|
1089
1213
|
switch (binding.kind) {
|
|
1090
1214
|
case "constant":
|
|
1091
1215
|
return binding.value;
|
|
@@ -1094,59 +1218,94 @@ export class DependencyResolver {
|
|
|
1094
1218
|
if (ctx === undefined) {
|
|
1095
1219
|
throw new InternalError("dynamic binding requires resolution context");
|
|
1096
1220
|
}
|
|
1097
|
-
|
|
1098
|
-
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
1221
|
+
return runFactoryPrefix(binding, ctx, resolutionStack);
|
|
1099
1222
|
}
|
|
1100
1223
|
case "class": {
|
|
1101
|
-
const
|
|
1224
|
+
const introspector = this.#introspector();
|
|
1225
|
+
const target = binding.target;
|
|
1226
|
+
const facts = introspector.facts(target);
|
|
1227
|
+
const params = this.#constructorParams(target, facts);
|
|
1228
|
+
const needsActiveContainer = introspector.needsActiveContainerOf(target, facts);
|
|
1102
1229
|
// Accessor initializers resolve synchronously, so the branch-owned path serves them directly.
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1230
|
+
const ambient = needsActiveContainer ? this.#ambientResolutionFor(resolutionStack) : undefined;
|
|
1231
|
+
if (params.length === 0) {
|
|
1232
|
+
return introspector.construct(target, NO_ARGUMENTS, needsActiveContainer, ambient);
|
|
1233
|
+
}
|
|
1234
|
+
const deps = this.#settleDeps(params, resolutionStack, branchDepth);
|
|
1235
|
+
if (isPending(deps)) {
|
|
1236
|
+
return deps.then((settled) => introspector.construct(target, settled, needsActiveContainer, ambient));
|
|
1237
|
+
}
|
|
1238
|
+
return introspector.construct(target, deps, needsActiveContainer, ambient);
|
|
1106
1239
|
}
|
|
1107
1240
|
case "resolved": {
|
|
1108
|
-
const
|
|
1109
|
-
const
|
|
1110
|
-
return
|
|
1241
|
+
const factory = binding.factory;
|
|
1242
|
+
const deps = this.#settleDeps(binding.deps, resolutionStack, branchDepth);
|
|
1243
|
+
return isPending(deps) ? deps.then((settled) => factory(...settled)) : factory(...deps);
|
|
1111
1244
|
}
|
|
1112
1245
|
case "resolved-async": {
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1246
|
+
const factory = binding.factory;
|
|
1247
|
+
const deps = this.#settleDeps(binding.deps, resolutionStack, branchDepth);
|
|
1248
|
+
return isPending(deps) ? deps.then((settled) => factory(...settled)) : factory(...deps);
|
|
1115
1249
|
}
|
|
1116
1250
|
case "alias":
|
|
1117
1251
|
throw new InternalError("alias should have been followed before instantiation");
|
|
1118
1252
|
}
|
|
1119
1253
|
}
|
|
1120
|
-
|
|
1254
|
+
/**
|
|
1255
|
+
* One level's dependencies: their values when every one settled in this tick, else the fan-out
|
|
1256
|
+
* that settles them in declaration order.
|
|
1257
|
+
*/
|
|
1258
|
+
#settleDeps(deps, resolutionStack, branchDepth) {
|
|
1121
1259
|
const count = deps.length;
|
|
1122
1260
|
if (count === 0) {
|
|
1123
1261
|
return [];
|
|
1124
1262
|
}
|
|
1125
1263
|
if (count === 1) {
|
|
1126
|
-
|
|
1264
|
+
const only = this.#settleDep(deps[0], resolutionStack, branchDepth);
|
|
1265
|
+
return isPending(only) ? only.then(settledAlone) : [only];
|
|
1127
1266
|
}
|
|
1128
|
-
// Siblings
|
|
1129
|
-
// place and the rest copy the prefix — no caller has to isolate them.
|
|
1267
|
+
// Siblings start concurrently and each extends the same branch, so the first appends in
|
|
1268
|
+
// place and the rest copy the prefix — no caller has to isolate them. A sibling's sync throw
|
|
1269
|
+
// becomes its slot's rejection so the ones after it still start, as on every other lane.
|
|
1130
1270
|
const pending = new Array(count);
|
|
1271
|
+
let yielded = false;
|
|
1131
1272
|
for (let index = 0; index < count; index += 1) {
|
|
1132
|
-
|
|
1273
|
+
let settled;
|
|
1274
|
+
try {
|
|
1275
|
+
settled = this.#settleDep(deps[index], resolutionStack, branchDepth);
|
|
1276
|
+
}
|
|
1277
|
+
catch (dependencyError) {
|
|
1278
|
+
settled = Promise.reject(dependencyError);
|
|
1279
|
+
}
|
|
1280
|
+
if (isPending(settled)) {
|
|
1281
|
+
yielded = true;
|
|
1282
|
+
}
|
|
1283
|
+
pending[index] = settled;
|
|
1133
1284
|
}
|
|
1134
|
-
return settleInOrder(pending, identity);
|
|
1285
|
+
return yielded ? settleInOrder(pending, identity) : pending;
|
|
1135
1286
|
}
|
|
1136
|
-
#
|
|
1287
|
+
#settleDep(dep, resolutionStack, branchDepth) {
|
|
1137
1288
|
const options = resolveOptionsForSlot(dep);
|
|
1138
1289
|
if (dep.multi) {
|
|
1139
|
-
return this
|
|
1290
|
+
return this.#settleCollection(dep.token, options, resolutionStack, branchDepth);
|
|
1140
1291
|
}
|
|
1141
1292
|
if (dep.optional) {
|
|
1142
|
-
return this
|
|
1293
|
+
return this.#settleOptionalRequest(dep.token, options, resolutionStack, branchDepth, singleCriterionForSlot(dep));
|
|
1143
1294
|
}
|
|
1144
1295
|
if (options === undefined) {
|
|
1145
|
-
return this
|
|
1296
|
+
return this.#settleDefault(dep.token, resolutionStack, branchDepth);
|
|
1297
|
+
}
|
|
1298
|
+
return this.#settleRequest(dep.token, options, resolutionStack, branchDepth, singleCriterionForSlot(dep));
|
|
1299
|
+
}
|
|
1300
|
+
resolveOptionalAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH, precomputedCriterion) {
|
|
1301
|
+
try {
|
|
1302
|
+
return asPromise(this.#settleOptionalRequest(token, options, resolutionStack, branchDepth, precomputedCriterion));
|
|
1303
|
+
}
|
|
1304
|
+
catch (requestError) {
|
|
1305
|
+
return Promise.reject(requestError);
|
|
1146
1306
|
}
|
|
1147
|
-
return this.resolveAsync(dep.token, options, resolutionStack, branchDepth, singleCriterionForSlot(dep));
|
|
1148
1307
|
}
|
|
1149
|
-
|
|
1308
|
+
#settleOptionalRequest(token, options, resolutionStack, branchDepth, precomputedCriterion) {
|
|
1150
1309
|
const path = ownPrefixOf(resolutionStack, branchDepth);
|
|
1151
1310
|
const depth = path === resolutionStack ? branchDepth : branchDepthOf(path);
|
|
1152
1311
|
const singleCriterion = precomputedCriterion === undefined ? singleCriterionOnlyOf(options) : (precomputedCriterion ?? undefined);
|
|
@@ -1161,19 +1320,46 @@ export class DependencyResolver {
|
|
|
1161
1320
|
}
|
|
1162
1321
|
const { binding, owner } = entry;
|
|
1163
1322
|
if (binding.scope === "singleton" && owner !== this) {
|
|
1164
|
-
return owner.#
|
|
1323
|
+
return owner.#settleBinding(binding, options, path, depth, owner);
|
|
1165
1324
|
}
|
|
1166
|
-
return this.#
|
|
1325
|
+
return this.#settleBinding(binding, options, path, depth, owner);
|
|
1167
1326
|
}
|
|
1168
|
-
|
|
1327
|
+
resolveAllAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
|
|
1328
|
+
try {
|
|
1329
|
+
return asPromise(this.#settleCollection(token, options, resolutionStack, branchDepth));
|
|
1330
|
+
}
|
|
1331
|
+
catch (collectionError) {
|
|
1332
|
+
return Promise.reject(collectionError);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
#settleCollection(token, options, resolutionStack, branchDepth) {
|
|
1169
1336
|
const path = ownPrefixOf(resolutionStack, branchDepth);
|
|
1170
1337
|
const depth = path === resolutionStack ? branchDepth : branchDepthOf(path);
|
|
1171
1338
|
const candidates = this.#candidateBindings(token, options, path);
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1339
|
+
return this.#settleCandidates(candidates, options, path, depth, identity);
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Every candidate of a fan-out, started in order: `apply` over their values when all settled in
|
|
1343
|
+
* this tick, else over the fan-out settled in declaration order.
|
|
1344
|
+
*/
|
|
1345
|
+
#settleCandidates(candidates, options, resolutionStack, branchDepth, apply) {
|
|
1346
|
+
const count = candidates.length;
|
|
1347
|
+
const pending = new Array(count);
|
|
1348
|
+
let yielded = false;
|
|
1349
|
+
for (let index = 0; index < count; index += 1) {
|
|
1350
|
+
let settled;
|
|
1351
|
+
try {
|
|
1352
|
+
settled = this.#settleCandidate(candidates[index], options, resolutionStack, branchDepth);
|
|
1353
|
+
}
|
|
1354
|
+
catch (memberError) {
|
|
1355
|
+
settled = Promise.reject(memberError);
|
|
1356
|
+
}
|
|
1357
|
+
if (isPending(settled)) {
|
|
1358
|
+
yielded = true;
|
|
1359
|
+
}
|
|
1360
|
+
pending[index] = settled;
|
|
1175
1361
|
}
|
|
1176
|
-
return settleInOrder(pending,
|
|
1362
|
+
return yielded ? settleInOrder(pending, apply) : apply(pending);
|
|
1177
1363
|
}
|
|
1178
1364
|
// ── Helpers ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
1179
1365
|
#allBindingsFromChain(token) {
|
|
@@ -1254,7 +1440,7 @@ export class DependencyResolver {
|
|
|
1254
1440
|
if (!this.#scope.isChild) {
|
|
1255
1441
|
throw new MissingScopeContextError(tokenName(binding.token));
|
|
1256
1442
|
}
|
|
1257
|
-
return this.#scope.readScoped(binding.
|
|
1443
|
+
return this.#scope.readScoped(binding.scopedCacheKey);
|
|
1258
1444
|
}
|
|
1259
1445
|
// The shared root context answers every top-level request; building one is the rarer half and
|
|
1260
1446
|
// lives outside, so what a selection inlines is the test and not the literal.
|
|
@@ -1294,7 +1480,19 @@ export class DependencyResolver {
|
|
|
1294
1480
|
binding.inFlight = false;
|
|
1295
1481
|
}
|
|
1296
1482
|
}
|
|
1297
|
-
|
|
1483
|
+
/** A transient factory level with no activation: its factory's own result over the level's context. */
|
|
1484
|
+
#settleTransientFactory(binding, resolutionStack, branchDepth) {
|
|
1485
|
+
const frame = this.#getResolutionFrame(binding);
|
|
1486
|
+
const levelStack = extendResolutionBranch(resolutionStack, branchDepth, frame);
|
|
1487
|
+
// Nothing this level appended is ever removed, so no level observes its own settlement.
|
|
1488
|
+
const ctx = new AsyncLevelContext(this, levelStack, undefined);
|
|
1489
|
+
if (branchDepth === ROOT_BRANCH) {
|
|
1490
|
+
this.#recentRootLevelContext = ctx;
|
|
1491
|
+
}
|
|
1492
|
+
return runFactoryPrefix(binding, ctx, levelStack);
|
|
1493
|
+
}
|
|
1494
|
+
// Deliberately not `async`: that would allocate a state machine and a promise per level. The
|
|
1495
|
+
// dominant lane keeps its body whole rather than reaching the settle core through a call.
|
|
1298
1496
|
#resolveTransientDynamicAsyncFromContext(binding, resolutionStack, branchDepth) {
|
|
1299
1497
|
const frame = this.#getResolutionFrame(binding);
|
|
1300
1498
|
let levelStack;
|
|
@@ -1405,33 +1603,33 @@ export class DependencyResolver {
|
|
|
1405
1603
|
}
|
|
1406
1604
|
return this.#resolveBinding(binding, options, resolutionStack, owner);
|
|
1407
1605
|
}
|
|
1408
|
-
#
|
|
1606
|
+
#settleCandidate(binding, options, resolutionStack, branchDepth) {
|
|
1409
1607
|
if (binding.kind === "constant" && binding.activationHook === undefined && this.#chainActivationVersion() === 0) {
|
|
1410
|
-
return
|
|
1608
|
+
return binding.value;
|
|
1411
1609
|
}
|
|
1412
1610
|
const owner = this.#ownerOf(binding);
|
|
1413
1611
|
if (owner.#isPlainConstant(binding)) {
|
|
1414
|
-
return
|
|
1612
|
+
return binding.value;
|
|
1415
1613
|
}
|
|
1416
1614
|
if (binding.kind === "alias") {
|
|
1417
|
-
return this
|
|
1615
|
+
return this.#settleRequest(binding.target, options, resolutionStack, branchDepth);
|
|
1418
1616
|
}
|
|
1419
1617
|
if (binding.scope === "singleton") {
|
|
1420
1618
|
if (binding.instance !== NO_INSTANCE) {
|
|
1421
|
-
return
|
|
1619
|
+
return binding.instance;
|
|
1422
1620
|
}
|
|
1423
|
-
return owner.#
|
|
1621
|
+
return owner.#settleBinding(binding, options, resolutionStack, branchDepth, owner);
|
|
1424
1622
|
}
|
|
1425
1623
|
// The dominant collection member — a transient factory with no activation, asked with no
|
|
1426
|
-
// options — takes the
|
|
1427
|
-
//
|
|
1624
|
+
// options — takes the lane a single resolve takes, so a fan-out costs one factory result per
|
|
1625
|
+
// member rather than a promise on top of each.
|
|
1428
1626
|
if (options === undefined &&
|
|
1429
1627
|
binding.scope === "transient" &&
|
|
1430
1628
|
(binding.kind === "dynamic" || binding.kind === "dynamic-async") &&
|
|
1431
1629
|
!owner.#hasAnyActivation(binding)) {
|
|
1432
|
-
return this.#
|
|
1630
|
+
return this.#settleTransientFactory(binding, resolutionStack, branchDepth);
|
|
1433
1631
|
}
|
|
1434
|
-
return this.#
|
|
1632
|
+
return this.#settleBinding(binding, options, resolutionStack, branchDepth, owner);
|
|
1435
1633
|
}
|
|
1436
1634
|
/** The resolver whose registry holds `binding` — `this` (the common case) when it is own. */
|
|
1437
1635
|
#ownerOf(binding) {
|
|
@@ -1547,6 +1745,26 @@ function asyncResolutionErrorFor(binding, resolutionStack) {
|
|
|
1547
1745
|
function identity(values) {
|
|
1548
1746
|
return values;
|
|
1549
1747
|
}
|
|
1748
|
+
/** A settled value, as it is. */
|
|
1749
|
+
function settledValue(value) {
|
|
1750
|
+
return value;
|
|
1751
|
+
}
|
|
1752
|
+
/** A promise of its own over a shared in-flight one: the same settlement, never the stored object. */
|
|
1753
|
+
function ownPromiseOf(shared) {
|
|
1754
|
+
return shared.then(settledValue);
|
|
1755
|
+
}
|
|
1756
|
+
/** A lone dependency's settled value as the argument list its level applies. */
|
|
1757
|
+
function settledAlone(value) {
|
|
1758
|
+
return [value];
|
|
1759
|
+
}
|
|
1760
|
+
/** Whether an async-lane answer is still pending, narrowing it to the promise it is. */
|
|
1761
|
+
function isPending(answer) {
|
|
1762
|
+
return answer instanceof Promise;
|
|
1763
|
+
}
|
|
1764
|
+
/** A settled answer as the promise an async entry point hands out; a pending one passes through. */
|
|
1765
|
+
function asPromise(answer) {
|
|
1766
|
+
return answer instanceof Promise ? answer : Promise.resolve(answer);
|
|
1767
|
+
}
|
|
1550
1768
|
/** Only a factory is handed the resolution context; everything else gets its deps directly. */
|
|
1551
1769
|
function requiresResolutionContext(binding) {
|
|
1552
1770
|
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|