@codefast/di 0.8.0 → 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 +13 -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 +1 -2
  7. package/dist/container/container.d.ts +1 -2
  8. package/dist/container/container.js +1 -2
  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 +1 -2
  12. package/dist/core/binding.js +1 -2
  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 -2
  22. package/dist/core/registry.js +1 -2
  23. package/dist/core/tag.d.ts +1 -2
  24. package/dist/core/tag.js +1 -2
  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 +1 -2
  39. package/dist/index.d.ts +1 -2
  40. package/dist/index.js +1 -2
  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 +1 -2
  44. package/dist/injection/resolve-options.js +1 -2
  45. package/dist/introspection/dependency-graph.d.ts +1 -2
  46. package/dist/introspection/dependency-graph.js +1 -2
  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 +1 -2
  74. package/dist/resolution/cache/binding-lookup-cache.js +1 -2
  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 +1 -2
  82. package/dist/resolution/plan/instantiation-plan.js +1 -2
  83. package/dist/resolution/resolver.d.ts +1 -2
  84. package/dist/resolution/resolver.js +1 -2
  85. package/dist/resolution/select/binding-select.d.ts +1 -2
  86. package/dist/resolution/select/binding-select.js +1 -2
  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 -337
  178. package/src/container/container.ts +0 -972
  179. package/src/core/binding-scope.ts +0 -15
  180. package/src/core/binding.ts +0 -505
  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 -319
  186. package/src/core/tag.ts +0 -212
  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 -499
  194. package/src/index.ts +0 -139
  195. package/src/injection/descriptor.ts +0 -218
  196. package/src/injection/resolve-options.ts +0 -201
  197. package/src/introspection/dependency-graph.ts +0 -296
  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 -169
  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 -592
  216. package/src/resolution/resolver.ts +0 -1654
  217. package/src/resolution/select/binding-select.ts +0 -207
  218. package/src/resolution/select/constraints.ts +0 -146
