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
@@ -4,6 +4,8 @@ var fs = require('node:fs');
4
4
  var path = require('node:path');
5
5
  var node_module = require('node:module');
6
6
  var node_url = require('node:url');
7
+ var magicString = require('magic-string');
8
+ var node_stream = require('node:stream');
7
9
 
8
10
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
9
11
  /**
@@ -892,6 +894,922 @@ function printGraphUrl(server, available = true) {
892
894
  };
893
895
  }
894
896
 
897
+ /**
898
+ * CV-2: compiled views, tier A (dev-plans/research/compile-wasm/compiled-views-plan.md). Rewrites a
899
+ * module's JSX before the JSX transform: an HTML element (not a form field, custom element or SVG
900
+ * tag) with plain attributes becomes a call to the pragma's `__cv.e(sel, data, children, key)`,
901
+ * its data made by the pragma's own sanitizeData (`__cv.sd`): once at module load (hoisted) when
902
+ * every attribute is static, else per render. Everything else (components, markers, fragments,
903
+ * fields, spreads, refs, ...) is left as JSX for the JSX transform; their children may still
904
+ * compile. The output vnodes are the ones the pragma would make (test/cv-compile.test.js).
905
+ *
906
+ * CV-1 (`ssr`, server builds): the outermost compiled element stays a vnode (`__cvs.r`: the
907
+ * root's `data-sygnal-ssr` stamp and the SSR walk need one) whose inside is HTML parts made by
908
+ * ssr.ts (`__cvs.f`, `__cvs.s`): open tags and static subtrees serialized by its own vnodeToHtml
909
+ * (cached per escaping mode), text escaped by its escapeHtml, anything not compiled (components,
910
+ * markers, fields, ...) kept as a vnode at the child path the JSX runtime's tree would give it.
911
+ * The HTML is renderToString's (test/cv-ssr.test.js).
912
+ *
913
+ * `parse` is Vite 8's parseAst (ESTree + JSX, UTF-16 offsets). The source map maps each rewritten
914
+ * JSX expression to its start (magic-string).
915
+ */
916
+ // the named export: magic-string's CJS build has no plain default for the plugin's CJS output
917
+ // HTML elements the compiler takes. Not here: the form fields (input, textarea, select, option:
918
+ // the controlled-input module re-syncs them; the pragma never makes them plain), SVG and MathML
919
+ // (the pragma sets the namespace), hyphenated custom elements, and any other lowercase tag (a host
920
+ // renderer's own tags go through the pragma, which knows its run-time tag lists)
921
+ const HTML = new Set(('a abbr address article aside audio b bdi bdo blockquote br button canvas caption cite code col colgroup ' +
922
+ '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 ' +
923
+ '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 ' +
924
+ '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 ' +
925
+ 'video wbr').split(' '));
926
+ // attributes the pragma gives run-time behaviour (hooks) or that change what the element is
927
+ const BAIL_ATTR = /^(ref|autoFocus|autoSelect|hook|children|key)$/;
928
+ // in server builds also: the SSR serializer gives these their own content
929
+ const SSR_BAIL_ATTR = /^(innerHTML|textContent)$/;
930
+ // an HTML entity: JSX decodes them, the parser keeps them raw (the pragma path decodes through the JSX transform)
931
+ const ENTITY = /&(#\d+|#x[\da-f]+|[a-z][a-z\d]*);/i;
932
+ function compileViews(code, id, parse, opts = {}) {
933
+ const ssr = !!opts.ssr, blocks = !ssr && !!opts.blocks;
934
+ let nblocks = 0;
935
+ if (!/<[a-z]/.test(code))
936
+ return null;
937
+ let ast;
938
+ try {
939
+ ast = parse(code, { lang: /\.tsx$/.test(id) ? 'tsx' : /\.ts$/.test(id) ? 'ts' : 'jsx' });
940
+ }
941
+ catch {
942
+ return null;
943
+ }
944
+ const hoisted = [];
945
+ let compiled = 0;
946
+ // CV-3: the JSX a component (a capitalized function) returns directly: its view's root
947
+ const roots = new Set();
948
+ if (blocks) {
949
+ const un = (n) => n?.type == 'ParenthesizedExpression' ? un(n.expression) : n;
950
+ const fnRoots = (f) => {
951
+ if (!f)
952
+ return;
953
+ if (f.expression) {
954
+ roots.add(un(f.body));
955
+ return;
956
+ }
957
+ for (const st of f.body?.body || [])
958
+ if (st.type == 'ReturnStatement')
959
+ roots.add(un(st.argument));
960
+ };
961
+ const scan = (n) => {
962
+ if (!n || typeof n != 'object')
963
+ return;
964
+ if (n.type == 'FunctionDeclaration' && /^[A-Z]/.test(n.id?.name || ''))
965
+ fnRoots(n);
966
+ if (n.type == 'VariableDeclarator' && /^[A-Z]/.test(n.id?.name || '') && /Function/.test(n.init?.type || ''))
967
+ fnRoots(n.init);
968
+ for (const k in n) {
969
+ if (k == 'start' || k == 'end' || k == 'loc')
970
+ continue;
971
+ const v = n[k];
972
+ if (Array.isArray(v))
973
+ v.forEach(scan);
974
+ else if (v && typeof v == 'object' && typeof v.type == 'string')
975
+ scan(v);
976
+ }
977
+ };
978
+ scan(ast);
979
+ }
980
+ const src = (s, e, subs) => {
981
+ let out = '', at = s;
982
+ for (const [a, b, t] of subs.sort((x, y) => x[0] - y[0])) {
983
+ out += code.slice(at, a) + t;
984
+ at = b;
985
+ }
986
+ return out + code.slice(at, e);
987
+ };
988
+ // every maximal JSX node under `node`, rewritten (a JSX node in expression position)
989
+ const subsIn = (node, subs = []) => {
990
+ if (!node || typeof node != 'object')
991
+ return subs;
992
+ if (node.type == 'JSXElement' || node.type == 'JSXFragment') {
993
+ subs.push([node.start, node.end, jsx(node)]);
994
+ return subs;
995
+ }
996
+ for (const k in node) {
997
+ if (k == 'start' || k == 'end' || k == 'loc' || k == 'range')
998
+ continue;
999
+ const v = node[k];
1000
+ if (Array.isArray(v))
1001
+ for (const c of v)
1002
+ subsIn(c, subs);
1003
+ else if (v && typeof v == 'object' && typeof v.type == 'string')
1004
+ subsIn(v, subs);
1005
+ }
1006
+ return subs;
1007
+ };
1008
+ const expr = (e) => src(e.start, e.end, subsIn(e));
1009
+ const tag = (el) => el.openingElement.name.type == 'JSXIdentifier' ? el.openingElement.name.name : null;
1010
+ const compilable = (el) => {
1011
+ const t = tag(el);
1012
+ if (!t || !HTML.has(t))
1013
+ return false;
1014
+ for (const a of el.openingElement.attributes) {
1015
+ if (a.type != 'JSXAttribute' || a.name.type != 'JSXIdentifier')
1016
+ return false;
1017
+ if (BAIL_ATTR.test(a.name.name) && a.name.name != 'key' || ssr && SSR_BAIL_ATTR.test(a.name.name))
1018
+ return false;
1019
+ const v = a.value;
1020
+ if (v && v.type == 'Literal' && ENTITY.test(String(v.value)))
1021
+ return false;
1022
+ if (v && v.type != 'Literal' && !(v.type == 'JSXExpressionContainer' && v.expression.type != 'JSXEmptyExpression'))
1023
+ return false;
1024
+ }
1025
+ for (const c of el.children)
1026
+ if (c.type == 'JSXText' && ENTITY.test(c.value) || c.type == 'JSXSpreadChild')
1027
+ return false;
1028
+ return true;
1029
+ };
1030
+ // no expression anywhere below (attributes, children): its HTML never changes (CV-1)
1031
+ const isStatic = (el) => compilable(el)
1032
+ && el.openingElement.attributes.every((a) => !a.value || a.value.type == 'Literal')
1033
+ && el.children.every((c) => c.type == 'JSXText' || c.type == 'JSXElement' && isStatic(c));
1034
+ // a JSX node in expression position: compiled, or JSX whose children may compile
1035
+ // (`vnodes`: inside a static SSR subtree's vnode factory, every element is a client one)
1036
+ let vnodes = 0;
1037
+ const jsx = (node) => node.type == 'JSXElement' && compilable(node)
1038
+ ? (ssr && !vnodes ? ssrRoot(node) : blocks && !vnodes && roots.has(node) && blockable(node) ? block(node) : element(node)) : jsxSource(node);
1039
+ const jsxSource = (node) => {
1040
+ const subs = [];
1041
+ const open = node.openingElement;
1042
+ if (open)
1043
+ for (const a of open.attributes) {
1044
+ const v = a.type == 'JSXSpreadAttribute' ? a.argument : a.value?.type == 'JSXExpressionContainer' ? a.value.expression : null;
1045
+ if (v)
1046
+ subsIn(v, subs);
1047
+ }
1048
+ for (const c of node.children) {
1049
+ if (c.type == 'JSXElement')
1050
+ subs.push([c.start, c.end, compilable(c) ? '{' + jsx(c) + '}' : jsxSource(c)]);
1051
+ else if (c.type == 'JSXFragment')
1052
+ subs.push([c.start, c.end, jsxSource(c)]);
1053
+ else if (c.type == 'JSXExpressionContainer')
1054
+ subsIn(c.expression, subs);
1055
+ }
1056
+ return src(node.start, node.end, subs);
1057
+ };
1058
+ // the element's data expression and key (a static one hoisted; see __cv in src/pragma/index.ts)
1059
+ const dataOf = (el) => {
1060
+ let key = 'undefined', dynamic = false;
1061
+ const names = [], vals = [];
1062
+ for (const a of el.openingElement.attributes) {
1063
+ const name = a.name.name, v = a.value;
1064
+ const val = !v ? 'true' : v.type == 'Literal' ? JSON.stringify(v.value) : (dynamic = name != 'key' || dynamic, '(' + expr(v.expression) + ')');
1065
+ if (name == 'key')
1066
+ key = val;
1067
+ // a duplicate keeps its first place and takes the last value, as in an object literal
1068
+ else {
1069
+ const i = names.indexOf(name);
1070
+ if (i >= 0)
1071
+ vals[i] = val;
1072
+ else
1073
+ names.push(name), vals.push(val);
1074
+ }
1075
+ }
1076
+ // static: made once at load by the pragma's sanitizeData; dynamic: the keys routed once at
1077
+ // load by the pragma's route(), the data made per render from the values (__cv.d)
1078
+ const data = !dynamic ? hoist('__cv.sd({' + names.map((n, i) => JSON.stringify(n) + ': ' + vals[i]).join(', ') + '})')
1079
+ : `__cv.d(${hoist('__cv.r(' + JSON.stringify(names) + ')')}, [${vals.join(', ')}])`;
1080
+ return [data, key, dynamic];
1081
+ };
1082
+ const childArg = (kids) => kids.length == 0 ? 'undefined' : kids.length == 1 ? kids[0] : '[' + kids.join(', ') + ']';
1083
+ const element = (el) => {
1084
+ compiled++;
1085
+ const [data, key] = dataOf(el);
1086
+ const c = childArg(children(el.children, false));
1087
+ return `__cv.e(${JSON.stringify(tag(el))}, ${data}, ${c}${key == 'undefined' ? '' : ', ' + key})`;
1088
+ };
1089
+ // CV-1: the outermost compiled element in server code: a vnode with HTML parts inside
1090
+ const ssrRoot = (el) => {
1091
+ compiled++;
1092
+ const [data, key] = dataOf(el);
1093
+ return `__cvs.r(${JSON.stringify(tag(el))}, ${data}, ${childArg(children(el.children, true))}${key == 'undefined' ? '' : ', ' + key})`;
1094
+ };
1095
+ // CV-1: a compiled element inside one: a static one's HTML made once (per escaping mode), else
1096
+ // its open tag (static: once) and its children's parts
1097
+ const ssrInner = (el) => {
1098
+ compiled++;
1099
+ if (isStatic(el)) {
1100
+ vnodes++;
1101
+ const v = element(el);
1102
+ vnodes--;
1103
+ return `__cvs.s(${hoist('__cvs.S(() => ' + v + ')')})`;
1104
+ }
1105
+ const [data, , dynamic] = dataOf(el), t = JSON.stringify(tag(el));
1106
+ const open = dynamic ? `__cvs.od(${t}, ${data})` : hoist(`__cvs.O(${t}, ${data})`);
1107
+ return `__cvs.f(${open}, ${childArg(children(el.children, true))})`;
1108
+ };
1109
+ // CV-1: JSX in a direct value position of a compiled element's child expression (the expression,
1110
+ // a ?: branch, either side of && || ??, an array element, a .map() arrow's expression body) can
1111
+ // only become that element's child, so a compilable one is an HTML chunk; anything else (JSX in a
1112
+ // variable, a call's argument, a component prop) stays a vnode
1113
+ // (client builds with blocks: a .map() row there is a block, `row`: inside a .map() body)
1114
+ const direct = (n, subs, row = false) => {
1115
+ if (!n)
1116
+ return;
1117
+ const t = n.type;
1118
+ if (t == 'JSXElement' && compilable(n))
1119
+ subs.push([n.start, n.end, ssr ? ssrInner(n) : row && blockable(n) ? block(n) : jsx(n)]);
1120
+ else if (t == 'ParenthesizedExpression')
1121
+ direct(n.expression, subs, row);
1122
+ else if (t == 'ConditionalExpression') {
1123
+ subsIn(n.test, subs);
1124
+ direct(n.consequent, subs, row);
1125
+ direct(n.alternate, subs, row);
1126
+ }
1127
+ else if (t == 'LogicalExpression') {
1128
+ direct(n.left, subs, row);
1129
+ direct(n.right, subs, row);
1130
+ }
1131
+ else if (t == 'ArrayExpression') {
1132
+ for (const e of n.elements)
1133
+ e && (e.type == 'SpreadElement' ? subsIn(e, subs) : direct(e, subs, row));
1134
+ }
1135
+ else if (t == 'CallExpression' && n.callee.type == 'MemberExpression' && !n.callee.computed && n.callee.property.name == 'map'
1136
+ && n.arguments.length == 1 && n.arguments[0].type == 'ArrowFunctionExpression' && n.arguments[0].expression) {
1137
+ subsIn(n.callee, subs);
1138
+ for (const p of n.arguments[0].params)
1139
+ subsIn(p, subs);
1140
+ direct(n.arguments[0].body, subs, true);
1141
+ }
1142
+ else
1143
+ subsIn(n, subs);
1144
+ };
1145
+ // CV-3: a block: compilable all the way down, at least two elements, children only text, text
1146
+ // holes (an expression with no JSX in it; not `children` / `slots`, which are vnodes) and such
1147
+ // elements
1148
+ const SLOTISH = /^(children|slots)$|\.(children|slots)$/;
1149
+ const hasJsx = (n) => {
1150
+ if (!n || typeof n != 'object')
1151
+ return false;
1152
+ if (n.type == 'JSXElement' || n.type == 'JSXFragment')
1153
+ return true;
1154
+ for (const k in n) {
1155
+ if (k == 'start' || k == 'end' || k == 'loc')
1156
+ continue;
1157
+ const v = n[k];
1158
+ if (Array.isArray(v) ? v.some(hasJsx) : v && typeof v == 'object' && typeof v.type == 'string' && hasJsx(v))
1159
+ return true;
1160
+ }
1161
+ return false;
1162
+ };
1163
+ const blockable = (el, top = true) => {
1164
+ if (!compilable(el))
1165
+ return false;
1166
+ let n = 0;
1167
+ const ok = (e) => {
1168
+ if (!compilable(e))
1169
+ return false;
1170
+ n++;
1171
+ for (const c of e.children) {
1172
+ if (c.type == 'JSXElement') {
1173
+ if (!ok(c))
1174
+ return false;
1175
+ }
1176
+ else if (c.type == 'JSXExpressionContainer') {
1177
+ if (c.expression.type == 'JSXEmptyExpression')
1178
+ continue;
1179
+ if (hasJsx(c.expression) || SLOTISH.test(code.slice(c.expression.start, c.expression.end).trim()))
1180
+ return false;
1181
+ }
1182
+ else if (c.type != 'JSXText')
1183
+ return false;
1184
+ }
1185
+ return true;
1186
+ };
1187
+ return ok(el) && (!top || n >= 2);
1188
+ };
1189
+ const block = (el) => {
1190
+ compiled++;
1191
+ nblocks++;
1192
+ const vals = [], kinds = [];
1193
+ const hole = (k, v) => (kinds.push(k), vals.push(v), `h(${vals.length - 1})`);
1194
+ const [rootData, key] = dataOf(el);
1195
+ hole('r', rootData);
1196
+ const bel = (e, root) => {
1197
+ let d;
1198
+ if (root)
1199
+ d = 'h(0)';
1200
+ else {
1201
+ const [data, , dynamic] = dataOf(e);
1202
+ d = dynamic ? hole('d', data) : data;
1203
+ }
1204
+ const kids = [];
1205
+ for (const c of e.children) {
1206
+ if (c.type == 'JSXText') {
1207
+ const t = cleanText(c.value);
1208
+ if (t)
1209
+ kids.push(JSON.stringify(t));
1210
+ }
1211
+ else if (c.type == 'JSXExpressionContainer') {
1212
+ if (c.expression.type != 'JSXEmptyExpression')
1213
+ kids.push(hole('t', '(' + expr(c.expression) + ')'));
1214
+ }
1215
+ else
1216
+ kids.push(bel(c, false));
1217
+ }
1218
+ return `__cv.e(${JSON.stringify(tag(e))}, ${d}, ${childArg(kids)})`;
1219
+ };
1220
+ const body = bel(el, true);
1221
+ const def = hoist(`__cvb.b((h) => ${body}, ${JSON.stringify(kinds.join(''))})`);
1222
+ return `__cvb.B(${def}, [${vals.join(', ')}]${key == 'undefined' ? '' : ', ' + key})`;
1223
+ };
1224
+ const hoist = (obj) => {
1225
+ const i = hoisted.indexOf(obj);
1226
+ if (i >= 0)
1227
+ return '$cv' + i;
1228
+ hoisted.push(obj);
1229
+ return '$cv' + (hoisted.length - 1);
1230
+ };
1231
+ // the children as the JSX transform passes them: JSX text cleaned as the JSX spec does (lines
1232
+ // trimmed, empty lines dropped, joined by a space), empty expressions dropped
1233
+ const children = (list, html) => {
1234
+ const out = [];
1235
+ for (const c of list) {
1236
+ if (c.type == 'JSXText') {
1237
+ const t = cleanText(c.value);
1238
+ if (t)
1239
+ out.push(JSON.stringify(t));
1240
+ }
1241
+ else if (c.type == 'JSXExpressionContainer') {
1242
+ const e = c.expression;
1243
+ if (e.type == 'JSXEmptyExpression')
1244
+ continue;
1245
+ if ((html || blocks) && !vnodes) {
1246
+ const subs = [];
1247
+ direct(e, subs);
1248
+ out.push('(' + src(e.start, e.end, subs) + ')');
1249
+ }
1250
+ else
1251
+ out.push('(' + expr(e) + ')');
1252
+ }
1253
+ else if (html && !vnodes && c.type == 'JSXElement' && compilable(c))
1254
+ out.push(ssrInner(c));
1255
+ else
1256
+ out.push(jsx(c));
1257
+ }
1258
+ return out;
1259
+ };
1260
+ const subs = subsIn(ast);
1261
+ if (!compiled)
1262
+ return null;
1263
+ const ms = new magicString.MagicString(code);
1264
+ for (const [a, b, t] of subs)
1265
+ ms.overwrite(a, b, t);
1266
+ ms.prepend(`import { __cv${ssr ? ', __cvs' : ''}${nblocks ? ', __cvb' : ''} } from 'sygnal';\n` + hoisted.map((h, i) => `const $cv${i} = ${h};\n`).join(''));
1267
+ return { code: ms.toString(), map: ms.generateMap({ hires: 'boundary', source: id, includeContent: true }) };
1268
+ }
1269
+ // JSX text as oxc's JSX transform cleans it (the transform the plugin configures): split on line
1270
+ // breaks, trim spaces and tabs at each line's edges (not the first line's start nor the last's
1271
+ // end), drop empty lines, join with ' '; tabs inside a line stay (Babel would make them spaces)
1272
+ function cleanText(value) {
1273
+ const lines = value.split(/\r\n|\n|\r/);
1274
+ let last = 0;
1275
+ for (let i = 0; i < lines.length; i++)
1276
+ if (/[^ \t]/.test(lines[i]))
1277
+ last = i;
1278
+ let str = '';
1279
+ for (let i = 0; i < lines.length; i++) {
1280
+ let line = lines[i];
1281
+ if (i !== 0)
1282
+ line = line.replace(/^[ \t]+/, '');
1283
+ if (i !== lines.length - 1)
1284
+ line = line.replace(/[ \t]+$/, '');
1285
+ if (line) {
1286
+ if (i !== last)
1287
+ line += ' ';
1288
+ str += line;
1289
+ }
1290
+ }
1291
+ return str;
1292
+ }
1293
+
1294
+ /*
1295
+ * 'sygnal/api' (dev preview; index.ts adds contractFetch: this module imports no diagnostics, so
1296
+ * sygnal/vite can bundle it without a second copy of their state; research: dev-plans/research/fullstack-contracts/): typed clients
1297
+ * for an API whose contract the server already has. A client turns a call into plain data the
1298
+ * existing drivers understand, so effects stay data and replies come back as actions:
1299
+ *
1300
+ * - a GET endpoint → a FetchRequest (`resources`, the query cache, retry, `latest`), tagged
1301
+ * `api:<id>` so `invalidates: [api.x]` / `{ invalidate: [api.x] }`-style tags can name it;
1302
+ * - any other method → a FetchRequest with a JSON body (reply actions);
1303
+ * - a stream (an oRPC event iterator, or `.sse(path)`) → an SseConnection for `connections`.
1304
+ *
1305
+ * Adapters (one per contract source), all over plain JSON on the wire:
1306
+ * - orpcClient(contract, options): an oRPC contract router served by oRPC's OpenAPIHandler
1307
+ * (compact input: path params from `{name}` / `{+name}`, the rest in the query for GET with
1308
+ * bracket notation, else the JSON body). The RPCHandler protocol ({ json, meta }) is not
1309
+ * supported: its rich serialisation is what the network-calls research ruled out.
1310
+ * - openApiClient<paths>(options): openapi-typescript's `paths` (types only); input is
1311
+ * openapi-fetch's `{ params: { path, query }, body }`; query arrays repeat the key (form,
1312
+ * explode), objects use deepObject brackets.
1313
+ * - honoClient<ExtractSchema<typeof app>>(options): Hono routes (types only); input is hc's
1314
+ * `{ param, query, json }`.
1315
+ *
1316
+ * Each endpoint is a function `(input?, options?) => request` with `id`, `kind`, `method`,
1317
+ * `path` and `match(request)` (renderComponent's t.respond / t.fail take an endpoint as target).
1318
+ * Requests carry `endpoint: id` (an app key: not sent). `options` are the request's own fields
1319
+ * (ok, error, key, latest, cache, retry, ...); `invalidates` takes endpoints or tags.
1320
+ *
1321
+ * Version skew (D: contract hash): contractHash(contract) is a stable hash of the contract (an
1322
+ * oRPC router: ids, routes, JSON Schemas of input/output, error codes; anything else: its
1323
+ * stable JSON). The app sends it in a header of its choice (`headers` option); on the server,
1324
+ * contractSkew(request, { hash, header }) answers a different one with 409 CONTRACT_SKEW (the
1325
+ * oRPC error shape). isContractSkew(failure) tells a reducer; contractFetch() wraps fetch for the
1326
+ * drivers and reports SYG690 once (and calls onSkew) when a reply says the contract changed.
1327
+ */
1328
+ // ── oRPC (OpenAPIHandler) ──────────────────────────────────────────────
1329
+ const isProcedure = (x) => !!x && typeof x == 'object' && '~orpc' in x && x['~orpc'] && 'route' in x['~orpc'];
1330
+ /** the yields schema of an oRPC eventIterator() output schema, or undefined */
1331
+ const eventYields = (schema) => {
1332
+ const std = schema?.['~standard'];
1333
+ if (!std)
1334
+ return undefined;
1335
+ const s = Object.getOwnPropertySymbols(std).find(s => s.description === 'ORPC_EVENT_ITERATOR_DETAILS');
1336
+ return s ? std[s].yields ?? {} : undefined;
1337
+ };
1338
+ /** every procedure of an oRPC contract router: [keys, procedure] */
1339
+ const procedures = (router, keys = [], out = [], seen = new Set()) => {
1340
+ if (isProcedure(router))
1341
+ out.push([keys, router]);
1342
+ // a router is plain nested objects: not a schema (a contract module may export those too), no cycles
1343
+ else if (router && typeof router == 'object' && !router['~standard'] && !seen.has(router)) {
1344
+ seen.add(router);
1345
+ Object.keys(router).forEach(k => procedures(router[k], [...keys, k], out, seen));
1346
+ }
1347
+ return out;
1348
+ };
1349
+ const orpcRoute = (keys, p) => {
1350
+ const def = p['~orpc'];
1351
+ const method = String(def.route.method || 'POST').toUpperCase();
1352
+ const path = def.route.path || '/' + keys.map(encodeURIComponent).join('/');
1353
+ // a manifest (a contract without its schemas, or a server function) says `stream` and `kind`
1354
+ const yields = def.stream ? def.outputSchema || {} : eventYields(def.outputSchema);
1355
+ const kind = yields ? 'stream' : def.kind || (method == 'GET' ? 'query' : 'mutation');
1356
+ return { id: keys.join('.'), def, method, path, yields, kind };
1357
+ };
1358
+ // ── contract hash and skew ─────────────────────────────────────────────
1359
+ const stable = (v) => {
1360
+ if (Array.isArray(v))
1361
+ return '[' + v.map(stable).join(',') + ']';
1362
+ if (v && typeof v == 'object')
1363
+ return '{' + Object.keys(v).sort().map(k => JSON.stringify(k) + ':' + stable(v[k])).join(',') + '}';
1364
+ return JSON.stringify(v) ?? 'null';
1365
+ };
1366
+ // cyrb53: a fast, well-distributed 53-bit string hash (not cryptographic: this only detects change)
1367
+ const cyrb53 = (s) => {
1368
+ let h1 = 0xdeadbeef, h2 = 0x41c6ce57;
1369
+ for (let i = 0; i < s.length; i++) {
1370
+ const c = s.charCodeAt(i);
1371
+ h1 = Math.imul(h1 ^ c, 2654435761);
1372
+ h2 = Math.imul(h2 ^ c, 1597334677);
1373
+ }
1374
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
1375
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
1376
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
1377
+ };
1378
+ /** a schema's JSON Schema (Standard JSON Schema), else its vendor: what the hash can see of it */
1379
+ const schemaShape = (s, io) => {
1380
+ const std = s?.['~standard'];
1381
+ if (!std)
1382
+ return null;
1383
+ try {
1384
+ if (std.jsonSchema?.[io])
1385
+ return std.jsonSchema[io]({ target: 'draft-2020-12', libraryOptions: { unrepresentable: 'any' } });
1386
+ }
1387
+ catch (_) { /* a schema it can't represent: the vendor below */ }
1388
+ return std.vendor || 'schema';
1389
+ };
1390
+ function contractHash(contract) {
1391
+ // a manifest made at build time (sygnal/vite's api.contracts) carries its hash
1392
+ if (contract && typeof contract['~hash'] == 'string')
1393
+ return contract['~hash'];
1394
+ const ps = procedures(contract);
1395
+ if (!ps.length)
1396
+ return cyrb53(stable(contract));
1397
+ const shape = ps.map(([keys, p]) => {
1398
+ const r = orpcRoute(keys, p);
1399
+ return [r.id, r.method, r.path, r.kind, schemaShape(r.def.inputSchema, 'input'),
1400
+ r.yields ? schemaShape(r.yields, 'output') : schemaShape(r.def.outputSchema, 'output'),
1401
+ Object.keys(r.def.errorMap || {}).sort()];
1402
+ }).sort((a, b) => (a[0] < b[0] ? -1 : 1));
1403
+ return cyrb53(stable(shape));
1404
+ }
1405
+ // ── build time: a contract without its schemas ─────────────────────────
1406
+ /**
1407
+ * sygnal/vite's `api.contracts`: what a client needs of a contract (routes, kinds, error codes and
1408
+ * statuses) and its hash, so the client bundle has no schema library. Plain JSON (`~hash` aside).
1409
+ */
1410
+ function contractManifest(contract) {
1411
+ const count = procedures(contract).length;
1412
+ if (!count)
1413
+ return { manifest: undefined, hash: '', count };
1414
+ const walk = (node, keys) => {
1415
+ if (isProcedure(node)) {
1416
+ const r = orpcRoute(keys, node);
1417
+ const errorMap = {};
1418
+ Object.keys(r.def.errorMap || {}).forEach(c => { errorMap[c] = { status: r.def.errorMap[c]?.status }; });
1419
+ const route = { method: r.method, path: r.path };
1420
+ if (r.def.route.inputStructure)
1421
+ route.inputStructure = r.def.route.inputStructure;
1422
+ return { '~orpc': { route, kind: r.kind, stream: r.kind == 'stream', manifest: true, errorMap } };
1423
+ }
1424
+ const out = {};
1425
+ Object.keys(node || {}).forEach(k => { if (procedures(node[k]).length)
1426
+ out[k] = walk(node[k], [...keys, k]); });
1427
+ return out;
1428
+ };
1429
+ return { manifest: walk(contract, []), hash: contractHash(contract), count };
1430
+ }
1431
+ // ── function-first: *.server.ts modules ────────────────────────────────
1432
+ /*
1433
+ * A `*.server.ts` file exports query(input, fn), mutation(input, fn) and stream(input, fn). Its
1434
+ * path and export names are the contract: `src/quotes.server.ts` → `export const list` is
1435
+ * `quotes.list`, POST /quotes/list (a stream: GET, read as server-sent events). sygnal/vite
1436
+ * (`api: { functions: true }`) replaces the file in the client by stubs that build the requests,
1437
+ * so nothing of it but its export names, kinds and error codes reaches the bundle; on the
1438
+ * server, serverFunctions(import.meta.glob(...), { os }) makes the oRPC router to mount.
1439
+ * Input is always validated (a Standard Schema, or none: then only an empty input passes), the
1440
+ * wire is plain JSON (oRPC's OpenAPIHandler), and only module exports are functions (no closures
1441
+ * over component scope, no ids made from a closure).
1442
+ */
1443
+ const FN = '~sygnal';
1444
+ /** `src/admin/users.server.ts` (or `/src/...`, `../src/...`) → `admin/users` */
1445
+ const fnNamespace = (file, dir = 'src') => {
1446
+ let f = file.split('?')[0].replace(/\\/g, '/').replace(/^(\.{1,2}\/)+|^\/+/, '');
1447
+ const d = dir.replace(/^\.?\/+|\/+$/g, '');
1448
+ if (d && f.startsWith(d + '/'))
1449
+ f = f.slice(d.length + 1);
1450
+ return f.replace(/\.server\.[cm]?[jt]sx?$/, '');
1451
+ };
1452
+ const fnSpec = (id, kind, errors, schemas) => ({ '~orpc': {
1453
+ route: { method: kind == 'stream' ? 'GET' : 'POST', path: '/' + id.split('.').map(encodeURIComponent).join('/') },
1454
+ kind, stream: kind == 'stream', manifest: !schemas, errorMap: errors,
1455
+ inputSchema: schemas?.input, outputSchema: schemas?.output,
1456
+ } });
1457
+ /** each file's server functions: its namespace, its own contract tree (for the hash), [name, meta] */
1458
+ function fnFiles(modules, dir = 'src') {
1459
+ return Object.keys(modules).sort().map(file => {
1460
+ const ns = fnNamespace(file, dir);
1461
+ const keys = ns.split('/');
1462
+ const tree = {};
1463
+ let node = tree;
1464
+ keys.forEach(k => { node = node[k] = {}; });
1465
+ const fns = [];
1466
+ Object.keys(modules[file] || {}).forEach(name => {
1467
+ const meta = modules[file][name]?.[FN];
1468
+ if (!meta)
1469
+ return;
1470
+ const id = [...keys, name].join('.');
1471
+ node[name] = Object.assign(fnSpec(id, meta.kind, meta.errors, meta), { [FN]: meta });
1472
+ fns.push([name, meta]);
1473
+ });
1474
+ return { file, ns, keys, tree, fns, hash: contractHash(tree) };
1475
+ });
1476
+ }
1477
+
1478
+ /**
1479
+ * sygnal/vite's `api` option (sygnal/api, dev preview; research: dev-plans/research/fullstack-contracts/):
1480
+ *
1481
+ * - `handler`: in dev, requests under `prefix` go to that module's default export, a Fetch handler
1482
+ * (Request → Response): the same module a production server mounts. The plugin only bridges
1483
+ * Node's request to it; the server is the app's own (oRPC), never one of Sygnal's.
1484
+ * - `contracts`: in client code (dev and build, never Vitest) each listed contract module is
1485
+ * replaced by what orpcClient needs of it (routes, kinds, error codes and statuses) with its
1486
+ * contractHash computed at build time, so the bundle has no schema library. The module is
1487
+ * evaluated on the server side (the dev server's SSR loader; for `vite build` a temporary one).
1488
+ * An export that is no contract is left out, so a client import of a schema from it fails
1489
+ * loudly (keep the module out of `contracts` to validate on the client).
1490
+ * - `functions`: `*.server.ts` files under `dir` (function-first). In client code a file becomes
1491
+ * stubs: `export const list = __serverFunction('quotes.list', { kind, errors }, client)`, plus
1492
+ * the file's contract hash in the stub header. Nothing else of the file (its imports, secrets,
1493
+ * handler bodies) is in the client graph. In server code and under Vitest the file keeps its
1494
+ * code and each export gets its id appended, so tests can call it as an endpoint.
1495
+ */
1496
+ // @ts-ignore
1497
+ const SERVER_FILE = /\.server\.[cm]?[jt]sx?$/;
1498
+ // set while the temporary build-time server loads the config: its own sygnal() then adds no api plugin
1499
+ const NESTED = '__SYGNAL_API_EVAL__';
1500
+ const g = globalThis;
1501
+ const nodeProcess$1 = g.process;
1502
+ function apiPlugin(o) {
1503
+ if (g[NESTED])
1504
+ return null;
1505
+ const prefix = (o.prefix ?? '/api').replace(/\/+$/, '');
1506
+ const fo = o.functions === true ? {} : o.functions || null;
1507
+ const dir = (fo && fo.dir) || 'src';
1508
+ const header = (fo && fo.header) || 'x-api-functions';
1509
+ let root = nodeProcess$1.cwd();
1510
+ let isVitest = false;
1511
+ let config;
1512
+ let dev;
1513
+ let temp;
1514
+ const rel = (file) => path.relative(root, file).split(path.sep).join('/');
1515
+ const contractFiles = () => new Set((o.contracts || []).map((f) => path.resolve(root, f)));
1516
+ const isFnFile = (file) => {
1517
+ if (!fo || !SERVER_FILE.test(file) || file.includes('/node_modules/'))
1518
+ return false;
1519
+ const r = rel(file);
1520
+ return !r.startsWith('..') && (r + '/').startsWith(dir.replace(/^\.?\/+|\/+$/g, '') + '/');
1521
+ };
1522
+ // evaluates a module on the server side: the dev server's SSR loader, or (vite build) a temporary one
1523
+ async function load(file) {
1524
+ if (dev)
1525
+ return dev.ssrLoadModule(file);
1526
+ temp ?? (temp = (async () => {
1527
+ const vite = await import(/* @vite-ignore */ projectVite(root));
1528
+ g[NESTED] = true;
1529
+ try {
1530
+ return await vite.createServer({
1531
+ root, mode: config?.mode, configFile: config?.configFile ?? false, logLevel: 'warn', appType: 'custom',
1532
+ server: { middlewareMode: true, hmr: false, ws: false, watch: null },
1533
+ optimizeDeps: { noDiscovery: true, include: [] },
1534
+ });
1535
+ }
1536
+ finally {
1537
+ delete g[NESTED];
1538
+ }
1539
+ })());
1540
+ return (await temp).ssrLoadModule(file);
1541
+ }
1542
+ const exportLine = (name, value) => (name === 'default' ? `export default ${value};` : `export const ${name} = ${value};`);
1543
+ return {
1544
+ name: 'vite-plugin-sygnal:api',
1545
+ config() {
1546
+ isVitest = !!nodeProcess$1?.env?.VITEST;
1547
+ },
1548
+ configResolved(c) {
1549
+ config = c;
1550
+ root = c.root;
1551
+ },
1552
+ configureServer(server) {
1553
+ dev = server;
1554
+ if (!o.handler || isVitest)
1555
+ return;
1556
+ const handlerFile = path.resolve(root, o.handler);
1557
+ server.middlewares.use(async (req, res, next) => {
1558
+ const url = req.url || '';
1559
+ if (!(url === prefix || url.startsWith(prefix + '/') || url.startsWith(prefix + '?')))
1560
+ return next();
1561
+ try {
1562
+ const mod = await server.ssrLoadModule(handlerFile);
1563
+ const handle = typeof mod.default === 'function' ? mod.default : mod.default?.fetch ?? mod.fetch;
1564
+ if (typeof handle !== 'function')
1565
+ throw new Error(`[sygnal] api.handler (${o.handler}) must export default a Fetch handler: (request: Request) => Response`);
1566
+ const ctl = new AbortController();
1567
+ res.on('close', () => ctl.abort());
1568
+ const body = req.method === 'GET' || req.method === 'HEAD' ? undefined : node_stream.Readable.toWeb(req);
1569
+ const request = new Request(new URL(url, 'http://' + (req.headers.host || 'localhost')), {
1570
+ method: req.method, headers: req.headers, body, signal: ctl.signal, ...(body && { duplex: 'half' }),
1571
+ });
1572
+ const response = await handle(request);
1573
+ res.writeHead(response.status, Object.fromEntries(response.headers));
1574
+ if (!response.body)
1575
+ return void res.end();
1576
+ node_stream.Readable.fromWeb(response.body).pipe(res);
1577
+ }
1578
+ catch (e) {
1579
+ next(e);
1580
+ }
1581
+ });
1582
+ },
1583
+ async transform(code, id, opts) {
1584
+ const file = id.split('?')[0];
1585
+ if (id.startsWith('\0'))
1586
+ return null;
1587
+ const client = !opts?.ssr && !isVitest;
1588
+ if (client && o.contracts && contractFiles().has(file)) {
1589
+ const mod = await load(file);
1590
+ const out = [];
1591
+ Object.keys(mod).forEach((name) => {
1592
+ const { manifest, hash, count } = contractManifest(mod[name]);
1593
+ if (!count)
1594
+ return;
1595
+ out.push(exportLine(name, `Object.defineProperty(${JSON.stringify(manifest)}, '~hash', { value: ${JSON.stringify(hash)} })`));
1596
+ });
1597
+ return { code: `// ${rel(file)}: routes and hash only (sygnal/vite api.contracts)\n${out.join('\n')}\n`, map: { mappings: '' } };
1598
+ }
1599
+ if (!isFnFile(file))
1600
+ return null;
1601
+ if (client) {
1602
+ const [f] = fnFiles({ [rel(file)]: await load(file) }, dir);
1603
+ const opts = JSON.stringify({ prefix, headers: { [header]: `${f.ns}:${f.hash}` } });
1604
+ const lines = f.fns.map(([name, meta]) => {
1605
+ const errors = {};
1606
+ Object.keys(meta.errors || {}).forEach((c) => { errors[c] = { status: meta.errors[c]?.status }; });
1607
+ return exportLine(name, `__serverFunction(${JSON.stringify(f.keys.concat(name).join('.'))}, ${JSON.stringify({ kind: meta.kind, errors })}, __o)`);
1608
+ });
1609
+ return {
1610
+ code: `// ${rel(file)}: client stubs (sygnal/vite api.functions)\nimport { __serverFunction } from 'sygnal/api';\nconst __o = ${opts};\n${lines.join('\n')}\n`,
1611
+ map: { mappings: '' },
1612
+ };
1613
+ }
1614
+ // server code and tests: the module as written, each export given its id
1615
+ const ns = fnNamespace(rel(file), dir);
1616
+ const named = exportedLocals(this.parse(code));
1617
+ if (!named.length)
1618
+ return null;
1619
+ const keys = ns.split('/');
1620
+ const tail = named.map(([exp, local]) => `__sygnalFn(${JSON.stringify(keys.concat(exp).join('.'))}, ${local}, ${JSON.stringify({ prefix })});`);
1621
+ return { code: `${code}\nimport { __serverFunction as __sygnalFn } from 'sygnal/api';\n${tail.join('\n')}\n`, map: null };
1622
+ },
1623
+ async closeBundle() {
1624
+ if (temp) {
1625
+ const s = await temp;
1626
+ temp = undefined;
1627
+ await s.close();
1628
+ }
1629
+ },
1630
+ };
1631
+ }
1632
+ /** [exported name, local binding] of a module's own named exports (declarations and `export { a as b }`) */
1633
+ function exportedLocals(ast) {
1634
+ const out = [];
1635
+ for (const node of ast.body || []) {
1636
+ if (node.type !== 'ExportNamedDeclaration' || node.source)
1637
+ continue;
1638
+ const d = node.declaration;
1639
+ if (d?.type === 'VariableDeclaration')
1640
+ d.declarations.forEach((x) => x.id?.type === 'Identifier' && out.push([x.id.name, x.id.name]));
1641
+ else if (d?.id?.name)
1642
+ out.push([d.id.name, d.id.name]);
1643
+ for (const s of node.specifiers || []) {
1644
+ const exp = s.exported?.name ?? s.exported?.value;
1645
+ if (s.local?.name && exp)
1646
+ out.push([exp, s.local.name]);
1647
+ }
1648
+ }
1649
+ return out;
1650
+ }
1651
+ /** the project's own Vite (not one next to sygnal), as an ESM URL */
1652
+ function projectVite(root) {
1653
+ const req = node_module.createRequire(path.join(root, 'package.json'));
1654
+ const pj = req.resolve('vite/package.json');
1655
+ const pkg = JSON.parse(fs.readFileSync(pj, 'utf8'));
1656
+ const dot = typeof pkg.exports === 'string' ? pkg.exports : pkg.exports?.['.'];
1657
+ let entry = typeof dot === 'string' ? dot : dot?.import ?? pkg.module ?? pkg.main;
1658
+ if (entry && typeof entry === 'object')
1659
+ entry = entry.default;
1660
+ return node_url.pathToFileURL(path.join(path.dirname(pj), entry)).href;
1661
+ }
1662
+
1663
+ /*
1664
+ * The QuickJS WASM module: `quickjs-emscripten-core` + `@jitl/quickjs-wasmfile-release-sync`,
1665
+ * optional peer dependencies (D209), loaded on first use. Stock packages only (README §9 Q6):
1666
+ * a quickjs-ng variant can be passed as `variant`.
1667
+ */
1668
+ // Node built-ins by a name bundlers don't follow (a browser bundle never loads them)
1669
+ const nodeImport = (name) => import(/* @vite-ignore */ /* webpackIgnore: true */ name);
1670
+
1671
+ /*
1672
+ * The guest kernel, built from the app's installed packages (README §7 option (a), §9 Q1; D209:
1673
+ * Sygnal's npm package never ships xstream or snabbdom prebuilt into it). sygnal/vite resolves
1674
+ * `sygnal/sandbox/kernel?raw` to this function's output (the project's own Vite), and
1675
+ * `buildSandboxKernel()` (sygnal/sandbox/build) does it for other toolchains (Vite, esbuild or
1676
+ * Rollup, whichever the project has).
1677
+ *
1678
+ * The output is one script: the platform prelude (timers, console, microtasks on `__host`,
1679
+ * which the core's modules read when they load), then dist/sandbox-kernel.esm.js bundled as an
1680
+ * IIFE with the installed sygnal (dist/index.esm.js and dist/host.esm.js of the same package: one
1681
+ * core), xstream and snabbdom. `process.env.NODE_ENV` is 'production'; xstream's `globalthis`
1682
+ * dependency is Sygnal's shim (as sygnal/vite does, G-099).
1683
+ */
1684
+ // The platform a bare QuickJS context lacks. A plain script, so it runs before the bundle's modules.
1685
+ const PLATFORM = `var global = globalThis;
1686
+ (function () {
1687
+ var H = globalThis.__host, G = globalThis, timers = new Map(), intervals = new Map(), seq = 0;
1688
+ G.__sandboxTimers = timers;
1689
+ G.queueMicrotask = G.queueMicrotask || function (f) { Promise.resolve().then(f) };
1690
+ G.setTimeout = function (f, ms) { var id = ++seq; timers.set(id, f); H.timer(id, +ms || 0); return id };
1691
+ G.clearTimeout = function (id) { if (timers.delete(id)) H.clearTimer(id) };
1692
+ // the core's macrotask hop (runtime.ts macro()): its own channel, so a component's timers can't starve it
1693
+ G.setImmediate = function (f) { var id = ++seq; timers.set(id, f); H.timer(id, -1); return id };
1694
+ G.clearImmediate = G.clearTimeout;
1695
+ 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 };
1696
+ G.clearInterval = function (id) { var b = intervals.get(id); if (b) { intervals.delete(id); G.clearTimeout(b.id) } };
1697
+ G.requestAnimationFrame = function (f) { return G.setTimeout(function () { f(Date.now()) }, 16) };
1698
+ G.cancelAnimationFrame = G.clearTimeout;
1699
+ 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(' ') };
1700
+ G.console = {
1701
+ log: function () { H.log('log', fmt([].slice.call(arguments))) }, info: function () { H.log('log', fmt([].slice.call(arguments))) },
1702
+ warn: function () { H.log('warn', fmt([].slice.call(arguments))) }, error: function () { H.log('error', fmt([].slice.call(arguments))) },
1703
+ debug: function () {},
1704
+ };
1705
+ })();
1706
+ `;
1707
+ /** The guest kernel script for createSandbox / <Sandboxed> (`kernel`), built from the installed packages */
1708
+ async function buildSandboxKernel(opts = {}) {
1709
+ const [path, { createRequire }, { pathToFileURL }, fs] = await Promise.all([nodeImport('node:path'), nodeImport('node:module'), nodeImport('node:url'), nodeImport('node:fs')]);
1710
+ const root = path.resolve(opts.root || globalThis.process.cwd());
1711
+ const fromRoot = createRequire(path.join(root, 'package.json'));
1712
+ // the app's sygnal (else this one): its dist/ holds the kernel entry, the core and the kit
1713
+ let main;
1714
+ try {
1715
+ main = fromRoot.resolve('sygnal');
1716
+ }
1717
+ catch {
1718
+ main = createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs.js', document.baseURI).href))).resolve('sygnal');
1719
+ }
1720
+ const dist = path.dirname(main);
1721
+ const entry = path.join(dist, 'sandbox-kernel.esm.js');
1722
+ const files = {
1723
+ sygnal: path.join(dist, 'index.esm.js'),
1724
+ 'sygnal/host': path.join(dist, 'host.esm.js'),
1725
+ globalthis: path.join(dist, 'shims/globalthis.cjs'),
1726
+ };
1727
+ for (const f of [entry, ...Object.values(files)])
1728
+ if (!fs.existsSync(f))
1729
+ throw new Error(`buildSandboxKernel: ${f} is missing (an incomplete sygnal install?)`);
1730
+ const minify = opts.minify !== false;
1731
+ const define = { 'process.env.NODE_ENV': '"production"' };
1732
+ const has = (name) => { try {
1733
+ fromRoot.resolve(name);
1734
+ return true;
1735
+ }
1736
+ catch {
1737
+ return false;
1738
+ } };
1739
+ const want = opts.bundler || 'auto';
1740
+ const bundler = want !== 'auto' ? want : has('vite') ? 'vite' : has('esbuild') ? 'esbuild' : has('rollup') ? 'rollup' : null;
1741
+ if (!bundler)
1742
+ throw new Error('buildSandboxKernel: needs vite, esbuild or rollup installed in ' + root);
1743
+ const load = (name) => import(/* @vite-ignore */ pathToFileURL(resolveEsm(fromRoot, fs, path, name)).href);
1744
+ let code;
1745
+ if (bundler === 'esbuild') {
1746
+ const esbuild = await load('esbuild');
1747
+ const r = await (esbuild.build || esbuild.default.build)({
1748
+ entryPoints: [entry], bundle: true, write: false, format: 'iife', platform: 'browser', target: 'es2020',
1749
+ minify, define, alias: files, logLevel: 'silent', legalComments: 'none',
1750
+ });
1751
+ code = r.outputFiles[0].text;
1752
+ }
1753
+ else if (bundler === 'rollup') {
1754
+ const { rollup } = await load('rollup');
1755
+ const plugins = [{
1756
+ name: 'sygnal-kernel', resolveId: (id) => files[id] || null,
1757
+ transform: (c) => (c.includes('process.env.NODE_ENV') ? c.split('process.env.NODE_ENV').join(define['process.env.NODE_ENV']) : null),
1758
+ }];
1759
+ const req = async (n) => { const m = await load(n); return m.default || m; };
1760
+ if (has('@rollup/plugin-node-resolve'))
1761
+ plugins.push((await load('@rollup/plugin-node-resolve')).nodeResolve({ browser: true }));
1762
+ if (has('@rollup/plugin-commonjs'))
1763
+ plugins.push((await req('@rollup/plugin-commonjs'))());
1764
+ if (minify && has('@rollup/plugin-terser'))
1765
+ plugins.push((await req('@rollup/plugin-terser'))({ ecma: 2020 }));
1766
+ const b = await rollup({ input: entry, plugins, onwarn: () => { } });
1767
+ code = (await b.generate({ format: 'iife', name: '__sygnalKernel' })).output[0].code;
1768
+ await b.close();
1769
+ }
1770
+ else {
1771
+ const vite = await load('vite');
1772
+ const out = await vite.build({
1773
+ configFile: false, root, logLevel: 'silent', mode: 'production', define,
1774
+ resolve: { alias: Object.entries(files).map(([find, replacement]) => ({ find: new RegExp(`^${find.replace('/', '\\/')}$`), replacement })) },
1775
+ build: { write: false, minify, target: 'es2020', lib: { entry, formats: ['iife'], name: '__sygnalKernel', fileName: () => 'kernel.js' }, rollupOptions: { output: { inlineDynamicImports: true } } },
1776
+ });
1777
+ const o = (Array.isArray(out) ? out[0] : out).output;
1778
+ code = o.find((c) => c.type === 'chunk').code;
1779
+ }
1780
+ return PLATFORM + code;
1781
+ }
1782
+ // a package's ESM entry, as resolved from the app (import() needs a file URL, not a bare name, to resolve from there)
1783
+ function resolveEsm(req, fs, path, name) {
1784
+ try {
1785
+ const pj = req.resolve(`${name}/package.json`);
1786
+ const pkg = JSON.parse(fs.readFileSync(pj, 'utf8'));
1787
+ const dot = typeof pkg.exports == 'string' ? pkg.exports : pkg.exports?.['.'];
1788
+ let e = typeof dot == 'string' ? dot : dot?.import ?? pkg.module ?? pkg.main ?? 'index.js';
1789
+ if (e && typeof e == 'object')
1790
+ e = e.default;
1791
+ return path.join(path.dirname(pj), e);
1792
+ }
1793
+ catch {
1794
+ return req.resolve(name);
1795
+ }
1796
+ }
1797
+
1798
+ /**
1799
+ * sygnal/sandbox's guest kernel in sygnal/vite (README §7 (a), D209): `import kernel from
1800
+ * 'sygnal/sandbox/kernel?raw'` (or without `?raw`) resolves to a module whose default export is
1801
+ * the kernel script, built once per server or build with the project's own Vite from the
1802
+ * installed sygnal, xstream and snabbdom (src/sandbox/build.ts). Nothing is shipped prebuilt.
1803
+ */
1804
+ const SANDBOX_KERNEL = /^sygnal\/sandbox\/kernel(\?raw)?$/;
1805
+ const SANDBOX_KERNEL_ID = '\0sygnal-sandbox-kernel';
1806
+ const built = new Map();
1807
+ async function sandboxKernelModule(root) {
1808
+ if (!built.has(root))
1809
+ built.set(root, buildSandboxKernel({ root, bundler: 'vite' }));
1810
+ return `// sygnal/sandbox's guest kernel, built from the installed packages (sygnal/vite)\nexport default ${JSON.stringify(await built.get(root))};\n`;
1811
+ }
1812
+
895
1813
  /**
896
1814
  * Sygnal Vite Plugin
897
1815
  *
@@ -1114,7 +2032,41 @@ function sygnal(options = {}) {
1114
2032
  let devSnippet = devtoolsImport + (devOn ? flags + devImports : '');
1115
2033
  const devInject = devOn || devtoolsOn;
1116
2034
  let astroClient;
1117
- return {
2035
+ // CV-2: its own plugin, before Vite's JSX transform (this plugin's transform runs after it)
2036
+ let warnedCompile = false;
2037
+ const co = options.compile === true ? {} : options.compile || null;
2038
+ const compileOn = (k) => !!co && co[k] !== false;
2039
+ // (its own Vitest flag: it may be used without the main plugin, e.g. added to a config that already
2040
+ // has sygnal())
2041
+ let cVitest = false;
2042
+ const compilePlugin = co && {
2043
+ name: 'vite-plugin-sygnal:compile',
2044
+ enforce: 'pre',
2045
+ config() {
2046
+ cVitest = !!nodeProcess?.env?.VITEST;
2047
+ },
2048
+ transform(code, id, opts) {
2049
+ const isVitest = cVitest;
2050
+ const file = id.split('?')[0];
2051
+ if (!/\.[jt]sx$/.test(file) || file.includes('/node_modules/') || id.startsWith('\0'))
2052
+ return null;
2053
+ if (isVitest && !compileOn('test'))
2054
+ return null;
2055
+ // server code renders to strings (never under Vitest: renderComponent's mock DOM needs vnodes)
2056
+ const ssr = !!opts?.ssr && !isVitest && compileOn('ssr');
2057
+ if (!ssr && !compileOn('views'))
2058
+ return null;
2059
+ if (viteMajor(this, root) < 8) {
2060
+ if (!warnedCompile) {
2061
+ warnedCompile = true;
2062
+ this.warn('[sygnal] `compile` needs Vite 8; views are not compiled');
2063
+ }
2064
+ return null;
2065
+ }
2066
+ return compileViews(code, file, (c, o) => this.parse(c, o), { ssr, blocks: !ssr && compileOn('blocks') });
2067
+ },
2068
+ };
2069
+ const main = {
1118
2070
  name: 'vite-plugin-sygnal',
1119
2071
  config(config, env) {
1120
2072
  isServe = env.command === 'serve';
@@ -1191,7 +2143,9 @@ function sygnal(options = {}) {
1191
2143
  const include = result.optimizeDeps?.include || [];
1192
2144
  result.optimizeDeps = {
1193
2145
  ...result.optimizeDeps,
1194
- exclude: [VIKE_CLIENT],
2146
+ // sygnal/sandbox: its worker script is found by `new URL(..., import.meta.url)`, which
2147
+ // only works served unbundled
2148
+ exclude: [VIKE_CLIENT, 'sygnal/sandbox'],
1195
2149
  ...(sygnalInNodeModules(root) ? { include: [...include, 'sygnal'] } : {}),
1196
2150
  };
1197
2151
  }
@@ -1222,6 +2176,9 @@ function sygnal(options = {}) {
1222
2176
  resolveId: {
1223
2177
  order: 'pre',
1224
2178
  async handler(source, importer, opts) {
2179
+ // sygnal/sandbox: the guest kernel, built from the installed packages (every mode)
2180
+ if (SANDBOX_KERNEL.test(source))
2181
+ return SANDBOX_KERNEL_ID;
1225
2182
  if (!isServe || isVitest)
1226
2183
  return null;
1227
2184
  if (source === DEV_CLIENT)
@@ -1248,6 +2205,8 @@ function sygnal(options = {}) {
1248
2205
  },
1249
2206
  },
1250
2207
  load(id) {
2208
+ if (id === SANDBOX_KERNEL_ID)
2209
+ return sandboxKernelModule(root);
1251
2210
  if (id === DEV_CLIENT_ID)
1252
2211
  return devClientModule();
1253
2212
  if (id === MCP_CLIENT_ID && mcpOn)
@@ -1316,6 +2275,9 @@ function sygnal(options = {}) {
1316
2275
  });
1317
2276
  },
1318
2277
  };
2278
+ const api = options.api ? apiPlugin(options.api) : null;
2279
+ const plugins = [compilePlugin, main, api].filter(Boolean);
2280
+ return plugins.length > 1 ? plugins : main;
1319
2281
  function devTransform(code, id, opts) {
1320
2282
  if (!isServe)
1321
2283
  return null;
@@ -1474,10 +2436,12 @@ const ADAPTER_PEERS = {
1474
2436
  'ui/menu': ['@zag-js/vanilla', '@zag-js/menu'],
1475
2437
  'ui/select': ['@zag-js/vanilla', '@zag-js/select'],
1476
2438
  'ui/combobox': ['@zag-js/vanilla', '@zag-js/combobox'],
2439
+ // sygnal/sandbox: QuickJS and its WASM variant (stock packages, README §9 Q6)
2440
+ sandbox: ['quickjs-emscripten-core', '@jitl/quickjs-wasmfile-release-sync'],
1477
2441
  };
1478
2442
  // G-440: a dynamic import's specifier may be a template literal without substitutions
1479
- const ADAPTER_RE = /(?:\bfrom|\bimport)\s*\(?\s*(['"`])sygnal\/(react|zag|ui\/(?:menu|select|combobox))\1/g;
1480
- const ADAPTER_ID = /^sygnal\/(react|zag|ui\/(?:menu|select|combobox))$/;
2443
+ const ADAPTER_RE = /(?:\bfrom|\bimport)\s*\(?\s*(['"`])sygnal\/(react|zag|sandbox|ui\/(?:menu|select|combobox))\1/g;
2444
+ const ADAPTER_ID = /^sygnal\/(react|zag|sandbox|ui\/(?:menu|select|combobox))$/;
1481
2445
  /**
1482
2446
  * The adapter entries an app module imports (statically, dynamically or in a re-export), or null.
1483
2447
  * G-415: real imports only. The module's AST from the plugin context's parse() (Vite and Rollup