@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -5,15 +5,101 @@
5
5
  * Uses globalThis to survive HMR in development.
6
6
  */
7
7
 
8
- import type { SegmentCacheStore, CachedEntryData, CacheDefaults, CacheGetResult } from "./types.js";
8
+ import type {
9
+ SegmentCacheStore,
10
+ CachedEntryData,
11
+ CacheDefaults,
12
+ CacheGetResult,
13
+ CacheItemResult,
14
+ CacheItemOptions,
15
+ ShellCacheEntry,
16
+ } from "./types.js";
9
17
  import type { RequestContext } from "../server/request-context.js";
18
+ import { isPerClientSignalHeader } from "../browser/cookie-name.js";
19
+ import {
20
+ resolveTtl,
21
+ resolveSwrWindow,
22
+ computeExpiration,
23
+ DEFAULT_FUNCTION_TTL,
24
+ } from "./cache-policy.js";
25
+ import { reportCacheError } from "./cache-error.js";
10
26
 
11
- const CACHE_GLOBAL_KEY = "__rsc_router_segment_cache_store__";
27
+ const CACHE_REGISTRY_KEY = "__rsc_router_segment_cache_registry__";
28
+ const RESPONSE_CACHE_REGISTRY_KEY = "__rsc_router_response_cache_registry__";
29
+ const ITEM_CACHE_REGISTRY_KEY = "__rsc_router_item_cache_registry__";
30
+ const SHELL_CACHE_REGISTRY_KEY = "__rsc_router_shell_cache_registry__";
31
+ const TAG_INDEX_REGISTRY_KEY = "__rsc_router_tag_index_registry__";
32
+ const KEY_TAGS_REGISTRY_KEY = "__rsc_router_key_tags_registry__";
33
+
34
+ /**
35
+ * Get or create a named Map from a globalThis-backed registry.
36
+ * The registry survives HMR; individual stores are keyed by name.
37
+ */
38
+ function getNamedMap<V>(registryKey: string, name: string): Map<string, V> {
39
+ let registry = (globalThis as any)[registryKey] as
40
+ | Map<string, Map<string, V>>
41
+ | undefined;
42
+ if (!registry) {
43
+ registry = new Map();
44
+ (globalThis as any)[registryKey] = registry;
45
+ }
46
+ let map = registry.get(name);
47
+ if (!map) {
48
+ map = new Map<string, V>();
49
+ registry.set(name, map);
50
+ }
51
+ return map;
52
+ }
53
+
54
+ interface CachedResponseEntry {
55
+ body: ArrayBuffer;
56
+ status: number;
57
+ headers: [string, string][];
58
+ expiresAt: number;
59
+ staleAt: number;
60
+ }
61
+
62
+ interface CachedItemEntry {
63
+ value: string;
64
+ handles?: string;
65
+ expiresAt: number;
66
+ staleAt: number;
67
+ tags?: string[];
68
+ }
69
+
70
+ interface CachedShellEntry {
71
+ entry: ShellCacheEntry;
72
+ expiresAt: number;
73
+ staleAt: number;
74
+ tags?: string[];
75
+ }
12
76
 
13
77
  /**
14
78
  * Options for MemorySegmentCacheStore
15
79
  */
16
80
  export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
