@rangojs/router 0.0.0-experimental.dfa55db4 → 0.0.0-experimental.e16b7c00

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 (1041) hide show
  1. package/README.md +120 -25
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/vite/index.js +2108 -829
  4. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  5. package/package.json +13 -8
  6. package/skills/breadcrumbs/SKILL.md +3 -1
  7. package/skills/bundle-analysis/SKILL.md +159 -0
  8. package/skills/cache-guide/SKILL.md +222 -30
  9. package/skills/caching/SKILL.md +188 -8
  10. package/skills/composability/SKILL.md +27 -2
  11. package/skills/document-cache/SKILL.md +78 -55
  12. package/skills/handler-use/SKILL.md +364 -0
  13. package/skills/hooks/SKILL.md +229 -20
  14. package/skills/host-router/SKILL.md +45 -20
  15. package/skills/i18n/SKILL.md +276 -0
  16. package/skills/intercept/SKILL.md +46 -4
  17. package/skills/layout/SKILL.md +28 -7
  18. package/skills/links/SKILL.md +247 -17
  19. package/skills/loader/SKILL.md +219 -9
  20. package/skills/middleware/SKILL.md +47 -12
  21. package/skills/migrate-nextjs/SKILL.md +582 -0
  22. package/skills/migrate-react-router/SKILL.md +769 -0
  23. package/skills/mime-routes/SKILL.md +27 -0
  24. package/skills/observability/SKILL.md +137 -0
  25. package/skills/parallel/SKILL.md +71 -6
  26. package/skills/prerender/SKILL.md +14 -33
  27. package/skills/rango/SKILL.md +236 -22
  28. package/skills/react-compiler/SKILL.md +168 -0
  29. package/skills/response-routes/SKILL.md +66 -9
  30. package/skills/route/SKILL.md +57 -4
  31. package/skills/router-setup/SKILL.md +3 -3
  32. package/skills/server-actions/SKILL.md +751 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/typesafety/SKILL.md +319 -27
  35. package/skills/use-cache/SKILL.md +36 -5
  36. package/skills/view-transitions/SKILL.md +294 -0
  37. package/src/__augment-tests__/augment.ts +81 -0
  38. package/src/__augment-tests__/augmented.check.ts +117 -0
  39. package/src/browser/action-coordinator.ts +53 -36
  40. package/src/browser/app-shell.ts +52 -0
  41. package/src/browser/event-controller.ts +86 -70
  42. package/src/browser/history-state.ts +21 -0
  43. package/src/browser/index.ts +3 -3
  44. package/src/browser/navigation-bridge.ts +86 -11
  45. package/src/browser/navigation-client.ts +76 -28
  46. package/src/browser/navigation-store.ts +32 -9
  47. package/src/browser/navigation-transaction.ts +10 -28
  48. package/src/browser/partial-update.ts +64 -26
  49. package/src/browser/prefetch/cache.ts +129 -21
  50. package/src/browser/prefetch/fetch.ts +148 -16
  51. package/src/browser/prefetch/queue.ts +36 -5
  52. package/src/browser/rango-state.ts +53 -13
  53. package/src/browser/react/Link.tsx +30 -2
  54. package/src/browser/react/NavigationProvider.tsx +72 -31
  55. package/src/browser/react/filter-segment-order.ts +51 -7
  56. package/src/browser/react/index.ts +3 -0
  57. package/src/browser/react/location-state-shared.ts +175 -4
  58. package/src/browser/react/location-state.ts +39 -13
  59. package/src/browser/react/use-handle.ts +17 -9
  60. package/src/browser/react/use-navigation.ts +22 -2
  61. package/src/browser/react/use-params.ts +20 -8
  62. package/src/browser/react/use-reverse.ts +106 -0
  63. package/src/browser/react/use-router.ts +22 -2
  64. package/src/browser/react/use-segments.ts +11 -8
  65. package/src/browser/response-adapter.ts +25 -0
  66. package/src/browser/rsc-router.tsx +64 -22
  67. package/src/browser/scroll-restoration.ts +22 -14
  68. package/src/browser/segment-reconciler.ts +36 -14
  69. package/src/browser/segment-structure-assert.ts +2 -2
  70. package/src/browser/server-action-bridge.ts +23 -30
  71. package/src/browser/types.ts +21 -0
  72. package/src/build/collect-fallback-refs.ts +107 -0
  73. package/src/build/generate-manifest.ts +60 -35
  74. package/src/build/generate-route-types.ts +2 -0
  75. package/src/build/index.ts +2 -0
  76. package/src/build/route-trie.ts +52 -25
  77. package/src/build/route-types/codegen.ts +4 -4
  78. package/src/build/route-types/include-resolution.ts +1 -1
  79. package/src/build/route-types/per-module-writer.ts +7 -4
  80. package/src/build/route-types/router-processing.ts +55 -14
  81. package/src/build/route-types/scan-filter.ts +1 -1
  82. package/src/build/route-types/source-scan.ts +118 -0
  83. package/src/build/runtime-discovery.ts +9 -20
  84. package/src/cache/cache-error.ts +104 -0
  85. package/src/cache/cache-policy.ts +95 -1
  86. package/src/cache/cache-runtime.ts +79 -13
  87. package/src/cache/cache-scope.ts +77 -46
  88. package/src/cache/cache-tag.ts +135 -0
  89. package/src/cache/cf/cf-cache-store.ts +1067 -176
  90. package/src/cache/cf/index.ts +4 -1
  91. package/src/cache/document-cache.ts +59 -7
  92. package/src/cache/index.ts +6 -0
  93. package/src/cache/memory-segment-store.ts +158 -14
  94. package/src/cache/tag-invalidation.ts +206 -0
  95. package/src/cache/types.ts +27 -0
  96. package/src/client.rsc.tsx +3 -0
  97. package/src/client.tsx +92 -182
  98. package/src/context-var.ts +5 -5
  99. package/src/decode-loader-results.ts +36 -0
  100. package/src/errors.ts +30 -1
  101. package/src/handle.ts +4 -6
  102. package/src/host/index.ts +2 -2
  103. package/src/host/router.ts +129 -57
  104. package/src/host/types.ts +31 -2
  105. package/src/host/utils.ts +1 -1
  106. package/src/href-client.ts +140 -20
  107. package/src/index.rsc.ts +16 -4
  108. package/src/index.ts +65 -15
  109. package/src/loader-store.ts +500 -0
  110. package/src/loader.rsc.ts +2 -5
  111. package/src/loader.ts +3 -10
  112. package/src/missing-id-error.ts +68 -0
  113. package/src/outlet-context.ts +1 -1
  114. package/src/prerender.ts +4 -4
  115. package/src/response-utils.ts +37 -0
  116. package/src/reverse.ts +65 -36
  117. package/src/route-content-wrapper.tsx +6 -28
  118. package/src/route-definition/dsl-helpers.ts +384 -257
  119. package/src/route-definition/helper-factories.ts +29 -139
  120. package/src/route-definition/helpers-types.ts +100 -28
  121. package/src/route-definition/resolve-handler-use.ts +6 -0
  122. package/src/route-definition/use-item-types.ts +32 -0
  123. package/src/route-types.ts +26 -41
  124. package/src/router/content-negotiation.ts +15 -2
  125. package/src/router/error-handling.ts +1 -1
  126. package/src/router/handler-context.ts +21 -38
  127. package/src/router/intercept-resolution.ts +4 -18
  128. package/src/router/lazy-includes.ts +8 -8
  129. package/src/router/loader-resolution.ts +19 -2
  130. package/src/router/manifest.ts +22 -13
  131. package/src/router/match-api.ts +4 -3
  132. package/src/router/match-handlers.ts +1 -0
  133. package/src/router/match-middleware/cache-lookup.ts +46 -92
  134. package/src/router/match-middleware/cache-store.ts +3 -2
  135. package/src/router/match-result.ts +103 -4
  136. package/src/router/metrics.ts +1 -1
  137. package/src/router/middleware-types.ts +15 -26
  138. package/src/router/middleware.ts +99 -84
  139. package/src/router/pattern-matching.ts +101 -17
  140. package/src/router/prerender-match.ts +3 -1
  141. package/src/router/preview-match.ts +3 -1
  142. package/src/router/request-classification.ts +4 -28
  143. package/src/router/revalidation.ts +58 -2
  144. package/src/router/router-interfaces.ts +45 -28
  145. package/src/router/router-options.ts +25 -1
  146. package/src/router/router-registry.ts +2 -5
  147. package/src/router/segment-resolution/fresh.ts +32 -6
  148. package/src/router/segment-resolution/loader-cache.ts +8 -17
  149. package/src/router/segment-resolution/revalidation.ts +154 -107
  150. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  151. package/src/router/substitute-pattern-params.ts +56 -0
  152. package/src/router/trie-matching.ts +18 -13
  153. package/src/router/types.ts +8 -0
  154. package/src/router/url-params.ts +49 -0
  155. package/src/router.ts +23 -18
  156. package/src/rsc/handler-context.ts +2 -2
  157. package/src/rsc/handler.ts +46 -74
  158. package/src/rsc/helpers.ts +72 -43
  159. package/src/rsc/index.ts +1 -1
  160. package/src/rsc/origin-guard.ts +28 -10
  161. package/src/rsc/progressive-enhancement.ts +4 -0
  162. package/src/rsc/response-route-handler.ts +54 -54
  163. package/src/rsc/rsc-rendering.ts +35 -51
  164. package/src/rsc/runtime-warnings.ts +9 -10
  165. package/src/rsc/server-action.ts +17 -37
  166. package/src/rsc/ssr-setup.ts +16 -0
  167. package/src/rsc/types.ts +8 -2
  168. package/src/search-params.ts +4 -4
  169. package/src/segment-content-promise.ts +67 -0
  170. package/src/segment-loader-promise.ts +122 -0
  171. package/src/segment-system.tsx +132 -116
  172. package/src/serialize.ts +243 -0
  173. package/src/server/context.ts +143 -53
  174. package/src/server/cookie-store.ts +28 -4
  175. package/src/server/request-context.ts +46 -44
  176. package/src/ssr/index.tsx +5 -1
  177. package/src/static-handler.ts +1 -1
  178. package/src/types/cache-types.ts +13 -4
  179. package/src/types/error-types.ts +5 -1
  180. package/src/types/global-namespace.ts +39 -26
  181. package/src/types/handler-context.ts +68 -50
  182. package/src/types/index.ts +1 -0
  183. package/src/types/loader-types.ts +5 -6
  184. package/src/types/request-scope.ts +126 -0
  185. package/src/types/route-entry.ts +11 -0
  186. package/src/types/segments.ts +36 -2
  187. package/src/urls/include-helper.ts +34 -67
  188. package/src/urls/index.ts +0 -3
  189. package/src/urls/path-helper-types.ts +41 -7
  190. package/src/urls/path-helper.ts +17 -52
  191. package/src/urls/pattern-types.ts +36 -19
  192. package/src/urls/response-types.ts +22 -29
  193. package/src/urls/type-extraction.ts +26 -116
  194. package/src/urls/urls-function.ts +1 -5
  195. package/src/use-loader.tsx +416 -42
  196. package/src/vite/debug.ts +185 -0
  197. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  198. package/src/vite/discovery/discover-routers.ts +101 -51
  199. package/src/vite/discovery/discovery-errors.ts +194 -0
  200. package/src/vite/discovery/gate-state.ts +171 -0
  201. package/src/vite/discovery/prerender-collection.ts +67 -26
  202. package/src/vite/discovery/route-types-writer.ts +40 -84
  203. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  204. package/src/vite/discovery/state.ts +33 -0
  205. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  206. package/src/vite/index.ts +2 -0
  207. package/src/vite/plugin-types.ts +67 -0
  208. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  209. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  210. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  211. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  212. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  213. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  214. package/src/vite/plugins/expose-action-id.ts +54 -30
  215. package/src/vite/plugins/expose-id-utils.ts +24 -8
  216. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  217. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  218. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  219. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  220. package/src/vite/plugins/expose-internal-ids.ts +503 -468
  221. package/src/vite/plugins/performance-tracks.ts +29 -25
  222. package/src/vite/plugins/use-cache-transform.ts +65 -50
  223. package/src/vite/plugins/version-injector.ts +39 -23
  224. package/src/vite/plugins/version-plugin.ts +59 -2
  225. package/src/vite/plugins/virtual-entries.ts +2 -2
  226. package/src/vite/rango.ts +116 -29
  227. package/src/vite/router-discovery.ts +750 -100
  228. package/src/vite/utils/ast-handler-extract.ts +15 -15
  229. package/src/vite/utils/banner.ts +1 -1
  230. package/src/vite/utils/bundle-analysis.ts +4 -2
  231. package/src/vite/utils/client-chunks.ts +190 -0
  232. package/src/vite/utils/forward-user-plugins.ts +193 -0
  233. package/src/vite/utils/manifest-utils.ts +21 -5
  234. package/src/vite/utils/package-resolution.ts +41 -1
  235. package/src/vite/utils/prerender-utils.ts +21 -6
  236. package/src/vite/utils/shared-utils.ts +107 -26
  237. package/dist/__internal.d.ts +0 -83
  238. package/dist/__internal.d.ts.map +0 -1
  239. package/dist/__internal.js +0 -19
  240. package/dist/__internal.js.map +0 -1
  241. package/dist/__mocks__/version.d.ts +0 -7
  242. package/dist/__mocks__/version.d.ts.map +0 -1
  243. package/dist/__mocks__/version.js +0 -7
  244. package/dist/__mocks__/version.js.map +0 -1
  245. package/dist/__tests__/client-href.test.d.ts +0 -2
  246. package/dist/__tests__/client-href.test.d.ts.map +0 -1
  247. package/dist/__tests__/client-href.test.js +0 -74
  248. package/dist/__tests__/client-href.test.js.map +0 -1
  249. package/dist/__tests__/component-utils.test.d.ts +0 -2
  250. package/dist/__tests__/component-utils.test.d.ts.map +0 -1
  251. package/dist/__tests__/component-utils.test.js +0 -51
  252. package/dist/__tests__/component-utils.test.js.map +0 -1
  253. package/dist/__tests__/event-controller.test.d.ts +0 -2
  254. package/dist/__tests__/event-controller.test.d.ts.map +0 -1
  255. package/dist/__tests__/event-controller.test.js +0 -538
  256. package/dist/__tests__/event-controller.test.js.map +0 -1
  257. package/dist/__tests__/helpers/route-tree.d.ts +0 -118
  258. package/dist/__tests__/helpers/route-tree.d.ts.map +0 -1
  259. package/dist/__tests__/helpers/route-tree.js +0 -374
  260. package/dist/__tests__/helpers/route-tree.js.map +0 -1
  261. package/dist/__tests__/match-result.test.d.ts +0 -2
  262. package/dist/__tests__/match-result.test.d.ts.map +0 -1
  263. package/dist/__tests__/match-result.test.js +0 -154
  264. package/dist/__tests__/match-result.test.js.map +0 -1
  265. package/dist/__tests__/navigation-store.test.d.ts +0 -2
  266. package/dist/__tests__/navigation-store.test.d.ts.map +0 -1
  267. package/dist/__tests__/navigation-store.test.js +0 -440
  268. package/dist/__tests__/navigation-store.test.js.map +0 -1
  269. package/dist/__tests__/partial-update.test.d.ts +0 -2
  270. package/dist/__tests__/partial-update.test.d.ts.map +0 -1
  271. package/dist/__tests__/partial-update.test.js +0 -1009
  272. package/dist/__tests__/partial-update.test.js.map +0 -1
  273. package/dist/__tests__/reverse-types.test.d.ts +0 -8
  274. package/dist/__tests__/reverse-types.test.d.ts.map +0 -1
  275. package/dist/__tests__/reverse-types.test.js +0 -656
  276. package/dist/__tests__/reverse-types.test.js.map +0 -1
  277. package/dist/__tests__/route-definition.test.d.ts +0 -2
  278. package/dist/__tests__/route-definition.test.d.ts.map +0 -1
  279. package/dist/__tests__/route-definition.test.js +0 -55
  280. package/dist/__tests__/route-definition.test.js.map +0 -1
  281. package/dist/__tests__/router-helpers.test.d.ts +0 -2
  282. package/dist/__tests__/router-helpers.test.d.ts.map +0 -1
  283. package/dist/__tests__/router-helpers.test.js +0 -377
  284. package/dist/__tests__/router-helpers.test.js.map +0 -1
  285. package/dist/__tests__/router-integration-2.test.d.ts +0 -2
  286. package/dist/__tests__/router-integration-2.test.d.ts.map +0 -1
  287. package/dist/__tests__/router-integration-2.test.js +0 -426
  288. package/dist/__tests__/router-integration-2.test.js.map +0 -1
  289. package/dist/__tests__/router-integration.test.d.ts +0 -2
  290. package/dist/__tests__/router-integration.test.d.ts.map +0 -1
  291. package/dist/__tests__/router-integration.test.js +0 -1051
  292. package/dist/__tests__/router-integration.test.js.map +0 -1
  293. package/dist/__tests__/search-params.test.d.ts +0 -5
  294. package/dist/__tests__/search-params.test.d.ts.map +0 -1
  295. package/dist/__tests__/search-params.test.js +0 -306
  296. package/dist/__tests__/search-params.test.js.map +0 -1
  297. package/dist/__tests__/segment-system.test.d.ts +0 -2
  298. package/dist/__tests__/segment-system.test.d.ts.map +0 -1
  299. package/dist/__tests__/segment-system.test.js +0 -627
  300. package/dist/__tests__/segment-system.test.js.map +0 -1
  301. package/dist/__tests__/static-handler-types.test.d.ts +0 -8
  302. package/dist/__tests__/static-handler-types.test.d.ts.map +0 -1
  303. package/dist/__tests__/static-handler-types.test.js +0 -63
  304. package/dist/__tests__/static-handler-types.test.js.map +0 -1
  305. package/dist/__tests__/urls.test.d.ts +0 -2
  306. package/dist/__tests__/urls.test.d.ts.map +0 -1
  307. package/dist/__tests__/urls.test.js +0 -421
  308. package/dist/__tests__/urls.test.js.map +0 -1
  309. package/dist/__tests__/use-mount.test.d.ts +0 -2
  310. package/dist/__tests__/use-mount.test.d.ts.map +0 -1
  311. package/dist/__tests__/use-mount.test.js +0 -35
  312. package/dist/__tests__/use-mount.test.js.map +0 -1
  313. package/dist/bin/rango.d.ts +0 -2
  314. package/dist/bin/rango.d.ts.map +0 -1
  315. package/dist/bin/rango.js.map +0 -1
  316. package/dist/browser/event-controller.d.ts +0 -191
  317. package/dist/browser/event-controller.d.ts.map +0 -1
  318. package/dist/browser/event-controller.js +0 -559
  319. package/dist/browser/event-controller.js.map +0 -1
  320. package/dist/browser/index.d.ts +0 -2
  321. package/dist/browser/index.d.ts.map +0 -1
  322. package/dist/browser/index.js +0 -14
  323. package/dist/browser/index.js.map +0 -1
  324. package/dist/browser/link-interceptor.d.ts +0 -38
  325. package/dist/browser/link-interceptor.d.ts.map +0 -1
  326. package/dist/browser/link-interceptor.js +0 -99
  327. package/dist/browser/link-interceptor.js.map +0 -1
  328. package/dist/browser/logging.d.ts +0 -10
  329. package/dist/browser/logging.d.ts.map +0 -1
  330. package/dist/browser/logging.js +0 -29
  331. package/dist/browser/logging.js.map +0 -1
  332. package/dist/browser/lru-cache.d.ts +0 -17
  333. package/dist/browser/lru-cache.d.ts.map +0 -1
  334. package/dist/browser/lru-cache.js +0 -50
  335. package/dist/browser/lru-cache.js.map +0 -1
  336. package/dist/browser/merge-segment-loaders.d.ts +0 -39
  337. package/dist/browser/merge-segment-loaders.d.ts.map +0 -1
  338. package/dist/browser/merge-segment-loaders.js +0 -102
  339. package/dist/browser/merge-segment-loaders.js.map +0 -1
  340. package/dist/browser/navigation-bridge.d.ts +0 -102
  341. package/dist/browser/navigation-bridge.d.ts.map +0 -1
  342. package/dist/browser/navigation-bridge.js +0 -708
  343. package/dist/browser/navigation-bridge.js.map +0 -1
  344. package/dist/browser/navigation-client.d.ts +0 -25
  345. package/dist/browser/navigation-client.d.ts.map +0 -1
  346. package/dist/browser/navigation-client.js +0 -157
  347. package/dist/browser/navigation-client.js.map +0 -1
  348. package/dist/browser/navigation-store.d.ts +0 -101
  349. package/dist/browser/navigation-store.d.ts.map +0 -1
  350. package/dist/browser/navigation-store.js +0 -625
  351. package/dist/browser/navigation-store.js.map +0 -1
  352. package/dist/browser/partial-update.d.ts +0 -75
  353. package/dist/browser/partial-update.d.ts.map +0 -1
  354. package/dist/browser/partial-update.js +0 -426
  355. package/dist/browser/partial-update.js.map +0 -1
  356. package/dist/browser/react/Link.d.ts +0 -86
  357. package/dist/browser/react/Link.d.ts.map +0 -1
  358. package/dist/browser/react/Link.js +0 -128
  359. package/dist/browser/react/Link.js.map +0 -1
  360. package/dist/browser/react/NavigationProvider.d.ts +0 -63
  361. package/dist/browser/react/NavigationProvider.d.ts.map +0 -1
  362. package/dist/browser/react/NavigationProvider.js +0 -216
  363. package/dist/browser/react/NavigationProvider.js.map +0 -1
  364. package/dist/browser/react/ScrollRestoration.d.ts +0 -75
  365. package/dist/browser/react/ScrollRestoration.d.ts.map +0 -1
  366. package/dist/browser/react/ScrollRestoration.js +0 -57
  367. package/dist/browser/react/ScrollRestoration.js.map +0 -1
  368. package/dist/browser/react/context.d.ts +0 -46
  369. package/dist/browser/react/context.d.ts.map +0 -1
  370. package/dist/browser/react/context.js +0 -10
  371. package/dist/browser/react/context.js.map +0 -1
  372. package/dist/browser/react/index.d.ts +0 -11
  373. package/dist/browser/react/index.d.ts.map +0 -1
  374. package/dist/browser/react/index.js +0 -22
  375. package/dist/browser/react/index.js.map +0 -1
  376. package/dist/browser/react/location-state-shared.d.ts +0 -63
  377. package/dist/browser/react/location-state-shared.d.ts.map +0 -1
  378. package/dist/browser/react/location-state-shared.js +0 -81
  379. package/dist/browser/react/location-state-shared.js.map +0 -1
  380. package/dist/browser/react/location-state.d.ts +0 -23
  381. package/dist/browser/react/location-state.d.ts.map +0 -1
  382. package/dist/browser/react/location-state.js +0 -29
  383. package/dist/browser/react/location-state.js.map +0 -1
  384. package/dist/browser/react/mount-context.d.ts +0 -24
  385. package/dist/browser/react/mount-context.d.ts.map +0 -1
  386. package/dist/browser/react/mount-context.js +0 -24
  387. package/dist/browser/react/mount-context.js.map +0 -1
  388. package/dist/browser/react/use-action.d.ts +0 -64
  389. package/dist/browser/react/use-action.d.ts.map +0 -1
  390. package/dist/browser/react/use-action.js +0 -134
  391. package/dist/browser/react/use-action.js.map +0 -1
  392. package/dist/browser/react/use-client-cache.d.ts +0 -41
  393. package/dist/browser/react/use-client-cache.d.ts.map +0 -1
  394. package/dist/browser/react/use-client-cache.js +0 -39
  395. package/dist/browser/react/use-client-cache.js.map +0 -1
  396. package/dist/browser/react/use-handle.d.ts +0 -31
  397. package/dist/browser/react/use-handle.d.ts.map +0 -1
  398. package/dist/browser/react/use-handle.js +0 -144
  399. package/dist/browser/react/use-handle.js.map +0 -1
  400. package/dist/browser/react/use-href.d.ts +0 -33
  401. package/dist/browser/react/use-href.d.ts.map +0 -1
  402. package/dist/browser/react/use-href.js +0 -39
  403. package/dist/browser/react/use-href.js.map +0 -1
  404. package/dist/browser/react/use-link-status.d.ts +0 -37
  405. package/dist/browser/react/use-link-status.d.ts.map +0 -1
  406. package/dist/browser/react/use-link-status.js +0 -99
  407. package/dist/browser/react/use-link-status.js.map +0 -1
  408. package/dist/browser/react/use-mount.d.ts +0 -25
  409. package/dist/browser/react/use-mount.d.ts.map +0 -1
  410. package/dist/browser/react/use-mount.js +0 -30
  411. package/dist/browser/react/use-mount.js.map +0 -1
  412. package/dist/browser/react/use-navigation.d.ts +0 -27
  413. package/dist/browser/react/use-navigation.d.ts.map +0 -1
  414. package/dist/browser/react/use-navigation.js +0 -87
  415. package/dist/browser/react/use-navigation.js.map +0 -1
  416. package/dist/browser/react/use-segments.d.ts +0 -38
  417. package/dist/browser/react/use-segments.d.ts.map +0 -1
  418. package/dist/browser/react/use-segments.js +0 -130
  419. package/dist/browser/react/use-segments.js.map +0 -1
  420. package/dist/browser/request-controller.d.ts +0 -26
  421. package/dist/browser/request-controller.d.ts.map +0 -1
  422. package/dist/browser/request-controller.js +0 -147
  423. package/dist/browser/request-controller.js.map +0 -1
  424. package/dist/browser/rsc-router.d.ts +0 -129
  425. package/dist/browser/rsc-router.d.ts.map +0 -1
  426. package/dist/browser/rsc-router.js +0 -195
  427. package/dist/browser/rsc-router.js.map +0 -1
  428. package/dist/browser/scroll-restoration.d.ts +0 -93
  429. package/dist/browser/scroll-restoration.d.ts.map +0 -1
  430. package/dist/browser/scroll-restoration.js +0 -321
  431. package/dist/browser/scroll-restoration.js.map +0 -1
  432. package/dist/browser/segment-structure-assert.d.ts +0 -17
  433. package/dist/browser/segment-structure-assert.d.ts.map +0 -1
  434. package/dist/browser/segment-structure-assert.js +0 -59
  435. package/dist/browser/segment-structure-assert.js.map +0 -1
  436. package/dist/browser/server-action-bridge.d.ts +0 -26
  437. package/dist/browser/server-action-bridge.d.ts.map +0 -1
  438. package/dist/browser/server-action-bridge.js +0 -668
  439. package/dist/browser/server-action-bridge.js.map +0 -1
  440. package/dist/browser/shallow.d.ts +0 -12
  441. package/dist/browser/shallow.d.ts.map +0 -1
  442. package/dist/browser/shallow.js +0 -34
  443. package/dist/browser/shallow.js.map +0 -1
  444. package/dist/browser/types.d.ts +0 -369
  445. package/dist/browser/types.d.ts.map +0 -1
  446. package/dist/browser/types.js +0 -2
  447. package/dist/browser/types.js.map +0 -1
  448. package/dist/build/__tests__/generate-cli.test.d.ts +0 -2
  449. package/dist/build/__tests__/generate-cli.test.d.ts.map +0 -1
  450. package/dist/build/__tests__/generate-cli.test.js +0 -237
  451. package/dist/build/__tests__/generate-cli.test.js.map +0 -1
  452. package/dist/build/__tests__/generate-manifest.test.d.ts +0 -2
  453. package/dist/build/__tests__/generate-manifest.test.d.ts.map +0 -1
  454. package/dist/build/__tests__/generate-manifest.test.js +0 -119
  455. package/dist/build/__tests__/generate-manifest.test.js.map +0 -1
  456. package/dist/build/__tests__/generate-route-types.test.d.ts +0 -2
  457. package/dist/build/__tests__/generate-route-types.test.d.ts.map +0 -1
  458. package/dist/build/__tests__/generate-route-types.test.js +0 -620
  459. package/dist/build/__tests__/generate-route-types.test.js.map +0 -1
  460. package/dist/build/__tests__/per-router-manifest.test.d.ts +0 -2
  461. package/dist/build/__tests__/per-router-manifest.test.d.ts.map +0 -1
  462. package/dist/build/__tests__/per-router-manifest.test.js +0 -308
  463. package/dist/build/__tests__/per-router-manifest.test.js.map +0 -1
  464. package/dist/build/generate-manifest.d.ts +0 -81
  465. package/dist/build/generate-manifest.d.ts.map +0 -1
  466. package/dist/build/generate-manifest.js +0 -276
  467. package/dist/build/generate-manifest.js.map +0 -1
  468. package/dist/build/generate-route-types.d.ts +0 -115
  469. package/dist/build/generate-route-types.d.ts.map +0 -1
  470. package/dist/build/generate-route-types.js +0 -740
  471. package/dist/build/generate-route-types.js.map +0 -1
  472. package/dist/build/index.d.ts +0 -21
  473. package/dist/build/index.d.ts.map +0 -1
  474. package/dist/build/index.js +0 -21
  475. package/dist/build/index.js.map +0 -1
  476. package/dist/build/route-trie.d.ts +0 -71
  477. package/dist/build/route-trie.d.ts.map +0 -1
  478. package/dist/build/route-trie.js +0 -175
  479. package/dist/build/route-trie.js.map +0 -1
  480. package/dist/cache/__tests__/cache-scope.test.d.ts +0 -2
  481. package/dist/cache/__tests__/cache-scope.test.d.ts.map +0 -1
  482. package/dist/cache/__tests__/cache-scope.test.js +0 -208
  483. package/dist/cache/__tests__/cache-scope.test.js.map +0 -1
  484. package/dist/cache/__tests__/document-cache.test.d.ts +0 -2
  485. package/dist/cache/__tests__/document-cache.test.d.ts.map +0 -1
  486. package/dist/cache/__tests__/document-cache.test.js +0 -345
  487. package/dist/cache/__tests__/document-cache.test.js.map +0 -1
  488. package/dist/cache/__tests__/memory-segment-store.test.d.ts +0 -2
  489. package/dist/cache/__tests__/memory-segment-store.test.d.ts.map +0 -1
  490. package/dist/cache/__tests__/memory-segment-store.test.js +0 -425
  491. package/dist/cache/__tests__/memory-segment-store.test.js.map +0 -1
  492. package/dist/cache/__tests__/memory-store.test.d.ts +0 -2
  493. package/dist/cache/__tests__/memory-store.test.d.ts.map +0 -1
  494. package/dist/cache/__tests__/memory-store.test.js +0 -367
  495. package/dist/cache/__tests__/memory-store.test.js.map +0 -1
  496. package/dist/cache/cache-scope.d.ts +0 -102
  497. package/dist/cache/cache-scope.d.ts.map +0 -1
  498. package/dist/cache/cache-scope.js +0 -440
  499. package/dist/cache/cache-scope.js.map +0 -1
  500. package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts +0 -2
  501. package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts.map +0 -1
  502. package/dist/cache/cf/__tests__/cf-cache-store.test.js +0 -330
  503. package/dist/cache/cf/__tests__/cf-cache-store.test.js.map +0 -1
  504. package/dist/cache/cf/cf-cache-store.d.ts +0 -165
  505. package/dist/cache/cf/cf-cache-store.d.ts.map +0 -1
  506. package/dist/cache/cf/cf-cache-store.js +0 -242
  507. package/dist/cache/cf/cf-cache-store.js.map +0 -1
  508. package/dist/cache/cf/index.d.ts +0 -14
  509. package/dist/cache/cf/index.d.ts.map +0 -1
  510. package/dist/cache/cf/index.js +0 -17
  511. package/dist/cache/cf/index.js.map +0 -1
  512. package/dist/cache/document-cache.d.ts +0 -64
  513. package/dist/cache/document-cache.d.ts.map +0 -1
  514. package/dist/cache/document-cache.js +0 -228
  515. package/dist/cache/document-cache.js.map +0 -1
  516. package/dist/cache/index.d.ts +0 -19
  517. package/dist/cache/index.d.ts.map +0 -1
  518. package/dist/cache/index.js +0 -21
  519. package/dist/cache/index.js.map +0 -1
  520. package/dist/cache/memory-segment-store.d.ts +0 -110
  521. package/dist/cache/memory-segment-store.d.ts.map +0 -1
  522. package/dist/cache/memory-segment-store.js +0 -117
  523. package/dist/cache/memory-segment-store.js.map +0 -1
  524. package/dist/cache/memory-store.d.ts +0 -41
  525. package/dist/cache/memory-store.d.ts.map +0 -1
  526. package/dist/cache/memory-store.js +0 -191
  527. package/dist/cache/memory-store.js.map +0 -1
  528. package/dist/cache/types.d.ts +0 -317
  529. package/dist/cache/types.d.ts.map +0 -1
  530. package/dist/cache/types.js +0 -12
  531. package/dist/cache/types.js.map +0 -1
  532. package/dist/client.d.ts +0 -248
  533. package/dist/client.d.ts.map +0 -1
  534. package/dist/client.js +0 -367
  535. package/dist/client.js.map +0 -1
  536. package/dist/client.rsc.d.ts +0 -26
  537. package/dist/client.rsc.d.ts.map +0 -1
  538. package/dist/client.rsc.js +0 -46
  539. package/dist/client.rsc.js.map +0 -1
  540. package/dist/component-utils.d.ts +0 -36
  541. package/dist/component-utils.d.ts.map +0 -1
  542. package/dist/component-utils.js +0 -61
  543. package/dist/component-utils.js.map +0 -1
  544. package/dist/components/DefaultDocument.d.ts +0 -13
  545. package/dist/components/DefaultDocument.d.ts.map +0 -1
  546. package/dist/components/DefaultDocument.js +0 -15
  547. package/dist/components/DefaultDocument.js.map +0 -1
  548. package/dist/debug.d.ts +0 -58
  549. package/dist/debug.d.ts.map +0 -1
  550. package/dist/debug.js +0 -157
  551. package/dist/debug.js.map +0 -1
  552. package/dist/default-error-boundary.d.ts +0 -11
  553. package/dist/default-error-boundary.d.ts.map +0 -1
  554. package/dist/default-error-boundary.js +0 -45
  555. package/dist/default-error-boundary.js.map +0 -1
  556. package/dist/deps/browser.d.ts +0 -2
  557. package/dist/deps/browser.d.ts.map +0 -1
  558. package/dist/deps/browser.js +0 -3
  559. package/dist/deps/browser.js.map +0 -1
  560. package/dist/deps/html-stream-client.d.ts +0 -2
  561. package/dist/deps/html-stream-client.d.ts.map +0 -1
  562. package/dist/deps/html-stream-client.js +0 -3
  563. package/dist/deps/html-stream-client.js.map +0 -1
  564. package/dist/deps/html-stream-server.d.ts +0 -2
  565. package/dist/deps/html-stream-server.d.ts.map +0 -1
  566. package/dist/deps/html-stream-server.js +0 -3
  567. package/dist/deps/html-stream-server.js.map +0 -1
  568. package/dist/deps/rsc.d.ts +0 -2
  569. package/dist/deps/rsc.d.ts.map +0 -1
  570. package/dist/deps/rsc.js +0 -4
  571. package/dist/deps/rsc.js.map +0 -1
  572. package/dist/deps/ssr.d.ts +0 -2
  573. package/dist/deps/ssr.d.ts.map +0 -1
  574. package/dist/deps/ssr.js +0 -3
  575. package/dist/deps/ssr.js.map +0 -1
  576. package/dist/errors.d.ts +0 -174
  577. package/dist/errors.d.ts.map +0 -1
  578. package/dist/errors.js +0 -241
  579. package/dist/errors.js.map +0 -1
  580. package/dist/handle.d.ts +0 -78
  581. package/dist/handle.d.ts.map +0 -1
  582. package/dist/handle.js +0 -82
  583. package/dist/handle.js.map +0 -1
  584. package/dist/handles/MetaTags.d.ts +0 -14
  585. package/dist/handles/MetaTags.d.ts.map +0 -1
  586. package/dist/handles/MetaTags.js +0 -136
  587. package/dist/handles/MetaTags.js.map +0 -1
  588. package/dist/handles/index.d.ts +0 -6
  589. package/dist/handles/index.d.ts.map +0 -1
  590. package/dist/handles/index.js +0 -6
  591. package/dist/handles/index.js.map +0 -1
  592. package/dist/handles/meta.d.ts +0 -39
  593. package/dist/handles/meta.d.ts.map +0 -1
  594. package/dist/handles/meta.js +0 -202
  595. package/dist/handles/meta.js.map +0 -1
  596. package/dist/host/__tests__/errors.test.d.ts +0 -2
  597. package/dist/host/__tests__/errors.test.d.ts.map +0 -1
  598. package/dist/host/__tests__/errors.test.js +0 -76
  599. package/dist/host/__tests__/errors.test.js.map +0 -1
  600. package/dist/host/__tests__/pattern-comprehensive.test.d.ts +0 -2
  601. package/dist/host/__tests__/pattern-comprehensive.test.d.ts.map +0 -1
  602. package/dist/host/__tests__/pattern-comprehensive.test.js +0 -732
  603. package/dist/host/__tests__/pattern-comprehensive.test.js.map +0 -1
  604. package/dist/host/__tests__/pattern-matcher.test.d.ts +0 -2
  605. package/dist/host/__tests__/pattern-matcher.test.d.ts.map +0 -1
  606. package/dist/host/__tests__/pattern-matcher.test.js +0 -251
  607. package/dist/host/__tests__/pattern-matcher.test.js.map +0 -1
  608. package/dist/host/__tests__/router.test.d.ts +0 -2
  609. package/dist/host/__tests__/router.test.d.ts.map +0 -1
  610. package/dist/host/__tests__/router.test.js +0 -241
  611. package/dist/host/__tests__/router.test.js.map +0 -1
  612. package/dist/host/__tests__/testing.test.d.ts +0 -2
  613. package/dist/host/__tests__/testing.test.d.ts.map +0 -1
  614. package/dist/host/__tests__/testing.test.js +0 -64
  615. package/dist/host/__tests__/testing.test.js.map +0 -1
  616. package/dist/host/__tests__/utils.test.d.ts +0 -2
  617. package/dist/host/__tests__/utils.test.d.ts.map +0 -1
  618. package/dist/host/__tests__/utils.test.js +0 -29
  619. package/dist/host/__tests__/utils.test.js.map +0 -1
  620. package/dist/host/cookie-handler.d.ts +0 -34
  621. package/dist/host/cookie-handler.d.ts.map +0 -1
  622. package/dist/host/cookie-handler.js +0 -124
  623. package/dist/host/cookie-handler.js.map +0 -1
  624. package/dist/host/errors.d.ts +0 -56
  625. package/dist/host/errors.d.ts.map +0 -1
  626. package/dist/host/errors.js +0 -79
  627. package/dist/host/errors.js.map +0 -1
  628. package/dist/host/index.d.ts +0 -29
  629. package/dist/host/index.d.ts.map +0 -1
  630. package/dist/host/index.js +0 -32
  631. package/dist/host/index.js.map +0 -1
  632. package/dist/host/pattern-matcher.d.ts +0 -36
  633. package/dist/host/pattern-matcher.d.ts.map +0 -1
  634. package/dist/host/pattern-matcher.js +0 -172
  635. package/dist/host/pattern-matcher.js.map +0 -1
  636. package/dist/host/router.d.ts +0 -26
  637. package/dist/host/router.d.ts.map +0 -1
  638. package/dist/host/router.js +0 -218
  639. package/dist/host/router.js.map +0 -1
  640. package/dist/host/testing.d.ts +0 -36
  641. package/dist/host/testing.d.ts.map +0 -1
  642. package/dist/host/testing.js +0 -55
  643. package/dist/host/testing.js.map +0 -1
  644. package/dist/host/types.d.ts +0 -115
  645. package/dist/host/types.d.ts.map +0 -1
  646. package/dist/host/types.js +0 -7
  647. package/dist/host/types.js.map +0 -1
  648. package/dist/host/utils.d.ts +0 -21
  649. package/dist/host/utils.d.ts.map +0 -1
  650. package/dist/host/utils.js +0 -23
  651. package/dist/host/utils.js.map +0 -1
  652. package/dist/href-client.d.ts +0 -131
  653. package/dist/href-client.d.ts.map +0 -1
  654. package/dist/href-client.js +0 -64
  655. package/dist/href-client.js.map +0 -1
  656. package/dist/href-context.d.ts +0 -29
  657. package/dist/href-context.d.ts.map +0 -1
  658. package/dist/href-context.js +0 -21
  659. package/dist/href-context.js.map +0 -1
  660. package/dist/index.d.ts +0 -73
  661. package/dist/index.d.ts.map +0 -1
  662. package/dist/index.js +0 -91
  663. package/dist/index.js.map +0 -1
  664. package/dist/index.rsc.d.ts +0 -32
  665. package/dist/index.rsc.d.ts.map +0 -1
  666. package/dist/index.rsc.js +0 -40
  667. package/dist/index.rsc.js.map +0 -1
  668. package/dist/internal-debug.d.ts +0 -2
  669. package/dist/internal-debug.d.ts.map +0 -1
  670. package/dist/internal-debug.js +0 -5
  671. package/dist/internal-debug.js.map +0 -1
  672. package/dist/loader.d.ts +0 -14
  673. package/dist/loader.d.ts.map +0 -1
  674. package/dist/loader.js +0 -20
  675. package/dist/loader.js.map +0 -1
  676. package/dist/loader.rsc.d.ts +0 -19
  677. package/dist/loader.rsc.d.ts.map +0 -1
  678. package/dist/loader.rsc.js +0 -99
  679. package/dist/loader.rsc.js.map +0 -1
  680. package/dist/network-error-thrower.d.ts +0 -17
  681. package/dist/network-error-thrower.d.ts.map +0 -1
  682. package/dist/network-error-thrower.js +0 -14
  683. package/dist/network-error-thrower.js.map +0 -1
  684. package/dist/outlet-context.d.ts +0 -13
  685. package/dist/outlet-context.d.ts.map +0 -1
  686. package/dist/outlet-context.js +0 -3
  687. package/dist/outlet-context.js.map +0 -1
  688. package/dist/prerender/__tests__/param-hash.test.d.ts +0 -2
  689. package/dist/prerender/__tests__/param-hash.test.d.ts.map +0 -1
  690. package/dist/prerender/__tests__/param-hash.test.js +0 -148
  691. package/dist/prerender/__tests__/param-hash.test.js.map +0 -1
  692. package/dist/prerender/param-hash.d.ts +0 -16
  693. package/dist/prerender/param-hash.d.ts.map +0 -1
  694. package/dist/prerender/param-hash.js +0 -36
  695. package/dist/prerender/param-hash.js.map +0 -1
  696. package/dist/prerender/store.d.ts +0 -38
  697. package/dist/prerender/store.d.ts.map +0 -1
  698. package/dist/prerender/store.js +0 -61
  699. package/dist/prerender/store.js.map +0 -1
  700. package/dist/prerender.d.ts +0 -66
  701. package/dist/prerender.d.ts.map +0 -1
  702. package/dist/prerender.js +0 -57
  703. package/dist/prerender.js.map +0 -1
  704. package/dist/reverse.d.ts +0 -196
  705. package/dist/reverse.d.ts.map +0 -1
  706. package/dist/reverse.js +0 -78
  707. package/dist/reverse.js.map +0 -1
  708. package/dist/root-error-boundary.d.ts +0 -33
  709. package/dist/root-error-boundary.d.ts.map +0 -1
  710. package/dist/root-error-boundary.js +0 -165
  711. package/dist/root-error-boundary.js.map +0 -1
  712. package/dist/route-content-wrapper.d.ts +0 -46
  713. package/dist/route-content-wrapper.d.ts.map +0 -1
  714. package/dist/route-content-wrapper.js +0 -77
  715. package/dist/route-content-wrapper.js.map +0 -1
  716. package/dist/route-definition.d.ts +0 -421
  717. package/dist/route-definition.d.ts.map +0 -1
  718. package/dist/route-definition.js +0 -868
  719. package/dist/route-definition.js.map +0 -1
  720. package/dist/route-map-builder.d.ts +0 -155
  721. package/dist/route-map-builder.d.ts.map +0 -1
  722. package/dist/route-map-builder.js +0 -237
  723. package/dist/route-map-builder.js.map +0 -1
  724. package/dist/route-types.d.ts +0 -165
  725. package/dist/route-types.d.ts.map +0 -1
  726. package/dist/route-types.js +0 -7
  727. package/dist/route-types.js.map +0 -1
  728. package/dist/router/__tests__/handler-context.test.d.ts +0 -2
  729. package/dist/router/__tests__/handler-context.test.d.ts.map +0 -1
  730. package/dist/router/__tests__/handler-context.test.js +0 -65
  731. package/dist/router/__tests__/handler-context.test.js.map +0 -1
  732. package/dist/router/__tests__/loader-cycle-detection.test.d.ts +0 -2
  733. package/dist/router/__tests__/loader-cycle-detection.test.d.ts.map +0 -1
  734. package/dist/router/__tests__/loader-cycle-detection.test.js +0 -221
  735. package/dist/router/__tests__/loader-cycle-detection.test.js.map +0 -1
  736. package/dist/router/__tests__/match-context.test.d.ts +0 -2
  737. package/dist/router/__tests__/match-context.test.d.ts.map +0 -1
  738. package/dist/router/__tests__/match-context.test.js +0 -92
  739. package/dist/router/__tests__/match-context.test.js.map +0 -1
  740. package/dist/router/__tests__/match-pipelines.test.d.ts +0 -2
  741. package/dist/router/__tests__/match-pipelines.test.d.ts.map +0 -1
  742. package/dist/router/__tests__/match-pipelines.test.js +0 -417
  743. package/dist/router/__tests__/match-pipelines.test.js.map +0 -1
  744. package/dist/router/__tests__/match-result.test.d.ts +0 -2
  745. package/dist/router/__tests__/match-result.test.d.ts.map +0 -1
  746. package/dist/router/__tests__/match-result.test.js +0 -457
  747. package/dist/router/__tests__/match-result.test.js.map +0 -1
  748. package/dist/router/__tests__/on-error.test.d.ts +0 -2
  749. package/dist/router/__tests__/on-error.test.d.ts.map +0 -1
  750. package/dist/router/__tests__/on-error.test.js +0 -678
  751. package/dist/router/__tests__/on-error.test.js.map +0 -1
  752. package/dist/router/__tests__/pattern-matching.test.d.ts +0 -2
  753. package/dist/router/__tests__/pattern-matching.test.d.ts.map +0 -1
  754. package/dist/router/__tests__/pattern-matching.test.js +0 -629
  755. package/dist/router/__tests__/pattern-matching.test.js.map +0 -1
  756. package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts +0 -2
  757. package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts.map +0 -1
  758. package/dist/router/__tests__/segment-resolution-parallel-loading.test.js +0 -155
  759. package/dist/router/__tests__/segment-resolution-parallel-loading.test.js.map +0 -1
  760. package/dist/router/error-handling.d.ts +0 -77
  761. package/dist/router/error-handling.d.ts.map +0 -1
  762. package/dist/router/error-handling.js +0 -202
  763. package/dist/router/error-handling.js.map +0 -1
  764. package/dist/router/handler-context.d.ts +0 -20
  765. package/dist/router/handler-context.d.ts.map +0 -1
  766. package/dist/router/handler-context.js +0 -198
  767. package/dist/router/handler-context.js.map +0 -1
  768. package/dist/router/intercept-resolution.d.ts +0 -66
  769. package/dist/router/intercept-resolution.d.ts.map +0 -1
  770. package/dist/router/intercept-resolution.js +0 -246
  771. package/dist/router/intercept-resolution.js.map +0 -1
  772. package/dist/router/loader-resolution.d.ts +0 -64
  773. package/dist/router/loader-resolution.d.ts.map +0 -1
  774. package/dist/router/loader-resolution.js +0 -284
  775. package/dist/router/loader-resolution.js.map +0 -1
  776. package/dist/router/logging.d.ts +0 -15
  777. package/dist/router/logging.d.ts.map +0 -1
  778. package/dist/router/logging.js +0 -99
  779. package/dist/router/logging.js.map +0 -1
  780. package/dist/router/manifest.d.ts +0 -22
  781. package/dist/router/manifest.d.ts.map +0 -1
  782. package/dist/router/manifest.js +0 -181
  783. package/dist/router/manifest.js.map +0 -1
  784. package/dist/router/match-api.d.ts +0 -35
  785. package/dist/router/match-api.d.ts.map +0 -1
  786. package/dist/router/match-api.js +0 -406
  787. package/dist/router/match-api.js.map +0 -1
  788. package/dist/router/match-context.d.ts +0 -206
  789. package/dist/router/match-context.d.ts.map +0 -1
  790. package/dist/router/match-context.js +0 -17
  791. package/dist/router/match-context.js.map +0 -1
  792. package/dist/router/match-middleware/background-revalidation.d.ts +0 -127
  793. package/dist/router/match-middleware/background-revalidation.d.ts.map +0 -1
  794. package/dist/router/match-middleware/background-revalidation.js +0 -75
  795. package/dist/router/match-middleware/background-revalidation.js.map +0 -1
  796. package/dist/router/match-middleware/cache-lookup.d.ts +0 -112
  797. package/dist/router/match-middleware/cache-lookup.d.ts.map +0 -1
  798. package/dist/router/match-middleware/cache-lookup.js +0 -257
  799. package/dist/router/match-middleware/cache-lookup.js.map +0 -1
  800. package/dist/router/match-middleware/cache-store.d.ts +0 -113
  801. package/dist/router/match-middleware/cache-store.d.ts.map +0 -1
  802. package/dist/router/match-middleware/cache-store.js +0 -108
  803. package/dist/router/match-middleware/cache-store.js.map +0 -1
  804. package/dist/router/match-middleware/index.d.ts +0 -81
  805. package/dist/router/match-middleware/index.d.ts.map +0 -1
  806. package/dist/router/match-middleware/index.js +0 -80
  807. package/dist/router/match-middleware/index.js.map +0 -1
  808. package/dist/router/match-middleware/intercept-resolution.d.ts +0 -117
  809. package/dist/router/match-middleware/intercept-resolution.d.ts.map +0 -1
  810. package/dist/router/match-middleware/intercept-resolution.js +0 -134
  811. package/dist/router/match-middleware/intercept-resolution.js.map +0 -1
  812. package/dist/router/match-middleware/segment-resolution.d.ts +0 -99
  813. package/dist/router/match-middleware/segment-resolution.d.ts.map +0 -1
  814. package/dist/router/match-middleware/segment-resolution.js +0 -53
  815. package/dist/router/match-middleware/segment-resolution.js.map +0 -1
  816. package/dist/router/match-pipelines.d.ts +0 -147
  817. package/dist/router/match-pipelines.d.ts.map +0 -1
  818. package/dist/router/match-pipelines.js +0 -82
  819. package/dist/router/match-pipelines.js.map +0 -1
  820. package/dist/router/match-result.d.ts +0 -126
  821. package/dist/router/match-result.d.ts.map +0 -1
  822. package/dist/router/match-result.js +0 -93
  823. package/dist/router/match-result.js.map +0 -1
  824. package/dist/router/metrics.d.ts +0 -20
  825. package/dist/router/metrics.d.ts.map +0 -1
  826. package/dist/router/metrics.js +0 -47
  827. package/dist/router/metrics.js.map +0 -1
  828. package/dist/router/middleware.d.ts +0 -249
  829. package/dist/router/middleware.d.ts.map +0 -1
  830. package/dist/router/middleware.js +0 -434
  831. package/dist/router/middleware.js.map +0 -1
  832. package/dist/router/middleware.test.d.ts +0 -2
  833. package/dist/router/middleware.test.d.ts.map +0 -1
  834. package/dist/router/middleware.test.js +0 -816
  835. package/dist/router/middleware.test.js.map +0 -1
  836. package/dist/router/pattern-matching.d.ts +0 -149
  837. package/dist/router/pattern-matching.d.ts.map +0 -1
  838. package/dist/router/pattern-matching.js +0 -349
  839. package/dist/router/pattern-matching.js.map +0 -1
  840. package/dist/router/revalidation.d.ts +0 -44
  841. package/dist/router/revalidation.d.ts.map +0 -1
  842. package/dist/router/revalidation.js +0 -147
  843. package/dist/router/revalidation.js.map +0 -1
  844. package/dist/router/router-context.d.ts +0 -135
  845. package/dist/router/router-context.d.ts.map +0 -1
  846. package/dist/router/router-context.js +0 -36
  847. package/dist/router/router-context.js.map +0 -1
  848. package/dist/router/segment-resolution.d.ts +0 -127
  849. package/dist/router/segment-resolution.d.ts.map +0 -1
  850. package/dist/router/segment-resolution.js +0 -919
  851. package/dist/router/segment-resolution.js.map +0 -1
  852. package/dist/router/trie-matching.d.ts +0 -40
  853. package/dist/router/trie-matching.d.ts.map +0 -1
  854. package/dist/router/trie-matching.js +0 -127
  855. package/dist/router/trie-matching.js.map +0 -1
  856. package/dist/router/types.d.ts +0 -136
  857. package/dist/router/types.d.ts.map +0 -1
  858. package/dist/router/types.js +0 -7
  859. package/dist/router/types.js.map +0 -1
  860. package/dist/router.d.ts +0 -753
  861. package/dist/router.d.ts.map +0 -1
  862. package/dist/router.gen.d.ts +0 -6
  863. package/dist/router.gen.d.ts.map +0 -1
  864. package/dist/router.gen.js +0 -6
  865. package/dist/router.gen.js.map +0 -1
  866. package/dist/router.js +0 -1304
  867. package/dist/router.js.map +0 -1
  868. package/dist/rsc/__tests__/helpers.test.d.ts +0 -2
  869. package/dist/rsc/__tests__/helpers.test.d.ts.map +0 -1
  870. package/dist/rsc/__tests__/helpers.test.js +0 -140
  871. package/dist/rsc/__tests__/helpers.test.js.map +0 -1
  872. package/dist/rsc/handler.d.ts +0 -45
  873. package/dist/rsc/handler.d.ts.map +0 -1
  874. package/dist/rsc/handler.js +0 -1172
  875. package/dist/rsc/handler.js.map +0 -1
  876. package/dist/rsc/helpers.d.ts +0 -16
  877. package/dist/rsc/helpers.d.ts.map +0 -1
  878. package/dist/rsc/helpers.js +0 -55
  879. package/dist/rsc/helpers.js.map +0 -1
  880. package/dist/rsc/index.d.ts +0 -22
  881. package/dist/rsc/index.d.ts.map +0 -1
  882. package/dist/rsc/index.js +0 -23
  883. package/dist/rsc/index.js.map +0 -1
  884. package/dist/rsc/nonce.d.ts +0 -9
  885. package/dist/rsc/nonce.d.ts.map +0 -1
  886. package/dist/rsc/nonce.js +0 -18
  887. package/dist/rsc/nonce.js.map +0 -1
  888. package/dist/rsc/types.d.ts +0 -206
  889. package/dist/rsc/types.d.ts.map +0 -1
  890. package/dist/rsc/types.js +0 -8
  891. package/dist/rsc/types.js.map +0 -1
  892. package/dist/search-params.d.ts +0 -103
  893. package/dist/search-params.d.ts.map +0 -1
  894. package/dist/search-params.js +0 -74
  895. package/dist/search-params.js.map +0 -1
  896. package/dist/segment-system.d.ts +0 -75
  897. package/dist/segment-system.d.ts.map +0 -1
  898. package/dist/segment-system.js +0 -336
  899. package/dist/segment-system.js.map +0 -1
  900. package/dist/server/context.d.ts +0 -245
  901. package/dist/server/context.d.ts.map +0 -1
  902. package/dist/server/context.js +0 -197
  903. package/dist/server/context.js.map +0 -1
  904. package/dist/server/fetchable-loader-store.d.ts +0 -18
  905. package/dist/server/fetchable-loader-store.d.ts.map +0 -1
  906. package/dist/server/fetchable-loader-store.js +0 -18
  907. package/dist/server/fetchable-loader-store.js.map +0 -1
  908. package/dist/server/handle-store.d.ts +0 -85
  909. package/dist/server/handle-store.d.ts.map +0 -1
  910. package/dist/server/handle-store.js +0 -142
  911. package/dist/server/handle-store.js.map +0 -1
  912. package/dist/server/loader-registry.d.ts +0 -55
  913. package/dist/server/loader-registry.d.ts.map +0 -1
  914. package/dist/server/loader-registry.js +0 -132
  915. package/dist/server/loader-registry.js.map +0 -1
  916. package/dist/server/request-context.d.ts +0 -226
  917. package/dist/server/request-context.d.ts.map +0 -1
  918. package/dist/server/request-context.js +0 -290
  919. package/dist/server/request-context.js.map +0 -1
  920. package/dist/server/root-layout.d.ts +0 -4
  921. package/dist/server/root-layout.d.ts.map +0 -1
  922. package/dist/server/root-layout.js +0 -5
  923. package/dist/server/root-layout.js.map +0 -1
  924. package/dist/server.d.ts +0 -15
  925. package/dist/server.d.ts.map +0 -1
  926. package/dist/server.js +0 -20
  927. package/dist/server.js.map +0 -1
  928. package/dist/ssr/__tests__/ssr-handler.test.d.ts +0 -2
  929. package/dist/ssr/__tests__/ssr-handler.test.d.ts.map +0 -1
  930. package/dist/ssr/__tests__/ssr-handler.test.js +0 -132
  931. package/dist/ssr/__tests__/ssr-handler.test.js.map +0 -1
  932. package/dist/ssr/index.d.ts +0 -98
  933. package/dist/ssr/index.d.ts.map +0 -1
  934. package/dist/ssr/index.js +0 -158
  935. package/dist/ssr/index.js.map +0 -1
  936. package/dist/static-handler.d.ts +0 -50
  937. package/dist/static-handler.d.ts.map +0 -1
  938. package/dist/static-handler.gen.d.ts +0 -5
  939. package/dist/static-handler.gen.d.ts.map +0 -1
  940. package/dist/static-handler.gen.js +0 -5
  941. package/dist/static-handler.gen.js.map +0 -1
  942. package/dist/static-handler.js +0 -29
  943. package/dist/static-handler.js.map +0 -1
  944. package/dist/theme/ThemeProvider.d.ts +0 -20
  945. package/dist/theme/ThemeProvider.d.ts.map +0 -1
  946. package/dist/theme/ThemeProvider.js +0 -240
  947. package/dist/theme/ThemeProvider.js.map +0 -1
  948. package/dist/theme/ThemeScript.d.ts +0 -48
  949. package/dist/theme/ThemeScript.d.ts.map +0 -1
  950. package/dist/theme/ThemeScript.js +0 -13
  951. package/dist/theme/ThemeScript.js.map +0 -1
  952. package/dist/theme/__tests__/theme.test.d.ts +0 -2
  953. package/dist/theme/__tests__/theme.test.d.ts.map +0 -1
  954. package/dist/theme/__tests__/theme.test.js +0 -103
  955. package/dist/theme/__tests__/theme.test.js.map +0 -1
  956. package/dist/theme/constants.d.ts +0 -29
  957. package/dist/theme/constants.d.ts.map +0 -1
  958. package/dist/theme/constants.js +0 -48
  959. package/dist/theme/constants.js.map +0 -1
  960. package/dist/theme/index.d.ts +0 -31
  961. package/dist/theme/index.d.ts.map +0 -1
  962. package/dist/theme/index.js +0 -36
  963. package/dist/theme/index.js.map +0 -1
  964. package/dist/theme/theme-context.d.ts +0 -40
  965. package/dist/theme/theme-context.d.ts.map +0 -1
  966. package/dist/theme/theme-context.js +0 -60
  967. package/dist/theme/theme-context.js.map +0 -1
  968. package/dist/theme/theme-script.d.ts +0 -27
  969. package/dist/theme/theme-script.d.ts.map +0 -1
  970. package/dist/theme/theme-script.js +0 -147
  971. package/dist/theme/theme-script.js.map +0 -1
  972. package/dist/theme/types.d.ts +0 -163
  973. package/dist/theme/types.d.ts.map +0 -1
  974. package/dist/theme/types.js +0 -11
  975. package/dist/theme/types.js.map +0 -1
  976. package/dist/theme/use-theme.d.ts +0 -12
  977. package/dist/theme/use-theme.d.ts.map +0 -1
  978. package/dist/theme/use-theme.js +0 -40
  979. package/dist/theme/use-theme.js.map +0 -1
  980. package/dist/types.d.ts +0 -1479
  981. package/dist/types.d.ts.map +0 -1
  982. package/dist/types.js +0 -10
  983. package/dist/types.js.map +0 -1
  984. package/dist/urls.d.ts +0 -441
  985. package/dist/urls.d.ts.map +0 -1
  986. package/dist/urls.gen.d.ts +0 -8
  987. package/dist/urls.gen.d.ts.map +0 -1
  988. package/dist/urls.gen.js +0 -8
  989. package/dist/urls.gen.js.map +0 -1
  990. package/dist/urls.js +0 -443
  991. package/dist/urls.js.map +0 -1
  992. package/dist/use-loader.d.ts +0 -127
  993. package/dist/use-loader.d.ts.map +0 -1
  994. package/dist/use-loader.js +0 -237
  995. package/dist/use-loader.js.map +0 -1
  996. package/dist/vite/__tests__/ast-handler-extract.test.d.ts +0 -2
  997. package/dist/vite/__tests__/ast-handler-extract.test.d.ts.map +0 -1
  998. package/dist/vite/__tests__/ast-handler-extract.test.js +0 -294
  999. package/dist/vite/__tests__/ast-handler-extract.test.js.map +0 -1
  1000. package/dist/vite/__tests__/expose-id-utils.test.d.ts +0 -2
  1001. package/dist/vite/__tests__/expose-id-utils.test.d.ts.map +0 -1
  1002. package/dist/vite/__tests__/expose-id-utils.test.js +0 -224
  1003. package/dist/vite/__tests__/expose-id-utils.test.js.map +0 -1
  1004. package/dist/vite/__tests__/expose-internal-ids.test.d.ts +0 -2
  1005. package/dist/vite/__tests__/expose-internal-ids.test.d.ts.map +0 -1
  1006. package/dist/vite/__tests__/expose-internal-ids.test.js +0 -647
  1007. package/dist/vite/__tests__/expose-internal-ids.test.js.map +0 -1
  1008. package/dist/vite/__tests__/expose-router-id.test.d.ts +0 -2
  1009. package/dist/vite/__tests__/expose-router-id.test.d.ts.map +0 -1
  1010. package/dist/vite/__tests__/expose-router-id.test.js +0 -39
  1011. package/dist/vite/__tests__/expose-router-id.test.js.map +0 -1
  1012. package/dist/vite/ast-handler-extract.d.ts +0 -49
  1013. package/dist/vite/ast-handler-extract.d.ts.map +0 -1
  1014. package/dist/vite/ast-handler-extract.js +0 -249
  1015. package/dist/vite/ast-handler-extract.js.map +0 -1
  1016. package/dist/vite/expose-action-id.d.ts +0 -19
  1017. package/dist/vite/expose-action-id.d.ts.map +0 -1
  1018. package/dist/vite/expose-action-id.js +0 -250
  1019. package/dist/vite/expose-action-id.js.map +0 -1
  1020. package/dist/vite/expose-id-utils.d.ts +0 -69
  1021. package/dist/vite/expose-id-utils.d.ts.map +0 -1
  1022. package/dist/vite/expose-id-utils.js +0 -289
  1023. package/dist/vite/expose-id-utils.js.map +0 -1
  1024. package/dist/vite/expose-internal-ids.d.ts +0 -22
  1025. package/dist/vite/expose-internal-ids.d.ts.map +0 -1
  1026. package/dist/vite/expose-internal-ids.js +0 -886
  1027. package/dist/vite/expose-internal-ids.js.map +0 -1
  1028. package/dist/vite/index.d.ts +0 -149
  1029. package/dist/vite/index.d.ts.map +0 -1
  1030. package/dist/vite/index.js.bak +0 -5448
  1031. package/dist/vite/index.js.map +0 -1
  1032. package/dist/vite/index.named-routes.gen.ts +0 -103
  1033. package/dist/vite/package-resolution.d.ts +0 -43
  1034. package/dist/vite/package-resolution.d.ts.map +0 -1
  1035. package/dist/vite/package-resolution.js +0 -112
  1036. package/dist/vite/package-resolution.js.map +0 -1
  1037. package/dist/vite/virtual-entries.d.ts +0 -25
  1038. package/dist/vite/virtual-entries.d.ts.map +0 -1
  1039. package/dist/vite/virtual-entries.js +0 -110
  1040. package/dist/vite/virtual-entries.js.map +0 -1
  1041. package/src/browser/action-response-classifier.ts +0 -99
