fuzor 0.5.1 → 0.5.3

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 (314) hide show
  1. package/dist/browser.d.ts +2 -0
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +1 -0
  4. package/dist/browser.js.map +1 -1
  5. package/dist/cli.js +10 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compiler/data-document.d.ts +20 -1
  8. package/dist/compiler/data-document.d.ts.map +1 -1
  9. package/dist/compiler/data-document.js +80 -7
  10. package/dist/compiler/data-document.js.map +1 -1
  11. package/dist/compiler/docs.d.ts.map +1 -1
  12. package/dist/compiler/docs.js +49 -11
  13. package/dist/compiler/docs.js.map +1 -1
  14. package/dist/compiler/index.d.ts +2 -0
  15. package/dist/compiler/index.d.ts.map +1 -1
  16. package/dist/compiler/index.js +23 -6
  17. package/dist/compiler/index.js.map +1 -1
  18. package/dist/compiler/islands.d.ts +1 -0
  19. package/dist/compiler/islands.d.ts.map +1 -1
  20. package/dist/compiler/native-authoring.d.ts +45 -0
  21. package/dist/compiler/native-authoring.d.ts.map +1 -0
  22. package/dist/compiler/native-authoring.js +569 -0
  23. package/dist/compiler/native-authoring.js.map +1 -0
  24. package/dist/compiler/native-manifest.d.ts +33 -0
  25. package/dist/compiler/native-manifest.d.ts.map +1 -0
  26. package/dist/compiler/native-manifest.js +53 -0
  27. package/dist/compiler/native-manifest.js.map +1 -0
  28. package/dist/compiler/native-ranges.d.ts +8 -0
  29. package/dist/compiler/native-ranges.d.ts.map +1 -0
  30. package/dist/compiler/native-ranges.js +92 -0
  31. package/dist/compiler/native-ranges.js.map +1 -0
  32. package/dist/compiler/native-scripts.js +2 -2
  33. package/dist/compiler/native-scripts.js.map +1 -1
  34. package/dist/compiler/page-scripts.d.ts +6 -0
  35. package/dist/compiler/page-scripts.d.ts.map +1 -1
  36. package/dist/compiler/page-scripts.js +25 -6
  37. package/dist/compiler/page-scripts.js.map +1 -1
  38. package/dist/compiler/route-policy.d.ts +1 -0
  39. package/dist/compiler/route-policy.d.ts.map +1 -1
  40. package/dist/compiler/route-policy.js +4 -1
  41. package/dist/compiler/route-policy.js.map +1 -1
  42. package/dist/compiler/select-options.d.ts +7 -0
  43. package/dist/compiler/select-options.d.ts.map +1 -0
  44. package/dist/compiler/select-options.js +22 -0
  45. package/dist/compiler/select-options.js.map +1 -0
  46. package/dist/compiler/server-actions.d.ts +9 -0
  47. package/dist/compiler/server-actions.d.ts.map +1 -0
  48. package/dist/compiler/server-actions.js +65 -0
  49. package/dist/compiler/server-actions.js.map +1 -0
  50. package/dist/data-bindings.d.ts +4 -0
  51. package/dist/data-bindings.d.ts.map +1 -0
  52. package/dist/data-bindings.js +18 -0
  53. package/dist/data-bindings.js.map +1 -0
  54. package/dist/dom-ownership.d.ts +5 -0
  55. package/dist/dom-ownership.d.ts.map +1 -0
  56. package/dist/dom-ownership.js +49 -0
  57. package/dist/dom-ownership.js.map +1 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/internal-specifiers.d.ts +3 -0
  63. package/dist/internal-specifiers.d.ts.map +1 -0
  64. package/dist/internal-specifiers.js +22 -0
  65. package/dist/internal-specifiers.js.map +1 -0
  66. package/dist/islands/react.d.ts +0 -6
  67. package/dist/islands/react.d.ts.map +1 -1
  68. package/dist/islands/react.js +9 -26
  69. package/dist/islands/react.js.map +1 -1
  70. package/dist/json-value.d.ts +4 -0
  71. package/dist/json-value.d.ts.map +1 -0
  72. package/dist/json-value.js +10 -0
  73. package/dist/json-value.js.map +1 -0
  74. package/dist/native-expressions.d.ts +34 -0
  75. package/dist/native-expressions.d.ts.map +1 -0
  76. package/dist/native-expressions.js +118 -0
  77. package/dist/native-expressions.js.map +1 -0
  78. package/dist/native-manifest.d.ts +7 -0
  79. package/dist/native-manifest.d.ts.map +1 -0
  80. package/dist/native-manifest.js +4 -0
  81. package/dist/native-manifest.js.map +1 -0
  82. package/dist/native-ranges.d.ts +25 -0
  83. package/dist/native-ranges.d.ts.map +1 -0
  84. package/dist/native-ranges.js +51 -0
  85. package/dist/native-ranges.js.map +1 -0
  86. package/dist/native-state.d.ts +14 -0
  87. package/dist/native-state.d.ts.map +1 -0
  88. package/dist/native-state.js +27 -0
  89. package/dist/native-state.js.map +1 -0
  90. package/dist/route-path.d.ts +2 -0
  91. package/dist/route-path.d.ts.map +1 -1
  92. package/dist/route-path.js +2 -0
  93. package/dist/route-path.js.map +1 -1
  94. package/dist/runtime/binding-updates.d.ts +11 -0
  95. package/dist/runtime/binding-updates.d.ts.map +1 -0
  96. package/dist/runtime/binding-updates.js +45 -0
  97. package/dist/runtime/binding-updates.js.map +1 -0
  98. package/dist/runtime/data.d.ts +28 -1
  99. package/dist/runtime/data.d.ts.map +1 -1
  100. package/dist/runtime/data.js +101 -25
  101. package/dist/runtime/data.js.map +1 -1
  102. package/dist/runtime/docs-search.d.ts +20 -4
  103. package/dist/runtime/docs-search.d.ts.map +1 -1
  104. package/dist/runtime/docs-search.js +135 -159
  105. package/dist/runtime/docs-search.js.map +1 -1
  106. package/dist/runtime/docs-ui.d.ts +3 -1
  107. package/dist/runtime/docs-ui.d.ts.map +1 -1
  108. package/dist/runtime/docs-ui.js +19 -29
  109. package/dist/runtime/docs-ui.js.map +1 -1
  110. package/dist/runtime/index.d.ts +1 -0
  111. package/dist/runtime/index.d.ts.map +1 -1
  112. package/dist/runtime/islands.d.ts.map +1 -1
  113. package/dist/runtime/islands.js +2 -1
  114. package/dist/runtime/islands.js.map +1 -1
  115. package/dist/runtime/native-graph.d.ts +15 -0
  116. package/dist/runtime/native-graph.d.ts.map +1 -0
  117. package/dist/runtime/native-graph.js +103 -0
  118. package/dist/runtime/native-graph.js.map +1 -0
  119. package/dist/runtime/native-manifest.d.ts +17 -0
  120. package/dist/runtime/native-manifest.d.ts.map +1 -0
  121. package/dist/runtime/native-manifest.js +111 -0
  122. package/dist/runtime/native-manifest.js.map +1 -0
  123. package/dist/runtime/native-ranges.d.ts +8 -0
  124. package/dist/runtime/native-ranges.d.ts.map +1 -0
  125. package/dist/runtime/native-ranges.js +100 -0
  126. package/dist/runtime/native-ranges.js.map +1 -0
  127. package/dist/runtime/native-stream-reader.d.ts +7 -0
  128. package/dist/runtime/native-stream-reader.d.ts.map +1 -0
  129. package/dist/runtime/native-stream-reader.js +79 -0
  130. package/dist/runtime/native-stream-reader.js.map +1 -0
  131. package/dist/runtime/native-stream.d.ts +5 -0
  132. package/dist/runtime/native-stream.d.ts.map +1 -0
  133. package/dist/runtime/native-stream.js +14 -0
  134. package/dist/runtime/native-stream.js.map +1 -0
  135. package/dist/runtime/native.d.ts +41 -0
  136. package/dist/runtime/native.d.ts.map +1 -1
  137. package/dist/runtime/native.js +176 -55
  138. package/dist/runtime/native.js.map +1 -1
  139. package/dist/runtime/page-scripts.d.ts.map +1 -1
  140. package/dist/runtime/page-scripts.js +8 -3
  141. package/dist/runtime/page-scripts.js.map +1 -1
  142. package/dist/runtime/route-views.d.ts +15 -0
  143. package/dist/runtime/route-views.d.ts.map +1 -0
  144. package/dist/runtime/route-views.js +133 -0
  145. package/dist/runtime/route-views.js.map +1 -0
  146. package/dist/runtime/router-embedded.d.ts +1 -0
  147. package/dist/runtime/router-embedded.d.ts.map +1 -1
  148. package/dist/runtime/router-navigation-api.d.ts +1 -0
  149. package/dist/runtime/router-navigation-api.d.ts.map +1 -1
  150. package/dist/runtime/router.d.ts +1 -0
  151. package/dist/runtime/router.d.ts.map +1 -1
  152. package/dist/runtime/router.js +11 -2
  153. package/dist/runtime/router.js.map +1 -1
  154. package/dist/runtime/server-actions.d.ts +5 -0
  155. package/dist/runtime/server-actions.d.ts.map +1 -0
  156. package/dist/runtime/server-actions.js +79 -0
  157. package/dist/runtime/server-actions.js.map +1 -0
  158. package/dist/server-actions.d.ts +18 -0
  159. package/dist/server-actions.d.ts.map +1 -0
  160. package/dist/server-actions.js +138 -0
  161. package/dist/server-actions.js.map +1 -0
  162. package/dist/server-components/closures.d.ts +1 -12
  163. package/dist/server-components/closures.d.ts.map +1 -1
  164. package/dist/server-components/closures.js +0 -75
  165. package/dist/server-components/closures.js.map +1 -1
  166. package/dist/server-components/continuation.d.ts +7 -0
  167. package/dist/server-components/continuation.d.ts.map +1 -0
  168. package/dist/server-components/continuation.js +17 -0
  169. package/dist/server-components/continuation.js.map +1 -0
  170. package/dist/server-components/environment.js +1 -1
  171. package/dist/server-components/environment.js.map +1 -1
  172. package/dist/server-components/protocol.d.ts +5 -1
  173. package/dist/server-components/protocol.d.ts.map +1 -1
  174. package/dist/server-components/protocol.js +9 -1
  175. package/dist/server-components/protocol.js.map +1 -1
  176. package/dist/server-components/server.d.ts +4 -7
  177. package/dist/server-components/server.d.ts.map +1 -1
  178. package/dist/server-components/server.js +2 -185
  179. package/dist/server-components/server.js.map +1 -1
  180. package/dist/server-components/stream-server.d.ts +6 -0
  181. package/dist/server-components/stream-server.d.ts.map +1 -0
  182. package/dist/server-components/stream-server.js +197 -0
  183. package/dist/server-components/stream-server.js.map +1 -0
  184. package/dist/server-components/stream.d.ts +16 -3
  185. package/dist/server-components/stream.d.ts.map +1 -1
  186. package/dist/server-components/stream.js +31 -4
  187. package/dist/server-components/stream.js.map +1 -1
  188. package/dist/server-components/transport.d.ts +20 -0
  189. package/dist/server-components/transport.d.ts.map +1 -0
  190. package/dist/server-components/transport.js +2 -0
  191. package/dist/server-components/transport.js.map +1 -0
  192. package/dist/server-components/updates.d.ts +56 -0
  193. package/dist/server-components/updates.d.ts.map +1 -0
  194. package/dist/server-components/updates.js +319 -0
  195. package/dist/server-components/updates.js.map +1 -0
  196. package/dist/server-data-protocol.d.ts +14 -0
  197. package/dist/server-data-protocol.d.ts.map +1 -0
  198. package/dist/server-data-protocol.js +15 -0
  199. package/dist/server-data-protocol.js.map +1 -0
  200. package/dist/server-data.d.ts +18 -0
  201. package/dist/server-data.d.ts.map +1 -1
  202. package/dist/server-data.js.map +1 -1
  203. package/dist/server-document.d.ts +21 -0
  204. package/dist/server-document.d.ts.map +1 -0
  205. package/dist/server-document.js +3 -0
  206. package/dist/server-document.js.map +1 -0
  207. package/dist/server-native-document.d.ts +13 -0
  208. package/dist/server-native-document.d.ts.map +1 -0
  209. package/dist/server-native-document.js +154 -0
  210. package/dist/server-native-document.js.map +1 -0
  211. package/dist/server-native-stream.d.ts +5 -0
  212. package/dist/server-native-stream.d.ts.map +1 -0
  213. package/dist/server-native-stream.js +39 -0
  214. package/dist/server-native-stream.js.map +1 -0
  215. package/dist/server-native.d.ts +24 -0
  216. package/dist/server-native.d.ts.map +1 -0
  217. package/dist/server-native.js +70 -0
  218. package/dist/server-native.js.map +1 -0
  219. package/dist/server-reactivity.d.ts +13 -0
  220. package/dist/server-reactivity.d.ts.map +1 -0
  221. package/dist/server-reactivity.js +49 -0
  222. package/dist/server-reactivity.js.map +1 -0
  223. package/dist/server.d.ts +11 -5
  224. package/dist/server.d.ts.map +1 -1
  225. package/dist/server.js +68 -16
  226. package/dist/server.js.map +1 -1
  227. package/dist/ui/behaviors/accordion.d.ts.map +1 -1
  228. package/dist/ui/behaviors/accordion.js +4 -3
  229. package/dist/ui/behaviors/accordion.js.map +1 -1
  230. package/dist/ui/behaviors/collapsible.d.ts.map +1 -1
  231. package/dist/ui/behaviors/collapsible.js +2 -1
  232. package/dist/ui/behaviors/collapsible.js.map +1 -1
  233. package/dist/ui/behaviors/dialog.d.ts.map +1 -1
  234. package/dist/ui/behaviors/dialog.js +5 -4
  235. package/dist/ui/behaviors/dialog.js.map +1 -1
  236. package/dist/ui/behaviors/menu.d.ts.map +1 -1
  237. package/dist/ui/behaviors/menu.js +4 -3
  238. package/dist/ui/behaviors/menu.js.map +1 -1
  239. package/dist/ui/behaviors/select.d.ts +23 -1
  240. package/dist/ui/behaviors/select.d.ts.map +1 -1
  241. package/dist/ui/behaviors/select.js +81 -11
  242. package/dist/ui/behaviors/select.js.map +1 -1
  243. package/dist/ui/behaviors/switch.d.ts.map +1 -1
  244. package/dist/ui/behaviors/switch.js +2 -1
  245. package/dist/ui/behaviors/switch.js.map +1 -1
  246. package/dist/ui/behaviors/toolbar.d.ts.map +1 -1
  247. package/dist/ui/behaviors/toolbar.js +2 -1
  248. package/dist/ui/behaviors/toolbar.js.map +1 -1
  249. package/dist/ui/catalogue.d.ts +1 -1
  250. package/dist/ui/components/accordion/accordion.d.ts.map +1 -1
  251. package/dist/ui/components/accordion/accordion.js +3 -2
  252. package/dist/ui/components/accordion/accordion.js.map +1 -1
  253. package/dist/ui/components/collapsible/collapsible.d.ts.map +1 -1
  254. package/dist/ui/components/collapsible/collapsible.js +2 -1
  255. package/dist/ui/components/collapsible/collapsible.js.map +1 -1
  256. package/dist/ui/components/dialog/dialog.d.ts.map +1 -1
  257. package/dist/ui/components/dialog/dialog.js +4 -3
  258. package/dist/ui/components/dialog/dialog.js.map +1 -1
  259. package/dist/ui/components/navigation/breadcrumbs.d.ts.map +1 -1
  260. package/dist/ui/components/navigation/breadcrumbs.js +2 -1
  261. package/dist/ui/components/navigation/breadcrumbs.js.map +1 -1
  262. package/dist/ui/components/select/select.d.ts +20 -0
  263. package/dist/ui/components/select/select.d.ts.map +1 -1
  264. package/dist/ui/components/select/select.js +20 -0
  265. package/dist/ui/components/select/select.js.map +1 -1
  266. package/dist/ui/components/tabs/tabs.d.ts.map +1 -1
  267. package/dist/ui/components/tabs/tabs.js +6 -5
  268. package/dist/ui/components/tabs/tabs.js.map +1 -1
  269. package/dist/ui/core/brand.d.ts +33 -0
  270. package/dist/ui/core/brand.d.ts.map +1 -0
  271. package/dist/ui/core/brand.js +96 -0
  272. package/dist/ui/core/brand.js.map +1 -0
  273. package/dist/ui/core/component-styles.d.ts.map +1 -1
  274. package/dist/ui/core/component-styles.js +14 -8
  275. package/dist/ui/core/component-styles.js.map +1 -1
  276. package/dist/ui/core/element.d.ts.map +1 -1
  277. package/dist/ui/core/element.js +2 -3
  278. package/dist/ui/core/element.js.map +1 -1
  279. package/dist/ui/core/index.d.ts +1 -0
  280. package/dist/ui/core/index.d.ts.map +1 -1
  281. package/dist/ui/core/index.js +1 -0
  282. package/dist/ui/core/index.js.map +1 -1
  283. package/dist/ui/core/layout-styles.d.ts.map +1 -1
  284. package/dist/ui/core/layout-styles.js +12 -6
  285. package/dist/ui/core/layout-styles.js.map +1 -1
  286. package/dist/ui/core/parts.d.ts.map +1 -1
  287. package/dist/ui/core/parts.js +5 -4
  288. package/dist/ui/core/parts.js.map +1 -1
  289. package/dist/ui/core/portal.d.ts.map +1 -1
  290. package/dist/ui/core/portal.js +17 -1
  291. package/dist/ui/core/portal.js.map +1 -1
  292. package/dist/ui/machine-state.d.ts +11 -11
  293. package/dist/ui/state.d.ts +1 -1
  294. package/dist/vite.d.ts +5 -10
  295. package/dist/vite.d.ts.map +1 -1
  296. package/dist/vite.js +119 -116
  297. package/dist/vite.js.map +1 -1
  298. package/package.json +2 -12
  299. package/dist/server-components/browser.d.ts +0 -75
  300. package/dist/server-components/browser.d.ts.map +0 -1
  301. package/dist/server-components/browser.js +0 -558
  302. package/dist/server-components/browser.js.map +0 -1
  303. package/dist/server-components/compiler.d.ts +0 -6
  304. package/dist/server-components/compiler.d.ts.map +0 -1
  305. package/dist/server-components/compiler.js +0 -118
  306. package/dist/server-components/compiler.js.map +0 -1
  307. package/dist/server-components/navigation.d.ts +0 -10
  308. package/dist/server-components/navigation.d.ts.map +0 -1
  309. package/dist/server-components/navigation.js +0 -105
  310. package/dist/server-components/navigation.js.map +0 -1
  311. package/dist/server-components/references.d.ts +0 -22
  312. package/dist/server-components/references.d.ts.map +0 -1
  313. package/dist/server-components/references.js +0 -94
  314. package/dist/server-components/references.js.map +0 -1