81
+ /**
82
+ * Optional name for this store instance. Named stores persist their
83
+ * backing Map on globalThis so data survives Vite HMR module reloads.
84
+ * Stores with different names get separate Maps.
85
+ *
86
+ * When omitted, the store uses a plain instance-level Map with no
87
+ * globalThis sharing, which is the safest default for isolation.
88
+ *
89
+ * Caveat: two instances constructed with the SAME name share all backing maps
90
+ * (data + tag index), but each keeps its OWN `defaults` and `keyGenerator` from
91
+ * its options - those are not shared. Use one instance per name, or keep the
92
+ * options identical, to avoid surprising divergence.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // Two named stores are isolated from each other
97
+ * const fast = new MemorySegmentCacheStore({ name: "fast", defaults: { ttl: 10 } });
98
+ * const slow = new MemorySegmentCacheStore({ name: "slow", defaults: { ttl: 300 } });
99
+ * ```
100
+ */
101
+ name?: string;
102
+
17
103
  /**
18
104
  * Default cache options for cache() boundaries.
19
105
  * When cache() is called without explicit ttl/swr,
@@ -35,14 +121,14 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
35
121
  * @example
36
122
  * ```typescript
37
123
  * keyGenerator: (ctx, defaultKey) => {
38
- * const locale = ctx.cookie('locale') || 'en';
124
+ * const locale = cookies().get('locale')?.value || 'en';
39
125
  * return `${locale}:${defaultKey}`;
40
126
  * }
41
127
  * ```
42
128
  */
43
129
  keyGenerator?: (
44
130
  ctx: RequestContext<TEnv>,
45
- defaultKey: string
131
+ defaultKey: string,
46
132
  ) => string | Promise<string>;
47
133
  }
48
134
 
@@ -53,6 +139,11 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
53
139
  * For production with multiple instances, use a distributed store
54
140
  * like Cloudflare KV or Redis.
55
141
  *
142
+ * Tag-index cleanup is lazy, mirroring the data maps: a tagged entry that
143
+ * expires but is never re-read or invalidated leaves its forward+reverse index
144
+ * entries resident until the key is reused or invalidated. This is bounded by
145
+ * the distinct-tag count and acceptable for a dev/single-instance store.
146
+ *
56
147
  * @example
57
148
  * ```typescript
58
149
  * // Basic usage
@@ -69,19 +160,57 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
69
160
  * })
70
161
  * ```
71
162
  */
72
- export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
163
+ export class MemorySegmentCacheStore<
164
+ TEnv = unknown,
165
+ > implements SegmentCacheStore<TEnv> {
73
166
  private cache: Map<string, CachedEntryData>;
167
+ private responseCache: Map<string, CachedResponseEntry>;
168
+ private itemCache: Map<string, CachedItemEntry>;
169
+ private shellCache: Map<string, CachedShellEntry>;
170
+ /** tag -> set of prefixed cache keys (seg:key, res:key, item:key, shell:key) */
171
+ private tagIndex: Map<string, Set<string>>;
172
+ /** prefixed cache key -> set of tags (reverse index for O(tags) unregister) */
173
+ private keyTags: Map<string, Set<string>>;
74
174
  readonly defaults?: CacheDefaults;
75
175
  readonly keyGenerator?: (
76
176
  ctx: RequestContext<TEnv>,
77
- defaultKey: string
177
+ defaultKey: string,
78
178
  ) => string | Promise<string>;
79
179
 
80
180
  constructor(options?: MemorySegmentCacheStoreOptions<TEnv>) {
81
- // Use globalThis to survive HMR in development
82
- this.cache =
83
- (globalThis as any)[CACHE_GLOBAL_KEY] ??
84
- ((globalThis as any)[CACHE_GLOBAL_KEY] = new Map<string, CachedEntryData>());
181
+ if (options?.name != null) {
182
+ this.cache = getNamedMap<CachedEntryData>(
183
+ CACHE_REGISTRY_KEY,
184
+ options.name,
185
+ );
186
+ this.responseCache = getNamedMap<CachedResponseEntry>(
187
+ RESPONSE_CACHE_REGISTRY_KEY,
188
+ options.name,
189
+ );
190
+ this.itemCache = getNamedMap<CachedItemEntry>(
191
+ ITEM_CACHE_REGISTRY_KEY,
192
+ options.name,
193
+ );
194
+ this.shellCache = getNamedMap<CachedShellEntry>(
195
+ SHELL_CACHE_REGISTRY_KEY,
196
+ options.name,
197
+ );
198
+ this.tagIndex = getNamedMap<Set<string>>(
199
+ TAG_INDEX_REGISTRY_KEY,
200
+ options.name,
201
+ );
202
+ this.keyTags = getNamedMap<Set<string>>(
203
+ KEY_TAGS_REGISTRY_KEY,
204
+ options.name,
205
+ );
206
+ } else {
207
+ this.cache = new Map<string, CachedEntryData>();
208
+ this.responseCache = new Map<string, CachedResponseEntry>();
209
+ this.itemCache = new Map<string, CachedItemEntry>();
210
+ this.shellCache = new Map<string, CachedShellEntry>();
211
+ this.tagIndex = new Map<string, Set<string>>();
212
+ this.keyTags = new Map<string, Set<string>>();
213
+ }
85
214
  this.defaults = options?.defaults;
86
215
  this.keyGenerator = options?.keyGenerator;
87
216
  }
@@ -95,6 +224,7 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
95
224
 
96
225
  // Check expiration
97
226
  if (Date.now() > cached.expiresAt) {
227
+ this.unregisterTags(`seg:${key}`);
98
228
  this.cache.delete(key);
99
229
  return null;
100
230
  }
@@ -103,28 +233,238 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
103
233
  return { data: cached, shouldRevalidate: false };
104
234
  }