@@ -45,6 +45,8 @@ import {
45
45
  resolveSwrWindow,
46
46
  DEFAULT_FUNCTION_TTL,
47
47
  } from "../cache-policy.js";
48
+ import { reportCacheError, reportingAsync } from "../cache-error.js";
49
+ import type { CacheErrorCategory } from "../cache-error.js";
48
50
 
49
51
  // ============================================================================
50
52
  // Constants
@@ -56,6 +58,49 @@ export const CACHE_STALE_AT_HEADER = "x-edge-cache-stale-at";
56
58
  /** Header storing cache status: HIT | REVALIDATING */
57
59
  export const CACHE_STATUS_HEADER = "x-edge-cache-status";
58
60
 
61
+ /**
62
+ * Header storing this entry's cache tags as a JSON array. JSON-encoded (not the
63
+ * comma-delimited CF `Cache-Tag` format) so tags containing commas round-trip
64
+ * safely; the read paths parse this to run the tag-invalidation check.
65
+ */
66
+ export const CACHE_TAGS_HEADER = "x-edge-cache-tags";
67
+
68
+ /** Header storing the ms-epoch timestamp when this entry's tags were attached. */
69
+ export const CACHE_TAGGED_AT_HEADER = "x-edge-cache-tagged-at";
70
+
71
+ /**
72
+ * KV key prefix for tag-invalidation markers. A marker stores the ms-epoch
73
+ * timestamp of the most recent invalidation of a tag; reads treat any entry
74
+ * whose taggedAt is older than its tags' latest marker as invalidated. Markers
75
+ * live in the SAME KV namespace as the cached entries - there is no separate
76
+ * tag-invalidation store.
77
+ */
78
+ const TAG_MARKER_PREFIX = "__tag__/";
79
+
80
+ /**
81
+ * Cache-API path prefix for the optional per-colo L1 cache of tag-invalidation
82
+ * markers (enabled by tagCacheTtl). Distinct from data keys (doc:/fn:/segment)
83
+ * and from the KV marker prefix so the two never collide.
84
+ */
85
+ const TAG_MARKER_CACHE_PREFIX = "__tagmarker__/";
86
+
87
+ /**
88
+ * Sentinel body for an L1-cached marker meaning "this tag has no invalidation
89
+ * marker." Distinct from any real ms-epoch timestamp (always a large positive
90
+ * integer). A Cache API miss (match() === undefined) always means "re-read KV",
91
+ * never "no marker" - absence is only ever represented by this cached sentinel.
92
+ */
93
+ const TAG_MARKER_ABSENT = "none";
94
+
95
+ /**
96
+ * Header stashing the route author's original Cache-Control on L1 document
97
+ * entries. putResponse/promoteResponseToL1 overwrite Cache-Control with a long
98
+ * `max-age` so the CF Cache API retains the entry across the whole SWR window;
99
+ * getResponse restores this original value before serving so the client and any
100
+ * upstream CDN see the author's intended directive, not the internal edge TTL.
101
+ */
102
+ const CACHE_ORIG_CC_HEADER = "x-edge-cache-orig-cc";
103
+
59
104
  /**
60
105
  * Maximum age in seconds for REVALIDATING status before allowing new revalidation.
61
106
  * After this period, a stale entry in REVALIDATING status will trigger revalidation again.
@@ -63,18 +108,84 @@ export const CACHE_STATUS_HEADER = "x-edge-cache-status";
63
108
  */
