@codefast/di 0.7.0 → 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.
Files changed (173) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/dist/ambient/active-container.d.ts +4 -0
  4. package/dist/ambient/active-container.d.ts.map +1 -1
  5. package/dist/ambient/active-container.js +4 -0
  6. package/dist/ambient/active-container.js.map +1 -1
  7. package/dist/container/binding-builders.d.ts.map +1 -1
  8. package/dist/container/binding-builders.js +4 -5
  9. package/dist/container/binding-builders.js.map +1 -1
  10. package/dist/container/container.d.ts +6 -0
  11. package/dist/container/container.d.ts.map +1 -1
  12. package/dist/container/container.js +5 -4
  13. package/dist/container/container.js.map +1 -1
  14. package/dist/core/binding.d.ts +15 -2
  15. package/dist/core/binding.d.ts.map +1 -1
  16. package/dist/core/binding.js +29 -4
  17. package/dist/core/binding.js.map +1 -1
  18. package/dist/core/module.d.ts +16 -0
  19. package/dist/core/module.d.ts.map +1 -1
  20. package/dist/core/module.js +8 -0
  21. package/dist/core/module.js.map +1 -1
  22. package/dist/core/registry.d.ts +2 -3
  23. package/dist/core/registry.d.ts.map +1 -1
  24. package/dist/core/registry.js +16 -61
  25. package/dist/core/registry.js.map +1 -1
  26. package/dist/core/tag.d.ts +26 -0
  27. package/dist/core/tag.d.ts.map +1 -1
  28. package/dist/core/tag.js +54 -11
  29. package/dist/core/tag.js.map +1 -1
  30. package/dist/core/token.d.ts +6 -0
  31. package/dist/core/token.d.ts.map +1 -1
  32. package/dist/core/token.js +4 -0
  33. package/dist/core/token.js.map +1 -1
  34. package/dist/core/types.d.ts +20 -0
  35. package/dist/core/types.d.ts.map +1 -1
  36. package/dist/decorators/inject.d.ts +2 -0
  37. package/dist/decorators/inject.d.ts.map +1 -1
  38. package/dist/decorators/inject.js +2 -0
  39. package/dist/decorators/inject.js.map +1 -1
  40. package/dist/decorators/injectable.d.ts +6 -0
  41. package/dist/decorators/injectable.d.ts.map +1 -1
  42. package/dist/decorators/injectable.js +4 -0
  43. package/dist/decorators/injectable.js.map +1 -1
  44. package/dist/decorators/lifecycle-decorators.d.ts +4 -0
  45. package/dist/decorators/lifecycle-decorators.d.ts.map +1 -1
  46. package/dist/decorators/lifecycle-decorators.js +4 -0
  47. package/dist/decorators/lifecycle-decorators.js.map +1 -1
  48. package/dist/errors/errors.d.ts +34 -0
  49. package/dist/errors/errors.d.ts.map +1 -1
  50. package/dist/errors/errors.js +60 -3
  51. package/dist/errors/errors.js.map +1 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/injection/descriptor.d.ts +12 -0
  57. package/dist/injection/descriptor.d.ts.map +1 -1
  58. package/dist/injection/descriptor.js +8 -0
  59. package/dist/injection/descriptor.js.map +1 -1
  60. package/dist/injection/resolve-options.d.ts +17 -12
  61. package/dist/injection/resolve-options.d.ts.map +1 -1
  62. package/dist/injection/resolve-options.js +69 -15
  63. package/dist/injection/resolve-options.js.map +1 -1
  64. package/dist/introspection/dependency-graph.d.ts +6 -0
  65. package/dist/introspection/dependency-graph.d.ts.map +1 -1
  66. package/dist/introspection/dependency-graph.js +24 -9
  67. package/dist/introspection/dependency-graph.js.map +1 -1
  68. package/dist/introspection/graph-adapters/cytoscape.d.ts +8 -0
  69. package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -1
  70. package/dist/introspection/graph-adapters/cytoscape.js +2 -0
  71. package/dist/introspection/graph-adapters/cytoscape.js.map +1 -1
  72. package/dist/introspection/graph-adapters/dot.d.ts +2 -0
  73. package/dist/introspection/graph-adapters/dot.d.ts.map +1 -1
  74. package/dist/introspection/graph-adapters/dot.js +2 -0
  75. package/dist/introspection/graph-adapters/dot.js.map +1 -1
  76. package/dist/introspection/graph-adapters/reactflow.d.ts +8 -0
  77. package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -1
  78. package/dist/introspection/graph-adapters/reactflow.js +2 -0
  79. package/dist/introspection/graph-adapters/reactflow.js.map +1 -1
  80. package/dist/introspection/inspector.d.ts +6 -0
  81. package/dist/introspection/inspector.d.ts.map +1 -1
  82. package/dist/introspection/inspector.js +2 -0
  83. package/dist/introspection/inspector.js.map +1 -1
  84. package/dist/lifecycle/lifecycle-manager.d.ts +2 -0
  85. package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -1
  86. package/dist/lifecycle/lifecycle-manager.js +2 -0
  87. package/dist/lifecycle/lifecycle-manager.js.map +1 -1
  88. package/dist/lifecycle/scope-manager.d.ts +2 -0
  89. package/dist/lifecycle/scope-manager.d.ts.map +1 -1
  90. package/dist/lifecycle/scope-manager.js +2 -0
  91. package/dist/lifecycle/scope-manager.js.map +1 -1
  92. package/dist/metadata/metadata-keys.d.ts +6 -0
  93. package/dist/metadata/metadata-keys.d.ts.map +1 -1
  94. package/dist/metadata/metadata-keys.js +6 -0
  95. package/dist/metadata/metadata-keys.js.map +1 -1
  96. package/dist/metadata/metadata-reader-token.d.ts +2 -0
  97. package/dist/metadata/metadata-reader-token.d.ts.map +1 -1
  98. package/dist/metadata/metadata-reader-token.js +2 -0
  99. package/dist/metadata/metadata-reader-token.js.map +1 -1
  100. package/dist/metadata/metadata-types.d.ts +6 -0
  101. package/dist/metadata/metadata-types.d.ts.map +1 -1
  102. package/dist/metadata/symbol-metadata-reader.d.ts +4 -0
  103. package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
  104. package/dist/metadata/symbol-metadata-reader.js +4 -0
  105. package/dist/metadata/symbol-metadata-reader.js.map +1 -1
  106. package/dist/resolution/cache/activation-need.d.ts +2 -0
  107. package/dist/resolution/cache/activation-need.d.ts.map +1 -1
  108. package/dist/resolution/cache/activation-need.js +2 -0
  109. package/dist/resolution/cache/activation-need.js.map +1 -1
  110. package/dist/resolution/cache/binding-lookup-cache.d.ts +2 -2
  111. package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -1
  112. package/dist/resolution/cache/binding-lookup-cache.js +2 -37
  113. package/dist/resolution/cache/binding-lookup-cache.js.map +1 -1
  114. package/dist/resolution/cache/class-introspector.d.ts +2 -0
  115. package/dist/resolution/cache/class-introspector.d.ts.map +1 -1
  116. package/dist/resolution/cache/class-introspector.js +2 -0
  117. package/dist/resolution/cache/class-introspector.js.map +1 -1
  118. package/dist/resolution/context.d.ts +6 -0
  119. package/dist/resolution/context.d.ts.map +1 -1
  120. package/dist/resolution/context.js +4 -0
  121. package/dist/resolution/context.js.map +1 -1
  122. package/dist/resolution/plan/instantiation-plan.d.ts +6 -12
  123. package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -1
  124. package/dist/resolution/plan/instantiation-plan.js +11 -27
  125. package/dist/resolution/plan/instantiation-plan.js.map +1 -1
  126. package/dist/resolution/resolver.d.ts +14 -5
  127. package/dist/resolution/resolver.d.ts.map +1 -1
  128. package/dist/resolution/resolver.js +108 -141
  129. package/dist/resolution/resolver.js.map +1 -1
  130. package/dist/resolution/select/binding-select.d.ts +4 -5
  131. package/dist/resolution/select/binding-select.d.ts.map +1 -1
  132. package/dist/resolution/select/binding-select.js +18 -20
  133. package/dist/resolution/select/binding-select.js.map +1 -1
  134. package/dist/resolution/select/constraints.d.ts +16 -0
  135. package/dist/resolution/select/constraints.d.ts.map +1 -1
  136. package/dist/resolution/select/constraints.js +16 -0
  137. package/dist/resolution/select/constraints.js.map +1 -1
  138. package/package.json +13 -6
  139. package/src/ambient/active-container.ts +4 -0
  140. package/src/container/binding-builders.ts +10 -5
  141. package/src/container/container.ts +9 -4
  142. package/src/core/binding.ts +33 -5
  143. package/src/core/module.ts +16 -0
  144. package/src/core/registry.ts +16 -66
  145. package/src/core/tag.ts +71 -17
  146. package/src/core/token.ts +6 -0
  147. package/src/core/types.ts +20 -0
  148. package/src/decorators/inject.ts +2 -0
  149. package/src/decorators/injectable.ts +8 -0
  150. package/src/decorators/lifecycle-decorators.ts +4 -0
  151. package/src/errors/errors.ts +62 -5
  152. package/src/index.ts +1 -1
  153. package/src/injection/descriptor.ts +12 -0
  154. package/src/injection/resolve-options.ts +75 -18
  155. package/src/introspection/dependency-graph.ts +29 -9
  156. package/src/introspection/graph-adapters/cytoscape.ts +8 -0
  157. package/src/introspection/graph-adapters/dot.ts +2 -0
  158. package/src/introspection/graph-adapters/reactflow.ts +8 -0
  159. package/src/introspection/inspector.ts +6 -0
  160. package/src/lifecycle/lifecycle-manager.ts +2 -0
  161. package/src/lifecycle/scope-manager.ts +2 -0
  162. package/src/metadata/metadata-keys.ts +6 -0
  163. package/src/metadata/metadata-reader-token.ts +2 -0
  164. package/src/metadata/metadata-types.ts +6 -0
  165. package/src/metadata/symbol-metadata-reader.ts +4 -0
  166. package/src/resolution/cache/activation-need.ts +2 -0
  167. package/src/resolution/cache/binding-lookup-cache.ts +2 -40
  168. package/src/resolution/cache/class-introspector.ts +2 -0
  169. package/src/resolution/context.ts +6 -0
  170. package/src/resolution/plan/instantiation-plan.ts +15 -38
  171. package/src/resolution/resolver.ts +122 -141
  172. package/src/resolution/select/binding-select.ts +18 -21
  173. package/src/resolution/select/constraints.ts +16 -0