105
235
 
106
- async set(key: string, data: CachedEntryData, ttl: number, _swr?: number): Promise<void> {
107
- // Note: Memory store doesn't implement SWR - entries just expire at TTL
108
- // For SWR support, use CFCacheStore or similar distributed cache
236
+ async set(
237
+ key: string,
238
+ data: CachedEntryData,
239
+ ttl: number,
240
+ _swr?: number,
241
+ ): Promise<void> {
109
242
  const entry: CachedEntryData = {
110
243
  ...data,
111
244
  expiresAt: Date.now() + ttl * 1000,
112
245
  };
246
+ const prefixedKey = `seg:${key}`;
247
+ this.unregisterTags(prefixedKey);
113
248
  this.cache.set(key, entry);
249
+ if (data.tags && data.tags.length > 0) {
250
+ this.registerTags(data.tags, prefixedKey);
251
+ }
114
252
  }
115
253
 
116
254
  async delete(key: string): Promise<boolean> {
255
+ this.unregisterTags(`seg:${key}`);
117
256
  return this.cache.delete(key);
118
257
  }
119
258
 
120
259
  async clear(): Promise<void> {
121
260
  this.cache.clear();
261
+ this.responseCache.clear();
262
+ this.itemCache.clear();
263
+ this.shellCache.clear();
264
+ this.tagIndex.clear();
265
+ this.keyTags.clear();
266
+ }
267
+
268
+ async getResponse(
269
+ key: string,
270
+ ): Promise<{ response: Response; shouldRevalidate: boolean } | null> {
271
+ const cached = this.responseCache.get(key);
272
+ if (!cached) return null;
273
+
274
+ if (Date.now() > cached.expiresAt) {
275
+ this.unregisterTags(`res:${key}`);
276
+ this.responseCache.delete(key);
277
+ return null;
278
+ }
279
+
280
+ const isStale = cached.staleAt > 0 && Date.now() > cached.staleAt;
281
+ const headers = new Headers(cached.headers);
282
+ return {
283
+ response: new Response(cached.body, {
284
+ status: cached.status,
285
+ headers,
286
+ }),
287
+ shouldRevalidate: isStale,
288
+ };
289
+ }
290
+
291
+ async putResponse(
292
+ key: string,
293
+ response: Response,
294
+ ttl: number,
295
+ swr?: number,
296
+ tags?: string[],
297
+ ): Promise<void> {
298
+ try {
299
+ const body = await response.clone().arrayBuffer();
300
+ const headers: [string, string][] = [];
301
+ response.headers.forEach((value, name) => {
302
+ if (isPerClientSignalHeader(name)) return;
303
+ headers.push([name, value]);
304
+ });
305
+
306
+ const swrWindow = resolveSwrWindow(swr, this.defaults);
307
+ const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
308
+
309
+ const prefixedKey = `res:${key}`;
310
+ this.unregisterTags(prefixedKey);
311
+ this.responseCache.set(key, {
312
+ body,
313
+ status: response.status,
314
+ headers,
315
+ expiresAt,
316
+ staleAt,
317
+ });
318
+ if (tags && tags.length > 0) {
319
+ this.registerTags(tags, prefixedKey);
320
+ }
321
+ } catch (error) {
322
+ reportCacheError(
323
+ error,
324
+ "cache-write",
325
+ "[MemorySegmentCacheStore] putResponse",
326
+ );
327
+ }
328
+ }
329
+
330
+ async getItem(key: string): Promise<CacheItemResult | null> {
331
+ const cached = this.itemCache.get(key);
332
+ if (!cached) return null;
333
+
334
+ const now = Date.now();
335
+ if (now > cached.expiresAt) {
336
+ this.unregisterTags(`item:${key}`);
337
+ this.itemCache.delete(key);
338
+ return null;
339
+ }
340
+
341
+ const isStale = now > cached.staleAt;
342
+ return {
343
+ value: cached.value,
344
+ handles: cached.handles,
345
+ shouldRevalidate: isStale,
346
+ tags: cached.tags,
347
+ };
348
+ }
349
+
350
+ async setItem(
351
+ key: string,
352
+ value: string,
353
+ options?: CacheItemOptions,
354
+ ): Promise<void> {
355
+ const ttl = resolveTtl(options?.ttl, this.defaults, DEFAULT_FUNCTION_TTL);
356
+ const swrWindow = resolveSwrWindow(options?.swr, this.defaults);
357
+ const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
358
+ const prefixedKey = `item:${key}`;
359
+ this.unregisterTags(prefixedKey);
360
+ this.itemCache.set(key, {
361
+ value,
362
+ handles: options?.handles,
363
+ expiresAt,
364
+ staleAt,
365
+ tags: options?.tags,
366
+ });
367
+ if (options?.tags && options.tags.length > 0) {
368
+ this.registerTags(options.tags, prefixedKey);
369
+ }
370
+ }
371
+
372
+ async getShell(
373
+ key: string,
374
+ ): Promise<{ entry: ShellCacheEntry; shouldRevalidate?: boolean } | null> {
375
+ const cached = this.shellCache.get(key);
376
+ if (!cached) return null;
377
+
378
+ const now = Date.now();
379
+ if (now > cached.expiresAt) {
380
+ this.unregisterTags(`shell:${key}`);
381
+ this.shellCache.delete(key);
382
+ return null;
383
+ }
384
+
385
+ // SWR mirrors the item family: stale within the swr window still serves, and
386
+ // signals shouldRevalidate so the middleware schedules a background recapture.
387
+ const shouldRevalidate = cached.staleAt > 0 && now > cached.staleAt;
388
+ return { entry: cached.entry, shouldRevalidate };
389
+ }
390
+
391
+ async putShell(
392
+ key: string,
393
+ entry: ShellCacheEntry,
394
+ ttlSeconds?: number,
395
+ swrSeconds?: number,
396
+ tags?: string[],
397
+ ): Promise<void> {
398
+ const ttl = resolveTtl(ttlSeconds, this.defaults, DEFAULT_FUNCTION_TTL);
399
+ const swrWindow = resolveSwrWindow(swrSeconds, this.defaults);
400
+ const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
401
+ const prefixedKey = `shell:${key}`;
402
+ this.unregisterTags(prefixedKey);
403
+ this.shellCache.set(key, { entry, expiresAt, staleAt, tags });
404
+ if (tags && tags.length > 0) {
405
+ this.registerTags(tags, prefixedKey);
406
+ }
407
+ }
408
+
409
+ async invalidateTags(tags: string[]): Promise<void> {
410
+ for (const tag of tags) {
411
+ const keys = this.tagIndex.get(tag);
412
+ if (!keys || keys.size === 0) continue;
413
+
414
+ const prefixedKeys = [...keys];
415
+
416
+ for (const prefixedKey of prefixedKeys) {
417
+ const colonIdx = prefixedKey.indexOf(":");
418
+ const prefix = prefixedKey.slice(0, colonIdx);
419
+ const rawKey = prefixedKey.slice(colonIdx + 1);
420
+
421
+ if (prefix === "seg") {
422
+ this.cache.delete(rawKey);
423
+ } else if (prefix === "res") {
424
+ this.responseCache.delete(rawKey);
425
+ } else if (prefix === "item") {
426
+ this.itemCache.delete(rawKey);
427
+ } else if (prefix === "shell") {
428
+ this.shellCache.delete(rawKey);
429
+ }
430
+
431
+ this.unregisterTags(prefixedKey);
432
+ }
433
+ }
434
+ }
435
+
436
+ private registerTags(tags: string[], prefixedKey: string): void {
437
+ let tagSet = this.keyTags.get(prefixedKey);
438
+ if (!tagSet) {
439
+ tagSet = new Set();
440
+ this.keyTags.set(prefixedKey, tagSet);
441
+ }
442
+ for (const tag of tags) {
443
+ tagSet.add(tag);
444
+ let keys = this.tagIndex.get(tag);
445
+ if (!keys) {
446
+ keys = new Set();
447
+ this.tagIndex.set(tag, keys);
448
+ }
449
+ keys.add(prefixedKey);
450
+ }
451
+ }
452
+
453
+ private unregisterTags(prefixedKey: string): void {
454
+ const tagSet = this.keyTags.get(prefixedKey);
455
+ if (!tagSet) return;
456
+ for (const tag of tagSet) {
457
+ const keys = this.tagIndex.get(tag);
458
+ if (keys) {
459
+ keys.delete(prefixedKey);
460
+ if (keys.size === 0) {
461
+ this.tagIndex.delete(tag);
462
+ }
463
+ }
464
+ }
465
+ this.keyTags.delete(prefixedKey);
122
466
  }
123
467
 
124
- /**
125
- * Get cache statistics for debugging purposes.
126
- * @internal
127
- */
128
468
  getStats(): { size: number; keys: string[] } {
129
469
  return {
130
470
  size: this.cache.size,
@@ -132,19 +472,12 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
132
472
  };
133
473
  }
134
474
 
135
- /**
136
- * Reset the global cache state.
137
- * Useful for test isolation - call this in beforeEach to ensure
138
- * tests don't share cache state via globalThis.
139
- *
140
- * @example
141
- * ```typescript
142
- * beforeEach(() => {
143
- * MemorySegmentCacheStore.resetGlobalCache();
144
- * });
145
- * ```
146
- */
147
475
  static resetGlobalCache(): void {
148
- delete (globalThis as any)[CACHE_GLOBAL_KEY];
476
+ delete (globalThis as any)[CACHE_REGISTRY_KEY];
477
+ delete (globalThis as any)[RESPONSE_CACHE_REGISTRY_KEY];
478
+ delete (globalThis as any)[ITEM_CACHE_REGISTRY_KEY];
479
+ delete (globalThis as any)[SHELL_CACHE_REGISTRY_KEY];
480
+ delete (globalThis as any)[TAG_INDEX_REGISTRY_KEY];
481
+ delete (globalThis as any)[KEY_TAGS_REGISTRY_KEY];
149
482
  }
150
483
  }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Cache profile resolution.
