@nifrajs/web 3.5.0 → 4.0.2

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 (390) hide show
  1. package/dist/backend-only.d.ts +15 -0
  2. package/dist/backend-only.d.ts.map +1 -0
  3. package/dist/backend-only.js +16 -0
  4. package/dist/backend-only.js.map +1 -0
  5. package/dist/boundary.d.ts +5 -0
  6. package/dist/boundary.d.ts.map +1 -1
  7. package/dist/boundary.js +11 -2
  8. package/dist/boundary.js.map +1 -1
  9. package/dist/build-plan.d.ts +18 -6
  10. package/dist/build-plan.d.ts.map +1 -1
  11. package/dist/build-plan.js +82 -34
  12. package/dist/build-plan.js.map +1 -1
  13. package/dist/build-vite.d.ts +4 -0
  14. package/dist/build-vite.d.ts.map +1 -1
  15. package/dist/build-vite.js +68 -13
  16. package/dist/build-vite.js.map +1 -1
  17. package/dist/build.d.ts +57 -23
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/build.js +356 -85
  20. package/dist/build.js.map +1 -1
  21. package/dist/bun-dev-entry.d.ts +1 -1
  22. package/dist/bun-dev-entry.js +1 -1
  23. package/dist/cdn.d.ts +74 -0
  24. package/dist/cdn.d.ts.map +1 -0
  25. package/dist/cdn.js +132 -0
  26. package/dist/cdn.js.map +1 -0
  27. package/dist/client.d.ts +3 -1
  28. package/dist/client.d.ts.map +1 -1
  29. package/dist/client.js +136 -35
  30. package/dist/client.js.map +1 -1
  31. package/dist/conformance.d.ts +4 -3
  32. package/dist/conformance.d.ts.map +1 -1
  33. package/dist/conformance.js +12 -3
  34. package/dist/conformance.js.map +1 -1
  35. package/dist/csp.d.ts +93 -0
  36. package/dist/csp.d.ts.map +1 -0
  37. package/dist/csp.js +122 -0
  38. package/dist/csp.js.map +1 -0
  39. package/dist/dev-client.d.ts +76 -0
  40. package/dist/dev-client.d.ts.map +1 -0
  41. package/dist/dev-client.js +280 -0
  42. package/dist/dev-client.js.map +1 -0
  43. package/dist/dev-diagnostics.d.ts +26 -2
  44. package/dist/dev-diagnostics.d.ts.map +1 -1
  45. package/dist/dev-diagnostics.js +38 -7
  46. package/dist/dev-diagnostics.js.map +1 -1
  47. package/dist/dev-error.d.ts +4 -13
  48. package/dist/dev-error.d.ts.map +1 -1
  49. package/dist/dev-error.js +46 -4
  50. package/dist/dev-error.js.map +1 -1
  51. package/dist/dev-feed.d.ts +298 -0
  52. package/dist/dev-feed.d.ts.map +1 -0
  53. package/dist/dev-feed.js +797 -0
  54. package/dist/dev-feed.js.map +1 -0
  55. package/dist/dev-indicator.d.ts +38 -0
  56. package/dist/dev-indicator.d.ts.map +1 -0
  57. package/dist/dev-indicator.js +93 -0
  58. package/dist/dev-indicator.js.map +1 -0
  59. package/dist/dev-session.d.ts +77 -0
  60. package/dist/dev-session.d.ts.map +1 -0
  61. package/dist/dev-session.js +580 -0
  62. package/dist/dev-session.js.map +1 -0
  63. package/dist/dev-sourcemap.d.ts +27 -0
  64. package/dist/dev-sourcemap.d.ts.map +1 -0
  65. package/dist/dev-sourcemap.js +235 -0
  66. package/dist/dev-sourcemap.js.map +1 -0
  67. package/dist/dev-ssr-graph.d.ts.map +1 -1
  68. package/dist/dev-ssr-graph.js +13 -5
  69. package/dist/dev-ssr-graph.js.map +1 -1
  70. package/dist/dev.d.ts +15 -2
  71. package/dist/dev.d.ts.map +1 -1
  72. package/dist/dev.js +69 -44
  73. package/dist/dev.js.map +1 -1
  74. package/dist/diagnostic-catalog.d.ts +35 -0
  75. package/dist/diagnostic-catalog.d.ts.map +1 -0
  76. package/dist/diagnostic-catalog.js +203 -0
  77. package/dist/diagnostic-catalog.js.map +1 -0
  78. package/dist/diagnostic-prompt.d.ts +49 -0
  79. package/dist/diagnostic-prompt.d.ts.map +1 -0
  80. package/dist/diagnostic-prompt.js +303 -0
  81. package/dist/diagnostic-prompt.js.map +1 -0
  82. package/dist/diagnostic.d.ts +17 -23
  83. package/dist/diagnostic.d.ts.map +1 -1
  84. package/dist/diagnostic.js +31 -39
  85. package/dist/diagnostic.js.map +1 -1
  86. package/dist/draft.d.ts.map +1 -1
  87. package/dist/draft.js +4 -1
  88. package/dist/draft.js.map +1 -1
  89. package/dist/fn.d.ts +5 -0
  90. package/dist/fn.d.ts.map +1 -1
  91. package/dist/fn.js +13 -3
  92. package/dist/fn.js.map +1 -1
  93. package/dist/fs.d.ts.map +1 -1
  94. package/dist/fs.js +4 -2
  95. package/dist/fs.js.map +1 -1
  96. package/dist/index.d.ts +13 -15
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +11 -12
  99. package/dist/index.js.map +1 -1
  100. package/dist/internal/cdn/cloudflare.d.ts +48 -0
  101. package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
  102. package/dist/internal/cdn/cloudflare.js +94 -0
  103. package/dist/internal/cdn/cloudflare.js.map +1 -0
  104. package/dist/internal/cdn/core.d.ts +91 -0
  105. package/dist/internal/cdn/core.d.ts.map +1 -0
  106. package/dist/internal/cdn/core.js +220 -0
  107. package/dist/internal/cdn/core.js.map +1 -0
  108. package/dist/internal/cdn/fastly.d.ts +17 -0
  109. package/dist/internal/cdn/fastly.d.ts.map +1 -0
  110. package/dist/internal/cdn/fastly.js +43 -0
  111. package/dist/internal/cdn/fastly.js.map +1 -0
  112. package/dist/internal/cdn/vercel.d.ts +31 -0
  113. package/dist/internal/cdn/vercel.d.ts.map +1 -0
  114. package/dist/internal/cdn/vercel.js +56 -0
  115. package/dist/internal/cdn/vercel.js.map +1 -0
  116. package/dist/internal/code-unit-order.d.ts +4 -0
  117. package/dist/internal/code-unit-order.d.ts.map +1 -0
  118. package/dist/internal/code-unit-order.js +6 -0
  119. package/dist/internal/code-unit-order.js.map +1 -0
  120. package/dist/internal/codegen.d.ts.map +1 -1
  121. package/dist/internal/codegen.js +141 -25
  122. package/dist/internal/codegen.js.map +1 -1
  123. package/dist/internal/dev-csp.d.ts +24 -0
  124. package/dist/internal/dev-csp.d.ts.map +1 -0
  125. package/dist/internal/dev-csp.js +160 -0
  126. package/dist/internal/dev-csp.js.map +1 -0
  127. package/dist/internal/dev-reserved.d.ts +25 -0
  128. package/dist/internal/dev-reserved.d.ts.map +1 -0
  129. package/dist/internal/dev-reserved.js +39 -0
  130. package/dist/internal/dev-reserved.js.map +1 -0
  131. package/dist/internal/html-spans.d.ts +10 -0
  132. package/dist/internal/html-spans.d.ts.map +1 -0
  133. package/dist/internal/html-spans.js +46 -0
  134. package/dist/internal/html-spans.js.map +1 -0
  135. package/dist/internal/loading-runtime.d.ts +44 -0
  136. package/dist/internal/loading-runtime.d.ts.map +1 -0
  137. package/dist/internal/loading-runtime.js +206 -0
  138. package/dist/internal/loading-runtime.js.map +1 -0
  139. package/dist/internal/matches-runtime.d.ts +8 -0
  140. package/dist/internal/matches-runtime.d.ts.map +1 -0
  141. package/dist/internal/matches-runtime.js +61 -0
  142. package/dist/internal/matches-runtime.js.map +1 -0
  143. package/dist/internal/mount-shadow.d.ts +36 -0
  144. package/dist/internal/mount-shadow.d.ts.map +1 -0
  145. package/dist/internal/mount-shadow.js +65 -0
  146. package/dist/internal/mount-shadow.js.map +1 -0
  147. package/dist/internal/node-builtins.d.ts +3 -0
  148. package/dist/internal/node-builtins.d.ts.map +1 -0
  149. package/dist/internal/node-builtins.js +79 -0
  150. package/dist/internal/node-builtins.js.map +1 -0
  151. package/dist/internal/output-guard.d.ts +65 -0
  152. package/dist/internal/output-guard.d.ts.map +1 -0
  153. package/dist/internal/output-guard.js +630 -0
  154. package/dist/internal/output-guard.js.map +1 -0
  155. package/dist/internal/page-execution.d.ts +20 -8
  156. package/dist/internal/page-execution.d.ts.map +1 -1
  157. package/dist/internal/page-execution.js +571 -105
  158. package/dist/internal/page-execution.js.map +1 -1
  159. package/dist/internal/parity.d.ts +21 -0
  160. package/dist/internal/parity.d.ts.map +1 -1
  161. package/dist/internal/parity.js +104 -22
  162. package/dist/internal/parity.js.map +1 -1
  163. package/dist/internal/private-env.d.ts +12 -0
  164. package/dist/internal/private-env.d.ts.map +1 -0
  165. package/dist/internal/private-env.js +335 -0
  166. package/dist/internal/private-env.js.map +1 -0
  167. package/dist/internal/public-url.d.ts +3 -0
  168. package/dist/internal/public-url.d.ts.map +1 -0
  169. package/dist/internal/public-url.js +18 -0
  170. package/dist/internal/public-url.js.map +1 -0
  171. package/dist/internal/render-document.d.ts +25 -19
  172. package/dist/internal/render-document.d.ts.map +1 -1
  173. package/dist/internal/render-document.js +77 -61
  174. package/dist/internal/render-document.js.map +1 -1
  175. package/dist/internal/response-controls.d.ts +75 -0
  176. package/dist/internal/response-controls.d.ts.map +1 -0
  177. package/dist/internal/response-controls.js +256 -0
  178. package/dist/internal/response-controls.js.map +1 -0
  179. package/dist/internal/runtime-contract.d.ts +11 -16
  180. package/dist/internal/runtime-contract.d.ts.map +1 -1
  181. package/dist/internal/runtime-contract.js.map +1 -1
  182. package/dist/internal/secret-scan.d.ts +104 -0
  183. package/dist/internal/secret-scan.d.ts.map +1 -0
  184. package/dist/internal/secret-scan.js +503 -0
  185. package/dist/internal/secret-scan.js.map +1 -0
  186. package/dist/internal/server-boundary.d.ts +6 -29
  187. package/dist/internal/server-boundary.d.ts.map +1 -1
  188. package/dist/internal/server-boundary.js +6 -66
  189. package/dist/internal/server-boundary.js.map +1 -1
  190. package/dist/internal/source-map.d.ts +47 -0
  191. package/dist/internal/source-map.d.ts.map +1 -0
  192. package/dist/internal/source-map.js +225 -0
  193. package/dist/internal/source-map.js.map +1 -0
  194. package/dist/internal/target-compat.d.ts +27 -0
  195. package/dist/internal/target-compat.d.ts.map +1 -0
  196. package/dist/internal/target-compat.js +77 -0
  197. package/dist/internal/target-compat.js.map +1 -0
  198. package/dist/internal/timing-safe-equal.d.ts +6 -0
  199. package/dist/internal/timing-safe-equal.d.ts.map +1 -1
  200. package/dist/internal/timing-safe-equal.js +10 -0
  201. package/dist/internal/timing-safe-equal.js.map +1 -1
  202. package/dist/internal/vitals-runtime.d.ts +18 -0
  203. package/dist/internal/vitals-runtime.d.ts.map +1 -0
  204. package/dist/internal/vitals-runtime.js +38 -0
  205. package/dist/internal/vitals-runtime.js.map +1 -0
  206. package/dist/internal/web-app.d.ts +38 -9
  207. package/dist/internal/web-app.d.ts.map +1 -1
  208. package/dist/internal/web-app.js +199 -59
  209. package/dist/internal/web-app.js.map +1 -1
  210. package/dist/internal/zone-graph.d.ts +89 -0
  211. package/dist/internal/zone-graph.d.ts.map +1 -0
  212. package/dist/internal/zone-graph.js +492 -0
  213. package/dist/internal/zone-graph.js.map +1 -0
  214. package/dist/isr.d.ts +83 -6
  215. package/dist/isr.d.ts.map +1 -1
  216. package/dist/isr.js +263 -36
  217. package/dist/isr.js.map +1 -1
  218. package/dist/llms-txt.d.ts.map +1 -1
  219. package/dist/llms-txt.js +31 -6
  220. package/dist/llms-txt.js.map +1 -1
  221. package/dist/manifest.d.ts +220 -13
  222. package/dist/manifest.d.ts.map +1 -1
  223. package/dist/manifest.js +394 -9
  224. package/dist/manifest.js.map +1 -1
  225. package/dist/module-graph.d.ts +26 -5
  226. package/dist/module-graph.d.ts.map +1 -1
  227. package/dist/module-graph.js +17 -7
  228. package/dist/module-graph.js.map +1 -1
  229. package/dist/navigation.d.ts +17 -3
  230. package/dist/navigation.d.ts.map +1 -1
  231. package/dist/navigation.js +11 -5
  232. package/dist/navigation.js.map +1 -1
  233. package/dist/nonce.d.ts +47 -0
  234. package/dist/nonce.d.ts.map +1 -0
  235. package/dist/nonce.js +63 -0
  236. package/dist/nonce.js.map +1 -0
  237. package/dist/plugins/css-modules.d.ts.map +1 -1
  238. package/dist/plugins/css-modules.js +4 -3
  239. package/dist/plugins/css-modules.js.map +1 -1
  240. package/dist/plugins/kit.d.ts +9 -0
  241. package/dist/plugins/kit.d.ts.map +1 -1
  242. package/dist/plugins/kit.js +18 -0
  243. package/dist/plugins/kit.js.map +1 -1
  244. package/dist/plugins/svg.d.ts +22 -3
  245. package/dist/plugins/svg.d.ts.map +1 -1
  246. package/dist/plugins/svg.js +112 -1
  247. package/dist/plugins/svg.js.map +1 -1
  248. package/dist/plugins/vite-leak-guard.d.ts +107 -3
  249. package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
  250. package/dist/plugins/vite-leak-guard.js +378 -17
  251. package/dist/plugins/vite-leak-guard.js.map +1 -1
  252. package/dist/plugins/vite-zone-guard.d.ts +61 -0
  253. package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
  254. package/dist/plugins/vite-zone-guard.js +208 -0
  255. package/dist/plugins/vite-zone-guard.js.map +1 -0
  256. package/dist/plugins/zone-guard.d.ts +27 -0
  257. package/dist/plugins/zone-guard.d.ts.map +1 -0
  258. package/dist/plugins/zone-guard.js +80 -0
  259. package/dist/plugins/zone-guard.js.map +1 -0
  260. package/dist/prerender.d.ts +10 -0
  261. package/dist/prerender.d.ts.map +1 -1
  262. package/dist/prerender.js +35 -3
  263. package/dist/prerender.js.map +1 -1
  264. package/dist/public-dir.d.ts +9 -0
  265. package/dist/public-dir.d.ts.map +1 -1
  266. package/dist/public-dir.js +67 -9
  267. package/dist/public-dir.js.map +1 -1
  268. package/dist/pwa-manifest.d.ts +68 -0
  269. package/dist/pwa-manifest.d.ts.map +1 -0
  270. package/dist/pwa-manifest.js +178 -0
  271. package/dist/pwa-manifest.js.map +1 -0
  272. package/dist/query.js +4 -4
  273. package/dist/query.js.map +1 -1
  274. package/dist/render-seam.d.ts +42 -2
  275. package/dist/render-seam.d.ts.map +1 -1
  276. package/dist/render-seam.js +6 -0
  277. package/dist/render-seam.js.map +1 -1
  278. package/dist/route-manifest.d.ts +3 -0
  279. package/dist/route-manifest.d.ts.map +1 -1
  280. package/dist/route-manifest.js +26 -3
  281. package/dist/route-manifest.js.map +1 -1
  282. package/dist/route-types.d.ts +24 -0
  283. package/dist/route-types.d.ts.map +1 -0
  284. package/dist/route-types.js +195 -0
  285. package/dist/route-types.js.map +1 -0
  286. package/dist/router.d.ts +28 -6
  287. package/dist/router.d.ts.map +1 -1
  288. package/dist/router.js +129 -40
  289. package/dist/router.js.map +1 -1
  290. package/dist/vitals.d.ts +33 -0
  291. package/dist/vitals.d.ts.map +1 -0
  292. package/dist/vitals.js +16 -0
  293. package/dist/vitals.js.map +1 -0
  294. package/dist/vite.d.ts +16 -1
  295. package/dist/vite.d.ts.map +1 -1
  296. package/dist/vite.js +248 -43
  297. package/dist/vite.js.map +1 -1
  298. package/dist/zones.d.ts +70 -0
  299. package/dist/zones.d.ts.map +1 -0
  300. package/dist/zones.js +365 -0
  301. package/dist/zones.js.map +1 -0
  302. package/package.json +67 -14
  303. package/src/backend-only.ts +16 -0
  304. package/src/boundary.ts +18 -5
  305. package/src/build-plan.ts +90 -35
  306. package/src/build-vite.ts +77 -11
  307. package/src/build.ts +466 -96
  308. package/src/bun-dev-entry.ts +1 -1
  309. package/src/cdn.ts +216 -0
  310. package/src/client.ts +135 -36
  311. package/src/conformance.ts +16 -3
  312. package/src/csp.ts +194 -0
  313. package/src/dev-client.ts +349 -0
  314. package/src/dev-diagnostics.ts +60 -7
  315. package/src/dev-error.ts +49 -5
  316. package/src/dev-feed.ts +1140 -0
  317. package/src/dev-indicator.ts +113 -0
  318. package/src/dev-session.ts +729 -0
  319. package/src/dev-sourcemap.ts +281 -0
  320. package/src/dev-ssr-graph.ts +16 -5
  321. package/src/dev.ts +86 -43
  322. package/src/diagnostic-catalog.ts +243 -0
  323. package/src/diagnostic-prompt.ts +355 -0
  324. package/src/diagnostic.ts +63 -66
  325. package/src/draft.ts +4 -1
  326. package/src/fn.ts +19 -3
  327. package/src/fs.ts +13 -2
  328. package/src/index.ts +42 -10
  329. package/src/internal/cdn/cloudflare.ts +146 -0
  330. package/src/internal/cdn/core.ts +316 -0
  331. package/src/internal/cdn/fastly.ts +62 -0
  332. package/src/internal/cdn/vercel.ts +95 -0
  333. package/src/internal/code-unit-order.ts +5 -0
  334. package/src/internal/codegen.ts +161 -24
  335. package/src/internal/dev-csp.ts +176 -0
  336. package/src/internal/dev-reserved.ts +52 -0
  337. package/src/internal/html-spans.ts +50 -0
  338. package/src/internal/loading-runtime.ts +244 -0
  339. package/src/internal/matches-runtime.ts +62 -0
  340. package/src/internal/mount-shadow.ts +88 -0
  341. package/src/internal/node-builtins.ts +79 -0
  342. package/src/internal/output-guard.ts +701 -0
  343. package/src/internal/page-execution.ts +795 -185
  344. package/src/internal/parity.ts +150 -36
  345. package/src/internal/private-env.ts +334 -0
  346. package/src/internal/public-url.ts +22 -0
  347. package/src/internal/render-document.ts +114 -67
  348. package/src/internal/response-controls.ts +284 -0
  349. package/src/internal/runtime-contract.ts +10 -15
  350. package/src/internal/secret-scan.ts +638 -0
  351. package/src/internal/server-boundary.ts +6 -69
  352. package/src/internal/source-map.ts +256 -0
  353. package/src/internal/target-compat.ts +92 -0
  354. package/src/internal/timing-safe-equal.ts +13 -0
  355. package/src/internal/vitals-runtime.ts +53 -0
  356. package/src/internal/web-app.ts +262 -84
  357. package/src/internal/zone-graph.ts +593 -0
  358. package/src/isr.ts +325 -37
  359. package/src/llms-txt.ts +28 -4
  360. package/src/manifest.ts +682 -16
  361. package/src/module-graph.ts +53 -9
  362. package/src/navigation.ts +29 -6
  363. package/src/nonce.ts +104 -0
  364. package/src/plugins/css-modules.ts +5 -3
  365. package/src/plugins/kit.ts +24 -0
  366. package/src/plugins/svg.ts +135 -4
  367. package/src/plugins/vite-leak-guard.ts +529 -16
  368. package/src/plugins/vite-zone-guard.ts +263 -0
  369. package/src/plugins/zone-guard.ts +119 -0
  370. package/src/prerender.ts +49 -3
  371. package/src/public-dir.ts +69 -8
  372. package/src/pwa-manifest.ts +226 -0
  373. package/src/query.ts +4 -4
  374. package/src/render-seam.ts +46 -1
  375. package/src/route-manifest.ts +15 -3
  376. package/src/route-types.ts +264 -0
  377. package/src/router.ts +163 -63
  378. package/src/vitals.ts +44 -0
  379. package/src/vite.ts +257 -42
  380. package/src/zones.ts +430 -0
  381. package/dist/plugins/vite-server-only.d.ts +0 -49
  382. package/dist/plugins/vite-server-only.d.ts.map +0 -1
  383. package/dist/plugins/vite-server-only.js +0 -57
  384. package/dist/plugins/vite-server-only.js.map +0 -1
  385. package/dist/server-only.d.ts +0 -21
  386. package/dist/server-only.d.ts.map +0 -1
  387. package/dist/server-only.js +0 -25
  388. package/dist/server-only.js.map +0 -1
  389. package/src/plugins/vite-server-only.ts +0 -75
  390. package/src/server-only.ts +0 -25
