@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17

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 (376) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +198 -44
  3. package/dist/bin/rango.js +287 -105
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3248 -1117
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +73 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +107 -1
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +245 -21
  12. package/skills/caching/SKILL.md +302 -6
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +364 -0
  17. package/skills/hooks/SKILL.md +270 -30
  18. package/skills/host-router/SKILL.md +82 -22
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +49 -5
  21. package/skills/layout/SKILL.md +35 -9
  22. package/skills/links/SKILL.md +249 -17
  23. package/skills/loader/SKILL.md +294 -30
  24. package/skills/middleware/SKILL.md +52 -13
  25. package/skills/migrate-nextjs/SKILL.md +584 -0
  26. package/skills/migrate-react-router/SKILL.md +769 -0
  27. package/skills/mime-routes/SKILL.md +27 -0
  28. package/skills/observability/SKILL.md +137 -0
  29. package/skills/parallel/SKILL.md +203 -7
  30. package/skills/prerender/SKILL.md +123 -100
  31. package/skills/rango/SKILL.md +250 -22
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +122 -47
  34. package/skills/route/SKILL.md +97 -5
  35. package/skills/router-setup/SKILL.md +90 -5
  36. package/skills/server-actions/SKILL.md +775 -0
  37. package/skills/streams-and-websockets/SKILL.md +283 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +129 -0
  40. package/skills/testing/bindings.md +89 -0
  41. package/skills/testing/cache-prerender.md +124 -0
  42. package/skills/testing/client-components.md +122 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +92 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +121 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +120 -0
  54. package/skills/typesafety/SKILL.md +329 -27
  55. package/skills/use-cache/SKILL.md +36 -5
  56. package/skills/view-transitions/SKILL.md +294 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +67 -40
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/app-version.ts +14 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +86 -147
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/link-interceptor.ts +4 -0
  70. package/src/browser/navigation-bridge.ts +148 -19
  71. package/src/browser/navigation-client.ts +187 -67
  72. package/src/browser/navigation-store-handle.ts +38 -0
  73. package/src/browser/navigation-store.ts +76 -67
  74. package/src/browser/navigation-transaction.ts +18 -66
  75. package/src/browser/partial-update.ts +123 -94
  76. package/src/browser/prefetch/cache.ts +214 -36
  77. package/src/browser/prefetch/fetch.ts +260 -38
  78. package/src/browser/prefetch/policy.ts +6 -0
  79. package/src/browser/prefetch/queue.ts +126 -20
  80. package/src/browser/prefetch/resource-ready.ts +77 -0
  81. package/src/browser/rango-state.ts +158 -76
  82. package/src/browser/react/Link.tsx +93 -11
  83. package/src/browser/react/NavigationProvider.tsx +115 -34
  84. package/src/browser/react/ScrollRestoration.tsx +10 -6
  85. package/src/browser/react/context.ts +7 -2
  86. package/src/browser/react/filter-segment-order.ts +49 -7
  87. package/src/browser/react/index.ts +0 -48
  88. package/src/browser/react/location-state-shared.ts +166 -8
  89. package/src/browser/react/location-state.ts +39 -14
  90. package/src/browser/react/use-action.ts +6 -15
  91. package/src/browser/react/use-handle.ts +23 -69
  92. package/src/browser/react/use-link-status.ts +0 -4
  93. package/src/browser/react/use-navigation.ts +22 -5
  94. package/src/browser/react/use-params.ts +20 -10
  95. package/src/browser/react/use-reverse.ts +106 -0
  96. package/src/browser/react/use-router.ts +46 -11
  97. package/src/browser/react/use-search-params.ts +0 -5
  98. package/src/browser/react/use-segments.ts +11 -21
  99. package/src/browser/response-adapter.ts +52 -1
  100. package/src/browser/rsc-router.tsx +215 -76
  101. package/src/browser/scroll-restoration.ts +46 -39
  102. package/src/browser/segment-reconciler.ts +36 -9
  103. package/src/browser/segment-structure-assert.ts +2 -2
  104. package/src/browser/server-action-bridge.ts +176 -50
  105. package/src/browser/types.ts +95 -11
  106. package/src/browser/validate-redirect-origin.ts +43 -16
  107. package/src/build/collect-fallback-refs.ts +107 -0
  108. package/src/build/generate-manifest.ts +65 -40
  109. package/src/build/generate-route-types.ts +5 -0
  110. package/src/build/index.ts +8 -2
  111. package/src/build/prefix-tree-utils.ts +123 -0
  112. package/src/build/route-trie.ts +137 -32
  113. package/src/build/route-types/codegen.ts +4 -4
  114. package/src/build/route-types/include-resolution.ts +9 -2
  115. package/src/build/route-types/param-extraction.ts +6 -3
  116. package/src/build/route-types/per-module-writer.ts +7 -4
  117. package/src/build/route-types/router-processing.ts +278 -96
  118. package/src/build/route-types/scan-filter.ts +9 -2
  119. package/src/build/route-types/source-scan.ts +118 -0
  120. package/src/build/runtime-discovery.ts +9 -20
  121. package/src/cache/cache-error.ts +104 -0
  122. package/src/cache/cache-policy.ts +68 -28
  123. package/src/cache/cache-runtime.ts +149 -43
  124. package/src/cache/cache-scope.ts +148 -81
  125. package/src/cache/cache-tag.ts +98 -0
  126. package/src/cache/cf/cf-cache-store.ts +2550 -93
  127. package/src/cache/cf/index.ts +11 -17
  128. package/src/cache/document-cache.ts +78 -27
  129. package/src/cache/handle-snapshot.ts +63 -0
  130. package/src/cache/index.ts +23 -20
  131. package/src/cache/memory-segment-store.ts +136 -37
  132. package/src/cache/profile-registry.ts +6 -30
  133. package/src/cache/read-through-swr.ts +41 -11
  134. package/src/cache/segment-codec.ts +0 -16
  135. package/src/cache/tag-invalidation.ts +230 -0
  136. package/src/cache/taint.ts +55 -0
  137. package/src/cache/types.ts +33 -100
  138. package/src/cache/vercel/index.ts +11 -0
  139. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  140. package/src/client.rsc.tsx +6 -21
  141. package/src/client.tsx +108 -290
  142. package/src/component-utils.ts +19 -0
  143. package/src/context-var.ts +84 -2
  144. package/src/debug.ts +2 -2
  145. package/src/decode-loader-results.ts +36 -0
  146. package/src/defer.ts +196 -0
  147. package/src/deps/ssr.ts +0 -1
  148. package/src/errors.ts +30 -4
  149. package/src/handle.ts +70 -22
  150. package/src/handles/MetaTags.tsx +0 -14
  151. package/src/handles/breadcrumbs.ts +16 -5
  152. package/src/handles/meta.ts +0 -39
  153. package/src/host/cookie-handler.ts +0 -36
  154. package/src/host/errors.ts +0 -24
  155. package/src/host/index.ts +8 -2
  156. package/src/host/pattern-matcher.ts +7 -50
  157. package/src/host/router.ts +107 -99
  158. package/src/host/testing.ts +40 -27
  159. package/src/host/types.ts +37 -4
  160. package/src/host/utils.ts +1 -1
  161. package/src/href-client.ts +137 -22
  162. package/src/index.rsc.ts +52 -26
  163. package/src/index.ts +100 -38
  164. package/src/internal-debug.ts +2 -4
  165. package/src/loader-store.ts +500 -0
  166. package/src/loader.rsc.ts +20 -13
  167. package/src/loader.ts +12 -11
  168. package/src/missing-id-error.ts +68 -0
  169. package/src/network-error-thrower.tsx +1 -6
  170. package/src/outlet-context.ts +1 -1
  171. package/src/outlet-provider.tsx +1 -5
  172. package/src/prerender/param-hash.ts +10 -11
  173. package/src/prerender/store.ts +37 -41
  174. package/src/prerender.ts +198 -82
  175. package/src/redirect-origin.ts +100 -0
  176. package/src/response-utils.ts +37 -0
  177. package/src/reverse.ts +65 -15
  178. package/src/root-error-boundary.tsx +1 -19
  179. package/src/route-content-wrapper.tsx +7 -72
  180. package/src/route-definition/dsl-helpers.ts +437 -274
  181. package/src/route-definition/helper-factories.ts +29 -139
  182. package/src/route-definition/helpers-types.ts +113 -37
  183. package/src/route-definition/index.ts +3 -0
  184. package/src/route-definition/redirect.ts +52 -10
  185. package/src/route-definition/resolve-handler-use.ts +161 -0
  186. package/src/route-definition/use-item-types.ts +32 -0
  187. package/src/route-map-builder.ts +7 -17
  188. package/src/route-types.ts +37 -41
  189. package/src/router/basename.ts +14 -0
  190. package/src/router/content-negotiation.ts +108 -9
  191. package/src/router/error-handling.ts +13 -17
  192. package/src/router/find-match.ts +45 -22
  193. package/src/router/handler-context.ts +83 -41
  194. package/src/router/intercept-resolution.ts +25 -23
  195. package/src/router/lazy-includes.ts +19 -53
  196. package/src/router/loader-resolution.ts +213 -30
  197. package/src/router/logging.ts +5 -8
  198. package/src/router/manifest.ts +49 -45
  199. package/src/router/match-api.ts +121 -205
  200. package/src/router/match-context.ts +0 -22
  201. package/src/router/match-handlers.ts +58 -58
  202. package/src/router/match-middleware/background-revalidation.ts +27 -6
  203. package/src/router/match-middleware/cache-lookup.ts +205 -249
  204. package/src/router/match-middleware/cache-store.ts +45 -32
  205. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  206. package/src/router/match-middleware/segment-resolution.ts +52 -18
  207. package/src/router/match-pipelines.ts +1 -42
  208. package/src/router/match-result.ts +104 -40
  209. package/src/router/metrics.ts +5 -34
  210. package/src/router/middleware-types.ts +13 -142
  211. package/src/router/middleware.ts +173 -143
  212. package/src/router/navigation-snapshot.ts +131 -0
  213. package/src/router/params-util.ts +23 -0
  214. package/src/router/pattern-matching.ts +109 -63
  215. package/src/router/prerender-match.ts +192 -54
  216. package/src/router/preview-match.ts +32 -102
  217. package/src/router/request-classification.ts +276 -0
  218. package/src/router/revalidation.ts +63 -55
  219. package/src/router/route-snapshot.ts +244 -0
  220. package/src/router/router-context.ts +6 -28
  221. package/src/router/router-interfaces.ts +100 -35
  222. package/src/router/router-options.ts +91 -11
  223. package/src/router/router-registry.ts +2 -5
  224. package/src/router/segment-resolution/fresh.ts +242 -75
  225. package/src/router/segment-resolution/helpers.ts +64 -25
  226. package/src/router/segment-resolution/loader-cache.ts +41 -37
  227. package/src/router/segment-resolution/revalidation.ts +456 -372
  228. package/src/router/segment-resolution/static-store.ts +19 -5
  229. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  230. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  231. package/src/router/segment-resolution.ts +4 -1
  232. package/src/router/segment-wrappers.ts +2 -3
  233. package/src/router/state-cookie-name.ts +33 -0
  234. package/src/router/substitute-pattern-params.ts +56 -0
  235. package/src/router/telemetry-otel.ts +0 -20
  236. package/src/router/telemetry.ts +96 -19
  237. package/src/router/timeout.ts +0 -20
  238. package/src/router/trie-matching.ts +91 -46
  239. package/src/router/types.ts +10 -63
  240. package/src/router/url-params.ts +44 -0
  241. package/src/router.ts +134 -43
  242. package/src/rsc/handler-context.ts +3 -2
  243. package/src/rsc/handler.ts +492 -383
  244. package/src/rsc/helpers.ts +162 -46
  245. package/src/rsc/index.ts +1 -1
  246. package/src/rsc/json-route-result.ts +38 -0
  247. package/src/rsc/loader-fetch.ts +23 -3
  248. package/src/rsc/manifest-init.ts +33 -42
  249. package/src/rsc/origin-guard.ts +39 -25
  250. package/src/rsc/progressive-enhancement.ts +30 -3
  251. package/src/rsc/redirect-guard.ts +99 -0
  252. package/src/rsc/response-error.ts +79 -12
  253. package/src/rsc/response-route-handler.ts +90 -63
  254. package/src/rsc/rsc-rendering.ts +56 -54
  255. package/src/rsc/runtime-warnings.ts +23 -10
  256. package/src/rsc/server-action.ts +74 -67
  257. package/src/rsc/ssr-setup.ts +18 -2
  258. package/src/rsc/types.ts +25 -6
  259. package/src/runtime-env.ts +18 -0
  260. package/src/search-params.ts +4 -20
  261. package/src/segment-content-promise.ts +67 -0
  262. package/src/segment-loader-promise.ts +134 -0
  263. package/src/segment-system.tsx +272 -129
  264. package/src/serialize.ts +243 -0
  265. package/src/server/context.ts +309 -61
  266. package/src/server/cookie-store.ts +80 -5
  267. package/src/server/handle-store.ts +26 -24
  268. package/src/server/loader-registry.ts +10 -28
  269. package/src/server/request-context.ts +348 -128
  270. package/src/ssr/index.tsx +23 -15
  271. package/src/static-handler.ts +27 -18
  272. package/src/testing/cache-status.ts +162 -0
  273. package/src/testing/collect-handle.ts +40 -0
  274. package/src/testing/dispatch.ts +618 -0
  275. package/src/testing/dom.entry.ts +22 -0
  276. package/src/testing/e2e/fixture.ts +188 -0
  277. package/src/testing/e2e/index.ts +128 -0
  278. package/src/testing/e2e/matchers.ts +35 -0
  279. package/src/testing/e2e/page-helpers.ts +272 -0
  280. package/src/testing/e2e/parity.ts +387 -0
  281. package/src/testing/e2e/server.ts +195 -0
  282. package/src/testing/flight-matchers.ts +97 -0
  283. package/src/testing/flight-normalize.ts +11 -0
  284. package/src/testing/flight-runtime.d.ts +57 -0
  285. package/src/testing/flight-tree.ts +682 -0
  286. package/src/testing/flight.entry.ts +52 -0
  287. package/src/testing/flight.ts +232 -0
  288. package/src/testing/generated-routes.ts +183 -0
  289. package/src/testing/index.ts +99 -0
  290. package/src/testing/internal/context.ts +348 -0
  291. package/src/testing/internal/flight-client-globals.ts +30 -0
  292. package/src/testing/internal/seed-vars.ts +54 -0
  293. package/src/testing/render-handler.ts +330 -0
  294. package/src/testing/render-route.tsx +566 -0
  295. package/src/testing/run-loader.ts +378 -0
  296. package/src/testing/run-middleware.ts +205 -0
  297. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  298. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  299. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  300. package/src/testing/vitest-stubs/version.ts +5 -0
  301. package/src/testing/vitest.ts +305 -0
  302. package/src/theme/ThemeProvider.tsx +0 -52
  303. package/src/theme/ThemeScript.tsx +0 -6
  304. package/src/theme/constants.ts +0 -12
  305. package/src/theme/index.ts +0 -7
  306. package/src/theme/theme-context.ts +1 -5
  307. package/src/theme/theme-script.ts +0 -14
  308. package/src/theme/use-theme.ts +0 -3
  309. package/src/types/boundaries.ts +0 -35
  310. package/src/types/cache-types.ts +17 -8
  311. package/src/types/error-types.ts +30 -90
  312. package/src/types/global-namespace.ts +54 -41
  313. package/src/types/handler-context.ts +233 -81
  314. package/src/types/index.ts +1 -10
  315. package/src/types/loader-types.ts +44 -15
  316. package/src/types/request-scope.ts +107 -0
  317. package/src/types/route-config.ts +6 -50
  318. package/src/types/route-entry.ts +19 -7
  319. package/src/types/segments.ts +37 -14
  320. package/src/urls/include-helper.ts +33 -70
  321. package/src/urls/index.ts +1 -11
  322. package/src/urls/path-helper-types.ts +58 -11
  323. package/src/urls/path-helper.ts +57 -111
  324. package/src/urls/pattern-types.ts +48 -19
  325. package/src/urls/response-types.ts +25 -22
  326. package/src/urls/type-extraction.ts +58 -139
  327. package/src/urls/urls-function.ts +1 -18
  328. package/src/use-loader.tsx +346 -89
  329. package/src/vite/debug.ts +185 -0
  330. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  331. package/src/vite/discovery/discover-routers.ts +130 -85
  332. package/src/vite/discovery/discovery-errors.ts +194 -0
  333. package/src/vite/discovery/gate-state.ts +171 -0
  334. package/src/vite/discovery/prerender-collection.ts +192 -99
  335. package/src/vite/discovery/route-types-writer.ts +40 -84
  336. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  337. package/src/vite/discovery/state.ts +51 -6
  338. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  339. package/src/vite/index.ts +8 -0
  340. package/src/vite/plugin-types.ts +187 -69
  341. package/src/vite/plugins/cjs-to-esm.ts +8 -18
  342. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  343. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  344. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  345. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  346. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  347. package/src/vite/plugins/expose-action-id.ts +49 -98
  348. package/src/vite/plugins/expose-id-utils.ts +11 -50
  349. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  350. package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
  351. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  352. package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
  353. package/src/vite/plugins/expose-internal-ids.ts +554 -317
  354. package/src/vite/plugins/performance-tracks.ts +89 -0
  355. package/src/vite/plugins/refresh-cmd.ts +89 -27
  356. package/src/vite/plugins/use-cache-transform.ts +73 -83
  357. package/src/vite/plugins/vercel-output.ts +258 -0
  358. package/src/vite/plugins/version-injector.ts +21 -25
  359. package/src/vite/plugins/version-plugin.ts +41 -20
  360. package/src/vite/plugins/virtual-entries.ts +2 -17
  361. package/src/vite/rango.ts +257 -289
  362. package/src/vite/router-discovery.ts +930 -140
  363. package/src/vite/utils/ast-handler-extract.ts +15 -31
  364. package/src/vite/utils/banner.ts +4 -4
  365. package/src/vite/utils/bundle-analysis.ts +10 -15
  366. package/src/vite/utils/client-chunks.ts +184 -0
  367. package/src/vite/utils/forward-user-plugins.ts +171 -0
  368. package/src/vite/utils/manifest-utils.ts +4 -59
  369. package/src/vite/utils/package-resolution.ts +20 -52
  370. package/src/vite/utils/prerender-utils.ts +27 -29
  371. package/src/vite/utils/shared-utils.ts +92 -42
  372. package/src/browser/action-response-classifier.ts +0 -99
  373. package/src/browser/react/use-client-cache.ts +0 -58
  374. package/src/browser/shallow.ts +0 -40
  375. package/src/handles/index.ts +0 -7
  376. package/src/router/middleware-cookies.ts +0 -55
