@rangojs/router 0.27.0 → 0.29.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 (243) hide show
  1. package/dist/bin/rango.js +1 -1
  2. package/dist/types/__internal.d.ts +7 -1
  3. package/dist/types/browser/action-fence.d.ts +2 -1
  4. package/dist/types/browser/event-controller.d.ts +8 -2
  5. package/dist/types/browser/history-state.d.ts +3 -9
  6. package/dist/types/browser/navigation-store.d.ts +8 -6
  7. package/dist/types/browser/partial-update.d.ts +11 -0
  8. package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
  9. package/dist/types/browser/react/use-action.d.ts +7 -2
  10. package/dist/types/browser/react/use-handle.d.ts +19 -16
  11. package/dist/types/browser/react/use-link-status.d.ts +5 -2
  12. package/dist/types/browser/segment-reconciler.d.ts +32 -4
  13. package/dist/types/browser/types.d.ts +24 -5
  14. package/dist/types/browser/wire-protocol.d.ts +26 -0
  15. package/dist/types/cache/cache-key-utils.d.ts +11 -5
  16. package/dist/types/cache/cache-scope.d.ts +34 -22
  17. package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
  18. package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
  19. package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
  20. package/dist/types/cache/handle-snapshot.d.ts +9 -4
  21. package/dist/types/cache/segment-codec.d.ts +14 -0
  22. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  23. package/dist/types/cache/taint.d.ts +5 -3
  24. package/dist/types/cache/types.d.ts +7 -3
  25. package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
  26. package/dist/types/prefetch-markers.d.ts +5 -14
  27. package/dist/types/prerender/store.d.ts +31 -0
  28. package/dist/types/prerender/warm-request.d.ts +3 -3
  29. package/dist/types/prerender.d.ts +2 -2
  30. package/dist/types/route-name.d.ts +5 -0
  31. package/dist/types/router/error-handling.d.ts +11 -5
  32. package/dist/types/router/instrument.d.ts +4 -3
  33. package/dist/types/router/intercept-resolution.d.ts +25 -9
  34. package/dist/types/router/match-context.d.ts +6 -11
  35. package/dist/types/router/match-handlers.d.ts +2 -6
  36. package/dist/types/router/match-middleware/background-revalidation.d.ts +13 -22
  37. package/dist/types/router/match-middleware/cache-lookup.d.ts +34 -86
  38. package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
  39. package/dist/types/router/navigation-snapshot.d.ts +7 -0
  40. package/dist/types/router/prerender-match.d.ts +21 -6
  41. package/dist/types/router/preview-match.d.ts +2 -7
  42. package/dist/types/router/request-classification.d.ts +5 -0
  43. package/dist/types/router/router-context.d.ts +7 -46
  44. package/dist/types/router/router-interfaces.d.ts +8 -14
  45. package/dist/types/router/segment-resolution/helpers.d.ts +19 -3
  46. package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
  47. package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
  48. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
  49. package/dist/types/router/segment-resolution/resolution-pass.d.ts +95 -0
  50. package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
  51. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
  52. package/dist/types/router/segment-resolution.d.ts +1 -2
  53. package/dist/types/router/segment-wrappers.d.ts +11 -48
  54. package/dist/types/router/telemetry.d.ts +2 -0
  55. package/dist/types/router/types.d.ts +11 -1
  56. package/dist/types/rsc/full-payload.d.ts +2 -2
  57. package/dist/types/rsc/handler-context.d.ts +6 -6
  58. package/dist/types/rsc/json-route-result.d.ts +5 -9
  59. package/dist/types/rsc/request-stages.d.ts +203 -0
  60. package/dist/types/rsc/response-cache-serve.d.ts +7 -12
  61. package/dist/types/rsc/response-route-handler.d.ts +2 -2
  62. package/dist/types/rsc/routine-plan.d.ts +1 -1
  63. package/dist/types/rsc/server-action.d.ts +2 -2
  64. package/dist/types/rsc/shell-capture-constants.d.ts +1 -1
  65. package/dist/types/rsc/shell-capture.d.ts +132 -15
  66. package/dist/types/rsc/shell-serve.d.ts +37 -12
  67. package/dist/types/server/context.d.ts +9 -8
  68. package/dist/types/server/handle-store.d.ts +16 -0
  69. package/dist/types/server/render-pass.d.ts +72 -0
  70. package/dist/types/server/request-context.d.ts +53 -104
  71. package/dist/types/server/rewrite-trail.d.ts +2 -2
  72. package/dist/types/testing/dispatch.d.ts +35 -18
  73. package/dist/types/testing/render-route.d.ts +43 -8
  74. package/dist/types/testing/serve-shell-request.d.ts +8 -1
  75. package/dist/types/types/cache-types.d.ts +6 -3
  76. package/dist/types/types/error-types.d.ts +30 -3
  77. package/dist/types/types/handler-context.d.ts +9 -1
  78. package/dist/types/types/segments.d.ts +1 -1
  79. package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
  80. package/dist/types/vite/discovery/build-env.d.ts +22 -0
  81. package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
  82. package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
  83. package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
  84. package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
  85. package/dist/types/vite/discovery/state.d.ts +12 -0
  86. package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
  87. package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
  88. package/dist/types/vite/router-discovery.d.ts +8 -47
  89. package/dist/types/vite/temp-rsc-server.d.ts +39 -0
  90. package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
  91. package/dist/types/wire-params.d.ts +39 -0
  92. package/dist/vite/index.js +3822 -3727
  93. package/package.json +1 -1
  94. package/skills/cache-guide/SKILL.md +8 -5
  95. package/skills/caching/SKILL.md +37 -18
  96. package/skills/client-urls/SKILL.md +4 -2
  97. package/skills/document-cache/SKILL.md +13 -1
  98. package/skills/handler-use/SKILL.md +1 -1
  99. package/skills/hooks/SKILL.md +1 -1
  100. package/skills/hooks/data.md +8 -1
  101. package/skills/hooks/handle-and-actions.md +33 -2
  102. package/skills/hooks/navigation.md +121 -15
  103. package/skills/hooks/state.md +6 -4
  104. package/skills/hooks/urls.md +16 -5
  105. package/skills/intercept/SKILL.md +23 -7
  106. package/skills/middleware/SKILL.md +18 -0
  107. package/skills/observability/SKILL.md +30 -0
  108. package/skills/ppr/SKILL.md +18 -3
  109. package/skills/prerender/SKILL.md +39 -15
  110. package/skills/rango/SKILL.md +5 -1
  111. package/skills/server-actions/SKILL.md +15 -1
  112. package/skills/testing/client-components.md +25 -18
  113. package/skills/testing/response-routes.md +6 -4
  114. package/skills/testing/serve-shell-request.md +7 -7
  115. package/skills/use-cache/SKILL.md +46 -8
  116. package/skills/view-transitions/SKILL.md +1 -1
  117. package/src/__internal.ts +7 -1
  118. package/src/browser/action-fence.ts +2 -1
  119. package/src/browser/connection-warmup.ts +3 -1
  120. package/src/browser/event-controller.ts +37 -13
  121. package/src/browser/history-state.ts +6 -12
  122. package/src/browser/navigation-bridge.ts +59 -26
  123. package/src/browser/navigation-client.ts +20 -25
  124. package/src/browser/navigation-store.ts +25 -27
  125. package/src/browser/navigation-transaction.ts +4 -1
  126. package/src/browser/network-error-handler.ts +4 -0
  127. package/src/browser/partial-update.ts +33 -6
  128. package/src/browser/prefetch/cache.ts +2 -1
  129. package/src/browser/prefetch/fetch.ts +8 -16
  130. package/src/browser/prefetch/queue.ts +4 -4
  131. package/src/browser/react/NavigationProvider.tsx +2 -1
  132. package/src/browser/react/filter-segment-order.ts +2 -2
  133. package/src/browser/react/use-action.ts +24 -48
  134. package/src/browser/react/use-handle.ts +22 -18
  135. package/src/browser/react/use-link-status.ts +48 -60
  136. package/src/browser/react/use-navigation.ts +92 -41
  137. package/src/browser/rsc-router.tsx +1 -0
  138. package/src/browser/segment-reconciler.ts +109 -53
  139. package/src/browser/server-action-bridge.ts +81 -61
  140. package/src/browser/types.ts +25 -5
  141. package/src/browser/wire-protocol.ts +43 -0
  142. package/src/cache/cache-key-utils.ts +11 -5
  143. package/src/cache/cache-runtime.ts +132 -75
  144. package/src/cache/cache-scope.ts +110 -64
  145. package/src/cache/cf/cf-cache-constants.ts +2 -2
  146. package/src/cache/cf/cf-cache-store.ts +486 -874
  147. package/src/cache/cf/cf-kv-utils.ts +1 -1
  148. package/src/cache/document-cache.ts +64 -46
  149. package/src/cache/handle-snapshot.ts +19 -7
  150. package/src/cache/read-through-swr.ts +20 -18
  151. package/src/cache/segment-codec.ts +55 -0
  152. package/src/cache/shell-snapshot.ts +2 -2
  153. package/src/cache/tag-invalidation.ts +6 -2
  154. package/src/cache/taint.ts +5 -3
  155. package/src/cache/types.ts +7 -3
  156. package/src/cache/vercel/vercel-cache-store.ts +243 -310
  157. package/src/handles/pushed-by.ts +1 -1
  158. package/src/prefetch-markers.ts +5 -16
  159. package/src/prerender/store.ts +76 -0
  160. package/src/prerender/warm-request.ts +8 -4
  161. package/src/prerender.ts +2 -2
  162. package/src/route-name.ts +8 -0
  163. package/src/router/error-handling.ts +22 -10
  164. package/src/router/instrument.ts +4 -3
  165. package/src/router/intercept-resolution.ts +82 -75
  166. package/src/router/loader-resolution.ts +5 -4
  167. package/src/router/match-api.ts +5 -5
  168. package/src/router/match-context.ts +23 -12
  169. package/src/router/match-handlers.ts +8 -20
  170. package/src/router/match-middleware/background-revalidation.ts +46 -57
  171. package/src/router/match-middleware/cache-lookup.ts +677 -726
  172. package/src/router/match-middleware/cache-store.ts +4 -8
  173. package/src/router/match-middleware/intercept-resolution.ts +3 -23
  174. package/src/router/match-middleware/segment-resolution.ts +34 -61
  175. package/src/router/middleware.ts +69 -11
  176. package/src/router/navigation-snapshot.ts +14 -4
  177. package/src/router/prerender-match.ts +100 -44
  178. package/src/router/preview-match.ts +6 -41
  179. package/src/router/request-classification.ts +13 -7
  180. package/src/router/route-snapshot.ts +2 -5
  181. package/src/router/router-context.ts +10 -95
  182. package/src/router/router-interfaces.ts +8 -14
  183. package/src/router/segment-resolution/helpers.ts +49 -13
  184. package/src/router/segment-resolution/loader-cache.ts +15 -9
  185. package/src/router/segment-resolution/loader-mask.ts +5 -5
  186. package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
  187. package/src/router/segment-resolution/resolution-pass.ts +139 -0
  188. package/src/router/segment-resolution/revalidation.ts +939 -789
  189. package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
  190. package/src/router/segment-resolution.ts +4 -13
  191. package/src/router/segment-wrappers.ts +51 -269
  192. package/src/router/telemetry.ts +5 -0
  193. package/src/router/types.ts +11 -1
  194. package/src/router.ts +19 -17
  195. package/src/rsc/full-payload.ts +6 -5
  196. package/src/rsc/handler-context.ts +8 -14
  197. package/src/rsc/handler.ts +126 -507
  198. package/src/rsc/json-route-result.ts +5 -9
  199. package/src/rsc/loader-fetch.ts +6 -5
  200. package/src/rsc/progressive-enhancement.ts +5 -0
  201. package/src/rsc/request-stages.ts +759 -0
  202. package/src/rsc/response-cache-serve.ts +10 -14
  203. package/src/rsc/response-route-handler.ts +13 -5
  204. package/src/rsc/rewrite-passes.ts +2 -1
  205. package/src/rsc/routine-plan.ts +1 -1
  206. package/src/rsc/rsc-rendering.ts +164 -349
  207. package/src/rsc/server-action.ts +87 -113
  208. package/src/rsc/shell-capture-constants.ts +1 -1
  209. package/src/rsc/shell-capture.ts +275 -227
  210. package/src/rsc/shell-serve.ts +121 -18
  211. package/src/rsc/ssr-setup.ts +5 -4
  212. package/src/segment-system.tsx +3 -3
  213. package/src/server/context.ts +22 -25
  214. package/src/server/handle-store.ts +55 -0
  215. package/src/server/render-pass.ts +291 -0
  216. package/src/server/request-context.ts +75 -115
  217. package/src/server/rewrite-trail.ts +6 -4
  218. package/src/ssr/index.tsx +1 -1
  219. package/src/testing/dispatch.ts +226 -710
  220. package/src/testing/e2e/page-helpers.ts +2 -2
  221. package/src/testing/render-route.tsx +199 -13
  222. package/src/testing/serve-shell-request.ts +36 -40
  223. package/src/types/cache-types.ts +6 -3
  224. package/src/types/error-types.ts +30 -3
  225. package/src/types/handler-context.ts +12 -1
  226. package/src/types/segments.ts +1 -1
  227. package/src/vite/discovery/build-discovery-phase.ts +204 -0
  228. package/src/vite/discovery/build-env.ts +148 -0
  229. package/src/vite/discovery/bundle-scan-phase.ts +128 -0
  230. package/src/vite/discovery/dev-prerender-cache.ts +8 -3
  231. package/src/vite/discovery/dev-server-phase.ts +1776 -0
  232. package/src/vite/discovery/discover-routers.ts +1 -1
  233. package/src/vite/discovery/discovery-errors.ts +1 -1
  234. package/src/vite/discovery/prerender-collection.ts +24 -39
  235. package/src/vite/discovery/state.ts +15 -0
  236. package/src/vite/plugins/version-plugin.ts +1 -1
  237. package/src/vite/plugins/virtual-entries.ts +1 -1
  238. package/src/vite/router-discovery.ts +29 -2428
  239. package/src/vite/temp-rsc-server.ts +223 -0
  240. package/src/vite/utils/prerender-utils.ts +47 -0
  241. package/src/wire-params.ts +40 -0
  242. package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
  243. package/src/router/segment-resolution/fresh.ts +0 -945
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Derived render passes over a request past its middleware (PPR shell
3
+ * capture, PPR HIT tail, background re-render, document cache re-run), each
4
+ * `Object.create(base)`.
5
+ * A field missing from a RENDER_PASS_FIELDS row reads the foreground
6
+ * request's value (#684). derived-context-shape.rsc-test.tsx pins each row's
7
+ * own keys.
8
+ */
9
+ import type { RecordingShellStore } from "../cache/shell-snapshot.js";
10
+ import type { HandleStore } from "./handle-store.js";
11
+ import {
12
+ bindRes,
13
+ createUseFunction,
14
+ wireRenderBarrier,
15
+ type RequestContext,
16
+ } from "./request-context.js";
17
+
18
+ type ShellImplicitCache = NonNullable<RequestContext["_shellImplicitCache"]>;
19
+
20
+ /** _handleStore, readonly on RequestContext, is written by the rows. */
21
+ type PassUnderConstruction = RequestContext<any> & {
22
+ _handleStore: HandleStore;
23
+ };
24
+
25
+ /** What each purpose's caller builds for its row. */
26
+ export interface RenderPassInit {
27
+ "shell-capture": {
28
+ handleStore: HandleStore;
29
+ /** A navigation-only capture renders this URL instead of the request's. */
30
+ identity: { request: Request; url: URL } | undefined;
31
+ /** Only with a cache store on the request. */
32
+ recording:
33
+ | { store: RecordingShellStore; implicitCache: ShellImplicitCache }
34
+ | undefined;
35
+ };
36
+ "ppr-tail": {
37
+ /** The request's store, shared: the tail's handles are this response's. */
38
+ handleStore: HandleStore;
39
+ loaderSeed: RequestContext["_shellLoaderSeed"];
40
+ implicitCache: ShellImplicitCache;
41
+ };
42
+ "background-revalidation": {
43
+ handleStore: HandleStore;
44
+ /** Throwaway: the pass's response writes land here and are dropped. */
45
+ sink: RequestContext<any>;
46
+ };
47
+ "document-revalidation": {
48
+ /** The request's: the stale HIT pushed nothing into it. */
49
+ handleStore: HandleStore;
50
+ /**
51
+ * Holds the re-run's response writes, which make the response the
52
+ * document cache stores; starts from the request's response stub.
53
+ */
54
+ sink: RequestContext<any>;
55
+ };
56
+ }
57
+
58
+ export type RenderPurpose = keyof RenderPassInit;
59
+
60
+ interface PassFieldPolicy<I> {
61
+ /** "own": wireRenderBarrier(pass, init.handleStore) before `own` runs. */
62
+ readonly renderBarrier: "own" | "inherited";
63
+ /**
64
+ * Named stores, not one Object.assign: on Node 24 an Object.assign that
65
+ * takes the object to 20 own properties leaves it in V8 dictionary mode
66
+ * (#1109).
67
+ */
68
+ readonly own: (
69
+ pass: PassUnderConstruction,
70
+ init: I,
71
+ base: RequestContext<any>,
72
+ ) => void;
73
+ }
74
+
75
+ export const RENDER_PASS_FIELDS: {
76
+ readonly [P in RenderPurpose]: PassFieldPolicy<RenderPassInit[P]>;
77
+ } = {
78
+ /** rsc/shell-capture.ts deriveShellCaptureContext. */
79
+ "shell-capture": {
80
+ // #684: an inherited barrier is the foreground's, already resolved:
81
+ // ctx.rendered() returns at once and ctx.use(handle) bakes the
82
+ // foreground's handles into the shell.
83
+ renderBarrier: "own",
84
+ own(pass, { handleStore, identity, recording }, base) {
85
+ // Fresh: the foreground's is drained and throws LateHandlePushError.
86
+ pass._handleStore = handleStore;
87
+ if (identity) {
88
+ pass.request = identity.request;
89
+ pass.url = identity.url;
90
+ pass.originalUrl = new URL(identity.url);
91
+ pass.pathname = identity.url.pathname;
92
+ pass.searchParams = identity.url.searchParams;
93
+ }
94
+ // The shell entry's invalidation tags (loader tags belong to holes).
95
+ pass._requestTags = new Set<string>();
96
+ // A capture-ephemeral store in the handler-lifetime set would warn on
97
+ // every later updateTag() and retain the snapshot.
98
+ pass._explicitTaggedStores = new Set();
99
+ // The capture's render errors refuse the capture only.
100
+ pass._renderErrors = [];
101
+ pass._recoveredHandlerErrors = undefined;
102
+ // `run` is read by the loader, cookies() and headers() capture guards.
103
+ // Every field is present from here on: one shape while the capture
104
+ // writes it.
105
+ pass._shellCaptureState = {
106
+ run: true,
107
+ forVisitor: undefined,
108
+ // Bake-lane loader containers: settleCaptureRecord awaits them, the
109
+ // drain pins them into the snapshot.
110
+ loaderRecords: new Map(),
111
+ unrecordedLoaderPush: undefined,
112
+ loaderLanes: undefined,
113
+ guardTripped: undefined,
114
+ guardTrippedLoaderId: undefined,
115
+ // The capture notes its own route records, not the foreground's.
116
+ routeRecordWindow: undefined,
117
+ };
118
+ // The inherited dynamic() writes the foreground, which already served;
119
+ // settleCaptureRecord and captureAndStoreShell read this latch.
120
+ pass._dynamic = false;
121
+ pass.dynamic = (): void => {
122
+ pass._dynamic = true;
123
+ };
124
+ // An inherited true makes serializeSegments double-encode a fragment.
125
+ pass._shellFragmentPayload = false;
126
+ // Metrics and spans are foreground surfaces, already ended.
127
+ pass._metricsStore = undefined;
128
+ pass._tracing = undefined;
129
+ // Fired by settleCaptureRecord: the capture builds no Response, and the
130
+ // doc record's write is gated on onResponse.
131
+ pass._onResponseCallbacks = [];
132
+ if (recording) {
133
+ // Own, so the request's _cacheStore is untouched.
134
+ pass._cacheStore = recording.store;
135
+ // Collects deferred cache writes (settleCaptureRecord awaits them)
136
+ // and still forwards each to the request's waitUntil.
137
+ pass.waitUntil = (fn: () => Promise<void>): void => {
138
+ const p = Promise.resolve().then(fn);
139
+ recording.store.trackWrite(p);
140
+ base.waitUntil(() => p);
141
+ };
142
+ // The doc record goes into the snapshot only, so a recapture misses
143
+ // and handlers re-run.
144
+ pass._shellImplicitCache = recording.implicitCache;
145
+ }
146
+ },
147
+ },
148
+
149
+ /** rsc/rsc-rendering.ts serveShellHit. */
150
+ "ppr-tail": {
151
+ // An inherited barrier carries the base's premature non-streaming handle
152
+ // snapshot into a streaming tail.
153
+ renderBarrier: "own",
154
+ own(pass, { handleStore, loaderSeed, implicitCache }) {
155
+ // #1035: the document hydrates with the handles as of the record
156
+ // replay, before any loader runs.
157
+ const resolveRenderBarrier = pass._resolveRenderBarrier;
158
+ pass._resolveRenderBarrier = (segments) => {
159
+ handleStore.freezeDocumentSnapshot();
160
+ resolveRenderBarrier(segments);
161
+ };
162
+ if (loaderSeed) pass._shellLoaderSeed = loaderSeed;
163
+ pass._shellImplicitCache = implicitCache;
164
+ // #700 fragment splice: own, so it never reaches a capture render.
165
+ pass._shellFragmentPayload = true;
166
+ },
167
+ },
168
+
169
+ /**
170
+ * router/match-middleware/background-revalidation.ts rerenderAndCacheRoute.
171
+ */
172
+ "background-revalidation": {
173
+ // The request's, resolved before the pass starts: a stale HIT resolves
174
+ // it in cache-lookup.ts before withBackgroundRevalidation starts the
175
+ // refresh, and proactive caching runs from onResponse, so a loader's
176
+ // rendered() here never waits. The route's and an open intercept's
177
+ // passes skip loaders: one runs only when a handler reads it, started
178
+ // through its registration (resolveOwnerLoaders), so its pushes stay out
179
+ // of the record (captureHandles drops loader pushes) and its value bakes
180
+ // into the handler's output, as on a MISS. A shell capture never opens
181
+ // an intercept (createNavigationCaptureRequest drops the intercept
182
+ // source).
183
+ renderBarrier: "inherited",
184
+ own(pass, { handleStore, sink }) {
185
+ // Read late by loader pushes and cache-scope.ts; setupLoaderAccess
186
+ // binds it at setup, so that also runs inside this pass.
187
+ pass._handleStore = handleStore;
188
+ // Nothing lands on the foreground's perf timeline.
189
+ pass._metricsStore = undefined;
190
+ pass._renderErrors = [];
191
+ pass._recoveredHandlerErrors = undefined;
192
+ // The barrier's deadlock bookkeeping is the pass's own and stays open.
193
+ // The visitor's is closed once its barrier resolves, so a handler's
194
+ // read of a loader that calls rendered() went unrecorded and rendered()
195
+ // let it through: the record baked the visitor's handle snapshot, or on
196
+ // a streaming tree rendered() awaited this pass's store, which awaits
197
+ // that handler, and the refresh never settled. Open, the read is
198
+ // refused as a MISS refuses it.
199
+ pass._handlerLoaderDeps = undefined;
200
+ pass._renderBarrierWaiters = undefined;
201
+ pass._renderBarrierGuardClosed = undefined;
202
+ // A route cache() record, not a shell, even when a capture's stale read
203
+ // starts it: under the capture's guard a loader body's headers() fails
204
+ // the refresh, and recaptures replay the stale record until it expires.
205
+ pass._shellCaptureState = undefined;
206
+ // Response writes go to the sink: a layout above the cache() boundary
207
+ // or an error boundary's status would otherwise reach the live response.
208
+ pass._onResponseCallbacks = [];
209
+ pass.header = sink.header;
210
+ pass.setCookie = sink.setCookie;
211
+ pass.deleteCookie = sink.deleteCookie;
212
+ pass.setStatus = sink.setStatus;
213
+ pass._setStatus = sink._setStatus;
214
+ pass.setTheme = sink.setTheme;
215
+ pass._rotateStateCookie = sink._rotateStateCookie;
216
+ pass._setKeepCacheDirective = sink._setKeepCacheDirective;
217
+ // An invalidation here is not the visitor's mutation.
218
+ pass._setFreshReadsCookie = sink._setFreshReadsCookie;
219
+ // A cached handler's dynamic() opts out nothing here: the inherited one
220
+ // would refuse the capture whose stale read started the pass, or opt
221
+ // out the visitor's request.
222
+ pass.dynamic = sink.dynamic;
223
+ bindRes(pass, () => sink.res);
224
+ },
225
+ },
226
+
227
+ /** cache/document-cache.ts: the re-run of a stale entry, a whole request. */
228
+ "document-revalidation": {
229
+ // The stale HIT matched and rendered nothing: the re-run is the
230
+ // request's render. The request's barrier closes over the request, whose
231
+ // _treeHasStreaming the re-run never sets.
232
+ renderBarrier: "own",
233
+ own(pass, { handleStore, sink }, base) {
234
+ pass._handleStore = handleStore;
235
+ // The request's use() reads the request's params and route name, which
236
+ // the re-run's match sets on this pass. Its own memo: a loader that
237
+ // middleware ahead of the cache started runs again here.
238
+ pass.use = createUseFunction({
239
+ handleStore,
240
+ loaderPromises: new Map(),
241
+ getContext: () => pass,
242
+ });
243
+ // Its own latch, from what middleware ahead of the cache set: the
244
+ // render reads it on this pass, as a MISS reads the request's. The
245
+ // sink's dynamic() clears the ppr header latch.
246
+ pass._dynamic = base._dynamic;
247
+ pass.dynamic = (): void => {
248
+ pass._dynamic = true;
249
+ sink.dynamic();
250
+ };
251
+ pass._metricsStore = undefined;
252
+ // The re-run's classify timing stays off the visitor's Server-Timing.
253
+ pass._handlerTiming = [];
254
+ pass._renderErrors = [];
255
+ pass._recoveredHandlerErrors = undefined;
256
+ pass._shellCaptureState = undefined;
257
+ // Response writes go to the sink, never the visitor's STALE response.
258
+ pass._onResponseCallbacks = [];
259
+ pass.header = sink.header;
260
+ pass.setCookie = sink.setCookie;
261
+ pass.deleteCookie = sink.deleteCookie;
262
+ pass.setStatus = sink.setStatus;
263
+ pass._setStatus = sink._setStatus;
264
+ pass.setTheme = sink.setTheme;
265
+ pass._setKeepCacheDirective = sink._setKeepCacheDirective;
266
+ // No client receives the re-run's response: no rotation, no
267
+ // fresh-reads cookie (the sink has no state cookie name).
268
+ pass._rotateStateCookie = sink._rotateStateCookie;
269
+ pass._setFreshReadsCookie = sink._setFreshReadsCookie;
270
+ bindRes(pass, () => sink.res);
271
+ },
272
+ },
273
+ };
274
+
275
+ /**
276
+ * `Object.create(base)` with RENDER_PASS_FIELDS[purpose]'s own fields. The only
277
+ * place a request context is derived (tools/check-internals.mjs).
278
+ */
279
+ export function deriveRenderPass<P extends RenderPurpose, TEnv>(
280
+ base: RequestContext<TEnv>,
281
+ purpose: P,
282
+ init: RenderPassInit[P],
283
+ ): RequestContext<TEnv> {
284
+ const policy = RENDER_PASS_FIELDS[purpose];
285
+ const pass: RequestContext<TEnv> = Object.create(base);
286
+ if (policy.renderBarrier === "own") {
287
+ wireRenderBarrier(pass, init.handleStore);
288
+ }
289
+ policy.own(pass, init, base);
290
+ return pass;
291
+ }
@@ -66,6 +66,7 @@ import {
66
66
  import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
67
67
  import type { RoutineTrace } from "../rsc/routine-plan.js";
68
68
  import type { ActionContinuation } from "../rsc/server-action.js";
69
+ import type { ShellCaptureState } from "../rsc/shell-capture.js";
69
70
  import type { FormActionOutcome } from "../rsc/progressive-enhancement.js";
70
71
  import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
71
72
  import {
@@ -273,8 +274,8 @@ export interface RequestContext<
273
274
 
274
275
  /**
275
276
  * @internal Handle store for tracking handle data across segments.
276
- * Readonly: a background render that needs its own store derives a context
277
- * (rerenderAndCacheRoute, shell-capture.ts) instead of swapping this field,
277
+ * Readonly: a render that needs its own store derives a render pass
278
+ * (deriveRenderPass, server/render-pass.ts) instead of swapping this field,
278
279
  * which the foreground still reads while it produces the page.
279
280
  */
280
281
  readonly _handleStore: HandleStore;
@@ -334,56 +335,13 @@ export interface RequestContext<
334
335
  _searchParamsFilter?: import("../cache/search-params-filter.js").SearchParamsFilter;
335
336
 
336
337
  /**
337
- * @internal PPR shell-capture ACTIVE marker. True ONLY inside the background
338
- * capture task's derived request context (built by shell-capture.ts). This is
339
- * the switch every capture-specific behavior reads: loader masking
340
- * (loader-mask.ts isShellCaptureActive / fresh.ts emitStreaming) and the
341
- * identity-read capture guard (server/context.ts guardIdentityRead). The
342
- * foreground render never sets it, so the
343
- * served response is byte-identical to axis 1. The capture descriptor itself
344
- * (key/ttl/swr/tags/store) is NOT threaded through the request context — the
345
- * integrated PPR serve path (rsc/shell-serve.ts + rsc-rendering.ts) builds it
346
- * locally and passes it to scheduleShellCapture directly.
338
+ * @internal A PPR shell capture's own state (rsc/shell-capture.ts
339
+ * ShellCaptureState): created by RENDER_PASS_FIELDS["shell-capture"]
340
+ * (server/render-pass.ts) on the capture's derived context. The foreground
341
+ * render and every background re-render never have one, so
342
+ * `_shellCaptureState?.run` is the capture switch.
347
343
  */
348
- _shellCaptureRun?: boolean;
349
-
350
- /**
351
- * @internal Set beside `_shellCaptureRun` when the shell is keyed by the
352
- * visitor's URL (`rewrite(...).shell("visitor")`): the capture renders the
353
- * visitor's location and `ctx.rewrittenFrom`; the theme stays the default.
354
- */
355
- _shellCaptureForVisitor?: boolean;
356
-
357
- /**
358
- * @internal Bake-lane loader containers collected DURING a shell capture:
359
- * segment-key -> the loader's (pre-wrap) result promise. Populated by
360
- * resolveLoaderData for bake-lane loaders (lane rule: see its JSDoc in
361
- * loader-cache.ts). Drained by captureAndStoreShell after the shell
362
- * quiesces: settled containers are promise-elided, Flight-serialized, and
363
- * pinned into the snapshot's loader family; a REJECTED container refuses
364
- * the capture (error UI must never bake into the shared shell). Own
365
- * property of the capture's derived context only.
366
- */
367
- _shellCaptureLoaderRecords?: Map<string, Promise<unknown>>;
368
-
369
- /**
370
- * @internal Set on a capture's derived context when a loader pushed a
371
- * value the record cannot keep (masked nested promises, a pushed promise
372
- * that settled to one, a live-lane loader's push, or a push outside a
373
- * loader body): the loader records then carry `runs: 1`, so a HIT still
374
- * runs their bodies for those pushes.
375
- */
376
- _shellCaptureUnrecordedLoaderPush?: true;
377
-
378
- /**
379
- * @internal The lane of each loader the capture's matched route registers
380
- * (loader-cache.ts routeLoaderLanes), set by createMatchContextForFull
381
- * before any loader runs. The capture's push funnel credits a push made
382
- * under a live-lane loader's body, at any depth, to that loader
383
- * (shell-capture.ts deriveShellCaptureContext). Own property of the
384
- * capture's derived context only.
385
- */
386
- _shellCaptureLoaderLanes?: ReadonlyMap<string, "live" | "bake">;
344
+ _shellCaptureState?: ShellCaptureState;
387
345
 
388
346
  /**
389
347
  * @internal Record keys resolved for this request (CacheScope
@@ -403,9 +361,9 @@ export interface RequestContext<
403
361
 
404
362
  /**
405
363
  * @internal The request carried the forced-MISS reload marker
406
- * (shell-serve.ts SHELL_MISS_PARAM). The handler strips the marker from the
407
- * request on entry and sets this; the shell gate renders the request like a
408
- * cache miss.
364
+ * (`WIRE.shellMiss`). The handler strips the marker from the request on
365
+ * entry (shell-serve.ts withoutShellMissMarker) and sets this; the shell
366
+ * gate renders the request like a cache miss.
409
367
  */
410
368
  _shellForcedMiss?: true;
411
369
 
@@ -425,17 +383,6 @@ export interface RequestContext<
425
383
  */
426
384
  _runAtRequestEntry?: <T>(fn: () => T) => T;
427
385
 
428
- /**
429
- * @internal The freshness window of the route cache() records this
430
- * request read or wrote (cache-scope.ts noteRouteRecordWindow), as ms
431
- * timestamps: `freshUntil` is the record's `expiresAt`, `staleUntil` adds
432
- * its scope's swr. The narrowest record wins. A shell capture caps its
433
- * entry's ttl and ttl+swr to it (shell-capture.ts capShellWindow), so a
434
- * shell is never fresher, or served longer, than the route cache() entry
435
- * it replayed or wrote.
436
- */
437
- _routeRecordWindow?: RouteRecordWindow;
438
-
439
386
  /**
440
387
  * @internal Loader-family snapshot seed for a shell HIT's tail render:
441
388
  * loader id -> the capture's elided container (already Flight-deserialized
@@ -563,29 +510,6 @@ export interface RequestContext<
563
510
  */
564
511
  _shellFragmentPayload?: boolean;
565
512
 
566
- /**
567
- * @internal Set by the capture guard (server/context.ts
568
- * guardIdentityRead) when it throws DURING a capture render: the read
569
- * ("cookies()", "headers()", `ctx.get() for a non-cacheable variable`,
570
- * "ctx.theme") and the fix the
571
- * refusal warning gives for it. Load-bearing for the
572
- * bake lane: a guard throw inside an executing loader is swallowed by
573
- * wrapLoaderPromise into per-loader error UI, which would otherwise bake
574
- * silently into the shared shell — the capture checks this flag after the
575
- * render and refuses instead. Deterministic, so the capture does not retry.
576
- */
577
- _shellCaptureGuardTripped?: { surface: string; fix: string };
578
-
579
- /**
580
- * @internal The loader $$id whose BODY was executing when the capture guard
581
- * tripped (read off the loader-body ALS scope at trip time), or undefined
582
- * when the read came from handler/render code. Only used to make the
583
- * once-per-key refusal warning name the real source — the old text
584
- * hardcoded "a bake-lane loader", which misattributed handler-land reads
585
- * and sent users debugging the wrong lane (issue #672, secondary).
586
- */
587
- _shellCaptureGuardTrippedLoaderId?: string;
588
-
589
513
  /**
590
514
  * @internal Handler-owned registry of explicit per-scope stores from
591
515
  * cache({ store }). Created once per createRSCHandler() and threaded into
@@ -882,8 +806,8 @@ export interface RequestContext<
882
806
  * (loader(Def, { ssr: false })): segment resolution awaits these
883
807
  * before returning, so the render barrier cannot resolve until they settle.
884
808
  * rendered() checks this set to fail fast — a flagged loader awaiting the
885
- * barrier is a guaranteed cycle, not a race. Registered by resolveLoaders
886
- * (fresh.ts) before loader kickoff; only ever populated on document renders
809
+ * barrier is a guaranteed cycle, not a race. Registered by resolveOwnerLoaders
810
+ * before loader kickoff; only ever populated on document renders
887
811
  * (the flag is stamped per-isSSR at DSL evaluation).
888
812
  */
889
813
  _awaitBeforeFlushLoaderIds?: Set<string>;
@@ -909,6 +833,13 @@ export interface RequestContext<
909
833
  /** @internal Per-request error dedup set for onError reporting */
910
834
  _reportedErrors: WeakSet<object>;
911
835
 
836
+ /**
837
+ * @internal The segment a parallel slot's or orphan layout's error came
838
+ * from, for the owning entry's catch (segment-resolution/helpers.ts
839
+ * noteThrower). Per request: an error object can be thrown by several.
840
+ */
841
+ _throwers?: WeakMap<object, { id: string; type: string }>;
842
+
912
843
  /**
913
844
  * @internal The errors this request printed to the server console, so a
914
845
  * failure prints once where it originated (server/print-server-error.ts). A derived context shares it through the prototype.
@@ -978,6 +909,13 @@ export interface RequestContext<
978
909
  */
979
910
  _handlerStart?: number;
980
911
 
912
+ /**
913
+ * @internal Handler-level Server-Timing entries (rsc/handler.ts): the
914
+ * bootstrap timings, then the core handler's classify. A stale document
915
+ * cache entry's re-run has its own (render-pass.ts).
916
+ */
917
+ _handlerTiming?: string[];
918
+
981
919
  /** @internal Resolved platform phase-span tracing for this request (Cloudflare or OTel) */
982
920
  _tracing?: ResolvedTracing;
983
921
 
@@ -1020,21 +958,6 @@ export interface RequestContext<
1020
958
  _cacheSignal?: import("../router/telemetry.js").CacheSegmentSignal[];
1021
959
  }
1022
960
 
1023
- /**
1024
- * @internal The freshness window of a route cache() record, as ms
1025
- * timestamps: fresh until `freshUntil` (its `expiresAt`), stale until
1026
- * `staleUntil` (plus its scope's swr). `ttl`, `swr` and `written` describe
1027
- * the record that sets `staleUntil`: its cache() config, and whether this
1028
- * request wrote it (cacheRoute) rather than read it.
1029
- */
1030
- export interface RouteRecordWindow {
1031
- freshUntil: number;
1032
- staleUntil: number;
1033
- ttl: number;
1034
- swr: number;
1035
- written: boolean;
1036
- }
1037
-
1038
961
  /**
1039
962
  * Public view of RequestContext, without internal methods and fields.
1040
963
  *
@@ -1059,22 +982,15 @@ export type PublicRequestContext<
1059
982
  | "_prerender"
1060
983
  | "_onDemandProducer"
1061
984
  | "_searchParamsFilter"
1062
- | "_shellCaptureRun"
1063
- | "_shellCaptureForVisitor"
985
+ | "_shellCaptureState"
1064
986
  | "_shellImplicitCache"
1065
987
  | "_shellLoaderSeed"
1066
- | "_shellCaptureLoaderRecords"
1067
- | "_shellCaptureUnrecordedLoaderPush"
1068
- | "_shellCaptureLoaderLanes"
1069
988
  | "_resolvedCacheKeys"
1070
989
  | "_shellKey"
1071
990
  | "_shellForcedMiss"
1072
991
  | "_prerenderWarm"
1073
992
  | "_runAtRequestEntry"
1074
- | "_routeRecordWindow"
1075
993
  | "_shellFragmentPayload"
1076
- | "_shellCaptureGuardTripped"
1077
- | "_shellCaptureGuardTrippedLoaderId"
1078
994
  | "_explicitTaggedStores"
1079
995
  | "_requestTags"
1080
996
  | "_recordTagOwners"
@@ -1096,6 +1012,7 @@ export type PublicRequestContext<
1096
1012
  | "_inActionRevalidation"
1097
1013
  | "_actionRan"
1098
1014
  | "_reportedErrors"
1015
+ | "_throwers"
1099
1016
  | "_printedErrors"
1100
1017
  | "_renderErrors"
1101
1018
  | "_recoveredHandlerErrors"
@@ -1115,6 +1032,7 @@ export type PublicRequestContext<
1115
1032
  | "_activeRoutine"
1116
1033
  | "_renderDiagnosticsEnabled"
1117
1034
  | "_handlerStart"
1035
+ | "_handlerTiming"
1118
1036
  | "_tracing"
1119
1037
  | "_basename"
1120
1038
  | "_routerId"
@@ -1230,6 +1148,46 @@ export function runWithRequestContext<TEnv, T>(
1230
1148
  return requestContextStorage.run(context, fn);
1231
1149
  }
1232
1150
 
1151
+ /** Inside background work: the errors its onError reports named. */
1152
+ const backgroundWork = new AsyncLocalStorage<WeakSet<object>>();
1153
+
1154
+ /**
1155
+ * Run work whose output goes only to a cache, never to the response of the
1156
+ * request that started it, on `context`: a stale entry's refresh (a "use
1157
+ * cache" function, a cached loader, a cache() record, a document), a
1158
+ * navigation's proactive cache() write, a PPR shell capture. No visitor made
1159
+ * the invalidations in it, so they set no fresh-reads cookie
1160
+ * (cache/tag-invalidation.ts), and onError marks every error reported inside
1161
+ * it `background` (router/error-handling.ts invokeOnError). Its failure is
1162
+ * reported inside it too: a catch outside runs on the visitor's frame.
1163
+ */
1164
+ export function runAsBackgroundWork<TEnv, T>(
1165
+ context: RequestContext<TEnv>,
1166
+ fn: () => T,
1167
+ ): T {
1168
+ const reported = backgroundWork.getStore() ?? new WeakSet<object>();
1169
+ return requestContextStorage.run(context, () =>
1170
+ backgroundWork.run(reported, fn),
1171
+ );
1172
+ }
1173
+
1174
+ /** Whether the caller runs inside runAsBackgroundWork. */
1175
+ export function isBackgroundWork(): boolean {
1176
+ return backgroundWork.getStore() !== undefined;
1177
+ }
1178
+
1179
+ /**
1180
+ * The set onError's dedupe records a reported error in. Background work keeps
1181
+ * its own: an error object both the visitor's render and background work
1182
+ * reach (a memoized rejection, a thrown constant) is reported by each, so a
1183
+ * background report never takes the place of the visitor's.
1184
+ */
1185
+ export function reportedErrorsOf(
1186
+ context: RequestContext<any>,
1187
+ ): WeakSet<object> {
1188
+ return backgroundWork.getStore() ?? context._reportedErrors;
1189
+ }
1190
+
1233
1191
  /**
1234
1192
  * Get the current request context
1235
1193
  * Throws if called outside of a request context
@@ -1835,6 +1793,7 @@ export function createRequestContext<TEnv>(
1835
1793
  _locationState: undefined,
1836
1794
 
1837
1795
  _reportedErrors: new WeakSet<object>(),
1796
+ _throwers: undefined,
1838
1797
  _printedErrors: new WeakSet<object>(),
1839
1798
  _renderErrors: [],
1840
1799
  _metricsStore: undefined,
@@ -1842,6 +1801,7 @@ export function createRequestContext<TEnv>(
1842
1801
  _activeRoutine: undefined,
1843
1802
  _renderDiagnosticsEnabled: undefined,
1844
1803
  _handlerStart: undefined,
1804
+ _handlerTiming: undefined,
1845
1805
 
1846
1806
  _resolveRenderBarrier: null as any,
1847
1807
  _renderBarrierSegmentOrder: undefined,
@@ -1926,8 +1886,8 @@ export function rebaseRequestContext(
1926
1886
  /**
1927
1887
  * Wire a fresh render barrier onto `ctx`, closure-bound to THIS ctx and THIS
1928
1888
  * handle store. Called by createRequestContext for every fresh context, and by
1929
- * deriveShellCaptureContext (rsc/shell-capture.ts) for the PPR capture's
1930
- * derived context.
1889
+ * deriveRenderPass (server/render-pass.ts) for a derived pass whose
1890
+ * RENDER_PASS_FIELDS row owns its barrier (the PPR capture and the HIT tail).
1931
1891
  *
1932
1892
  * The derived-context call is load-bearing (issue #684, plan 009): the capture
1933
1893
  * context is `Object.create(reqCtx)`, so without its own wiring every
@@ -89,17 +89,19 @@ export function rewriteShellAudience(
89
89
 
90
90
  type AudienceContext = Pick<
91
91
  RequestContext<any>,
92
- "_shellCaptureRun" | "_documentCacheRender" | "res"
92
+ "_shellCaptureState" | "_documentCacheRender" | "res"
93
93
  >;
94
94
 
95
95
  /**
96
96
  * A render other visitors may be served: a PPR shell capture, or a render
97
97
  * the document cache stores (its response opted in before next()). It shows
98
98
  * no visitor theme, and no visitor URL unless it is a visitor-keyed shell
99
- * capture (`_shellCaptureForVisitor`).
99
+ * capture (`_shellCaptureState.forVisitor`).
100
100
  */
101
101
  export function rendersForSharedAudience(reqCtx: AudienceContext): boolean {
102
- return reqCtx._shellCaptureRun === true || documentCacheStoresRender(reqCtx);
102
+ return (
103
+ reqCtx._shellCaptureState?.run === true || documentCacheStoresRender(reqCtx)
104
+ );
103
105
  }
104
106
 
105
107
  /**
@@ -150,7 +152,7 @@ function readRewrittenFrom(
150
152
  ): string | undefined {
151
153
  if (!reqCtx) return undefined;
152
154
  if (reader === "render" && rendersForSharedAudience(reqCtx)) {
153
- if (!reqCtx._shellCaptureForVisitor) return undefined;
155
+ if (!reqCtx._shellCaptureState?.forVisitor) return undefined;
154
156
  // guardIdentityRead minus the capture guard: a cache() boundary, a "use
155
157
  // cache" body or a loader's own cache() inside the capture is keyed
156
158
  // without the URL.
package/src/ssr/index.tsx CHANGED
@@ -442,7 +442,7 @@ const SSR_FALSE_FIRST_FLUSH_TURNS = 3;
442
442
 
443
443
  /**
444
444
  * True when the matched chain has a `loader(Def, { ssr: false })` entry: its
445
- * payload segments carry `awaitBeforeFlush` (fresh.ts).
445
+ * payload segments carry `awaitBeforeFlush` (resolveOwnerLoaders).
446
446
  */
447
447
  function hasAwaitBeforeFlush(payload: {
448
448
  metadata?: { segments?: readonly { awaitBeforeFlush?: true }[] };