64
109
  export const MAX_REVALIDATION_INTERVAL = 30;
65
110
 
66
- // ============================================================================
67
- // Types
68
- // ============================================================================
111
+ /**
112
+ * Per-request memo of tag-invalidation markers (tag -> latest invalidatedAt, or
113
+ * null when no marker exists). Keyed by the request context object so it is
114
+ * naturally request-scoped and garbage-collected with the request.
115
+ *
116
+ * Without it, isGloballyInvalidated() issues a KV read per tag on every tagged
117
+ * cache read, so a page composed of many segments/items sharing a tag pays that
118
+ * cost N times. The memo collapses it to one KV read per distinct tag per
119
+ * request. invalidateTags() writes through so a same-request updateTag() stays
120
+ * read-your-own-writes consistent (the action's own re-render sees its own
121
+ * invalidation from the memo, without a re-read).
122
+ *
123
+ * It does NOT span requests, so a hot single-entry route still pays one KV read
124
+ * per request; that read hits Cloudflare KV's own edge read cache for hot keys.
125
+ */
126
+ const tagMarkerMemo = new WeakMap<object, Map<string, number | null>>();
127
+
128
+ function getTagMarkerMemo(ctx: object): Map<string, number | null> {
129
+ let memo = tagMarkerMemo.get(ctx);
130
+ if (!memo) {
131
+ memo = new Map();
132
+ tagMarkerMemo.set(ctx, memo);
133
+ }
134
+ return memo;
135
+ }
69
136
 
