@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. package/src/router/middleware-cookies.ts +0 -55
@@ -123,21 +123,14 @@ export function withCacheStore<TEnv>(
123
123
  ): AsyncGenerator<ResolvedSegment> {
124
124
  const ms = ctx.metricsStore;
125
125
 
126
- // Collect all segments while passing them through
127
126
  const allSegments: ResolvedSegment[] = [];
128
127
  for await (const segment of source) {
129
128
  allSegments.push(segment);
130
129
  yield segment;
131
130
  }
132
131
 
133
- // Measure own work only (after source iteration completes)
134
132
  const ownStart = performance.now();
135
133
 
136
- // Skip caching if:
137
- // 1. Cache miss but cache scope is disabled
138
- // 2. This is an action (actions don't cache)
139
- // 3. Cache was already hit (no need to re-cache)
140
- // 4. Non-GET request (only cache GET requests)
141
134
  if (
142
135
  !ctx.cacheScope?.enabled ||
143
136
  ctx.isAction ||
@@ -162,17 +155,13 @@ export function withCacheStore<TEnv>(
162
155
  createHandleStore,
163
156
  } = getRouterContext<TEnv>();
164
157
 
165
- // Combine main segments with intercept segments
166
158
  const allSegmentsToCache = [...allSegments, ...state.interceptSegments];
167
159
 
168
- // Check if any non-loader segments have null components from revalidation
169
- // skip (client already had them). Segments where the handler intentionally
170
- // returned null are not revalidation skips — re-rendering them will still
171
- // produce null, so proactive caching would be wasted work.
172
- const clientIdSet = new Set(ctx.clientSegmentIds);
173
160
  const hasNullComponents = allSegmentsToCache.some(
174
161
  (s) =>
175
- s.component === null && s.type !== "loader" && clientIdSet.has(s.id),
162
+ s.component === null &&
163
+ s.type !== "loader" &&
164
+ ctx.clientSegmentSet.has(s.id),
176
165
  );
177
166
 
178
167
  const requestCtx = getRequestContext();
@@ -183,10 +172,7 @@ export function withCacheStore<TEnv>(
183
172
  ? getOrCreateRequestId(ctx.request)
184
173
  : undefined;
185
174
 
186
- // Register onResponse callback to skip caching for non-200 responses
187
- // Note: error/notFound status codes are set elsewhere (not caching-specific)
188
175
  requestCtx.onResponse((response) => {
189
- // Only cache successful responses
190
176
  if (response.status !== 200) {
191
177
  debugLog("cacheStore", "skipping cache for non-200 response", {
192
178
  status: response.status,
@@ -196,10 +182,7 @@ export function withCacheStore<TEnv>(
196
182
  }
197
183
 
198
184
  if (hasNullComponents) {
199
- // Proactive caching: render all segments fresh in background
200
- // This ensures cache has complete components for future requests
201
185
  requestCtx.waitUntil(async () => {
202
- // Prevent background metrics from polluting foreground timeline.
203
186
  const savedMetrics = ctx.Store.metrics;
204
187
  ctx.Store.metrics = undefined;
205
188
 
@@ -207,15 +190,9 @@ export function withCacheStore<TEnv>(
207
190
  debugLog("cacheStore", "proactive caching started", {
208
191
  pathname: ctx.pathname,
209
192
  });
210
- // Swap to a fresh HandleStore so handle.push() calls from
211
- // proactive resolution are captured (not silenced). The original
212
- // store's stream is already sent by waitUntil time.
213
- // cacheRoute reads from requestCtx._handleStore, so this ensures
214
- // complete handle data (e.g. breadcrumbs) is cached.
215
193
  const originalHandleStore = requestCtx._handleStore;
216
194
  requestCtx._handleStore = createHandleStore();
217
195
  try {
218
- // Create fresh context for proactive caching
219
196
  const proactiveHandlerContext = createHandlerContext(
220
197
  ctx.matched.params,
221
198
  ctx.request,
@@ -230,12 +207,8 @@ export function withCacheStore<TEnv>(
230
207
  );
231
208
  const proactiveLoaderPromises = new Map<string, Promise<any>>();
232
209
 
233
- // Use normal loader access so handle data is captured
234
210
  setupLoaderAccess(proactiveHandlerContext, proactiveLoaderPromises);
235
211
 
236
- // Re-resolve ALL segments without revalidation.
237
- // Skip DSL loaders — they are never cached (cacheRoute filters them)
238
- // and are always resolved fresh on each request.
239
212
  const Store = ctx.Store;
240
213
  const freshSegments = await Store.run(() =>
241
214
  resolveAllSegments(
@@ -248,7 +221,6 @@ export function withCacheStore<TEnv>(
248
221
  ),
249
222
  );
250
223
 
251
- // Also resolve intercept segments fresh if applicable
252
224
  let freshInterceptSegments: ResolvedSegment[] = [];
253
225
  if (ctx.interceptResult) {
254
226
  freshInterceptSegments = await Store.run(() =>
@@ -300,8 +272,6 @@ export function withCacheStore<TEnv>(
300
272
  }
301
273
  });
302
274
  } else {
303
- // All segments have components - cache directly
304
- // Schedule caching in waitUntil since cacheRoute is now async (key resolution)
305
275
  if (INTERNAL_RANGO_DEBUG) {
306
276
  console.log(
307
277
  `[RSC CacheStore][req:${reqId}] Direct cache path: scheduling cacheRoute for ${ctx.pathname} (${allSegmentsToCache.length} segments, hasNullComponents=${hasNullComponents})`,
@@ -125,17 +125,14 @@ export function withInterceptResolution<TEnv>(
125
125
  ): AsyncGenerator<ResolvedSegment> {
126
126
  const ms = ctx.metricsStore;
127
127
 
128
- // First, yield all segments from the source (main segment resolution or cache)
129
128
  const segments: ResolvedSegment[] = [];
130
129
  for await (const segment of source) {
131
130
  segments.push(segment);
132
131
  yield segment;
133
132
  }
134
133
 
135
- // Measure own work only (after source iteration completes)
136
134
  const ownStart = performance.now();
137
135
 
138
- // Skip intercept resolution for full match (document requests don't have intercepts)
139
136
  if (ctx.isFullMatch) {
140
137
  if (ms) {
141
138
  ms.metrics.push({
@@ -147,18 +144,12 @@ export function withInterceptResolution<TEnv>(
147
144
  return;
148
145
  }
149
146
 
150
- // Skip intercept resolution if:
151
- // 1. No intercept result
152
- // 2. Already have intercept segments (from cache hit with intercept key)
153
- // 3. Cache hit with intercept key
154
147
  const skipInterceptResolution =
155
148
  !ctx.interceptResult ||
156
149
  state.interceptSegments.length > 0 ||
157
150
  (state.cacheHit && ctx.isIntercept);
158
151
 
159
152
  if (skipInterceptResolution) {
160
- // For cache hit with intercept, extract intercept segments from cached data for slots
161
- // and re-resolve loaders for fresh data
162
153
  if (ctx.interceptResult && state.cacheHit && ctx.isIntercept) {
163
154
  await handleCacheHitIntercept(ctx, state, segments);
164
155
  }
@@ -172,7 +163,6 @@ export function withInterceptResolution<TEnv>(
172
163
  return;
173
164
  }
174
165
 
175
- // Resolve intercept segments
176
166
  const { resolveInterceptEntry } = getRouterContext<TEnv>();
177
167
 
178
168
  const slotName = ctx.interceptResult!.intercept.slotName;
@@ -181,7 +171,6 @@ export function withInterceptResolution<TEnv>(
181
171
  slotName,
182
172
  });
183
173
 
184
- // Resolve intercept entry (middleware, loaders, handler)
185
174
  const Store = ctx.Store;
186
175
  const interceptSegments = await Store.run(() =>
187
176
  resolveInterceptEntry(
@@ -203,14 +192,12 @@ export function withInterceptResolution<TEnv>(
203
192
  ),
204
193
  );
205
194
 
206
- // Update state
207
195
  state.interceptSegments = interceptSegments;
208
196
  state.slots[slotName] = {
209
197
  active: true,
210
198
  segments: interceptSegments,
211
199
  };
212
200
 
213
- // Yield intercept segments
214
201
  for (const segment of interceptSegments) {
215
202
  yield segment;
216
203
  }
@@ -225,11 +212,6 @@ export function withInterceptResolution<TEnv>(
225
212
  };
226
213
  }
227
214
 
228
- /**
229
- * Handle cache hit with intercept scenario
230
- *
231
- * Extract intercept segments from cached data and re-resolve loaders for fresh data.
232
- */
233
215
  async function handleCacheHitIntercept<TEnv>(
234
216
  ctx: MatchContext<TEnv>,
235
217
  state: MatchPipelineState,
@@ -241,14 +223,11 @@ async function handleCacheHitIntercept<TEnv>(
241
223
 
242
224
  const slotName = ctx.interceptResult.intercept.slotName;
243
225
 
244
- // Find intercept segments from cached segments (namespace starts with "intercept:")
245
226
  const interceptSegments = segments.filter((s) =>
246
227
  s.namespace?.startsWith("intercept:"),
247
228
  );
248
229
  state.interceptSegments = interceptSegments;
249
230
 
250
- // Re-resolve intercept loaders for fresh data on cache hit
251
- // This keeps cached component/layout but fetches fresh loader data
252
231
  if (resolveInterceptLoadersOnly) {
253
232
  const Store = ctx.Store;
254
233
  const freshLoaderResult = await Store.run(() =>
@@ -271,7 +250,6 @@ async function handleCacheHitIntercept<TEnv>(
271
250
  ),
272
251
  );
273
252
 
274
- // Update intercept segment's loaderDataPromise with fresh data
275
253
  if (freshLoaderResult) {
276
254
  const interceptMainSegment = interceptSegments.find(
277
255
  (s) => s.type === "parallel" && s.slot,
@@ -93,12 +93,6 @@ import type { MatchContext, MatchPipelineState } from "../match-context.js";
93
93
  import { getRouterContext } from "../router-context.js";
94
94
  import type { GeneratorMiddleware } from "./cache-lookup.js";
95
95
 
96
- /**
97
- * Check whether any entry in the tree uses loading() (streaming).
98
- * Matches the router's streaming semantics in fresh.ts: streaming is
99
- * enabled when `loading` is defined AND not `false`. `loading: false`
100
- * explicitly disables streaming; `undefined` means no loading at all.
101
- */
102
96
  export function treeHasStreaming(entries: EntryData[]): boolean {
103
97
  for (const entry of entries) {
104
98
  if (
@@ -130,12 +124,6 @@ export function treeHasStreaming(entries: EntryData[]): boolean {
130
124
  return false;
131
125
  }
132
126
 
133
- /**
134
- * Creates segment resolution middleware
135
- *
136
- * Only runs on cache miss (state.cacheHit === false).
137
- * Uses resolveAllSegmentsWithRevalidation from RouterContext to resolve segments.
138
- */
139
127
  export function withSegmentResolution<TEnv>(
140
128
  ctx: MatchContext<TEnv>,
141
129
  state: MatchPipelineState,
@@ -145,17 +133,12 @@ export function withSegmentResolution<TEnv>(
145
133
  ): AsyncGenerator<ResolvedSegment> {
146
134
  const ms = ctx.metricsStore;
147
135
 
148
- // IMPORTANT: Always iterate source first to give cache-lookup a chance
149
- // to run and set state.cacheHit. Without this, cache-lookup never executes!
150
136
  for await (const segment of source) {
151
137
  yield segment;
152
138
  }
153
139
 
154
- // Measure own work only (after source iteration completes)
155
140
  const ownStart = performance.now();
156
141
 
157
- // If cache hit, segments were already yielded by cache lookup
158
- // (render barrier is resolved on the cache-hit path)
159
142
  if (state.cacheHit) {
160
143
  if (ms) {
161
144
  ms.metrics.push({
@@ -178,7 +161,6 @@ export function withSegmentResolution<TEnv>(
178
161
  const Store = ctx.Store;
179
162
 
180
163
  if (ctx.isFullMatch) {
181
- // Full match (document request) - simple resolution without revalidation
182
164
  const segments = await Store.run(() =>
183
165
  resolveAllSegments(
184
166
  ctx.entries,
@@ -189,15 +171,12 @@ export function withSegmentResolution<TEnv>(
189
171
  ),
190
172
  );
191
173
 
192
- // Update state with resolved segments
193
174
  state.segments = segments;
194
175
  state.matchedIds = segments.map((s: { id: string }) => s.id);
195
176
 
196
177
  if (reqCtx) {
197
178
  reqCtx._resolveRenderBarrier(segments);
198
179
  }
199
-
200
- // Yield all resolved segments
201
180
  for (const segment of segments) {
202
181
  yield segment;
203
182
  }
@@ -214,7 +193,6 @@ export function withSegmentResolution<TEnv>(
214
193
  ctx.request,
215
194
  ctx.prevUrl,
216
195
  ctx.url,
217
- ctx.loaderPromises,
218
196
  ctx.actionContext,
219
197
  ctx.interceptResult,
220
198
  ctx.localRouteName,
@@ -106,16 +106,6 @@ import {
106
106
  withSegmentResolution,
107
107
  } from "./match-middleware/index.js";
108
108
 
109
- /**
110
- * Compose multiple async generator middleware into a single middleware
111
- *
112
- * Middleware are applied in reverse order (rightmost runs first, innermost).
113
- * For the pipeline:
114
- * compose(A, B, C)(source)
115
- *
116
- * The flow is: source -> C -> B -> A -> output
117
- * Where C is the innermost (runs first on input) and A is outermost (runs last).
118
- */
119
109
  export function compose<T>(
120
110
  ...middleware: GeneratorMiddleware<T>[]
121
111
  ): GeneratorMiddleware<T> {
@@ -126,54 +116,23 @@ export function compose<T>(
126
116
  return middleware[0];
127
117
  }
128
118
  return (source) => {
129
- // Apply middleware in reverse order (rightmost first)
130
119
  return middleware.reduceRight((prev, fn) => fn(prev), source);
131
120
  };
132
121
  }
133
122
 
134
- /**
135
- * Create an empty async generator (source for pipeline)
136
- */
137
- export async function* empty<T>(): AsyncGenerator<T> {
138
- // Yields nothing - used as the initial source for the pipeline
139
- }
123
+ export async function* empty<T>(): AsyncGenerator<T> {}
140
124
 
141
- /**
142
- * Create the match partial pipeline
143
- *
144
- * Pipeline order (innermost to outermost):
145
- * 1. cache-lookup - Check cache first, yield cached segments if hit
146
- * 2. segment-resolution - Resolve segments if cache miss
147
- * 3. intercept-resolution - Resolve intercept segments
148
- * 4. cache-store - Store segments in cache
149
- * 5. background-revalidation - Trigger SWR if cache was stale
150
- *
151
- * Data flow:
152
- * - empty() produces no segments
153
- * - cache-lookup either yields cached segments OR passes through to segment-resolution
154
- * - segment-resolution resolves fresh segments on cache miss
155
- * - intercept-resolution adds intercept segments
156
- * - cache-store observes and caches segments
157
- * - background-revalidation triggers SWR revalidation if needed
158
- */
159
125
  export function createMatchPartialPipeline<TEnv>(
160
126
  ctx: MatchContext<TEnv>,
161
127
  state: MatchPipelineState,
162
128
  ): AsyncGenerator<ResolvedSegment> {
163
- // Build the middleware chain
164
129
  const pipeline = compose<ResolvedSegment>(
165
- // Outermost - observes segments and triggers background revalidation
166
130
  withBackgroundRevalidation(ctx, state),
167
- // Observes and stores segments in cache
168
131
  withCacheStore(ctx, state),
169
- // Adds intercept segments after main segments
170
132
  withInterceptResolution(ctx, state),
171
- // Resolves segments on cache miss
172
133
  withSegmentResolution(ctx, state),
173
- // Innermost - checks cache first
174
134
  withCacheLookup(ctx, state),
175
135
  );
176
136
 
177
- // Start with empty source - cache lookup or segment resolution will produce segments
178
137
  return pipeline(empty());
179
138
  }
@@ -112,9 +112,6 @@ import type { MatchContext, MatchPipelineState } from "./match-context.js";
112
112
  import { debugLog } from "./logging.js";
113
113
  import { appendMetric } from "./metrics.js";
114
114
 
115
- /**
116
- * Collect all segments from an async generator
117
- */
118
115
  export async function collectSegments(
119
116
  generator: AsyncGenerator<ResolvedSegment>,
120
117
  ): Promise<ResolvedSegment[]> {
@@ -138,34 +135,36 @@ export async function collectSegments(
138
135
  function deduplicateLoaderSegments(
139
136
  segments: ResolvedSegment[],
140
137
  logPrefix: string,
141
- ): ResolvedSegment[] {
142
- // First pass: collect loaderIds of original (non-inherited) segments
143
- // and whether their parent entry uses loading()
138
+ ): { segments: ResolvedSegment[]; removedIds: Set<string> } {
139
+ // Single pass: original (non-inherited) loaderIds, all loaderIds grouped by
140
+ // namespace, and namespaces of segments that declare loading().
144
141
  const originalLoaders = new Set<string>();
145
- const loadersWithLoading = new Set<string>();
142
+ const loaderIdsByNamespace = new Map<string, string[]>();
143
+ const namespacesWithLoading = new Set<string>();
146
144
  for (const s of segments) {
147
- if (s.type === "loader" && s.loaderId && !s._inherited) {
148
- originalLoaders.add(s.loaderId);
149
- // If the segment has a sibling with loading, the parent uses loading()
150
- // We detect this by checking if any non-loader segment in the same
151
- // namespace has loading defined
145
+ if (s.type === "loader" && s.loaderId) {
146
+ if (!s._inherited) originalLoaders.add(s.loaderId);
147
+ const ids = loaderIdsByNamespace.get(s.namespace);
148
+ if (ids) ids.push(s.loaderId);
149
+ else loaderIdsByNamespace.set(s.namespace, [s.loaderId]);
150
+ } else if (
151
+ s.type !== "loader" &&
152
+ s.loading !== undefined &&
153
+ s.loading !== false
154
+ ) {
155
+ namespacesWithLoading.add(s.namespace);
152
156
  }
153
157
  }
154
- // Check if any layout/route segment has loading — if a loader's namespace
155
- // matches a segment with loading, the inherited copy is needed
156
- for (const s of segments) {
157
- if (s.type !== "loader" && s.loading !== undefined && s.loading !== false) {
158
- // Find loaders in this namespace
159
- for (const l of segments) {
160
- if (l.type === "loader" && l.namespace === s.namespace && l.loaderId) {
161
- loadersWithLoading.add(l.loaderId);
162
- }
163
- }
158
+
159
+ const loadersWithLoading = new Set<string>();
160
+ for (const ns of namespacesWithLoading) {
161
+ for (const id of loaderIdsByNamespace.get(ns) ?? []) {
162
+ loadersWithLoading.add(id);
164
163
  }
165
164
  }
166
165
 
167
166
  const result: ResolvedSegment[] = [];
168
- let dedupCount = 0;
167
+ const removedIds = new Set<string>();
169
168
 
170
169
  for (const s of segments) {
171
170
  if (
@@ -175,22 +174,22 @@ function deduplicateLoaderSegments(
175
174
  originalLoaders.has(s.loaderId) &&
176
175
  !loadersWithLoading.has(s.loaderId)
177
176
  ) {
178
- dedupCount++;
177
+ removedIds.add(s.id);
179
178
  continue;
180
179
  }
181
180
  result.push(s);
182
181
  }
183
182
 
184
- if (dedupCount > 0) {
185
- debugLog(logPrefix, `deduped ${dedupCount} inherited loader segment(s)`);
183
+ if (removedIds.size > 0) {
184
+ debugLog(
185
+ logPrefix,
186
+ `deduped ${removedIds.size} inherited loader segment(s)`,
187
+ );
186
188
  }
187
189
 
188
- return result;
190
+ return { segments: result, removedIds };
189
191
  }
190
192
 
191
- /**
192
- * Build the final MatchResult from collected segments and context
193
- */
194
193
  export function buildMatchResult<TEnv>(
195
194
  allSegments: ResolvedSegment[],
196
195
  ctx: MatchContext<TEnv>,
@@ -204,11 +203,6 @@ export function buildMatchResult<TEnv>(
204
203
  let segmentsToRender: ResolvedSegment[];
205
204
 
206
205
  if (ctx.isFullMatch) {
207
- // Full match (document request) - all segments are rendered
208
- // Deduplicate by segment ID (defense-in-depth). The primary dedup is in
209
- // resolveAllSegments, but this guards against any path that bypasses it.
210
- // include() scopes can produce entries that resolve the same shared layout,
211
- // and duplicate IDs change the client's React tree depth causing remounts.
212
206
  const seen = new Set<string>();
213
207
  segmentsToRender = [];
214
208
  for (const s of allSegments) {
@@ -219,24 +213,14 @@ export function buildMatchResult<TEnv>(
219
213
  }
220
214
  allIds = segmentsToRender.map((s) => s.id);
221
215
  } else {
222
- // Partial match (navigation) - filter and handle intercepts
223
- // When intercepting, tell browser to keep its current segments + add modal
224
- // This prevents the browser from discarding the current page content
225
- // If client sent empty segments (HMR recovery), use segment IDs from allSegments
226
216
  allIds = ctx.interceptResult
227
217
  ? ctx.clientSegmentIds.length > 0
228
218
  ? [...ctx.clientSegmentIds, ...state.interceptSegments.map((s) => s.id)]
229
- : allSegments.map((s) => s.id) // Use actual segments, not matchedIds
219
+ : allSegments.map((s) => s.id)
230
220
  : [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
231
221
 
232
- // Deduplicate allIds (defense-in-depth for partial match path)
233
222
  allIds = [...new Set(allIds)];
234
223
 
235
- // Filter out null-component segments only when the client already has
236
- // them cached (revalidation skip). If the client doesn't have the segment,
237
- // it must be included even with null component — it's structurally required
238
- // as a parent node for child layouts/parallels to reconcile against.
239
- // Loader segments are always included as they carry data.
240
224
  const clientIdSet = new Set(ctx.clientSegmentIds);
241
225
  segmentsToRender = allSegments.filter(
242
226
  (s) =>
@@ -244,44 +228,18 @@ export function buildMatchResult<TEnv>(
244
228
  );
245
229
  }
246
230
 
247
- const dedupedSegments = deduplicateLoaderSegments(
231
+ const { segments: dedupedSegments, removedIds } = deduplicateLoaderSegments(
248
232
  segmentsToRender,
249
233
  logPrefix,
250
234
  );
251
235
 
252
- debugLog(logPrefix, "all segments", {
253
- segments: allSegments.map((s) => ({
254
- id: s.id,
255
- type: s.type,
256
- hasComponent: s.component !== null,
257
- })),
258
- });
259
- debugLog(logPrefix, "segments to render", {
260
- segmentIds: dedupedSegments.map((s) => s.id),
261
- });
262
-
263
- // Remove deduped loader IDs from matched so the client doesn't treat
264
- // them as missing segments and trigger a fallback refetch.
265
- const removedIds = new Set(
266
- segmentsToRender
267
- .filter((s) => !dedupedSegments.includes(s))
268
- .map((s) => s.id),
269
- );
270
236
  const matchedIds =
271
237
  removedIds.size > 0 ? allIds.filter((id) => !removedIds.has(id)) : allIds;
272
238
 
273
- // resolvedIds: every segment whose handler actually ran this request.
274
- // For full-match every segment is fresh; for partial-match we filter by
275
- // the internal `_handlerRan` flag set in revalidation.ts. Drives the
276
- // client's handle-bucket cleanup — a slot that re-resolved and pushed
277
- // nothing must have its previous handle data cleared, but `diff` won't
278
- // carry it because the segment payload skips null-component cached
279
- // segments to save bytes.
280
239
  const resolvedIds = ctx.isFullMatch
281
240
  ? allSegments.map((s) => s.id)
282
241
  : allSegments.filter((s) => s._handlerRan).map((s) => s.id);
283
242
 
284
- // Strip internal-only fields from the segments going on the wire.
285
243
  const cleanedSegments = dedupedSegments.map((s) => {
286
244
  if (s._handlerRan === undefined) return s;
287
245
  const { _handlerRan: _drop, ...rest } = s;
@@ -301,12 +259,6 @@ export function buildMatchResult<TEnv>(
301
259
  };
302
260
  }
303
261
 
304
- /**
305
- * Collect segments from pipeline and build MatchResult
306
- *
307
- * This is the main entry point for building the final result after
308
- * the pipeline has processed all segments.
309
- */
310
262
  export async function collectMatchResult<TEnv>(
311
263
  pipeline: AsyncGenerator<ResolvedSegment>,
312
264
  ctx: MatchContext<TEnv>,
@@ -316,7 +268,6 @@ export async function collectMatchResult<TEnv>(
316
268
 
317
269
  const buildStart = performance.now();
318
270
 
319
- // Update state with collected segments if not already set
320
271
  if (state.segments.length === 0) {
321
272
  state.segments = allSegments;
322
273
  }
@@ -1,9 +1,3 @@
1
- /**
2
- * Router Metrics Utilities
3
- *
4
- * Performance metrics collection and reporting for RSC Router.
5
- */
6
-
7
1
  import type { MetricsStore, PerformanceMetric } from "../server/context";
8
2
 
9
3
  const BASE_INDENT = 2;
@@ -16,7 +10,6 @@ function formatMs(value: number): string {
16
10
 
17
11
  function sortMetrics(metrics: PerformanceMetric[]): PerformanceMetric[] {
18
12
  return [...metrics].sort((a, b) => {
19
- // handler:total always goes last (it wraps everything)
20
13
  if (a.label === "handler:total") return 1;
21
14
  if (b.label === "handler:total") return -1;
22
15
  return a.startTime - b.startTime;
@@ -68,11 +61,6 @@ function createTimelineAxis(total: number): string {
68
61
  )}${totalLabel}`;
69
62
  }
70
63
 
71
- /**
72
- * Create a metrics store for the request if debugPerformance is enabled.
73
- * An optional `requestStart` timestamp can anchor the store to an earlier
74
- * point (e.g. handler start) so that handler:total has startTime=0.
75
- */
76
64
  export function createMetricsStore(
77
65
  debugPerformance: boolean,
78
66
  requestStart?: number,
@@ -85,9 +73,6 @@ export function createMetricsStore(
85
73
  };
86
74
  }
87
75
 
88
- /**
89
- * Append a metric to the request store using an absolute start timestamp.
90
- */
91
76
  export function appendMetric(
92
77
  metricsStore: MetricsStore | undefined,
93
78
  label: string,
@@ -104,9 +89,6 @@ export function appendMetric(
104
89
  });
105
90
  }
106
91
 
107
- /**
108
- * Log the current request metrics and return the corresponding Server-Timing value.
109
- */
110
92
  export function buildMetricsTiming(
111
93
  method: string,
112
94
  pathname: string,
@@ -117,7 +99,6 @@ export function buildMetricsTiming(
117
99
  return generateServerTiming(metricsStore) || undefined;
118
100
  }
119
101
 
120
- /** Display row produced by merging :pre/:post metric pairs. */
121
102
  interface DisplayRow {
122
103
  label: string;
123
104
  startTime: number;
@@ -126,12 +107,7 @@ interface DisplayRow {
126
107
  spans: Span[];
127
108
  }
128
109
 
129
- /**
130
- * Build display rows from sorted metrics, merging :pre/:post pairs into
131
- * a single row with disjoint timeline segments.
132
- */
133
110
  function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
134
- // Index :pre and :post metrics by their base label
135
111
  const preMap = new Map<string, PerformanceMetric>();
136
112
  const postMap = new Map<string, PerformanceMetric>();
137
113
  const consumed = new Set<PerformanceMetric>();
@@ -211,11 +187,6 @@ function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
211
187
  return rows;
212
188
  }
213
189
 
214
- /**
215
- * Log metrics to console in a formatted way.
216
- * Uses a shared-axis timeline so overlapping work stays visible.
217
- * Merges :pre/:post pairs onto one row with disjoint timeline segments.
218
- */
219
190
  export function logMetrics(
220
191
  method: string,
221
192
  pathname: string,
@@ -267,11 +238,6 @@ export function logMetrics(
267
238
  }
268
239
  }
269
240
 
270
- /**
271
- * Generate Server-Timing header value from metrics
272
- * Format: metric-name;dur=X.XX
273
- * Depth is encoded as a "d{N}-" prefix for nested metrics.
274
- */
275
241
  export function generateServerTiming(metricsStore: MetricsStore): string {
276
242
  return metricsStore.metrics
277
243
  .map((m) => {