@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,6 +1,233 @@
1
1
  // src/testing/vitest.ts
2
2
  import { createRequire } from "node:module";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
+
5
+ // src/vite/plugins/use-cache-transform.ts
6
+ import path2 from "node:path";
7
+
8
+ // src/vite/plugins/expose-id-utils.ts
9
+ import path from "node:path";
10
+ import crypto from "node:crypto";
11
+ function normalizePath(p) {
12
+ return p.split(path.sep).join("/");
13
+ }
14
+ function hashId(filePath, exportName) {
15
+ const input = `${filePath}#${exportName}`;
16
+ const hash = crypto.createHash("sha256").update(input).digest("hex");
17
+ return `${hash.slice(0, 8)}#${exportName}`;
18
+ }
19
+
20
+ // src/vite/plugins/use-cache-transform.ts
21
+ var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
22
+ var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
23
+ var USE_CACHE_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
24
+ function isUseCacheCandidate(code, id) {
25
+ return (
26
+ // Quick bail: no "use cache" in source
27
+ code.includes("use cache") && // Skip node_modules and virtual modules
28
+ !id.includes("/node_modules/") && !id.startsWith("\0") && // Only JS/TS files
29
+ /\.(tsx?|jsx?|mjs)$/.test(id)
30
+ );
31
+ }
32
+ var rscTransforms = null;
33
+ async function transformUseCache(code, id, options) {
34
+ if (!rscTransforms) {
35
+ try {
36
+ rscTransforms = await import("@vitejs/plugin-rsc/transforms");
37
+ } catch (error) {
38
+ options.onTransformsError?.(error);
39
+ return;
40
+ }
41
+ }
42
+ const { hasDirective, transformWrapExport, transformHoistInlineDirective } = rscTransforms;
43
+ let ast;
44
+ try {
45
+ const { parseAst } = await import("vite");
46
+ ast = parseAst(code, { lang: "tsx" });
47
+ } catch {
48
+ return;
49
+ }
50
+ stripNullDirectiveFields(ast);
51
+ const filePath = normalizePath(path2.relative(options.root, id));
52
+ const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
53
+ if (hasDirective(ast.body, "use cache")) {
54
+ return transformFileLevelUseCache(
55
+ code,
56
+ ast,
57
+ filePath,
58
+ id,
59
+ options.isBuild,
60
+ isLayoutOrTemplate,
61
+ transformWrapExport,
62
+ hasDirective
63
+ );
64
+ }
65
+ const functionResult = transformFunctionLevelUseCache(
66
+ code,
67
+ ast,
68
+ filePath,
69
+ id,
70
+ options.isBuild,
71
+ transformHoistInlineDirective
72
+ );
73
+ warnOnNearMissDirectives(ast, id, options.warn);
74
+ if (functionResult) return functionResult;
75
+ }
76
+ async function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLayoutOrTemplate, transformWrapExport, hasDirective) {
77
+ const unconfirmedExports = [];
78
+ const { exportNames, output } = transformWrapExport(code, ast, {
79
+ runtime: (value, name) => {
80
+ const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
81
+ return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, "default")`;
82
+ },
83
+ rejectNonAsyncFunction: false,
84
+ filter: (name, meta) => {
85
+ if (name === "default" && isLayoutOrTemplate) return false;
86
+ if (name.startsWith("$$hoist_")) return false;
87
+ if (isHoistedServerReferenceRebind(meta.valueNode)) return false;
88
+ if (functionHasUseServerDirective(meta.valueNode, hasDirective)) {
89
+ return false;
90
+ }
91
+ if (meta.isFunction !== true) {
92
+ unconfirmedExports.push(name);
93
+ return false;
94
+ }
95
+ return true;
96
+ }
97
+ });
98
+ if (unconfirmedExports.length > 0) {
99
+ const plural = unconfirmedExports.length > 1;
100
+ throw new Error(
101
+ `[rango:use-cache] File-level "use cache" in ${sourceId} only wraps exports that are statically-confirmed functions. ${plural ? "These exports are" : "This export is"} not: ${unconfirmedExports.map((n) => `"${n}"`).join(", ")}. Declare them directly (export async function foo() {} or export const foo = async () => {}). A factory or otherwise statically-indeterminate initializer (export const foo = makeCached(fn)) is rejected even if it returns a function at runtime -- rewrite it as a direct async function, or move non-function exports to a separate module.`
102
+ );
103
+ }
104
+ if (exportNames.length === 0) {
105
+ const { default: MagicString } = await import("magic-string");
106
+ const s = new MagicString(code);
107
+ const directive2 = findFileLevelDirective(ast);
108
+ if (directive2) {
109
+ s.overwrite(
110
+ directive2.start,
111
+ directive2.end,
112
+ `/* "use cache" -- wrapped by rango */`
113
+ );
114
+ return {
115
+ code: s.toString(),
116
+ map: s.generateMap({ source: sourceId, hires: "boundary" })
117
+ };
118
+ }
119
+ return;
120
+ }
121
+ output.prepend(
122
+ `import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};
123
+ `
124
+ );
125
+ const directive = findFileLevelDirective(ast);
126
+ if (directive) {
127
+ output.overwrite(
128
+ directive.start,
129
+ directive.end,
130
+ `/* "use cache" -- wrapped by rango */`
131
+ );
132
+ }
133
+ return {
134
+ code: output.toString(),
135
+ map: output.generateMap({ source: sourceId, hires: "boundary" })
136
+ };
137
+ }
138
+ function transformFunctionLevelUseCache(code, ast, filePath, sourceId, isBuild, transformHoistInlineDirective) {
139
+ try {
140
+ const { output, names } = transformHoistInlineDirective(code, ast, {
141
+ directive: USE_CACHE_DIRECTIVE_RE,
142
+ runtime: (value, name, meta) => {
143
+ const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
144
+ const profileMatch = meta.directiveMatch[1];
145
+ const profileName = profileMatch ? profileMatch.replace(/^:\s*/, "").trim() : "default";
146
+ return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, ${JSON.stringify(profileName)})`;
147
+ },
148
+ rejectNonAsyncFunction: false
149
+ });
150
+ if (names.length === 0) return;
151
+ output.prepend(
152
+ `import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};
153
+ `
154
+ );
155
+ return {
156
+ code: output.toString(),
157
+ map: output.generateMap({ source: sourceId, hires: "boundary" })
158
+ };
159
+ } catch {
160
+ return;
161
+ }
162
+ }
163
+ function stripNullDirectiveFields(node) {
164
+ if (!node || typeof node !== "object") return;
165
+ const rec = node;
166
+ if (rec.type === "ExpressionStatement" && typeof rec.directive !== "string") {
167
+ delete rec.directive;
168
+ }
169
+ for (const value of Object.values(rec)) {
170
+ if (Array.isArray(value)) {
171
+ for (const item of value) stripNullDirectiveFields(item);
172
+ } else if (value && typeof value === "object" && "type" in value) {
173
+ stripNullDirectiveFields(value);
174
+ }
175
+ }
176
+ }
177
+ function isHoistedServerReferenceRebind(valueNode) {
178
+ if (!valueNode || valueNode.type !== "CallExpression") return false;
179
+ const first = valueNode.arguments[0];
180
+ return first !== void 0 && first.type === "Identifier" && first.name.startsWith("$$hoist_");
181
+ }
182
+ function functionHasUseServerDirective(valueNode, hasDirective) {
183
+ if (!valueNode || !("body" in valueNode)) return false;
184
+ const { body } = valueNode;
185
+ if (!body || Array.isArray(body) || body.type !== "BlockStatement") {
186
+ return false;
187
+ }
188
+ return hasDirective(
189
+ body.body,
190
+ "use server"
191
+ );
192
+ }
193
+ function findFileLevelDirective(ast) {
194
+ for (const node of ast.body ?? []) {
195
+ if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string" && node.expression.value.startsWith("use cache")) {
196
+ return { start: node.start, end: node.end };
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ var NEAR_MISS_RE = /^use cache:\s*.+$/;
202
+ function warnOnNearMissDirectives(ast, fileId, warn) {
203
+ const visit = (node) => {
204
+ if (!node || typeof node !== "object") return;
205
+ if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
206
+ const value = node.expression.value;
207
+ if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !USE_CACHE_DIRECTIVE_RE.test(value)) {
208
+ const profilePart = value.slice("use cache:".length).trim();
209
+ warn(
210
+ `[rango:use-cache] "${value}" in ${fileId} has an invalid profile name "${profilePart}". Profile names must match [a-zA-Z0-9_-]+. This directive will be ignored.`
211
+ );
212
+ }
213
+ }
214
+ for (const key of Object.keys(node)) {
215
+ const child = node[key];
216
+ if (Array.isArray(child)) {
217
+ for (const item of child) {
218
+ visit(item);
219
+ }
220
+ } else if (child && typeof child === "object" && child.type) {
221
+ visit(child);
222
+ }
223
+ }
224
+ };
225
+ for (const node of ast.body ?? []) {
226
+ visit(node);
227
+ }
228
+ }
229
+
230
+ // src/testing/vitest.ts
4
231
  function here(relativeFromRoot) {
5
232
  return fileURLToPath(new URL(`../../${relativeFromRoot}`, import.meta.url));
6
233
  }
