@rangojs/router 0.18.0 → 0.19.0

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 (244) hide show
  1. package/dist/types/browser/event-controller.d.ts +3 -3
  2. package/dist/types/browser/history-state.d.ts +2 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +20 -2
  4. package/dist/types/browser/partial-update.d.ts +16 -1
  5. package/dist/types/browser/prefetch/cache.d.ts +6 -6
  6. package/dist/types/browser/react/Link.d.ts +19 -9
  7. package/dist/types/browser/react/context.d.ts +2 -2
  8. package/dist/types/browser/react/location-state-shared.d.ts +95 -56
  9. package/dist/types/browser/react/location-state.d.ts +1 -1
  10. package/dist/types/browser/transition-when.d.ts +80 -0
  11. package/dist/types/browser/types.d.ts +104 -9
  12. package/dist/types/build/generate-manifest.d.ts +8 -0
  13. package/dist/types/cache/background-task.d.ts +24 -2
  14. package/dist/types/cache/cache-exec-scope.d.ts +31 -0
  15. package/dist/types/cache/cache-key-utils.d.ts +44 -13
  16. package/dist/types/cache/cache-policy.d.ts +1 -1
  17. package/dist/types/cache/cache-runtime.d.ts +7 -0
  18. package/dist/types/cache/cache-scope.d.ts +117 -35
  19. package/dist/types/cache/cache-tag.d.ts +24 -4
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
  21. package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
  22. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
  23. package/dist/types/cache/document-cache.d.ts +10 -0
  24. package/dist/types/cache/handle-snapshot.d.ts +40 -8
  25. package/dist/types/cache/invalidation-order.d.ts +23 -0
  26. package/dist/types/cache/request-tag-mask.d.ts +54 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +88 -141
  28. package/dist/types/cache/tag-invalidation.d.ts +38 -14
  29. package/dist/types/cache/types.d.ts +68 -61
  30. package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
  31. package/dist/types/client-urls/navigation.d.ts +28 -2
  32. package/dist/types/client-urls/server-projection.d.ts +5 -2
  33. package/dist/types/client-urls/types.d.ts +5 -5
  34. package/dist/types/client.d.ts +1 -1
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/types/index.rsc.d.ts +1 -1
  37. package/dist/types/route-definition/helpers-types.d.ts +11 -11
  38. package/dist/types/route-name.d.ts +7 -0
  39. package/dist/types/router/error-handling.d.ts +1 -1
  40. package/dist/types/router/intercept-resolution.d.ts +2 -0
  41. package/dist/types/router/match-context.d.ts +5 -0
  42. package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
  43. package/dist/types/router/router-options.d.ts +4 -1
  44. package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
  45. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
  46. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
  47. package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
  48. package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
  49. package/dist/types/router/transition-when.d.ts +24 -10
  50. package/dist/types/rsc/attach-transition-when.d.ts +18 -0
  51. package/dist/types/rsc/full-payload.d.ts +14 -7
  52. package/dist/types/rsc/response-cache-serve.d.ts +15 -12
  53. package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
  54. package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
  55. package/dist/types/rsc/shell-capture.d.ts +78 -81
  56. package/dist/types/rsc/shell-serve.d.ts +19 -48
  57. package/dist/types/rsc/types.d.ts +2 -0
  58. package/dist/types/segment-system.d.ts +7 -0
  59. package/dist/types/server/context.d.ts +89 -61
  60. package/dist/types/server/cookie-store.d.ts +30 -18
  61. package/dist/types/server/handle-store.d.ts +52 -1
  62. package/dist/types/server/request-context.d.ts +66 -45
  63. package/dist/types/server/request-headers.d.ts +44 -0
  64. package/dist/types/server.d.ts +1 -0
  65. package/dist/types/testing/e2e/index.d.ts +1 -1
  66. package/dist/types/testing/index.d.ts +3 -2
  67. package/dist/types/testing/internal/action-id.d.ts +7 -0
  68. package/dist/types/testing/location-state-key.d.ts +24 -0
  69. package/dist/types/testing/render-route.d.ts +17 -1
  70. package/dist/types/testing/run-transition-when.d.ts +44 -69
  71. package/dist/types/testing/serve-shell-request.d.ts +7 -6
  72. package/dist/types/testing/shell-status.d.ts +34 -22
  73. package/dist/types/transition-when-ref.d.ts +79 -0
  74. package/dist/types/types/cache-types.d.ts +15 -3
  75. package/dist/types/types/index.d.ts +1 -1
  76. package/dist/types/types/segments.d.ts +86 -71
  77. package/dist/types/urls/path-helper-types.d.ts +3 -4
  78. package/dist/types/urls/pattern-types.d.ts +3 -4
  79. package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
  80. package/dist/vite/index.js +960 -97
  81. package/package.json +1 -1
  82. package/skills/cache-guide/SKILL.md +76 -49
  83. package/skills/caching/SKILL.md +167 -69
  84. package/skills/client-urls/SKILL.md +1 -1
  85. package/skills/comparison/references/framework-comparison.md +8 -5
  86. package/skills/document-cache/SKILL.md +24 -0
  87. package/skills/hooks/SKILL.md +3 -1
  88. package/skills/hooks/navigation.md +23 -2
  89. package/skills/hooks/state.md +98 -6
  90. package/skills/intercept/SKILL.md +29 -12
  91. package/skills/loader/SKILL.md +52 -21
  92. package/skills/middleware/SKILL.md +18 -0
  93. package/skills/migrate-nextjs/SKILL.md +3 -3
  94. package/skills/observability/SKILL.md +6 -5
  95. package/skills/parallel/SKILL.md +1 -1
  96. package/skills/ppr/SKILL.md +129 -77
  97. package/skills/prerender/SKILL.md +3 -3
  98. package/skills/rango/SKILL.md +25 -6
  99. package/skills/route/SKILL.md +2 -0
  100. package/skills/testing/SKILL.md +27 -25
  101. package/skills/testing/cache-prerender.md +9 -9
  102. package/skills/testing/client-components.md +45 -3
  103. package/skills/testing/loader.md +3 -2
  104. package/skills/testing/middleware.md +1 -0
  105. package/skills/testing/predicates.md +49 -49
  106. package/skills/testing/render-handler.md +1 -0
  107. package/skills/testing/serve-shell-request.md +14 -0
  108. package/skills/testing/server-actions.md +9 -3
  109. package/skills/theme/SKILL.md +8 -4
  110. package/skills/typesafety/env-and-bindings.md +8 -0
  111. package/skills/use-cache/SKILL.md +64 -19
  112. package/skills/view-transitions/SKILL.md +150 -29
  113. package/src/__augment-tests__/augmented.check.ts +23 -0
  114. package/src/browser/event-controller.ts +4 -4
  115. package/src/browser/history-state.ts +52 -1
  116. package/src/browser/link-interceptor.ts +4 -0
  117. package/src/browser/navigation-bridge.ts +77 -18
  118. package/src/browser/navigation-client.ts +4 -3
  119. package/src/browser/navigation-store.ts +43 -1
  120. package/src/browser/navigation-transaction.ts +31 -11
  121. package/src/browser/partial-update.ts +146 -35
  122. package/src/browser/prefetch/cache.ts +6 -6
  123. package/src/browser/prefetch/fetch.ts +2 -2
  124. package/src/browser/react/Link.tsx +38 -33
  125. package/src/browser/react/NavigationProvider.tsx +2 -2
  126. package/src/browser/react/context.ts +2 -2
  127. package/src/browser/react/location-state-shared.ts +241 -49
  128. package/src/browser/react/location-state.ts +5 -2
  129. package/src/browser/rsc-router.tsx +4 -0
  130. package/src/browser/server-action-bridge.ts +84 -15
  131. package/src/browser/transition-when.ts +205 -0
  132. package/src/browser/types.ts +124 -10
  133. package/src/build/generate-manifest.ts +77 -1
  134. package/src/cache/background-task.ts +55 -2
  135. package/src/cache/cache-exec-scope.ts +48 -1
  136. package/src/cache/cache-key-utils.ts +56 -16
  137. package/src/cache/cache-policy.ts +9 -4
  138. package/src/cache/cache-runtime.ts +197 -144
  139. package/src/cache/cache-scope.ts +304 -112
  140. package/src/cache/cache-tag.ts +105 -13
  141. package/src/cache/cf/cf-cache-store.ts +152 -76
  142. package/src/cache/cf/cf-shell-frame.ts +2 -2
  143. package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
  144. package/src/cache/document-cache.ts +53 -7
  145. package/src/cache/handle-snapshot.ts +52 -9
  146. package/src/cache/invalidation-order.ts +84 -0
  147. package/src/cache/request-tag-mask.ts +147 -0
  148. package/src/cache/shell-snapshot.ts +130 -348
  149. package/src/cache/tag-invalidation.ts +57 -51
  150. package/src/cache/types.ts +71 -67
  151. package/src/cache/vercel/vercel-cache-store.ts +61 -58
  152. package/src/client-urls/client-root.tsx +8 -4
  153. package/src/client-urls/client-urls.ts +8 -6
  154. package/src/client-urls/navigation.ts +55 -4
  155. package/src/client-urls/server-projection.ts +31 -10
  156. package/src/client-urls/types.ts +12 -5
  157. package/src/client.tsx +2 -0
  158. package/src/host/cookie-handler.ts +2 -1
  159. package/src/index.rsc.ts +3 -0
  160. package/src/index.ts +3 -0
  161. package/src/loader-redirect.tsx +7 -3
  162. package/src/prerender/build-shell-capture.ts +155 -146
  163. package/src/route-definition/dsl-helpers.ts +2 -0
  164. package/src/route-definition/helpers-types.ts +11 -11
  165. package/src/route-name.ts +12 -0
  166. package/src/router/content-negotiation.ts +4 -1
  167. package/src/router/error-handling.ts +5 -2
  168. package/src/router/handler-context.ts +10 -7
  169. package/src/router/intercept-resolution.ts +11 -1
  170. package/src/router/loader-resolution.ts +28 -3
  171. package/src/router/logging.ts +5 -3
  172. package/src/router/match-api.ts +62 -34
  173. package/src/router/match-context.ts +5 -0
  174. package/src/router/match-handlers.ts +9 -29
  175. package/src/router/match-middleware/background-revalidation.ts +3 -5
  176. package/src/router/match-middleware/cache-lookup.ts +35 -19
  177. package/src/router/match-middleware/cache-store.ts +15 -9
  178. package/src/router/match-result.ts +1 -0
  179. package/src/router/middleware.ts +2 -5
  180. package/src/router/navigation-snapshot.ts +6 -5
  181. package/src/router/prerender-match.ts +4 -0
  182. package/src/router/request-classification.ts +7 -4
  183. package/src/router/router-options.ts +4 -1
  184. package/src/router/segment-resolution/fresh.ts +0 -4
  185. package/src/router/segment-resolution/loader-cache.ts +150 -25
  186. package/src/router/segment-resolution/loader-mask.ts +3 -4
  187. package/src/router/segment-resolution/loader-snapshot.ts +15 -78
  188. package/src/router/segment-resolution/mask-nested.ts +47 -22
  189. package/src/router/segment-resolution/revalidation.ts +0 -3
  190. package/src/router/segment-resolution/view-transition-default.ts +6 -21
  191. package/src/router/telemetry.ts +6 -3
  192. package/src/router/transition-when.ts +34 -52
  193. package/src/router.ts +2 -1
  194. package/src/rsc/attach-transition-when.ts +47 -0
  195. package/src/rsc/full-payload.ts +30 -10
  196. package/src/rsc/handler.ts +12 -10
  197. package/src/rsc/loader-fetch.ts +2 -1
  198. package/src/rsc/manifest-init.ts +4 -0
  199. package/src/rsc/origin-guard.ts +8 -5
  200. package/src/rsc/progressive-enhancement.ts +7 -30
  201. package/src/rsc/response-cache-serve.ts +68 -80
  202. package/src/rsc/response-route-handler.ts +2 -2
  203. package/src/rsc/rsc-rendering.ts +206 -252
  204. package/src/rsc/server-action.ts +8 -20
  205. package/src/rsc/shell-build-manifest.ts +22 -20
  206. package/src/rsc/shell-capture-constants.ts +136 -20
  207. package/src/rsc/shell-capture.ts +352 -589
  208. package/src/rsc/shell-serve.ts +73 -109
  209. package/src/rsc/ssr-setup.ts +3 -2
  210. package/src/rsc/types.ts +2 -0
  211. package/src/segment-system.tsx +40 -7
  212. package/src/server/context.ts +189 -124
  213. package/src/server/cookie-store.ts +153 -127
  214. package/src/server/handle-store.ts +245 -27
  215. package/src/server/request-context.ts +123 -84
  216. package/src/server/request-headers.ts +134 -0
  217. package/src/server.ts +4 -0
  218. package/src/testing/dispatch.ts +4 -7
  219. package/src/testing/e2e/index.ts +1 -0
  220. package/src/testing/index.ts +5 -0
  221. package/src/testing/internal/action-id.ts +25 -0
  222. package/src/testing/internal/context.ts +1 -1
  223. package/src/testing/location-state-key.ts +45 -0
  224. package/src/testing/render-handler.ts +1 -1
  225. package/src/testing/render-route.tsx +109 -16
  226. package/src/testing/run-client-revalidate.ts +3 -19
  227. package/src/testing/run-loader.ts +5 -1
  228. package/src/testing/run-transition-when.ts +109 -172
  229. package/src/testing/serve-shell-request.ts +10 -7
  230. package/src/testing/shell-status.ts +60 -56
  231. package/src/transition-when-ref.ts +220 -0
  232. package/src/types/cache-types.ts +15 -3
  233. package/src/types/index.ts +3 -0
  234. package/src/types/segments.ts +94 -91
  235. package/src/urls/path-helper-types.ts +3 -4
  236. package/src/urls/pattern-types.ts +3 -4
  237. package/src/vite/discovery/discover-routers.ts +3 -0
  238. package/src/vite/discovery/shell-prerender-phase.ts +0 -2
  239. package/src/vite/discovery/virtual-module-codegen.ts +16 -6
  240. package/src/vite/plugins/transition-when-hoist.ts +1064 -0
  241. package/src/vite/rango.ts +2 -0
  242. package/src/vite/router-discovery.ts +75 -5
  243. package/dist/types/rsc/transition-gate.d.ts +0 -23
  244. package/src/rsc/transition-gate.ts +0 -86
