@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +105 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +357 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
package/src/ssr/index.tsx CHANGED
@@ -1,6 +1,9 @@
1
1
  import React from "react";
2
2
  import { renderSegments } from "../segment-system.js";
3
- import { filterSegmentOrder } from "../browser/react/filter-segment-order.js";
3
+ import {
4
+ filterSegmentOrder,
5
+ filterRouteSegmentIds,
6
+ } from "../browser/react/filter-segment-order.js";
4
7
  import { ThemeProvider } from "../theme/ThemeProvider.js";
5
8
  import { NonceContext } from "../browser/react/nonce-context.js";
6
9
  import { NavigationStoreContext } from "../browser/react/context.js";
@@ -71,7 +74,7 @@ export interface SSRRenderOptions {
71
74
  */
72
75
  export interface SSRDependencies<TEnv = unknown> {
73
76
  /**
74
- * createFromReadableStream from @vitejs/plugin-rsc/ssr
77
+ * createFromReadableStream from @rangojs/router/internal/deps/ssr
75
78
  */
76
79
  createFromReadableStream: <T>(
77
80
  stream: ReadableStream<Uint8Array>,
@@ -86,7 +89,7 @@ export interface SSRDependencies<TEnv = unknown> {
86
89
  ) => Promise<ReactDOMReadableStream>;
87
90
 
88
91
  /**
89
- * injectRSCPayload from rsc-html-stream/server
92
+ * injectRSCPayload from @rangojs/router/internal/deps/html-stream-server
90
93
  */
91
94
  injectRSCPayload: (
92
95
  rscStream: ReadableStream<Uint8Array>,
@@ -129,6 +132,7 @@ interface RscPayload {
129
132
  matched?: string[];
130
133
  pathname?: string;
131
134
  params?: Record<string, string>;
135
+ basename?: string;
132
136
  themeConfig?: ResolvedThemeConfig | null;
133
137
  initialTheme?: Theme;
134
138
  version?: string;
@@ -161,9 +165,11 @@ function createSsrEventController(opts: {
161
165
  }): EventController {
162
166
  const location = new URL(opts.pathname, "http://localhost");
163
167
  let params = opts.params ?? {};
168
+ const rawMatched = opts.matched ?? [];
164
169
  const handleState = {
165
170
  data: opts.handleData ?? {},
166
- segmentOrder: filterSegmentOrder(opts.matched ?? []),
171
+ segmentOrder: filterSegmentOrder(rawMatched),
172
+ routeSegmentIds: filterRouteSegmentIds(rawMatched),
167
173
  };
168
174
  const state: DerivedNavigationState = {
169
175
  state: "idle",
@@ -213,10 +219,10 @@ function createSsrEventController(opts: {
213
219
  *
214
220
  * @example
215
221
  * ```tsx
216
- * import { createSSRHandler } from "rsc-router/ssr";
217
- * import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
222
+ * import { createSSRHandler } from "@rangojs/router/ssr";
223
+ * import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
218
224
  * import { renderToReadableStream } from "react-dom/server.edge";
219
- * import { injectRSCPayload } from "rsc-html-stream/server";
225
+ * import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
220
226
  *
221
227
  * export const renderHTML = createSSRHandler({
222
228
  * createFromReadableStream,
@@ -258,9 +264,11 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
258
264
  let payload: Promise<RscPayload> | undefined;
259
265
  let handlesPromise: Promise<HandleData> | undefined;
260
266
  let ssrContextValue: NavigationStoreContextValue | undefined;
267
+ let rootPromise: Promise<React.ReactNode> | undefined;
261
268
  function SsrRoot() {
262
269
  payload ??= createFromReadableStream<RscPayload>(rscStream1);
263
270
  const resolved = React.use(payload);
271
+
264
272
  const themeConfig = resolved.metadata?.themeConfig ?? null;
265
273
  const pathname = resolved.metadata?.pathname ?? "/";
266
274
 
@@ -286,20 +294,20 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
286
294
  navigate: async () => {},
287
295
  refresh: async () => {},
288
296
  version: resolved.metadata?.version,
297
+ basename: resolved.metadata?.basename,
289
298
  };
290
299
 
291
300
  // Build content tree from segments.
292
- // Order must match NavigationProvider: NavigationStoreContext > ThemeProvider > content
293
- const reconstructedRoot = renderSegments(
294
- resolved.metadata?.segments ?? [],
295
- {
301
+ // Order must match NavigationProvider: NavigationStoreContext > NonceContext > ThemeProvider > content
302
+ // Memoize like payload/handles above: renderSegments is async, so
303
+ // React.use() on a fresh promise suspends and replays SsrRoot, which
304
+ // would re-run the entire segment-tree build on every initial render.
305
+ rootPromise ??= Promise.resolve(
306
+ renderSegments(resolved.metadata?.segments ?? [], {
296
307
  rootLayout: resolved.metadata?.rootLayout,
297
- },
308
+ }),
298
309
  );
299
- let content: React.ReactNode =
300
- reconstructedRoot instanceof Promise
301
- ? React.use(reconstructedRoot)
302
- : reconstructedRoot;
310
+ let content: React.ReactNode = React.use(rootPromise);
303
311
 
304
312
  // Wrap content with ThemeProvider if theme is enabled
305
313
  if (themeConfig) {
@@ -32,10 +32,19 @@
32
32
  */
33
33
  import type { ReactNode } from "react";
34
34
  import type { Handler } from "./types.js";
35
- import type { PrerenderOptions, StaticBuildContext } from "./prerender.js";
35
+ import type { StaticBuildContext } from "./prerender.js";
36
+ import type { UseItems, HandlerUseItem } from "./route-types.js";
36
37
  import { isCachedFunction } from "./cache/taint.js";
38
+ import { isUnderTestRunner } from "./runtime-env.js";
37
39
 
38
- // -- Types ------------------------------------------------------------------
40
+ export interface StaticHandlerOptions {
41
+ /**
42
+ * Keep handler in server bundle for live fallback (default: false).
43
+ * false: handler replaced with stub, source-only APIs excluded from bundle.
44
+ * true: handler stays in bundle, renders live at request time.
45
+ */
46
+ passthrough?: boolean;
47
+ }
39
48
 
40
49
  export interface StaticHandlerDefinition<
41
50
  TParams extends Record<string, any> = any,
@@ -46,22 +55,24 @@ export interface StaticHandlerDefinition<
46
55
  /** In dev mode, the actual handler function that layout/path/parallel can call. */
47
56
  handler: Handler<TParams>;
48
57
  /** Static handler options (passthrough support). */
49
- options?: PrerenderOptions;
58
+ options?: StaticHandlerOptions;
59
+ /** Composable default DSL items merged when the handler is mounted. */
60
+ use?: () => UseItems<HandlerUseItem>;
50
61
  }
51
62
 
52
- // -- Function ---------------------------------------------------------------
63
+ // Process-stable fallback id counter (mirrors createHandle/createLoader/Prerender).
64
+ // Only assigned in bare unit tests where the Vite plugin did not inject an id.
65
+ let runtimeStaticIdCounter = 0;
53
66
 
54
67
  export function Static<TParams extends Record<string, any> = {}>(
55
68
  handler: (ctx: StaticBuildContext) => ReactNode | Promise<ReactNode>,
56
- options?: PrerenderOptions,
69
+ options?: StaticHandlerOptions,
57
70
  __injectedId?: string,
58
71
  ): StaticHandlerDefinition<TParams>;
59
72
 
60
- // -- Implementation ---------------------------------------------------------
61
-
62
73
  export function Static<TParams extends Record<string, any>>(
63
74
  handler: Function,
64
- optionsOrId?: PrerenderOptions | string,
75
+ optionsOrId?: StaticHandlerOptions | string,
65
76
  maybeId?: string,
66
77
  ): StaticHandlerDefinition<TParams> {
67
78
  if (isCachedFunction(handler)) {
@@ -72,22 +83,25 @@ export function Static<TParams extends Record<string, any>>(
72
83
  );
73
84
  }
74
85
 
75
- let options: PrerenderOptions | undefined;
86
+ let options: StaticHandlerOptions | undefined;
76
87
  let id: string;
77
88
 
78
89
  if (typeof optionsOrId === "string") {
79
90
  id = optionsOrId;
80
91
  } else {
81
- options = optionsOrId as PrerenderOptions | undefined;
92
+ options = optionsOrId as StaticHandlerOptions | undefined;
82
93
  id = maybeId ?? "";
83
94
  }
84
95
 
85
- if (!id) {
96
+ if (!id && !isUnderTestRunner()) {
86
97
  throw new Error(
87
- "[rsc-router] Static: missing $$id. " +
88
- "Ensure the exposeInternalIds Vite plugin is configured.",
98
+ "[rango] Static: missing $$id. Use `export const X = Static(...)` and " +
99
+ "ensure the exposeInternalIds Vite plugin is configured.",
89
100
  );
90
101
  }
102
+ if (!id) {
103
+ id = `__rango_runtime_static_${runtimeStaticIdCounter++}`;
104
+ }
91
105
 
92
106
  return {
93
107
  __brand: "staticHandler" as const,
@@ -97,11 +111,6 @@ export function Static<TParams extends Record<string, any>>(
97
111
  };
98
112
  }
99
113
 
100
- // -- Type guard -------------------------------------------------------------
101
-
102
- /**
103
- * Type guard to check if a value is a StaticHandlerDefinition.
104
- */
105
114
  export function isStaticHandler(
106
115
  value: unknown,
107
116
  ): value is StaticHandlerDefinition {
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Cache-status testing primitives for @rangojs/router consumers.
3
+ *
4
+ * Two complementary paths, both DEVELOPMENT/TEST ONLY:
5
+ *
6
+ * 1. Header path — `parseCacheHeader` / `assertCacheStatus` read the
7
+ * `X-Rango-Cache` response header. The header is emitted only when the
8
+ * router's debug cache signal gate is on (the `debugCacheSignal` option or
9
+ * `RANGO_TEST_SIGNALS=1`). With the gate off there is no header and these
10
+ * helpers throw a clear "header missing" error.
11
+ *
12
+ * 2. Telemetry path — `createCacheSink` returns a `{ sink, events }` pair the
13
+ * consumer wires via `createRouter({ telemetry: sink })`. This has ZERO
14
+ * production surface: no header, just structured `cache.decision` events
15
+ * (which carry the same coarse `segments` cache signal). Assert with
16
+ * `assertCacheDecision(events, routeKey, expected)` (the one-call counterpart
17
+ * of `assertCacheStatus`) or filter raw via `filterCacheDecisions`.
18
+ *
19
+ * Both paths report the SAME coarse route-level signal — pick by TRANSPORT, not
20
+ * by meaning: the header is the only signal a black-box Playwright `Response`
21
+ * carries (needs the debug gate ON); the sink is the only zero-production-surface
22
+ * option and the only one exposing per-segment `shouldRevalidate`.
23
+ *
24
+ * v1 cache status is COARSE (route-level): the router reports a single entry
25
+ * keyed by the route key (the route NAME), not per individual segment.
26
+ *
27
+ * Import path: from a Vitest unit/integration test use `@rangojs/router/testing`;
28
+ * from a Playwright e2e use `@rangojs/router/testing/e2e` (the barrel pulls a
29
+ * build-only virtual that does not resolve in a plain Playwright runner).
30
+ */
31
+
32
+ import type {
33
+ CacheDecisionEvent,
34
+ CacheSegmentStatus,
35
+ TelemetryEvent,
36
+ TelemetrySink,
37
+ } from "../router/telemetry.js";
38
+
39
+ const CACHE_HEADER = "X-Rango-Cache";
40
+
41
+ /** Expected cache status passed to assertCacheStatus. */
42
+ export type ExpectedCacheStatus = CacheSegmentStatus;
43
+
44
+ /** A target carrying response headers (a Response or a `{ headers }` object). */
45
+ export type CacheStatusTarget = Response | { headers: Headers };
46
+
47
+ export function parseCacheHeader(
48
+ headerValue: string | null | undefined,
49
+ ): Record<string, string> {
50
+ const result: Record<string, string> = {};
51
+ if (!headerValue) return result;
52
+ for (const rawEntry of headerValue.split(",")) {
53
+ const entry = rawEntry.trim();
54
+ if (entry.length === 0) continue;
55
+ const eq = entry.indexOf("=");
56
+ if (eq === -1) continue;
57
+ const id = entry.slice(0, eq).trim();
58
+ const status = entry.slice(eq + 1).trim();
59
+ if (id.length === 0 || status.length === 0) continue;
60
+ result[id] = status;
61
+ }
62
+ return result;
63
+ }
64
+
65
+ function getHeaders(target: CacheStatusTarget): Headers {
66
+ return target.headers;
67
+ }
68
+
69
+ export function assertCacheStatus(
70
+ target: CacheStatusTarget,
71
+ segment: string,
72
+ expected: ExpectedCacheStatus,
73
+ ): void {
74
+ const headerValue = getHeaders(target).get(CACHE_HEADER);
75
+ if (headerValue === null) {
76
+ throw new Error(
77
+ `assertCacheStatus: response has no ${CACHE_HEADER} header. ` +
78
+ `Enable the debug cache signal via createRouter({ debugCacheSignal: true }) ` +
79
+ `or RANGO_TEST_SIGNALS=1.`,
80
+ );
81
+ }
82
+ const map = parseCacheHeader(headerValue);
83
+ const actual = map[segment];
84
+ if (actual === undefined) {
85
+ const known = Object.keys(map);
86
+ throw new Error(
87
+ `assertCacheStatus: segment "${segment}" not found in ${CACHE_HEADER} ` +
88
+ `("${headerValue}"). Known segments: ${
89
+ known.length > 0 ? known.join(", ") : "(none)"
90
+ }.`,
91
+ );
92
+ }
93
+ if (actual !== expected) {
94
+ throw new Error(
95
+ `assertCacheStatus: segment "${segment}" expected "${expected}" but got "${actual}".`,
96
+ );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * A telemetry sink paired with the array it records events into.
102
+ */
103
+ export interface CacheSink {
104
+ /** Wire into `createRouter({ telemetry: sink })`. */
105
+ sink: TelemetrySink;
106
+ /** All telemetry events captured so far, in emit order. */
107
+ events: TelemetryEvent[];
108
+ }
109
+
110
+ export function createCacheSink(): CacheSink {
111
+ const events: TelemetryEvent[] = [];
112
+ const sink: TelemetrySink = {
113
+ emit(event: TelemetryEvent): void {
114
+ events.push(event);
115
+ },
116
+ };
117
+ return { sink, events };
118
+ }
119
+
120
+ export function filterCacheDecisions(
121
+ events: readonly TelemetryEvent[],
122
+ ): CacheDecisionEvent[] {
123
+ return events.filter(
124
+ (e): e is CacheDecisionEvent => e.type === "cache.decision",
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Telemetry-path counterpart of {@link assertCacheStatus}: assert a captured
130
+ * `cache.decision` event reported `expected` for the segment keyed by `routeKey`
131
+ * (the route NAME, the same coarse key the header path uses). Throws an
132
+ * actionable error when no matching segment was captured, or on a mismatch.
133
+ *
134
+ * Pairs with {@link createCacheSink}: wire `createRouter({ telemetry: sink })`,
135
+ * drive an RSC request, then assert against the recorded `events`. This is the
136
+ * zero-production-surface path (no header to enable). NOTE: `events` accumulates
137
+ * across requests, so the FIRST matching segment wins — slice or recreate the
138
+ * sink between requests for the same `routeKey`.
139
+ */
140
+ export function assertCacheDecision(
141
+ events: readonly TelemetryEvent[],
142
+ routeKey: string,
143
+ expected: ExpectedCacheStatus,
144
+ ): void {
145
+ const segments = filterCacheDecisions(events).flatMap(
146
+ (d) => d.segments ?? [],
147
+ );
148
+ const seg = segments.find((s) => s.id === routeKey);
149
+ if (seg === undefined) {
150
+ const known = segments.map((s) => s.id);
151
+ throw new Error(
152
+ `assertCacheDecision: no cache.decision segment for routeKey "${routeKey}". ` +
153
+ `Seen: ${known.length > 0 ? known.join(", ") : "(none)"}. Wire ` +
154
+ `createRouter({ telemetry: createCacheSink().sink }) and drive an RSC request.`,
155
+ );
156
+ }
157
+ if (seg.cacheStatus !== expected) {
158
+ throw new Error(
159
+ `assertCacheDecision: routeKey "${routeKey}" expected "${expected}" but got "${seg.cacheStatus}".`,
160
+ );
161
+ }
162
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * collectHandle — unit-test a handle's `collect`/accumulator function directly.
3
+ *
4
+ * A handle's collect function (the `createHandle(collect)` argument that maps the
5
+ * per-segment pushed values into the accumulated result) is otherwise not
6
+ * directly reachable: createHandle keeps it in a private registry keyed by the
7
+ * handle's `$$id` and returns only `{ __brand, $$id }`. This primitive runs that
8
+ * REAL registered collect on per-segment values you provide and returns the
9
+ * accumulated result — so the mapper/accumulator is unit-testable without a full
10
+ * route match.
11
+ *
12
+ * It relies on createHandle registering the collect even in a bare test (it
13
+ * assigns a runtime fallback id when the Vite plugin did not inject one). If a
14
+ * handle's module was never imported (so createHandle never ran), the collect is
15
+ * unregistered and this falls back to a flat array with a warning.
16
+ */
17
+
18
+ import { getCollectFn, type Handle } from "../handle.js";
19
+
20
+ export function collectHandle<TData, TAccumulated>(
21
+ handle: Handle<TData, TAccumulated>,
22
+ segments: ReadonlyArray<ReadonlyArray<TData>>,
23
+ ): TAccumulated {
24
+ const collectFn = getCollectFn(handle.$$id) as
25
+ | ((segments: TData[][]) => TAccumulated)
26
+ | undefined;
27
+
28
+ if (!collectFn) {
29
+ console.warn(
30
+ `[rango] collectHandle: handle "${handle.$$id}" has no registered collect ` +
31
+ `function. Import the handle's module so createHandle() runs. Falling ` +
32
+ `back to a flat array.`,
33
+ );
34
+ return segments.flat() as unknown as TAccumulated;
35
+ }
36
+
37
+ // Drop empty arrays matching production behavior (segment count/indices).
38
+ const nonEmpty = segments.filter((seg) => seg.length > 0) as TData[][];
39
+ return collectFn(nonEmpty);
40
+ }