@rangojs/router 0.16.0 → 0.17.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.
- package/README.md +254 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-scope.d.ts +34 -5
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +53 -13
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +15 -3
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +3 -2
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
- package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +10 -0
- package/dist/types/server/context.d.ts +33 -3
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +24 -21
- package/dist/types/ssr/index.d.ts +15 -0
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/vite/index.js +1381 -1346
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +65 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +207 -97
- package/skills/caching/SKILL.md +295 -93
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +53 -31
- package/skills/cloudflare/SKILL.md +72 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +39 -33
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +2 -1
- package/skills/deployment-caching/SKILL.md +6 -1
- package/skills/document-cache/SKILL.md +92 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +53 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +31 -16
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +37 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +242 -106
- package/skills/middleware/SKILL.md +119 -23
- package/skills/migrate-nextjs/SKILL.md +100 -45
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +49 -38
- package/skills/migrate-react-router/component-migration.md +25 -6
- package/skills/migrate-react-router/data-and-actions.md +18 -5
- package/skills/migrate-react-router/route-mapping.md +12 -6
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +28 -14
- package/skills/parallel/SKILL.md +75 -39
- package/skills/ppr/SKILL.md +345 -150
- package/skills/prerender/SKILL.md +189 -100
- package/skills/rango/SKILL.md +65 -34
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +165 -60
- package/skills/router-setup/SKILL.md +418 -240
- package/skills/scripts/SKILL.md +31 -20
- package/skills/server-actions/SKILL.md +220 -59
- package/skills/shell-manifest/SKILL.md +12 -7
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +68 -32
- package/skills/testing/cache-prerender.md +55 -29
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +8 -5
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +106 -29
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +126 -47
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +200 -82
- package/skills/vercel/SKILL.md +21 -11
- package/skills/view-transitions/SKILL.md +41 -20
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +1 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +1 -6
- package/src/cache/cache-runtime.ts +230 -61
- package/src/cache/cache-scope.ts +76 -12
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +132 -28
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/tag-invalidation.ts +4 -3
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +16 -3
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +117 -30
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +34 -25
- package/src/router/intercept-resolution.ts +125 -39
- package/src/router/loader-resolution.ts +56 -11
- package/src/router/match-api.ts +12 -17
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +93 -10
- package/src/router/match-middleware/cache-store.ts +17 -90
- package/src/router/middleware-types.ts +3 -5
- package/src/router/middleware.ts +52 -9
- package/src/router/prerender-match.ts +53 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +44 -12
- package/src/router/segment-resolution/loader-cache.ts +205 -45
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +6 -6
- package/src/router/segment-resolution/mask-nested.ts +7 -7
- package/src/router/segment-resolution/revalidation.ts +34 -6
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +8 -45
- package/src/rsc/shell-capture-constants.ts +13 -0
- package/src/rsc/shell-capture.ts +111 -90
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +11 -0
- package/src/server/context.ts +88 -11
- package/src/server/cookie-store.ts +5 -4
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +35 -30
- package/src/ssr/index.tsx +40 -7
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +8 -6
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
|
@@ -82,13 +82,13 @@
|
|
|
82
82
|
* ISOLATION FROM RESPONSE
|
|
83
83
|
* =======================
|
|
84
84
|
*
|
|
85
|
-
* Background revalidation
|
|
86
|
-
*
|
|
85
|
+
* Background revalidation re-renders through rerenderAndCacheRoute (shared
|
|
86
|
+
* with proactive caching in cache-store.ts):
|
|
87
|
+
* - Derived request context with its own handleStore, transition({ when })
|
|
88
|
+
* predicates and no perf metrics (the shared fields are never touched;
|
|
89
|
+
* the foreground is still producing the page)
|
|
87
90
|
* - Fresh handlerContext + loaderPromises (prevents reusing memoized
|
|
88
91
|
* loader results from the foreground pass)
|
|
89
|
-
* - handleStore is saved/restored in try/finally
|
|
90
|
-
*
|
|
91
|
-
* This matches the proactive caching pattern in cache-store.ts.
|
|
92
92
|
*
|
|
93
93
|
*
|
|
94
94
|
* FRESH RESOLUTION (NO REVALIDATION)
|
|
@@ -101,15 +101,163 @@
|
|
|
101
101
|
*/
|
|
102
102
|
import type { ResolvedSegment } from "../../types.js";
|
|
103
103
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
104
|
-
import { getRouterContext } from "../router-context.js";
|
|
104
|
+
import { getRouterContext, type RouterContext } from "../router-context.js";
|
|
105
|
+
import type { CacheScope } from "../../cache/cache-scope.js";
|
|
105
106
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
106
107
|
import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
107
108
|
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
109
|
+
import { getContext } from "../../server/context.js";
|
|
108
110
|
import {
|
|
111
|
+
createRequestContext,
|
|
109
112
|
runWithRequestContext,
|
|
110
113
|
type RequestContext,
|
|
111
114
|
} from "../../server/request-context.js";
|
|
112
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Re-render a cached route in the background and write it with
|
|
118
|
+
* cacheScope.cacheRoute: the stale-hit refresh below and proactive caching
|
|
119
|
+
* (cache-store.ts). Returns the number of segments written.
|
|
120
|
+
*
|
|
121
|
+
* A render whose status is not 200 (an error or notFound boundary resolved)
|
|
122
|
+
* is not written and returns 0, as a non-200 MISS is not cached
|
|
123
|
+
* (cache-store.ts): the entry it would replace keeps serving until a later
|
|
124
|
+
* refresh succeeds or it expires. Nothing is held in process; a store's own
|
|
125
|
+
* revalidation marker (CFCacheStore's REVALIDATING) re-arms after
|
|
126
|
+
* MAX_REVALIDATION_INTERVAL, as after a refresh that throws.
|
|
127
|
+
*
|
|
128
|
+
* Runs on a DERIVED request context that owns the per-render state the
|
|
129
|
+
* foreground reads while it is still producing the page (a stale HIT re-runs
|
|
130
|
+
* its loaders; a proactive render starts once the Response exists, while its
|
|
131
|
+
* body streams):
|
|
132
|
+
* - _handleStore: read late (loader pushes and aux-lane tracking in
|
|
133
|
+
* loader-resolution.ts, nested cache restores and captures in
|
|
134
|
+
* cache-scope.ts), so a swap sent live pushes into this render.
|
|
135
|
+
* setupLoaderAccess binds the store at setup, so it runs inside the
|
|
136
|
+
* derived context too.
|
|
137
|
+
* - _transitionWhen: fresh resolution appends transition({ when })
|
|
138
|
+
* predicates; the foreground's gateTransitions reads the array after the
|
|
139
|
+
* match, and a stale HIT (which replays the stored transition) must not
|
|
140
|
+
* evaluate this render's predicates.
|
|
141
|
+
* - _metricsStore: undefined, and the DSL store below has `metrics` unset
|
|
142
|
+
* (track() reads that one), so nothing lands on the foreground's perf
|
|
143
|
+
* timeline. ctx.Store.run closes over ctx.Store, so the derived store
|
|
144
|
+
* needs its own run.
|
|
145
|
+
* - response writes (headers, cookies, status, onResponse callbacks) go to
|
|
146
|
+
* a throwaway context (whose status gates the write, above) and are
|
|
147
|
+
* dropped: a layout above the cache() boundary is outside the header
|
|
148
|
+
* guard and an error boundary sets a status, so they would otherwise
|
|
149
|
+
* reach the live response.
|
|
150
|
+
* runWithRequestContext also re-establishes the request ALS, which a waitUntil
|
|
151
|
+
* task on workerd loses; the DSL store is a different ALS (build context).
|
|
152
|
+
*/
|
|
153
|
+
export async function rerenderAndCacheRoute<TEnv>(
|
|
154
|
+
ctx: MatchContext<TEnv>,
|
|
155
|
+
requestCtx: RequestContext<TEnv>,
|
|
156
|
+
cacheScope: CacheScope,
|
|
157
|
+
routerCtx: RouterContext<TEnv>,
|
|
158
|
+
): Promise<number> {
|
|
159
|
+
const handleStore = routerCtx.createHandleStore();
|
|
160
|
+
// Response writes land in a throwaway context nothing merges or drains. Its
|
|
161
|
+
// mutators are closures over its own stub, and they keep the same guards.
|
|
162
|
+
const sink = createRequestContext({
|
|
163
|
+
env: ctx.env,
|
|
164
|
+
request: ctx.request,
|
|
165
|
+
url: ctx.url,
|
|
166
|
+
variables: {},
|
|
167
|
+
themeConfig: requestCtx._themeConfig,
|
|
168
|
+
});
|
|
169
|
+
const renderCtx: RequestContext<TEnv> = Object.assign(
|
|
170
|
+
Object.create(requestCtx, {
|
|
171
|
+
res: Object.getOwnPropertyDescriptor(sink, "res")!,
|
|
172
|
+
}),
|
|
173
|
+
{
|
|
174
|
+
_handleStore: handleStore,
|
|
175
|
+
_transitionWhen: [],
|
|
176
|
+
_metricsStore: undefined,
|
|
177
|
+
_onResponseCallbacks: [],
|
|
178
|
+
header: sink.header,
|
|
179
|
+
setCookie: sink.setCookie,
|
|
180
|
+
deleteCookie: sink.deleteCookie,
|
|
181
|
+
setStatus: sink.setStatus,
|
|
182
|
+
_setStatus: sink._setStatus,
|
|
183
|
+
setTheme: sink.setTheme,
|
|
184
|
+
_rotateStateCookie: sink._rotateStateCookie,
|
|
185
|
+
_setKeepCacheDirective: sink._setKeepCacheDirective,
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
const store = Object.assign(Object.create(ctx.Store), { metrics: undefined });
|
|
189
|
+
const runInStore = <T>(fn: () => T): T =>
|
|
190
|
+
getContext().runWithStore(
|
|
191
|
+
store,
|
|
192
|
+
store.namespace || "#router",
|
|
193
|
+
store.parent,
|
|
194
|
+
fn,
|
|
195
|
+
);
|
|
196
|
+
return runWithRequestContext(renderCtx, async () => {
|
|
197
|
+
const handlerContext = routerCtx.createHandlerContext(
|
|
198
|
+
ctx.matched.params,
|
|
199
|
+
ctx.request,
|
|
200
|
+
ctx.url.searchParams,
|
|
201
|
+
ctx.pathname,
|
|
202
|
+
ctx.url,
|
|
203
|
+
ctx.env,
|
|
204
|
+
ctx.routeMap,
|
|
205
|
+
ctx.matched.routeKey,
|
|
206
|
+
ctx.matched.responseType,
|
|
207
|
+
ctx.matched.pt === true,
|
|
208
|
+
);
|
|
209
|
+
const loaderPromises = new Map<string, Promise<any>>();
|
|
210
|
+
routerCtx.setupLoaderAccess(handlerContext, loaderPromises);
|
|
211
|
+
|
|
212
|
+
const segments = await runInStore(() =>
|
|
213
|
+
routerCtx.resolveAllSegments(
|
|
214
|
+
ctx.entries,
|
|
215
|
+
ctx.routeKey,
|
|
216
|
+
ctx.matched.params,
|
|
217
|
+
handlerContext,
|
|
218
|
+
loaderPromises,
|
|
219
|
+
{ skipLoaders: true },
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
if (ctx.interceptResult) {
|
|
223
|
+
segments.push(
|
|
224
|
+
...(await runInStore(() =>
|
|
225
|
+
routerCtx.resolveInterceptEntry(
|
|
226
|
+
ctx.interceptResult!.intercept,
|
|
227
|
+
ctx.interceptResult!.entry,
|
|
228
|
+
ctx.matched.params,
|
|
229
|
+
handlerContext,
|
|
230
|
+
true, // belongsToRoute
|
|
231
|
+
undefined, // no revalidationContext: render fresh
|
|
232
|
+
// Skip intercept middleware: the foreground already ran it.
|
|
233
|
+
// Re-running it here would double its side effects, and a
|
|
234
|
+
// short-circuit Response would abort the write.
|
|
235
|
+
{ skipMiddleware: true },
|
|
236
|
+
),
|
|
237
|
+
)),
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
handleStore.seal();
|
|
242
|
+
// Boundaries set 500/404 through _setStatus (catchSegmentError), which
|
|
243
|
+
// lands on the sink.
|
|
244
|
+
if (sink.res.status !== 200) {
|
|
245
|
+
debugLog("backgroundRevalidation", "skipping cache for non-200 render", {
|
|
246
|
+
status: sink.res.status,
|
|
247
|
+
pathname: ctx.pathname,
|
|
248
|
+
});
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
await cacheScope.cacheRoute(
|
|
252
|
+
ctx.pathname,
|
|
253
|
+
ctx.matched.params,
|
|
254
|
+
segments,
|
|
255
|
+
ctx.isIntercept,
|
|
256
|
+
);
|
|
257
|
+
return segments.length;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
113
261
|
/**
|
|
114
262
|
* Creates background revalidation middleware
|
|
115
263
|
*
|
|
@@ -137,111 +285,31 @@ export function withBackgroundRevalidation<TEnv>(
|
|
|
137
285
|
return;
|
|
138
286
|
}
|
|
139
287
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
createHandleStore,
|
|
143
|
-
createHandlerContext,
|
|
144
|
-
setupLoaderAccess,
|
|
145
|
-
resolveAllSegments,
|
|
146
|
-
resolveInterceptEntry,
|
|
147
|
-
} = getRouterContext<TEnv>();
|
|
148
|
-
|
|
149
|
-
const requestCtx = getRequestContext();
|
|
288
|
+
const routerCtx = getRouterContext<TEnv>();
|
|
289
|
+
const requestCtx = routerCtx.getRequestContext();
|
|
150
290
|
const cacheScope = ctx.cacheScope;
|
|
151
291
|
const reqId = INTERNAL_RANGO_DEBUG
|
|
152
292
|
? getOrCreateRequestId(ctx.request)
|
|
153
293
|
: undefined;
|
|
154
294
|
|
|
155
295
|
requestCtx?.waitUntil(async () => {
|
|
156
|
-
// Prevent background metrics from polluting foreground timeline.
|
|
157
|
-
// The foreground uses its own metricsStore reference directly (via
|
|
158
|
-
// appendMetric), so nulling Store.metrics only affects track() calls
|
|
159
|
-
// inside this background Store.run() scope.
|
|
160
|
-
const savedMetrics = ctx.Store.metrics;
|
|
161
|
-
ctx.Store.metrics = undefined;
|
|
162
|
-
|
|
163
296
|
const start = performance.now();
|
|
164
297
|
debugLog("backgroundRevalidation", "revalidating stale route", {
|
|
165
298
|
pathname: ctx.pathname,
|
|
166
299
|
fullMatch: ctx.isFullMatch,
|
|
167
300
|
});
|
|
168
301
|
|
|
169
|
-
// Save and replace handleStore to avoid polluting the response stream.
|
|
170
|
-
// Restore in finally (same pattern as proactive caching in cache-store).
|
|
171
|
-
const originalHandleStore = requestCtx._handleStore;
|
|
172
|
-
requestCtx._handleStore = createHandleStore();
|
|
173
|
-
|
|
174
302
|
try {
|
|
175
|
-
const
|
|
176
|
-
ctx
|
|
177
|
-
ctx.request,
|
|
178
|
-
ctx.url.searchParams,
|
|
179
|
-
ctx.pathname,
|
|
180
|
-
ctx.url,
|
|
181
|
-
ctx.env,
|
|
182
|
-
ctx.routeMap,
|
|
183
|
-
ctx.matched.routeKey,
|
|
184
|
-
ctx.matched.responseType,
|
|
185
|
-
ctx.matched.pt === true,
|
|
186
|
-
);
|
|
187
|
-
const freshLoaderPromises = new Map<string, Promise<any>>();
|
|
188
|
-
setupLoaderAccess(freshHandlerContext, freshLoaderPromises);
|
|
189
|
-
|
|
190
|
-
// Re-establish the request-context ALS around the re-render. ctx.Store
|
|
191
|
-
// is a different ALS (DSL build context); on workerd a waitUntil task
|
|
192
|
-
// runs detached from the request's I/O context, so a handler/component
|
|
193
|
-
// that reads the ambient getRequestContext() during this background
|
|
194
|
-
// re-render would otherwise throw "called outside of a request context".
|
|
195
|
-
const freshSegments = await runWithRequestContext(
|
|
303
|
+
const count = await rerenderAndCacheRoute(
|
|
304
|
+
ctx,
|
|
196
305
|
requestCtx as RequestContext<TEnv>,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
resolveAllSegments(
|
|
200
|
-
ctx.entries,
|
|
201
|
-
ctx.routeKey,
|
|
202
|
-
ctx.matched.params,
|
|
203
|
-
freshHandlerContext,
|
|
204
|
-
freshLoaderPromises,
|
|
205
|
-
{ skipLoaders: true },
|
|
206
|
-
),
|
|
207
|
-
),
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
let freshInterceptSegments: ResolvedSegment[] = [];
|
|
211
|
-
if (ctx.interceptResult) {
|
|
212
|
-
freshInterceptSegments = await runWithRequestContext(
|
|
213
|
-
requestCtx as RequestContext<TEnv>,
|
|
214
|
-
() =>
|
|
215
|
-
ctx.Store.run(() =>
|
|
216
|
-
resolveInterceptEntry(
|
|
217
|
-
ctx.interceptResult!.intercept,
|
|
218
|
-
ctx.interceptResult!.entry,
|
|
219
|
-
ctx.matched.params,
|
|
220
|
-
freshHandlerContext,
|
|
221
|
-
true,
|
|
222
|
-
undefined,
|
|
223
|
-
// Skip intercept middleware: this is a post-response background
|
|
224
|
-
// re-render to refresh a stale cached route. The foreground
|
|
225
|
-
// already ran the middleware; re-running it would double its
|
|
226
|
-
// side effects and a short-circuit Response would abort the write.
|
|
227
|
-
{ skipMiddleware: true },
|
|
228
|
-
),
|
|
229
|
-
),
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const completeSegments = [...freshSegments, ...freshInterceptSegments];
|
|
234
|
-
requestCtx._handleStore.seal();
|
|
235
|
-
await cacheScope.cacheRoute(
|
|
236
|
-
ctx.pathname,
|
|
237
|
-
ctx.matched.params,
|
|
238
|
-
completeSegments,
|
|
239
|
-
ctx.isIntercept,
|
|
306
|
+
cacheScope,
|
|
307
|
+
routerCtx,
|
|
240
308
|
);
|
|
241
309
|
if (INTERNAL_RANGO_DEBUG) {
|
|
242
310
|
const dur = performance.now() - start;
|
|
243
311
|
console.log(
|
|
244
|
-
`[RSC Background][req:${reqId}] SWR revalidation ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${
|
|
312
|
+
`[RSC Background][req:${reqId}] SWR revalidation ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${count}`,
|
|
245
313
|
);
|
|
246
314
|
}
|
|
247
315
|
debugLog("backgroundRevalidation", "revalidation complete", {
|
|
@@ -258,9 +326,6 @@ export function withBackgroundRevalidation<TEnv>(
|
|
|
258
326
|
pathname: ctx.pathname,
|
|
259
327
|
error: String(error),
|
|
260
328
|
});
|
|
261
|
-
} finally {
|
|
262
|
-
requestCtx._handleStore = originalHandleStore;
|
|
263
|
-
ctx.Store.metrics = savedMetrics;
|
|
264
329
|
}
|
|
265
330
|
});
|
|
266
331
|
};
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
* - Request object
|
|
92
92
|
* - Action context (if POST)
|
|
93
93
|
*/
|
|
94
|
-
import type { ResolvedSegment } from "../../types.js";
|
|
94
|
+
import type { InternalHandlerContext, ResolvedSegment } from "../../types.js";
|
|
95
95
|
import type { EntryData } from "../../server/context.js";
|
|
96
96
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
97
97
|
import { getRouterContext, type RouterContext } from "../router-context.js";
|
|
@@ -103,7 +103,10 @@ import {
|
|
|
103
103
|
_getRequestContext,
|
|
104
104
|
type RequestContext,
|
|
105
105
|
} from "../../server/request-context.js";
|
|
106
|
-
import {
|
|
106
|
+
import {
|
|
107
|
+
createShellImplicitDocScope,
|
|
108
|
+
type CacheScope,
|
|
109
|
+
} from "../../cache/cache-scope.js";
|
|
107
110
|
import { prerenderStoreShortCircuits } from "../navigation-snapshot.js";
|
|
108
111
|
import { paramsEqual } from "../params-util.js";
|
|
109
112
|
|
|
@@ -160,6 +163,7 @@ async function ensurePrerenderDeps() {
|
|
|
160
163
|
// awaits before reaching this point (workerd can disrupt ALS mid-pipeline).
|
|
161
164
|
async function* resolveFreshLoadersAndYield<TEnv>(
|
|
162
165
|
ctx: MatchContext<TEnv>,
|
|
166
|
+
entries: EntryData[],
|
|
163
167
|
state: MatchPipelineState,
|
|
164
168
|
pipelineStart: number,
|
|
165
169
|
ms: MatchContext<TEnv>["metricsStore"],
|
|
@@ -171,7 +175,7 @@ async function* resolveFreshLoadersAndYield<TEnv>(
|
|
|
171
175
|
if (ctx.isFullMatch) {
|
|
172
176
|
if (resolveLoadersOnly) {
|
|
173
177
|
const loaderSegments = await ctx.Store.run(() =>
|
|
174
|
-
resolveLoadersOnly(
|
|
178
|
+
resolveLoadersOnly(entries, ctx.handlerContext),
|
|
175
179
|
);
|
|
176
180
|
state.matchedIds = state.cachedMatchedIds!;
|
|
177
181
|
for (const segment of loaderSegments) {
|
|
@@ -184,7 +188,7 @@ async function* resolveFreshLoadersAndYield<TEnv>(
|
|
|
184
188
|
if (resolveLoadersOnlyWithRevalidation) {
|
|
185
189
|
const loaderResult = await ctx.Store.run(() =>
|
|
186
190
|
resolveLoadersOnlyWithRevalidation(
|
|
187
|
-
|
|
191
|
+
entries,
|
|
188
192
|
ctx.handlerContext,
|
|
189
193
|
ctx.clientSegmentSet,
|
|
190
194
|
ctx.prevParams,
|
|
@@ -301,6 +305,7 @@ async function* yieldFromStore<TEnv>(
|
|
|
301
305
|
// Resolve loaders fresh (loaders are never pre-rendered/cached).
|
|
302
306
|
yield* resolveFreshLoadersAndYield(
|
|
303
307
|
ctx,
|
|
308
|
+
ctx.entries,
|
|
304
309
|
state,
|
|
305
310
|
pipelineStart,
|
|
306
311
|
ctx.metricsStore,
|
|
@@ -448,6 +453,8 @@ export function withCacheLookup<TEnv>(
|
|
|
448
453
|
buildEntryRevalidateMap,
|
|
449
454
|
resolveLoadersOnlyWithRevalidation,
|
|
450
455
|
resolveLoadersOnly,
|
|
456
|
+
resolveAllSegments,
|
|
457
|
+
resolveAllSegmentsWithRevalidation,
|
|
451
458
|
} = getRouterContext<TEnv>();
|
|
452
459
|
|
|
453
460
|
if (prerenderStoreShortCircuits(ctx.matched.pr, ctx.request)) {
|
|
@@ -513,13 +520,20 @@ export function withCacheLookup<TEnv>(
|
|
|
513
520
|
return;
|
|
514
521
|
}
|
|
515
522
|
|
|
523
|
+
// A record's loader-owned handle values claim their loader, so its own
|
|
524
|
+
// cache() HIT does not replay them again (restoreHandles).
|
|
525
|
+
const claimLoaderPushes = (ctx.handlerContext as InternalHandlerContext)
|
|
526
|
+
._claimLoaderPushes;
|
|
516
527
|
const explicitLookup = await ctx.cacheScope.lookupRouteDetailed(
|
|
517
528
|
ctx.pathname,
|
|
518
529
|
ctx.matched.params,
|
|
519
530
|
ctx.isIntercept,
|
|
531
|
+
claimLoaderPushes,
|
|
520
532
|
);
|
|
521
533
|
let cacheResult =
|
|
522
534
|
explicitLookup.status === "hit" ? explicitLookup.result : null;
|
|
535
|
+
// The scope whose record answered decides which entries it covers.
|
|
536
|
+
let hitScope: CacheScope = ctx.cacheScope;
|
|
523
537
|
|
|
524
538
|
// PPR navigation replay composed with a route-derived cache() scope. The
|
|
525
539
|
// explicit tier stays authoritative: its hit serves under its own
|
|
@@ -553,9 +567,13 @@ export function withCacheLookup<TEnv>(
|
|
|
553
567
|
// no-eligible-snapshot path purely for truthful status) inert: a
|
|
554
568
|
// store-less marker minting a doc scope here would resolve the APP
|
|
555
569
|
// store and read the REAL doc: partition — a cross-partition serve.
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
570
|
+
hitScope = createShellImplicitDocScope(replayMarker);
|
|
571
|
+
cacheResult = await hitScope.lookupRoute(
|
|
572
|
+
ctx.pathname,
|
|
573
|
+
ctx.matched.params,
|
|
574
|
+
ctx.isIntercept,
|
|
575
|
+
claimLoaderPushes,
|
|
576
|
+
);
|
|
559
577
|
} else if (explicitLookup.status === "bypass") {
|
|
560
578
|
// condition() refused at lookup time (the gate only pre-decides the
|
|
561
579
|
// static cache(false) case) — report cache-disabled truthfully.
|
|
@@ -578,11 +596,68 @@ export function withCacheLookup<TEnv>(
|
|
|
578
596
|
return;
|
|
579
597
|
}
|
|
580
598
|
|
|
599
|
+
// Entries above the cache() boundary are not in the record: resolve them
|
|
600
|
+
// fresh, exactly as an uncached render of this request would.
|
|
601
|
+
const boundaryIndex =
|
|
602
|
+
hitScope.boundary === undefined
|
|
603
|
+
? 0
|
|
604
|
+
: Math.max(
|
|
605
|
+
0,
|
|
606
|
+
ctx.entries.findIndex((e) => e.shortCode === hitScope.boundary),
|
|
607
|
+
);
|
|
608
|
+
let liveSegments: ResolvedSegment[] = [];
|
|
609
|
+
let liveMatchedIds: string[] = [];
|
|
610
|
+
if (boundaryIndex > 0) {
|
|
611
|
+
const liveEntries = ctx.entries.slice(0, boundaryIndex);
|
|
612
|
+
const live: { segments: ResolvedSegment[]; matchedIds: string[] } =
|
|
613
|
+
await ctx.Store.run(async () => {
|
|
614
|
+
if (!ctx.isFullMatch) {
|
|
615
|
+
return resolveAllSegmentsWithRevalidation(
|
|
616
|
+
liveEntries,
|
|
617
|
+
ctx.routeKey,
|
|
618
|
+
ctx.matched.params,
|
|
619
|
+
ctx.handlerContext,
|
|
620
|
+
ctx.clientSegmentSet,
|
|
621
|
+
ctx.prevParams,
|
|
622
|
+
ctx.request,
|
|
623
|
+
ctx.prevUrl,
|
|
624
|
+
ctx.url,
|
|
625
|
+
ctx.actionContext,
|
|
626
|
+
ctx.interceptResult,
|
|
627
|
+
ctx.localRouteName,
|
|
628
|
+
ctx.pathname,
|
|
629
|
+
ctx.stale,
|
|
630
|
+
ctx.entries,
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
// The full chain keeps a bare cache() marker, which also sits in
|
|
634
|
+
// its layout's orphan list, out of the live pass: the record
|
|
635
|
+
// supplies it and its subtree (issue #918).
|
|
636
|
+
const segments = await resolveAllSegments(
|
|
637
|
+
liveEntries,
|
|
638
|
+
ctx.routeKey,
|
|
639
|
+
ctx.matched.params,
|
|
640
|
+
ctx.handlerContext,
|
|
641
|
+
ctx.loaderPromises,
|
|
642
|
+
{ chain: ctx.entries },
|
|
643
|
+
);
|
|
644
|
+
return { segments, matchedIds: segments.map((s) => s.id) };
|
|
645
|
+
});
|
|
646
|
+
// The record supplies every covered segment.
|
|
647
|
+
liveSegments = live.segments.filter(
|
|
648
|
+
(s) => !hitScope.covers(s.id, s.namespace),
|
|
649
|
+
);
|
|
650
|
+
liveMatchedIds = live.matchedIds.filter((id) => !hitScope.covers(id));
|
|
651
|
+
}
|
|
652
|
+
|
|
581
653
|
state.cacheHit = true;
|
|
582
654
|
state.cacheSource = "runtime";
|
|
583
655
|
state.shouldRevalidate = cacheResult.shouldRevalidate;
|
|
584
656
|
state.cachedSegments = cacheResult.segments;
|
|
585
|
-
state.cachedMatchedIds =
|
|
657
|
+
state.cachedMatchedIds = [
|
|
658
|
+
...liveMatchedIds,
|
|
659
|
+
...cacheResult.segments.map((s) => s.id),
|
|
660
|
+
];
|
|
586
661
|
const pprTransitionDecisions = pipelineReqCtx?._pprTransitionDecisions;
|
|
587
662
|
|
|
588
663
|
const canCheckSegmentRevalidation =
|
|
@@ -593,6 +668,8 @@ export function withCacheLookup<TEnv>(
|
|
|
593
668
|
? buildEntryRevalidateMap(ctx.entries)
|
|
594
669
|
: undefined;
|
|
595
670
|
|
|
671
|
+
yield* liveSegments;
|
|
672
|
+
|
|
596
673
|
for (const segment of cacheResult.segments) {
|
|
597
674
|
if (!ctx.clientSegmentSet.has(segment.id)) {
|
|
598
675
|
if (isTraceActive()) {
|
|
@@ -708,13 +785,19 @@ export function withCacheLookup<TEnv>(
|
|
|
708
785
|
if (barrierReqCtx._treeHasStreaming === undefined) {
|
|
709
786
|
barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
710
787
|
}
|
|
711
|
-
barrierReqCtx._resolveRenderBarrier(
|
|
788
|
+
barrierReqCtx._resolveRenderBarrier(
|
|
789
|
+
liveSegments.length > 0
|
|
790
|
+
? [...liveSegments, ...cacheResult.segments]
|
|
791
|
+
: cacheResult.segments,
|
|
792
|
+
);
|
|
712
793
|
}
|
|
713
794
|
|
|
714
795
|
// Resolve loaders fresh (loaders are never cached). Shared with the
|
|
715
|
-
// prerender-store path via resolveFreshLoadersAndYield.
|
|
796
|
+
// prerender-store path via resolveFreshLoadersAndYield. The live pass
|
|
797
|
+
// already resolved the loaders of the entries above the boundary.
|
|
716
798
|
yield* resolveFreshLoadersAndYield(
|
|
717
799
|
ctx,
|
|
800
|
+
ctx.entries.slice(boundaryIndex),
|
|
718
801
|
state,
|
|
719
802
|
pipelineStart,
|
|
720
803
|
ms,
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
import type { ResolvedSegment } from "../../types.js";
|
|
104
104
|
import {
|
|
105
105
|
getRequestContext,
|
|
106
|
-
|
|
106
|
+
type RequestContext,
|
|
107
107
|
} from "../../server/request-context.js";
|
|
108
108
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
109
109
|
import { createShellImplicitDocScope } from "../../cache/cache-scope.js";
|
|
@@ -111,6 +111,7 @@ import { getRouterContext } from "../router-context.js";
|
|
|
111
111
|
import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
112
112
|
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
113
113
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
114
|
+
import { rerenderAndCacheRoute } from "./background-revalidation.js";
|
|
114
115
|
|
|
115
116
|
/**
|
|
116
117
|
* Creates cache store middleware
|
|
@@ -159,13 +160,9 @@ export function withCacheStore<TEnv>(
|
|
|
159
160
|
return;
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
resolveAllSegments,
|
|
166
|
-
resolveInterceptEntry,
|
|
167
|
-
createHandleStore,
|
|
168
|
-
} = getRouterContext<TEnv>();
|
|
163
|
+
// Read synchronously in the pipeline: the proactive write runs in
|
|
164
|
+
// waitUntil, where the router-context ALS may be gone.
|
|
165
|
+
const routerCtx = getRouterContext<TEnv>();
|
|
169
166
|
|
|
170
167
|
// On a fresh intercept miss the intercept slot segments flow through
|
|
171
168
|
// `source` into allSegments AND are also recorded on state.interceptSegments.
|
|
@@ -178,18 +175,21 @@ export function withCacheStore<TEnv>(
|
|
|
178
175
|
...state.interceptSegments.filter((s) => !seenSegmentIds.has(s.id)),
|
|
179
176
|
];
|
|
180
177
|
|
|
178
|
+
const cacheScope = ctx.cacheScope;
|
|
179
|
+
|
|
180
|
+
// Only the segments the entry stores count: a null layout above the
|
|
181
|
+
// boundary (the client kept it) is never written, so it needs no re-render.
|
|
181
182
|
const hasNullComponents = allSegmentsToCache.some(
|
|
182
183
|
(s) =>
|
|
183
184
|
s.component === null &&
|
|
184
185
|
s.type !== "loader" &&
|
|
185
|
-
ctx.clientSegmentSet.has(s.id)
|
|
186
|
+
ctx.clientSegmentSet.has(s.id) &&
|
|
187
|
+
cacheScope.covers(s.id, s.namespace),
|
|
186
188
|
);
|
|
187
189
|
|
|
188
190
|
const requestCtx = getRequestContext();
|
|
189
191
|
if (!requestCtx) return;
|
|
190
192
|
|
|
191
|
-
const cacheScope = ctx.cacheScope;
|
|
192
|
-
|
|
193
193
|
// Record the route's segment-DSL cache tags into the request tag union NOW,
|
|
194
194
|
// synchronously in the pipeline. The actual store write (cacheRoute) runs in
|
|
195
195
|
// requestCtx.waitUntil() below — and the proactive path re-resolves the whole
|
|
@@ -216,91 +216,21 @@ export function withCacheStore<TEnv>(
|
|
|
216
216
|
|
|
217
217
|
if (hasNullComponents) {
|
|
218
218
|
requestCtx.waitUntil(async () => {
|
|
219
|
-
const savedMetrics = ctx.Store.metrics;
|
|
220
|
-
ctx.Store.metrics = undefined;
|
|
221
|
-
|
|
222
219
|
const start = performance.now();
|
|
223
220
|
debugLog("cacheStore", "proactive caching started", {
|
|
224
221
|
pathname: ctx.pathname,
|
|
225
222
|
});
|
|
226
|
-
const originalHandleStore = requestCtx._handleStore;
|
|
227
|
-
requestCtx._handleStore = createHandleStore();
|
|
228
223
|
try {
|
|
229
|
-
const
|
|
230
|
-
ctx
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
ctx.url,
|
|
235
|
-
ctx.env,
|
|
236
|
-
ctx.routeMap,
|
|
237
|
-
ctx.matched.routeKey,
|
|
238
|
-
ctx.matched.responseType,
|
|
239
|
-
ctx.matched.pt === true,
|
|
240
|
-
);
|
|
241
|
-
const proactiveLoaderPromises = new Map<string, Promise<any>>();
|
|
242
|
-
|
|
243
|
-
setupLoaderAccess(proactiveHandlerContext, proactiveLoaderPromises);
|
|
244
|
-
|
|
245
|
-
const Store = ctx.Store;
|
|
246
|
-
// Re-establish the request-context ALS around the re-render. Store
|
|
247
|
-
// is a different ALS (DSL build context); on workerd a waitUntil
|
|
248
|
-
// task runs detached from the request's I/O context, so a handler/
|
|
249
|
-
// component that reads the ambient getRequestContext() during this
|
|
250
|
-
// background re-render would otherwise throw "called outside of a
|
|
251
|
-
// request context".
|
|
252
|
-
const freshSegments = await runWithRequestContext(requestCtx, () =>
|
|
253
|
-
Store.run(() =>
|
|
254
|
-
resolveAllSegments(
|
|
255
|
-
ctx.entries,
|
|
256
|
-
ctx.routeKey,
|
|
257
|
-
ctx.matched.params,
|
|
258
|
-
proactiveHandlerContext,
|
|
259
|
-
proactiveLoaderPromises,
|
|
260
|
-
{ skipLoaders: true },
|
|
261
|
-
),
|
|
262
|
-
),
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
let freshInterceptSegments: ResolvedSegment[] = [];
|
|
266
|
-
if (ctx.interceptResult) {
|
|
267
|
-
freshInterceptSegments = await runWithRequestContext(
|
|
268
|
-
requestCtx,
|
|
269
|
-
() =>
|
|
270
|
-
Store.run(() =>
|
|
271
|
-
resolveInterceptEntry(
|
|
272
|
-
ctx.interceptResult!.intercept,
|
|
273
|
-
ctx.interceptResult!.entry,
|
|
274
|
-
ctx.matched.params,
|
|
275
|
-
proactiveHandlerContext,
|
|
276
|
-
true, // belongsToRoute
|
|
277
|
-
// No revalidationContext = render fresh
|
|
278
|
-
undefined,
|
|
279
|
-
// Skip intercept middleware: the foreground already ran it
|
|
280
|
-
// before the response was sent. Re-running here (post-
|
|
281
|
-
// response, background) would fire side effects twice and a
|
|
282
|
-
// short-circuit Response would silently abort this write.
|
|
283
|
-
{ skipMiddleware: true },
|
|
284
|
-
),
|
|
285
|
-
),
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const completeSegments = [
|
|
290
|
-
...freshSegments,
|
|
291
|
-
...freshInterceptSegments,
|
|
292
|
-
];
|
|
293
|
-
requestCtx._handleStore.seal();
|
|
294
|
-
await cacheScope.cacheRoute(
|
|
295
|
-
ctx.pathname,
|
|
296
|
-
ctx.matched.params,
|
|
297
|
-
completeSegments,
|
|
298
|
-
ctx.isIntercept,
|
|
224
|
+
const count = await rerenderAndCacheRoute<TEnv>(
|
|
225
|
+
ctx,
|
|
226
|
+
requestCtx as RequestContext<TEnv>,
|
|
227
|
+
cacheScope,
|
|
228
|
+
routerCtx,
|
|
299
229
|
);
|
|
300
230
|
if (INTERNAL_RANGO_DEBUG) {
|
|
301
231
|
const dur = performance.now() - start;
|
|
302
232
|
console.log(
|
|
303
|
-
`[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${
|
|
233
|
+
`[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${count}`,
|
|
304
234
|
);
|
|
305
235
|
}
|
|
306
236
|
debugLog("cacheStore", "proactive caching complete", {
|
|
@@ -317,9 +247,6 @@ export function withCacheStore<TEnv>(
|
|
|
317
247
|
pathname: ctx.pathname,
|
|
318
248
|
error: String(error),
|
|
319
249
|
});
|
|
320
|
-
} finally {
|
|
321
|
-
requestCtx._handleStore = originalHandleStore;
|
|
322
|
-
ctx.Store.metrics = savedMetrics;
|
|
323
250
|
}
|
|
324
251
|
});
|
|
325
252
|
} else {
|