@@ -12,19 +12,22 @@ import type {
12
12
  CacheGetResult,
13
13
  CacheItemResult,
14
14
  CacheItemOptions,
15
- SegmentHandleData,
16
15
  } from "./types.js";
17
16
  import type { RequestContext } from "../server/request-context.js";
17
+ import { isPerClientSignalHeader } from "../browser/cookie-name.js";
18
18
  import {
19
19
  resolveTtl,
20
20
  resolveSwrWindow,
21
21
  computeExpiration,
22
22
  DEFAULT_FUNCTION_TTL,
23
23
  } from "./cache-policy.js";
24
+ import { reportCacheError } from "./cache-error.js";
24
25
 
25
26
  const CACHE_REGISTRY_KEY = "__rsc_router_segment_cache_registry__";
26
27
  const RESPONSE_CACHE_REGISTRY_KEY = "__rsc_router_response_cache_registry__";
27
28
  const ITEM_CACHE_REGISTRY_KEY = "__rsc_router_item_cache_registry__";
29
+ const TAG_INDEX_REGISTRY_KEY = "__rsc_router_tag_index_registry__";
30
+ const KEY_TAGS_REGISTRY_KEY = "__rsc_router_key_tags_registry__";
28
31
 
29
32
  /**
30
33
  * Get or create a named Map from a globalThis-backed registry.
@@ -56,9 +59,10 @@ interface CachedResponseEntry {
56
59
 
57
60
  interface CachedItemEntry {
58
61
  value: string;
59
- handles?: Record<string, SegmentHandleData>;
62
+ handles?: string;
60
63
  expiresAt: number;
61
64
  staleAt: number;
65
+ tags?: string[];
62
66
  }
63
67
 
64
68
  /**
@@ -73,6 +77,11 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
73
77
  * When omitted, the store uses a plain instance-level Map with no
74
78
  * globalThis sharing, which is the safest default for isolation.
75
79
  *
80
+ * Caveat: two instances constructed with the SAME name share all backing maps
81
+ * (data + tag index), but each keeps its OWN `defaults` and `keyGenerator` from
82
+ * its options - those are not shared. Use one instance per name, or keep the
83
+ * options identical, to avoid surprising divergence.
84
+ *
76
85
  * @example
77
86
  * ```typescript
78
87
  * // Two named stores are isolated from each other
@@ -121,6 +130,11 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
121
130
  * For production with multiple instances, use a distributed store
122
131
  * like Cloudflare KV or Redis.
123
132
  *
133
+ * Tag-index cleanup is lazy, mirroring the data maps: a tagged entry that
134
+ * expires but is never re-read or invalidated leaves its forward+reverse index
135
+ * entries resident until the key is reused or invalidated. This is bounded by
136
+ * the distinct-tag count and acceptable for a dev/single-instance store.
137
+ *
124
138
  * @example
125
139
  * ```typescript
126
140
  * // Basic usage
@@ -143,6 +157,10 @@ export class MemorySegmentCacheStore<
143
157
  private cache: Map<string, CachedEntryData>;
144
158
  private responseCache: Map<string, CachedResponseEntry>;
145
159
  private itemCache: Map<string, CachedItemEntry>;
160
+ /** tag -> set of prefixed cache keys (seg:key, res:key, item:key) */
161
+ private tagIndex: Map<string, Set<string>>;
162
+ /** prefixed cache key -> set of tags (reverse index for O(tags) unregister) */
163
+ private keyTags: Map<string, Set<string>>;
146
164
  readonly defaults?: CacheDefaults;
