@rangojs/router 0.0.0-experimental.32 → 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 +120 -204
  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 +190 -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 +63 -24
  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 +338 -126
  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
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Cache Tag Invalidation API
3
+ *
4
+ * Two on-demand invalidation verbs, mirroring the distinction popularized by
5
+ * Next.js so consumers can pick the right consistency model:
6
+ *
7
+ * - updateTag(...tags): read-your-own-writes. Awaitable - resolves only after
8
+ * in-process invalidation across every configured store completes. Use in a
9
+ * Server Action and `await` it before the action re-renders, so the action's
10
+ * own response reflects the mutation.
11
+ *
12
+ * - revalidateTag(...tags): fire-and-forget via waitUntil - the response is not
13
+ * blocked. Use in Route Handlers / webhooks. NOTE: both verbs hard-purge; the
14
+ * only difference is awaitability. revalidateTag does NOT serve stale content -
15
+ * the next read after the invalidation lands is a hard miss that re-renders.
16
+ * (The name mirrors Next.js, where it is SWR; here it is background-purge.)
17
+ *
18
+ * Both fan out across the app-level store (ctx._cacheStore) and every explicit
19
+ * per-scope store from cache({ store }) registered for this handler
20
+ * (ctx._explicitTaggedStores), calling the store-level invalidateTags()
21
+ * primitive for each tag. A single configured store (the common case) owns its
22
+ * own tag index and distributed invalidation - there is no separate
23
+ * tag-invalidation store.
24
+ */
25
+
26
+ import { _getRequestContext } from "../server/request-context.js";
27
+ import { reportingAsync } from "./cache-error.js";
28
+ import { normalizeTags } from "./cache-tag.js";
29
+ import type { SegmentCacheStore } from "./types.js";
30
+
31
+ /**
32
+ * Collect every store that may hold entries tagged for this request's handler:
33
+ * the app-level store plus all explicit per-scope stores (deduplicated). Splits
34
+ * them into tag-capable (implement invalidateTags()) and not, so callers can
35
+ * warn about configured stores whose tagged entries will NOT be invalidated.
36
+ *
37
+ * `hasContext` reports whether an ALS request context existed at all. Without one
38
+ * (e.g. a queue consumer or cron job calling updateTag/revalidateTag) no stores
39
+ * are reachable, and the empty-capable case is a missing-context problem, not a
40
+ * store-config problem - callers branch on this to warn about the right cause.
41
+ */
42
+ function collectStores(): {
43
+ capable: SegmentCacheStore[];
44
+ incapable: number;
45
+ hasContext: boolean;
46
+ } {
47
+ const ctx = _getRequestContext();
48
+ const stores = new Set<SegmentCacheStore>();
49
+ if (ctx?._cacheStore) stores.add(ctx._cacheStore);
50
+ if (ctx?._explicitTaggedStores) {
51
+ for (const store of ctx._explicitTaggedStores) stores.add(store);
52
+ }
53
+ const capable: SegmentCacheStore[] = [];
54
+ let incapable = 0;
55
+ for (const store of stores) {
56
+ if (typeof store.invalidateTags === "function") capable.push(store);
57
+ else incapable++;
58
+ }
59
+ return { capable, incapable, hasContext: ctx != null };
60
+ }
61
+
62
+ /**
63
+ * Production-visible warning. A misconfigured store silently dropping
64
+ * invalidations is a data-correctness footgun, so this surfaces in every
65
+ * environment (not dev-only).
66
+ */
67
+ function warnNoTagStore(fn: string, tags: string[]): void {
68
+ console.warn(
69
+ `[${fn}] No tag-capable cache store is configured; tags ` +
70
+ `[${tags.join(", ")}] were not invalidated. The configured store must ` +
71
+ `implement invalidateTags() (the built-in MemorySegmentCacheStore and ` +
72
+ `CFCacheStore do).`,
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Production-visible warning for the no-request-context case. Distinct from
78
+ * warnNoTagStore: the stores are not unreachable because they are misconfigured,
79
+ * but because there is no ALS request context to reach them through (e.g. a queue
80
+ * consumer or scheduled job). Naming the real cause keeps consumers from chasing
81
+ * a store-config red herring.
82
+ */
83
+ function warnNoRequestContext(fn: string, tags: string[]): void {
84
+ console.warn(
85
+ `[${fn}] Called outside a request context (e.g. from a queue consumer or ` +
86
+ `scheduled job); no cache stores are reachable and tags ` +
87
+ `[${tags.join(", ")}] were not invalidated. Invoke it within a request ` +
88
+ `(Server Action or route handler).`,
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Production-visible warning for mixed-store configs: at least one configured
94
+ * store does not support tag invalidation, so its tagged entries (if any) are
95
+ * left stale even though other stores were invalidated.
96
+ */
97
+ function warnPartialTagStore(fn: string, incapable: number): void {
98
+ console.warn(
99
+ `[${fn}] ${incapable} configured cache store(s) do not implement ` +
100
+ `invalidateTags(); their tagged entries were NOT invalidated. Use a ` +
101
+ `tag-capable store (e.g. MemorySegmentCacheStore / CFCacheStore) for any ` +
102
+ `cache({ store }) boundary whose entries you invalidate by tag.`,
103
+ );
104
+ }
105
+
106
+ async function invalidateAcross(
107
+ stores: SegmentCacheStore[],
108
+ tags: string[],
109
+ ): Promise<void> {
110
+ // One invalidateTags(tags) call per store: the store receives the whole tag
111
+ // batch so it can do a single CDN purge request rather than one per tag.
112
+ //
113
+ // allSettled, not all: a store's invalidateTags() can reject (e.g. CFCacheStore
114
+ // surfaces a failed durable KV marker write). With Promise.all, the first
115
+ // rejection would short-circuit and the other stores' outcomes would go
116
+ // unobserved. Attempt every store, then surface a combined error so an awaited
117
+ // updateTag() still rejects (read-your-own-writes honesty) without masking the
118
+ // stores that did succeed.
119
+ const results = await Promise.allSettled(
120
+ stores.map((store) => store.invalidateTags!(tags)),
121
+ );
122
+ const errors = results
123
+ .filter((r): r is PromiseRejectedResult => r.status === "rejected")
124
+ .map((r) => r.reason);
125
+ if (errors.length > 0) {
126
+ const err = new Error(
127
+ `[tag invalidation] ${errors.length}/${stores.length} store(s) failed to ` +
128
+ `invalidate tags [${tags.join(", ")}]; their entries may still serve ` +
129
+ `stale data. Retry the invalidation.`,
130
+ );
131
+ (err as Error & { cause?: unknown }).cause = errors[0];
132
+ throw err;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Immediately expire every cache entry tagged with any of `tags`, resolving
138
+ * once in-process invalidation across all configured stores completes.
139
+ *
140
+ * Read-your-own-writes: because the returned promise resolves before you return
141
+ * from a Server Action, awaiting it guarantees the action's own re-render (and
142
+ * any subsequent read) sees fresh data.
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * async function updateProduct(formData: FormData) {
147
+ * "use server";
148
+ * await db.updateProduct(formData);
149
+ * await updateTag("products"); // next render is fresh
150
+ * }
151
+ * ```
152
+ */
153
+ export async function updateTag(...tags: string[]): Promise<void> {
154
+ const valid = normalizeTags(tags);
155
+ if (valid.length === 0) return;
156
+
157
+ const { capable, incapable, hasContext } = collectStores();
158
+ if (capable.length === 0) {
159
+ if (hasContext) warnNoTagStore("updateTag", valid);
160
+ else warnNoRequestContext("updateTag", valid);
161
+ return;
162
+ }
163
+ if (incapable > 0) warnPartialTagStore("updateTag", incapable);
164
+
165
+ await invalidateAcross(capable, valid);
166
+ }
167
+
168
+ /**
169
+ * Invalidate every cache entry tagged with any of `tags` in the background,
170
+ * without blocking the current response (fire-and-forget via waitUntil).
171
+ *
172
+ * This is NOT stale-while-revalidate: like updateTag() it hard-purges, so the
173
+ * next read after the invalidation lands is a miss that re-renders fresh. The
174
+ * only difference from updateTag() is awaitability - revalidateTag() defers the
175
+ * purge off the response path and is not awaited.
176
+ *
177
+ * Use in Route Handlers / webhooks. For read-your-own-writes inside a Server
178
+ * Action, use updateTag() instead so the action's own response is fresh.
179
+ *
180
+ * Fire-and-forget: because this returns void and runs in the background, a
181
+ * failed durable marker write (e.g. a transient KV outage) is NOT surfaced to
182
+ * the caller. It IS reported - logged loudly and routed through the router's
183
+ * `onError` callback (phase `cache`, `metadata.category === "cache-invalidate"`)
184
+ * via reportingAsync - so the failure is observable in telemetry even though it
185
+ * cannot be awaited. If you need the invalidation to be CONFIRMED (and to retry
186
+ * on failure), use `await updateTag()` instead, which rejects when a store's
187
+ * durable write fails.
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * // route handler invoked by an external webhook
192
+ * export async function POST() {
193
+ * "use server";
194
+ * revalidateTag("products");
195
+ * return new Response("ok");
196
+ * }
197
+ * ```
198
+ */
199
+ export function revalidateTag(...tags: string[]): void {
200
+ const valid = normalizeTags(tags);
201
+ if (valid.length === 0) return;
202
+
203
+ const { capable, incapable, hasContext } = collectStores();
204
+ if (capable.length === 0) {
205
+ if (hasContext) warnNoTagStore("revalidateTag", valid);
206
+ else warnNoRequestContext("revalidateTag", valid);
207
+ return;
208
+ }
209
+ if (incapable > 0) warnPartialTagStore("revalidateTag", incapable);
210
+
211
+ const ctx = _getRequestContext();
212
+ // reportingAsync never rejects: it catches a failed durable write and routes
213
+ // it through reportCacheError (loud log + onError). This is the only place a
214
+ // revalidateTag failure can be observed, since it is not awaitable. Pass ctx
215
+ // explicitly - the run executes in a detached waitUntil where the ALS context
216
+ // is gone, so onError fires only if we hand it the captured context.
217
+ const run = () =>
218
+ reportingAsync(
219
+ () => invalidateAcross(capable, valid),
220
+ "cache-invalidate",
221
+ "[revalidateTag] background invalidation",
222
+ ctx,
223
+ );
224
+ if (ctx?.waitUntil) {
225
+ ctx.waitUntil(run);
226
+ } else {
227
+ // No request context (e.g. called outside ALS): best-effort background run.
228
+ void run();
229
+ }
230
+ }
@@ -81,6 +81,61 @@ export function assertNotInsideCacheExec(
81
81
  }
82
82
  }
83
83
 
84
+ /**
85
+ * Symbol stamped on ctx when resolving handlers inside a cache() DSL boundary.
86
+ * Separate from INSIDE_CACHE_EXEC ("use cache") because cache() allows
87
+ * ctx.set() (children are also cached) but blocks response-level side effects
88
+ * (headers, cookies, status) which are lost on cache hit.
89
+ */
90
+ export const INSIDE_CACHE_SCOPE: unique symbol = Symbol.for(
91
+ "rango:inside-cache-scope",
92
+ ) as any;
93
+
94
+ /**
95
+ * Mark ctx as inside a cache() scope. Must be paired with unstampCacheScope.
96
+ */
97
+ export function stampCacheScope(obj: object): void {
98
+ const current = (obj as any)[INSIDE_CACHE_SCOPE] ?? 0;
99
+ (obj as any)[INSIDE_CACHE_SCOPE] = current + 1;
100
+ }
101
+
102
+ /**
103
+ * Remove cache() scope mark.
104
+ */
105
+ export function unstampCacheScope(obj: object): void {
106
+ const current = (obj as any)[INSIDE_CACHE_SCOPE] ?? 0;
107
+ if (current <= 1) {
108
+ delete (obj as any)[INSIDE_CACHE_SCOPE];
109
+ } else {
110
+ (obj as any)[INSIDE_CACHE_SCOPE] = current - 1;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Throw if ctx is inside a cache() DSL boundary.
116
+ * Call from response-level side effects (header, setCookie, setStatus, etc.)
117
+ * which are lost on cache hit because the handler body is skipped.
118
+ * ctx.set() is allowed inside cache() — children are also cached and can
119
+ * read the value.
120
+ */
121
+ export function assertNotInsideCacheScope(
122
+ ctx: unknown,
123
+ methodName: string,
124
+ ): void {
125
+ if (
126
+ ctx !== null &&
127
+ ctx !== undefined &&
128
+ typeof ctx === "object" &&
129
+ (INSIDE_CACHE_SCOPE as symbol) in (ctx as Record<symbol, unknown>)
130
+ ) {
131
+ throw new Error(
132
+ `ctx.${methodName}() cannot be called inside a cache() boundary. ` +
133
+ `On cache hit the handler is skipped, so this side effect would be lost. ` +
134
+ `Move ctx.${methodName}() to a middleware or layout outside the cache() scope.`,
135
+ );
136
+ }
137
+ }
138
+
84
139
  /**
85
140
  * Brand symbol for functions wrapped by registerCachedFunction().
86
141
  * Used at runtime to detect when a "use cache" function is misused
@@ -12,10 +12,6 @@
12
12
  import type { ResolvedSegment } from "../types.js";
13
13
  import type { RequestContext } from "../server/request-context.js";
14
14
 
15
- // ============================================================================
16
- // Segment Cache Store (low-level storage interface)
17
- // ============================================================================
18
-
19
15
  /**
20
16
  * Result from cache get() including data and revalidation status
21
17
  */
@@ -116,12 +112,6 @@ export interface SegmentCacheStore<TEnv = unknown> {
116
112
  */
117
113
  clear?(): Promise<void>;
118
114
 
119
- // ============================================================================
120
- // Document Cache Methods (optional)
121
- // ============================================================================
122
- // These methods are for caching full HTTP responses (document-level caching).
123
- // Stores that support response caching should implement these methods.
124
-
125
115
  /**
126
116
  * Get a cached Response by key.
127
117
  * Returns the response and whether it should be revalidated (SWR).
@@ -136,20 +126,16 @@ export interface SegmentCacheStore<TEnv = unknown> {
136
126
  * @param response - Response to cache (will be cloned)
137
127
  * @param ttl - Time-to-live in seconds
138
128
  * @param swr - Optional stale-while-revalidate window in seconds
129
+ * @param tags - Optional cache tags for invalidation
139
130
  */
140
131
  putResponse?(
141
132
  key: string,
142
133
  response: Response,
143
134
  ttl: number,
144
135
  swr?: number,
136
+ tags?: string[],
145
137
  ): Promise<void>;
146
138
 
147
- // ============================================================================
148
- // Function Cache Methods (optional, for "use cache" directive)
149
- // ============================================================================
150
- // These methods cache individual function/component return values.
151
- // Stores that support "use cache" should implement these methods.
152
-
153
139
  /**
154
140
  * Get a cached function result by key.
155
141
  * Returns the serialized value, optional handle data, and staleness flag.
@@ -167,6 +153,16 @@ export interface SegmentCacheStore<TEnv = unknown> {
167
153
  value: string,
168
154
  options?: CacheItemOptions,
169
155
  ): Promise<void>;
156
+
157
+ /**
158
+ * Invalidate every cache entry (segment, response, item) tagged with any of
159
+ * `tags`. Store-level primitive that the public updateTag()/revalidateTag()
160
+ * APIs delegate to. Receives ALL of one invalidation call's tags at once so
161
+ * stores can batch their work (e.g. a single CDN purge request rather than
162
+ * one per tag). Stores that do not support tags simply omit this method.
163
+ * @param tags - The cache tags to invalidate
164
+ */
165
+ invalidateTags?(tags: string[]): Promise<void>;
170
166
  }
171
167
 
172
168
  /**
@@ -175,18 +171,27 @@ export interface SegmentCacheStore<TEnv = unknown> {
175
171
  export interface CacheItemResult {
176
172
  /** RSC-serialized return value */
177
173
  value: string;
178
- /** Handle data captured during execution (breadcrumbs, metadata, etc.) */
179
- handles?: Record<string, SegmentHandleData>;
174
+ /** RSC-encoded handle data captured during execution (breadcrumbs, metadata,
175
+ * etc.). Encoded via the Flight codec so Promise/ReactNode handle values
176
+ * survive JSON-serializing stores — see handle-snapshot.ts encodeHandles. */
177
+ handles?: string;
180
178
  /** Whether the entry is stale and should be revalidated */
181
179
  shouldRevalidate: boolean;
180
+ /**
181
+ * The entry's cache tags (including runtime cacheTag() tags), surfaced on read
182
+ * so a "use cache" HIT can still contribute its tags to the request-scoped tag
183
+ * set used by document-level caching. On a hit the cached function is not
184
+ * re-run, so its runtime tags are only available here, not from re-execution.
185
+ */
186
+ tags?: string[];
182
187
  }
183
188
 
184
189
  /**
185
190
  * Options for setItem() for function-level caching ("use cache").
186
191
  */
187
192
  export interface CacheItemOptions {
188
- /** Handle data to store alongside the value */
189
- handles?: Record<string, SegmentHandleData>;
193
+ /** RSC-encoded handle data to store alongside the value (see encodeHandles). */
194
+ handles?: string;
190
195
  /** Time-to-live in seconds */
191
196
  ttl?: number;
192
197
  /** Stale-while-revalidate window in seconds */
@@ -227,16 +232,18 @@ export interface SerializedSegmentData {
227
232
  export interface CachedEntryData {
228
233
  /** Serialized segments for this entry */
229
234
  segments: SerializedSegmentData[];
230
- /** Handle data keyed by segment ID */
231
- handles: Record<string, SegmentHandleData>;
235
+ /** RSC-encoded handle data keyed by segment ID. Encoded via the Flight codec
236
+ * (see handle-snapshot.ts encodeHandles) so Promise/ReactNode handle values
237
+ * round-trip through JSON-serializing stores instead of being flattened. */
238
+ handles: string;
232
239
  /** Expiration timestamp (ms since epoch) */
233
240
  expiresAt: number;
241
+ /** Cache tags for invalidation */
242
+ tags?: string[];
243
+ /** Timestamp (ms since epoch) when tags were attached, for distributed invalidation */
244
+ taggedAt?: number;
234
245
  }
235
246
 
236
- // ============================================================================
237
- // Cache Configuration
238
- // ============================================================================
239
-
240
247
  /**
241
248
  * Default cache options applied to all cache() boundaries.
242
249
  * Individual cache() calls can override any of these values.
@@ -261,82 +268,8 @@ export interface CacheDefaults {
261
268
  swr?: number;
262
269
  }
263
270
 
264
- /**
265
- * Cache configuration for RSC handler
266
- */
267
- export interface CacheConfig {
268
- /** Cache store implementation (includes defaults) */
269
- store: SegmentCacheStore;
270
- /** Enable/disable caching (default: true) */
271
- enabled?: boolean;
272
- }
273
-
274
- /**
275
- * Cache configuration - can be static or a function receiving env
276
- */
277
- export type CacheConfigOrFactory<TEnv> =
278
- | CacheConfig
279
- | ((env: TEnv) => CacheConfig);
280
-
281
- // ============================================================================
282
- // Segment Cache Provider (request-level interface)
283
- // ============================================================================
284
-
285
271
  /**
286
272
  * Handle data for a single segment
287
273
  * Structure: { handleName: [values...] }
288
274
  */
289
275
  export type SegmentHandleData = Record<string, unknown[]>;
290
-
291
- /**
292
- * Result from cache get() including segments and their handle data
293
- * Each entry can produce multiple segments (main + parallels)
294
- */
295
- export interface CachedEntryResult {
296
- /** All segments for this entry (main segment + parallels) */
297
- segments: ResolvedSegment[];
298
- /** Handle data keyed by segment ID */
299
- handles: Record<string, SegmentHandleData>;
300
- }
301
-
302
- /**
303
- * Segment cache provider interface
304
- *
305
- * Used by router to check/store segment cache during matching.
306
- * Accessed via request context - if not present, caching is disabled.
307
- *
308
- * @internal Not currently implemented - CacheScope is used directly.
309
- * Reserved for future extensibility.
310
- */
311
- export interface SegmentCacheProvider {
312
- /** Whether caching is enabled for this request */
313
- readonly enabled: boolean;
314
-
315
- /**
316
- * Get cached segments and restore handles/loaders.
317
- *
318
- * Combines cache get with handle replay and loader data restoration.
319
- * Returns tuple of [segments, segmentIds] if cache hit, null if miss or disabled.
320
- *
321
- * @param cacheKey - Cache key to look up
322
- * @param params - Route params for cache key generation
323
- * @param loaderPromises - Map to restore loader data into
324
- * @returns Tuple of [segments, segmentIds] or null if miss
325
- */
326
- restore(
327
- cacheKey: string,
328
- params: Record<string, string>,
329
- loaderPromises: Map<string, Promise<any>>,
330
- ): Promise<[ResolvedSegment[], string[]] | null>;
331
-
332
- /**
333
- * Cache entry with automatic handle collection (non-blocking).
334
- *
335
- * Schedules caching via waitUntil - handles are collected after they settle.
336
- * Validates segments have actual components before caching.
337
- *
338
- * @param cacheKey - The cache key to store under
339
- * @param segments - All resolved segments for this entry
340
- */
341
- cacheEntry(cacheKey: string, segments: ResolvedSegment[]): void;
342
- }
@@ -0,0 +1,11 @@
1
+ export {
2
+ VercelCacheStore,
3
+ type VercelCacheStoreOptions,
4
+ type VercelRuntimeCache,
5
+ type VercelCacheDebug,
6
+ type VercelCacheReadDebugEvent,
7
+ type VercelCacheReadOutcome,
8
+ VERCEL_MAX_ITEM_BYTES,
9
+ VERCEL_MAX_TAGS_PER_ITEM,
10
+ VERCEL_MAX_TAG_BYTES,
11
+ } from "./vercel-cache-store.js";