@alloy-js/core 0.23.0-dev.1 → 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.
Files changed (263) hide show
  1. package/CHANGELOG.md +0 -22
  2. package/dist/devtools/index.html +68 -0
  3. package/dist/src/binder.d.ts +2 -0
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +55 -12
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/components/AppendFile.d.ts.map +1 -1
  8. package/dist/src/components/AppendFile.js +14 -3
  9. package/dist/src/components/AppendFile.js.map +1 -1
  10. package/dist/src/components/Block.js +1 -1
  11. package/dist/src/components/Block.js.map +1 -1
  12. package/dist/src/components/Declaration.d.ts.map +1 -1
  13. package/dist/src/components/Declaration.js +2 -1
  14. package/dist/src/components/Declaration.js.map +1 -1
  15. package/dist/src/components/Scope.d.ts.map +1 -1
  16. package/dist/src/components/Scope.js +4 -1
  17. package/dist/src/components/Scope.js.map +1 -1
  18. package/dist/src/components/TemplateFile.d.ts.map +1 -1
  19. package/dist/src/components/TemplateFile.js +18 -3
  20. package/dist/src/components/TemplateFile.js.map +1 -1
  21. package/dist/src/content-slot.d.ts.map +1 -1
  22. package/dist/src/content-slot.js +6 -5
  23. package/dist/src/content-slot.js.map +1 -1
  24. package/dist/src/context.d.ts.map +1 -1
  25. package/dist/src/context.js +8 -1
  26. package/dist/src/context.js.map +1 -1
  27. package/dist/src/debug/cli.d.ts +6 -0
  28. package/dist/src/debug/cli.d.ts.map +1 -0
  29. package/dist/src/{debug.js → debug/cli.js} +79 -82
  30. package/dist/src/debug/cli.js.map +1 -0
  31. package/dist/src/debug/diagnostics.test.d.ts +2 -0
  32. package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
  33. package/dist/src/debug/diagnostics.test.js +45 -0
  34. package/dist/src/debug/diagnostics.test.js.map +1 -0
  35. package/dist/src/debug/effects.d.ts +69 -0
  36. package/dist/src/debug/effects.d.ts.map +1 -0
  37. package/dist/src/debug/effects.js +228 -0
  38. package/dist/src/debug/effects.js.map +1 -0
  39. package/dist/src/debug/effects.test.d.ts +2 -0
  40. package/dist/src/debug/effects.test.d.ts.map +1 -0
  41. package/dist/src/debug/effects.test.js +86 -0
  42. package/dist/src/debug/effects.test.js.map +1 -0
  43. package/dist/src/debug/files.d.ts +14 -0
  44. package/dist/src/debug/files.d.ts.map +1 -0
  45. package/dist/src/debug/files.js +40 -0
  46. package/dist/src/debug/files.js.map +1 -0
  47. package/dist/src/debug/files.test.d.ts +2 -0
  48. package/dist/src/debug/files.test.d.ts.map +1 -0
  49. package/dist/src/debug/files.test.js +89 -0
  50. package/dist/src/debug/files.test.js.map +1 -0
  51. package/dist/src/debug/index.d.ts +60 -0
  52. package/dist/src/debug/index.d.ts.map +1 -0
  53. package/dist/src/debug/index.js +68 -0
  54. package/dist/src/debug/index.js.map +1 -0
  55. package/dist/src/debug/render.d.ts +57 -0
  56. package/dist/src/debug/render.d.ts.map +1 -0
  57. package/dist/src/debug/render.js +519 -0
  58. package/dist/src/debug/render.js.map +1 -0
  59. package/dist/src/debug/render.test.d.ts +2 -0
  60. package/dist/src/debug/render.test.d.ts.map +1 -0
  61. package/dist/src/debug/render.test.js +328 -0
  62. package/dist/src/debug/render.test.js.map +1 -0
  63. package/dist/src/debug/serialize.d.ts +9 -0
  64. package/dist/src/debug/serialize.d.ts.map +1 -0
  65. package/dist/src/debug/serialize.js +70 -0
  66. package/dist/src/debug/serialize.js.map +1 -0
  67. package/dist/src/debug/symbols.d.ts +9 -0
  68. package/dist/src/debug/symbols.d.ts.map +1 -0
  69. package/dist/src/debug/symbols.js +164 -0
  70. package/dist/src/debug/symbols.js.map +1 -0
  71. package/dist/src/debug/symbols.test.d.ts +2 -0
  72. package/dist/src/debug/symbols.test.d.ts.map +1 -0
  73. package/dist/src/debug/symbols.test.js +104 -0
  74. package/dist/src/debug/symbols.test.js.map +1 -0
  75. package/dist/src/debug/trace.d.ts +342 -0
  76. package/dist/src/debug/trace.d.ts.map +1 -0
  77. package/dist/src/debug/trace.js +443 -0
  78. package/dist/src/debug/trace.js.map +1 -0
  79. package/dist/src/devtools/devtools-protocol.d.ts +232 -0
  80. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
  81. package/dist/src/devtools/devtools-protocol.js +2 -0
  82. package/dist/src/devtools/devtools-protocol.js.map +1 -0
  83. package/dist/src/devtools/devtools-server.browser.d.ts +28 -0
  84. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
  85. package/dist/src/devtools/devtools-server.browser.js +36 -0
  86. package/dist/src/devtools/devtools-server.browser.js.map +1 -0
  87. package/dist/src/devtools/devtools-server.d.ts +72 -0
  88. package/dist/src/devtools/devtools-server.d.ts.map +1 -0
  89. package/dist/src/devtools/devtools-server.js +256 -0
  90. package/dist/src/devtools/devtools-server.js.map +1 -0
  91. package/dist/src/devtools/devtools-transport.d.ts +23 -0
  92. package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
  93. package/dist/src/devtools/devtools-transport.js +114 -0
  94. package/dist/src/devtools/devtools-transport.js.map +1 -0
  95. package/dist/src/devtools-entry.browser.d.ts +4 -0
  96. package/dist/src/devtools-entry.browser.d.ts.map +1 -0
  97. package/dist/src/devtools-entry.browser.js +2 -0
  98. package/dist/src/devtools-entry.browser.js.map +1 -0
  99. package/dist/src/devtools-entry.d.ts +4 -0
  100. package/dist/src/devtools-entry.d.ts.map +1 -0
  101. package/dist/src/devtools-entry.js +2 -0
  102. package/dist/src/devtools-entry.js.map +1 -0
  103. package/dist/src/diagnostics.d.ts +34 -0
  104. package/dist/src/diagnostics.d.ts.map +1 -0
  105. package/dist/src/diagnostics.js +89 -0
  106. package/dist/src/diagnostics.js.map +1 -0
  107. package/dist/src/index.d.ts +3 -2
  108. package/dist/src/index.d.ts.map +1 -1
  109. package/dist/src/index.js +3 -2
  110. package/dist/src/index.js.map +1 -1
  111. package/dist/src/print-hook.d.ts +14 -0
  112. package/dist/src/print-hook.d.ts.map +1 -0
  113. package/dist/src/print-hook.js +10 -0
  114. package/dist/src/print-hook.js.map +1 -0
  115. package/dist/src/reactive-union-set.d.ts.map +1 -1
  116. package/dist/src/reactive-union-set.js +15 -0
  117. package/dist/src/reactive-union-set.js.map +1 -1
  118. package/dist/src/reactivity.d.ts +17 -3
  119. package/dist/src/reactivity.d.ts.map +1 -1
  120. package/dist/src/reactivity.js +162 -14
  121. package/dist/src/reactivity.js.map +1 -1
  122. package/dist/src/render-stack.d.ts +17 -1
  123. package/dist/src/render-stack.d.ts.map +1 -1
  124. package/dist/src/render-stack.js +57 -1
  125. package/dist/src/render-stack.js.map +1 -1
  126. package/dist/src/render.d.ts +8 -15
  127. package/dist/src/render.d.ts.map +1 -1
  128. package/dist/src/render.js +362 -103
  129. package/dist/src/render.js.map +1 -1
  130. package/dist/src/resource.d.ts.map +1 -1
  131. package/dist/src/resource.js +5 -0
  132. package/dist/src/resource.js.map +1 -1
  133. package/dist/src/scheduler.d.ts +3 -0
  134. package/dist/src/scheduler.d.ts.map +1 -1
  135. package/dist/src/scheduler.js +45 -2
  136. package/dist/src/scheduler.js.map +1 -1
  137. package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
  138. package/dist/src/symbols/basic-symbol.js +6 -1
  139. package/dist/src/symbols/basic-symbol.js.map +1 -1
  140. package/dist/src/symbols/decl.d.ts.map +1 -1
  141. package/dist/src/symbols/decl.js +5 -1
  142. package/dist/src/symbols/decl.js.map +1 -1
  143. package/dist/src/symbols/output-scope.d.ts +2 -1
  144. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  145. package/dist/src/symbols/output-scope.js +13 -8
  146. package/dist/src/symbols/output-scope.js.map +1 -1
  147. package/dist/src/symbols/output-symbol.d.ts +1 -0
  148. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  149. package/dist/src/symbols/output-symbol.js +23 -6
  150. package/dist/src/symbols/output-symbol.js.map +1 -1
  151. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  152. package/dist/src/symbols/symbol-flow.js +22 -6
  153. package/dist/src/symbols/symbol-flow.js.map +1 -1
  154. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  155. package/dist/src/symbols/symbol-slot.js +15 -0
  156. package/dist/src/symbols/symbol-slot.js.map +1 -1
  157. package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
  158. package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
  159. package/dist/src/symbols/symbol-slot.test.js +35 -0
  160. package/dist/src/symbols/symbol-slot.test.js.map +1 -0
  161. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  162. package/dist/src/symbols/symbol-table.js +6 -5
  163. package/dist/src/symbols/symbol-table.js.map +1 -1
  164. package/dist/src/trace.d.ts +2 -0
  165. package/dist/src/trace.d.ts.map +1 -0
  166. package/dist/src/trace.js +2 -0
  167. package/dist/src/trace.js.map +1 -0
  168. package/dist/src/tracer.d.ts +2 -228
  169. package/dist/src/tracer.d.ts.map +1 -1
  170. package/dist/src/tracer.js +5 -298
  171. package/dist/src/tracer.js.map +1 -1
  172. package/dist/src/utils.d.ts.map +1 -1
  173. package/dist/src/utils.js +5 -0
  174. package/dist/src/utils.js.map +1 -1
  175. package/dist/test/components/append-file.test.d.ts.map +1 -1
  176. package/dist/test/components/append-file.test.js +18 -10
  177. package/dist/test/components/append-file.test.js.map +1 -1
  178. package/dist/test/components/template-file.test.d.ts.map +1 -1
  179. package/dist/test/components/template-file.test.js +6 -4
  180. package/dist/test/components/template-file.test.js.map +1 -1
  181. package/dist/test/rendering/basic.test.js +3 -0
  182. package/dist/test/rendering/basic.test.js.map +1 -1
  183. package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -1
  184. package/dist/test/rendering/print-render-stack.test.js +91 -98
  185. package/dist/test/rendering/print-render-stack.test.js.map +1 -1
  186. package/dist/testing/create-test-wrapper.d.ts +1 -1
  187. package/dist/testing/create-test-wrapper.d.ts.map +1 -1
  188. package/dist/testing/create-test-wrapper.js +1 -1
  189. package/dist/testing/create-test-wrapper.js.map +1 -1
  190. package/dist/testing/devtools-utils.d.ts +26 -0
  191. package/dist/testing/devtools-utils.d.ts.map +1 -0
  192. package/dist/testing/devtools-utils.js +140 -0
  193. package/dist/testing/devtools-utils.js.map +1 -0
  194. package/dist/testing/extend-expect.d.ts.map +1 -1
  195. package/dist/testing/extend-expect.js +63 -1
  196. package/dist/testing/extend-expect.js.map +1 -1
  197. package/dist/testing/render.d.ts +2 -2
  198. package/dist/testing/render.d.ts.map +1 -1
  199. package/dist/testing/render.js +2 -2
  200. package/dist/testing/render.js.map +1 -1
  201. package/dist/tsconfig.tsbuildinfo +1 -1
  202. package/package.json +21 -7
  203. package/scripts/copy-devtools-ui.mjs +26 -0
  204. package/src/binder.ts +71 -16
  205. package/src/components/AppendFile.tsx +14 -9
  206. package/src/components/Block.tsx +1 -1
  207. package/src/components/Declaration.tsx +2 -1
  208. package/src/components/Scope.tsx +4 -1
  209. package/src/components/TemplateFile.tsx +18 -9
  210. package/src/content-slot.tsx +6 -6
  211. package/src/context.ts +15 -4
  212. package/src/{debug.ts → debug/cli.ts} +114 -125
  213. package/src/debug/diagnostics.test.tsx +55 -0
  214. package/src/debug/effects.test.tsx +96 -0
  215. package/src/debug/effects.ts +313 -0
  216. package/src/debug/files.test.tsx +96 -0
  217. package/src/debug/files.ts +40 -0
  218. package/src/debug/index.ts +126 -0
  219. package/src/debug/render.test.tsx +379 -0
  220. package/src/debug/render.ts +639 -0
  221. package/src/debug/serialize.ts +85 -0
  222. package/src/debug/symbols.test.tsx +106 -0
  223. package/src/debug/symbols.ts +230 -0
  224. package/src/debug/trace.ts +312 -0
  225. package/src/devtools/devtools-protocol.ts +312 -0
  226. package/src/devtools/devtools-server.browser.ts +71 -0
  227. package/src/devtools/devtools-server.ts +290 -0
  228. package/src/devtools/devtools-transport.ts +154 -0
  229. package/src/devtools-entry.browser.ts +52 -0
  230. package/src/devtools-entry.ts +54 -0
  231. package/src/diagnostics.ts +141 -0
  232. package/src/index.ts +2 -6
  233. package/src/print-hook.ts +22 -0
  234. package/src/reactive-union-set.ts +71 -41
  235. package/src/reactivity.ts +206 -23
  236. package/src/render-stack.ts +68 -1
  237. package/src/render.ts +464 -157
  238. package/src/resource.ts +28 -19
  239. package/src/scheduler.ts +55 -3
  240. package/src/symbols/basic-symbol.ts +6 -1
  241. package/src/symbols/decl.ts +5 -1
  242. package/src/symbols/output-scope.ts +21 -12
  243. package/src/symbols/output-symbol.ts +33 -12
  244. package/src/symbols/symbol-flow.ts +68 -37
  245. package/src/symbols/symbol-slot.test.tsx +41 -0
  246. package/src/symbols/symbol-slot.tsx +47 -20
  247. package/src/symbols/symbol-table.ts +6 -10
  248. package/src/trace.ts +1 -0
  249. package/src/tracer.ts +13 -242
  250. package/src/utils.tsx +22 -13
  251. package/temp/api.json +1675 -162
  252. package/test/components/append-file.test.tsx +36 -29
  253. package/test/components/template-file.test.tsx +11 -11
  254. package/test/rendering/basic.test.tsx +4 -0
  255. package/test/rendering/print-render-stack.test.tsx +52 -43
  256. package/testing/create-test-wrapper.tsx +1 -1
  257. package/testing/devtools-utils.ts +203 -0
  258. package/testing/extend-expect.ts +89 -0
  259. package/testing/render.ts +2 -2
  260. package/testing/vitest.d.ts +9 -0
  261. package/dist/src/debug.d.ts +0 -14
  262. package/dist/src/debug.d.ts.map +0 -1
  263. package/dist/src/debug.js.map +0 -1
