@codefast/di 0.5.0 → 0.6.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.
- package/CHANGELOG.md +712 -0
- package/README.md +119 -30
- package/dist/ambient/active-container.d.ts +37 -0
- package/dist/ambient/active-container.d.ts.map +1 -0
- package/dist/ambient/active-container.js +41 -0
- package/dist/ambient/active-container.js.map +1 -0
- package/dist/container/binding-builders.d.ts +12 -8
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +62 -31
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts +33 -19
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +281 -118
- package/dist/container/container.js.map +1 -1
- package/dist/{resolution → core}/binding-scope.d.ts +2 -2
- package/dist/core/binding-scope.d.ts.map +1 -0
- package/dist/core/binding-scope.js.map +1 -0
- package/dist/{binding.d.ts → core/binding.d.ts} +70 -15
- package/dist/core/binding.d.ts.map +1 -0
- package/dist/{binding.js → core/binding.js} +27 -12
- package/dist/core/binding.js.map +1 -0
- package/dist/core/constraint-requirement.d.ts +53 -0
- package/dist/core/constraint-requirement.d.ts.map +1 -0
- package/dist/core/constraint-requirement.js +62 -0
- package/dist/core/constraint-requirement.js.map +1 -0
- package/dist/{constructor-type.d.ts → core/constructor-type.d.ts} +1 -1
- package/dist/core/constructor-type.d.ts.map +1 -0
- package/dist/core/constructor-type.js.map +1 -0
- package/dist/{module.d.ts → core/module.d.ts} +5 -5
- package/dist/core/module.d.ts.map +1 -0
- package/dist/{module.js → core/module.js} +4 -4
- package/dist/core/module.js.map +1 -0
- package/dist/{registry.d.ts → core/registry.d.ts} +27 -7
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/{registry.js → core/registry.js} +125 -52
- package/dist/core/registry.js.map +1 -0
- package/dist/core/tag.d.ts +80 -0
- package/dist/core/tag.d.ts.map +1 -0
- package/dist/core/tag.js +96 -0
- package/dist/core/tag.js.map +1 -0
- package/dist/{token.d.ts → core/token.d.ts} +2 -6
- package/dist/core/token.d.ts.map +1 -0
- package/dist/core/token.js +13 -0
- package/dist/core/token.js.map +1 -0
- package/dist/{types.d.ts → core/types.d.ts} +27 -21
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js.map +1 -0
- package/dist/decorators/inject.d.ts +5 -49
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js +34 -104
- package/dist/decorators/inject.js.map +1 -1
- package/dist/decorators/injectable.d.ts +25 -5
- package/dist/decorators/injectable.d.ts.map +1 -1
- package/dist/decorators/injectable.js +2 -3
- package/dist/decorators/injectable.js.map +1 -1
- package/dist/decorators/lifecycle-decorators.d.ts +4 -2
- package/dist/decorators/lifecycle-decorators.d.ts.map +1 -1
- package/dist/decorators/lifecycle-decorators.js +19 -25
- package/dist/decorators/lifecycle-decorators.js.map +1 -1
- package/dist/{resolution → errors}/diagnostics.d.ts +2 -0
- package/dist/errors/diagnostics.d.ts.map +1 -0
- package/dist/errors/diagnostics.js.map +1 -0
- package/dist/{errors.d.ts → errors/errors.d.ts} +89 -4
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/{errors.js → errors/errors.js} +129 -7
- package/dist/errors/errors.js.map +1 -0
- package/dist/index.d.ts +21 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/injection/descriptor.d.ts +79 -0
- package/dist/injection/descriptor.d.ts.map +1 -0
- package/dist/injection/descriptor.js +118 -0
- package/dist/injection/descriptor.js.map +1 -0
- package/dist/{resolution → injection}/resolve-options.d.ts +21 -11
- package/dist/injection/resolve-options.d.ts.map +1 -0
- package/dist/injection/resolve-options.js +101 -0
- package/dist/injection/resolve-options.js.map +1 -0
- package/dist/introspection/dependency-graph.d.ts +16 -5
- package/dist/introspection/dependency-graph.d.ts.map +1 -1
- package/dist/introspection/dependency-graph.js +138 -57
- package/dist/introspection/dependency-graph.js.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.d.ts +6 -3
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.js +3 -0
- package/dist/introspection/graph-adapters/cytoscape.js.map +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/dot.js +12 -5
- package/dist/introspection/graph-adapters/dot.js.map +1 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts +9 -0
- package/dist/introspection/graph-adapters/mermaid.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/mermaid.js +47 -0
- package/dist/introspection/graph-adapters/mermaid.js.map +1 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts +6 -3
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/reactflow.js +5 -1
- package/dist/introspection/graph-adapters/reactflow.js.map +1 -1
- package/dist/introspection/inspector.d.ts +4 -4
- package/dist/introspection/inspector.d.ts.map +1 -1
- package/dist/introspection/inspector.js +10 -6
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/lifecycle/lifecycle-manager.d.ts +27 -0
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
- package/dist/{resolution/lifecycle.js → lifecycle/lifecycle-manager.js} +39 -9
- package/dist/lifecycle/lifecycle-manager.js.map +1 -0
- package/dist/lifecycle/scope-manager.d.ts +48 -0
- package/dist/lifecycle/scope-manager.d.ts.map +1 -0
- package/dist/lifecycle/scope-manager.js +135 -0
- package/dist/lifecycle/scope-manager.js.map +1 -0
- package/dist/metadata/metadata-reader-token.d.ts +1 -1
- package/dist/metadata/metadata-reader-token.d.ts.map +1 -1
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/metadata/metadata-reader-token.js.map +1 -1
- package/dist/metadata/metadata-types.d.ts +9 -9
- package/dist/metadata/metadata-types.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.d.ts +19 -6
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.js +89 -9
- package/dist/metadata/symbol-metadata-reader.js.map +1 -1
- package/dist/metadata/verifying-metadata-reader.d.ts +16 -0
- package/dist/metadata/verifying-metadata-reader.d.ts.map +1 -0
- package/dist/metadata/verifying-metadata-reader.js +32 -0
- package/dist/metadata/verifying-metadata-reader.js.map +1 -0
- package/dist/resolution/{activation-need.d.ts → cache/activation-need.d.ts} +5 -5
- package/dist/resolution/cache/activation-need.d.ts.map +1 -0
- package/dist/resolution/cache/activation-need.js.map +1 -0
- package/dist/resolution/{binding-lookup-cache.d.ts → cache/binding-lookup-cache.d.ts} +9 -5
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +55 -0
- package/dist/resolution/cache/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/cache/class-introspector.d.ts +66 -0
- package/dist/resolution/cache/class-introspector.d.ts.map +1 -0
- package/dist/resolution/cache/class-introspector.js +194 -0
- package/dist/resolution/cache/class-introspector.js.map +1 -0
- package/dist/resolution/context.d.ts +84 -0
- package/dist/resolution/context.d.ts.map +1 -0
- package/dist/resolution/{environment.js → context.js} +54 -80
- package/dist/resolution/context.js.map +1 -0
- package/dist/resolution/path/resolution-path.d.ts +94 -0
- package/dist/resolution/path/resolution-path.d.ts.map +1 -0
- package/dist/resolution/path/resolution-path.js +117 -0
- package/dist/resolution/path/resolution-path.js.map +1 -0
- package/dist/resolution/{instantiation-plan.d.ts → plan/instantiation-plan.d.ts} +37 -7
- package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
- package/dist/resolution/plan/instantiation-plan.js +419 -0
- package/dist/resolution/plan/instantiation-plan.js.map +1 -0
- package/dist/resolution/resolver.d.ts +28 -28
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +581 -239
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/select/binding-select.d.ts +34 -0
- package/dist/resolution/select/binding-select.d.ts.map +1 -0
- package/dist/resolution/select/binding-select.js +175 -0
- package/dist/resolution/select/binding-select.js.map +1 -0
- package/dist/resolution/{constraints.d.ts → select/constraints.d.ts} +13 -12
- package/dist/resolution/select/constraints.d.ts.map +1 -0
- package/dist/resolution/{constraints.js → select/constraints.js} +33 -22
- package/dist/resolution/select/constraints.js.map +1 -0
- package/package.json +160 -20
- package/src/ambient/active-container.ts +65 -0
- package/src/container/binding-builders.ts +79 -44
- package/src/container/container.ts +357 -170
- package/src/{resolution → core}/binding-scope.ts +2 -2
- package/src/{binding.ts → core/binding.ts} +101 -39
- package/src/core/constraint-requirement.ts +94 -0
- package/src/{constructor-type.ts → core/constructor-type.ts} +1 -1
- package/src/{module.ts → core/module.ts} +10 -10
- package/src/core/registry.ts +365 -0
- package/src/core/tag.ts +158 -0
- package/src/{token.ts → core/token.ts} +2 -14
- package/src/{types.ts → core/types.ts} +37 -34
- package/src/decorators/inject.ts +39 -175
- package/src/decorators/injectable.ts +40 -10
- package/src/decorators/lifecycle-decorators.ts +22 -34
- package/src/{resolution → errors}/diagnostics.ts +2 -0
- package/src/{errors.ts → errors/errors.ts} +152 -8
- package/src/index.ts +43 -15
- package/src/injection/descriptor.ts +206 -0
- package/src/injection/resolve-options.ts +144 -0
- package/src/introspection/dependency-graph.ts +223 -63
- package/src/introspection/graph-adapters/cytoscape.ts +9 -3
- package/src/introspection/graph-adapters/dot.ts +13 -5
- package/src/introspection/graph-adapters/mermaid.ts +61 -0
- package/src/introspection/graph-adapters/reactflow.ts +11 -4
- package/src/introspection/inspector.ts +16 -12
- package/src/{resolution/lifecycle.ts → lifecycle/lifecycle-manager.ts} +68 -32
- package/src/lifecycle/scope-manager.ts +156 -0
- package/src/metadata/metadata-reader-token.ts +2 -2
- package/src/metadata/metadata-types.ts +9 -9
- package/src/metadata/symbol-metadata-reader.ts +95 -18
- package/src/metadata/verifying-metadata-reader.ts +41 -0
- package/src/resolution/{activation-need.ts → cache/activation-need.ts} +8 -8
- package/src/resolution/{binding-lookup-cache.ts → cache/binding-lookup-cache.ts} +63 -5
- package/src/resolution/cache/class-introspector.ts +251 -0
- package/src/resolution/{environment.ts → context.ts} +84 -145
- package/src/resolution/path/resolution-path.ts +167 -0
- package/src/resolution/plan/instantiation-plan.ts +615 -0
- package/src/resolution/resolver.ts +663 -313
- package/src/resolution/select/binding-select.ts +210 -0
- package/src/resolution/select/constraints.ts +130 -0
- package/dist/binding.d.ts.map +0 -1
- package/dist/binding.js.map +0 -1
- package/dist/constructor-type.d.ts.map +0 -1
- package/dist/constructor-type.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/module.d.ts.map +0 -1
- package/dist/module.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/resolution/activation-need.d.ts.map +0 -1
- package/dist/resolution/activation-need.js.map +0 -1
- package/dist/resolution/binding-lookup-cache.d.ts.map +0 -1
- package/dist/resolution/binding-lookup-cache.js.map +0 -1
- package/dist/resolution/binding-scope.d.ts.map +0 -1
- package/dist/resolution/binding-scope.js.map +0 -1
- package/dist/resolution/binding-select.d.ts +0 -23
- package/dist/resolution/binding-select.d.ts.map +0 -1
- package/dist/resolution/binding-select.js +0 -121
- package/dist/resolution/binding-select.js.map +0 -1
- package/dist/resolution/class-introspector.d.ts +0 -27
- package/dist/resolution/class-introspector.d.ts.map +0 -1
- package/dist/resolution/class-introspector.js +0 -60
- package/dist/resolution/class-introspector.js.map +0 -1
- package/dist/resolution/constraints.d.ts.map +0 -1
- package/dist/resolution/constraints.js.map +0 -1
- package/dist/resolution/diagnostics.d.ts.map +0 -1
- package/dist/resolution/diagnostics.js.map +0 -1
- package/dist/resolution/environment.d.ts +0 -94
- package/dist/resolution/environment.d.ts.map +0 -1
- package/dist/resolution/environment.js.map +0 -1
- package/dist/resolution/instantiation-plan.d.ts.map +0 -1
- package/dist/resolution/instantiation-plan.js +0 -183
- package/dist/resolution/instantiation-plan.js.map +0 -1
- package/dist/resolution/lifecycle.d.ts +0 -23
- package/dist/resolution/lifecycle.d.ts.map +0 -1
- package/dist/resolution/lifecycle.js.map +0 -1
- package/dist/resolution/resolution-path.d.ts +0 -98
- package/dist/resolution/resolution-path.d.ts.map +0 -1
- package/dist/resolution/resolution-path.js +0 -98
- package/dist/resolution/resolution-path.js.map +0 -1
- package/dist/resolution/resolve-options.d.ts.map +0 -1
- package/dist/resolution/resolve-options.js +0 -56
- package/dist/resolution/resolve-options.js.map +0 -1
- package/dist/resolution/scope.d.ts +0 -28
- package/dist/resolution/scope.d.ts.map +0 -1
- package/dist/resolution/scope.js +0 -88
- package/dist/resolution/scope.js.map +0 -1
- package/dist/token.d.ts.map +0 -1
- package/dist/token.js +0 -22
- package/dist/token.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/registry.ts +0 -282
- package/src/resolution/binding-select.ts +0 -154
- package/src/resolution/class-introspector.ts +0 -74
- package/src/resolution/constraints.ts +0 -121
- package/src/resolution/instantiation-plan.ts +0 -292
- package/src/resolution/resolution-path.ts +0 -156
- package/src/resolution/resolve-options.ts +0 -89
- package/src/resolution/scope.ts +0 -105
- /package/dist/{resolution → core}/binding-scope.js +0 -0
- /package/dist/{constructor-type.js → core/constructor-type.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/dist/{resolution → errors}/diagnostics.js +0 -0
- /package/dist/resolution/{activation-need.js → cache/activation-need.js} +0 -0
|
@@ -1,29 +1,45 @@
|
|
|
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 { BindingKind, BindingScope, Constructor } from "#/core/types";
|
|
7
|
+
import type { DependencySlot } from "#/injection/resolve-options";
|
|
8
|
+
import { bindingSlotToResolveOptions } from "#/injection/resolve-options";
|
|
1
9
|
import type { MetadataReader } from "#/metadata/metadata-types";
|
|
2
|
-
import
|
|
3
|
-
import { effectiveBindingScope } from "#/resolution/binding-scope";
|
|
4
|
-
import { tokenName } from "#/token";
|
|
5
|
-
import type { BindingScope, Constructor } from "#/types";
|
|
10
|
+
import { matchesSlot } from "#/resolution/select/binding-select";
|
|
6
11
|
|
|
7
|
-
// ── Types
|
|
12
|
+
// ── Types ────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
8
13
|
|
|
9
14
|
/**
|
|
15
|
+
* @remarks `kind`/`scope` are `"unbound"` for the placeholder node an optional, currently
|
|
16
|
+
* unsatisfied dependency points at.
|
|
17
|
+
*
|
|
10
18
|
* @since 0.3.16-canary.0
|
|
11
19
|
*/
|
|
12
20
|
export interface GraphNode {
|
|
13
21
|
readonly id: string;
|
|
14
22
|
readonly tokenName: string;
|
|
15
|
-
|
|
16
|
-
readonly
|
|
23
|
+
/** Identifies the token itself, so bindings that share a display name stay distinguishable. */
|
|
24
|
+
readonly tokenKey: string;
|
|
25
|
+
readonly kind: BindingKind | "unbound";
|
|
26
|
+
readonly scope: BindingScope | "unbound";
|
|
17
27
|
readonly fromParent: boolean;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
/**
|
|
31
|
+
* @remarks `label` is presentation, assembled for the adapters; read `optional` and `slotName`
|
|
32
|
+
* rather than parsing it.
|
|
33
|
+
*
|
|
21
34
|
* @since 0.3.16-canary.0
|
|
22
35
|
*/
|
|
23
36
|
export interface GraphEdge {
|
|
24
37
|
readonly from: string;
|
|
25
38
|
readonly to: string;
|
|
26
39
|
readonly label?: string;
|
|
40
|
+
readonly optional: boolean;
|
|
41
|
+
/** The named slot this edge resolves to, when the binding declares one. */
|
|
42
|
+
readonly slotName?: string;
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
/**
|
|
@@ -39,10 +55,204 @@ export interface ContainerGraphJson {
|
|
|
39
55
|
* @since 0.3.16-canary.0
|
|
40
56
|
*/
|
|
41
57
|
export interface GraphOptions {
|
|
42
|
-
readonly includeParent?: boolean;
|
|
58
|
+
readonly includeParent?: boolean | undefined;
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
// ── Builder
|
|
61
|
+
// ── Builder ──────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
// Tokens are compared by object identity, and a name is free to repeat, so the graph mints its
|
|
64
|
+
// own per-process key. Weakly held: a discarded token takes its key with it.
|
|
65
|
+
const tokenKeys = new WeakMap<object, string>();
|
|
66
|
+
let tokenKeySequence = 0;
|
|
67
|
+
|
|
68
|
+
function tokenKeyOf(dependency: Token<unknown> | Constructor): string {
|
|
69
|
+
const existing = tokenKeys.get(dependency);
|
|
70
|
+
|
|
71
|
+
if (existing !== undefined) {
|
|
72
|
+
return existing;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
tokenKeySequence += 1;
|
|
76
|
+
const key = `${tokenName(dependency)}@${String(tokenKeySequence)}`;
|
|
77
|
+
|
|
78
|
+
tokenKeys.set(dependency, key);
|
|
79
|
+
|
|
80
|
+
return key;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mirrors filterBindings' slot semantics; predicates need a live resolution context, so the graph
|
|
84
|
+
// keeps every predicate-carrying candidate.
|
|
85
|
+
function matchingTargets(candidates: ReadonlyArray<Binding>, ref: DependencySlot): ReadonlyArray<Binding> {
|
|
86
|
+
const criterion = bindingSlotToResolveOptions(ref);
|
|
87
|
+
|
|
88
|
+
if (ref.multi && criterion === undefined) {
|
|
89
|
+
return candidates;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return candidates.filter((candidate) => matchesSlot(candidate.slot, criterion));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function edgeLabel(ref: DependencySlot, index: number): string {
|
|
96
|
+
const criterion =
|
|
97
|
+
ref.name !== undefined
|
|
98
|
+
? `name:${ref.name}`
|
|
99
|
+
: ref.tags !== undefined && ref.tags.length > 0
|
|
100
|
+
? `tag:${ref.tags[0]?.key.name}=${String(ref.tags[0]?.value)}`
|
|
101
|
+
: `[${index}]`;
|
|
102
|
+
|
|
103
|
+
return ref.optional ? `${criterion} optional` : criterion;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The collections a graph walk fills, so each step of the walk can be a function of its own. */
|
|
107
|
+
interface GraphAccumulator {
|
|
108
|
+
readonly nodes: Array<GraphNode>;
|
|
109
|
+
readonly edges: Array<GraphEdge>;
|
|
110
|
+
// One placeholder node per optional-but-unbound token keeps the declared edge visible.
|
|
111
|
+
readonly unboundNodeIds: Map<string, string>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Own bindings shadow the fallback, mirroring resolution's upward walk. */
|
|
115
|
+
type BindingLookup = (token: Token<unknown> | Constructor) => ReadonlyArray<Binding>;
|
|
116
|
+
|
|
117
|
+
function bindingLookup(sourceRegistry: BindingRegistry, fallbackRegistry: BindingRegistry | undefined): BindingLookup {
|
|
118
|
+
return (token) => {
|
|
119
|
+
const own = sourceRegistry.getAll(token);
|
|
120
|
+
|
|
121
|
+
if (own.length > 0 || fallbackRegistry === undefined) {
|
|
122
|
+
return own;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return fallbackRegistry.getAll(token);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The placeholder node an optional-but-unbound dependency points at, minted once per token. */
|
|
130
|
+
function unboundNodeIdFor(accumulator: GraphAccumulator, dependency: Token<unknown> | Constructor): string {
|
|
131
|
+
const key = tokenKeyOf(dependency);
|
|
132
|
+
const existing = accumulator.unboundNodeIds.get(key);
|
|
133
|
+
|
|
134
|
+
if (existing !== undefined) {
|
|
135
|
+
return existing;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const id = `unbound:${key}`;
|
|
139
|
+
|
|
140
|
+
accumulator.unboundNodeIds.set(key, id);
|
|
141
|
+
accumulator.nodes.push({
|
|
142
|
+
id,
|
|
143
|
+
tokenName: tokenName(dependency),
|
|
144
|
+
tokenKey: key,
|
|
145
|
+
kind: "unbound",
|
|
146
|
+
scope: "unbound",
|
|
147
|
+
fromParent: false,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return id;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function addDependencyEdges(
|
|
154
|
+
accumulator: GraphAccumulator,
|
|
155
|
+
from: string,
|
|
156
|
+
ref: DependencySlot,
|
|
157
|
+
index: number,
|
|
158
|
+
lookup: BindingLookup,
|
|
159
|
+
): void {
|
|
160
|
+
const targets = matchingTargets(lookup(ref.token), ref);
|
|
161
|
+
const label = edgeLabel(ref, index);
|
|
162
|
+
|
|
163
|
+
if (targets.length === 0) {
|
|
164
|
+
// A required-but-unbound dependency is validate()'s story, not the graph's.
|
|
165
|
+
if (!ref.optional) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
accumulator.edges.push({
|
|
170
|
+
from,
|
|
171
|
+
to: unboundNodeIdFor(accumulator, ref.token),
|
|
172
|
+
label,
|
|
173
|
+
optional: true,
|
|
174
|
+
...(ref.name !== undefined ? { slotName: ref.name } : {}),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const target of targets) {
|
|
181
|
+
// A multi dep with no criterion of its own fans out — each edge names the slot it hits.
|
|
182
|
+
const slotName = target.slot.name ?? ref.name;
|
|
183
|
+
const perTargetLabel =
|
|
184
|
+
ref.multi && ref.name === undefined && slotName !== undefined
|
|
185
|
+
? edgeLabel({ ...ref, name: slotName }, index)
|
|
186
|
+
: label;
|
|
187
|
+
|
|
188
|
+
accumulator.edges.push({
|
|
189
|
+
from,
|
|
190
|
+
to: target.id,
|
|
191
|
+
label: perTargetLabel,
|
|
192
|
+
optional: ref.optional,
|
|
193
|
+
...(slotName !== undefined ? { slotName } : {}),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** What one binding declares up front — a class's params, a factory's descriptors, an alias's target. */
|
|
199
|
+
function addBindingEdges(
|
|
200
|
+
accumulator: GraphAccumulator,
|
|
201
|
+
binding: Binding,
|
|
202
|
+
metadataReader: MetadataReader,
|
|
203
|
+
lookup: BindingLookup,
|
|
204
|
+
): void {
|
|
205
|
+
if (binding.kind === "class") {
|
|
206
|
+
const meta = metadataReader.getConstructorMetadata(binding.target);
|
|
207
|
+
|
|
208
|
+
if (meta !== undefined) {
|
|
209
|
+
for (const [index, param] of meta.params.entries()) {
|
|
210
|
+
addDependencyEdges(accumulator, binding.id, param, index, lookup);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (binding.kind === "resolved" || binding.kind === "resolved-async") {
|
|
218
|
+
for (const [index, dependency] of binding.deps.entries()) {
|
|
219
|
+
addDependencyEdges(accumulator, binding.id, dependency, index, lookup);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (binding.kind === "alias") {
|
|
226
|
+
const aliasRef: DependencySlot = { token: binding.target, optional: false, multi: false };
|
|
227
|
+
|
|
228
|
+
for (const target of matchingTargets(lookup(binding.target), aliasRef)) {
|
|
229
|
+
accumulator.edges.push({ from: binding.id, to: target.id, label: "alias", optional: false });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function addRegistryBindings(
|
|
235
|
+
accumulator: GraphAccumulator,
|
|
236
|
+
sourceRegistry: BindingRegistry,
|
|
237
|
+
metadataReader: MetadataReader,
|
|
238
|
+
fromParent: boolean,
|
|
239
|
+
fallbackRegistry?: BindingRegistry,
|
|
240
|
+
): void {
|
|
241
|
+
const lookup = bindingLookup(sourceRegistry, fallbackRegistry);
|
|
242
|
+
|
|
243
|
+
for (const binding of sourceRegistry.allBindings()) {
|
|
244
|
+
accumulator.nodes.push({
|
|
245
|
+
id: binding.id,
|
|
246
|
+
tokenName: tokenName(binding.token),
|
|
247
|
+
tokenKey: tokenKeyOf(binding.token),
|
|
248
|
+
kind: binding.kind,
|
|
249
|
+
scope: effectiveBindingScope(binding),
|
|
250
|
+
fromParent,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
addBindingEdges(accumulator, binding, metadataReader, lookup);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
46
256
|
|
|
47
257
|
/**
|
|
48
258
|
* @since 0.3.16-canary.0
|
|
@@ -53,64 +263,14 @@ export function buildDependencyGraph(
|
|
|
53
263
|
options: GraphOptions | undefined,
|
|
54
264
|
parentRegistry?: BindingRegistry,
|
|
55
265
|
): ContainerGraphJson {
|
|
56
|
-
const
|
|
57
|
-
const edges: Array<GraphEdge> = [];
|
|
266
|
+
const accumulator: GraphAccumulator = { nodes: [], edges: [], unboundNodeIds: new Map() };
|
|
58
267
|
const includesParent = options?.includeParent === true;
|
|
59
268
|
|
|
60
|
-
|
|
61
|
-
for (const binding of sourceRegistry.allBindings()) {
|
|
62
|
-
const scope = effectiveBindingScope(binding);
|
|
63
|
-
nodes.push({
|
|
64
|
-
id: binding.id,
|
|
65
|
-
tokenName: tokenName(binding.token),
|
|
66
|
-
kind: binding.kind,
|
|
67
|
-
scope,
|
|
68
|
-
fromParent,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Build edges
|
|
72
|
-
if (binding.kind === "class") {
|
|
73
|
-
const meta = metadataReader.getConstructorMetadata(binding.target as Constructor);
|
|
74
|
-
if (meta !== undefined) {
|
|
75
|
-
for (let index = 0; index < meta.params.length; index += 1) {
|
|
76
|
-
const param = meta.params[index]!;
|
|
77
|
-
const dependencyBinding = sourceRegistry.getAll(param.token as Constructor)[0];
|
|
78
|
-
if (dependencyBinding !== undefined) {
|
|
79
|
-
edges.push({
|
|
80
|
-
from: binding.id,
|
|
81
|
-
to: dependencyBinding.id,
|
|
82
|
-
label: `[${index}]`,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} else if (binding.kind === "resolved" || binding.kind === "resolved-async") {
|
|
88
|
-
for (let index = 0; index < binding.deps.length; index += 1) {
|
|
89
|
-
const dependency = binding.deps[index]!;
|
|
90
|
-
const dependencyBindings = sourceRegistry.getAll(dependency.token as Constructor);
|
|
91
|
-
if (dependencyBindings.length > 0 && dependencyBindings[0] !== undefined) {
|
|
92
|
-
const label =
|
|
93
|
-
dependency.name !== undefined
|
|
94
|
-
? `name:${dependency.name}`
|
|
95
|
-
: dependency.tags !== undefined && dependency.tags.length > 0
|
|
96
|
-
? `tag:${dependency.tags[0]?.[0]}=${String(dependency.tags[0]?.[1])}`
|
|
97
|
-
: `[${index}]`;
|
|
98
|
-
edges.push({ from: binding.id, to: dependencyBindings[0].id, label });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} else if (binding.kind === "alias") {
|
|
102
|
-
const targetBindings = sourceRegistry.getAll(binding.target as Constructor);
|
|
103
|
-
if (targetBindings.length > 0 && targetBindings[0] !== undefined) {
|
|
104
|
-
edges.push({ from: binding.id, to: targetBindings[0].id, label: "alias" });
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
269
|
+
addRegistryBindings(accumulator, registry, metadataReader, false, includesParent ? parentRegistry : undefined);
|
|
109
270
|
|
|
110
|
-
addBindings(registry, false);
|
|
111
271
|
if (includesParent && parentRegistry !== undefined) {
|
|
112
|
-
|
|
272
|
+
addRegistryBindings(accumulator, parentRegistry, metadataReader, true);
|
|
113
273
|
}
|
|
114
274
|
|
|
115
|
-
return { nodes, edges, includesParent };
|
|
275
|
+
return { nodes: accumulator.nodes, edges: accumulator.edges, includesParent };
|
|
116
276
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContainerGraphJson } from "#/introspection/dependency-graph";
|
|
1
|
+
import type { ContainerGraphJson, GraphNode } from "#/introspection/dependency-graph";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @since 0.3.16-canary.0
|
|
@@ -7,8 +7,9 @@ export interface CytoscapeNode {
|
|
|
7
7
|
readonly data: {
|
|
8
8
|
readonly id: string;
|
|
9
9
|
readonly label: string;
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
10
|
+
readonly tokenKey: string;
|
|
11
|
+
readonly kind: GraphNode["kind"];
|
|
12
|
+
readonly scope: GraphNode["scope"];
|
|
12
13
|
readonly fromParent: boolean;
|
|
13
14
|
};
|
|
14
15
|
}
|
|
@@ -22,6 +23,8 @@ export interface CytoscapeEdge {
|
|
|
22
23
|
readonly source: string;
|
|
23
24
|
readonly target: string;
|
|
24
25
|
readonly label?: string;
|
|
26
|
+
readonly optional: boolean;
|
|
27
|
+
readonly slotName?: string;
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -41,6 +44,7 @@ export function toCytoscapeGraph(graph: ContainerGraphJson): CytoscapeElements {
|
|
|
41
44
|
data: {
|
|
42
45
|
id: node.id,
|
|
43
46
|
label: node.tokenName,
|
|
47
|
+
tokenKey: node.tokenKey,
|
|
44
48
|
kind: node.kind,
|
|
45
49
|
scope: node.scope,
|
|
46
50
|
fromParent: node.fromParent,
|
|
@@ -56,6 +60,8 @@ export function toCytoscapeGraph(graph: ContainerGraphJson): CytoscapeElements {
|
|
|
56
60
|
source: edge.from,
|
|
57
61
|
target: edge.to,
|
|
58
62
|
...(edge.label !== undefined ? { label: edge.label } : {}),
|
|
63
|
+
optional: edge.optional,
|
|
64
|
+
...(edge.slotName !== undefined ? { slotName: edge.slotName } : {}),
|
|
59
65
|
},
|
|
60
66
|
});
|
|
61
67
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { ContainerGraphJson } from "#/introspection/dependency-graph";
|
|
2
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
|
+
|
|
3
9
|
/**
|
|
4
10
|
* @since 0.3.16-canary.0
|
|
5
11
|
*/
|
|
@@ -7,14 +13,16 @@ export function toDotGraph(graph: ContainerGraphJson): string {
|
|
|
7
13
|
const lines: Array<string> = ["digraph DI {", " rankdir=TB;"];
|
|
8
14
|
|
|
9
15
|
for (const node of graph.nodes) {
|
|
10
|
-
const label = `${node.tokenName}\\n[${node.kind}/${node.scope}]`;
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
const label = `${escapeDotString(node.tokenName)}\\n[${node.kind}/${node.scope}]`;
|
|
17
|
+
// Dashed for anything that is not a live binding of this container: the parent chain
|
|
18
|
+
// and unbound-optional placeholders.
|
|
19
|
+
const style = node.fromParent || node.kind === "unbound" ? ' style="dashed"' : "";
|
|
20
|
+
lines.push(` "${escapeDotString(node.id)}" [label="${label}"${style}];`);
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
for (const edge of graph.edges) {
|
|
16
|
-
const label = edge.label
|
|
17
|
-
lines.push(` "${edge.from}" -> "${edge.to}"${label};`);
|
|
24
|
+
const label = edge.label === undefined ? "" : ` [label="${escapeDotString(edge.label)}"]`;
|
|
25
|
+
lines.push(` "${escapeDotString(edge.from)}" -> "${escapeDotString(edge.to)}"${label};`);
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
lines.push("}");
|
|
@@ -0,0 +1,61 @@
|
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import type { ContainerGraphJson } from "#/introspection/dependency-graph";
|
|
1
|
+
import type { ContainerGraphJson, GraphNode } from "#/introspection/dependency-graph";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @since 0.3.16-canary.0
|
|
@@ -7,8 +7,9 @@ export interface ReactFlowNode {
|
|
|
7
7
|
readonly id: string;
|
|
8
8
|
readonly data: {
|
|
9
9
|
readonly label: string;
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
10
|
+
readonly tokenKey: string;
|
|
11
|
+
readonly kind: GraphNode["kind"];
|
|
12
|
+
readonly scope: GraphNode["scope"];
|
|
12
13
|
readonly fromParent: boolean;
|
|
13
14
|
};
|
|
14
15
|
readonly position: { readonly x: number; readonly y: number };
|
|
@@ -22,6 +23,8 @@ export interface ReactFlowEdge {
|
|
|
22
23
|
readonly source: string;
|
|
23
24
|
readonly target: string;
|
|
24
25
|
readonly label?: string;
|
|
26
|
+
readonly optional: boolean;
|
|
27
|
+
readonly slotName?: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -33,9 +36,10 @@ export interface ReactFlowGraph {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
39
|
+
* Initial grid layout: React Flow expects concrete positions; viewers re-layout anyway.
|
|
40
|
+
*
|
|
36
41
|
* @since 0.3.16-canary.0
|
|
37
42
|
*/
|
|
38
|
-
// Initial grid layout: React Flow expects concrete positions; viewers re-layout anyway.
|
|
39
43
|
const GRID_COLUMN_COUNT = 5;
|
|
40
44
|
const GRID_CELL_WIDTH_PX = 200;
|
|
41
45
|
const GRID_CELL_HEIGHT_PX = 100;
|
|
@@ -48,6 +52,7 @@ export function toReactFlowGraph(graph: ContainerGraphJson): ReactFlowGraph {
|
|
|
48
52
|
id: node.id,
|
|
49
53
|
data: {
|
|
50
54
|
label: node.tokenName,
|
|
55
|
+
tokenKey: node.tokenKey,
|
|
51
56
|
kind: node.kind,
|
|
52
57
|
scope: node.scope,
|
|
53
58
|
fromParent: node.fromParent,
|
|
@@ -63,6 +68,8 @@ export function toReactFlowGraph(graph: ContainerGraphJson): ReactFlowGraph {
|
|
|
63
68
|
source: edge.from,
|
|
64
69
|
target: edge.to,
|
|
65
70
|
...(edge.label !== undefined ? { label: edge.label } : {}),
|
|
71
|
+
optional: edge.optional,
|
|
72
|
+
...(edge.slotName !== undefined ? { slotName: edge.slotName } : {}),
|
|
66
73
|
}));
|
|
67
74
|
|
|
68
75
|
return { nodes, edges };
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { Binding } from "#/binding";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import type { Token } from "#/token";
|
|
7
|
-
import { tokenName } from "#/token";
|
|
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";
|
|
8
6
|
import type {
|
|
9
7
|
BindingIdentifier,
|
|
10
8
|
BindingKind,
|
|
@@ -13,9 +11,11 @@ import type {
|
|
|
13
11
|
ConstraintContext,
|
|
14
12
|
Constructor,
|
|
15
13
|
ResolveOptions,
|
|
16
|
-
} from "#/types";
|
|
14
|
+
} from "#/core/types";
|
|
15
|
+
import type { ScopeManager } from "#/lifecycle/scope-manager";
|
|
16
|
+
import { selectAllBindings } from "#/resolution/select/binding-select";
|
|
17
17
|
|
|
18
|
-
// ── Public types
|
|
18
|
+
// ── Public types ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @since 0.3.16-canary.0
|
|
@@ -41,7 +41,7 @@ export interface ContainerSnapshot {
|
|
|
41
41
|
readonly isDisposed: boolean;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// ── Inspector
|
|
44
|
+
// ── Inspector ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* @since 0.3.16-canary.0
|
|
@@ -76,8 +76,10 @@ export class Inspector {
|
|
|
76
76
|
has(token: Token<unknown> | Constructor, options?: ResolveOptions, parentHas?: () => boolean): boolean {
|
|
77
77
|
const bindings = this.#registry.getAll(token);
|
|
78
78
|
if (bindings.length > 0) {
|
|
79
|
+
// An existence probe answers ambiguity with `true` — several matches still exist; only
|
|
80
|
+
// resolution has to pick one.
|
|
79
81
|
if (options !== undefined) {
|
|
80
|
-
if (
|
|
82
|
+
if (selectAllBindings(bindings, options, this.#makeConstraintContext(options)).length > 0) {
|
|
81
83
|
return true;
|
|
82
84
|
}
|
|
83
85
|
} else {
|
|
@@ -93,7 +95,7 @@ export class Inspector {
|
|
|
93
95
|
return false;
|
|
94
96
|
}
|
|
95
97
|
if (options !== undefined) {
|
|
96
|
-
return
|
|
98
|
+
return selectAllBindings(bindings, options, this.#makeConstraintContext(options)).length > 0;
|
|
97
99
|
}
|
|
98
100
|
return true;
|
|
99
101
|
}
|
|
@@ -109,6 +111,8 @@ export class Inspector {
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
#toSnapshot(binding: Binding): BindingSnapshot {
|
|
114
|
+
// Aliased, not copied: slot tags are frozen where they are built, so a caller's write throws
|
|
115
|
+
// instead of corrupting the registry — and the snapshot skips an allocation per binding.
|
|
112
116
|
const slot: BindingSnapshot["slot"] =
|
|
113
117
|
binding.slot.name !== undefined
|
|
114
118
|
? { name: binding.slot.name, tags: binding.slot.tags }
|