@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,106 @@
1
+ import { afterEach, beforeEach, expect, it } from "vitest";
2
+ import WebSocket from "ws";
3
+ import { createMessageCollector } from "../../testing/devtools-utils.js";
4
+ import { createOutputBinder, createScope, createSymbol } from "../binder.js";
5
+ import {
6
+ enableDevtools,
7
+ resetDevtoolsServerForTests,
8
+ } from "../devtools/devtools-server.js";
9
+ import { effect } from "../reactivity.js";
10
+ import { flushJobsAsync } from "../scheduler.js";
11
+ import { BasicScope } from "../symbols/basic-scope.js";
12
+ import { BasicSymbol } from "../symbols/basic-symbol.js";
13
+ import { debug } from "./index.js";
14
+
15
+ let socket: WebSocket | undefined;
16
+
17
+ beforeEach(async () => {
18
+ const server = await enableDevtools({ port: 0 });
19
+ socket = new WebSocket(`ws://127.0.0.1:${server.port}`);
20
+
21
+ await new Promise<void>((resolve, reject) => {
22
+ socket?.once("open", resolve);
23
+ socket?.once("error", reject);
24
+ });
25
+ });
26
+
27
+ afterEach(async () => {
28
+ if (socket) {
29
+ socket.close();
30
+ socket = undefined;
31
+ }
32
+
33
+ await resetDevtoolsServerForTests();
34
+ });
35
+
36
+ it("emits tracking info", async () => {
37
+ const collector = createMessageCollector(socket!);
38
+ const binder = createOutputBinder();
39
+
40
+ const scope = createScope(BasicScope, "root", undefined, { binder });
41
+ const symbol = createSymbol(BasicSymbol, "Foo", scope.symbols, { binder });
42
+ effect(() => {
43
+ void symbol.name;
44
+ });
45
+ symbol.name = "hi";
46
+ await flushJobsAsync();
47
+ const items = (await collector.waitForFlush()).filter((m) =>
48
+ m.type.startsWith("effect:track"),
49
+ );
50
+ expect(items.length).toBeGreaterThan(0);
51
+ });
52
+
53
+ it("emits symbol and scope add/update/remove messages", async () => {
54
+ const collector = createMessageCollector(socket!);
55
+ const binder = createOutputBinder();
56
+
57
+ const scope = createScope(BasicScope, "root", undefined, { binder });
58
+ const symbol = createSymbol(BasicSymbol, "Foo", scope.symbols, { binder });
59
+
60
+ effect(() => {
61
+ void scope.name;
62
+ });
63
+ scope.name = "root-updated";
64
+ symbol.name = "FooUpdated";
65
+
66
+ debug.symbols.unregisterSymbol(symbol);
67
+ debug.symbols.unregisterScope(scope);
68
+
69
+ await flushJobsAsync();
70
+
71
+ const messages = await collector.waitForFlush();
72
+ const symbolsMessages = messages.filter((m) => {
73
+ return m.type.startsWith("scope:") || m.type.startsWith("symbol:");
74
+ });
75
+ collector.stop();
76
+
77
+ expect(symbolsMessages[0]).toMatchObject({
78
+ type: "scope:create",
79
+ scope: expect.objectContaining({ name: "root" }),
80
+ });
81
+
82
+ expect(symbolsMessages[1]).toMatchObject({
83
+ type: "symbol:addToScope",
84
+ });
85
+
86
+ expect(symbolsMessages[2]).toMatchObject({
87
+ type: "symbol:create",
88
+ symbol: expect.objectContaining({ name: "Foo" }),
89
+ });
90
+ expect(symbolsMessages[3]).toMatchObject({
91
+ type: "scope:update",
92
+ scope: expect.objectContaining({ name: "root-updated" }),
93
+ });
94
+ expect(symbolsMessages[4]).toMatchObject({
95
+ type: "symbol:update",
96
+ symbol: expect.objectContaining({ name: "FooUpdated" }),
97
+ });
98
+ expect(symbolsMessages[5]).toMatchObject({
99
+ type: "symbol:delete",
100
+ id: expect.any(Number),
101
+ });
102
+ expect(symbolsMessages[6]).toMatchObject({
103
+ type: "scope:delete",
104
+ id: expect.any(Number),
105
+ });
106
+ });
@@ -0,0 +1,230 @@
1
+ import { watch } from "@vue/reactivity";
2
+ import { getContext, untrack } from "../reactivity.js";
3
+ import type { OutputScope } from "../symbols/output-scope.js";
4
+ import type { OutputSymbol } from "../symbols/output-symbol.js";
5
+ import { getRenderNodeId } from "./render.js";
6
+ import { sanitizeRecord } from "./serialize.js";
7
+ import {
8
+ emitDevtoolsMessage,
9
+ isDevtoolsEnabled,
10
+ TracePhase,
11
+ traceType,
12
+ } from "./trace.js";
13
+
14
+ interface ScopeSnapshot {
15
+ id: number;
16
+ name: string;
17
+ parentId: number | null;
18
+ ownerSymbolId: number | null;
19
+ isMemberScope: boolean;
20
+ renderNodeId: number | null;
21
+ metadata: Record<string, unknown> | undefined;
22
+ debugInfo: Record<string, unknown> | undefined;
23
+ children: Array<{ id: number; name: string }>;
24
+ spaces: Array<{ key: string; symbols: Array<{ id: number; name: string }> }>;
25
+ }
26
+
27
+ interface SymbolSnapshot {
28
+ id: number;
29
+ name: string;
30
+ originalName: string;
31
+ scopeId: number | null;
32
+ ownerSymbolId: number | null;
33
+ isMemberSymbol: boolean;
34
+ isTransient: boolean;
35
+ isAlias: boolean;
36
+ movedToId: number | null;
37
+ renderNodeId: number | null;
38
+ metadata: Record<string, unknown> | undefined;
39
+ debugInfo: Record<string, unknown> | undefined;
40
+ memberSpaces: Array<{
41
+ key: string;
42
+ symbols: Array<{ id: number; name: string }>;
43
+ }>;
44
+ }
45
+
46
+ const scopeWatchers = new Map<number, () => void>();
47
+ const symbolWatchers = new Map<number, () => void>();
48
+
49
+ function shallowEqual<T extends object>(a: T, b: T) {
50
+ const aRecord = a as Record<string, unknown>;
51
+ const bRecord = b as Record<string, unknown>;
52
+ const aKeys = Object.keys(aRecord);
53
+ const bKeys = Object.keys(bRecord);
54
+ if (aKeys.length !== bKeys.length) return false;
55
+ for (const key of aKeys) {
56
+ const aValue = aRecord[key];
57
+ const bValue = bRecord[key];
58
+ if (aValue === bValue) continue;
59
+ if (aValue === undefined || bValue === undefined) return false;
60
+ const aType = typeof aValue;
61
+ const bType = typeof bValue;
62
+ if (aType === "object" || bType === "object") {
63
+ if (JSON.stringify(aValue) !== JSON.stringify(bValue)) return false;
64
+ } else if (aValue !== bValue) {
65
+ return false;
66
+ }
67
+ }
68
+ return true;
69
+ }
70
+
71
+ function sanitizeDebugInfo(input: Record<string, unknown> | undefined) {
72
+ return sanitizeRecord(input);
73
+ }
74
+
75
+ function getRenderNodeIdForCurrentContext() {
76
+ return untrack(() => {
77
+ const context = getContext();
78
+ let current = context;
79
+ while (current) {
80
+ const renderNode = current.meta?.renderNode as
81
+ | Parameters<typeof getRenderNodeId>[0]
82
+ | undefined;
83
+ if (renderNode) {
84
+ return getRenderNodeId(renderNode) ?? null;
85
+ }
86
+ current = current.owner;
87
+ }
88
+ return null;
89
+ });
90
+ }
91
+
92
+ function snapshotScope(
93
+ scope: OutputScope,
94
+ renderNodeId: number | null,
95
+ ): ScopeSnapshot {
96
+ return {
97
+ id: scope.id,
98
+ name: scope.name,
99
+ parentId: scope.parent?.id ?? null,
100
+ ownerSymbolId: scope.ownerSymbol?.id ?? null,
101
+ isMemberScope: scope.isMemberScope,
102
+ renderNodeId,
103
+ metadata: sanitizeRecord(scope.metadata),
104
+ debugInfo: sanitizeDebugInfo(scope.debugInfo),
105
+ children: untrack(() =>
106
+ Array.from(scope.children).map((child) => ({
107
+ id: child.id,
108
+ name: child.name,
109
+ })),
110
+ ),
111
+ spaces: untrack(() =>
112
+ scope.spaces.map((space) => ({
113
+ key: space.key,
114
+ symbols: Array.from(space).map((symbol) => ({
115
+ id: symbol.id,
116
+ name: symbol.name,
117
+ })),
118
+ })),
119
+ ),
120
+ };
121
+ }
122
+
123
+ function snapshotSymbol(
124
+ symbol: OutputSymbol,
125
+ renderNodeId: number | null,
126
+ ): SymbolSnapshot {
127
+ return {
128
+ id: symbol.id,
129
+ name: symbol.name,
130
+ originalName: symbol.originalName,
131
+ scopeId: symbol.scope?.id ?? null,
132
+ ownerSymbolId: symbol.ownerSymbol?.id ?? null,
133
+ isMemberSymbol: symbol.isMemberSymbol,
134
+ isTransient: symbol.isTransient,
135
+ isAlias: symbol.isAlias,
136
+ movedToId: symbol.movedTo?.id ?? null,
137
+ renderNodeId,
138
+ metadata: sanitizeRecord(symbol.metadata),
139
+ debugInfo: sanitizeDebugInfo(symbol.debugInfo),
140
+ memberSpaces: symbol.memberSpaces.map((space) => ({
141
+ key: space.key,
142
+ symbols: Array.from(space).map((member) => ({
143
+ id: member.id,
144
+ name: member.name,
145
+ })),
146
+ })),
147
+ };
148
+ }
149
+
150
+ export function registerScope(scope: OutputScope) {
151
+ if (!isDevtoolsEnabled()) return;
152
+ if (scopeWatchers.has(scope.id)) return;
153
+ untrack(() => {
154
+ const renderNodeId = getRenderNodeIdForCurrentContext();
155
+ let previous = snapshotScope(scope, renderNodeId);
156
+ emitDevtoolsMessage({
157
+ type: traceType(TracePhase.scope.create),
158
+ scope: previous,
159
+ });
160
+ const stop = watch(
161
+ () => snapshotScope(scope, renderNodeId),
162
+ (next) => {
163
+ if (!shallowEqual(previous, next)) {
164
+ previous = next;
165
+ emitDevtoolsMessage({
166
+ type: traceType(TracePhase.scope.update),
167
+ scope: next,
168
+ });
169
+ }
170
+ },
171
+ );
172
+ scopeWatchers.set(scope.id, stop);
173
+ });
174
+ }
175
+
176
+ export function unregisterScope(scope: OutputScope) {
177
+ if (!isDevtoolsEnabled()) return;
178
+ const stop = scopeWatchers.get(scope.id);
179
+ if (stop) stop();
180
+ scopeWatchers.delete(scope.id);
181
+ emitDevtoolsMessage({
182
+ type: traceType(TracePhase.scope.delete),
183
+ id: scope.id,
184
+ });
185
+ }
186
+
187
+ export function registerSymbol(symbol: OutputSymbol) {
188
+ if (!isDevtoolsEnabled()) return;
189
+ if (symbolWatchers.has(symbol.id)) return;
190
+ untrack(() => {
191
+ const renderNodeId = getRenderNodeIdForCurrentContext();
192
+ let previous = snapshotSymbol(symbol, renderNodeId);
193
+ emitDevtoolsMessage({
194
+ type: traceType(TracePhase.symbol.create),
195
+ symbol: previous,
196
+ });
197
+ const stop = watch(
198
+ () => snapshotSymbol(symbol, renderNodeId),
199
+ (next) => {
200
+ if (!shallowEqual(previous, next)) {
201
+ previous = next;
202
+ emitDevtoolsMessage({
203
+ type: traceType(TracePhase.symbol.update),
204
+ symbol: next,
205
+ });
206
+ }
207
+ },
208
+ );
209
+ symbolWatchers.set(symbol.id, stop);
210
+ });
211
+ }
212
+
213
+ export function unregisterSymbol(symbol: OutputSymbol) {
214
+ if (!isDevtoolsEnabled()) return;
215
+ const stop = symbolWatchers.get(symbol.id);
216
+ if (stop) stop();
217
+ symbolWatchers.delete(symbol.id);
218
+ emitDevtoolsMessage({
219
+ type: traceType(TracePhase.symbol.delete),
220
+ id: symbol.id,
221
+ });
222
+ }
223
+
224
+ /** Stop all watchers and clear tracked state. Called when a new render begins. */
225
+ export function reset() {
226
+ for (const stop of scopeWatchers.values()) stop();
227
+ for (const stop of symbolWatchers.values()) stop();
228
+ scopeWatchers.clear();
229
+ symbolWatchers.clear();
230
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Trace helpers and configuration.
3
+ */
4
+ import type { ServerToClientMessage } from "../devtools/devtools-protocol.js";
5
+ import {
6
+ broadcastDevtoolsMessage,
7
+ isDevtoolsEnabled,
8
+ type DevtoolsMessage,
9
+ } from "../devtools/devtools-server.js";
10
+ import { untrack } from "../reactivity.js";
11
+
12
+ export { isDevtoolsEnabled } from "../devtools/devtools-server.js";
13
+
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ // Environment configuration
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+
18
+ const traceEnv = process.env.ALLOY_TRACE ?? "";
19
+ const tracePhases = new Set<string>(
20
+ traceEnv === "" ? [] : traceEnv.split(",").map((t) => t.trim()),
21
+ );
22
+
23
+ const debuggerIdsEnv = process.env.ALLOY_BREAK_ON_DID ?? "";
24
+ const debuggerIds = new Set<number>();
25
+ debuggerIdsEnv.split(",").forEach((id) => {
26
+ const num = parseInt(id, 10);
27
+ if (!isNaN(num)) {
28
+ debuggerIds.add(num);
29
+ }
30
+ });
31
+
32
+ /** Parse the ALLOY_BREAK_ON_DID environment variable into a set of IDs. */
33
+ export function parseBreakOnIds(): Set<number> {
34
+ const env = process.env.ALLOY_BREAK_ON_DID ?? "";
35
+ const ids = new Set<number>();
36
+ env.split(",").forEach((id) => {
37
+ const num = parseInt(id, 10);
38
+ if (!isNaN(num)) {
39
+ ids.add(num);
40
+ }
41
+ });
42
+ return ids;
43
+ }
44
+
45
+ /** Returns true if console tracing is enabled for the given phase (or any phase if not specified). */
46
+ export function isConsoleTraceEnabled(phase?: string): boolean {
47
+ if (tracePhases.size === 0) return false;
48
+ if (!phase) return true;
49
+ const [area, subarea] = phase.split(".");
50
+ return tracePhases.has(area) || (subarea ? tracePhases.has(phase) : false);
51
+ }
52
+
53
+ if (tracePhases.size > 0) {
54
+ // eslint-disable-next-line no-console
55
+ console.log(
56
+ "Tracing enabled for phases:",
57
+ Array.from(tracePhases).join(", "),
58
+ );
59
+ }
60
+
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+ // Trace phases
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+
65
+ interface Color {
66
+ r: number;
67
+ g: number;
68
+ b: number;
69
+ }
70
+
71
+ export interface TracePhaseInfo {
72
+ area: string;
73
+ subarea: string;
74
+ bg: Color;
75
+ }
76
+
77
+ export const TracePhase = {
78
+ scope: {
79
+ update: { area: "scope", subarea: "update", bg: { r: 0, g: 255, b: 100 } },
80
+ create: { area: "scope", subarea: "create", bg: { r: 0, g: 150, b: 100 } },
81
+ delete: { area: "scope", subarea: "delete", bg: { r: 150, g: 0, b: 50 } },
82
+ copySymbols: {
83
+ area: "scope",
84
+ subarea: "copySymbols",
85
+ bg: { r: 0, g: 100, b: 100 },
86
+ },
87
+ moveSymbols: {
88
+ area: "scope",
89
+ subarea: "moveSymbols",
90
+ bg: { r: 0, g: 100, b: 100 },
91
+ },
92
+ },
93
+ symbol: {
94
+ update: { area: "symbol", subarea: "update", bg: { r: 0, g: 0, b: 255 } },
95
+ resolve: { area: "symbol", subarea: "resolve", bg: { r: 0, g: 0, b: 200 } },
96
+ create: { area: "symbol", subarea: "create", bg: { r: 0, g: 0, b: 150 } },
97
+ flow: { area: "symbol", subarea: "flow", bg: { r: 0, g: 0, b: 100 } },
98
+ addToScope: {
99
+ area: "symbol",
100
+ subarea: "addToScope",
101
+ bg: { r: 0, g: 0, b: 50 },
102
+ },
103
+ instantiate: {
104
+ area: "symbol",
105
+ subarea: "instantiate",
106
+ bg: { r: 0, g: 0, b: 25 },
107
+ },
108
+ clone: { area: "symbol", subarea: "clone", bg: { r: 0, g: 0, b: 25 } },
109
+ delete: { area: "symbol", subarea: "delete", bg: { r: 100, g: 0, b: 100 } },
110
+ removeFromScope: {
111
+ area: "symbol",
112
+ subarea: "removeFromScope",
113
+ bg: { r: 50, g: 0, b: 50 },
114
+ },
115
+ },
116
+ resolve: {
117
+ success: {
118
+ area: "resolve",
119
+ subarea: "success",
120
+ bg: { r: 0, g: 255, b: 0 },
121
+ },
122
+ pending: {
123
+ area: "resolve",
124
+ subarea: "pending",
125
+ bg: { r: 255, g: 255, b: 0 },
126
+ },
127
+ failure: {
128
+ area: "resolve",
129
+ subarea: "failure",
130
+ bg: { r: 100, g: 50, b: 50 },
131
+ },
132
+ },
133
+ effect: {
134
+ schedule: {
135
+ area: "effect",
136
+ subarea: "schedule",
137
+ bg: { r: 100, g: 100, b: 0 },
138
+ },
139
+ track: { area: "effect", subarea: "track", bg: { r: 75, g: 75, b: 0 } },
140
+ trigger: { area: "effect", subarea: "trigger", bg: { r: 50, g: 50, b: 0 } },
141
+ effectAdded: {
142
+ area: "effect",
143
+ subarea: "effectAdded",
144
+ bg: { r: 75, g: 100, b: 0 },
145
+ },
146
+ effectUpdated: {
147
+ area: "effect",
148
+ subarea: "effectUpdated",
149
+ bg: { r: 75, g: 75, b: 0 },
150
+ },
151
+ refAdded: {
152
+ area: "effect",
153
+ subarea: "refAdded",
154
+ bg: { r: 100, g: 75, b: 0 },
155
+ },
156
+ },
157
+ render: {
158
+ worker: { area: "render", subarea: "worker", bg: { r: 100, g: 50, b: 0 } },
159
+ appendChild: {
160
+ area: "render",
161
+ subarea: "appendChild",
162
+ bg: { r: 100, g: 50, b: 0 },
163
+ },
164
+ appendTextNode: {
165
+ area: "render",
166
+ subarea: "appendChild.textNode",
167
+ bg: { r: 100, g: 50, b: 0 },
168
+ },
169
+ appendCachedFragment: {
170
+ area: "render",
171
+ subarea: "appendChild.cachedFragment",
172
+ bg: { r: 100, g: 50, b: 0 },
173
+ },
174
+ appendCustomContext: {
175
+ area: "render",
176
+ subarea: "appendChild.customContext",
177
+ bg: { r: 100, g: 50, b: 0 },
178
+ },
179
+ appendPrintHook: {
180
+ area: "render",
181
+ subarea: "appendChild.printHook",
182
+ bg: { r: 100, g: 50, b: 0 },
183
+ },
184
+ appendComponent: {
185
+ area: "render",
186
+ subarea: "appendChild.component",
187
+ bg: { r: 100, g: 50, b: 0 },
188
+ },
189
+ appendMemo: {
190
+ area: "render",
191
+ subarea: "appendChild.memo",
192
+ bg: { r: 100, g: 50, b: 0 },
193
+ },
194
+ renderEffect: {
195
+ area: "render",
196
+ subarea: "renderEffect",
197
+ bg: { r: 100, g: 50, b: 0 },
198
+ },
199
+ },
200
+ } as const;
201
+
202
+ // ─────────────────────────────────────────────────────────────────────────────
203
+ // Console formatting utilities
204
+ // ─────────────────────────────────────────────────────────────────────────────
205
+
206
+ export interface TextFormat {
207
+ fg?: Color;
208
+ bg?: Color;
209
+ bold?: boolean;
210
+ }
211
+
212
+ export function colorText(text: string, fmt?: TextFormat): string {
213
+ if (!fmt) return text;
214
+ const codes: string[] = [];
215
+ if (fmt.bold) codes.push("1");
216
+ if (fmt.fg) codes.push(`38;2;${fmt.fg.r};${fmt.fg.g};${fmt.fg.b}`);
217
+ if (fmt.bg) codes.push(`48;2;${fmt.bg.r};${fmt.bg.g};${fmt.bg.b}`);
218
+ if (codes.length === 0) return text;
219
+ return `\x1b[${codes.join(";")}m${text}\x1b[0m`;
220
+ }
221
+
222
+ // ─────────────────────────────────────────────────────────────────────────────
223
+ // Low-level trace API
224
+ // ─────────────────────────────────────────────────────────────────────────────
225
+
226
+ let traceCount = 0;
227
+
228
+ function shouldTracePhase(area: string, subarea: string): boolean {
229
+ return (
230
+ isConsoleTraceEnabled(area) || isConsoleTraceEnabled(area + "." + subarea)
231
+ );
232
+ }
233
+
234
+ function shouldTrace(phase: TracePhaseInfo): boolean {
235
+ return shouldTracePhase(phase.area, phase.subarea);
236
+ }
237
+
238
+ export function traceType(phase: TracePhaseInfo): string {
239
+ return `${phase.area}:${phase.subarea}`;
240
+ }
241
+
242
+ export function logDevtoolsMessage(
243
+ message: ServerToClientMessage | DevtoolsMessage,
244
+ ) {
245
+ if (!isConsoleTraceEnabled()) return;
246
+ const type = String(message.type ?? "");
247
+ const colonIndex = type.indexOf(":");
248
+ if (colonIndex === -1) return;
249
+ const area = type.slice(0, colonIndex);
250
+ const subarea = type.slice(colonIndex + 1);
251
+ if (!area || !subarea) return;
252
+ if (!shouldTracePhase(area, subarea)) return;
253
+ // eslint-disable-next-line no-console
254
+ console.log("devtools:", message.type, message);
255
+ }
256
+
257
+ /**
258
+ * Emit a devtools message. Logs to console if tracing is enabled for the
259
+ * message's area, and broadcasts over the WebSocket if devtools are connected.
260
+ *
261
+ * Accepts both strictly-typed protocol messages and loosely-typed trace
262
+ * messages (which construct the type string dynamically).
263
+ */
264
+ export function emitDevtoolsMessage(
265
+ message: ServerToClientMessage | DevtoolsMessage,
266
+ ) {
267
+ logDevtoolsMessage(message);
268
+ if (!isDevtoolsEnabled()) return;
269
+ broadcastDevtoolsMessage(message as DevtoolsMessage);
270
+ }
271
+
272
+ /**
273
+ * Low-level trace function for emitting console output.
274
+ * Use the `debug` object methods for most use cases.
275
+ */
276
+ export function trace(
277
+ phase: TracePhaseInfo,
278
+ cb: () => string,
279
+ triggerIds: Set<number> = new Set(),
280
+ ): void {
281
+ if (shouldTrace(phase)) {
282
+ if (triggerIds.size === 0) {
283
+ const id = traceCount++;
284
+ triggerIds.add(id);
285
+ if (debuggerIds.has(id)) {
286
+ // eslint-disable-next-line no-debugger
287
+ debugger;
288
+ }
289
+ }
290
+
291
+ const areaTag = ` ${phase.area}:${phase.subarea} `;
292
+ const message = untrack(cb);
293
+ // eslint-disable-next-line no-console
294
+ console.log(
295
+ colorText(areaTag, { ...phase, bold: true }) +
296
+ " " +
297
+ colorText("[" + [...triggerIds].join(",") + "]", {
298
+ fg: { r: 50, g: 50, b: 50 },
299
+ }) +
300
+ " " +
301
+ message +
302
+ "\n",
303
+ );
304
+ }
305
+
306
+ if (isDevtoolsEnabled()) {
307
+ broadcastDevtoolsMessage({
308
+ type: traceType(phase),
309
+ triggerIds: [...triggerIds],
310
+ });
311
+ }
312
+ }