@@ -50,6 +50,8 @@ import { DependencyResolver } from "#/resolution/resolver";
50
50
  // ── Container interface ──────────────────────────────────────────────────────────────────────────────────────────────
51
51
 
52
52
  /**
53
+ * The public surface: binding, resolving, modules, lifecycle hooks, child scopes, and disposal.
54
+ *
53
55
  * @since 0.3.16-canary.0
54
56
  */
55
57
  export interface Container {
@@ -114,6 +116,8 @@ export interface ContainerOptions {
114
116
  }
115
117
 
116
118
  /**
119
+ * The factory surface the `Container` companion object exposes.
120
+ *
117
121
  * @since 0.3.16-canary.0
118
122
  */
119
123
  export interface ContainerStatic {
@@ -182,9 +186,6 @@ class DefaultContainer implements Container {
182
186
  if (this.#moduleRefs !== undefined || this.#moduleBindingIds !== undefined) {
183
187
  builtSubsystems.push("container.moduleTables");
184
188
  }
185
- if (this.#registry.isNamedIndexBuilt) {
186
- builtSubsystems.push("registry.namedIndex");
187
- }
188
189
  if (this.#registry.isTaggedIndexBuilt) {
189
190
  builtSubsystems.push("registry.taggedIndex");
190
191
  }
@@ -695,8 +696,10 @@ class DefaultContainer implements Container {
695
696
  ) {
696
697
  continue;
697
698
  }
699
+ // Warm the binding itself — re-selecting by its own criteria could pick a different
700
+ // candidate whose criteria are a subset of them.
698
701
  const slotOptions = bindingSlotToResolveOptions(binding.slot);
699
- await this.resolveAsync(binding.token, slotOptions);
702
+ await this.#resolver.warmBindingAsync(binding, slotOptions);
700
703
  }
701
704
  }
702
705
  }