70
137
  /**
71
- * Cloudflare Workers ExecutionContext (subset we need)
138
+ * Per-request map of IN-FLIGHT marker reads (tag -> the pending read promise).
139
+ * The resolved-value memo above only collapses SEQUENTIAL reads of a tag; the
140
+ * router resolves sibling segments in PARALLEL, so without this several
141
+ * concurrently-resolving segments sharing a tag would each issue their own KV
142
+ * read before any of them populates the memo. Sharing the in-flight promise
143
+ * collapses those to a single KV read. Entries are dropped once resolved (the
144
+ * value is then in the memo), so this only spans the concurrent read window.
72
145
  */
73
- export interface ExecutionContext {
74
- waitUntil(promise: Promise<any>): void;
75
- passThroughOnException(): void;
146
+ const tagMarkerInflight = new WeakMap<
147
+ object,
148
+ Map<string, Promise<number | null>>
149
+ >();
150
+
151
+ function getTagMarkerInflight(
152
+ ctx: object,
153
+ ): Map<string, Promise<number | null>> {
154
+ let inflight = tagMarkerInflight.get(ctx);
155
+ if (!inflight) {
156
+ inflight = new Map();
157
+ tagMarkerInflight.set(ctx, inflight);
158
+ }
159
+ return inflight;
76
160
  }
77
161
 
162
+ /** KV key byte-length ceiling. Cloudflare KV rejects keys larger than this. */
163
+ const KV_MAX_KEY_BYTES = 512;
164
+
165
+ const kvKeyEncoder = new TextEncoder();
166
+
167
+ /** UTF-8 byte length of a KV key (multibyte tags can exceed the char count). */
168
+ function kvKeyByteLength(key: string): number {
169
+ return kvKeyEncoder.encode(key).length;
170
+ }
171
+
172
+ /**
173
+ * Stores (by namespace) already warned about tag machinery configured without a
174
+ * KV namespace, so the warning fires once per process rather than per request
175
+ * (CFCacheStore is constructed per request).
176
+ */
177
+ const warnedNoKvReadInvalidation = new Set<string>();
178
+
179
+ // ============================================================================
180
+ // Types
181
+ // ============================================================================
182
+
183
+ // Re-exported from the canonical home so cf-cache-store consumers keep
184
+ // importing `ExecutionContext` from this module without a second interface
185
+ // drifting over time.
186
+ export type { ExecutionContext } from "../../types/request-scope.js";
187
+ import type { ExecutionContext } from "../../types/request-scope.js";
188
+
78
189
  /**
79
190
  * Minimal Cloudflare KV Namespace interface.
80
191
  * Avoids hard dependency on @cloudflare/workers-types.
@@ -115,6 +226,10 @@ interface KVItemEnvelope {
115
226
  s: number;
116
227
  /** When entry hard-expires (ms epoch) */
117
228
  e: number;
229
+ /** Cache tags (for distributed tag invalidation) */
230
+ t?: string[];
231
+ /** Timestamp when tags were attached (ms epoch) */
232
+ ta?: number;
118
233
  }
