@epoch32/visage 0.0.0-stage → 1.0.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/LICENSE +373 -0
- package/README.md +110 -2
- package/dist/src/api/index.js +4002 -0
- package/dist/src/charts/index.js +2717 -0
- package/dist/src/devtools/index.js +4677 -0
- package/dist/src/dom/index.js +1550 -0
- package/dist/src/form/index.js +1415 -0
- package/dist/src/index.js +11826 -0
- package/dist/src/jsx-runtime/index.js +1630 -0
- package/dist/src/query/index.js +2422 -0
- package/dist/src/router/index.js +1856 -0
- package/dist/src/rpc/index.js +3484 -0
- package/dist/src/server/index.js +1173 -0
- package/dist/src/signals/index.js +1077 -0
- package/dist/src/style/index.js +2340 -0
- package/dist/src/template/index.js +1149 -0
- package/dist/src/ui/index.js +4155 -0
- package/dist/src/virtual/index.js +1955 -0
- package/dist/src/vite/index.js +984 -0
- package/package.json +140 -4
- package/src/api/client.ts +371 -0
- package/src/api/file-router.ts +183 -0
- package/src/api/handler.ts +326 -0
- package/src/api/index.ts +71 -0
- package/src/api/middleware.ts +54 -0
- package/src/api/pragma.ts +155 -0
- package/src/api/procedure.ts +172 -0
- package/src/api/types.ts +249 -0
- package/src/charts/Area.tsx +111 -0
- package/src/charts/Axis.tsx +126 -0
- package/src/charts/Bar.tsx +86 -0
- package/src/charts/CartesianGrid.tsx +72 -0
- package/src/charts/Chart.tsx +269 -0
- package/src/charts/Cursor.tsx +42 -0
- package/src/charts/Legend.tsx +56 -0
- package/src/charts/Line.tsx +106 -0
- package/src/charts/Pie.tsx +88 -0
- package/src/charts/Tooltip.tsx +127 -0
- package/src/charts/context.ts +30 -0
- package/src/charts/index.ts +14 -0
- package/src/charts/path.ts +155 -0
- package/src/charts/scale.ts +133 -0
- package/src/charts/types.ts +50 -0
- package/src/devtools/DomTab.tsx +134 -0
- package/src/devtools/GraphInspector.tsx +196 -0
- package/src/devtools/GraphTab.tsx +315 -0
- package/src/devtools/QueryTab.tsx +200 -0
- package/src/devtools/RpcTab.tsx +190 -0
- package/src/devtools/SignalsTab.tsx +129 -0
- package/src/devtools/component.tsx +243 -0
- package/src/devtools/highlighter.ts +134 -0
- package/src/devtools/hub.ts +175 -0
- package/src/devtools/index.ts +20 -0
- package/src/devtools/styles.ts +313 -0
- package/src/devtools/types.ts +45 -0
- package/src/dom/async.ts +232 -0
- package/src/dom/control-flow.ts +353 -0
- package/src/dom/index.ts +32 -0
- package/src/dom/lazy.ts +82 -0
- package/src/dom/render.ts +286 -0
- package/src/dom/resource.ts +120 -0
- package/src/form/form.ts +218 -0
- package/src/form/index.ts +17 -0
- package/src/form/schema.ts +299 -0
- package/src/index.ts +413 -0
- package/src/jsx-dev-runtime/index.ts +1 -0
- package/src/jsx-runtime/index.ts +112 -0
- package/src/jsx-runtime/jsx.d.ts +278 -0
- package/src/query/create-mutation.ts +64 -0
- package/src/query/create-query.ts +426 -0
- package/src/query/focus-manager.ts +68 -0
- package/src/query/index.ts +63 -0
- package/src/query/mutation-cache.ts +138 -0
- package/src/query/online-manager.ts +67 -0
- package/src/query/query-cache.ts +369 -0
- package/src/query/query-client.ts +248 -0
- package/src/query/types.ts +221 -0
- package/src/query/utils.ts +132 -0
- package/src/router/index.ts +19 -0
- package/src/router/router.tsx +352 -0
- package/src/rpc/client.ts +317 -0
- package/src/rpc/error.ts +70 -0
- package/src/rpc/index.ts +52 -0
- package/src/rpc/procedure.ts +88 -0
- package/src/rpc/router.ts +105 -0
- package/src/rpc/server.ts +511 -0
- package/src/rpc/types.ts +328 -0
- package/src/server/hydrate.ts +21 -0
- package/src/server/index.ts +10 -0
- package/src/server/render-to-stream.ts +31 -0
- package/src/server/render-to-string.ts +125 -0
- package/src/signals/computed.ts +217 -0
- package/src/signals/context.ts +89 -0
- package/src/signals/effect.ts +161 -0
- package/src/signals/index.ts +57 -0
- package/src/signals/scope.ts +132 -0
- package/src/signals/signal.ts +203 -0
- package/src/signals/store.ts +163 -0
- package/src/signals/system.ts +124 -0
- package/src/signals/types.ts +64 -0
- package/src/style/component.tsx +122 -0
- package/src/style/create-style.ts +229 -0
- package/src/style/cx.ts +75 -0
- package/src/style/hash.ts +19 -0
- package/src/style/index.ts +8 -0
- package/src/style/layout.tsx +406 -0
- package/src/style/manager.ts +87 -0
- package/src/style/parser.ts +257 -0
- package/src/style/scale.ts +237 -0
- package/src/template/index.ts +4 -0
- package/src/template/template.ts +126 -0
- package/src/ui/accordion.tsx +286 -0
- package/src/ui/command-dialog.tsx +49 -0
- package/src/ui/command.tsx +400 -0
- package/src/ui/data-table.tsx +322 -0
- package/src/ui/dialog.tsx +362 -0
- package/src/ui/dropdown-menu.tsx +381 -0
- package/src/ui/icon.tsx +69 -0
- package/src/ui/index.ts +138 -0
- package/src/ui/layout.tsx +13 -0
- package/src/ui/popover.tsx +249 -0
- package/src/ui/switch.tsx +114 -0
- package/src/ui/tabs.tsx +297 -0
- package/src/ui/toast.tsx +280 -0
- package/src/ui/tooltip.tsx +262 -0
- package/src/ui/utils.ts +110 -0
- package/src/virtual/index.ts +12 -0
- package/src/virtual/virtual-list.tsx +185 -0
- package/src/virtual/virtualizer.ts +274 -0
- package/src/vite/hmr.ts +38 -0
- package/src/vite/index.ts +3 -0
- package/src/vite/lucide.ts +124 -0
- package/src/vite/plugin.ts +102 -0
- package/src/vite/rpc-middleware.ts +82 -0
- package/src/vite/types.ts +65 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { onCleanup, signal } from "../signals";
|
|
2
|
+
import { Style } from "../style";
|
|
3
|
+
import { Tabs } from "../ui/tabs";
|
|
4
|
+
import { Tooltip } from "../ui/tooltip";
|
|
5
|
+
import { DomTab } from "./DomTab";
|
|
6
|
+
import { GraphTab } from "./GraphTab";
|
|
7
|
+
import { setupDomMutationHighlighter } from "./highlighter";
|
|
8
|
+
import { devtoolsHub } from "./hub";
|
|
9
|
+
import { QueryTab } from "./QueryTab";
|
|
10
|
+
import { RpcTab } from "./RpcTab";
|
|
11
|
+
import { SignalsTab } from "./SignalsTab";
|
|
12
|
+
import { devtoolsStyles } from "./styles";
|
|
13
|
+
import type { DevToolsOptions, DevToolsTab } from "./types";
|
|
14
|
+
|
|
15
|
+
export function VisageDevTools(props: DevToolsOptions = {}) {
|
|
16
|
+
if (typeof window === "undefined") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const hub = devtoolsHub;
|
|
21
|
+
if (typeof window !== "undefined" && !(window as any).__VISAGE_DEVTOOLS__) {
|
|
22
|
+
(window as any).__VISAGE_DEVTOOLS__ = hub;
|
|
23
|
+
}
|
|
24
|
+
if (props.initialOpen !== undefined) {
|
|
25
|
+
hub.isOpen.set(props.initialOpen);
|
|
26
|
+
}
|
|
27
|
+
if (props.initialTab !== undefined) {
|
|
28
|
+
hub.activeTab.set(props.initialTab);
|
|
29
|
+
}
|
|
30
|
+
if (props.highlightDOM !== undefined) {
|
|
31
|
+
hub.highlightDOM.set(props.highlightDOM);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Set up real-time visual DOM mutation highlighting
|
|
35
|
+
setupDomMutationHighlighter(() => hub.highlightDOM());
|
|
36
|
+
|
|
37
|
+
const selectedId = signal<string | null>(null, { internal: true });
|
|
38
|
+
const searchQuery = signal("", { internal: true });
|
|
39
|
+
|
|
40
|
+
// Keyboard shortcut: Shift + Alt + V to toggle DevTools
|
|
41
|
+
if (typeof window !== "undefined") {
|
|
42
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
43
|
+
if (e.shiftKey && e.altKey && e.key.toLowerCase() === "v") {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
hub.toggle();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
49
|
+
onCleanup(() => window.removeEventListener("keydown", handleKeyDown));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const formatJSON = (val: any) => {
|
|
53
|
+
try {
|
|
54
|
+
if (val === undefined) return "undefined";
|
|
55
|
+
return JSON.stringify(val, null, 2);
|
|
56
|
+
} catch {
|
|
57
|
+
return String(val);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div class="visage-devtools-root">
|
|
63
|
+
<Style>{devtoolsStyles}</Style>
|
|
64
|
+
|
|
65
|
+
{/* Floating Toggle Launcher Button with Tooltip */}
|
|
66
|
+
{() =>
|
|
67
|
+
!hub.isOpen() ? (
|
|
68
|
+
<Tooltip content="Press Shift+Alt+V to toggle Visage DevTools" side="top">
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
class="visage-devtools-trigger"
|
|
72
|
+
onClick={() => hub.toggle()}
|
|
73
|
+
aria-label="Open Visage DevTools"
|
|
74
|
+
>
|
|
75
|
+
<svg
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
width="14"
|
|
78
|
+
height="14"
|
|
79
|
+
viewBox="0 0 24 24"
|
|
80
|
+
fill="none"
|
|
81
|
+
stroke="currentColor"
|
|
82
|
+
stroke-width="2"
|
|
83
|
+
stroke-linecap="round"
|
|
84
|
+
stroke-linejoin="round"
|
|
85
|
+
>
|
|
86
|
+
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
|
|
87
|
+
</svg>
|
|
88
|
+
<span>DevTools</span>
|
|
89
|
+
</button>
|
|
90
|
+
</Tooltip>
|
|
91
|
+
) : (
|
|
92
|
+
<div class="visage-devtools-drawer" role="dialog" aria-label="Visage In-Browser DevTools">
|
|
93
|
+
{/* Header with Navigation Tabs */}
|
|
94
|
+
<div class="visage-devtools-header">
|
|
95
|
+
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
|
96
|
+
<svg
|
|
97
|
+
aria-hidden="true"
|
|
98
|
+
width="14"
|
|
99
|
+
height="14"
|
|
100
|
+
viewBox="0 0 24 24"
|
|
101
|
+
fill="none"
|
|
102
|
+
stroke="#38bdf8"
|
|
103
|
+
stroke-width="2"
|
|
104
|
+
stroke-linecap="round"
|
|
105
|
+
stroke-linejoin="round"
|
|
106
|
+
>
|
|
107
|
+
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
|
|
108
|
+
</svg>
|
|
109
|
+
<strong
|
|
110
|
+
style={{
|
|
111
|
+
fontSize: "12px",
|
|
112
|
+
letterSpacing: "0.04em",
|
|
113
|
+
color: "#f8fafc",
|
|
114
|
+
fontWeight: 700
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
VISAGE
|
|
118
|
+
</strong>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{/* DevTools Tab Bar powered by visage/ui <Tabs> */}
|
|
122
|
+
<Tabs
|
|
123
|
+
value={() => hub.activeTab()}
|
|
124
|
+
onValueChange={(val) => {
|
|
125
|
+
hub.setTab(val as DevToolsTab);
|
|
126
|
+
selectedId.set(null);
|
|
127
|
+
}}
|
|
128
|
+
listClass="visage-devtools-tabs"
|
|
129
|
+
items={[
|
|
130
|
+
{
|
|
131
|
+
value: "signals",
|
|
132
|
+
label: () => (
|
|
133
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: "6px" }}>
|
|
134
|
+
<span>Signals</span>
|
|
135
|
+
<span class="visage-devtools-badge">{() => hub.signalsList().length}</span>
|
|
136
|
+
</span>
|
|
137
|
+
)
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
value: "graph",
|
|
141
|
+
label: () => (
|
|
142
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: "6px" }}>
|
|
143
|
+
<span>DAG Graph</span>
|
|
144
|
+
<span
|
|
145
|
+
class="visage-devtools-badge"
|
|
146
|
+
style={{ background: "#581c87", color: "#fff" }}
|
|
147
|
+
>
|
|
148
|
+
LIVE
|
|
149
|
+
</span>
|
|
150
|
+
</span>
|
|
151
|
+
)
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
value: "query",
|
|
155
|
+
label: () => (
|
|
156
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: "6px" }}>
|
|
157
|
+
<span>Query Cache</span>
|
|
158
|
+
<span class="visage-devtools-badge">{() => hub.queriesList().length}</span>
|
|
159
|
+
</span>
|
|
160
|
+
)
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
value: "rpc",
|
|
164
|
+
label: () => (
|
|
165
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: "6px" }}>
|
|
166
|
+
<span>RPC</span>
|
|
167
|
+
<span class="visage-devtools-badge">{() => hub.rpcLogs().length}</span>
|
|
168
|
+
</span>
|
|
169
|
+
)
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
value: "dom",
|
|
173
|
+
label: "DOM Inspector"
|
|
174
|
+
}
|
|
175
|
+
]}
|
|
176
|
+
/>
|
|
177
|
+
|
|
178
|
+
{/* Close Button */}
|
|
179
|
+
<div class="visage-devtools-actions">
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
class="visage-devtools-btn"
|
|
183
|
+
onClick={() => hub.isOpen.set(false)}
|
|
184
|
+
title="Close DevTools (Shift+Alt+V)"
|
|
185
|
+
>
|
|
186
|
+
Close
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
{/* DevTools Body Switch */}
|
|
192
|
+
<div class="visage-devtools-body">
|
|
193
|
+
{() => {
|
|
194
|
+
const tab = hub.activeTab();
|
|
195
|
+
if (tab === "signals") {
|
|
196
|
+
return (
|
|
197
|
+
<SignalsTab
|
|
198
|
+
hub={hub}
|
|
199
|
+
selectedId={selectedId}
|
|
200
|
+
searchQuery={searchQuery}
|
|
201
|
+
formatJSON={formatJSON}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (tab === "graph") {
|
|
206
|
+
return (
|
|
207
|
+
<GraphTab
|
|
208
|
+
hub={hub}
|
|
209
|
+
selectedId={selectedId}
|
|
210
|
+
searchQuery={searchQuery}
|
|
211
|
+
formatJSON={formatJSON}
|
|
212
|
+
/>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (tab === "query") {
|
|
216
|
+
return (
|
|
217
|
+
<QueryTab
|
|
218
|
+
hub={hub}
|
|
219
|
+
selectedId={selectedId}
|
|
220
|
+
searchQuery={searchQuery}
|
|
221
|
+
formatJSON={formatJSON}
|
|
222
|
+
/>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (tab === "rpc") {
|
|
226
|
+
return (
|
|
227
|
+
<RpcTab
|
|
228
|
+
hub={hub}
|
|
229
|
+
selectedId={selectedId}
|
|
230
|
+
searchQuery={searchQuery}
|
|
231
|
+
formatJSON={formatJSON}
|
|
232
|
+
/>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return <DomTab hub={hub} />;
|
|
236
|
+
}}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { effect, onCleanup } from "../signals";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Visual DOM element mutation highlighter for Visage DevTools
|
|
5
|
+
*/
|
|
6
|
+
let styleInjected = false;
|
|
7
|
+
const activeTimeouts = new WeakMap<Element, ReturnType<typeof setTimeout>>();
|
|
8
|
+
|
|
9
|
+
function injectHighlighterStyles() {
|
|
10
|
+
if (styleInjected || typeof document === "undefined") return;
|
|
11
|
+
const style = document.createElement("style");
|
|
12
|
+
style.id = "visage-devtools-highlighter";
|
|
13
|
+
style.textContent = `
|
|
14
|
+
@keyframes visage-devtools-flash {
|
|
15
|
+
0% {
|
|
16
|
+
outline: 2px solid rgba(56, 189, 248, 0.9);
|
|
17
|
+
box-shadow: 0 0 12px rgba(56, 189, 248, 0.45);
|
|
18
|
+
}
|
|
19
|
+
100% {
|
|
20
|
+
outline: 2px solid transparent;
|
|
21
|
+
box-shadow: none;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
.visage-dev-highlight {
|
|
25
|
+
outline-offset: -1px;
|
|
26
|
+
animation: visage-devtools-flash 0.5s ease-out !important;
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
document.head.appendChild(style);
|
|
30
|
+
styleInjected = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function highlightElement(element: Element | null) {
|
|
34
|
+
if (!element || typeof document === "undefined" || !(element instanceof Element)) return;
|
|
35
|
+
injectHighlighterStyles();
|
|
36
|
+
|
|
37
|
+
const prevTimeout = activeTimeouts.get(element);
|
|
38
|
+
if (prevTimeout) {
|
|
39
|
+
clearTimeout(prevTimeout);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
element.classList.remove("visage-dev-highlight");
|
|
43
|
+
// Force reflow
|
|
44
|
+
void (element as HTMLElement).offsetWidth;
|
|
45
|
+
element.classList.add("visage-dev-highlight");
|
|
46
|
+
|
|
47
|
+
const timeout = setTimeout(() => {
|
|
48
|
+
element.classList.remove("visage-dev-highlight");
|
|
49
|
+
activeTimeouts.delete(element);
|
|
50
|
+
}, 500);
|
|
51
|
+
|
|
52
|
+
activeTimeouts.set(element, timeout);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let observer: MutationObserver | null = null;
|
|
56
|
+
|
|
57
|
+
export function setupDomMutationHighlighter(enabledGetter: () => boolean) {
|
|
58
|
+
if (typeof window === "undefined" || typeof MutationObserver === "undefined") return;
|
|
59
|
+
|
|
60
|
+
const startObserver = () => {
|
|
61
|
+
if (observer) return;
|
|
62
|
+
injectHighlighterStyles();
|
|
63
|
+
observer = new MutationObserver((mutations) => {
|
|
64
|
+
if (!enabledGetter()) return;
|
|
65
|
+
const seen = new Set<Element>();
|
|
66
|
+
for (const mutation of mutations) {
|
|
67
|
+
const target = mutation.target as Node;
|
|
68
|
+
let el: Element | null = null;
|
|
69
|
+
if (target instanceof Element) {
|
|
70
|
+
el = target;
|
|
71
|
+
} else if (target.parentElement) {
|
|
72
|
+
el = target.parentElement;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!el) continue;
|
|
76
|
+
|
|
77
|
+
// Skip DevTools UI itself
|
|
78
|
+
if (el.closest?.(".visage-devtools-root") || el.closest?.("#visage-devtools-highlighter")) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Avoid infinite loops when adding/removing the highlight class
|
|
83
|
+
if (mutation.type === "attributes" && mutation.attributeName === "class") {
|
|
84
|
+
const oldCls = (mutation.oldValue || "")
|
|
85
|
+
.replace(/\bvisage-dev-highlight\b/g, "")
|
|
86
|
+
.replace(/\s+/g, " ")
|
|
87
|
+
.trim();
|
|
88
|
+
const newCls = (el.getAttribute("class") || "")
|
|
89
|
+
.replace(/\bvisage-dev-highlight\b/g, "")
|
|
90
|
+
.replace(/\s+/g, " ")
|
|
91
|
+
.trim();
|
|
92
|
+
if (oldCls === newCls) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!seen.has(el)) {
|
|
98
|
+
seen.add(el);
|
|
99
|
+
highlightElement(el);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const targetNode = document.body || document.documentElement;
|
|
105
|
+
if (targetNode) {
|
|
106
|
+
observer.observe(targetNode, {
|
|
107
|
+
childList: true,
|
|
108
|
+
attributes: true,
|
|
109
|
+
attributeOldValue: true,
|
|
110
|
+
characterData: true,
|
|
111
|
+
subtree: true
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const stopObserver = () => {
|
|
117
|
+
if (observer) {
|
|
118
|
+
observer.disconnect();
|
|
119
|
+
observer = null;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
effect(() => {
|
|
124
|
+
if (enabledGetter()) {
|
|
125
|
+
startObserver();
|
|
126
|
+
} else {
|
|
127
|
+
stopObserver();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
onCleanup(() => {
|
|
132
|
+
stopObserver();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { QueryClient } from "../query/query-client";
|
|
2
|
+
import { signal } from "../signals";
|
|
3
|
+
import type { DevToolsTab, QueryRecord, RpcRecord, SignalRecord } from "./types";
|
|
4
|
+
|
|
5
|
+
export class DevToolsHub {
|
|
6
|
+
readonly isOpen = signal(false, { internal: true });
|
|
7
|
+
readonly activeTab = signal<DevToolsTab>("signals", { internal: true });
|
|
8
|
+
readonly highlightDOM = signal(false, { internal: true });
|
|
9
|
+
readonly searchQuery = signal("", { internal: true });
|
|
10
|
+
|
|
11
|
+
// Store active records
|
|
12
|
+
private _signalsMap = new Map<string, SignalRecord>();
|
|
13
|
+
readonly signalsList = signal<SignalRecord[]>([], { internal: true });
|
|
14
|
+
|
|
15
|
+
private _queriesMap = new Map<string, QueryRecord>();
|
|
16
|
+
readonly queriesList = signal<QueryRecord[]>([], { internal: true });
|
|
17
|
+
|
|
18
|
+
readonly rpcLogs = signal<RpcRecord[]>([], { internal: true });
|
|
19
|
+
private _queryClients = new Set<QueryClient>();
|
|
20
|
+
|
|
21
|
+
private _signalCounter = 0;
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
if (typeof window !== "undefined") {
|
|
25
|
+
(window as any).__VISAGE_DEVTOOLS__ = this;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
toggle() {
|
|
30
|
+
this.isOpen.update((v) => !v);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setTab(tab: DevToolsTab) {
|
|
34
|
+
this.activeTab.set(tab);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// --- Signal Tracking ---
|
|
38
|
+
registerSignal(options: {
|
|
39
|
+
id?: string;
|
|
40
|
+
name?: string;
|
|
41
|
+
type?: "signal" | "computed";
|
|
42
|
+
getValue: () => any;
|
|
43
|
+
version: () => number;
|
|
44
|
+
dependencies?: () => string[];
|
|
45
|
+
dependents?: () => string[];
|
|
46
|
+
subscribersCount?: () => number;
|
|
47
|
+
}): string {
|
|
48
|
+
const id = options.id ?? `sig_${++this._signalCounter}`;
|
|
49
|
+
const name = options.name ?? id;
|
|
50
|
+
const type = options.type ?? "signal";
|
|
51
|
+
|
|
52
|
+
const record: SignalRecord = {
|
|
53
|
+
id,
|
|
54
|
+
name,
|
|
55
|
+
type,
|
|
56
|
+
getValue: options.getValue,
|
|
57
|
+
version: options.version,
|
|
58
|
+
dependencies: options.dependencies,
|
|
59
|
+
dependents: options.dependents,
|
|
60
|
+
subscribersCount: options.subscribersCount,
|
|
61
|
+
lastUpdatedAt: Date.now()
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
this._signalsMap.set(id, record);
|
|
65
|
+
this.refreshSignals();
|
|
66
|
+
return id;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
unregisterSignal(id: string) {
|
|
70
|
+
if (this._signalsMap.delete(id)) {
|
|
71
|
+
this.refreshSignals();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
clearSignals() {
|
|
76
|
+
this._signalsMap.clear();
|
|
77
|
+
this._signalCounter = 0;
|
|
78
|
+
this.refreshSignals();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
clearQueries() {
|
|
82
|
+
this._queriesMap.clear();
|
|
83
|
+
this.queriesList.set([]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
clearAll() {
|
|
87
|
+
this.clearSignals();
|
|
88
|
+
this.clearQueries();
|
|
89
|
+
this.clearLogs();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private refreshSignals() {
|
|
93
|
+
this.signalsList.set(Array.from(this._signalsMap.values()));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Query Cache Tracking ---
|
|
97
|
+
attachQueryClient(client: QueryClient) {
|
|
98
|
+
if (this._queryClients.has(client)) return;
|
|
99
|
+
this._queryClients.add(client);
|
|
100
|
+
|
|
101
|
+
const syncQueries = () => {
|
|
102
|
+
const cache = client.getQueryCache();
|
|
103
|
+
const allQueries = cache.getAll();
|
|
104
|
+
const records: QueryRecord[] = allQueries.map((q) => ({
|
|
105
|
+
hash: q.queryHash,
|
|
106
|
+
key: q.queryKey,
|
|
107
|
+
status: () => q.status(),
|
|
108
|
+
fetchStatus: () => q.fetchStatus(),
|
|
109
|
+
data: () => q.data(),
|
|
110
|
+
error: () => q.error(),
|
|
111
|
+
dataUpdatedAt: () => q.dataUpdatedAt(),
|
|
112
|
+
refetch: () => q.fetch(),
|
|
113
|
+
invalidate: () => q.invalidate()
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
this._queriesMap.clear();
|
|
117
|
+
for (const r of records) {
|
|
118
|
+
this._queriesMap.set(r.hash, r);
|
|
119
|
+
}
|
|
120
|
+
this.queriesList.set(records);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
client.getQueryCache().subscribe(() => {
|
|
124
|
+
syncQueries();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
syncQueries();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- RPC & SSE Logging ---
|
|
131
|
+
logRpcCall(record: Omit<RpcRecord, "status" | "startTime"> & { status?: RpcRecord["status"] }) {
|
|
132
|
+
const fullRecord: RpcRecord = {
|
|
133
|
+
...record,
|
|
134
|
+
status: record.status ?? "pending",
|
|
135
|
+
startTime: Date.now(),
|
|
136
|
+
events: []
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.rpcLogs.update((prev) => [fullRecord, ...prev.slice(0, 99)]);
|
|
140
|
+
return fullRecord.id;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
updateRpcCall(id: string, updates: Partial<RpcRecord>) {
|
|
144
|
+
this.rpcLogs.update((prev) =>
|
|
145
|
+
prev.map((item) => {
|
|
146
|
+
if (item.id === id) {
|
|
147
|
+
const duration =
|
|
148
|
+
updates.duration ?? (updates.status ? Date.now() - item.startTime : item.duration);
|
|
149
|
+
return { ...item, ...updates, duration };
|
|
150
|
+
}
|
|
151
|
+
return item;
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
logRpcEvent(id: string, data: any) {
|
|
157
|
+
this.rpcLogs.update((prev) =>
|
|
158
|
+
prev.map((item) => {
|
|
159
|
+
if (item.id === id) {
|
|
160
|
+
const events = item.events
|
|
161
|
+
? [...item.events, { timestamp: Date.now(), data }]
|
|
162
|
+
: [{ timestamp: Date.now(), data }];
|
|
163
|
+
return { ...item, events, status: "streaming" as const };
|
|
164
|
+
}
|
|
165
|
+
return item;
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
clearLogs() {
|
|
171
|
+
this.rpcLogs.set([]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const devtoolsHub = new DevToolsHub();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { QueryClient } from "../query/query-client";
|
|
2
|
+
import { devtoolsHub } from "./hub";
|
|
3
|
+
|
|
4
|
+
export { VisageDevTools } from "./component";
|
|
5
|
+
export { highlightElement } from "./highlighter";
|
|
6
|
+
export { DevToolsHub, devtoolsHub } from "./hub";
|
|
7
|
+
export type {
|
|
8
|
+
DevToolsOptions,
|
|
9
|
+
DevToolsTab,
|
|
10
|
+
QueryRecord,
|
|
11
|
+
RpcRecord,
|
|
12
|
+
SignalRecord
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Connect QueryClient or RPC client to Visage DevTools
|
|
17
|
+
*/
|
|
18
|
+
export function connectDevTools(client: QueryClient) {
|
|
19
|
+
devtoolsHub.attachQueryClient(client);
|
|
20
|
+
}
|