@@ -0,0 +1,569 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { readFile } from "node:fs/promises";
4
+ import { parse } from "@babel/parser";
5
+ import { parseFragment } from "parse5";
6
+ import MagicString from "magic-string";
7
+ import { isManagedScript } from "./page-scripts.js";
8
+ import * as Effect from "effect/Effect";
9
+ import { nativeSeed } from "../native-state.js";
10
+ import { freeNames } from "../server-components/closures.js";
11
+ import { assertJson, assertName, isBindingAttribute, scalar } from "../server-components/protocol.js";
12
+ import { escapeAttribute, escapeText } from "../html-escape.js";
13
+ import { isServerData } from "../server-data.js";
14
+ import { compileServerActions } from "./server-actions.js";
15
+ import { computeNativeValues } from "../native-expressions.js";
16
+ import { compileNativeRange } from "./native-ranges.js";
17
+ import { selectOptions } from "./select-options.js";
18
+ const hash = (value) => createHash("sha256").update(value).digest("hex").slice(0, 20);
19
+ const eventTypes = new Set(["click", "input", "change", "keydown", "keyup", "submit", "focusin", "focusout", "pointerdown", "pointerup"]);
20
+ const attr = (node, name) => node.attrs.find(a => a.name === name)?.value;
21
+ const fail = (source, message) => { throw new Error(`${source}: native Fuzor script: ${message}`); };
22
+ const unwrap = (node) => ["TSAsExpression", "TSSatisfiesExpression", "TSNonNullExpression"].includes(node.type) ? unwrap(node.expression) : node;
23
+ function literal(raw, source) {
24
+ const node = unwrap(raw);
25
+ if (node.type === "NullLiteral")
26
+ return null;
27
+ if (["StringLiteral", "NumericLiteral", "BooleanLiteral"].includes(node.type)) {
28
+ assertJson(node.value);
29
+ return node.value;
30
+ }
31
+ if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "NumericLiteral")
32
+ return -node.argument.value;
33
+ if (node.type === "ArrayExpression")
34
+ return node.elements.map((n) => n ? literal(n, source) : fail(source, "state arrays cannot have holes"));
35
+ if (node.type === "ObjectExpression") {
36
+ const entries = node.properties.map((p) => {
37
+ if (p.type !== "ObjectProperty" || p.computed || p.shorthand)
38
+ return fail(source, "state defaults must be explicit JSON");
39
+ const key = p.key.name ?? p.key.value;
40
+ assertName(key);
41
+ return [key, literal(p.value, source)];
42
+ });
43
+ const value = Object.fromEntries(entries);
44
+ assertJson(value);
45
+ return value;
46
+ }
47
+ return fail(source, "state defaults must be JSON literals or fuzor.props paths with a JSON fallback");
48
+ }
49
+ function analyze(code, source, language) {
50
+ const body = parse(code, { sourceType: "module", plugins: language === "ts" ? ["typescript"] : [], allowAwaitOutsideFunction: true }).program.body;
51
+ const imports = body.filter(n => n.type === "ImportDeclaration");
52
+ const aliases = new Set(["fuzor"]);
53
+ for (const node of imports)
54
+ if (["fuzor", "fuzor/runtime/native"].includes(node.source.value))
55
+ for (const s of node.specifiers)
56
+ if (s.imported?.name === "fuzor")
57
+ aliases.add(s.local.name);
58
+ const member = (node, name) => node?.type === "MemberExpression" && !node.computed && node.object.type === "Identifier" && aliases.has(node.object.name) && node.property.name === name;
59
+ // Opt in only through declarations, never identifiers in strings/comments or unrelated imperative code.
60
+ const calls = body.flatMap(n => n.type === "VariableDeclaration" ? n.declarations.map((d) => d.init) : n.type === "ExpressionStatement" ? [n.expression] : []);
61
+ if (!calls.some(n => n?.type === "CallExpression" && (member(n.callee, "state") || member(n.callee, "computed") || member(n.callee, "action") || member(n.callee, "effect"))))
62
+ return undefined;
63
+ const states = [], handles = new Map(), actions = new Map(), types = [];
64
+ const computed = {};
65
+ const effects = [];
66
+ const expression = (raw) => {
67
+ const node = unwrap(raw);
68
+ if (node.type === "CallExpression" && node.arguments.length === 0 && node.callee.type === "MemberExpression" && !node.callee.computed && node.callee.property.name === "get" && handles.has(node.callee.object.name))
69
+ return { op: "read", name: handles.get(node.callee.object.name) };
70
+ if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
71
+ assertName(node.property.name);
72
+ return { op: "property", value: expression(node.object), key: node.property.name };
73
+ }
74
+ if (node.type === "UnaryExpression" && ["!", "+", "-"].includes(node.operator))
75
+ return { op: node.operator, value: expression(node.argument) };
76
+ if (["BinaryExpression", "LogicalExpression"].includes(node.type) && ["+", "-", "*", "/", "%", "**", "===", "!==", "<", "<=", ">", ">=", "&&", "||", "??"].includes(node.operator))
77
+ return { op: "binary", operator: node.operator, left: expression(node.left), right: expression(node.right) };
78
+ if (node.type === "ConditionalExpression")
79
+ return { op: "choose", test: expression(node.test), yes: expression(node.consequent), no: expression(node.alternate) };
80
+ if (node.type === "ArrayExpression")
81
+ return { op: "array", items: node.elements.map((item) => item ? expression(item) : fail(source, "computed arrays cannot have holes")) };
82
+ try {
83
+ return { op: "literal", value: literal(node, source) };
84
+ }
85
+ catch {
86
+ return fail(source, `computed values require pure expressions over previously declared state handles; unsupported ${node.type} at column ${node.start + 1}`);
87
+ }
88
+ };
89
+ const propsPath = (node) => {
90
+ if (member(node, "props"))
91
+ return [];
92
+ if (node.type !== "MemberExpression" || node.computed || node.property.type !== "Identifier")
93
+ return undefined;
94
+ const parent = propsPath(node.object);
95
+ return parent ? [...parent, node.property.name] : undefined;
96
+ };
97
+ for (const node of body) {
98
+ if (node.type === "ImportDeclaration") {
99
+ if (!node.specifiers.length && !/\.(?:css|scss|sass|less|styl)(?:\?.*)?$/.test(node.source.value))
100
+ fail(source, "side-effect imports belong in an imperative script");
101
+ if (node.specifiers.some((s) => aliases.has(s.local.name) && s.imported?.name !== "fuzor"))
102
+ fail(source, "fuzor is reserved for the compiler context");
103
+ continue;
104
+ }
105
+ if (["TSTypeAliasDeclaration", "TSInterfaceDeclaration"].includes(node.type)) {
106
+ types.push(code.slice(node.start, node.end));
107
+ continue;
108
+ }
109
+ if (node.type === "EmptyStatement")
110
+ continue;
111
+ if (node.type === "VariableDeclaration" && node.kind === "const") {
112
+ for (const declaration of node.declarations) {
113
+ const call = declaration.init;
114
+ const derived = call?.type === "CallExpression" && member(call.callee, "computed");
115
+ if (declaration.id.type !== "Identifier" || call?.type !== "CallExpression" || !derived && !member(call.callee, "state") || call.arguments.length !== 2)
116
+ fail(source, 'use const handle = fuzor.state("name", initialValue) or fuzor.computed("name", () => expression)');
117
+ const name = literal(call.arguments[0], source);
118
+ if (typeof name !== "string" || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(name))
119
+ fail(source, "state names must be lowercase HTML names (for example count or first-name)");
120
+ assertName(name);
121
+ if (states.some(s => s.name === name) || handles.has(declaration.id.name) || aliases.has(declaration.id.name))
122
+ fail(source, "duplicate state name or reserved handle");
123
+ if (derived) {
124
+ const fn = unwrap(call.arguments[1]);
125
+ if (fn.type !== "ArrowFunctionExpression" || fn.async || fn.params.length || fn.body.type === "BlockStatement")
126
+ fail(source, "computed values require a synchronous zero-argument expression arrow");
127
+ computed[name] = expression(fn.body);
128
+ const fallback = computeNativeValues(Object.fromEntries([...states.map(cell => [cell.name, cell.fallback]), [name, null]]), computed)[name];
129
+ states.push({ name, fallback, readonly: true });
130
+ handles.set(declaration.id.name, name);
131
+ continue;
132
+ }
133
+ let initial = unwrap(call.arguments[1]), fallback, from;
134
+ if (initial.type === "LogicalExpression" && initial.operator === "??") {
135
+ from = propsPath(initial.left);
136
+ if (!from?.length)
137
+ fail(source, "only fuzor.props paths may use an initializer fallback");
138
+ fallback = literal(initial.right, source);
139
+ }
140
+ else {
141
+ from = propsPath(initial);
142
+ fallback = from?.length ? null : literal(initial, source);
143
+ }
144
+ if (from)
145
+ from.forEach(assertName);
146
+ states.push({ name, fallback, ...(from?.length ? { from } : {}) });
147
+ handles.set(declaration.id.name, name);
148
+ }
149
+ continue;
150
+ }
151
+ const call = node.type === "ExpressionStatement" ? node.expression : undefined;
152
+ if (call?.type === "CallExpression" && (member(call.callee, "action") && call.arguments.length === 2 || member(call.callee, "effect") && call.arguments.length === 3)) {
153
+ const effect = member(call.callee, "effect");
154
+ const name = literal(call.arguments[0], source), fn = call.arguments[effect ? 2 : 1];
155
+ if (effect) {
156
+ const dependencies = call.arguments[1];
157
+ if (dependencies.type !== "ArrayExpression" || dependencies.elements.some((item) => item?.type !== "Identifier" || !handles.has(item.name)))
158
+ fail(source, "effects require an explicit array of state/computed handles");
159
+ effects.push({ action: String(name), dependencies: dependencies.elements.map((item) => handles.get(item.name)) });
160
+ }
161
+ if (typeof name !== "string" || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name) || name.length > 256 || actions.has(name))
162
+ fail(source, "actions require a unique local action name without slashes");
163
+ if (!["ArrowFunctionExpression", "FunctionExpression"].includes(fn.type) || fn.generator || fn.params.length > 1 || fn.params.some((p) => p.type !== "Identifier"))
164
+ fail(source, "actions take a function with zero or one context parameter");
165
+ actions.set(name, fn);
166
+ continue;
167
+ }
168
+ fail(source, "use imports, types, state declarations and actions; imperative setup belongs in a separate script");
169
+ }
170
+ const imported = new Set(imports.flatMap(n => n.specifiers.map((s) => s.local.name)));
171
+ const outer = new Set([...imported, ...handles.keys(), ...aliases]);
172
+ for (const fn of actions.values())
173
+ for (const name of freeNames(fn, outer))
174
+ if (!outer.has(name))
175
+ fail(source, `unsupported action capture ${name}; capture a state handle or import a browser module`);
176
+ for (const effect of effects)
177
+ for (const name of freeNames(actions.get(effect.action), outer))
178
+ if (handles.has(name) && !effect.dependencies.includes(handles.get(name)))
179
+ fail(source, `effect ${effect.action} reads ${name} without declaring its dependency`);
180
+ return { states, computed, effects, actions, handles, imports, types, aliases, code, language, source, id: `native-${hash(JSON.stringify([source, code]))}` };
181
+ }
182
+ /** Extract independently imported actions; declarations themselves are inert HTML data. */
183
+ export function compileAuthoredNativeScript(code, script, buildId) {
184
+ const analysis = analyze(code, script.source, script.language) ?? (script.native ? { states: [], computed: {}, effects: [], actions: new Map(), handles: new Map(), imports: [], types: [], aliases: new Set(["fuzor"]), code, language: script.language, source: script.source, id: script.native.id } : undefined);
185
+ if (!analysis)
186
+ return undefined;
187
+ if (!script.native)
188
+ return fail(script.source, "native declarations require an HTML instance compiled by Fuzor");
189
+ const { imports, types, aliases, handles, actions } = analysis;
190
+ const imported = new Set(imports.flatMap(n => n.specifiers.map((s) => s.local.name)));
191
+ const handlers = [];
192
+ const actionIds = new Map();
193
+ const unique = (prefix) => { let name = prefix; while (new RegExp(`\\b${name}\\b`).test(code))
194
+ name += "_"; return name; };
195
+ const context = unique("__fz_context"), state = unique("__fz_state");
196
+ for (const [action, fn] of actions) {
197
+ const free = freeNames(fn, new Set([...imported, ...handles.keys(), ...aliases]));
198
+ const needed = imports.flatMap(node => {
199
+ const specifiers = node.specifiers.filter((s) => !aliases.has(s.local.name) && (node.importKind === "type" || free.includes(s.local.name)));
200
+ if (!specifiers.length)
201
+ return [];
202
+ const parts = specifiers.map((s) => s.type === "ImportDefaultSpecifier" ? s.local.name : s.type === "ImportNamespaceSpecifier" ? `* as ${s.local.name}` : `{ ${s.importKind === "type" ? "type " : ""}${s.imported.name ?? JSON.stringify(s.imported.value)}${s.local.name === s.imported.name ? "" : ` as ${s.local.name}`} }`);
203
+ // Separate named specifiers are legal as separate imports and preserve aliases/types.
204
+ return parts.map((part) => `import ${node.importKind === "type" ? "type " : ""}${part} from ${JSON.stringify(node.source.value)};`);
205
+ });
206
+ const id = `${analysis.id}-${hash(action)}`;
207
+ actionIds.set(action, id);
208
+ const locals = free.flatMap(name => handles.has(name) ? [`const ${name} = ${state}(${context}, ${JSON.stringify(handles.get(name))});`] : aliases.has(name) ? [`const ${name} = { props: ${context}.props };`] : []);
209
+ handlers.push({ id, source: script.source, language: script.language, code: `import { createNativeState as ${state} } from "fuzor/runtime/native";\n${needed.join("\n")}\n${types.join("\n")}\nexport default async function(${context}) { ${locals.join("\n")} return await (${code.slice(fn.start, fn.end)})(${context}); }` });
210
+ }
211
+ const metadata = script.native;
212
+ for (const [name, action] of Object.entries(metadata.serverActions ?? {})) {
213
+ const id = `${metadata.id}-${hash(name)}`;
214
+ actionIds.set(name, id);
215
+ handlers.push({ id, source: script.source, language: "js", code: `import { invokeServerAction } from "fuzor/runtime/native";\nexport default context => invokeServerAction(context, ${JSON.stringify(action)}, ${JSON.stringify(buildId ?? "development")});` });
216
+ }
217
+ return { handlers, actionIds, ...(metadata.computed || metadata.ranges || metadata.effects ? { graphId: `graph-${hash(JSON.stringify([metadata.id, metadata.computed, metadata.ranges, metadata.effects]))}` } : {}), css: imports.filter(node => !node.specifiers.length).map(node => node.source.value) };
218
+ }
219
+ function allScripts(html, includeTemplates = false) {
220
+ const fragment = parseFragment(html, { sourceCodeLocationInfo: true }), scripts = [];
221
+ const visit = (node) => { if (!("tagName" in node))
222
+ return; if (node.tagName === "script")
223
+ scripts.push(node); if (node.tagName !== "template")
224
+ node.childNodes.forEach(visit);
225
+ else if (includeTemplates)
226
+ node.content.childNodes.forEach(visit); };
227
+ fragment.childNodes.forEach(visit);
228
+ return { fragment, scripts };
229
+ }
230
+ /** A server annotation must never silently become a browser script in unsupported surfaces. */
231
+ export function assertNoServerScripts(html, source) {
232
+ if (allScripts(html, true).scripts.some(node => attr(node, "server") !== undefined))
233
+ fail(source, "server scripts belong in a page or component with a native declaration");
234
+ }
235
+ function hostFor(node) {
236
+ if (attr(node, "data-fuzor-script-host") !== "next-element" && "tagName" in node.parentNode)
237
+ return node.parentNode;
238
+ const siblings = node.parentNode.childNodes;
239
+ const next = siblings.slice(siblings.indexOf(node) + 1).find(n => "tagName" in n && !["script", "style"].includes(n.tagName));
240
+ if (!next || !("tagName" in next))
241
+ return fail(attr(node, "data-fuzor-script-source") ?? "HTML", "a declaration needs a following markup root");
242
+ return next;
243
+ }
244
+ const serverQuery = "?fuzor-server-script=";
245
+ export function resolveHtmlServer(id, ssr, modules) {
246
+ if (!id.includes(serverQuery) && !modules.has(id))
247
+ return;
248
+ if (!ssr)
249
+ throw new Error("HTML server scripts cannot be imported by browser code");
250
+ return id;
251
+ }
252
+ export async function loadHtmlServer(id, modules, watch) {
253
+ const generated = modules.get(id);
254
+ if (generated !== undefined)
255
+ return generated;
256
+ const index = id.indexOf(serverQuery);
257
+ if (index < 0)
258
+ return;
259
+ const file = id.slice(0, index), key = id.slice(index + serverQuery.length);
260
+ watch(file);
261
+ const html = await readFile(file, "utf8");
262
+ const script = allScripts(html).scripts.find(n => attr(n, "server") !== undefined && hash(html.slice(n.sourceCodeLocation.startTag.endOffset, n.sourceCodeLocation.endTag.startOffset)) === key);
263
+ if (!script)
264
+ throw new Error(`${file}: server script no longer exists`);
265
+ const location = script.sourceCodeLocation;
266
+ const code = html.slice(location.startTag.endOffset, location.endTag.startOffset);
267
+ const language = attr(script, "lang") ?? "js";
268
+ if (!["js", "ts"].includes(language))
269
+ throw new Error(`${file}: server script lang must be js or ts`);
270
+ return (await import("vite")).transformWithEsbuild(compileServerActions(code, file, language).code, `${file}.server.${language}`, { loader: language, target: "es2022" });
271
+ }
272
+ export function htmlServerPlugin(modules = new Map()) {
273
+ return { name: "fuzor:html-server", enforce: "pre",
274
+ resolveId(id, _importer, options) { return resolveHtmlServer(id, options.ssr, modules); },
275
+ load(id) { return loadHtmlServer(id, modules, file => this.addWatchFile(file)); }
276
+ };
277
+ }
278
+ /** Adoptable HTML is produced without running any client setup or action. */
279
+ export async function prepareNativeScripts(project, root, loadModule, serverModules) {
280
+ const compile = Effect.fnUntraced(function* (html, source, route, request, baseData) {
281
+ const { scripts } = allScripts(html), output = new MagicString(html);
282
+ const active = new Set(scripts.map(node => node.sourceCodeLocation?.startOffset));
283
+ if (allScripts(html, true).scripts.some(node => attr(node, "server") !== undefined && !active.has(node.sourceCodeLocation?.startOffset)))
284
+ fail(source, "server scripts cannot appear in inert templates");
285
+ const clients = new Map();
286
+ for (const node of scripts) {
287
+ if (!isManagedScript(node))
288
+ continue;
289
+ const origin = attr(node, "data-fuzor-script-source") ?? path.resolve(root, source), loc = node.sourceCodeLocation;
290
+ const src = attr(node, "src"), file = src ? path.resolve(src.startsWith("/") ? root : path.dirname(origin), src.startsWith("/") ? `.${src}` : src) : origin;
291
+ if (src && /^(?:[a-z]+:|\/\/)/i.test(src))
292
+ continue;
293
+ const code = src ? yield* Effect.tryPromise(() => readFile(file, "utf8")) : html.slice(loc.startTag.endOffset, loc.endTag.startOffset);
294
+ const language = attr(node, "lang") ?? (src && /\.[cm]?ts$/.test(src) ? "ts" : "js");
295
+ if (language !== "js" && language !== "ts")
296
+ continue;
297
+ const analysis = analyze(code, file, language);
298
+ if (!analysis)
299
+ continue;
300
+ const host = hostFor(node);
301
+ if ([...clients.values()].some(c => c.host === host))
302
+ fail(origin, "one native declaration script owns each markup root");
303
+ clients.set(node, { node, host, analysis });
304
+ }
305
+ const servers = new Map();
306
+ for (const node of scripts.filter(n => attr(n, "server") !== undefined)) {
307
+ const origin = attr(node, "data-fuzor-script-source") ?? path.resolve(root, source), loc = node.sourceCodeLocation;
308
+ if (attr(node, "src") !== undefined || !loc.startTag || !loc.endTag || node.attrs.some(a => a.name.startsWith("client:") || a.name === "data-fuzor-global"))
309
+ fail(origin, "server scripts must be inline with a closing tag");
310
+ const host = hostFor(node);
311
+ if (servers.has(host))
312
+ fail(origin, "one server script owns each markup root");
313
+ const code = html.slice(loc.startTag.endOffset, loc.endTag.startOffset);
314
+ const language = (attr(node, "lang") ?? "js");
315
+ if (language !== "js" && language !== "ts")
316
+ fail(origin, "server script lang must be js or ts");
317
+ const authored = compileServerActions(code, origin, language);
318
+ const module = origin + serverQuery + hash(code);
319
+ const loaded = yield* Effect.tryPromise(() => loadModule(module));
320
+ if (Object.keys(loaded).some(key => !["props", "default", "__fuzor_actions"].includes(key)) || loaded.props !== undefined && loaded.default !== undefined)
321
+ fail(origin, "export either public const props or a default request loader, not both");
322
+ const dynamic = loaded.default !== undefined;
323
+ if (dynamic && !isServerData(loaded.default) && typeof loaded.default !== "function")
324
+ fail(origin, "request scripts export a default loader function or defineServerData(...)");
325
+ if (authored.names.length && loaded.props !== undefined)
326
+ fail(origin, "server actions need request-time defineServerData for fresh props; export a loader instead of build-only props");
327
+ const actions = Object.fromEntries(authored.names.map(name => {
328
+ if (typeof loaded.__fuzor_actions?.[name] !== "function")
329
+ fail(origin, "server action registry does not match the compiled declarations");
330
+ return [name, `action-${hash(JSON.stringify([origin, code, name]))}`];
331
+ }));
332
+ const props = loaded.props ?? {};
333
+ assertJson(props);
334
+ if (!props || typeof props !== "object" || Array.isArray(props))
335
+ fail(origin, "public props must be a JSON object");
336
+ const synthetic = ![...clients.values()].some(c => c.host === host);
337
+ if (synthetic)
338
+ clients.set(node, { node, host, analysis: { states: [], computed: {}, effects: [], actions: new Map(), handles: new Map(), imports: [], types: [], aliases: new Set(["fuzor"]), code: "", language, source: origin, id: `native-${hash(JSON.stringify([origin, code]))}` } });
339
+ const client = [...clients.values()].find(c => c.host === host);
340
+ if (authored.names.some(name => client.analysis.actions.has(name)))
341
+ fail(origin, "client and server actions on one root need distinct names");
342
+ servers.set(host, { module, props: props, request: dynamic || authored.names.length > 0, actions, synthetic });
343
+ output.remove(loc.startOffset, loc.endOffset);
344
+ }
345
+ const usedKeys = new Set(), counts = new Map();
346
+ const entries = [];
347
+ const childHosts = new Set([...clients.values()].map(c => c.host));
348
+ for (const script of scripts)
349
+ if (isManagedScript(script)) {
350
+ if (attr(script, "data-fuzor-script-host") === "next-element")
351
+ childHosts.add(hostFor(script));
352
+ else if (script.parentNode && "tagName" in script.parentNode)
353
+ childHosts.add(script.parentNode);
354
+ }
355
+ for (const { node, host, analysis } of clients.values()) {
356
+ const count = counts.get(analysis.id) ?? 0;
357
+ counts.set(analysis.id, count + 1);
358
+ const explicit = attr(host, "data-fuzor-key") ?? attr(host, "id");
359
+ const key = `fz-${hash(JSON.stringify([route, analysis.id, explicit ?? count]))}`;
360
+ if (usedKeys.has(key))
361
+ fail(analysis.source, "duplicate native instance key");
362
+ usedKeys.add(key);
363
+ const server = servers.get(host);
364
+ // Server-only values are read-only cells; client declarations can explicitly own editable state.
365
+ if (server) {
366
+ const discover = (element) => {
367
+ if (element !== host && (childHosts.has(element) || element.tagName === "script"))
368
+ return;
369
+ if (element.tagName === "fz-state") {
370
+ const name = attr(element, "name") ?? fail(analysis.source, "fz-state requires a local state name");
371
+ if (!analysis.states.some(cell => cell.name === name))
372
+ analysis.states.push({ name, from: [name], fallback: null, readonly: true });
373
+ }
374
+ for (const kind of ["each", "if"]) {
375
+ const name = attr(element, `data-fz-${kind}`);
376
+ if (name !== undefined && !analysis.states.some(cell => cell.name === name))
377
+ analysis.states.push({ name, from: [name], fallback: kind === "each" ? [] : false, readonly: true });
378
+ }
379
+ for (const attribute of element.attrs)
380
+ if (attribute.name === "data-fz-value" || attribute.name === "data-fz-checked" || attribute.name.startsWith("data-fz-attr-")) {
381
+ if (!analysis.states.some(cell => cell.name === attribute.value))
382
+ analysis.states.push({ name: attribute.value, from: [attribute.value], fallback: attribute.name === "data-fz-checked" ? false : null, ...(!["data-fz-value", "data-fz-checked"].includes(attribute.name) ? { readonly: true } : {}) });
383
+ }
384
+ element.childNodes.forEach(child => { if ("tagName" in child)
385
+ discover(child); });
386
+ if (element.tagName === "template")
387
+ element.content.childNodes.forEach(child => { if ("tagName" in child)
388
+ discover(child); });
389
+ };
390
+ discover(host);
391
+ }
392
+ const baseSeed = nativeSeed(server?.props ?? {}, analysis.states);
393
+ const seed = { ...baseSeed, state: computeNativeValues(baseSeed.state, analysis.computed) };
394
+ const bindings = [], events = [];
395
+ const ranges = [];
396
+ const additions = new Map();
397
+ const add = (element, name, value) => { if (attr(element, name) !== undefined)
398
+ fail(analysis.source, `reserved compiler attribute ${name}`); const list = additions.get(element) ?? []; list.push(` ${name}="${escapeAttribute(value)}"`); additions.set(element, list); };
399
+ add(host, "data-fz-instance", key);
400
+ add(host, "data-fz-seed", JSON.stringify(seed));
401
+ if (server?.request) {
402
+ add(host, "data-fz-seed-source", `__fuzor_native.${key}`);
403
+ }
404
+ const walk = (element) => {
405
+ if (element !== host && (childHosts.has(element) || element.tagName === "template" || element.tagName === "script" || element.tagName.startsWith("fuzor-") || element.attrs.some(a => a.name === "data-fuzor-ui")))
406
+ return;
407
+ const local = [];
408
+ if (element.tagName.startsWith("fz-")) {
409
+ if (element.tagName !== "fz-state")
410
+ fail(analysis.source, 'value markers use <fz-state name="count">');
411
+ const state = attr(element, "name") ?? fail(analysis.source, "fz-state requires a local state name");
412
+ if (!state || !Object.hasOwn(seed.state, state))
413
+ fail(analysis.source, `unknown value marker state ${state ?? "(missing name)"}`);
414
+ if (element.childNodes.some(n => "tagName" in n))
415
+ fail(analysis.source, "value markers contain text only");
416
+ local.push({ state, kind: "text" });
417
+ }
418
+ for (const a of element.attrs) {
419
+ if (a.name === "data-fz-value" || a.name === "data-fz-checked" || a.name.startsWith("data-fz-attr-")) {
420
+ if (!Object.hasOwn(seed.state, a.value))
421
+ fail(analysis.source, `unknown binding state ${a.value}`);
422
+ const kind = a.name === "data-fz-value" ? "value" : a.name === "data-fz-checked" ? "checked" : "attribute";
423
+ const attribute = kind === "attribute" ? a.name.slice("data-fz-attr-".length) : undefined;
424
+ if (attribute && !isBindingAttribute(attribute))
425
+ fail(analysis.source, "unsupported attribute binding");
426
+ if (kind === "checked" && (element.tagName !== "input" || attr(element, "type") !== "checkbox") || kind === "value" && !["input", "textarea", "select"].includes(element.tagName))
427
+ fail(analysis.source, "field bindings require native form controls");
428
+ if (["checked", "value"].includes(kind) && Object.hasOwn(analysis.computed, a.value))
429
+ fail(analysis.source, "computed values are read-only; bind form controls to writable state");
430
+ local.push({ state: a.value, kind, ...(attribute ? { attribute } : {}) });
431
+ }
432
+ }
433
+ const render = [];
434
+ const tokens = [];
435
+ for (const binding of local) {
436
+ const token = `${key}-b${bindings.length}`, value = seed.state[binding.state];
437
+ tokens.push(token);
438
+ bindings.push({ ...binding, selector: `[data-fz-bind~="${token}"]` });
439
+ if (binding.kind === "checked" && typeof value !== "boolean")
440
+ fail(analysis.source, "checked state needs a boolean default or props fallback");
441
+ if (binding.kind !== "checked")
442
+ scalar(value);
443
+ const loc = element.sourceCodeLocation;
444
+ if (binding.kind === "text" || binding.kind === "value" && element.tagName === "textarea") {
445
+ if (!loc.endTag)
446
+ fail(analysis.source, "value markers and textareas require closing tags");
447
+ const start = loc.startTag.endOffset, end = loc.endTag.startOffset;
448
+ if (start === end)
449
+ output.appendLeft(start, escapeText(scalar(value)));
450
+ else
451
+ output.overwrite(start, end, escapeText(scalar(value)));
452
+ }
453
+ else if (binding.kind === "value" && element.tagName === "select") {
454
+ if (attr(element, "multiple") !== undefined)
455
+ fail(analysis.source, "value bindings require single-value selects");
456
+ let matched = false;
457
+ for (const { element: option, value: optionValue } of selectOptions(element)) {
458
+ const selected = option.sourceCodeLocation.attrs?.selected;
459
+ if (selected)
460
+ output.remove(selected.startOffset, selected.endOffset);
461
+ if (!matched && optionValue === scalar(value)) {
462
+ matched = true;
463
+ const list = additions.get(option) ?? [];
464
+ list.push(" selected");
465
+ additions.set(option, list);
466
+ }
467
+ }
468
+ }
469
+ else {
470
+ const attribute = binding.kind === "value" ? "value" : binding.kind === "checked" ? "checked" : binding.attribute;
471
+ const location = loc.attrs?.[attribute];
472
+ if (location)
473
+ output.remove(location.startOffset, location.endOffset);
474
+ if (value !== null && !(["checked", "hidden", "disabled"].includes(attribute) && value === false)) {
475
+ const list = additions.get(element) ?? [];
476
+ list.push(` ${attribute}="${escapeAttribute(scalar(value))}"`);
477
+ additions.set(element, list);
478
+ }
479
+ }
480
+ if (server?.request)
481
+ render.push({ name: `__fuzor_native.${key}.state.${binding.state}`, kind: binding.kind, ...(binding.attribute ? { attribute: binding.attribute } : {}) });
482
+ }
483
+ if (tokens.length)
484
+ add(element, "data-fz-bind", tokens.join(" "));
485
+ if (render.length)
486
+ add(element, "data-fz-render", JSON.stringify(render));
487
+ const eventTokens = [];
488
+ for (const a of element.attrs)
489
+ if (a.name.startsWith("data-fz-") && eventTypes.has(a.name.slice(8))) {
490
+ const type = a.name.slice(8);
491
+ if (analysis.effects.some(effect => effect.action === a.value) || !analysis.actions.has(a.value) && !Object.hasOwn(server?.actions ?? {}, a.value))
492
+ fail(analysis.source, `unknown action ${a.value}`);
493
+ const token = `${key}-e${events.length}`;
494
+ eventTokens.push(token);
495
+ events.push({ selector: `[data-fz-on~="${token}"]`, type, action: a.value, ...(attr(element, "data-fz-prevent-default")?.split(/\s+/).includes(type) || type === "submit" && Object.hasOwn(server?.actions ?? {}, a.value) ? { preventDefault: true } : {}) });
496
+ }
497
+ if (eventTokens.length)
498
+ add(element, "data-fz-on", eventTokens.join(" "));
499
+ const each = attr(element, "data-fz-each"), condition = attr(element, "data-fz-if");
500
+ if (each !== undefined || condition !== undefined) {
501
+ if (each !== undefined && condition !== undefined)
502
+ fail(analysis.source, "a range cannot be both keyed and conditional");
503
+ const state = each ?? condition;
504
+ if (!Object.hasOwn(seed.state, state))
505
+ fail(analysis.source, `unknown range state ${state}`);
506
+ const rowKey = each === undefined ? undefined : attr(element, "data-fz-key");
507
+ if (each !== undefined && !rowKey)
508
+ fail(analysis.source, "keyed ranges require data-fz-key with a stable row property");
509
+ const templates = element.childNodes.filter(n => "tagName" in n && n.tagName === "template");
510
+ if (templates.length !== 1 || element.childNodes.some(n => n !== templates[0] && !(n.nodeName === "#text" && !n.value.trim())))
511
+ fail(analysis.source, "a range container contains exactly one inert template");
512
+ const loc = templates[0].sourceCodeLocation, rangeId = `r${ranges.length}`;
513
+ const compiled = compileNativeRange(html.slice(loc.startTag.endOffset, loc.endTag.startOffset), rangeId, state, rowKey, seed.state, (type, action) => {
514
+ const serverAction = Object.hasOwn(server?.actions ?? {}, action);
515
+ if (!analysis.actions.has(action) && !serverAction || analysis.effects.some(effect => effect.action === action))
516
+ fail(analysis.source, "range actions require an admitted action");
517
+ const token = `e${events.length}`;
518
+ events.push({ selector: `[data-fz-range-on~="${token}"]`, type, action, delegated: true, ...(serverAction && type === "submit" ? { preventDefault: true } : {}) });
519
+ return token;
520
+ });
521
+ ranges.push(compiled.range);
522
+ add(element, "data-fz-range", rangeId);
523
+ if (server?.request) {
524
+ add(element, "data-fz-range-source", `__fuzor_native.${key}.state`);
525
+ add(element, "data-fz-range-program", JSON.stringify(compiled.range));
526
+ }
527
+ output.overwrite(element.sourceCodeLocation.startTag.endOffset, element.sourceCodeLocation.endTag.startOffset, compiled.html);
528
+ return;
529
+ }
530
+ for (const child of element.childNodes)
531
+ if ("tagName" in child)
532
+ walk(child);
533
+ };
534
+ walk(host);
535
+ if (server?.request)
536
+ entries.push({ key, module: server.module, actions: server.actions, computed: analysis.computed, ranges, states: analysis.states.map(cell => ({ ...cell, ...(bindings.some(b => b.state === cell.name && b.kind === "checked") ? { binding: "boolean" } : bindings.some(b => b.state === cell.name) ? { binding: "scalar" } : {}) })) });
537
+ for (const [element, attributes] of additions)
538
+ output.appendLeft(element.sourceCodeLocation.startTag.endOffset - (html[element.sourceCodeLocation.startTag.endOffset - 2] === "/" ? 2 : 1), attributes.join(""));
539
+ const metadata = { id: analysis.id, state: Object.fromEntries(analysis.states.map(s => [s.name, s.fallback])), seeds: analysis.states, ...(server?.request ? { request: true } : {}), ...(Object.keys(analysis.computed).length ? { computed: analysis.computed } : {}), ...(ranges.length ? { ranges } : {}), ...(analysis.effects.length ? { effects: analysis.effects } : {}), ...(server ? { serverActions: server.actions } : {}), bindings, events };
540
+ if (server?.synthetic)
541
+ output.appendLeft(node.sourceCodeLocation.startOffset, `<script type="module" data-fuzor-script-source="${escapeAttribute(analysis.source)}" data-fz-native="${escapeAttribute(JSON.stringify(metadata))}"${host !== node.parentNode ? ' data-fuzor-script-host="next-element"' : ""}></script>`);
542
+ else
543
+ output.appendLeft(node.sourceCodeLocation.startTag.endOffset - 1, ` data-fz-native="${escapeAttribute(JSON.stringify(metadata))}"`);
544
+ // Root declarations (also in pages) share the same host contract as component declarations.
545
+ if (!server?.synthetic && host !== node.parentNode && attr(node, "data-fuzor-script-host") === undefined)
546
+ output.appendLeft(node.sourceCodeLocation.startTag.endOffset - 1, ' data-fuzor-script-host="next-element"');
547
+ }
548
+ let data = baseData;
549
+ if (entries.length) {
550
+ data = `virtual:fuzor/native-data/${hash(JSON.stringify([route, entries, baseData]))}`;
551
+ serverModules.set(data, `import { composeNativeData } from "fuzor/server";\nimport { nativeDataStream } from "fuzor/server/native-stream";\n${baseData ? `import base from ${JSON.stringify(baseData)};` : "const base = undefined;"}\n${entries.map((entry, i) => `import * as loader${i} from ${JSON.stringify(entry.module)};`).join("\n")}\nconst entries = [${entries.map((entry, i) => `{key:${JSON.stringify(entry.key)},data:loader${i}.default,states:${JSON.stringify(entry.states)},computed:${JSON.stringify(entry.computed)},ranges:${JSON.stringify(entry.ranges)},actions:{${Object.entries(entry.actions).map(([name, id]) => `${JSON.stringify(id)}:loader${i}.__fuzor_actions[${JSON.stringify(name)}]`).join(",")}}}`).join(",")}];\nexport default { ...composeNativeData(base, entries), stream: nativeDataStream(base, entries) };`);
552
+ }
553
+ return { html: output.toString(), ...(data ? { serverData: data } : {}), runtimeServer: entries.length > 0 };
554
+ });
555
+ return Effect.runPromise(Effect.gen(function* () {
556
+ const routes = [];
557
+ for (const route of project.routes) {
558
+ const { runtimeServer, ...compiled } = yield* compile(route.html, route.source, route.path, route.policy?.rendering === "request", route.serverData);
559
+ if (runtimeServer && route.renderingDeclaration === "static")
560
+ fail(route.source, "rendering=static conflicts with request-time HTML server work");
561
+ routes.push({ ...route, ...compiled, ...(runtimeServer ? { policy: { ...route.policy, rendering: "request", navigation: route.policy?.navigation ?? "document" } } : {}) });
562
+ }
563
+ const boundaries = [];
564
+ for (const boundary of project.boundaries ?? [])
565
+ boundaries.push({ ...boundary, ...yield* compile(boundary.html, boundary.source, `${boundary.kind}:${boundary.scope}`, false) });
566
+ return { ...project, routes, ...(project.boundaries ? { boundaries } : {}) };
567
+ }));
568
+ }
569
+ //# sourceMappingURL=native-authoring.js.map