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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David L Dyess II
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Davaux
2
+
3
+ #### Please note Davaux is in early alpha. Expect breaking changes, and please report any issues you encounter.
4
+
5
+ An SSR-first JSX framework for Node.js. The server is a first-class citizen — not an afterthought bolted onto a client-side runtime.
6
+
7
+ - **File-based routing** with HTTP methods explicit in the filename
8
+ - **Async JSX** — any component can `await` data, no `getServerSideProps` ceremony
9
+ - **Layouts** — drop `_layout.tsx` in any directory, nesting composes automatically
10
+ - **Islands + signals** — zero client JS by default, opt-in fine-grained reactivity
11
+ - **Typed request context** — params, query, cookies, head, body parsing, redirect
12
+ - **Static generation** — `davaux static` pre-renders your app to static HTML + assets
13
+ - **Multi-sites** — multiple apps in one codebase, shared components, separate configs
14
+
15
+ ---
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ npx davaux create my-app
21
+ cd my-app
22
+ npm install
23
+ npm run dev
24
+ ```
25
+
26
+ Davaux requires Node.js 22+.
27
+
28
+ `davaux create` scaffolds a complete project: `package.json`, `tsconfig.json`, `davaux.config.ts`, `biome.json`, a root layout, and a starter index page — ready to run.
29
+
30
+ ## Documentation
31
+
32
+ Full documentation at **[davaux.codeberg.page/docs](https://davaux.codeberg.page/docs/)** — routing, request context, middleware, layouts, islands, signals, CLI, configuration, static generation, and more.
33
+
34
+ ## License
35
+
36
+ MIT © David L Dyess II
package/ROADMAP.md ADDED
@@ -0,0 +1,198 @@
1
+ # Davaux — Roadmap
2
+
3
+ What the framework needs, roughly in the order I'd build it.
4
+
5
+ ---
6
+
7
+ ## Tier 1 — What to build next
8
+
9
+ ### `@davaux/docs-gen` — CSS base theme
10
+
11
+ The generated site has no stylesheet. docs-gen should write a minimal `src/layout.css` alongside `_layout.tsx` — enough to be readable and usable out of the box: sidebar layout, prose width, code block styling, light/dark via `data-theme`, active nav link. Opinionated but overridable; users can replace the file entirely. The CSS class names in the generated layout (`docs-shell`, `sidebar`, `prose`, etc.) are the stable contract.
12
+
13
+ ### `@davaux/docs-gen` — Route tree page
14
+
15
+ Scan a Davaux project's `src/routes/` directory and generate a `routes.page.mdx` showing the full routing tree: every URL pattern, its HTTP method(s), whether it's a page or an API handler, dynamic segments, and which layouts and middlewares apply. Reuses the same filename convention logic as `src/router/scanner.ts`.
16
+
17
+ ```
18
+ Method Path File
19
+ ────────────────────────────────────────────────
20
+ GET / index.page.tsx
21
+ GET /about about.page.tsx
22
+ GET /api/users api/users.get.ts
23
+ POST /api/users api/users.post.ts
24
+ DELETE /api/users/:id api/users.[id].delete.ts
25
+ GET /blog/:slug blog/[slug].page.tsx
26
+ └─ layout blog/_layout.tsx
27
+ └─ middleware blog/_middleware.ts
28
+ ```
29
+
30
+ Configured via a `routes` field in `docs-gen.config.ts` that points at the routes directory to scan.
31
+
32
+ ### `@davaux/docs-gen` — JSDoc API extraction
33
+
34
+ Walk exported TypeScript symbols using the TypeScript compiler API. For each export, extract the full type signature, `@param` / `@returns` / `@example` / `@throws` JSDoc tags, and whether it's deprecated. Output one `api.page.mdx` per package, rendered as a structured API reference table alongside the README page.
35
+
36
+ Because the content derives from the source, the only way to have outdated API docs is to have outdated comments — and those show up in code review diffs next to the functions they describe.
37
+
38
+ **Prerequisite**: consistent JSDoc coverage on all public exports. The `@davaux/*` packages have this; the core `davaux` package is close.
39
+
40
+ ### `@davaux/docs-gen` — Error page
41
+
42
+ Generate a `_error.tsx` for the docs site so 404s and runtime errors render within the docs layout rather than a blank page.
43
+
44
+ ---
45
+
46
+ ## Tier 2 — Developer experience and ecosystem
47
+
48
+ ### `@davaux/multisite` — CLI integration
49
+
50
+ The MVP is shipped — see the Shipped section for the full feature list. Remaining work: `davaux.config.ts`-driven multisite with no boilerplate entry files:
51
+
52
+ ```ts
53
+ // davaux.config.ts
54
+ import { defineConfig } from 'davaux/config'
55
+ import { defineSites } from '@davaux/multisite'
56
+
57
+ export default defineConfig({
58
+ server: { port: 3000 },
59
+ sites: defineSites<SiteConfig>({
60
+ baseDir: './src/routes',
61
+ sites: [...],
62
+ }),
63
+ })
64
+ ```
65
+
66
+ The CLI detects `config.sites` and switches to multisite mode automatically. `server.ts` remains available as an escape hatch.
67
+
68
+ ### Custom server entry point (`server.ts`) — dev mode
69
+
70
+ Build and start support is shipped — see the Shipped section. Remaining work: dev-mode integration. Currently `davaux dev` warns when `server.ts` is present and suggests `node --watch --import tsx/esm server.ts` as the development approach. Proper dev-mode integration would compile `server.ts` into an esbuild watch context alongside routes and re-invoke the start function on rebuild, similar to how the standard dev server hot-swaps the compiled app without restarting. This requires carefully threading the server lifecycle through the watch loop — the start function must be disposable so a new server can replace it when `server.ts` or its imports change.
71
+
72
+ ### Server-Sent Events support for handlers (`ctx.sse()`)
73
+
74
+ Users building real-time features (live notifications, dashboards, log tails) currently have to set SSE headers and write the wire format manually against `ctx.res`. A `ctx.sse(setup)` method would handle the protocol details:
75
+
76
+ ```ts
77
+ export default defineHandler((ctx) => {
78
+ ctx.sse((stream) => {
79
+ const interval = setInterval(() => {
80
+ stream.send({ data: JSON.stringify({ ts: Date.now() }) })
81
+ }, 1000)
82
+ stream.onClose(() => clearInterval(interval))
83
+ })
84
+ })
85
+ ```
86
+
87
+ `SSEStream` would expose `send({ data, event?, id? })` for typed events and `onClose(fn)` for cleanup. Lives in core (`types.ts` + `makeContext`) alongside `ctx.send()` — no separate package needed since SSE is a standard web primitive and the implementation is ~40 lines.
88
+
89
+ ### Islands lazy hydration strategy
90
+
91
+ Allow islands to declare when they should hydrate rather than always hydrating immediately on page load:
92
+
93
+ ```tsx
94
+ <Counter client:visible /> // hydrate when scrolled into view (IntersectionObserver)
95
+ <Analytics client:idle /> // hydrate during requestIdleCallback
96
+ <Modal client:load /> // hydrate immediately (current default)
97
+ ```
98
+
99
+ Useful for content-heavy pages with many islands below the fold — deferring hydration improves Time-to-Interactive without changing how the islands are authored. The `island()` wrapper would emit extra data attributes; `hydrate.ts` would route each island through the appropriate scheduler.
100
+
101
+ ---
102
+
103
+ ## Tier 3 — Production completeness
104
+
105
+ ### Deployment adapters
106
+ Davaux is currently Node.js-only — it starts an `http.createServer` and assumes a persistent process. This rules out Cloudflare Workers, Vercel Edge Functions, Deno Deploy, and similar platforms that use a fetch-handler model (`(req: Request) => Response`). An adapter system would let the framework core stay agnostic while platform-specific packages (`@davaux/adapter-cloudflare`, `@davaux/adapter-vercel`) wrap the dispatch logic in the right runtime interface.
107
+
108
+ The main challenge is that `RequestContext` is built on Node's `IncomingMessage`/`ServerResponse` — a web-fetch adapter would need a compatibility shim or a new context model. The SSG mock req/res (already in `ssg.ts`) is a proof of concept that the context can be faked; a real adapter would do the same thing cleanly.
109
+
110
+
111
+ ### Streaming SSR
112
+ Sending HTML as it renders rather than waiting for the full page. Perceived performance wins on pages with slow data fetches — the browser renders the shell while deeper components load.
113
+
114
+ This is a significant architectural change: `JSX.Element = Promise<string>` would need to become a `ReadableStream`, and the server would switch from `res.end(html)` to streaming. The signals/islands architecture doesn't depend on it. I'd revisit after the framework is otherwise solid.
115
+
116
+ ---
117
+
118
+ ## Tier 4 — Things worth thinking about but not building yet
119
+
120
+ ### Islands HMR
121
+ After live reload (full page refresh) is working, the next level is hot-replacing only the changed island without a full reload — preserving counter state across edits, for example. Requires the dev server to send a more specific message (which island changed) and the client runtime to tear down and remount just that component. Genuinely useful for iterating on island UI; not urgent.
122
+
123
+ ---
124
+
125
+ ## Known Limitations
126
+
127
+ Intentional tradeoffs and deferred design decisions. Not bugs, but worth understanding before building on top of them.
128
+
129
+ ### Island props must be JSON-serializable
130
+ The `island()` wrapper serializes props into a `data-props` attribute as JSON for client hydration. `Date` objects, class instances, `undefined`, functions, and circular references are silently lost or corrupted. Pass primitive values and plain objects only. This is inherent to the SSR→hydration boundary and would require a serialization library (e.g. superjson) to work around.
131
+
132
+ ### `<For>` without `key` does full re-renders
133
+ Without a `key` prop, every array change destroys and recreates all child nodes — O(n) on every update. Pass `key={(item) => item.id}` to enable keyed diffing (create once, move/update in O(n) DOM ops).
134
+
135
+ ### Scoped middleware (`_middleware.ts`) does not run for 404s
136
+ `_middleware.ts` only applies to matched routes. An unmatched request goes directly to `sendErrorPage` without passing through any scoped middleware. This is intentional — auth middleware that redirects on no-session would otherwise loop on a missing asset. Use `src/middleware.ts` (app-level) for middleware that must run on every request, including 404s.
137
+
138
+ ### Signals implementation covers the common cases but not the edge cases
139
+ The custom signal implementation handles the 90% case cleanly — `createSignal`, `createEffect`, `createMemo`, `batch`, `createStore` all behave correctly for typical island patterns. The remaining 10% is the territory that SolidJS spent years hardening: nested effects that create child effects, signals read inside async gaps (after an `await`), effects that conditionally read different signals on different runs, and memory leaks when reactive roots aren't disposed. None of these are currently tested. For complex islands, it's worth being aware that the implementation may have subtle differences from SolidJS's semantics in these edge cases.
140
+
141
+ ### `@davaux/session` uses `writeHead` monkey-patching to set cookies
142
+
143
+ The session middleware needs to write the `Set-Cookie` header after `next()` resolves but before headers are sent. The problem is that route handlers call `res.writeHead()` themselves (via `sendPage` / `sendErrorPage`), at which point `res.setHeader()` is no longer valid.
144
+
145
+ The current implementation patches `ctx.res.writeHead` at middleware setup time, injects the cookie inside the patched function, then restores the original. This works correctly in all tested cases but is a non-obvious workaround. A cleaner approach would be a framework-level "before-send" hook that middleware can register callbacks on — similar to Fastify's `onSend` hook — so cookie injection doesn't need to reach into Node's internals. This is a Tier 3 architectural item; the monkey-patch is not broken, just worth revisiting when the response lifecycle is formalised.
146
+
147
+ ### Cookie-based sessions are capped at ~4 KB
148
+ `@davaux/session` stores the entire session payload in a signed cookie. Browsers enforce a per-cookie size limit (typically 4096 bytes). Large session objects — deep user profiles, embedded tokens, permission sets — will be silently truncated or rejected by the browser. Keep session data minimal (a user ID, a CSRF token) and fetch the rest from a database in the handler.
149
+
150
+ ---
151
+
152
+ ## Tooling
153
+
154
+ ### `@davaux/docs-gen` — Watch mode
155
+
156
+ `davaux-gen --watch` — rebuild pages when source files change (READMEs, TypeScript exports, routes directory). Pairs with the generated site's `npm run dev` so the docs stay live during development. Uses Node's `fs.watch` or a similar low-dependency watcher; rebuilds only the affected pages rather than the full site.
157
+
158
+ ### `@davaux/docs-gen` — Changelog page
159
+
160
+ Generate a `changelog.page.mdx` from a `CHANGELOG.md` file or by walking git log with conventional commit format. Useful for communicating what changed between versions without maintaining the changelog manually.
161
+
162
+ ### `@davaux/docs-gen` — Package dependency graph
163
+
164
+ Visualise how `@davaux/*` packages relate to each other — which ones are peers, which are runtime dependencies, which are dev-only. Rendered as a simple Mermaid diagram embedded in a generated page. Helps contributors understand the dependency order before making changes.
165
+
166
+ ---
167
+
168
+ ## Ecosystem — @davaux packages
169
+
170
+ The `@davaux` NPM organisation makes it possible to keep the core lean while shipping official, well-integrated add-ons. The strategy: `davaux` (core) handles routing, rendering, islands, and the request/response model. `@davaux/*` packages build on top of that without pulling unnecessary weight into every app.
171
+
172
+ ### `@davaux/docs-gen` — documentation site generator
173
+
174
+ Shipped MVP: scaffolds a complete Davaux project (package.json, davaux.config.ts, tsconfig.json, `_layout.tsx`) and generates MDX pages from package READMEs with auto-built sidebar nav. Run `npm run docs` at the monorepo root.
175
+
176
+ Companion to `@davaux/pka`: `pka` produces machine-readable project knowledge for LLMs; `docs-gen` produces the human-readable docs site. Both derive from the same source artifacts — write a JSDoc comment or README once, both audiences benefit. `pka` is optional and fully decoupled from `docs-gen`.
177
+
178
+ **Planned additions** (see Tier 1 and Tooling above): CSS base theme, route tree page, JSDoc API extraction, error page, watch mode, changelog, dependency graph.
179
+
180
+ ---
181
+
182
+ ## What I'd build next
183
+
184
+ 1. **`@davaux/docs-gen` CSS theme** — the generated site is functional but unstyled; a base `layout.css` is the quickest way to make it presentable
185
+ 2. **`@davaux/docs-gen` route tree** — Davaux-specific killer feature; scan `src/routes/` and render a URL × method × file table for any project
186
+ 3. **`@davaux/docs-gen` JSDoc extraction** — TypeScript compiler API walk of exported symbols into structured API reference pages; JSDoc coverage on `@davaux/*` is already in good shape
187
+
188
+ ---
189
+
190
+ ## Shipped
191
+
192
+ - **Lazy `<Show>` children** — `children` and `fallback` now accept `() => Node` factories; construction is deferred until the condition is first true and cached for subsequent toggles
193
+ - **Reactive source for `createResource`** — `createResource(source, fetcher)` re-fetches whenever the source signal changes; stale in-flight responses are discarded automatically
194
+ - **`<ErrorBoundary>`** — catches errors during initial island construction (including first-run effects) and renders a fallback; errors from subsequent reactive updates are not caught
195
+ - **`@davaux/scoped-css`** — location-based CSS scoping for islands; class names get a deterministic hash suffix, `?global` opt-out, and a reactive `cx()` helper that returns a signal getter when conditions are signals
196
+ - **`@davaux/docs-gen`** — CLI tool (`davaux-gen`) that generates a complete deployable Davaux site from source artifacts: scaffolds `package.json`/`davaux.config.ts`/`tsconfig.json`, generates MDX pages from package READMEs, and produces a `_layout.tsx` with auto-built nav. pka (`@davaux/pka`) remains a separate optional step for CLAUDE.md generation
197
+ - **`@davaux/multisite`** — Multi-site hosting from a single Davaux process. Core runtime: `buildMultisiteApps(config, opts)` scans and merges route trees for each site; `startMultisiteServer(apps, opts)` dispatches by `Host` header; `getSite<T>(ctx)` injects per-site config via a `WeakMap` keyed on the request; `defineSites<T>` provides TypeScript inference; `mergeScanResults(base, overlay)` applies site-specific overrides (same URL pattern + type wins, layout/middleware override at `dirPath`, error page fallback to base); `dispatchToSite` for custom server embedding and testing. Convenience layer: `startMultisite(config, opts)` combines build + start in one call with automatic `isDev` detection from `NODE_ENV`; `buildMultisite(config, opts)` from `@davaux/multisite/build` compiles all site route trees and entry points to `dist/` using esbuild. Three operating modes: shared base + per-site overlay (layered CMS), fully independent co-located sites, same routes + different config (SaaS tenancy). CLI integration via `config.sites` and per-site island bundles are planned.
198
+ - **Custom server entry point (`server.ts`)** — An optional `server.ts` at the project root lets single-site apps take full control of server startup. `davaux build` compiles it to `dist/server.js` alongside routes and islands. `davaux start` detects the default export and calls `start(app, serveStatics)` instead of starting its own server; `serveStatics` pre-handles `/_davaux/*.js`, `/_davaux/*.css`, and `public/` so the custom server doesn't need to. `davaux dev` warns when `server.ts` is present and points to `node --watch --import tsx/esm server.ts`. Unlocks HTTPS/TLS, HTTP/2, WebSocket co-hosting, Node.js clustering, and custom middleware stacks without framework changes. Dev-mode integration is planned.
package/build.mjs ADDED
@@ -0,0 +1,101 @@
1
+ import { execSync } from 'node:child_process'
2
+ import { build } from 'esbuild'
3
+
4
+ const shared = { bundle: false, sourcemap: true }
5
+
6
+ // ── Server-side top-level entries ──────────────────────────────────────────────
7
+ await build({
8
+ ...shared,
9
+ entryPoints: [
10
+ 'src/index.ts',
11
+ 'src/types.ts',
12
+ 'src/jsx-runtime.ts',
13
+ 'src/signal.ts',
14
+ 'src/island.ts',
15
+ 'src/link.ts',
16
+ 'src/config.ts',
17
+ 'src/errors.ts',
18
+ 'src/create.ts',
19
+ 'src/create-multisite.ts',
20
+ 'src/generate.ts',
21
+ 'src/ssg.ts',
22
+ ],
23
+ outdir: 'dist',
24
+ format: 'esm',
25
+ platform: 'node',
26
+ target: 'node22',
27
+ })
28
+
29
+ // ── Router, server, dev internals ──────────────────────────────────────────────
30
+ await build({
31
+ ...shared,
32
+ entryPoints: [
33
+ 'src/router/scanner.ts',
34
+ 'src/router/matcher.ts',
35
+ 'src/router/handler.ts',
36
+ 'src/server/index.ts',
37
+ 'src/build/index.ts',
38
+ 'src/build/plugins.ts',
39
+ 'src/build/config.ts',
40
+ 'src/dev/blueprints.ts',
41
+ 'src/dev/components.ts',
42
+ 'src/dev/insert.ts',
43
+ 'src/dev/remove.ts',
44
+ 'src/dev/watch.ts',
45
+ 'src/oml/fragment.ts',
46
+ 'src/oml/index.ts',
47
+ 'src/oml/jsx-runtime.ts',
48
+ 'src/oml/jsx.ts',
49
+ 'src/oml/page.ts',
50
+ 'src/oml/render.ts',
51
+ 'src/oml/types.ts',
52
+ ],
53
+ outdir: 'dist',
54
+ outbase: 'src',
55
+ format: 'esm',
56
+ platform: 'node',
57
+ target: 'node22',
58
+ })
59
+
60
+ // ── CLI — unbundled so all imports resolve to dist/* at runtime, keeping a
61
+ // single module instance of types.ts shared with user route files. ──────────
62
+ await build({
63
+ ...shared,
64
+ entryPoints: ['src/cli.ts'],
65
+ outfile: 'dist/cli.js',
66
+ format: 'esm',
67
+ platform: 'node',
68
+ target: 'node22',
69
+ banner: { js: '#!/usr/bin/env node' },
70
+ })
71
+
72
+ // ── Client (browser islands) ────────────────────────────────────────────────────
73
+ // bundle: false so the app's bundler sees a single shared signal.js and
74
+ // deduplicates it. Bundling here would inline signal.ts into both
75
+ // jsx-runtime.js and index.js, giving two separate currentSubscriber globals.
76
+ await build({
77
+ bundle: false,
78
+ sourcemap: true,
79
+ entryPoints: [
80
+ 'client/signal.ts',
81
+ 'client/jsx-runtime.ts',
82
+ 'client/hydrate.ts',
83
+ 'client/control.ts',
84
+ 'client/resource.ts',
85
+ 'client/store.ts',
86
+ 'client/useHead.ts',
87
+ 'client/index.ts',
88
+ ],
89
+ outdir: 'dist/client',
90
+ format: 'esm',
91
+ platform: 'browser',
92
+ target: 'es2022',
93
+ })
94
+
95
+ // ── Type declarations ───────────────────────────────────────────────────────────
96
+ execSync('npx tsc -p tsconfig.json --emitDeclarationOnly --noEmit false', { stdio: 'inherit' })
97
+ execSync('npx tsc -p tsconfig.client.json --emitDeclarationOnly --noEmit false', {
98
+ stdio: 'inherit',
99
+ })
100
+
101
+ console.log('Build complete.')
@@ -0,0 +1,247 @@
1
+ import { createEffect, createSignal } from './signal.js'
2
+
3
+ // DocumentFragment is consumed on first insert, so we must spread its children
4
+ // into a plain array before we can track and re-attach them.
5
+ function flatNodes(node: Node): Node[] {
6
+ if (node instanceof DocumentFragment) return [...node.childNodes]
7
+ return [node]
8
+ }
9
+
10
+ // ─── <For> ───────────────────────────────────────────────────────────────────
11
+
12
+ export interface ForProps<T> {
13
+ each: (() => T[]) | T[]
14
+ /** When provided, enables keyed diffing: nodes are reused/moved instead of
15
+ * destroyed and recreated on every array change. The key must be unique
16
+ * within the list. Same key → same DOM node; the index signal updates in place. */
17
+ key?: (item: T) => string | number
18
+ children: (item: T, index: () => number) => Node
19
+ }
20
+
21
+ /**
22
+ * Render a reactive list. Re-renders the full list on every array change by default.
23
+ * Pass `key` to enable keyed diffing — each item's DOM node is created once per key
24
+ * and moved or removed rather than destroyed and recreated on every update.
25
+ * The `index` argument to the render function is a reactive signal that stays
26
+ * correct as items are inserted, removed, or reordered.
27
+ */
28
+ export function For<T>(props: ForProps<T>): Node {
29
+ const anchor = document.createTextNode('')
30
+
31
+ if (!props.key) {
32
+ // Non-keyed: full re-render on every change.
33
+ let prevNodes: Node[] = []
34
+ let initialized = false
35
+
36
+ createEffect(() => {
37
+ const items = typeof props.each === 'function' ? props.each() : props.each
38
+ const newNodes: Node[] = items.flatMap((item, i) => flatNodes(props.children(item, () => i)))
39
+
40
+ if (!initialized) {
41
+ initialized = true
42
+ prevNodes = newNodes
43
+ } else {
44
+ const parent = anchor.parentNode
45
+ if (parent) {
46
+ for (const n of newNodes) parent.insertBefore(n, anchor)
47
+ for (const n of prevNodes) n.parentNode?.removeChild(n)
48
+ prevNodes = newNodes
49
+ }
50
+ }
51
+ })
52
+
53
+ const frag = document.createDocumentFragment()
54
+ for (const n of prevNodes) frag.append(n)
55
+ frag.append(anchor)
56
+ return frag as unknown as Node
57
+ }
58
+
59
+ // Keyed diffing: create once per key, move/update on subsequent changes.
60
+ type Entry = { nodes: Node[]; setIndex: (i: number) => void }
61
+ const keyFn = props.key
62
+ const entries = new Map<string | number, Entry>()
63
+ let initialized = false
64
+
65
+ createEffect(() => {
66
+ const items = typeof props.each === 'function' ? props.each() : props.each
67
+ const nextKeys = new Set<string | number>()
68
+ const ordered: Entry[] = []
69
+
70
+ for (let i = 0; i < items.length; i++) {
71
+ const k = keyFn(items[i])
72
+ nextKeys.add(k)
73
+ let entry = entries.get(k)
74
+ if (entry) {
75
+ entry.setIndex(i)
76
+ } else {
77
+ const [index, setIndex] = createSignal(i)
78
+ entry = { nodes: flatNodes(props.children(items[i], index)), setIndex }
79
+ entries.set(k, entry)
80
+ }
81
+ ordered.push(entry)
82
+ }
83
+
84
+ if (!initialized) {
85
+ initialized = true
86
+ return
87
+ }
88
+
89
+ // Remove entries no longer in the list.
90
+ for (const [k, entry] of entries) {
91
+ if (!nextKeys.has(k)) {
92
+ for (const n of entry.nodes) n.parentNode?.removeChild(n)
93
+ entries.delete(k)
94
+ }
95
+ }
96
+
97
+ // Reorder: walk backwards, inserting each entry's nodes before a running
98
+ // reference node. This places every entry exactly once in O(n) DOM ops.
99
+ const parent = anchor.parentNode
100
+ if (parent) {
101
+ let ref: Node = anchor
102
+ for (let i = ordered.length - 1; i >= 0; i--) {
103
+ const { nodes } = ordered[i]
104
+ for (let ni = nodes.length - 1; ni >= 0; ni--) {
105
+ parent.insertBefore(nodes[ni], ref)
106
+ }
107
+ ref = nodes[0]
108
+ }
109
+ }
110
+ })
111
+
112
+ // Initial fragment — entries were populated synchronously by the first
113
+ // effect run above; anchor hasn't been inserted yet so no DOM ops ran.
114
+ const frag = document.createDocumentFragment()
115
+ for (const [, { nodes }] of entries) {
116
+ for (const n of nodes) frag.append(n)
117
+ }
118
+ frag.append(anchor)
119
+ return frag as unknown as Node
120
+ }
121
+
122
+ // ─── <Show> ──────────────────────────────────────────────────────────────────
123
+ // Conditional rendering. Children and fallback are resolved lazily — pass a
124
+ // function child `() => <Node>` to defer construction until `when` is first
125
+ // true. Once resolved, nodes are cached and only attached/detached on toggle.
126
+
127
+ export interface ShowProps {
128
+ when: (() => boolean) | boolean
129
+ children: Node | Node[] | (() => Node | Node[])
130
+ fallback?: Node | (() => Node)
131
+ }
132
+
133
+ /**
134
+ * Conditionally render content. Children and fallback are cached after first
135
+ * construction and only attached or detached as the condition toggles.
136
+ *
137
+ * Pass a function child `() => <Node>` to defer construction until `when` is
138
+ * first true — useful when children contain expensive effects or data fetching.
139
+ */
140
+ export function Show(props: ShowProps): Node {
141
+ const anchor = document.createTextNode('')
142
+
143
+ let childNodes: Node[] | null = null
144
+ let fallbackNodes: Node[] | null = null
145
+
146
+ function getChildNodes(): Node[] {
147
+ if (childNodes !== null) return childNodes
148
+ const raw = typeof props.children === 'function' ? props.children() : props.children
149
+ childNodes = flatNodes(
150
+ Array.isArray(raw)
151
+ ? (() => {
152
+ const f = document.createDocumentFragment()
153
+ for (const n of raw) f.append(n)
154
+ return f
155
+ })()
156
+ : raw,
157
+ )
158
+ return childNodes
159
+ }
160
+
161
+ function getFallbackNodes(): Node[] {
162
+ if (fallbackNodes !== null) return fallbackNodes
163
+ if (!props.fallback) {
164
+ fallbackNodes = []
165
+ return fallbackNodes
166
+ }
167
+ const raw = typeof props.fallback === 'function' ? props.fallback() : props.fallback
168
+ fallbackNodes = flatNodes(raw)
169
+ return fallbackNodes
170
+ }
171
+
172
+ let prevVisible: boolean | undefined
173
+
174
+ createEffect(() => {
175
+ const visible = typeof props.when === 'function' ? props.when() : props.when
176
+ if (visible === prevVisible) return
177
+ prevVisible = visible
178
+
179
+ const parent = anchor.parentNode
180
+ if (!parent) return
181
+
182
+ const toAdd = visible ? getChildNodes() : getFallbackNodes()
183
+ const toRemove = visible ? (fallbackNodes ?? []) : (childNodes ?? [])
184
+
185
+ for (const n of toAdd) parent.insertBefore(n, anchor)
186
+ for (const n of toRemove) n.parentNode?.removeChild(n)
187
+ })
188
+
189
+ const frag = document.createDocumentFragment()
190
+ const initialVisible = typeof props.when === 'function' ? props.when() : props.when
191
+ const initialNodes = initialVisible ? getChildNodes() : getFallbackNodes()
192
+ for (const n of initialNodes) frag.append(n)
193
+ frag.append(anchor)
194
+ return frag as unknown as Node
195
+ }
196
+
197
+ // ─── <ErrorBoundary> ─────────────────────────────────────────────────────────
198
+
199
+ export interface ErrorBoundaryProps {
200
+ /** Rendered when children throw. Receives the caught error if passed as a function. */
201
+ fallback: Node | ((error: Error) => Node)
202
+ /** Must be a function so construction is deferred into the try/catch. */
203
+ children: () => Node | Node[]
204
+ }
205
+
206
+ /**
207
+ * Catch errors thrown during the initial construction of children — including
208
+ * errors in the first run of any `createEffect` inside them — and render
209
+ * `fallback` instead.
210
+ *
211
+ * Children must be a function so their construction is deferred into the
212
+ * boundary's try/catch. A plain JSX child would be evaluated before
213
+ * `ErrorBoundary` runs and could not be caught.
214
+ *
215
+ * Errors from subsequent reactive updates are not caught — add try/catch
216
+ * inside those effects for fine-grained recovery.
217
+ */
218
+ export function ErrorBoundary(props: ErrorBoundaryProps): Node {
219
+ let nodes: Node[] = []
220
+ let caught: Error | null = null
221
+
222
+ try {
223
+ const raw = props.children()
224
+ nodes = Array.isArray(raw)
225
+ ? flatNodes(
226
+ (() => {
227
+ const f = document.createDocumentFragment()
228
+ for (const n of raw) f.append(n)
229
+ return f
230
+ })(),
231
+ )
232
+ : flatNodes(raw)
233
+ } catch (e) {
234
+ caught = e instanceof Error ? e : new Error(String(e))
235
+ }
236
+
237
+ const frag = document.createDocumentFragment()
238
+
239
+ if (caught !== null) {
240
+ const fallback = typeof props.fallback === 'function' ? props.fallback(caught) : props.fallback
241
+ for (const n of flatNodes(fallback)) frag.append(n)
242
+ } else {
243
+ for (const n of nodes) frag.append(n)
244
+ }
245
+
246
+ return frag as unknown as Node
247
+ }
@@ -0,0 +1,37 @@
1
+ // Island hydration — runs in the browser after the server-rendered page loads.
2
+ // Finds [data-island] elements, imports the matching component module, and
3
+ // mounts it with the serialized props so signals become reactive.
4
+
5
+ import { createRoot } from './signal.js'
6
+
7
+ export interface IslandManifest {
8
+ [islandId: string]: () => Promise<{ default: (props: Record<string, unknown>) => Node }>
9
+ }
10
+
11
+ export async function hydrate(manifest: IslandManifest): Promise<void> {
12
+ const islands = document.querySelectorAll<HTMLElement>('[data-island]')
13
+
14
+ await Promise.all(
15
+ Array.from(islands).map(async (el) => {
16
+ const id = el.dataset.island
17
+ if (!id || !(id in manifest)) return
18
+
19
+ let props: Record<string, unknown> = {}
20
+ try {
21
+ props = JSON.parse(el.dataset.props ?? '{}')
22
+ } catch {
23
+ console.warn(`[davaux] Failed to parse props for island "${id}"`)
24
+ }
25
+
26
+ const { default: Component } = await manifest[id]()
27
+
28
+ // Run the component inside a reactive root so any onCleanup() calls
29
+ // (e.g. from createResource refetchInterval) are collected and can be
30
+ // disposed if the island is ever unmounted.
31
+ const node = createRoot(() => Component(props))
32
+
33
+ // Replace server-rendered children with the live DOM subtree
34
+ el.replaceChildren(node)
35
+ }),
36
+ )
37
+ }
@@ -0,0 +1,19 @@
1
+ export type { ErrorBoundaryProps, ForProps, ShowProps } from './control.js'
2
+ export { ErrorBoundary, For, Show } from './control.js'
3
+ export type { IslandManifest } from './hydrate.js'
4
+ export { hydrate } from './hydrate.js'
5
+ export type { Resource, ResourceOptions, ResourceReturn } from './resource.js'
6
+ export { createEventSource, createResource } from './resource.js'
7
+ export {
8
+ batch,
9
+ createEffect,
10
+ createMemo,
11
+ createRoot,
12
+ createSignal,
13
+ onCleanup,
14
+ untrack,
15
+ } from './signal.js'
16
+ export type { SetStoreFn, Updater } from './store.js'
17
+ export { createStore } from './store.js'
18
+ export type { MetaDescriptor, UseHeadOptions } from './useHead.js'
19
+ export { useHead } from './useHead.js'