@@ -0,0 +1,244 @@
1
+ /**
2
+ * `_loading` pages. The router is untouched: {@link withLoading} only swaps the leaf of the state the
3
+ * view is told while a navigation is pending, so no adapter knows a loading page exists.
4
+ */
5
+ import type { MatchChain } from "../render-seam.ts"
6
+ import { type ClientRouter, type RouterState, redirectOf } from "../router.ts"
7
+
8
+ /** How long a navigation may run before its `_loading` page shows. Shorter loads swap straight to the
9
+ * new page: a skeleton that flashes for a few frames reads as a glitch, not as feedback. */
10
+ export const LOADING_DELAY_MS = 120
11
+
12
+ /**
13
+ * One route's row: its layout ids (outermost → innermost), and the `_loading` pages above it
14
+ * (outermost → innermost), each paired with how many of those layouts sit at or above it.
15
+ */
16
+ export type LoadingRoute = readonly [
17
+ layoutIds: readonly string[],
18
+ loadings: ReadonlyArray<readonly [id: string, layouts: number]>,
19
+ ]
20
+
21
+ export interface LoadingOptions {
22
+ /** Every route id → its {@link LoadingRoute} row. A route id absent here has no layouts. */
23
+ readonly routes: Readonly<Record<string, LoadingRoute>>
24
+ /** `_loading` id → a lazy import of its module. */
25
+ readonly modules: Readonly<
26
+ Record<string, () => Promise<{ readonly default?: unknown; readonly handle?: unknown }>>
27
+ >
28
+ /** The mounted view's route id → component chain table. The wrapper adds a chain per loading view. */
29
+ readonly chains: Record<string, readonly unknown[]>
30
+ /** Route id → search-schema chain, kept in step with {@link chains}. */
31
+ readonly searchSchemas: Record<string, readonly unknown[]>
32
+ /** Route id → what `useMatches` reports, kept in step with {@link chains}. */
33
+ readonly matchChains?: Record<string, MatchChain>
34
+ /** Override {@link LOADING_DELAY_MS}. */
35
+ readonly delayMs?: number
36
+ /** Where a navigation goes when it fails after its loading page was shown. Defaults to a document
37
+ * load of the path. */
38
+ readonly fallback?: (path: string) => void
39
+ }
40
+
41
+ const pathnameOf = (path: string): string => {
42
+ const q = path.indexOf("?")
43
+ return q === -1 ? path : path.slice(0, q)
44
+ }
45
+
46
+ /**
47
+ * Shows the innermost `_loading` within the layouts both pages share once a navigation outlasts
48
+ * `delayMs`. `navigate` settles when it shows, so a view transition does not hold across the wait; a
49
+ * later failure goes to `fallback`, or replaces the entry for a redirect out of the app.
50
+ */
51
+ export function withLoading(router: ClientRouter, options: LoadingOptions): ClientRouter {
52
+ const { routes, modules, chains, searchSchemas, matchChains } = options
53
+ const delayMs = options.delayMs ?? LOADING_DELAY_MS
54
+ const fallback = options.fallback ?? ((path: string) => location.assign(path))
55
+
56
+ const listeners = new Set<() => void>()
57
+ const components = new Map<string, unknown>()
58
+ const handles = new Map<string, unknown>()
59
+ const inflight = new Map<string, Promise<void>>()
60
+ /** Bumped per navigation through this wrapper - the latest one owns the page slot. */
61
+ let seq = 0
62
+ /** The loading view on screen (its chain id), and the navigation it belongs to. */
63
+ let shown: string | undefined
64
+ let owner = 0
65
+ // One derived state per (router state, loading view) so `snapshot` is referentially stable.
66
+ let base: RouterState | undefined
67
+ let derived: RouterState | undefined
68
+
69
+ const emit = (): void => {
70
+ for (const listener of [...listeners]) listener()
71
+ }
72
+
73
+ const load = (id: string): Promise<void> => {
74
+ if (components.has(id)) return Promise.resolve()
75
+ const running = inflight.get(id)
76
+ if (running !== undefined) return running
77
+ const importer = modules[id]
78
+ const job = (
79
+ importer === undefined ? Promise.reject(new Error(`no _loading module "${id}"`)) : importer()
80
+ )
81
+ .then((mod) => {
82
+ if (mod.default !== undefined) components.set(id, mod.default)
83
+ handles.set(id, mod.handle)
84
+ })
85
+ .finally(() => {
86
+ inflight.delete(id)
87
+ })
88
+ inflight.set(id, job)
89
+ return job
90
+ }
91
+
92
+ const pick = (
93
+ fromId: string,
94
+ toId: string,
95
+ ): { readonly id: string; readonly shared: number } | undefined => {
96
+ const target = routes[toId]
97
+ if (target === undefined) return undefined
98
+ const current = routes[fromId]?.[0] ?? []
99
+ const [layouts, candidates] = target
100
+ let shared = 0
101
+ while (
102
+ shared < current.length &&
103
+ shared < layouts.length &&
104
+ current[shared] === layouts[shared]
105
+ ) {
106
+ shared++
107
+ }
108
+ for (let i = candidates.length - 1; i >= 0; i--) {
109
+ const candidate = candidates[i]
110
+ if (candidate !== undefined && candidate[1] <= shared) return { id: candidate[0], shared }
111
+ }
112
+ return undefined
113
+ }
114
+
115
+ const snapshot = (): RouterState => {
116
+ const state = router.snapshot()
117
+ if (shown === undefined || !state.pending) return state
118
+ if (base !== state || derived === undefined || derived.routeId !== shown) {
119
+ base = state
120
+ // The layouts keep their data and the URL-derived fields stay the current page's; only the leaf
121
+ // changes, and it has no loader data of its own.
122
+ derived = { ...state, routeId: shown, data: null }
123
+ }
124
+ return derived
125
+ }
126
+
127
+ const navigate = (path: string): Promise<void> => {
128
+ const match = router.match(path)
129
+ // Unmatched: the router ignores it, so whatever is on screen stays as it is.
130
+ if (match === null) return router.navigate(path)
131
+ const mine = ++seq
132
+ const from = router.snapshot()
133
+ const choice =
134
+ pathnameOf(from.path) === pathnameOf(path) ? undefined : pick(from.routeId, match.routeId)
135
+ // NUL-prefixed: a chain id no route file can produce.
136
+ const view =
137
+ choice === undefined ? undefined : `\0${choice.id}\0${from.routeId}\0${choice.shared}`
138
+ const wasShown = shown !== undefined
139
+ // A newer navigation that would show the same view keeps it, rather than flashing the old page.
140
+ const carried = wasShown && shown === view
141
+ if (carried) owner = mine
142
+ else shown = undefined
143
+
144
+ const done = router.navigate(path)
145
+ if (choice === undefined || view === undefined) {
146
+ if (wasShown) emit()
147
+ return done
148
+ }
149
+
150
+ let finished = false
151
+ let revealed = carried
152
+ let timer: ReturnType<typeof setTimeout> | undefined
153
+ const reveal = new Promise<void>((resolve) => {
154
+ if (carried) {
155
+ setTimeout(resolve, 0)
156
+ return
157
+ }
158
+ const show = (): void => {
159
+ if (finished || mine !== seq || !router.snapshot().pending) return
160
+ const component = components.get(choice.id)
161
+ if (component === undefined) return
162
+ chains[view] ??= [...(chains[from.routeId] ?? []).slice(0, choice.shared), component]
163
+ // The current route's schemas: the URL is still its URL, so its layouts read the same search.
164
+ searchSchemas[view] ??= searchSchemas[from.routeId] ?? []
165
+ if (matchChains !== undefined) {
166
+ const current = matchChains[from.routeId]
167
+ matchChains[view] ??= {
168
+ ids: [...(current?.ids ?? []).slice(0, choice.shared), choice.id],
169
+ handles: [...(current?.handles ?? []).slice(0, choice.shared), handles.get(choice.id)],
170
+ }
171
+ }
172
+ shown = view
173
+ owner = mine
174
+ revealed = true
175
+ emit()
176
+ // A macrotask later, so the view has committed the loading page before the caller resumes.
177
+ setTimeout(resolve, 0)
178
+ }
179
+ const ready = load(choice.id)
180
+ // A `_loading` module that fails to load costs only the loading page, never the navigation.
181
+ ready.catch(() => {})
182
+ timer = setTimeout(
183
+ () => {
184
+ ready.then(show, () => {})
185
+ },
186
+ wasShown ? 0 : delayMs,
187
+ )
188
+ })
189
+
190
+ const finish = (): void => {
191
+ finished = true
192
+ clearTimeout(timer)
193
+ if (owner !== mine || shown === undefined) return
194
+ shown = undefined
195
+ // A navigation that ended without publishing (superseded inside the router) leaves the store
196
+ // pending: tell the view the page slot is the current page's again.
197
+ if (router.snapshot().pending) emit()
198
+ }
199
+ const settled = done.then(finish, (error: unknown) => {
200
+ finish()
201
+ // A newer navigation owns the document now; even an aborted older fetch can reject late.
202
+ if (mine !== seq) return
203
+ if (!revealed) throw error
204
+ const to = redirectOf(error)
205
+ if (to === undefined) fallback(path)
206
+ else location.replace(to)
207
+ })
208
+ return Promise.race([settled, reveal])
209
+ }
210
+
211
+ return {
212
+ ...router,
213
+ snapshot,
214
+ subscribe(listener) {
215
+ listeners.add(listener)
216
+ const unsubscribe = router.subscribe(listener)
217
+ return () => {
218
+ listeners.delete(listener)
219
+ unsubscribe()
220
+ }
221
+ },
222
+ navigate,
223
+ submit(action, body, opts) {
224
+ ++seq
225
+ shown = undefined
226
+ return router.submit(action, body, opts)
227
+ },
228
+ invalidate(paths) {
229
+ if (paths === undefined || paths.includes(router.snapshot().path)) {
230
+ ++seq
231
+ shown = undefined
232
+ }
233
+ return router.invalidate(paths)
234
+ },
235
+ prefetch(path) {
236
+ // Warm the loading pages a navigation there could show, alongside the route itself.
237
+ const match = router.match(path)
238
+ if (match !== null) {
239
+ for (const [id] of routes[match.routeId]?.[1] ?? []) load(id).catch(() => {})
240
+ }
241
+ return router.prefetch(path)
242
+ },
243
+ }
244
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `useMatches` for every adapter. It reads only {@link RenderProps}, which SSR and the mounted router
3
+ * both build, so server and browser agree. Layout prefixes follow the `filePathToRoutes` grammar.
4
+ */
5
+ import type { RenderProps, UIMatch } from "../render-seam.ts"
6
+
7
+ const GROUP = /^\(.+\)$/
8
+ const OPTIONAL = /^\[\[([A-Za-z_][A-Za-z0-9_]*)\]\]$/
9
+ const CATCH_ALL = /^\[\.\.\.([A-Za-z_][A-Za-z0-9_]*)\]$/
10
+ const MARKER = /\[([A-Za-z_][A-Za-z0-9_]*)\]/g
11
+
12
+ /** The matches of one render, outermost layout first. Empty when the render carries no chain. */
13
+ export function chainMatches(props: RenderProps): readonly UIMatch[] {
14
+ const chain = props.matchChain
15
+ if (chain === undefined) return []
16
+ const path = props.path ?? ""
17
+ const query = path.indexOf("?")
18
+ const pathname = query === -1 ? path : path.slice(0, query)
19
+ const segments = pathname.split("/").filter((segment) => segment !== "")
20
+ const params = props.params ?? {}
21
+ const last = chain.ids.length - 1
22
+ return chain.ids.map((id, index): UIMatch => {
23
+ const handle = chain.handles[index]
24
+ if (index === last) return { id, pathname, params, data: props.data, handle }
25
+ const owned: Record<string, string> = {}
26
+ let depth = 0
27
+ let rest = false
28
+ const slash = id.lastIndexOf("/")
29
+ for (const part of slash === -1 ? [] : id.slice(0, slash).split("/")) {
30
+ if (part === "index" || GROUP.test(part)) continue
31
+ const optional = OPTIONAL.exec(part)?.[1]
32
+ if (optional !== undefined) {
33
+ const value = params[optional]
34
+ if (value !== undefined) {
35
+ owned[optional] = value
36
+ depth += 1
37
+ }
38
+ continue
39
+ }
40
+ const catchAll = CATCH_ALL.exec(part)?.[1]
41
+ if (catchAll !== undefined) {
42
+ const value = params[catchAll]
43
+ if (value !== undefined) owned[catchAll] = value
44
+ rest = true
45
+ break
46
+ }
47
+ for (const marker of part.matchAll(MARKER)) {
48
+ const name = marker[1] as string
49
+ const value = params[name]
50
+ if (value !== undefined) owned[name] = value
51
+ }
52
+ depth += 1
53
+ }
54
+ return {
55
+ id,
56
+ pathname: rest ? pathname : `/${segments.slice(0, depth).join("/")}`,
57
+ params: owned,
58
+ data: props.layoutData?.[index] ?? null,
59
+ handle,
60
+ }
61
+ })
62
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Page routes a mount makes unreachable.
3
+ *
4
+ * A pre-route mount (the auto-mounted backend at `apiPrefix`, a `mounts` entry, an `app.mount()` in
5
+ * `use`) answers every request under its path before page routing, and its 404 is final -
6
+ * `fallbackOn: 404` only tries the next mount. A page file whose URL sits under one therefore never
7
+ * renders, and nothing says so: the request gets the mount's 404. This finds those pages so the app
8
+ * fails at startup, at build, and in `nifra check` instead.
9
+ *
10
+ * Pure and fs-free: it takes the manifest and the mount paths, so the runtime, the build and the static
11
+ * check all run the same comparison.
12
+ */
13
+ import type { Manifest } from "../manifest.ts"
14
+
15
+ /** A page file whose URL pattern sits under a mount. */
16
+ export interface ShadowedPage {
17
+ /** The route file, as the manifest names it (relative to `routes/`). */
18
+ readonly file: string
19
+ /** The URL pattern the file serves. */
20
+ readonly pattern: string
21
+ /** The mount path, normalized as the server matches it. */
22
+ readonly mount: string
23
+ }
24
+
25
+ /**
26
+ * Normalize a mount path the way the server's mount table does: drop a trailing `/*` and a trailing
27
+ * `/`, keep `/` for a root mount. `undefined` for a path the server would refuse (not absolute, a
28
+ * query or hash, a param or an inner wildcard) - the mount itself reports that.
29
+ */
30
+ export function normalizeMountPath(path: string): string | undefined {
31
+ if (!path.startsWith("/") || path.includes("?") || path.includes("#")) return undefined
32
+ const bare = path.endsWith("/*") ? path.slice(0, -2) : path
33
+ if (bare.includes("*") || bare.includes(":")) return undefined
34
+ if (bare.length === 0) return "/"
35
+ return bare.length > 1 && bare.endsWith("/") ? bare.slice(0, -1) : bare
36
+ }
37
+
38
+ /** Whether every request a pattern serves lands under `mount`: its leading segments equal the mount's
39
+ * segments literally. A param or wildcard segment is not the mount's literal text, so `/:lang/x` is
40
+ * reachable for every other value and is not reported. */
41
+ function patternUnderMount(pattern: string, mount: string): boolean {
42
+ if (mount === "/") return true
43
+ return pattern === mount || pattern.startsWith(`${mount}/`)
44
+ }
45
+
46
+ /**
47
+ * The page routes and nested `_404` scopes that sit under one of `mountPaths`, in manifest order. Paths
48
+ * are normalized here; one the server would refuse is skipped.
49
+ */
50
+ export function shadowedPages(
51
+ manifest: Pick<Manifest, "routes" | "notFounds">,
52
+ mountPaths: readonly string[],
53
+ ): readonly ShadowedPage[] {
54
+ const mounts: string[] = []
55
+ for (const path of mountPaths) {
56
+ const normalized = normalizeMountPath(path)
57
+ if (normalized !== undefined && !mounts.includes(normalized)) mounts.push(normalized)
58
+ }
59
+ if (mounts.length === 0) return []
60
+ const found: ShadowedPage[] = []
61
+ const under = (pattern: string): string | undefined => {
62
+ for (const mount of mounts) if (patternUnderMount(pattern, mount)) return mount
63
+ return undefined
64
+ }
65
+ for (const route of manifest.routes) {
66
+ const mount = under(route.pattern)
67
+ if (mount !== undefined) found.push({ file: route.file, pattern: route.pattern, mount })
68
+ }
69
+ for (const entry of Object.values(manifest.notFounds ?? {})) {
70
+ for (const scope of entry.scopes) {
71
+ const mount = under(scope.pattern)
72
+ if (mount !== undefined) found.push({ file: entry.file, pattern: scope.pattern, mount })
73
+ }
74
+ }
75
+ return found
76
+ }
77
+
78
+ /** The message every surface reports shadowed pages with. */
79
+ export function formatShadowedPages(pages: readonly ShadowedPage[]): string {
80
+ const lines = pages.map(
81
+ (page) => ` ${page.file} serves ${page.pattern}, under the mount at ${page.mount}`,
82
+ )
83
+ return (
84
+ `${pages.length === 1 ? "1 page route" : `${pages.length} page routes`} can never render: a mount answers every request under its path before page routing, and its 404 is final.\n` +
85
+ `${lines.join("\n")}\n` +
86
+ 'Move each file out of the mounted path, or serve that URL from the mounted app. The backend\'s mount path is `apiPrefix` (default "/api"; export it from framework.ts, or set it to "" to turn the mount off).'
87
+ )
88
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Node's built-in module names without the `node:` prefix (`fs`, `fs/promises`, ...), for the client
3
+ * build guards. A fixed list rather than `module.builtinModules`, which differs between Node and Bun
4
+ * (Bun adds `bun:*`), so a leak reads the same whichever runtime runs the build. Names that exist only
5
+ * with the prefix (`node:test`, `node:sqlite`) are covered by the prefix itself.
6
+ */
7
+ const NODE_BUILTIN_MODULES: ReadonlySet<string> = new Set([
8
+ "_http_agent",
9
+ "_http_client",
10
+ "_http_common",
11
+ "_http_incoming",
12
+ "_http_outgoing",
13
+ "_http_server",
14
+ "_stream_duplex",
15
+ "_stream_passthrough",
16
+ "_stream_readable",
17
+ "_stream_transform",
18
+ "_stream_wrap",
19
+ "_stream_writable",
20
+ "_tls_common",
21
+ "_tls_wrap",
22
+ "assert",
23
+ "assert/strict",
24
+ "async_hooks",
25
+ "buffer",
26
+ "child_process",
27
+ "cluster",
28
+ "console",
29
+ "constants",
30
+ "crypto",
31
+ "dgram",
32
+ "diagnostics_channel",
33
+ "dns",
34
+ "dns/promises",
35
+ "domain",
36
+ "events",
37
+ "fs",
38
+ "fs/promises",
39
+ "http",
40
+ "http2",
41
+ "https",
42
+ "inspector",
43
+ "inspector/promises",
44
+ "module",
45
+ "net",
46
+ "os",
47
+ "path",
48
+ "path/posix",
49
+ "path/win32",
50
+ "perf_hooks",
51
+ "process",
52
+ "punycode",
53
+ "querystring",
54
+ "readline",
55
+ "readline/promises",
56
+ "repl",
57
+ "stream",
58
+ "stream/consumers",
59
+ "stream/promises",
60
+ "stream/web",
61
+ "string_decoder",
62
+ "sys",
63
+ "timers",
64
+ "timers/promises",
65
+ "tls",
66
+ "trace_events",
67
+ "tty",
68
+ "url",
69
+ "util",
70
+ "util/types",
71
+ "v8",
72
+ "vm",
73
+ "wasi",
74
+ "worker_threads",
75
+ "zlib",
76
+ ])
77
+
78
+ /** Whether `specifier` names a Node built-in without the `node:` prefix (`fs/promises`). */
79
+ export const isBareNodeBuiltin = (specifier: string): boolean => NODE_BUILTIN_MODULES.has(specifier)