@rangojs/router 0.15.1 → 0.17.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 (249) hide show
  1. package/README.md +254 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  4. package/dist/types/browser/partial-update.d.ts +11 -0
  5. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  6. package/dist/types/browser/rsc-router.d.ts +1 -1
  7. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  8. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  9. package/dist/types/cache/cache-scope.d.ts +34 -5
  10. package/dist/types/cache/handle-capture.d.ts +41 -3
  11. package/dist/types/cache/handle-snapshot.d.ts +53 -13
  12. package/dist/types/cache/segment-codec.d.ts +10 -3
  13. package/dist/types/cache/taint.d.ts +1 -1
  14. package/dist/types/cache/types.d.ts +15 -3
  15. package/dist/types/client.d.ts +4 -4
  16. package/dist/types/client.rsc.d.ts +4 -5
  17. package/dist/types/components/DefaultDocument.d.ts +5 -2
  18. package/dist/types/context-var.d.ts +6 -5
  19. package/dist/types/escape-script.d.ts +1 -1
  20. package/dist/types/handles/MetaTags.d.ts +5 -4
  21. package/dist/types/handles/Scripts.d.ts +8 -7
  22. package/dist/types/handles/meta.d.ts +2 -2
  23. package/dist/types/handles/script.d.ts +17 -16
  24. package/dist/types/href-client.d.ts +1 -1
  25. package/dist/types/html.d.ts +12 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.rsc.d.ts +3 -2
  28. package/dist/types/loader-store.d.ts +52 -0
  29. package/dist/types/reverse.d.ts +36 -4
  30. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  31. package/dist/types/route-definition/helpers-types.d.ts +36 -10
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +3 -1
  34. package/dist/types/router/debug-manifest.d.ts +3 -2
  35. package/dist/types/router/error-handling.d.ts +20 -0
  36. package/dist/types/router/handler-context.d.ts +6 -0
  37. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  38. package/dist/types/router/middleware-types.d.ts +3 -2
  39. package/dist/types/router/router-context.d.ts +3 -1
  40. package/dist/types/router/router-interfaces.d.ts +24 -2
  41. package/dist/types/router/router-options.d.ts +8 -5
  42. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  43. package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
  44. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  45. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
  46. package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
  47. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  48. package/dist/types/router/segment-wrappers.d.ts +1 -1
  49. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  50. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  51. package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
  52. package/dist/types/rsc/shell-capture.d.ts +5 -4
  53. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  54. package/dist/types/rsc/types.d.ts +10 -0
  55. package/dist/types/server/context.d.ts +33 -3
  56. package/dist/types/server/handle-store.d.ts +26 -2
  57. package/dist/types/server/request-context.d.ts +24 -21
  58. package/dist/types/ssr/index.d.ts +15 -0
  59. package/dist/types/static-handler.d.ts +4 -1
  60. package/dist/types/testing/generated-routes.d.ts +2 -2
  61. package/dist/types/testing/internal/context.d.ts +1 -1
  62. package/dist/types/testing/render-route.d.ts +32 -6
  63. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  64. package/dist/types/testing/vitest.d.ts +35 -1
  65. package/dist/types/theme/ThemeScript.d.ts +3 -3
  66. package/dist/types/thenable-status.d.ts +9 -0
  67. package/dist/types/types/cache-types.d.ts +14 -10
  68. package/dist/types/types/handler-context.d.ts +14 -0
  69. package/dist/types/types/loader-types.d.ts +8 -6
  70. package/dist/types/urls/response-types.d.ts +3 -12
  71. package/dist/types/use-loader.d.ts +12 -3
  72. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  73. package/dist/types/vite/rango.d.ts +1 -1
  74. package/dist/vite/index.js +884 -768
  75. package/package.json +4 -4
  76. package/skills/api-client/SKILL.md +22 -10
  77. package/skills/breadcrumbs/SKILL.md +65 -22
  78. package/skills/bundle-analysis/SKILL.md +22 -22
  79. package/skills/cache-guide/SKILL.md +207 -97
  80. package/skills/caching/SKILL.md +295 -93
  81. package/skills/catalog.json +16 -16
  82. package/skills/client-urls/SKILL.md +53 -31
  83. package/skills/cloudflare/SKILL.md +72 -20
  84. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  85. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  86. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  87. package/skills/comparison/SKILL.md +5 -0
  88. package/skills/comparison/references/framework-comparison.md +39 -33
  89. package/skills/composability/SKILL.md +38 -29
  90. package/skills/css/SKILL.md +10 -13
  91. package/skills/debug-manifest/SKILL.md +74 -31
  92. package/skills/defer-hydration/SKILL.md +2 -1
  93. package/skills/deployment-caching/SKILL.md +6 -1
  94. package/skills/document-cache/SKILL.md +92 -28
  95. package/skills/fonts/SKILL.md +16 -8
  96. package/skills/handler-use/SKILL.md +53 -17
  97. package/skills/hooks/SKILL.md +28 -20
  98. package/skills/hooks/data.md +43 -1
  99. package/skills/hooks/handle-and-actions.md +31 -16
  100. package/skills/hooks/navigation.md +39 -10
  101. package/skills/hooks/outlets.md +13 -6
  102. package/skills/hooks/state.md +37 -15
  103. package/skills/hooks/urls.md +7 -5
  104. package/skills/host-router/SKILL.md +64 -12
  105. package/skills/i18n/SKILL.md +60 -34
  106. package/skills/intercept/SKILL.md +161 -26
  107. package/skills/layout/SKILL.md +106 -50
  108. package/skills/links/SKILL.md +51 -19
  109. package/skills/loader/SKILL.md +242 -106
  110. package/skills/middleware/SKILL.md +119 -23
  111. package/skills/migrate-nextjs/SKILL.md +100 -45
  112. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  113. package/skills/migrate-react-router/SKILL.md +49 -38
  114. package/skills/migrate-react-router/component-migration.md +25 -6
  115. package/skills/migrate-react-router/data-and-actions.md +18 -5
  116. package/skills/migrate-react-router/route-mapping.md +12 -6
  117. package/skills/mime-routes/SKILL.md +12 -7
  118. package/skills/observability/SKILL.md +28 -14
  119. package/skills/parallel/SKILL.md +75 -39
  120. package/skills/ppr/SKILL.md +345 -150
  121. package/skills/prerender/SKILL.md +189 -100
  122. package/skills/rango/SKILL.md +65 -34
  123. package/skills/react-compiler/SKILL.md +6 -5
  124. package/skills/response-routes/SKILL.md +133 -25
  125. package/skills/route/SKILL.md +165 -60
  126. package/skills/router-setup/SKILL.md +418 -240
  127. package/skills/scripts/SKILL.md +31 -20
  128. package/skills/server-actions/SKILL.md +220 -59
  129. package/skills/shell-manifest/SKILL.md +12 -7
  130. package/skills/streams-and-websockets/SKILL.md +32 -14
  131. package/skills/tailwind/SKILL.md +37 -33
  132. package/skills/testing/SKILL.md +68 -32
  133. package/skills/testing/cache-prerender.md +55 -29
  134. package/skills/testing/client-components.md +59 -6
  135. package/skills/testing/e2e-parity.md +12 -2
  136. package/skills/testing/flight.md +19 -11
  137. package/skills/testing/handles.md +8 -5
  138. package/skills/testing/loader.md +8 -5
  139. package/skills/testing/middleware.md +8 -3
  140. package/skills/testing/predicates.md +120 -0
  141. package/skills/testing/render-handler.md +70 -22
  142. package/skills/testing/response-routes.md +11 -4
  143. package/skills/testing/reverse-and-types.md +20 -14
  144. package/skills/testing/server-actions.md +12 -3
  145. package/skills/testing/server-tree.md +9 -0
  146. package/skills/testing/setup.md +9 -5
  147. package/skills/theme/SKILL.md +106 -29
  148. package/skills/typesafety/SKILL.md +20 -9
  149. package/skills/typesafety/env-and-bindings.md +126 -47
  150. package/skills/typesafety/generated-files-and-cli.md +26 -14
  151. package/skills/typesafety/params-and-search.md +44 -19
  152. package/skills/typesafety/route-types.md +50 -14
  153. package/skills/use-cache/SKILL.md +200 -82
  154. package/skills/vercel/SKILL.md +21 -11
  155. package/skills/view-transitions/SKILL.md +41 -20
  156. package/src/__augment-tests__/augmented.check.ts +26 -1
  157. package/src/browser/index.ts +2 -2
  158. package/src/browser/navigation-bridge.ts +3 -4
  159. package/src/browser/navigation-transaction.ts +22 -2
  160. package/src/browser/partial-update.ts +75 -80
  161. package/src/browser/react/ScrollRestoration.tsx +6 -6
  162. package/src/browser/rsc-router.tsx +1 -1
  163. package/src/browser/scroll-restoration.ts +53 -6
  164. package/src/cache/cache-exec-scope.ts +37 -4
  165. package/src/cache/cache-key-utils.ts +1 -6
  166. package/src/cache/cache-runtime.ts +230 -61
  167. package/src/cache/cache-scope.ts +76 -12
  168. package/src/cache/document-cache.ts +15 -0
  169. package/src/cache/handle-capture.ts +90 -10
  170. package/src/cache/handle-snapshot.ts +132 -28
  171. package/src/cache/segment-codec.ts +26 -7
  172. package/src/cache/tag-invalidation.ts +4 -3
  173. package/src/cache/taint.ts +1 -1
  174. package/src/cache/types.ts +16 -3
  175. package/src/client.rsc.tsx +10 -9
  176. package/src/client.tsx +5 -4
  177. package/src/components/DefaultDocument.tsx +7 -2
  178. package/src/context-var.ts +6 -5
  179. package/src/escape-script.ts +1 -1
  180. package/src/handles/MetaTags.tsx +13 -11
  181. package/src/handles/Scripts.tsx +11 -10
  182. package/src/handles/meta.ts +2 -2
  183. package/src/handles/script.ts +17 -16
  184. package/src/href-client.ts +1 -1
  185. package/src/html.ts +12 -0
  186. package/src/index.rsc.ts +5 -0
  187. package/src/index.ts +1 -0
  188. package/src/loader-store.ts +106 -0
  189. package/src/prerender/build-shell-capture.ts +8 -11
  190. package/src/reverse.ts +44 -4
  191. package/src/route-definition/dsl-helpers.ts +134 -42
  192. package/src/route-definition/helpers-types.ts +36 -10
  193. package/src/route-definition/resolve-handler-use.ts +121 -6
  194. package/src/route-types.ts +2 -3
  195. package/src/router/debug-manifest.ts +10 -2
  196. package/src/router/error-handling.ts +51 -15
  197. package/src/router/handler-context.ts +34 -25
  198. package/src/router/intercept-resolution.ts +125 -39
  199. package/src/router/loader-resolution.ts +56 -11
  200. package/src/router/match-api.ts +12 -17
  201. package/src/router/match-middleware/background-revalidation.ts +161 -96
  202. package/src/router/match-middleware/cache-lookup.ts +93 -10
  203. package/src/router/match-middleware/cache-store.ts +17 -90
  204. package/src/router/middleware-types.ts +3 -5
  205. package/src/router/middleware.ts +52 -9
  206. package/src/router/prerender-match.ts +53 -19
  207. package/src/router/route-snapshot.ts +11 -1
  208. package/src/router/router-context.ts +2 -0
  209. package/src/router/router-interfaces.ts +25 -2
  210. package/src/router/router-options.ts +8 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -12
  212. package/src/router/segment-resolution/loader-cache.ts +205 -45
  213. package/src/router/segment-resolution/loader-mask.ts +12 -22
  214. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  215. package/src/router/segment-resolution/mask-nested.ts +7 -7
  216. package/src/router/segment-resolution/revalidation.ts +34 -6
  217. package/src/router/segment-wrappers.ts +4 -0
  218. package/src/rsc/render-pipeline.ts +3 -0
  219. package/src/rsc/response-route-handler.ts +11 -3
  220. package/src/rsc/rsc-rendering.ts +8 -45
  221. package/src/rsc/shell-capture-constants.ts +13 -0
  222. package/src/rsc/shell-capture.ts +111 -90
  223. package/src/rsc/ssr-setup.ts +14 -8
  224. package/src/rsc/types.ts +11 -0
  225. package/src/server/context.ts +88 -11
  226. package/src/server/cookie-store.ts +5 -4
  227. package/src/server/handle-store.ts +232 -7
  228. package/src/server/request-context.ts +35 -30
  229. package/src/ssr/index.tsx +40 -7
  230. package/src/ssr-suspension-warning.ts +2 -13
  231. package/src/static-handler.ts +4 -1
  232. package/src/testing/flight-runtime.d.ts +21 -0
  233. package/src/testing/generated-routes.ts +2 -2
  234. package/src/testing/internal/context.ts +1 -1
  235. package/src/testing/internal/flight-client-globals.ts +7 -5
  236. package/src/testing/render-route.tsx +85 -24
  237. package/src/testing/run-loader.ts +5 -1
  238. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  239. package/src/testing/vitest.ts +62 -1
  240. package/src/theme/ThemeProvider.tsx +14 -12
  241. package/src/theme/ThemeScript.tsx +3 -3
  242. package/src/thenable-status.ts +16 -0
  243. package/src/types/cache-types.ts +14 -10
  244. package/src/types/handler-context.ts +14 -0
  245. package/src/types/loader-types.ts +8 -6
  246. package/src/urls/response-types.ts +3 -23
  247. package/src/use-loader.tsx +24 -4
  248. package/src/vite/plugins/use-cache-transform.ts +118 -74
  249. package/src/vite/rango.ts +7 -3