119
234
 
120
235
  /**
@@ -128,12 +243,16 @@ interface KVResponseEnvelope {
128
243
  st: number;
129
244
  /** HTTP status text */
130
245
  stx: string;
131
- /** Serialized headers as key-value pairs */
246
+ /** Serialized headers as key-value pairs (client-facing; no internal headers) */
132
247
  hd: [string, string][];
133
248
  /** When entry becomes stale (ms epoch) */
134
249
  s: number;
135
250
  /** When entry hard-expires (ms epoch) */
136
251
  e: number;
252
+ /** Cache tags (for distributed tag invalidation) */
253
+ t?: string[];
254
+ /** Timestamp when tags were attached (ms epoch) */
255
+ ta?: number;
137
256
  }
138
257
 
139
258
  export interface CFCacheStoreOptions<TEnv = unknown> {
@@ -177,14 +296,89 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
177
296
  * ```typescript
178
297
  * new CFCacheStore({ ctx: env.ctx, kv: env.CACHE_KV })
179
298
  * ```
299
+ *
300
+ * Tag-based invalidation (updateTag/revalidateTag) requires KV: the
301
+ * tag-invalidation markers are stored in this same namespace. There is no
302
+ * separate tag-invalidation store to configure.
180
303
  */
181
304
  kv?: KVNamespace;
182
305
 
306
+ /**
307
+ * Optional eager-purge hook, called ONCE per updateTag()/revalidateTag() with
308
+ * the namespaced Cloudflare Cache-Tags to purge (one batched call for the
309
+ * whole invalidation, not one per tag). These exactly match the `Cache-Tag`
310
+ * header this store writes on its tag-lookup marker entries
311
+ * (`rg:{namespace}:lk:{encodedTag}`), so forwarding them to Cloudflare's
312
+ * purge-by-tag API evicts the cached lookups in every colo - making
313
+ * cross-colo invalidation prompt instead of waiting out `tagCacheTtl`.
314
+ *
315
+ * Only meaningful with `tagCacheTtl > 0` (otherwise there are no cached
316
+ * lookups to purge). The values are pre-encoded, so commas in tag names are
317
+ * safe to pass straight to the purge API.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * onRevalidateTag: async (cacheTags) => {
322
+ * await fetch(`https://api.cloudflare.com/client/v4/zones/${ZONE}/purge_cache`, {
323
+ * method: "POST",
324
+ * headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
325
+ * body: JSON.stringify({ tags: cacheTags }),
326
+ * });
327
+ * }
328
+ * ```
329
+ */
330
+ onRevalidateTag?: (cacheTags: string[]) => Promise<void>;
331
+
332
+ /**
333
+ * Optional expiration (seconds) for tag-invalidation markers in KV. A marker
334
+ * must outlive every entry tagged before the invalidation, so this MUST
335
+ * exceed your largest entry TTL+SWR. Defaults to no expiration (markers
336
+ * persist; they are tiny - one timestamp per distinct invalidated tag).
337
+ *
338
+ * Note the opposite sizing from `tagCacheTtl` below: `tagInvalidationTtl` must
339
+ * be LARGE (outlive data); `tagCacheTtl` should be SMALL (a staleness ceiling).
340
+ *
341
+ * Cardinality matters: each DISTINCT invalidated tag writes one permanent KV
342
+ * marker (with the no-expiry default). Keep tags LOW-cardinality and never
343
+ * derive an invalidation tag from untrusted input (e.g.
344
+ * `revalidateTag(req.query.tag)`) - an attacker could otherwise grow your KV
345
+ * namespace without bound. Set a `tagInvalidationTtl` only if your tags are
346
+ * unavoidably high-cardinality AND it can still safely exceed your max entry
347
+ * TTL+SWR.
348
+ */
349
+ tagInvalidationTtl?: number;
350
+
351
+ /**
352
+ * Optional TTL (seconds) for caching tag-invalidation markers in the per-colo
353
+ * Cache API (L1), to avoid a KV marker read on every tagged cache read.
354
+ *
355
+ * Default `0` = disabled: the marker is read from KV on every tagged read
356
+ * (today's behavior), giving the strongest cross-colo invalidation latency
357
+ * (~KV consistency). A positive value caches each marker (including the
358
+ * "no marker yet" state) in L1 for that many seconds, so within the window a
359
+ * colo answers from L1 with no KV read.
360
+ *
361
+ * The colo that runs `updateTag`/`revalidateTag` writes the fresh marker
362
+ * straight into its own L1 (write-through), so it observes the invalidation
363
+ * immediately. By default OTHER colos only converge when their cached marker
364
+ * expires, so `tagCacheTtl` is the MAXIMUM extra cross-colo invalidation
365
+ * latency for them. Recommended 30-60 for high-read, low-mutation tags; leave
366
+ * at 0 when prompt global invalidation matters and you cannot wire a purge.
367
+ *
368
+ * To make other colos prompt WITHOUT a short TTL, wire `onRevalidateTag` to a
369
+ * Cloudflare purge-by-tag call: each marker entry carries a namespaced
370
+ * `Cache-Tag`, and `onRevalidateTag` is handed exactly those tags to purge, so
371
+ * the cached lookups are evicted everywhere on invalidation. With a purge
372
+ * wired, `tagCacheTtl` becomes purely a read-cost reducer + fallback window
373
+ * (safe to set large) rather than the invalidation-latency ceiling.
374
+ */
375
+ tagCacheTtl?: number;
376
+
183
377
  /**
184
378
  * Cache version string override. When this changes, all cached entries are
185
379
  * effectively invalidated (new keys won't match old entries).
186
380
  *
187
- * Defaults to the auto-generated VERSION from `rsc-router:version` virtual module.
381
+ * Defaults to the auto-generated VERSION from the `@rangojs/router:version` virtual module.
188
382
  * Only set this if you need a custom versioning strategy.
189
383
  */
190
384
  version?: string;
@@ -194,6 +388,12 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
194
388
  * Receives the full RequestContext (including env) and the default-generated key.
195
389
  * Return value becomes the final cache key (unless route overrides with `key` option).
196
390
  *
391
+ * Reserved prefixes: tag-invalidation markers live in the SAME KV namespace as
392
+ * data, keyed `__tag__/<tag>` (and `__tagmarker__/<tag>` for the L1 cache). A
393
+ * returned key must NOT begin with `__tag__/` or `__tagmarker__/`, or it can
394
+ * collide with a tag marker and corrupt invalidation. The documented
395
+ * prepend-style generators below are safe.
396
+ *
197
397
  * @example Using headers for user segmentation
198
398
  * ```typescript
199
399
  * keyGenerator: (ctx, defaultKey) => {
@@ -246,6 +446,9 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
246
446
  private readonly waitUntil?: (fn: () => Promise<void>) => void;
247
447
  private readonly version?: string;
248
448
  private readonly kv?: KVNamespace;
449
+ private readonly onRevalidateTag?: (tags: string[]) => Promise<void>;
450
+ private readonly tagInvalidationTtl?: number;
451
+ private readonly tagCacheTtl: number;
249
452
 
250
453
  constructor(options: CFCacheStoreOptions<TEnv>) {
251
454
  if (!options.ctx) {
@@ -263,6 +466,28 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
263
466
  this.keyGenerator = options.keyGenerator;
264
467
  this.waitUntil = (fn) => options.ctx.waitUntil(fn());
265
468
  this.kv = options.kv;
469
+ this.onRevalidateTag = options.onRevalidateTag;
470
+ this.tagInvalidationTtl = options.tagInvalidationTtl;
471
+ this.tagCacheTtl = options.tagCacheTtl ?? 0;
472
+
473
+ // Read-side tag invalidation requires KV: isGloballyInvalidated() compares an
474
+ // entry's taggedAt against the per-tag KV marker and short-circuits to "not
475
+ // invalidated" when no KV namespace is configured. A consumer who wires the
476
+ // tag machinery (tagCacheTtl for L1 markers, or onRevalidateTag for CDN purge)
477
+ // but omits kv gets markers written and the purge fired, yet every tagged read
478
+ // still serves stale data with no other signal. Surface that misconfiguration.
479
+ if (!this.kv && (this.tagCacheTtl > 0 || this.onRevalidateTag)) {
480
+ const id = this.namespace ?? "default";
481
+ if (!warnedNoKvReadInvalidation.has(id)) {
482
+ warnedNoKvReadInvalidation.add(id);
483
+ console.warn(
484
+ `[CFCacheStore] tagCacheTtl/onRevalidateTag is configured without a KV ` +
485
+ `namespace, so tag invalidation has NO read-side effect: tagged reads ` +
486
+ `are never treated as invalidated and serve stale data. Configure ` +
487
+ `{ kv } for distributed tag invalidation.`,
488
+ );
489
+ }
490
+ }
266
491
  }
267
492
 
268
493
  /**
@@ -320,6 +545,39 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
320
545
  // Segment Cache Methods
321
546
  // ============================================================================
322
547
 
548
+ /**
549
+ * Guard the segment tier against a `keyGenerator` that returns a key colliding
550
+ * with a reserved tag-marker namespace: `__tag__/` (the KV marker key) or
551
+ * `__tagmarker__/` (the L1 Cache API marker request). The item/doc tiers are
552
+ * internally prefixed (`fn:`/`doc:`) so only the bare segment key can collide;
553
+ * a collision would let a segment write clobber - or a segment read/delete
554
+ * evict - a live tag marker, silently breaking invalidation. Report loudly
555
+ * (so a misconfigured keyGenerator surfaces immediately) and treat the segment
556
+ * operation as a miss/no-op rather than corrupting the marker namespace.
557
+ * @internal
558
+ */
559
+ private isReservedSegmentKey(
560
+ key: string,
561
+ category: CacheErrorCategory,
562
+ ): boolean {
563
+ const reserved = key.startsWith(TAG_MARKER_PREFIX)
564
+ ? TAG_MARKER_PREFIX
565
+ : key.startsWith(TAG_MARKER_CACHE_PREFIX)
566
+ ? TAG_MARKER_CACHE_PREFIX
567
+ : null;
568
+ if (!reserved) return false;
569
+ reportCacheError(
570
+ new Error(
571
+ `segment key "${key}" collides with the reserved "${reserved}" ` +
572
+ `tag-marker namespace; the operation is ignored. Fix the store ` +
573
+ `keyGenerator so it does not produce keys with this prefix.`,
574
+ ),
575
+ category,
576
+ "[CFCacheStore] reserved key",
577
+ );
578
+ return true;
579
+ }
580
+
323
581
  /**
324
582
  * Get cached entry data by key.
325
583
  *
@@ -332,6 +590,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
332
590
  * KV hits are promoted to L1 in the background.
333
591
  */
334
592
  async get(key: string): Promise<CacheGetResult | null> {
593
+ if (this.isReservedSegmentKey(key, "cache-read")) return null;
335
594
  try {
336
595
  const cache = await this.getCache();
337
596
  const request = this.keyToRequest(key);
@@ -341,6 +600,13 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
341
600
  return this.kvGetSegment(key);
342
601
  }
343
602
 
603
+ // Tag invalidation: an entry whose tags were invalidated after it was
604
+ // cached is treated as a miss, so the next render re-populates it.
605
+ const tagInfo = this.readTagInfo(response.headers);
606
+ if (await this.isGloballyInvalidated(tagInfo.tags, tagInfo.taggedAt)) {
607
+ return null;
608
+ }
609
+
344
610
  // Read status headers
345
611
  const status = response.headers.get(CACHE_STATUS_HEADER);
346
612
  const age = Number(response.headers.get("age") ?? "0");
@@ -354,8 +620,12 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
354
620
 
355
621
  // Case 1: Fresh or already being revalidated - just return data
356
622
  if (!isStale || isRevalidating) {
357
- const data = (await response.json()) as CachedEntryData;
358
- return { data, shouldRevalidate: false };
623
+ const data = await this.parseOrEvict<CachedEntryData>(
624
+ () => response.json() as Promise<CachedEntryData>,
625
+ () => cache.delete(request),
626
+ "get",
627
+ );
628
+ return data === null ? null : { data, shouldRevalidate: false };
359
629
  }
360
630
 
361
631
  // Case 2: Stale and needs revalidation - atomically mark REVALIDATING
@@ -370,10 +640,14 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
370
640
  new Response(b1, { status: response.status, headers }),
371
641
  );
372
642
 
373
- const data = (await new Response(b2).json()) as CachedEntryData;
374
- return { data, shouldRevalidate: true };
643
+ const data = await this.parseOrEvict<CachedEntryData>(
644
+ () => new Response(b2).json() as Promise<CachedEntryData>,
645
+ () => cache.delete(request),
646
+ "get(revalidating)",
647
+ );
648
+ return data === null ? null : { data, shouldRevalidate: true };
375
649
  } catch (error) {
376
- console.error("[CFCacheStore] get failed:", error);
650
+ reportCacheError(error, "cache-read", "[CFCacheStore] get");
377
651
  return null;
378
652
  }
379
653
  }
@@ -389,6 +663,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
389
663
  ttl: number,
390
664
  swr?: number,
391
665
  ): Promise<void> {
666
+ if (this.isReservedSegmentKey(key, "cache-write")) return;
392
667
  try {
393
668
  const cache = await this.getCache();
394
669
  const request = this.keyToRequest(key);
@@ -398,32 +673,51 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
398
673
  const totalTtl = ttl + swrWindow;
399
674
  const staleAt = Date.now() + ttl * 1000;
400
675
 
401
- const body = JSON.stringify(data);
676
+ // Stamp the tag timestamp at write time and carry it (with the tags)
677
+ // into both the L1 body and the KV envelope so reads can run the
678
+ // invalidation check.
679
+ const taggedAt =
680
+ data.tags && data.tags.length > 0 ? Date.now() : undefined;
681
+ const dataToStore: CachedEntryData = taggedAt
682
+ ? { ...data, taggedAt }
683
+ : data;
684
+
685
+ const body = JSON.stringify(dataToStore);
402
686
  const response = new Response(body, {
403
687
  headers: {
404
688
  "Content-Type": "application/json",
405
689
  "Cache-Control": `public, max-age=${totalTtl}`,
406
690
  [CACHE_STALE_AT_HEADER]: String(staleAt),
407
691
  [CACHE_STATUS_HEADER]: "HIT",
692
+ ...this.tagHeaderEntries(dataToStore.tags, taggedAt),
408
693
  },
409
694
  });
410
695
 
411
696
  const putPromise = cache.put(request, response);
412
697
 
413
698
  if (this.waitUntil) {
414
- // Non-blocking write
415
- this.waitUntil(async () => {
416
- await putPromise;
417
- });
699
+ // Non-blocking write. These store-level background tasks intentionally
700
+ // omit the reportingAsync ctx argument: the store is a request-agnostic
701
+ // singleton and this.waitUntil is the execution context's, not a single
702
+ // request's, so a failure is reported console-loud only (it cannot be
703
+ // attributed to one request's onError). The request-scoped tag verbs
704
+ // (revalidateTag / stale-revalidation) DO thread their captured ctx.
705
+ this.waitUntil(() =>
706
+ reportingAsync(
707
+ () => putPromise,
708
+ "cache-write",
709
+ "[CFCacheStore] L1 write",
710
+ ),
711
+ );
418
712
  } else {
419
713
  // Blocking fallback
420
714
  await putPromise;
421
715
  }
422
716
 
423
717
  // L2: persist to KV
424
- this.kvSetSegment(key, data, staleAt, totalTtl);
718
+ this.kvSetSegment(key, dataToStore, staleAt, totalTtl, swrWindow);
425
719
  } catch (error) {
426
- console.error("[CFCacheStore] set failed:", error);
720
+ reportCacheError(error, "cache-write", "[CFCacheStore] set");
427
721
  }
428
722
  }
429
723
 
@@ -431,6 +725,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
431
725
  * Delete a cached entry from L1 and L2.
432
726
  */
433
727
  async delete(key: string): Promise<boolean> {
728
+ if (this.isReservedSegmentKey(key, "cache-delete")) return false;
434
729
  try {
435
730
  const cache = await this.getCache();
436
731
  const result = await cache.delete(this.keyToRequest(key));
@@ -438,18 +733,18 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
438
733
  // L2: delete from KV
439
734
  if (this.kv && this.waitUntil) {
440
735
  const kvKey = this.toKVKey(key);
441
- this.waitUntil(async () => {
442
- try {
443
- await this.kv!.delete(kvKey);
444
- } catch {
445
- // KV delete failures are non-critical
446
- }
447
- });
736
+ this.waitUntil(() =>
737
+ reportingAsync(
738
+ () => this.kv!.delete(kvKey),
739
+ "cache-delete",
740
+ "[CFCacheStore] delete L2",
741
+ ),
742
+ );
448
743
  }
449
744
 
450
745
  return result;
451
746
  } catch (error) {
452
- console.error("[CFCacheStore] delete failed:", error);
747
+ reportCacheError(error, "cache-delete", "[CFCacheStore] delete");
453
748
  return false;
454
749
  }
455
750
  }
@@ -475,20 +770,59 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
475
770
  return this.kvGetResponse(key);
476
771
  }
477
772
 
773
+ // Tag invalidation check (treat invalidated entry as a miss).
774
+ const tagInfo = this.readTagInfo(response.headers);
775
+ if (await this.isGloballyInvalidated(tagInfo.tags, tagInfo.taggedAt)) {
776
+ return null;
777
+ }
778
+
478
779
  // Check staleness
479
780
  const staleAt = Number(response.headers.get(CACHE_STALE_AT_HEADER) || 0);
480
781
  const isStale = staleAt > 0 && Date.now() > staleAt;
481
782
 
783
+ // L1 document bodies are streamed through verbatim - unlike the segment/
784
+ // item tiers (which JSON-parse and so structurally detect corruption) and
785
+ // the KV doc tier (validated in kvGetResponse, KV being the real partial-
786
+ // read vector). Integrity here relies on the Cache API: cache.put stores a
787
+ // response atomically or fails, so a truncated body is not served back. We
788
+ // deliberately do NOT buffer+hash the body to re-verify it: that would
789
+ // defeat streaming the document and add a full read to every cache hit.
482
790
  return {
483
- response,
791
+ response: this.toClientResponse(response),
484
792
  shouldRevalidate: isStale,
485
793
  };
486
794
  } catch (error) {
487
- console.error("[CFCacheStore] getResponse failed:", error);
795
+ reportCacheError(error, "cache-read", "[CFCacheStore] getResponse");
488
796
  return null;
489
797
  }
490
798
  }
491
799
 
800
+ /**
801
+ * Strip internal edge headers and restore the author's Cache-Control before a
802
+ * cached document Response is served to a client. L1 entries carry the
803
+ * internal staleness/status headers and a rewritten Cache-Control; none of
804
+ * those should reach the browser or an upstream CDN.
805
+ */
806
+ private toClientResponse(response: Response): Response {
807
+ const headers = new Headers(response.headers);
808
+ const originalCacheControl = headers.get(CACHE_ORIG_CC_HEADER);
809
+ if (originalCacheControl !== null) {
810
+ headers.set("Cache-Control", originalCacheControl);
811
+ } else {
812
+ headers.delete("Cache-Control");
813
+ }
814
+ headers.delete(CACHE_ORIG_CC_HEADER);
815
+ headers.delete(CACHE_STALE_AT_HEADER);
816
+ headers.delete(CACHE_STATUS_HEADER);
817
+ headers.delete(CACHE_TAGS_HEADER);
818
+ headers.delete(CACHE_TAGGED_AT_HEADER);
819
+ return new Response(response.body, {
820
+ status: response.status,
821
+ statusText: response.statusText,
822
+ headers,
823
+ });
824
+ }
825
+
492
826
  /**
493
827
  * Store a Response with TTL and optional SWR window (for document-level caching).
494
828
  * When KV is configured, also persists to L2.
@@ -498,6 +832,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
498
832
  response: Response,
499
833
  ttl: number,
500
834
  swr?: number,
835
+ tags?: string[],
501
836
  ): Promise<void> {
502
837
  try {
503
838
  const cache = await this.getCache();
@@ -507,6 +842,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
507
842
  const swrWindow = resolveSwrWindow(swr, this.defaults);
508
843
  const totalTtl = ttl + swrWindow;
509
844
  const staleAt = Date.now() + ttl * 1000;
845
+ const taggedAt = tags && tags.length > 0 ? Date.now() : undefined;
510
846
 
511
847
  // Clone body for potential KV write before consuming it for L1
512
848
  const [l1Body, kvBody] = this.kv
@@ -515,10 +851,21 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
515
851
  : [null, null]
516
852
  : [response.body, null];
517
853
 
518
- // Clone and add cache headers
854
+ // Clone and add cache headers. The author's Cache-Control is stashed and
855
+ // replaced with a long max-age so the CF Cache API holds the entry across
856
+ // the SWR window; getResponse restores the original before serving.
519
857
  const headers = new Headers(response.headers);
858
+ const originalCacheControl = response.headers.get("Cache-Control");
859
+ if (originalCacheControl !== null) {
860
+ headers.set(CACHE_ORIG_CC_HEADER, originalCacheControl);
861
+ }
520
862
  headers.set("Cache-Control", `public, max-age=${totalTtl}`);
521
863
  headers.set(CACHE_STALE_AT_HEADER, String(staleAt));
864
+ // Internal tag headers (stripped by toClientResponse before serving).
865
+ const tagHeaders = this.tagHeaderEntries(tags, taggedAt);
866
+ for (const [name, value] of Object.entries(tagHeaders)) {
867
+ headers.set(name, value);
868
+ }
522
869
 
523
870
  const toCache = new Response(l1Body, {
524
871
  status: response.status,
@@ -530,9 +877,13 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
530
877
 
531
878
  if (this.waitUntil) {
532
879
  // Non-blocking write
533
- this.waitUntil(async () => {
534
- await putPromise;
535
- });
880
+ this.waitUntil(() =>
881
+ reportingAsync(
882
+ () => putPromise,
883
+ "cache-write",
884
+ "[CFCacheStore] L1 write",
885
+ ),
886
+ );
536
887
  } else {
537
888
  // Blocking fallback
538
889
  await putPromise;
@@ -549,26 +900,30 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
549
900
  : new ArrayBuffer(0);
550
901
  const bodyBase64 = bufferToBase64(bodyBuf);
551
902
 
552
- this.waitUntil(async () => {
553
- try {
554
- const envelope: KVResponseEnvelope = {
555
- b: bodyBase64,
556
- st: response.status,
557
- stx: response.statusText,
558
- hd: headersArray,
559
- s: staleAt,
560
- e: staleAt + swrWindow * 1000,
561
- };
562
- await this.kv!.put(kvKey, JSON.stringify(envelope), {
563
- expirationTtl: totalTtl,
564
- });
565
- } catch (error) {
566
- console.error("[CFCacheStore] KV putResponse failed:", error);
567
- }
568
- });
903
+ this.waitUntil(() =>
904
+ reportingAsync(
905
+ () => {
906
+ const envelope: KVResponseEnvelope = {
907
+ b: bodyBase64,
908
+ st: response.status,
909
+ stx: response.statusText,
910
+ hd: headersArray,
911
+ s: staleAt,
912
+ e: staleAt + swrWindow * 1000,
913
+ t: tags,
914
+ ta: taggedAt,
915
+ };
916
+ return this.kv!.put(kvKey, JSON.stringify(envelope), {
917
+ expirationTtl: totalTtl,
918
+ });
919
+ },
920
+ "cache-write",
921
+ "[CFCacheStore] kvPutResponse",
922
+ ),
923
+ );
569
924
  }
570
925
  } catch (error) {
571
- console.error("[CFCacheStore] putResponse failed:", error);
926
+ reportCacheError(error, "cache-write", "[CFCacheStore] putResponse");
572
927
  }
573
928
  }
574
929
 
@@ -589,6 +944,12 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
589
944
 
590
945
  if (!response) return this.kvGetItem(key);
591
946
 
947
+ // Tag invalidation check (treat invalidated entry as a miss).
948
+ const tagInfo = this.readTagInfo(response.headers);
949
+ if (await this.isGloballyInvalidated(tagInfo.tags, tagInfo.taggedAt)) {
950
+ return null;
951
+ }
952
+
592
953
  const staleAt = Number(
593
954
  response.headers.get(CACHE_STALE_AT_HEADER) ?? "0",
594
955
  );
@@ -599,16 +960,26 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
599
960
  const isRevalidating =
600
961
  status === "REVALIDATING" && age < MAX_REVALIDATION_INTERVAL;
601
962
 
602
- const data = (await response.json()) as {
963
+ const data = await this.parseOrEvict<{
603
964
  value: string;
604
965
  handles?: Record<string, Record<string, unknown[]>>;
605
- };
966
+ }>(
967
+ () =>
968
+ response.json() as Promise<{
969
+ value: string;
970
+ handles?: Record<string, Record<string, unknown[]>>;
971
+ }>,
972
+ () => cache.delete(request),
973
+ "getItem",
974
+ );
975
+ if (data === null) return null;
606
976
 
607
977
  if (!isStale || isRevalidating) {
608
978
  return {
609
979
  value: data.value,
610
980
  handles: data.handles,
611
981
  shouldRevalidate: false,
982
+ tags: tagInfo.tags,
612
983
  };
613
984
  }
614
985
 
@@ -624,9 +995,10 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
624
995
  value: data.value,
625
996
  handles: data.handles,
626
997
  shouldRevalidate: true,
998
+ tags: tagInfo.tags,
627
999
  };
628
1000
  } catch (error) {
629
- console.error("[CFCacheStore] getItem failed:", error);
1001
+ reportCacheError(error, "cache-read", "[CFCacheStore] getItem");
630
1002
  return null;
631
1003
  }
632
1004
  }
@@ -649,6 +1021,9 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
649
1021
  const totalTtl = ttl + swrWindow;
650
1022
  const staleAt = Date.now() + ttl * 1000;
651
1023
 
1024
+ const tags = options?.tags;
1025
+ const taggedAt = tags && tags.length > 0 ? Date.now() : undefined;
1026
+
652
1027
  const body = JSON.stringify({ value, handles: options?.handles });
653
1028
  const response = new Response(body, {
654
1029
  headers: {
@@ -656,15 +1031,20 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
656
1031
  "Cache-Control": `public, max-age=${totalTtl}`,
657
1032
  [CACHE_STALE_AT_HEADER]: String(staleAt),
658
1033
  [CACHE_STATUS_HEADER]: "HIT",
1034
+ ...this.tagHeaderEntries(tags, taggedAt),
659
1035
  },
660
1036
  });
661
1037
 
662
1038
  const putPromise = cache.put(request, response);
663
1039
 
664
1040
  if (this.waitUntil) {
665
- this.waitUntil(async () => {
666
- await putPromise;
667
- });
1041
+ this.waitUntil(() =>
1042
+ reportingAsync(
1043
+ () => putPromise,
1044
+ "cache-write",
1045
+ "[CFCacheStore] L1 write",
1046
+ ),
1047
+ );
668
1048
  } else {
669
1049
  await putPromise;
670
1050
  }
@@ -672,24 +1052,28 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
672
1052
  // L2: persist to KV (KV requires expirationTtl >= 60s)
673
1053
  if (this.kv && this.waitUntil && totalTtl >= 60) {
674
1054
  const kvKey = this.toKVKey(`fn:${key}`);
675
- this.waitUntil(async () => {
676
- try {
677
- const envelope: KVItemEnvelope = {
678
- v: value,
679
- h: options?.handles,
680
- s: staleAt,
681
- e: staleAt + swrWindow * 1000,
682
- };
683
- await this.kv!.put(kvKey, JSON.stringify(envelope), {
684
- expirationTtl: totalTtl,
685
- });
686
- } catch (error) {
687
- console.error("[CFCacheStore] KV setItem failed:", error);
688
- }
689
- });
1055
+ this.waitUntil(() =>
1056
+ reportingAsync(
1057
+ () => {
1058
+ const envelope: KVItemEnvelope = {
1059
+ v: value,
1060
+ h: options?.handles,
1061
+ s: staleAt,
1062
+ e: staleAt + swrWindow * 1000,
1063
+ t: tags,
1064
+ ta: taggedAt,
1065
+ };
1066
+ return this.kv!.put(kvKey, JSON.stringify(envelope), {
1067
+ expirationTtl: totalTtl,
1068
+ });
1069
+ },
1070
+ "cache-write",
1071
+ "[CFCacheStore] kvSetItem",
1072
+ ),
1073
+ );
690
1074
  }
691
1075
  } catch (error) {
692
- console.error("[CFCacheStore] setItem failed:", error);
1076
+ reportCacheError(error, "cache-write", "[CFCacheStore] setItem");
693
1077
  }
694
1078
  }
695
1079
 
@@ -721,6 +1105,426 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
721
1105
  return `${versionPath}${key}`;
722
1106
  }
723
1107
 
1108
+ /**
1109
+ * Parse a stored entry, EVICTING it if the parse fails. A parse failure means
1110
+ * the entry is corrupt or partial (truncated Cache API body, malformed KV
1111
+ * envelope/base64) - it would deterministically fail every future read - so it
1112
+ * is deleted to self-heal (the next read misses and re-renders) and reported as
1113
+ * cache-corrupt. This is distinct from a transient infra error (the caller's
1114
+ * outer catch reports cache-read and does NOT delete a still-good entry).
1115
+ * Returns null on corruption; the caller treats null as a miss.
1116
+ * @internal
1117
+ */
1118
+ private async parseOrEvict<T>(
1119
+ parse: () => Promise<T> | T,
1120
+ evict: () => Promise<unknown>,
1121
+ label: string,
1122
+ ): Promise<T | null> {
1123
+ try {
1124
+ return await parse();
1125
+ } catch (error) {
1126
+ reportCacheError(
1127
+ error,
1128
+ "cache-corrupt",
1129
+ `[CFCacheStore] ${label}: corrupt/partial entry, evicting`,
1130
+ );
1131
+ try {
1132
+ await evict();
1133
+ } catch (evictError) {
1134
+ reportCacheError(
1135
+ evictError,
1136
+ "cache-delete",
1137
+ `[CFCacheStore] ${label}: evicting corrupt entry failed`,
1138
+ );
1139
+ }
1140
+ return null;
1141
+ }
1142
+ }
1143
+
1144
+ /**
1145
+ * Best-effort delete of a single KV key, reporting (not swallowing) a delete
1146
+ * failure as cache-delete. Used by the corrupt-entry self-heal paths.
1147
+ * @internal
1148
+ */
1149
+ private async evictKvKey(kvKey: string, label: string): Promise<void> {
1150
+ try {
1151
+ await this.kv!.delete(kvKey);
1152
+ } catch (error) {
1153
+ reportCacheError(
1154
+ error,
1155
+ "cache-delete",
1156
+ `[CFCacheStore] ${label}: evict failed`,
1157
+ );
1158
+ }
1159
+ }
1160
+
1161
+ /**
1162
+ * KV-get a JSON envelope, EVICTING the key only when it is genuinely corrupt.
1163
+ *
1164
+ * Reads as { type: "text" }, NOT { type: "json" }, on purpose: the "json" form
1165
+ * fuses the network read and the JSON parse, so a transient KV outage (5xx/429/
1166
+ * network blip) is indistinguishable from a malformed body and would delete a
1167
+ * still-good cross-colo entry - a self-inflicted miss storm. Reading text lets a
1168
+ * transient read error propagate to the caller's outer catch (reported
1169
+ * cache-read, the entry left intact); only a JSON.parse failure on a body that
1170
+ * WAS successfully read - or an envelope that parses but fails `validate`
1171
+ * (fields missing from a truncated write) - is true corruption that evicts +
1172
+ * reports cache-corrupt. A MISSING key (kv.get -> null) is a normal miss.
1173
+ * @internal
1174
+ */
1175
+ private async kvGetOrEvict<T>(
1176
+ kvKey: string,
1177
+ validate: (envelope: T) => boolean,
1178
+ label: string,
1179
+ ): Promise<T | null> {
1180
+ // A transient error here throws and is reported cache-read (no eviction) by
1181
+ // the caller's outer catch - deliberately NOT caught as corruption.
1182
+ const text = await this.kv!.get(kvKey, { type: "text" });
1183
+ if (text == null) return null; // missing key = miss, not corruption
1184
+
1185
+ let raw: T;
1186
+ try {
1187
+ raw = JSON.parse(text) as T;
1188
+ } catch (error) {
1189
+ reportCacheError(
1190
+ error,
1191
+ "cache-corrupt",
1192
+ `[CFCacheStore] ${label}: corrupt JSON in KV, evicting`,
1193
+ );
1194
+ await this.evictKvKey(kvKey, label);
1195
+ return null;
1196
+ }
1197
+
1198
+ if (!validate(raw)) {
1199
+ reportCacheError(
1200
+ new Error("malformed/partial KV envelope"),
1201
+ "cache-corrupt",
1202
+ `[CFCacheStore] ${label}: malformed envelope, evicting`,
1203
+ );
1204
+ await this.evictKvKey(kvKey, label);
1205
+ return null;
1206
+ }
1207
+ return raw;
1208
+ }
1209
+
1210
+ // ============================================================================
1211
+ // Tag Invalidation (single-store: markers live in this.kv)
1212
+ // ============================================================================
1213
+
1214
+ /** KV key for a tag's invalidation marker. */
1215
+ private tagMarkerKey(tag: string): string {
1216
+ return this.toKVKey(`${TAG_MARKER_PREFIX}${tag}`);
1217
+ }
1218
+
1219
+ /**
1220
+ * Header entries carrying an entry's tags (JSON-encoded, comma-safe) and the
1221
+ * timestamp they were attached. Returns an empty object when there are no
1222
+ * tags so untagged entries stay header-free and skip the invalidation check.
1223
+ */
1224
+ private tagHeaderEntries(
1225
+ tags: string[] | undefined,
1226
+ taggedAt: number | undefined,
1227
+ ): Record<string, string> {
1228
+ if (!tags || tags.length === 0 || !taggedAt) return {};
1229
+ return {
1230
+ // encodeURIComponent so the value is pure ASCII: HTTP header values are
1231
+ // ByteStrings, but JSON.stringify leaves codepoints > U+00FF (emoji/CJK)
1232
+ // verbatim, which makes new Response({ headers }) throw and the outer
1233
+ // try/catch silently drop the whole entry from cache. Decoded in
1234
+ // readTagInfo. The L1 marker Cache-Tag path encodes for the same reason.
1235
+ [CACHE_TAGS_HEADER]: encodeURIComponent(JSON.stringify(tags)),
1236
+ [CACHE_TAGGED_AT_HEADER]: String(taggedAt),
1237
+ };
1238
+ }
1239
+
1240
+ /** Read an entry's tags/taggedAt back from its headers. */
1241
+ private readTagInfo(headers: Headers): {
1242
+ tags?: string[];
1243
+ taggedAt?: number;
1244
+ } {
1245
+ const rawTags = headers.get(CACHE_TAGS_HEADER);
1246
+ const rawTaggedAt = headers.get(CACHE_TAGGED_AT_HEADER);
1247
+ if (!rawTags || !rawTaggedAt) return {};
1248
+ try {
1249
+ return {
1250
+ tags: JSON.parse(decodeURIComponent(rawTags)) as string[],
1251
+ taggedAt: Number(rawTaggedAt),
1252
+ };
1253
+ } catch {
1254
+ return {};
1255
+ }
1256
+ }
1257
+
1258
+ /**
1259
+ * Whether an entry tagged at `taggedAt` with `tags` has been invalidated since.
1260
+ * Reads the per-tag invalidation markers from KV and returns true if any tag's
1261
+ * latest invalidation is at or after taggedAt (>= so a same-millisecond
1262
+ * invalidate wins, favouring freshness over staleness). Fails open: KV errors
1263
+ * never turn a hit into a wrongful miss-storm beyond this single read.
1264
+ */
1265
+ private async isGloballyInvalidated(
1266
+ tags: string[] | undefined,
1267
+ taggedAt: number | undefined,
1268
+ ): Promise<boolean> {
1269
+ if (!this.kv || !tags || tags.length === 0 || !taggedAt) return false;
1270
+ const ctx = _getRequestContext();
1271
+ const memo = ctx ? getTagMarkerMemo(ctx) : undefined;
1272
+ const inflight = ctx ? getTagMarkerInflight(ctx) : undefined;
1273
+ try {
1274
+ const markers = await Promise.all(
1275
+ tags.map((tag) => this.readTagMarker(tag, memo, inflight)),
1276
+ );
1277
+ for (const marker of markers) {
1278
+ if (marker != null && marker >= taggedAt) return true;
1279
+ }
1280
+ return false;
1281
+ } catch (error) {
1282
+ reportCacheError(
1283
+ error,
1284
+ "cache-read",
1285
+ "[CFCacheStore] tag invalidation check",
1286
+ );
1287
+ return false;
1288
+ }
1289
+ }
1290
+
1291
+ /** Synthetic Cache API request for a tag's L1-cached invalidation marker. */
1292
+ private tagMarkerRequest(tag: string): Request {
1293
+ return this.keyToRequest(`${TAG_MARKER_CACHE_PREFIX}${tag}`);
1294
+ }
1295
+
1296
+ /**
1297
+ * Read a tag's latest invalidation timestamp (or null if never invalidated)
1298
+ * through the cascade: per-request memo -> per-colo L1 cache (only when
1299
+ * tagCacheTtl > 0) -> KV (the global truth). The memo is always consulted
1300
+ * first so it stays authoritative within a request (read-your-own-writes),
1301
+ * and every KV/L1 result is written back into the memo. A Cache API miss
1302
+ * always falls through to KV; absence is represented by a cached sentinel,
1303
+ * never by a miss.
1304
+ *
1305
+ * Concurrent reads of the same tag within a request share one in-flight read
1306
+ * (the resolved-value memo only collapses sequential reads; parallel segment
1307
+ * loading would otherwise issue one KV read per concurrent reader).
1308
+ * @internal
1309
+ */
1310
+ private async readTagMarker(
1311
+ tag: string,
1312
+ memo: Map<string, number | null> | undefined,
1313
+ inflight: Map<string, Promise<number | null>> | undefined,
1314
+ ): Promise<number | null> {
1315
+ if (memo && memo.has(tag)) return memo.get(tag) ?? null;
1316
+
1317
+ // Collapse concurrent (not-yet-resolved) reads of this tag onto one promise.
1318
+ if (inflight) {
1319
+ const pending = inflight.get(tag);
1320
+ if (pending) return pending;
1321
+ const read = this.fetchTagMarker(tag, memo);
1322
+ inflight.set(tag, read);
1323
+ try {
1324
+ return await read;
1325
+ } finally {
1326
+ // Resolved values now live in the memo; drop the in-flight entry.
1327
+ inflight.delete(tag);
1328
+ }
1329
+ }
1330
+
1331
+ return this.fetchTagMarker(tag, memo);
1332
+ }
1333
+
1334
+ /**
1335
+ * Uncached body of readTagMarker: L1 (per-colo Cache API, opt-in via
1336
+ * tagCacheTtl) -> KV. Writes the resolved value back into the memo.
1337
+ * @internal
1338
+ */
1339
+ private async fetchTagMarker(
1340
+ tag: string,
1341
+ memo: Map<string, number | null> | undefined,
1342
+ ): Promise<number | null> {
1343
+ // L1 (per-colo) marker cache - opt-in via tagCacheTtl.
1344
+ if (this.tagCacheTtl > 0) {
1345
+ try {
1346
+ const cache = await this.getCache();
1347
+ const hit = await cache.match(this.tagMarkerRequest(tag));
1348
+ if (hit) {
1349
+ const body = await hit.text();
1350
+ const value = body === TAG_MARKER_ABSENT ? null : Number(body);
1351
+ memo?.set(tag, value);
1352
+ return value;
1353
+ }
1354
+ } catch {
1355
+ // Fall through to KV on any L1 read error.
1356
+ }
1357
+ }
1358
+
1359
+ // KV (global truth).
1360
+ const raw = await this.kv!.get(this.tagMarkerKey(tag), { type: "text" });
1361
+ const value = raw != null ? Number(raw) : null;
1362
+ memo?.set(tag, value);
1363
+
1364
+ // Populate L1 for subsequent reads in this colo (non-blocking).
1365
+ if (this.tagCacheTtl > 0) {
1366
+ const put = () => this.putTagMarkerL1(tag, value);
1367
+ if (this.waitUntil) this.waitUntil(put);
1368
+ else void put();
1369
+ }
1370
+ return value;
1371
+ }
1372
+
1373
+ /**
1374
+ * Cloudflare Cache-Tags written on a tag's L1 marker entry, namespaced per
1375
+ * store so purges never collide with other Cache-Tags in the zone. Three
1376
+ * tiers, broad to specific:
1377
+ * rg:{ns} - everything this store cached (deploy/nuclear reset)
1378
+ * rg:{ns}:lk - all tag-lookup markers
1379
+ * rg:{ns}:lk:{tag} - this tag's lookup (the normal updateTag purge target)
1380
+ * The tag value is encodeURIComponent'd so commas/spaces can't corrupt the
1381
+ * comma-delimited Cache-Tag header.
1382
+ * @internal
1383
+ */
1384
+ private lookupCacheTags(tag: string): string[] {
1385
+ const ns = this.namespace ?? "default";
1386
+ return [`rg:${ns}`, `rg:${ns}:lk`, this.lookupPurgeTag(tag)];
1387
+ }
1388
+
1389
+ /** The specific Cache-Tag a consumer purges to evict tag `tag`'s lookup. */
1390
+ private lookupPurgeTag(tag: string): string {
1391
+ const ns = this.namespace ?? "default";
1392
+ return `rg:${ns}:lk:${encodeURIComponent(tag)}`;
1393
+ }
1394
+
1395
+ /**
1396
+ * Write a tag marker value into the per-colo L1 Cache API with tagCacheTtl.
1397
+ * `null` is stored as the TAG_MARKER_ABSENT sentinel so "no marker yet" is
1398
+ * cacheable (most tags are never invalidated - that is where the read savings
1399
+ * come from). The entry also carries a namespaced Cache-Tag so an external
1400
+ * purge-by-tag (via onRevalidateTag) can evict it across colos promptly,
1401
+ * rather than waiting out tagCacheTtl. Best-effort.
1402
+ * @internal
1403
+ */
1404
+ private async putTagMarkerL1(
1405
+ tag: string,
1406
+ value: number | null,
1407
+ ): Promise<void> {
1408
+ if (this.tagCacheTtl <= 0) return;
1409
+ try {
1410
+ const cache = await this.getCache();
1411
+ const body = value != null ? String(value) : TAG_MARKER_ABSENT;
1412
+ await cache.put(
1413
+ this.tagMarkerRequest(tag),
1414
+ new Response(body, {
1415
+ headers: {
1416
+ "Cache-Control": `public, max-age=${this.tagCacheTtl}`,
1417
+ "Cache-Tag": this.lookupCacheTags(tag).join(","),
1418
+ },
1419
+ }),
1420
+ );
1421
+ } catch {
1422
+ // Best-effort: a failed L1 populate just means the next read consults KV.
1423
+ }
1424
+ }
1425
+
1426
+ /**
1427
+ * Invalidate every entry tagged with any of `tags`. Receives the whole batch
1428
+ * from one updateTag()/revalidateTag() call so the eager-purge hook fires
1429
+ * ONCE (one CDN purge request, not one per tag). For each tag: records the KV
1430
+ * marker (the durable cross-colo truth that reads compare taggedAt against),
1431
+ * writes the fresh marker straight into this colo's L1 (write-through, NOT
1432
+ * delete - a delete would let the next read re-read a not-yet-converged KV
1433
+ * value and re-arm the stale window), and memoizes it for same-request
1434
+ * read-your-own-writes. Finally fires onRevalidateTag with the namespaced
1435
+ * lookup Cache-Tags so a consumer purge evicts the cached lookups in other
1436
+ * colos promptly (otherwise they converge within tagCacheTtl).
1437
+ *
1438
+ * Durable-write integrity: the in-memory write-through (memo + L1) for a tag
1439
+ * runs ONLY after that tag's KV marker write is confirmed. If any KV write
1440
+ * fails (transient error, or an over-512-byte key), this rejects with the
1441
+ * failed tags so an awaiting updateTag() surfaces the failure instead of
1442
+ * silently reporting success while other requests/colos serve stale data. The
1443
+ * eager purge still fires for the whole batch first (it is additive).
1444
+ */
1445
+ async invalidateTags(tags: string[]): Promise<void> {
1446
+ if (tags.length === 0) return;
1447
+ const invalidatedAt = Date.now();
1448
+ const ctx = _getRequestContext();
1449
+ const memo = ctx ? getTagMarkerMemo(ctx) : undefined;
1450
+
1451
+ if (!this.kv && !this.onRevalidateTag) {
1452
+ console.warn(
1453
+ `[CFCacheStore] invalidateTags had no effect: configure a KV namespace ` +
1454
+ `for distributed invalidation, or an onRevalidateTag hook.`,
1455
+ );
1456
+ }
1457
+
1458
+ const failedTags = new Set<string>();
1459
+ const errors: unknown[] = [];
1460
+ if (this.kv) {
1461
+ await Promise.all(
1462
+ tags.map(async (tag) => {
1463
+ const markerKey = this.tagMarkerKey(tag);
1464
+ if (kvKeyByteLength(markerKey) > KV_MAX_KEY_BYTES) {
1465
+ failedTags.add(tag);
1466
+ errors.push(
1467
+ new Error(
1468
+ `tag "${tag}" produces a ${kvKeyByteLength(markerKey)}-byte KV ` +
1469
+ `marker key, over the ${KV_MAX_KEY_BYTES}-byte limit`,
1470
+ ),
1471
+ );
1472
+ return;
1473
+ }
1474
+ try {
1475
+ await this.kv!.put(markerKey, String(invalidatedAt), {
1476
+ ...(this.tagInvalidationTtl
1477
+ ? { expirationTtl: this.tagInvalidationTtl }
1478
+ : {}),
1479
+ });
1480
+ } catch (error) {
1481
+ failedTags.add(tag);
1482
+ errors.push(error);
1483
+ }
1484
+ }),
1485
+ );
1486
+ }
1487
+
1488
+ // Write-through memo + L1 only for tags with a confirmed durable marker (or
1489
+ // for every tag when there is no KV at all - a purge-only/dev config, where
1490
+ // the in-memory write-through is the only invalidation signal there is). The
1491
+ // memo write is synchronous (read-your-own-writes); the L1 Cache API writes
1492
+ // are independent, so fan them out in parallel rather than awaiting each.
1493
+ const l1Writes: Promise<void>[] = [];
1494
+ for (const tag of tags) {
1495
+ if (failedTags.has(tag)) continue;
1496
+ memo?.set(tag, invalidatedAt);
1497
+ if (this.tagCacheTtl > 0) {
1498
+ l1Writes.push(this.putTagMarkerL1(tag, invalidatedAt));
1499
+ }
1500
+ }
1501
+ if (l1Writes.length > 0) await Promise.all(l1Writes);
1502
+
1503
+ // One batched eager purge of the lookup markers for the whole call. Fired
1504
+ // regardless of KV write outcome (it is additive and uses pure string ops).
1505
+ if (this.onRevalidateTag) {
1506
+ try {
1507
+ await this.onRevalidateTag(tags.map((tag) => this.lookupPurgeTag(tag)));
1508
+ } catch (error) {
1509
+ reportCacheError(
1510
+ error,
1511
+ "cache-invalidate",
1512
+ "[CFCacheStore] onRevalidateTag hook",
1513
+ );
1514
+ }
1515
+ }
1516
+
1517
+ if (failedTags.size > 0) {
1518
+ const err = new Error(
1519
+ `[CFCacheStore] ${failedTags.size}/${tags.length} tag marker write(s) ` +
1520
+ `failed: ${[...failedTags].join(", ")}. Those tags may still serve ` +
1521
+ `stale data across requests/colos; retry the invalidation.`,
1522
+ );
1523
+ (err as Error & { cause?: unknown }).cause = errors[0];
1524
+ throw err;
1525
+ }
1526
+ }
1527
+
724
1528
  // ============================================================================
725
1529
  // KV L2 Helpers
726
1530
  // ============================================================================
@@ -736,15 +1540,26 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
736
1540
 
737
1541
  try {
738
1542
  const kvKey = this.toKVKey(key);
739
- const raw = await this.kv.get(kvKey, { type: "json" });
740
- if (!raw) return null;
1543
+ const envelope = await this.kvGetOrEvict<KVSegmentEnvelope>(
1544
+ kvKey,
1545
+ (e) =>
1546
+ typeof e.e === "number" && typeof e.s === "number" && e.d != null,
1547
+ "kvGetSegment",
1548
+ );
1549
+ if (!envelope) return null;
741
1550
 
742
- const envelope = raw as KVSegmentEnvelope;
743
1551
  const now = Date.now();
744
1552
 
745
1553
  // Hard-expired — treat as miss
746
1554
  if (now > envelope.e) return null;
747
1555
 
1556
+ // Tag invalidation check (also covers the KV tier, not just L1).
1557
+ if (
1558
+ await this.isGloballyInvalidated(envelope.d.tags, envelope.d.taggedAt)
1559
+ ) {
1560
+ return null;
1561
+ }
1562
+
748
1563
  const shouldRevalidate = now > envelope.s;
749
1564
 
750
1565
  // Promote to L1 in background
@@ -752,7 +1567,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
752
1567
 
753
1568
  return { data: envelope.d, shouldRevalidate };
754
1569
  } catch (error) {
755
- console.error("[CFCacheStore] KV get failed:", error);
1570
+ reportCacheError(error, "cache-read", "[CFCacheStore] kvGetSegment");
756
1571
  return null;
757
1572
  }
758
1573
  }
@@ -766,28 +1581,30 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
766
1581
  data: CachedEntryData,
767
1582
  staleAt: number,
768
1583
  totalTtl: number,
1584
+ swrWindow: number,
769
1585
  ): void {
770
1586
  // KV requires expirationTtl >= 60s. Skip write for short-lived entries.
771
1587
  if (!this.kv || !this.waitUntil || totalTtl < 60) return;
772
1588
 
773
1589
  const kvKey = this.toKVKey(key);
774
- const swrWindow = totalTtl * 1000 - (staleAt - Date.now());
775
- const expiresAt = staleAt + swrWindow;
776
-
777
- this.waitUntil(async () => {
778
- try {
779
- const envelope: KVSegmentEnvelope = {
780
- d: data,
781
- s: staleAt,
782
- e: expiresAt,
783
- };
784
- await this.kv!.put(kvKey, JSON.stringify(envelope), {
785
- expirationTtl: totalTtl,
786
- });
787
- } catch (error) {
788
- console.error("[CFCacheStore] KV set failed:", error);
789
- }
790
- });
1590
+ const expiresAt = staleAt + swrWindow * 1000;
1591
+
1592
+ this.waitUntil(() =>
1593
+ reportingAsync(
1594
+ () => {
1595
+ const envelope: KVSegmentEnvelope = {
1596
+ d: data,
1597
+ s: staleAt,
1598
+ e: expiresAt,
1599
+ };
1600
+ return this.kv!.put(kvKey, JSON.stringify(envelope), {
1601
+ expirationTtl: totalTtl,
1602
+ });
1603
+ },
1604
+ "cache-write",
1605
+ "[CFCacheStore] kvSetSegment",
1606
+ ),
1607
+ );
791
1608
  }
792
1609
 
793
1610
  /**
@@ -797,27 +1614,35 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
797
1614
  private promoteSegmentToL1(key: string, envelope: KVSegmentEnvelope): void {
798
1615
  if (!this.waitUntil) return;
799
1616
 
800
- this.waitUntil(async () => {
801
- try {
802
- const now = Date.now();
803
- const remainingTtl = Math.max(1, Math.floor((envelope.e - now) / 1000));
804
- const cache = await this.getCache();
805
- const request = this.keyToRequest(key);
806
-
807
- const response = new Response(JSON.stringify(envelope.d), {
808
- headers: {
809
- "Content-Type": "application/json",
810
- "Cache-Control": `public, max-age=${remainingTtl}`,
811
- [CACHE_STALE_AT_HEADER]: String(envelope.s),
812
- [CACHE_STATUS_HEADER]: "HIT",
813
- },
814
- });
815
-
816
- await cache.put(request, response);
817
- } catch (error) {
818
- console.error("[CFCacheStore] L1 promote failed:", error);
819
- }
820
- });
1617
+ this.waitUntil(() =>
1618
+ reportingAsync(
1619
+ async () => {
1620
+ const now = Date.now();
1621
+ const remainingTtl = Math.max(
1622
+ 1,
1623
+ Math.floor((envelope.e - now) / 1000),
1624
+ );
1625
+ const cache = await this.getCache();
1626
+ const request = this.keyToRequest(key);
1627
+
1628
+ const response = new Response(JSON.stringify(envelope.d), {
1629
+ headers: {
1630
+ "Content-Type": "application/json",
1631
+ "Cache-Control": `public, max-age=${remainingTtl}`,
1632
+ [CACHE_STALE_AT_HEADER]: String(envelope.s),
1633
+ [CACHE_STATUS_HEADER]: "HIT",
1634
+ // Preserve tags across KV->L1 promotion so the promoted entry
1635
+ // stays tag-invalidatable.
1636
+ ...this.tagHeaderEntries(envelope.d.tags, envelope.d.taggedAt),
1637
+ },
1638
+ });
1639
+
1640
+ await cache.put(request, response);
1641
+ },
1642
+ "cache-write",
1643
+ "[CFCacheStore] promoteSegmentToL1",
1644
+ ),
1645
+ );
821
1646
  }
822
1647
 
823
1648
  /**
@@ -829,14 +1654,25 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
829
1654
 
830
1655
  try {
831
1656
  const kvKey = this.toKVKey(`fn:${key}`);
832
- const raw = await this.kv.get(kvKey, { type: "json" });
833
- if (!raw) return null;
1657
+ const envelope = await this.kvGetOrEvict<KVItemEnvelope>(
1658
+ kvKey,
1659
+ (e) =>
1660
+ typeof e.v === "string" &&
1661
+ typeof e.e === "number" &&
1662
+ typeof e.s === "number",
1663
+ "kvGetItem",
1664
+ );
1665
+ if (!envelope) return null;
834
1666
 
835
- const envelope = raw as KVItemEnvelope;
836
1667
  const now = Date.now();
837
1668
 
838
1669
  if (now > envelope.e) return null;
839
1670
 
1671
+ // Tag invalidation check (also covers the KV tier, not just L1).
1672
+ if (await this.isGloballyInvalidated(envelope.t, envelope.ta)) {
1673
+ return null;
1674
+ }
1675
+
840
1676
  const shouldRevalidate = now > envelope.s;
841
1677
 
842
1678
  // Promote to L1
@@ -846,9 +1682,10 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
846
1682
  value: envelope.v,
847
1683
  handles: envelope.h,
848
1684
  shouldRevalidate,
1685
+ tags: envelope.t,
849
1686
  };
850
1687
  } catch (error) {
851
- console.error("[CFCacheStore] KV getItem failed:", error);
1688
+ reportCacheError(error, "cache-read", "[CFCacheStore] kvGetItem");
852
1689
  return null;
853
1690
  }
854
1691
  }
@@ -860,28 +1697,39 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
860
1697
  private promoteItemToL1(key: string, envelope: KVItemEnvelope): void {
861
1698
  if (!this.waitUntil) return;
862
1699
 
863
- this.waitUntil(async () => {
864
- try {
865
- const now = Date.now();
866
- const remainingTtl = Math.max(1, Math.floor((envelope.e - now) / 1000));
867
- const cache = await this.getCache();
868
- const request = this.keyToRequest(`fn:${key}`);
869
-
870
- const body = JSON.stringify({ value: envelope.v, handles: envelope.h });
871
- const response = new Response(body, {
872
- headers: {
873
- "Content-Type": "application/json",
874
- "Cache-Control": `public, max-age=${remainingTtl}`,
875
- [CACHE_STALE_AT_HEADER]: String(envelope.s),
876
- [CACHE_STATUS_HEADER]: "HIT",
877
- },
878
- });
879
-
880
- await cache.put(request, response);
881
- } catch (error) {
882
- console.error("[CFCacheStore] L1 item promote failed:", error);
883
- }
884
- });
1700
+ this.waitUntil(() =>
1701
+ reportingAsync(
1702
+ async () => {
1703
+ const now = Date.now();
1704
+ const remainingTtl = Math.max(
1705
+ 1,
1706
+ Math.floor((envelope.e - now) / 1000),
1707
+ );
1708
+ const cache = await this.getCache();
1709
+ const request = this.keyToRequest(`fn:${key}`);
1710
+
1711
+ const body = JSON.stringify({
1712
+ value: envelope.v,
1713
+ handles: envelope.h,
1714
+ });
1715
+ const response = new Response(body, {
1716
+ headers: {
1717
+ "Content-Type": "application/json",
1718
+ "Cache-Control": `public, max-age=${remainingTtl}`,
1719
+ [CACHE_STALE_AT_HEADER]: String(envelope.s),
1720
+ [CACHE_STATUS_HEADER]: "HIT",
1721
+ // Preserve tags across KV->L1 promotion (the item tier previously
1722
+ // dropped them, permanently disabling tag invalidation here).
1723
+ ...this.tagHeaderEntries(envelope.t, envelope.ta),
1724
+ },
1725
+ });
1726
+
1727
+ await cache.put(request, response);
1728
+ },
1729
+ "cache-write",
1730
+ "[CFCacheStore] promoteItemToL1",
1731
+ ),
1732
+ );
885
1733
  }
886
1734
 
887
1735
  /**
@@ -895,31 +1743,58 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
895
1743
 
896
1744
  try {
897
1745
  const kvKey = this.toKVKey(`doc:${key}`);
898
- const raw = await this.kv.get(kvKey, { type: "json" });
899
- if (!raw) return null;
1746
+ const envelope = await this.kvGetOrEvict<KVResponseEnvelope>(
1747
+ kvKey,
1748
+ (e) =>
1749
+ typeof e.b === "string" &&
1750
+ typeof e.st === "number" &&
1751
+ typeof e.e === "number" &&
1752
+ typeof e.s === "number" &&
1753
+ Array.isArray(e.hd),
1754
+ "kvGetResponse",
1755
+ );
1756
+ if (!envelope) return null;
900
1757
 
901
- const envelope = raw as KVResponseEnvelope;
902
1758
  const now = Date.now();
903
1759
 
904
1760
  if (now > envelope.e) return null;
905
1761
 
1762
+ // Tag invalidation check (also covers the KV tier, not just L1).
1763
+ if (await this.isGloballyInvalidated(envelope.t, envelope.ta)) {
1764
+ return null;
1765
+ }
1766
+
906
1767
  const shouldRevalidate = now > envelope.s;
907
1768
 
908
- // Reconstruct Response (decode base64 binary)
909
- const headers = new Headers(envelope.hd);
910
- const bodyBuffer = base64ToBuffer(envelope.b);
911
- const response = new Response(bodyBuffer, {
912
- status: envelope.st,
913
- statusText: envelope.stx,
914
- headers,
915
- });
1769
+ // Reconstruct Response: decode base64 -> binary, rebuild headers/status.
1770
+ // Corrupt/partial base64 throws in atob; malformed `hd` or an out-of-range
1771
+ // `st` throws in new Headers/new Response. Any of these is a faulty entry,
1772
+ // so evict it and miss rather than re-failing every read until TTL.
1773
+ let response: Response;
1774
+ try {
1775
+ const bodyBuffer = base64ToBuffer(envelope.b);
1776
+ const headers = new Headers(envelope.hd);
1777
+ response = new Response(bodyBuffer, {
1778
+ status: envelope.st,
1779
+ statusText: envelope.stx,
1780
+ headers,
1781
+ });
1782
+ } catch (error) {
1783
+ reportCacheError(
1784
+ error,
1785
+ "cache-corrupt",
1786
+ "[CFCacheStore] kvGetResponse: corrupt response envelope, evicting",
1787
+ );
1788
+ await this.evictKvKey(kvKey, "kvGetResponse");
1789
+ return null;
1790
+ }
916
1791
 
917
1792
  // Promote to L1
918
1793
  this.promoteResponseToL1(key, envelope);
919
1794
 
920
1795
  return { response, shouldRevalidate };
921
1796
  } catch (error) {
922
- console.error("[CFCacheStore] KV getResponse failed:", error);
1797
+ reportCacheError(error, "cache-read", "[CFCacheStore] kvGetResponse");
923
1798
  return null;
924
1799
  }
925
1800
  }
@@ -931,29 +1806,45 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
931
1806
  private promoteResponseToL1(key: string, envelope: KVResponseEnvelope): void {
932
1807
  if (!this.waitUntil) return;
933
1808
 
934
- this.waitUntil(async () => {
935
- try {
936
- const now = Date.now();
937
- const remainingTtl = Math.max(1, Math.floor((envelope.e - now) / 1000));
938
- const cache = await this.getCache();
939
- const request = this.keyToRequest(`doc:${key}`);
1809
+ this.waitUntil(() =>
1810
+ reportingAsync(
1811
+ async () => {
1812
+ const now = Date.now();
1813
+ const remainingTtl = Math.max(
1814
+ 1,
1815
+ Math.floor((envelope.e - now) / 1000),
1816
+ );
1817
+ const cache = await this.getCache();
1818
+ const request = this.keyToRequest(`doc:${key}`);
1819
+
1820
+ const headers = new Headers(envelope.hd);
1821
+ const originalCacheControl = headers.get("Cache-Control");
1822
+ if (originalCacheControl !== null) {
1823
+ headers.set(CACHE_ORIG_CC_HEADER, originalCacheControl);
1824
+ }
1825
+ headers.set("Cache-Control", `public, max-age=${remainingTtl}`);
1826
+ headers.set(CACHE_STALE_AT_HEADER, String(envelope.s));
1827
+ // Re-attach the internal tag headers (envelope.hd is client-facing
1828
+ // and intentionally excludes them) so the promoted entry stays
1829
+ // invalidatable.
1830
+ const tagHeaders = this.tagHeaderEntries(envelope.t, envelope.ta);
1831
+ for (const [name, value] of Object.entries(tagHeaders)) {
1832
+ headers.set(name, value);
1833
+ }
940
1834
 
941
- const headers = new Headers(envelope.hd);
942
- headers.set("Cache-Control", `public, max-age=${remainingTtl}`);
943
- headers.set(CACHE_STALE_AT_HEADER, String(envelope.s));
1835
+ const bodyBuffer = base64ToBuffer(envelope.b);
1836
+ const response = new Response(bodyBuffer, {
1837
+ status: envelope.st,
1838
+ statusText: envelope.stx,
1839
+ headers,
1840
+ });
944
1841
 
945
- const bodyBuffer = base64ToBuffer(envelope.b);
946
- const response = new Response(bodyBuffer, {
947
- status: envelope.st,
948
- statusText: envelope.stx,
949
- headers,
950
- });
951
-
952
- await cache.put(request, response);
953
- } catch (error) {
954
- console.error("[CFCacheStore] L1 response promote failed:", error);
955
- }
956
- });
1842
+ await cache.put(request, response);
1843
+ },
1844
+ "cache-write",
1845
+ "[CFCacheStore] promoteResponseToL1",
1846
+ ),
1847
+ );
957
1848
  }
958
1849
  }
959
1850