3
+ *
4
+ * Named cache profiles for the "use cache" directive define TTL, SWR, and
5
+ * optional default tags. createRouter() resolves the user's profiles once via
6
+ * resolveCacheProfiles() and threads the resulting map onto each request
7
+ * context; the "use cache: <profile>" runtime path reads it from there
8
+ * (request-scoped) — there is no global registry.
9
+ */
10
+
11
+ export interface CacheProfile {
12
+ /** Time-to-live in seconds */
13
+ ttl: number;
14
+ /** Stale-while-revalidate window in seconds */
15
+ swr?: number;
16
+ /** Default cache tags for invalidation */
17
+ tags?: string[];
18
+ /**
19
+ * When true, a stale entry encountered during a server action's revalidation
20
+ * render is re-executed in the FOREGROUND (the action response reflects the
21
+ * refreshed value) instead of being served stale + revalidated in the
22
+ * background. Only the store write is deferred. Use this for mutation-related
23
+ * cached data that an action should refresh immediately; ordinary navigations
24
+ * always keep SWR. Default false: incidental TTL staleness must not turn an
25
+ * unrelated action into a synchronous cache-refresh barrier — for strong
26
+ * read-your-own-writes after a mutation, prefer updateTag() (a hard purge, so
27
+ * the action's own re-render is a fresh foreground miss).
28
+ *
29
+ * Honored only by `"use cache"` functions; route-level `cache()` and cached
30
+ * loaders ignore it (use updateTag() there).
31
+ */
32
+ foregroundOnAction?: boolean;
33
+ }
34
+
35
+ const DEFAULT_PROFILE: CacheProfile = { ttl: 900, swr: 1800 };
36
+
37
+ const PROFILE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
38
+
39
+ /**
40
+ * Validate and merge user profiles with the default profile.
41
+ * Returns a new object suitable for both DSL-time and request-scoped use.
42
+ *
43
+ * Used by createRouter() to compute the resolved profile map once,
44
+ * stored on the router instance and passed to every request context.
45
+ */
46
+ export function resolveCacheProfiles(
47
+ profiles?: Record<string, CacheProfile>,
48
+ ): Record<string, CacheProfile> {
49
+ const merged: Record<string, CacheProfile> = {
50
+ default: DEFAULT_PROFILE,
51
+ };
52
+ if (profiles) {
53
+ for (const name of Object.keys(profiles)) {
54
+ if (!PROFILE_NAME_RE.test(name)) {
55
+ throw new Error(
56
+ `Invalid cache profile name "${name}". ` +
57
+ `Profile names must match [a-zA-Z0-9_-]+.`,
58
+ );
59
+ }
60
+ const profile = profiles[name];
61
+ // Validate ttl/swr VALUES, not just the name. An unvalidated NaN/Infinity
62
+ // ttl flows into computeExpiration -> staleAt/expiresAt = NaN, and every
63
+ // expiry check (`now > NaN`) is false, so the entry never evicts and never
64
+ // revalidates: it is served fresh forever and accumulates unbounded. A
65
+ // negative ttl makes every read a guaranteed miss. This guard's policy is
66
+ // to FAIL FAST at config time on any non-finite/negative ttl — distinct
67
+ // from prefetch-cache-ttl.ts (falls back to the default) and defer.ts
68
+ // (treats Infinity as an intentional disable). Do not conflate them.
69
+ if (!Number.isFinite(profile.ttl) || profile.ttl < 0) {
70
+ throw new Error(
71
+ `Invalid cache profile "${name}": ttl must be a finite non-negative ` +
72
+ `number (got ${profile.ttl}).`,
73
+ );
74
+ }
75
+ if (
76
+ profile.swr !== undefined &&
77
+ (!Number.isFinite(profile.swr) || profile.swr < 0)
78
+ ) {
79
+ throw new Error(
80
+ `Invalid cache profile "${name}": swr must be a finite non-negative ` +
81
+ `number (got ${profile.swr}).`,
82
+ );
83
+ }
84
+ merged[name] = profile;
85
+ }
86
+ }
87
+ return merged;
88
+ }