@rangojs/router 0.20.0 → 0.22.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 (331) hide show
  1. package/dist/bin/rango.js +39 -15
  2. package/dist/types/browser/event-controller.d.ts +49 -0
  3. package/dist/types/browser/history-state.d.ts +16 -4
  4. package/dist/types/browser/navigation-store.d.ts +9 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +7 -0
  6. package/dist/types/browser/partial-update.d.ts +2 -1
  7. package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
  8. package/dist/types/browser/rango-state.d.ts +3 -1
  9. package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
  10. package/dist/types/browser/react/context.d.ts +25 -0
  11. package/dist/types/browser/react/location-state-shared.d.ts +84 -10
  12. package/dist/types/browser/react/location-state.d.ts +7 -2
  13. package/dist/types/browser/rsc-router.d.ts +13 -2
  14. package/dist/types/browser/types.d.ts +10 -0
  15. package/dist/types/build/generate-manifest.d.ts +2 -0
  16. package/dist/types/cache/background-task.d.ts +22 -0
  17. package/dist/types/cache/cache-key-utils.d.ts +50 -5
  18. package/dist/types/cache/cache-policy.d.ts +17 -0
  19. package/dist/types/cache/cache-scope.d.ts +5 -6
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
  21. package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
  22. package/dist/types/cache/document-cache.d.ts +8 -5
  23. package/dist/types/cache/handle-snapshot.d.ts +59 -50
  24. package/dist/types/cache/index.d.ts +3 -1
  25. package/dist/types/cache/invalidation-order.d.ts +14 -0
  26. package/dist/types/cache/memory-segment-store.d.ts +33 -1
  27. package/dist/types/cache/shell-snapshot.d.ts +29 -4
  28. package/dist/types/cache/store-scope.d.ts +22 -0
  29. package/dist/types/cache/tag-invalidation.d.ts +7 -6
  30. package/dist/types/cache/types.d.ts +84 -24
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
  32. package/dist/types/client-urls/navigation.d.ts +7 -1
  33. package/dist/types/client-urls/optimistic-location.d.ts +10 -4
  34. package/dist/types/errors.d.ts +6 -0
  35. package/dist/types/handles/deferred-resolution.d.ts +18 -1
  36. package/dist/types/internal-suspense-audit.d.ts +1 -0
  37. package/dist/types/loader-store.d.ts +4 -8
  38. package/dist/types/outlet-provider.d.ts +2 -1
  39. package/dist/types/prerender/build-shell-capture.d.ts +8 -6
  40. package/dist/types/prerender/cloudflare.d.ts +49 -0
  41. package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
  42. package/dist/types/prerender/index.d.ts +12 -0
  43. package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
  44. package/dist/types/prerender/on-demand.d.ts +272 -0
  45. package/dist/types/prerender/producer-guard.d.ts +33 -0
  46. package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
  47. package/dist/types/prerender/warm-request.d.ts +65 -0
  48. package/dist/types/prerender/warm.d.ts +65 -0
  49. package/dist/types/prerender/writable-store.d.ts +170 -0
  50. package/dist/types/prerender.d.ts +27 -0
  51. package/dist/types/reverse.d.ts +1 -1
  52. package/dist/types/route-content-wrapper.d.ts +3 -1
  53. package/dist/types/router/error-handling.d.ts +2 -0
  54. package/dist/types/router/handler-context.d.ts +1 -1
  55. package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
  56. package/dist/types/router/navigation-snapshot.d.ts +8 -1
  57. package/dist/types/router/pattern-matching.d.ts +2 -0
  58. package/dist/types/router/prefetch-default.d.ts +4 -4
  59. package/dist/types/router/prerender-match.d.ts +23 -1
  60. package/dist/types/router/route-trie-builder.d.ts +8 -1
  61. package/dist/types/router/router-context.d.ts +1 -0
  62. package/dist/types/router/router-interfaces.d.ts +32 -1
  63. package/dist/types/router/router-options.d.ts +39 -5
  64. package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
  65. package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
  67. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
  68. package/dist/types/router/trie-matching.d.ts +2 -0
  69. package/dist/types/router-versions.d.ts +39 -0
  70. package/dist/types/rsc/handler-context.d.ts +5 -0
  71. package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
  72. package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
  73. package/dist/types/rsc/shell-capture.d.ts +14 -1
  74. package/dist/types/rsc/shell-serve.d.ts +2 -2
  75. package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
  76. package/dist/types/rsc/types.d.ts +12 -6
  77. package/dist/types/segment-boundary-content.d.ts +17 -0
  78. package/dist/types/segment-loader-promise.d.ts +2 -6
  79. package/dist/types/server/build-version-table.d.ts +53 -0
  80. package/dist/types/server/context.d.ts +12 -4
  81. package/dist/types/server/handle-store.d.ts +89 -46
  82. package/dist/types/server/request-context.d.ts +84 -9
  83. package/dist/types/suspense-audit-react.d.ts +26 -0
  84. package/dist/types/suspense-audit.d.ts +157 -0
  85. package/dist/types/testing/action-form.d.ts +34 -0
  86. package/dist/types/testing/build-versions.d.ts +32 -0
  87. package/dist/types/testing/dom.entry.d.ts +1 -1
  88. package/dist/types/testing/flight.entry.d.ts +3 -1
  89. package/dist/types/testing/index.d.ts +6 -0
  90. package/dist/types/testing/internal/seed-vars.d.ts +7 -2
  91. package/dist/types/testing/internal/server-modules.d.ts +8 -0
  92. package/dist/types/testing/location-state-key.d.ts +5 -1
  93. package/dist/types/testing/render-route.d.ts +77 -6
  94. package/dist/types/testing/serve-shell-request.d.ts +55 -4
  95. package/dist/types/testing/shell-status.d.ts +14 -6
  96. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
  97. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  98. package/dist/types/thenable-status.d.ts +6 -0
  99. package/dist/types/types/handler-context.d.ts +15 -5
  100. package/dist/types/types/route-entry.d.ts +5 -0
  101. package/dist/types/urls/path-helper.d.ts +18 -0
  102. package/dist/types/vite/discovery/build-versions.d.ts +393 -0
  103. package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
  104. package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
  105. package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
  106. package/dist/types/vite/discovery/state.d.ts +25 -0
  107. package/dist/types/vite/encryption-key.d.ts +18 -0
  108. package/dist/types/vite/inject-client-debug.d.ts +6 -25
  109. package/dist/types/vite/plugin-types.d.ts +27 -0
  110. package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
  111. package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
  112. package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
  113. package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
  114. package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
  115. package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
  116. package/dist/types/vite/router-discovery.d.ts +6 -0
  117. package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
  118. package/dist/types/vite/utils/client-chunks.d.ts +2 -0
  119. package/dist/types/vite/utils/compare-strings.d.ts +7 -0
  120. package/dist/types/vite/utils/package-resolution.d.ts +8 -0
  121. package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
  122. package/dist/vite/index.js +1707 -931
  123. package/package.json +12 -3
  124. package/skills/cache-guide/SKILL.md +102 -37
  125. package/skills/caching/SKILL.md +56 -9
  126. package/skills/catalog.json +1 -1
  127. package/skills/client-urls/SKILL.md +16 -0
  128. package/skills/cloudflare/SKILL.md +133 -1
  129. package/skills/comparison/references/framework-comparison.md +17 -13
  130. package/skills/deployment-caching/SKILL.md +33 -12
  131. package/skills/document-cache/SKILL.md +16 -14
  132. package/skills/hooks/SKILL.md +25 -0
  133. package/skills/hooks/handle-and-actions.md +5 -0
  134. package/skills/hooks/navigation.md +38 -0
  135. package/skills/hooks/state.md +213 -11
  136. package/skills/hooks/urls.md +40 -1
  137. package/skills/host-router/SKILL.md +31 -0
  138. package/skills/intercept/SKILL.md +6 -0
  139. package/skills/loader/SKILL.md +25 -5
  140. package/skills/middleware/SKILL.md +2 -0
  141. package/skills/migrate-nextjs/SKILL.md +82 -9
  142. package/skills/ppr/SKILL.md +150 -48
  143. package/skills/prerender/SKILL.md +651 -10
  144. package/skills/rango/SKILL.md +9 -7
  145. package/skills/response-routes/SKILL.md +5 -5
  146. package/skills/route/SKILL.md +29 -0
  147. package/skills/router-setup/SKILL.md +15 -1
  148. package/skills/testing/SKILL.md +32 -27
  149. package/skills/testing/cache-prerender.md +18 -17
  150. package/skills/testing/client-components.md +193 -8
  151. package/skills/testing/handles.md +2 -0
  152. package/skills/testing/loader.md +1 -1
  153. package/skills/testing/middleware.md +1 -1
  154. package/skills/testing/render-handler.md +18 -18
  155. package/skills/testing/serve-shell-request.md +167 -22
  156. package/skills/testing/server-actions.md +17 -17
  157. package/skills/testing/server-tree.md +1 -1
  158. package/skills/typesafety/generated-files-and-cli.md +1 -1
  159. package/skills/use-cache/SKILL.md +25 -10
  160. package/skills/vercel/SKILL.md +5 -3
  161. package/skills/view-transitions/SKILL.md +9 -4
  162. package/src/bin/rango.ts +1 -1
  163. package/src/browser/event-controller.ts +119 -2
  164. package/src/browser/history-state.ts +39 -17
  165. package/src/browser/link-interceptor.ts +2 -2
  166. package/src/browser/navigation-bridge.ts +104 -41
  167. package/src/browser/navigation-store.ts +26 -0
  168. package/src/browser/navigation-transaction.ts +24 -12
  169. package/src/browser/network-error-handler.ts +2 -0
  170. package/src/browser/partial-update.ts +58 -14
  171. package/src/browser/prefetch/default-strategy.ts +22 -42
  172. package/src/browser/rango-state.ts +3 -1
  173. package/src/browser/react/Link.tsx +37 -43
  174. package/src/browser/react/NavigationProvider.tsx +85 -1
  175. package/src/browser/react/context.ts +30 -0
  176. package/src/browser/react/location-state-shared.ts +200 -34
  177. package/src/browser/react/location-state.ts +28 -77
  178. package/src/browser/react/use-action.ts +4 -1
  179. package/src/browser/react/use-handle.ts +3 -0
  180. package/src/browser/react/use-link-status.ts +4 -3
  181. package/src/browser/react/use-navigation.ts +4 -1
  182. package/src/browser/react/use-params.ts +4 -1
  183. package/src/browser/react/use-pathname.ts +4 -1
  184. package/src/browser/react/use-search-params.ts +5 -3
  185. package/src/browser/react/use-segments.ts +36 -54
  186. package/src/browser/rsc-router.tsx +93 -31
  187. package/src/browser/segment-reconciler.ts +3 -3
  188. package/src/browser/server-action-bridge.ts +43 -7
  189. package/src/browser/types.ts +11 -0
  190. package/src/build/generate-manifest.ts +17 -0
  191. package/src/build/merge-full-manifests.ts +5 -0
  192. package/src/build/route-trie.ts +1 -0
  193. package/src/cache/background-task.ts +61 -0
  194. package/src/cache/cache-key-utils.ts +91 -12
  195. package/src/cache/cache-policy.ts +11 -2
  196. package/src/cache/cache-runtime.ts +23 -23
  197. package/src/cache/cache-scope.ts +48 -36
  198. package/src/cache/cf/cf-cache-store.ts +91 -66
  199. package/src/cache/cf/cf-cache-types.ts +12 -4
  200. package/src/cache/document-cache.ts +35 -14
  201. package/src/cache/handle-snapshot.ts +118 -86
  202. package/src/cache/index.ts +5 -0
  203. package/src/cache/invalidation-order.ts +53 -0
  204. package/src/cache/memory-segment-store.ts +52 -1
  205. package/src/cache/shell-snapshot.ts +52 -5
  206. package/src/cache/store-scope.ts +36 -0
  207. package/src/cache/tag-invalidation.ts +7 -5
  208. package/src/cache/types.ts +90 -24
  209. package/src/cache/vercel/vercel-cache-store.ts +68 -26
  210. package/src/client-urls/client-root.tsx +1 -0
  211. package/src/client-urls/navigation.ts +10 -0
  212. package/src/client-urls/optimistic-location.ts +10 -4
  213. package/src/client.tsx +40 -3
  214. package/src/errors.ts +13 -6
  215. package/src/handles/deferred-resolution.ts +36 -0
  216. package/src/internal-suspense-audit.ts +12 -0
  217. package/src/loader-store.ts +4 -9
  218. package/src/outlet-provider.tsx +25 -1
  219. package/src/prerender/build-shell-capture.ts +11 -9
  220. package/src/prerender/cloudflare.ts +130 -0
  221. package/src/prerender/create-prerender-trigger.ts +780 -0
  222. package/src/prerender/index.ts +39 -0
  223. package/src/prerender/memory-prerender-store.ts +115 -0
  224. package/src/prerender/on-demand.ts +339 -0
  225. package/src/prerender/producer-guard.ts +66 -0
  226. package/src/prerender/shell-manifest-key.ts +12 -17
  227. package/src/prerender/warm-request.ts +154 -0
  228. package/src/prerender/warm.ts +193 -0
  229. package/src/prerender/writable-store.ts +278 -0
  230. package/src/prerender.ts +29 -0
  231. package/src/reverse.ts +1 -1
  232. package/src/route-content-wrapper.tsx +107 -13
  233. package/src/router/error-handling.ts +12 -12
  234. package/src/router/find-match.ts +1 -0
  235. package/src/router/handler-context.ts +2 -0
  236. package/src/router/intercept-resolution.ts +15 -8
  237. package/src/router/loader-resolution.ts +2 -10
  238. package/src/router/manifest.ts +2 -0
  239. package/src/router/match-middleware/background-revalidation.ts +2 -0
  240. package/src/router/match-middleware/cache-lookup.ts +239 -54
  241. package/src/router/navigation-snapshot.ts +25 -6
  242. package/src/router/pattern-matching.ts +14 -0
  243. package/src/router/prefetch-default.ts +4 -4
  244. package/src/router/prerender-match.ts +460 -352
  245. package/src/router/request-classification.ts +15 -12
  246. package/src/router/route-trie-builder.ts +9 -0
  247. package/src/router/router-context.ts +1 -0
  248. package/src/router/router-interfaces.ts +35 -1
  249. package/src/router/router-options.ts +45 -5
  250. package/src/router/segment-resolution/fresh.ts +20 -19
  251. package/src/router/segment-resolution/loader-cache.ts +110 -77
  252. package/src/router/segment-resolution/revalidation.ts +34 -24
  253. package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
  254. package/src/router/trie-matching.ts +3 -0
  255. package/src/router-versions.ts +60 -0
  256. package/src/router.ts +107 -1
  257. package/src/rsc/full-payload.ts +6 -2
  258. package/src/rsc/handler-context.ts +5 -0
  259. package/src/rsc/handler.ts +64 -7
  260. package/src/rsc/progressive-enhancement.ts +5 -1
  261. package/src/rsc/response-cache-serve.ts +10 -4
  262. package/src/rsc/rsc-rendering.ts +65 -12
  263. package/src/rsc/shell-build-manifest.ts +17 -12
  264. package/src/rsc/shell-capture-constants.ts +39 -7
  265. package/src/rsc/shell-capture.ts +144 -45
  266. package/src/rsc/shell-serve.ts +12 -6
  267. package/src/rsc/ssr-module-loader.ts +26 -0
  268. package/src/rsc/types.ts +12 -6
  269. package/src/segment-boundary-content.ts +25 -0
  270. package/src/segment-loader-promise.ts +28 -38
  271. package/src/segment-system.tsx +117 -57
  272. package/src/server/build-version-table.ts +130 -0
  273. package/src/server/context.ts +40 -17
  274. package/src/server/cookie-store.ts +5 -0
  275. package/src/server/handle-store.ts +357 -127
  276. package/src/server/request-context.ts +149 -12
  277. package/src/ssr/ssr-root.tsx +32 -13
  278. package/src/suspense-audit-react.tsx +98 -0
  279. package/src/suspense-audit.ts +1030 -0
  280. package/src/testing/action-form.ts +66 -0
  281. package/src/testing/build-versions.ts +64 -0
  282. package/src/testing/dispatch.ts +6 -0
  283. package/src/testing/dom.entry.ts +1 -0
  284. package/src/testing/flight-runtime.d.ts +27 -0
  285. package/src/testing/flight.entry.ts +4 -0
  286. package/src/testing/index.ts +22 -0
  287. package/src/testing/internal/context.ts +2 -1
  288. package/src/testing/internal/seed-vars.ts +14 -2
  289. package/src/testing/internal/server-modules.ts +32 -0
  290. package/src/testing/location-state-key.ts +18 -8
  291. package/src/testing/render-handler.ts +2 -1
  292. package/src/testing/render-route.tsx +293 -46
  293. package/src/testing/serve-shell-request.ts +262 -37
  294. package/src/testing/shell-status.ts +17 -6
  295. package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
  296. package/src/testing/vitest-stubs/version.ts +4 -1
  297. package/src/thenable-status.ts +10 -0
  298. package/src/types/handler-context.ts +15 -5
  299. package/src/types/route-entry.ts +6 -0
  300. package/src/urls/path-helper.ts +69 -9
  301. package/src/use-loader.tsx +57 -30
  302. package/src/vite/discovery/build-versions.ts +1281 -0
  303. package/src/vite/discovery/bundle-postprocess.ts +4 -0
  304. package/src/vite/discovery/discover-routers.ts +15 -0
  305. package/src/vite/discovery/installed-packages.ts +159 -0
  306. package/src/vite/discovery/prerender-collection.ts +38 -1
  307. package/src/vite/discovery/router-versions-phase.ts +336 -0
  308. package/src/vite/discovery/shell-prerender-phase.ts +33 -43
  309. package/src/vite/discovery/state.ts +36 -1
  310. package/src/vite/discovery/virtual-module-codegen.ts +7 -2
  311. package/src/vite/encryption-key.ts +101 -9
  312. package/src/vite/inject-client-debug.ts +42 -20
  313. package/src/vite/plugin-types.ts +28 -0
  314. package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
  315. package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
  316. package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
  317. package/src/vite/plugins/expose-internal-ids.ts +12 -3
  318. package/src/vite/plugins/stable-rsc-output.ts +147 -0
  319. package/src/vite/plugins/version-plugin.ts +19 -24
  320. package/src/vite/plugins/version.d.ts +17 -2
  321. package/src/vite/plugins/virtual-entries.ts +39 -6
  322. package/src/vite/rango.ts +28 -3
  323. package/src/vite/router-discovery.ts +96 -13
  324. package/src/vite/utils/bundle-analysis.ts +39 -7
  325. package/src/vite/utils/client-chunks.ts +5 -1
  326. package/src/vite/utils/compare-strings.ts +9 -0
  327. package/src/vite/utils/package-resolution.ts +9 -0
  328. package/src/vite/utils/prerender-utils.ts +4 -1
  329. package/src/vite/utils/shared-utils.ts +1 -1
  330. package/dist/types/segment-content-promise.d.ts +0 -13
  331. package/src/segment-content-promise.ts +0 -67
