@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,15 +0,0 @@
1
- import type { Binding } from "#/core/binding";
2
- import type { BindingScope } from "#/core/types";
3
-
4
- /**
5
- * The scope a binding resolves under.
6
- *
7
- * @remarks Every kind declares one — an alias declares `transient`, since it defers scoping to the
8
- * binding it points at — so this is a field read, kept as a named function because it is the
9
- * vocabulary validation and introspection speak.
10
- *
11
- * @since 0.3.16-canary.0
12
- */
13
- export function effectiveBindingScope(binding: Binding): BindingScope {
14
- return binding.scope;
15
- }
@@ -1,477 +0,0 @@
1
- import type { BindingTag, TagKeyMask } from "#/core/tag";
2
- import { NO_TAG_KEYS } from "#/core/tag";
3
- import type { Token } from "#/core/token";
4
- import type {
5
- ActivationHandler,
6
- BindingIdentifier,
7
- BindingScope,
8
- Constructor,
9
- DeactivationHandler,
10
- ResolutionContext,
11
- ResolutionFrame,
12
- BindingConstraint,
13
- } from "#/core/types";
14
- import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#/injection/descriptor";
15
-
16
- // ── BindingSlot ──────────────────────────────────────────────────────────────────────────────────────────────────────
17
-
18
- /**
19
- * The name-plus-tags coordinate a binding registers under and a request matches against.
20
- *
21
- * @since 0.3.16-canary.0
22
- */
23
- export interface BindingSlot {
24
- readonly name: string | undefined;
25
- readonly tags: ReadonlyArray<BindingTag>;
26
- /** OR of this slot's tag keys, so the subset test is one word compare. */
27
- readonly keyMask: TagKeyMask;
28
- }
29
-
30
- /**
31
- * Returns whether two slots carry the same name and tag set, in any tag order.
32
- *
33
- * @since 0.3.16-canary.0
34
- */
35
- export function bindingSlotEquals(left: BindingSlot, right: BindingSlot): boolean {
36
- if (left.name !== right.name || left.keyMask !== right.keyMask || left.tags.length !== right.tags.length) {
37
- return false;
38
- }
39
- for (const criterion of left.tags) {
40
- if (!right.tags.includes(criterion)) {
41
- return false;
42
- }
43
- }
44
- return true;
45
- }
46
-
47
- /**
48
- * Cached singleton absent — distinguishes "not resolved yet" from a cached `undefined`.
49
- *
50
- * @since 0.5.0-canary.8
51
- */
52
- export const NO_INSTANCE: unique symbol = Symbol("di:no-instance");
53
-
54
- /**
55
- * The slot every unconstrained binding shares.
56
- *
57
- * @remarks Tags are frozen where they are built (here and in the builder's re-tag), never per
58
- * binding: frames and snapshots alias the array, so a caller's write throws instead of corrupting
59
- * the registry.
60
- *
61
- * @since 0.3.16-canary.0
62
- */
63
- export const DEFAULT_BINDING_SLOT: BindingSlot = { name: undefined, tags: Object.freeze([]), keyMask: NO_TAG_KEYS };
64
-
65
- /**
66
- * Formats a slot for diagnostics — `default`, or its `name:`/`tag:` parts.
67
- *
68
- * @since 0.3.16-canary.0
69
- */
70
- export function bindingSlotToString(slot: BindingSlot): string {
71
- if (slot.name === undefined && slot.tags.length === 0) {
72
- return "default";
73
- }
74
- const parts: Array<string> = [];
75
- if (slot.name !== undefined) {
76
- parts.push(`name:${slot.name}`);
77
- }
78
- for (const criterion of slot.tags) {
79
- parts.push(`tag:${criterion.key.name}=${String(criterion.value)}`);
80
- }
81
- return parts.join(",");
82
- }
83
-
84
- // ── BindingBase ──────────────────────────────────────────────────────────────────────────────────────────────────────
85
-
86
- interface BindingBase<Value> {
87
- readonly id: BindingIdentifier;
88
- /**
89
- * True while this binding's factory is executing on the current synchronous call stack.
90
- *
91
- * @remarks Both cycle guards that can use an `O(1)` flag read this — the sync transient-dynamic
92
- * lane and the async cascade lane — because synchronous code does not interleave, so the flag *is*
93
- * exact path membership. Not optional: `createBinding` always sets it, and a field that may be
94
- * absent is a field that can cost the shared hidden class. Resolver-owned; callers never set it.
95
- */
96
- inFlight: boolean;
97
- /**
98
- * Memoized resolution frame for this binding. Its contents derive only from immutable binding
99
- * fields, so it is computed once on first resolve and reused instead of a per-resolver Map
100
- * lookup on every hop.
101
- *
102
- * @remarks Resolver-owned bookkeeping — `registry.add` normalizes it, so callers never set it.
103
- */
104
- frame: ResolutionFrame | undefined;
105
- /**
106
- * Cached singleton instance, or {@link NO_INSTANCE}.
107
- *
108
- * @remarks A binding belongs to exactly one container, so its singleton slot is per-binding —
109
- * a field read replaces a keyed lookup on the hottest resolve shape there is.
110
- */
111
- instance: unknown;
112
- readonly token: Token<Value> | Constructor<Value>;
113
- readonly slot: BindingSlot;
114
- readonly predicate?: BindingConstraint | undefined;
115
- }
116
-
117
- type BindingBaseKeys = keyof BindingBase<unknown>;
118
-
119
- /**
120
- * The lifecycle hooks every kind but `alias` may carry.
121
- *
122
- * @remarks Declared as **methods**, not function-typed properties, so their parameters compare
123
- * bivariantly and `Binding<Value>` stays assignable to `Binding`. The engine erases the value type at
124
- * every lane boundary regardless; the public `ActivationHandler` / `DeactivationHandler` keep strict
125
- * checking, which is where a user's handler is actually verified. Not `readonly`: a fluent chain
126
- * refines both in place — see {@link RefinableBindingFields}.
127
- */
128
- interface BindingLifecycleHooks<Value> {
129
- onActivation?(ctx: ResolutionContext, instance: Value): Value | Promise<Value>;
130
- onDeactivation?(instance: Value): void | Promise<void>;
131
- }
132
-
133
- // ── Binding kinds ────────────────────────────────────────────────────────────────────────────────────────────────────
134
-
135
- /**
136
- * A binding that instantiates a constructor.
137
- *
138
- * @since 0.3.16-canary.0
139
- */
140
- export interface ClassBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
141
- readonly kind: "class";
142
- readonly target: Constructor<Value>;
143
- readonly scope: BindingScope;
144
- }
145
-
146
- /**
147
- * A binding that computes its value with a synchronous factory.
148
- *
149
- * @since 0.3.16-canary.0
150
- */
151
- export interface DynamicBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
152
- readonly kind: "dynamic";
153
- readonly factory: (ctx: ResolutionContext) => Value;
154
- readonly scope: BindingScope;
155
- }
156
-
157
- /**
158
- * A binding whose factory returns a promise of the value.
159
- *
160
- * @since 0.3.16-canary.0
161
- */
162
- export interface DynamicAsyncBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
163
- readonly kind: "dynamic-async";
164
- readonly factory: (ctx: ResolutionContext) => Promise<Value>;
165
- readonly scope: BindingScope;
166
- }
167
-
168
- /**
169
- * A binding whose factory is called with its declared dependencies already resolved.
170
- *
171
- * @since 0.3.16-canary.0
172
- */
173
- export interface ResolvedBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
174
- readonly kind: "resolved";
175
- readonly factory: (...args: Array<unknown>) => Value;
176
- readonly deps: ReadonlyArray<InjectionDescriptor>;
177
- readonly scope: BindingScope;
178
- }
179
-
180
- /**
181
- * The async form of {@link ResolvedBinding} — the factory returns a promise.
182
- *
183
- * @since 0.3.16-canary.0
184
- */
185
- export interface ResolvedAsyncBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
186
- readonly kind: "resolved-async";
187
- readonly factory: (...args: Array<unknown>) => Promise<Value>;
188
- readonly deps: ReadonlyArray<InjectionDescriptor>;
189
- readonly scope: BindingScope;
190
- }
191
-
192
- /**
193
- * A binding that hands out one fixed value, so its scope is always `singleton`.
194
- *
195
- * @since 0.3.16-canary.0
196
- */
197
- export interface ConstantBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
198
- readonly kind: "constant";
199
- readonly value: Value;
200
- readonly scope: "singleton";
201
- }
202
-
203
- /**
204
- * A binding that defers to whatever binding its target token selects.
205
- *
206
- * @since 0.3.16-canary.0
207
- */
208
- export interface AliasBinding<Value> extends BindingBase<Value> {
209
- readonly kind: "alias";
210
- readonly target: Token<Value> | Constructor<Value>;
211
- /**
212
- * Always `transient` — an alias defers scoping to the binding it points at.
213
- *
214
- * @remarks Declared so `scope` is present on every kind, which is what lets the engine read it
215
- * as a plain field instead of testing for the one kind that lacks it.
216
- */
217
- readonly scope: "transient";
218
- }
219
-
220
- /**
221
- * Every binding shape the engine resolves, discriminated by `kind`.
222
- *
223
- * @since 0.3.16-canary.0
224
- */
225
- export type Binding<Value = unknown> =
226
- | ClassBinding<Value>
227
- | DynamicBinding<Value>
228
- | DynamicAsyncBinding<Value>
229
- | ResolvedBinding<Value>
230
- | ResolvedAsyncBinding<Value>
231
- | ConstantBinding<Value>
232
- | AliasBinding<Value>;
233
-
234
- /** `Omit` applied per union member, since a bare `Omit` would collapse the union into one shape. */
235
- type DistributiveOmit<Union, Keys extends PropertyKey> = Union extends unknown ? Omit<Union, Keys> : never;
236
-
237
- /**
238
- * Builder-only payload before `id`, `token`, `slot`, and `predicate` are applied.
239
- *
240
- * @remarks Derived rather than listed: a new binding kind joins this the moment it joins
241
- * {@link Binding}, so the two unions cannot diverge.
242
- *
243
- * @since 0.3.16-canary.0
244
- */
245
- export type PartialBinding<Value> = DistributiveOmit<Binding<Value>, BindingBaseKeys>;
246
-
247
- // ── ID generation ────────────────────────────────────────────────────────────────────────────────────────────────────
248
-
249
- let bindingIdCounter = 0;
250
- /**
251
- * Returns a process-unique identifier for a new binding.
252
- *
253
- * @since 0.3.16-canary.0
254
- */
255
- export function generateBindingId(): BindingIdentifier {
256
- return String(++bindingIdCounter) as BindingIdentifier;
257
- }
258
-
259
- // ── Construction ─────────────────────────────────────────────────────────────────────────────────────────────────────
260
-
261
- /** Every key any member declares — a bare `keyof` on a union gives only the shared ones. */
262
- type KeysOfUnion<Union> = Union extends unknown ? keyof Union : never;
263
-
264
- // Superset of every kind's fields, so one literal can copy any binding shape.
265
- type BindingFieldName = KeysOfUnion<Binding<unknown>>;
266
-
267
- /**
268
- * Completeness guard for {@link createBinding}'s literal.
269
- *
270
- * @remarks The literal is `satisfies` this, so a field added to any binding kind that the literal
271
- * forgets to write is a compile error rather than a binding silently missing it.
272
- */
273
- type ConstructedBindingFields = Record<BindingFieldName, unknown>;
274
-
275
- type BindingFieldSuperset = {
276
- readonly kind: Binding["kind"];
277
- readonly instance?: unknown;
278
- readonly scope: BindingScope;
279
- readonly target?: unknown;
280
- readonly factory?: unknown;
281
- readonly deps?: unknown;
282
- readonly value?: unknown;
283
- readonly onActivation?: unknown;
284
- readonly onDeactivation?: unknown;
285
- };
286
-
287
- /**
288
- * The single construction site for bindings — one literal, one V8 hidden class.
289
- *
290
- * @remarks Field order is fixed and this is the only construction site, so every binding shares one
291
- * hidden class. Reordering the fields, or adding a second site, gives that up.
292
- *
293
- * @param source - the kind-specific payload, or an existing binding to re-slot
294
- * @param token - the key requests resolve the binding by
295
- * @param slot - the name + tags a request must match to select this binding
296
- * @param predicate - a custom constraint, or `undefined` for none
297
- * @param id - reuse a caller's id to keep a fluent chain's `id()` stable across refinements
298
- *
299
- * @since 0.5.0-canary.8
300
- */
301
- export function createBinding<Value>(
302
- source: PartialBinding<Value> | Binding<Value>,
303
- token: Token<Value> | Constructor<Value>,
304
- slot: BindingSlot,
305
- predicate: BindingConstraint | undefined,
306
- id: BindingIdentifier = generateBindingId(),
307
- ): Binding<Value> {
308
- const fields = source as BindingFieldSuperset;
309
- return {
310
- kind: fields.kind,
311
- id,
312
- inFlight: false,
313
- frame: undefined,
314
- // An `in` probe, not `??`: a re-slotted singleton may legitimately hold a cached `undefined`.
315
- instance: "instance" in fields ? fields.instance : NO_INSTANCE,
316
- token,
317
- slot,
318
- predicate,
319
- scope: fields.scope,
320
- target: fields.target,
321
- factory: fields.factory,
322
- deps: fields.deps,
323
- value: fields.value,
324
- onActivation: fields.onActivation,
325
- onDeactivation: fields.onDeactivation,
326
- } satisfies ConstructedBindingFields as Binding<Value>;
327
- }
328
-
329
- /**
330
- * Writable view of the only fields a fluent chain may refine after registration.
331
- *
332
- * @remarks No registry index is keyed on these, so a builder that owns the registered object
333
- * can write them directly instead of re-registering. `token`, `slot`, `predicate` and `id`
334
- * are excluded on purpose — changing those means re-indexing.
335
- *
336
- * @since 0.5.0-canary.8
337
- */
338
- export interface RefinableBindingFields<Value> {
339
- onActivation: ActivationHandler<Value> | undefined;
340
- onDeactivation: DeactivationHandler<Value> | undefined;
341
- scope: BindingScope;
342
- }
343
-
344
- /**
345
- * Narrows a registered binding to the fields a fluent chain may still refine.
346
- *
347
- * @since 0.5.0-canary.8
348
- */
349
- export function refinableFields<Value>(binding: Binding<Value>): RefinableBindingFields<Value> {
350
- return binding as RefinableBindingFields<Value>;
351
- }
352
-
353
- /**
354
- * Writable view of the memoized frame, which is a cache rather than part of a binding's identity.
355
- *
356
- * @remarks Named for the same reason as {@link RefinableBindingFields}: a write view stated once
357
- * cannot drift from `Binding`, where an inline cast at each site can.
358
- */
359
- interface MemoizedFrameField {
360
- frame: ResolutionFrame | undefined;
361
- }
362
-
363
- /**
364
- * Drops the memoized resolution frame, for a refinement that changes what the frame reports.
365
- *
366
- * @remarks `scope` is the only field a chain writes in place that the frame derives from — a
367
- * re-slot builds a fresh binding, whose frame starts empty anyway.
368
- *
369
- * @since 0.5.0-canary.9
370
- */
371
- export function clearBindingFrame<Value>(binding: Binding<Value>): void {
372
- (binding as MemoizedFrameField).frame = undefined;
373
- }
374
-
375
- // ── Builder interfaces ───────────────────────────────────────────────────────────────────────────────────────────────
376
-
377
- /**
378
- * Common slot-constraint + id methods shared by all concrete binding builders.
379
- *
380
- * @since 0.3.16-canary.0
381
- */
382
- export interface SlotConstrainedBuilder {
383
- when(predicate: BindingConstraint): this;
384
- whenNamed(name: string): this;
385
- whenTagged(criterion: BindingTag): this;
386
- whenDefault(): this;
387
- id(): BindingIdentifier;
388
- }
389
-
390
- /**
391
- * The `to*` step of the fluent chain, choosing what a token resolves to.
392
- *
393
- * @since 0.3.16-canary.0
394
- */
395
- export interface BindToBuilder<Value> {
396
- to(type: Constructor<Value>): BindingBuilder<Value>;
397
- toSelf(): BindingBuilder<Value>;
398
- toConstantValue(value: Value): ConstantBindingBuilder<Value>;
399
- toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
400
- toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
401
- toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(
402
- factory: (...args: { [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]> }) => Value,
403
- deps: Deps,
404
- ): BindingBuilder<Value>;
405
- toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(
406
- factory: (...args: { [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]> }) => Promise<Value>,
407
- deps: Deps,
408
- ): BindingBuilder<Value>;
409
- toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
410
- }
411
-
412
- /**
413
- * The scope-selection step of the fluent chain.
414
- *
415
- * @since 0.3.16-canary.0
416
- */
417
- export interface BindingBuilder<Value> extends SlotConstrainedBuilder {
418
- singleton(): SingletonBindingBuilder<Value>;
419
- transient(): TransientBindingBuilder<Value>;
420
- scoped(): ScopedBindingBuilder<Value>;
421
- }
422
-
423
- /**
424
- * The fluent chain for a constant — lifecycle hooks only, since the scope is fixed.
425
- *
426
- * @since 0.3.16-canary.0
427
- */
428
- export interface ConstantBindingBuilder<Value> extends SlotConstrainedBuilder {
429
- onActivation(fn: ActivationHandler<Value>): SingletonLifecycleBuilder<Value>;
430
- onDeactivation(fn: DeactivationHandler<Value>): SingletonLifecycleBuilder<Value>;
431
- }
432
-
433
- /**
434
- * The fluent chain for an alias — slot constraints only, since scoping belongs to the target.
435
- *
436
- * @since 0.3.16-canary.0
437
- */
438
- export interface AliasBindingBuilder extends SlotConstrainedBuilder {}
439
-
440
- /**
441
- * The fluent chain after `singleton()`, where both lifecycle hooks stay available.
442
- *
443
- * @since 0.3.16-canary.0
444
- */
445
- export interface SingletonBindingBuilder<Value> {
446
- onActivation(fn: ActivationHandler<Value>): this;
447
- onDeactivation(fn: DeactivationHandler<Value>): this;
448
- id(): BindingIdentifier;
449
- }
450
-
451
- /**
452
- * The fluent chain after `transient()`, where activation is the one lifecycle hook offered.
453
- *
454
- * @since 0.3.16-canary.0
455
- */
456
- export interface TransientBindingBuilder<Value> {
457
- onActivation(fn: ActivationHandler<Value>): this;
458
- id(): BindingIdentifier;
459
- }
460
-
461
- /**
462
- * The fluent chain after `scoped()`, sharing the `transient()` surface.
463
- *
464
- * @since 0.3.16-canary.0
465
- */
466
- export interface ScopedBindingBuilder<Value> extends TransientBindingBuilder<Value> {}
467
-
468
- /**
469
- * The fluent chain a constant enters once a lifecycle hook is added.
470
- *
471
- * @since 0.3.16-canary.0
472
- */
473
- export interface SingletonLifecycleBuilder<Value> {
474
- onActivation(fn: ActivationHandler<Value>): this;
475
- onDeactivation(fn: DeactivationHandler<Value>): this;
476
- id(): BindingIdentifier;
477
- }
@@ -1,94 +0,0 @@
1
- /** What a constraint needs to exist before it can ever match, so `validate()` can check for it. */
2
- import type { BindingConstraint } from "#/core/types";
3
-
4
- /**
5
- * Key the requirement is attached under.
6
- *
7
- * @remarks A symbol on the predicate rather than a field in the binding: resolution never reads it,
8
- * so carrying it costs a resolve nothing.
9
- *
10
- * @since 0.6.0
11
- */
12
- export const CONSTRAINT_REQUIREMENT: unique symbol = Symbol("di:constraint-requirement");
13
-
14
- /**
15
- * The slot name a constraint waits for on an ancestor.
16
- *
17
- * @remarks Only names are described. A tag criterion is interned, so a typo cannot produce one that
18
- * looks valid, while a name is a bare string that nothing checks.
19
- *
20
- * @since 0.6.0
21
- */
22
- export interface ConstraintRequirement {
23
- readonly requires: "ancestorSlotName";
24
- readonly name: string;
25
- /** The helper that built the predicate, so a report can name what the caller wrote. */
26
- readonly helperName: string;
27
- }
28
-
29
- /**
30
- * Records what a predicate waits for. Called once, where the predicate is built.
31
- *
32
- * @since 0.6.0
33
- */
34
- export function requiringAncestorSlotName(
35
- predicate: BindingConstraint,
36
- name: string,
37
- helperName: string,
38
- ): BindingConstraint {
39
- const requirement: ConstraintRequirement = { requires: "ancestorSlotName", name, helperName };
40
- Object.defineProperty(predicate, CONSTRAINT_REQUIREMENT, { value: requirement, enumerable: false });
41
- return predicate;
42
- }
43
-
44
- /**
45
- * The requirement a predicate carries, if it was built by a helper that records one.
46
- *
47
- * @remarks A composed predicate may carry several; this answers the first. `validate()` reads
48
- * {@link constraintRequirementsOf} so no recorded requirement is skipped.
49
- *
50
- * @since 0.6.0
51
- */
52
- export function constraintRequirementOf(predicate: BindingConstraint): ConstraintRequirement | undefined {
53
- return constraintRequirementsOf(predicate)[0];
54
- }
55
-
56
- const NO_REQUIREMENTS: ReadonlyArray<ConstraintRequirement> = [];
57
-
58
- /**
59
- * Every requirement a predicate carries — one from a helper, several from a composed chain.
60
- *
61
- * @since 0.6.1
62
- */
63
- export function constraintRequirementsOf(predicate: BindingConstraint): ReadonlyArray<ConstraintRequirement> {
64
- const payload = (
65
- predicate as { [CONSTRAINT_REQUIREMENT]?: ConstraintRequirement | ReadonlyArray<ConstraintRequirement> }
66
- )[CONSTRAINT_REQUIREMENT];
67
- if (payload === undefined) {
68
- return NO_REQUIREMENTS;
69
- }
70
- return Array.isArray(payload)
71
- ? (payload as ReadonlyArray<ConstraintRequirement>)
72
- : [payload as ConstraintRequirement];
73
- }
74
-
75
- /**
76
- * Carries both sides' requirements onto a composed predicate, so chaining does not lose them.
77
- *
78
- * @since 0.6.1
79
- */
80
- export function mergingConstraintRequirements(
81
- composite: BindingConstraint,
82
- left: BindingConstraint,
83
- right: BindingConstraint,
84
- ): BindingConstraint {
85
- const merged = [...constraintRequirementsOf(left), ...constraintRequirementsOf(right)];
86
- if (merged.length === 0) {
87
- return composite;
88
- }
89
- Object.defineProperty(composite, CONSTRAINT_REQUIREMENT, {
90
- value: merged.length === 1 ? merged[0] : merged,
91
- enumerable: false,
92
- });
93
- return composite;
94
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * A class token: newable, producing `Value`.
3
- *
4
- * @remarks Rest parameters are `never[]` so classes with typed constructors stay assignable under
5
- * `strictFunctionTypes`. Construction uses the real shape; this alias is the token surface only.
6
- *
7
- * @since 0.3.16-canary.0
8
- */
9
- export type Constructor<out Value = unknown> = new (...args: Array<never>) => Value;
10
-
11
- /**
12
- * Class constructor as invoked by the resolver after metadata-driven
13
- * resolution of `unknown[]` dependencies — separate from {@link Constructor},
14
- * which is the public assignable class token.
15
- *
16
- * @since 0.3.16-canary.0
17
- */
18
- export type ConstructorInvocation = new (...args: Array<unknown>) => unknown;
@@ -1,53 +0,0 @@
1
- /** The two `Map` upsert forms the container indexes on, owned here so the package's Node floor stays put. */
2
-
3
- /**
4
- * Returns the value stored under a key, inserting the given fallback first when the key is absent.
5
- *
6
- * @remarks The caller evaluates the fallback either way, so this form fits an insertion that usually
7
- * misses. Absence is one `get`, which is why the value type excludes `undefined`.
8
- *
9
- * @param map - mutated in place on a miss
10
- * @param key - looked up by the map's own key equality
11
- * @param value - stored and returned when the key is absent
12
- *
13
- * @since 0.7.0
14
- */
15
- export function getOrInsert<Key, Value extends {} | null>(
16
- map: Map<Key, Value>,
17
- key: NoInfer<Key>,
18
- value: NoInfer<Value>,
19
- ): Value {
20
- const existing = map.get(key);
21
- if (existing !== undefined) {
22
- return existing;
23
- }
24
- map.set(key, value);
25
- return value;
26
- }
27
-
28
- /**
29
- * Returns the value stored under a key, computing and inserting a fallback only when the key is absent.
30
- *
31
- * @remarks Fits a lookup that usually hits, where an eager fallback would allocate per call; hoist
32
- * the factory to module scope and no closure is allocated either. Absence is decided as in
33
- * {@link getOrInsert}.
34
- *
35
- * @param map - mutated in place on a miss
36
- * @param key - looked up by the map's own key equality, and handed to the factory
37
- * @param create - called only on a miss
38
- *
39
- * @since 0.7.0
40
- */
41
- export function getOrInsertComputed<Key, Value extends {} | null>(
42
- map: Map<Key, Value>,
43
- key: NoInfer<Key>,
44
- create: (key: NoInfer<Key>) => NoInfer<Value>,
45
- ): Value {
46
- const existing = map.get(key);
47
- if (existing !== undefined) {
48
- return existing;
49
- }
50
- const created = create(key);
51
- map.set(key, created);
52
- return created;
53
- }