@alloy-js/core 0.23.0-dev.11 → 0.23.0-dev.13

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 (380) hide show
  1. package/dist/dev/src/binder.js +488 -0
  2. package/dist/dev/src/binder.js.map +1 -0
  3. package/dist/dev/src/code.js +181 -0
  4. package/dist/dev/src/code.js.map +1 -0
  5. package/dist/dev/src/components/AccessExpression.js +230 -0
  6. package/dist/dev/src/components/AccessExpression.js.map +1 -0
  7. package/dist/dev/src/components/AccessExpression.test.js +237 -0
  8. package/dist/dev/src/components/AccessExpression.test.js.map +1 -0
  9. package/dist/dev/src/components/AppendFile.js +246 -0
  10. package/dist/dev/src/components/AppendFile.js.map +1 -0
  11. package/dist/dev/src/components/Block.js +66 -0
  12. package/dist/dev/src/components/Block.js.map +1 -0
  13. package/dist/dev/src/components/CopyFile.js +16 -0
  14. package/dist/dev/src/components/CopyFile.js.map +1 -0
  15. package/dist/dev/src/components/Declaration.js +70 -0
  16. package/dist/dev/src/components/Declaration.js.map +1 -0
  17. package/dist/dev/src/components/For.js +40 -0
  18. package/dist/dev/src/components/For.js.map +1 -0
  19. package/dist/dev/src/components/Indent.js +37 -0
  20. package/dist/dev/src/components/Indent.js.map +1 -0
  21. package/dist/dev/src/components/List.js +29 -0
  22. package/dist/dev/src/components/List.js.map +1 -0
  23. package/dist/dev/src/components/MemberDeclaration.js +70 -0
  24. package/dist/dev/src/components/MemberDeclaration.js.map +1 -0
  25. package/dist/dev/src/components/MemberName.js +11 -0
  26. package/dist/dev/src/components/MemberName.js.map +1 -0
  27. package/dist/dev/src/components/MemberScope.js +57 -0
  28. package/dist/dev/src/components/MemberScope.js.map +1 -0
  29. package/dist/dev/src/components/Name.js +11 -0
  30. package/dist/dev/src/components/Name.js.map +1 -0
  31. package/dist/dev/src/components/Output.js +72 -0
  32. package/dist/dev/src/components/Output.js.map +1 -0
  33. package/dist/dev/src/components/Prose.js +32 -0
  34. package/dist/dev/src/components/Prose.js.map +1 -0
  35. package/dist/dev/src/components/ReferenceOrContent.js +12 -0
  36. package/dist/dev/src/components/ReferenceOrContent.js.map +1 -0
  37. package/dist/dev/src/components/Scope.js +50 -0
  38. package/dist/dev/src/components/Scope.js.map +1 -0
  39. package/dist/dev/src/components/Show.js +4 -0
  40. package/dist/dev/src/components/Show.js.map +1 -0
  41. package/dist/dev/src/components/SourceDirectory.js +41 -0
  42. package/dist/dev/src/components/SourceDirectory.js.map +1 -0
  43. package/dist/dev/src/components/SourceFile.js +52 -0
  44. package/dist/dev/src/components/SourceFile.js.map +1 -0
  45. package/dist/dev/src/components/StatementList.js +20 -0
  46. package/dist/dev/src/components/StatementList.js.map +1 -0
  47. package/dist/dev/src/components/Switch.js +42 -0
  48. package/dist/dev/src/components/Switch.js.map +1 -0
  49. package/dist/dev/src/components/TemplateFile.js +153 -0
  50. package/dist/dev/src/components/TemplateFile.js.map +1 -0
  51. package/dist/dev/src/components/UpdateFile.js +71 -0
  52. package/dist/dev/src/components/UpdateFile.js.map +1 -0
  53. package/dist/dev/src/components/Wrap.js +18 -0
  54. package/dist/dev/src/components/Wrap.js.map +1 -0
  55. package/dist/dev/src/components/index.js +25 -0
  56. package/dist/dev/src/components/index.js.map +1 -0
  57. package/dist/dev/src/components/stc/index.js +27 -0
  58. package/dist/dev/src/components/stc/index.js.map +1 -0
  59. package/dist/dev/src/components/stc/sti.js +10 -0
  60. package/dist/dev/src/components/stc/sti.js.map +1 -0
  61. package/dist/dev/src/content-slot.js +78 -0
  62. package/dist/dev/src/content-slot.js.map +1 -0
  63. package/dist/dev/src/content-slot.test.js +89 -0
  64. package/dist/dev/src/content-slot.test.js.map +1 -0
  65. package/dist/dev/src/context/assignment.js +46 -0
  66. package/dist/dev/src/context/assignment.js.map +1 -0
  67. package/dist/dev/src/context/binder.js +12 -0
  68. package/dist/dev/src/context/binder.js.map +1 -0
  69. package/dist/dev/src/context/declaration.js +3 -0
  70. package/dist/dev/src/context/declaration.js.map +1 -0
  71. package/dist/dev/src/context/format-options.js +32 -0
  72. package/dist/dev/src/context/format-options.js.map +1 -0
  73. package/dist/dev/src/context/index.js +11 -0
  74. package/dist/dev/src/context/index.js.map +1 -0
  75. package/dist/dev/src/context/member-declaration.js +11 -0
  76. package/dist/dev/src/context/member-declaration.js.map +1 -0
  77. package/dist/dev/src/context/member-scope.js +12 -0
  78. package/dist/dev/src/context/member-scope.js.map +1 -0
  79. package/dist/dev/src/context/name-policy.js +13 -0
  80. package/dist/dev/src/context/name-policy.js.map +1 -0
  81. package/dist/dev/src/context/scope.js +13 -0
  82. package/dist/dev/src/context/scope.js.map +1 -0
  83. package/dist/dev/src/context/source-directory.js +3 -0
  84. package/dist/dev/src/context/source-directory.js.map +1 -0
  85. package/dist/dev/src/context/source-file.js +3 -0
  86. package/dist/dev/src/context/source-file.js.map +1 -0
  87. package/dist/dev/src/context.js +47 -0
  88. package/dist/dev/src/context.js.map +1 -0
  89. package/dist/dev/src/debug/cli.js +164 -0
  90. package/dist/dev/src/debug/cli.js.map +1 -0
  91. package/dist/dev/src/debug/diagnostics.test.js +50 -0
  92. package/dist/dev/src/debug/diagnostics.test.js.map +1 -0
  93. package/dist/dev/src/debug/effects.js +293 -0
  94. package/dist/dev/src/debug/effects.js.map +1 -0
  95. package/dist/dev/src/debug/effects.test.js +280 -0
  96. package/dist/dev/src/debug/effects.test.js.map +1 -0
  97. package/dist/dev/src/debug/files.js +29 -0
  98. package/dist/dev/src/debug/files.js.map +1 -0
  99. package/dist/dev/src/debug/files.test.js +78 -0
  100. package/dist/dev/src/debug/files.test.js.map +1 -0
  101. package/dist/dev/src/debug/index.js +71 -0
  102. package/dist/dev/src/debug/index.js.map +1 -0
  103. package/dist/dev/src/debug/message-format.test.js +836 -0
  104. package/dist/dev/src/debug/message-format.test.js.map +1 -0
  105. package/dist/dev/src/debug/render-tree-orphans.test.js +365 -0
  106. package/dist/dev/src/debug/render-tree-orphans.test.js.map +1 -0
  107. package/dist/dev/src/debug/render.js +479 -0
  108. package/dist/dev/src/debug/render.js.map +1 -0
  109. package/dist/dev/src/debug/render.test.js +363 -0
  110. package/dist/dev/src/debug/render.test.js.map +1 -0
  111. package/dist/dev/src/debug/serialize.js +70 -0
  112. package/dist/dev/src/debug/serialize.js.map +1 -0
  113. package/dist/dev/src/debug/source-map.browser.js +24 -0
  114. package/dist/dev/src/debug/source-map.browser.js.map +1 -0
  115. package/dist/dev/src/debug/source-map.js +111 -0
  116. package/dist/dev/src/debug/source-map.js.map +1 -0
  117. package/dist/dev/src/debug/symbols.js +196 -0
  118. package/dist/dev/src/debug/symbols.js.map +1 -0
  119. package/dist/dev/src/debug/symbols.test.js +93 -0
  120. package/dist/dev/src/debug/symbols.test.js.map +1 -0
  121. package/dist/dev/src/debug/trace-writer.js +658 -0
  122. package/dist/dev/src/debug/trace-writer.js.map +1 -0
  123. package/dist/dev/src/debug/trace.js +460 -0
  124. package/dist/dev/src/debug/trace.js.map +1 -0
  125. package/dist/dev/src/devtools/devtools-protocol.js +2 -0
  126. package/dist/dev/src/devtools/devtools-protocol.js.map +1 -0
  127. package/dist/dev/src/devtools/devtools-server.browser.js +33 -0
  128. package/dist/dev/src/devtools/devtools-server.browser.js.map +1 -0
  129. package/dist/dev/src/devtools/devtools-server.js +444 -0
  130. package/dist/dev/src/devtools/devtools-server.js.map +1 -0
  131. package/dist/dev/src/devtools/devtools-transport.js +114 -0
  132. package/dist/dev/src/devtools/devtools-transport.js.map +1 -0
  133. package/dist/dev/src/devtools-entry.browser.js +2 -0
  134. package/dist/dev/src/devtools-entry.browser.js.map +1 -0
  135. package/dist/dev/src/devtools-entry.js +2 -0
  136. package/dist/dev/src/devtools-entry.js.map +1 -0
  137. package/dist/dev/src/diagnostics.js +89 -0
  138. package/dist/dev/src/diagnostics.js.map +1 -0
  139. package/dist/dev/src/host/alloy-host.browser.js +32 -0
  140. package/dist/dev/src/host/alloy-host.browser.js.map +1 -0
  141. package/dist/dev/src/host/alloy-host.js +144 -0
  142. package/dist/dev/src/host/alloy-host.js.map +1 -0
  143. package/dist/dev/src/host/interface.js +2 -0
  144. package/dist/dev/src/host/interface.js.map +1 -0
  145. package/dist/dev/src/host/node-host.browser.js +21 -0
  146. package/dist/dev/src/host/node-host.browser.js.map +1 -0
  147. package/dist/dev/src/host/node-host.js +20 -0
  148. package/dist/dev/src/host/node-host.js.map +1 -0
  149. package/dist/dev/src/index.browser.js +3 -0
  150. package/dist/dev/src/index.browser.js.map +1 -0
  151. package/dist/dev/src/index.js +27 -0
  152. package/dist/dev/src/index.js.map +1 -0
  153. package/dist/dev/src/inspect.browser.js +6 -0
  154. package/dist/dev/src/inspect.browser.js.map +1 -0
  155. package/dist/dev/src/inspect.js +2 -0
  156. package/dist/dev/src/inspect.js.map +1 -0
  157. package/dist/dev/src/jsx-runtime.js +17 -0
  158. package/dist/dev/src/jsx-runtime.js.map +1 -0
  159. package/dist/dev/src/library-symbol-reference.js +5 -0
  160. package/dist/dev/src/library-symbol-reference.js.map +1 -0
  161. package/dist/dev/src/name-policy.js +24 -0
  162. package/dist/dev/src/name-policy.js.map +1 -0
  163. package/dist/dev/src/pretty-string/pretty-string.js +100 -0
  164. package/dist/dev/src/pretty-string/pretty-string.js.map +1 -0
  165. package/dist/dev/src/pretty-string/pretty-string.test.js +38 -0
  166. package/dist/dev/src/pretty-string/pretty-string.test.js.map +1 -0
  167. package/dist/dev/src/print-hook.js +10 -0
  168. package/dist/dev/src/print-hook.js.map +1 -0
  169. package/dist/dev/src/props-combinators.js +109 -0
  170. package/dist/dev/src/props-combinators.js.map +1 -0
  171. package/dist/dev/src/reactive-union-set.js +213 -0
  172. package/dist/dev/src/reactive-union-set.js.map +1 -0
  173. package/dist/dev/src/reactivity.js +426 -0
  174. package/dist/dev/src/reactivity.js.map +1 -0
  175. package/dist/dev/src/refkey.js +167 -0
  176. package/dist/dev/src/refkey.js.map +1 -0
  177. package/dist/dev/src/render-stack.js +252 -0
  178. package/dist/dev/src/render-stack.js.map +1 -0
  179. package/dist/dev/src/render.js +872 -0
  180. package/dist/dev/src/render.js.map +1 -0
  181. package/dist/dev/src/resource.js +124 -0
  182. package/dist/dev/src/resource.js.map +1 -0
  183. package/dist/dev/src/runtime/component.js +41 -0
  184. package/dist/dev/src/runtime/component.js.map +1 -0
  185. package/dist/dev/src/runtime/intrinsic.js +12 -0
  186. package/dist/dev/src/runtime/intrinsic.js.map +1 -0
  187. package/dist/dev/src/scheduler.js +217 -0
  188. package/dist/dev/src/scheduler.js.map +1 -0
  189. package/dist/dev/src/stc.js +40 -0
  190. package/dist/dev/src/stc.js.map +1 -0
  191. package/dist/dev/src/sti.js +31 -0
  192. package/dist/dev/src/sti.js.map +1 -0
  193. package/dist/dev/src/symbols/basic-scope.js +21 -0
  194. package/dist/dev/src/symbols/basic-scope.js.map +1 -0
  195. package/dist/dev/src/symbols/basic-symbol.js +34 -0
  196. package/dist/dev/src/symbols/basic-symbol.js.map +1 -0
  197. package/dist/dev/src/symbols/decl.js +26 -0
  198. package/dist/dev/src/symbols/decl.js.map +1 -0
  199. package/dist/dev/src/symbols/index.js +10 -0
  200. package/dist/dev/src/symbols/index.js.map +1 -0
  201. package/dist/dev/src/symbols/output-scope.js +202 -0
  202. package/dist/dev/src/symbols/output-scope.js.map +1 -0
  203. package/dist/dev/src/symbols/output-space.js +36 -0
  204. package/dist/dev/src/symbols/output-space.js.map +1 -0
  205. package/dist/dev/src/symbols/output-symbol.js +504 -0
  206. package/dist/dev/src/symbols/output-symbol.js.map +1 -0
  207. package/dist/dev/src/symbols/symbol-flow.js +106 -0
  208. package/dist/dev/src/symbols/symbol-flow.js.map +1 -0
  209. package/dist/dev/src/symbols/symbol-slot.js +68 -0
  210. package/dist/dev/src/symbols/symbol-slot.js.map +1 -0
  211. package/dist/dev/src/symbols/symbol-slot.test.js +43 -0
  212. package/dist/dev/src/symbols/symbol-slot.test.js.map +1 -0
  213. package/dist/dev/src/symbols/symbol-table.js +93 -0
  214. package/dist/dev/src/symbols/symbol-table.js.map +1 -0
  215. package/dist/dev/src/tap.js +109 -0
  216. package/dist/dev/src/tap.js.map +1 -0
  217. package/dist/dev/src/trace.js +2 -0
  218. package/dist/dev/src/trace.js.map +1 -0
  219. package/dist/dev/src/tracer.js +180 -0
  220. package/dist/dev/src/tracer.js.map +1 -0
  221. package/dist/dev/src/utils.js +487 -0
  222. package/dist/dev/src/utils.js.map +1 -0
  223. package/dist/dev/src/write-output.js +48 -0
  224. package/dist/dev/src/write-output.js.map +1 -0
  225. package/dist/dev/test/browser-build.test.js +85 -0
  226. package/dist/dev/test/browser-build.test.js.map +1 -0
  227. package/dist/dev/test/children.test.js +44 -0
  228. package/dist/dev/test/children.test.js.map +1 -0
  229. package/dist/dev/test/components/append-file.test.js +394 -0
  230. package/dist/dev/test/components/append-file.test.js.map +1 -0
  231. package/dist/dev/test/components/block.test.js +83 -0
  232. package/dist/dev/test/components/block.test.js.map +1 -0
  233. package/dist/dev/test/components/copy-file.test.js +119 -0
  234. package/dist/dev/test/components/copy-file.test.js.map +1 -0
  235. package/dist/dev/test/components/declaration.test.js +40 -0
  236. package/dist/dev/test/components/declaration.test.js.map +1 -0
  237. package/dist/dev/test/components/list.test.js +250 -0
  238. package/dist/dev/test/components/list.test.js.map +1 -0
  239. package/dist/dev/test/components/prose.test.js +42 -0
  240. package/dist/dev/test/components/prose.test.js.map +1 -0
  241. package/dist/dev/test/components/reference-or-content.test.js +246 -0
  242. package/dist/dev/test/components/reference-or-content.test.js.map +1 -0
  243. package/dist/dev/test/components/source-file.test.js +271 -0
  244. package/dist/dev/test/components/source-file.test.js.map +1 -0
  245. package/dist/dev/test/components/template-file.test.js +200 -0
  246. package/dist/dev/test/components/template-file.test.js.map +1 -0
  247. package/dist/dev/test/components/update-file.test.js +210 -0
  248. package/dist/dev/test/components/update-file.test.js.map +1 -0
  249. package/dist/dev/test/components/wrap.test.js +48 -0
  250. package/dist/dev/test/components/wrap.test.js.map +1 -0
  251. package/dist/dev/test/control-flow/for.test.js +318 -0
  252. package/dist/dev/test/control-flow/for.test.js.map +1 -0
  253. package/dist/dev/test/control-flow/match.test.js +112 -0
  254. package/dist/dev/test/control-flow/match.test.js.map +1 -0
  255. package/dist/dev/test/control-flow/show.test.js +38 -0
  256. package/dist/dev/test/control-flow/show.test.js.map +1 -0
  257. package/dist/dev/test/lazy-isempty.test.js +121 -0
  258. package/dist/dev/test/lazy-isempty.test.js.map +1 -0
  259. package/dist/dev/test/name-policy.test.js +28 -0
  260. package/dist/dev/test/name-policy.test.js.map +1 -0
  261. package/dist/dev/test/props-with-defaults.test.js +94 -0
  262. package/dist/dev/test/props-with-defaults.test.js.map +1 -0
  263. package/dist/dev/test/reactive-union-set-disposers.test.js +98 -0
  264. package/dist/dev/test/reactive-union-set-disposers.test.js.map +1 -0
  265. package/dist/dev/test/reactive-union-set.test.js +171 -0
  266. package/dist/dev/test/reactive-union-set.test.js.map +1 -0
  267. package/dist/dev/test/reactivity/circular-reactives.test.js +62 -0
  268. package/dist/dev/test/reactivity/circular-reactives.test.js.map +1 -0
  269. package/dist/dev/test/reactivity/cleanup.test.js +96 -0
  270. package/dist/dev/test/reactivity/cleanup.test.js.map +1 -0
  271. package/dist/dev/test/reactivity/memo.test.js +17 -0
  272. package/dist/dev/test/reactivity/memo.test.js.map +1 -0
  273. package/dist/dev/test/reactivity/ref-rendering.test.js +38 -0
  274. package/dist/dev/test/reactivity/ref-rendering.test.js.map +1 -0
  275. package/dist/dev/test/reactivity/shallow-reactive.test.js +52 -0
  276. package/dist/dev/test/reactivity/shallow-reactive.test.js.map +1 -0
  277. package/dist/dev/test/reactivity/test.test.js +74 -0
  278. package/dist/dev/test/reactivity/test.test.js.map +1 -0
  279. package/dist/dev/test/reactivity/untrack.test.js +27 -0
  280. package/dist/dev/test/reactivity/untrack.test.js.map +1 -0
  281. package/dist/dev/test/refkey.test.js +36 -0
  282. package/dist/dev/test/refkey.test.js.map +1 -0
  283. package/dist/dev/test/rendering/basic.test.js +194 -0
  284. package/dist/dev/test/rendering/basic.test.js.map +1 -0
  285. package/dist/dev/test/rendering/code.test.js +64 -0
  286. package/dist/dev/test/rendering/code.test.js.map +1 -0
  287. package/dist/dev/test/rendering/formatting.test.js +797 -0
  288. package/dist/dev/test/rendering/formatting.test.js.map +1 -0
  289. package/dist/dev/test/rendering/indent.test.js +183 -0
  290. package/dist/dev/test/rendering/indent.test.js.map +1 -0
  291. package/dist/dev/test/rendering/memoization.test.js +37 -0
  292. package/dist/dev/test/rendering/memoization.test.js.map +1 -0
  293. package/dist/dev/test/rendering/print-render-stack.test.js +287 -0
  294. package/dist/dev/test/rendering/print-render-stack.test.js.map +1 -0
  295. package/dist/dev/test/rendering/refkeys.test.js +37 -0
  296. package/dist/dev/test/rendering/refkeys.test.js.map +1 -0
  297. package/dist/dev/test/scheduler-extended.test.js +96 -0
  298. package/dist/dev/test/scheduler-extended.test.js.map +1 -0
  299. package/dist/dev/test/scheduler.test.js +46 -0
  300. package/dist/dev/test/scheduler.test.js.map +1 -0
  301. package/dist/dev/test/split-props.test.js +78 -0
  302. package/dist/dev/test/split-props.test.js.map +1 -0
  303. package/dist/dev/test/stc.test.js +35 -0
  304. package/dist/dev/test/stc.test.js.map +1 -0
  305. package/dist/dev/test/symbols/output-scope.test.js +180 -0
  306. package/dist/dev/test/symbols/output-scope.test.js.map +1 -0
  307. package/dist/dev/test/symbols/output-symbol.test.js +202 -0
  308. package/dist/dev/test/symbols/output-symbol.test.js.map +1 -0
  309. package/dist/dev/test/symbols/resolution.test.js +487 -0
  310. package/dist/dev/test/symbols/resolution.test.js.map +1 -0
  311. package/dist/dev/test/symbols/symbol-table.test.js +15 -0
  312. package/dist/dev/test/symbols/symbol-table.test.js.map +1 -0
  313. package/dist/dev/test/symbols/utils.js +26 -0
  314. package/dist/dev/test/symbols/utils.js.map +1 -0
  315. package/dist/dev/test/utils.test.js +317 -0
  316. package/dist/dev/test/utils.test.js.map +1 -0
  317. package/dist/dev/testing/create-test-wrapper.js +76 -0
  318. package/dist/dev/testing/create-test-wrapper.js.map +1 -0
  319. package/dist/dev/testing/devtools-utils.js +162 -0
  320. package/dist/dev/testing/devtools-utils.js.map +1 -0
  321. package/dist/dev/testing/extend-expect.js +167 -0
  322. package/dist/dev/testing/extend-expect.js.map +1 -0
  323. package/dist/dev/testing/extend-expect.test.js +234 -0
  324. package/dist/dev/testing/extend-expect.test.js.map +1 -0
  325. package/dist/dev/testing/index.js +4 -0
  326. package/dist/dev/testing/index.js.map +1 -0
  327. package/dist/dev/testing/render.js +23 -0
  328. package/dist/dev/testing/render.js.map +1 -0
  329. package/dist/dev/testing/vitest.d.js +2 -0
  330. package/dist/dev/testing/vitest.d.js.map +1 -0
  331. package/dist/src/debug/cli.d.ts.map +1 -1
  332. package/dist/src/debug/cli.js +3 -2
  333. package/dist/src/debug/cli.js.map +1 -1
  334. package/dist/src/debug/effects.d.ts.map +1 -1
  335. package/dist/src/debug/effects.js +2 -67
  336. package/dist/src/debug/effects.js.map +1 -1
  337. package/dist/src/debug/render.d.ts.map +1 -1
  338. package/dist/src/debug/render.js +15 -8
  339. package/dist/src/debug/render.js.map +1 -1
  340. package/dist/src/debug/source-map.browser.d.ts +16 -0
  341. package/dist/src/debug/source-map.browser.d.ts.map +1 -0
  342. package/dist/src/debug/source-map.browser.js +24 -0
  343. package/dist/src/debug/source-map.browser.js.map +1 -0
  344. package/dist/src/debug/source-map.d.ts +22 -0
  345. package/dist/src/debug/source-map.d.ts.map +1 -0
  346. package/dist/src/debug/source-map.js +111 -0
  347. package/dist/src/debug/source-map.js.map +1 -0
  348. package/dist/src/debug/trace.d.ts.map +1 -1
  349. package/dist/src/debug/trace.js +19 -5
  350. package/dist/src/debug/trace.js.map +1 -1
  351. package/dist/src/host/node-host.browser.d.ts +11 -0
  352. package/dist/src/host/node-host.browser.d.ts.map +1 -0
  353. package/dist/src/host/node-host.browser.js +21 -0
  354. package/dist/src/host/node-host.browser.js.map +1 -0
  355. package/dist/src/host/node-host.d.ts +11 -0
  356. package/dist/src/host/node-host.d.ts.map +1 -0
  357. package/dist/src/host/node-host.js +20 -0
  358. package/dist/src/host/node-host.js.map +1 -0
  359. package/dist/src/render-stack.d.ts.map +1 -1
  360. package/dist/src/render-stack.js +4 -3
  361. package/dist/src/render-stack.js.map +1 -1
  362. package/dist/src/write-output.d.ts.map +1 -1
  363. package/dist/src/write-output.js +6 -5
  364. package/dist/src/write-output.js.map +1 -1
  365. package/dist/test/browser-build.test.js +66 -67
  366. package/dist/test/browser-build.test.js.map +1 -1
  367. package/dist/tsconfig.tsbuildinfo +1 -1
  368. package/package.json +13 -3
  369. package/src/debug/cli.ts +3 -2
  370. package/src/debug/effects.ts +2 -82
  371. package/src/debug/render.ts +25 -13
  372. package/src/debug/source-map.browser.ts +30 -0
  373. package/src/debug/source-map.ts +135 -0
  374. package/src/debug/trace.ts +22 -5
  375. package/src/host/node-host.browser.ts +23 -0
  376. package/src/host/node-host.ts +22 -0
  377. package/src/render-stack.ts +4 -3
  378. package/src/write-output.ts +6 -5
  379. package/test/browser-build.test.ts +71 -78
  380. package/vitest.config.ts +8 -0
@@ -0,0 +1,836 @@
1
+ import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
2
+ /**
3
+ * Tests that verify the WebSocket message format produced by the server
4
+ * matches what the devtools frontend expects. Each test renders something,
5
+ * collects the raw JSON messages over the WebSocket, and checks the shape.
6
+ *
7
+ * Tests subscribe to specific channels to validate the subscription mechanism
8
+ * and avoid filtering noise from unrelated channels.
9
+ */
10
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
11
+ import WebSocket from "ws";
12
+ import { createMessageCollector } from "../../testing/devtools-utils.js";
13
+ import { Declaration } from "../components/Declaration.js";
14
+ import { For } from "../components/For.js";
15
+ import { Output } from "../components/Output.js";
16
+ import { Scope } from "../components/Scope.js";
17
+ import { SourceFile } from "../components/SourceFile.js";
18
+ import { enableDevtools, resetDevtoolsServerForTests } from "../devtools/devtools-server.js";
19
+ import { effect, ref } from "../reactivity.js";
20
+ import { renderAsync } from "../render.js";
21
+ import { flushJobsAsync } from "../scheduler.js";
22
+ import { debug } from "./index.js";
23
+ let socket;
24
+ let port;
25
+ beforeEach(async () => {
26
+ debug.effect.reset();
27
+ const server = await enableDevtools({
28
+ port: 0
29
+ });
30
+ port = server.port;
31
+ socket = new WebSocket(`ws://127.0.0.1:${port}`);
32
+ await new Promise((resolve, reject) => {
33
+ socket?.once("open", resolve);
34
+ socket?.once("error", reject);
35
+ });
36
+ });
37
+ afterEach(async () => {
38
+ if (socket) {
39
+ socket.close();
40
+ socket = undefined;
41
+ }
42
+ await resetDevtoolsServerForTests();
43
+ debug.effect.reset();
44
+ });
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Subscriptions
48
+ // ---------------------------------------------------------------------------
49
+
50
+ describe("subscriptions", () => {
51
+ it("only receives messages for subscribed channels", async () => {
52
+ // Subscribe to render only — should NOT see effects, refs, etc.
53
+ const collector = await createMessageCollector(socket, ["render"]);
54
+ await renderAsync(_$createComponent(Output, {
55
+ children: "hello"
56
+ }, {
57
+ fileName: import.meta.url,
58
+ lineNumber: 60,
59
+ columnNumber: 23
60
+ }));
61
+ const messages = await collector.waitForRender();
62
+ collector.stop();
63
+ const types = new Set(messages.map(m => m.type));
64
+ // Should have render messages
65
+ expect(types.has("render:node_added")).toBe(true);
66
+ // Should NOT have effect/ref/symbol/scope/edge messages
67
+ expect(types.has("effect:added")).toBe(false);
68
+ expect(types.has("ref:added")).toBe(false);
69
+ expect(types.has("symbol:added")).toBe(false);
70
+ expect(types.has("scope:added")).toBe(false);
71
+ });
72
+ it("receives messages from multiple subscribed channels", async () => {
73
+ const collector = await createMessageCollector(socket, ["render", "effects", "refs"]);
74
+ await renderAsync(_$createComponent(Output, {
75
+ children: "hello"
76
+ }, {
77
+ fileName: import.meta.url,
78
+ lineNumber: 80,
79
+ columnNumber: 23
80
+ }));
81
+ const messages = await collector.waitForRender();
82
+ collector.stop();
83
+ const types = new Set(messages.map(m => m.type));
84
+ expect(types.has("render:node_added")).toBe(true);
85
+ expect(types.has("effect:added")).toBe(true);
86
+ expect(types.has("ref:added")).toBe(true);
87
+ // Not subscribed to these
88
+ expect(types.has("symbol:added")).toBe(false);
89
+ expect(types.has("scope:added")).toBe(false);
90
+ });
91
+ it("lifecycle signals are received regardless of subscription", async () => {
92
+ // Subscribe to nothing useful — lifecycle signals bypass subscriptions
93
+ const collector = await createMessageCollector(socket, ["diagnostics"]);
94
+ await renderAsync(_$createComponent(Output, {}, {
95
+ fileName: import.meta.url,
96
+ lineNumber: 96,
97
+ columnNumber: 23
98
+ }));
99
+ const messages = await collector.waitForRender();
100
+ collector.stop();
101
+ expect(messages.some(m => m.type === "render:complete")).toBe(true);
102
+ });
103
+ });
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // render channel
107
+ // ---------------------------------------------------------------------------
108
+
109
+ describe("render:node_added", () => {
110
+ it("root node has null parent_id and kind 'root'", async () => {
111
+ const collector = await createMessageCollector(socket, ["render"]);
112
+ await renderAsync(_$createComponent(Output, {}, {
113
+ fileName: import.meta.url,
114
+ lineNumber: 111,
115
+ columnNumber: 23
116
+ }));
117
+ const messages = await collector.waitForRender();
118
+ collector.stop();
119
+ const root = messages.find(m => m.type === "render:node_added" && m.kind === "root");
120
+ expect(root).toMatchObject({
121
+ type: "render:node_added",
122
+ id: expect.any(Number),
123
+ parent_id: null,
124
+ kind: "root",
125
+ seq: expect.any(Number)
126
+ });
127
+ });
128
+ it("component node has numeric id, parent_id, and name", async () => {
129
+ const collector = await createMessageCollector(socket, ["render"]);
130
+ await renderAsync(_$createComponent(Output, {}, {
131
+ fileName: import.meta.url,
132
+ lineNumber: 129,
133
+ columnNumber: 23
134
+ }));
135
+ const messages = await collector.waitForRender();
136
+ collector.stop();
137
+ const output = messages.find(m => m.type === "render:node_added" && m.name === "Output");
138
+ expect(output).toMatchObject({
139
+ type: "render:node_added",
140
+ id: expect.any(Number),
141
+ parent_id: expect.any(Number),
142
+ kind: "component",
143
+ name: "Output"
144
+ });
145
+ });
146
+ it("text node has string value", async () => {
147
+ const collector = await createMessageCollector(socket, ["render"]);
148
+ await renderAsync(_$createComponent(Output, {
149
+ children: "hello"
150
+ }, {
151
+ fileName: import.meta.url,
152
+ lineNumber: 147,
153
+ columnNumber: 23
154
+ }));
155
+ const messages = await collector.waitForRender();
156
+ collector.stop();
157
+ const text = messages.find(m => m.type === "render:node_added" && m.kind === "text");
158
+ expect(text).toBeDefined();
159
+ expect(text.value).toBe("hello");
160
+ expect(text.parent_id).toEqual(expect.any(Number));
161
+ });
162
+ it("non-text node has null value (not undefined)", async () => {
163
+ const collector = await createMessageCollector(socket, ["render"]);
164
+ await renderAsync(_$createComponent(Output, {
165
+ children: "hello"
166
+ }, {
167
+ fileName: import.meta.url,
168
+ lineNumber: 161,
169
+ columnNumber: 23
170
+ }));
171
+ const messages = await collector.waitForRender();
172
+ collector.stop();
173
+ const component = messages.find(m => m.type === "render:node_added" && m.kind === "component");
174
+ expect(component.value).toBeNull();
175
+ });
176
+ });
177
+ describe("render:node_removed", () => {
178
+ it("is emitted when a reactive component removes children", async () => {
179
+ const show = ref(true);
180
+ const collector = await createMessageCollector(socket, ["render"]);
181
+ function Conditional() {
182
+ return () => show.value ? "visible" : "";
183
+ }
184
+ await renderAsync(_$createComponent(Output, {
185
+ get children() {
186
+ return _$createComponent(Conditional, {}, {
187
+ fileName: import.meta.url,
188
+ lineNumber: 183,
189
+ columnNumber: 9
190
+ });
191
+ }
192
+ }, {
193
+ fileName: import.meta.url,
194
+ lineNumber: 182,
195
+ columnNumber: 7
196
+ }));
197
+ const renderMessages = await collector.waitForRender();
198
+
199
+ // Verify the text node "visible" was added
200
+ const textAdded = renderMessages.find(m => m.type === "render:node_added" && m.value === "visible");
201
+ expect(textAdded).toBeDefined();
202
+
203
+ // Toggle visibility to trigger removal
204
+ show.value = false;
205
+ await flushJobsAsync();
206
+ const flushMessages = await collector.waitForFlush();
207
+ collector.stop();
208
+ const removed = flushMessages.filter(m => m.type === "render:node_removed");
209
+ expect(removed.length).toBeGreaterThan(0);
210
+ expect(removed[0]).toMatchObject({
211
+ type: "render:node_removed",
212
+ id: expect.any(Number)
213
+ });
214
+ });
215
+ });
216
+ describe("render:node_updated", () => {
217
+ it("emits node_added during reactive list growth", async () => {
218
+ const items = ref(["a"]);
219
+ const collector = await createMessageCollector(socket, ["render"]);
220
+ await renderAsync(_$createComponent(Output, {
221
+ get children() {
222
+ return _$createComponent(For, {
223
+ each: items,
224
+ children: item => [item]
225
+ }, {
226
+ fileName: import.meta.url,
227
+ lineNumber: 217,
228
+ columnNumber: 9
229
+ });
230
+ }
231
+ }, {
232
+ fileName: import.meta.url,
233
+ lineNumber: 216,
234
+ columnNumber: 7
235
+ }));
236
+ await collector.waitForRender();
237
+
238
+ // Add an item to trigger reactive update
239
+ items.value = [...items.value, "b"];
240
+ await flushJobsAsync();
241
+ const flushMessages = await collector.waitForFlush();
242
+ collector.stop();
243
+
244
+ // Reactive updates produce new node_added messages for new content
245
+ const added = flushMessages.filter(m => m.type === "render:node_added");
246
+ expect(added.length).toBeGreaterThan(0);
247
+ // Should contain the new "b" text node
248
+ const bNode = added.find(m => m.value === "b");
249
+ expect(bNode).toBeDefined();
250
+ });
251
+ });
252
+ describe("render:reset", () => {
253
+ it("is sent before node_added messages", async () => {
254
+ const collector = await createMessageCollector(socket, ["render"]);
255
+ await renderAsync(_$createComponent(Output, {}, {
256
+ fileName: import.meta.url,
257
+ lineNumber: 240,
258
+ columnNumber: 23
259
+ }));
260
+ const messages = await collector.waitForRender();
261
+ collector.stop();
262
+ const renderMessages = messages.filter(m => m.type === "render:reset" || m.type === "render:node_added" || m.type === "render:complete");
263
+ expect(renderMessages[0]).toMatchObject({
264
+ type: "render:reset"
265
+ });
266
+ });
267
+ });
268
+ describe("render:complete", () => {
269
+ it("is sent after all node_added messages", async () => {
270
+ const collector = await createMessageCollector(socket, ["render"]);
271
+ await renderAsync(_$createComponent(Output, {}, {
272
+ fileName: import.meta.url,
273
+ lineNumber: 257,
274
+ columnNumber: 23
275
+ }));
276
+ const messages = await collector.waitForRender();
277
+ collector.stop();
278
+ const renderMessages = messages.filter(m => m.type === "render:reset" || m.type === "render:node_added" || m.type === "render:complete");
279
+ const last = renderMessages[renderMessages.length - 1];
280
+ expect(last).toMatchObject({
281
+ type: "render:complete"
282
+ });
283
+ });
284
+ });
285
+
286
+ // ---------------------------------------------------------------------------
287
+ // effects channel
288
+ // ---------------------------------------------------------------------------
289
+
290
+ describe("effect:added", () => {
291
+ it("has id, effect_type, and seq", async () => {
292
+ const collector = await createMessageCollector(socket, ["effects"]);
293
+ await renderAsync(_$createComponent(Output, {
294
+ children: "hello"
295
+ }, {
296
+ fileName: import.meta.url,
297
+ lineNumber: 279,
298
+ columnNumber: 23
299
+ }));
300
+ const messages = await collector.waitForRender();
301
+ collector.stop();
302
+ const effects = messages.filter(m => m.type === "effect:added");
303
+ expect(effects.length).toBeGreaterThan(0);
304
+ const e = effects[0];
305
+ expect(e).toMatchObject({
306
+ type: "effect:added",
307
+ id: expect.any(Number),
308
+ seq: expect.any(Number)
309
+ });
310
+ expect("effect_type" in e).toBe(true);
311
+ });
312
+ });
313
+ describe("effect:updated", () => {
314
+ it("is emitted when an effect's component is set", async () => {
315
+ const collector = await createMessageCollector(socket, ["effects"]);
316
+ await renderAsync(_$createComponent(Output, {
317
+ children: "hello"
318
+ }, {
319
+ fileName: import.meta.url,
320
+ lineNumber: 299,
321
+ columnNumber: 23
322
+ }));
323
+ const messages = await collector.waitForRender();
324
+ collector.stop();
325
+ const updated = messages.filter(m => m.type === "effect:updated");
326
+ // Effects get updated with component info during render
327
+ if (updated.length > 0) {
328
+ expect(updated[0]).toMatchObject({
329
+ type: "effect:updated"
330
+ });
331
+ }
332
+ });
333
+ });
334
+
335
+ // ---------------------------------------------------------------------------
336
+ // refs channel
337
+ // ---------------------------------------------------------------------------
338
+
339
+ describe("ref:added", () => {
340
+ it("has id, kind, and seq", async () => {
341
+ const collector = await createMessageCollector(socket, ["refs"]);
342
+ await renderAsync(_$createComponent(Output, {
343
+ children: "hello"
344
+ }, {
345
+ fileName: import.meta.url,
346
+ lineNumber: 320,
347
+ columnNumber: 23
348
+ }));
349
+ const messages = await collector.waitForRender();
350
+ collector.stop();
351
+ const refs = messages.filter(m => m.type === "ref:added");
352
+ expect(refs.length).toBeGreaterThan(0);
353
+ const r = refs[0];
354
+ expect(r).toMatchObject({
355
+ type: "ref:added",
356
+ id: expect.any(Number),
357
+ seq: expect.any(Number)
358
+ });
359
+ // kind is nullable
360
+ expect("kind" in r).toBe(true);
361
+ });
362
+ });
363
+
364
+ // ---------------------------------------------------------------------------
365
+ // edges channel
366
+ // ---------------------------------------------------------------------------
367
+
368
+ describe("edge:track and edge:trigger", () => {
369
+ it("emits edge messages with effect_id and ref_id", async () => {
370
+ const collector = await createMessageCollector(socket, ["edges"]);
371
+ await renderAsync(_$createComponent(Output, {
372
+ children: "hello"
373
+ }, {
374
+ fileName: import.meta.url,
375
+ lineNumber: 345,
376
+ columnNumber: 23
377
+ }));
378
+ const messages = await collector.waitForRender();
379
+ collector.stop();
380
+ const edges = messages.filter(m => m.type === "edge:track" || m.type === "edge:trigger");
381
+ expect(edges.length).toBeGreaterThan(0);
382
+ const edge = edges[0];
383
+ expect(edge).toMatchObject({
384
+ seq: expect.any(Number),
385
+ effect_id: expect.any(Number)
386
+ });
387
+ // ref_id is present (may be null for some edge types)
388
+ expect("ref_id" in edge).toBe(true);
389
+ });
390
+ });
391
+
392
+ // ---------------------------------------------------------------------------
393
+ // symbols channel
394
+ // ---------------------------------------------------------------------------
395
+
396
+ describe("symbol:added", () => {
397
+ it("has id, name, boolean flags, and seq", async () => {
398
+ const collector = await createMessageCollector(socket, ["symbols"]);
399
+ await renderAsync(_$createComponent(Output, {
400
+ get children() {
401
+ return _$createComponent(Scope, {
402
+ name: "myScope",
403
+ get children() {
404
+ return _$createComponent(Declaration, {
405
+ name: "Foo",
406
+ children: "foo content"
407
+ }, {
408
+ fileName: import.meta.url,
409
+ lineNumber: 374,
410
+ columnNumber: 11
411
+ });
412
+ }
413
+ }, {
414
+ fileName: import.meta.url,
415
+ lineNumber: 373,
416
+ columnNumber: 9
417
+ });
418
+ }
419
+ }, {
420
+ fileName: import.meta.url,
421
+ lineNumber: 372,
422
+ columnNumber: 7
423
+ }));
424
+ const messages = await collector.waitForRender();
425
+ collector.stop();
426
+ const symbols = messages.filter(m => m.type === "symbol:added");
427
+ expect(symbols.length).toBeGreaterThan(0);
428
+ const sym = symbols.find(m => m.name === "Foo");
429
+ expect(sym).toBeDefined();
430
+ expect(sym).toMatchObject({
431
+ type: "symbol:added",
432
+ id: expect.any(Number),
433
+ name: "Foo",
434
+ seq: expect.any(Number)
435
+ });
436
+ // Boolean flags are 0/1 integers
437
+ expect([0, 1]).toContain(sym.is_member);
438
+ expect([0, 1]).toContain(sym.is_transient);
439
+ expect([0, 1]).toContain(sym.is_alias);
440
+ // Nullable fields exist as keys
441
+ expect("scope_id" in sym).toBe(true);
442
+ expect("owner_symbol_id" in sym).toBe(true);
443
+ expect("metadata" in sym).toBe(true);
444
+ });
445
+ });
446
+
447
+ // ---------------------------------------------------------------------------
448
+ // scopes channel
449
+ // ---------------------------------------------------------------------------
450
+
451
+ describe("scope:added", () => {
452
+ it("has id, name, is_member_scope, and seq", async () => {
453
+ const collector = await createMessageCollector(socket, ["scopes"]);
454
+ await renderAsync(_$createComponent(Output, {
455
+ get children() {
456
+ return _$createComponent(Scope, {
457
+ name: "TestScope",
458
+ children: "content"
459
+ }, {
460
+ fileName: import.meta.url,
461
+ lineNumber: 412,
462
+ columnNumber: 9
463
+ });
464
+ }
465
+ }, {
466
+ fileName: import.meta.url,
467
+ lineNumber: 411,
468
+ columnNumber: 7
469
+ }));
470
+ const messages = await collector.waitForRender();
471
+ collector.stop();
472
+ const scopes = messages.filter(m => m.type === "scope:added");
473
+ expect(scopes.length).toBeGreaterThan(0);
474
+ const scope = scopes.find(s => s.name === "TestScope");
475
+ expect(scope).toBeDefined();
476
+ expect(scope).toMatchObject({
477
+ type: "scope:added",
478
+ id: expect.any(Number),
479
+ name: "TestScope",
480
+ seq: expect.any(Number)
481
+ });
482
+ expect([0, 1]).toContain(scope.is_member_scope);
483
+ expect("parent_id" in scope).toBe(true);
484
+ expect("metadata" in scope).toBe(true);
485
+ });
486
+ });
487
+
488
+ // ---------------------------------------------------------------------------
489
+ // files channel
490
+ // ---------------------------------------------------------------------------
491
+
492
+ describe("file:added", () => {
493
+ it("has path, filetype, and seq", async () => {
494
+ const collector = await createMessageCollector(socket, ["files"]);
495
+ await renderAsync(_$createComponent(Output, {
496
+ get children() {
497
+ return _$createComponent(SourceFile, {
498
+ path: "test.ts",
499
+ filetype: "typescript",
500
+ children: "content"
501
+ }, {
502
+ fileName: import.meta.url,
503
+ lineNumber: 444,
504
+ columnNumber: 9
505
+ });
506
+ }
507
+ }, {
508
+ fileName: import.meta.url,
509
+ lineNumber: 443,
510
+ columnNumber: 7
511
+ }));
512
+ const messages = await collector.waitForRender();
513
+ collector.stop();
514
+ const files = messages.filter(m => m.type === "file:added");
515
+ expect(files.length).toBeGreaterThan(0);
516
+ const file = files.find(f => f.path.endsWith("test.ts"));
517
+ expect(file).toBeDefined();
518
+ expect(file).toMatchObject({
519
+ type: "file:added",
520
+ path: expect.stringContaining("test.ts"),
521
+ filetype: "typescript",
522
+ seq: expect.any(Number)
523
+ });
524
+ });
525
+ });
526
+
527
+ // ---------------------------------------------------------------------------
528
+ // directories channel
529
+ // ---------------------------------------------------------------------------
530
+
531
+ describe("directory:added", () => {
532
+ it("has path and seq", async () => {
533
+ const collector = await createMessageCollector(socket, ["directories"]);
534
+ await renderAsync(_$createComponent(Output, {
535
+ children: "hello"
536
+ }, {
537
+ fileName: import.meta.url,
538
+ lineNumber: 473,
539
+ columnNumber: 23
540
+ }));
541
+ const messages = await collector.waitForRender();
542
+ collector.stop();
543
+ const dirs = messages.filter(m => m.type === "directory:added");
544
+ expect(dirs.length).toBeGreaterThan(0);
545
+ expect(dirs[0]).toMatchObject({
546
+ type: "directory:added",
547
+ path: expect.any(String),
548
+ seq: expect.any(Number)
549
+ });
550
+ });
551
+ });
552
+
553
+ // ---------------------------------------------------------------------------
554
+ // scheduler channel
555
+ // ---------------------------------------------------------------------------
556
+
557
+ describe("scheduler:job", () => {
558
+ it("is emitted during synchronous flush", async () => {
559
+ // Scheduler events are only traced during synchronous flushJobs(),
560
+ // not flushJobsAsync(). Subscribe to scheduler + render to see them.
561
+ const collector = await createMessageCollector(socket, ["scheduler", "render"]);
562
+ // A simple render triggers flushJobs internally
563
+ await renderAsync(_$createComponent(Output, {
564
+ children: "hello"
565
+ }, {
566
+ fileName: import.meta.url,
567
+ lineNumber: 501,
568
+ columnNumber: 23
569
+ }));
570
+ const messages = await collector.waitForRender();
571
+ collector.stop();
572
+ const jobs = messages.filter(m => m.type === "scheduler:job");
573
+ // Scheduler events occur during the synchronous flush inside renderAsync
574
+ if (jobs.length > 0) {
575
+ expect(jobs[0]).toMatchObject({
576
+ type: "scheduler:job",
577
+ seq: expect.any(Number)
578
+ });
579
+ expect("event" in jobs[0] || "jobs_run" in jobs[0]).toBe(true);
580
+ }
581
+ });
582
+ });
583
+
584
+ // ---------------------------------------------------------------------------
585
+ // lifecycle channel
586
+ // ---------------------------------------------------------------------------
587
+
588
+ describe("effect:lifecycle", () => {
589
+ it("is emitted for effect lifecycle events", async () => {
590
+ const collector = await createMessageCollector(socket, ["lifecycle"]);
591
+ const r = ref(0);
592
+ effect(() => {
593
+ void r.value;
594
+ });
595
+ await renderAsync(_$createComponent(Output, {
596
+ children: "hello"
597
+ }, {
598
+ fileName: import.meta.url,
599
+ lineNumber: 529,
600
+ columnNumber: 23
601
+ }));
602
+ const messages = await collector.waitForRender();
603
+
604
+ // Trigger a lifecycle event
605
+ r.value = 1;
606
+ await flushJobsAsync();
607
+ const flushMessages = await collector.waitForFlush();
608
+ collector.stop();
609
+ const all = [...messages, ...flushMessages];
610
+ const lifecycle = all.filter(m => m.type === "effect:lifecycle");
611
+ if (lifecycle.length > 0) {
612
+ expect(lifecycle[0]).toMatchObject({
613
+ type: "effect:lifecycle",
614
+ seq: expect.any(Number)
615
+ });
616
+ expect("effect_id" in lifecycle[0]).toBe(true);
617
+ expect("event" in lifecycle[0]).toBe(true);
618
+ }
619
+ });
620
+ });
621
+
622
+ // ---------------------------------------------------------------------------
623
+ // diagnostics channel
624
+ // ---------------------------------------------------------------------------
625
+
626
+ describe("diagnostics:report", () => {
627
+ it("has message, severity, and seq", async () => {
628
+ const {
629
+ insertDiagnostic
630
+ } = await import("./trace-writer.js");
631
+ const collector = await createMessageCollector(socket, ["diagnostics"]);
632
+
633
+ // Insert a diagnostic directly through the trace writer
634
+ insertDiagnostic("test warning", "warning", undefined, undefined, undefined, undefined);
635
+
636
+ // Render to get a completion signal for the collector
637
+ await renderAsync(_$createComponent(Output, {}, {
638
+ fileName: import.meta.url,
639
+ lineNumber: 571,
640
+ columnNumber: 23
641
+ }));
642
+ const messages = await collector.waitForRender();
643
+ collector.stop();
644
+ const diags = messages.filter(m => m.type === "diagnostics:report");
645
+ expect(diags.length).toBeGreaterThan(0);
646
+ expect(diags[0]).toMatchObject({
647
+ type: "diagnostics:report",
648
+ message: "test warning",
649
+ severity: "warning",
650
+ seq: expect.any(Number)
651
+ });
652
+ // Nullable source location fields exist
653
+ expect("source_file" in diags[0]).toBe(true);
654
+ expect("source_line" in diags[0]).toBe(true);
655
+ });
656
+ });
657
+
658
+ // ---------------------------------------------------------------------------
659
+ // errors channel
660
+ // ---------------------------------------------------------------------------
661
+
662
+ describe("render:error", () => {
663
+ it("has name, message, and seq", async () => {
664
+ const {
665
+ insertRenderError
666
+ } = await import("./trace-writer.js");
667
+ const collector = await createMessageCollector(socket, ["errors"]);
668
+
669
+ // Insert an error directly through the trace writer
670
+ insertRenderError("Error", "kaboom", "Error: kaboom\n at ...", undefined);
671
+
672
+ // Render to get a completion signal
673
+ await renderAsync(_$createComponent(Output, {}, {
674
+ fileName: import.meta.url,
675
+ lineNumber: 603,
676
+ columnNumber: 23
677
+ }));
678
+ const messages = await collector.waitForRender();
679
+ collector.stop();
680
+ const errors = messages.filter(m => m.type === "render:error");
681
+ expect(errors.length).toBeGreaterThan(0);
682
+ expect(errors[0]).toMatchObject({
683
+ type: "render:error",
684
+ name: "Error",
685
+ message: "kaboom",
686
+ seq: expect.any(Number)
687
+ });
688
+ expect("stack" in errors[0]).toBe(true);
689
+ expect("component_stack" in errors[0]).toBe(true);
690
+ });
691
+ });
692
+
693
+ // ---------------------------------------------------------------------------
694
+ // flushJobs:complete lifecycle signal
695
+ // ---------------------------------------------------------------------------
696
+
697
+ describe("flushJobs:complete", () => {
698
+ it("is emitted after a reactive flush", async () => {
699
+ const items = ref(["a"]);
700
+ const collector = await createMessageCollector(socket, ["render"]);
701
+ await renderAsync(_$createComponent(Output, {
702
+ get children() {
703
+ return _$createComponent(For, {
704
+ each: items,
705
+ children: item => [item]
706
+ }, {
707
+ fileName: import.meta.url,
708
+ lineNumber: 632,
709
+ columnNumber: 9
710
+ });
711
+ }
712
+ }, {
713
+ fileName: import.meta.url,
714
+ lineNumber: 631,
715
+ columnNumber: 7
716
+ }));
717
+ await collector.waitForRender();
718
+ items.value = ["a", "b"];
719
+ await flushJobsAsync();
720
+ const flushMessages = await collector.waitForFlush();
721
+ collector.stop();
722
+
723
+ // waitForFlush resolves on flushJobs:complete, so if we get here it was received
724
+ expect(flushMessages.some(m => m.type === "flushJobs:complete")).toBe(true);
725
+ });
726
+ });
727
+
728
+ // ---------------------------------------------------------------------------
729
+ // debugger:info (connection handshake)
730
+ // ---------------------------------------------------------------------------
731
+
732
+ describe("debugger:info", () => {
733
+ it("is sent on connection before any other messages", async () => {
734
+ // Close the existing socket so the server accepts a new one
735
+ socket.close();
736
+ await new Promise(resolve => setTimeout(resolve, 100));
737
+ const freshSocket = new WebSocket(`ws://127.0.0.1:${port}`);
738
+ const firstMessage = new Promise((resolve, reject) => {
739
+ const timeout = setTimeout(() => reject(new Error("No debugger:info received")), 3000);
740
+ freshSocket.once("message", data => {
741
+ clearTimeout(timeout);
742
+ resolve(JSON.parse(String(data)));
743
+ });
744
+ });
745
+ await new Promise((resolve, reject) => {
746
+ freshSocket.once("open", resolve);
747
+ freshSocket.once("error", reject);
748
+ });
749
+ const first = await firstMessage;
750
+ freshSocket.close();
751
+ // Replace socket so afterEach doesn't close an already-closed socket
752
+ socket = undefined;
753
+ expect(first).toMatchObject({
754
+ type: "debugger:info",
755
+ version: expect.any(String)
756
+ });
757
+ });
758
+ });
759
+
760
+ // ---------------------------------------------------------------------------
761
+ // null vs undefined contract
762
+ // ---------------------------------------------------------------------------
763
+
764
+ describe("null vs undefined contract", () => {
765
+ it("nullable fields arrive as null (not undefined or missing)", async () => {
766
+ const collector = await createMessageCollector(socket, ["render"]);
767
+ await renderAsync(_$createComponent(Output, {
768
+ children: "hello"
769
+ }, {
770
+ fileName: import.meta.url,
771
+ lineNumber: 695,
772
+ columnNumber: 23
773
+ }));
774
+ const messages = await collector.waitForRender();
775
+ collector.stop();
776
+
777
+ // Root node: parent_id should be explicit null
778
+ const root = messages.find(m => m.type === "render:node_added" && m.kind === "root");
779
+ expect(root).toBeDefined();
780
+ expect(root.parent_id).toBeNull();
781
+ expect("parent_id" in root).toBe(true);
782
+
783
+ // Component node: value should be null, not undefined
784
+ const component = messages.find(m => m.type === "render:node_added" && m.kind === "component");
785
+ expect(component).toBeDefined();
786
+ expect(component.value).toBeNull();
787
+ expect("value" in component).toBe(true);
788
+
789
+ // source_file is nullable
790
+ expect("source_file" in root).toBe(true);
791
+ });
792
+ it("non-null fields have correct types", async () => {
793
+ const collector = await createMessageCollector(socket, ["render"]);
794
+ await renderAsync(_$createComponent(Output, {
795
+ children: "hello"
796
+ }, {
797
+ fileName: import.meta.url,
798
+ lineNumber: 723,
799
+ columnNumber: 23
800
+ }));
801
+ const messages = await collector.waitForRender();
802
+ collector.stop();
803
+ const text = messages.find(m => m.type === "render:node_added" && m.kind === "text");
804
+ expect(text).toBeDefined();
805
+ // value is a string for text nodes
806
+ expect(typeof text.value).toBe("string");
807
+ // id is always a number
808
+ expect(typeof text.id).toBe("number");
809
+ // parent_id is a number for non-root nodes
810
+ expect(typeof text.parent_id).toBe("number");
811
+ });
812
+ });
813
+
814
+ // ---------------------------------------------------------------------------
815
+ // seq ordering
816
+ // ---------------------------------------------------------------------------
817
+
818
+ describe("seq ordering", () => {
819
+ it("messages have monotonically increasing seq", async () => {
820
+ const collector = await createMessageCollector(socket, ["render"]);
821
+ await renderAsync(_$createComponent(Output, {
822
+ children: "hello"
823
+ }, {
824
+ fileName: import.meta.url,
825
+ lineNumber: 748,
826
+ columnNumber: 23
827
+ }));
828
+ const messages = await collector.waitForRender();
829
+ collector.stop();
830
+ const seqs = messages.filter(m => typeof m.seq === "number").map(m => m.seq);
831
+ for (let i = 1; i < seqs.length; i++) {
832
+ expect(seqs[i]).toBeGreaterThan(seqs[i - 1]);
833
+ }
834
+ });
835
+ });
836
+ //# sourceMappingURL=message-format.test.js.map