@shadow-js/devtools 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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +576 -2
- package/dist/dev/agent.d.ts +239 -0
- package/dist/dev/agent.js +626 -0
- package/dist/dev/agent.js.map +1 -0
- package/dist/dev/backend.d.ts +53 -0
- package/dist/dev/backend.js +53 -0
- package/dist/dev/backend.js.map +1 -0
- package/dist/dev/hook.d.ts +83 -0
- package/dist/dev/hook.js +136 -0
- package/dist/dev/hook.js.map +1 -0
- package/dist/dev/index.d.ts +17 -0
- package/dist/dev/index.js +17 -0
- package/dist/dev/inspection.d.ts +226 -0
- package/dist/dev/inspection.js +263 -0
- package/dist/dev/inspection.js.map +1 -0
- package/dist/dev/inspector.d.ts +331 -0
- package/dist/dev/inspector.js +429 -0
- package/dist/dev/inspector.js.map +1 -0
- package/dist/dev/model.d.ts +418 -0
- package/dist/dev/model.js +559 -0
- package/dist/dev/model.js.map +1 -0
- package/dist/dev/mount.d.ts +207 -0
- package/dist/dev/mount.js +256 -0
- package/dist/dev/mount.js.map +1 -0
- package/dist/dev/overlay.d.ts +102 -0
- package/dist/dev/overlay.js +176 -0
- package/dist/dev/overlay.js.map +1 -0
- package/dist/dev/preview.d.ts +74 -0
- package/dist/dev/preview.js +122 -0
- package/dist/dev/preview.js.map +1 -0
- package/dist/dev/protocol.d.ts +288 -0
- package/dist/dev/protocol.js +109 -0
- package/dist/dev/protocol.js.map +1 -0
- package/dist/dev/ring.d.ts +50 -0
- package/dist/dev/ring.js +94 -0
- package/dist/dev/ring.js.map +1 -0
- package/dist/dev/transport.d.ts +347 -0
- package/dist/dev/transport.js +394 -0
- package/dist/dev/transport.js.map +1 -0
- package/dist/dev/ui/details.js +294 -0
- package/dist/dev/ui/details.js.map +1 -0
- package/dist/dev/ui/inspection-state.d.ts +43 -0
- package/dist/dev/ui/inspection-state.js +62 -0
- package/dist/dev/ui/inspection-state.js.map +1 -0
- package/dist/dev/ui/issues.js +85 -0
- package/dist/dev/ui/issues.js.map +1 -0
- package/dist/dev/ui/panel.d.ts +55 -0
- package/dist/dev/ui/panel.js +160 -0
- package/dist/dev/ui/panel.js.map +1 -0
- package/dist/dev/ui/rows.d.ts +44 -0
- package/dist/dev/ui/rows.js +132 -0
- package/dist/dev/ui/rows.js.map +1 -0
- package/dist/dev/ui/state-tab.js +88 -0
- package/dist/dev/ui/state-tab.js.map +1 -0
- package/dist/dev/ui/state.d.ts +192 -0
- package/dist/dev/ui/state.js +147 -0
- package/dist/dev/ui/state.js.map +1 -0
- package/dist/dev/ui/styles.d.ts +31 -0
- package/dist/dev/ui/styles.js +113 -0
- package/dist/dev/ui/styles.js.map +1 -0
- package/dist/dev/ui/timeline.js +135 -0
- package/dist/dev/ui/timeline.js.map +1 -0
- package/dist/dev/ui/tree.js +186 -0
- package/dist/dev/ui/tree.js.map +1 -0
- package/dist/prod/agent.js +2 -0
- package/dist/prod/agent.js.map +1 -0
- package/dist/prod/backend.js +2 -0
- package/dist/prod/backend.js.map +1 -0
- package/dist/prod/hook.js +2 -0
- package/dist/prod/hook.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/inspection.js +2 -0
- package/dist/prod/inspection.js.map +1 -0
- package/dist/prod/inspector.js +2 -0
- package/dist/prod/inspector.js.map +1 -0
- package/dist/prod/model.js +2 -0
- package/dist/prod/model.js.map +1 -0
- package/dist/prod/mount.js +2 -0
- package/dist/prod/mount.js.map +1 -0
- package/dist/prod/overlay.js +11 -0
- package/dist/prod/overlay.js.map +1 -0
- package/dist/prod/preview.js +2 -0
- package/dist/prod/preview.js.map +1 -0
- package/dist/prod/protocol.js +2 -0
- package/dist/prod/protocol.js.map +1 -0
- package/dist/prod/ring.js +2 -0
- package/dist/prod/ring.js.map +1 -0
- package/dist/prod/transport.js +2 -0
- package/dist/prod/transport.js.map +1 -0
- package/dist/prod/ui/details.js +2 -0
- package/dist/prod/ui/details.js.map +1 -0
- package/dist/prod/ui/inspection-state.js +2 -0
- package/dist/prod/ui/inspection-state.js.map +1 -0
- package/dist/prod/ui/issues.js +2 -0
- package/dist/prod/ui/issues.js.map +1 -0
- package/dist/prod/ui/panel.js +2 -0
- package/dist/prod/ui/panel.js.map +1 -0
- package/dist/prod/ui/rows.js +2 -0
- package/dist/prod/ui/rows.js.map +1 -0
- package/dist/prod/ui/state-tab.js +2 -0
- package/dist/prod/ui/state-tab.js.map +1 -0
- package/dist/prod/ui/state.js +2 -0
- package/dist/prod/ui/state.js.map +1 -0
- package/dist/prod/ui/styles.js +82 -0
- package/dist/prod/ui/styles.js.map +1 -0
- package/dist/prod/ui/timeline.js +2 -0
- package/dist/prod/ui/timeline.js.map +1 -0
- package/dist/prod/ui/tree.js +2 -0
- package/dist/prod/ui/tree.js.map +1 -0
- package/package.json +69 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.js","names":["_$anchor","_$createComponent"],"sources":["../../../src/ui/timeline.tsx"],"sourcesContent":["import { For, Show } from \"@shadow-js/web\";\nimport type { Component } from \"@shadow-js/web/jsx-runtime\";\nimport type { FlameFlush, FlameSpan } from \"../inspector\";\nimport type { TabProps } from \"./tree\";\n\ninterface SpanProps extends TabProps {\n flush: FlameFlush;\n span: FlameSpan;\n}\n\nconst LANE_HEIGHT = 16;\n\n/** One bar of the flame graph: positioned by time inside its flush, one lane per nesting level. */\nconst Bar: Component<SpanProps> = (props) => (\n <div\n class={`bar ${props.span.kind}${props.span.changed ? \"\" : \" unchanged\"}`}\n data-span={props.span.seq}\n title={`${props.span.label}: ${props.span.duration.toFixed(2)} ms${props.span.changed ? \"\" : \" (value unchanged)\"}`}\n style={{\n left: `${props.flush.duration <= 0 ? 0 : ((props.span.start - props.flush.start) / props.flush.duration) * 100}%`,\n width: `${props.flush.duration <= 0 ? 100 : Math.max((props.span.duration / props.flush.duration) * 100, 1)}%`,\n top: `${props.span.depth * LANE_HEIGHT}px`,\n }}\n on:click={() => props.state.select(props.span.id)}\n >\n {props.span.label}\n </div>\n);\n\n/**\n * The Timeline tab. Props: `state` is the panel's {@link PanelState}. It shows the newest 12 flushes, each\n * with its run count and duration (flushes also show their queue sizes), and its runs laid out as flame lanes (a\n * memo pulled by an effect sits one lane below the effect). Clicking a bar selects its node. Below the\n * flushes it lists up to 10 components that re-ran most, with their update counts and total time.\n *\n * Internal to the panel: the package index does not export this component. Used in development builds\n * only.\n *\n * @internal\n */\nexport const TimelineTab: Component<TabProps> = (props) => (\n <section class=\"list\" aria-label=\"Timeline\">\n <Show\n when={props.state.flames().length > 0}\n fallback={<div class=\"empty\">No flushes recorded yet.</div>}\n >\n <For each={props.state.flames().slice().reverse()} key={(flush) => flush.flush}>\n {(flush) => (\n <div class=\"flush\" data-flush={flush().flush}>\n <div class=\"flush-head\">\n <strong>{flush().flush === 0 ? \"outside a flush\" : `flush #${flush().flush}`}</strong>\n <span>{flush().spans.length} runs</span>\n <span>{flush().duration.toFixed(2)} ms</span>\n <Show when={flush().flush !== 0}>\n <span>\n queue: {flush().render} render / {flush().user} user\n </span>\n </Show>\n </div>\n <div class=\"lanes\" style={{ height: `${Math.max(flush().lanes, 1) * LANE_HEIGHT}px` }}>\n <For each={flush().spans} key={(span) => span.seq}>\n {(span) => <Bar state={props.state} flush={flush()} span={span()} />}\n </For>\n </div>\n </div>\n )}\n </For>\n </Show>\n <Show when={props.state.hot().length > 0}>\n <div class=\"flush\">\n <div class=\"flush-head\">\n <strong>Components that re-ran most</strong>\n </div>\n <table class=\"table\" aria-label=\"Component update counts\">\n <thead>\n <tr>\n <th>Component</th>\n <th class=\"num\">Updates</th>\n <th class=\"num\">Total ms</th>\n </tr>\n </thead>\n <tbody>\n <For each={props.state.hot()} key={(stat) => stat.id}>\n {(stat) => (\n <tr data-component={stat().id}>\n <td>\n <button\n type=\"button\"\n class=\"link\"\n on:click={() => props.state.select(stat().id)}\n >\n {stat().name}\n </button>\n </td>\n <td class=\"num\">{stat().updates}</td>\n <td class=\"num\">{stat().totalDuration.toFixed(2)}</td>\n </tr>\n )}\n </For>\n </tbody>\n </table>\n </div>\n </Show>\n </section>\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { kindLetter } from "./rows.js";
|
|
2
|
+
import { Details } from "./details.js";
|
|
3
|
+
import { For, Show, addEventListener, anchor, className, createComponent, effect, insert, setAttribute, style, template } from "@shadow-js/web";
|
|
4
|
+
//#region src/ui/tree.tsx
|
|
5
|
+
const _tmpl$ = /*#__PURE__*/ template("<span class=\"twisty\"></span>");
|
|
6
|
+
const _tmpl$2 = /*#__PURE__*/ template("<span class=\"updates\" title=\"Re-runs of effects and memos below this node\">×</span>");
|
|
7
|
+
const _tmpl$3 = /*#__PURE__*/ template("<div class=\"row\" role=\"treeitem\"><button type=\"button\" class=\"twisty\" tabindex=\"-1\" aria-label=\"Toggle children\"></button><span></span><span></span><span class=\"value\"></span></div>");
|
|
8
|
+
const _tmpl$4 = /*#__PURE__*/ template("<div class=\"empty\">Nothing recorded yet. Interact with the app.</div>");
|
|
9
|
+
const _tmpl$5 = /*#__PURE__*/ template("<div class=\"more\"><button type=\"button\" class=\"btn\">Show more ( hidden)</button></div>");
|
|
10
|
+
const _tmpl$6 = /*#__PURE__*/ template("<div class=\"split\"><div class=\"toolbar\"><input class=\"input\" type=\"search\" placeholder=\"Filter by name, kind or value\" aria-label=\"Filter\"><label><input type=\"checkbox\"> components only</label></div><div class=\"list\" role=\"tree\" aria-label=\"Reactive nodes\"></div></div>");
|
|
11
|
+
/** One line of the tree: twisty, kind, name, value and the re-run count. */
|
|
12
|
+
const TreeRowView = (props) => {
|
|
13
|
+
const _el$ = _tmpl$3(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling, _el$5 = _el$4.nextSibling;
|
|
14
|
+
effect(() => {
|
|
15
|
+
setAttribute(_el$, "aria-level", props.row.depth + 1);
|
|
16
|
+
});
|
|
17
|
+
effect(() => {
|
|
18
|
+
setAttribute(_el$, "aria-selected", props.state.selected() === props.row.id);
|
|
19
|
+
});
|
|
20
|
+
effect(() => {
|
|
21
|
+
setAttribute(_el$, "aria-expanded", props.row.hasChildren ? !props.row.collapsed : void 0);
|
|
22
|
+
});
|
|
23
|
+
effect(() => {
|
|
24
|
+
setAttribute(_el$, "data-id", props.row.id);
|
|
25
|
+
});
|
|
26
|
+
effect(() => {
|
|
27
|
+
setAttribute(_el$, "tabindex", props.state.selected() === props.row.id ? 0 : -1);
|
|
28
|
+
});
|
|
29
|
+
effect(() => ({ paddingLeft: `${8 + props.row.depth * 14}px` }), (_v$, _p$) => {
|
|
30
|
+
style(_el$, _v$, _p$);
|
|
31
|
+
});
|
|
32
|
+
addEventListener(_el$, "click", () => props.state.select(props.row.id));
|
|
33
|
+
addEventListener(_el$, "pointerenter", () => props.state.highlight(props.row.id));
|
|
34
|
+
addEventListener(_el$, "pointerleave", () => props.state.highlight(null));
|
|
35
|
+
effect(() => {
|
|
36
|
+
_el$2.hidden = !props.row.hasChildren;
|
|
37
|
+
});
|
|
38
|
+
addEventListener(_el$2, "click", (event) => {
|
|
39
|
+
event.stopPropagation();
|
|
40
|
+
props.state.toggleCollapsed(props.row.id);
|
|
41
|
+
});
|
|
42
|
+
insert(_el$2, () => props.row.collapsed ? "▸" : "▾");
|
|
43
|
+
insert(_el$, createComponent(Show, {
|
|
44
|
+
get when() {
|
|
45
|
+
return !props.row.hasChildren;
|
|
46
|
+
},
|
|
47
|
+
get children() {
|
|
48
|
+
return _tmpl$();
|
|
49
|
+
}
|
|
50
|
+
}, "Show"), _el$3);
|
|
51
|
+
effect(() => {
|
|
52
|
+
className(_el$3, `kind ${props.row.isComponent ? "component" : props.row.type}`);
|
|
53
|
+
});
|
|
54
|
+
insert(_el$3, () => kindLetter(props.row));
|
|
55
|
+
effect(() => {
|
|
56
|
+
className(_el$4, props.row.isComponent ? "name component" : "name");
|
|
57
|
+
});
|
|
58
|
+
insert(_el$4, () => props.row.label);
|
|
59
|
+
insert(_el$5, () => props.row.detail);
|
|
60
|
+
insert(_el$, createComponent(Show, {
|
|
61
|
+
get when() {
|
|
62
|
+
return props.row.updates > 0;
|
|
63
|
+
},
|
|
64
|
+
get children() {
|
|
65
|
+
const _el$ = _tmpl$2();
|
|
66
|
+
insert(_el$, () => props.row.updates);
|
|
67
|
+
return _el$;
|
|
68
|
+
}
|
|
69
|
+
}, "Show"));
|
|
70
|
+
return _el$;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Keyboard navigation of the tree: arrows move the selection, left and right collapse and expand,
|
|
74
|
+
* Home and End jump to the ends. Focus follows the selected row (roving tabindex).
|
|
75
|
+
*/
|
|
76
|
+
function navigate(state, event) {
|
|
77
|
+
const rows = state.rows().rows;
|
|
78
|
+
const index = rows.findIndex((row) => row.id === state.selected());
|
|
79
|
+
const select = (next) => {
|
|
80
|
+
const row = rows[next];
|
|
81
|
+
if (row === void 0) return;
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
state.select(row.id);
|
|
84
|
+
state.highlight(row.id);
|
|
85
|
+
const list = event.currentTarget;
|
|
86
|
+
if (list instanceof HTMLElement) queueMicrotask(() => list.querySelector(`[data-id="${row.id}"]`)?.focus());
|
|
87
|
+
};
|
|
88
|
+
const current = rows[index];
|
|
89
|
+
switch (event.key) {
|
|
90
|
+
case "ArrowDown":
|
|
91
|
+
select(Math.min(index + 1, rows.length - 1));
|
|
92
|
+
break;
|
|
93
|
+
case "ArrowUp":
|
|
94
|
+
select(Math.max(index - 1, 0));
|
|
95
|
+
break;
|
|
96
|
+
case "Home":
|
|
97
|
+
select(0);
|
|
98
|
+
break;
|
|
99
|
+
case "End":
|
|
100
|
+
select(rows.length - 1);
|
|
101
|
+
break;
|
|
102
|
+
case "ArrowRight":
|
|
103
|
+
if (current?.hasChildren === true && current.collapsed) {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
state.toggleCollapsed(current.id);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case "ArrowLeft": if (current?.hasChildren === true && !current.collapsed) {
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
state.toggleCollapsed(current.id);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The Components tab, rendered by the panel from a {@link TabProps} `state`. It shows a filter box, a
|
|
116
|
+
* components-only switch, the owners tree (every reactive node: components, owners, memos, effects,
|
|
117
|
+
* signals, resources and stores) with each row's kind letter, value and re-run count, and the details of
|
|
118
|
+
* the selected node below it. Hovering a row highlights its component on the page.
|
|
119
|
+
*
|
|
120
|
+
* Internal to the panel: the package index does not export this component. Used in development builds
|
|
121
|
+
* only.
|
|
122
|
+
*
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
const TreeTab = (props) => {
|
|
126
|
+
const _el$ = _tmpl$6(), _el$2 = _el$.firstChild, _el$4 = _el$2.firstChild, _el$6 = _el$4.nextSibling.firstChild, _el$3 = _el$2.nextSibling, _el$7 = anchor(_el$3, null);
|
|
127
|
+
addEventListener(_el$4, "input", (event) => props.state.setFilter(event.currentTarget.value));
|
|
128
|
+
effect(() => {
|
|
129
|
+
_el$4.value = props.state.filter() ?? "";
|
|
130
|
+
});
|
|
131
|
+
addEventListener(_el$6, "change", (event) => props.state.setComponentsOnly(event.currentTarget.checked));
|
|
132
|
+
effect(() => {
|
|
133
|
+
_el$6.checked = props.state.componentsOnly();
|
|
134
|
+
});
|
|
135
|
+
addEventListener(_el$3, "keydown", (event) => navigate(props.state, event));
|
|
136
|
+
insert(_el$3, createComponent(Show, {
|
|
137
|
+
get when() {
|
|
138
|
+
return props.state.rows().rows.length > 0;
|
|
139
|
+
},
|
|
140
|
+
get fallback() {
|
|
141
|
+
return _tmpl$4();
|
|
142
|
+
},
|
|
143
|
+
get children() {
|
|
144
|
+
return createComponent(For, {
|
|
145
|
+
get each() {
|
|
146
|
+
return props.state.rows().rows;
|
|
147
|
+
},
|
|
148
|
+
key: (row) => row.id,
|
|
149
|
+
children: (row) => createComponent(TreeRowView, {
|
|
150
|
+
get state() {
|
|
151
|
+
return props.state;
|
|
152
|
+
},
|
|
153
|
+
get row() {
|
|
154
|
+
return row();
|
|
155
|
+
}
|
|
156
|
+
}, "TreeRowView")
|
|
157
|
+
}, "For");
|
|
158
|
+
}
|
|
159
|
+
}, "Show"), _el$7);
|
|
160
|
+
insert(_el$3, createComponent(Show, {
|
|
161
|
+
get when() {
|
|
162
|
+
return props.state.rows().hidden > 0;
|
|
163
|
+
},
|
|
164
|
+
get children() {
|
|
165
|
+
const _el$ = _tmpl$5(), _el$2 = _el$.firstChild, _el$4 = _el$2.firstChild.splitText(11);
|
|
166
|
+
addEventListener(_el$2, "click", () => props.state.showMoreRows());
|
|
167
|
+
insert(_el$2, () => props.state.rows().hidden, _el$4);
|
|
168
|
+
return _el$;
|
|
169
|
+
}
|
|
170
|
+
}, "Show"));
|
|
171
|
+
insert(_el$, createComponent(Show, {
|
|
172
|
+
get when() {
|
|
173
|
+
return props.state.selectedNode() !== void 0;
|
|
174
|
+
},
|
|
175
|
+
get children() {
|
|
176
|
+
return createComponent(Details, { get state() {
|
|
177
|
+
return props.state;
|
|
178
|
+
} }, "Details");
|
|
179
|
+
}
|
|
180
|
+
}, "Show"));
|
|
181
|
+
return _el$;
|
|
182
|
+
};
|
|
183
|
+
//#endregion
|
|
184
|
+
export { TreeTab };
|
|
185
|
+
|
|
186
|
+
//# sourceMappingURL=tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.js","names":["_$createComponent","_$anchor"],"sources":["../../../src/ui/tree.tsx"],"sourcesContent":["import { For, Show } from \"@shadow-js/web\";\nimport type { Component } from \"@shadow-js/web/jsx-runtime\";\nimport { Details } from \"./details\";\nimport { kindLetter, type TreeRow } from \"./rows\";\nimport type { PanelState } from \"./state\";\n\ninterface RowProps {\n state: PanelState;\n row: TreeRow;\n}\n\n/** One line of the tree: twisty, kind, name, value and the re-run count. */\nconst TreeRowView: Component<RowProps> = (props) => (\n <div\n class=\"row\"\n role=\"treeitem\"\n aria-level={props.row.depth + 1}\n aria-selected={props.state.selected() === props.row.id}\n aria-expanded={props.row.hasChildren ? !props.row.collapsed : undefined}\n data-id={props.row.id}\n tabIndex={props.state.selected() === props.row.id ? 0 : -1}\n style={{ paddingLeft: `${8 + props.row.depth * 14}px` }}\n on:click={() => props.state.select(props.row.id)}\n on:pointerenter={() => props.state.highlight(props.row.id)}\n on:pointerleave={() => props.state.highlight(null)}\n >\n <button\n type=\"button\"\n class=\"twisty\"\n tabIndex={-1}\n aria-label=\"Toggle children\"\n hidden={!props.row.hasChildren}\n on:click={(event) => {\n event.stopPropagation();\n props.state.toggleCollapsed(props.row.id);\n }}\n >\n {props.row.collapsed ? \"▸\" : \"▾\"}\n </button>\n <Show when={!props.row.hasChildren}>\n <span class=\"twisty\" />\n </Show>\n <span class={`kind ${props.row.isComponent ? \"component\" : props.row.type}`}>\n {kindLetter(props.row)}\n </span>\n <span class={props.row.isComponent ? \"name component\" : \"name\"}>{props.row.label}</span>\n <span class=\"value\">{props.row.detail}</span>\n <Show when={props.row.updates > 0}>\n <span class=\"updates\" title=\"Re-runs of effects and memos below this node\">\n ×{props.row.updates}\n </span>\n </Show>\n </div>\n);\n\n/**\n * Keyboard navigation of the tree: arrows move the selection, left and right collapse and expand,\n * Home and End jump to the ends. Focus follows the selected row (roving tabindex).\n */\nfunction navigate(state: PanelState, event: KeyboardEvent): void {\n const rows = state.rows().rows;\n const index = rows.findIndex((row) => row.id === state.selected());\n const select = (next: number): void => {\n const row = rows[next];\n if (row === undefined) return;\n event.preventDefault();\n state.select(row.id);\n state.highlight(row.id);\n const list = event.currentTarget;\n if (list instanceof HTMLElement) {\n queueMicrotask(() => list.querySelector<HTMLElement>(`[data-id=\"${row.id}\"]`)?.focus());\n }\n };\n const current = rows[index];\n switch (event.key) {\n case \"ArrowDown\":\n select(Math.min(index + 1, rows.length - 1));\n break;\n case \"ArrowUp\":\n select(Math.max(index - 1, 0));\n break;\n case \"Home\":\n select(0);\n break;\n case \"End\":\n select(rows.length - 1);\n break;\n case \"ArrowRight\":\n if (current?.hasChildren === true && current.collapsed) {\n event.preventDefault();\n state.toggleCollapsed(current.id);\n }\n break;\n case \"ArrowLeft\":\n if (current?.hasChildren === true && !current.collapsed) {\n event.preventDefault();\n state.toggleCollapsed(current.id);\n }\n break;\n default:\n break;\n }\n}\n\n/**\n * Props of the tab views: the panel's {@link PanelState}.\n *\n * @since 1.0.0\n */\nexport interface TabProps {\n /**\n * The panel's state.\n */\n state: PanelState;\n}\n\n/**\n * The Components tab, rendered by the panel from a {@link TabProps} `state`. It shows a filter box, a\n * components-only switch, the owners tree (every reactive node: components, owners, memos, effects,\n * signals, resources and stores) with each row's kind letter, value and re-run count, and the details of\n * the selected node below it. Hovering a row highlights its component on the page.\n *\n * Internal to the panel: the package index does not export this component. Used in development builds\n * only.\n *\n * @internal\n */\nexport const TreeTab: Component<TabProps> = (props) => (\n <div class=\"split\">\n <div class=\"toolbar\">\n <input\n class=\"input\"\n type=\"search\"\n placeholder=\"Filter by name, kind or value\"\n aria-label=\"Filter\"\n value={props.state.filter()}\n on:input={(event) => props.state.setFilter(event.currentTarget.value)}\n />\n <label>\n <input\n type=\"checkbox\"\n checked={props.state.componentsOnly()}\n on:change={(event) => props.state.setComponentsOnly(event.currentTarget.checked)}\n />{\" \"}\n components only\n </label>\n </div>\n <div\n class=\"list\"\n role=\"tree\"\n aria-label=\"Reactive nodes\"\n on:keydown={(event) => navigate(props.state, event)}\n >\n <Show\n when={props.state.rows().rows.length > 0}\n fallback={<div class=\"empty\">Nothing recorded yet. Interact with the app.</div>}\n >\n <For each={props.state.rows().rows} key={(row) => row.id}>\n {(row) => <TreeRowView state={props.state} row={row()} />}\n </For>\n </Show>\n <Show when={props.state.rows().hidden > 0}>\n <div class=\"more\">\n <button type=\"button\" class=\"btn\" on:click={() => props.state.showMoreRows()}>\n Show more ({props.state.rows().hidden} hidden)\n </button>\n </div>\n </Show>\n </div>\n <Show when={props.state.selectedNode() !== undefined}>\n <Details state={props.state} />\n </Show>\n </div>\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{EVENT as e}from"./protocol.js";import{errorMessage as t,parseEditableText as n,preview as r,toEditableText as i}from"./preview.js";import{readInspections as a}from"./inspection.js";import{snapshotOf as o}from"./inspector.js";import{RingBuffer as s}from"./ring.js";import{InspectorModel as c}from"./model.js";function l(){return typeof performance<`u`?performance.now():Date.now()}function u(e){return typeof e==`object`&&!!e||typeof e==`function`}function d(e){return typeof e==`number`&&Number.isFinite(e)?e:0}function f(e){if(typeof e==`string`)return e.length>200?`${e.slice(0,199)}…`:e;if(typeof Node<`u`&&e instanceof Node){if(e instanceof Element){let t=e.outerHTML;return t.length>200?`${t.slice(0,199)}…`:t}return`#${e.nodeName}`}return r(e,200)}function p(p={}){let m=p.now??l,h=p.previewLength??80,g=p.maxPending??1e4,_=new c(p.limits),v=new s(p.events??1e4),y={received:0,recorded:0,ignored:0,unknown:0},b=/* @__PURE__ */ new WeakMap,x=/* @__PURE__ */ new WeakSet,S=/* @__PURE__ */ new Map,C=/* @__PURE__ */ new Map,w=/* @__PURE__ */ new Map,T=1,E=0,D=!1,O=0,k,A=[],j=!1,M=null,N=!1,P=/* @__PURE__ */ new Set,F=/* @__PURE__ */ new Set,I=typeof FinalizationRegistry==`function`?new FinalizationRegistry(e=>{S.delete(e),w.delete(e),_.nodes.has(e)&&R({k:`owner-`,seq:0,t:0,id:e})}):void 0;_.onBury=e=>{S.delete(e.id),C.delete(e.id),w.delete(e.id)};let L=()=>{if(j=!1,A.length===0)return;let e=A;A=[];for(let t of P)t(e)};function R(e,t){E+=1,e.seq=E,e.t=t??m(),y.recorded+=1,v.push(e),_.apply(e),P.size!==0&&(A.push(e),A.length>=g?L():j||(j=!0,queueMicrotask(L)))}let z=()=>{if(M===null)return;let e=M;M=null,N=!0,R({k:`flush+`,seq:0,t:0,render:e.render,user:e.user},e.t)},B=e=>u(e)?b.get(e)??0:0,V=e=>{let t=b.get(e);if(t!==void 0)return t;let n=T;return T+=1,b.set(e,n),n},H=e=>{let t=b.get(e);if(t!==void 0)return t;let n=V(e);return I?.register(e,n),n},U=(e,t)=>u(e)?O>0||u(t)&&x.has(t)?(x.add(e),!0):x.has(e):!1,W=e=>{let t=[];if(!Array.isArray(e))return t;for(let n=0;n<e.length;n+=1){let r=B(e[n]);r!==0&&t.push(r)}return t},G=e=>{let t=[];if(typeof Node>`u`)return t;let n=e=>{if(e instanceof DocumentFragment)for(let n of Array.from(e.childNodes))t.push(new WeakRef(n));else e instanceof Node&&t.push(new WeakRef(e))};if(Array.isArray(e))for(let t of e)n(t);else n(e);return t};function K(n,i,a,o,s,c){if(!D)switch(y.received+=1,n){case e.OWNER_CREATED:{if(!u(i)||U(i,a)){y.ignored+=1;return}let e=typeof s==`string`?s:void 0;R({k:`owner+`,seq:0,t:0,id:V(i),parent:B(a),label:typeof o==`string`?o:`owner`,...e===void 0?{}:{name:e}});return}case e.OWNER_DISPOSED:{let e=B(i);if(e===0){y.ignored+=1;return}R({k:`owner-`,seq:0,t:0,id:e});return}case e.SIGNAL_CREATED:{if(!u(i)||U(i,a)){y.ignored+=1;return}let e=H(i);S.set(e,new WeakRef(i));let t=typeof o==`string`?o:void 0;R({k:`signal+`,seq:0,t:0,id:e,owner:B(a),preview:r(s,h),...t===void 0?{}:{name:t}});return}case e.SIGNAL_WRITTEN:{let e=B(i);if(e===0){u(i)&&x.has(i)?y.ignored+=1:y.unknown+=1;return}R({k:`write`,seq:0,t:0,id:e,preview:r(a,h),writer:B(o),observers:W(s)});return}case e.COMPUTATION_CREATED:{if(!u(i)||U(i,a)){y.ignored+=1;return}let e=typeof s==`string`?s:void 0;R({k:`comp+`,seq:0,t:0,id:V(i),owner:B(a),kind:o===`memo`?`memo`:`effect`,render:c===!0,...e===void 0?{}:{name:e}});return}case e.COMPUTATION_RUN:{let e=B(i);if(e===0){u(i)&&x.has(i)?y.ignored+=1:y.unknown+=1;return}z(),R({k:`run`,seq:0,t:0,id:e,start:d(a),duration:d(o),changed:s===!0,sources:W(c)});return}case e.COMPUTATION_DISPOSED:{let e=B(i);if(e===0){y.ignored+=1;return}R({k:`comp-`,seq:0,t:0,id:e});return}case e.COMPONENT_MOUNTED:{let e=B(i);if(e===0){u(i)&&x.has(i)?y.ignored+=1:y.unknown+=1;return}let t=G(o);t.length>0&&C.set(e,t),R({k:`mount`,seq:0,t:0,id:e,name:typeof a==`string`?a:`Component`}),_.nodes.has(e)||C.delete(e);return}case e.COMPONENT_UNMOUNTED:{let e=B(i);if(e===0){y.ignored+=1;return}C.delete(e),R({k:`unmount`,seq:0,t:0,id:e});return}case e.RESOURCE_STATE:{if(!u(i)||U(i,s)){y.ignored+=1;return}let e=typeof o==`string`?o:void 0;R({k:`resource`,seq:0,t:0,id:H(i),owner:B(s),state:typeof a==`string`?a:String(a),...e===void 0?{}:{name:e}});return}case e.ERROR:{let e=typeof o==`string`?o:void 0;R({k:`error`,seq:0,t:0,id:B(i),message:t(a),...e===void 0?{}:{code:e}});return}case e.HYDRATION_MISMATCH:R({k:`hydration`,seq:0,t:0,message:typeof i==`string`?i:t(i),expected:f(a),actual:f(o)});return;case e.FLUSH_START:M={render:d(i),user:d(a),t:m()};return;case e.FLUSH_END:M=null,N&&(N=!1,R({k:`flush-`,seq:0,t:0,runs:d(i)}));return;case e.STORE_CREATED:{if(!u(i)||U(i,a)){y.ignored+=1;return}let e=q(i);if(e===null){y.ignored+=1;return}let t=H(i);w.set(t,new WeakRef(e)),R({k:`store+`,seq:0,t:0,id:t,owner:B(a),name:e.name}),_.nodes.has(t)||w.delete(t);return}default:y.ignored+=1}}let q=e=>{let t=Reflect.get(e,`name`),n=Reflect.get(e,`snapshot`),r=Reflect.get(e,`set`);return typeof t!=`string`||typeof n!=`function`||typeof r!=`function`?null:e},J=e=>{let t=S.get(e)?.deref();return t===void 0?{found:!1}:{found:!0,value:Reflect.get(t,`value`)}},Y=e=>({ok:!1,error:e});function X(e){try{switch(e.type){case`snapshot`:return{ok:!0,result:_.exportState()};case`stats`:return{ok:!0,result:{...y,events:v.size,dropped:v.dropped}};case`value`:{let t=J(e.id);return t.found?{ok:!0,result:{id:e.id,kind:`signal`,text:i(t.value),editable:p.writeSignal!==void 0}}:Y(`The signal is gone.`)}case`write`:{let t=S.get(e.id)?.deref();if(t===void 0)return Y(`The signal is gone.`);if(p.writeSignal===void 0)return Y(`Editing is not enabled.`);let r=n(e.text);if(!r.ok)return Y(r.error);let i=p.writeSignal;return i(t,r.value),{ok:!0,result:null}}case`store`:{let t=w.get(e.id)?.deref();return t===void 0?Y(`The store is gone.`):{ok:!0,result:{id:e.id,kind:`store`,text:i(t.snapshot()),editable:!0}}}case`store-set`:{let t=w.get(e.id)?.deref();if(t===void 0)return Y(`The store is gone.`);let r=n(e.text);return r.ok?(t.set(e.path,r.value),{ok:!0,result:null}):Y(r.error)}case`pause`:return D=!0,{ok:!0,result:null};case`resume`:return D=!1,{ok:!0,result:null};case`clear`:return Z(),{ok:!0,result:null};case`highlight`:return{ok:!0,result:null};case`inspect`:return{ok:!0,result:{sources:a(k?.inspections?.()??[])}};default:return Y(`Unknown command.`)}}catch(e){return Y(t(e))}}function Z(){_.importState({lastSeq:_.lastSeq,overflow:0,unknown:0,nodes:[],graveyard:[],runs:[],writes:[],flushes:[],errors:[],hydration:[]}),v.clear(),A=[],M=null,N=!1,S.clear(),C.clear(),w.clear()}let Q={model:_,events:v,stats:y,get paused(){return D},receive:K,attach(e){k=e;let t=e.attach(Q);return F.add(t),()=>{F.delete(t),t()}},subscribe(e){return P.add(e),()=>{P.delete(e)}},flush:L,quiet(e){O+=1;try{return e()}finally{--O}},pause(){D=!0},resume(){D=!1},clear:Z,snapshot:()=>o(_),state:()=>_.exportState(),handle:X,nodesOf(e){let t=[],n=C.get(e);if(n!==void 0)for(let e of n){let n=e.deref();n?.isConnected===!0&&t.push(n)}return t},dispose(){for(let e of F)e();F.clear(),k=void 0,P.clear(),A=[],S.clear(),C.clear(),w.clear(),_.onBury=void 0}};return Q}export{p as createAgent};
|
|
2
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","names":["now","isObject","value","toNumber","describeSide","html","preview","createAgent","clock","options","previewLength","maxPending","model","InspectorModel","events","RingBuffer","stats","ids","ignored","signals","components","stores","nextId","seq","paused","quietDepth","inspectionHook","pending","scheduled","queuedFlush","flushOpen","listeners","detachers","finalizer","id","emit","record","deliver","batch","listener","at","event","openFlush","held","lookup","register","existing","registerLeaf","isIgnored","parent","mapIds","out","values","i","nodesIn","push","candidate","child","entry","receive","kind","EVENT","a","b","name","d","c","e","refs","code","errorMessage","handle","readStoreHandle","snapshot","set","signalValue","node","fail","request","read","toEditableText","parsed","parseEditableText","write","store","clear","readInspections","error","agent","hook","detach","fn","snapshotOf","own","ref"],"sources":["../../src/agent.ts"],"sourcesContent":["import { readInspections } from \"./inspection\";\nimport { type GraphSnapshot, snapshotOf } from \"./inspector\";\nimport { InspectorModel, type ModelLimits, type ModelState, type NodeRecord } from \"./model\";\nimport { errorMessage, parseEditableText, preview, toEditableText } from \"./preview\";\nimport {\n type DevtoolsHook,\n EVENT,\n type HookSink,\n NO_ID,\n type StoreHandle,\n type WireEvent,\n} from \"./protocol\";\nimport { RingBuffer } from \"./ring\";\n\n/**\n * Options of {@link createAgent}. Every field is optional.\n *\n * @since 1.0.0\n */\nexport interface AgentOptions {\n /** Capacities of the model's buffers. The values left out take the defaults of `DEFAULT_LIMITS`. */\n limits?: Partial<ModelLimits>;\n /**\n * Raw events kept for the events log. The newest are kept; older ones are overwritten and counted\n * in `dropped`.\n *\n * @default 10000\n */\n events?: number;\n /**\n * Most events waiting for subscribers. When this many are pending they are delivered at once,\n * instead of at the end of the current microtask.\n *\n * @default 10000\n */\n maxPending?: number;\n /**\n * Maximum length of a value preview, in characters.\n *\n * @default 80\n */\n previewLength?: number;\n /**\n * The clock, in milliseconds. Defaults to `performance.now` (or `Date.now` where it is missing).\n */\n now?: () => number;\n /**\n * Writes a signal node for the value editor. Pass `writeSignal` from `@shadow-js/core/extend`.\n * Without it signals are shown but not editable: a write is refused with \"Editing is not enabled.\".\n */\n writeSignal?: (node: never, value: never) => unknown;\n}\n\n/**\n * Counters of what the agent saw. They keep growing while the agent runs; {@link Agent.clear} does not\n * reset them.\n *\n * @since 1.0.0\n */\nexport interface AgentStats {\n /** Events the agent was offered while it was not paused. */\n received: number;\n /** Events turned into wire events. */\n recorded: number;\n /**\n * Events the agent skipped on purpose: objects created inside {@link Agent.quiet} (the tools' own\n * UI), dispose and unmount events for objects it never named, malformed store handles, and event\n * kinds from a newer runtime.\n */\n ignored: number;\n /**\n * Writes, runs and mounts about objects the agent never saw created (reported before it attached,\n * when the hook's replay buffer had been released).\n */\n unknown: number;\n}\n\n/**\n * A command a panel sends to the agent, answered by {@link Agent.handle}. All fields are plain data,\n * so a command can cross a message channel:\n *\n * - `snapshot`: the model's state.\n * - `value` and `write`: read a signal as editable text, or write it.\n * - `store` and `store-set`: read a store as editable text, or write one path of it.\n * - `highlight`: the agent acknowledges it; the page draws the highlight (see `createLocalBackend`).\n * - `pause`, `resume`, `clear`: control recording. `stats`: the counters.\n * - `inspect`: every inspection source registered with `registerInspection`, read now.\n *\n * @since 1.0.0\n */\nexport type AgentRequest =\n | { type: \"snapshot\" }\n | { type: \"value\"; id: number }\n | { type: \"write\"; id: number; text: string }\n | { type: \"store\"; id: number }\n | { type: \"store-set\"; id: number; path: Array<string | number>; text: string }\n | { type: \"highlight\"; id: number | null }\n | { type: \"pause\" }\n | { type: \"resume\" }\n | { type: \"clear\" }\n | { type: \"stats\" }\n | { type: \"inspect\" };\n\n/**\n * The answer to an {@link AgentRequest}: the result, or an error message to show the user.\n *\n * @since 1.0.0\n */\nexport type AgentResponse = { ok: true; result: unknown } | { ok: false; error: string };\n\n/**\n * The value of a node, as text, for the value editor.\n *\n * @since 1.0.0\n */\nexport interface ValueResult {\n /** The node that was asked for. */\n id: number;\n /** `signal` for a signal, `store` for a store. */\n kind: \"signal\" | \"store\";\n /** The value as editable text (see {@link toEditableText}). */\n text: string;\n /** True when the panel may write it: a store always, a signal only when `writeSignal` is set. */\n editable: boolean;\n}\n\n/**\n * The agent. It receives the runtime's own objects from the hook, turns them into id-based\n * {@link WireEvent}s and folds them into an {@link InspectorModel}. Recording is O(1) per event and\n * every buffer is bounded: the raw event ring, the model's run, write and flush rings, the disposed-node\n * graveyard and the live node count.\n *\n * A development tool: the hook it attaches to is installed only in development builds (see\n * `installHook`).\n *\n * @since 1.0.0\n */\nexport interface Agent extends HookSink {\n /** The model the events are folded into. */\n readonly model: InspectorModel;\n /** The newest raw events, as many as the `events` option holds (default 10000). */\n readonly events: RingBuffer<WireEvent>;\n /** Counters of what the agent saw. */\n readonly stats: Readonly<AgentStats>;\n /** True while the agent drops the events the runtime reports (see {@link Agent.pause}). */\n readonly paused: boolean;\n /**\n * Attaches the agent to `hook`, so that the hook delivers its events to the agent. Pass the hook\n * from `installHook` or `getHook`.\n *\n * @param hook The hook to listen to.\n * @returns A function that detaches the agent from the hook.\n */\n attach(hook: DevtoolsHook): () => void;\n /**\n * Calls `listener` with batches of new events: once per microtask, or sooner when `maxPending`\n * events are waiting. Only events recorded after the call are delivered.\n *\n * @param listener Receives each batch, oldest first.\n * @returns A function that unsubscribes the listener.\n */\n subscribe(listener: (events: readonly WireEvent[]) => void): () => void;\n /** Delivers the pending events to the subscribers now, instead of at the end of the microtask. */\n flush(): void;\n /**\n * Runs `fn` while the agent ignores everything the runtime creates, and everything created under\n * those nodes. Use it to mount the tools' own UI, so that the tools never inspect themselves. An error\n * thrown by `fn` propagates.\n *\n * @param fn The work to run.\n * @returns What `fn` returns.\n */\n quiet<T>(fn: () => T): T;\n /** Stops recording: events the runtime reports are dropped until {@link Agent.resume}. */\n pause(): void;\n /** Resumes recording after {@link Agent.pause}. */\n resume(): void;\n /**\n * Forgets everything recorded so far: the model's nodes and records, the event ring, and the\n * references to live nodes. Nodes created later are tracked; live ones are not, and later events about\n * them count as unknown. {@link AgentStats} are not reset.\n */\n clear(): void;\n /** The graph snapshot of the model (see `snapshotOf`). Each call builds a new object. */\n snapshot(): GraphSnapshot;\n /** The model state, for sending to a remote panel. */\n state(): ModelState;\n /**\n * Answers a panel command. Never throws: a failure, such as an error from writing user state, comes\n * back as `ok: false`, and so does an unknown command.\n *\n * @param request The command.\n * @returns The answer.\n */\n handle(request: AgentRequest): AgentResponse;\n /**\n * The connected DOM nodes that the component `id` rendered, for highlighting. Empty when the\n * component is unknown or its nodes left the page. The agent holds the nodes weakly.\n *\n * @param id The component's node id.\n * @returns The nodes.\n */\n nodesOf(id: number): Node[];\n /**\n * Detaches from every hook it was attached to, and drops the listeners and the references to live\n * signals, components and stores. The model and the event ring keep what they recorded.\n */\n dispose(): void;\n}\n\n/**\n * Store handles by node id, held weakly: the handle lives as long as the store it reads, because the\n * runtime keeps it reachable from the store's root (see `createDeepStore` in `@shadow-js/core`).\n */\ntype Registry = Map<number, WeakRef<StoreHandle>>;\n\nfunction now(): number {\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\nfunction isObject(value: unknown): value is object {\n return (typeof value === \"object\" && value !== null) || typeof value === \"function\";\n}\n\nfunction toNumber(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\nfunction describeSide(value: unknown): string {\n if (typeof value === \"string\") return value.length > 200 ? `${value.slice(0, 199)}…` : value;\n if (typeof Node !== \"undefined\" && value instanceof Node) {\n if (value instanceof Element) {\n const html = value.outerHTML;\n return html.length > 200 ? `${html.slice(0, 199)}…` : html;\n }\n return `#${value.nodeName}`;\n }\n return preview(value, 200);\n}\n\n/**\n * Creates an agent that records the runtime's events. Attach it to a hook with `agent.attach(hook)`.\n * A development tool: the events come only from development builds, where the hook is installed.\n *\n * @param options Buffer sizes, clock and the signal writer. Every field is optional.\n * @returns The agent.\n * @example\n * import { createAgent, installHook } from \"@shadow-js/devtools\";\n * import { writeSignal } from \"@shadow-js/core/extend\";\n *\n * const hook = installHook();\n * const agent = createAgent({ writeSignal });\n * if (hook !== undefined) agent.attach(hook);\n * agent.snapshot().counts.nodes;\n * @since 1.0.0\n */\nexport function createAgent(options: AgentOptions = {}): Agent {\n const clock = options.now ?? now;\n const previewLength = options.previewLength ?? 80;\n const maxPending = options.maxPending ?? 10_000;\n const model = new InspectorModel(options.limits);\n const events = new RingBuffer<WireEvent>(options.events ?? 10_000);\n const stats: AgentStats = { received: 0, recorded: 0, ignored: 0, unknown: 0 };\n\n // Objects are named by id through weak maps: the agent never keeps the runtime's nodes alive.\n const ids = new WeakMap<object, number>();\n const ignored = new WeakSet<object>();\n const signals = new Map<number, WeakRef<object>>();\n const components = new Map<number, WeakRef<Node>[]>();\n const stores: Registry = new Map();\n let nextId = 1;\n let seq = 0;\n let paused = false;\n let quietDepth = 0;\n /** The hook the agent listens to: its registry of inspection sources answers `inspect`. */\n let inspectionHook: DevtoolsHook | undefined;\n let pending: WireEvent[] = [];\n let scheduled = false;\n /** A flush that started but has not recorded any run yet: empty flushes are not worth a record. */\n let queuedFlush: { render: number; user: number; t: number } | null = null;\n let flushOpen = false;\n const listeners = new Set<(events: readonly WireEvent[]) => void>();\n const detachers = new Set<() => void>();\n // Signals, stores and resources have no dispose event of their own when nothing owns them (or their\n // owner never disposes): the finalizer retires their node once the runtime has collected them. It is\n // registered once per object, so a resource that reports many states is not registered many times.\n const finalizer: FinalizationRegistry<number> | undefined =\n typeof FinalizationRegistry === \"function\"\n ? new FinalizationRegistry<number>((id) => {\n signals.delete(id);\n stores.delete(id);\n if (model.nodes.has(id)) emit({ k: \"owner-\", seq: 0, t: 0, id });\n })\n : undefined;\n\n model.onBury = (record: NodeRecord) => {\n signals.delete(record.id);\n components.delete(record.id);\n stores.delete(record.id);\n };\n\n const deliver = (): void => {\n scheduled = false;\n if (pending.length === 0) return;\n const batch = pending;\n pending = [];\n for (const listener of listeners) listener(batch);\n };\n\n /** Stamps, records and fans out one event. */\n function emit(event: WireEvent, at?: number): void {\n seq += 1;\n event.seq = seq;\n event.t = at ?? clock();\n stats.recorded += 1;\n events.push(event);\n model.apply(event);\n if (listeners.size === 0) return;\n pending.push(event);\n if (pending.length >= maxPending) {\n deliver();\n } else if (!scheduled) {\n scheduled = true;\n queueMicrotask(deliver);\n }\n }\n\n /** Emits the held-back flush start before the first event that belongs to it. */\n const openFlush = (): void => {\n if (queuedFlush === null) return;\n const held = queuedFlush;\n queuedFlush = null;\n flushOpen = true;\n emit({ k: \"flush+\", seq: 0, t: 0, render: held.render, user: held.user }, held.t);\n };\n\n const lookup = (value: unknown): number => {\n if (!isObject(value)) return NO_ID;\n return ids.get(value) ?? NO_ID;\n };\n\n const register = (value: object): number => {\n const existing = ids.get(value);\n if (existing !== undefined) return existing;\n const id = nextId;\n nextId += 1;\n ids.set(value, id);\n return id;\n };\n\n /**\n * Like `register`, for a leaf node (signal, store, resource): the finalizer retires it once the runtime\n * has collected the object. Owners and computations are not registered: an owner can be unreachable\n * while its signals are still in use, and retiring it would bury them too.\n */\n const registerLeaf = (value: object): number => {\n const existing = ids.get(value);\n if (existing !== undefined) return existing;\n const id = register(value);\n finalizer?.register(value, id);\n return id;\n };\n\n /** True (and remembered) when the object belongs to the tools' own UI. */\n const isIgnored = (value: unknown, parent: unknown): boolean => {\n if (!isObject(value)) return false;\n if (quietDepth > 0 || (isObject(parent) && ignored.has(parent))) {\n ignored.add(value);\n return true;\n }\n return ignored.has(value);\n };\n\n const mapIds = (values: unknown): number[] => {\n const out: number[] = [];\n if (!Array.isArray(values)) return out;\n for (let i = 0; i < values.length; i += 1) {\n const id = lookup(values[i]);\n if (id !== NO_ID) out.push(id);\n }\n return out;\n };\n\n const nodesIn = (value: unknown): WeakRef<Node>[] => {\n const out: WeakRef<Node>[] = [];\n if (typeof Node === \"undefined\") return out;\n const push = (candidate: unknown): void => {\n if (candidate instanceof DocumentFragment) {\n for (const child of Array.from(candidate.childNodes)) out.push(new WeakRef(child));\n } else if (candidate instanceof Node) {\n out.push(new WeakRef(candidate));\n }\n };\n if (Array.isArray(value)) for (const entry of value) push(entry);\n else push(value);\n return out;\n };\n\n function receive(kind: number, a: unknown, b: unknown, c: unknown, d: unknown, e: unknown): void {\n if (paused) return;\n stats.received += 1;\n switch (kind) {\n case EVENT.OWNER_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof d === \"string\" ? d : undefined;\n emit({\n k: \"owner+\",\n seq: 0,\n t: 0,\n id: register(a),\n parent: lookup(b),\n label: typeof c === \"string\" ? c : \"owner\",\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.OWNER_DISPOSED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n emit({ k: \"owner-\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.SIGNAL_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const id = registerLeaf(a);\n signals.set(id, new WeakRef(a));\n const name = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"signal+\",\n seq: 0,\n t: 0,\n id,\n owner: lookup(b),\n preview: preview(d, previewLength),\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.SIGNAL_WRITTEN: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n emit({\n k: \"write\",\n seq: 0,\n t: 0,\n id,\n preview: preview(b, previewLength),\n writer: lookup(c),\n observers: mapIds(d),\n });\n return;\n }\n case EVENT.COMPUTATION_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof d === \"string\" ? d : undefined;\n emit({\n k: \"comp+\",\n seq: 0,\n t: 0,\n id: register(a),\n owner: lookup(b),\n kind: c === \"memo\" ? \"memo\" : \"effect\",\n render: e === true,\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.COMPUTATION_RUN: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n openFlush();\n emit({\n k: \"run\",\n seq: 0,\n t: 0,\n id,\n start: toNumber(b),\n duration: toNumber(c),\n changed: d === true,\n sources: mapIds(e),\n });\n return;\n }\n case EVENT.COMPUTATION_DISPOSED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n emit({ k: \"comp-\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.COMPONENT_MOUNTED: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n const refs = nodesIn(c);\n if (refs.length > 0) components.set(id, refs);\n emit({ k: \"mount\", seq: 0, t: 0, id, name: typeof b === \"string\" ? b : \"Component\" });\n // A node beyond maxNodes is never buried, so its entry would never be removed: forget it.\n if (!model.nodes.has(id)) components.delete(id);\n return;\n }\n case EVENT.COMPONENT_UNMOUNTED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n components.delete(id);\n emit({ k: \"unmount\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.RESOURCE_STATE: {\n if (!isObject(a) || isIgnored(a, d)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"resource\",\n seq: 0,\n t: 0,\n id: registerLeaf(a),\n owner: lookup(d),\n state: typeof b === \"string\" ? b : String(b),\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.ERROR: {\n const code = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"error\",\n seq: 0,\n t: 0,\n id: lookup(a),\n message: errorMessage(b),\n ...(code === undefined ? {} : { code }),\n });\n return;\n }\n case EVENT.HYDRATION_MISMATCH:\n emit({\n k: \"hydration\",\n seq: 0,\n t: 0,\n message: typeof a === \"string\" ? a : errorMessage(a),\n expected: describeSide(b),\n actual: describeSide(c),\n });\n return;\n case EVENT.FLUSH_START:\n queuedFlush = { render: toNumber(a), user: toNumber(b), t: clock() };\n return;\n case EVENT.FLUSH_END:\n queuedFlush = null;\n if (flushOpen) {\n flushOpen = false;\n emit({ k: \"flush-\", seq: 0, t: 0, runs: toNumber(a) });\n }\n return;\n case EVENT.STORE_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const handle = readStoreHandle(a);\n if (handle === null) {\n stats.ignored += 1;\n return;\n }\n const id = registerLeaf(a);\n stores.set(id, new WeakRef(handle));\n emit({ k: \"store+\", seq: 0, t: 0, id, owner: lookup(b), name: handle.name });\n // A store beyond maxNodes is never buried: nothing else would forget its handle.\n if (!model.nodes.has(id)) stores.delete(id);\n return;\n }\n default:\n // An event kind from a newer runtime: ignore it.\n stats.ignored += 1;\n }\n }\n\n const readStoreHandle = (value: object): StoreHandle | null => {\n const name: unknown = Reflect.get(value, \"name\");\n const snapshot: unknown = Reflect.get(value, \"snapshot\");\n const set: unknown = Reflect.get(value, \"set\");\n if (typeof name !== \"string\" || typeof snapshot !== \"function\" || typeof set !== \"function\") {\n return null;\n }\n return value as StoreHandle;\n };\n\n const signalValue = (id: number): { found: true; value: unknown } | { found: false } => {\n const node = signals.get(id)?.deref();\n if (node === undefined) return { found: false };\n return { found: true, value: Reflect.get(node, \"value\") };\n };\n\n const fail = (error: string): AgentResponse => ({ ok: false, error });\n\n function handle(request: AgentRequest): AgentResponse {\n try {\n switch (request.type) {\n case \"snapshot\":\n return { ok: true, result: model.exportState() };\n case \"stats\":\n return { ok: true, result: { ...stats, events: events.size, dropped: events.dropped } };\n case \"value\": {\n const read = signalValue(request.id);\n if (!read.found) return fail(\"The signal is gone.\");\n const result: ValueResult = {\n id: request.id,\n kind: \"signal\",\n text: toEditableText(read.value),\n editable: options.writeSignal !== undefined,\n };\n return { ok: true, result };\n }\n case \"write\": {\n const node = signals.get(request.id)?.deref();\n if (node === undefined) return fail(\"The signal is gone.\");\n if (options.writeSignal === undefined) return fail(\"Editing is not enabled.\");\n const parsed = parseEditableText(request.text);\n if (!parsed.ok) return fail(parsed.error);\n const write = options.writeSignal as (node: object, value: unknown) => unknown;\n write(node, parsed.value);\n return { ok: true, result: null };\n }\n case \"store\": {\n const store = stores.get(request.id)?.deref();\n if (store === undefined) return fail(\"The store is gone.\");\n const result: ValueResult = {\n id: request.id,\n kind: \"store\",\n text: toEditableText(store.snapshot()),\n editable: true,\n };\n return { ok: true, result };\n }\n case \"store-set\": {\n const store = stores.get(request.id)?.deref();\n if (store === undefined) return fail(\"The store is gone.\");\n const parsed = parseEditableText(request.text);\n if (!parsed.ok) return fail(parsed.error);\n store.set(request.path, parsed.value);\n return { ok: true, result: null };\n }\n case \"pause\":\n paused = true;\n return { ok: true, result: null };\n case \"resume\":\n paused = false;\n return { ok: true, result: null };\n case \"clear\":\n clear();\n return { ok: true, result: null };\n case \"highlight\":\n // Drawing needs the page; the page side draws it (see createLocalBackend).\n return { ok: true, result: null };\n case \"inspect\":\n return {\n ok: true,\n result: { sources: readInspections(inspectionHook?.inspections?.() ?? []) },\n };\n default:\n return fail(\"Unknown command.\");\n }\n } catch (error) {\n // Writing user state can throw (a validation in the app): report it to the panel.\n return fail(errorMessage(error));\n }\n }\n\n function clear(): void {\n model.importState({\n lastSeq: model.lastSeq,\n overflow: 0,\n unknown: 0,\n nodes: [],\n graveyard: [],\n runs: [],\n writes: [],\n flushes: [],\n errors: [],\n hydration: [],\n });\n events.clear();\n pending = [];\n queuedFlush = null;\n flushOpen = false;\n signals.clear();\n components.clear();\n stores.clear();\n }\n\n const agent: Agent = {\n model,\n events,\n stats,\n get paused(): boolean {\n return paused;\n },\n receive,\n attach(hook): () => void {\n inspectionHook = hook;\n const detach = hook.attach(agent);\n detachers.add(detach);\n return () => {\n detachers.delete(detach);\n detach();\n };\n },\n subscribe(listener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n flush: deliver,\n quiet<T>(fn: () => T): T {\n quietDepth += 1;\n try {\n return fn();\n } finally {\n quietDepth -= 1;\n }\n },\n pause(): void {\n paused = true;\n },\n resume(): void {\n paused = false;\n },\n clear,\n snapshot: () => snapshotOf(model),\n state: () => model.exportState(),\n handle,\n nodesOf(id): Node[] {\n const out: Node[] = [];\n const own = components.get(id);\n if (own !== undefined) {\n for (const ref of own) {\n const node = ref.deref();\n if (node?.isConnected === true) out.push(node);\n }\n }\n return out;\n },\n dispose(): void {\n for (const detach of detachers) detach();\n detachers.clear();\n inspectionHook = undefined;\n listeners.clear();\n pending = [];\n signals.clear();\n components.clear();\n stores.clear();\n model.onBury = undefined;\n },\n };\n return agent;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t={}){let n=/* @__PURE__ */ new Set,r=!1;return{model:e.model,ready:Promise.resolve(),subscribe(t){let r=e.subscribe(()=>t());return n.add(r),()=>{n.delete(r),r()}},request(n){return r?Promise.reject(/* @__PURE__ */ Error(`The devtools backend was disposed.`)):n.type===`highlight`?(t.highlight?.(n.id),Promise.resolve({ok:!0,result:null})):Promise.resolve(e.handle(n))},dispose(){r=!0;for(let e of n)e();n.clear()}}}export{e as createLocalBackend};
|
|
2
|
+
//# sourceMappingURL=backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.js","names":["createLocalBackend","unsubscribers","disposed","agent","off","listener","request","page"],"sources":["../../src/backend.ts"],"sourcesContent":["import type { Agent, AgentRequest, AgentResponse } from \"./agent\";\nimport type { InspectorModel } from \"./model\";\n\n/**\n * What a panel talks to: a model that stays up to date, and requests. The panel never knows\n * whether the agent is in the same page ({@link createLocalBackend}) or behind a transport\n * (`connectRemote`), so the same UI serves the floating overlay and a browser-extension panel.\n *\n * @since 1.0.0\n */\nexport interface Backend {\n /** The inspector model, kept current. Read it; do not apply events to it yourself. */\n readonly model: InspectorModel;\n /** Resolves once the first complete state is available. */\n readonly ready: Promise<void>;\n /** Calls `listener` after the model changed (batched). Returns the unsubscribe function. */\n subscribe(listener: () => void): () => void;\n /** Sends a command to the agent. Resolves with its answer; rejects only when the backend is disposed. */\n request(request: AgentRequest): Promise<AgentResponse>;\n /** Stops listening. */\n dispose(): void;\n}\n\n/**\n * Page-side behavior that a backend cannot do through the agent alone.\n *\n * @since 1.0.0\n */\nexport interface PageHooks {\n /**\n * Highlights a component on the page. `null` clears the highlight. Without this hook a highlight\n * request is acknowledged and nothing is drawn.\n */\n highlight?(id: number | null): void;\n}\n\n/**\n * A backend for an agent in the same page: the model is the agent's own model, and the requests go to\n * the agent. A development tool: the page-side panel (`mountDevtools`) uses it in development builds.\n *\n * @param agent The agent.\n * @param page Page-side behavior such as the highlight overlay.\n * @returns The backend.\n * @example\n * import { createAgent, createLocalBackend } from \"@shadow-js/devtools\";\n *\n * const agent = createAgent();\n * const backend = createLocalBackend(agent, {\n * highlight: (id) => console.info(\"highlight\", id),\n * });\n * @since 1.0.0\n */\nexport function createLocalBackend(agent: Agent, page: PageHooks = {}): Backend {\n const unsubscribers = new Set<() => void>();\n let disposed = false;\n return {\n model: agent.model,\n ready: Promise.resolve(),\n subscribe(listener): () => void {\n const off = agent.subscribe(() => listener());\n unsubscribers.add(off);\n return () => {\n unsubscribers.delete(off);\n off();\n };\n },\n request(request): Promise<AgentResponse> {\n if (disposed) return Promise.reject(new Error(\"The devtools backend was disposed.\"));\n if (request.type === \"highlight\") {\n page.highlight?.(request.id);\n return Promise.resolve({ ok: true, result: null });\n }\n return Promise.resolve(agent.handle(request));\n },\n dispose(): void {\n disposed = true;\n for (const off of unsubscribers) off();\n unsubscribers.clear();\n },\n };\n}\n"],"mappings":"AAoDA,SAAgBA,EAAmB,EAAc,EAAkB,CAAC,EAAY,CAC9E,IAAMC,kBAAgB,IAAI,IACtBC,EAAW,GACf,MAAO,CACL,MAAOC,EAAM,MACb,MAAO,QAAQ,QAAQ,EACvB,UAAU,EAAsB,CAC9B,IAAMC,EAAMD,EAAM,cAAgBE,EAAS,CAAC,EAE5C,OADAJ,EAAc,IAAIG,CAAG,MACR,CACXH,EAAc,OAAOG,CAAG,EACxBA,EAAI,CACN,CACF,EACA,QAAQ,EAAiC,CAMvC,OALIF,EAAiB,QAAQ,OAAO,gBAAI,MAAM,oCAAoC,CAAC,EAC/EI,EAAQ,OAAS,aACnBC,EAAK,YAAYD,EAAQ,EAAE,EACpB,QAAQ,QAAQ,CAAE,GAAI,GAAM,OAAQ,IAAK,CAAC,GAE5C,QAAQ,QAAQH,EAAM,OAAOG,CAAO,CAAC,CAC9C,EACA,SAAgB,CACd,EAAW,GACX,IAAK,IAAMF,KAAOH,EAAeG,EAAI,EACrCH,EAAc,MAAM,CACtB,CACF,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{HOOK_KEY as e}from"./protocol.js";function t(t){return Reflect.get(t,e)}function n(e){return typeof e==`object`&&!!e&&typeof Reflect.get(e,`report`)==`function`&&typeof Reflect.get(e,`attach`)==`function`&&typeof Reflect.get(e,`version`)==`number`}function r(e={}){}function i(e=globalThis){let r=t(e);return n(r)&&r.version===1?r:void 0}function a(t=globalThis){Reflect.deleteProperty(t,e)}export{i as getHook,r as installHook,a as uninstallHook};
|
|
2
|
+
//# sourceMappingURL=hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook.js","names":["lookup","target","HOOK_KEY","isHook","value","installHook","getHook","existing","uninstallHook"],"sources":["../../src/hook.ts"],"sourcesContent":["import type { InspectionEntry, InspectionSource } from \"./inspection\";\nimport {\n type DevtoolsHook,\n HOOK_KEY,\n type HookSink,\n MAX_INSPECTIONS,\n PROTOCOL_VERSION,\n} from \"./protocol\";\n\n/**\n * Options of {@link installHook}. Every field is optional.\n *\n * @since 1.0.0\n */\nexport interface InstallHookOptions {\n /**\n * The object to install the hook on.\n *\n * @default globalThis\n */\n target?: object;\n /**\n * How many events to keep for a sink that attaches later. The first events reported are kept and\n * later ones are dropped. The buffer is released when the first sink attaches, so a hook that nobody\n * listens to cannot grow without bound.\n *\n * @default 5000\n */\n buffer?: number;\n}\n\n/**\n * The hook that {@link installHook} installs: a {@link DevtoolsHook} with the counters of its sinks.\n *\n * @since 1.0.0\n */\nexport interface InstalledHook extends DevtoolsHook {\n /** Number of times a sink threw while receiving an event. The hook swallows each error so the app keeps running. */\n readonly faults: number;\n /** The last error a sink threw, or `undefined` when none did. */\n readonly lastFault: unknown;\n}\n\ntype Buffered = readonly [number, unknown, unknown, unknown, unknown, unknown];\n\nfunction lookup(target: object): unknown {\n return Reflect.get(target, HOOK_KEY);\n}\n\nfunction isHook(value: unknown): value is InstalledHook {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof Reflect.get(value, \"report\") === \"function\" &&\n typeof Reflect.get(value, \"attach\") === \"function\" &&\n typeof Reflect.get(value, \"version\") === \"number\"\n );\n}\n\n/**\n * Installs the dev hook on `globalThis.__SHADOWJS_DEVTOOLS_HOOK__` (or on `options.target`), once, and\n * returns it. A development tool: in production builds (`__DEV__` false) it installs nothing and returns\n * `undefined`. The runtime looks the global up on each event, so call this before the application code\n * runs: events reported earlier are lost, and those reported before a sink attaches are buffered.\n *\n * When a hook with the same protocol version is already installed, it is returned. When the global holds\n * a hook of another version (or anything that is not a hook), nothing is installed and `undefined` is\n * returned.\n *\n * @param options Target object and replay buffer size. Every field is optional.\n * @returns The installed hook, or `undefined`.\n * @example\n * import { createAgent, installHook } from \"@shadow-js/devtools\";\n *\n * const hook = installHook();\n * const agent = createAgent();\n * if (hook !== undefined) agent.attach(hook);\n * @since 1.0.0\n */\nexport function installHook(options: InstallHookOptions = {}): InstalledHook | undefined {\n if (!__DEV__) return undefined;\n const target = options.target ?? globalThis;\n const existing = lookup(target);\n if (existing !== undefined) {\n return isHook(existing) && existing.version === PROTOCOL_VERSION ? existing : undefined;\n }\n const limit = options.buffer ?? 5000;\n const sinks: HookSink[] = [];\n const registry = new Map<string, InspectionSource>();\n let buffer: Buffered[] | null = [];\n let faults = 0;\n let lastFault: unknown;\n\n const deliver = (\n sink: HookSink,\n kind: number,\n a: unknown,\n b: unknown,\n c: unknown,\n d: unknown,\n e: unknown,\n ): void => {\n try {\n sink.receive(kind, a, b, c, d, e);\n } catch (error) {\n // A broken tool must never break the application: count the fault and keep the error.\n faults += 1;\n lastFault = error;\n }\n };\n\n const hook: InstalledHook = {\n version: PROTOCOL_VERSION,\n get sinks(): number {\n return sinks.length;\n },\n get faults(): number {\n return faults;\n },\n get lastFault(): unknown {\n return lastFault;\n },\n report(kind, a, b, c, d, e): void {\n if (sinks.length === 0) {\n if (buffer !== null && buffer.length < limit) buffer.push([kind, a, b, c, d, e]);\n return;\n }\n for (let i = 0; i < sinks.length; i += 1) {\n const sink = sinks[i];\n if (sink !== undefined) deliver(sink, kind, a, b, c, d, e);\n }\n },\n inspect(name, source): boolean {\n if (!registry.has(name) && registry.size >= MAX_INSPECTIONS) return false;\n registry.set(name, source);\n return true;\n },\n inspections(): readonly InspectionEntry[] {\n return [...registry].map(([name, source]) => ({ name, source }));\n },\n attach(sink): () => void {\n sinks.push(sink);\n if (buffer !== null) {\n const replay = buffer;\n buffer = null;\n for (const entry of replay)\n deliver(sink, entry[0], entry[1], entry[2], entry[3], entry[4], entry[5]);\n }\n return () => {\n const index = sinks.indexOf(sink);\n if (index !== -1) sinks.splice(index, 1);\n };\n },\n };\n Reflect.set(target, HOOK_KEY, hook);\n return hook;\n}\n\n/**\n * The hook that {@link installHook} installed, or `undefined` when none is installed or its protocol\n * version differs. A development tool: production builds never install a hook.\n *\n * @param target Object to look at (default `globalThis`).\n * @returns The installed hook, or `undefined`.\n * @example\n * import { getHook } from \"@shadow-js/devtools\";\n *\n * const sinks = getHook()?.sinks ?? 0;\n * @since 1.0.0\n */\nexport function getHook(target: object = globalThis): InstalledHook | undefined {\n const existing = lookup(target);\n return isHook(existing) && existing.version === PROTOCOL_VERSION ? existing : undefined;\n}\n\n/**\n * Removes the installed hook, so that a later {@link installHook} installs a new one. For tests and for\n * hot reloading the tools; a development tool, and a no-op in production builds. Sinks stay attached to\n * the old hook object.\n *\n * @param target Object to remove it from (default `globalThis`).\n * @example\n * import { uninstallHook } from \"@shadow-js/devtools\";\n *\n * uninstallHook();\n * @since 1.0.0\n */\nexport function uninstallHook(target: object = globalThis): void {\n Reflect.deleteProperty(target, HOOK_KEY);\n}\n"],"mappings":"yCA6CA,SAASA,EAAO,EAAyB,CACvC,OAAO,QAAQ,IAAIC,EAAQC,CAAQ,CACrC,CAEA,SAASC,EAAO,EAAwC,CACtD,OACE,OAAOC,GAAU,YACjBA,GACA,OAAO,QAAQ,IAAIA,EAAO,QAAQ,GAAM,YACxC,OAAO,QAAQ,IAAIA,EAAO,QAAQ,GAAM,YACxC,OAAO,QAAQ,IAAIA,EAAO,SAAS,GAAM,QAE7C,CAsBA,SAAgBC,EAAY,EAA8B,CAAC,EAA8B,CA6EzF,CAcA,SAAgBC,EAAQ,EAAiB,WAAuC,CAC9E,IAAMC,EAAWP,EAAOC,CAAM,EAC9B,OAAOE,EAAOI,CAAQ,GAAKA,EAAS,UAAA,EAA+BA,EAAW,IAAA,EAChF,CAcA,SAAgBC,EAAc,EAAiB,WAAkB,CAC/D,QAAQ,eAAeP,EAAQC,CAAQ,CACzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EVENT as e,HOOK_KEY as t,MAX_INSPECTIONS as n,MAX_INSPECTION_ROWS as r,NO_ID as i,PROTOCOL_VERSION as a}from"./protocol.js";import{getHook as o,installHook as s,uninstallHook as c}from"./hook.js";import{errorMessage as l,parseEditableText as u,preview as d,toEditableText as f}from"./preview.js";import{parseInspectResult as p,queryInspection as m,readInspections as h,registerInspection as g,routerInspection as _,sanitizeRows as v}from"./inspection.js";import{componentStats as y,explainRun as b,flameTimeline as x,nodeLabel as S,ownerTree as C,snapshotOf as w}from"./inspector.js";import{RingBuffer as T}from"./ring.js";import{DEFAULT_LIMITS as E,InspectorModel as D,foldEvents as O}from"./model.js";import{createAgent as k}from"./agent.js";import{createLocalBackend as A}from"./backend.js";import{boxOf as j,createOverlay as M}from"./overlay.js";import{CHANNEL as N,connectRemote as P,createBroadcastChannelTransport as F,createMemoryTransportPair as I,createPortTransport as L,createWindowTransport as R,isAgentMessage as z,isPanelMessage as B,serveAgent as V}from"./transport.js";import{TABS as H,createPanelState as U}from"./ui/state.js";import{Panel as W}from"./ui/panel.js";import{LAUNCHER_CSS as G,PANEL_CSS as K}from"./ui/styles.js";import{matchesShortcut as q,mountDevtools as J,parseShortcut as Y}from"./mount.js";import{createInspectionState as X}from"./ui/inspection-state.js";export{N as CHANNEL,E as DEFAULT_LIMITS,e as EVENT,t as HOOK_KEY,D as InspectorModel,G as LAUNCHER_CSS,n as MAX_INSPECTIONS,r as MAX_INSPECTION_ROWS,i as NO_ID,K as PANEL_CSS,a as PROTOCOL_VERSION,W as Panel,T as RingBuffer,H as TABS,j as boxOf,y as componentStats,P as connectRemote,k as createAgent,F as createBroadcastChannelTransport,X as createInspectionState,A as createLocalBackend,I as createMemoryTransportPair,M as createOverlay,U as createPanelState,L as createPortTransport,R as createWindowTransport,l as errorMessage,b as explainRun,x as flameTimeline,O as foldEvents,o as getHook,s as installHook,z as isAgentMessage,B as isPanelMessage,q as matchesShortcut,J as mountDevtools,S as nodeLabel,C as ownerTree,u as parseEditableText,p as parseInspectResult,Y as parseShortcut,d as preview,m as queryInspection,h as readInspections,g as registerInspection,_ as routerInspection,v as sanitizeRows,V as serveAgent,w as snapshotOf,f as toEditableText,c as uninstallHook};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./protocol.js";import{errorMessage as e,preview as t}from"./preview.js";function n(e){try{let n=JSON.stringify(e);return n===void 0?t(e,200):r(n)}catch{return t(e,200)}}function r(e){return e.length>200?`${e.slice(0,199)}…`:e}function i(e){let t=[],n=Math.min(e.length,500);for(let i=0;i<n;i+=1){let n=e[i];n!==void 0&&t.push({id:r(String(n.id)),label:r(String(n.label)),detail:r(String(n.detail)),state:r(String(n.state))})}return e.length>500&&t.push({id:`truncated`,label:`…`,detail:`${e.length-500} more rows not shown`,state:`truncated`}),t}function a(t,r=`Router`){return{title:r,rows(){let r=[],i=t.state.location(),a=t.state.status();r.push({id:`location`,label:`location`,detail:i===void 0?`none`:`${i.pathname}${i.search}${i.hash}`,state:a}),r.push({id:`pending`,label:`pending`,detail:String(t.state.pending()),state:t.state.pending()?`pending`:`settled`});let o=t.state.error();return o!==void 0&&r.push({id:`error`,label:`error`,detail:e(o),state:`errored`}),t.state.matches().forEach((e,t)=>{r.push({id:`match:${t}`,label:e.pattern,detail:n(e.params),state:e.id})}),r}}}function o(e,t=`Queries`){return{title:t,subscribe(t){try{return e.queryCache.subscribe(()=>t())}catch(e){if(e instanceof TypeError)return()=>void 0;throw e}},rows(){return e.queryCache.find().map(e=>{let t=[e.fetchStatus,`${e.observers} observers`];return e.isInvalidated&&t.push(`invalidated`),t.push(e.dataUpdatedAt>0?`updated ${new Date(e.dataUpdatedAt).toISOString()}`:`never updated`),{id:e.queryHash,label:n(e.queryKey),detail:t.join(`, `),state:e.status}})}}}function s(e,t){return!1}function c(t){return t.map(t=>{try{return{name:t.name,title:r(String(t.source.title)),rows:i(t.source.rows())}}catch(n){return{name:t.name,title:r(String(t.source.title)),rows:[],error:e(n)}}})}function l(e){return typeof e==`object`&&!!e}function u(e){return l(e)&&typeof e.id==`string`&&typeof e.label==`string`&&typeof e.detail==`string`&&typeof e.state==`string`}function d(e){if(!l(e)||!Array.isArray(e.sources))return null;let t=e.sources;if(t.length>16)return null;let n=[];for(let e of t){if(!l(e))return null;let t=e.name,r=e.title,i=e.rows,a=e.error;if(typeof t!=`string`||typeof r!=`string`||!Array.isArray(i)||i.length>501||!i.every(u)||a!==void 0&&typeof a!=`string`)return null;n.push(a===void 0?{name:t,title:r,rows:i}:{name:t,title:r,rows:i,error:a})}return n}export{d as parseInspectResult,o as queryInspection,c as readInspections,s as registerInspection,a as routerInspection,i as sanitizeRows};
|
|
2
|
+
//# sourceMappingURL=inspection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspection.js","names":["jsonText","text","value","preview","clip","sanitizeRows","out","limit","rows","index","row","routerInspection","location","router","status","error","errorMessage","match","queryInspection","client","notify","flags","query","registerInspection","readInspections","entries","entry","isRecord","isRow","\"id\"","\"label\"","\"detail\"","\"state\"","parseInspectResult","\"sources\"","sources","name","\"name\"","title","\"title\"","\"rows\"","\"error\""],"sources":["../../src/inspection.ts"],"sourcesContent":["import { getHook } from \"./hook\";\nimport { errorMessage, preview } from \"./preview\";\nimport { MAX_INSPECTION_ROWS, MAX_INSPECTIONS } from \"./protocol\";\n\n/** Longest text of one row field, in characters. Longer text is cut with an ellipsis. */\nconst FIELD_LENGTH = 200;\n\n/**\n * One line of an inspection view. Every field is text, so a row is plain data and can cross a\n * message channel unchanged.\n *\n * @since 1.0.0\n */\nexport interface InspectionRow {\n /** Stable within the source (a query hash, a route match index, or a fixed name). */\n readonly id: string;\n /** What the row is about: a query key, a route pattern, or a fixed label such as `location`. */\n readonly label: string;\n /** The details, as one line of text. */\n readonly detail: string;\n /** A short state word for the row, such as `success`, `fetching` or `idle`. */\n readonly state: string;\n}\n\n/**\n * Something that can describe itself to a devtools panel: a router, a query cache, or any other part of\n * the app. `rows()` is called when the panel asks, so it reports the current state, not a copy taken\n * earlier. It may throw; the agent reports the error to the panel instead of failing the request.\n *\n * @example\n * import { type InspectionSource, registerInspection } from \"@shadow-js/devtools\";\n *\n * const source: InspectionSource = {\n * title: \"Feature flags\",\n * rows: () => [{ id: \"beta\", label: \"beta\", detail: \"on\", state: \"on\" }],\n * };\n * registerInspection(\"flags\", source);\n * @since 1.0.0\n */\nexport interface InspectionSource {\n /** The heading of the view, for example `Router`. */\n readonly title: string;\n /** The rows now. At most {@link MAX_INSPECTION_ROWS} are sent. */\n rows(): readonly InspectionRow[];\n /**\n * Optional. Calls `notify` whenever the rows may have changed, and returns the function that stops the\n * calls. The extension's page side listens to it, so the panel refreshes at the moment of the change\n * instead of on a timer. A source without it is read when the panel asks.\n */\n subscribe?(notify: () => void): () => void;\n}\n\n/**\n * A registered source with its name (the key the app registered it under).\n *\n * @since 1.0.0\n */\nexport interface InspectionEntry {\n /** The name the source was registered under. Unique per hook. */\n readonly name: string;\n /** The source. */\n readonly source: InspectionSource;\n}\n\n/**\n * What the agent answers to an `inspect` request: every registered source, read now. A source that\n * threw has no rows and an `error` message instead.\n *\n * @since 1.0.0\n */\nexport interface InspectionSnapshot {\n /** The registration name. */\n readonly name: string;\n /** The heading of the view. */\n readonly title: string;\n /** The rows (at most {@link MAX_INSPECTION_ROWS}). */\n readonly rows: readonly InspectionRow[];\n /** Why the rows could not be read, or `undefined` when they were. */\n readonly error?: string;\n}\n\n/**\n * The part of a router the router inspection reads. A `Router` from `@shadow-js/router` fits this\n * shape; it is structural, so devtools does not depend on the router package.\n *\n * @since 1.0.0\n */\nexport interface RouterLike {\n /** The router's live state (accessors). */\n readonly state: {\n location():\n | { readonly pathname: string; readonly search: string; readonly hash: string }\n | undefined;\n status(): string;\n pending(): boolean;\n error(): unknown;\n matches(): readonly {\n readonly id: string;\n readonly pattern: string;\n readonly params: Readonly<Record<string, string>>;\n }[];\n };\n}\n\n/**\n * One query as the query cache reports it (a `QueryInfo` snapshot of `@shadow-js/query`).\n *\n * @since 1.0.0\n */\nexport interface QueryInfoLike {\n /** The key of the query (plain data). */\n readonly queryKey: unknown;\n /** The hash of the key, which identifies the cache entry. */\n readonly queryHash: string;\n /** `pending`, `success` or `error`. */\n readonly status: string;\n /** `idle`, `fetching` or `paused`. */\n readonly fetchStatus: string;\n /** Time (ms since epoch) of the last successful fetch or write; `0` when there was none. */\n readonly dataUpdatedAt: number;\n /** True after an invalidation, until the next successful fetch or write. */\n readonly isInvalidated: boolean;\n /** Mounted observers of the query. */\n readonly observers: number;\n}\n\n/**\n * The part of a query client the query inspection reads: its cache, as snapshots. A `QueryClient` from\n * `@shadow-js/query` fits this shape.\n *\n * @since 1.0.0\n */\nexport interface QueryLike {\n /** The query cache, read through its snapshots, and told about its changes. */\n readonly queryCache: {\n /** Every query, as snapshots. Untracked. */\n find(): readonly QueryInfoLike[];\n /** Calls `listener` for every change of the cache; returns the function that stops it. */\n subscribe(listener: () => void): () => void;\n };\n}\n\n/**\n * The JSON text of plain data (query keys and route params are plain data), cut to the field length.\n * Falls back to {@link preview} when the value does not serialize (a cycle, a bigint).\n *\n * @param value The value.\n * @returns One line of text.\n */\nfunction jsonText(value: unknown): string {\n try {\n const text = JSON.stringify(value);\n return text === undefined ? preview(value, FIELD_LENGTH) : clip(text);\n } catch {\n return preview(value, FIELD_LENGTH);\n }\n}\n\n/**\n * Cuts text to the row field length, with an ellipsis when it was longer.\n *\n * @param text The text.\n * @returns The text, at most {@link FIELD_LENGTH} characters.\n */\nfunction clip(text: string): string {\n return text.length > FIELD_LENGTH ? `${text.slice(0, FIELD_LENGTH - 1)}…` : text;\n}\n\n/**\n * Turns a source's rows into plain rows, cut to the row limit. Each field is coerced to text and cut,\n * so a source cannot send an object, a function or an unbounded string to the panel.\n *\n * @param rows The rows a source returned.\n * @returns At most {@link MAX_INSPECTION_ROWS} plain rows, and a last row that says how many were cut.\n * @example\n * import { type InspectionRow, sanitizeRows } from \"@shadow-js/devtools\";\n *\n * const rows: InspectionRow[] = [{ id: \"a\", label: \"a\", detail: \"b\", state: \"c\" }];\n * sanitizeRows(rows); // one plain row\n * @since 1.0.0\n */\nexport function sanitizeRows(rows: readonly InspectionRow[]): InspectionRow[] {\n const out: InspectionRow[] = [];\n const limit = Math.min(rows.length, MAX_INSPECTION_ROWS);\n for (let index = 0; index < limit; index += 1) {\n const row = rows[index];\n if (row === undefined) continue;\n out.push({\n id: clip(String(row.id)),\n label: clip(String(row.label)),\n detail: clip(String(row.detail)),\n state: clip(String(row.state)),\n });\n }\n if (rows.length > MAX_INSPECTION_ROWS) {\n out.push({\n id: \"truncated\",\n label: \"…\",\n detail: `${rows.length - MAX_INSPECTION_ROWS} more rows not shown`,\n state: \"truncated\",\n });\n }\n return out;\n}\n\n/**\n * Describes a router: its location, whether a navigation is pending, the error it last reported, and\n * the chain of matched routes. The source reads the router when the panel asks.\n *\n * @param router The router (`createRouter` from `@shadow-js/router`).\n * @param title The heading of the view. Default `Router`.\n * @returns An {@link InspectionSource}.\n * @example\n * import { type RouterLike, registerInspection, routerInspection } from \"@shadow-js/devtools\";\n *\n * declare const router: RouterLike; // the router from your app's createRouter()\n * registerInspection(\"router\", routerInspection(router));\n * @since 1.0.0\n */\nexport function routerInspection(router: RouterLike, title = \"Router\"): InspectionSource {\n return {\n title,\n rows() {\n const rows: InspectionRow[] = [];\n const location = router.state.location();\n const status = router.state.status();\n rows.push({\n id: \"location\",\n label: \"location\",\n detail:\n location === undefined\n ? \"none\"\n : `${location.pathname}${location.search}${location.hash}`,\n state: status,\n });\n rows.push({\n id: \"pending\",\n label: \"pending\",\n detail: String(router.state.pending()),\n state: router.state.pending() ? \"pending\" : \"settled\",\n });\n const error = router.state.error();\n if (error !== undefined) {\n rows.push({ id: \"error\", label: \"error\", detail: errorMessage(error), state: \"errored\" });\n }\n router.state.matches().forEach((match, index) => {\n rows.push({\n id: `match:${index}`,\n label: match.pattern,\n detail: jsonText(match.params),\n state: match.id,\n });\n });\n return rows;\n },\n };\n}\n\n/**\n * Describes a query cache: one row per query, with its key, fetch state, observers and age. The\n * source reads the cache when the panel asks, and it subscribes to the cache so that a change is\n * pushed to a connected panel as soon as it happens. A client without the `inspection` feature of\n * `@shadow-js/query` cannot notify, so the panel then reads the cache only when it asks.\n *\n * @param client The query client (`createQueryClient` from `@shadow-js/query`, created with\n * `features: [inspection]` for live updates), or anything with the same `queryCache.find()`.\n * @param title The heading of the view. Default `Queries`.\n * @returns An {@link InspectionSource}.\n * @example\n * import { type QueryLike, queryInspection, registerInspection } from \"@shadow-js/devtools\";\n *\n * declare const client: QueryLike; // the query client of your app\n * registerInspection(\"query\", queryInspection(client));\n * @since 1.0.0\n */\nexport function queryInspection(client: QueryLike, title = \"Queries\"): InspectionSource {\n return {\n title,\n subscribe(notify) {\n try {\n return client.queryCache.subscribe(() => notify());\n } catch (error) {\n // Only the missing `inspection` feature is expected here (a TypeError); anything else is a bug.\n if (error instanceof TypeError) return () => undefined;\n throw error;\n }\n },\n rows() {\n return client.queryCache.find().map((query) => {\n const flags: string[] = [query.fetchStatus, `${query.observers} observers`];\n if (query.isInvalidated) flags.push(\"invalidated\");\n flags.push(\n query.dataUpdatedAt > 0\n ? `updated ${new Date(query.dataUpdatedAt).toISOString()}`\n : \"never updated\",\n );\n return {\n id: query.queryHash,\n label: jsonText(query.queryKey),\n detail: flags.join(\", \"),\n state: query.status,\n };\n });\n },\n };\n}\n\n/**\n * Registers an inspection source on the installed devtools hook, under `name`, so a panel can show it\n * (see the `inspect` request of {@link createAgent}). Re-registering a name replaces its source. Returns\n * `false` when nothing was registered: no hook is installed (no development build, or the hook of\n * another protocol version), the name is empty, or {@link MAX_INSPECTIONS} sources are already\n * registered under other names. A development tool; production builds register nothing.\n *\n * @param name The name to show the source under. Not empty.\n * @param source The source.\n * @returns True when the source is registered.\n * @example\n * import { type RouterLike, registerInspection, routerInspection } from \"@shadow-js/devtools\";\n *\n * declare const router: RouterLike; // the router from your app's createRouter()\n * registerInspection(\"router\", routerInspection(router));\n * @since 1.0.0\n */\nexport function registerInspection(name: string, source: InspectionSource): boolean {\n if (!__DEV__) return false;\n if (name === \"\") return false;\n const hook = getHook();\n if (hook === undefined || hook.inspect === undefined) return false;\n return hook.inspect(name, source);\n}\n\n/**\n * Reads every registered source, now, as plain snapshots. A source that throws is reported with its\n * `error` and no rows. Used by the agent for the `inspect` request.\n *\n * @param entries The registered entries (the hook's `inspections()`).\n * @returns One snapshot per entry, in registration order.\n * @example\n * import { readInspections } from \"@shadow-js/devtools\";\n *\n * const snapshots = readInspections([]); // []\n * @since 1.0.0\n */\nexport function readInspections(entries: readonly InspectionEntry[]): InspectionSnapshot[] {\n return entries.map((entry) => {\n try {\n return {\n name: entry.name,\n title: clip(String(entry.source.title)),\n rows: sanitizeRows(entry.source.rows()),\n };\n } catch (error) {\n return {\n name: entry.name,\n title: clip(String(entry.source.title)),\n rows: [],\n error: errorMessage(error),\n };\n }\n });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isRow(value: unknown): value is InspectionRow {\n return (\n isRecord(value) &&\n typeof value[\"id\"] === \"string\" &&\n typeof value[\"label\"] === \"string\" &&\n typeof value[\"detail\"] === \"string\" &&\n typeof value[\"state\"] === \"string\"\n );\n}\n\n/**\n * Reads the answer of an `inspect` request. The panel gets it from the page, so it checks the shape\n * and the bounds before it trusts anything: every field is text, there are at most\n * {@link MAX_INSPECTIONS} sources and at most {@link MAX_INSPECTION_ROWS} + 1 rows each.\n *\n * @param value The `result` of an ok `inspect` response.\n * @returns The snapshots, or `null` when the value is not an answer to `inspect`.\n * @example\n * import { parseInspectResult } from \"@shadow-js/devtools\";\n *\n * parseInspectResult({ sources: [] }); // []\n * parseInspectResult({ nope: true }); // null\n * @since 1.0.0\n */\nexport function parseInspectResult(value: unknown): InspectionSnapshot[] | null {\n if (!isRecord(value) || !Array.isArray(value[\"sources\"])) return null;\n const sources: unknown[] = value[\"sources\"];\n if (sources.length > MAX_INSPECTIONS) return null;\n const out: InspectionSnapshot[] = [];\n for (const entry of sources) {\n if (!isRecord(entry)) return null;\n const name = entry[\"name\"];\n const title = entry[\"title\"];\n const rows = entry[\"rows\"];\n const error = entry[\"error\"];\n if (typeof name !== \"string\" || typeof title !== \"string\" || !Array.isArray(rows)) return null;\n if (rows.length > MAX_INSPECTION_ROWS + 1 || !rows.every(isRow)) return null;\n if (error !== undefined && typeof error !== \"string\") return null;\n out.push(error === undefined ? { name, title, rows } : { name, title, rows, error });\n }\n return out;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./protocol.js";function e(e){return e.name!==void 0&&e.name!==``?e.name:`${e.label}#${e.id}`}function t(e,t){let n=e.type===`memo`||e.type===`effect`;return{id:e.id,type:e.type,label:e.label,name:e.name,parent:e.parent,children:[],depth:0,isComponent:e.isComponent,component:t.componentOf(e.id),sources:e.sources.slice(),observers:e.observers===null?[]:[...e.observers],runs:e.runs,updates:n?Math.max(e.runs-1,0):0,lastDuration:e.lastDuration,totalDuration:e.totalDuration,preview:e.preview,writes:e.writes,state:e.state,render:e.render}}function n(e){let n=[],r=/* @__PURE__ */ new Map;for(let i of e.nodes.values()){let a=t(i,e);n.push(a),r.set(a.id,a)}let i=[];for(let e of n){let t=e.parent===0?void 0:r.get(e.parent);t===void 0?i.push(e.id):t.children.push(e.id)}let a=[...i].reverse();for(;a.length>0;){let e=a.pop();if(e===void 0)break;let t=r.get(e);if(t!==void 0)for(let e of t.children){let n=r.get(e);n!==void 0&&(n.depth=t.depth+1,a.push(e))}}let o=e=>n.filter(t=>t.type===e).length;return{seq:e.lastSeq,nodes:n,roots:i,overflow:e.overflow,counts:{nodes:n.length,owners:o(`owner`),components:n.filter(e=>e.isComponent).length,signals:o(`signal`),memos:o(`memo`),effects:o(`effect`),resources:o(`resource`),stores:o(`store`),runs:e.runs.size,writes:e.writes.size,flushes:e.flushes.size,errors:e.errors.size,hydration:e.hydration.size}}}function r(e){let t=/* @__PURE__ */ new Map;for(let n of e.nodes)t.set(n.id,{id:n.id,children:[]});let n=[];for(let r of e.nodes){let e=t.get(r.id);if(e===void 0)continue;let i=r.parent===0?void 0:t.get(r.parent);i===void 0?n.push(e):i.children.push(e)}return n}function i(e){let t=/* @__PURE__ */ new Map,n=e=>{let n=t.get(e.id);return n===void 0&&(n={id:e.id,name:e.name??`component#${e.id}`,alive:e.alive,mountedAt:e.mountedAt,computations:0,updates:0,totalDuration:0,lastUpdateSeq:0},t.set(e.id,n)),n};for(let t of e.nodes.values())t.isComponent&&n(t);for(let t of e.nodes.values()){if(t.type!==`memo`&&t.type!==`effect`)continue;let r=e.componentOf(t.id);if(r===0)continue;let i=e.node(r);if(i===void 0)continue;let a=n(i);a.computations+=1,a.updates+=Math.max(t.runs-1,0),a.totalDuration+=t.totalDuration,t.runs>1&&t.lastRunSeq>a.lastUpdateSeq&&(a.lastUpdateSeq=t.lastRunSeq)}return[...t.values()].sort((e,t)=>t.updates-e.updates||e.id-t.id)}function a(t,n={}){let r=/* @__PURE__ */ new Map;for(let e of t.runs){let t=r.get(e.flush);t===void 0?r.set(e.flush,[e]):t.push(e)}let i=n=>{let r=n.slice().sort((e,t)=>e.start-t.start||t.duration-e.duration||e.seq-t.seq),i=[],a=[],o=0;for(let n of r){for(;i.length>0&&(i[i.length-1]??0)<=n.start;)i.pop();let r=t.node(n.id),s=n.start+n.duration;a.push({seq:n.seq,id:n.id,label:r===void 0?`#${n.id}`:e(r),kind:r===void 0?`unknown`:r.type,start:n.start,end:s,duration:n.duration,depth:i.length,changed:n.changed,component:t.componentOf(n.id)}),i.push(s),i.length>o&&(o=i.length)}return{spans:a,lanes:o}},a=t.flushes.toArray();n.last!==void 0&&n.last>=0&&(a=n.last===0?[]:a.slice(-n.last));let o=[],s=r.get(0);if(s!==void 0&&s.length>0&&n.last===void 0){let{spans:e,lanes:t}=i(s),n=e[0]?.start??0,r=e.reduce((e,t)=>Math.max(e,t.end),n);o.push({flush:0,start:n,end:r,duration:r-n,render:0,user:0,lanes:t,spans:e})}for(let e of a){let{spans:t,lanes:n}=i(r.get(e.seq)??[]);o.push({flush:e.seq,start:e.start,end:e.end,duration:Math.max(e.end-e.start,0),render:e.render,user:e.user,lanes:n,spans:t})}return o}function o(e,t){for(let n=e.runs.size-1;n>=0;--n){let r=e.runs.at(n);if(r!==void 0&&t(r))return r}}function s(e,t,n){for(let r=e.writes.size-1;r>=0;--r){let i=e.writes.at(r);if(i!==void 0){if(i.seq===t&&i.id===n)return i;if(i.seq<t)return}}}function c(t,n){let r=t.node(n);return r===void 0?`#${n}`:e(r)}function l(e,t,n={}){let r=o(e,e=>e.id===t&&(n.runSeq===void 0||e.seq===n.runSeq));if(r===void 0)return null;let i=n.maxDepth??64,a=[{kind:`run`,id:r.id,label:c(e,r.id),seq:r.seq,changed:r.changed}],l=[],d=/* @__PURE__ */ new Set([r.seq]),f=!1,p=r;for(let t=0;t<i;t+=1){let[t,...n]=p.causes;if(t===void 0)break;if(p===r)for(let t of n)l.push({kind:t.kind,id:t.id,label:c(e,t.id),seq:t.seq});if(t.kind===`initial`||t.kind===`unknown`){a.push({kind:t.kind,id:t.id,label:t.kind,seq:t.seq});break}if(t.kind===`write`){let n=s(e,t.seq,t.id);if(n===void 0){a.push({kind:`write`,id:t.id,label:c(e,t.id),seq:t.seq}),f=!0;break}let r={kind:`write`,id:n.id,label:c(e,n.id),seq:n.seq,preview:n.preview};if(n.writer!==0&&(r.writer=c(e,n.writer)),a.push(r),n.writer===0)break;let i=u(e,n.writer,n.seq);if(i===void 0||d.has(i.seq)){f=i===void 0;break}d.add(i.seq),a.push({kind:`run`,id:i.id,label:c(e,i.id),seq:i.seq,changed:i.changed}),p=i;continue}let i=e.node(t.id);if(i!==void 0&&i.type!==`memo`&&i.type!==`effect`){a.push({kind:`unknown`,id:t.id,label:c(e,t.id),seq:t.seq});break}let m=o(e,e=>e.seq===t.seq);if(m===void 0||d.has(m.seq)){a.push({kind:`run`,id:t.id,label:c(e,t.id),seq:t.seq}),f=m===void 0;break}d.add(m.seq),a.push({kind:`run`,id:m.id,label:c(e,m.id),seq:m.seq,changed:m.changed}),p=m}return{target:{id:r.id,label:c(e,r.id),seq:r.seq,duration:r.duration},chain:a.reverse(),alsoCausedBy:l,truncated:f}}function u(e,t,n){let r;for(let i=e.runs.size-1;i>=0;--i){let a=e.runs.at(i);if(a===void 0||a.seq<=n)break;a.id===t&&(r=a)}return r}export{i as componentStats,l as explainRun,a as flameTimeline,e as nodeLabel,r as ownerTree,n as snapshotOf};
|
|
2
|
+
//# sourceMappingURL=inspector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector.js","names":["nodeLabel","node","toSnapshotNode","computation","record","model","snapshotOf","nodes","byId","roots","parent","stack","id","childId","child","count","type","ownerTree","snapshot","tree","componentStats","stats","ensure","stat","componentId","owner","b","a","flameTimeline","byFlush","run","bucket","lay","sorted","runs","open","spans","lanes","end","flushes","options","out","loose","start","latest","span","flush","findRun","i","predicate","findWrite","write","seq","label","explainRun","target","maxDepth","reverse","alsoCausedBy","visited","truncated","current","depth","primary","others","other","step","writerRun","findRunAfter","source","upstream","found"],"sources":["../../src/inspector.ts"],"sourcesContent":["import type { InspectorModel, NodeRecord, NodeType, RunRecord, WriteRecord } from \"./model\";\nimport { NO_ID } from \"./protocol\";\n\n/**\n * One node of the graph snapshot: an owner, signal, computation, resource or store, with its edges\n * and update counts. Plain data, safe to send to a remote panel as JSON. Built by {@link snapshotOf}.\n *\n * @since 1.0.0\n */\nexport interface SnapshotNode {\n /** Node id, as the runtime assigned it. */\n id: number;\n /** The kind of node. */\n type: NodeType;\n /** `root`, `owner`, `memo`, `effect`, `signal`, `resource`, `store`, or `component` after a mount. */\n label: string;\n /** The name given in the source, or `undefined`. */\n name: string | undefined;\n /** Owner id, or {@link NO_ID} for roots and detached nodes. */\n parent: number;\n /** Ids of the live nodes this node owns, in creation order. */\n children: number[];\n /** Ownership depth: 0 for roots. */\n depth: number;\n /** True when a component was mounted on this node. */\n isComponent: boolean;\n /** Nearest component that owns this node (the node itself for a component), or {@link NO_ID} for none. */\n component: number;\n /** Computations: ids read by the last run. */\n sources: number[];\n /** Ids of the computations that read this node. */\n observers: number[];\n /** Computations: how many times it ran, the first run included. */\n runs: number;\n /** Computations: runs after the first (the first run is the initial computation, not an update). */\n updates: number;\n /** Computations: duration of the last run, in milliseconds. */\n lastDuration: number;\n /** Computations: total duration of all runs, in milliseconds. */\n totalDuration: number;\n /** Signals and stores: a short text of the current value (empty for other kinds). */\n preview: string;\n /** Signals: number of writes. */\n writes: number;\n /** Resources: the last reported state (empty for other kinds). */\n state: string;\n /** Computations: created during the render phase. */\n render: boolean;\n}\n\n/**\n * Counts shown in the toolbar. The node counts are live nodes; `runs`, `writes`, `flushes`, `errors`\n * and `hydration` are the records the bounded buffers hold, not totals since the start.\n *\n * @since 1.0.0\n */\nexport interface SnapshotCounts {\n /** Live nodes of every kind. */\n nodes: number;\n /** Live owners, roots included. */\n owners: number;\n /** Live nodes a component was mounted on. */\n components: number;\n /** Live signals. */\n signals: number;\n /** Live memos. */\n memos: number;\n /** Live effects. */\n effects: number;\n /** Live resources. */\n resources: number;\n /** Live stores. */\n stores: number;\n /** Run records held (capacity: `ModelLimits.runs`). */\n runs: number;\n /** Write records held (capacity: `ModelLimits.writes`). */\n writes: number;\n /** Flush records held. */\n flushes: number;\n /** Error records held. */\n errors: number;\n /** Hydration mismatch records held. */\n hydration: number;\n}\n\n/**\n * The graph at one point in time: every live node with its edges, the root ids and the counts. Built\n * by {@link snapshotOf}.\n *\n * @since 1.0.0\n */\nexport interface GraphSnapshot {\n /** Sequence number of the last event folded into the model. */\n seq: number;\n /** Every live node, in creation order. */\n nodes: SnapshotNode[];\n /** Ids without a live parent, oldest first. */\n roots: number[];\n /** Counts for the toolbar. */\n counts: SnapshotCounts;\n /** Nodes the model ignored because `maxNodes` was reached. */\n overflow: number;\n}\n\n/**\n * Update statistics of one component, from {@link componentStats}.\n *\n * @since 1.0.0\n */\nexport interface ComponentStat {\n /** Id of the component's owner node. */\n id: number;\n /** The component's name, or `component#<id>` when it has none. */\n name: string;\n /** False once the component's owner has been disposed. */\n alive: boolean;\n /** Time the component was mounted, in milliseconds on the reporting clock. */\n mountedAt: number;\n /** Effects and memos whose nearest component is this one. */\n computations: number;\n /** Runs after the first, summed over those computations. */\n updates: number;\n /** Total run time of those computations, in milliseconds. */\n totalDuration: number;\n /** Sequence number of the latest update run, or 0 when there was none. */\n lastUpdateSeq: number;\n}\n\n/**\n * One bar of the flame timeline, from {@link flameTimeline}.\n *\n * @since 1.0.0\n */\nexport interface FlameSpan {\n /** Sequence number of the run event. */\n seq: number;\n /** The computation that ran. */\n id: number;\n /** Its display name (see {@link nodeLabel}), or `#id` when the node is unknown. */\n label: string;\n /** Its node type (`memo` or `effect`), or `unknown` when the node is unknown. */\n kind: string;\n /** Start time, in milliseconds on the reporting clock. */\n start: number;\n /** End time (`start` plus `duration`), in milliseconds. */\n end: number;\n /** Run time, in milliseconds. */\n duration: number;\n /** Lane: the number of runs of the same flush still running when this one started. */\n depth: number;\n /** Whether the run reported a changed value. */\n changed: boolean;\n /** The nearest component, or {@link NO_ID} for none. */\n component: number;\n}\n\n/**\n * The timeline of one flush, from {@link flameTimeline}.\n *\n * @since 1.0.0\n */\nexport interface FlameFlush {\n /** Flush sequence number, or `0` for runs that ran outside any flush. */\n flush: number;\n /** Start time in milliseconds (for flush `0`, the start of its first run). */\n start: number;\n /** End time in milliseconds (for flush `0`, the latest end of its runs). */\n end: number;\n /** `end - start` in milliseconds, never negative. */\n duration: number;\n /** Render queue size when the flush started (`0` for flush `0`). */\n render: number;\n /** User queue size when the flush started (`0` for flush `0`). */\n user: number;\n /** Number of lanes: the deepest span depth plus one, or `0` when there are no spans. */\n lanes: number;\n /** The runs of the flush, ordered by start time. */\n spans: FlameSpan[];\n}\n\n/**\n * A step of a causal chain, from {@link explainRun}.\n *\n * @since 1.0.0\n */\nexport interface ExplainStep {\n /**\n * `write`: a signal write. `run`: a computation run. `initial`: the first run of a computation.\n * `unknown`: a cause that is neither a write nor a run, such as a resource state change, or one that\n * was not recorded.\n */\n kind: \"write\" | \"run\" | \"initial\" | \"unknown\";\n /** The node involved, or {@link NO_ID} for an `initial` step. */\n id: number;\n /** The node's display name (see {@link nodeLabel}); for `initial` and `unknown` steps, the kind name. */\n label: string;\n /** Sequence number of the event that recorded the step. */\n seq: number;\n /** Writes: the new value (preview). */\n preview?: string;\n /** Writes: label of the computation that wrote, when known. */\n writer?: string;\n /** Runs: whether the value changed. */\n changed?: boolean;\n}\n\n/**\n * The answer to \"why did this run?\", from {@link explainRun}.\n *\n * @since 1.0.0\n */\nexport interface Explanation {\n /** The run that is explained: node id, label, event sequence number and duration in milliseconds. */\n target: { id: number; label: string; seq: number; duration: number };\n /** Root cause first, ending with the run itself. */\n chain: ExplainStep[];\n /** Other sources that also changed before the run. The chain follows only the earliest one. */\n alsoCausedBy: ExplainStep[];\n /** True when history ran out (the ring buffers dropped older records) before the chain reached its root. */\n truncated: boolean;\n}\n\n/**\n * A node of the nested owners tree, from {@link ownerTree}.\n *\n * @since 1.0.0\n */\nexport interface TreeNode {\n /** Node id. */\n id: number;\n /** Owned nodes, in creation order. */\n children: TreeNode[];\n}\n\n/**\n * The display name of a node: its given name, or `label#id` when it has none.\n *\n * @param node Any node record or snapshot node.\n * @returns The name, or `label#id`.\n * @example\n * import { nodeLabel } from \"@shadow-js/devtools\";\n *\n * nodeLabel({ name: \"count\", label: \"signal\", id: 3 }); // \"count\"\n * nodeLabel({ name: undefined, label: \"signal\", id: 3 }); // \"signal#3\"\n * @since 1.0.0\n */\nexport function nodeLabel(node: { name: string | undefined; label: string; id: number }): string {\n return node.name !== undefined && node.name !== \"\" ? node.name : `${node.label}#${node.id}`;\n}\n\nfunction toSnapshotNode(record: NodeRecord, model: InspectorModel): SnapshotNode {\n const computation = record.type === \"memo\" || record.type === \"effect\";\n return {\n id: record.id,\n type: record.type,\n label: record.label,\n name: record.name,\n parent: record.parent,\n children: [],\n depth: 0,\n isComponent: record.isComponent,\n component: model.componentOf(record.id),\n sources: record.sources.slice(),\n observers: record.observers === null ? [] : [...record.observers],\n runs: record.runs,\n updates: computation ? Math.max(record.runs - 1, 0) : 0,\n lastDuration: record.lastDuration,\n totalDuration: record.totalDuration,\n preview: record.preview,\n writes: record.writes,\n state: record.state,\n render: record.render,\n };\n}\n\n/**\n * Builds the graph snapshot: the owners tree, every live node with its sources and observers, and the\n * per-node run counts. Disposed nodes are not included. O(nodes); the model is not changed.\n *\n * @param model The folded model.\n * @returns A JSON-safe snapshot.\n * @example\n * import { foldEvents, snapshotOf } from \"@shadow-js/devtools\";\n *\n * const snap = snapshotOf(foldEvents([]));\n * const roots = snap.roots.map((id) => snap.nodes.find((node) => node.id === id));\n * @see ownerTree\n * @since 1.0.0\n */\nexport function snapshotOf(model: InspectorModel): GraphSnapshot {\n const nodes: SnapshotNode[] = [];\n const byId = new Map<number, SnapshotNode>();\n for (const record of model.nodes.values()) {\n const node = toSnapshotNode(record, model);\n nodes.push(node);\n byId.set(node.id, node);\n }\n const roots: number[] = [];\n for (const node of nodes) {\n const parent = node.parent === NO_ID ? undefined : byId.get(node.parent);\n if (parent === undefined) roots.push(node.id);\n else parent.children.push(node.id);\n }\n // Depth, iteratively: ownership depth is user controlled.\n const stack: number[] = [...roots].reverse();\n while (stack.length > 0) {\n const id = stack.pop();\n if (id === undefined) break;\n const node = byId.get(id);\n if (node === undefined) continue;\n for (const childId of node.children) {\n const child = byId.get(childId);\n if (child === undefined) continue;\n child.depth = node.depth + 1;\n stack.push(childId);\n }\n }\n const count = (type: NodeType): number => nodes.filter((node) => node.type === type).length;\n return {\n seq: model.lastSeq,\n nodes,\n roots,\n overflow: model.overflow,\n counts: {\n nodes: nodes.length,\n owners: count(\"owner\"),\n components: nodes.filter((node) => node.isComponent).length,\n signals: count(\"signal\"),\n memos: count(\"memo\"),\n effects: count(\"effect\"),\n resources: count(\"resource\"),\n stores: count(\"store\"),\n runs: model.runs.size,\n writes: model.writes.size,\n flushes: model.flushes.size,\n errors: model.errors.size,\n hydration: model.hydration.size,\n },\n };\n}\n\n/**\n * The owners as a nested tree, children in creation order. A node whose owner is no longer live\n * becomes a root. Built without recursion, so deep ownership cannot overflow the stack.\n *\n * @param snapshot A snapshot from {@link snapshotOf}.\n * @returns The roots, each with its children.\n * @example\n * import { foldEvents, ownerTree, snapshotOf } from \"@shadow-js/devtools\";\n *\n * const roots = ownerTree(snapshotOf(foldEvents([]))); // [] for an empty model\n * @see snapshotOf\n * @since 1.0.0\n */\nexport function ownerTree(snapshot: GraphSnapshot): TreeNode[] {\n const byId = new Map<number, TreeNode>();\n for (const node of snapshot.nodes) byId.set(node.id, { id: node.id, children: [] });\n const roots: TreeNode[] = [];\n for (const node of snapshot.nodes) {\n const tree = byId.get(node.id);\n if (tree === undefined) continue;\n const parent = node.parent === NO_ID ? undefined : byId.get(node.parent);\n if (parent === undefined) roots.push(tree);\n else parent.children.push(tree);\n }\n return roots;\n}\n\n/**\n * Per-component update counts: how many times the effects and memos under each component re-ran\n * after their first run. Components are the owners a `mount` event named. A component with no\n * computations still gets an entry.\n *\n * @param model The folded model.\n * @returns One entry per component, most updated first (ties by id).\n * @example\n * import { componentStats, foldEvents } from \"@shadow-js/devtools\";\n *\n * const top = componentStats(foldEvents([]))[0]; // undefined: no component is mounted yet\n * @since 1.0.0\n */\nexport function componentStats(model: InspectorModel): ComponentStat[] {\n const stats = new Map<number, ComponentStat>();\n const ensure = (record: NodeRecord): ComponentStat => {\n let stat = stats.get(record.id);\n if (stat === undefined) {\n stat = {\n id: record.id,\n name: record.name ?? `component#${record.id}`,\n alive: record.alive,\n mountedAt: record.mountedAt,\n computations: 0,\n updates: 0,\n totalDuration: 0,\n lastUpdateSeq: 0,\n };\n stats.set(record.id, stat);\n }\n return stat;\n };\n for (const record of model.nodes.values()) if (record.isComponent) ensure(record);\n for (const record of model.nodes.values()) {\n if (record.type !== \"memo\" && record.type !== \"effect\") continue;\n const componentId = model.componentOf(record.id);\n if (componentId === NO_ID) continue;\n const owner = model.node(componentId);\n if (owner === undefined) continue;\n const stat = ensure(owner);\n stat.computations += 1;\n stat.updates += Math.max(record.runs - 1, 0);\n stat.totalDuration += record.totalDuration;\n if (record.runs > 1 && record.lastRunSeq > stat.lastUpdateSeq)\n stat.lastUpdateSeq = record.lastRunSeq;\n }\n return [...stats.values()].sort((a, b) => b.updates - a.updates || a.id - b.id);\n}\n\n/**\n * Lays the runs of each flush out as flame-graph lanes: a run that started inside another run (a memo\n * pulled by an effect) sits one lane deeper. Runs outside any flush are grouped as flush `0`.\n *\n * @param model The folded model.\n * @param options `last`: only the newest N flushes (default: all kept). When `last` is set, flush `0`\n * is left out, and `last: 0` returns no flushes.\n * @returns One entry per flush, oldest first. Flush `0` comes first, when there are runs outside any\n * flush and `last` is not set.\n * @example\n * import { flameTimeline, foldEvents } from \"@shadow-js/devtools\";\n *\n * const lanes = flameTimeline(foldEvents([]), { last: 10 }).map((f) => [f.flush, f.lanes]); // []\n * @since 1.0.0\n */\nexport function flameTimeline(\n model: InspectorModel,\n options: { last?: number } = {},\n): FlameFlush[] {\n const byFlush = new Map<number, RunRecord[]>();\n for (const run of model.runs) {\n const bucket = byFlush.get(run.flush);\n if (bucket === undefined) byFlush.set(run.flush, [run]);\n else bucket.push(run);\n }\n const lay = (runs: RunRecord[]): { spans: FlameSpan[]; lanes: number } => {\n const sorted = runs\n .slice()\n .sort((a, b) => a.start - b.start || b.duration - a.duration || a.seq - b.seq);\n const open: number[] = [];\n const spans: FlameSpan[] = [];\n let lanes = 0;\n for (const run of sorted) {\n while (open.length > 0 && (open[open.length - 1] ?? 0) <= run.start) open.pop();\n const node = model.node(run.id);\n const end = run.start + run.duration;\n spans.push({\n seq: run.seq,\n id: run.id,\n label: node === undefined ? `#${run.id}` : nodeLabel(node),\n kind: node === undefined ? \"unknown\" : node.type,\n start: run.start,\n end,\n duration: run.duration,\n depth: open.length,\n changed: run.changed,\n component: model.componentOf(run.id),\n });\n open.push(end);\n if (open.length > lanes) lanes = open.length;\n }\n return { spans, lanes };\n };\n let flushes = model.flushes.toArray();\n if (options.last !== undefined && options.last >= 0)\n flushes = options.last === 0 ? [] : flushes.slice(-options.last);\n const out: FlameFlush[] = [];\n const loose = byFlush.get(0);\n if (loose !== undefined && loose.length > 0 && options.last === undefined) {\n const { spans, lanes } = lay(loose);\n const start = spans[0]?.start ?? 0;\n const end = spans.reduce((latest, span) => Math.max(latest, span.end), start);\n out.push({\n flush: 0,\n start,\n end,\n duration: end - start,\n render: 0,\n user: 0,\n lanes,\n spans,\n });\n }\n for (const flush of flushes) {\n const { spans, lanes } = lay(byFlush.get(flush.seq) ?? []);\n out.push({\n flush: flush.seq,\n start: flush.start,\n end: flush.end,\n duration: Math.max(flush.end - flush.start, 0),\n render: flush.render,\n user: flush.user,\n lanes,\n spans,\n });\n }\n return out;\n}\n\nfunction findRun(\n model: InspectorModel,\n predicate: (run: RunRecord) => boolean,\n): RunRecord | undefined {\n for (let i = model.runs.size - 1; i >= 0; i -= 1) {\n const run = model.runs.at(i);\n if (run !== undefined && predicate(run)) return run;\n }\n return undefined;\n}\n\nfunction findWrite(model: InspectorModel, seq: number, id: number): WriteRecord | undefined {\n for (let i = model.writes.size - 1; i >= 0; i -= 1) {\n const write = model.writes.at(i);\n if (write === undefined) continue;\n if (write.seq === seq && write.id === id) return write;\n if (write.seq < seq) return undefined;\n }\n return undefined;\n}\n\nfunction label(model: InspectorModel, id: number): string {\n const node = model.node(id);\n return node === undefined ? `#${id}` : nodeLabel(node);\n}\n\n/**\n * \"Why did this run?\": follows the changed sources of a run back to the signal write that started\n * it. A write made inside another effect continues with that effect's own run, so the chain can span\n * several computations. The primary path follows the earliest changed source; the others are\n * listed in `alsoCausedBy`. A chain longer than `maxDepth` is cut at that length.\n *\n * @param model The folded model.\n * @param id The computation to explain.\n * @param options `runSeq`: explain this run instead of the latest; `maxDepth`: chain length limit (default 64).\n * @returns The explanation, or `null` when the computation has no recorded run.\n * @example\n * import { explainRun, foldEvents } from \"@shadow-js/devtools\";\n *\n * const why = explainRun(foldEvents([]), 3);\n * const labels = why?.chain.map((step) => step.label); // undefined: no run of node 3 is recorded\n * @see nodeLabel\n * @since 1.0.0\n */\nexport function explainRun(\n model: InspectorModel,\n id: number,\n options: { runSeq?: number; maxDepth?: number } = {},\n): Explanation | null {\n const target = findRun(\n model,\n (run) => run.id === id && (options.runSeq === undefined || run.seq === options.runSeq),\n );\n if (target === undefined) return null;\n const maxDepth = options.maxDepth ?? 64;\n const reverse: ExplainStep[] = [\n {\n kind: \"run\",\n id: target.id,\n label: label(model, target.id),\n seq: target.seq,\n changed: target.changed,\n },\n ];\n const alsoCausedBy: ExplainStep[] = [];\n const visited = new Set<number>([target.seq]);\n let truncated = false;\n let current: RunRecord = target;\n for (let depth = 0; depth < maxDepth; depth += 1) {\n const [primary, ...others] = current.causes;\n if (primary === undefined) break;\n if (current === target) {\n for (const other of others) {\n alsoCausedBy.push({\n kind: other.kind,\n id: other.id,\n label: label(model, other.id),\n seq: other.seq,\n });\n }\n }\n if (primary.kind === \"initial\" || primary.kind === \"unknown\") {\n reverse.push({ kind: primary.kind, id: primary.id, label: primary.kind, seq: primary.seq });\n break;\n }\n if (primary.kind === \"write\") {\n const write = findWrite(model, primary.seq, primary.id);\n if (write === undefined) {\n reverse.push({\n kind: \"write\",\n id: primary.id,\n label: label(model, primary.id),\n seq: primary.seq,\n });\n truncated = true;\n break;\n }\n const step: ExplainStep = {\n kind: \"write\",\n id: write.id,\n label: label(model, write.id),\n seq: write.seq,\n preview: write.preview,\n };\n if (write.writer !== NO_ID) step.writer = label(model, write.writer);\n reverse.push(step);\n if (write.writer === NO_ID) break;\n // The writing computation's run ends after the write: take its first run recorded later.\n const writerRun = findRunAfter(model, write.writer, write.seq);\n if (writerRun === undefined || visited.has(writerRun.seq)) {\n truncated = writerRun === undefined;\n break;\n }\n visited.add(writerRun.seq);\n reverse.push({\n kind: \"run\",\n id: writerRun.id,\n label: label(model, writerRun.id),\n seq: writerRun.seq,\n changed: writerRun.changed,\n });\n current = writerRun;\n continue;\n }\n // A source that is not a computation (a resource changed state) has no run to step into.\n const source = model.node(primary.id);\n if (source !== undefined && source.type !== \"memo\" && source.type !== \"effect\") {\n reverse.push({\n kind: \"unknown\",\n id: primary.id,\n label: label(model, primary.id),\n seq: primary.seq,\n });\n break;\n }\n const upstream = findRun(model, (run) => run.seq === primary.seq);\n if (upstream === undefined || visited.has(upstream.seq)) {\n reverse.push({\n kind: \"run\",\n id: primary.id,\n label: label(model, primary.id),\n seq: primary.seq,\n });\n truncated = upstream === undefined;\n break;\n }\n visited.add(upstream.seq);\n reverse.push({\n kind: \"run\",\n id: upstream.id,\n label: label(model, upstream.id),\n seq: upstream.seq,\n changed: upstream.changed,\n });\n current = upstream;\n }\n return {\n target: {\n id: target.id,\n label: label(model, target.id),\n seq: target.seq,\n duration: target.duration,\n },\n chain: reverse.reverse(),\n alsoCausedBy,\n truncated,\n };\n}\n\nfunction findRunAfter(model: InspectorModel, id: number, seq: number): RunRecord | undefined {\n let found: RunRecord | undefined;\n for (let i = model.runs.size - 1; i >= 0; i -= 1) {\n const run = model.runs.at(i);\n if (run === undefined || run.seq <= seq) break;\n if (run.id === id) found = run;\n }\n return found;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./protocol.js";import{RingBuffer as e}from"./ring.js";const t={runs:2e4,writes:1e4,flushes:1e3,errors:200,graveyard:4e3,maxNodes:1e5};function n(e,t,n,r,i){return{id:e,type:t,label:n,name:void 0,parent:0,alive:!0,createdSeq:r,createdAt:i,disposedSeq:0,render:!1,isComponent:!1,mountedAt:0,sources:[],observers:null,runs:0,totalDuration:0,lastDuration:0,lastStart:0,lastRunSeq:0,lastChangeSeq:0,preview:``,writes:0,lastWriteSeq:0,lastWriter:0,state:``}}function r(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}var i=class{limits;nodes=/* @__PURE__ */ new Map;graveyard=/* @__PURE__ */ new Map;runs;writes;flushes;errors;hydration;lastSeq=0;overflow=0;unknown=0;componentEpoch=0;get componentCacheSize(){return this.componentCache.size}onBury;openFlush=null;flushRuns=0;leaves=/* @__PURE__ */ new Map;componentCache=/* @__PURE__ */ new Map;constructor(n={}){this.limits={...t,...n},this.runs=new e(this.limits.runs),this.writes=new e(this.limits.writes),this.flushes=new e(this.limits.flushes),this.errors=new e(this.limits.errors),this.hydration=new e(this.limits.errors)}node(e){return this.nodes.get(e)??this.graveyard.get(e)}create(e,t,r){if(this.nodes.size>=this.limits.maxNodes)return this.overflow+=1,null;let i=n(e.id,t,r,e.seq,e.t);return this.nodes.set(e.id,i),i}addLeaf(e,t){if(e===0)return;let n=this.leaves.get(e);if(n===void 0&&(n=[],this.leaves.set(e,n)),n.push(t),n.length>1024&&n.length%1024==0){let e=n.filter(e=>this.nodes.has(e));n.length=0,n.push(...e)}}bury(e,t){let n=[e];for(;n.length>0;){let e=n.pop();if(e===void 0)break;if(!this.nodes.has(e.id))continue;if(e.alive=!1,e.disposedSeq=t,this.nodes.delete(e.id),e.sources.length>0&&this.setSources(e,[]),e.observers!==null)for(let t of e.observers){let n=this.nodes.get(t);n!==void 0&&(n.sources=n.sources.filter(t=>t!==e.id))}if(e.observers=null,this.graveyard.set(e.id,e),this.graveyard.size>this.limits.graveyard){let e=this.graveyard.keys().next();e.done!==!0&&this.graveyard.delete(e.value)}this.componentCache.delete(e.id);let r=this.leaves.get(e.id);if(r!==void 0){this.leaves.delete(e.id);for(let e of r){let t=this.nodes.get(e);t!==void 0&&n.push(t)}}this.onBury?.(e)}}setSources(e,t){let n=e.sources;if(r(n,t))return;let i=new Set(t);for(let t of n)i.has(t)||this.nodes.get(t)?.observers?.delete(e.id);for(let n of t){let t=this.nodes.get(n);t!==void 0&&(t.observers===null&&(t.observers=/* @__PURE__ */ new Set),t.observers.add(e.id))}e.sources=t.slice()}apply(e){if(!(e.seq<=this.lastSeq))switch(this.lastSeq=e.seq,e.k){case`owner+`:{let t=this.create(e,`owner`,e.label);if(t===null)return;t.parent=e.parent,t.name=e.name;return}case`owner-`:case`comp-`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.isComponent&&(this.componentEpoch+=1),this.bury(t,e.seq);return}case`signal+`:{let t=this.create(e,`signal`,`signal`);if(t===null)return;t.parent=e.owner,t.name=e.name,t.preview=e.preview,this.addLeaf(e.owner,e.id);return}case`write`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.writes+=1,t.preview=e.preview,t.lastWriteSeq=e.seq,t.lastChangeSeq=e.seq,t.lastWriter=e.writer,this.writes.push({seq:e.seq,t:e.t,id:e.id,preview:e.preview,writer:e.writer,observers:e.observers});return}case`comp+`:{let t=this.create(e,e.kind,e.kind);if(t===null)return;t.parent=e.owner,t.name=e.name,t.render=e.render;return}case`run`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}let n=this.causesOf(t);t.runs+=1,t.totalDuration+=e.duration,t.lastDuration=e.duration,t.lastStart=e.start,t.lastRunSeq=e.seq,e.changed&&(t.lastChangeSeq=e.seq),this.setSources(t,e.sources),this.runs.push({seq:e.seq,t:e.t,id:e.id,start:e.start,duration:e.duration,changed:e.changed,flush:this.openFlush===null?0:this.openFlush.seq,causes:n}),this.openFlush!==null&&(this.flushRuns+=1);return}case`mount`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.isComponent=!0,t.label=`component`,t.name=e.name,t.mountedAt=e.t,this.componentEpoch+=1;return}case`unmount`:if(this.nodes.get(e.id)===void 0){this.unknown+=1;return}this.componentEpoch+=1;return;case`resource`:{let t=this.nodes.get(e.id);if(t===void 0){if(t=this.create(e,`resource`,`resource`)??void 0,t===void 0)return;t.parent=e.owner,this.addLeaf(e.owner,e.id)}t.state=e.state,e.name!==void 0&&(t.name=e.name),t.lastChangeSeq=e.seq;return}case`store+`:{let t=this.create(e,`store`,`store`);if(t===null)return;t.parent=e.owner,t.name=e.name,this.addLeaf(e.owner,e.id);return}case`error`:this.errors.push({seq:e.seq,t:e.t,id:e.id,message:e.message,code:e.code});return;case`hydration`:this.hydration.push({seq:e.seq,t:e.t,message:e.message,expected:e.expected,actual:e.actual});return;case`flush+`:{let t={seq:e.seq,startSeq:e.seq,start:e.t,end:e.t,render:e.render,user:e.user,runs:0,open:!0};this.flushes.push(t),this.openFlush=t,this.flushRuns=0;return}case`flush-`:{let t=this.openFlush;if(t===null)return;t.end=e.t,t.runs=this.flushRuns,t.open=!1,this.openFlush=null;return}}}causesOf(e){if(e.runs===0)return[{kind:`initial`,id:0,seq:e.createdSeq}];let t=[];for(let n of e.sources){let r=this.nodes.get(n);r===void 0||r.lastChangeSeq<=e.lastRunSeq||t.push({kind:r.type===`signal`?`write`:`run`,id:n,seq:r.lastChangeSeq})}return t.length===0&&t.push({kind:`unknown`,id:0,seq:e.lastRunSeq}),t.sort((e,t)=>e.seq-t.seq),t}componentOf(e){let t=this.componentCache.get(e);if(t!==void 0&&t.epoch===this.componentEpoch)return t.component;let n=[],r=0,i=e,a=this.nodes.size+this.graveyard.size+1;for(let e=0;e<a;e+=1){let e=this.componentCache.get(i);if(e!==void 0&&e.epoch===this.componentEpoch){r=e.component;break}let t=this.node(i);if(t===void 0)break;if(n.push(i),t.isComponent){r=t.id;break}if(t.parent===0)break;i=t.parent}let o={epoch:this.componentEpoch,component:r};for(let e of n)this.componentCache.set(e,o);return this.componentCache.size>this.limits.maxNodes&&this.componentCache.clear(),r}exportState(){let e=e=>({...e,sources:e.sources.slice(),observers:e.observers===null?[]:[...e.observers]});return{lastSeq:this.lastSeq,overflow:this.overflow,unknown:this.unknown,nodes:[...this.nodes.values()].map(e),graveyard:[...this.graveyard.values()].map(e),runs:this.runs.toArray(),writes:this.writes.toArray(),flushes:this.flushes.toArray().map(e=>({...e})),errors:this.errors.toArray(),hydration:this.hydration.toArray()}}importState(e){this.nodes.clear(),this.graveyard.clear(),this.runs.clear(),this.writes.clear(),this.flushes.clear(),this.errors.clear(),this.hydration.clear(),this.componentCache.clear(),this.openFlush=null,this.flushRuns=0;let t=e=>({...e,sources:e.sources.slice(),observers:e.observers.length===0?null:new Set(e.observers)});this.leaves.clear();for(let n of e.nodes){let e=t(n);this.nodes.set(n.id,e),(e.type===`signal`||e.type===`store`||e.type===`resource`)&&this.addLeaf(e.parent,e.id)}for(let n of e.graveyard)this.graveyard.set(n.id,t(n));for(let t of e.runs)this.runs.push(t);for(let t of e.writes)this.writes.push(t);for(let t of e.flushes){let e={...t};this.flushes.push(e),e.open&&(this.openFlush=e)}for(let t of e.errors)this.errors.push(t);for(let t of e.hydration)this.hydration.push(t);this.lastSeq=e.lastSeq,this.overflow=e.overflow,this.unknown=e.unknown,this.componentEpoch+=1}};function a(e,t){let n=new i(t);for(let t of e)n.apply(t);return n}export{t as DEFAULT_LIMITS,i as InspectorModel,a as foldEvents};
|
|
2
|
+
//# sourceMappingURL=model.js.map
|