@@ -116,6 +116,7 @@ stated, greppable contract.
116
116
  | set meta/breadcrumbs from loader data | `ctx.use(Handle)` in the loader | /loader |
117
117
  | guarantee loader output in the SSR HTML | `loader(L, { ssr: false })` | /loader |
118
118
  | pre-render a route at build time | `Prerender(...)` wrapper | /prerender |
119
+ | make a route ready before traffic | `router.prerender({ env })(url)` | /prerender |
119
120
  | feed live loaders from a cached shell | replayed handle + `ctx.rendered()` | /shell-manifest |
120
121
  | cache HTML shell, live loaders as holes | `ppr` path option | /ppr |
121
122
  | choose in-function vs CDN caching | deployment cache boundary | /deployment-caching |
@@ -157,8 +158,9 @@ stated, greppable contract.
157
158
  `cacheTag(...tags)`, then `updateTag(...tags)` (awaitable) or `revalidateTag(...tags)`
158
159
  (background). Built-in stores index by tag.
159
160
  - `useLoader` / `useHandle` / `useFetchLoader` are client-only.
160
- - Caches are correctness-first: persistent store keys are version-segmented (no
161
- cross-deploy drift), the forward/back cache is mutation-aware, and
161
+ - Caches are correctness-first: persistent store keys are version-segmented per
162
+ `createRouter()` by a hash of its built code (a deploy that does not change a
163
+ router keeps its cache; one that does reads under new keys), the forward/back cache is mutation-aware, and
162
164
  `createVar({ cache: false })` throws on a **direct** read inside a `cache()`
