@alloy-js/core 0.23.0-dev.0 → 0.23.0-dev.8
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 +0 -22
- package/dist/devtools/index.html +68 -0
- package/dist/src/binder.d.ts +2 -0
- package/dist/src/binder.d.ts.map +1 -1
- package/dist/src/binder.js +55 -12
- package/dist/src/binder.js.map +1 -1
- package/dist/src/components/AppendFile.d.ts.map +1 -1
- package/dist/src/components/AppendFile.js +14 -3
- package/dist/src/components/AppendFile.js.map +1 -1
- package/dist/src/components/Block.js +1 -1
- package/dist/src/components/Block.js.map +1 -1
- package/dist/src/components/Declaration.d.ts.map +1 -1
- package/dist/src/components/Declaration.js +2 -1
- package/dist/src/components/Declaration.js.map +1 -1
- package/dist/src/components/Scope.d.ts.map +1 -1
- package/dist/src/components/Scope.js +4 -1
- package/dist/src/components/Scope.js.map +1 -1
- package/dist/src/components/TemplateFile.d.ts.map +1 -1
- package/dist/src/components/TemplateFile.js +18 -3
- package/dist/src/components/TemplateFile.js.map +1 -1
- package/dist/src/content-slot.d.ts.map +1 -1
- package/dist/src/content-slot.js +6 -5
- package/dist/src/content-slot.js.map +1 -1
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.js +8 -1
- package/dist/src/context.js.map +1 -1
- package/dist/src/debug/cli.d.ts +6 -0
- package/dist/src/debug/cli.d.ts.map +1 -0
- package/dist/src/{debug.js → debug/cli.js} +78 -84
- package/dist/src/debug/cli.js.map +1 -0
- package/dist/src/debug/diagnostics.test.d.ts +2 -0
- package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
- package/dist/src/debug/diagnostics.test.js +45 -0
- package/dist/src/debug/diagnostics.test.js.map +1 -0
- package/dist/src/debug/effects.d.ts +69 -0
- package/dist/src/debug/effects.d.ts.map +1 -0
- package/dist/src/debug/effects.js +228 -0
- package/dist/src/debug/effects.js.map +1 -0
- package/dist/src/debug/effects.test.d.ts +2 -0
- package/dist/src/debug/effects.test.d.ts.map +1 -0
- package/dist/src/debug/effects.test.js +86 -0
- package/dist/src/debug/effects.test.js.map +1 -0
- package/dist/src/debug/files.d.ts +14 -0
- package/dist/src/debug/files.d.ts.map +1 -0
- package/dist/src/debug/files.js +40 -0
- package/dist/src/debug/files.js.map +1 -0
- package/dist/src/debug/files.test.d.ts +2 -0
- package/dist/src/debug/files.test.d.ts.map +1 -0
- package/dist/src/debug/files.test.js +89 -0
- package/dist/src/debug/files.test.js.map +1 -0
- package/dist/src/debug/index.d.ts +60 -0
- package/dist/src/debug/index.d.ts.map +1 -0
- package/dist/src/debug/index.js +68 -0
- package/dist/src/debug/index.js.map +1 -0
- package/dist/src/debug/render.d.ts +57 -0
- package/dist/src/debug/render.d.ts.map +1 -0
- package/dist/src/debug/render.js +519 -0
- package/dist/src/debug/render.js.map +1 -0
- package/dist/src/debug/render.test.d.ts +2 -0
- package/dist/src/debug/render.test.d.ts.map +1 -0
- package/dist/src/debug/render.test.js +328 -0
- package/dist/src/debug/render.test.js.map +1 -0
- package/dist/src/debug/serialize.d.ts +9 -0
- package/dist/src/debug/serialize.d.ts.map +1 -0
- package/dist/src/debug/serialize.js +70 -0
- package/dist/src/debug/serialize.js.map +1 -0
- package/dist/src/debug/symbols.d.ts +9 -0
- package/dist/src/debug/symbols.d.ts.map +1 -0
- package/dist/src/debug/symbols.js +164 -0
- package/dist/src/debug/symbols.js.map +1 -0
- package/dist/src/debug/symbols.test.d.ts +2 -0
- package/dist/src/debug/symbols.test.d.ts.map +1 -0
- package/dist/src/debug/symbols.test.js +104 -0
- package/dist/src/debug/symbols.test.js.map +1 -0
- package/dist/src/debug/trace.d.ts +342 -0
- package/dist/src/debug/trace.d.ts.map +1 -0
- package/dist/src/debug/trace.js +443 -0
- package/dist/src/debug/trace.js.map +1 -0
- package/dist/src/devtools/devtools-protocol.d.ts +232 -0
- package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
- package/dist/src/devtools/devtools-protocol.js +2 -0
- package/dist/src/devtools/devtools-protocol.js.map +1 -0
- package/dist/src/devtools/devtools-server.browser.d.ts +28 -0
- package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
- package/dist/src/devtools/devtools-server.browser.js +36 -0
- package/dist/src/devtools/devtools-server.browser.js.map +1 -0
- package/dist/src/devtools/devtools-server.d.ts +72 -0
- package/dist/src/devtools/devtools-server.d.ts.map +1 -0
- package/dist/src/devtools/devtools-server.js +256 -0
- package/dist/src/devtools/devtools-server.js.map +1 -0
- package/dist/src/devtools/devtools-transport.d.ts +23 -0
- package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
- package/dist/src/devtools/devtools-transport.js +114 -0
- package/dist/src/devtools/devtools-transport.js.map +1 -0
- package/dist/src/devtools-entry.browser.d.ts +4 -0
- package/dist/src/devtools-entry.browser.d.ts.map +1 -0
- package/dist/src/devtools-entry.browser.js +2 -0
- package/dist/src/devtools-entry.browser.js.map +1 -0
- package/dist/src/devtools-entry.d.ts +4 -0
- package/dist/src/devtools-entry.d.ts.map +1 -0
- package/dist/src/devtools-entry.js +2 -0
- package/dist/src/devtools-entry.js.map +1 -0
- package/dist/src/diagnostics.d.ts +34 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +89 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/print-hook.d.ts +14 -0
- package/dist/src/print-hook.d.ts.map +1 -0
- package/dist/src/print-hook.js +10 -0
- package/dist/src/print-hook.js.map +1 -0
- package/dist/src/reactive-union-set.d.ts.map +1 -1
- package/dist/src/reactive-union-set.js +15 -0
- package/dist/src/reactive-union-set.js.map +1 -1
- package/dist/src/reactivity.d.ts +17 -3
- package/dist/src/reactivity.d.ts.map +1 -1
- package/dist/src/reactivity.js +162 -14
- package/dist/src/reactivity.js.map +1 -1
- package/dist/src/render-stack.d.ts +29 -0
- package/dist/src/render-stack.d.ts.map +1 -0
- package/dist/src/render-stack.js +247 -0
- package/dist/src/render-stack.js.map +1 -0
- package/dist/src/render.d.ts +9 -19
- package/dist/src/render.d.ts.map +1 -1
- package/dist/src/render.js +363 -153
- package/dist/src/render.js.map +1 -1
- package/dist/src/resource.d.ts.map +1 -1
- package/dist/src/resource.js +5 -0
- package/dist/src/resource.js.map +1 -1
- package/dist/src/runtime/component.d.ts +7 -1
- package/dist/src/runtime/component.d.ts.map +1 -1
- package/dist/src/runtime/component.js +4 -1
- package/dist/src/runtime/component.js.map +1 -1
- package/dist/src/scheduler.d.ts +3 -0
- package/dist/src/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler.js +45 -2
- package/dist/src/scheduler.js.map +1 -1
- package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
- package/dist/src/symbols/basic-symbol.js +6 -1
- package/dist/src/symbols/basic-symbol.js.map +1 -1
- package/dist/src/symbols/decl.d.ts.map +1 -1
- package/dist/src/symbols/decl.js +5 -1
- package/dist/src/symbols/decl.js.map +1 -1
- package/dist/src/symbols/output-scope.d.ts +2 -1
- package/dist/src/symbols/output-scope.d.ts.map +1 -1
- package/dist/src/symbols/output-scope.js +13 -8
- package/dist/src/symbols/output-scope.js.map +1 -1
- package/dist/src/symbols/output-symbol.d.ts +1 -0
- package/dist/src/symbols/output-symbol.d.ts.map +1 -1
- package/dist/src/symbols/output-symbol.js +23 -6
- package/dist/src/symbols/output-symbol.js.map +1 -1
- package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
- package/dist/src/symbols/symbol-flow.js +22 -6
- package/dist/src/symbols/symbol-flow.js.map +1 -1
- package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
- package/dist/src/symbols/symbol-slot.js +15 -0
- package/dist/src/symbols/symbol-slot.js.map +1 -1
- package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
- package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
- package/dist/src/symbols/symbol-slot.test.js +35 -0
- package/dist/src/symbols/symbol-slot.test.js.map +1 -0
- package/dist/src/symbols/symbol-table.d.ts.map +1 -1
- package/dist/src/symbols/symbol-table.js +6 -5
- package/dist/src/symbols/symbol-table.js.map +1 -1
- package/dist/src/trace.d.ts +2 -0
- package/dist/src/trace.d.ts.map +1 -0
- package/dist/src/trace.js +2 -0
- package/dist/src/trace.js.map +1 -0
- package/dist/src/tracer.d.ts +2 -228
- package/dist/src/tracer.d.ts.map +1 -1
- package/dist/src/tracer.js +5 -298
- package/dist/src/tracer.js.map +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +5 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/test/components/append-file.test.d.ts.map +1 -1
- package/dist/test/components/append-file.test.js +18 -10
- package/dist/test/components/append-file.test.js.map +1 -1
- package/dist/test/components/template-file.test.d.ts.map +1 -1
- package/dist/test/components/template-file.test.js +6 -4
- package/dist/test/components/template-file.test.js.map +1 -1
- package/dist/test/rendering/basic.test.js +3 -0
- package/dist/test/rendering/basic.test.js.map +1 -1
- package/dist/test/rendering/print-render-stack.test.d.ts +2 -0
- package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -0
- package/dist/test/rendering/print-render-stack.test.js +207 -0
- package/dist/test/rendering/print-render-stack.test.js.map +1 -0
- package/dist/testing/create-test-wrapper.d.ts +1 -1
- package/dist/testing/create-test-wrapper.d.ts.map +1 -1
- package/dist/testing/create-test-wrapper.js +1 -1
- package/dist/testing/create-test-wrapper.js.map +1 -1
- package/dist/testing/devtools-utils.d.ts +26 -0
- package/dist/testing/devtools-utils.d.ts.map +1 -0
- package/dist/testing/devtools-utils.js +140 -0
- package/dist/testing/devtools-utils.js.map +1 -0
- package/dist/testing/extend-expect.d.ts.map +1 -1
- package/dist/testing/extend-expect.js +63 -1
- package/dist/testing/extend-expect.js.map +1 -1
- package/dist/testing/render.d.ts +2 -2
- package/dist/testing/render.d.ts.map +1 -1
- package/dist/testing/render.js +2 -2
- package/dist/testing/render.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +21 -7
- package/scripts/copy-devtools-ui.mjs +26 -0
- package/src/binder.ts +71 -16
- package/src/components/AppendFile.tsx +14 -9
- package/src/components/Block.tsx +1 -1
- package/src/components/Declaration.tsx +2 -1
- package/src/components/Scope.tsx +4 -1
- package/src/components/TemplateFile.tsx +18 -9
- package/src/content-slot.tsx +6 -6
- package/src/context.ts +15 -4
- package/src/{debug.ts → debug/cli.ts} +112 -127
- package/src/debug/diagnostics.test.tsx +55 -0
- package/src/debug/effects.test.tsx +96 -0
- package/src/debug/effects.ts +313 -0
- package/src/debug/files.test.tsx +96 -0
- package/src/debug/files.ts +40 -0
- package/src/debug/index.ts +126 -0
- package/src/debug/render.test.tsx +379 -0
- package/src/debug/render.ts +639 -0
- package/src/debug/serialize.ts +85 -0
- package/src/debug/symbols.test.tsx +106 -0
- package/src/debug/symbols.ts +230 -0
- package/src/debug/trace.ts +312 -0
- package/src/devtools/devtools-protocol.ts +312 -0
- package/src/devtools/devtools-server.browser.ts +71 -0
- package/src/devtools/devtools-server.ts +290 -0
- package/src/devtools/devtools-transport.ts +154 -0
- package/src/devtools-entry.browser.ts +52 -0
- package/src/devtools-entry.ts +54 -0
- package/src/diagnostics.ts +141 -0
- package/src/index.ts +2 -6
- package/src/print-hook.ts +22 -0
- package/src/reactive-union-set.ts +71 -41
- package/src/reactivity.ts +206 -23
- package/src/render-stack.ts +289 -0
- package/src/render.ts +464 -212
- package/src/resource.ts +28 -19
- package/src/runtime/component.ts +11 -0
- package/src/scheduler.ts +55 -3
- package/src/symbols/basic-symbol.ts +6 -1
- package/src/symbols/decl.ts +5 -1
- package/src/symbols/output-scope.ts +21 -12
- package/src/symbols/output-symbol.ts +33 -12
- package/src/symbols/symbol-flow.ts +68 -37
- package/src/symbols/symbol-slot.test.tsx +41 -0
- package/src/symbols/symbol-slot.tsx +47 -20
- package/src/symbols/symbol-table.ts +6 -10
- package/src/trace.ts +1 -0
- package/src/tracer.ts +13 -242
- package/src/utils.tsx +22 -13
- package/temp/api.json +1811 -277
- package/test/components/append-file.test.tsx +36 -29
- package/test/components/template-file.test.tsx +11 -11
- package/test/rendering/basic.test.tsx +4 -0
- package/test/rendering/print-render-stack.test.tsx +244 -0
- package/testing/create-test-wrapper.tsx +1 -1
- package/testing/devtools-utils.ts +203 -0
- package/testing/extend-expect.ts +89 -0
- package/testing/render.ts +2 -2
- package/testing/vitest.d.ts +9 -0
- package/dist/src/debug.d.ts +0 -15
- package/dist/src/debug.d.ts.map +0 -1
- package/dist/src/debug.js.map +0 -1
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import { watch } from "@vue/reactivity";
|
|
2
|
+
import * as devalue from "devalue";
|
|
3
|
+
import type {
|
|
4
|
+
RenderErrorStackEntry as ProtocolRenderErrorStackEntry,
|
|
5
|
+
RenderTreeNode,
|
|
6
|
+
} from "../devtools/devtools-protocol.js";
|
|
7
|
+
import {
|
|
8
|
+
isDevtoolsEnabled,
|
|
9
|
+
registerDevtoolsMessageHandler,
|
|
10
|
+
} from "../devtools/devtools-server.js";
|
|
11
|
+
import {
|
|
12
|
+
isPrintHook,
|
|
13
|
+
type PrintHook,
|
|
14
|
+
type RenderedTextTree,
|
|
15
|
+
} from "../print-hook.js";
|
|
16
|
+
import { untrack } from "../reactivity.js";
|
|
17
|
+
import type { ComponentCreator } from "../runtime/component.js";
|
|
18
|
+
import { flushJobsAsync } from "../scheduler.js";
|
|
19
|
+
import { sanitizeRecord } from "./serialize.js";
|
|
20
|
+
import { emitDevtoolsMessage } from "./trace.js";
|
|
21
|
+
|
|
22
|
+
/** The kind discriminant for render tree nodes. */
|
|
23
|
+
export type RenderTreeNodeKind = RenderTreeNode["kind"];
|
|
24
|
+
|
|
25
|
+
/** Information about a render tree node, used when recording nodes to devtools. */
|
|
26
|
+
export interface RenderTreeNodeInfo {
|
|
27
|
+
kind: RenderTreeNodeKind;
|
|
28
|
+
name?: string;
|
|
29
|
+
propsSerialized?: string;
|
|
30
|
+
value?: string;
|
|
31
|
+
source?: RenderTreeNode["source"];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RenderNodeActions {
|
|
35
|
+
rerender: () => void;
|
|
36
|
+
rerenderAndBreak: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BeginComponentOptions {
|
|
40
|
+
parent: RenderedTextTree;
|
|
41
|
+
index: number;
|
|
42
|
+
node: RenderedTextTree;
|
|
43
|
+
component: ComponentCreator<unknown>;
|
|
44
|
+
propsSource: Record<string, unknown> | undefined;
|
|
45
|
+
source: RenderTreeNodeInfo["source"] | undefined;
|
|
46
|
+
isExisting: boolean;
|
|
47
|
+
actions: RenderNodeActions;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ComponentDebugSession {
|
|
51
|
+
recordDirectory(path: string): void;
|
|
52
|
+
recordFile(path: string, filetype: string): void;
|
|
53
|
+
dispose(): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Any node tracked by the devtools render tree. */
|
|
57
|
+
type TrackedNode = RenderedTextTree | PrintHook;
|
|
58
|
+
|
|
59
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
60
|
+
// Module state — reset in initialize()
|
|
61
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
let nodeIds = new WeakMap<TrackedNode, number>();
|
|
64
|
+
let idToNode = new Map<number, TrackedNode>();
|
|
65
|
+
let entryIds = new WeakMap<RenderedTextTree, number[]>();
|
|
66
|
+
let fileNodes = new Map<number, { path: string; filetype: string }>();
|
|
67
|
+
let directoryNodes = new Map<number, { path: string }>();
|
|
68
|
+
let nodeProps = new Map<number, string | undefined>();
|
|
69
|
+
let rerenderActions = new Map<number, RenderNodeActions>();
|
|
70
|
+
let nextId = 1;
|
|
71
|
+
let handlerRegistered = false;
|
|
72
|
+
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
// Props serialization
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
function serializeRenderTreeProps(input: Record<string, unknown> | undefined) {
|
|
78
|
+
return untrack(() => {
|
|
79
|
+
if (!input) return undefined;
|
|
80
|
+
const { children: _children, ...rest } = input;
|
|
81
|
+
const sanitized = sanitizeRecord(rest);
|
|
82
|
+
if (!sanitized) return undefined;
|
|
83
|
+
try {
|
|
84
|
+
return devalue.stringify(sanitized);
|
|
85
|
+
} catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
92
|
+
// Node ID management & tree structure
|
|
93
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
function emitNodeRemoved(parentId: number | null, id: number) {
|
|
96
|
+
clearRenderTreeChildrenForId(id);
|
|
97
|
+
emitDevtoolsMessage({
|
|
98
|
+
type: "render:nodeRemoved",
|
|
99
|
+
parentId,
|
|
100
|
+
id,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
rerenderActions.delete(id);
|
|
104
|
+
nodeProps.delete(id);
|
|
105
|
+
idToNode.delete(id);
|
|
106
|
+
|
|
107
|
+
const fileInfo = fileNodes.get(id);
|
|
108
|
+
if (fileInfo) {
|
|
109
|
+
emitDevtoolsMessage({
|
|
110
|
+
type: "files:fileRemoved",
|
|
111
|
+
path: fileInfo.path,
|
|
112
|
+
});
|
|
113
|
+
fileNodes.delete(id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const dirInfo = directoryNodes.get(id);
|
|
117
|
+
if (dirInfo) {
|
|
118
|
+
emitDevtoolsMessage({
|
|
119
|
+
type: "files:directoryRemoved",
|
|
120
|
+
path: dirInfo.path,
|
|
121
|
+
});
|
|
122
|
+
directoryNodes.delete(id);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getEntryList(parent: RenderedTextTree) {
|
|
127
|
+
let list = entryIds.get(parent);
|
|
128
|
+
if (!list) {
|
|
129
|
+
list = [];
|
|
130
|
+
entryIds.set(parent, list);
|
|
131
|
+
}
|
|
132
|
+
return list;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getOrCreateNodeId(node: TrackedNode) {
|
|
136
|
+
const existing = nodeIds.get(node);
|
|
137
|
+
if (existing) return existing;
|
|
138
|
+
const id = nextId++;
|
|
139
|
+
nodeIds.set(node, id);
|
|
140
|
+
idToNode.set(id, node);
|
|
141
|
+
return id;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getRenderNodeId(node: RenderedTextTree | PrintHook) {
|
|
145
|
+
if (!isDevtoolsEnabled()) return undefined;
|
|
146
|
+
return getOrCreateNodeId(node);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function setEntryId(parent: RenderedTextTree, index: number, id: number) {
|
|
150
|
+
const list = getEntryList(parent);
|
|
151
|
+
list[index] = id;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function initialize(root: RenderedTextTree) {
|
|
155
|
+
if (!isDevtoolsEnabled()) return;
|
|
156
|
+
ensureDevtoolsHandler();
|
|
157
|
+
nodeIds = new WeakMap();
|
|
158
|
+
idToNode = new Map();
|
|
159
|
+
entryIds = new WeakMap();
|
|
160
|
+
fileNodes = new Map();
|
|
161
|
+
directoryNodes = new Map();
|
|
162
|
+
nodeProps = new Map();
|
|
163
|
+
rerenderActions = new Map();
|
|
164
|
+
nextId = 1;
|
|
165
|
+
emitDevtoolsMessage({ type: "render:reset" });
|
|
166
|
+
const rootId = getOrCreateNodeId(root);
|
|
167
|
+
emitDevtoolsMessage({
|
|
168
|
+
type: "render:nodeAdded",
|
|
169
|
+
parentId: null,
|
|
170
|
+
node: {
|
|
171
|
+
id: rootId,
|
|
172
|
+
kind: "root",
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function registerRenderNodeActions(
|
|
178
|
+
node: RenderedTextTree | PrintHook,
|
|
179
|
+
actions: RenderNodeActions,
|
|
180
|
+
) {
|
|
181
|
+
if (!isDevtoolsEnabled()) return;
|
|
182
|
+
const id = getOrCreateNodeId(node);
|
|
183
|
+
rerenderActions.set(id, actions);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function unregisterRenderNodeActions(
|
|
187
|
+
node: RenderedTextTree | PrintHook,
|
|
188
|
+
) {
|
|
189
|
+
if (!isDevtoolsEnabled()) return;
|
|
190
|
+
const id = nodeIds.get(node);
|
|
191
|
+
if (id !== undefined) {
|
|
192
|
+
rerenderActions.delete(id);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function ensureDevtoolsHandler() {
|
|
197
|
+
if (handlerRegistered || !isDevtoolsEnabled()) return;
|
|
198
|
+
handlerRegistered = true;
|
|
199
|
+
registerDevtoolsMessageHandler((message) => {
|
|
200
|
+
if (
|
|
201
|
+
message.type !== "render:rerender" &&
|
|
202
|
+
message.type !== "render:rerenderAndBreak"
|
|
203
|
+
) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const rawId = (message as { id?: unknown }).id;
|
|
207
|
+
if (typeof rawId !== "number" && typeof rawId !== "string") return;
|
|
208
|
+
const id = Number(rawId);
|
|
209
|
+
if (!Number.isFinite(id)) return;
|
|
210
|
+
const actions = rerenderActions.get(id);
|
|
211
|
+
if (!actions) return;
|
|
212
|
+
if (message.type === "render:rerender") {
|
|
213
|
+
actions.rerender();
|
|
214
|
+
} else {
|
|
215
|
+
actions.rerenderAndBreak();
|
|
216
|
+
}
|
|
217
|
+
void flushJobsAsync();
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function recordTextNode(
|
|
222
|
+
parent: RenderedTextTree,
|
|
223
|
+
index: number,
|
|
224
|
+
value: string,
|
|
225
|
+
) {
|
|
226
|
+
if (!isDevtoolsEnabled()) return;
|
|
227
|
+
const id = nextId++;
|
|
228
|
+
setEntryId(parent, index, id);
|
|
229
|
+
emitDevtoolsMessage({
|
|
230
|
+
type: "render:nodeAdded",
|
|
231
|
+
parentId: getOrCreateNodeId(parent),
|
|
232
|
+
node: {
|
|
233
|
+
id,
|
|
234
|
+
kind: "text",
|
|
235
|
+
value,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function recordNodeAdded(
|
|
241
|
+
parent: RenderedTextTree,
|
|
242
|
+
index: number,
|
|
243
|
+
node: RenderedTextTree | PrintHook,
|
|
244
|
+
info: RenderTreeNodeInfo,
|
|
245
|
+
) {
|
|
246
|
+
if (!isDevtoolsEnabled()) return;
|
|
247
|
+
const id = getOrCreateNodeId(node);
|
|
248
|
+
if (info.propsSerialized !== undefined) {
|
|
249
|
+
nodeProps.set(id, info.propsSerialized);
|
|
250
|
+
}
|
|
251
|
+
setEntryId(parent, index, id);
|
|
252
|
+
emitDevtoolsMessage({
|
|
253
|
+
type: "render:nodeAdded",
|
|
254
|
+
parentId: getOrCreateNodeId(parent),
|
|
255
|
+
node: {
|
|
256
|
+
id,
|
|
257
|
+
...info,
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function recordSubtreeAdded(
|
|
263
|
+
parentNode: RenderedTextTree | PrintHook,
|
|
264
|
+
subtree: RenderedTextTree,
|
|
265
|
+
info: RenderTreeNodeInfo = { kind: "fragment" },
|
|
266
|
+
) {
|
|
267
|
+
if (!isDevtoolsEnabled()) return;
|
|
268
|
+
const parentId = getOrCreateNodeId(parentNode);
|
|
269
|
+
// Check if this node was previously rendered (cached) by seeing if it already has an ID
|
|
270
|
+
const existingId = nodeIds.get(subtree);
|
|
271
|
+
const isCached = existingId !== undefined;
|
|
272
|
+
const id = isCached ? existingId : getOrCreateNodeId(subtree);
|
|
273
|
+
// Track in entryIds so clearRenderTreeChildren can find and remove it
|
|
274
|
+
if (Array.isArray(parentNode)) {
|
|
275
|
+
const list = getEntryList(parentNode);
|
|
276
|
+
list.push(id);
|
|
277
|
+
}
|
|
278
|
+
emitDevtoolsMessage({
|
|
279
|
+
type: "render:nodeAdded",
|
|
280
|
+
parentId,
|
|
281
|
+
node: {
|
|
282
|
+
id,
|
|
283
|
+
...info,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
// For cached nodes, we need to recursively re-add all their children since
|
|
287
|
+
// clearRenderTreeChildren removed them when the parent re-rendered
|
|
288
|
+
if (isCached) {
|
|
289
|
+
recordCachedSubtreeChildrenRecursively(subtree);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Recursively re-adds all children of a cached render tree node to devtools.
|
|
295
|
+
* This is needed because clearRenderTreeChildren recursively removes all
|
|
296
|
+
* descendants, but cached nodes aren't re-rendered so their children need
|
|
297
|
+
* to be explicitly re-added.
|
|
298
|
+
*/
|
|
299
|
+
function recordCachedSubtreeChildrenRecursively(node: RenderedTextTree) {
|
|
300
|
+
if (!isDevtoolsEnabled()) return;
|
|
301
|
+
const parentId = getOrCreateNodeId(node);
|
|
302
|
+
|
|
303
|
+
// Rebuild the entryIds for this node
|
|
304
|
+
const list = getEntryList(node);
|
|
305
|
+
list.length = 0;
|
|
306
|
+
|
|
307
|
+
for (let i = 0; i < node.length; i++) {
|
|
308
|
+
const child = node[i];
|
|
309
|
+
if (typeof child === "string") {
|
|
310
|
+
// Text nodes - re-record them with new IDs
|
|
311
|
+
if (child !== "") {
|
|
312
|
+
const id = nextId++;
|
|
313
|
+
list.push(id);
|
|
314
|
+
idToNode.set(id, child as unknown as RenderedTextTree);
|
|
315
|
+
emitDevtoolsMessage({
|
|
316
|
+
type: "render:nodeAdded",
|
|
317
|
+
parentId,
|
|
318
|
+
node: {
|
|
319
|
+
id,
|
|
320
|
+
kind: "text",
|
|
321
|
+
value: child,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
} else if (Array.isArray(child)) {
|
|
326
|
+
// Nested RenderedTextTree - record and recurse
|
|
327
|
+
const id = getOrCreateNodeId(child);
|
|
328
|
+
list.push(id);
|
|
329
|
+
emitDevtoolsMessage({
|
|
330
|
+
type: "render:nodeAdded",
|
|
331
|
+
parentId,
|
|
332
|
+
node: {
|
|
333
|
+
id,
|
|
334
|
+
kind: "fragment",
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
recordCachedSubtreeChildrenRecursively(child);
|
|
338
|
+
} else if (isPrintHook(child)) {
|
|
339
|
+
// PrintHook - record and recurse into subtree
|
|
340
|
+
const id = getOrCreateNodeId(child);
|
|
341
|
+
list.push(id);
|
|
342
|
+
emitDevtoolsMessage({
|
|
343
|
+
type: "render:nodeAdded",
|
|
344
|
+
parentId,
|
|
345
|
+
node: {
|
|
346
|
+
id,
|
|
347
|
+
kind: "printHook",
|
|
348
|
+
name: (child as { name?: string }).name ?? "hook",
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
if (child.subtree) {
|
|
352
|
+
const subtreeId = getOrCreateNodeId(child.subtree);
|
|
353
|
+
const hookList = getEntryList(child as unknown as RenderedTextTree);
|
|
354
|
+
hookList.length = 0;
|
|
355
|
+
hookList.push(subtreeId);
|
|
356
|
+
emitDevtoolsMessage({
|
|
357
|
+
type: "render:nodeAdded",
|
|
358
|
+
parentId: id,
|
|
359
|
+
node: {
|
|
360
|
+
id: subtreeId,
|
|
361
|
+
kind: "fragment",
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
recordCachedSubtreeChildrenRecursively(child.subtree);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function recordNodePropsUpdated(
|
|
371
|
+
node: RenderedTextTree | PrintHook,
|
|
372
|
+
propsSerialized: string | undefined,
|
|
373
|
+
) {
|
|
374
|
+
if (!isDevtoolsEnabled()) return;
|
|
375
|
+
const id = getOrCreateNodeId(node);
|
|
376
|
+
const previous = nodeProps.get(id);
|
|
377
|
+
if (previous === propsSerialized) return;
|
|
378
|
+
nodeProps.set(id, propsSerialized);
|
|
379
|
+
emitDevtoolsMessage({
|
|
380
|
+
type: "render:nodeUpdated",
|
|
381
|
+
id,
|
|
382
|
+
propsSerialized,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function clearRenderTreeChildren(parent: RenderedTextTree) {
|
|
387
|
+
if (!isDevtoolsEnabled()) return;
|
|
388
|
+
const list = entryIds.get(parent);
|
|
389
|
+
if (!list || list.length === 0) return;
|
|
390
|
+
const parentId = getOrCreateNodeId(parent);
|
|
391
|
+
for (const id of list) {
|
|
392
|
+
if (id !== undefined) {
|
|
393
|
+
emitNodeRemoved(parentId, id);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
entryIds.set(parent, []);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function clearRenderTreeChildrenForId(id: number) {
|
|
400
|
+
const node = idToNode.get(id);
|
|
401
|
+
if (!node) return;
|
|
402
|
+
if (Array.isArray(node)) {
|
|
403
|
+
clearRenderTreeChildren(node);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const subtree = (node as { subtree?: RenderedTextTree }).subtree;
|
|
407
|
+
if (subtree && Array.isArray(subtree)) {
|
|
408
|
+
clearRenderTreeChildren(subtree);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
413
|
+
// File / directory node tracking
|
|
414
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
function recordDirectoryNode(node: RenderedTextTree, path: string) {
|
|
417
|
+
if (!isDevtoolsEnabled()) return;
|
|
418
|
+
const id = getOrCreateNodeId(node);
|
|
419
|
+
if (directoryNodes.has(id)) return;
|
|
420
|
+
directoryNodes.set(id, { path });
|
|
421
|
+
emitDevtoolsMessage({
|
|
422
|
+
type: "files:directoryAdded",
|
|
423
|
+
path,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function recordFileNode(
|
|
428
|
+
node: RenderedTextTree,
|
|
429
|
+
path: string,
|
|
430
|
+
filetype: string,
|
|
431
|
+
) {
|
|
432
|
+
if (!isDevtoolsEnabled()) return;
|
|
433
|
+
const id = getOrCreateNodeId(node);
|
|
434
|
+
if (fileNodes.has(id)) return;
|
|
435
|
+
fileNodes.set(id, { path, filetype });
|
|
436
|
+
emitDevtoolsMessage({
|
|
437
|
+
type: "files:fileAdded",
|
|
438
|
+
path,
|
|
439
|
+
filetype,
|
|
440
|
+
renderNodeId: id,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function removeFileEntriesForNode(node: RenderedTextTree | PrintHook) {
|
|
445
|
+
if (!isDevtoolsEnabled()) return;
|
|
446
|
+
const id = nodeIds.get(node);
|
|
447
|
+
if (id === undefined) return;
|
|
448
|
+
const fileInfo = fileNodes.get(id);
|
|
449
|
+
if (fileInfo) {
|
|
450
|
+
emitDevtoolsMessage({
|
|
451
|
+
type: "files:fileRemoved",
|
|
452
|
+
path: fileInfo.path,
|
|
453
|
+
});
|
|
454
|
+
fileNodes.delete(id);
|
|
455
|
+
}
|
|
456
|
+
const dirInfo = directoryNodes.get(id);
|
|
457
|
+
if (dirInfo) {
|
|
458
|
+
emitDevtoolsMessage({
|
|
459
|
+
type: "files:directoryRemoved",
|
|
460
|
+
path: dirInfo.path,
|
|
461
|
+
});
|
|
462
|
+
directoryNodes.delete(id);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
467
|
+
// Public API — called from render.ts via the debug object
|
|
468
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
/** Begin tracking a component render. Returns a session to record files/dirs and dispose watchers. */
|
|
471
|
+
export function beginComponent(
|
|
472
|
+
options: BeginComponentOptions,
|
|
473
|
+
): ComponentDebugSession {
|
|
474
|
+
const {
|
|
475
|
+
parent,
|
|
476
|
+
index,
|
|
477
|
+
node,
|
|
478
|
+
component,
|
|
479
|
+
propsSource,
|
|
480
|
+
source,
|
|
481
|
+
isExisting,
|
|
482
|
+
actions,
|
|
483
|
+
} = options;
|
|
484
|
+
|
|
485
|
+
if (!isDevtoolsEnabled()) {
|
|
486
|
+
return {
|
|
487
|
+
recordDirectory() {},
|
|
488
|
+
recordFile() {},
|
|
489
|
+
dispose() {},
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return untrack(() => {
|
|
494
|
+
let componentName = component.component.name;
|
|
495
|
+
if (componentName === "Provider") {
|
|
496
|
+
const contextName = (component.component as any).contextName as
|
|
497
|
+
| string
|
|
498
|
+
| undefined;
|
|
499
|
+
if (contextName) {
|
|
500
|
+
componentName = `Context ${contextName}`;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const propsSerialized = serializeRenderTreeProps(propsSource);
|
|
504
|
+
if (isExisting) {
|
|
505
|
+
clearRenderTreeChildren(node);
|
|
506
|
+
} else {
|
|
507
|
+
recordNodeAdded(parent, index, node, {
|
|
508
|
+
kind: "component",
|
|
509
|
+
name: componentName,
|
|
510
|
+
propsSerialized,
|
|
511
|
+
source,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
recordNodePropsUpdated(node, propsSerialized);
|
|
515
|
+
registerRenderNodeActions(node, actions);
|
|
516
|
+
|
|
517
|
+
let stopWatch: (() => void) | undefined;
|
|
518
|
+
if (propsSource) {
|
|
519
|
+
const propKeys = Object.keys(propsSource).filter(
|
|
520
|
+
(key) => key !== "children",
|
|
521
|
+
);
|
|
522
|
+
if (propKeys.length > 0) {
|
|
523
|
+
stopWatch = watch(
|
|
524
|
+
() => propKeys.map((key) => propsSource[key]),
|
|
525
|
+
() => {
|
|
526
|
+
const nextSerialized = serializeRenderTreeProps(propsSource);
|
|
527
|
+
recordNodePropsUpdated(node, nextSerialized);
|
|
528
|
+
},
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
recordDirectory(path: string) {
|
|
535
|
+
recordDirectoryNode(node, path);
|
|
536
|
+
},
|
|
537
|
+
recordFile(path: string, filetype: string) {
|
|
538
|
+
recordFileNode(node, path, filetype);
|
|
539
|
+
},
|
|
540
|
+
dispose() {
|
|
541
|
+
stopWatch?.();
|
|
542
|
+
removeFileEntriesForNode(node);
|
|
543
|
+
unregisterRenderNodeActions(node);
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export function appendCustomContext(
|
|
550
|
+
parent: RenderedTextTree,
|
|
551
|
+
node: RenderedTextTree,
|
|
552
|
+
) {
|
|
553
|
+
recordSubtreeAdded(parent, node, { kind: "customContext" });
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export function appendPrintHook(
|
|
557
|
+
parent: RenderedTextTree,
|
|
558
|
+
index: number,
|
|
559
|
+
hook: PrintHook,
|
|
560
|
+
name: string,
|
|
561
|
+
subtree?: RenderedTextTree,
|
|
562
|
+
) {
|
|
563
|
+
recordNodeAdded(parent, index, hook, { kind: "printHook", name });
|
|
564
|
+
if (subtree) {
|
|
565
|
+
recordSubtreeAdded(hook, subtree);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export function appendFragmentChild(
|
|
570
|
+
parent: RenderedTextTree,
|
|
571
|
+
child: RenderedTextTree,
|
|
572
|
+
) {
|
|
573
|
+
recordSubtreeAdded(parent, child, { kind: "fragment" });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export function appendTextNode(
|
|
577
|
+
parent: RenderedTextTree,
|
|
578
|
+
index: number,
|
|
579
|
+
value: string,
|
|
580
|
+
) {
|
|
581
|
+
recordTextNode(parent, index, value);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export function prepareMemoNode(
|
|
585
|
+
parent: RenderedTextTree,
|
|
586
|
+
node: RenderedTextTree,
|
|
587
|
+
isExisting: boolean,
|
|
588
|
+
) {
|
|
589
|
+
if (isExisting) {
|
|
590
|
+
clearRenderTreeChildren(node);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
recordSubtreeAdded(parent, node, { kind: "memo" });
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
let nextErrorId = 1;
|
|
597
|
+
|
|
598
|
+
export interface RenderErrorInfo {
|
|
599
|
+
name: string;
|
|
600
|
+
message: string;
|
|
601
|
+
stack?: string;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** Render error stack entry with optional runtime props (extends protocol type). */
|
|
605
|
+
export interface RenderErrorStackEntry extends ProtocolRenderErrorStackEntry {
|
|
606
|
+
props?: Record<string, unknown> | undefined;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export function error(
|
|
610
|
+
error: RenderErrorInfo,
|
|
611
|
+
componentStack: RenderErrorStackEntry[],
|
|
612
|
+
) {
|
|
613
|
+
if (!isDevtoolsEnabled()) return;
|
|
614
|
+
const serializedStack = untrack(() =>
|
|
615
|
+
componentStack.map((entry) => ({
|
|
616
|
+
name: entry.name,
|
|
617
|
+
propsSerialized: entry.propsSerialized,
|
|
618
|
+
renderNodeId: entry.renderNodeId,
|
|
619
|
+
source: entry.source,
|
|
620
|
+
})),
|
|
621
|
+
);
|
|
622
|
+
const message = {
|
|
623
|
+
type: "render:error" as const,
|
|
624
|
+
id: nextErrorId++,
|
|
625
|
+
name: error.name,
|
|
626
|
+
message: error.message,
|
|
627
|
+
stack: error.stack,
|
|
628
|
+
componentStack: serializedStack,
|
|
629
|
+
};
|
|
630
|
+
emitDevtoolsMessage(message);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export function complete() {
|
|
634
|
+
emitDevtoolsMessage({ type: "render:complete" });
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function flushJobsComplete() {
|
|
638
|
+
emitDevtoolsMessage({ type: "flushJobs:complete" });
|
|
639
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared serialization utilities for debug modules.
|
|
3
|
+
*
|
|
4
|
+
* Provides safe, depth-limited serialization of arbitrary values for
|
|
5
|
+
* transmission over the devtools protocol. Handles reactive refs, circular
|
|
6
|
+
* references, and non-plain objects.
|
|
7
|
+
*/
|
|
8
|
+
import { isReactive, isRef } from "@vue/reactivity";
|
|
9
|
+
import { untrack } from "../reactivity.js";
|
|
10
|
+
|
|
11
|
+
const MAX_ENTRIES = 50;
|
|
12
|
+
const MAX_DEPTH = 3;
|
|
13
|
+
|
|
14
|
+
function isPlainObject(value: unknown): boolean {
|
|
15
|
+
if (!value || typeof value !== "object") return false;
|
|
16
|
+
const proto = Object.getPrototypeOf(value);
|
|
17
|
+
return proto === Object.prototype || proto === null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sanitize(
|
|
21
|
+
value: unknown,
|
|
22
|
+
depth: number,
|
|
23
|
+
seen: WeakSet<object>,
|
|
24
|
+
): unknown {
|
|
25
|
+
if (depth > MAX_DEPTH) return "[MaxDepth]";
|
|
26
|
+
if (
|
|
27
|
+
value === null ||
|
|
28
|
+
typeof value === "string" ||
|
|
29
|
+
typeof value === "number" ||
|
|
30
|
+
typeof value === "boolean"
|
|
31
|
+
) {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === "bigint") return value.toString();
|
|
35
|
+
if (typeof value === "symbol") return value.toString();
|
|
36
|
+
if (typeof value === "function") return "[Function]";
|
|
37
|
+
if (isRef(value)) {
|
|
38
|
+
return sanitize(value.value, depth + 1, seen);
|
|
39
|
+
}
|
|
40
|
+
if (isReactive(value)) {
|
|
41
|
+
return "[Reactive]";
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return value
|
|
45
|
+
.slice(0, MAX_ENTRIES)
|
|
46
|
+
.map((item) => sanitize(item, depth + 1, seen));
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === "object") {
|
|
49
|
+
const obj = value as Record<string, unknown>;
|
|
50
|
+
if (seen.has(obj)) return "[Circular]";
|
|
51
|
+
seen.add(obj);
|
|
52
|
+
if (!isPlainObject(obj)) {
|
|
53
|
+
const name = obj.constructor?.name ?? "Object";
|
|
54
|
+
return `[${name}]`;
|
|
55
|
+
}
|
|
56
|
+
const entries = Object.entries(obj).slice(0, MAX_ENTRIES);
|
|
57
|
+
const result: Record<string, unknown> = {};
|
|
58
|
+
for (const [key, val] of entries) {
|
|
59
|
+
result[key] = sanitize(val, depth + 1, seen);
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
return String(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sanitize a record of metadata for safe serialization over the devtools
|
|
68
|
+
* protocol. Unwraps refs, replaces reactive objects with placeholders, limits
|
|
69
|
+
* depth and entry count, and detects circular references.
|
|
70
|
+
*
|
|
71
|
+
* Runs inside `untrack()` to avoid creating reactive dependencies.
|
|
72
|
+
*/
|
|
73
|
+
export function sanitizeRecord(
|
|
74
|
+
input: Record<string, unknown> | undefined,
|
|
75
|
+
): Record<string, unknown> | undefined {
|
|
76
|
+
return untrack(() => {
|
|
77
|
+
if (!input) return undefined;
|
|
78
|
+
const seen = new WeakSet<object>();
|
|
79
|
+
const result: Record<string, unknown> = {};
|
|
80
|
+
for (const [key, value] of Object.entries(input).slice(0, MAX_ENTRIES)) {
|
|
81
|
+
result[key] = sanitize(value, 0, seen);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
});
|
|
85
|
+
}
|