@rangojs/router 0.16.0 → 0.18.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 +255 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -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-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +132 -19
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +54 -13
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +220 -44
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- 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/prerender/build-shell-capture.d.ts +11 -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/instrument.d.ts +8 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +9 -3
- 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 +66 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
- package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
- 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/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.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-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
- package/dist/types/rsc/shell-capture.d.ts +151 -19
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +17 -0
- package/dist/types/server/context.d.ts +139 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +182 -53
- package/dist/types/ssr/index.d.ts +24 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- 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/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- 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 +28 -1
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/pattern-types.d.ts +22 -18
- 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/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1420 -1348
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +75 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +306 -124
- package/skills/caching/SKILL.md +530 -98
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +65 -38
- package/skills/cloudflare/SKILL.md +87 -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 +75 -57
- 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 +52 -31
- package/skills/deployment-caching/SKILL.md +11 -3
- package/skills/document-cache/SKILL.md +98 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +55 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +34 -17
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +39 -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 +386 -128
- package/skills/middleware/SKILL.md +129 -26
- package/skills/migrate-nextjs/SKILL.md +156 -70
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +51 -39
- package/skills/migrate-react-router/component-migration.md +28 -7
- package/skills/migrate-react-router/data-and-actions.md +21 -6
- package/skills/migrate-react-router/route-mapping.md +19 -10
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +92 -14
- package/skills/parallel/SKILL.md +88 -47
- package/skills/ppr/SKILL.md +866 -379
- package/skills/prerender/SKILL.md +210 -108
- package/skills/rango/SKILL.md +115 -65
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +170 -60
- package/skills/router-setup/SKILL.md +420 -240
- package/skills/scripts/SKILL.md +39 -21
- package/skills/server-actions/SKILL.md +231 -59
- package/skills/shell-manifest/SKILL.md +28 -14
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +73 -33
- package/skills/testing/cache-prerender.md +60 -33
- 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 +26 -6
- 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/serve-shell-request.md +188 -0
- 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 +155 -30
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +137 -50
- 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 +224 -83
- package/skills/vercel/SKILL.md +35 -13
- package/skills/view-transitions/SKILL.md +42 -21
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- 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 +6 -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 +23 -8
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +264 -68
- package/src/cache/cache-scope.ts +419 -43
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +133 -28
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +108 -22
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +225 -44
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- 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 +73 -22
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +121 -33
- 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 +71 -28
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +141 -43
- package/src/router/loader-resolution.ts +115 -39
- package/src/router/match-api.ts +40 -26
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +110 -10
- package/src/router/match-middleware/cache-store.ts +48 -101
- package/src/router/middleware-types.ts +9 -6
- package/src/router/middleware.ts +75 -13
- package/src/router/prerender-match.ts +59 -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 +62 -21
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +407 -76
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +208 -56
- package/src/router/segment-resolution/mask-nested.ts +138 -44
- package/src/router/segment-resolution/revalidation.ts +61 -16
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +608 -225
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +73 -6
- package/src/rsc/shell-capture.ts +1068 -296
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +18 -0
- package/src/server/context.ts +300 -32
- package/src/server/cookie-store.ts +87 -61
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +297 -67
- package/src/ssr/index.tsx +136 -44
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +14 -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 +68 -6
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +28 -1
- package/src/types/loader-types.ts +8 -6
- package/src/urls/pattern-types.ts +22 -18
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
- package/src/vite/router-discovery.ts +14 -4
|
@@ -103,14 +103,19 @@
|
|
|
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
|
-
import {
|
|
109
|
+
import {
|
|
110
|
+
createShellImplicitDocScope,
|
|
111
|
+
resolveCacheTags,
|
|
112
|
+
} from "../../cache/cache-scope.js";
|
|
113
|
+
import { recordSegmentTags } from "../../cache/cache-tag.js";
|
|
110
114
|
import { getRouterContext } from "../router-context.js";
|
|
111
115
|
import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
112
116
|
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
113
117
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
118
|
+
import { rerenderAndCacheRoute } from "./background-revalidation.js";
|
|
114
119
|
|
|
115
120
|
/**
|
|
116
121
|
* Creates cache store middleware
|
|
@@ -159,13 +164,9 @@ export function withCacheStore<TEnv>(
|
|
|
159
164
|
return;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
resolveAllSegments,
|
|
166
|
-
resolveInterceptEntry,
|
|
167
|
-
createHandleStore,
|
|
168
|
-
} = getRouterContext<TEnv>();
|
|
167
|
+
// Read synchronously in the pipeline: the proactive write runs in
|
|
168
|
+
// waitUntil, where the router-context ALS may be gone.
|
|
169
|
+
const routerCtx = getRouterContext<TEnv>();
|
|
169
170
|
|
|
170
171
|
// On a fresh intercept miss the intercept slot segments flow through
|
|
171
172
|
// `source` into allSegments AND are also recorded on state.interceptSegments.
|
|
@@ -178,18 +179,21 @@ export function withCacheStore<TEnv>(
|
|
|
178
179
|
...state.interceptSegments.filter((s) => !seenSegmentIds.has(s.id)),
|
|
179
180
|
];
|
|
180
181
|
|
|
182
|
+
const cacheScope = ctx.cacheScope;
|
|
183
|
+
|
|
184
|
+
// Only the segments the entry stores count: a null layout above the
|
|
185
|
+
// boundary (the client kept it) is never written, so it needs no re-render.
|
|
181
186
|
const hasNullComponents = allSegmentsToCache.some(
|
|
182
187
|
(s) =>
|
|
183
188
|
s.component === null &&
|
|
184
189
|
s.type !== "loader" &&
|
|
185
|
-
ctx.clientSegmentSet.has(s.id)
|
|
190
|
+
ctx.clientSegmentSet.has(s.id) &&
|
|
191
|
+
cacheScope.covers(s.id, s.namespace),
|
|
186
192
|
);
|
|
187
193
|
|
|
188
194
|
const requestCtx = getRequestContext();
|
|
189
195
|
if (!requestCtx) return;
|
|
190
196
|
|
|
191
|
-
const cacheScope = ctx.cacheScope;
|
|
192
|
-
|
|
193
197
|
// Record the route's segment-DSL cache tags into the request tag union NOW,
|
|
194
198
|
// synchronously in the pipeline. The actual store write (cacheRoute) runs in
|
|
195
199
|
// requestCtx.waitUntil() below — and the proactive path re-resolves the whole
|
|
@@ -216,91 +220,21 @@ export function withCacheStore<TEnv>(
|
|
|
216
220
|
|
|
217
221
|
if (hasNullComponents) {
|
|
218
222
|
requestCtx.waitUntil(async () => {
|
|
219
|
-
const savedMetrics = ctx.Store.metrics;
|
|
220
|
-
ctx.Store.metrics = undefined;
|
|
221
|
-
|
|
222
223
|
const start = performance.now();
|
|
223
224
|
debugLog("cacheStore", "proactive caching started", {
|
|
224
225
|
pathname: ctx.pathname,
|
|
225
226
|
});
|
|
226
|
-
const originalHandleStore = requestCtx._handleStore;
|
|
227
|
-
requestCtx._handleStore = createHandleStore();
|
|
228
227
|
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,
|
|
228
|
+
const count = await rerenderAndCacheRoute<TEnv>(
|
|
229
|
+
ctx,
|
|
230
|
+
requestCtx as RequestContext<TEnv>,
|
|
231
|
+
cacheScope,
|
|
232
|
+
routerCtx,
|
|
299
233
|
);
|
|
300
234
|
if (INTERNAL_RANGO_DEBUG) {
|
|
301
235
|
const dur = performance.now() - start;
|
|
302
236
|
console.log(
|
|
303
|
-
`[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${
|
|
237
|
+
`[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${count}`,
|
|
304
238
|
);
|
|
305
239
|
}
|
|
306
240
|
debugLog("cacheStore", "proactive caching complete", {
|
|
@@ -317,9 +251,6 @@ export function withCacheStore<TEnv>(
|
|
|
317
251
|
pathname: ctx.pathname,
|
|
318
252
|
error: String(error),
|
|
319
253
|
});
|
|
320
|
-
} finally {
|
|
321
|
-
requestCtx._handleStore = originalHandleStore;
|
|
322
|
-
ctx.Store.metrics = savedMetrics;
|
|
323
254
|
}
|
|
324
255
|
});
|
|
325
256
|
} else {
|
|
@@ -389,9 +320,10 @@ export function withCacheStore<TEnv>(
|
|
|
389
320
|
* store) on the no-eligible-snapshot path, so the lookup's own refusal is
|
|
390
321
|
* still surfaced while the fallback has nothing to serve.
|
|
391
322
|
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
323
|
+
* Registered through requestCtx.onResponse, which a capture fires only after
|
|
324
|
+
* the handler pushes settle (settleCaptureRecord), and wrapped in
|
|
325
|
+
* requestCtx.waitUntil — during a capture that is the tracked-write override,
|
|
326
|
+
* so settleWrites awaits the record before the capture reads it.
|
|
395
327
|
*/
|
|
396
328
|
function recordShellCaptureDocRecord<TEnv>(
|
|
397
329
|
ctx: MatchContext<TEnv>,
|
|
@@ -409,12 +341,27 @@ function recordShellCaptureDocRecord<TEnv>(
|
|
|
409
341
|
if (!scope.allowsCache("write")) return;
|
|
410
342
|
|
|
411
343
|
const docScope = createShellImplicitDocScope(marker);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
),
|
|
344
|
+
// The route scope's cache({ tags }) describe these segments too: on its
|
|
345
|
+
// HIT they arrive with the replayed record (recordSegmentTags in
|
|
346
|
+
// lookupRouteDetailed); on a fresh render they are recorded here, so the
|
|
347
|
+
// doc record, and the shell taking its tags, carries them either way.
|
|
348
|
+
recordSegmentTags(
|
|
349
|
+
segments.map((s) => s.id),
|
|
350
|
+
resolveCacheTags(scope.config, requestCtx),
|
|
351
|
+
requestCtx,
|
|
419
352
|
);
|
|
353
|
+
// Through onResponse, like the implicit scope's own write: the capture
|
|
354
|
+
// fires these callbacks (settleCaptureRecord) only after the handler
|
|
355
|
+
// pushes settled, so the record's handle encode never races them.
|
|
356
|
+
requestCtx.onResponse((response) => {
|
|
357
|
+
requestCtx.waitUntil(() =>
|
|
358
|
+
docScope.cacheRoute(
|
|
359
|
+
ctx.pathname,
|
|
360
|
+
ctx.matched.params,
|
|
361
|
+
segments,
|
|
362
|
+
ctx.isIntercept,
|
|
363
|
+
),
|
|
364
|
+
);
|
|
365
|
+
return response;
|
|
366
|
+
});
|
|
420
367
|
}
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
DefaultRouteName,
|
|
5
5
|
DefaultVars,
|
|
6
6
|
} from "../types/global-namespace.js";
|
|
7
|
-
import type {
|
|
7
|
+
import type { GlobalReverseFunction } from "../reverse.js";
|
|
8
8
|
import type { Theme } from "../theme/types.js";
|
|
9
9
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
10
10
|
import type { RequestScope } from "../types/request-scope.js";
|
|
@@ -68,16 +68,19 @@ export interface MiddlewareContext<
|
|
|
68
68
|
|
|
69
69
|
debugPerformance(): void;
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The visitor's theme (cookie or default); undefined without theme config.
|
|
73
|
+
* Guarded like `cookies()`: a read inside a `"use cache"` function throws
|
|
74
|
+
* (#971).
|
|
75
|
+
*/
|
|
76
|
+
readonly theme?: Theme;
|
|
72
77
|
|
|
73
78
|
setTheme?: (theme: Theme) => void;
|
|
74
79
|
|
|
75
80
|
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
DefaultReverseRouteMap
|
|
80
|
-
>;
|
|
82
|
+
/** Global route names only; see {@link GlobalReverseFunction}. */
|
|
83
|
+
reverse: GlobalReverseFunction<DefaultReverseRouteMap>;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
export type MiddlewareFn<
|
package/src/router/middleware.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="vite/types/importMeta.d.ts" />
|
|
2
2
|
|
|
3
|
-
import { contextGet, contextSet } from "../context-var.js";
|
|
3
|
+
import { contextGet, contextSet, isNonCacheable } from "../context-var.js";
|
|
4
4
|
import { escapeRegExp } from "../regex-escape.js";
|
|
5
5
|
import { parsePattern as parseRoutePattern } from "./pattern-matching.js";
|
|
6
6
|
import { safeDecodeURIComponent } from "./url-params.js";
|
|
@@ -22,8 +22,17 @@ import {
|
|
|
22
22
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
23
23
|
import { appendMetric, createMetricsStore } from "./metrics.js";
|
|
24
24
|
import { observePhase, PHASES } from "./instrument.js";
|
|
25
|
-
import { stripInternalParams } from "./handler-context.js";
|
|
25
|
+
import { guardHeaderWrites, stripInternalParams } from "./handler-context.js";
|
|
26
26
|
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
27
|
+
import {
|
|
28
|
+
INSIDE_CACHE_EXEC,
|
|
29
|
+
NOCACHE_SYMBOL,
|
|
30
|
+
assertNotInsideCacheExec,
|
|
31
|
+
} from "../cache/taint.js";
|
|
32
|
+
import { getCacheExecScope } from "../cache/cache-exec-scope.js";
|
|
33
|
+
import { assertNonCacheableReadAllowed } from "../server/context.js";
|
|
34
|
+
import { assertThemeReadAllowed } from "../server/cookie-store.js";
|
|
35
|
+
import type { Theme } from "../theme/types.js";
|
|
27
36
|
|
|
28
37
|
// Re-export types consumed through this module's path.
|
|
29
38
|
export type {
|
|
@@ -33,6 +42,25 @@ export type {
|
|
|
33
42
|
MiddlewareFn,
|
|
34
43
|
} from "./middleware-types.js";
|
|
35
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Middleware ctx.theme: the visitor's theme cookie, guarded like the handler
|
|
47
|
+
* ctx.theme and cookies() (#971, #946), so a read inside a "use cache"
|
|
48
|
+
* function the middleware calls throws. Non-enumerable like the handler and
|
|
49
|
+
* request-context getters: a spread of the ctx (inside a "use cache"
|
|
50
|
+
* function, say) does not read it. One shared descriptor, so every ctx keeps
|
|
51
|
+
* the same property shape.
|
|
52
|
+
*/
|
|
53
|
+
const MIDDLEWARE_THEME_DESCRIPTOR: PropertyDescriptor = {
|
|
54
|
+
get(): Theme | undefined {
|
|
55
|
+
const reqCtx = _getRequestContext();
|
|
56
|
+
if (!reqCtx?._themeConfig) return undefined;
|
|
57
|
+
assertThemeReadAllowed(reqCtx, "ctx.theme");
|
|
58
|
+
return reqCtx._readTheme();
|
|
59
|
+
},
|
|
60
|
+
enumerable: false,
|
|
61
|
+
configurable: true,
|
|
62
|
+
};
|
|
63
|
+
|
|
36
64
|
const MIDDLEWARE_METRIC_DEPTH = 1;
|
|
37
65
|
const POST_METRIC_MIN_DURATION_MS = 0.01;
|
|
38
66
|
|
|
@@ -230,7 +258,12 @@ export function createMiddlewareContext<TEnv>(
|
|
|
230
258
|
// so snapshotting beats re-reading ALS on every access. The lazy getters
|
|
231
259
|
// below (routeName, theme, setTheme) stay lazy because those can change
|
|
232
260
|
// during `await next()`.
|
|
233
|
-
|
|
261
|
+
//
|
|
262
|
+
// Request-scoped like the handler ctx (issue #940): the NOCACHE_SYMBOL brand
|
|
263
|
+
// below makes a "use cache" function taking it key by host, _routeName,
|
|
264
|
+
// pathname, params and search; set/header/headers throw inside that
|
|
265
|
+
// function, and non-cacheable get reads run the shared guard.
|
|
266
|
+
const ctx: MiddlewareContext<TEnv> & { readonly _routeName?: string } = {
|
|
234
267
|
request,
|
|
235
268
|
url,
|
|
236
269
|
originalUrl: reqCtx?.originalUrl ?? new URL(request.url),
|
|
@@ -254,17 +287,43 @@ export function createMiddlewareContext<TEnv>(
|
|
|
254
287
|
) as MiddlewareContext<TEnv>["routeName"];
|
|
255
288
|
},
|
|
256
289
|
|
|
290
|
+
// The matched route key, auto-generated names included (the handler
|
|
291
|
+
// ctx's _routeName); undefined before routing.
|
|
292
|
+
get _routeName(): string | undefined {
|
|
293
|
+
return _getRequestContext()?._routeName;
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
// Guarded only inside a "use cache" body: the stamp covers a miss that
|
|
297
|
+
// took this ctx as an argument, the exec scope a stale refresh (which does
|
|
298
|
+
// not stamp its args, cache-runtime.ts). A write there is lost on a hit.
|
|
257
299
|
get headers(): Headers {
|
|
258
|
-
|
|
300
|
+
const headers = getResponse().headers;
|
|
301
|
+
return (INSIDE_CACHE_EXEC as symbol) in ctx ||
|
|
302
|
+
getCacheExecScope() !== undefined
|
|
303
|
+
? guardHeaderWrites(headers, () =>
|
|
304
|
+
assertNotInsideCacheExec(ctx, "headers"),
|
|
305
|
+
)
|
|
306
|
+
: headers;
|
|
259
307
|
},
|
|
260
308
|
|
|
261
|
-
get: ((keyOrVar: any) =>
|
|
262
|
-
|
|
309
|
+
get: ((keyOrVar: any) => {
|
|
310
|
+
// "use cache" only: intercept middleware runs inside a cache()
|
|
311
|
+
// subtree's render scope, and a middleware read is never cached there.
|
|
312
|
+
if (
|
|
313
|
+
isNonCacheable(variables, keyOrVar) &&
|
|
314
|
+
getCacheExecScope() !== undefined
|
|
315
|
+
) {
|
|
316
|
+
assertNonCacheableReadAllowed(keyOrVar);
|
|
317
|
+
}
|
|
318
|
+
return contextGet(variables, keyOrVar);
|
|
319
|
+
}) as MiddlewareContext<TEnv>["get"],
|
|
263
320
|
|
|
264
321
|
set: ((keyOrVar: any, value: unknown, options?: any) => {
|
|
322
|
+
assertNotInsideCacheExec(ctx, "set");
|
|
265
323
|
contextSet(variables, keyOrVar, value, options);
|
|
266
324
|
}) as MiddlewareContext<TEnv>["set"],
|
|
267
325
|
header(name: string, value: string): void {
|
|
326
|
+
assertNotInsideCacheExec(ctx, "header");
|
|
268
327
|
// Before next(): delegate to shared RequestContext stub
|
|
269
328
|
if (isPreNext()) {
|
|
270
329
|
const reqCtx = _getRequestContext();
|
|
@@ -282,10 +341,7 @@ export function createMiddlewareContext<TEnv>(
|
|
|
282
341
|
responseHolder.response.headers.set(name, value);
|
|
283
342
|
},
|
|
284
343
|
|
|
285
|
-
|
|
286
|
-
return _getRequestContext()?.theme;
|
|
287
|
-
},
|
|
288
|
-
|
|
344
|
+
// theme: MIDDLEWARE_THEME_DESCRIPTOR, defined after this literal.
|
|
289
345
|
get setTheme(): MiddlewareContext<TEnv>["setTheme"] {
|
|
290
346
|
return _getRequestContext()?.setTheme;
|
|
291
347
|
},
|
|
@@ -323,6 +379,9 @@ export function createMiddlewareContext<TEnv>(
|
|
|
323
379
|
}
|
|
324
380
|
},
|
|
325
381
|
};
|
|
382
|
+
Object.defineProperty(ctx, "theme", MIDDLEWARE_THEME_DESCRIPTOR);
|
|
383
|
+
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
384
|
+
return ctx;
|
|
326
385
|
}
|
|
327
386
|
|
|
328
387
|
/**
|
|
@@ -852,6 +911,7 @@ export function collectRouteMiddleware(
|
|
|
852
911
|
params: Record<string, string>,
|
|
853
912
|
): CollectedMiddleware[] {
|
|
854
913
|
const result: CollectedMiddleware[] = [];
|
|
914
|
+
const chain = [...entries];
|
|
855
915
|
|
|
856
916
|
const collect = (entry: MiddlewareCollectableEntry): void => {
|
|
857
917
|
// Collect entry's own middleware
|
|
@@ -860,15 +920,17 @@ export function collectRouteMiddleware(
|
|
|
860
920
|
result.push({ handler: mw, params });
|
|
861
921
|
}
|
|
862
922
|
}
|
|
863
|
-
// Collect middleware from orphan layouts (recursive)
|
|
923
|
+
// Collect middleware from orphan layouts (recursive). A bare cache()
|
|
924
|
+
// marker is both its layout's orphan and a chain entry of the routes
|
|
925
|
+
// after it; it is collected once, at its chain position (issue #918).
|
|
864
926
|
if (entry.layout && entry.layout.length > 0) {
|
|
865
927
|
for (const orphan of entry.layout) {
|
|
866
|
-
collect(orphan);
|
|
928
|
+
if (!chain.includes(orphan)) collect(orphan);
|
|
867
929
|
}
|
|
868
930
|
}
|
|
869
931
|
};
|
|
870
932
|
|
|
871
|
-
for (const entry of
|
|
933
|
+
for (const entry of chain) {
|
|
872
934
|
collect(entry);
|
|
873
935
|
}
|
|
874
936
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { detectPrerenderPassthrough } from "../prerender.js";
|
|
16
16
|
import { isRouteRootScoped } from "../route-map-builder.js";
|
|
17
17
|
import { setupBuildUse } from "./loader-resolution.js";
|
|
18
|
+
import { findInOrphans } from "./error-handling.js";
|
|
18
19
|
import { loadManifest } from "./manifest.js";
|
|
19
20
|
import { traverseBack } from "./pattern-matching.js";
|
|
20
21
|
import type { RouterContext } from "./router-context.js";
|
|
@@ -229,6 +230,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
229
230
|
_setStatus: () => {},
|
|
230
231
|
_rotateStateCookie: () => {},
|
|
231
232
|
_setKeepCacheDirective: () => {},
|
|
233
|
+
_setFreshReadsCookie: () => {},
|
|
232
234
|
use: (() => {
|
|
233
235
|
throw new Error("use() not available during pre-rendering");
|
|
234
236
|
}) as any,
|
|
@@ -238,6 +240,8 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
238
240
|
waitUntil: () => {},
|
|
239
241
|
onResponse: () => {},
|
|
240
242
|
_onResponseCallbacks: [],
|
|
243
|
+
// No theme at build time: no request cookie.
|
|
244
|
+
_readTheme: () => undefined,
|
|
241
245
|
setLocationState() {},
|
|
242
246
|
_locationState: undefined,
|
|
243
247
|
_renderBarrier: Promise.resolve(),
|
|
@@ -306,7 +310,18 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
306
310
|
// 12. Serialize segments using the cache serializer
|
|
307
311
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
308
312
|
const { encodeHandles } = await import("../cache/handle-snapshot.js");
|
|
309
|
-
|
|
313
|
+
// Flight encodes a component that throws (an async server component in
|
|
314
|
+
// the tree) as an error row and completes normally. Rethrown below like
|
|
315
|
+
// a handler throw, so the caller's policy decides instead of baking the
|
|
316
|
+
// error (#914).
|
|
317
|
+
const flightErrors: unknown[] = [];
|
|
318
|
+
const onFlightError = (error: unknown): void => {
|
|
319
|
+
flightErrors.push(error);
|
|
320
|
+
};
|
|
321
|
+
const serializedSegments = await serializeSegments(
|
|
322
|
+
nonLoaderSegments,
|
|
323
|
+
onFlightError,
|
|
324
|
+
);
|
|
310
325
|
|
|
311
326
|
// 13. Collect handle data per segment (skip segments with no handle data).
|
|
312
327
|
// Encoded through the Flight codec (not stored raw) so Promise/ReactNode
|
|
@@ -322,7 +337,9 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
322
337
|
handlesRecord[seg.id] = await resolveSegmentHandleValues(segHandles);
|
|
323
338
|
}
|
|
324
339
|
}
|
|
325
|
-
const handles = await encodeHandles(handlesRecord);
|
|
340
|
+
const handles = await encodeHandles(handlesRecord, onFlightError);
|
|
341
|
+
// Before intercept resolution, as a handler throw would be.
|
|
342
|
+
if (flightErrors.length > 0) throw flightErrors[0];
|
|
326
343
|
|
|
327
344
|
// Use the trie-level route key (e.g., "docs", "docs.article")
|
|
328
345
|
const routeName = matched.routeKey;
|
|
@@ -350,19 +367,23 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
350
367
|
intercept: InterceptEntry;
|
|
351
368
|
entry: EntryData;
|
|
352
369
|
}[] = [];
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
for (const ic of source.intercept) {
|
|
361
|
-
if (ic.routeName === matched.routeKey) {
|
|
362
|
-
foundIntercepts.push({ intercept: ic, entry: source });
|
|
363
|
-
}
|
|
370
|
+
// The entry, then its orphans (nested ones included): the same traversal
|
|
371
|
+
// findInterceptForRoute uses; the build keeps ALL matches (not just the
|
|
372
|
+
// innermost) and skips when().
|
|
373
|
+
const collect = (source: EntryData): undefined => {
|
|
374
|
+
for (const ic of source.intercept) {
|
|
375
|
+
if (ic.routeName === matched.routeKey) {
|
|
376
|
+
foundIntercepts.push({ intercept: ic, entry: source });
|
|
364
377
|
}
|
|
365
378
|
}
|
|
379
|
+
return undefined;
|
|
380
|
+
};
|
|
381
|
+
let current: EntryData | null = manifestEntry;
|
|
382
|
+
let prev: EntryData | null = null;
|
|
383
|
+
while (current) {
|
|
384
|
+
collect(current);
|
|
385
|
+
findInOrphans(current, collect, prev);
|
|
386
|
+
prev = current;
|
|
366
387
|
current = current.parent;
|
|
367
388
|
}
|
|
368
389
|
|
|
@@ -427,6 +448,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
427
448
|
await handleStore.settled;
|
|
428
449
|
interceptSegments = await serializeSegments(
|
|
429
450
|
interceptResolvedSegments,
|
|
451
|
+
onFlightError,
|
|
430
452
|
);
|
|
431
453
|
const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
|
|
432
454
|
for (const seg of interceptResolvedSegments) {
|
|
@@ -439,13 +461,18 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
439
461
|
// The intercept artifact serves main + intercept segments together, so
|
|
440
462
|
// encode the MERGED handle map here (the sinks no longer merge raw
|
|
441
463
|
// records — they store this pre-encoded string as-is).
|
|
442
|
-
interceptHandles = await encodeHandles(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
464
|
+
interceptHandles = await encodeHandles(
|
|
465
|
+
{
|
|
466
|
+
...handlesRecord,
|
|
467
|
+
...interceptHandlesRecord,
|
|
468
|
+
},
|
|
469
|
+
onFlightError,
|
|
470
|
+
);
|
|
446
471
|
}
|
|
447
472
|
}
|
|
448
473
|
|
|
474
|
+
if (flightErrors.length > 0) throw flightErrors[0];
|
|
475
|
+
|
|
449
476
|
return {
|
|
450
477
|
segments: serializedSegments,
|
|
451
478
|
handles,
|
|
@@ -506,6 +533,7 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
506
533
|
_setStatus: () => {},
|
|
507
534
|
_rotateStateCookie: () => {},
|
|
508
535
|
_setKeepCacheDirective: () => {},
|
|
536
|
+
_setFreshReadsCookie: () => {},
|
|
509
537
|
use: (() => {
|
|
510
538
|
throw new Error("use() not available during static pre-rendering");
|
|
511
539
|
}) as any,
|
|
@@ -515,6 +543,8 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
515
543
|
waitUntil: () => {},
|
|
516
544
|
onResponse: () => {},
|
|
517
545
|
_onResponseCallbacks: [],
|
|
546
|
+
// No theme at build time: no request cookie.
|
|
547
|
+
_readTheme: () => undefined,
|
|
518
548
|
setLocationState() {},
|
|
519
549
|
_locationState: undefined,
|
|
520
550
|
_renderBarrier: Promise.resolve(),
|
|
@@ -576,7 +606,12 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
576
606
|
|
|
577
607
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
578
608
|
const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
|
|
579
|
-
|
|
609
|
+
// Same error-row guard as matchForPrerender (#914).
|
|
610
|
+
const flightErrors: unknown[] = [];
|
|
611
|
+
const onFlightError = (error: unknown): void => {
|
|
612
|
+
flightErrors.push(error);
|
|
613
|
+
};
|
|
614
|
+
const [serialized] = await serializeSegments([segment], onFlightError);
|
|
580
615
|
|
|
581
616
|
// Collect handle data pushed during rendering and Flight-encode it (so
|
|
582
617
|
// Promise/ReactNode handle values survive the JSON build artifact). "" when
|
|
@@ -584,9 +619,14 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
584
619
|
const segHandles = handleStore.getDataForSegment(handlerId);
|
|
585
620
|
const handles =
|
|
586
621
|
Object.keys(segHandles).length > 0
|
|
587
|
-
? await encodeHandleValue(
|
|
622
|
+
? await encodeHandleValue(
|
|
623
|
+
await resolveSegmentHandleValues(segHandles),
|
|
624
|
+
onFlightError,
|
|
625
|
+
)
|
|
588
626
|
: "";
|
|
589
627
|
|
|
628
|
+
if (flightErrors.length > 0) throw flightErrors[0];
|
|
629
|
+
|
|
590
630
|
return { encoded: serialized.encoded, handles };
|
|
591
631
|
});
|
|
592
632
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import type { CacheScope } from "../cache/cache-scope.js";
|
|
16
16
|
import { createCacheScope } from "../cache/cache-scope.js";
|
|
17
17
|
import type { EntryData, MetricsStore } from "../server/context.js";
|
|
18
|
+
import { isPprEntry } from "../server/context.js";
|
|
18
19
|
import { loadManifest } from "./manifest.js";
|
|
19
20
|
import { collectRouteMiddleware } from "./middleware.js";
|
|
20
21
|
import type { CollectedMiddleware } from "./middleware-types.js";
|
|
@@ -222,10 +223,19 @@ function buildEntriesAndCacheScope(manifestEntry: EntryData): {
|
|
|
222
223
|
cacheScope: CacheScope | null;
|
|
223
224
|
} {
|
|
224
225
|
const entries = [...traverseBack(manifestEntry)];
|
|
226
|
+
// A cache() boundary keeps the entries above it live. A ppr route is a
|
|
227
|
+
// document-scoped cache(): its whole chain bakes into the shell, and the
|
|
228
|
+
// shell HIT tail must replay it to match the prelude, so its scope covers
|
|
229
|
+
// the whole chain.
|
|
230
|
+
const wholeChain = isPprEntry(manifestEntry);
|
|
225
231
|
let cacheScope: CacheScope | null = null;
|
|
226
232
|
for (const entry of entries) {
|
|
227
233
|
if (entry.cache) {
|
|
228
|
-
cacheScope = createCacheScope(
|
|
234
|
+
cacheScope = createCacheScope(
|
|
235
|
+
entry.cache,
|
|
236
|
+
cacheScope,
|
|
237
|
+
wholeChain ? undefined : entry.shortCode,
|
|
238
|
+
);
|
|
229
239
|
}
|
|
230
240
|
}
|
|
231
241
|
return { entries, cacheScope };
|
|
@@ -131,6 +131,8 @@ export interface RouterContext<TEnv = any> {
|
|
|
131
131
|
localRouteName: string,
|
|
132
132
|
pathname: string,
|
|
133
133
|
stale?: boolean,
|
|
134
|
+
/** The matched route's full chain when `entries` is part of it (ResolveSegmentOptions.chain). */
|
|
135
|
+
chain?: readonly EntryData[],
|
|
134
136
|
) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
|
|
135
137
|
|
|
136
138
|
// Generator-based segment resolution (for pipeline)
|
|
@@ -177,6 +177,23 @@ export interface Rango<
|
|
|
177
177
|
*/
|
|
178
178
|
readonly routeMap: TRoutes;
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Serialize the route manifest for inspection: every route and layout the
|
|
182
|
+
* router's `.routes()` / `urls` mounts register, with shortCodes, parent
|
|
183
|
+
* links, and loader/middleware/error-boundary/parallel/intercept flags.
|
|
184
|
+
*
|
|
185
|
+
* Server-only development aid. It re-evaluates each mount's `urls()` tree.
|
|
186
|
+
* Groups mounted with `include()` are lazy and are not expanded: their
|
|
187
|
+
* routes are absent from the result.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* const manifest = await router.debugManifest();
|
|
192
|
+
* console.log(manifest.totalRoutes, Object.keys(manifest.routes));
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
debugManifest(): Promise<SerializedManifest>;
|
|
196
|
+
|
|
180
197
|
/**
|
|
181
198
|
* Handle an RSC request.
|
|
182
199
|
*
|
|
@@ -187,7 +204,13 @@ export interface Rango<
|
|
|
187
204
|
* ```tsx
|
|
188
205
|
* import { router } from "./router";
|
|
189
206
|
*
|
|
190
|
-
*
|
|
207
|
+
* // Workers call fetch(request, env, ctx); `{ fetch: router.fetch }` would
|
|
208
|
+
* // drop env and ctx.
|
|
209
|
+
* export default {
|
|
210
|
+
* fetch(request, env, ctx) {
|
|
211
|
+
* return router.fetch(request, { env, ctx });
|
|
212
|
+
* },
|
|
213
|
+
* } satisfies ExportedHandler;
|
|
191
214
|
* ```
|
|
192
215
|
*
|
|
193
216
|
* @example Direct export
|
|
@@ -316,7 +339,7 @@ export interface RangoInternal<
|
|
|
316
339
|
|
|
317
340
|
/**
|
|
318
341
|
* Resolved theme configuration (null if theme not enabled)
|
|
319
|
-
* Used by NavigationProvider to include ThemeProvider and by
|
|
342
|
+
* Used by NavigationProvider to include ThemeProvider and by `Html.Meta` to render theme script
|
|
320
343
|
*/
|
|
321
344
|
readonly themeConfig: import("../theme/types.js").ResolvedThemeConfig | null;
|
|
322
345
|
|