163
165
  boundary or a `"use cache"` function (a deliberately non-propagating guard). See `/cache-guide` →
164
166
  "Correctness & invalidation".
@@ -422,11 +424,11 @@ npx rango generate src/urls.tsx src/api/
422
424
 
423
425
  Three modes:
424
426
 
425
- | Flag | Behavior |
426
- | ----------- | ------------------------------------------------------------------------------------------------------------------------- |
427
- | (none) | Static parser. Exits with an error, writing nothing, if an `include()` can't be resolved statically (factory or dynamic). |
428
- | `--static` | Static parser, but writes partial output and prints the unresolvable includes as warnings. |
429
- | `--runtime` | Vite-based runtime discovery (full coverage; needs `vite` and `@vitejs/plugin-rsc`). `--config <path>` picks the config. |
427
+ | Flag | Behavior |
428
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
429
+ | (none) | Static parser. Exits with an error, writing nothing, if an `include()` can't be resolved statically (factory or dynamic). |
430
+ | `--static` | Static parser, but writes partial output and prints the unresolvable includes as warnings. |
431
+ | `--runtime` | Vite-based runtime discovery (full coverage; needs `vite`; `@vitejs/plugin-rsc` comes with the router). `--config <path>` picks the config. |
430
432
 
431
433
  ### Auto-detection