147
165
  readonly keyGenerator?: (
148
166
  ctx: RequestContext<TEnv>,
@@ -151,8 +169,6 @@ export class MemorySegmentCacheStore<
151
169
 
152
170
  constructor(options?: MemorySegmentCacheStoreOptions<TEnv>) {
153
171
  if (options?.name != null) {
154
- // Named stores use the globalThis registry so data survives HMR.
155
- // Each name gets its own isolated Map.
156
172
  this.cache = getNamedMap<CachedEntryData>(
157
173
  CACHE_REGISTRY_KEY,
158
174
  options.name,
@@ -165,11 +181,20 @@ export class MemorySegmentCacheStore<
165
181
  ITEM_CACHE_REGISTRY_KEY,
166
182
  options.name,
167
183
  );
184
+ this.tagIndex = getNamedMap<Set<string>>(
185
+ TAG_INDEX_REGISTRY_KEY,
186
+ options.name,
187
+ );
188
+ this.keyTags = getNamedMap<Set<string>>(
189
+ KEY_TAGS_REGISTRY_KEY,
190
+ options.name,
191
+ );
168
192
  } else {
169
- // Unnamed stores get a plain instance-level Map (no globalThis sharing).
170
193
  this.cache = new Map<string, CachedEntryData>();
171
194
  this.responseCache = new Map<string, CachedResponseEntry>();
172
195
  this.itemCache = new Map<string, CachedItemEntry>();
196
+ this.tagIndex = new Map<string, Set<string>>();
197
+ this.keyTags = new Map<string, Set<string>>();
173
198
  }
174
199
  this.defaults = options?.defaults;
175
200
  this.keyGenerator = options?.keyGenerator;
@@ -184,6 +209,7 @@ export class MemorySegmentCacheStore<
184
209
 
185
210
  // Check expiration
186
211
  if (Date.now() > cached.expiresAt) {
212
+ this.unregisterTags(`seg:${key}`);
187
213
  this.cache.delete(key);
188
214
  return null;
189
215
  }
@@ -198,16 +224,20 @@ export class MemorySegmentCacheStore<
198
224
  ttl: number,
199
225
  _swr?: number,
200
226
  ): Promise<void> {
201
- // Note: Memory store doesn't implement SWR - entries just expire at TTL
202
- // For SWR support, use CFCacheStore or similar distributed cache
203
227
  const entry: CachedEntryData = {
204
228
  ...data,
205
229
  expiresAt: Date.now() + ttl * 1000,
206
230
  };
231
+ const prefixedKey = `seg:${key}`;
232
+ this.unregisterTags(prefixedKey);
207
233
  this.cache.set(key, entry);
234
+ if (data.tags && data.tags.length > 0) {
235
+ this.registerTags(data.tags, prefixedKey);
236
+ }
208
237
  }
209
238
 
210
239
  async delete(key: string): Promise<boolean> {
240
+ this.unregisterTags(`seg:${key}`);
211
241
  return this.cache.delete(key);
212
242
  }
213
243
 
@@ -215,6 +245,8 @@ export class MemorySegmentCacheStore<
215
245
  this.cache.clear();
216
246
  this.responseCache.clear();
217
247
  this.itemCache.clear();
248
+ this.tagIndex.clear();
249
+ this.keyTags.clear();
218
250
  }
