@codefast/di 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +242 -159
  3. package/dist/ambient/active-container.d.ts +1 -2
  4. package/dist/ambient/active-container.js +1 -2
  5. package/dist/container/binding-builders.d.ts +1 -2
  6. package/dist/container/binding-builders.js +5 -7
  7. package/dist/container/container.d.ts +1 -2
  8. package/dist/container/container.js +4 -6
  9. package/dist/core/binding-scope.d.ts +1 -2
  10. package/dist/core/binding-scope.js +1 -2
  11. package/dist/core/binding.d.ts +16 -4
  12. package/dist/core/binding.js +30 -6
  13. package/dist/core/constraint-requirement.d.ts +1 -2
  14. package/dist/core/constraint-requirement.js +1 -2
  15. package/dist/core/constructor-type.d.ts +1 -2
  16. package/dist/core/constructor-type.js +1 -2
  17. package/dist/core/map-upsert.d.ts +1 -2
  18. package/dist/core/map-upsert.js +1 -2
  19. package/dist/core/module.d.ts +1 -2
  20. package/dist/core/module.js +1 -2
  21. package/dist/core/registry.d.ts +1 -5
  22. package/dist/core/registry.js +15 -63
  23. package/dist/core/tag.d.ts +27 -2
  24. package/dist/core/tag.js +55 -13
  25. package/dist/core/token.d.ts +1 -2
  26. package/dist/core/token.js +1 -2
  27. package/dist/core/types.d.ts +1 -2
  28. package/dist/core/types.js +1 -2
  29. package/dist/decorators/inject.d.ts +1 -2
  30. package/dist/decorators/inject.js +1 -2
  31. package/dist/decorators/injectable.d.ts +1 -2
  32. package/dist/decorators/injectable.js +1 -2
  33. package/dist/decorators/lifecycle-decorators.d.ts +1 -2
  34. package/dist/decorators/lifecycle-decorators.js +1 -2
  35. package/dist/errors/diagnostics.d.ts +1 -2
  36. package/dist/errors/diagnostics.js +1 -2
  37. package/dist/errors/errors.d.ts +1 -2
  38. package/dist/errors/errors.js +29 -5
  39. package/dist/index.d.ts +2 -3
  40. package/dist/index.js +2 -3
  41. package/dist/injection/descriptor.d.ts +1 -2
  42. package/dist/injection/descriptor.js +1 -2
  43. package/dist/injection/resolve-options.d.ts +18 -14
  44. package/dist/injection/resolve-options.js +70 -17
  45. package/dist/introspection/dependency-graph.d.ts +1 -2
  46. package/dist/introspection/dependency-graph.js +23 -11
  47. package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -2
  48. package/dist/introspection/graph-adapters/cytoscape.js +1 -2
  49. package/dist/introspection/graph-adapters/dot.d.ts +1 -2
  50. package/dist/introspection/graph-adapters/dot.js +1 -2
  51. package/dist/introspection/graph-adapters/mermaid.d.ts +1 -2
  52. package/dist/introspection/graph-adapters/mermaid.js +1 -2
  53. package/dist/introspection/graph-adapters/reactflow.d.ts +1 -2
  54. package/dist/introspection/graph-adapters/reactflow.js +1 -2
  55. package/dist/introspection/inspector.d.ts +1 -2
  56. package/dist/introspection/inspector.js +1 -2
  57. package/dist/lifecycle/lifecycle-manager.d.ts +1 -2
  58. package/dist/lifecycle/lifecycle-manager.js +1 -2
  59. package/dist/lifecycle/scope-manager.d.ts +1 -2
  60. package/dist/lifecycle/scope-manager.js +1 -2
  61. package/dist/metadata/metadata-keys.d.ts +1 -2
  62. package/dist/metadata/metadata-keys.js +1 -2
  63. package/dist/metadata/metadata-reader-token.d.ts +1 -2
  64. package/dist/metadata/metadata-reader-token.js +1 -2
  65. package/dist/metadata/metadata-types.d.ts +1 -2
  66. package/dist/metadata/metadata-types.js +1 -2
  67. package/dist/metadata/symbol-metadata-reader.d.ts +1 -2
  68. package/dist/metadata/symbol-metadata-reader.js +1 -2
  69. package/dist/metadata/verifying-metadata-reader.d.ts +1 -2
  70. package/dist/metadata/verifying-metadata-reader.js +1 -2
  71. package/dist/resolution/cache/activation-need.d.ts +1 -2
  72. package/dist/resolution/cache/activation-need.js +1 -2
  73. package/dist/resolution/cache/binding-lookup-cache.d.ts +2 -5
  74. package/dist/resolution/cache/binding-lookup-cache.js +2 -40
  75. package/dist/resolution/cache/class-introspector.d.ts +1 -2
  76. package/dist/resolution/cache/class-introspector.js +1 -2
  77. package/dist/resolution/context.d.ts +1 -2
  78. package/dist/resolution/context.js +1 -2
  79. package/dist/resolution/path/resolution-path.d.ts +1 -2
  80. package/dist/resolution/path/resolution-path.js +1 -2
  81. package/dist/resolution/plan/instantiation-plan.d.ts +5 -14
  82. package/dist/resolution/plan/instantiation-plan.js +10 -29
  83. package/dist/resolution/resolver.d.ts +13 -7
  84. package/dist/resolution/resolver.js +107 -143
  85. package/dist/resolution/select/binding-select.d.ts +5 -7
  86. package/dist/resolution/select/binding-select.js +19 -22
  87. package/dist/resolution/select/constraints.d.ts +1 -2
  88. package/dist/resolution/select/constraints.js +1 -2
  89. package/package.json +6 -58
  90. package/dist/ambient/active-container.d.ts.map +0 -1
  91. package/dist/ambient/active-container.js.map +0 -1
  92. package/dist/container/binding-builders.d.ts.map +0 -1
  93. package/dist/container/binding-builders.js.map +0 -1
  94. package/dist/container/container.d.ts.map +0 -1
  95. package/dist/container/container.js.map +0 -1
  96. package/dist/core/binding-scope.d.ts.map +0 -1
  97. package/dist/core/binding-scope.js.map +0 -1
  98. package/dist/core/binding.d.ts.map +0 -1
  99. package/dist/core/binding.js.map +0 -1
  100. package/dist/core/constraint-requirement.d.ts.map +0 -1
  101. package/dist/core/constraint-requirement.js.map +0 -1
  102. package/dist/core/constructor-type.d.ts.map +0 -1
  103. package/dist/core/constructor-type.js.map +0 -1
  104. package/dist/core/map-upsert.d.ts.map +0 -1
  105. package/dist/core/map-upsert.js.map +0 -1
  106. package/dist/core/module.d.ts.map +0 -1
  107. package/dist/core/module.js.map +0 -1
  108. package/dist/core/registry.d.ts.map +0 -1
  109. package/dist/core/registry.js.map +0 -1
  110. package/dist/core/tag.d.ts.map +0 -1
  111. package/dist/core/tag.js.map +0 -1
  112. package/dist/core/token.d.ts.map +0 -1
  113. package/dist/core/token.js.map +0 -1
  114. package/dist/core/types.d.ts.map +0 -1
  115. package/dist/core/types.js.map +0 -1
  116. package/dist/decorators/inject.d.ts.map +0 -1
  117. package/dist/decorators/inject.js.map +0 -1
  118. package/dist/decorators/injectable.d.ts.map +0 -1
  119. package/dist/decorators/injectable.js.map +0 -1
  120. package/dist/decorators/lifecycle-decorators.d.ts.map +0 -1
  121. package/dist/decorators/lifecycle-decorators.js.map +0 -1
  122. package/dist/errors/diagnostics.d.ts.map +0 -1
  123. package/dist/errors/diagnostics.js.map +0 -1
  124. package/dist/errors/errors.d.ts.map +0 -1
  125. package/dist/errors/errors.js.map +0 -1
  126. package/dist/index.d.ts.map +0 -1
  127. package/dist/index.js.map +0 -1
  128. package/dist/injection/descriptor.d.ts.map +0 -1
  129. package/dist/injection/descriptor.js.map +0 -1
  130. package/dist/injection/resolve-options.d.ts.map +0 -1
  131. package/dist/injection/resolve-options.js.map +0 -1
  132. package/dist/introspection/dependency-graph.d.ts.map +0 -1
  133. package/dist/introspection/dependency-graph.js.map +0 -1
  134. package/dist/introspection/graph-adapters/cytoscape.d.ts.map +0 -1
  135. package/dist/introspection/graph-adapters/cytoscape.js.map +0 -1
  136. package/dist/introspection/graph-adapters/dot.d.ts.map +0 -1
  137. package/dist/introspection/graph-adapters/dot.js.map +0 -1
  138. package/dist/introspection/graph-adapters/mermaid.d.ts.map +0 -1
  139. package/dist/introspection/graph-adapters/mermaid.js.map +0 -1
  140. package/dist/introspection/graph-adapters/reactflow.d.ts.map +0 -1
  141. package/dist/introspection/graph-adapters/reactflow.js.map +0 -1
  142. package/dist/introspection/inspector.d.ts.map +0 -1
  143. package/dist/introspection/inspector.js.map +0 -1
  144. package/dist/lifecycle/lifecycle-manager.d.ts.map +0 -1
  145. package/dist/lifecycle/lifecycle-manager.js.map +0 -1
  146. package/dist/lifecycle/scope-manager.d.ts.map +0 -1
  147. package/dist/lifecycle/scope-manager.js.map +0 -1
  148. package/dist/metadata/metadata-keys.d.ts.map +0 -1
  149. package/dist/metadata/metadata-keys.js.map +0 -1
  150. package/dist/metadata/metadata-reader-token.d.ts.map +0 -1
  151. package/dist/metadata/metadata-reader-token.js.map +0 -1
  152. package/dist/metadata/metadata-types.d.ts.map +0 -1
  153. package/dist/metadata/metadata-types.js.map +0 -1
  154. package/dist/metadata/symbol-metadata-reader.d.ts.map +0 -1
  155. package/dist/metadata/symbol-metadata-reader.js.map +0 -1
  156. package/dist/metadata/verifying-metadata-reader.d.ts.map +0 -1
  157. package/dist/metadata/verifying-metadata-reader.js.map +0 -1
  158. package/dist/resolution/cache/activation-need.d.ts.map +0 -1
  159. package/dist/resolution/cache/activation-need.js.map +0 -1
  160. package/dist/resolution/cache/binding-lookup-cache.d.ts.map +0 -1
  161. package/dist/resolution/cache/binding-lookup-cache.js.map +0 -1
  162. package/dist/resolution/cache/class-introspector.d.ts.map +0 -1
  163. package/dist/resolution/cache/class-introspector.js.map +0 -1
  164. package/dist/resolution/context.d.ts.map +0 -1
  165. package/dist/resolution/context.js.map +0 -1
  166. package/dist/resolution/path/resolution-path.d.ts.map +0 -1
  167. package/dist/resolution/path/resolution-path.js.map +0 -1
  168. package/dist/resolution/plan/instantiation-plan.d.ts.map +0 -1
  169. package/dist/resolution/plan/instantiation-plan.js.map +0 -1
  170. package/dist/resolution/resolver.d.ts.map +0 -1
  171. package/dist/resolution/resolver.js.map +0 -1
  172. package/dist/resolution/select/binding-select.d.ts.map +0 -1
  173. package/dist/resolution/select/binding-select.js.map +0 -1
  174. package/dist/resolution/select/constraints.d.ts.map +0 -1
  175. package/dist/resolution/select/constraints.js.map +0 -1
  176. package/src/ambient/active-container.ts +0 -69
  177. package/src/container/binding-builders.ts +0 -332
  178. package/src/container/container.ts +0 -973
  179. package/src/core/binding-scope.ts +0 -15
  180. package/src/core/binding.ts +0 -477
  181. package/src/core/constraint-requirement.ts +0 -94
  182. package/src/core/constructor-type.ts +0 -18
  183. package/src/core/map-upsert.ts +0 -53
  184. package/src/core/module.ts +0 -117
  185. package/src/core/registry.ts +0 -371
  186. package/src/core/tag.ts +0 -158
  187. package/src/core/token.ts +0 -31
  188. package/src/core/types.ts +0 -149
  189. package/src/decorators/inject.ts +0 -101
  190. package/src/decorators/injectable.ts +0 -134
  191. package/src/decorators/lifecycle-decorators.ts +0 -43
  192. package/src/errors/diagnostics.ts +0 -45
  193. package/src/errors/errors.ts +0 -476
  194. package/src/index.ts +0 -139
  195. package/src/injection/descriptor.ts +0 -218
  196. package/src/injection/resolve-options.ts +0 -144
  197. package/src/introspection/dependency-graph.ts +0 -282
  198. package/src/introspection/graph-adapters/cytoscape.ts +0 -78
  199. package/src/introspection/graph-adapters/dot.ts +0 -32
  200. package/src/introspection/graph-adapters/mermaid.ts +0 -61
  201. package/src/introspection/graph-adapters/reactflow.ts +0 -84
  202. package/src/introspection/inspector.ts +0 -134
  203. package/src/lifecycle/lifecycle-manager.ts +0 -272
  204. package/src/lifecycle/scope-manager.ts +0 -158
  205. package/src/metadata/metadata-keys.ts +0 -28
  206. package/src/metadata/metadata-reader-token.ts +0 -10
  207. package/src/metadata/metadata-types.ts +0 -57
  208. package/src/metadata/symbol-metadata-reader.ts +0 -126
  209. package/src/metadata/verifying-metadata-reader.ts +0 -41
  210. package/src/resolution/cache/activation-need.ts +0 -87
  211. package/src/resolution/cache/binding-lookup-cache.ts +0 -209
  212. package/src/resolution/cache/class-introspector.ts +0 -256
  213. package/src/resolution/context.ts +0 -331
  214. package/src/resolution/path/resolution-path.ts +0 -167
  215. package/src/resolution/plan/instantiation-plan.ts +0 -617
  216. package/src/resolution/resolver.ts +0 -1675
  217. package/src/resolution/select/binding-select.ts +0 -210
  218. package/src/resolution/select/constraints.ts +0 -146
