davaux 0.8.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 (333) hide show
  1. package/BASELINE.md +169 -0
  2. package/CLAUDE.md +518 -0
  3. package/LICENSE +21 -0
  4. package/README.md +36 -0
  5. package/ROADMAP.md +198 -0
  6. package/build.mjs +101 -0
  7. package/client/control.ts +247 -0
  8. package/client/hydrate.ts +37 -0
  9. package/client/index.ts +19 -0
  10. package/client/jsx-runtime.ts +209 -0
  11. package/client/resource.ts +122 -0
  12. package/client/signal.ts +211 -0
  13. package/client/store.ts +110 -0
  14. package/client/useHead.ts +63 -0
  15. package/dist/build/config.d.ts +3 -0
  16. package/dist/build/config.d.ts.map +1 -0
  17. package/dist/build/config.js +38 -0
  18. package/dist/build/config.js.map +7 -0
  19. package/dist/build/index.d.ts +2 -0
  20. package/dist/build/index.d.ts.map +1 -0
  21. package/dist/build/index.js +13 -0
  22. package/dist/build/index.js.map +7 -0
  23. package/dist/build/plugins.d.ts +7 -0
  24. package/dist/build/plugins.d.ts.map +1 -0
  25. package/dist/build/plugins.js +85 -0
  26. package/dist/build/plugins.js.map +7 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +427 -0
  30. package/dist/cli.js.map +7 -0
  31. package/dist/client/control.d.ts +49 -0
  32. package/dist/client/control.d.ts.map +1 -0
  33. package/dist/client/control.js +154 -0
  34. package/dist/client/control.js.map +7 -0
  35. package/dist/client/hydrate.d.ts +7 -0
  36. package/dist/client/hydrate.d.ts.map +1 -0
  37. package/dist/client/hydrate.js +23 -0
  38. package/dist/client/hydrate.js.map +7 -0
  39. package/dist/client/index.d.ts +12 -0
  40. package/dist/client/index.d.ts.map +1 -0
  41. package/dist/client/index.js +32 -0
  42. package/dist/client/index.js.map +7 -0
  43. package/dist/client/jsx-runtime.d.ts +40 -0
  44. package/dist/client/jsx-runtime.d.ts.map +1 -0
  45. package/dist/client/jsx-runtime.js +139 -0
  46. package/dist/client/jsx-runtime.js.map +7 -0
  47. package/dist/client/resource.d.ts +31 -0
  48. package/dist/client/resource.d.ts.map +1 -0
  49. package/dist/client/resource.js +64 -0
  50. package/dist/client/resource.js.map +7 -0
  51. package/dist/client/signal.d.ts +90 -0
  52. package/dist/client/signal.d.ts.map +1 -0
  53. package/dist/client/signal.js +115 -0
  54. package/dist/client/signal.js.map +7 -0
  55. package/dist/client/store.d.ts +26 -0
  56. package/dist/client/store.d.ts.map +1 -0
  57. package/dist/client/store.js +63 -0
  58. package/dist/client/store.js.map +7 -0
  59. package/dist/client/useHead.d.ts +28 -0
  60. package/dist/client/useHead.d.ts.map +1 -0
  61. package/dist/client/useHead.js +33 -0
  62. package/dist/client/useHead.js.map +7 -0
  63. package/dist/config.d.ts +182 -0
  64. package/dist/config.d.ts.map +1 -0
  65. package/dist/config.js +21 -0
  66. package/dist/config.js.map +7 -0
  67. package/dist/create-multisite.d.ts +2 -0
  68. package/dist/create-multisite.d.ts.map +1 -0
  69. package/dist/create-multisite.js +291 -0
  70. package/dist/create-multisite.js.map +7 -0
  71. package/dist/create.d.ts +2 -0
  72. package/dist/create.d.ts.map +1 -0
  73. package/dist/create.js +179 -0
  74. package/dist/create.js.map +7 -0
  75. package/dist/dev/blueprints.d.ts +11 -0
  76. package/dist/dev/blueprints.d.ts.map +1 -0
  77. package/dist/dev/blueprints.js +65 -0
  78. package/dist/dev/blueprints.js.map +7 -0
  79. package/dist/dev/components.d.ts +19 -0
  80. package/dist/dev/components.d.ts.map +1 -0
  81. package/dist/dev/components.js +87 -0
  82. package/dist/dev/components.js.map +7 -0
  83. package/dist/dev/insert.d.ts +11 -0
  84. package/dist/dev/insert.d.ts.map +1 -0
  85. package/dist/dev/insert.js +160 -0
  86. package/dist/dev/insert.js.map +7 -0
  87. package/dist/dev/remove.d.ts +53 -0
  88. package/dist/dev/remove.d.ts.map +1 -0
  89. package/dist/dev/remove.js +518 -0
  90. package/dist/dev/remove.js.map +7 -0
  91. package/dist/dev/watch.d.ts +26 -0
  92. package/dist/dev/watch.d.ts.map +1 -0
  93. package/dist/dev/watch.js +2905 -0
  94. package/dist/dev/watch.js.map +7 -0
  95. package/dist/errors.d.ts +6 -0
  96. package/dist/errors.d.ts.map +1 -0
  97. package/dist/errors.js +63 -0
  98. package/dist/errors.js.map +7 -0
  99. package/dist/generate.d.ts +2 -0
  100. package/dist/generate.d.ts.map +1 -0
  101. package/dist/generate.js +191 -0
  102. package/dist/generate.js.map +7 -0
  103. package/dist/index.d.ts +9 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +57 -0
  106. package/dist/index.js.map +7 -0
  107. package/dist/island.d.ts +24 -0
  108. package/dist/island.d.ts.map +1 -0
  109. package/dist/island.js +15 -0
  110. package/dist/island.js.map +7 -0
  111. package/dist/jsx-runtime.d.ts +406 -0
  112. package/dist/jsx-runtime.d.ts.map +1 -0
  113. package/dist/jsx-runtime.js +90 -0
  114. package/dist/jsx-runtime.js.map +7 -0
  115. package/dist/link.d.ts +27 -0
  116. package/dist/link.d.ts.map +1 -0
  117. package/dist/link.js +29 -0
  118. package/dist/link.js.map +7 -0
  119. package/dist/oml/fragment.d.ts +16 -0
  120. package/dist/oml/fragment.d.ts.map +1 -0
  121. package/dist/oml/fragment.js +26 -0
  122. package/dist/oml/fragment.js.map +7 -0
  123. package/dist/oml/index.d.ts +11 -0
  124. package/dist/oml/index.d.ts.map +1 -0
  125. package/dist/oml/index.js +21 -0
  126. package/dist/oml/index.js.map +7 -0
  127. package/dist/oml/jsx-runtime.d.ts +34 -0
  128. package/dist/oml/jsx-runtime.d.ts.map +1 -0
  129. package/dist/oml/jsx-runtime.js +59 -0
  130. package/dist/oml/jsx-runtime.js.map +7 -0
  131. package/dist/oml/jsx.d.ts +14 -0
  132. package/dist/oml/jsx.d.ts.map +1 -0
  133. package/dist/oml/jsx.js +96 -0
  134. package/dist/oml/jsx.js.map +7 -0
  135. package/dist/oml/page.d.ts +7 -0
  136. package/dist/oml/page.d.ts.map +1 -0
  137. package/dist/oml/page.js +6 -0
  138. package/dist/oml/page.js.map +7 -0
  139. package/dist/oml/render.d.ts +13 -0
  140. package/dist/oml/render.d.ts.map +1 -0
  141. package/dist/oml/render.js +117 -0
  142. package/dist/oml/render.js.map +7 -0
  143. package/dist/oml/types.d.ts +79 -0
  144. package/dist/oml/types.d.ts.map +1 -0
  145. package/dist/oml/types.js +64 -0
  146. package/dist/oml/types.js.map +7 -0
  147. package/dist/router/handler.d.ts +53 -0
  148. package/dist/router/handler.d.ts.map +1 -0
  149. package/dist/router/handler.js +342 -0
  150. package/dist/router/handler.js.map +7 -0
  151. package/dist/router/matcher.d.ts +21 -0
  152. package/dist/router/matcher.d.ts.map +1 -0
  153. package/dist/router/matcher.js +28 -0
  154. package/dist/router/matcher.js.map +7 -0
  155. package/dist/router/scanner.d.ts +17 -0
  156. package/dist/router/scanner.d.ts.map +1 -0
  157. package/dist/router/scanner.js +197 -0
  158. package/dist/router/scanner.js.map +7 -0
  159. package/dist/server/index.d.ts +23 -0
  160. package/dist/server/index.d.ts.map +1 -0
  161. package/dist/server/index.js +29 -0
  162. package/dist/server/index.js.map +7 -0
  163. package/dist/signal.d.ts +15 -0
  164. package/dist/signal.d.ts.map +1 -0
  165. package/dist/signal.js +29 -0
  166. package/dist/signal.js.map +7 -0
  167. package/dist/ssg.d.ts +45 -0
  168. package/dist/ssg.d.ts.map +1 -0
  169. package/dist/ssg.js +175 -0
  170. package/dist/ssg.js.map +7 -0
  171. package/dist/test/actions.test.d.ts +2 -0
  172. package/dist/test/actions.test.d.ts.map +1 -0
  173. package/dist/test/body-limits.test.d.ts +2 -0
  174. package/dist/test/body-limits.test.d.ts.map +1 -0
  175. package/dist/test/errors.test.d.ts +2 -0
  176. package/dist/test/errors.test.d.ts.map +1 -0
  177. package/dist/test/fixtures/routes/[id].page.d.ts +4 -0
  178. package/dist/test/fixtures/routes/[id].page.d.ts.map +1 -0
  179. package/dist/test/fixtures/routes/_error.d.ts +3 -0
  180. package/dist/test/fixtures/routes/_error.d.ts.map +1 -0
  181. package/dist/test/fixtures/routes/_global.d.ts +3 -0
  182. package/dist/test/fixtures/routes/_global.d.ts.map +1 -0
  183. package/dist/test/fixtures/routes/_layout-template.d.ts +3 -0
  184. package/dist/test/fixtures/routes/_layout-template.d.ts.map +1 -0
  185. package/dist/test/fixtures/routes/_layout.d.ts +3 -0
  186. package/dist/test/fixtures/routes/_layout.d.ts.map +1 -0
  187. package/dist/test/fixtures/routes/_layout_scripts.d.ts +3 -0
  188. package/dist/test/fixtures/routes/_layout_scripts.d.ts.map +1 -0
  189. package/dist/test/fixtures/routes/_middleware.d.ts +3 -0
  190. package/dist/test/fixtures/routes/_middleware.d.ts.map +1 -0
  191. package/dist/test/fixtures/routes/_redirect301_mw.d.ts +3 -0
  192. package/dist/test/fixtures/routes/_redirect301_mw.d.ts.map +1 -0
  193. package/dist/test/fixtures/routes/_redirect_mw.d.ts +3 -0
  194. package/dist/test/fixtures/routes/_redirect_mw.d.ts.map +1 -0
  195. package/dist/test/fixtures/routes/about.page.d.ts +3 -0
  196. package/dist/test/fixtures/routes/about.page.d.ts.map +1 -0
  197. package/dist/test/fixtures/routes/action.page.d.ts +6 -0
  198. package/dist/test/fixtures/routes/action.page.d.ts.map +1 -0
  199. package/dist/test/fixtures/routes/api/form-all.post.d.ts +3 -0
  200. package/dist/test/fixtures/routes/api/form-all.post.d.ts.map +1 -0
  201. package/dist/test/fixtures/routes/api/form-limited.post.d.ts +6 -0
  202. package/dist/test/fixtures/routes/api/form-limited.post.d.ts.map +1 -0
  203. package/dist/test/fixtures/routes/api/response-obj.get.d.ts +3 -0
  204. package/dist/test/fixtures/routes/api/response-obj.get.d.ts.map +1 -0
  205. package/dist/test/fixtures/routes/api/upload.post.d.ts +12 -0
  206. package/dist/test/fixtures/routes/api/upload.post.d.ts.map +1 -0
  207. package/dist/test/fixtures/routes/api/users.get.d.ts +6 -0
  208. package/dist/test/fixtures/routes/api/users.get.d.ts.map +1 -0
  209. package/dist/test/fixtures/routes/api/xml.get.d.ts +3 -0
  210. package/dist/test/fixtures/routes/api/xml.get.d.ts.map +1 -0
  211. package/dist/test/fixtures/routes/auth/_middleware.d.ts +3 -0
  212. package/dist/test/fixtures/routes/auth/_middleware.d.ts.map +1 -0
  213. package/dist/test/fixtures/routes/auth/protected.page.d.ts +3 -0
  214. package/dist/test/fixtures/routes/auth/protected.page.d.ts.map +1 -0
  215. package/dist/test/fixtures/routes/index.page.d.ts +3 -0
  216. package/dist/test/fixtures/routes/index.page.d.ts.map +1 -0
  217. package/dist/test/fixtures/routes/oml.page.d.ts +3 -0
  218. package/dist/test/fixtures/routes/oml.page.d.ts.map +1 -0
  219. package/dist/test/fixtures/routes/redirect.page.d.ts +3 -0
  220. package/dist/test/fixtures/routes/redirect.page.d.ts.map +1 -0
  221. package/dist/test/fixtures/routes/ssg/[slug].page.d.ts +5 -0
  222. package/dist/test/fixtures/routes/ssg/[slug].page.d.ts.map +1 -0
  223. package/dist/test/fixtures/routes/ssg/server.page.d.ts +4 -0
  224. package/dist/test/fixtures/routes/ssg/server.page.d.ts.map +1 -0
  225. package/dist/test/fixtures/routes/state.page.d.ts +3 -0
  226. package/dist/test/fixtures/routes/state.page.d.ts.map +1 -0
  227. package/dist/test/fixtures/routes/throw.page.d.ts +3 -0
  228. package/dist/test/fixtures/routes/throw.page.d.ts.map +1 -0
  229. package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts +3 -0
  230. package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts.map +1 -0
  231. package/dist/test/helpers.d.ts +37 -0
  232. package/dist/test/helpers.d.ts.map +1 -0
  233. package/dist/test/layouts.test.d.ts +2 -0
  234. package/dist/test/layouts.test.d.ts.map +1 -0
  235. package/dist/test/middleware.test.d.ts +2 -0
  236. package/dist/test/middleware.test.d.ts.map +1 -0
  237. package/dist/test/multipart.test.d.ts +2 -0
  238. package/dist/test/multipart.test.d.ts.map +1 -0
  239. package/dist/test/oml-routing.test.d.ts +2 -0
  240. package/dist/test/oml-routing.test.d.ts.map +1 -0
  241. package/dist/test/oml.test.d.ts +2 -0
  242. package/dist/test/oml.test.d.ts.map +1 -0
  243. package/dist/test/redirects.test.d.ts +2 -0
  244. package/dist/test/redirects.test.d.ts.map +1 -0
  245. package/dist/test/routing.test.d.ts +2 -0
  246. package/dist/test/routing.test.d.ts.map +1 -0
  247. package/dist/test/ssg.test.d.ts +2 -0
  248. package/dist/test/ssg.test.d.ts.map +1 -0
  249. package/dist/test/web-response.test.d.ts +2 -0
  250. package/dist/test/web-response.test.d.ts.map +1 -0
  251. package/dist/types.d.ts +314 -0
  252. package/dist/types.d.ts.map +1 -0
  253. package/dist/types.js +292 -0
  254. package/dist/types.js.map +7 -0
  255. package/package.json +103 -0
  256. package/pka.config.json +32 -0
  257. package/src/build/config.ts +42 -0
  258. package/src/build/index.ts +6 -0
  259. package/src/build/plugins.ts +118 -0
  260. package/src/cli.ts +502 -0
  261. package/src/config.ts +197 -0
  262. package/src/create-multisite.ts +310 -0
  263. package/src/create.ts +194 -0
  264. package/src/dev/blueprints.ts +75 -0
  265. package/src/dev/components.ts +108 -0
  266. package/src/dev/insert.ts +221 -0
  267. package/src/dev/remove.ts +677 -0
  268. package/src/dev/watch.ts +3098 -0
  269. package/src/env.d.ts +5 -0
  270. package/src/errors.ts +64 -0
  271. package/src/generate.ts +228 -0
  272. package/src/index.ts +67 -0
  273. package/src/island.ts +47 -0
  274. package/src/jsx-runtime.d.ts +408 -0
  275. package/src/jsx-runtime.d.ts.map +1 -0
  276. package/src/jsx-runtime.ts +536 -0
  277. package/src/link.ts +49 -0
  278. package/src/oml/fragment.ts +54 -0
  279. package/src/oml/index.ts +21 -0
  280. package/src/oml/jsx-runtime.ts +121 -0
  281. package/src/oml/jsx.ts +151 -0
  282. package/src/oml/page.ts +13 -0
  283. package/src/oml/render.ts +181 -0
  284. package/src/oml/types.ts +159 -0
  285. package/src/router/handler.ts +515 -0
  286. package/src/router/matcher.ts +52 -0
  287. package/src/router/scanner.ts +272 -0
  288. package/src/server/index.ts +49 -0
  289. package/src/signal.ts +39 -0
  290. package/src/ssg.ts +253 -0
  291. package/src/test/actions.test.ts +40 -0
  292. package/src/test/body-limits.test.ts +83 -0
  293. package/src/test/errors.test.ts +53 -0
  294. package/src/test/fixtures/routes/[id].page.ts +3 -0
  295. package/src/test/fixtures/routes/_error.ts +6 -0
  296. package/src/test/fixtures/routes/_global.ts +8 -0
  297. package/src/test/fixtures/routes/_layout-template.ts +7 -0
  298. package/src/test/fixtures/routes/_layout.ts +7 -0
  299. package/src/test/fixtures/routes/_layout_scripts.ts +8 -0
  300. package/src/test/fixtures/routes/_middleware.ts +8 -0
  301. package/src/test/fixtures/routes/_redirect301_mw.ts +5 -0
  302. package/src/test/fixtures/routes/_redirect_mw.ts +5 -0
  303. package/src/test/fixtures/routes/about.page.ts +6 -0
  304. package/src/test/fixtures/routes/action.page.ts +11 -0
  305. package/src/test/fixtures/routes/api/form-all.post.ts +5 -0
  306. package/src/test/fixtures/routes/api/form-limited.post.ts +6 -0
  307. package/src/test/fixtures/routes/api/response-obj.get.ts +17 -0
  308. package/src/test/fixtures/routes/api/upload.post.ts +14 -0
  309. package/src/test/fixtures/routes/api/users.get.ts +3 -0
  310. package/src/test/fixtures/routes/api/xml.get.ts +5 -0
  311. package/src/test/fixtures/routes/auth/_middleware.ts +11 -0
  312. package/src/test/fixtures/routes/auth/protected.page.ts +3 -0
  313. package/src/test/fixtures/routes/index.page.ts +3 -0
  314. package/src/test/fixtures/routes/oml.page.ts +7 -0
  315. package/src/test/fixtures/routes/redirect.page.ts +3 -0
  316. package/src/test/fixtures/routes/ssg/[slug].page.ts +8 -0
  317. package/src/test/fixtures/routes/ssg/server.page.ts +5 -0
  318. package/src/test/fixtures/routes/state.page.ts +4 -0
  319. package/src/test/fixtures/routes/throw.page.ts +5 -0
  320. package/src/test/fixtures/routes/wiki/[...slug].page.ts +3 -0
  321. package/src/test/helpers.ts +132 -0
  322. package/src/test/layouts.test.ts +76 -0
  323. package/src/test/middleware.test.ts +69 -0
  324. package/src/test/multipart.test.ts +91 -0
  325. package/src/test/oml-routing.test.ts +59 -0
  326. package/src/test/oml.test.ts +429 -0
  327. package/src/test/redirects.test.ts +32 -0
  328. package/src/test/routing.test.ts +118 -0
  329. package/src/test/ssg.test.ts +273 -0
  330. package/src/test/web-response.test.ts +33 -0
  331. package/src/types.ts +670 -0
  332. package/tsconfig.client.json +17 -0
  333. package/tsconfig.json +20 -0