219
251
 
220
252
  async getResponse(
@@ -224,6 +256,7 @@ export class MemorySegmentCacheStore<
224
256
  if (!cached) return null;
225
257
 
226
258
  if (Date.now() > cached.expiresAt) {
259
+ this.unregisterTags(`res:${key}`);
227
260
  this.responseCache.delete(key);
228
261
  return null;
229
262
  }
@@ -244,23 +277,38 @@ export class MemorySegmentCacheStore<
244
277
  response: Response,
245
278
  ttl: number,
246
279
  swr?: number,
280
+ tags?: string[],
247
281
  ): Promise<void> {
248
- const body = await response.clone().arrayBuffer();
249
- const headers: [string, string][] = [];
250
- response.headers.forEach((value, name) => {
251
- headers.push([name, value]);
252
- });
282
+ try {
283
+ const body = await response.clone().arrayBuffer();
284
+ const headers: [string, string][] = [];
285
+ response.headers.forEach((value, name) => {
286
+ if (isPerClientSignalHeader(name)) return;
287
+ headers.push([name, value]);
288
+ });
253
289
 
254
- const swrWindow = resolveSwrWindow(swr, this.defaults);
255
- const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
290
+ const swrWindow = resolveSwrWindow(swr, this.defaults);
291
+ const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
256
292
 
257
- this.responseCache.set(key, {
258
- body,
259
- status: response.status,
260
- headers,
261
- expiresAt,
262
- staleAt,
263
- });
293
+ const prefixedKey = `res:${key}`;
294
+ this.unregisterTags(prefixedKey);
295
+ this.responseCache.set(key, {
296
+ body,
297
+ status: response.status,
298
+ headers,
299
+ expiresAt,
300
+ staleAt,
301
+ });
302
+ if (tags && tags.length > 0) {
303
+ this.registerTags(tags, prefixedKey);
304
+ }
305
+ } catch (error) {
306
+ reportCacheError(
307
+ error,
308
+ "cache-write",
309
+ "[MemorySegmentCacheStore] putResponse",
310
+ );
311
+ }
264
312
  }