@@ -94,9 +321,34 @@ function rangoUseClientTransform() {
94
321
  }
95
322
  };
96
323
  }
324
+ function rangoUseCacheTransform() {
325
+ let root = process.cwd();
326
+ return {
327
+ name: "rango:testing-use-cache",
328
+ enforce: "post",
329
+ configResolved(config) {
330
+ root = config.root;
331
+ },
332
+ async transform(code, id) {
333
+ if (!isUseCacheCandidate(code, id)) return void 0;
334
+ return transformUseCache(code, id, {
335
+ root,
336
+ isBuild: false,
337
+ warn: (message) => this.warn(message),
338
+ onTransformsError: (error) => {
339
+ throw new Error(
340
+ `rangoUseCacheTransform: @vitejs/plugin-rsc/transforms failed to load, so "use cache" functions in ${id} would run unwrapped.`,
341
+ { cause: error }
342
+ );
343
+ }
344
+ });
345
+ }
346
+ };
347
+ }
97
348
  export {
98
349
  rangoInlineDeps,
99
350
  rangoTestAliases,
100
351
  rangoTestConfig,
352
+ rangoUseCacheTransform,
101
353
  rangoUseClientTransform
102
354
  };
@@ -12,6 +12,12 @@
12
12
  */
13
13
  /** Default prefix when `stateCookiePrefix` is unset or empty after sanitization. */