@@ -0,0 +1,536 @@
1
+ // Server JSX runtime — renders component trees to HTML strings.
2
+ // TypeScript resolves this via jsxImportSource: "davaux" in tsconfig.json.
3
+ // Async components are fully supported: any component may return Promise<string>.
4
+
5
+ type Primitive = string | number | boolean | null | undefined
6
+ // Promise<object|null> lets OML JSX elements (Promise<OmlNode|string>) pass as children without errors.
7
+ type Child = Primitive | Promise<string | object | null> | Child[]
8
+ type StyleObject = Record<string, string | number>
9
+
10
+ export type Props = {
11
+ children?: Child | Child[]
12
+ key?: string | number
13
+ ref?: unknown
14
+ dangerouslySetInnerHTML?: { __html: string }
15
+ style?: string | StyleObject
16
+ className?: string
17
+ htmlFor?: string
18
+ tabIndex?: number
19
+ [prop: string]: unknown
20
+ }
21
+
22
+ export type ComponentType<P extends Props = Props> = (props: P) => JSX.Element
23
+
24
+ // ─── HTML helpers ─────────────────────────────────────────────────────────────
25
+
26
+ const VOID_ELEMENTS = new Set([
27
+ 'area',
28
+ 'base',
29
+ 'br',
30
+ 'col',
31
+ 'embed',
32
+ 'hr',
33
+ 'img',
34
+ 'input',
35
+ 'link',
36
+ 'meta',
37
+ 'param',
38
+ 'source',
39
+ 'track',
40
+ 'wbr',
41
+ ])
42
+
43
+ function escapeHtml(s: string): string {
44
+ return s
45
+ .replace(/&/g, '&amp;')
46
+ .replace(/</g, '&lt;')
47
+ .replace(/>/g, '&gt;')
48
+ .replace(/"/g, '&quot;')
49
+ .replace(/'/g, '&#39;')
50
+ }
51
+
52
+ function styleToString(style: StyleObject): string {
53
+ return Object.entries(style)
54
+ .map(([k, v]) => {
55
+ // camelCase → kebab-case
56
+ const prop = k.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
57
+ return `${prop}:${v}`
58
+ })
59
+ .join(';')
60
+ }
61
+
62
+ async function renderChildren(children: Child | Child[] | undefined): Promise<string> {
63
+ if (children == null || children === false) return ''
64
+ if (Array.isArray(children)) {
65
+ const parts = await Promise.all(children.map((c) => renderChildren(c)))
66
+ return parts.join('')
67
+ }
68
+ if (children instanceof Promise) {
69
+ const resolved = await children
70
+ // The OML jsx runtime produces OmlNode objects instead of strings. Render them to
71
+ // HTML on the fly — the import is cached by Node after the first call.
72
+ if (resolved !== null && typeof resolved === 'object') {
73
+ const { renderToHtml } = await import('./oml/render.js')
74
+ return renderToHtml(resolved as never)
75
+ }
76
+ return (resolved ?? '') as string
77
+ }
78
+ if (typeof children === 'boolean') return ''
79
+ return escapeHtml(String(children))
80
+ }
81
+
82
+ function renderAttrs(props: Props): string {
83
+ const parts: string[] = []
84
+
85
+ for (const [key, val] of Object.entries(props)) {
86
+ if (key === 'children' || key === 'key' || key === 'ref' || key === 'dangerouslySetInnerHTML')
87
+ continue
88
+
89
+ if (val === false || val == null) continue
90
+
91
+ // Map React-isms to HTML attributes
92
+ const attr =
93
+ key === 'className'
94
+ ? 'class'
95
+ : key === 'htmlFor'
96
+ ? 'for'
97
+ : key === 'tabIndex'
98
+ ? 'tabindex'
99
+ : key
100
+
101
+ // Skip event handlers — they have no meaning in SSR
102
+ if (attr.startsWith('on') && attr[2] === attr[2]?.toUpperCase()) continue
103
+
104
+ if (val === true) {
105
+ parts.push(attr)
106
+ continue
107
+ }
108
+
109
+ if (attr === 'style') {
110
+ const css = typeof val === 'string' ? val : styleToString(val as StyleObject)
111
+ parts.push(`style="${escapeHtml(css)}"`)
112
+ continue
113
+ }
114
+
115
+ parts.push(`${attr}="${escapeHtml(String(val))}"`)
116
+ }
117
+
118
+ return parts.length ? ` ${parts.join(' ')}` : ''
119
+ }
120
+
121
+ // ─── JSX factory ──────────────────────────────────────────────────────────────
122
+
123
+ export async function jsx(
124
+ type: string | ComponentType,
125
+ props: Props,
126
+ _key?: string | number,
127
+ ): Promise<string> {
128
+ if (typeof type === 'function') {
129
+ const result = type(props)
130
+ return (result instanceof Promise ? await result : result) as unknown as string
131
+ }
132
+
133
+ if (type === Fragment) {
134
+ return renderChildren(props.children)
135
+ }
136
+
137
+ if (props.dangerouslySetInnerHTML) {
138
+ const attrs = renderAttrs(props)
139
+ return `<${type}${attrs}>${props.dangerouslySetInnerHTML.__html}</${type}>`
140
+ }
141
+
142
+ const attrs = renderAttrs(props)
143
+ if (VOID_ELEMENTS.has(type)) return `<${type}${attrs}>`
144
+
145
+ const children = await renderChildren(props.children)
146
+ return `<${type}${attrs}>${children}</${type}>`
147
+ }
148
+
149
+ export const jsxs = jsx
150
+ export const jsxDEV = jsx
151
+
152
+ export const Fragment = '__Fragment__'
153
+
154
+ // ─── JSX namespace (used by TypeScript for element type checking) ──────────────
155
+
156
+ export namespace JSX {
157
+ // Always Promise<string> — async functions returning JSX auto-unwrap to Promise<string>.
158
+ // Sync functions returning JSX also satisfy this because JSX.Element = Promise<string>
159
+ // and a sync function returning a promise is valid where Promise<string> is expected.
160
+ export type Element = Promise<string>
161
+
162
+ export interface ElementChildrenAttribute {
163
+ children: object
164
+ }
165
+
166
+ export interface IntrinsicAttributes {
167
+ key?: string | number
168
+ }
169
+
170
+ // Shared HTML attribute base
171
+ interface HTMLAttributes {
172
+ id?: string
173
+ class?: string
174
+ className?: string
175
+ style?: string | StyleObject
176
+ title?: string
177
+ lang?: string
178
+ dir?: 'ltr' | 'rtl' | 'auto'
179
+ hidden?: boolean
180
+ tabIndex?: number
181
+ tabindex?: number
182
+ role?: string
183
+ slot?: string
184
+ draggable?: boolean
185
+ spellcheck?: boolean
186
+ contenteditable?: boolean | 'true' | 'false'
187
+ dangerouslySetInnerHTML?: { __html: string }
188
+ // data-* and aria-*
189
+ [attr: string]: unknown
190
+ }
191
+
192
+ interface AnchorAttributes extends HTMLAttributes {
193
+ href?: string
194
+ target?: '_blank' | '_self' | '_parent' | '_top' | string
195
+ rel?: string
196
+ download?: string | boolean
197
+ hreflang?: string
198
+ type?: string
199
+ }
200
+
201
+ interface InputAttributes extends HTMLAttributes {
202
+ type?: string
203
+ name?: string
204
+ value?: string | number
205
+ defaultValue?: string | number
206
+ checked?: boolean
207
+ defaultChecked?: boolean
208
+ placeholder?: string
209
+ disabled?: boolean
210
+ readonly?: boolean
211
+ required?: boolean
212
+ min?: string | number
213
+ max?: string | number
214
+ step?: string | number
215
+ multiple?: boolean
216
+ accept?: string
217
+ autocomplete?: string
218
+ autofocus?: boolean
219
+ form?: string
220
+ pattern?: string
221
+ list?: string
222
+ }
223
+
224
+ interface FormAttributes extends HTMLAttributes {
225
+ action?: string
226
+ method?: 'get' | 'post'
227
+ enctype?: string
228
+ target?: string
229
+ novalidate?: boolean
230
+ autocomplete?: string
231
+ }
232
+
233
+ interface ButtonAttributes extends HTMLAttributes {
234
+ type?: 'button' | 'submit' | 'reset'
235
+ disabled?: boolean
236
+ form?: string
237
+ name?: string
238
+ value?: string
239
+ autofocus?: boolean
240
+ }
241
+
242
+ interface ImgAttributes extends HTMLAttributes {
243
+ src?: string
244
+ alt?: string
245
+ width?: number | string
246
+ height?: number | string
247
+ loading?: 'lazy' | 'eager'
248
+ decoding?: 'async' | 'auto' | 'sync'
249
+ srcset?: string
250
+ sizes?: string
251
+ }
252
+
253
+ interface LinkAttributes extends HTMLAttributes {
254
+ href?: string
255
+ rel?: string
256
+ type?: string
257
+ media?: string
258
+ as?: string
259
+ crossorigin?: string
260
+ integrity?: string
261
+ }
262
+
263
+ interface ScriptAttributes extends HTMLAttributes {
264
+ src?: string
265
+ type?: string
266
+ async?: boolean
267
+ defer?: boolean
268
+ module?: boolean
269
+ crossorigin?: string
270
+ integrity?: string
271
+ nonce?: string
272
+ }
273
+
274
+ interface MetaAttributes extends HTMLAttributes {
275
+ name?: string
276
+ content?: string
277
+ charset?: string
278
+ httpEquiv?: string
279
+ property?: string
280
+ }
281
+
282
+ interface LabelAttributes extends HTMLAttributes {
283
+ htmlFor?: string
284
+ for?: string
285
+ form?: string
286
+ }
287
+
288
+ interface SelectAttributes extends HTMLAttributes {
289
+ name?: string
290
+ value?: string
291
+ defaultValue?: string
292
+ multiple?: boolean
293
+ disabled?: boolean
294
+ required?: boolean
295
+ size?: number
296
+ form?: string
297
+ autocomplete?: string
298
+ }
299
+
300
+ interface TextareaAttributes extends HTMLAttributes {
301
+ name?: string
302
+ value?: string
303
+ defaultValue?: string
304
+ placeholder?: string
305
+ disabled?: boolean
306
+ readonly?: boolean
307
+ required?: boolean
308
+ rows?: number
309
+ cols?: number
310
+ maxlength?: number
311
+ form?: string
312
+ autocomplete?: string
313
+ autofocus?: boolean
314
+ wrap?: 'hard' | 'soft'
315
+ }
316
+
317
+ interface VideoAttributes extends HTMLAttributes {
318
+ src?: string
319
+ controls?: boolean
320
+ autoplay?: boolean
321
+ loop?: boolean
322
+ muted?: boolean
323
+ poster?: string
324
+ width?: number | string
325
+ height?: number | string
326
+ preload?: 'auto' | 'metadata' | 'none'
327
+ }
328
+
329
+ interface AudioAttributes extends HTMLAttributes {
330
+ src?: string
331
+ controls?: boolean
332
+ autoplay?: boolean
333
+ loop?: boolean
334
+ muted?: boolean
335
+ preload?: 'auto' | 'metadata' | 'none'
336
+ }
337
+
338
+ interface ColAttributes extends HTMLAttributes {
339
+ span?: number
340
+ }
341
+
342
+ interface TdAttributes extends HTMLAttributes {
343
+ colspan?: number
344
+ rowspan?: number
345
+ headers?: string
346
+ }
347
+
348
+ interface ThAttributes extends TdAttributes {
349
+ scope?: 'col' | 'row' | 'colgroup' | 'rowgroup'
350
+ abbr?: string
351
+ }
352
+
353
+ interface IframeAttributes extends HTMLAttributes {
354
+ src?: string
355
+ srcdoc?: string
356
+ name?: string
357
+ sandbox?: string
358
+ allow?: string
359
+ allowfullscreen?: boolean
360
+ width?: number | string
361
+ height?: number | string
362
+ loading?: 'lazy' | 'eager'
363
+ referrerpolicy?: string
364
+ }
365
+
366
+ export interface IntrinsicElements {
367
+ // Document structure
368
+ html: HTMLAttributes
369
+ head: HTMLAttributes
370
+ body: HTMLAttributes
371
+ title: HTMLAttributes
372
+ base: HTMLAttributes & { href?: string; target?: string }
373
+ link: LinkAttributes
374
+ meta: MetaAttributes
375
+ style: HTMLAttributes & { media?: string }
376
+ script: ScriptAttributes
377
+ noscript: HTMLAttributes
378
+
379
+ // Sectioning
380
+ main: HTMLAttributes
381
+ header: HTMLAttributes
382
+ footer: HTMLAttributes
383
+ nav: HTMLAttributes
384
+ section: HTMLAttributes
385
+ article: HTMLAttributes
386
+ aside: HTMLAttributes
387
+ h1: HTMLAttributes
388
+ h2: HTMLAttributes
389
+ h3: HTMLAttributes
390
+ h4: HTMLAttributes
391
+ h5: HTMLAttributes
392
+ h6: HTMLAttributes
393
+ hgroup: HTMLAttributes
394
+ address: HTMLAttributes
395
+
396
+ // Content
397
+ div: HTMLAttributes
398
+ span: HTMLAttributes
399
+ p: HTMLAttributes
400
+ pre: HTMLAttributes
401
+ blockquote: HTMLAttributes & { cite?: string }
402
+ hr: HTMLAttributes
403
+ br: HTMLAttributes
404
+ wbr: HTMLAttributes
405
+ figure: HTMLAttributes
406
+ figcaption: HTMLAttributes
407
+ details: HTMLAttributes & { open?: boolean }
408
+ summary: HTMLAttributes
409
+ dialog: HTMLAttributes & { open?: boolean }
410
+
411
+ // Lists
412
+ ul: HTMLAttributes
413
+ ol: HTMLAttributes & { start?: number; reversed?: boolean; type?: string }
414
+ li: HTMLAttributes & { value?: number }
415
+ dl: HTMLAttributes
416
+ dt: HTMLAttributes
417
+ dd: HTMLAttributes
418
+
419
+ // Inline text
420
+ a: AnchorAttributes
421
+ em: HTMLAttributes
422
+ strong: HTMLAttributes
423
+ small: HTMLAttributes
424
+ s: HTMLAttributes
425
+ cite: HTMLAttributes
426
+ q: HTMLAttributes & { cite?: string }
427
+ dfn: HTMLAttributes
428
+ abbr: HTMLAttributes
429
+ code: HTMLAttributes
430
+ var: HTMLAttributes
431
+ samp: HTMLAttributes
432
+ kbd: HTMLAttributes
433
+ sub: HTMLAttributes
434
+ sup: HTMLAttributes
435
+ b: HTMLAttributes
436
+ u: HTMLAttributes
437
+ i: HTMLAttributes
438
+ mark: HTMLAttributes
439
+ ruby: HTMLAttributes
440
+ rp: HTMLAttributes
441
+ rt: HTMLAttributes
442
+ bdi: HTMLAttributes
443
+ bdo: HTMLAttributes
444
+ time: HTMLAttributes & { datetime?: string }
445
+ data: HTMLAttributes & { value?: string }
446
+
447
+ // Forms
448
+ form: FormAttributes
449
+ label: LabelAttributes
450
+ input: InputAttributes
451
+ button: ButtonAttributes
452
+ select: SelectAttributes
453
+ datalist: HTMLAttributes
454
+ optgroup: HTMLAttributes & { label?: string; disabled?: boolean }
455
+ option: HTMLAttributes & {
456
+ value?: string
457
+ disabled?: boolean
458
+ selected?: boolean
459
+ label?: string
460
+ }
461
+ textarea: TextareaAttributes
462
+ output: HTMLAttributes & { for?: string; form?: string; name?: string }
463
+ progress: HTMLAttributes & { value?: number; max?: number }
464
+ meter: HTMLAttributes & {
465
+ value?: number
466
+ min?: number
467
+ max?: number
468
+ low?: number
469
+ high?: number
470
+ optimum?: number
471
+ }
472
+ fieldset: HTMLAttributes & { disabled?: boolean; form?: string; name?: string }
473
+ legend: HTMLAttributes
474
+
475
+ // Media
476
+ img: ImgAttributes
477
+ video: VideoAttributes
478
+ audio: AudioAttributes
479
+ source: HTMLAttributes & {
480
+ src?: string
481
+ type?: string
482
+ srcset?: string
483
+ sizes?: string
484
+ media?: string
485
+ }
486
+ track: HTMLAttributes & {
487
+ src?: string
488
+ kind?: string
489
+ label?: string
490
+ srclang?: string
491
+ default?: boolean
492
+ }
493
+ embed: HTMLAttributes & {
494
+ src?: string
495
+ type?: string
496
+ width?: number | string
497
+ height?: number | string
498
+ }
499
+ object: HTMLAttributes & {
500
+ data?: string
501
+ type?: string
502
+ width?: number | string
503
+ height?: number | string
504
+ }
505
+ picture: HTMLAttributes
506
+ canvas: HTMLAttributes & { width?: number | string; height?: number | string }
507
+ map: HTMLAttributes & { name?: string }
508
+ area: HTMLAttributes & {
509
+ alt?: string
510
+ coords?: string
511
+ shape?: string
512
+ href?: string
513
+ target?: string
514
+ }
515
+
516
+ // Tables
517
+ table: HTMLAttributes
518
+ caption: HTMLAttributes
519
+ colgroup: HTMLAttributes & { span?: number }
520
+ col: ColAttributes
521
+ thead: HTMLAttributes
522
+ tbody: HTMLAttributes
523
+ tfoot: HTMLAttributes
524
+ tr: HTMLAttributes
525
+ td: TdAttributes
526
+ th: ThAttributes
527
+
528
+ // Interactive
529
+ iframe: IframeAttributes
530
+ portal: HTMLAttributes
531
+ menu: HTMLAttributes
532
+
533
+ // Obsolete / catch-all
534
+ [tag: string]: HTMLAttributes
535
+ }
536
+ }
package/src/link.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { JSX } from './jsx-runtime.js'
2
+ import { jsx } from './jsx-runtime.js'
3
+ import type { RequestContext } from './types.js'
4
+
5
+ type AnchorProps = JSX.IntrinsicElements['a']
6
+ type LinkProps = AnchorProps & {
7
+ activeClass?: string
8
+ external?: boolean
9
+ }
10
+
11
+ /**
12
+ * Create basePath-aware link utilities for use in layouts and pages.
13
+ *
14
+ * Returns `{ link, Link }`:
15
+ * - `link(href)` — prefixes the configured `basePath` to absolute paths.
16
+ * - `Link` — a JSX `<a>` component that also handles `activeClass` detection
17
+ * and sets safe defaults (`rel="noopener noreferrer"`, `target="_blank"`) for
18
+ * external links.
19
+ *
20
+ * @example
21
+ * const { link, Link } = createLink(ctx)
22
+ * <Link href="/about" activeClass="active">About</Link>
23
+ * <Link href="https://example.com" external>External</Link>
24
+ */
25
+ export function createLink(ctx: RequestContext) {
26
+ const link = (href: string) => (href.startsWith('/') ? `${ctx.basePath}${href}` : href)
27
+ const Link = ({
28
+ href = '',
29
+ activeClass,
30
+ external,
31
+ class: className,
32
+ rel,
33
+ target,
34
+ ...rest
35
+ }: LinkProps) => {
36
+ const resolvedHref = link(href)
37
+ const isActive = activeClass != null && resolvedHref === ctx.url.pathname
38
+ const classes =
39
+ [className, isActive ? activeClass : undefined].filter(Boolean).join(' ') || undefined
40
+ const resolvedTarget = external && !target ? '_blank' : target
41
+ const resolvedRel = external && !rel ? 'noopener noreferrer' : rel
42
+ return jsx(
43
+ 'a',
44
+ { href: resolvedHref, class: classes, target: resolvedTarget, rel: resolvedRel, ...rest },
45
+ undefined,
46
+ )
47
+ }
48
+ return { link, Link }
49
+ }
@@ -0,0 +1,54 @@
1
+ import type { OmlNode } from './types.js'
2
+
3
+ export type OmlFragmentOptions<P> = {
4
+ /** Derive a stable string cache key from props. Defaults to JSON.stringify(props). */
5
+ key?: (props: P) => string
6
+ /** Time-to-live in milliseconds. Omit for indefinite caching. */
7
+ ttl?: number
8
+ }
9
+
10
+ type CacheEntry = { node: OmlNode; at: number }
11
+
12
+ const registry = new Set<Map<string, CacheEntry>>()
13
+
14
+ /** Invalidate all fragment caches — useful after CMS updates or programmatic cache busting. */
15
+ export function clearAllFragments(): void {
16
+ for (const cache of registry) cache.clear()
17
+ }
18
+
19
+ /** Define a cached, no-props OML fragment. The function runs once; subsequent calls return the cached node. */
20
+ export function defineFragment(
21
+ fn: () => OmlNode | Promise<OmlNode>,
22
+ options?: { ttl?: number },
23
+ ): () => Promise<OmlNode>
24
+
25
+ /** Define a cached OML fragment that accepts props. Caches per unique prop combination. */
26
+ export function defineFragment<P extends Record<string, unknown>>(
27
+ fn: (props: P) => OmlNode | Promise<OmlNode>,
28
+ options?: OmlFragmentOptions<P>,
29
+ ): (props: P) => Promise<OmlNode>
30
+
31
+ export function defineFragment<P extends Record<string, unknown>>(
32
+ fn: (props?: P) => OmlNode | Promise<OmlNode>,
33
+ options?: OmlFragmentOptions<P>,
34
+ ): (props?: P) => Promise<OmlNode> {
35
+ const cache = new Map<string, CacheEntry>()
36
+ registry.add(cache)
37
+
38
+ return async (props?: P): Promise<OmlNode> => {
39
+ const key =
40
+ options?.key && props !== undefined ? options.key(props) : JSON.stringify(props ?? null)
41
+
42
+ const entry = cache.get(key)
43
+ if (entry !== undefined) {
44
+ if (!options?.ttl || Date.now() - entry.at < options.ttl) {
45
+ return entry.node
46
+ }
47
+ cache.delete(key)
48
+ }
49
+
50
+ const node = await fn(props)
51
+ cache.set(key, { node, at: Date.now() })
52
+ return node
53
+ }
54
+ }
@@ -0,0 +1,21 @@
1
+ export type { OmlFragmentOptions } from './fragment.js'
2
+ export { clearAllFragments, defineFragment } from './fragment.js'
3
+ export { blueprintToFile, renderToJsx } from './jsx.js'
4
+ export type { ComponentType as OmlComponentType, Props as OmlJsxProps } from './jsx-runtime.js'
5
+ export { Fragment, jsx, jsxDEV, jsxs } from './jsx-runtime.js'
6
+ export type { OmlPageHandler } from './page.js'
7
+ export { defineOmlPage } from './page.js'
8
+ export { renderToHtml } from './render.js'
9
+ export type {
10
+ OmlBlueprint,
11
+ OmlComponent,
12
+ OmlElement,
13
+ OmlFragment,
14
+ OmlNode,
15
+ OmlPropSchema,
16
+ OmlProps,
17
+ OmlPropType,
18
+ OmlRaw,
19
+ OmlText,
20
+ } from './types.js'
21
+ export { parseOml, parseOmlBlueprint } from './types.js'