265
313
 
266
314
  async getItem(key: string): Promise<CacheItemResult | null> {
@@ -269,6 +317,7 @@ export class MemorySegmentCacheStore<
269
317
 
270
318
  const now = Date.now();
271
319
  if (now > cached.expiresAt) {
320
+ this.unregisterTags(`item:${key}`);
272
321
  this.itemCache.delete(key);
273
322
  return null;
274
323
  }
@@ -278,6 +327,7 @@ export class MemorySegmentCacheStore<
278
327
  value: cached.value,
279
328
  handles: cached.handles,
280
329
  shouldRevalidate: isStale,
330
+ tags: cached.tags,
281
331
  };
282
332
  }
283
333
 
@@ -289,18 +339,77 @@ export class MemorySegmentCacheStore<
289
339
  const ttl = resolveTtl(options?.ttl, this.defaults, DEFAULT_FUNCTION_TTL);
290
340
  const swrWindow = resolveSwrWindow(options?.swr, this.defaults);
291
341
  const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
342
+ const prefixedKey = `item:${key}`;
343
+ this.unregisterTags(prefixedKey);
292
344
  this.itemCache.set(key, {
293
345
  value,
294
346
  handles: options?.handles,
295
347
  expiresAt,
296
348
  staleAt,
349
+ tags: options?.tags,
297
350
  });
351
+ if (options?.tags && options.tags.length > 0) {
352
+ this.registerTags(options.tags, prefixedKey);
353
+ }
354
+ }
355
+
356
+ async invalidateTags(tags: string[]): Promise<void> {
357
+ for (const tag of tags) {
358
+ const keys = this.tagIndex.get(tag);
359
+ if (!keys || keys.size === 0) continue;
360
+
361
+ const prefixedKeys = [...keys];
362
+
363
+ for (const prefixedKey of prefixedKeys) {
364
+ const colonIdx = prefixedKey.indexOf(":");
365
+ const prefix = prefixedKey.slice(0, colonIdx);
366
+ const rawKey = prefixedKey.slice(colonIdx + 1);
367
+
368
+ if (prefix === "seg") {
369
+ this.cache.delete(rawKey);
370
+ } else if (prefix === "res") {
371
+ this.responseCache.delete(rawKey);
372
+ } else if (prefix === "item") {
373
+ this.itemCache.delete(rawKey);
374
+ }
375
+
376
+ this.unregisterTags(prefixedKey);
377
+ }
378
+ }
379
+ }
380
+
381
+ private registerTags(tags: string[], prefixedKey: string): void {
382
+ let tagSet = this.keyTags.get(prefixedKey);
383
+ if (!tagSet) {
384
+ tagSet = new Set();
385
+ this.keyTags.set(prefixedKey, tagSet);
386
+ }
387
+ for (const tag of tags) {
388
+ tagSet.add(tag);
389
+ let keys = this.tagIndex.get(tag);
390
+ if (!keys) {
391
+ keys = new Set();
392
+ this.tagIndex.set(tag, keys);
393
+ }
394
+ keys.add(prefixedKey);
395
+ }
396
+ }
397
+
398
+ private unregisterTags(prefixedKey: string): void {
399
+ const tagSet = this.keyTags.get(prefixedKey);
400
+ if (!tagSet) return;
401
+ for (const tag of tagSet) {
402
+ const keys = this.tagIndex.get(tag);
403
+ if (keys) {
404
+ keys.delete(prefixedKey);
405
+ if (keys.size === 0) {
406
+ this.tagIndex.delete(tag);
407
+ }
408
+ }
409
+ }
410
+ this.keyTags.delete(prefixedKey);
298
411
  }
