@route-intelligence/visualizer 2.1.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/App.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import * as react from 'react';
2
+ import { SerializedGraph } from '@route-intelligence/shared';
3
+
4
+ type LayoutType = 'hierarchical' | 'force' | 'radial' | 'flow';
5
+ interface VisualizerProps {
6
+ graph: SerializedGraph;
7
+ layout?: LayoutType;
8
+ }
9
+ interface OverlayFilters {
10
+ showPublic: boolean;
11
+ showAuth: boolean;
12
+ showAdmin: boolean;
13
+ showMiddleware: boolean;
14
+ showApi: boolean;
15
+ showDynamic: boolean;
16
+ showRedirects: boolean;
17
+ showDead: boolean;
18
+ }
19
+ declare function RouteVisualizer({ graph, layout }: VisualizerProps): react.JSX.Element;
20
+
21
+ export { type LayoutType, type OverlayFilters, RouteVisualizer, type VisualizerProps, RouteVisualizer as default };
package/dist/App.js ADDED
@@ -0,0 +1,8 @@
1
+ import {
2
+ App_default,
3
+ RouteVisualizer
4
+ } from "./chunk-6ML5PYGT.js";
5
+ export {
6
+ RouteVisualizer,
7
+ App_default as default
8
+ };
@@ -0,0 +1,231 @@
1
+ // src/App.tsx
2
+ import {
3
+ Background,
4
+ Controls,
5
+ MiniMap,
6
+ ReactFlow,
7
+ useEdgesState,
8
+ useNodesState
9
+ } from "@xyflow/react";
10
+ import "@xyflow/react/dist/style.css";
11
+ import { useCallback, useMemo, useState } from "react";
12
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
+ var defaultFilters = {
14
+ showPublic: true,
15
+ showAuth: true,
16
+ showAdmin: true,
17
+ showMiddleware: true,
18
+ showApi: true,
19
+ showDynamic: true,
20
+ showRedirects: true,
21
+ showDead: false
22
+ };
23
+ function graphToFlowNodes(graph, filters, search) {
24
+ return graph.nodes.filter((n) => {
25
+ if (!filters.showDead && n.attributes.isDead) return false;
26
+ if (!filters.showApi && n.attributes.type === "api-route") return false;
27
+ if (!filters.showDynamic && n.attributes.isDynamic) return false;
28
+ if (!filters.showMiddleware && n.attributes.type === "middleware") return false;
29
+ if (search && !n.attributes.path.toLowerCase().includes(search.toLowerCase())) return false;
30
+ return true;
31
+ }).map((n, i) => ({
32
+ id: n.id,
33
+ position: { x: i % 5 * 220, y: Math.floor(i / 5) * 120 },
34
+ data: {
35
+ label: `${n.attributes.type}
36
+ ${n.attributes.path}`,
37
+ ...n.attributes
38
+ },
39
+ style: {
40
+ background: n.attributes.isDead ? "#3f1515" : "#1e3a5f",
41
+ color: "#fff",
42
+ border: `1px solid ${n.attributes.isDead ? "#ef4444" : "#3b82f6"}`,
43
+ borderRadius: 8,
44
+ padding: 8,
45
+ fontSize: 12,
46
+ whiteSpace: "pre-wrap"
47
+ }
48
+ }));
49
+ }
50
+ function graphToFlowEdges(graph, filters) {
51
+ return graph.edges.filter((e) => {
52
+ if (!filters.showRedirects && (e.attributes.type === "redirect" || e.attributes.type === "rewrite")) {
53
+ return false;
54
+ }
55
+ return true;
56
+ }).map((e) => ({
57
+ id: e.id,
58
+ source: e.source,
59
+ target: e.target,
60
+ label: e.attributes.type,
61
+ animated: e.attributes.type === "navigation",
62
+ style: { stroke: e.attributes.isExternal ? "#f59e0b" : "#64748b" }
63
+ }));
64
+ }
65
+ function RouteVisualizer({ graph, layout = "hierarchical" }) {
66
+ const [filters, setFilters] = useState(defaultFilters);
67
+ const [search, setSearch] = useState("");
68
+ const [selectedNode, setSelectedNode] = useState(null);
69
+ const initialNodes = useMemo(
70
+ () => graphToFlowNodes(graph, filters, search),
71
+ [graph, filters, search]
72
+ );
73
+ const initialEdges = useMemo(() => graphToFlowEdges(graph, filters), [graph, filters]);
74
+ const [nodes, , onNodesChange] = useNodesState(initialNodes);
75
+ const [edges, , onEdgesChange] = useEdgesState(initialEdges);
76
+ const onNodeClick = useCallback((_, node) => {
77
+ setSelectedNode(node.id);
78
+ }, []);
79
+ const selected = graph.nodes.find((n) => n.id === selectedNode);
80
+ return /* @__PURE__ */ jsxs("div", { style: { width: "100%", height: "100vh", display: "flex", flexDirection: "column" }, children: [
81
+ /* @__PURE__ */ jsxs(
82
+ "header",
83
+ {
84
+ style: {
85
+ padding: "1rem",
86
+ background: "#111",
87
+ borderBottom: "1px solid #333",
88
+ display: "flex",
89
+ gap: "1rem",
90
+ alignItems: "center",
91
+ flexWrap: "wrap"
92
+ },
93
+ children: [
94
+ /* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: "1.25rem" }, children: "Route Intelligence" }),
95
+ /* @__PURE__ */ jsx(
96
+ "input",
97
+ {
98
+ type: "search",
99
+ placeholder: "Search routes...",
100
+ value: search,
101
+ onChange: (e) => setSearch(e.target.value),
102
+ style: {
103
+ flex: 1,
104
+ minWidth: 200,
105
+ padding: "0.5rem",
106
+ background: "#1a1a1a",
107
+ border: "1px solid #333",
108
+ color: "#fff",
109
+ borderRadius: 4
110
+ }
111
+ }
112
+ ),
113
+ /* @__PURE__ */ jsxs(
114
+ "select",
115
+ {
116
+ value: layout,
117
+ onChange: () => {
118
+ },
119
+ style: {
120
+ padding: "0.5rem",
121
+ background: "#1a1a1a",
122
+ color: "#fff",
123
+ border: "1px solid #333"
124
+ },
125
+ children: [
126
+ /* @__PURE__ */ jsx("option", { value: "hierarchical", children: "Hierarchical" }),
127
+ /* @__PURE__ */ jsx("option", { value: "force", children: "Force" }),
128
+ /* @__PURE__ */ jsx("option", { value: "radial", children: "Radial" }),
129
+ /* @__PURE__ */ jsx("option", { value: "flow", children: "Flow Chart" })
130
+ ]
131
+ }
132
+ ),
133
+ ["showDead", "showApi", "showDynamic", "showMiddleware", "showRedirects"].map(
134
+ (key) => /* @__PURE__ */ jsxs("label", { style: { fontSize: "0.875rem" }, children: [
135
+ /* @__PURE__ */ jsx(
136
+ "input",
137
+ {
138
+ type: "checkbox",
139
+ checked: filters[key],
140
+ onChange: (e) => setFilters((f) => ({ ...f, [key]: e.target.checked }))
141
+ }
142
+ ),
143
+ " ",
144
+ key.replace("show", "")
145
+ ] }, key)
146
+ )
147
+ ]
148
+ }
149
+ ),
150
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex" }, children: [
151
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(
152
+ ReactFlow,
153
+ {
154
+ nodes,
155
+ edges,
156
+ onNodesChange,
157
+ onEdgesChange,
158
+ onNodeClick,
159
+ fitView: true,
160
+ children: [
161
+ /* @__PURE__ */ jsx(Background, {}),
162
+ /* @__PURE__ */ jsx(Controls, {}),
163
+ /* @__PURE__ */ jsx(MiniMap, {})
164
+ ]
165
+ }
166
+ ) }),
167
+ selected && /* @__PURE__ */ jsxs(
168
+ "aside",
169
+ {
170
+ style: {
171
+ width: 320,
172
+ background: "#111",
173
+ borderLeft: "1px solid #333",
174
+ padding: "1rem",
175
+ overflow: "auto"
176
+ },
177
+ children: [
178
+ /* @__PURE__ */ jsx("h2", { style: { marginTop: 0 }, children: selected.attributes.path }),
179
+ /* @__PURE__ */ jsxs("p", { children: [
180
+ /* @__PURE__ */ jsx("strong", { children: "Type:" }),
181
+ " ",
182
+ selected.attributes.type
183
+ ] }),
184
+ /* @__PURE__ */ jsxs("p", { children: [
185
+ /* @__PURE__ */ jsx("strong", { children: "File:" }),
186
+ " ",
187
+ selected.attributes.filePath
188
+ ] }),
189
+ /* @__PURE__ */ jsxs("p", { children: [
190
+ /* @__PURE__ */ jsx("strong", { children: "Depth:" }),
191
+ " ",
192
+ selected.attributes.depth
193
+ ] }),
194
+ /* @__PURE__ */ jsxs("p", { children: [
195
+ /* @__PURE__ */ jsx("strong", { children: "Runtime:" }),
196
+ " ",
197
+ selected.attributes.runtime
198
+ ] }),
199
+ selected.attributes.isDead && /* @__PURE__ */ jsx("p", { style: { color: "#ef4444" }, children: "Dead route" }),
200
+ selected.attributes.conditions.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
201
+ /* @__PURE__ */ jsx("h3", { children: "Conditions" }),
202
+ /* @__PURE__ */ jsx("ul", { children: selected.attributes.conditions.map((c) => /* @__PURE__ */ jsx("li", { children: c.expression }, c.expression)) })
203
+ ] }),
204
+ selected.attributes.diagnostics.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
205
+ /* @__PURE__ */ jsx("h3", { children: "Diagnostics" }),
206
+ /* @__PURE__ */ jsx("ul", { children: selected.attributes.diagnostics.map((d) => /* @__PURE__ */ jsxs(
207
+ "li",
208
+ {
209
+ style: { color: d.severity === "error" ? "#ef4444" : "#f59e0b" },
210
+ children: [
211
+ "[",
212
+ d.ruleId,
213
+ "] ",
214
+ d.message
215
+ ]
216
+ },
217
+ `${d.ruleId}:${d.message}`
218
+ )) })
219
+ ] })
220
+ ]
221
+ }
222
+ )
223
+ ] })
224
+ ] });
225
+ }
226
+ var App_default = RouteVisualizer;
227
+
228
+ export {
229
+ RouteVisualizer,
230
+ App_default
231
+ };
@@ -0,0 +1,3 @@
1
+ export { LayoutType, OverlayFilters, RouteVisualizer, VisualizerProps, RouteVisualizer as default } from './App.js';
2
+ import 'react';
3
+ import '@route-intelligence/shared';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import {
2
+ App_default,
3
+ RouteVisualizer
4
+ } from "./chunk-6ML5PYGT.js";
5
+ export {
6
+ RouteVisualizer,
7
+ App_default as default
8
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@route-intelligence/visualizer",
3
+ "version": "2.1.0",
4
+ "description": "Interactive route graph visualizer",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "development": "./src/index.tsx",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup src/index.tsx src/App.tsx --format esm --dts --clean --external react --external react-dom --external @xyflow/react",
19
+ "dev": "vite",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "dependencies": {
23
+ "@route-intelligence/shared": "*",
24
+ "@xyflow/react": "^12.6.4",
25
+ "react": "^19.1.0",
26
+ "react-dom": "^19.1.0"
27
+ },
28
+ "devDependencies": {
29
+ "@route-intelligence/tsconfig": "*",
30
+ "@types/react": "^19.1.8",
31
+ "@types/react-dom": "^19.1.6",
32
+ "@vitejs/plugin-react": "^4.5.2",
33
+ "tsup": "^8.4.0",
34
+ "typescript": "^5.8.3",
35
+ "vite": "^6.3.5"
36
+ },
37
+ "license": "MIT",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }