sygnal 7.0.0 → 7.2.0-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/CHANGELOG.md +632 -0
  2. package/dist/agent-ui.cjs.js +592 -0
  3. package/dist/agent-ui.cjs.js.map +1 -0
  4. package/dist/agent-ui.esm.js +582 -0
  5. package/dist/agent-ui.esm.js.map +1 -0
  6. package/dist/api.cjs.js +63 -0
  7. package/dist/api.cjs.js.map +1 -0
  8. package/dist/api.esm.js +2 -0
  9. package/dist/api.esm.js.map +1 -0
  10. package/dist/astro/index.cjs.js +968 -4
  11. package/dist/astro/index.cjs.js.map +1 -1
  12. package/dist/astro/index.mjs +968 -4
  13. package/dist/astro/index.mjs.map +1 -1
  14. package/dist/astro/server.cjs.js +73 -45
  15. package/dist/astro/server.cjs.js.map +1 -1
  16. package/dist/astro/server.mjs +73 -45
  17. package/dist/astro/server.mjs.map +1 -1
  18. package/dist/canvas.cjs.js +2919 -0
  19. package/dist/canvas.cjs.js.map +1 -0
  20. package/dist/canvas.esm.js +2866 -0
  21. package/dist/canvas.esm.js.map +1 -0
  22. package/dist/devtools.cjs.js +38 -6
  23. package/dist/devtools.cjs.js.map +1 -1
  24. package/dist/devtools.esm.js +38 -6
  25. package/dist/devtools.esm.js.map +1 -1
  26. package/dist/diagnostics.cjs.js +224 -20
  27. package/dist/diagnostics.cjs.js.map +1 -1
  28. package/dist/diagnostics.esm.js +224 -20
  29. package/dist/diagnostics.esm.js.map +1 -1
  30. package/dist/guide/accessibility.md +1 -1
  31. package/dist/guide/ai-compose.md +150 -0
  32. package/dist/guide/behaviors.md +2 -1
  33. package/dist/guide/collaborative-state.md +265 -0
  34. package/dist/guide/element-commands.md +36 -2
  35. package/dist/guide/forms-reference.md +5 -5
  36. package/dist/guide/forms.md +3 -2
  37. package/dist/guide/http.md +28 -1
  38. package/dist/guide/islands.md +240 -0
  39. package/dist/guide/recipes/code-editor.md +1 -1
  40. package/dist/guide/renderers/agent-ui.md +138 -0
  41. package/dist/guide/renderers/custom-targets.md +126 -0
  42. package/dist/guide/renderers/overview.md +120 -0
  43. package/dist/guide/renderers/react-native.md +128 -0
  44. package/dist/guide/renderers/terminal.md +306 -0
  45. package/dist/guide/renderers/three.md +332 -0
  46. package/dist/guide/renderers/write-once.md +137 -0
  47. package/dist/guide/resources.md +2 -0
  48. package/dist/guide/sandboxed-components.md +223 -0
  49. package/dist/guide/timers.md +32 -5
  50. package/dist/guide/typed-apis.md +442 -0
  51. package/dist/guide/ui/accordion.md +11 -0
  52. package/dist/guide/ui/combobox.md +112 -8
  53. package/dist/guide/ui/dialog.md +96 -20
  54. package/dist/guide/ui/menu.md +57 -5
  55. package/dist/guide/ui/overview.md +8 -4
  56. package/dist/guide/ui/popover.md +69 -9
  57. package/dist/guide/ui/select.md +54 -4
  58. package/dist/guide/ui/tabs.md +1 -1
  59. package/dist/guide/ui/tooltip.md +7 -5
  60. package/dist/guide/virtual-collections.md +3 -0
  61. package/dist/guide/widgets.md +90 -1
  62. package/dist/host.cjs.js +2181 -0
  63. package/dist/host.cjs.js.map +1 -0
  64. package/dist/host.esm.js +2146 -0
  65. package/dist/host.esm.js.map +1 -0
  66. package/dist/index.cjs.js +3948 -586
  67. package/dist/index.cjs.js.map +1 -1
  68. package/dist/index.d.ts +584 -44
  69. package/dist/index.esm.js +3918 -586
  70. package/dist/index.esm.js.map +1 -1
  71. package/dist/primitives.cjs.js +143 -0
  72. package/dist/primitives.cjs.js.map +1 -0
  73. package/dist/primitives.esm.js +133 -0
  74. package/dist/primitives.esm.js.map +1 -0
  75. package/dist/react-native.cjs.js +248 -0
  76. package/dist/react-native.cjs.js.map +1 -0
  77. package/dist/react-native.esm.js +244 -0
  78. package/dist/react-native.esm.js.map +1 -0
  79. package/dist/replay.cjs.js +39 -0
  80. package/dist/replay.cjs.js.map +1 -0
  81. package/dist/replay.esm.js +2 -0
  82. package/dist/replay.esm.js.map +1 -0
  83. package/dist/sandbox-build.cjs.js +140 -0
  84. package/dist/sandbox-build.cjs.js.map +1 -0
  85. package/dist/sandbox-build.esm.js +137 -0
  86. package/dist/sandbox-build.esm.js.map +1 -0
  87. package/dist/sandbox-kernel.esm.js +212 -0
  88. package/dist/sandbox-kernel.esm.js.map +1 -0
  89. package/dist/sandbox-worker.cjs.js +857 -0
  90. package/dist/sandbox-worker.cjs.js.map +1 -0
  91. package/dist/sandbox-worker.esm.js +854 -0
  92. package/dist/sandbox-worker.esm.js.map +1 -0
  93. package/dist/sandbox.cjs.js +1425 -0
  94. package/dist/sandbox.cjs.js.map +1 -0
  95. package/dist/sandbox.esm.js +1406 -0
  96. package/dist/sandbox.esm.js.map +1 -0
  97. package/dist/sygnal.min.js +1 -1
  98. package/dist/sygnal.min.js.map +1 -1
  99. package/dist/terminal.cjs.js +1314 -0
  100. package/dist/terminal.cjs.js.map +1 -0
  101. package/dist/terminal.esm.js +1304 -0
  102. package/dist/terminal.esm.js.map +1 -0
  103. package/dist/three.cjs.js +1111 -0
  104. package/dist/three.cjs.js.map +1 -0
  105. package/dist/three.esm.js +1079 -0
  106. package/dist/three.esm.js.map +1 -0
  107. package/dist/ui-combobox.cjs.js +256 -18
  108. package/dist/ui-combobox.cjs.js.map +1 -1
  109. package/dist/ui-combobox.esm.js +256 -18
  110. package/dist/ui-combobox.esm.js.map +1 -1
  111. package/dist/ui-menu.cjs.js +150 -16
  112. package/dist/ui-menu.cjs.js.map +1 -1
  113. package/dist/ui-menu.esm.js +151 -17
  114. package/dist/ui-menu.esm.js.map +1 -1
  115. package/dist/ui-select.cjs.js +95 -9
  116. package/dist/ui-select.cjs.js.map +1 -1
  117. package/dist/ui-select.esm.js +95 -9
  118. package/dist/ui-select.esm.js.map +1 -1
  119. package/dist/ui.cjs.js +679 -173
  120. package/dist/ui.cjs.js.map +1 -1
  121. package/dist/ui.esm.js +679 -175
  122. package/dist/ui.esm.js.map +1 -1
  123. package/dist/vike/config/package.json +1 -1
  124. package/dist/vite/plugin.cjs.js +968 -4
  125. package/dist/vite/plugin.cjs.js.map +1 -1
  126. package/dist/vite/plugin.mjs +968 -4
  127. package/dist/vite/plugin.mjs.map +1 -1
  128. package/dist/worker-page.cjs.js +669 -0
  129. package/dist/worker-page.cjs.js.map +1 -0
  130. package/dist/worker-page.esm.js +665 -0
  131. package/dist/worker-page.esm.js.map +1 -0
  132. package/dist/worker.cjs.js +377 -0
  133. package/dist/worker.cjs.js.map +1 -0
  134. package/dist/worker.esm.js +374 -0
  135. package/dist/worker.esm.js.map +1 -0
  136. package/dist/yjs-testing.cjs.js +98 -0
  137. package/dist/yjs-testing.cjs.js.map +1 -0
  138. package/dist/yjs-testing.esm.js +77 -0
  139. package/dist/yjs-testing.esm.js.map +1 -0
  140. package/dist/yjs.cjs.js +923 -0
  141. package/dist/yjs.cjs.js.map +1 -0
  142. package/dist/yjs.esm.js +900 -0
  143. package/dist/yjs.esm.js.map +1 -0
  144. package/dist/zag.cjs.js +11 -1
  145. package/dist/zag.cjs.js.map +1 -1
  146. package/dist/zag.esm.js +11 -1
  147. package/dist/zag.esm.js.map +1 -1
  148. package/llms/ai.md +65 -0
  149. package/llms/collaboration.md +5 -0
  150. package/llms/graphics.md +10 -0
  151. package/llms/renderers.md +7 -0
  152. package/llms/replay.md +5 -0
  153. package/llms/sandbox.md +5 -0
  154. package/llms/typed-apis.md +5 -0
  155. package/llms/ui.md +10 -0
  156. package/llms-full.txt +444 -0
  157. package/llms.txt +37 -35
  158. package/package.json +139 -3
  159. package/src/agent-ui/mcp.ts +152 -0
  160. package/src/agent-ui/snapshot.ts +212 -0
  161. package/src/agent-ui.d.ts +106 -0
  162. package/src/agent-ui.ts +249 -0
  163. package/src/ai.d.ts +95 -2
  164. package/src/api.d.ts +303 -0
  165. package/src/api.ts +9 -0
  166. package/src/canvas/Canvas.ts +585 -0
  167. package/src/canvas/backend.ts +627 -0
  168. package/src/canvas/geometry.ts +187 -0
  169. package/src/canvas/gesture.ts +23 -0
  170. package/src/canvas/image.ts +197 -0
  171. package/src/canvas/kit.ts +130 -0
  172. package/src/canvas/paint.ts +260 -0
  173. package/src/canvas/pointer.ts +25 -0
  174. package/src/canvas/snap.ts +68 -0
  175. package/src/canvas/svg.ts +99 -0
  176. package/src/canvas.d.ts +373 -0
  177. package/src/canvas.ts +46 -0
  178. package/src/core/actions.ts +14 -3
  179. package/src/core/cell.ts +12 -4
  180. package/src/core/debug.ts +8 -3
  181. package/src/core/hooks.ts +16 -2
  182. package/src/core/hosts/collection.ts +1 -1
  183. package/src/core/hosts/switchable.ts +26 -8
  184. package/src/core/instance.ts +98 -45
  185. package/src/core/markers/portal.ts +4 -0
  186. package/src/core/registry.ts +4 -0
  187. package/src/core/runtime.ts +34 -25
  188. package/src/core/statics.ts +5 -5
  189. package/src/cycle/dom/EventDelegator.ts +37 -33
  190. package/src/cycle/dom/MainDOMSource.ts +16 -14
  191. package/src/cycle/dom/enrichEventStream.ts +13 -9
  192. package/src/cycle/dom/fromEvent.ts +6 -10
  193. package/src/cycle/dom/index.ts +11 -7
  194. package/src/cycle/dom/isolate.ts +13 -6
  195. package/src/cycle/dom/makeDOMDriver.ts +6 -2
  196. package/src/cycle/dom/utils.ts +9 -2
  197. package/src/cycle/run/adapt.ts +4 -12
  198. package/src/devtools.d.ts +9 -0
  199. package/src/devtools.ts +2 -1
  200. package/src/extra/ai/compose/catalog.ts +2 -1
  201. package/src/extra/ai/compose/decisions.ts +1 -2
  202. package/src/extra/ai/compose/generated.ts +234 -0
  203. package/src/extra/ai/compose/index.ts +6 -2
  204. package/src/extra/ai/compose/layout.ts +3 -1
  205. package/src/extra/ai/compose/llm.ts +7 -3
  206. package/src/extra/ai/compose/types.ts +2 -2
  207. package/src/extra/ai/transports/shared.ts +3 -1
  208. package/src/extra/api/contract.ts +485 -0
  209. package/src/extra/api/index.ts +26 -0
  210. package/src/extra/behaviors.ts +3 -3
  211. package/src/extra/controlled.ts +44 -0
  212. package/src/extra/copyAsTest.ts +22 -0
  213. package/src/extra/devtools.ts +5 -3
  214. package/src/extra/devtoolsActions.ts +8 -0
  215. package/src/extra/devtoolsNext.ts +1 -1
  216. package/src/extra/diagnostics/checks/actionLog.ts +8 -3
  217. package/src/extra/diagnostics/checks/behaviors.ts +25 -0
  218. package/src/extra/diagnostics/checks/elementCommands.ts +17 -5
  219. package/src/extra/diagnostics/checks/index.ts +1 -1
  220. package/src/extra/diagnostics/checks/next.ts +78 -4
  221. package/src/extra/diagnostics/checks/props.ts +6 -1
  222. package/src/extra/diagnostics/checks/replies.ts +11 -7
  223. package/src/extra/diagnostics/checks/timers.ts +8 -2
  224. package/src/extra/diagnostics/checks/virtual.ts +11 -0
  225. package/src/extra/diagnostics/codes.ts +51 -1
  226. package/src/extra/elementCommands.ts +20 -4
  227. package/src/extra/fetchDriver.ts +4 -1
  228. package/src/extra/fetchEventSource.ts +6 -2
  229. package/src/extra/field.ts +29 -0
  230. package/src/extra/form.ts +42 -16
  231. package/src/extra/frame.ts +54 -0
  232. package/src/extra/keymap.ts +41 -0
  233. package/src/extra/pager.ts +2 -0
  234. package/src/extra/persist.ts +2 -1
  235. package/src/extra/pointer.ts +262 -0
  236. package/src/extra/processForm.ts +34 -1
  237. package/src/extra/replay/address.ts +49 -0
  238. package/src/extra/replay/entropy.ts +99 -0
  239. package/src/extra/replay/format.ts +263 -0
  240. package/src/extra/replay/index.ts +8 -0
  241. package/src/extra/replay/inert.ts +22 -0
  242. package/src/extra/replay/recorder.ts +247 -0
  243. package/src/extra/replay/replayer.ts +234 -0
  244. package/src/extra/replay/report.ts +126 -0
  245. package/src/extra/replay/schema.ts +52 -0
  246. package/src/extra/replay/version.ts +3 -0
  247. package/src/extra/router.ts +7 -3
  248. package/src/extra/selection.ts +2 -0
  249. package/src/extra/socketDriver.ts +51 -6
  250. package/src/extra/ssr.ts +128 -42
  251. package/src/extra/testing.ts +295 -39
  252. package/src/extra/timers.ts +96 -7
  253. package/src/extra/undo.ts +2 -0
  254. package/src/extra/virtual.ts +25 -2
  255. package/src/extra/widget.ts +106 -17
  256. package/src/host/actions.ts +383 -0
  257. package/src/host/delegation.ts +101 -0
  258. package/src/host/dom.ts +553 -0
  259. package/src/host/driver.ts +185 -0
  260. package/src/host/selector.ts +119 -0
  261. package/src/host/style.ts +51 -0
  262. package/src/host.d.ts +298 -0
  263. package/src/host.ts +46 -0
  264. package/src/index.d.ts +302 -38
  265. package/src/index.ts +15 -3
  266. package/src/pragma/blocks.ts +211 -0
  267. package/src/pragma/index.ts +44 -10
  268. package/src/primitives.d.ts +54 -0
  269. package/src/primitives.ts +136 -0
  270. package/src/react-native.d.ts +57 -0
  271. package/src/react-native.ts +285 -0
  272. package/src/react-peers.d.ts +21 -3
  273. package/src/replay.d.ts +185 -0
  274. package/src/replay.ts +6 -0
  275. package/src/sandbox/build.ts +119 -0
  276. package/src/sandbox/create.ts +187 -0
  277. package/src/sandbox/engine.ts +267 -0
  278. package/src/sandbox/generated.ts +195 -0
  279. package/src/sandbox/kernel.ts +180 -0
  280. package/src/sandbox/mirror.ts +269 -0
  281. package/src/sandbox/protocol.ts +128 -0
  282. package/src/sandbox/quickjs.ts +74 -0
  283. package/src/sandbox/ssr.ts +41 -0
  284. package/src/sandbox/widget.ts +60 -0
  285. package/src/sandbox/worker-entry.ts +59 -0
  286. package/src/sandbox/worker.ts +137 -0
  287. package/src/sandbox-build.d.ts +12 -0
  288. package/src/sandbox-build.ts +6 -0
  289. package/src/sandbox.d.ts +299 -0
  290. package/src/sandbox.ts +25 -0
  291. package/src/shared.ts +12 -0
  292. package/src/terminal/paint.ts +712 -0
  293. package/src/terminal.d.ts +167 -0
  294. package/src/terminal.ts +530 -0
  295. package/src/three/assets.ts +170 -0
  296. package/src/three/host.ts +36 -0
  297. package/src/three/scene.ts +217 -0
  298. package/src/three/target.ts +252 -0
  299. package/src/three.d.ts +221 -0
  300. package/src/three.ts +30 -0
  301. package/src/ui/accordion.ts +21 -7
  302. package/src/ui/dialog.ts +192 -90
  303. package/src/ui/disclosure.ts +2 -0
  304. package/src/ui/dom.ts +30 -0
  305. package/src/ui/layers.ts +132 -0
  306. package/src/ui/popover.ts +120 -21
  307. package/src/ui/shared.ts +98 -12
  308. package/src/ui/stack.ts +18 -0
  309. package/src/ui/tabs.ts +2 -0
  310. package/src/ui/toaster.ts +16 -15
  311. package/src/ui/tooltip.ts +24 -8
  312. package/src/ui/tooltips.ts +26 -14
  313. package/src/ui/zag/combobox.ts +180 -13
  314. package/src/ui/zag/menu.ts +97 -17
  315. package/src/ui/zag/select.ts +15 -4
  316. package/src/ui/zag/shared.ts +60 -8
  317. package/src/ui-combobox.d.ts +16 -1
  318. package/src/ui-menu.d.ts +22 -7
  319. package/src/ui-zag-types.d.ts +13 -5
  320. package/src/ui.d.ts +62 -7
  321. package/src/ui.ts +2 -2
  322. package/src/vite/api.ts +186 -0
  323. package/src/vite/compile.ts +280 -0
  324. package/src/vite/plugin.d.ts +34 -0
  325. package/src/vite/plugin.ts +78 -4
  326. package/src/vite/sandbox.ts +16 -0
  327. package/src/worker/page.ts +396 -0
  328. package/src/worker/protocol.ts +48 -0
  329. package/src/worker/target.ts +282 -0
  330. package/src/worker-page.d.ts +42 -0
  331. package/src/worker-page.ts +12 -0
  332. package/src/worker.d.ts +65 -0
  333. package/src/worker.ts +16 -0
  334. package/src/yjs/driver.ts +253 -0
  335. package/src/yjs/mapping.ts +389 -0
  336. package/src/yjs/shared.ts +138 -0
  337. package/src/yjs/testing.ts +91 -0
  338. package/src/yjs-testing.d.ts +34 -0
  339. package/src/yjs-testing.ts +7 -0
  340. package/src/yjs.d.ts +109 -0
  341. package/src/yjs.ts +18 -0
  342. package/src/zag.d.ts +2 -0
  343. package/src/zag.ts +8 -1
@@ -2,6 +2,8 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createRequire } from 'node:module';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ import { MagicString } from 'magic-string';
6
+ import { Readable } from 'node:stream';
5
7
 
6
8
  /**
7
9
  * G-099: xstream does `require('globalthis').getPolyfill()`, which pulls the
@@ -889,6 +891,922 @@ function printGraphUrl(server, available = true) {
889
891
  };
890
892
  }
891
893
 
894
+ /**
895
+ * CV-2: compiled views, tier A (dev-plans/research/compile-wasm/compiled-views-plan.md). Rewrites a
896
+ * module's JSX before the JSX transform: an HTML element (not a form field, custom element or SVG
897
+ * tag) with plain attributes becomes a call to the pragma's `__cv.e(sel, data, children, key)`,
898
+ * its data made by the pragma's own sanitizeData (`__cv.sd`): once at module load (hoisted) when
899
+ * every attribute is static, else per render. Everything else (components, markers, fragments,
900
+ * fields, spreads, refs, ...) is left as JSX for the JSX transform; their children may still
901
+ * compile. The output vnodes are the ones the pragma would make (test/cv-compile.test.js).
902
+ *
903
+ * CV-1 (`ssr`, server builds): the outermost compiled element stays a vnode (`__cvs.r`: the
904
+ * root's `data-sygnal-ssr` stamp and the SSR walk need one) whose inside is HTML parts made by
905
+ * ssr.ts (`__cvs.f`, `__cvs.s`): open tags and static subtrees serialized by its own vnodeToHtml
906
+ * (cached per escaping mode), text escaped by its escapeHtml, anything not compiled (components,
907
+ * markers, fields, ...) kept as a vnode at the child path the JSX runtime's tree would give it.
908
+ * The HTML is renderToString's (test/cv-ssr.test.js).
909
+ *
910
+ * `parse` is Vite 8's parseAst (ESTree + JSX, UTF-16 offsets). The source map maps each rewritten
911
+ * JSX expression to its start (magic-string).
912
+ */
913
+ // the named export: magic-string's CJS build has no plain default for the plugin's CJS output
914
+ // HTML elements the compiler takes. Not here: the form fields (input, textarea, select, option:
915
+ // the controlled-input module re-syncs them; the pragma never makes them plain), SVG and MathML
916
+ // (the pragma sets the namespace), hyphenated custom elements, and any other lowercase tag (a host
917
+ // renderer's own tags go through the pragma, which knows its run-time tag lists)
918
+ const HTML = new Set(('a abbr address article aside audio b bdi bdo blockquote br button canvas caption cite code col colgroup ' +
919
+ 'data datalist dd del details dfn dialog div dl dt em fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hgroup hr i ' +
920
+ 'iframe img ins kbd label legend li main map mark menu meter nav noscript object ol optgroup output p picture pre progress q rp rt ' +
921
+ 'ruby s samp search section small source span strong sub summary sup table tbody td template tfoot th thead time tr track u ul var ' +
922
+ 'video wbr').split(' '));
923
+ // attributes the pragma gives run-time behaviour (hooks) or that change what the element is
924
+ const BAIL_ATTR = /^(ref|autoFocus|autoSelect|hook|children|key)$/;
925
+ // in server builds also: the SSR serializer gives these their own content
926
+ const SSR_BAIL_ATTR = /^(innerHTML|textContent)$/;
927
+ // an HTML entity: JSX decodes them, the parser keeps them raw (the pragma path decodes through the JSX transform)
928
+ const ENTITY = /&(#\d+|#x[\da-f]+|[a-z][a-z\d]*);/i;
929
+ function compileViews(code, id, parse, opts = {}) {
930
+ const ssr = !!opts.ssr, blocks = !ssr && !!opts.blocks;
931
+ let nblocks = 0;
932
+ if (!/<[a-z]/.test(code))
933
+ return null;
934
+ let ast;
935
+ try {
936
+ ast = parse(code, { lang: /\.tsx$/.test(id) ? 'tsx' : /\.ts$/.test(id) ? 'ts' : 'jsx' });
937
+ }
938
+ catch {
939
+ return null;
940
+ }
941
+ const hoisted = [];
942
+ let compiled = 0;
943
+ // CV-3: the JSX a component (a capitalized function) returns directly: its view's root
944
+ const roots = new Set();
945
+ if (blocks) {
946
+ const un = (n) => n?.type == 'ParenthesizedExpression' ? un(n.expression) : n;
947
+ const fnRoots = (f) => {
948
+ if (!f)
949
+ return;
950
+ if (f.expression) {
951
+ roots.add(un(f.body));
952
+ return;
953
+ }
954
+ for (const st of f.body?.body || [])
955
+ if (st.type == 'ReturnStatement')
956
+ roots.add(un(st.argument));
957
+ };
958
+ const scan = (n) => {
959
+ if (!n || typeof n != 'object')
960
+ return;
961
+ if (n.type == 'FunctionDeclaration' && /^[A-Z]/.test(n.id?.name || ''))
962
+ fnRoots(n);
963
+ if (n.type == 'VariableDeclarator' && /^[A-Z]/.test(n.id?.name || '') && /Function/.test(n.init?.type || ''))
964
+ fnRoots(n.init);
965
+ for (const k in n) {
966
+ if (k == 'start' || k == 'end' || k == 'loc')
967
+ continue;
968
+ const v = n[k];
969
+ if (Array.isArray(v))
970
+ v.forEach(scan);
971
+ else if (v && typeof v == 'object' && typeof v.type == 'string')
972
+ scan(v);
973
+ }
974
+ };
975
+ scan(ast);
976
+ }
977
+ const src = (s, e, subs) => {
978
+ let out = '', at = s;
979
+ for (const [a, b, t] of subs.sort((x, y) => x[0] - y[0])) {
980
+ out += code.slice(at, a) + t;
981
+ at = b;
982
+ }
983
+ return out + code.slice(at, e);
984
+ };
985
+ // every maximal JSX node under `node`, rewritten (a JSX node in expression position)
986
+ const subsIn = (node, subs = []) => {
987
+ if (!node || typeof node != 'object')
988
+ return subs;
989
+ if (node.type == 'JSXElement' || node.type == 'JSXFragment') {
990
+ subs.push([node.start, node.end, jsx(node)]);
991
+ return subs;
992
+ }
993
+ for (const k in node) {
994
+ if (k == 'start' || k == 'end' || k == 'loc' || k == 'range')
995
+ continue;
996
+ const v = node[k];
997
+ if (Array.isArray(v))
998
+ for (const c of v)
999
+ subsIn(c, subs);
1000
+ else if (v && typeof v == 'object' && typeof v.type == 'string')
1001
+ subsIn(v, subs);
1002
+ }
1003
+ return subs;
1004
+ };
1005
+ const expr = (e) => src(e.start, e.end, subsIn(e));
1006
+ const tag = (el) => el.openingElement.name.type == 'JSXIdentifier' ? el.openingElement.name.name : null;
1007
+ const compilable = (el) => {
1008
+ const t = tag(el);
1009
+ if (!t || !HTML.has(t))
1010
+ return false;
1011
+ for (const a of el.openingElement.attributes) {
1012
+ if (a.type != 'JSXAttribute' || a.name.type != 'JSXIdentifier')
1013
+ return false;
1014
+ if (BAIL_ATTR.test(a.name.name) && a.name.name != 'key' || ssr && SSR_BAIL_ATTR.test(a.name.name))
1015
+ return false;
1016
+ const v = a.value;
1017
+ if (v && v.type == 'Literal' && ENTITY.test(String(v.value)))
1018
+ return false;
1019
+ if (v && v.type != 'Literal' && !(v.type == 'JSXExpressionContainer' && v.expression.type != 'JSXEmptyExpression'))
1020
+ return false;
1021
+ }
1022
+ for (const c of el.children)
1023
+ if (c.type == 'JSXText' && ENTITY.test(c.value) || c.type == 'JSXSpreadChild')
1024
+ return false;
1025
+ return true;
1026
+ };
1027
+ // no expression anywhere below (attributes, children): its HTML never changes (CV-1)
1028
+ const isStatic = (el) => compilable(el)
1029
+ && el.openingElement.attributes.every((a) => !a.value || a.value.type == 'Literal')
1030
+ && el.children.every((c) => c.type == 'JSXText' || c.type == 'JSXElement' && isStatic(c));
1031
+ // a JSX node in expression position: compiled, or JSX whose children may compile
1032
+ // (`vnodes`: inside a static SSR subtree's vnode factory, every element is a client one)
1033
+ let vnodes = 0;
1034
+ const jsx = (node) => node.type == 'JSXElement' && compilable(node)
1035
+ ? (ssr && !vnodes ? ssrRoot(node) : blocks && !vnodes && roots.has(node) && blockable(node) ? block(node) : element(node)) : jsxSource(node);
1036
+ const jsxSource = (node) => {
1037
+ const subs = [];
1038
+ const open = node.openingElement;
1039
+ if (open)
1040
+ for (const a of open.attributes) {
1041
+ const v = a.type == 'JSXSpreadAttribute' ? a.argument : a.value?.type == 'JSXExpressionContainer' ? a.value.expression : null;
1042
+ if (v)
1043
+ subsIn(v, subs);
1044
+ }
1045
+ for (const c of node.children) {
1046
+ if (c.type == 'JSXElement')
1047
+ subs.push([c.start, c.end, compilable(c) ? '{' + jsx(c) + '}' : jsxSource(c)]);
1048
+ else if (c.type == 'JSXFragment')
1049
+ subs.push([c.start, c.end, jsxSource(c)]);
1050
+ else if (c.type == 'JSXExpressionContainer')
1051
+ subsIn(c.expression, subs);
1052
+ }
1053
+ return src(node.start, node.end, subs);
1054
+ };
1055
+ // the element's data expression and key (a static one hoisted; see __cv in src/pragma/index.ts)
1056
+ const dataOf = (el) => {
1057
+ let key = 'undefined', dynamic = false;
1058
+ const names = [], vals = [];
1059
+ for (const a of el.openingElement.attributes) {
1060
+ const name = a.name.name, v = a.value;
1061
+ const val = !v ? 'true' : v.type == 'Literal' ? JSON.stringify(v.value) : (dynamic = name != 'key' || dynamic, '(' + expr(v.expression) + ')');
1062
+ if (name == 'key')
1063
+ key = val;
1064
+ // a duplicate keeps its first place and takes the last value, as in an object literal
1065
+ else {
1066
+ const i = names.indexOf(name);
1067
+ if (i >= 0)
1068
+ vals[i] = val;
1069
+ else
1070
+ names.push(name), vals.push(val);
1071
+ }
1072
+ }
1073
+ // static: made once at load by the pragma's sanitizeData; dynamic: the keys routed once at
1074
+ // load by the pragma's route(), the data made per render from the values (__cv.d)
1075
+ const data = !dynamic ? hoist('__cv.sd({' + names.map((n, i) => JSON.stringify(n) + ': ' + vals[i]).join(', ') + '})')
1076
+ : `__cv.d(${hoist('__cv.r(' + JSON.stringify(names) + ')')}, [${vals.join(', ')}])`;
1077
+ return [data, key, dynamic];
1078
+ };
1079
+ const childArg = (kids) => kids.length == 0 ? 'undefined' : kids.length == 1 ? kids[0] : '[' + kids.join(', ') + ']';
1080
+ const element = (el) => {
1081
+ compiled++;
1082
+ const [data, key] = dataOf(el);
1083
+ const c = childArg(children(el.children, false));
1084
+ return `__cv.e(${JSON.stringify(tag(el))}, ${data}, ${c}${key == 'undefined' ? '' : ', ' + key})`;
1085
+ };
1086
+ // CV-1: the outermost compiled element in server code: a vnode with HTML parts inside
1087
+ const ssrRoot = (el) => {
1088
+ compiled++;
1089
+ const [data, key] = dataOf(el);
1090
+ return `__cvs.r(${JSON.stringify(tag(el))}, ${data}, ${childArg(children(el.children, true))}${key == 'undefined' ? '' : ', ' + key})`;
1091
+ };
1092
+ // CV-1: a compiled element inside one: a static one's HTML made once (per escaping mode), else
1093
+ // its open tag (static: once) and its children's parts
1094
+ const ssrInner = (el) => {
1095
+ compiled++;
1096
+ if (isStatic(el)) {
1097
+ vnodes++;
1098
+ const v = element(el);
1099
+ vnodes--;
1100
+ return `__cvs.s(${hoist('__cvs.S(() => ' + v + ')')})`;
1101
+ }
1102
+ const [data, , dynamic] = dataOf(el), t = JSON.stringify(tag(el));
1103
+ const open = dynamic ? `__cvs.od(${t}, ${data})` : hoist(`__cvs.O(${t}, ${data})`);
1104
+ return `__cvs.f(${open}, ${childArg(children(el.children, true))})`;
1105
+ };
1106
+ // CV-1: JSX in a direct value position of a compiled element's child expression (the expression,
1107
+ // a ?: branch, either side of && || ??, an array element, a .map() arrow's expression body) can
1108
+ // only become that element's child, so a compilable one is an HTML chunk; anything else (JSX in a
1109
+ // variable, a call's argument, a component prop) stays a vnode
1110
+ // (client builds with blocks: a .map() row there is a block, `row`: inside a .map() body)
1111
+ const direct = (n, subs, row = false) => {
1112
+ if (!n)
1113
+ return;
1114
+ const t = n.type;
1115
+ if (t == 'JSXElement' && compilable(n))
1116
+ subs.push([n.start, n.end, ssr ? ssrInner(n) : row && blockable(n) ? block(n) : jsx(n)]);
1117
+ else if (t == 'ParenthesizedExpression')
1118
+ direct(n.expression, subs, row);
1119
+ else if (t == 'ConditionalExpression') {
1120
+ subsIn(n.test, subs);
1121
+ direct(n.consequent, subs, row);
1122
+ direct(n.alternate, subs, row);
1123
+ }
1124
+ else if (t == 'LogicalExpression') {
1125
+ direct(n.left, subs, row);
1126
+ direct(n.right, subs, row);
1127
+ }
1128
+ else if (t == 'ArrayExpression') {
1129
+ for (const e of n.elements)
1130
+ e && (e.type == 'SpreadElement' ? subsIn(e, subs) : direct(e, subs, row));
1131
+ }
1132
+ else if (t == 'CallExpression' && n.callee.type == 'MemberExpression' && !n.callee.computed && n.callee.property.name == 'map'
1133
+ && n.arguments.length == 1 && n.arguments[0].type == 'ArrowFunctionExpression' && n.arguments[0].expression) {
1134
+ subsIn(n.callee, subs);
1135
+ for (const p of n.arguments[0].params)
1136
+ subsIn(p, subs);
1137
+ direct(n.arguments[0].body, subs, true);
1138
+ }
1139
+ else
1140
+ subsIn(n, subs);
1141
+ };
1142
+ // CV-3: a block: compilable all the way down, at least two elements, children only text, text
1143
+ // holes (an expression with no JSX in it; not `children` / `slots`, which are vnodes) and such
1144
+ // elements
1145
+ const SLOTISH = /^(children|slots)$|\.(children|slots)$/;
1146
+ const hasJsx = (n) => {
1147
+ if (!n || typeof n != 'object')
1148
+ return false;
1149
+ if (n.type == 'JSXElement' || n.type == 'JSXFragment')
1150
+ return true;
1151
+ for (const k in n) {
1152
+ if (k == 'start' || k == 'end' || k == 'loc')
1153
+ continue;
1154
+ const v = n[k];
1155
+ if (Array.isArray(v) ? v.some(hasJsx) : v && typeof v == 'object' && typeof v.type == 'string' && hasJsx(v))
1156
+ return true;
1157
+ }
1158
+ return false;
1159
+ };
1160
+ const blockable = (el, top = true) => {
1161
+ if (!compilable(el))
1162
+ return false;
1163
+ let n = 0;
1164
+ const ok = (e) => {
1165
+ if (!compilable(e))
1166
+ return false;
1167
+ n++;
1168
+ for (const c of e.children) {
1169
+ if (c.type == 'JSXElement') {
1170
+ if (!ok(c))
1171
+ return false;
1172
+ }
1173
+ else if (c.type == 'JSXExpressionContainer') {
1174
+ if (c.expression.type == 'JSXEmptyExpression')
1175
+ continue;
1176
+ if (hasJsx(c.expression) || SLOTISH.test(code.slice(c.expression.start, c.expression.end).trim()))
1177
+ return false;
1178
+ }
1179
+ else if (c.type != 'JSXText')
1180
+ return false;
1181
+ }
1182
+ return true;
1183
+ };
1184
+ return ok(el) && (!top || n >= 2);
1185
+ };
1186
+ const block = (el) => {
1187
+ compiled++;
1188
+ nblocks++;
1189
+ const vals = [], kinds = [];
1190
+ const hole = (k, v) => (kinds.push(k), vals.push(v), `h(${vals.length - 1})`);
1191
+ const [rootData, key] = dataOf(el);
1192
+ hole('r', rootData);
1193
+ const bel = (e, root) => {
1194
+ let d;
1195
+ if (root)
1196
+ d = 'h(0)';
1197
+ else {
1198
+ const [data, , dynamic] = dataOf(e);
1199
+ d = dynamic ? hole('d', data) : data;
1200
+ }
1201
+ const kids = [];
1202
+ for (const c of e.children) {
1203
+ if (c.type == 'JSXText') {
1204
+ const t = cleanText(c.value);
1205
+ if (t)
1206
+ kids.push(JSON.stringify(t));
1207
+ }
1208
+ else if (c.type == 'JSXExpressionContainer') {
1209
+ if (c.expression.type != 'JSXEmptyExpression')
1210
+ kids.push(hole('t', '(' + expr(c.expression) + ')'));
1211
+ }
1212
+ else
1213
+ kids.push(bel(c, false));
1214
+ }
1215
+ return `__cv.e(${JSON.stringify(tag(e))}, ${d}, ${childArg(kids)})`;
1216
+ };
1217
+ const body = bel(el, true);
1218
+ const def = hoist(`__cvb.b((h) => ${body}, ${JSON.stringify(kinds.join(''))})`);
1219
+ return `__cvb.B(${def}, [${vals.join(', ')}]${key == 'undefined' ? '' : ', ' + key})`;
1220
+ };
1221
+ const hoist = (obj) => {
1222
+ const i = hoisted.indexOf(obj);
1223
+ if (i >= 0)
1224
+ return '$cv' + i;
1225
+ hoisted.push(obj);
1226
+ return '$cv' + (hoisted.length - 1);
1227
+ };
1228
+ // the children as the JSX transform passes them: JSX text cleaned as the JSX spec does (lines
1229
+ // trimmed, empty lines dropped, joined by a space), empty expressions dropped
1230
+ const children = (list, html) => {
1231
+ const out = [];
1232
+ for (const c of list) {
1233
+ if (c.type == 'JSXText') {
1234
+ const t = cleanText(c.value);
1235
+ if (t)
1236
+ out.push(JSON.stringify(t));
1237
+ }
1238
+ else if (c.type == 'JSXExpressionContainer') {
1239
+ const e = c.expression;
1240
+ if (e.type == 'JSXEmptyExpression')
1241
+ continue;
1242
+ if ((html || blocks) && !vnodes) {
1243
+ const subs = [];
1244
+ direct(e, subs);
1245
+ out.push('(' + src(e.start, e.end, subs) + ')');
1246
+ }
1247
+ else
1248
+ out.push('(' + expr(e) + ')');
1249
+ }
1250
+ else if (html && !vnodes && c.type == 'JSXElement' && compilable(c))
1251
+ out.push(ssrInner(c));
1252
+ else
1253
+ out.push(jsx(c));
1254
+ }
1255
+ return out;
1256
+ };
1257
+ const subs = subsIn(ast);
1258
+ if (!compiled)
1259
+ return null;
1260
+ const ms = new MagicString(code);
1261
+ for (const [a, b, t] of subs)
1262
+ ms.overwrite(a, b, t);
1263
+ ms.prepend(`import { __cv${ssr ? ', __cvs' : ''}${nblocks ? ', __cvb' : ''} } from 'sygnal';\n` + hoisted.map((h, i) => `const $cv${i} = ${h};\n`).join(''));
1264
+ return { code: ms.toString(), map: ms.generateMap({ hires: 'boundary', source: id, includeContent: true }) };
1265
+ }
1266
+ // JSX text as oxc's JSX transform cleans it (the transform the plugin configures): split on line
1267
+ // breaks, trim spaces and tabs at each line's edges (not the first line's start nor the last's
1268
+ // end), drop empty lines, join with ' '; tabs inside a line stay (Babel would make them spaces)
1269
+ function cleanText(value) {
1270
+ const lines = value.split(/\r\n|\n|\r/);
1271
+ let last = 0;
1272
+ for (let i = 0; i < lines.length; i++)
1273
+ if (/[^ \t]/.test(lines[i]))
1274
+ last = i;
1275
+ let str = '';
1276
+ for (let i = 0; i < lines.length; i++) {
1277
+ let line = lines[i];
1278
+ if (i !== 0)
1279
+ line = line.replace(/^[ \t]+/, '');
1280
+ if (i !== lines.length - 1)
1281
+ line = line.replace(/[ \t]+$/, '');
1282
+ if (line) {
1283
+ if (i !== last)
1284
+ line += ' ';
1285
+ str += line;
1286
+ }
1287
+ }
1288
+ return str;
1289
+ }
1290
+
1291
+ /*
1292
+ * 'sygnal/api' (dev preview; index.ts adds contractFetch: this module imports no diagnostics, so
1293
+ * sygnal/vite can bundle it without a second copy of their state; research: dev-plans/research/fullstack-contracts/): typed clients
1294
+ * for an API whose contract the server already has. A client turns a call into plain data the
1295
+ * existing drivers understand, so effects stay data and replies come back as actions:
1296
+ *
1297
+ * - a GET endpoint → a FetchRequest (`resources`, the query cache, retry, `latest`), tagged
1298
+ * `api:<id>` so `invalidates: [api.x]` / `{ invalidate: [api.x] }`-style tags can name it;
1299
+ * - any other method → a FetchRequest with a JSON body (reply actions);
1300
+ * - a stream (an oRPC event iterator, or `.sse(path)`) → an SseConnection for `connections`.
1301
+ *
1302
+ * Adapters (one per contract source), all over plain JSON on the wire:
1303
+ * - orpcClient(contract, options): an oRPC contract router served by oRPC's OpenAPIHandler
1304
+ * (compact input: path params from `{name}` / `{+name}`, the rest in the query for GET with
1305
+ * bracket notation, else the JSON body). The RPCHandler protocol ({ json, meta }) is not
1306
+ * supported: its rich serialisation is what the network-calls research ruled out.
1307
+ * - openApiClient<paths>(options): openapi-typescript's `paths` (types only); input is
1308
+ * openapi-fetch's `{ params: { path, query }, body }`; query arrays repeat the key (form,
1309
+ * explode), objects use deepObject brackets.
1310
+ * - honoClient<ExtractSchema<typeof app>>(options): Hono routes (types only); input is hc's
1311
+ * `{ param, query, json }`.
1312
+ *
1313
+ * Each endpoint is a function `(input?, options?) => request` with `id`, `kind`, `method`,
1314
+ * `path` and `match(request)` (renderComponent's t.respond / t.fail take an endpoint as target).
1315
+ * Requests carry `endpoint: id` (an app key: not sent). `options` are the request's own fields
1316
+ * (ok, error, key, latest, cache, retry, ...); `invalidates` takes endpoints or tags.
1317
+ *
1318
+ * Version skew (D: contract hash): contractHash(contract) is a stable hash of the contract (an
1319
+ * oRPC router: ids, routes, JSON Schemas of input/output, error codes; anything else: its
1320
+ * stable JSON). The app sends it in a header of its choice (`headers` option); on the server,
1321
+ * contractSkew(request, { hash, header }) answers a different one with 409 CONTRACT_SKEW (the
1322
+ * oRPC error shape). isContractSkew(failure) tells a reducer; contractFetch() wraps fetch for the
1323
+ * drivers and reports SYG690 once (and calls onSkew) when a reply says the contract changed.
1324
+ */
1325
+ // ── oRPC (OpenAPIHandler) ──────────────────────────────────────────────
1326
+ const isProcedure = (x) => !!x && typeof x == 'object' && '~orpc' in x && x['~orpc'] && 'route' in x['~orpc'];
1327
+ /** the yields schema of an oRPC eventIterator() output schema, or undefined */
1328
+ const eventYields = (schema) => {
1329
+ const std = schema?.['~standard'];
1330
+ if (!std)
1331
+ return undefined;
1332
+ const s = Object.getOwnPropertySymbols(std).find(s => s.description === 'ORPC_EVENT_ITERATOR_DETAILS');
1333
+ return s ? std[s].yields ?? {} : undefined;
1334
+ };
1335
+ /** every procedure of an oRPC contract router: [keys, procedure] */
1336
+ const procedures = (router, keys = [], out = [], seen = new Set()) => {
1337
+ if (isProcedure(router))
1338
+ out.push([keys, router]);
1339
+ // a router is plain nested objects: not a schema (a contract module may export those too), no cycles
1340
+ else if (router && typeof router == 'object' && !router['~standard'] && !seen.has(router)) {
1341
+ seen.add(router);
1342
+ Object.keys(router).forEach(k => procedures(router[k], [...keys, k], out, seen));
1343
+ }
1344
+ return out;
1345
+ };
1346
+ const orpcRoute = (keys, p) => {
1347
+ const def = p['~orpc'];
1348
+ const method = String(def.route.method || 'POST').toUpperCase();
1349
+ const path = def.route.path || '/' + keys.map(encodeURIComponent).join('/');
1350
+ // a manifest (a contract without its schemas, or a server function) says `stream` and `kind`
1351
+ const yields = def.stream ? def.outputSchema || {} : eventYields(def.outputSchema);
1352
+ const kind = yields ? 'stream' : def.kind || (method == 'GET' ? 'query' : 'mutation');
1353
+ return { id: keys.join('.'), def, method, path, yields, kind };
1354
+ };
1355
+ // ── contract hash and skew ─────────────────────────────────────────────
1356
+ const stable = (v) => {
1357
+ if (Array.isArray(v))
1358
+ return '[' + v.map(stable).join(',') + ']';
1359
+ if (v && typeof v == 'object')
1360
+ return '{' + Object.keys(v).sort().map(k => JSON.stringify(k) + ':' + stable(v[k])).join(',') + '}';
1361
+ return JSON.stringify(v) ?? 'null';
1362
+ };
1363
+ // cyrb53: a fast, well-distributed 53-bit string hash (not cryptographic: this only detects change)
1364
+ const cyrb53 = (s) => {
1365
+ let h1 = 0xdeadbeef, h2 = 0x41c6ce57;
1366
+ for (let i = 0; i < s.length; i++) {
1367
+ const c = s.charCodeAt(i);
1368
+ h1 = Math.imul(h1 ^ c, 2654435761);
1369
+ h2 = Math.imul(h2 ^ c, 1597334677);
1370
+ }
1371
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
1372
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
1373
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
1374
+ };
1375
+ /** a schema's JSON Schema (Standard JSON Schema), else its vendor: what the hash can see of it */
1376
+ const schemaShape = (s, io) => {
1377
+ const std = s?.['~standard'];
1378
+ if (!std)
1379
+ return null;
1380
+ try {
1381
+ if (std.jsonSchema?.[io])
1382
+ return std.jsonSchema[io]({ target: 'draft-2020-12', libraryOptions: { unrepresentable: 'any' } });
1383
+ }
1384
+ catch (_) { /* a schema it can't represent: the vendor below */ }
1385
+ return std.vendor || 'schema';
1386
+ };
1387
+ function contractHash(contract) {
1388
+ // a manifest made at build time (sygnal/vite's api.contracts) carries its hash
1389
+ if (contract && typeof contract['~hash'] == 'string')
1390
+ return contract['~hash'];
1391
+ const ps = procedures(contract);
1392
+ if (!ps.length)
1393
+ return cyrb53(stable(contract));
1394
+ const shape = ps.map(([keys, p]) => {
1395
+ const r = orpcRoute(keys, p);
1396
+ return [r.id, r.method, r.path, r.kind, schemaShape(r.def.inputSchema, 'input'),
1397
+ r.yields ? schemaShape(r.yields, 'output') : schemaShape(r.def.outputSchema, 'output'),
1398
+ Object.keys(r.def.errorMap || {}).sort()];
1399
+ }).sort((a, b) => (a[0] < b[0] ? -1 : 1));
1400
+ return cyrb53(stable(shape));
1401
+ }
1402
+ // ── build time: a contract without its schemas ─────────────────────────
1403
+ /**
1404
+ * sygnal/vite's `api.contracts`: what a client needs of a contract (routes, kinds, error codes and
1405
+ * statuses) and its hash, so the client bundle has no schema library. Plain JSON (`~hash` aside).
1406
+ */
1407
+ function contractManifest(contract) {
1408
+ const count = procedures(contract).length;
1409
+ if (!count)
1410
+ return { manifest: undefined, hash: '', count };
1411
+ const walk = (node, keys) => {
1412
+ if (isProcedure(node)) {
1413
+ const r = orpcRoute(keys, node);
1414
+ const errorMap = {};
1415
+ Object.keys(r.def.errorMap || {}).forEach(c => { errorMap[c] = { status: r.def.errorMap[c]?.status }; });
1416
+ const route = { method: r.method, path: r.path };
1417
+ if (r.def.route.inputStructure)
1418
+ route.inputStructure = r.def.route.inputStructure;
1419
+ return { '~orpc': { route, kind: r.kind, stream: r.kind == 'stream', manifest: true, errorMap } };
1420
+ }
1421
+ const out = {};
1422
+ Object.keys(node || {}).forEach(k => { if (procedures(node[k]).length)
1423
+ out[k] = walk(node[k], [...keys, k]); });
1424
+ return out;
1425
+ };
1426
+ return { manifest: walk(contract, []), hash: contractHash(contract), count };
1427
+ }
1428
+ // ── function-first: *.server.ts modules ────────────────────────────────
1429
+ /*
1430
+ * A `*.server.ts` file exports query(input, fn), mutation(input, fn) and stream(input, fn). Its
1431
+ * path and export names are the contract: `src/quotes.server.ts` → `export const list` is
1432
+ * `quotes.list`, POST /quotes/list (a stream: GET, read as server-sent events). sygnal/vite
1433
+ * (`api: { functions: true }`) replaces the file in the client by stubs that build the requests,
1434
+ * so nothing of it but its export names, kinds and error codes reaches the bundle; on the
1435
+ * server, serverFunctions(import.meta.glob(...), { os }) makes the oRPC router to mount.
1436
+ * Input is always validated (a Standard Schema, or none: then only an empty input passes), the
1437
+ * wire is plain JSON (oRPC's OpenAPIHandler), and only module exports are functions (no closures
1438
+ * over component scope, no ids made from a closure).
1439
+ */
1440
+ const FN = '~sygnal';
1441
+ /** `src/admin/users.server.ts` (or `/src/...`, `../src/...`) → `admin/users` */
1442
+ const fnNamespace = (file, dir = 'src') => {
1443
+ let f = file.split('?')[0].replace(/\\/g, '/').replace(/^(\.{1,2}\/)+|^\/+/, '');
1444
+ const d = dir.replace(/^\.?\/+|\/+$/g, '');
1445
+ if (d && f.startsWith(d + '/'))
1446
+ f = f.slice(d.length + 1);
1447
+ return f.replace(/\.server\.[cm]?[jt]sx?$/, '');
1448
+ };
1449
+ const fnSpec = (id, kind, errors, schemas) => ({ '~orpc': {
1450
+ route: { method: kind == 'stream' ? 'GET' : 'POST', path: '/' + id.split('.').map(encodeURIComponent).join('/') },
1451
+ kind, stream: kind == 'stream', manifest: !schemas, errorMap: errors,
1452
+ inputSchema: schemas?.input, outputSchema: schemas?.output,
1453
+ } });
1454
+ /** each file's server functions: its namespace, its own contract tree (for the hash), [name, meta] */
1455
+ function fnFiles(modules, dir = 'src') {
1456
+ return Object.keys(modules).sort().map(file => {
1457
+ const ns = fnNamespace(file, dir);
1458
+ const keys = ns.split('/');
1459
+ const tree = {};
1460
+ let node = tree;
1461
+ keys.forEach(k => { node = node[k] = {}; });
1462
+ const fns = [];
1463
+ Object.keys(modules[file] || {}).forEach(name => {
1464
+ const meta = modules[file][name]?.[FN];
1465
+ if (!meta)
1466
+ return;
1467
+ const id = [...keys, name].join('.');
1468
+ node[name] = Object.assign(fnSpec(id, meta.kind, meta.errors, meta), { [FN]: meta });
1469
+ fns.push([name, meta]);
1470
+ });
1471
+ return { file, ns, keys, tree, fns, hash: contractHash(tree) };
1472
+ });
1473
+ }
1474
+
1475
+ /**
1476
+ * sygnal/vite's `api` option (sygnal/api, dev preview; research: dev-plans/research/fullstack-contracts/):
1477
+ *
1478
+ * - `handler`: in dev, requests under `prefix` go to that module's default export, a Fetch handler
1479
+ * (Request → Response): the same module a production server mounts. The plugin only bridges
1480
+ * Node's request to it; the server is the app's own (oRPC), never one of Sygnal's.
1481
+ * - `contracts`: in client code (dev and build, never Vitest) each listed contract module is
1482
+ * replaced by what orpcClient needs of it (routes, kinds, error codes and statuses) with its
1483
+ * contractHash computed at build time, so the bundle has no schema library. The module is
1484
+ * evaluated on the server side (the dev server's SSR loader; for `vite build` a temporary one).
1485
+ * An export that is no contract is left out, so a client import of a schema from it fails
1486
+ * loudly (keep the module out of `contracts` to validate on the client).
1487
+ * - `functions`: `*.server.ts` files under `dir` (function-first). In client code a file becomes
1488
+ * stubs: `export const list = __serverFunction('quotes.list', { kind, errors }, client)`, plus
1489
+ * the file's contract hash in the stub header. Nothing else of the file (its imports, secrets,
1490
+ * handler bodies) is in the client graph. In server code and under Vitest the file keeps its
1491
+ * code and each export gets its id appended, so tests can call it as an endpoint.
1492
+ */
1493
+ // @ts-ignore
1494
+ const SERVER_FILE = /\.server\.[cm]?[jt]sx?$/;
1495
+ // set while the temporary build-time server loads the config: its own sygnal() then adds no api plugin
1496
+ const NESTED = '__SYGNAL_API_EVAL__';
1497
+ const g = globalThis;
1498
+ const nodeProcess$1 = g.process;
1499
+ function apiPlugin(o) {
1500
+ if (g[NESTED])
1501
+ return null;
1502
+ const prefix = (o.prefix ?? '/api').replace(/\/+$/, '');
1503
+ const fo = o.functions === true ? {} : o.functions || null;
1504
+ const dir = (fo && fo.dir) || 'src';
1505
+ const header = (fo && fo.header) || 'x-api-functions';
1506
+ let root = nodeProcess$1.cwd();
1507
+ let isVitest = false;
1508
+ let config;
1509
+ let dev;
1510
+ let temp;
1511
+ const rel = (file) => path.relative(root, file).split(path.sep).join('/');
1512
+ const contractFiles = () => new Set((o.contracts || []).map((f) => path.resolve(root, f)));
1513
+ const isFnFile = (file) => {
1514
+ if (!fo || !SERVER_FILE.test(file) || file.includes('/node_modules/'))
1515
+ return false;
1516
+ const r = rel(file);
1517
+ return !r.startsWith('..') && (r + '/').startsWith(dir.replace(/^\.?\/+|\/+$/g, '') + '/');
1518
+ };
1519
+ // evaluates a module on the server side: the dev server's SSR loader, or (vite build) a temporary one
1520
+ async function load(file) {
1521
+ if (dev)
1522
+ return dev.ssrLoadModule(file);
1523
+ temp ?? (temp = (async () => {
1524
+ const vite = await import(/* @vite-ignore */ projectVite(root));
1525
+ g[NESTED] = true;
1526
+ try {
1527
+ return await vite.createServer({
1528
+ root, mode: config?.mode, configFile: config?.configFile ?? false, logLevel: 'warn', appType: 'custom',
1529
+ server: { middlewareMode: true, hmr: false, ws: false, watch: null },
1530
+ optimizeDeps: { noDiscovery: true, include: [] },
1531
+ });
1532
+ }
1533
+ finally {
1534
+ delete g[NESTED];
1535
+ }
1536
+ })());
1537
+ return (await temp).ssrLoadModule(file);
1538
+ }
1539
+ const exportLine = (name, value) => (name === 'default' ? `export default ${value};` : `export const ${name} = ${value};`);
1540
+ return {
1541
+ name: 'vite-plugin-sygnal:api',
1542
+ config() {
1543
+ isVitest = !!nodeProcess$1?.env?.VITEST;
1544
+ },
1545
+ configResolved(c) {
1546
+ config = c;
1547
+ root = c.root;
1548
+ },
1549
+ configureServer(server) {
1550
+ dev = server;
1551
+ if (!o.handler || isVitest)
1552
+ return;
1553
+ const handlerFile = path.resolve(root, o.handler);
1554
+ server.middlewares.use(async (req, res, next) => {
1555
+ const url = req.url || '';
1556
+ if (!(url === prefix || url.startsWith(prefix + '/') || url.startsWith(prefix + '?')))
1557
+ return next();
1558
+ try {
1559
+ const mod = await server.ssrLoadModule(handlerFile);
1560
+ const handle = typeof mod.default === 'function' ? mod.default : mod.default?.fetch ?? mod.fetch;
1561
+ if (typeof handle !== 'function')
1562
+ throw new Error(`[sygnal] api.handler (${o.handler}) must export default a Fetch handler: (request: Request) => Response`);
1563
+ const ctl = new AbortController();
1564
+ res.on('close', () => ctl.abort());
1565
+ const body = req.method === 'GET' || req.method === 'HEAD' ? undefined : Readable.toWeb(req);
1566
+ const request = new Request(new URL(url, 'http://' + (req.headers.host || 'localhost')), {
1567
+ method: req.method, headers: req.headers, body, signal: ctl.signal, ...(body && { duplex: 'half' }),
1568
+ });
1569
+ const response = await handle(request);
1570
+ res.writeHead(response.status, Object.fromEntries(response.headers));
1571
+ if (!response.body)
1572
+ return void res.end();
1573
+ Readable.fromWeb(response.body).pipe(res);
1574
+ }
1575
+ catch (e) {
1576
+ next(e);
1577
+ }
1578
+ });
1579
+ },
1580
+ async transform(code, id, opts) {
1581
+ const file = id.split('?')[0];
1582
+ if (id.startsWith('\0'))
1583
+ return null;
1584
+ const client = !opts?.ssr && !isVitest;
1585
+ if (client && o.contracts && contractFiles().has(file)) {
1586
+ const mod = await load(file);
1587
+ const out = [];
1588
+ Object.keys(mod).forEach((name) => {
1589
+ const { manifest, hash, count } = contractManifest(mod[name]);
1590
+ if (!count)
1591
+ return;
1592
+ out.push(exportLine(name, `Object.defineProperty(${JSON.stringify(manifest)}, '~hash', { value: ${JSON.stringify(hash)} })`));
1593
+ });
1594
+ return { code: `// ${rel(file)}: routes and hash only (sygnal/vite api.contracts)\n${out.join('\n')}\n`, map: { mappings: '' } };
1595
+ }
1596
+ if (!isFnFile(file))
1597
+ return null;
1598
+ if (client) {
1599
+ const [f] = fnFiles({ [rel(file)]: await load(file) }, dir);
1600
+ const opts = JSON.stringify({ prefix, headers: { [header]: `${f.ns}:${f.hash}` } });
1601
+ const lines = f.fns.map(([name, meta]) => {
1602
+ const errors = {};
1603
+ Object.keys(meta.errors || {}).forEach((c) => { errors[c] = { status: meta.errors[c]?.status }; });
1604
+ return exportLine(name, `__serverFunction(${JSON.stringify(f.keys.concat(name).join('.'))}, ${JSON.stringify({ kind: meta.kind, errors })}, __o)`);
1605
+ });
1606
+ return {
1607
+ code: `// ${rel(file)}: client stubs (sygnal/vite api.functions)\nimport { __serverFunction } from 'sygnal/api';\nconst __o = ${opts};\n${lines.join('\n')}\n`,
1608
+ map: { mappings: '' },
1609
+ };
1610
+ }
1611
+ // server code and tests: the module as written, each export given its id
1612
+ const ns = fnNamespace(rel(file), dir);
1613
+ const named = exportedLocals(this.parse(code));
1614
+ if (!named.length)
1615
+ return null;
1616
+ const keys = ns.split('/');
1617
+ const tail = named.map(([exp, local]) => `__sygnalFn(${JSON.stringify(keys.concat(exp).join('.'))}, ${local}, ${JSON.stringify({ prefix })});`);
1618
+ return { code: `${code}\nimport { __serverFunction as __sygnalFn } from 'sygnal/api';\n${tail.join('\n')}\n`, map: null };
1619
+ },
1620
+ async closeBundle() {
1621
+ if (temp) {
1622
+ const s = await temp;
1623
+ temp = undefined;
1624
+ await s.close();
1625
+ }
1626
+ },
1627
+ };
1628
+ }
1629
+ /** [exported name, local binding] of a module's own named exports (declarations and `export { a as b }`) */
1630
+ function exportedLocals(ast) {
1631
+ const out = [];
1632
+ for (const node of ast.body || []) {
1633
+ if (node.type !== 'ExportNamedDeclaration' || node.source)
1634
+ continue;
1635
+ const d = node.declaration;
1636
+ if (d?.type === 'VariableDeclaration')
1637
+ d.declarations.forEach((x) => x.id?.type === 'Identifier' && out.push([x.id.name, x.id.name]));
1638
+ else if (d?.id?.name)
1639
+ out.push([d.id.name, d.id.name]);
1640
+ for (const s of node.specifiers || []) {
1641
+ const exp = s.exported?.name ?? s.exported?.value;
1642
+ if (s.local?.name && exp)
1643
+ out.push([exp, s.local.name]);
1644
+ }
1645
+ }
1646
+ return out;
1647
+ }
1648
+ /** the project's own Vite (not one next to sygnal), as an ESM URL */
1649
+ function projectVite(root) {
1650
+ const req = createRequire(path.join(root, 'package.json'));
1651
+ const pj = req.resolve('vite/package.json');
1652
+ const pkg = JSON.parse(fs.readFileSync(pj, 'utf8'));
1653
+ const dot = typeof pkg.exports === 'string' ? pkg.exports : pkg.exports?.['.'];
1654
+ let entry = typeof dot === 'string' ? dot : dot?.import ?? pkg.module ?? pkg.main;
1655
+ if (entry && typeof entry === 'object')
1656
+ entry = entry.default;
1657
+ return pathToFileURL(path.join(path.dirname(pj), entry)).href;
1658
+ }
1659
+
1660
+ /*
1661
+ * The QuickJS WASM module: `quickjs-emscripten-core` + `@jitl/quickjs-wasmfile-release-sync`,
1662
+ * optional peer dependencies (D209), loaded on first use. Stock packages only (README §9 Q6):
1663
+ * a quickjs-ng variant can be passed as `variant`.
1664
+ */
1665
+ // Node built-ins by a name bundlers don't follow (a browser bundle never loads them)
1666
+ const nodeImport = (name) => import(/* @vite-ignore */ /* webpackIgnore: true */ name);
1667
+
1668
+ /*
1669
+ * The guest kernel, built from the app's installed packages (README §7 option (a), §9 Q1; D209:
1670
+ * Sygnal's npm package never ships xstream or snabbdom prebuilt into it). sygnal/vite resolves
1671
+ * `sygnal/sandbox/kernel?raw` to this function's output (the project's own Vite), and
1672
+ * `buildSandboxKernel()` (sygnal/sandbox/build) does it for other toolchains (Vite, esbuild or
1673
+ * Rollup, whichever the project has).
1674
+ *
1675
+ * The output is one script: the platform prelude (timers, console, microtasks on `__host`,
1676
+ * which the core's modules read when they load), then dist/sandbox-kernel.esm.js bundled as an
1677
+ * IIFE with the installed sygnal (dist/index.esm.js and dist/host.esm.js of the same package: one
1678
+ * core), xstream and snabbdom. `process.env.NODE_ENV` is 'production'; xstream's `globalthis`
1679
+ * dependency is Sygnal's shim (as sygnal/vite does, G-099).
1680
+ */
1681
+ // The platform a bare QuickJS context lacks. A plain script, so it runs before the bundle's modules.
1682
+ const PLATFORM = `var global = globalThis;
1683
+ (function () {
1684
+ var H = globalThis.__host, G = globalThis, timers = new Map(), intervals = new Map(), seq = 0;
1685
+ G.__sandboxTimers = timers;
1686
+ G.queueMicrotask = G.queueMicrotask || function (f) { Promise.resolve().then(f) };
1687
+ G.setTimeout = function (f, ms) { var id = ++seq; timers.set(id, f); H.timer(id, +ms || 0); return id };
1688
+ G.clearTimeout = function (id) { if (timers.delete(id)) H.clearTimer(id) };
1689
+ // the core's macrotask hop (runtime.ts macro()): its own channel, so a component's timers can't starve it
1690
+ G.setImmediate = function (f) { var id = ++seq; timers.set(id, f); H.timer(id, -1); return id };
1691
+ G.clearImmediate = G.clearTimeout;
1692
+ G.setInterval = function (f, ms) { var box = {}; var tick = function () { box.id = G.setTimeout(tick, ms); f() }; box.id = G.setTimeout(tick, ms); var id = ++seq; intervals.set(id, box); return id };
1693
+ G.clearInterval = function (id) { var b = intervals.get(id); if (b) { intervals.delete(id); G.clearTimeout(b.id) } };
1694
+ G.requestAnimationFrame = function (f) { return G.setTimeout(function () { f(Date.now()) }, 16) };
1695
+ G.cancelAnimationFrame = G.clearTimeout;
1696
+ var fmt = function (a) { return a.map(function (x) { if (typeof x == 'string') return x; if (x instanceof Error) return x.name + ': ' + x.message; try { return JSON.stringify(x) } catch (e) { return String(x) } }).join(' ') };
1697
+ G.console = {
1698
+ log: function () { H.log('log', fmt([].slice.call(arguments))) }, info: function () { H.log('log', fmt([].slice.call(arguments))) },
1699
+ warn: function () { H.log('warn', fmt([].slice.call(arguments))) }, error: function () { H.log('error', fmt([].slice.call(arguments))) },
1700
+ debug: function () {},
1701
+ };
1702
+ })();
1703
+ `;
1704
+ /** The guest kernel script for createSandbox / <Sandboxed> (`kernel`), built from the installed packages */
1705
+ async function buildSandboxKernel(opts = {}) {
1706
+ const [path, { createRequire }, { pathToFileURL }, fs] = await Promise.all([nodeImport('node:path'), nodeImport('node:module'), nodeImport('node:url'), nodeImport('node:fs')]);
1707
+ const root = path.resolve(opts.root || globalThis.process.cwd());
1708
+ const fromRoot = createRequire(path.join(root, 'package.json'));
1709
+ // the app's sygnal (else this one): its dist/ holds the kernel entry, the core and the kit
1710
+ let main;
1711
+ try {
1712
+ main = fromRoot.resolve('sygnal');
1713
+ }
1714
+ catch {
1715
+ main = createRequire(import.meta.url).resolve('sygnal');
1716
+ }
1717
+ const dist = path.dirname(main);
1718
+ const entry = path.join(dist, 'sandbox-kernel.esm.js');
1719
+ const files = {
1720
+ sygnal: path.join(dist, 'index.esm.js'),
1721
+ 'sygnal/host': path.join(dist, 'host.esm.js'),
1722
+ globalthis: path.join(dist, 'shims/globalthis.cjs'),
1723
+ };
1724
+ for (const f of [entry, ...Object.values(files)])
1725
+ if (!fs.existsSync(f))
1726
+ throw new Error(`buildSandboxKernel: ${f} is missing (an incomplete sygnal install?)`);
1727
+ const minify = opts.minify !== false;
1728
+ const define = { 'process.env.NODE_ENV': '"production"' };
1729
+ const has = (name) => { try {
1730
+ fromRoot.resolve(name);
1731
+ return true;
1732
+ }
1733
+ catch {
1734
+ return false;
1735
+ } };
1736
+ const want = opts.bundler || 'auto';
1737
+ const bundler = want !== 'auto' ? want : has('vite') ? 'vite' : has('esbuild') ? 'esbuild' : has('rollup') ? 'rollup' : null;
1738
+ if (!bundler)
1739
+ throw new Error('buildSandboxKernel: needs vite, esbuild or rollup installed in ' + root);
1740
+ const load = (name) => import(/* @vite-ignore */ pathToFileURL(resolveEsm(fromRoot, fs, path, name)).href);
1741
+ let code;
1742
+ if (bundler === 'esbuild') {
1743
+ const esbuild = await load('esbuild');
1744
+ const r = await (esbuild.build || esbuild.default.build)({
1745
+ entryPoints: [entry], bundle: true, write: false, format: 'iife', platform: 'browser', target: 'es2020',
1746
+ minify, define, alias: files, logLevel: 'silent', legalComments: 'none',
1747
+ });
1748
+ code = r.outputFiles[0].text;
1749
+ }
1750
+ else if (bundler === 'rollup') {
1751
+ const { rollup } = await load('rollup');
1752
+ const plugins = [{
1753
+ name: 'sygnal-kernel', resolveId: (id) => files[id] || null,
1754
+ transform: (c) => (c.includes('process.env.NODE_ENV') ? c.split('process.env.NODE_ENV').join(define['process.env.NODE_ENV']) : null),
1755
+ }];
1756
+ const req = async (n) => { const m = await load(n); return m.default || m; };
1757
+ if (has('@rollup/plugin-node-resolve'))
1758
+ plugins.push((await load('@rollup/plugin-node-resolve')).nodeResolve({ browser: true }));
1759
+ if (has('@rollup/plugin-commonjs'))
1760
+ plugins.push((await req('@rollup/plugin-commonjs'))());
1761
+ if (minify && has('@rollup/plugin-terser'))
1762
+ plugins.push((await req('@rollup/plugin-terser'))({ ecma: 2020 }));
1763
+ const b = await rollup({ input: entry, plugins, onwarn: () => { } });
1764
+ code = (await b.generate({ format: 'iife', name: '__sygnalKernel' })).output[0].code;
1765
+ await b.close();
1766
+ }
1767
+ else {
1768
+ const vite = await load('vite');
1769
+ const out = await vite.build({
1770
+ configFile: false, root, logLevel: 'silent', mode: 'production', define,
1771
+ resolve: { alias: Object.entries(files).map(([find, replacement]) => ({ find: new RegExp(`^${find.replace('/', '\\/')}$`), replacement })) },
1772
+ build: { write: false, minify, target: 'es2020', lib: { entry, formats: ['iife'], name: '__sygnalKernel', fileName: () => 'kernel.js' }, rollupOptions: { output: { inlineDynamicImports: true } } },
1773
+ });
1774
+ const o = (Array.isArray(out) ? out[0] : out).output;
1775
+ code = o.find((c) => c.type === 'chunk').code;
1776
+ }
1777
+ return PLATFORM + code;
1778
+ }
1779
+ // a package's ESM entry, as resolved from the app (import() needs a file URL, not a bare name, to resolve from there)
1780
+ function resolveEsm(req, fs, path, name) {
1781
+ try {
1782
+ const pj = req.resolve(`${name}/package.json`);
1783
+ const pkg = JSON.parse(fs.readFileSync(pj, 'utf8'));
1784
+ const dot = typeof pkg.exports == 'string' ? pkg.exports : pkg.exports?.['.'];
1785
+ let e = typeof dot == 'string' ? dot : dot?.import ?? pkg.module ?? pkg.main ?? 'index.js';
1786
+ if (e && typeof e == 'object')
1787
+ e = e.default;
1788
+ return path.join(path.dirname(pj), e);
1789
+ }
1790
+ catch {
1791
+ return req.resolve(name);
1792
+ }
1793
+ }
1794
+
1795
+ /**
1796
+ * sygnal/sandbox's guest kernel in sygnal/vite (README §7 (a), D209): `import kernel from
1797
+ * 'sygnal/sandbox/kernel?raw'` (or without `?raw`) resolves to a module whose default export is
1798
+ * the kernel script, built once per server or build with the project's own Vite from the
1799
+ * installed sygnal, xstream and snabbdom (src/sandbox/build.ts). Nothing is shipped prebuilt.
1800
+ */
1801
+ const SANDBOX_KERNEL = /^sygnal\/sandbox\/kernel(\?raw)?$/;
1802
+ const SANDBOX_KERNEL_ID = '\0sygnal-sandbox-kernel';
1803
+ const built = new Map();
1804
+ async function sandboxKernelModule(root) {
1805
+ if (!built.has(root))
1806
+ built.set(root, buildSandboxKernel({ root, bundler: 'vite' }));
1807
+ return `// sygnal/sandbox's guest kernel, built from the installed packages (sygnal/vite)\nexport default ${JSON.stringify(await built.get(root))};\n`;
1808
+ }
1809
+
892
1810
  /**
893
1811
  * Sygnal Vite Plugin
894
1812
  *
@@ -1111,7 +2029,41 @@ function sygnal(options = {}) {
1111
2029
  let devSnippet = devtoolsImport + (devOn ? flags + devImports : '');
1112
2030
  const devInject = devOn || devtoolsOn;
1113
2031
  let astroClient;
1114
- return {
2032
+ // CV-2: its own plugin, before Vite's JSX transform (this plugin's transform runs after it)
2033
+ let warnedCompile = false;
2034
+ const co = options.compile === true ? {} : options.compile || null;
2035
+ const compileOn = (k) => !!co && co[k] !== false;
2036
+ // (its own Vitest flag: it may be used without the main plugin, e.g. added to a config that already
2037
+ // has sygnal())
2038
+ let cVitest = false;
2039
+ const compilePlugin = co && {
2040
+ name: 'vite-plugin-sygnal:compile',
2041
+ enforce: 'pre',
2042
+ config() {
2043
+ cVitest = !!nodeProcess?.env?.VITEST;
2044
+ },
2045
+ transform(code, id, opts) {
2046
+ const isVitest = cVitest;
2047
+ const file = id.split('?')[0];
2048
+ if (!/\.[jt]sx$/.test(file) || file.includes('/node_modules/') || id.startsWith('\0'))
2049
+ return null;
2050
+ if (isVitest && !compileOn('test'))
2051
+ return null;
2052
+ // server code renders to strings (never under Vitest: renderComponent's mock DOM needs vnodes)
2053
+ const ssr = !!opts?.ssr && !isVitest && compileOn('ssr');
2054
+ if (!ssr && !compileOn('views'))
2055
+ return null;
2056
+ if (viteMajor(this, root) < 8) {
2057
+ if (!warnedCompile) {
2058
+ warnedCompile = true;
2059
+ this.warn('[sygnal] `compile` needs Vite 8; views are not compiled');
2060
+ }
2061
+ return null;
2062
+ }
2063
+ return compileViews(code, file, (c, o) => this.parse(c, o), { ssr, blocks: !ssr && compileOn('blocks') });
2064
+ },
2065
+ };
2066
+ const main = {
1115
2067
  name: 'vite-plugin-sygnal',
1116
2068
  config(config, env) {
1117
2069
  isServe = env.command === 'serve';
@@ -1188,7 +2140,9 @@ function sygnal(options = {}) {
1188
2140
  const include = result.optimizeDeps?.include || [];
1189
2141
  result.optimizeDeps = {
1190
2142
  ...result.optimizeDeps,
1191
- exclude: [VIKE_CLIENT],
2143
+ // sygnal/sandbox: its worker script is found by `new URL(..., import.meta.url)`, which
2144
+ // only works served unbundled
2145
+ exclude: [VIKE_CLIENT, 'sygnal/sandbox'],
1192
2146
  ...(sygnalInNodeModules(root) ? { include: [...include, 'sygnal'] } : {}),
1193
2147
  };
1194
2148
  }
@@ -1219,6 +2173,9 @@ function sygnal(options = {}) {
1219
2173
  resolveId: {
1220
2174
  order: 'pre',
1221
2175
  async handler(source, importer, opts) {
2176
+ // sygnal/sandbox: the guest kernel, built from the installed packages (every mode)
2177
+ if (SANDBOX_KERNEL.test(source))
2178
+ return SANDBOX_KERNEL_ID;
1222
2179
  if (!isServe || isVitest)
1223
2180
  return null;
1224
2181
  if (source === DEV_CLIENT)
@@ -1245,6 +2202,8 @@ function sygnal(options = {}) {
1245
2202
  },
1246
2203
  },
1247
2204
  load(id) {
2205
+ if (id === SANDBOX_KERNEL_ID)
2206
+ return sandboxKernelModule(root);
1248
2207
  if (id === DEV_CLIENT_ID)
1249
2208
  return devClientModule();
1250
2209
  if (id === MCP_CLIENT_ID && mcpOn)
@@ -1313,6 +2272,9 @@ function sygnal(options = {}) {
1313
2272
  });
1314
2273
  },
1315
2274
  };
2275
+ const api = options.api ? apiPlugin(options.api) : null;
2276
+ const plugins = [compilePlugin, main, api].filter(Boolean);
2277
+ return plugins.length > 1 ? plugins : main;
1316
2278
  function devTransform(code, id, opts) {
1317
2279
  if (!isServe)
1318
2280
  return null;
@@ -1471,10 +2433,12 @@ const ADAPTER_PEERS = {
1471
2433
  'ui/menu': ['@zag-js/vanilla', '@zag-js/menu'],
1472
2434
  'ui/select': ['@zag-js/vanilla', '@zag-js/select'],
1473
2435
  'ui/combobox': ['@zag-js/vanilla', '@zag-js/combobox'],
2436
+ // sygnal/sandbox: QuickJS and its WASM variant (stock packages, README §9 Q6)
2437
+ sandbox: ['quickjs-emscripten-core', '@jitl/quickjs-wasmfile-release-sync'],
1474
2438
  };
1475
2439
  // G-440: a dynamic import's specifier may be a template literal without substitutions
1476
- const ADAPTER_RE = /(?:\bfrom|\bimport)\s*\(?\s*(['"`])sygnal\/(react|zag|ui\/(?:menu|select|combobox))\1/g;
1477
- const ADAPTER_ID = /^sygnal\/(react|zag|ui\/(?:menu|select|combobox))$/;
2440
+ const ADAPTER_RE = /(?:\bfrom|\bimport)\s*\(?\s*(['"`])sygnal\/(react|zag|sandbox|ui\/(?:menu|select|combobox))\1/g;
2441
+ const ADAPTER_ID = /^sygnal\/(react|zag|sandbox|ui\/(?:menu|select|combobox))$/;
1478
2442
  /**
1479
2443
  * The adapter entries an app module imports (statically, dynamically or in a re-export), or null.
1480
2444
  * G-415: real imports only. The module's AST from the plugin context's parse() (Vite and Rollup