@streetui/compiler 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/diagnostics.cjs +142 -0
- package/dist/diagnostics.cjs.map +1 -0
- package/dist/diagnostics.d.cts +103 -0
- package/dist/diagnostics.d.ts +103 -0
- package/dist/diagnostics.js +113 -0
- package/dist/diagnostics.js.map +1 -0
- package/package.json +15 -5
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/diagnostics.ts
|
|
21
|
+
var diagnostics_exports = {};
|
|
22
|
+
__export(diagnostics_exports, {
|
|
23
|
+
analyzeGraph: () => analyzeGraph,
|
|
24
|
+
formatInspection: () => formatInspection,
|
|
25
|
+
inspectCompilation: () => inspectCompilation
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(diagnostics_exports);
|
|
28
|
+
|
|
29
|
+
// src/analysis/analyze.ts
|
|
30
|
+
var TEXT_PROP_KEYS = /* @__PURE__ */ new Set(["text", "label", "value"]);
|
|
31
|
+
function analyzeGraph(graph) {
|
|
32
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
33
|
+
const summary = {
|
|
34
|
+
totalNodes: 0,
|
|
35
|
+
staticNodes: 0,
|
|
36
|
+
staticSubtrees: 0,
|
|
37
|
+
dynamicTextNodes: 0,
|
|
38
|
+
dynamicAttrNodes: 0,
|
|
39
|
+
eventNodes: 0,
|
|
40
|
+
lists: 0,
|
|
41
|
+
conditionals: 0
|
|
42
|
+
};
|
|
43
|
+
const visit = (node) => {
|
|
44
|
+
let allChildrenStatic = true;
|
|
45
|
+
for (const child of node.children) {
|
|
46
|
+
const childSubtreeStatic = visit(child);
|
|
47
|
+
if (!childSubtreeStatic) allChildrenStatic = false;
|
|
48
|
+
}
|
|
49
|
+
let hasDynamicText = false;
|
|
50
|
+
let hasDynamicAttr = false;
|
|
51
|
+
for (const ref of node.stateRefs) {
|
|
52
|
+
if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;
|
|
53
|
+
else hasDynamicAttr = true;
|
|
54
|
+
}
|
|
55
|
+
const hasEvents = node.events.length > 0;
|
|
56
|
+
const isList = node.type === "reactive-list";
|
|
57
|
+
const isConditional = node.type === "conditional";
|
|
58
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;
|
|
59
|
+
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
60
|
+
nodes.set(node.id, {
|
|
61
|
+
isStatic,
|
|
62
|
+
isStaticSubtree,
|
|
63
|
+
hasDynamicText,
|
|
64
|
+
hasDynamicAttr,
|
|
65
|
+
hasEvents,
|
|
66
|
+
isList,
|
|
67
|
+
isConditional
|
|
68
|
+
});
|
|
69
|
+
summary.totalNodes += 1;
|
|
70
|
+
if (isStatic) summary.staticNodes += 1;
|
|
71
|
+
if (isStaticSubtree) summary.staticSubtrees += 1;
|
|
72
|
+
if (hasDynamicText) summary.dynamicTextNodes += 1;
|
|
73
|
+
if (hasDynamicAttr) summary.dynamicAttrNodes += 1;
|
|
74
|
+
if (hasEvents) summary.eventNodes += 1;
|
|
75
|
+
if (isList) summary.lists += 1;
|
|
76
|
+
if (isConditional) summary.conditionals += 1;
|
|
77
|
+
return isStaticSubtree;
|
|
78
|
+
};
|
|
79
|
+
visit(graph.root);
|
|
80
|
+
return { nodes, summary };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/analysis/inspect.ts
|
|
84
|
+
function inspectCompilation(graph) {
|
|
85
|
+
const analysis = analyzeGraph(graph);
|
|
86
|
+
const nodes = [];
|
|
87
|
+
const walk = (node, depth) => {
|
|
88
|
+
const a = analysis.nodes.get(node.id);
|
|
89
|
+
if (a !== void 0) {
|
|
90
|
+
const classification = a.isStaticSubtree ? "static-subtree-root" : a.isStatic ? "static" : "dynamic";
|
|
91
|
+
nodes.push({
|
|
92
|
+
id: node.id,
|
|
93
|
+
type: node.type,
|
|
94
|
+
depth,
|
|
95
|
+
classification,
|
|
96
|
+
dynamicText: a.hasDynamicText,
|
|
97
|
+
dynamicAttrs: a.hasDynamicAttr,
|
|
98
|
+
events: node.events.map((e) => e.type),
|
|
99
|
+
boundProps: node.stateRefs.map((r) => r.propKey),
|
|
100
|
+
isList: a.isList,
|
|
101
|
+
isConditional: a.isConditional,
|
|
102
|
+
hydration: a.isStaticSubtree ? "adopt-static" : "verify-dynamic"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
for (const child of node.children) walk(child, depth + 1);
|
|
106
|
+
};
|
|
107
|
+
walk(graph.root, 0);
|
|
108
|
+
const staticRatio = analysis.summary.totalNodes === 0 ? 0 : analysis.summary.staticNodes / analysis.summary.totalNodes;
|
|
109
|
+
return {
|
|
110
|
+
name: graph.name,
|
|
111
|
+
version: graph.version,
|
|
112
|
+
summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },
|
|
113
|
+
nodes
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function formatInspection(inspection) {
|
|
117
|
+
const s = inspection.summary;
|
|
118
|
+
const lines = [];
|
|
119
|
+
lines.push(`StreetUI compiler inspection \u2014 ${inspection.name} v${inspection.version}`);
|
|
120
|
+
lines.push(
|
|
121
|
+
` nodes=${s.totalNodes} static=${s.staticNodes} staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} lists=${s.lists} conditionals=${s.conditionals} staticRatio=${(s.staticRatio * 100).toFixed(1)}%`
|
|
122
|
+
);
|
|
123
|
+
for (const n of inspection.nodes) {
|
|
124
|
+
const flags = [];
|
|
125
|
+
if (n.dynamicText) flags.push("text");
|
|
126
|
+
if (n.dynamicAttrs) flags.push("attr:" + n.boundProps.join(","));
|
|
127
|
+
if (n.events.length > 0) flags.push("on:" + n.events.join(","));
|
|
128
|
+
if (n.isList) flags.push("list");
|
|
129
|
+
if (n.isConditional) flags.push("cond");
|
|
130
|
+
lines.push(
|
|
131
|
+
` ${" ".repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` + (flags.length > 0 ? ` {${flags.join(" ")}}` : "")
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
|
136
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
137
|
+
0 && (module.exports = {
|
|
138
|
+
analyzeGraph,
|
|
139
|
+
formatInspection,
|
|
140
|
+
inspectCompilation
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=diagnostics.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/diagnostics.ts","../src/analysis/analyze.ts","../src/analysis/inspect.ts"],"sourcesContent":["/**\n * `@streetui/compiler/diagnostics` — opt-in, build/dev-time compiler diagnostics.\n *\n * The static-graph analysis (`analyzeGraph`) and the human/machine-readable\n * compiler-inspection report (`inspectCompilation` / `formatInspection`) live on\n * this dedicated subpath — deliberately NOT re-exported from the compiler's main\n * barrel — so they never enter the runtime bundle (`streetui`'s `dist/index.js`).\n * They are reached only via the diagnostic subpath (`streetui/testing`), keeping\n * the shipped runtime lean (spec §6/§7/§14).\n */\nexport * from './analysis/analyze.js';\nexport * from './analysis/inspect.js';\n","/**\n * Static graph analysis (v1.2, spec §3/§12/§14).\n *\n * A single post-order walk over the Semantic Application Graph that classifies\n * every node as static (no bound signals, no events, not a reactive\n * region) or dynamic, and rolls that up into whole-static-subtree flags. This\n * is COMPILE-TIME metadata only — it introduces no virtual DOM, no second tree\n * and no runtime reactive system. It is consumed by:\n * - the hydration path, to skip per-node attribute/text re-verification on\n * provably-static subtrees (§12), and\n * - the diagnostic compiler-inspection report (§14).\n *\n * The renderer's initial-mount fast path does not need this map: it already\n * skips reactive wiring per node via cheap `events.length`/`stateRefs.length`\n * guards. Keeping the analysis out of the mount hot path avoids adding lookup\n * cost (and keeps the shipped runtime lean).\n */\n\nimport type { NodeId } from '@streetui/core';\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\n\n/** Prop keys whose bound signal drives text content rather than an attribute. */\nconst TEXT_PROP_KEYS: ReadonlySet<string> = new Set(['text', 'label', 'value']);\n\nexport interface NodeAnalysis {\n /** No bound signals, no events, and not a reactive-list/conditional region. */\n readonly isStatic: boolean;\n /** This node is static AND every descendant is a static subtree. */\n readonly isStaticSubtree: boolean;\n /** A signal is bound to this node's text/label/value content. */\n readonly hasDynamicText: boolean;\n /** A signal is bound to a non-content prop (a reactive attribute). */\n readonly hasDynamicAttr: boolean;\n /** This node wires one or more DOM event handlers. */\n readonly hasEvents: boolean;\n /** This node is a keyed reactive list. */\n readonly isList: boolean;\n /** This node is a conditional (0..1 branch) region. */\n readonly isConditional: boolean;\n}\n\nexport interface GraphAnalysisSummary {\n readonly totalNodes: number;\n readonly staticNodes: number;\n readonly staticSubtrees: number;\n readonly dynamicTextNodes: number;\n readonly dynamicAttrNodes: number;\n readonly eventNodes: number;\n readonly lists: number;\n readonly conditionals: number;\n}\n\nexport interface GraphAnalysis {\n readonly nodes: ReadonlyMap<NodeId, NodeAnalysis>;\n readonly summary: GraphAnalysisSummary;\n}\n\n/**\n * Analyze a fully-built graph. O(n) single post-order pass; allocates one small\n * record per node. Safe to skip entirely when neither hydration nor diagnostics\n * need it.\n */\nexport function analyzeGraph(graph: ApplicationGraph): GraphAnalysis {\n const nodes = new Map<NodeId, NodeAnalysis>();\n const summary = {\n totalNodes: 0, staticNodes: 0, staticSubtrees: 0, dynamicTextNodes: 0,\n dynamicAttrNodes: 0, eventNodes: 0, lists: 0, conditionals: 0,\n };\n\n const visit = (node: GraphNode): boolean => {\n // Post-order: children first so subtree rollup is exact.\n let allChildrenStatic = true;\n for (const child of node.children) {\n const childSubtreeStatic = visit(child);\n if (!childSubtreeStatic) allChildrenStatic = false;\n }\n\n let hasDynamicText = false;\n let hasDynamicAttr = false;\n for (const ref of node.stateRefs) {\n if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;\n else hasDynamicAttr = true;\n }\n const hasEvents = node.events.length > 0;\n const isList = node.type === 'reactive-list';\n const isConditional = node.type === 'conditional';\n const isStatic =\n node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;\n const isStaticSubtree = isStatic && allChildrenStatic;\n\n nodes.set(node.id, {\n isStatic, isStaticSubtree, hasDynamicText, hasDynamicAttr,\n hasEvents, isList, isConditional,\n });\n\n summary.totalNodes += 1;\n if (isStatic) summary.staticNodes += 1;\n if (isStaticSubtree) summary.staticSubtrees += 1;\n if (hasDynamicText) summary.dynamicTextNodes += 1;\n if (hasDynamicAttr) summary.dynamicAttrNodes += 1;\n if (hasEvents) summary.eventNodes += 1;\n if (isList) summary.lists += 1;\n if (isConditional) summary.conditionals += 1;\n\n return isStaticSubtree;\n };\n\n visit(graph.root);\n return { nodes, summary };\n}\n","/**\n * Diagnostic compiler-inspection mode (v1.2, spec §14).\n *\n * Produces a machine-readable (and optionally text-formatted) description of\n * what the compiler understands about an application: which nodes are static\n * vs dynamic, which carry dynamic text or attributes, which wire events, and\n * which are conditional regions or keyed lists — plus the hydration metadata\n * derived from that classification.\n *\n * This is a DIAGNOSTIC tool. It is not part of the runtime, is not consulted\n * during mount, and is tree-shakeable out of any app that never calls it.\n */\n\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\nimport { analyzeGraph, type GraphAnalysisSummary } from './analyze.js';\n\nexport interface InspectedCompilationNode {\n readonly id: string;\n readonly type: string;\n readonly depth: number;\n readonly classification: 'static' | 'static-subtree-root' | 'dynamic';\n readonly dynamicText: boolean;\n readonly dynamicAttrs: boolean;\n readonly events: readonly string[];\n readonly boundProps: readonly string[];\n readonly isList: boolean;\n readonly isConditional: boolean;\n /** Hydration hint: how the hydration path should treat this node. */\n readonly hydration: 'adopt-static' | 'verify-dynamic';\n}\n\nexport interface CompilerInspection {\n readonly name: string;\n readonly version: string;\n readonly summary: GraphAnalysisSummary & {\n /** Fraction of nodes provably static (0..1). */\n readonly staticRatio: number;\n };\n readonly nodes: readonly InspectedCompilationNode[];\n}\n\n/**\n * Inspect a compiled/built graph and return a structured diagnostic report.\n * Purely observational — never mutates the graph.\n */\nexport function inspectCompilation(graph: ApplicationGraph): CompilerInspection {\n const analysis = analyzeGraph(graph);\n const nodes: InspectedCompilationNode[] = [];\n\n const walk = (node: GraphNode, depth: number): void => {\n const a = analysis.nodes.get(node.id);\n if (a !== undefined) {\n const classification: InspectedCompilationNode['classification'] = a.isStaticSubtree\n ? 'static-subtree-root'\n : a.isStatic\n ? 'static'\n : 'dynamic';\n nodes.push({\n id: node.id,\n type: node.type,\n depth,\n classification,\n dynamicText: a.hasDynamicText,\n dynamicAttrs: a.hasDynamicAttr,\n events: node.events.map((e) => e.type),\n boundProps: node.stateRefs.map((r) => r.propKey),\n isList: a.isList,\n isConditional: a.isConditional,\n hydration: a.isStaticSubtree ? 'adopt-static' : 'verify-dynamic',\n });\n }\n for (const child of node.children) walk(child, depth + 1);\n };\n walk(graph.root, 0);\n\n const staticRatio =\n analysis.summary.totalNodes === 0\n ? 0\n : analysis.summary.staticNodes / analysis.summary.totalNodes;\n\n return {\n name: graph.name,\n version: graph.version,\n summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },\n nodes,\n };\n}\n\n/** Render an inspection as a compact human-readable text report. */\nexport function formatInspection(inspection: CompilerInspection): string {\n const s = inspection.summary;\n const lines: string[] = [];\n lines.push(`StreetUI compiler inspection — ${inspection.name} v${inspection.version}`);\n lines.push(\n ` nodes=${s.totalNodes} static=${s.staticNodes} ` +\n `staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} ` +\n `dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} ` +\n `lists=${s.lists} conditionals=${s.conditionals} ` +\n `staticRatio=${(s.staticRatio * 100).toFixed(1)}%`,\n );\n for (const n of inspection.nodes) {\n const flags: string[] = [];\n if (n.dynamicText) flags.push('text');\n if (n.dynamicAttrs) flags.push('attr:' + n.boundProps.join(','));\n if (n.events.length > 0) flags.push('on:' + n.events.join(','));\n if (n.isList) flags.push('list');\n if (n.isConditional) flags.push('cond');\n lines.push(\n ` ${' '.repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` +\n (flags.length > 0 ? ` {${flags.join(' ')}}` : ''),\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { NodeId } from '@streetui/core';
|
|
2
|
+
import { ApplicationGraph } from '@streetui/graph';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Static graph analysis (v1.2, spec §3/§12/§14).
|
|
6
|
+
*
|
|
7
|
+
* A single post-order walk over the Semantic Application Graph that classifies
|
|
8
|
+
* every node as static (no bound signals, no events, not a reactive
|
|
9
|
+
* region) or dynamic, and rolls that up into whole-static-subtree flags. This
|
|
10
|
+
* is COMPILE-TIME metadata only — it introduces no virtual DOM, no second tree
|
|
11
|
+
* and no runtime reactive system. It is consumed by:
|
|
12
|
+
* - the hydration path, to skip per-node attribute/text re-verification on
|
|
13
|
+
* provably-static subtrees (§12), and
|
|
14
|
+
* - the diagnostic compiler-inspection report (§14).
|
|
15
|
+
*
|
|
16
|
+
* The renderer's initial-mount fast path does not need this map: it already
|
|
17
|
+
* skips reactive wiring per node via cheap `events.length`/`stateRefs.length`
|
|
18
|
+
* guards. Keeping the analysis out of the mount hot path avoids adding lookup
|
|
19
|
+
* cost (and keeps the shipped runtime lean).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface NodeAnalysis {
|
|
23
|
+
/** No bound signals, no events, and not a reactive-list/conditional region. */
|
|
24
|
+
readonly isStatic: boolean;
|
|
25
|
+
/** This node is static AND every descendant is a static subtree. */
|
|
26
|
+
readonly isStaticSubtree: boolean;
|
|
27
|
+
/** A signal is bound to this node's text/label/value content. */
|
|
28
|
+
readonly hasDynamicText: boolean;
|
|
29
|
+
/** A signal is bound to a non-content prop (a reactive attribute). */
|
|
30
|
+
readonly hasDynamicAttr: boolean;
|
|
31
|
+
/** This node wires one or more DOM event handlers. */
|
|
32
|
+
readonly hasEvents: boolean;
|
|
33
|
+
/** This node is a keyed reactive list. */
|
|
34
|
+
readonly isList: boolean;
|
|
35
|
+
/** This node is a conditional (0..1 branch) region. */
|
|
36
|
+
readonly isConditional: boolean;
|
|
37
|
+
}
|
|
38
|
+
interface GraphAnalysisSummary {
|
|
39
|
+
readonly totalNodes: number;
|
|
40
|
+
readonly staticNodes: number;
|
|
41
|
+
readonly staticSubtrees: number;
|
|
42
|
+
readonly dynamicTextNodes: number;
|
|
43
|
+
readonly dynamicAttrNodes: number;
|
|
44
|
+
readonly eventNodes: number;
|
|
45
|
+
readonly lists: number;
|
|
46
|
+
readonly conditionals: number;
|
|
47
|
+
}
|
|
48
|
+
interface GraphAnalysis {
|
|
49
|
+
readonly nodes: ReadonlyMap<NodeId, NodeAnalysis>;
|
|
50
|
+
readonly summary: GraphAnalysisSummary;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Analyze a fully-built graph. O(n) single post-order pass; allocates one small
|
|
54
|
+
* record per node. Safe to skip entirely when neither hydration nor diagnostics
|
|
55
|
+
* need it.
|
|
56
|
+
*/
|
|
57
|
+
declare function analyzeGraph(graph: ApplicationGraph): GraphAnalysis;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Diagnostic compiler-inspection mode (v1.2, spec §14).
|
|
61
|
+
*
|
|
62
|
+
* Produces a machine-readable (and optionally text-formatted) description of
|
|
63
|
+
* what the compiler understands about an application: which nodes are static
|
|
64
|
+
* vs dynamic, which carry dynamic text or attributes, which wire events, and
|
|
65
|
+
* which are conditional regions or keyed lists — plus the hydration metadata
|
|
66
|
+
* derived from that classification.
|
|
67
|
+
*
|
|
68
|
+
* This is a DIAGNOSTIC tool. It is not part of the runtime, is not consulted
|
|
69
|
+
* during mount, and is tree-shakeable out of any app that never calls it.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
interface InspectedCompilationNode {
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly type: string;
|
|
75
|
+
readonly depth: number;
|
|
76
|
+
readonly classification: 'static' | 'static-subtree-root' | 'dynamic';
|
|
77
|
+
readonly dynamicText: boolean;
|
|
78
|
+
readonly dynamicAttrs: boolean;
|
|
79
|
+
readonly events: readonly string[];
|
|
80
|
+
readonly boundProps: readonly string[];
|
|
81
|
+
readonly isList: boolean;
|
|
82
|
+
readonly isConditional: boolean;
|
|
83
|
+
/** Hydration hint: how the hydration path should treat this node. */
|
|
84
|
+
readonly hydration: 'adopt-static' | 'verify-dynamic';
|
|
85
|
+
}
|
|
86
|
+
interface CompilerInspection {
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly version: string;
|
|
89
|
+
readonly summary: GraphAnalysisSummary & {
|
|
90
|
+
/** Fraction of nodes provably static (0..1). */
|
|
91
|
+
readonly staticRatio: number;
|
|
92
|
+
};
|
|
93
|
+
readonly nodes: readonly InspectedCompilationNode[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Inspect a compiled/built graph and return a structured diagnostic report.
|
|
97
|
+
* Purely observational — never mutates the graph.
|
|
98
|
+
*/
|
|
99
|
+
declare function inspectCompilation(graph: ApplicationGraph): CompilerInspection;
|
|
100
|
+
/** Render an inspection as a compact human-readable text report. */
|
|
101
|
+
declare function formatInspection(inspection: CompilerInspection): string;
|
|
102
|
+
|
|
103
|
+
export { type CompilerInspection, type GraphAnalysis, type GraphAnalysisSummary, type InspectedCompilationNode, type NodeAnalysis, analyzeGraph, formatInspection, inspectCompilation };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { NodeId } from '@streetui/core';
|
|
2
|
+
import { ApplicationGraph } from '@streetui/graph';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Static graph analysis (v1.2, spec §3/§12/§14).
|
|
6
|
+
*
|
|
7
|
+
* A single post-order walk over the Semantic Application Graph that classifies
|
|
8
|
+
* every node as static (no bound signals, no events, not a reactive
|
|
9
|
+
* region) or dynamic, and rolls that up into whole-static-subtree flags. This
|
|
10
|
+
* is COMPILE-TIME metadata only — it introduces no virtual DOM, no second tree
|
|
11
|
+
* and no runtime reactive system. It is consumed by:
|
|
12
|
+
* - the hydration path, to skip per-node attribute/text re-verification on
|
|
13
|
+
* provably-static subtrees (§12), and
|
|
14
|
+
* - the diagnostic compiler-inspection report (§14).
|
|
15
|
+
*
|
|
16
|
+
* The renderer's initial-mount fast path does not need this map: it already
|
|
17
|
+
* skips reactive wiring per node via cheap `events.length`/`stateRefs.length`
|
|
18
|
+
* guards. Keeping the analysis out of the mount hot path avoids adding lookup
|
|
19
|
+
* cost (and keeps the shipped runtime lean).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface NodeAnalysis {
|
|
23
|
+
/** No bound signals, no events, and not a reactive-list/conditional region. */
|
|
24
|
+
readonly isStatic: boolean;
|
|
25
|
+
/** This node is static AND every descendant is a static subtree. */
|
|
26
|
+
readonly isStaticSubtree: boolean;
|
|
27
|
+
/** A signal is bound to this node's text/label/value content. */
|
|
28
|
+
readonly hasDynamicText: boolean;
|
|
29
|
+
/** A signal is bound to a non-content prop (a reactive attribute). */
|
|
30
|
+
readonly hasDynamicAttr: boolean;
|
|
31
|
+
/** This node wires one or more DOM event handlers. */
|
|
32
|
+
readonly hasEvents: boolean;
|
|
33
|
+
/** This node is a keyed reactive list. */
|
|
34
|
+
readonly isList: boolean;
|
|
35
|
+
/** This node is a conditional (0..1 branch) region. */
|
|
36
|
+
readonly isConditional: boolean;
|
|
37
|
+
}
|
|
38
|
+
interface GraphAnalysisSummary {
|
|
39
|
+
readonly totalNodes: number;
|
|
40
|
+
readonly staticNodes: number;
|
|
41
|
+
readonly staticSubtrees: number;
|
|
42
|
+
readonly dynamicTextNodes: number;
|
|
43
|
+
readonly dynamicAttrNodes: number;
|
|
44
|
+
readonly eventNodes: number;
|
|
45
|
+
readonly lists: number;
|
|
46
|
+
readonly conditionals: number;
|
|
47
|
+
}
|
|
48
|
+
interface GraphAnalysis {
|
|
49
|
+
readonly nodes: ReadonlyMap<NodeId, NodeAnalysis>;
|
|
50
|
+
readonly summary: GraphAnalysisSummary;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Analyze a fully-built graph. O(n) single post-order pass; allocates one small
|
|
54
|
+
* record per node. Safe to skip entirely when neither hydration nor diagnostics
|
|
55
|
+
* need it.
|
|
56
|
+
*/
|
|
57
|
+
declare function analyzeGraph(graph: ApplicationGraph): GraphAnalysis;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Diagnostic compiler-inspection mode (v1.2, spec §14).
|
|
61
|
+
*
|
|
62
|
+
* Produces a machine-readable (and optionally text-formatted) description of
|
|
63
|
+
* what the compiler understands about an application: which nodes are static
|
|
64
|
+
* vs dynamic, which carry dynamic text or attributes, which wire events, and
|
|
65
|
+
* which are conditional regions or keyed lists — plus the hydration metadata
|
|
66
|
+
* derived from that classification.
|
|
67
|
+
*
|
|
68
|
+
* This is a DIAGNOSTIC tool. It is not part of the runtime, is not consulted
|
|
69
|
+
* during mount, and is tree-shakeable out of any app that never calls it.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
interface InspectedCompilationNode {
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly type: string;
|
|
75
|
+
readonly depth: number;
|
|
76
|
+
readonly classification: 'static' | 'static-subtree-root' | 'dynamic';
|
|
77
|
+
readonly dynamicText: boolean;
|
|
78
|
+
readonly dynamicAttrs: boolean;
|
|
79
|
+
readonly events: readonly string[];
|
|
80
|
+
readonly boundProps: readonly string[];
|
|
81
|
+
readonly isList: boolean;
|
|
82
|
+
readonly isConditional: boolean;
|
|
83
|
+
/** Hydration hint: how the hydration path should treat this node. */
|
|
84
|
+
readonly hydration: 'adopt-static' | 'verify-dynamic';
|
|
85
|
+
}
|
|
86
|
+
interface CompilerInspection {
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly version: string;
|
|
89
|
+
readonly summary: GraphAnalysisSummary & {
|
|
90
|
+
/** Fraction of nodes provably static (0..1). */
|
|
91
|
+
readonly staticRatio: number;
|
|
92
|
+
};
|
|
93
|
+
readonly nodes: readonly InspectedCompilationNode[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Inspect a compiled/built graph and return a structured diagnostic report.
|
|
97
|
+
* Purely observational — never mutates the graph.
|
|
98
|
+
*/
|
|
99
|
+
declare function inspectCompilation(graph: ApplicationGraph): CompilerInspection;
|
|
100
|
+
/** Render an inspection as a compact human-readable text report. */
|
|
101
|
+
declare function formatInspection(inspection: CompilerInspection): string;
|
|
102
|
+
|
|
103
|
+
export { type CompilerInspection, type GraphAnalysis, type GraphAnalysisSummary, type InspectedCompilationNode, type NodeAnalysis, analyzeGraph, formatInspection, inspectCompilation };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/analysis/analyze.ts
|
|
2
|
+
var TEXT_PROP_KEYS = /* @__PURE__ */ new Set(["text", "label", "value"]);
|
|
3
|
+
function analyzeGraph(graph) {
|
|
4
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
5
|
+
const summary = {
|
|
6
|
+
totalNodes: 0,
|
|
7
|
+
staticNodes: 0,
|
|
8
|
+
staticSubtrees: 0,
|
|
9
|
+
dynamicTextNodes: 0,
|
|
10
|
+
dynamicAttrNodes: 0,
|
|
11
|
+
eventNodes: 0,
|
|
12
|
+
lists: 0,
|
|
13
|
+
conditionals: 0
|
|
14
|
+
};
|
|
15
|
+
const visit = (node) => {
|
|
16
|
+
let allChildrenStatic = true;
|
|
17
|
+
for (const child of node.children) {
|
|
18
|
+
const childSubtreeStatic = visit(child);
|
|
19
|
+
if (!childSubtreeStatic) allChildrenStatic = false;
|
|
20
|
+
}
|
|
21
|
+
let hasDynamicText = false;
|
|
22
|
+
let hasDynamicAttr = false;
|
|
23
|
+
for (const ref of node.stateRefs) {
|
|
24
|
+
if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;
|
|
25
|
+
else hasDynamicAttr = true;
|
|
26
|
+
}
|
|
27
|
+
const hasEvents = node.events.length > 0;
|
|
28
|
+
const isList = node.type === "reactive-list";
|
|
29
|
+
const isConditional = node.type === "conditional";
|
|
30
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;
|
|
31
|
+
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
32
|
+
nodes.set(node.id, {
|
|
33
|
+
isStatic,
|
|
34
|
+
isStaticSubtree,
|
|
35
|
+
hasDynamicText,
|
|
36
|
+
hasDynamicAttr,
|
|
37
|
+
hasEvents,
|
|
38
|
+
isList,
|
|
39
|
+
isConditional
|
|
40
|
+
});
|
|
41
|
+
summary.totalNodes += 1;
|
|
42
|
+
if (isStatic) summary.staticNodes += 1;
|
|
43
|
+
if (isStaticSubtree) summary.staticSubtrees += 1;
|
|
44
|
+
if (hasDynamicText) summary.dynamicTextNodes += 1;
|
|
45
|
+
if (hasDynamicAttr) summary.dynamicAttrNodes += 1;
|
|
46
|
+
if (hasEvents) summary.eventNodes += 1;
|
|
47
|
+
if (isList) summary.lists += 1;
|
|
48
|
+
if (isConditional) summary.conditionals += 1;
|
|
49
|
+
return isStaticSubtree;
|
|
50
|
+
};
|
|
51
|
+
visit(graph.root);
|
|
52
|
+
return { nodes, summary };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/analysis/inspect.ts
|
|
56
|
+
function inspectCompilation(graph) {
|
|
57
|
+
const analysis = analyzeGraph(graph);
|
|
58
|
+
const nodes = [];
|
|
59
|
+
const walk = (node, depth) => {
|
|
60
|
+
const a = analysis.nodes.get(node.id);
|
|
61
|
+
if (a !== void 0) {
|
|
62
|
+
const classification = a.isStaticSubtree ? "static-subtree-root" : a.isStatic ? "static" : "dynamic";
|
|
63
|
+
nodes.push({
|
|
64
|
+
id: node.id,
|
|
65
|
+
type: node.type,
|
|
66
|
+
depth,
|
|
67
|
+
classification,
|
|
68
|
+
dynamicText: a.hasDynamicText,
|
|
69
|
+
dynamicAttrs: a.hasDynamicAttr,
|
|
70
|
+
events: node.events.map((e) => e.type),
|
|
71
|
+
boundProps: node.stateRefs.map((r) => r.propKey),
|
|
72
|
+
isList: a.isList,
|
|
73
|
+
isConditional: a.isConditional,
|
|
74
|
+
hydration: a.isStaticSubtree ? "adopt-static" : "verify-dynamic"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
for (const child of node.children) walk(child, depth + 1);
|
|
78
|
+
};
|
|
79
|
+
walk(graph.root, 0);
|
|
80
|
+
const staticRatio = analysis.summary.totalNodes === 0 ? 0 : analysis.summary.staticNodes / analysis.summary.totalNodes;
|
|
81
|
+
return {
|
|
82
|
+
name: graph.name,
|
|
83
|
+
version: graph.version,
|
|
84
|
+
summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },
|
|
85
|
+
nodes
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function formatInspection(inspection) {
|
|
89
|
+
const s = inspection.summary;
|
|
90
|
+
const lines = [];
|
|
91
|
+
lines.push(`StreetUI compiler inspection \u2014 ${inspection.name} v${inspection.version}`);
|
|
92
|
+
lines.push(
|
|
93
|
+
` nodes=${s.totalNodes} static=${s.staticNodes} staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} lists=${s.lists} conditionals=${s.conditionals} staticRatio=${(s.staticRatio * 100).toFixed(1)}%`
|
|
94
|
+
);
|
|
95
|
+
for (const n of inspection.nodes) {
|
|
96
|
+
const flags = [];
|
|
97
|
+
if (n.dynamicText) flags.push("text");
|
|
98
|
+
if (n.dynamicAttrs) flags.push("attr:" + n.boundProps.join(","));
|
|
99
|
+
if (n.events.length > 0) flags.push("on:" + n.events.join(","));
|
|
100
|
+
if (n.isList) flags.push("list");
|
|
101
|
+
if (n.isConditional) flags.push("cond");
|
|
102
|
+
lines.push(
|
|
103
|
+
` ${" ".repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` + (flags.length > 0 ? ` {${flags.join(" ")}}` : "")
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
analyzeGraph,
|
|
110
|
+
formatInspection,
|
|
111
|
+
inspectCompilation
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/analysis/analyze.ts","../src/analysis/inspect.ts"],"sourcesContent":["/**\n * Static graph analysis (v1.2, spec §3/§12/§14).\n *\n * A single post-order walk over the Semantic Application Graph that classifies\n * every node as static (no bound signals, no events, not a reactive\n * region) or dynamic, and rolls that up into whole-static-subtree flags. This\n * is COMPILE-TIME metadata only — it introduces no virtual DOM, no second tree\n * and no runtime reactive system. It is consumed by:\n * - the hydration path, to skip per-node attribute/text re-verification on\n * provably-static subtrees (§12), and\n * - the diagnostic compiler-inspection report (§14).\n *\n * The renderer's initial-mount fast path does not need this map: it already\n * skips reactive wiring per node via cheap `events.length`/`stateRefs.length`\n * guards. Keeping the analysis out of the mount hot path avoids adding lookup\n * cost (and keeps the shipped runtime lean).\n */\n\nimport type { NodeId } from '@streetui/core';\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\n\n/** Prop keys whose bound signal drives text content rather than an attribute. */\nconst TEXT_PROP_KEYS: ReadonlySet<string> = new Set(['text', 'label', 'value']);\n\nexport interface NodeAnalysis {\n /** No bound signals, no events, and not a reactive-list/conditional region. */\n readonly isStatic: boolean;\n /** This node is static AND every descendant is a static subtree. */\n readonly isStaticSubtree: boolean;\n /** A signal is bound to this node's text/label/value content. */\n readonly hasDynamicText: boolean;\n /** A signal is bound to a non-content prop (a reactive attribute). */\n readonly hasDynamicAttr: boolean;\n /** This node wires one or more DOM event handlers. */\n readonly hasEvents: boolean;\n /** This node is a keyed reactive list. */\n readonly isList: boolean;\n /** This node is a conditional (0..1 branch) region. */\n readonly isConditional: boolean;\n}\n\nexport interface GraphAnalysisSummary {\n readonly totalNodes: number;\n readonly staticNodes: number;\n readonly staticSubtrees: number;\n readonly dynamicTextNodes: number;\n readonly dynamicAttrNodes: number;\n readonly eventNodes: number;\n readonly lists: number;\n readonly conditionals: number;\n}\n\nexport interface GraphAnalysis {\n readonly nodes: ReadonlyMap<NodeId, NodeAnalysis>;\n readonly summary: GraphAnalysisSummary;\n}\n\n/**\n * Analyze a fully-built graph. O(n) single post-order pass; allocates one small\n * record per node. Safe to skip entirely when neither hydration nor diagnostics\n * need it.\n */\nexport function analyzeGraph(graph: ApplicationGraph): GraphAnalysis {\n const nodes = new Map<NodeId, NodeAnalysis>();\n const summary = {\n totalNodes: 0, staticNodes: 0, staticSubtrees: 0, dynamicTextNodes: 0,\n dynamicAttrNodes: 0, eventNodes: 0, lists: 0, conditionals: 0,\n };\n\n const visit = (node: GraphNode): boolean => {\n // Post-order: children first so subtree rollup is exact.\n let allChildrenStatic = true;\n for (const child of node.children) {\n const childSubtreeStatic = visit(child);\n if (!childSubtreeStatic) allChildrenStatic = false;\n }\n\n let hasDynamicText = false;\n let hasDynamicAttr = false;\n for (const ref of node.stateRefs) {\n if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;\n else hasDynamicAttr = true;\n }\n const hasEvents = node.events.length > 0;\n const isList = node.type === 'reactive-list';\n const isConditional = node.type === 'conditional';\n const isStatic =\n node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;\n const isStaticSubtree = isStatic && allChildrenStatic;\n\n nodes.set(node.id, {\n isStatic, isStaticSubtree, hasDynamicText, hasDynamicAttr,\n hasEvents, isList, isConditional,\n });\n\n summary.totalNodes += 1;\n if (isStatic) summary.staticNodes += 1;\n if (isStaticSubtree) summary.staticSubtrees += 1;\n if (hasDynamicText) summary.dynamicTextNodes += 1;\n if (hasDynamicAttr) summary.dynamicAttrNodes += 1;\n if (hasEvents) summary.eventNodes += 1;\n if (isList) summary.lists += 1;\n if (isConditional) summary.conditionals += 1;\n\n return isStaticSubtree;\n };\n\n visit(graph.root);\n return { nodes, summary };\n}\n","/**\n * Diagnostic compiler-inspection mode (v1.2, spec §14).\n *\n * Produces a machine-readable (and optionally text-formatted) description of\n * what the compiler understands about an application: which nodes are static\n * vs dynamic, which carry dynamic text or attributes, which wire events, and\n * which are conditional regions or keyed lists — plus the hydration metadata\n * derived from that classification.\n *\n * This is a DIAGNOSTIC tool. It is not part of the runtime, is not consulted\n * during mount, and is tree-shakeable out of any app that never calls it.\n */\n\nimport type { ApplicationGraph, GraphNode } from '@streetui/graph';\nimport { analyzeGraph, type GraphAnalysisSummary } from './analyze.js';\n\nexport interface InspectedCompilationNode {\n readonly id: string;\n readonly type: string;\n readonly depth: number;\n readonly classification: 'static' | 'static-subtree-root' | 'dynamic';\n readonly dynamicText: boolean;\n readonly dynamicAttrs: boolean;\n readonly events: readonly string[];\n readonly boundProps: readonly string[];\n readonly isList: boolean;\n readonly isConditional: boolean;\n /** Hydration hint: how the hydration path should treat this node. */\n readonly hydration: 'adopt-static' | 'verify-dynamic';\n}\n\nexport interface CompilerInspection {\n readonly name: string;\n readonly version: string;\n readonly summary: GraphAnalysisSummary & {\n /** Fraction of nodes provably static (0..1). */\n readonly staticRatio: number;\n };\n readonly nodes: readonly InspectedCompilationNode[];\n}\n\n/**\n * Inspect a compiled/built graph and return a structured diagnostic report.\n * Purely observational — never mutates the graph.\n */\nexport function inspectCompilation(graph: ApplicationGraph): CompilerInspection {\n const analysis = analyzeGraph(graph);\n const nodes: InspectedCompilationNode[] = [];\n\n const walk = (node: GraphNode, depth: number): void => {\n const a = analysis.nodes.get(node.id);\n if (a !== undefined) {\n const classification: InspectedCompilationNode['classification'] = a.isStaticSubtree\n ? 'static-subtree-root'\n : a.isStatic\n ? 'static'\n : 'dynamic';\n nodes.push({\n id: node.id,\n type: node.type,\n depth,\n classification,\n dynamicText: a.hasDynamicText,\n dynamicAttrs: a.hasDynamicAttr,\n events: node.events.map((e) => e.type),\n boundProps: node.stateRefs.map((r) => r.propKey),\n isList: a.isList,\n isConditional: a.isConditional,\n hydration: a.isStaticSubtree ? 'adopt-static' : 'verify-dynamic',\n });\n }\n for (const child of node.children) walk(child, depth + 1);\n };\n walk(graph.root, 0);\n\n const staticRatio =\n analysis.summary.totalNodes === 0\n ? 0\n : analysis.summary.staticNodes / analysis.summary.totalNodes;\n\n return {\n name: graph.name,\n version: graph.version,\n summary: { ...analysis.summary, staticRatio: +staticRatio.toFixed(4) },\n nodes,\n };\n}\n\n/** Render an inspection as a compact human-readable text report. */\nexport function formatInspection(inspection: CompilerInspection): string {\n const s = inspection.summary;\n const lines: string[] = [];\n lines.push(`StreetUI compiler inspection — ${inspection.name} v${inspection.version}`);\n lines.push(\n ` nodes=${s.totalNodes} static=${s.staticNodes} ` +\n `staticSubtrees=${s.staticSubtrees} dynamicText=${s.dynamicTextNodes} ` +\n `dynamicAttrs=${s.dynamicAttrNodes} events=${s.eventNodes} ` +\n `lists=${s.lists} conditionals=${s.conditionals} ` +\n `staticRatio=${(s.staticRatio * 100).toFixed(1)}%`,\n );\n for (const n of inspection.nodes) {\n const flags: string[] = [];\n if (n.dynamicText) flags.push('text');\n if (n.dynamicAttrs) flags.push('attr:' + n.boundProps.join(','));\n if (n.events.length > 0) flags.push('on:' + n.events.join(','));\n if (n.isList) flags.push('list');\n if (n.isConditional) flags.push('cond');\n lines.push(\n ` ${' '.repeat(n.depth)}${n.type}#${n.id} [${n.classification}]` +\n (flags.length > 0 ? ` {${flags.join(' ')}}` : ''),\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streetui/compiler",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "StreetUI compiler — DSL → validation → Semantic Application Graph",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
"types": "./dist/index.d.cts",
|
|
17
17
|
"default": "./dist/index.cjs"
|
|
18
18
|
}
|
|
19
|
+
},
|
|
20
|
+
"./diagnostics": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/diagnostics.d.ts",
|
|
23
|
+
"default": "./dist/diagnostics.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/diagnostics.d.cts",
|
|
27
|
+
"default": "./dist/diagnostics.cjs"
|
|
28
|
+
}
|
|
19
29
|
}
|
|
20
30
|
},
|
|
21
31
|
"scripts": {
|
|
@@ -25,10 +35,10 @@
|
|
|
25
35
|
"clean": "rm -rf dist"
|
|
26
36
|
},
|
|
27
37
|
"dependencies": {
|
|
28
|
-
"@streetui/core": "1.
|
|
29
|
-
"@streetui/graph": "1.
|
|
30
|
-
"@streetui/dsl": "1.
|
|
31
|
-
"@streetui/state": "1.
|
|
38
|
+
"@streetui/core": "1.3.0",
|
|
39
|
+
"@streetui/graph": "1.3.0",
|
|
40
|
+
"@streetui/dsl": "1.3.0",
|
|
41
|
+
"@streetui/state": "1.3.0"
|
|
32
42
|
},
|
|
33
43
|
"devDependencies": {
|
|
34
44
|
"typescript": "*",
|