432
434
 
@@ -422,11 +422,11 @@ path.json("/api/users", handler, { name: "users" }, () => [
422
422
  Response-route `cache()` stores whole `Response`s. The serve leaf
423
423
  (`serveResponseRouteWithCache`) applies the same safety gates as document cache:
424
424
 
425
- | Gate | Behavior |
426
- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
427
- | **GET/HEAD only** | Non-GET/HEAD methods skip the cache (handler runs uncached). |
428
- | **Per-client signals** | A response with `Set-Cookie` or `x-rango-keep-cache` is returned live but **not** stored (MISS + SWR revalidation also skip put). |
429
- | **Default key** | `response:{type}:{cacheKeyBase(host, path, searchParams)}` — sorted search, reserved `_rsc*` / allowlisted `__*` params excluded. Custom `key()` / store `keyGenerator` still override. |
425
+ | Gate | Behavior |
426
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
427
+ | **GET/HEAD only** | Non-GET/HEAD methods skip the cache (handler runs uncached). |
428
+ | **Per-client signals** | A response with `Set-Cookie` or `x-rango-keep-cache` is returned live but **not** stored (MISS + SWR revalidation also skip put). |
429
+ | **Default key** | `response:{type}:{routerId}@{host}{path}[?search]` — the serving router's id, sorted search, reserved `_rsc*` / allowlisted `__*` params excluded. Custom `key()` / store `keyGenerator` still override. |
430
430
 
431
431
  ## CSRF
432
432
 
@@ -404,6 +404,35 @@ path("/checkout", CheckoutPage, { name: "checkout" }, () => [
404
404
  ]);
405
405
  ```
406
406
 
407
+ ## Errors in a streamed handler
408
+
409
+ A handler under `loading()` (a route, or a parallel slot) streams: it settles
410
+ after the response started. When it rejects, the nearest `errorBoundary()`
411
+ renders in place of that segment; `notFound()` renders the nearest
412
+ `notFoundBoundary()`. A slot finds boundaries declared on its owning layout or
413
+ route. Layouts and nav stay, on the document load and on a client navigation.
414
+
415
+ ```typescript
416
+ path("/product/:slug", ProductPage, { name: "product" }, () => [
417
+ loading(<ProductSkeleton />),
418
+ errorBoundary(({ error }) => <ProductError message={error.message} />),
419
+ notFoundBoundary(() => <ProductNotFound />),
420
+ ]);
421
+ ```
422
+
423
+ - The HTTP status stays what the stream already sent (200), `notFound()`
424
+ included; a failure before the flush (no `loading()`, or
425
+ `loading(fallback, { ssr: false })` on a document load) still sets 500 / 404.
426
+ A loader's 404 is likewise only set when its rejection settles before the
427
+ response is built.
428
+ - `onError` fires once per failure.
429
+ - A failed render is never stored: `cache()` skips the write for the scope that
430
+ holds the failed segment, a `ppr` shell and the document cache refuse the
431
+ whole document, and a Prerender route still fails the build.
432
+ - With no declared boundary the rejection reaches the client and
433
+ `RootErrorBoundary` replaces the page. A `redirect()` thrown from a streamed
434
+ handler is not recovered either (see "Redirects").
435
+
407
436
  ## Redirects
408
437
 
409
438
  `redirect(url, statusOrOptions?)` returns a `Response` (default status `302`).
@@ -170,7 +170,7 @@ interface RangoOptions<TEnv> {
170
170
  request: Request,
171
171
  env: TEnv,
172
172
  ) => string | boolean | Promise<string | boolean>;
173
- version?: string; // Client/server version string; defaults to the build VERSION
173
+ version?: string; // Cache version and client/server version; replaces both computed versions (data and document); defaults to the versions `vite build` computes per router
174
174
  originCheck?:
175
175
  | boolean
176
176
  | ((
@@ -215,6 +215,7 @@ rango({
215
215
  headScripts: "preinit", // "preinit" (executing head module scripts) | "preload" (modulepreload hints only)
216
216
  prerender: { onError: "fail" }, // "fail" | "warn" when a Prerender/Static render throws (/prerender)
217
217
  buildEnv: false, // Build-time ctx.env for Prerender/Static handlers (/prerender)
218
+ encryptionKey: process.env.RANGO_ENCRYPTION_KEY, // Stable server-action encryption key (see Notes)
218
219
  });
219
220
  ```
220
221
 
@@ -254,6 +255,19 @@ Notes:
254
255
  renders and PPR shell capture. Unset, documents whose route has a
255
256
  `loader(Def, { ssr: false })` raise it automatically so that content stays
256
257
  in place; see `/loader`.
258
+ - `encryptionKey` is the key that encrypts inline server-action bound
259
+ arguments: base64-encoded 32 bytes (`openssl rand -base64 32`). Pass it as
260
+ `rango({ encryptionKey: process.env.RANGO_ENCRYPTION_KEY })`, never as a
261
+ literal. It is validated when `rango()` is called (invalid base64 or a wrong
262
+ size throws). `undefined` falls back to the `RANGO_ENCRYPTION_KEY`
263
+ environment variable, then to a key generated per build. The key is part of
264
+ the cache version of every router whose server code encrypts inline-action
265
+ bound arguments, so without a stable key those routers get a new version (and
266
+ a cleared cache) on every build; the build prints one note when that applies.
267
+ A router that renders no such action keeps its cache without one (`/use-cache`,
268
+ `/cache-guide`).
269
+ - `createRouter({ version })` takes effect on every preset, including `node`
270
+ and `vercel`. It replaces both computed cache versions with that exact value.
257
271
  - `@rangojs/router/vite` also exports `poke()`, a dev-server plugin: type `e`
258
272
  then Enter (or Ctrl+R where the terminal passes it through) to full-reload
259
273
  the browser.
@@ -89,32 +89,36 @@ The single rule that drives everything:
89
89
 
90
90
  Each primitive links to its sub-file (API + recipe + caveats).
91
91
 
92
- | The behavior is… | Layer | Primitive | Import root |
93
- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------- | -------------------------------- |
94
- | a `reverse` / `href` / params / env TYPE contract (a wrong name or missing param must not compile) | types (tsc) | [`@ts-expect-error` / `expectTypeOf`](./reverse-and-types.md) | `@rangojs/router/client` + `tsc` |
95
- | one loader's data logic | unit (node) | [`runLoader`](./loader.md) | `@rangojs/router/testing` |
96
- | a loader's cookie / header / thrown `redirect()`/`notFound()` / handle-push output | unit (node) | [`runLoaderResult`](./loader.md) | `@rangojs/router/testing` |
97
- | one middleware's ordering / short-circuit / cookie+header merge | unit (node) | [`runMiddleware`](./middleware.md) | `@rangojs/router/testing` |
98
- | a `"use server"` action's cookie / header / flash output (even on `throw redirect()`) | unit (node) | [`runInRequestContext`](./server-actions.md) | `@rangojs/router/testing` |
99
- | a `transition({ when })` browser predicate against `{ kind, from, to, action }` | unit (node) | [`runTransitionWhen`](./predicates.md) (`{ applied, gatedOff, context }`) | `@rangojs/router/testing` |
100
- | a `clientUrls()` `revalidate()` / `isAction(ref)` predicate | unit (node) | [`runClientRevalidate`](./predicates.md) (production chain evaluator + locked defaults) | `@rangojs/router/testing` |
101
- | a handle's `collect`/accumulator, a seeded handle read, or a loader handle write | unit | [`collectHandle` / seeded `handles` / `handlePushes`](./handles.md) | `@rangojs/router/testing` |
102
- | a CLIENT component reading router context (`useParams`/`useReverse`/`Outlet`/`useNavigation`/`useLoader`) | unit (DOM) | [`renderRoute`](./client-components.md) | `@rangojs/router/testing/dom` |
103
- | a stale indicator from `useLoader().isLoading` while a `transition()` navigation re-runs the loader | unit (DOM) | [`renderRoute` + `navigate(url, { loaders })`](./client-components.md#held-navigation) | `@rangojs/router/testing/dom` |
104
- | a component reading `useLocationState(Def)` after `router.push(url, { state: [Def(value)] })` | unit (DOM) | [`renderRoute` + `withLocationStateKey`](./client-components.md#location-state) | `@rangojs/router/testing[/dom]` |
105
- | a redirect / status / headers / cookies / **response route** (json/text/html/xml/md), no Flight | integration | [`dispatch`](./response-routes.md) | `@rangojs/router/testing` |
106
- | a real async **Server Component** (assert what it rendered: typed boundary props, server-rendered host content, inlined-vs-island) | RSC unit | [`renderServerTree` + `findClientBoundaries`/`findElements`](./server-tree.md) | `@rangojs/router/testing/flight` |
107
- | the exact Flight **wire payload** shape (a drift snapshot) | RSC unit | [`renderToFlightString` + `toMatchFlightSnapshot`](./flight.md) | `@rangojs/router/testing/flight` |
108
- | a real route **handler** `(ctx) => rsc` (params/loaders/vars -> rendered RSC + effects) | RSC unit | [`renderHandler`](./render-handler.md) | `@rangojs/router/testing/flight` |
109
- | navigation, hydration, PE parity, view transitions, real SSR | e2e | [`createRangoE2E` -> `parityDescribe`/`expectParity`](./e2e-parity.md) | `@rangojs/router/testing/e2e` |
110
- | a `"use cache"` hit in a handler (the body runs once across renders; the value round-trips) | RSC unit | [`renderHandler` + seeded `cacheStore`](./render-handler.md#a-use-cache-hit) | `@rangojs/router/testing/flight` |
111
- | a `"use cache"` hit in a loader (the same, in the react-server project) | RSC unit | [`runLoader` + seeded `cacheStore`](./loader.md) | `@rangojs/router/testing` |
112
- | a loader's own `cache()` (HIT, `updateTag()` of a tag its body recorded) | RSC unit | [`runLoader` + `cache` + seeded `cacheStore`](./loader.md) | `@rangojs/router/testing` |
113
- | cache hit/miss/stale, prerender (= a cache hit by design) | e2e + signal | [`assertCacheStatus` (header) / `assertCacheDecision` (telemetry sink)](./cache-prerender.md) | `@rangojs/router/testing[/e2e]` |
114
- | a `ppr` route's shell: MISS -> capture -> HIT, what the shell froze vs what a HIT renders live, tag eviction, navigation replay | RSC unit | [`serveShellRequest`](./serve-shell-request.md) | `@rangojs/router/testing/flight` |
115
- | PPR shell HIT/MISS in real HTML (`ppr` path option), shell store key / getShell after flush | e2e + signal | [`assertShellStatus` / `shellCacheKey` + store](./cache-prerender.md#ppr-shell-and-navigation-replay) | `@rangojs/router/testing[/e2e]` |
116
- | generated route map drift vs runtime | unit (node) | [`assertGeneratedRoutesMatch`](./reverse-and-types.md) | `@rangojs/router/testing` |
117
- | a platform binding (`env.DB` / Durable Object / `env.R2`) | unit/integr. | [your own double via `env`](./bindings.md) | (any primitive's `env` option) |
92
+ | The behavior is… | Layer | Primitive | Import root |
93
+ | ---------------------------------------------------------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
94
+ | a `reverse` / `href` / params / env TYPE contract (a wrong name or missing param must not compile) | types (tsc) | [`@ts-expect-error` / `expectTypeOf`](./reverse-and-types.md) | `@rangojs/router/client` + `tsc` |
95
+ | one loader's data logic | unit (node) | [`runLoader`](./loader.md) | `@rangojs/router/testing` |
96
+ | a loader's cookie / header / thrown `redirect()`/`notFound()` / handle-push output | unit (node) | [`runLoaderResult`](./loader.md) | `@rangojs/router/testing` |
97
+ | one middleware's ordering / short-circuit / cookie+header merge | unit (node) | [`runMiddleware`](./middleware.md) | `@rangojs/router/testing` |
98
+ | a `"use server"` action's cookie / header / flash output (even on `throw redirect()`) | unit (node) | [`runInRequestContext`](./server-actions.md) | `@rangojs/router/testing` |
99
+ | a `transition({ when })` browser predicate against `{ kind, from, to, action }` | unit (node) | [`runTransitionWhen`](./predicates.md) (`{ applied, gatedOff, context }`) | `@rangojs/router/testing` |
100
+ | a `clientUrls()` `revalidate()` / `isAction(ref)` predicate | unit (node) | [`runClientRevalidate`](./predicates.md) (production chain evaluator + locked defaults) | `@rangojs/router/testing` |
101
+ | a handle's `collect`/accumulator, a seeded handle read, or a loader handle write | unit | [`collectHandle` / seeded `handles` / `handlePushes`](./handles.md) | `@rangojs/router/testing` |
102
+ | a CLIENT component reading router context (`useParams`/`useReverse`/`Outlet`/`useNavigation`/`useLoader`) | unit (DOM) | [`renderRoute`](./client-components.md) | `@rangojs/router/testing/dom` |
103
+ | a stale indicator from `useLoader().isLoading` while a `transition()` navigation re-runs the loader | unit (DOM) | [`renderRoute` + `navigate(url, { loaders })`](./client-components.md#held-navigation) | `@rangojs/router/testing/dom` |
104
+ | a component reading `useLocationState(Def)` after `router.push(url, { state: [Def(value)] })` | unit (DOM) | [`renderRoute` + `withLocationStateKey`](./client-components.md#location-state) | `@rangojs/router/testing[/dom]` |
105
+ | location state while a navigation is pending (a loader the test holds), or after a back/forward | unit (DOM) | [`navigate(url, { state, loaders })`, a `popstate` event](./client-components.md#state-while-a-navigation-is-pending) | `@rangojs/router/testing[/dom]` |
106
+ | a CLIENT component's hydration: its first client render against its own server HTML (`typeof window`, a late `<Suspense>`) | unit (DOM) | [`renderRoute` + `hydrate: true`](./client-components.md#hydration) | `@rangojs/router/testing/dom` |
107
+ | location state with `clearOnReload`: state after a refresh vs a client navigation | unit (DOM) | [`renderRoute` seeds, with and without `hydrate: true`](./client-components.md#location-state) | `@rangojs/router/testing[/dom]` |
108
+ | a redirect / status / headers / cookies / **response route** (json/text/html/xml/md), no Flight | integration | [`dispatch`](./response-routes.md) | `@rangojs/router/testing` |
109
+ | a real async **Server Component** (assert what it rendered: typed boundary props, server-rendered host content, inlined-vs-island) | RSC unit | [`renderServerTree` + `findClientBoundaries`/`findElements`](./server-tree.md) | `@rangojs/router/testing/flight` |
110
+ | the exact Flight **wire payload** shape (a drift snapshot) | RSC unit | [`renderToFlightString` + `toMatchFlightSnapshot`](./flight.md) | `@rangojs/router/testing/flight` |
111
+ | a real route **handler** `(ctx) => rsc` (params/loaders/vars -> rendered RSC + effects) | RSC unit | [`renderHandler`](./render-handler.md) | `@rangojs/router/testing/flight` |
112
+ | navigation, hydration, PE parity, view transitions, real SSR | e2e | [`createRangoE2E` -> `parityDescribe`/`expectParity`](./e2e-parity.md) | `@rangojs/router/testing/e2e` |
113
+ | a `"use cache"` hit in a handler (the body runs once across renders; the value round-trips) | RSC unit | [`renderHandler` + seeded `cacheStore`](./render-handler.md#a-use-cache-hit) | `@rangojs/router/testing/flight` |
114
+ | a `"use cache"` hit in a loader (the same, in the react-server project) | RSC unit | [`runLoader` + seeded `cacheStore`](./loader.md) | `@rangojs/router/testing` |
115
+ | a loader's own `cache()` (HIT, `updateTag()` of a tag its body recorded) | RSC unit | [`runLoader` + `cache` + seeded `cacheStore`](./loader.md) | `@rangojs/router/testing` |
116
+ | cache hit/miss/stale, prerender (= a cache hit by design) | e2e + signal | [`assertCacheStatus` (header) / `assertCacheDecision` (telemetry sink)](./cache-prerender.md) | `@rangojs/router/testing[/e2e]` |
117
+ | a `ppr` route's shell: MISS -> capture -> HIT, what the shell froze vs what a HIT renders live, tag eviction, navigation replay | RSC unit | [`serveShellRequest`](./serve-shell-request.md) | `@rangojs/router/testing/flight` |
118
+ | a form submitted before hydration: a `useActionState` result the no-JS POST renders, the form state its payload carries | RSC unit | [`createActionForm` + `serveShellRequest({ form })`](./serve-shell-request.md) | `@rangojs/router/testing/flight` |
119
+ | PPR shell HIT/MISS in real HTML (`ppr` path option), shell store key / getShell after flush | e2e + signal | [`assertShellStatus` / `shellCacheKey` + store](./cache-prerender.md#ppr-shell-and-navigation-replay) | `@rangojs/router/testing[/e2e]` |
120
+ | generated route map drift vs runtime | unit (node) | [`assertGeneratedRoutesMatch`](./reverse-and-types.md) | `@rangojs/router/testing` |
121
+ | a platform binding (`env.DB` / Durable Object / `env.R2`) | unit/integr. | [your own double via `env`](./bindings.md) | (any primitive's `env` option) |
118
122
 
119
123
  Cross-references to the DSL skills: `/loader`, `/middleware`, `/server-actions`,
120
124
  `/handler-use`, `/hooks`, `/response-routes`, `/route`, `/caching`, `/prerender`,
@@ -147,6 +151,7 @@ beside their function):
147
151
  | | `collectHandle` | [`handles.md`](./handles.md) |
148
152
  | | `withLocationStateKey` | [`client-components.md`](./client-components.md) |
149
153
  | | `dispatch` | [`response-routes.md`](./response-routes.md) |
154
+ | | `setBuildVersions` | [`serve-shell-request.md`](./serve-shell-request.md) |
150
155
  | | `assertCacheStatus`, `assertCacheDecision`, `parseCacheHeader`, `createCacheSink`, `filterCacheDecisions` | [`cache-prerender.md`](./cache-prerender.md) |
151
156
  | | `assertShellStatus`, `parseShellStatus`, `shellCacheKey`, `SHELL_STATUS_HEADER`, `assertPprReplayStatus`, `parsePprReplayStatus`, `PPR_REPLAY_STATUS_HEADER` | [`cache-prerender.md`](./cache-prerender.md) |
152
157
  | | `assertGeneratedRoutesMatch`, `diffGeneratedRoutes` | [`reverse-and-types.md`](./reverse-and-types.md) |
@@ -154,7 +159,7 @@ beside their function):
154
159
  | `@rangojs/router/testing/flight` | `renderToFlightString`, `normalizeFlight`, `assertFlightRuntimeAvailable` | [`flight.md`](./flight.md) |
155
160
  | | `renderServerTree`, `findClientBoundaries`, `findElements`, `textContent`, `assertFlightTreeRuntimeAvailable` | [`server-tree.md`](./server-tree.md) |
156
161
  | | `renderHandler` | [`render-handler.md`](./render-handler.md) |
157
- | | `serveShellRequest`, `resetShellTestState` | [`serve-shell-request.md`](./serve-shell-request.md) |
162
+ | | `serveShellRequest`, `resetShellTestState`, `createActionForm` | [`serve-shell-request.md`](./serve-shell-request.md) |
158
163
  | `@rangojs/router/testing/flight-matchers` | `flightMatchers` (`toMatchFlight`, `toMatchFlightSnapshot`) | [`flight.md`](./flight.md) |
159
164
  | `@rangojs/router/testing/vitest` | `rangoTestConfig`, `rangoTestAliases`, `rangoInlineDeps`, `rangoUseClientTransform`, `rangoUseCacheTransform` | [`setup.md`](./setup.md) |
160
165
  | `@rangojs/router/testing/e2e` | `createRangoE2E` and its standalone helpers; the cache/shell status helpers above are re-exported here | [`e2e-parity.md`](./e2e-parity.md) |
@@ -121,25 +121,25 @@ expect(decision.segments?.[0].shouldRevalidate).toBe(true);
121
121
 
122
122
  **DSL:** `ppr: true | PartialPrerenderProps` on a page route (see `/ppr`). **Not** the same header as `X-Rango-Cache` — the shell is cached separately from the segments.
123
123
 
124
- | Helper | Import | Role |
125
- | ----------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
126
- | `assertShellStatus(res, "HIT" \| "MISS")` | `@rangojs/router/testing` or `…/e2e` | Assert `x-rango-shell` on a **real document** Response |
127
- | `parseShellStatus(res)` | same | `"HIT" \| "MISS" \| null` (null = header absent / unrecognized) |
128
- | `shellCacheKey(url, searchParams?, partition?)` | same | Production shell store key (`host+pathname+sorted search+:shell`, then `\|partition`) for `store.getShell` / custom stores. Pass your router's `cache.searchParams` config as the second argument when it sets one — the production key applies it. For a route whose `cache({ key })` partitions its record, pass that function's result for the request as `partition`: the shell is partitioned the same way, the result namespaced as production stores it (`key:` plus its URI encoding). Under nested keyed `cache()` boundaries, pass their `key()` results as an array, outermost first. For a store `keyGenerator` that partitions it, pass `{ keys, generated }` (`ShellCachePartition`): `generated` lists the `keyGenerator` results one per store, outermost first: leave them out when every one returns the default key, else keep each position and pass `""` for one that returns it. |
129
- | `SHELL_STATUS_HEADER` | same | `"x-rango-shell"` constant |
130
- | `assertPprReplayStatus(res, expected)` | same | Assert fresh/stale replay or a bounded bypass decision |
131
- | `parsePprReplayStatus(res)` | same | Structured replay/bypass status, or null for an absent/unrecognized header |
132
- | `PPR_REPLAY_STATUS_HEADER` | same | `"x-rango-ppr-replay"` constant |
124
+ | Helper | Import | Role |
125
+ | ------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
126
+ | `assertShellStatus(res, "HIT" \| "MISS")` | `@rangojs/router/testing` or `…/e2e` | Assert `x-rango-shell` on a **real document** Response |
127
+ | `parseShellStatus(res)` | same | `"HIT" \| "MISS" \| null` (null = header absent / unrecognized) |
128
+ | `shellCacheKey(router, url, searchParams?, partition?)` | same | Production shell store key (`routerId@host+pathname+sorted search+:shell`, then `\|partition`) for `store.getShell` / custom stores. `router` is the router that serves the URL, or `{ id }` where it cannot be imported (a Playwright test): a shell belongs to one router. Pass your router's `cache.searchParams` config as the second argument when it sets one — the production key applies it. For a route whose `cache({ key })` partitions its record, pass that function's result for the request as `partition`: the shell is partitioned the same way, the result namespaced as production stores it (`key:` plus its URI encoding). Under nested keyed `cache()` boundaries, pass their `key()` results as an array, outermost first. For a store `keyGenerator` that partitions it, pass `{ keys, generated }` (`ShellCachePartition`): `generated` lists the `keyGenerator` results one per store, outermost first: leave them out when every one returns the default key, else keep each position and pass `""` for one that returns it. |
129
+ | `SHELL_STATUS_HEADER` | same | `"x-rango-shell"` constant |
130
+ | `assertPprReplayStatus(res, expected)` | same | Assert fresh/stale replay or a bounded bypass decision |
131
+ | `parsePprReplayStatus(res)` | same | Structured replay/bypass status, or null for an absent/unrecognized header |
132
+ | `PPR_REPLAY_STATUS_HEADER` | same | `"x-rango-ppr-replay"` constant |
133
133
 
134
134
  ### What unit can prove vs e2e
135
135
 
136
- | Layer | What you assert | How |
137
- | -------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
138
- | **Unit** | store family + key identity | `MemorySegmentCacheStore` + `shellCacheKey(url)` + `putShell`/`getShell` / tag eviction — dogfood in `e2e/mini/test/shell-store-family.test.ts` and `src/testing/__tests__/shell-status.test.ts` |
139
- | **Unit** | header helper contract | `assertShellStatus` on a Response that already carries the header (characterizes the helper; **never** invent a HIT to claim capture worked) |
140
- | **Unit** | real capture → HIT, replay | `serveShellRequest(router, url, { cacheStore })` (react-server project); only the HTML step is stubbed — [`./serve-shell-request.md`](./serve-shell-request.md) |
141
- | **E2E** | live MISS → capture → HIT | document GET, poll until `x-rango-shell: HIT` (background capture) — `e2e/shell-cache.test.ts` |
142
- | **E2E** | partial replay decision | soft navigation to a warmed shell and `assertPprReplayStatus(response, { outcome: "HIT", freshness: "fresh" })` |
136
+ | Layer | What you assert | How |
137
+ | -------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
138
+ | **Unit** | store family + key identity | `MemorySegmentCacheStore` + `shellCacheKey(router, url)` + `putShell`/`getShell` / tag eviction — dogfood in `e2e/mini/test/shell-store-family.test.ts` and `src/testing/__tests__/shell-status.test.ts` |
139
+ | **Unit** | header helper contract | `assertShellStatus` on a Response that already carries the header (characterizes the helper; **never** invent a HIT to claim capture worked) |
140
+ | **Unit** | real capture → HIT, replay | `serveShellRequest(router, url, { cacheStore })` (react-server project); only the HTML step is stubbed — [`./serve-shell-request.md`](./serve-shell-request.md) |
141
+ | **E2E** | live MISS → capture → HIT | document GET, poll until `x-rango-shell: HIT` (background capture) — `e2e/shell-cache.test.ts` |
142
+ | **E2E** | partial replay decision | soft navigation to a warmed shell and `assertPprReplayStatus(response, { outcome: "HIT", freshness: "fresh" })` |
143
143
 
144
144
  `dispatch` is RSC-free: it never runs shell serve/capture. `renderHandler` only surfaces `ctx.dynamic()` opt-out, not bake/serve. The HIT contract — no handler runs, the handler output is the capture's copy, live loaders re-run — is asserted through `serveShellRequest`: count your handler's runs and a live loader's across a MISS and the HITs after it, and check that only the loader's moves ([`./serve-shell-request.md`](./serve-shell-request.md)). A route partitioned by `cache({ key })` or a store `keyGenerator` has a shell per partition: `serveShellRequest` reports the key the serve path resolved (`result.key`), and `shellCacheKey` takes the partition as its third argument.
145
145
 
@@ -175,10 +175,11 @@ parityDescribe("product shell", (f) => {
175
175
  // Unit (Vitest): the key the serve path uses, against your own store instance
176
176
  import { expect, it } from "vitest";
177
177
  import { shellCacheKey } from "@rangojs/router/testing";
178
+ import { router } from "../src/router";
178
179
 
179
180
  it("stores the shell under the production key", async () => {
180
181
  // ...after your code under test captured a shell into `store`...
181
- const key = shellCacheKey(new URL("http://localhost/products/1"));
182
+ const key = shellCacheKey(router, new URL("http://localhost/products/1"));
182
183
  expect(await store.getShell(key)).not.toBeNull();
183
184
  });
184
185
  ```