@@ -944,6 +947,8 @@ function throwCollected(errors: ReadonlyArray<unknown>, message: string): void {
944
947
  // ── Container static ─────────────────────────────────────────────────────────────────────────────────────────────────
945
948
 
946
949
  /**
950
+ * The companion factory that creates containers, empty or preloaded from modules.
951
+ *
947
952
  * @since 0.3.16-canary.0
948
953
  */
949
954
  export const Container: ContainerStatic = {
@@ -1,5 +1,5 @@
1
1
  import type { BindingTag, TagKeyMask } from "#/core/tag";
2
- import { NO_TAG_KEYS } from "#/core/tag";
2
+ import { NO_TAG_KEYS, slotName, tagKeyMaskOf } from "#/core/tag";
3
3
  import type { Token } from "#/core/token";
4
4
  import type {
5
5
  ActivationHandler,
@@ -16,24 +16,48 @@ import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue
16
16
  // ── BindingSlot ──────────────────────────────────────────────────────────────────────────────────────────────────────
17
17
 
18
18
  /**
19
- * The name-plus-tags coordinate a binding registers under and a request matches against.
19
+ * The criterion set a binding registers under and a request matches against.
20
20
  *
21
21
  * @since 0.3.16-canary.0
22
22
  */
23
23
  export interface BindingSlot {
24
+ /** Derived view of the reserved `slotName` criterion — `undefined` when the slot carries none. */
24
25
  readonly name: string | undefined;
26
+ /** The whole criterion set, the reserved name criterion included. */
25
27
  readonly tags: ReadonlyArray<BindingTag>;
26
28
  /** OR of this slot's tag keys, so the subset test is one word compare. */
27
29
  readonly keyMask: TagKeyMask;
28
30
  }
29
31
 
30
32
  /**
31
- * Returns whether two slots carry the same name and tag set, in any tag order.
33
+ * Builds a slot from its criterion set, deriving the name view and key mask.
34
+ *
35
+ * @remarks The one place the derived `name` is computed — a slot assembled any other way can carry
36
+ * a name criterion the readers of the view never see.
37
+ *
38
+ * @since 0.8.0
39
+ */
40
+ export function createBindingSlot(tags: ReadonlyArray<BindingTag>): BindingSlot {
41
+ let name: string | undefined;
42
+ for (const criterion of tags) {
43
+ if (criterion.key === slotName) {
44
+ name = criterion.value as string;
45
+ break;
46
+ }
47
+ }
48
+ // Frozen like DEFAULT_BINDING_SLOT's list: frames and snapshots alias a slot's tags.
49
+ return { name, tags: Object.freeze([...tags]), keyMask: tagKeyMaskOf(tags) };
50
+ }
51
+
52
+ /**
53
+ * Returns whether two slots carry the same criterion set, in any order.
54
+ *
55
+ * @remarks The derived `name` is not compared — the criterion set alone is the identity.
32
56
  *
33
57
  * @since 0.3.16-canary.0
34
58
  */
35
59
  export function bindingSlotEquals(left: BindingSlot, right: BindingSlot): boolean {
36
- if (left.name !== right.name || left.keyMask !== right.keyMask || left.tags.length !== right.tags.length) {
60
+ if (left.keyMask !== right.keyMask || left.tags.length !== right.tags.length) {
37
61
  return false;
38
62
  }
39
63
  for (const criterion of left.tags) {
@@ -68,7 +92,7 @@ export const DEFAULT_BINDING_SLOT: BindingSlot = { name: undefined, tags: Object
68
92
  * @since 0.3.16-canary.0
69
93
  */
70
94
  export function bindingSlotToString(slot: BindingSlot): string {
71
- if (slot.name === undefined && slot.tags.length === 0) {
95
+ if (slot.tags.length === 0) {
72
96
  return "default";
73
97
  }
74
98
  const parts: Array<string> = [];
@@ -76,6 +100,10 @@ export function bindingSlotToString(slot: BindingSlot): string {
76
100
  parts.push(`name:${slot.name}`);
77
101
  }
78
102
  for (const criterion of slot.tags) {
103
+ // The reserved criterion already printed as the `name:` part.
104
+ if (criterion.key === slotName) {
105
+ continue;
106
+ }
79
107
  parts.push(`tag:${criterion.key.name}=${String(criterion.value)}`);
80
108
  }
81
109
  return parts.join(",");
@@ -16,6 +16,8 @@ const ASYNC_MODULE_BRAND: unique symbol = Symbol("di:async-module");
16
16
  export const MODULE_SETUP: unique symbol = Symbol("di:module-setup");
17
17
 
18
18
  /**
19
+ * A named, reusable group of bindings a container applies synchronously via `load()`.
20
+ *
19
21
  * @since 0.3.16-canary.0
20
22
  */
21
23
  export interface SyncModule {
@@ -25,6 +27,8 @@ export interface SyncModule {
25
27
  }
26
28
 
27
29
  /**
30
+ * A named group of bindings whose setup is async, applied via `loadAsync()`.
31
+ *
28
32
  * @since 0.3.16-canary.0
29
33
  */
30
34
  export interface AsyncModule {
@@ -36,6 +40,8 @@ export interface AsyncModule {
36
40
  // ── Builder interfaces ───────────────────────────────────────────────────────────────────────────────────────────────
37
41
 
38
42
  /**
43
+ * The binding surface a sync module's setup callback receives.
44
+ *
39
45
  * @since 0.3.16-canary.0
40
46
  */
41
47
  export interface ModuleBuilder {
@@ -44,6 +50,8 @@ export interface ModuleBuilder {
44
50
  }
45
51
 
46
52
  /**
53
+ * The binding surface an async module's setup callback receives; its `import` accepts async modules too.
54
+ *
47
55
  * @since 0.3.16-canary.0
48
56
  */
49
57
  export interface AsyncModuleBuilder {
@@ -54,6 +62,8 @@ export interface AsyncModuleBuilder {
54
62
  // ── Static factories ─────────────────────────────────────────────────────────────────────────────────────────────────
55
63
 
56
64
  /**
65
+ * The companion factory that creates `SyncModule` values.
66
+ *
57
67
  * @since 0.3.16-canary.0
58
68
  */
59
69
  export const SyncModule = {
@@ -67,6 +77,8 @@ export const SyncModule = {
67
77
  };
68
78
 
69
79
  /**
80
+ * The companion factory that creates `AsyncModule` values.
81
+ *
70
82
  * @since 0.3.16-canary.0
71
83
  */
72
84
  export const AsyncModule = {
@@ -82,6 +94,8 @@ export const AsyncModule = {
82
94
  // ── Module — unified API ─────────────────────────────────────────────────────────────────────────────────────────────
83
95
 
84
96
  /**
97
+ * The unified module factory — `create` for sync modules, `createAsync` for async ones.
98
+ *
85
99
  * @since 0.3.16-canary.0
86
100
  */
87
101
  export const Module = {
@@ -94,6 +108,8 @@ export const Module = {
94
108
  };
95
109
 
96
110
  /**
111
+ * Narrows a module union to `SyncModule` by checking its brand.
112
+ *
97
113
  * @since 0.3.16-canary.0
98
114
  */
99
115
  export function isSyncModule(module: SyncModule | AsyncModule): module is SyncModule {
@@ -6,6 +6,8 @@ import type { Token } from "#/core/token";
6
6
  import type { BindingIdentifier, Constructor, DependencyKey } from "#/core/types";
7
7
 
8
8
  /**
9
+ * One container's binding store, indexed by token, binding id, and slot for fast lookup.
10
+ *
9
11
  * @since 0.3.16-canary.0
10
12
  */
11
13
  export class BindingRegistry {
@@ -15,15 +17,13 @@ export class BindingRegistry {
15
17
  readonly #bindings = new Map<DependencyKey, Array<Binding>>();
16
18
  // Fast lookup by binding ID
17
19
  readonly #byId = new Map<BindingIdentifier, Binding>();
18
- // Fast lookup for slot { name, tags: [] } — unallocated until a named binding is registered.
19
- #simpleNamed: Map<DependencyKey, Map<string, Binding>> | undefined;
20
20
  // Fast path for one default slot binding with no predicate
21
21
  readonly #fastDefault = new Map<DependencyKey, Binding>();
22
- // Fast lookup for a slot carrying exactly one tag — keyed by the interned criterion itself, so
23
- // the (key, value) pair is one hash rather than two. Unallocated until a tagged binding lands.
22
+ // Fast lookup for a slot carrying exactly one criterion (a lone name folds here too) — keyed by
23
+ // the interned criterion itself, so the pair is one hash. Unallocated until such a slot lands.
24
24
  #simpleTagged: Map<DependencyKey, Map<BindingTag, Binding>> | undefined;
25
- // Name-less slots with two or more tags, bucketed by their FIRST criterion. A matching slot's
26
- // every tag is in the request, so walking the request's buckets finds each candidate exactly once.
25
+ // Slots with two or more criteria, bucketed by their FIRST criterion. A matching slot's every
26
+ // criterion is in the request, so walking the request's buckets finds each candidate exactly once.
27
27
  #multiTagged: Map<DependencyKey, Map<BindingTag, Array<Binding>>> | undefined;
28
28
 
29
29
  // Set on the first constant registered and never cleared. Teardown only needs the negative answer
@@ -77,7 +77,6 @@ export class BindingRegistry {
77
77
  if (existingIndex !== -1) {
78
78
  displacedBinding = bindingsForToken[existingIndex]!;
79
79
  this.#byId.delete(displacedBinding.id);
80
- this.#deindexSimpleNamedBinding(key, displacedBinding);
81
80
  this.#deindexSimpleTaggedBinding(key, displacedBinding);
82
81
  this.#deindexMultiTaggedBinding(key, displacedBinding);
83
82
  }
@@ -90,7 +89,6 @@ export class BindingRegistry {
90
89
 
91
90
  this.#bindings.set(key, nextBindings);
92
91
  this.#byId.set(binding.id, binding);
93
- this.#indexSimpleNamedBinding(key, binding);
94
92
  this.#indexSimpleTaggedBinding(key, binding);
95
93
  this.#indexMultiTaggedBinding(key, binding);
96
94
  this.#refreshFastDefaultForToken(key);
@@ -103,7 +101,6 @@ export class BindingRegistry {
103
101
  const key: DependencyKey = token;
104
102
  const bindingsForToken = this.#bindings.get(key) ?? [];
105
103
  this.#bindings.delete(key);
106
- this.#simpleNamed?.delete(key);
107
104
  this.#simpleTagged?.delete(key);
108
105
  this.#multiTagged?.delete(key);
109
106
  this.#fastDefault.delete(key);
@@ -127,12 +124,10 @@ export class BindingRegistry {
127
124
  const bindingIndex = bindingsForToken.findIndex((candidate) => candidate.id === id);
128
125
  // Copy-on-write, like `add`: a walk holding the current array must not lose its place.
129
126
  const remaining = bindingIndex === -1 ? bindingsForToken : bindingsForToken.toSpliced(bindingIndex, 1);
130
- this.#deindexSimpleNamedBinding(key, binding);
131
127
  this.#deindexSimpleTaggedBinding(key, binding);
132
128
  this.#deindexMultiTaggedBinding(key, binding);
133
129
  if (remaining.length === 0) {
134
130
  this.#bindings.delete(key);
135
- this.#simpleNamed?.delete(key);
136
131
  this.#simpleTagged?.delete(key);
137
132
  this.#multiTagged?.delete(key);
138
133
  this.#fastDefault.delete(key);
@@ -176,7 +171,6 @@ export class BindingRegistry {
176
171
  const all = this.allBindings();
177
172
  this.#bindings.clear();
178
173
  this.#byId.clear();
179
- this.#simpleNamed?.clear();
180
174
  this.#simpleTagged?.clear();
181
175
  this.#multiTagged?.clear();
182
176
  this.#fastDefault.clear();
@@ -197,10 +191,6 @@ export class BindingRegistry {
197
191
  );
198
192
  }
199
193
 
200
- getSimpleNamed(token: Token<unknown> | Constructor, name: string): Binding | undefined {
201
- return this.#simpleNamed?.get(token)?.get(name);
202
- }
203
-
204
194
  /**
205
195
  * The binding indexed under one criterion.
206
196
  *
@@ -285,33 +275,6 @@ export class BindingRegistry {
285
275
  }
286
276
  }
287
277
 
288
- #indexSimpleNamedBinding(tokenKey: DependencyKey, binding: Binding): void {
289
- const name = simpleNameOf(binding);
290
- if (name === undefined) {
291
- return;
292
- }
293
- this.#simpleNamed ??= new Map();
294
- const bindingsByName = getOrInsert(this.#simpleNamed, tokenKey, new Map<string, Binding>());
295
- bindingsByName.set(name, binding);
296
- }
297
-
298
- #deindexSimpleNamedBinding(tokenKey: DependencyKey, binding: Binding): void {
299
- const name = simpleNameOf(binding);
300
- if (name === undefined) {
301
- return;
302
- }
303
- const bindingsByName = this.#simpleNamed?.get(tokenKey);
304
- if (bindingsByName === undefined) {
305
- return;
306
- }
307
- if (bindingsByName.get(name)?.id === binding.id) {
308
- bindingsByName.delete(name);
309
- if (bindingsByName.size === 0) {
310
- this.#simpleNamed!.delete(tokenKey);
311
- }
312
- }
313
- }
314
-
315
278
  #refreshFastDefaultForToken(tokenKey: DependencyKey): void {
316
279
  const bindingsForToken = this.#bindings.get(tokenKey);
317
280
  const onlyBinding = bindingsForToken?.length === 1 ? bindingsForToken[0]! : undefined;
@@ -322,48 +285,35 @@ export class BindingRegistry {
322
285
  this.#fastDefault.delete(tokenKey);
323
286
  }
324
287
 
325
- /** Whether the deferred named-slot index has had to be built. */
326
- get isNamedIndexBuilt(): boolean {
327
- return this.#simpleNamed !== undefined;
328
- }
329
-
330
288
  /** Whether the deferred tagged-slot index has had to be built. */
331
289
  get isTaggedIndexBuilt(): boolean {
332
290
  return this.#simpleTagged !== undefined;
333
291
  }
334
292
  }
335
293
 
336
- /** The name a binding is indexed under, or `undefined` when its slot is more than a plain name. */
337
- function simpleNameOf(binding: Binding): string | undefined {
338
- const { name, tags } = binding.slot;
339
- return name !== undefined && tags.length === 0 ? name : undefined;
340
- }
341
-
342
294
  /**
343
- * The tag a binding is indexed under, or `undefined` when its slot is more than one plain tag.
295
+ * The criterion a binding is indexed under, or `undefined` when its slot carries more than one.
344
296
  *
345
- * @remarks Carries predicate-bearing bindings too, exactly as the name index does: every lane that
346
- * reads this index already re-checks what it finds, so an indexed hit was never unconditional.
297
+ * @remarks Carries predicate-bearing bindings too: every lane that reads this index already
298
+ * re-checks what it finds, so an indexed hit was never unconditional.
347
299
  */
348
300
  function simpleTagOf(binding: Binding): BindingTag | undefined {
349
- const { name, tags } = binding.slot;
350
- return name === undefined && tags.length === 1 ? tags[0] : undefined;
301
+ const { tags } = binding.slot;
302
+ return tags.length === 1 ? tags[0] : undefined;
351
303
  }
352
304
 
353
- /** The first criterion a multi-tag slot is bucketed under, or `undefined` for any other shape. */
305
+ /** The first criterion a multi-criterion slot is bucketed under, or `undefined` for any other shape. */
354
306
  function multiTagFirstOf(binding: Binding): BindingTag | undefined {
355
- const { name, tags } = binding.slot;
356
- return name === undefined && tags.length >= 2 ? tags[0] : undefined;
307
+ const { tags } = binding.slot;
308
+ return tags.length >= 2 ? tags[0] : undefined;
357
309
  }
358
310
 
359
311
  /** A binding nothing has to be matched against: the default slot, no predicate. */
360
312
  function isDefaultSlotBinding(binding: Binding): boolean {
361
- const { name, tags } = binding.slot;
362
- return name === undefined && tags.length === 0 && binding.predicate === undefined;
313
+ return binding.slot.tags.length === 0 && binding.predicate === undefined;
363
314
  }
364
315
 
365
316
  /** A predicate with no slot constraint: last-wins does not apply to it. */
366
317
  function isPurePredicateBinding(binding: Binding): boolean {
367
- const { name, tags } = binding.slot;
368
- return binding.predicate !== undefined && name === undefined && tags.length === 0;
318
+ return binding.predicate !== undefined && binding.slot.tags.length === 0;
369
319
  }
package/src/core/tag.ts CHANGED
@@ -58,6 +58,14 @@ export interface TagKey<Value = unknown> {
58
58
  readonly mask: TagKeyMask;
59
59
  /** The criterion for one value, interned: the same value always yields the same object. */
60
60
  of(value: Value): BindingTag<Value>;
61
+ /**
62
+ * The interned criterion for a value, or `undefined` when none was ever minted.
63
+ *
64
+ * @remarks Reading without minting is what keeps a request-side value from being retained for
65
+ * the process lifetime: a value no binding ever declared has no criterion, so a lookup can
66
+ * answer "no match" without inserting one.
67
+ */
68
+ peek(value: Value): BindingTag<Value> | undefined;
61
69
  }
62
70
 
63
71
  let tagKeyCounter = -1;
@@ -101,6 +109,28 @@ export function tag<Value = unknown>(name: string): TagKey<Value> {
101
109
  let lastValue: Value | undefined;
102
110
  let lastPair: BindingTag<Value> | undefined;
103
111
 
112
+ // The miss paths live outside `of()`/`peek()` so the hot wrappers stay small enough to inline.
113
+ const internPair = (value: Value): BindingTag<Value> => {
114
+ const cacheKey = internKeyFor(value);
115
+ const existing = interned.get(cacheKey);
116
+
117
+ if (existing !== undefined) {
118
+ lastValue = value;
119
+ lastPair = existing;
120
+
121
+ return existing;
122
+ }
123
+
124
+ const pair = { key, value, mask } as BindingTag<Value>;
125
+
126
+ interned.set(cacheKey, pair);
127
+ lastValue = value;
128
+ lastPair = pair;
129
+
130
+ return pair;
131
+ };
132
+ const peekInterned = (value: Value): BindingTag<Value> | undefined => interned.get(internKeyFor(value));
133
+
104
134
  const key: TagKey<Value> = {
105
135
  name,
106
136
  id,
@@ -109,30 +139,54 @@ export function tag<Value = unknown>(name: string): TagKey<Value> {
109
139
  if (lastPair !== undefined && Object.is(value, lastValue)) {
110
140
  return lastPair;
111
141
  }
112
-
113
- const cacheKey = internKeyFor(value);
114
- const existing = interned.get(cacheKey);
115
-
116
- if (existing !== undefined) {
117
- lastValue = value;
118
- lastPair = existing;
119
-
120
- return existing;
142
+ return internPair(value);
143
+ },
144
+ peek(value: Value): BindingTag<Value> | undefined {
145
+ if (lastPair !== undefined && Object.is(value, lastValue)) {
146
+ return lastPair;
121
147
  }
122
-
123
- const pair = { key, value, mask } as BindingTag<Value>;
124
-
125
- interned.set(cacheKey, pair);
126
- lastValue = value;
127
- lastPair = pair;
128
-
129
- return pair;
148
+ return peekInterned(value);
130
149
  },
131
150
  };
132
151
 
133
152
  return key;
134
153
  }
135
154
 
155
+ /**
156
+ * The reserved key a slot's name is a criterion of.
157
+ *
158
+ * @remarks `whenNamed(n)` and a request's `name` are sugar for `slotName.of(n)`, so one selection
159
+ * model serves both spellings — a name takes part in key masks, indexes and specificity like any
160
+ * criterion. What reserves the key is its identity; diagnostics render its criteria as `name:<value>`.
161
+ *
162
+ * @since 0.8.0
163
+ */
164
+ export const slotName: TagKey<string> = tag<string>("di:name");
165
+
166
+ // One-entry front for the reserved key's read: a name's criterion never changes once minted, so a
167
+ // hit is sound forever, and a miss is never cached so a later `whenNamed` bind is still seen.
168
+ let lastPeekedName: string | undefined;
169
+ let lastPeekedCriterion: BindingTag<string> | undefined;
170
+
171
+ /**
172
+ * The reserved criterion for a name, or `undefined` while no binding has declared it.
173
+ *
174
+ * @remarks Reads without minting, so a request-side name no binding declared is never retained.
175
+ *
176
+ * @since 0.8.0
177
+ */
178
+ export function slotNameCriterionOf(name: string): BindingTag<string> | undefined {
179
+ if (name === lastPeekedName) {
180
+ return lastPeekedCriterion;
181
+ }
182
+ const criterion = slotName.peek(name);
183
+ if (criterion !== undefined) {
184
+ lastPeekedName = name;
185
+ lastPeekedCriterion = criterion;
186
+ }
187
+ return criterion;
188
+ }
189
+
136
190
  /**
137
191
  * The key set a list of criteria covers.
138
192
  *
package/src/core/token.ts CHANGED
@@ -3,6 +3,8 @@ import type { Constructor } from "#/core/constructor-type";
3
3
  declare const TOKEN_BRAND: unique symbol;
4
4
 
5
5
  /**
6
+ * A branded identifier carrying the value type its bindings resolve to.
7
+ *
6
8
  * @since 0.3.16-canary.0
7
9
  */
8
10
  export interface Token<out Value> {
@@ -11,6 +13,8 @@ export interface Token<out Value> {
11
13
  }
12
14
 
13
15
  /**
16
+ * Creates a named `Token` for the given value type.
17
+ *
14
18
  * @since 0.3.16-canary.0
15
19
  */
16
20
  export function token<Value>(name: string): Token<Value> {
@@ -18,6 +22,8 @@ export function token<Value>(name: string): Token<Value> {
18
22
  }
19
23
 
20
24
  /**
25
+ * Returns the display name of a token or class used as a dependency key.
26
+ *
21
27
  * @since 0.3.16-canary.0
22
28
  */
23
29
  export function tokenName(dependency: Token<unknown> | Constructor): string {
package/src/core/types.ts CHANGED
@@ -16,6 +16,8 @@ export type DependencyKey = Token<unknown> | Constructor;
16
16
  // ── BindingScope ─────────────────────────────────────────────────────────────────────────────────────────────────────
17
17
 
18
18
  /**
19
+ * The lifetime a binding's instances are cached under.
20
+ *
19
21
  * @since 0.3.16-canary.0
20
22
  */
21
23
  export type BindingScope = "singleton" | "transient" | "scoped";
@@ -24,6 +26,8 @@ export type BindingScope = "singleton" | "transient" | "scoped";
24
26
 
25
27
  declare const BINDING_ID_BRAND: unique symbol;
26
28
  /**
29
+ * A branded string that uniquely identifies one binding.
30
+ *
27
31
  * @since 0.3.16-canary.0
28
32
  */
29
33
  export type BindingIdentifier = string & { readonly [BINDING_ID_BRAND]: true };
@@ -31,6 +35,8 @@ export type BindingIdentifier = string & { readonly [BINDING_ID_BRAND]: true };
31
35
  // ── BindingKind ──────────────────────────────────────────────────────────────────────────────────────────────────────
32
36
 
33
37
  /**
38
+ * The strategy a binding produces values with, set by the `to*()` call that created it.
39
+ *
34
40
  * @since 0.3.16-canary.0
35
41
  */
36
42
  export type BindingKind = "class" | "dynamic" | "dynamic-async" | "resolved" | "resolved-async" | "constant" | "alias";
@@ -38,11 +44,15 @@ export type BindingKind = "class" | "dynamic" | "dynamic-async" | "resolved" | "
38
44
  // ── Handlers ─────────────────────────────────────────────────────────────────────────────────────────────────────────
39
45
 
40
46
  /**
47
+ * A hook that runs after an instance is created and returns the instance to hand out.
48
+ *
41
49
  * @since 0.3.16-canary.0
42
50
  */
43
51
  export type ActivationHandler<Value> = (ctx: ResolutionContext, instance: Value) => Value | Promise<Value>;
44
52
 
45
53
  /**
54
+ * A hook that runs when a cached instance is released from its scope.
55
+ *
46
56
  * @since 0.3.16-canary.0
47
57
  */
48
58
  export type DeactivationHandler<Value> = (instance: Value) => void | Promise<void>;
@@ -50,6 +60,8 @@ export type DeactivationHandler<Value> = (instance: Value) => void | Promise<voi
50
60
  // ── ResolveOptions ───────────────────────────────────────────────────────────────────────────────────────────────────
51
61
 
52
62
  /**
63
+ * Slot-selection criteria — a name and tags — that narrow which binding a resolve call matches.
64
+ *
53
65
  * @since 0.3.16-canary.0
54
66
  */
55
67
  export interface ResolveOptions {
@@ -67,6 +79,8 @@ export interface ResolveOptions {
67
79
  // ── ResolutionFrame ──────────────────────────────────────────────────────────────────────────────────────────────────
68
80
 
69
81
  /**
82
+ * One entry of the resolution stack: the token, scope, binding, and slot being resolved.
83
+ *
70
84
  * @since 0.3.16-canary.0
71
85
  */
72
86
  export interface ResolutionFrame {
@@ -84,6 +98,8 @@ export interface ResolutionFrame {
84
98
  // ── ConstraintContext ────────────────────────────────────────────────────────────────────────────────────────────────
85
99
 
86
100
  /**
101
+ * The resolution-time view — path, stack, parent, and ancestors — a `BindingConstraint` inspects.
102
+ *
87
103
  * @since 0.3.16-canary.0
88
104
  */
89
105
  export interface ConstraintContext {
@@ -105,6 +121,8 @@ export type BindingConstraint = (ctx: ConstraintContext) => boolean;
105
121
  // ── ResolutionContext ────────────────────────────────────────────────────────────────────────────────────────────────
106
122
 
107
123
  /**
124
+ * The container view a dynamic factory or activation handler resolves further dependencies through.
125
+ *
108
126
  * @since 0.3.16-canary.0
109
127
  */
110
128
  export interface ResolutionContext {
@@ -123,6 +141,8 @@ export interface ResolutionContext {
123
141
  // ── TokenValue ───────────────────────────────────────────────────────────────────────────────────────────────────────
124
142
 
125
143
  /**
144
+ * The value type a token or class constructor resolves to.
145
+ *
126
146
  * @since 0.3.16-canary.0
127
147
  */
128
148
  export type TokenValue<Type> =
@@ -35,6 +35,8 @@ type ClassAccessorDecorator<This, Value> = (
35
35
  ) => ClassAccessorDecoratorResult<This, Value> | void;
36
36
 
37
37
  /**
38
+ * Creates a dual-role value: an injection descriptor that also works as a class accessor decorator.
39
+ *
38
40
  * @since 0.3.16-canary.0
39
41
  */
40
42
  export function inject<Value>(
@@ -7,6 +7,8 @@ import type { ParamMetadata } from "#/metadata/metadata-types";
7
7
  // ── AutoRegisterRegistry ─────────────────────────────────────────────────────────────────────────────────────────────
8
8
 
9
9
  /**
10
+ * The collector `@injectable` registers a class into, for a container to bind later.
11
+ *
10
12
  * @since 0.3.16-canary.0
11
13
  */
12
14
  export interface AutoRegisterRegistry {
@@ -15,6 +17,8 @@ export interface AutoRegisterRegistry {
15
17
  }
16
18
 
17
19
  /**
20
+ * Creates an empty auto-register registry.
21
+ *
18
22
  * @since 0.3.16-canary.0
19
23
  */
20
24
  export function createAutoRegisterRegistry(): AutoRegisterRegistry {
@@ -32,6 +36,8 @@ export function createAutoRegisterRegistry(): AutoRegisterRegistry {
32
36
  // ── InjectableOptions ────────────────────────────────────────────────────────────────────────────────────────────────
33
37
 
34
38
  /**
39
+ * Auto-registration options `@injectable` accepts alongside its deps list.
40
+ *
35
41
  * @since 0.3.16-canary.0
36
42
  */
37
43
  export interface InjectableOptions {
@@ -83,6 +89,8 @@ export function injectable<const Deps extends ReadonlyArray<InjectableDependency
83
89
  ) => void;
84
90
 
85
91
  /**
92
+ * Writes the declared dependencies into the class's metadata and optionally auto-registers it.
93
+ *
86
94
  * @since 0.6.0
87
95
  */
88
96
  export function injectable(
@@ -25,6 +25,8 @@ function recordLifecycleMethod(phase: "postConstruct" | "preDestroy"): MethodDec
25
25
  }
26
26
 
27
27
  /**
28
+ * Marks an instance method to run after the container constructs and wires the instance.
29
+ *
28
30
  * @since 0.3.16-canary.0
29
31
  */
30
32
  export function postConstruct(): MethodDecorator {
@@ -32,6 +34,8 @@ export function postConstruct(): MethodDecorator {
32
34
  }
33
35
 
34
36
  /**
37
+ * Marks an instance method to run when the instance's container or scope is disposed.
38
+ *
35
39
  * @since 0.3.16-canary.0
36
40
  */
37
41
  export function preDestroy(): MethodDecorator {