@@ -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
  };
@@ -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;
@@ -44,11 +44,36 @@ export type CacheRouteLookupOutcome = {
44
44
  */
45
45
  export declare class CacheScope {
46
46
  private readonly defaultKeyPrefix?;
47
+ /**
48
+ * @internal shortCode of the outermost cache() entry of this scope's
49
+ * enabled chain (createCacheScope). Entries above it are live: their
50
+ * segments are not stored and resolve fresh on a hit (withCacheLookup).
51
+ * Undefined covers the whole matched chain (the implicit shell doc scope,
52
+ * a ppr route).
53
+ */
54
+ readonly boundary?: string | undefined;
47
55
  readonly config: PartialCacheOptions | false;
48
56
  readonly parent: CacheScope | null;
49
57
  /** Explicit store from cache() options, if specified */
50
58
  private readonly explicitStore;
51
- constructor(config: PartialCacheOptions | false, parent?: CacheScope | null, defaultKeyPrefix?: "doc" | undefined);
59
+ constructor(config: PartialCacheOptions | false, parent?: CacheScope | null, defaultKeyPrefix?: "doc" | undefined,
60
+ /**
61
+ * @internal shortCode of the outermost cache() entry of this scope's
62
+ * enabled chain (createCacheScope). Entries above it are live: their
63
+ * segments are not stored and resolve fresh on a hit (withCacheLookup).
64
+ * Undefined covers the whole matched chain (the implicit shell doc scope,
65
+ * a ppr route).
66
+ */
67
+ boundary?: string | undefined);
68
+ /**
69
+ * @internal Whether a segment belongs to this scope's cache entry. ShortCodes are
70
+ * hierarchical (server/context.ts getShortCode: parent shortCode + include
71
+ * scope + type letter + counter), so the boundary's subtree is every id that
72
+ * extends the boundary shortCode at a non-digit (`C1` must not claim `C10`).
73
+ * Intercept slot segments ride the target route's entry wherever the
74
+ * intercept is declared.
75
+ */
76
+ covers(id: string, namespace?: string): boolean;
52
77
  /**
53
78
  * Whether caching is enabled for this scope
54
79
  */
@@ -127,8 +152,10 @@ export declare class CacheScope {
127
152
  * @param pathname - URL pathname for cache key generation
128
153
  * @param params - Route params for cache key generation
129
154
  * @param isIntercept - Whether this is an intercept navigation (uses different cache key)
155
+ * @param claimLoaderPushes - The handler context's _claimLoaderPushes, for
156
+ * restoreHandles' loader-owned values
130
157
  */
131
- lookupRoute(pathname: string, params: Record<string, string>, isIntercept?: boolean): Promise<{
158
+ lookupRoute(pathname: string, params: Record<string, string>, isIntercept?: boolean, claimLoaderPushes?: (loaderId: string) => boolean): Promise<{
132
159
  segments: ResolvedSegment[];
133
160
  shouldRevalidate: boolean;
134
161
  } | null>;
@@ -150,7 +177,7 @@ export declare class CacheScope {
150
177
  * A corrupt entry that was evicted reads as `miss`: the store was consulted
151
178
  * and holds nothing servable, which is the post-eviction truth.
152
179
  */
153
- lookupRouteDetailed(pathname: string, params: Record<string, string>, isIntercept?: boolean): Promise<CacheRouteLookupOutcome>;
180
+ lookupRouteDetailed(pathname: string, params: Record<string, string>, isIntercept?: boolean, claimLoaderPushes?: (loaderId: string) => boolean): Promise<CacheRouteLookupOutcome>;
154
181
  /**
155
182
  * Record this scope's segment-DSL cache({ tags }) into the request tag union
156
183
  * synchronously, under the same gate cacheRoute() uses for a write.
@@ -179,11 +206,13 @@ export declare class CacheScope {
179
206
  cacheRoute(pathname: string, params: Record<string, string>, segments: ResolvedSegment[], isIntercept?: boolean): Promise<void>;
180
207
  }
181
208
  /**
182
- * Create a cache scope from entry's cache config
209
+ * Create a cache scope from entry's cache config. `shortCode` is the cache()
210
+ * entry's: a scope nested in an enabled parent keeps the parent's boundary
211
+ * (one entry covers both), any other opens its own at this entry.
183
212
  */
184
213
  export declare function createCacheScope(config: {
185
214
  options: PartialCacheOptions | false;
186
- } | undefined, parent?: CacheScope | null): CacheScope | null;
215
+ } | undefined, parent?: CacheScope | null, shortCode?: string): CacheScope | null;
187
216
  type ShellImplicitCacheMarker = NonNullable<RequestContext["_shellImplicitCache"]>;
188
217
  /**
189
218
  * Mint the implicit doc-level scope for a `_shellImplicitCache` marker: key
@@ -7,17 +7,55 @@
7
7
  */
8
8
  import type { HandleStore } from "../server/handle-store.js";
9
9
  import type { SegmentHandleData } from "./types.js";
10
- export interface HandleCapture {
10
+ /**
11
+ * Scoping for one capture of a cached unit (a loader's own cache(),
12
+ * loader-cache.ts; a "use cache" function, cache-runtime.ts) that runs
13
+ * concurrently with live pushes into the same store.
14
+ */
15
+ export interface HandleCaptureOptions {
16
+ /** Push-time predicate; a rejected push is not recorded by this capture. */
17
+ accept?: () => boolean;
18
+ /**
19
+ * Recorded pushes do not reach the store. A stale hit's background
20
+ * revalidation: the foreground already replayed the entry's pushes, so the
21
+ * fresh body's pushes belong only to the refreshed entry.
22
+ */
23
+ divert?: boolean;
24
+ /**
25
+ * Record key for an accepted push, in place of its segment id. A loader's
26
+ * own cache() (loader-cache.ts recordOwnerKey) and "use cache"
27
+ * (useCacheRecordKey) group pushes by the loader body that made them.
28
+ */
29
+ key?: () => string;
30
+ }
31
+ /**
32
+ * HandleStore.pushReplayed, attributed to `loaderId` for the record key of an
33
+ * enclosing "use cache" MISS: an outer cached function records an inner HIT's
34
+ * replayed loader pushes under that loader, not as its own.
35
+ */
36
+ export declare function replayLoaderPush(handleStore: HandleStore, handleName: string, segmentId: string, value: unknown, loaderId: string): void;
37
+ /**
38
+ * Record key for a "use cache" capture: run-length groups `${seq}:${owner}`,
39
+ * so the HIT (handle-snapshot.ts appendHandles) delivers each loader's pushes
40
+ * at most once per request. The owner is the loader whose body, entered
41
+ * inside this execution via ctx.use, made the push; empty for the function's
42
+ * own pushes and those of cached functions it calls. Groups keep push order
43
+ * across owners. Create at call time: the caller's loader body is the
44
+ * function's own scope.
45
+ */
46
+ export declare function useCacheRecordKey(): () => string;
47
+ export interface HandleCapture extends HandleCaptureOptions {
11
48
  data: Record<string, SegmentHandleData>;
12
49
  }
13
50
  /**
14
- * Start capturing handle pushes for a cached function execution.
51
+ * Start capturing handle pushes for a cached function or cached loader
52
+ * execution (`options`: see HandleCaptureOptions).
15
53
  *
16
54
  * Concurrency-safe: multiple overlapping captures on the same
17
55
  * HandleStore are independent. Each capture registers a token in a
18
56
  * Set; stopping removes it. No ordering requirement (LIFO not needed).
19
57
  */
20
- export declare function startHandleCapture(handleStore: HandleStore): {
58
+ export declare function startHandleCapture(handleStore: HandleStore, options?: HandleCaptureOptions): {
21
59
  capture: HandleCapture;
22
60
  stop: () => void;
23
61
  };
@@ -7,11 +7,18 @@
7
7
  */
8
8
  import type { ResolvedSegment } from "../types.js";
9
9
  import type { HandleStore } from "../server/handle-store.js";
10
- import type { SegmentHandleData } from "./types.js";
10
+ import type { HandleOwners, SegmentHandleData } from "./types.js";
11
11
  type HandleRecord = Record<string, SegmentHandleData>;
12
- export declare function encodeHandles(handles: HandleRecord): Promise<string>;
12
+ /**
13
+ * `onError` reaches the Flight encode (see serializeResult). A cache writer
14
+ * passes the collector it gives the value's encode, so a handle value that
15
+ * fails to encode (a rejected pushed promise) refuses the whole entry like a
16
+ * failed value does; prerender (prerender-match.ts) passes one too and
17
+ * hands the error to its prerender.onError policy.
18
+ */
19
+ export declare function encodeHandles(handles: HandleRecord, onError?: (error: unknown) => void): Promise<string>;
13
20
  export declare function decodeHandles(encoded: string): Promise<HandleRecord | null>;
14
- export declare function encodeHandleValue(value: unknown): Promise<string>;
21
+ export declare function encodeHandleValue(value: unknown, onError?: (error: unknown) => void): Promise<string>;
15
22
  /**
16
23
  * Decode a Flight-encoded handle-data string. Returns null on any decode
17
24
  * failure so the caller can skip handle restore without discarding valid
@@ -22,18 +29,51 @@ export declare function decodeHandleValue<T>(encoded: string): Promise<T | null>
22
29
  * Capture handle data for a set of segments from the handle store.
23
30
  * Used when caching segments to preserve their handle data.
24
31
  *
25
- * `exclude` (shell captures: RequestContext._shellCaptureLoaderHandleValues)
26
- * drops DSL-loader-scoped push values from the CACHE WRITE only: loaders
27
- * re-run fresh on every HIT, so replaying their captured values would
28
- * duplicate the fresh push — and their masked nested promises would stall the
29
- * Flight handle encode to its timeout. Threaded as an explicit argument so
30
- * every other getDataForSegment consumer (the render-barrier snapshot,
31
- * prerender) provably sees every push.
32
+ * DSL-loader pushes stay out of the CACHE WRITE: a HIT runs loaders exactly
33
+ * as an uncached render of the same request would (cache-lookup.ts
34
+ * resolveFreshLoadersAndYield), so replaying a recorded copy would duplicate
35
+ * the live push. Handler pushes, including
36
+ * handler-invoked ctx.use(Loader) bodies (skipped with their handler on a
37
+ * HIT), are kept. The store tags loader pushes by array position at push
38
+ * time, so primitive values are covered. A PPR shell capture untags the
39
+ * settled bake-lane pushes its record keeps (shell-capture.ts) and gives
40
+ * them an owner (captureHandleOwners). Only this call site passes
41
+ * excludeLoaderPushes; every other getDataForSegment consumer (the
42
+ * render-barrier snapshot, prerender) sees every push.
32
43
  */
33
- export declare function captureHandles(segments: ResolvedSegment[], handleStore: HandleStore, exclude?: WeakSet<object>): Record<string, SegmentHandleData>;
44
+ export declare function captureHandles(segments: ResolvedSegment[], handleStore: HandleStore): Record<string, SegmentHandleData>;
45
+ /** The owners of captureHandles' values; undefined when none is owned. */
46
+ export declare function captureHandleOwners(segments: ResolvedSegment[], handleStore: HandleStore): HandleOwners | undefined;
34
47
  /**
35
48
  * Restore handle data from a cached snapshot into the handle store.
36
- * Used when serving cached segments to replay their handle data.
49
+ * Used when serving cached segments to replay their handle data: a route
50
+ * cache() record owns its segments' arrays, so it REPLACES them.
51
+ *
52
+ * A segment with `owners` is replaced with empty arrays and re-pushed in
53
+ * recorded order, an owned value through pushReplayed: the owning loader
54
+ * re-runs on the HIT (loaders stay live), and its live pushes replace the
55
+ * recorded ones in place instead of appending a second copy. `claim`
56
+ * (setupLoaderAccess _claimLoaderPushes) is asked once per owner, so the
57
+ * loader's own cache() HIT does not replay its pushes a second time
58
+ * (loader-cache.ts replayLoaderHandles); an owner it refuses is skipped. A
59
+ * record without owners restores as a plain replay.
60
+ */
61
+ export declare function restoreHandles(handles: Record<string, SegmentHandleData>, handleStore: HandleStore, owners?: HandleOwners, claim?: (loaderId: string) => boolean): void;
62
+ /**
63
+ * Append recorded handle pushes to the store, in recorded order, via push()
64
+ * so active captures and loader-scope tagging see them like live pushes. For
65
+ * a cached unit that shares its segments with live pushes ("use cache");
66
+ * restoreHandles would wipe those. `segmentId` redirects every value to one
67
+ * segment (the caller's).
68
+ *
69
+ * Owner-keyed groups (useCacheRecordKey, `${seq}:${loaderId}`) are a loader's
70
+ * pushes, which reach the page once per request: `claim` (setupLoaderAccess
71
+ * _claimLoaderPushes) skips a loader that already ran or was replayed in this
72
+ * request, and a claimed loader that runs later replaces its replayed values
73
+ * (HandleStore.pushReplayed). `${seq}:` groups are the function's own pushes.
74
+ * A key without ":" is a segment id (records written before owner keys) and
75
+ * replays unchanged. A loader's own cache() replays through loader-cache.ts
76
+ * replayLoaderHandles.
37
77
  */
38
- export declare function restoreHandles(handles: Record<string, SegmentHandleData>, handleStore: HandleStore): void;
78
+ export declare function appendHandles(handles: Record<string, SegmentHandleData>, handleStore: HandleStore, segmentId?: string, claim?: (loaderId: string) => boolean): void;
39
79
  export {};
@@ -23,7 +23,7 @@ export declare function stringToStream(str: string): ReadableStream<Uint8Array>;
23
23
  * For contexts where null is a valid result (loader caching, "use cache"),
24
24
  * use serializeResult() instead which preserves null through RSC Flight.
25
25
  */
26
- export declare function rscSerialize(value: unknown): Promise<string | undefined>;
26
+ export declare function rscSerialize(value: unknown, onError?: (error: unknown) => void): Promise<string | undefined>;
27
27
  /**
28
28
  * RSC-deserialize a value from a stored string.
29
29
  */
@@ -36,7 +36,7 @@ export declare function rscDeserialize<T>(encoded: string | undefined): Promise<
36
36
  *
37
37
  * Returns null only on serialization failure.
38
38
  */
39
- export declare function serializeResult(value: unknown): Promise<string | null>;
39
+ export declare function serializeResult(value: unknown, onError?: (error: unknown) => void): Promise<string | null>;
40
40
  /**
41
41
  * RSC-deserialize a cached result string.
42
42
  * Counterpart to serializeResult() — always receives a non-empty string.
@@ -52,8 +52,15 @@ export declare const deserializeComponent: (encoded: string) => Promise<unknown>
52
52
  * Serialize segments for storage.
53
53
  * Each segment's component, layout, loading, and loaderData are RSC-serialized.
54
54
  * Metadata is preserved as-is.
55
+ *
56
+ * `onError` goes to every Flight encode. A component that throws while
57
+ * encoding (e.g. an async server component in the tree) does not reject the
58
+ * encode: Flight reports it through onError and writes an error row
59
+ * (`1:E{...}`) that throws when the decoded tree renders. cacheRoute and the
60
+ * prerender producers (prerender-match.ts) pass it to refuse such an entry;
61
+ * without it the encode is unchanged.
55
62
  */
56
- export declare function serializeSegments(segments: ResolvedSegment[]): Promise<SerializedSegmentData[]>;
63
+ export declare function serializeSegments(segments: ResolvedSegment[], onError?: (error: unknown) => void): Promise<SerializedSegmentData[]>;
57
64
  /**
58
65
  * Build ResolvedSegments that carry the STORED fragment strings verbatim
59
66
  * instead of decoding them (PPR fast-path payload splice, issue #700; see
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Taint symbol for request-scoped objects.
3
3
  *
4
- * Objects branded with NOCACHE_SYMBOL (ctx, env, req) are excluded from
4
+ * Objects branded with NOCACHE_SYMBOL (ctx objects) are excluded from
5
5
  * "use cache" cache keys and trigger handle capture mode so that side
6
6
  * effects (breadcrumbs, metadata) are recorded and replayed on cache hit.
7
7
  */