@@ -0,0 +1,313 @@
1
+ import { isReactive, isRef } from "@vue/reactivity";
2
+ import {
3
+ emitDevtoolsMessage,
4
+ isDevtoolsEnabled,
5
+ TracePhase,
6
+ traceType,
7
+ } from "./trace.js";
8
+
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ // Effects debug
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+
13
+ export interface SourceLocation {
14
+ fileName?: string;
15
+ lineNumber?: number;
16
+ columnNumber?: number;
17
+ stack?: string;
18
+ }
19
+
20
+ export interface EffectDebugInfo {
21
+ id: number;
22
+ name?: string;
23
+ type?: string;
24
+ createdAt?: SourceLocation;
25
+ lastTriggeredByRefId?: number;
26
+ lastTriggeredAt?: SourceLocation;
27
+ }
28
+
29
+ export interface RefDebugInfo {
30
+ id: number;
31
+ kind?: string;
32
+ createdAt?: SourceLocation;
33
+ createdByEffectId?: number;
34
+ }
35
+
36
+ export interface EffectEdgeDebugInfo {
37
+ id: number;
38
+ type: "track" | "trigger" | "triggered-by";
39
+ effectId: number;
40
+ refId?: number;
41
+ targetId?: number;
42
+ targetKind?: "ref" | "target";
43
+ targetLabel?: string;
44
+ targetKey?: string | number;
45
+ location?: SourceLocation;
46
+ }
47
+
48
+ const effects = new Map<number, EffectDebugInfo>();
49
+ const refs = new Map<number, RefDebugInfo>();
50
+ let effectIdCounter = 1;
51
+ let edgeEventIdCounter = 1;
52
+ let nonRefTargetIdCounter = 1;
53
+ const nonRefTargetIds = new WeakMap<object, number>();
54
+ const primitiveTargetIds = new Map<unknown, number>();
55
+
56
+ const STACK_LINE = /\s*at\s+(?:.+?\s+\()?(.+?):(\d+):(\d+)\)?$/;
57
+ const STACK_SKIP = [
58
+ "node:internal",
59
+ "/node_modules/",
60
+ "\\node_modules\\",
61
+ "/@vue/",
62
+ "\\@vue\\",
63
+ "/@alloy-js/",
64
+ "\\@alloy-js\\",
65
+ "/packages/core/src/reactivity",
66
+ "/packages/core/src/devtools/effects-debug",
67
+ "/packages/core/dist/src/reactivity",
68
+ "\\packages\\core\\dist\\src\\reactivity",
69
+ "/packages/core/dist/src/devtools/effects-debug",
70
+ "\\packages\\core\\dist\\src\\devtools\\effects-debug",
71
+ "captureSourceLocation",
72
+ ];
73
+
74
+ const VUE_REACTIVITY_MARKERS = [
75
+ "@vue/reactivity",
76
+ "reactivity.esm",
77
+ "reactivity.cjs",
78
+ "reactivity.global",
79
+ "/@vue/",
80
+ "\\@vue\\",
81
+ ];
82
+
83
+ function isVueReactivityLine(line: string) {
84
+ return VUE_REACTIVITY_MARKERS.some((marker) => line.includes(marker));
85
+ }
86
+
87
+ function parseStackLine(
88
+ line: string,
89
+ stack?: string,
90
+ ): SourceLocation | undefined {
91
+ const match = STACK_LINE.exec(line);
92
+ if (!match) return undefined;
93
+ const [, fileName, lineNumber, columnNumber] = match;
94
+ return {
95
+ fileName,
96
+ lineNumber: Number(lineNumber),
97
+ columnNumber: Number(columnNumber),
98
+ stack,
99
+ };
100
+ }
101
+
102
+ export function captureSourceLocation(
103
+ skipReactives = true,
104
+ ): SourceLocation | undefined {
105
+ if (!isDevtoolsEnabled()) return undefined;
106
+ const stack = new Error().stack;
107
+ if (!stack) {
108
+ return { stack: "" };
109
+ }
110
+ const lines = stack.split("\n").slice(1);
111
+ for (const line of lines) {
112
+ if (STACK_SKIP.some((skip) => line.includes(skip))) continue;
113
+ const parsed = parseStackLine(line, stack);
114
+ if (parsed) return parsed;
115
+ }
116
+
117
+ if (skipReactives) {
118
+ for (const line of lines) {
119
+ if (STACK_SKIP.some((skip) => line.includes(skip))) continue;
120
+ if (isVueReactivityLine(line)) continue;
121
+ const parsed = parseStackLine(line, stack);
122
+ if (parsed) return parsed;
123
+ }
124
+ }
125
+
126
+ return { stack };
127
+ }
128
+
129
+ function getNonRefTargetId(target: unknown): number {
130
+ if (typeof target === "object" && target !== null) {
131
+ const existing = nonRefTargetIds.get(target);
132
+ if (existing) return existing;
133
+ const id = nonRefTargetIdCounter++;
134
+ nonRefTargetIds.set(target, id);
135
+ return id;
136
+ }
137
+ if (typeof target === "function") {
138
+ const existing = nonRefTargetIds.get(target as object);
139
+ if (existing) return existing;
140
+ const id = nonRefTargetIdCounter++;
141
+ nonRefTargetIds.set(target as object, id);
142
+ return id;
143
+ }
144
+ const existing = primitiveTargetIds.get(target);
145
+ if (existing) return existing;
146
+ const id = nonRefTargetIdCounter++;
147
+ primitiveTargetIds.set(target, id);
148
+ return id;
149
+ }
150
+
151
+ function formatNonRefTargetLabel(target: unknown): string {
152
+ if (Array.isArray(target)) return "[]";
153
+ try {
154
+ return String(target);
155
+ } catch {
156
+ return "[Unserializable]";
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Sanitize a Map/object key to ensure it's serializable.
162
+ */
163
+ function sanitizeTargetKey(key: unknown): string | number | undefined {
164
+ if (key === undefined) return undefined;
165
+ if (typeof key === "string" || typeof key === "number") return key;
166
+ if (typeof key === "symbol") return key.toString();
167
+ if (typeof key === "object" || typeof key === "function") return "Object";
168
+ return String(key);
169
+ }
170
+
171
+ function buildEffectTargetInfo(input: {
172
+ target: unknown;
173
+ refId?: number;
174
+ }): Pick<
175
+ EffectEdgeDebugInfo,
176
+ "refId" | "targetId" | "targetKind" | "targetLabel"
177
+ > {
178
+ if (input.refId !== undefined) {
179
+ return {
180
+ refId: input.refId,
181
+ targetId: input.refId,
182
+ targetKind: "ref",
183
+ };
184
+ }
185
+
186
+ const targetId = getNonRefTargetId(input.target);
187
+ return {
188
+ targetId,
189
+ targetKind: "target",
190
+ targetLabel: formatNonRefTargetLabel(input.target),
191
+ };
192
+ }
193
+
194
+ function emitEffect(message: { type: string; [key: string]: unknown }) {
195
+ emitDevtoolsMessage(message);
196
+ }
197
+
198
+ export function update(input: Partial<EffectDebugInfo> & { id: number }) {
199
+ if (!isDevtoolsEnabled()) return;
200
+ const existing = effects.get(input.id);
201
+ if (!existing) return;
202
+ const next: EffectDebugInfo = { ...existing, ...input };
203
+ effects.set(input.id, next);
204
+ emitEffect({
205
+ type: traceType(TracePhase.effect.effectUpdated),
206
+ effect: next,
207
+ });
208
+ }
209
+
210
+ export function register(input: {
211
+ name?: string;
212
+ type?: string;
213
+ createdAt?: SourceLocation;
214
+ }): number {
215
+ if (!isDevtoolsEnabled()) return -1;
216
+ const id = effectIdCounter++;
217
+ const info: EffectDebugInfo = {
218
+ id,
219
+ name: input.name,
220
+ type: input.type,
221
+ createdAt: input.createdAt ?? captureSourceLocation(),
222
+ };
223
+ effects.set(id, info);
224
+ emitEffect({ type: traceType(TracePhase.effect.effectAdded), effect: info });
225
+ return id;
226
+ }
227
+
228
+ export function registerRef(input: {
229
+ id: number;
230
+ kind?: string;
231
+ createdAt?: SourceLocation;
232
+ createdByEffectId?: number;
233
+ }) {
234
+ if (!isDevtoolsEnabled()) return;
235
+ if (refs.has(input.id)) return;
236
+ const info: RefDebugInfo = {
237
+ id: input.id,
238
+ kind: input.kind,
239
+ createdAt: input.createdAt ?? captureSourceLocation(),
240
+ createdByEffectId: input.createdByEffectId,
241
+ };
242
+ refs.set(input.id, info);
243
+ emitEffect({ type: traceType(TracePhase.effect.refAdded), ref: info });
244
+ }
245
+
246
+ export function ensureRef(input: { id: number; kind?: string }) {
247
+ if (!isDevtoolsEnabled()) return;
248
+ if (refs.has(input.id)) return;
249
+ registerRef({ id: input.id, kind: input.kind });
250
+ }
251
+
252
+ export function track(input: {
253
+ effectId: number;
254
+ target: unknown;
255
+ refId?: number;
256
+ targetKey?: unknown;
257
+ location?: SourceLocation;
258
+ }) {
259
+ if (!isDevtoolsEnabled()) return;
260
+ const edge: EffectEdgeDebugInfo = {
261
+ id: edgeEventIdCounter++,
262
+ type: "track",
263
+ effectId: input.effectId,
264
+ ...buildEffectTargetInfo({ target: input.target, refId: input.refId }),
265
+ targetKey: sanitizeTargetKey(input.targetKey),
266
+ location: input.location ?? captureSourceLocation(),
267
+ };
268
+ emitEffect({ type: traceType(TracePhase.effect.track), edge });
269
+ }
270
+
271
+ export function trigger(input: {
272
+ effectId: number;
273
+ target: unknown;
274
+ refId?: number;
275
+ targetKey?: unknown;
276
+ location?: SourceLocation;
277
+ kind?: "trigger" | "triggered-by";
278
+ }) {
279
+ if (!isDevtoolsEnabled()) return;
280
+ const edge: EffectEdgeDebugInfo = {
281
+ id: edgeEventIdCounter++,
282
+ type: input.kind ?? "triggered-by",
283
+ effectId: input.effectId,
284
+ ...buildEffectTargetInfo({ target: input.target, refId: input.refId }),
285
+ targetKey: sanitizeTargetKey(input.targetKey),
286
+ location: input.location ?? captureSourceLocation(),
287
+ };
288
+ emitEffect({ type: traceType(TracePhase.effect.trigger), edge });
289
+
290
+ update({
291
+ id: input.effectId,
292
+ ...(input.refId !== undefined ? { lastTriggeredByRefId: input.refId } : {}),
293
+ lastTriggeredAt: input.location ?? captureSourceLocation(),
294
+ });
295
+ }
296
+
297
+ export function reset() {
298
+ effects.clear();
299
+ refs.clear();
300
+ primitiveTargetIds.clear();
301
+ effectIdCounter = 1;
302
+ edgeEventIdCounter = 1;
303
+ nonRefTargetIdCounter = 1;
304
+ }
305
+
306
+ // Utilities used by other debug sections
307
+ export function isRefTarget(value: unknown) {
308
+ return isRef(value);
309
+ }
310
+
311
+ export function isReactiveTarget(value: unknown) {
312
+ return isReactive(value);
313
+ }
@@ -0,0 +1,96 @@
1
+ import { afterEach, beforeEach, expect, it } from "vitest";
2
+ import WebSocket from "ws";
3
+ import {
4
+ createMessageCollector,
5
+ type DevtoolsMessage,
6
+ } from "../../testing/devtools-utils.js";
7
+ import { Output } from "../components/Output.jsx";
8
+ import { Show } from "../components/Show.jsx";
9
+ import { SourceDirectory } from "../components/SourceDirectory.jsx";
10
+ import { SourceFile } from "../components/SourceFile.jsx";
11
+ import {
12
+ enableDevtools,
13
+ resetDevtoolsServerForTests,
14
+ } from "../devtools/devtools-server.js";
15
+ import { ref } from "../reactivity.js";
16
+ import { renderAsync } from "../render.js";
17
+ import { flushJobsAsync } from "../scheduler.js";
18
+
19
+ let socket: WebSocket | undefined;
20
+
21
+ beforeEach(async () => {
22
+ const server = await enableDevtools({ port: 0 });
23
+ socket = new WebSocket(`ws://127.0.0.1:${server.port}`);
24
+
25
+ await new Promise<void>((resolve, reject) => {
26
+ socket?.once("open", resolve);
27
+ socket?.once("error", reject);
28
+ });
29
+ });
30
+
31
+ afterEach(async () => {
32
+ if (socket) {
33
+ socket.close();
34
+ socket = undefined;
35
+ }
36
+
37
+ await resetDevtoolsServerForTests();
38
+ });
39
+
40
+ it("emits file and directory add/update/remove messages", async () => {
41
+ const show = ref(true);
42
+ const collector = createMessageCollector(socket!);
43
+
44
+ await renderAsync(
45
+ <Output>
46
+ <Show when={show.value}>
47
+ <SourceDirectory path="src">
48
+ <SourceFile path="index.ts" filetype="ts">
49
+ {"export const value = 1;"}
50
+ </SourceFile>
51
+ </SourceDirectory>
52
+ </Show>
53
+ </Output>,
54
+ );
55
+
56
+ const initialMessages = await collector.waitForRender();
57
+ const initialFiles = initialMessages.filter((m: DevtoolsMessage) =>
58
+ m.type.startsWith("files:"),
59
+ );
60
+ expect(initialFiles[0]).toMatchObject({
61
+ path: "./",
62
+ });
63
+ expect(initialFiles[1]).toMatchObject({
64
+ type: "files:directoryAdded",
65
+ path: "src",
66
+ });
67
+ expect(initialFiles[2]).toMatchObject({
68
+ type: "files:fileAdded",
69
+ path: "src/index.ts",
70
+ filetype: "ts",
71
+ });
72
+ expect(initialFiles[3]).toMatchObject({
73
+ type: "files:fileUpdated",
74
+ path: "src/index.ts",
75
+ filetype: "ts",
76
+ contents: expect.any(String),
77
+ });
78
+
79
+ show.value = false;
80
+ await flushJobsAsync();
81
+
82
+ const updateMessages = await collector.waitForFlush();
83
+ const updateFiles = updateMessages.filter((m: DevtoolsMessage) =>
84
+ m.type.startsWith("files:"),
85
+ );
86
+ collector.stop();
87
+
88
+ expect(updateFiles[0]).toMatchObject({
89
+ type: "files:fileRemoved",
90
+ path: "src/index.ts",
91
+ });
92
+ expect(updateFiles[1]).toMatchObject({
93
+ type: "files:directoryRemoved",
94
+ path: "src",
95
+ });
96
+ });
@@ -0,0 +1,40 @@
1
+ import { emitDevtoolsMessage, isDevtoolsEnabled } from "./trace.js";
2
+
3
+ export interface FileUpdateInfo {
4
+ path: string;
5
+ filetype: string;
6
+ contents: string;
7
+ }
8
+
9
+ const fileContentCache = new Map<string, string>();
10
+
11
+ /** Record a directory being added to the output. */
12
+ export function recordDirectory(path: string) {
13
+ if (!isDevtoolsEnabled()) return;
14
+ emitDevtoolsMessage({ type: "files:directoryAdded", path });
15
+ }
16
+
17
+ /** Record a file being added to the output. */
18
+ export function recordFile(path: string, filetype: string) {
19
+ if (!isDevtoolsEnabled()) return;
20
+ emitDevtoolsMessage({ type: "files:fileAdded", path, filetype });
21
+ }
22
+
23
+ /** Notify devtools that a file's contents have changed. De-duplicates by content. */
24
+ export function updated(info: FileUpdateInfo) {
25
+ const previous = fileContentCache.get(info.path);
26
+ if (previous === info.contents) return;
27
+ fileContentCache.set(info.path, info.contents);
28
+
29
+ emitDevtoolsMessage({
30
+ type: "files:fileUpdated",
31
+ path: info.path,
32
+ filetype: info.filetype,
33
+ contents: info.contents,
34
+ });
35
+ }
36
+
37
+ /** Clear all cached file state. Called when a new render begins. */
38
+ export function reset() {
39
+ fileContentCache.clear();
40
+ }
@@ -0,0 +1,126 @@
1
+ import {
2
+ assertDevtoolsConnectedForSyncRender,
3
+ initDevtoolsIfEnabled,
4
+ isDevtoolsEnabled,
5
+ } from "../devtools/devtools-server.js";
6
+ import {
7
+ debugContext,
8
+ debugRender,
9
+ debugStack,
10
+ debugTree,
11
+ debugWatch,
12
+ } from "./cli.js";
13
+ import {
14
+ ensureRef,
15
+ register,
16
+ registerRef,
17
+ reset,
18
+ track,
19
+ trigger,
20
+ update,
21
+ } from "./effects.js";
22
+ import {
23
+ recordDirectory,
24
+ recordFile,
25
+ reset as resetFiles,
26
+ updated,
27
+ } from "./files.js";
28
+ import {
29
+ appendCustomContext,
30
+ appendFragmentChild,
31
+ appendPrintHook,
32
+ appendTextNode,
33
+ beginComponent,
34
+ complete,
35
+ error,
36
+ flushJobsComplete,
37
+ initialize,
38
+ prepareMemoNode,
39
+ } from "./render.js";
40
+ import {
41
+ registerScope,
42
+ registerSymbol,
43
+ reset as resetSymbols,
44
+ unregisterScope,
45
+ unregisterSymbol,
46
+ } from "./symbols.js";
47
+ import { trace, type TracePhaseInfo } from "./trace.js";
48
+
49
+ export { captureSourceLocation } from "./effects.js";
50
+ export type {
51
+ EffectDebugInfo,
52
+ EffectEdgeDebugInfo,
53
+ RefDebugInfo,
54
+ } from "./effects.js";
55
+ export { getRenderNodeId, type RenderTreeNodeInfo } from "./render.js";
56
+ export type {
57
+ BeginComponentOptions,
58
+ ComponentDebugSession,
59
+ RenderErrorInfo,
60
+ RenderErrorStackEntry,
61
+ } from "./render.js";
62
+ export {
63
+ isConsoleTraceEnabled,
64
+ isDevtoolsEnabled,
65
+ trace,
66
+ TracePhase,
67
+ type TracePhaseInfo,
68
+ } from "./trace.js";
69
+
70
+ /** The full debug runtime interface, derived from the `debug` object implementation. */
71
+ export type DebugRuntime = typeof debug;
72
+
73
+ export const debug = {
74
+ component: {
75
+ stack: debugStack,
76
+ tree: debugTree,
77
+ watch: debugWatch,
78
+ render: debugRender,
79
+ context: debugContext,
80
+ },
81
+ effect: {
82
+ register,
83
+ update,
84
+ registerRef,
85
+ ensureRef,
86
+ track,
87
+ trigger,
88
+ reset,
89
+ },
90
+ render: {
91
+ initialize,
92
+ appendTextNode,
93
+ appendCustomContext,
94
+ appendPrintHook,
95
+ appendFragmentChild,
96
+ beginComponent,
97
+ prepareMemoNode,
98
+ error,
99
+ complete,
100
+ flushJobsComplete,
101
+ },
102
+ files: {
103
+ recordDirectory,
104
+ recordFile,
105
+ updated,
106
+ reset: resetFiles,
107
+ },
108
+ symbols: {
109
+ registerScope,
110
+ unregisterScope,
111
+ registerSymbol,
112
+ unregisterSymbol,
113
+ reset: resetSymbols,
114
+ },
115
+ trace(phase: TracePhaseInfo, cb: () => string, triggerIds?: Set<number>) {
116
+ trace(phase, cb, triggerIds ?? new Set());
117
+ },
118
+ async prepare(): Promise<void> {
119
+ await initDevtoolsIfEnabled();
120
+ },
121
+ assertReadyForSyncRender(): void {
122
+ if (isDevtoolsEnabled()) {
123
+ assertDevtoolsConnectedForSyncRender();
124
+ }
125
+ },
126
+ };