@@ -1,296 +0,0 @@
1
- import type { Binding } from "#/core/binding";
2
- import { effectiveBindingScope } from "#/core/binding-scope";
3
- import type { BindingRegistry } from "#/core/registry";
4
- import { slotName } from "#/core/tag";
5
- import type { Token } from "#/core/token";
6
- import { tokenName } from "#/core/token";
7
- import type { BindingKind, BindingScope, Constructor } from "#/core/types";
8
- import type { DependencySlot } from "#/injection/resolve-options";
9
- import { bindingSlotToResolveOptions } from "#/injection/resolve-options";
10
- import type { MetadataReader } from "#/metadata/metadata-types";
11
- import { matchesSlot } from "#/resolution/select/binding-select";
12
-
13
- // ── Types ────────────────────────────────────────────────────────────────────────────────────────────────────────────
14
-
15
- /**
16
- * @remarks `kind`/`scope` are `"unbound"` for the placeholder node an optional, currently
17
- * unsatisfied dependency points at.
18
- *
19
- * @since 0.3.16-canary.0
20
- */
21
- export interface GraphNode {
22
- readonly id: string;
23
- readonly tokenName: string;
24
- /** Identifies the token itself, so bindings that share a display name stay distinguishable. */
25
- readonly tokenKey: string;
26
- readonly kind: BindingKind | "unbound";
27
- readonly scope: BindingScope | "unbound";
28
- readonly fromParent: boolean;
29
- }
30
-
31
- /**
32
- * @remarks `label` is presentation, assembled for the adapters; read `optional` and `slotName`
33
- * rather than parsing it.
34
- *
35
- * @since 0.3.16-canary.0
36
- */
37
- export interface GraphEdge {
38
- readonly from: string;
39
- readonly to: string;
40
- readonly label?: string;
41
- readonly optional: boolean;
42
- /** The named slot this edge resolves to, when the binding declares one. */
43
- readonly slotName?: string;
44
- }
45
-
46
- /**
47
- * A container's dependency graph as plain JSON — nodes, edges, and whether the parent is included.
48
- *
49
- * @since 0.3.16-canary.0
50
- */
51
- export interface ContainerGraphJson {
52
- readonly nodes: ReadonlyArray<GraphNode>;
53
- readonly edges: ReadonlyArray<GraphEdge>;
54
- readonly includesParent: boolean;
55
- }
56
-
57
- /**
58
- * Options controlling what a dependency graph includes.
59
- *
60
- * @since 0.3.16-canary.0
61
- */
62
- export interface GraphOptions {
63
- readonly includeParent?: boolean | undefined;
64
- }
65
-
66
- // ── Builder ──────────────────────────────────────────────────────────────────────────────────────────────────────────
67
-
68
- // Tokens are compared by object identity, and a name is free to repeat, so the graph mints its
69
- // own per-process key. Weakly held: a discarded token takes its key with it.
70
- const tokenKeys = new WeakMap<object, string>();
71
- let tokenKeySequence = 0;
72
-
73
- function tokenKeyOf(dependency: Token<unknown> | Constructor): string {
74
- const existing = tokenKeys.get(dependency);
75
-
76
- if (existing !== undefined) {
77
- return existing;
78
- }
79
-
80
- tokenKeySequence += 1;
81
- const key = `${tokenName(dependency)}@${String(tokenKeySequence)}`;
82
-
83
- tokenKeys.set(dependency, key);
84
-
85
- return key;
86
- }
87
-
88
- // Mirrors filterBindings' slot semantics; predicates need a live resolution context, so the graph
89
- // keeps every predicate-carrying candidate.
90
- function matchingTargets(candidates: ReadonlyArray<Binding>, ref: DependencySlot): ReadonlyArray<Binding> {
91
- const criterion = bindingSlotToResolveOptions(ref);
92
-
93
- if (ref.multi && criterion === undefined) {
94
- return candidates;
95
- }
96
-
97
- return candidates.filter((candidate) => matchesSlot(candidate.slot, criterion));
98
- }
99
-
100
- /** The name a dependency asks for, whichever spelling carries it. */
101
- function refSlotName(ref: DependencySlot): string | undefined {
102
- if (ref.name !== undefined) {
103
- return ref.name;
104
- }
105
- const reserved = ref.tags?.find((criterion) => criterion.key === slotName);
106
- return reserved === undefined ? undefined : String(reserved.value);
107
- }
108
-
109
- function edgeLabel(ref: DependencySlot, index: number): string {
110
- const name = refSlotName(ref);
111
- const firstPlainTag = ref.tags?.find((criterion) => criterion.key !== slotName);
112
- const criterion =
113
- name !== undefined
114
- ? `name:${name}`
115
- : firstPlainTag !== undefined
116
- ? `tag:${firstPlainTag.key.name}=${String(firstPlainTag.value)}`
117
- : `[${index}]`;
118
-
119
- return ref.optional ? `${criterion} optional` : criterion;
120
- }
121
-
122
- /** The collections a graph walk fills, so each step of the walk can be a function of its own. */
123
- interface GraphAccumulator {
124
- readonly nodes: Array<GraphNode>;
125
- readonly edges: Array<GraphEdge>;
126
- // One placeholder node per optional-but-unbound token keeps the declared edge visible.
127
- readonly unboundNodeIds: Map<string, string>;
128
- }
129
-
130
- /** Own bindings shadow the fallback, mirroring resolution's upward walk. */
131
- type BindingLookup = (token: Token<unknown> | Constructor) => ReadonlyArray<Binding>;
132
-
133
- function bindingLookup(sourceRegistry: BindingRegistry, fallbackRegistry: BindingRegistry | undefined): BindingLookup {
134
- return (token) => {
135
- const own = sourceRegistry.getAll(token);
136
-
137
- if (own.length > 0 || fallbackRegistry === undefined) {
138
- return own;
139
- }
140
-
141
- return fallbackRegistry.getAll(token);
142
- };
143
- }
144
-
145
- /** The placeholder node an optional-but-unbound dependency points at, minted once per token. */
146
- function unboundNodeIdFor(accumulator: GraphAccumulator, dependency: Token<unknown> | Constructor): string {
147
- const key = tokenKeyOf(dependency);
148
- const existing = accumulator.unboundNodeIds.get(key);
149
-
150
- if (existing !== undefined) {
151
- return existing;
152
- }
153
-
154
- const id = `unbound:${key}`;
155
-
156
- accumulator.unboundNodeIds.set(key, id);
157
- accumulator.nodes.push({
158
- id,
159
- tokenName: tokenName(dependency),
160
- tokenKey: key,
161
- kind: "unbound",
162
- scope: "unbound",
163
- fromParent: false,
164
- });
165
-
166
- return id;
167
- }
168
-
169
- function addDependencyEdges(
170
- accumulator: GraphAccumulator,
171
- from: string,
172
- ref: DependencySlot,
173
- index: number,
174
- lookup: BindingLookup,
175
- ): void {
176
- const targets = matchingTargets(lookup(ref.token), ref);
177
- const label = edgeLabel(ref, index);
178
-
179
- if (targets.length === 0) {
180
- // A required-but-unbound dependency is validate()'s story, not the graph's.
181
- if (!ref.optional) {
182
- return;
183
- }
184
-
185
- const unboundSlotName = refSlotName(ref);
186
- accumulator.edges.push({
187
- from,
188
- to: unboundNodeIdFor(accumulator, ref.token),
189
- label,
190
- optional: true,
191
- ...(unboundSlotName !== undefined ? { slotName: unboundSlotName } : {}),
192
- });
193
-
194
- return;
195
- }
196
-
197
- const requestedName = refSlotName(ref);
198
- for (const target of targets) {
199
- // A multi dep with no criterion of its own fans out — each edge names the slot it hits.
200
- const edgeSlotName = target.slot.name ?? requestedName;
201
- const perTargetLabel =
202
- ref.multi && requestedName === undefined && edgeSlotName !== undefined
203
- ? edgeLabel({ ...ref, name: edgeSlotName }, index)
204
- : label;
205
-
206
- accumulator.edges.push({
207
- from,
208
- to: target.id,
209
- label: perTargetLabel,
210
- optional: ref.optional,
211
- ...(edgeSlotName !== undefined ? { slotName: edgeSlotName } : {}),
212
- });
213
- }
214
- }
215
-
216
- /** What one binding declares up front — a class's params, a factory's descriptors, an alias's target. */
217
- function addBindingEdges(
218
- accumulator: GraphAccumulator,
219
- binding: Binding,
220
- metadataReader: MetadataReader,
221
- lookup: BindingLookup,
222
- ): void {
223
- if (binding.kind === "class") {
224
- const meta = metadataReader.getConstructorMetadata(binding.target);
225
-
226
- if (meta !== undefined) {
227
- for (const [index, param] of meta.params.entries()) {
228
- addDependencyEdges(accumulator, binding.id, param, index, lookup);
229
- }
230
- }
231
-
232
- return;
233
- }
234
-
235
- if (binding.kind === "resolved" || binding.kind === "resolved-async") {
236
- for (const [index, dependency] of binding.deps.entries()) {
237
- addDependencyEdges(accumulator, binding.id, dependency, index, lookup);
238
- }
239
-
240
- return;
241
- }
242
-
243
- if (binding.kind === "alias") {
244
- const aliasRef: DependencySlot = { token: binding.target, optional: false, multi: false };
245
-
246
- for (const target of matchingTargets(lookup(binding.target), aliasRef)) {
247
- accumulator.edges.push({ from: binding.id, to: target.id, label: "alias", optional: false });
248
- }
249
- }
250
- }
251
-
252
- function addRegistryBindings(
253
- accumulator: GraphAccumulator,
254
- sourceRegistry: BindingRegistry,
255
- metadataReader: MetadataReader,
256
- fromParent: boolean,
257
- fallbackRegistry?: BindingRegistry,
258
- ): void {
259
- const lookup = bindingLookup(sourceRegistry, fallbackRegistry);
260
-
261
- for (const binding of sourceRegistry.allBindings()) {
262
- accumulator.nodes.push({
263
- id: binding.id,
264
- tokenName: tokenName(binding.token),
265
- tokenKey: tokenKeyOf(binding.token),
266
- kind: binding.kind,
267
- scope: effectiveBindingScope(binding),
268
- fromParent,
269
- });
270
-
271
- addBindingEdges(accumulator, binding, metadataReader, lookup);
272
- }
273
- }
274
-
275
- /**
276
- * Builds the JSON dependency graph of a registry's bindings, optionally including the parent's.
277
- *
278
- * @since 0.3.16-canary.0
279
- */
280
- export function buildDependencyGraph(
281
- registry: BindingRegistry,
282
- metadataReader: MetadataReader,
283
- options: GraphOptions | undefined,
284
- parentRegistry?: BindingRegistry,
285
- ): ContainerGraphJson {
286
- const accumulator: GraphAccumulator = { nodes: [], edges: [], unboundNodeIds: new Map() };
287
- const includesParent = options?.includeParent === true;
288
-
289
- addRegistryBindings(accumulator, registry, metadataReader, false, includesParent ? parentRegistry : undefined);
290
-
291
- if (includesParent && parentRegistry !== undefined) {
292
- addRegistryBindings(accumulator, parentRegistry, metadataReader, true);
293
- }
294
-
295
- return { nodes: accumulator.nodes, edges: accumulator.edges, includesParent };
296
- }
@@ -1,78 +0,0 @@
1
- import type { ContainerGraphJson, GraphNode } from "#/introspection/dependency-graph";
2
-
3
- /**
4
- * A dependency-graph node in Cytoscape's element format.
5
- *
6
- * @since 0.3.16-canary.0
7
- */
8
- export interface CytoscapeNode {
9
- readonly data: {
10
- readonly id: string;
11
- readonly label: string;
12
- readonly tokenKey: string;
13
- readonly kind: GraphNode["kind"];
14
- readonly scope: GraphNode["scope"];
15
- readonly fromParent: boolean;
16
- };
17
- }
18
-
19
- /**
20
- * A dependency-graph edge in Cytoscape's element format.
21
- *
22
- * @since 0.3.16-canary.0
23
- */
24
- export interface CytoscapeEdge {
25
- readonly data: {
26
- readonly id: string;
27
- readonly source: string;
28
- readonly target: string;
29
- readonly label?: string;
30
- readonly optional: boolean;
31
- readonly slotName?: string;
32
- };
33
- }
34
-
35
- /**
36
- * The flat node-and-edge list Cytoscape takes as its `elements` input.
37
- *
38
- * @since 0.3.16-canary.0
39
- */
40
- export type CytoscapeElements = ReadonlyArray<CytoscapeNode | CytoscapeEdge>;
41
-
42
- /**
43
- * Converts a container's dependency graph into Cytoscape's element list.
44
- *
45
- * @since 0.3.16-canary.0
46
- */
47
- export function toCytoscapeGraph(graph: ContainerGraphJson): CytoscapeElements {
48
- const elements: Array<CytoscapeNode | CytoscapeEdge> = [];
49
-
50
- for (const node of graph.nodes) {
51
- elements.push({
52
- data: {
53
- id: node.id,
54
- label: node.tokenName,
55
- tokenKey: node.tokenKey,
56
- kind: node.kind,
57
- scope: node.scope,
58
- fromParent: node.fromParent,
59
- },
60
- });
61
- }
62
-
63
- for (let idx = 0; idx < graph.edges.length; idx += 1) {
64
- const edge = graph.edges[idx]!;
65
- elements.push({
66
- data: {
67
- id: `edge-${idx}`,
68
- source: edge.from,
69
- target: edge.to,
70
- ...(edge.label !== undefined ? { label: edge.label } : {}),
71
- optional: edge.optional,
72
- ...(edge.slotName !== undefined ? { slotName: edge.slotName } : {}),
73
- },
74
- });
75
- }
76
-
77
- return elements;
78
- }
@@ -1,32 +0,0 @@
1
- import type { ContainerGraphJson } from "#/introspection/dependency-graph";
2
-
3
- // Token names are caller-supplied and land inside DOT quoted strings — ids included, since an
4
- // unbound placeholder id embeds the name it was minted from.
5
- function escapeDotString(value: string): string {
6
- return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
7
- }
8
-
9
- /**
10
- * Renders a container's dependency graph as Graphviz DOT source.
11
- *
12
- * @since 0.3.16-canary.0
13
- */
14
- export function toDotGraph(graph: ContainerGraphJson): string {
15
- const lines: Array<string> = ["digraph DI {", " rankdir=TB;"];
16
-
17
- for (const node of graph.nodes) {
18
- const label = `${escapeDotString(node.tokenName)}\\n[${node.kind}/${node.scope}]`;
19
- // Dashed for anything that is not a live binding of this container: the parent chain
20
- // and unbound-optional placeholders.
21
- const style = node.fromParent || node.kind === "unbound" ? ' style="dashed"' : "";
22
- lines.push(` "${escapeDotString(node.id)}" [label="${label}"${style}];`);
23
- }
24
-
25
- for (const edge of graph.edges) {
26
- const label = edge.label === undefined ? "" : ` [label="${escapeDotString(edge.label)}"]`;
27
- lines.push(` "${escapeDotString(edge.from)}" -> "${escapeDotString(edge.to)}"${label};`);
28
- }
29
-
30
- lines.push("}");
31
- return lines.join("\n");
32
- }
@@ -1,61 +0,0 @@
1
- import type { ContainerGraphJson } from "#/introspection/dependency-graph";
2
-
3
- // Token names are caller-supplied and Mermaid labels are HTML-bearing quoted strings, so quotes
4
- // and markup are neutralized with Mermaid's decimal entity codes before they reach a renderer.
5
- function escapeMermaidLabel(value: string): string {
6
- return value.replaceAll("&", "#38;").replaceAll('"', "#34;").replaceAll("<", "#60;").replaceAll(">", "#62;");
7
- }
8
-
9
- /**
10
- * Mermaid `flowchart TD` source for a container graph — renders anywhere Mermaid does
11
- * (GitHub markdown, docs tooling, mermaid.live) with no extra library.
12
- *
13
- * @since 0.6.0
14
- */
15
- export function toMermaidGraph(graph: ContainerGraphJson): string {
16
- // Mermaid identifiers must stay word-safe; labels carry the real token names.
17
- const idByNode = new Map<string, string>();
18
- const lines: Array<string> = ["flowchart TD"];
19
- const parentIds: Array<string> = [];
20
- const unboundIds: Array<string> = [];
21
-
22
- graph.nodes.forEach((node, index) => {
23
- const id = `n${String(index)}`;
24
-
25
- idByNode.set(node.id, id);
26
- lines.push(` ${id}["${escapeMermaidLabel(node.tokenName)}<br/>${node.kind} · ${node.scope}"]`);
27
-
28
- if (node.fromParent) {
29
- parentIds.push(id);
30
- }
31
-
32
- if (node.kind === "unbound") {
33
- unboundIds.push(id);
34
- }
35
- });
36
-
37
- for (const edge of graph.edges) {
38
- const from = idByNode.get(edge.from);
39
- const to = idByNode.get(edge.to);
40
-
41
- if (from === undefined || to === undefined) {
42
- continue;
43
- }
44
-
45
- lines.push(
46
- edge.label === undefined ? ` ${from} --> ${to}` : ` ${from} -->|"${escapeMermaidLabel(edge.label)}"| ${to}`,
47
- );
48
- }
49
-
50
- if (parentIds.length > 0) {
51
- lines.push(" classDef fromParent stroke-dasharray: 4 4;");
52
- lines.push(` class ${parentIds.join(",")} fromParent;`);
53
- }
54
-
55
- if (unboundIds.length > 0) {
56
- lines.push(" classDef unbound stroke-dasharray: 4 4,opacity:0.6;");
57
- lines.push(` class ${unboundIds.join(",")} unbound;`);
58
- }
59
-
60
- return lines.join("\n");
61
- }
@@ -1,84 +0,0 @@
1
- import type { ContainerGraphJson, GraphNode } from "#/introspection/dependency-graph";
2
-
3
- /**
4
- * A dependency-graph node in React Flow's node format.
5
- *
6
- * @since 0.3.16-canary.0
7
- */
8
- export interface ReactFlowNode {
9
- readonly id: string;
10
- readonly data: {
11
- readonly label: string;
12
- readonly tokenKey: string;
13
- readonly kind: GraphNode["kind"];
14
- readonly scope: GraphNode["scope"];
15
- readonly fromParent: boolean;
16
- };
17
- readonly position: { readonly x: number; readonly y: number };
18
- }
19
-
20
- /**
21
- * A dependency-graph edge in React Flow's edge format.
22
- *
23
- * @since 0.3.16-canary.0
24
- */
25
- export interface ReactFlowEdge {
26
- readonly id: string;
27
- readonly source: string;
28
- readonly target: string;
29
- readonly label?: string;
30
- readonly optional: boolean;
31
- readonly slotName?: string;
32
- }
33
-
34
- /**
35
- * The nodes-and-edges pair a React Flow viewer renders.
36
- *
37
- * @since 0.3.16-canary.0
38
- */
39
- export interface ReactFlowGraph {
40
- readonly nodes: ReadonlyArray<ReactFlowNode>;
41
- readonly edges: ReadonlyArray<ReactFlowEdge>;
42
- }
43
-
44
- /**
45
- * Initial grid layout: React Flow expects concrete positions; viewers re-layout anyway.
46
- *
47
- * @since 0.3.16-canary.0
48
- */
49
- const GRID_COLUMN_COUNT = 5;
50
- const GRID_CELL_WIDTH_PX = 200;
51
- const GRID_CELL_HEIGHT_PX = 100;
52
-
53
- /**
54
- * Converts a container's dependency graph into React Flow nodes and edges on an initial grid.
55
- *
56
- * @since 0.5.0-canary.7
57
- */
58
- export function toReactFlowGraph(graph: ContainerGraphJson): ReactFlowGraph {
59
- const nodes: Array<ReactFlowNode> = graph.nodes.map((node, idx) => ({
60
- id: node.id,
61
- data: {
62
- label: node.tokenName,
63
- tokenKey: node.tokenKey,
64
- kind: node.kind,
65
- scope: node.scope,
66
- fromParent: node.fromParent,
67
- },
68
- position: {
69
- x: (idx % GRID_COLUMN_COUNT) * GRID_CELL_WIDTH_PX,
70
- y: Math.floor(idx / GRID_COLUMN_COUNT) * GRID_CELL_HEIGHT_PX,
71
- },
72
- }));
73
-
74
- const edges: Array<ReactFlowEdge> = graph.edges.map((edge, idx) => ({
75
- id: `edge-${idx}`,
76
- source: edge.from,
77
- target: edge.to,
78
- ...(edge.label !== undefined ? { label: edge.label } : {}),
79
- optional: edge.optional,
80
- ...(edge.slotName !== undefined ? { slotName: edge.slotName } : {}),
81
- }));
82
-
83
- return { nodes, edges };
84
- }
@@ -1,134 +0,0 @@
1
- import type { Binding } from "#/core/binding";
2
- import { effectiveBindingScope } from "#/core/binding-scope";
3
- import type { BindingRegistry } from "#/core/registry";
4
- import type { Token } from "#/core/token";
5
- import { tokenName } from "#/core/token";
6
- import type {
7
- BindingIdentifier,
8
- BindingKind,
9
- BindingScope,
10
- BindingTag,
11
- ConstraintContext,
12
- Constructor,
13
- ResolveOptions,
14
- } from "#/core/types";
15
- import type { ScopeManager } from "#/lifecycle/scope-manager";
16
- import { selectAllBindings } from "#/resolution/select/binding-select";
17
-
18
- // ── Public types ─────────────────────────────────────────────────────────────────────────────────────────────────────
19
-
20
- /**
21
- * A read-only view of one binding: token, kind, scope, slot, and id.
22
- *
23
- * @since 0.3.16-canary.0
24
- */
25
- export interface BindingSnapshot {
26
- readonly tokenName: string;
27
- readonly kind: BindingKind;
28
- readonly scope: BindingScope;
29
- readonly slot: {
30
- readonly name?: string;
31
- readonly tags: ReadonlyArray<BindingTag>;
32
- };
33
- readonly id: BindingIdentifier;
34
- }
35
-
36
- /**
37
- * A read-only view of one container's own bindings and state.
38
- *
39
- * @since 0.3.16-canary.0
40
- */
41
- export interface ContainerSnapshot {
42
- readonly ownBindings: ReadonlyArray<BindingSnapshot>;
43
- readonly cachedSingletonCount: number;
44
- readonly hasParent: boolean;
45
- readonly isDisposed: boolean;
46
- }
47
-
48
- // ── Inspector ────────────────────────────────────────────────────────────────────────────────────────────────────────
49
-
50
- /**
51
- * The read-only introspection surface behind a container's `inspect()`.
52
- *
53
- * @since 0.3.16-canary.0
54
- */
55
- export class Inspector {
56
- readonly #registry: BindingRegistry;
57
- readonly #scope: ScopeManager;
58
- readonly #hasParent: boolean;
59
- readonly #isDisposed: () => boolean;
60
-
61
- constructor(registry: BindingRegistry, scope: ScopeManager, hasParent: boolean, isDisposed: () => boolean) {
62
- this.#registry = registry;
63
- this.#scope = scope;
64
- this.#hasParent = hasParent;
65
- this.#isDisposed = isDisposed;
66
- }
67
-
68
- inspect(): ContainerSnapshot {
69
- return {
70
- ownBindings: this.#registry.allBindings().map((binding) => this.#toSnapshot(binding)),
71
- cachedSingletonCount: this.#scope.cachedSingletons().length,
72
- hasParent: this.#hasParent,
73
- isDisposed: this.#isDisposed(),
74
- };
75
- }
76
-
77
- lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot> {
78
- const bindings = this.#registry.getAll(token);
79
- return bindings.map((binding) => this.#toSnapshot(binding));
80
- }
81
-
82
- has(token: Token<unknown> | Constructor, options?: ResolveOptions, parentHas?: () => boolean): boolean {
83
- const bindings = this.#registry.getAll(token);
84
- if (bindings.length > 0) {
85
- // An existence probe answers ambiguity with `true` — several matches still exist; only
86
- // resolution has to pick one.
87
- if (options !== undefined) {
88
- if (selectAllBindings(bindings, options, this.#makeConstraintContext(options)).length > 0) {
89
- return true;
90
- }
91
- } else {
92
- return true;
93
- }
94
- }
95
- return parentHas?.() ?? false;
96
- }
97
-
98
- hasOwn(token: Token<unknown> | Constructor, options?: ResolveOptions): boolean {
99
- const bindings = this.#registry.getAll(token);
100
- if (bindings.length === 0) {
101
- return false;
102
- }
103
- if (options !== undefined) {
104
- return selectAllBindings(bindings, options, this.#makeConstraintContext(options)).length > 0;
105
- }
106
- return true;
107
- }
108
-
109
- #makeConstraintContext(options: ResolveOptions): ConstraintContext {
110
- return {
111
- resolutionPath: [],
112
- resolutionStack: [],
113
- parent: undefined,
114
- ancestors: [],
115
- currentResolveOptions: options,
116
- };
117
- }
118
-
119
- #toSnapshot(binding: Binding): BindingSnapshot {
120
- // Aliased, not copied: slot tags are frozen where they are built, so a caller's write throws
121
- // instead of corrupting the registry — and the snapshot skips an allocation per binding.
122
- const slot: BindingSnapshot["slot"] =
123
- binding.slot.name !== undefined
124
- ? { name: binding.slot.name, tags: binding.slot.tags }
125
- : { tags: binding.slot.tags };
126
- return {
127
- tokenName: tokenName(binding.token),
128
- kind: binding.kind,
129
- scope: effectiveBindingScope(binding),
130
- slot,
131
- id: binding.id,
132
- };
133
- }
134
- }