299
412
 
300
- /**
301
- * Get cache statistics for debugging purposes.
302
- * @internal
303
- */
304
413
  getStats(): { size: number; keys: string[] } {
305
414
  return {
306
415
  size: this.cache.size,
@@ -308,21 +417,11 @@ export class MemorySegmentCacheStore<
308
417
  };
309
418
  }
310
419
 
311
- /**
312
- * Reset the global cache registry.
313
- * Useful for test isolation - call this in beforeEach to ensure
314
- * tests don't share cache state via globalThis.
315
- *
316
- * @example
317
- * ```typescript
318
- * beforeEach(() => {
319
- * MemorySegmentCacheStore.resetGlobalCache();
320
- * });
321
- * ```
322
- */
323
420
  static resetGlobalCache(): void {
324
421
  delete (globalThis as any)[CACHE_REGISTRY_KEY];
325
422
  delete (globalThis as any)[RESPONSE_CACHE_REGISTRY_KEY];
326
423
  delete (globalThis as any)[ITEM_CACHE_REGISTRY_KEY];
424
+ delete (globalThis as any)[TAG_INDEX_REGISTRY_KEY];
425
+ delete (globalThis as any)[KEY_TAGS_REGISTRY_KEY];
327
426
  }
328
427
  }
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Cache Profile Registry
2
+ * Cache profile resolution.
3
3
  *
4
- * Named cache profiles for "use cache" directive.
5
- * Profiles define TTL, SWR, and optional default tags.
6
- * Set by createRouter() at startup, read by registerCachedFunction() at runtime.
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.
7
9
  */
8
10
 