@@ -3,24 +3,27 @@
3
3
  *
4
4
  * Owns the single response-cache contract — cache-scope resolution from the
5
5
  * matched entry tree, condition eval, key resolution (route key() > store
6
- * keyGenerator > default), tag resolution, pre-handler-callback timing, and the
7
- * fresh-hit / SWR-revalidate / miss-write branches — for BOTH the production
6
+ * keyGenerator > default, CacheScope.resolveKeyFrom), tag resolution,
7
+ * pre-handler-callback timing, and the fresh-hit / SWR-revalidate /
8
+ * miss-write branches — for BOTH the production
8
9
  * response-route handler (rsc/response-route-handler.ts) and the dispatch testing
9
10
  * primitive (testing/dispatch.ts), so the two can never drift.
10
11
  *
11
12
  * Plugin-rsc hazard: cache-scope.ts pulls @vitejs/plugin-rsc (via segment-codec),
12
13
  * which the non-Vite unit-test runner cannot resolve, and this module is on the
13
- * testing barrel's EAGER graph (dispatch imports it). So `createCacheScope` and
14
- * `resolveCacheTags` are NOT imported here at runtime — they are INJECTED by the
15
- * caller (production imports them statically; dispatch lazy-imports them only once
16
- * a response route matches). The only runtime imports here are plugin-rsc-free
14
+ * testing barrel's EAGER graph (dispatch imports it). So `createCacheScope` is
15
+ * NOT imported here at runtime — it is INJECTED by the caller (production
16
+ * imports it statically; dispatch lazy-imports it only once a response route
17
+ * matches). The only runtime imports here are plugin-rsc-free
17
18
  * (helpers' isCacheableStatus/finalizeResponse, traverseBack); cache-scope is a
18
19
  * type-only import (erased at build).
19
20
  */
20
21
 
21
22
  import type { CacheScope } from "../cache/cache-scope.js";
22
- import type { PartialCacheOptions } from "../types.js";
23
- import type { RequestContext } from "../server/request-context.js";
23
+ import {
24
+ _getRequestContext,
25
+ type RequestContext,
26
+ } from "../server/request-context.js";
24
27
  import type { SegmentCacheStore } from "../cache/types.js";
25
28
  import type { EntryCacheConfig, EntryData } from "../server/context.js";
26
29
  import { traverseBack } from "../router/pattern-matching.js";
@@ -31,18 +34,19 @@ import {
31
34
  } from "./helpers.js";
32
35
  import { reportCacheError } from "../cache/cache-error.js";
33
36
  import { cacheKeyBase } from "../cache/cache-key-utils.js";
37
+ import {
38
+ executionStart,
39
+ predatesInvalidation,
40
+ type ExecutionStart,
41
+ } from "../cache/tag-invalidation.js";
34
42
  import { hasPerClientSignal } from "../browser/cookie-name.js";
35
43
 
36
- /** Injected cache-scope builders (kept off this module's runtime import graph). */
44
+ /** Injected cache-scope builder (kept off this module's runtime import graph). */
37
45
  export interface CacheScopeDeps {
38
46
  createCacheScope: (
39
47
  config: EntryCacheConfig | undefined,
40
48
  parent?: CacheScope | null,
41
49
  ) => CacheScope | null;
42
- resolveCacheTags: (
43
- config: PartialCacheOptions | false,
44
- ctx: RequestContext | undefined,
45
- ) => string[] | undefined;
46
50
  }
47
51
 
48
52
  export interface ServeResponseRouteWithCacheArgs {
@@ -60,8 +64,12 @@ export interface ServeResponseRouteWithCacheArgs {
60
64
  * applies (no scope, disabled, condition false, or store lacks get/putResponse)
61
65
  * so the caller falls through to a plain `executeHandler()` run.
62
66
  *
63
- * Must run inside runWithRequestContext (reads the ambient request context via
64
- * the helpers and reqCtx.waitUntil for background writes).
67
+ * Must run inside runWithRequestContext for `reqCtx` (reads the ambient
68
+ * request context via the helpers and reqCtx.waitUntil for background writes).
69
+ * Outside it, the route runs uncached: the scope resolves `key()`, a store
70
+ * keyGenerator and `condition()` against the ambient context, and without
71
+ * it a keyGenerator was skipped and the entry landed on the broad default
72
+ * key.
65
73
  */
66
74
  export async function serveResponseRouteWithCache(
67
75
  args: ServeResponseRouteWithCacheArgs,
@@ -75,6 +83,7 @@ export async function serveResponseRouteWithCache(
75
83
  if (method !== "GET" && method !== "HEAD") {
76
84
  return undefined;
77
85
  }
86
+ if (_getRequestContext() !== reqCtx) return undefined;
78
87
 
79
88
  let cacheScope: CacheScope | null = null;
80
89
  for (const entry of traverseBack(manifestEntry)) {
@@ -83,20 +92,12 @@ export async function serveResponseRouteWithCache(
83
92
  }
84
93
  }
85
94
 
86
- if (!cacheScope?.enabled) return undefined;
87
-
88
- // Evaluate condition — skip the response cache when condition returns false.
89
- let conditionPassed = true;
90
- if (cacheScope.config !== false && cacheScope.config.condition) {
91
- try {
92
- conditionPassed = !!cacheScope.config.condition(reqCtx);
93
- } catch {
94
- conditionPassed = false;
95
- }
96
- }
95
+ // Skip the response cache when a condition() of the scope, or of any
96
+ // cache() enclosing it (#974), refuses this request.
97
+ if (!cacheScope?.allowsCache("read")) return undefined;
97
98
 
98
99
  const store = cacheScope.getStore() ?? reqCtx._cacheStore;
99
- if (!conditionPassed || !store?.getResponse || !store?.putResponse) {
100
+ if (!store?.getResponse || !store?.putResponse) {
100
101
  return undefined;
101
102
  }
102
103
 
@@ -105,11 +106,12 @@ export async function serveResponseRouteWithCache(
105
106
  // the host-namespacing and search-normalization rules cannot drift.
106
107
  let cacheKey = `response:${responseType}:${cacheKeyBase(url.host, url.pathname, url.searchParams, undefined, reqCtx._searchParamsFilter)}`;
107
108
 
108
- // Priority 1: route-level key() (full override): the key() results of every
109
- // cache() enclosing the route, composed like the segment record key
110
- // (CacheScope.resolveKeyFrom, #970), so a nested cache() never shares an
111
- // entry across the enclosing partitions. Priority 2: store-level
112
- // keyGenerator (modifies the default key).
109
+ // Keyed like the segment record (CacheScope.resolveKeyFrom): with key()
110
+ // results on the cache() chain (a full override, namespaced, #975) or an
111
+ // enclosing scope on another store whose keyGenerator partitions (#974),
112
+ // `response:` plus the composed key, so a nested cache() never shares an
113
+ // entry across the enclosing partitions (#970); otherwise the store's
114
+ // keyGenerator result (it modifies the default key), else the default key.
113
115
  //
114
116
  // A CONFIGURED key()/keyGenerator that THROWS must DEGRADE TO A MISS, not fall
115
117
  // back to the broad default key. The default key is intentionally broad; if the
@@ -118,45 +120,26 @@ export async function serveResponseRouteWithCache(
118
120
  // poisoning). Mirrors the segment-cache behavior (cache-scope.ts lookupRoute):
119
121
  // a throwing key degrades to a cache miss, never a collision onto the default
120
122
  // slot, and so does a key() with no ambient request context to run in
121
- // (resolveKeyFrom rejects). The no-key default path is left untouched (the
122
- // broad key is correct when no key is configured).
123
- let keyResolutionFailed = false;
124
- if (cacheScope.keyFns.length > 0) {
125
- try {
126
- cacheKey = `response:${await cacheScope.resolveKeyFrom(cacheKey)}`;
127
- } catch (error) {
128
- keyResolutionFailed = true;
129
- reportCacheError(
130
- error,
131
- "cache-read",
132
- "[ResponseCache] Key resolution failed",
133
- reqCtx,
134
- );
135
- }
136
- } else if (store.keyGenerator) {
137
- try {
138
- cacheKey = await store.keyGenerator(reqCtx, cacheKey);
139
- } catch (error) {
140
- keyResolutionFailed = true;
141
- reportCacheError(
142
- error,
143
- "cache-read",
144
- "[ResponseCache] keyGenerator failed",
145
- reqCtx,
146
- );
147
- }
148
- }
149
-
150
- // Degrade to a MISS: return undefined so the caller runs the route UNCACHED.
151
- // This early-returns BEFORE _onResponseCallbacks is saved/cleared below, so the
152
- // pre-handler onResponse callbacks are still intact for the uncached run.
153
- if (keyResolutionFailed) {
123
+ // (resolveKeyFrom rejects).
124
+ try {
125
+ cacheKey = await cacheScope.resolveKeyFrom(cacheKey, "response:");
126
+ } catch (error) {
127
+ reportCacheError(
128
+ error,
129
+ "cache-read",
130
+ "[ResponseCache] Key resolution failed",
131
+ reqCtx,
132
+ );
133
+ // Degrade to a MISS: the caller runs the route UNCACHED. This returns
134
+ // BEFORE _onResponseCallbacks is saved/cleared below, so the pre-handler
135
+ // onResponse callbacks are still intact for the uncached run.
154
136
  return undefined;
155
137
  }
156
138
 
157
- // Resolve cache tags for this document entry (static or dynamic) while the
158
- // request context is available, so the stored entry is tag-invalidatable.
159
- const responseTags = deps.resolveCacheTags(cacheScope.config, reqCtx);
139
+ // Resolve cache tags for this entry (static or dynamic, the enclosing
140
+ // scopes' included, #974) while the request context is available, so the
141
+ // stored entry is tag-invalidatable.
142
+ const responseTags = cacheScope.resolveTags(reqCtx);
160
143
 
161
144
  // Pre-handler callbacks (registered by app-level middleware before the cache
162
145
  // block) are saved and the live array is cleared:
@@ -183,17 +166,26 @@ export async function serveResponseRouteWithCache(
183
166
  const canServeCached = (response: Response): boolean =>
184
167
  isCacheableStatus(response.status) && !hasPerClientSignal(response.headers);
185
168
 
186
- const putFresh = (
169
+ // A handler run that started before one of the entry's tags was
170
+ // invalidated is not written (#977, predatesInvalidation).
171
+ const putFresh = async (
187
172
  store2: SegmentCacheStore,
188
173
  fresh: Response,
189
- ): Promise<void> =>
190
- store2.putResponse!(
174
+ start: ExecutionStart,
175
+ ): Promise<void> => {
176
+ if (await predatesInvalidation(store2, responseTags, start)) {
177
+ // Nothing reads the copy now: release it (a tee branch buffers).
178
+ await fresh.body?.cancel();
179
+ return;
180
+ }
181
+ await store2.putResponse!(
191
182
  cacheKey,
192
- fresh.clone(),
183
+ fresh,
193
184
  cacheScope!.ttl,
194
185
  cacheScope!.swr,
195
186
  responseTags,
196
187
  );
188
+ };
197
189
 
198
190
  try {
199
191
  const cached = await store.getResponse(cacheKey);
@@ -204,8 +196,9 @@ export async function serveResponseRouteWithCache(
204
196
  // Stale hit (SWR): return cached, revalidate in background.
205
197
  reqCtx.waitUntil(async () => {
206
198
  try {
199
+ const start = executionStart();
207
200
  const fresh = finalizeResponse(await executeHandler());
208
- if (canStore(fresh)) await putFresh(store, fresh);
201
+ if (canStore(fresh)) await putFresh(store, fresh, start);
209
202
  } catch (error) {
210
203
  reportCacheError(
211
204
  error,
@@ -227,6 +220,7 @@ export async function serveResponseRouteWithCache(
227
220
  }
228
221
 
229
222
  // Cache miss: execute the handler and cache the result when storeable.
223
+ const start = executionStart();
230
224
  const response = finalizeResponse(await executeHandler());
231
225
  if (canStore(response)) {
232
226
  // Clone SYNCHRONOUSLY here, before returning. The original `response` is
@@ -241,13 +235,7 @@ export async function serveResponseRouteWithCache(
241
235
  const toCache = response.clone();
242
236
  reqCtx.waitUntil(async () => {
243
237
  try {
244
- await store.putResponse!(
245
- cacheKey,
246
- toCache,
247
- cacheScope!.ttl,
248
- cacheScope!.swr,
249
- responseTags,
250
- );
238
+ await putFresh(store, toCache, start);
251
239
  } catch (error) {
252
240
  reportCacheError(
253
241
  error,
@@ -11,7 +11,7 @@ import { getRequestContext } from "../server/request-context.js";
11
11
  import { contextGet, isNonCacheable } from "../context-var.js";
12
12
  import { NOCACHE_SYMBOL } from "../cache/taint.js";
13
13
  import { RESPONSE_TYPE_MIME } from "../router/content-negotiation.js";
14
- import { createCacheScope, resolveCacheTags } from "../cache/cache-scope.js";
14
+ import { createCacheScope } from "../cache/cache-scope.js";
15
15
  import { serveResponseRouteWithCache } from "./response-cache-serve.js";
16
16
  import { executeMiddleware } from "../router/middleware.js";
17
17
  import {
@@ -220,7 +220,7 @@ export async function handleResponseRoute<TEnv>(
220
220
  responseType: preview.responseType,
221
221
  url,
222
222
  executeHandler,
223
- deps: { createCacheScope, resolveCacheTags },
223
+ deps: { createCacheScope },
224
224
  });
225
225
  if (cached !== undefined) return cached;
226
226
  }