@@ -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 { isNameOnlyOptions, resolveOptionsForSlot, singleTagOnlyOf } from "#/injection/resolve-options";
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
- #findBinding(token, options, resolutionStack) {
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 (isNameOnlyOptions(options)) {
107
- const namedBinding = this.#registry.getSimpleNamed(token, options.name);
108
- if (namedBinding !== undefined && this.#matchesBindingFast(namedBinding, options, resolutionStack)) {
109
- return { binding: namedBinding, owner: this };
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
- const singleTag = singleTagOnlyOf(options);
114
- if (singleTag !== undefined) {
115
- const tagged = this.#registry.getSimpleTagged(token, singleTag);
116
- if (tagged !== undefined && this.#satisfiesPredicate(tagged, options, resolutionStack)) {
117
- return { binding: tagged, owner: this };
118
- }
119
- }
120
- else if (options.name === undefined &&
121
- // A threshold switches the data structure, never the semantics: under it the generic scan
122
- // below beats walking the indexes, and both paths answer identically. Sized first, so a
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.#matchesBindingFast(bindings[0], options, resolutionStack)
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 named lane accepts, minus the half that reads a path: a predicate
333
- // is the compiler's cue to leave the selection to the runtime.
334
- lookupPathIndependentNamedEntry: (token, options) => {
335
- const entry = this.#lookup.namedEntry(token, options.name);
336
- if (entry === null || entry.binding.predicate !== undefined || !matchesSlot(entry.binding.slot, options)) {
337
- return null;
338
- }
339
- return { binding: entry.binding };
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) {
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) {
345
337
  return null;
346
338
  }
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
- // Name-only fast lane: memoized lookup, dispatching just the shapes whose
405
- // semantics involve no resolution context (constants, cached singletons).
406
- if (options !== undefined && isNameOnlyOptions(options)) {
407
- const namedEntry = this.#lookup.namedEntry(token, options.name);
408
- if (namedEntry !== null) {
409
- const namedBinding = namedEntry.binding;
410
- if (namedEntry.owner.#isPlainConstant(namedBinding)) {
411
- return namedBinding.value;
412
- }
413
- if (namedBinding.scope === "singleton" && namedBinding.instance !== NO_INSTANCE) {
414
- return namedBinding.instance;
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 (taggedBinding.scope === "singleton" && taggedBinding.instance !== NO_INSTANCE) {
430
- return taggedBinding.instance;
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
- async resolveAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
683
- const { binding, owner } = this.#requireBinding(token, options, resolutionStack);
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 an index can name outright, or `null` when the request needs full selection.
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 neither
907
- * index serves must not pay for the two that do.
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
- if (isNameOnlyOptions(options)) {
913
- const named = this.#namedBindingsFromChain(token, options.name);
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 tagged = this.#taggedBindingsFromChain(token, singleTag);
923
- return anyPredicate(tagged)
924
- ? selectAllBindings(tagged, options, this.#makeConstraintContext(resolutionStack, options))
925
- : tagged;
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 tag indexes hold for one tag, nearest container first.
894
+ * Every binding the chain's criterion indexes hold for one criterion, nearest container first.
929
895
  *
930
- * @remarks A request for one tag and no name matches exactly the bindings the index keys, so this
931
- * is the whole candidate set rather than a prefilter — a named or multi-tag slot cannot satisfy it.
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 tag matches at most one binding per registry, so a root container's answer is built whole
935
- // rather than grown — the shape `#namedBindingsFromChain` takes, for the same reason.
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 name-less multi-tag request, over the union of the two tag indexes. */
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
- this.#gatherTagCandidates(token, options.tag, candidates);
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
- if (criterion === undefined) {
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
- // The includes probes only guard a request repeating a criterion across its two spellings.
976
+ gathered.push(criterion);
1003
977
  const single = this.#registry.getSimpleTagged(token, criterion);
1004
- if (single !== undefined && !out.includes(single)) {
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
- const candidate = bucket[index];
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;
@@ -1301,5 +1266,4 @@ function asyncResolutionErrorFor(binding, resolutionStack) {
1301
1266
  /** Only a factory is handed the resolution context; everything else gets its deps directly. */
1302
1267
  function requiresResolutionContext(binding) {
1303
1268
  return binding.kind === "dynamic" || binding.kind === "dynamic-async";
1304
- }
1305
- //# sourceMappingURL=resolver.js.map
1269
+ }
@@ -15,12 +15,11 @@ export declare function selectBinding(bindings: ReadonlyArray<Binding>, options:
15
15
  */
16
16
  export declare function selectAllBindings(bindings: ReadonlyArray<Binding>, options: ResolveOptions | undefined, ctx: ConstraintContext): Array<Binding>;
17
17
  /**
18
- * Whether a binding's slot satisfies a request: names must be equal, and every tag the slot
19
- * declares must be among the tags requested.
18
+ * Whether a binding's slot satisfies a request: every criterion the slot declares must be among
19
+ * the request's criteria, a name spelling either side folding to the reserved criterion.
20
20
  *
21
- * @remarks The tag half is a key-mask subset test before any criterion is read, so a slot the
22
- * request cannot satisfy is rejected in one word compare. Criteria are interned, so what follows is
23
- * identity.
21
+ * @remarks A key-mask subset test runs before any criterion is read, so a slot the request cannot
22
+ * satisfy is rejected in one word compare. Criteria are interned, so what follows is identity.
24
23
  *
25
24
  * @since 0.5.0-canary.9
26
25
  */
@@ -30,5 +29,4 @@ export declare function matchesSlot(slot: BindingSlot, options: ResolveOptions |
30
29
  *
31
30
  * @since 0.6.0
32
31
  */
33
- export declare function requestedTagKeyMask(options: ResolveOptions | undefined): TagKeyMask;
34
- //# sourceMappingURL=binding-select.d.ts.map
32
+ export declare function requestedTagKeyMask(options: ResolveOptions | undefined): TagKeyMask;
@@ -1,4 +1,4 @@
1
- import { coversTagKeys, NO_TAG_KEYS } from "#/core/tag";
1
+ import { coversTagKeys, NO_TAG_KEYS, slotName, slotNameCriterionOf } from "#/core/tag";
2
2
  import { AmbiguousBindingError } from "#/errors/errors";
3
3
  /**
4
4
  * Selects a single candidate from a list of bindings using slot matching + predicates.
@@ -32,14 +32,14 @@ export function selectBinding(bindings, options, ctx, tokenDisplayName) {
32
32
  }
33
33
  // Reached only where the throw was: a slot declaring more of what the request carries is the more
34
34
  // specific match, so an over-specified request resolves instead of being ambiguous.
35
- const mostSpecific = mostSpecificByTagCount(candidates);
35
+ const mostSpecific = mostSpecificByCriterionCount(candidates);
36
36
  if (mostSpecific !== undefined) {
37
37
  return mostSpecific;
38
38
  }
39
39
  throw new AmbiguousBindingError(tokenDisplayName, candidates.map((c) => c.id));
40
40
  }
41
- /** The lone candidate declaring more tags than every other, or `undefined` when that is a tie. */
42
- function mostSpecificByTagCount(candidates) {
41
+ /** The lone candidate declaring more criteria than every other, or `undefined` when that is a tie. */
42
+ function mostSpecificByCriterionCount(candidates) {
43
43
  let best;
44
44
  let bestCount = -1;
45
45
  let tied = false;
@@ -94,32 +94,22 @@ function filterBindings(bindings, options, ctx, requiresSlotMatch) {
94
94
  return result;
95
95
  }
96
96
  function hasSlotCriterion(options) {
97
- return options.name !== undefined || requestedTagKeyMask(options) !== NO_TAG_KEYS;
97
+ return requestedTagKeyMask(options) !== NO_TAG_KEYS;
98
98
  }
99
99
  /**
100
- * Whether a binding's slot satisfies a request: names must be equal, and every tag the slot
101
- * declares must be among the tags requested.
100
+ * Whether a binding's slot satisfies a request: every criterion the slot declares must be among
101
+ * the request's criteria, a name spelling either side folding to the reserved criterion.
102
102
  *
103
- * @remarks The tag half is a key-mask subset test before any criterion is read, so a slot the
104
- * request cannot satisfy is rejected in one word compare. Criteria are interned, so what follows is
105
- * identity.
103
+ * @remarks A key-mask subset test runs before any criterion is read, so a slot the request cannot
104
+ * satisfy is rejected in one word compare. Criteria are interned, so what follows is identity.
106
105
  *
107
106
  * @since 0.5.0-canary.9
108
107
  */
109
108
  export function matchesSlot(slot, options) {
110
- const requestedName = options?.name;
111
- if (slot.name !== undefined) {
112
- if (slot.name !== requestedName) {
113
- return false;
114
- }
115
- }
116
- else if (requestedName !== undefined) {
117
- return false;
118
- }
119
109
  const slotMask = slot.keyMask;
120
110
  const requestMask = requestedTagKeyMask(options);
121
111
  if (slotMask === NO_TAG_KEYS) {
122
- // A request carrying tags needs a tagged slot: an untagged binding never matches.
112
+ // A request carrying any criterion never falls back to the default slot.
123
113
  return requestMask === NO_TAG_KEYS;
124
114
  }
125
115
  if (!coversTagKeys(requestMask, slotMask)) {
@@ -146,6 +136,9 @@ export function requestedTagKeyMask(options) {
146
136
  const single = options.tag;
147
137
  const listed = options.tags;
148
138
  let mask = single === undefined ? NO_TAG_KEYS : single.mask;
139
+ if (options.name !== undefined) {
140
+ mask = (mask | slotName.mask);
141
+ }
149
142
  if (listed !== undefined) {
150
143
  for (let index = 0; index < listed.length; index += 1) {
151
144
  mask = (mask | listed[index].mask);
@@ -158,6 +151,11 @@ function requestCarries(options, criterion) {
158
151
  if (options === undefined) {
159
152
  return false;
160
153
  }
154
+ // The `name` spelling folds through the intern read, so this too is identity — a hand-built
155
+ // criterion matches nothing on any lane, and an unminted name retains nothing.
156
+ if (criterion.key === slotName && options.name !== undefined && criterion === slotNameCriterionOf(options.name)) {
157
+ return true;
158
+ }
161
159
  if (options.tag === criterion) {
162
160
  return true;
163
161
  }
@@ -171,5 +169,4 @@ function requestCarries(options, criterion) {
171
169
  }
172
170
  }
173
171
  return false;
174
- }
175
- //# sourceMappingURL=binding-select.js.map
172
+ }
@@ -64,5 +64,4 @@ export declare function whenParentTaggedAll(tags: ReadonlyArray<BindingTag>): Bi
64
64
  *
65
65
  * @since 0.3.16-canary.1
66
66
  */
67
- export declare function whenAnyAncestorTaggedAll(tags: ReadonlyArray<BindingTag>): BindingConstraint;
68
- //# sourceMappingURL=constraints.d.ts.map
67
+ export declare function whenAnyAncestorTaggedAll(tags: ReadonlyArray<BindingTag>): BindingConstraint;
@@ -111,5 +111,4 @@ function assertHasCriteria(tags, helperName) {
111
111
  if (tags.length === 0) {
112
112
  throw new EmptyTagCriteriaError(helperName);
113
113
  }
114
- }
115
- //# sourceMappingURL=constraints.js.map
114
+ }