@rangojs/router 0.16.0 → 0.18.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 (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -1,24 +1,127 @@
1
- // Stub for `@vitejs/plugin-rsc/rsc` and the split `/rsc/server`, `/rsc/client`
2
- // entries, shipped so consumers do not have to write a per-file `vi.mock(...)`.
3
- // Importing a router internal transitively pulls this module, whose real
4
- // top-level body imports Vite virtuals that do not resolve in plain node. The
5
- // unit/integration primitives (dispatch/runLoader/runMiddleware) never render
6
- // RSC, so empty fns suffice.
7
- export const createFromReadableStream = (): never => {
8
- throw new Error("plugin-rsc stub: createFromReadableStream not available");
9
- };
10
- export const renderToReadableStream = (): never => {
11
- throw new Error("plugin-rsc stub: renderToReadableStream not available");
12
- };
1
+ /// <reference path="../flight-runtime.d.ts" />
2
+ /**
3
+ * Stub for `@vitejs/plugin-rsc/rsc` and the split `/rsc/server`, `/rsc/client`
4
+ * entries, shipped so consumers do not have to write a per-file `vi.mock(...)`.
5
+ * The real entries top-level import Vite virtuals that do not resolve in plain
6
+ * node.
7
+ *
8
+ * Under the `react-server` condition (the Flight project) the "use cache"
9
+ * codec runs on real Flight: the vendored react-server-dom `server.edge` /
10
+ * `client.edge` builds that plugin-rsc's `react/rsc/server` and
11
+ * `react/rsc/client` wrap, with the same manifests and the same server-side
12
+ * reference loader (`@vitejs/plugin-rsc/core/rsc` `setRequireModule`), so
13
+ * keys, writes, hits, value round trips and error rows match a build. The
14
+ * wrappers are not reused: their manifests read `import.meta.env.DEV`, which
15
+ * is undefined in a module node loads directly (vitest externalizes
16
+ * node_modules).
17
+ *
18
+ * Without the condition React's server build refuses to load, so the codec
19
+ * functions throw and a "use cache" call runs uncached. Any other load failure
20
+ * (a moved vendored path, a missing peer) throws. The action entries stay
21
+ * inert either way: no primitive decodes an action request.
22
+ */
23
+
24
+ /** React's server build throws this on load without the react-server condition. */
25
+ const MISSING_REACT_SERVER_CONDITION =
26
+ /"react-server" condition must be enabled/;
27
+
28
+ async function loadFlightRuntime() {
29
+ // The server build first, alone. `@vitejs/plugin-rsc/core/rsc` statically
30
+ // imports it, and importing that ESM module after the CJS build failed to
31
+ // load leaks an unhandled rejection from Node's module loader even when every
32
+ // import() is caught (Node 22 and 24). So core/rsc loads only once the server
33
+ // build has.
34
+ const server =
35
+ await import("@vitejs/plugin-rsc/vendor/react-server-dom/server.edge").catch(
36
+ (error: unknown) => {
37
+ if (
38
+ error instanceof Error &&
39
+ MISSING_REACT_SERVER_CONDITION.test(error.message)
40
+ ) {
41
+ return undefined;
42
+ }
43
+ throw error;
44
+ },
45
+ );
46
+ if (!server) return undefined;
47
+ const [client, core] = await Promise.all([
48
+ import("@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"),
49
+ import("@vitejs/plugin-rsc/core/rsc"),
50
+ ]);
51
+ await import("../internal/flight-client-globals.js");
52
+ // Resolves the `$$decode-client:` / `$$preserve:` ids a cached value's client
53
+ // and server references decode to; any other id would load a server module.
54
+ core.setRequireModule({
55
+ load: (id) => {
56
+ throw new Error(
57
+ `plugin-rsc stub: server reference module "${id}" is not loadable in a test`,
58
+ );
59
+ },
60
+ });
61
+ return { server, client, core };
62
+ }
63
+
64
+ const runtime = await loadFlightRuntime();
65
+
66
+ /** `<module id>#<export name>` -> manifest entry, as plugin-rsc builds it. */
67
+ function referenceManifest(prefix: string): Record<string, unknown> {
68
+ return new Proxy(
69
+ {},
70
+ {
71
+ get(_target, $$id) {
72
+ if (typeof $$id !== "string") return undefined;
73
+ const [id, name] = $$id.split("#");
74
+ return { id: prefix + id, name, chunks: [], async: true };
75
+ },
76
+ },
77
+ );
78
+ }
79
+
80
+ const CLIENT_MANIFEST = referenceManifest("");
81
+
82
+ function unavailable(name: string): never {
83
+ throw new Error(
84
+ `plugin-rsc stub: ${name} needs the react-server condition (the Flight test project)`,
85
+ );
86
+ }
87
+
88
+ export const renderToReadableStream = (
89
+ value: unknown,
90
+ options?: object,
91
+ ): ReadableStream<Uint8Array> =>
92
+ runtime
93
+ ? runtime.server.renderToReadableStream(value, CLIENT_MANIFEST, options)
94
+ : unavailable("renderToReadableStream");
95
+ export const createTemporaryReferenceSet = (): unknown =>
96
+ runtime ? runtime.server.createTemporaryReferenceSet() : {};
97
+ export const createFromReadableStream = (
98
+ stream: ReadableStream<Uint8Array>,
99
+ options?: object,
100
+ extraOptions?: { preserveServerReferences?: boolean },
101
+ ): Promise<unknown> =>
102
+ runtime
103
+ ? runtime.client.createFromReadableStream(stream, {
104
+ serverConsumerManifest: {
105
+ serverModuleMap: referenceManifest(
106
+ extraOptions?.preserveServerReferences
107
+ ? "$$server:$$preserve:"
108
+ : "$$server:",
109
+ ),
110
+ moduleMap: runtime.core.createServerDecodeClientManifest(),
111
+ },
112
+ ...options,
113
+ })
114
+ : unavailable("createFromReadableStream");
115
+ export const encodeReply = (
116
+ value: unknown,
117
+ options?: object,
118
+ ): Promise<string | FormData> =>
119
+ runtime
120
+ ? runtime.client.encodeReply(value, options)
121
+ : unavailable("encodeReply");
122
+ export const createClientTemporaryReferenceSet = (): unknown =>
123
+ runtime ? runtime.client.createTemporaryReferenceSet() : {};
13
124
  export const loadServerAction = (): undefined => undefined;
14
125
  export const decodeReply = (): undefined => undefined;
15
126
  export const decodeAction = (): undefined => undefined;
16
127
  export const decodeFormState = (): undefined => undefined;
17
- export const createTemporaryReferenceSet = (): Record<string, never> => ({});
18
- export const encodeReply = (): never => {
19
- throw new Error("plugin-rsc stub: encodeReply not available");
20
- };
21
- export const createClientTemporaryReferenceSet = (): Record<
22
- string,
23
- never
24
- > => ({});
@@ -78,6 +78,10 @@
78
78
 
79
79
  import { createRequire } from "node:module";
80
80
  import { fileURLToPath, pathToFileURL } from "node:url";
81
+ import {
82
+ isUseCacheCandidate,
83
+ transformUseCache,
84
+ } from "../vite/plugins/use-cache-transform.js";
81
85
 
82
86
  /** A single Vite/Vitest resolve alias entry. Structurally a Vite `Alias`. */
83
87
  export interface TestAlias {
@@ -262,6 +266,7 @@ interface FlightTransformPlugin {
262
266
  * import { defineConfig } from "vitest/config";
263
267
  * import {
264
268
  * rangoUseClientTransform,
269
+ * rangoUseCacheTransform,
265
270
  * rangoTestAliases,
266
271
  * rangoInlineDeps,
267
272
  * } from "@rangojs/router/testing/vitest";
@@ -271,7 +276,7 @@ interface FlightTransformPlugin {
271
276
  * process.env.NODE_ENV = "production";
272
277
  *
273
278
  * export default defineConfig({
274
- * plugins: [rangoUseClientTransform()],
279
+ * plugins: [rangoUseClientTransform(), rangoUseCacheTransform()],
275
280
  * resolve: {
276
281
  * conditions: ["react-server"],
277
282
  * // Bare `@rangojs/router` -> its react-server build, so a handler/component
@@ -340,3 +345,59 @@ export function rangoUseClientTransform(): FlightTransformPlugin {
340
345
  },
341
346
  };
342
347
  }
348
+
349
+ /** The Vite plugin shape {@link rangoUseCacheTransform} returns. */
350
+ interface UseCacheTransformPlugin {
351
+ name: string;
352
+ enforce: "post";
353
+ configResolved(config: { root: string }): void;
354
+ transform(
355
+ this: { warn(message: string): void },
356
+ code: string,
357
+ id: string,
358
+ ): Promise<{ code: string; map: unknown } | undefined>;
359
+ }
360
+
361
+ /**
362
+ * A Vite plugin that applies the rango plugin's `"use cache"` transform in a
363
+ * Vitest project, so a function written with the directive is wrapped with
364
+ * `registerCachedFunction` as in dev and a build. `rango()` runs that transform
365
+ * only in its `rsc` environment and Vitest transforms in `ssr`, so without this
366
+ * plugin the directive is an inert string and the function runs on every call.
367
+ *
368
+ * Add it to the react-server project next to {@link rangoUseClientTransform}
369
+ * (see that function's config). There, with a seeded `cacheStore`,
370
+ * `renderHandler` / `runLoader` observe real hits. In the node project the
371
+ * wrap applies too, but the stubbed Flight serializer cannot write an entry.
372
+ *
373
+ * Ids are the ones `vite dev` emits: `<path relative to the Vitest root>#<name>`
374
+ * (a function-level directive gives `src/data.ts#$$hoist_0_getData`), not the
375
+ * build's hashed path, so store keys stay readable. `configResolved` sets that
376
+ * root. If `@vitejs/plugin-rsc/transforms` fails to load, the transform throws
377
+ * instead of leaving the module unwrapped (which the rango plugin does).
378
+ */
379
+ export function rangoUseCacheTransform(): UseCacheTransformPlugin {
380
+ let root = process.cwd();
381
+ return {
382
+ name: "rango:testing-use-cache",
383
+ enforce: "post",
384
+ configResolved(config) {
385
+ root = config.root;
386
+ },
387
+ async transform(code, id) {
388
+ if (!isUseCacheCandidate(code, id)) return undefined;
389
+ return transformUseCache(code, id, {
390
+ root,
391
+ isBuild: false,
392
+ warn: (message) => this.warn(message),
393
+ onTransformsError: (error) => {
394
+ throw new Error(
395
+ `rangoUseCacheTransform: @vitejs/plugin-rsc/transforms failed to ` +
396
+ `load, so "use cache" functions in ${id} would run unwrapped.`,
397
+ { cause: error },
398
+ );
399
+ },
400
+ });
401
+ },
402
+ };
403
+ }
@@ -133,12 +133,13 @@ export function ThemeProvider({
133
133
  setSystemTheme(getSystemTheme());
134
134
  // Re-sync state from an EXPLICITLY stored theme after mount. initialTheme
135
135
  // comes from the payload and can legitimately differ from the visitor's
136
- // stored theme — on a PPR shell HIT it is deliberately the CAPTURE's theme
137
- // (the resume tree must match the frozen prelude; see
138
- // ShellCacheEntry.initialTheme). The FOUC script already applied the stored
139
- // theme to the document pre-paint; this brings the provider state (toggles,
140
- // useTheme readers) in line with it, and is the ONLY place the provider
141
- // reads storage (the initializer must not — see the parity note above).
136
+ // stored theme — on a PPR shell HIT it is deliberately the CAPTURE's
137
+ // initialTheme, the no-cookie default (the resume tree must match the
138
+ // frozen prelude; see ShellCacheEntry.initialTheme). The FOUC script
139
+ // already applied the stored theme to the document pre-paint; this brings
140
+ // the provider state (toggles, useTheme readers) in line with it, and is
141
+ // the ONLY place the provider reads storage (the initializer must not —
142
+ // see the parity note above).
142
143
  // Only an explicit cookie/localStorage value re-syncs — a defaultTheme
143
144
  // fallback must not override a server-provided initialTheme when the
144
145
  // visitor never chose a theme.
@@ -8,14 +8,14 @@
8
8
  *
9
9
  * Must be placed in the <head> element of your document, before any stylesheets.
10
10
  *
11
- * Note: when theme is enabled in the router config, `<MetaTags />` ALREADY
11
+ * Note: when theme is enabled in the router config, `<Html.Meta />` ALREADY
12
12
  * renders this FOUC script. Use `<ThemeScript />` only if you do NOT render
13
- * `<MetaTags />`. Rendering both is safe — the inline script guards the
13
+ * `<Html.Meta />`. Rendering both is safe — the inline script guards the
14
14
  * matchMedia listener registration against double-running — but it is redundant.
15
15
  *
16
16
  * @example
17
17
  * ```tsx
18
- * // In your document component. Use ThemeScript only when you do not render MetaTags.
18
+ * // In your document component. Use ThemeScript only when you do not render Html.Meta.
19
19
  * import { ThemeScript } from "@rangojs/router/theme";
20
20
  *
21
21
  * export function Document({ children }) {
@@ -1,12 +1,13 @@
1
1
  /**
2
- * Context passed to cache condition/key/tags functions.
2
+ * Context passed to cache condition/key/tags functions (the full
3
+ * RequestContext).
3
4
  *
4
- * This is a subset of RequestContext that's guaranteed to be available
5
- * during cache key generation (before middleware runs).
6
- *
7
- * Note: While the full RequestContext is passed, middleware-set variables
8
- * read via `ctx.get()` may not be populated yet since cache lookup happens
9
- * before middleware execution.
5
+ * Ordering: global middleware wraps the whole request, and on RSC routes route
6
+ * middleware wraps the match/render pipeline (rsc/handler.ts executeRender), so
7
+ * variables they set before `next()` are readable via `ctx.get()`. On response
8
+ * routes the cache lookup runs BEFORE route middleware, which wraps only the
9
+ * handler run on a miss (rsc/response-route-handler.ts); only global-middleware
10
+ * variables are populated there.
10
11
  */
11
12
  export type { RequestContext as CacheContext } from "../server/request-context.js";
12
13
 
@@ -109,7 +110,8 @@ export interface CacheOptions<TEnv = unknown> {
109
110
  * Return false to skip cache for this request (always fetch fresh).
110
111
  *
111
112
  * Has access to full RequestContext including env, request, params, cookies, etc.
112
- * Note: Middleware-set variables read via `ctx.get()` may not be populated yet.
113
+ * Runs after global and route middleware on RSC routes; on response routes,
114
+ * before route middleware (see CacheContext).
113
115
  *
114
116
  * @example
115
117
  * ```typescript
@@ -131,7 +133,8 @@ export interface CacheOptions<TEnv = unknown> {
131
133
  * Bypasses default key generation AND store's keyGenerator.
132
134
  *
133
135
  * Has access to full RequestContext including env, request, params, cookies, etc.
134
- * Note: Middleware-set variables read via `ctx.get()` may not be populated yet.
136
+ * Runs after global and route middleware on RSC routes; on response routes,
137
+ * before route middleware (see CacheContext).
135
138
  *
136
139
  * @example
137
140
  * ```typescript
@@ -153,7 +156,8 @@ export interface CacheOptions<TEnv = unknown> {
153
156
  * Tags for cache invalidation.
154
157
  * Can be a static array or a function that returns tags.
155
158
  *
156
- * The built-in `MemorySegmentCacheStore` and `CFCacheStore` index by tag.
159
+ * The built-in `MemorySegmentCacheStore`, `CFCacheStore`, and
160
+ * `VercelCacheStore` index by tag.
157
161
  * Invalidate on demand with `updateTag(...tags)` (awaitable, read-your-own-writes;
158
162
  * for server actions) or `revalidateTag(...tags)` (background hard-purge, not
159
163
  * awaited; for route handlers / webhooks). For `CFCacheStore`, distributed
@@ -342,6 +342,12 @@ export type HandlerContext<
342
342
  * Current theme (from cookie or default).
343
343
  * Only available when theme is enabled in router config.
344
344
  *
345
+ * The visitor's cookie, so it is guarded like `cookies()`: reading it on a
346
+ * `ppr` route refuses the shell capture, and inside a `cache()` boundary or
347
+ * a `"use cache"` function it throws (#971). Read it with `useTheme()` in a
348
+ * client component, or in a live loader, on those routes. A read-only,
349
+ * non-enumerable getter: `{ ...ctx }` does not carry it.
350
+ *
345
351
  * @example
346
352
  * ```typescript
347
353
  * route("settings", (ctx) => {
@@ -350,7 +356,7 @@ export type HandlerContext<
350
356
  * });
351
357
  * ```
352
358
  */
353
- theme?: Theme;
359
+ readonly theme?: Theme;
354
360
  /**
355
361
  * Set the theme (only available when theme is enabled in router config).
356
362
  * Sets a cookie with the new theme value.
@@ -455,6 +461,27 @@ export type InternalHandlerContext<
455
461
  * table (so a loader cannot await its own in-flight memoized promise).
456
462
  */
457
463
  _loaderCacheOriginalUse?: (item: any) => any;
464
+ /**
465
+ * @internal A loader-cache HIT asks before replaying a loader's recorded
466
+ * pushes: true when that loader has not run in this request and no other
467
+ * replay delivered it; its later run then replaces the replayed values
468
+ * (HandleStore.pushReplayed). One copy per loader per request. See
469
+ * setupLoaderAccess.
470
+ */
471
+ _claimLoaderPushes?: (loaderId: string) => boolean;
472
+ /**
473
+ * @internal Run a loader on a fresh executor (own memo map): a loader-cache
474
+ * stale refresh, whose diverted pushes must not take a dependency's run
475
+ * from the page. See setupLoaderAccess.
476
+ */
477
+ _runLoaderIsolated?: (loader: any) => Promise<any>;
478
+ /**
479
+ * @internal A loader's cache() binding started with these cache() tags: an
480
+ * execution of that loader outside the binding (a reader started it first,
481
+ * or a stale refresh runs it) answers for them too (#964). See
482
+ * setupLoaderAccess.
483
+ */
484
+ _bindLoaderCacheTags?: (loaderId: string, tags: Set<string>) => void;
458
485
  };
459
486
 
460
487
  /**
@@ -28,9 +28,10 @@ import type { RequestScope } from "./request-scope.js";
28
28
  * return await db.cart.get(user.id);
29
29
  * });
30
30
  *
31
- * // With typed params:
32
- * const ProductLoader = createLoader<Product, { slug: string }>(async (ctx) => {
33
- * const { slug } = ctx.params; // slug is typed as string
31
+ * // createLoader's one type parameter is the data type; params are
32
+ * // Record<string, string | undefined>:
33
+ * const ProductLoader = createLoader<Product>(async (ctx) => {
34
+ * const { slug } = ctx.params; // string | undefined
34
35
  * return await db.products.findBySlug(slug);
35
36
  * });
36
37
  * ```
@@ -243,9 +244,10 @@ export type LoadOptions =
243
244
  * return await db.cart.get(ctx.get("user").id);
244
245
  * });
245
246
  *
246
- * // With typed params:
247
- * export const ProductLoader = createLoader<Product, { slug: string }>(async (ctx) => {
248
- * const { slug } = ctx.params; // slug is typed as string
247
+ * // createLoader's one type parameter is the data type; params are
248
+ * // Record<string, string | undefined>:
249
+ * export const ProductLoader = createLoader<Product>(async (ctx) => {
250
+ * const { slug } = ctx.params; // string | undefined
249
251
  * return await db.products.findBySlug(slug);
250
252
  * });
251
253
  *
@@ -59,30 +59,34 @@ export interface PartialPrerenderProps {
59
59
  /**
60
60
  * Operational tags attached to the captured shell entry for
61
61
  * `updateTag()`/`revalidateTag()`-driven eviction. UNIONED with the tags the
62
- * capture render auto-collects (the shell's own non-loader request tags).
62
+ * capture collects: its doc record's tags (what the handler layer and the
63
+ * server components it rendered recorded) and its bake-lane loaders'.
63
64
  */
64
65
  tags?: string[];
65
66
  /**
66
- * Upper bound (serialized UTF-8 bytes) on the capture data snapshot riding
67
- * inside the shell entry. The snapshot duplicates every cache-store value
68
- * the capture pinned, so a page over a large cache() segment can push the
69
- * entry toward store value limits (Cloudflare KV caps a value at 25 MiB).
70
- * Over the cap the snapshot is skipped: the shell is still stored and
71
- * served, but pinned reads fall back to the live store, so drifted cached
72
- * content can hydration-mismatch and be repaired client-side (the
73
- * pre-snapshot behavior). Reported once per key. Defaults to 8 MiB.
67
+ * Upper bound (serialized UTF-8 bytes) on the loader pins in the capture
68
+ * data snapshot: the loader records and the cache values loaders read,
69
+ * duplicated into the shell entry. The doc record (the handler layer every
70
+ * HIT replays) is exempt. Over the cap the pins are dropped: the shell is
71
+ * still stored and every HIT still replays the handler layer, but the
72
+ * pinned loaders read the live store, so drifted cached content can
73
+ * hydration-mismatch and be repaired client-side. Reported once per key.
74
+ * Defaults to 8 MiB. The whole entry is bounded separately by the store's
75
+ * value limit (Cloudflare KV: 25 MiB); an entry over it is refused.
74
76
  */
75
77
  maxSnapshotBytes?: number;
76
78
  /**
77
- * Capture settle budget in MILLISECONDS (default 15000). Bounds the whole
78
- * background capture: the wait for deferred shell material — top-level
79
- * pushed handle promises (`ctx.use(Meta)(promise.then(...))` and friends)
80
- * are AWAITED and their settled values baked into the stored shell — AND
81
- * the fizz prerender deadline. Declare it to tighten the budget below the
82
- * default or when a route's shell material takes longer than 15s to
83
- * settle. A budget that expires with pushes still pending REFUSES the
84
- * capture (the route stays MISS with the once-per-key warning) — a shell
85
- * with missing head material is never stored. Capture is background work
79
+ * Capture settle budget in MILLISECONDS (default 15000). One deadline for
80
+ * the whole background capture: the match (the handlers, and the loaders
81
+ * they await), the wait for handler output (handle pushes and the promises
82
+ * nested in them, bake-lane loaders, and the doc record's encode, which
83
+ * awaits handler promises and async server components), AND the fizz
84
+ * prerender. Everything a handler produces is
85
+ * awaited and baked into the stored shell. Declare it to tighten the
86
+ * budget below the default or when a route's shell material takes longer
87
+ * than 15s to settle. A budget that expires before the doc record is
88
+ * written REFUSES the capture (the route stays MISS with the once-per-key
89
+ * warning): a shell with missing handler output is never stored. Capture is background work
86
90
  * (waitUntil), so a longer budget costs latency-to-HIT only, never a served
87
91
  * response; the platform waitUntil lifetime (workerd: ~30s past response
88
92
  * completion) is the physical ceiling — the default's derivation lives in
@@ -1,5 +1,5 @@
1
1
  import type { ContextVar } from "../context-var.js";
2
- import type { ReverseFunction } from "../reverse.js";
2
+ import type { GlobalReverseFunction } from "../reverse.js";
3
3
  import type {
4
4
  DefaultReverseRouteMap,
5
5
  DefaultVars,
@@ -7,27 +7,6 @@ import type {
7
7
  import type { UseItems, ResponseRouteUseItem } from "../route-types.js";
8
8
  import type { RequestScope } from "../types/request-scope.js";
9
9
 
10
- /**
11
- * Reverse function for response handler contexts.
12
- * Global names get autocomplete and param validation from the generated route map.
13
- * Local `.name` calls are accepted but not validated (scope unknown at type level).
14
- */
15
- type ResponseReverseFunction = [DefaultReverseRouteMap] extends [
16
- Record<string, string>,
17
- ]
18
- ? (
19
- name: string,
20
- params?: Record<string, string>,
21
- search?: Record<string, unknown>,
22
- ) => string
23
- : ReverseFunction<DefaultReverseRouteMap> & {
24
- (
25
- name: `.${string}`,
26
- params?: Record<string, string>,
27
- search?: Record<string, unknown>,
28
- ): string;
29
- };
30
-
31
10
  /**
32
11
  * Symbol marking a route as a response route (non-RSC).
33
12
  * Stored on PathOptions and UrlPatterns to signal the trie to short-circuit.
@@ -99,7 +78,8 @@ export interface ResponseHandlerContext<
99
78
  params: TParams;
100
79
  /** @internal Phantom property for params type invariance. Prevents mounting handlers on wrong routes. */
101
80
  readonly _paramCheck?: (params: TParams) => TParams;
102
- reverse: ResponseReverseFunction;
81
+ /** Global route names only; see {@link GlobalReverseFunction}. */
82
+ reverse: GlobalReverseFunction<DefaultReverseRouteMap>;
103
83
  /** Read a variable set by middleware via ctx.set(key, value) or ctx.set(ContextVar, value). */
104
84
  get: {
105
85
  <T>(contextVar: ContextVar<T>): T | undefined;
@@ -117,6 +117,14 @@ export type LoadFunction<T> = (options?: LoadOptions) => Promise<T>;
117
117
 
118
118
  export interface UseLoaderResult<T> {
119
119
  data: T;
120
+ /**
121
+ * True while this loader family (`loader.$$id`) is in flight: a `load()` /
122
+ * group refresh, or a held navigation still streaming a re-run of this
123
+ * loader. Family-keyed, not segment-keyed: a layout `useLoader` of the same
124
+ * `createLoader` the child route is re-running also reports true, even if
125
+ * the layout copy is not replaced. Not used for the initial streamed read
126
+ * (that suspends).
127
+ */
120
128
  isLoading: boolean;
121
129
  error: Error | null;
122
130
  load: LoadFunction<T>;
@@ -125,6 +133,7 @@ export interface UseLoaderResult<T> {
125
133
 
126
134
  export interface UseFetchLoaderResult<T> {
127
135
  data: T | undefined;
136
+ /** Same family-keyed contract as {@link UseLoaderResult.isLoading}. */
128
137
  isLoading: boolean;
129
138
  error: Error | null;
130
139
  load: LoadFunction<T>;
@@ -560,7 +569,7 @@ function useLoaderInternal<T>(
560
569
  * @example Basic usage - accessing route loader data
561
570
  * ```tsx
562
571
  * "use client";
563
- * import { useLoader } from "rsc-router/client";
572
+ * import { useLoader } from "@rangojs/router/client";
564
573
  * import { CartLoader } from "../loaders/cart";
565
574
  *
566
575
  * // In route definition: loader(CartLoader)
@@ -608,7 +617,7 @@ export function useLoader<T>(
608
617
  * @example On-demand fetching
609
618
  * ```tsx
610
619
  * "use client";
611
- * import { useFetchLoader } from "rsc-router/client";
620
+ * import { useFetchLoader } from "@rangojs/router/client";
612
621
  * import { SearchLoader } from "../loaders/search";
613
622
  *
614
623
  * export function SearchResults() {
@@ -670,7 +679,7 @@ export function useFetchLoader<T>(
670
679
  * @example
671
680
  * ```tsx
672
681
  * "use client";
673
- * import { useLoader, useRefreshLoaders } from "rsc-router/client";
682
+ * import { useLoader, useRefreshLoaders } from "@rangojs/router/client";
674
683
  *
675
684
  * function Profile() {
676
685
  * const { data } = useLoader(ProfileLoader, { key: userId, refreshGroup: "account" });
@@ -64,6 +64,14 @@ export async function runShellPrerenderPhase(
64
64
  const startTotal = performance.now();
65
65
  console.log(`[rango] Shell-prerendering ${candidates.length} URL(s)...`);
66
66
 
67
+ // Hold the process open for the phase. The capture's waits are unref'd
68
+ // timers (retry delay, Flight quiesce hops) so a runtime capture never keeps
69
+ // Node alive; every bundle is already written and the build temp server has
70
+ // no file watcher (#947). Its only remaining handle is the HMR WebSocket on
71
+ // 24678, which fails to bind silently when another Vite server holds the
72
+ // port; `vite build` then exited 0 mid-phase without writing
73
+ // __shell-manifest.js.
74
+ const keepAlive = setInterval(() => {}, 60_000);
67
75
  let restoreClientRequire: (() => void) | undefined;
68
76
  try {
69
77
  // Runner access needs the RunnableDevEnvironment surface; the environments
@@ -343,6 +351,7 @@ export async function runShellPrerenderPhase(
343
351
  // buildApp post hook's finally on success, buildEnd on an aborted build.
344
352
  delete (globalThis as any).__loadPrerenderManifestModule;
345
353
  restoreClientRequire?.();
354
+ clearInterval(keepAlive);
346
355
  }
347
356
  }
348
357