14
14
  export declare const DEFAULT_STATE_COOKIE_PREFIX = "rango-state";
15
+ /**
16
+ * The fresh-reads cookie name for a router's resolved state cookie name
17
+ * (`{prefix}_{routerId}`; the prefix never contains `_`, see
18
+ * router/state-cookie-name.ts).
19
+ */
20
+ export declare function freshReadsCookieName(stateCookieName: string): string;
15
21
  /** Internal response header carrying the keepClientCache() directive. */
16
22
  export declare const KEEP_CACHE_HEADER = "x-rango-keep-cache";
17
23
  /** True for a per-client signal header. */
@@ -18,6 +18,22 @@ export declare function buildHistoryState(userState: unknown, routerState?: {
18
18
  * relying on the Navigation API.
19
19
  */
20
20
  export declare function pushHistoryWithIdx(state: Record<string, unknown> | null, url: string, replace: boolean): void;
21
+ /**
22
+ * The forced-MISS reload marker (`SHELL_MISS_PARAM` in rsc/shell-serve.ts,
23
+ * which the browser bundle must not import): a degraded PPR shell HIT
24
+ * reloads the page with it, and the server renders a marked request like a
25
+ * cache miss.
26
+ */
27
+ export declare const SHELL_MISS_MARKER: string;
28
+ /**
29
+ * Drop the forced-MISS marker from the address bar at boot, keeping the
30
+ * history entry's state (initBrowserApp calls it before any location read).
31
+ * Left in place, a refresh or a shared link would stay off the shell and the
32
+ * app's search params would carry it. The server strips the same marker from
33
+ * the request before rendering (shell-serve.ts withoutShellMissMarker), so
34
+ * the page was rendered for the clean URL and the hydrating client agrees.
35
+ */
36
+ export declare function stripShellMissMarker(): void;
21
37
  /**
22
38
  * Merge server-set location state into the current history entry.
23
39
  * Replaces the current history state and dispatches notification event
@@ -22,6 +22,12 @@ interface CommitOptions {
22
22
  cacheOnly?: boolean;
23
23
  /** Server-set location state to merge into history.pushState */
24
24
  serverState?: Record<string, unknown>;
25
+ /**
26
+ * Back/forward: history is already at this entry. Its state (scroll key,
27
+ * location state, idx) stays as the browser restored it; serverState is
28
+ * merged in. The page left was saved by handleTraversalStart.
29
+ */
30
+ traversal?: boolean;
25
31
  }
26
32
  /**
27
33
  * Options that can override the pre-configured commit settings
@@ -2,6 +2,17 @@ import type { NavigationStore, NavigationClient, UpdateSubscriber, ResolvedSegme
2
2
  import type { ReactNode } from "react";
3
3
  import type { RenderSegmentsOptions } from "../segment-system.js";
4
4
  import type { BoundTransaction } from "./navigation-transaction.js";
5
+ import type { NavigationUpdate } from "./types.js";
6
+ export declare function shouldStartViewTransition(segments: ResolvedSegment[]): boolean;
7
+ /**
8
+ * Transition commit. Every held commit announces the loader streams the
9
+ * committed tree is still receiving (loader-store.ts announcePendingStreams)
10
+ * so held useLoader readers pin isLoading:true until this transition lands;
11
+ * settled/cached segments make it a no-op. Urgent commits call onUpdate
12
+ * directly: nothing is held there, a reader of a pending stream suspends to
13
+ * its fallback. Also used by renderRoute's navigate() (testing/render-route.tsx).
14
+ */
15
+ export declare function commitInTransition(onUpdate: UpdateSubscriber, segments: readonly ResolvedSegment[], update: NavigationUpdate, transitionTypes: readonly string[]): void;
5
16
  /**
6
17
  * Configuration for creating a partial updater
7
18
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Props for ScrollRestoration component
2
+ * Props for `<Html.ScrollRestoration />`.
3
3
  */
4
4
  export interface ScrollRestorationProps {
5
5
  /**
@@ -20,10 +20,10 @@ export interface ScrollRestorationProps {
20
20
  * const byPathname = (location) => location.pathname;
21
21
  *
22
22
  * // Restore based on pathname (same URL = same scroll)
23
- * <ScrollRestoration getKey={byPathname} />
23
+ * <Html.ScrollRestoration getKey={byPathname} />
24
24
  *
25
25
  * // Restore based on unique history entry (default)
26
- * // <ScrollRestoration /> — omit getKey to use location.key
26
+ * // <Html.ScrollRestoration /> — omit getKey to use location.key
27
27
  * ```
28
28
  */
29
29
  getKey?: (location: {
@@ -34,7 +34,7 @@ export interface ScrollRestorationProps {
34
34
  }) => string;
35
35
  }
36
36
  /**
37
- * ScrollRestoration component
37
+ * Public as `Html.ScrollRestoration` (`@rangojs/router/client`).
38
38
  *
39
39
  * Enables scroll position restoration across navigations:
40
40
  * - Saves scroll positions to sessionStorage
@@ -51,7 +51,7 @@ export interface ScrollRestorationProps {
51
51
  * return (
52
52
  * <html>
53
53
  * <body>
54
- * <ScrollRestoration />
54
+ * <Html.ScrollRestoration />
55
55
  * {children}
56
56
  * </body>
57
57
  * </html>
@@ -63,7 +63,7 @@ export declare function ScrollRestoration({ getKey }: ScrollRestorationProps): n
63
63
  /**
64
64
  * Hook to initialize scroll restoration
65
65
  *
66
- * Alternative to the ScrollRestoration component for more control.
66
+ * Alternative to `<Html.ScrollRestoration />` for more control.
67
67
  *
68
68
  * @example
69
69
  * ```tsx
@@ -126,7 +126,7 @@ export interface RangoProps {
126
126
  *
127
127
  * @example
128
128
  * ```tsx
129
- * import { initBrowserApp, Rango } from "rsc-router/browser";
129
+ * import { initBrowserApp, Rango } from "@rangojs/router/browser";
130
130
  * import { rscStream } from "rsc-html-stream/client";
131
131
  * import * as rscBrowser from "@vitejs/plugin-rsc/browser";
132
132
  *
@@ -84,6 +84,13 @@ export declare function scrollToTop(): void;
84
84
  * - Ensures new history entry has a key
85
85
  */
86
86
  export declare function handleNavigationStart(): void;
87
+ /**
88
+ * Handle scroll for a back/forward traversal, at popstate before anything
89
+ * renders: save the page being left under ITS key (history has already moved,
90
+ * so saveCurrentScrollPosition would use the destination's key) and track the
91
+ * destination as the displayed entry.
92
+ */
93
+ export declare function handleTraversalStart(): void;
87
94
  /**
88
95
  * Handle scroll after navigation completes.
89
96
  * @param options.restore - If true, restore saved position (for popstate)
@@ -22,10 +22,27 @@
22
22
  * registration below wires assertNotInsideCacheExec to this scope without
23
23
  * taint.ts importing it.
24
24
  */
25
+ /**
26
+ * One "use cache" execution. Identity matters: the execution's handle capture
27
+ * (cache-runtime.ts) records only pushes from its own chain, including cached
28
+ * functions it calls (`parent` links them), not concurrent pushes into the
29
+ * same request store.
30
+ */
31
+ export interface CacheExecScope {
32
+ readonly parent?: CacheExecScope;
33
+ }
25
34
  /**
26
35
  * Run fn with the "use cache" execution scope active. Continuations spawned
27
36
  * from fn's synchronous kickoff inherit the scope; parallel chains do not.
37
+ * Pass `scope` (createCacheExecScope) to test membership with
38
+ * isInCacheExecChain.
28
39
  */
29
- export declare function runWithCacheExecScope<T>(fn: () => T): T;
40
+ export declare function runWithCacheExecScope<T>(fn: () => T, scope?: CacheExecScope): T;
41
+ /** A scope nested in the calling chain's current one, if any. */
42
+ export declare function createCacheExecScope(): CacheExecScope;
30
43
  /** True when the calling async chain is inside a "use cache" body. */
31
44
  export declare function isInsideCacheExecScope(): boolean;
45
+ /** The calling chain's innermost "use cache" scope, if any. */
46
+ export declare function getCacheExecScope(): CacheExecScope | undefined;
47
+ /** True when the calling async chain runs inside `scope`, at any depth. */
48
+ export declare function isInCacheExecChain(scope: CacheExecScope): boolean;
@@ -2,8 +2,8 @@
2
2
  * Shared Cache Key Utilities
3
3
  *
4
4
  * Deterministic normalization of search params and route params
5
- * for cache key generation. Used by cache-runtime, cache-scope,
6
- * document-cache, and loader-cache.
5
+ * for cache key generation, and the composition of nested cache() keys.
6
+ * Used by cache-runtime, cache-scope, document-cache, and loader-cache.
7
7
  */
8
8
  import type { SearchParamsFilter } from "./search-params-filter.js";
9
9
  /**
@@ -41,3 +41,18 @@ export declare function sortedRouteParams(params: Record<string, string> | undef
41
41
  * tier-specific suffixes (`:rsc`/`:html`, segment hash) after this base.
42
42
  */
43
43
  export declare function cacheKeyBase(host: string, pathname: string, searchParams?: URLSearchParams, params?: Record<string, string>, filter?: SearchParamsFilter): string;
44
+ /**
45
+ * The record key, or shell partition, of nested keyed cache() scopes (#970)
46
+ * from its parts, outermost first: the chain's `key()` results and, for an
47
+ * inner scope without its own `key()`, that scope's default key
48
+ * (CacheScope.resolveKeyFrom). One part is returned raw, so a single keyed
49
+ * scope keeps its key byte for byte. Two or more are joined by `|`, each
50
+ * URI-encoded (like partitionShellKey, rsc/shell-capture-constants.ts): an
51
+ * encoded part holds no `|`, so every tuple maps to its own key, of any
52
+ * depth. Raw, `("a|b", "c")` and `("a", "b|c")` named one record. A composed
53
+ * key holds no `:` either, so a single raw result with one (a prefixed
54
+ * `tier:...`, a `doc:` default key) never equals it; an unprefixed raw result
55
+ * can. Shared with the testing helper `shellCacheKey`, which composes a
56
+ * nested partition the same way.
57
+ */
58
+ export declare function composeCacheKeys(parts: readonly string[]): string;
@@ -42,6 +42,23 @@ export declare function resolveTtl(explicit: number | undefined, defaults: Cache
42
42
  * non-finite/negative (degrade rather than feed bad math into expiry).
43
43
  */
44
44
  export declare function resolveSwrWindow(explicit: number | undefined, defaults: CacheDefaults | undefined): number;
45
+ /**
46
+ * Minimum age (ms) of a stale PPR shell before a HIT schedules its recapture.
47
+ * A shell capped to a route cache() record inside its swr window is stale
48
+ * from the start (rsc/shell-capture.ts capShellWindow), so without a floor
49
+ * every HIT of it recaptured, and a capture's store write is one KV write per
50
+ * key: Cloudflare KV accepts one per second per key. Inside the floor the
51
+ * stale shell is still served. Per isolate: each isolate's stale HIT past the
52
+ * floor can schedule one (stampede-guarded per isolate).
53
+ */
54
+ export declare const SHELL_MIN_RECAPTURE_INTERVAL_MS = 1000;
55
+ /**
56
+ * Whether a stale shell HIT schedules its recapture (see the floor above).
57
+ * Fails open: an entry without a numeric `createdAt` recaptures.
58
+ */
59
+ export declare function staleShellRecaptureDue(entry: {
60
+ readonly createdAt?: number;
61
+ }, now: number): boolean;
45
62
  /**
46
63
  * Compute staleAt and expiresAt timestamps from TTL and SWR window.
47
64
  *