9
11
  export interface CacheProfile {
@@ -17,10 +19,6 @@ export interface CacheProfile {
17
19
 
18
20
  const DEFAULT_PROFILE: CacheProfile = { ttl: 900, swr: 1800 };
19
21
 
20
- let _profiles: Record<string, CacheProfile> = {
21
- default: DEFAULT_PROFILE,
22
- };
23
-
24
22
  const PROFILE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
25
23
 
26
24
  /**
@@ -49,25 +47,3 @@ export function resolveCacheProfiles(
49
47
  }
50
48
  return merged;
51
49
  }
52
-
53
- /**
54
- * Set all cache profiles in the global registry.
55
- * Called by createRouter() at startup for DSL-time resolution
56
- * (cache("profileName") reads from this during route definition).
57
- *
58
- * WARNING: This is global mutable state. It exists only for DSL-time
59
- * reads. Runtime resolution (registerCachedFunction) uses request-scoped
60
- * profiles and does NOT read from this registry.
61
- */
62
- export function setCacheProfiles(profiles: Record<string, CacheProfile>): void {
63
- _profiles = resolveCacheProfiles(profiles);
64
- }
65
-
66
- /**
67
- * Get a cache profile by name from the global registry.
68
- * Used only at DSL-time (cache("profileName") inside urls() evaluation).
69
- * Runtime code uses request-scoped profiles instead.
70
- */
71
- export function getCacheProfile(name: string): CacheProfile | undefined {
72
- return _profiles[name];
73
- }
@@ -13,10 +13,15 @@
13
13
 
14
14
  import type { CacheItemResult, CacheItemOptions } from "./types.js";
15
15
  import { runBackground } from "./background-task.js";
16
+ import { reportCacheError } from "./cache-error.js";
17
+ import type { CacheErrorReporter } from "./cache-error.js";
16
18
 
17
- interface WaitUntilHost {
19
+ // The host carries both the optional waitUntil (for background scheduling) and
20
+ // the CacheErrorReporter seam (for routing degradation errors through onError).
21
+ // loader-cache.ts passes the request context, which provides both.
22
+ type WaitUntilHost = {
18
23
  waitUntil?: (fn: () => Promise<void>) => void;
19
- }
24
+ } & CacheErrorReporter;
20
25
 
21
26
  export interface ReadThroughItemConfig<T> {
22
27
  /** Retrieve a cached item by key */
@@ -74,11 +79,20 @@ export async function readThroughItem<T>(
74
79
  host,
75
80
  } = config;
76
81
 
77
- // Cache lookup
82
+ // Cache lookup. An infra read failure (getItem) is reported by the store
83
+ // itself, so here we just degrade to a miss. A deserialize failure is a
84
+ // corrupt/truncated stored entry, which this layer owns: report it LOUD as
85
+ // cache-corrupt, then fall through to a fresh execution (the miss-path write
86
+ // self-heals the bad entry).
87
+ let cached: CacheItemResult | null = null;
78
88
  try {
79
- const cached = await getItem(key);
89
+ cached = await getItem(key);
90
+ } catch {
91
+ cached = null;
92
+ }
80
93
 
81
- if (cached) {
94
+ if (cached) {
95
+ try {
82
96
  const data = await deserialize(cached.value);
83
97
 
84
98
  if (!cached.shouldRevalidate) {
@@ -97,16 +111,27 @@ export async function readThroughItem<T>(
97
111
  if (serialized !== null) {
98
112
  await setItem(key, serialized, storeOptions);
99
113
  }
100
- } catch {
101
- // Background revalidation failed silently
114
+ } catch (error) {
115
+ reportCacheError(
116
+ error,
117
+ "stale-revalidation",
118
+ "[read-through] background revalidation",
119
+ host ?? undefined,
120
+ );
102
121
  }
103
122
  },
104
123
  true,
105
124
  );
106
125
  return data;
126
+ } catch (error) {
127
+ reportCacheError(
128
+ error,
129
+ "cache-corrupt",
130
+ "[read-through] deserialize stored entry",
131
+ host ?? undefined,
132
+ );
133
+ // fall through to fresh execution
107
134
  }
108
- } catch {
109
- // Cache lookup failed, fall through to fresh execution
110
135
  }
111
136
 
112
137
  // Cache miss
@@ -123,8 +148,13 @@ export async function readThroughItem<T>(
123
148
  await setItem(key, serialized, storeOptions);
124
149
  onCached?.();
125
150
  }
126
- } catch {
127
- // Cache write failed silently
151
+ } catch (error) {
152
+ reportCacheError(
153
+ error,
154
+ "cache-write",
155
+ "[read-through] cache write",
156
+ host ?? undefined,
157
+ );
128
158
  }
129
159
  },
130
160
  true,
@@ -16,10 +16,6 @@ import {
16
16
  } from "@vitejs/plugin-rsc/rsc";
17
17
  import { createFromReadableStream } from "@vitejs/plugin-rsc/rsc";
18
18
 
19
- // ============================================================================
20
- // Stream Utilities (internal)
21
- // ============================================================================
22
-
23
19
  /**
24
20
  * Convert a ReadableStream to a string.
25
21
  */
@@ -55,10 +51,6 @@ export function stringToStream(str: string): ReadableStream<Uint8Array> {
55
51
  });
56
52
  }
57
53
 
58
- // ============================================================================
59
- // RSC Serialization Primitives (internal)
60
- // ============================================================================
61
-
62
54
  /**
63
55
  * RSC-serialize a value using React Server Components stream.
64
56
  * Used for serializing loaderData, layout, loading components etc.
@@ -90,10 +82,6 @@ export async function rscDeserialize<T>(
90
82
  return createFromReadableStream<T>(stream, { temporaryReferences });
91
83
  }
92
84
 
93
- // ============================================================================
94
- // Null-Preserving RSC Serialization (for caching)
95
- // ============================================================================
96
-
97
85
  /**
98
86
  * RSC-serialize any value including null.
99
87
  * Unlike rscSerialize(), this does NOT skip null — it serializes it through
@@ -122,10 +110,6 @@ export async function deserializeResult<T>(encoded: string): Promise<T> {
122
110
  return createFromReadableStream<T>(stream, { temporaryReferences });
123
111
  }
124
112
 
125
- // ============================================================================
126
- // Public API
127
- // ============================================================================
128
-
129
113
  /**
130
114
  * RSC-deserialize a single encoded component string back to a React element.
131
115
  * Used by the static handler runtime to revive pre-rendered components.