@rangojs/router 0.15.1 → 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 +52 -0
- 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 +36 -10
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +3 -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/thenable-status.d.ts +9 -0
- 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 +884 -768
- package/package.json +4 -4
- 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 +43 -1
- 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 +75 -80
- 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 +106 -0
- package/src/prerender/build-shell-capture.ts +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +134 -42
- package/src/route-definition/helpers-types.ts +36 -10
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +2 -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/ssr-suspension-warning.ts +2 -13
- 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/ThemeProvider.tsx +14 -12
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/thenable-status.ts +16 -0
- 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 +24 -4
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
|
@@ -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 {
|
|
@@ -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";
|
|
@@ -74,10 +74,8 @@ export interface MiddlewareContext<
|
|
|
74
74
|
|
|
75
75
|
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
DefaultReverseRouteMap
|
|
80
|
-
>;
|
|
77
|
+
/** Global route names only; see {@link GlobalReverseFunction}. */
|
|
78
|
+
reverse: GlobalReverseFunction<DefaultReverseRouteMap>;
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
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,15 @@ 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";
|
|
27
34
|
|
|
28
35
|
// Re-export types consumed through this module's path.
|
|
29
36
|
export type {
|
|
@@ -230,7 +237,12 @@ export function createMiddlewareContext<TEnv>(
|
|
|
230
237
|
// so snapshotting beats re-reading ALS on every access. The lazy getters
|
|
231
238
|
// below (routeName, theme, setTheme) stay lazy because those can change
|
|
232
239
|
// during `await next()`.
|
|
233
|
-
|
|
240
|
+
//
|
|
241
|
+
// Request-scoped like the handler ctx (issue #940): the NOCACHE_SYMBOL brand
|
|
242
|
+
// below makes a "use cache" function taking it key by host, _routeName,
|
|
243
|
+
// pathname, params and search; set/header/headers throw inside that
|
|
244
|
+
// function, and non-cacheable get reads run the shared guard.
|
|
245
|
+
const ctx: MiddlewareContext<TEnv> & { readonly _routeName?: string } = {
|
|
234
246
|
request,
|
|
235
247
|
url,
|
|
236
248
|
originalUrl: reqCtx?.originalUrl ?? new URL(request.url),
|
|
@@ -254,17 +266,43 @@ export function createMiddlewareContext<TEnv>(
|
|
|
254
266
|
) as MiddlewareContext<TEnv>["routeName"];
|
|
255
267
|
},
|
|
256
268
|
|
|
269
|
+
// The matched route key, auto-generated names included (the handler
|
|
270
|
+
// ctx's _routeName); undefined before routing.
|
|
271
|
+
get _routeName(): string | undefined {
|
|
272
|
+
return _getRequestContext()?._routeName;
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// Guarded only inside a "use cache" body: the stamp covers a miss that
|
|
276
|
+
// took this ctx as an argument, the exec scope a stale refresh (which does
|
|
277
|
+
// not stamp its args, cache-runtime.ts). A write there is lost on a hit.
|
|
257
278
|
get headers(): Headers {
|
|
258
|
-
|
|
279
|
+
const headers = getResponse().headers;
|
|
280
|
+
return (INSIDE_CACHE_EXEC as symbol) in ctx ||
|
|
281
|
+
getCacheExecScope() !== undefined
|
|
282
|
+
? guardHeaderWrites(headers, () =>
|
|
283
|
+
assertNotInsideCacheExec(ctx, "headers"),
|
|
284
|
+
)
|
|
285
|
+
: headers;
|
|
259
286
|
},
|
|
260
287
|
|
|
261
|
-
get: ((keyOrVar: any) =>
|
|
262
|
-
|
|
288
|
+
get: ((keyOrVar: any) => {
|
|
289
|
+
// "use cache" only: intercept middleware runs inside a cache()
|
|
290
|
+
// subtree's render scope, and a middleware read is never cached there.
|
|
291
|
+
if (
|
|
292
|
+
isNonCacheable(variables, keyOrVar) &&
|
|
293
|
+
getCacheExecScope() !== undefined
|
|
294
|
+
) {
|
|
295
|
+
assertNonCacheableReadAllowed(keyOrVar);
|
|
296
|
+
}
|
|
297
|
+
return contextGet(variables, keyOrVar);
|
|
298
|
+
}) as MiddlewareContext<TEnv>["get"],
|
|
263
299
|
|
|
264
300
|
set: ((keyOrVar: any, value: unknown, options?: any) => {
|
|
301
|
+
assertNotInsideCacheExec(ctx, "set");
|
|
265
302
|
contextSet(variables, keyOrVar, value, options);
|
|
266
303
|
}) as MiddlewareContext<TEnv>["set"],
|
|
267
304
|
header(name: string, value: string): void {
|
|
305
|
+
assertNotInsideCacheExec(ctx, "header");
|
|
268
306
|
// Before next(): delegate to shared RequestContext stub
|
|
269
307
|
if (isPreNext()) {
|
|
270
308
|
const reqCtx = _getRequestContext();
|
|
@@ -323,6 +361,8 @@ export function createMiddlewareContext<TEnv>(
|
|
|
323
361
|
}
|
|
324
362
|
},
|
|
325
363
|
};
|
|
364
|
+
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
365
|
+
return ctx;
|
|
326
366
|
}
|
|
327
367
|
|
|
328
368
|
/**
|
|
@@ -852,6 +892,7 @@ export function collectRouteMiddleware(
|
|
|
852
892
|
params: Record<string, string>,
|
|
853
893
|
): CollectedMiddleware[] {
|
|
854
894
|
const result: CollectedMiddleware[] = [];
|
|
895
|
+
const chain = [...entries];
|
|
855
896
|
|
|
856
897
|
const collect = (entry: MiddlewareCollectableEntry): void => {
|
|
857
898
|
// Collect entry's own middleware
|
|
@@ -860,15 +901,17 @@ export function collectRouteMiddleware(
|
|
|
860
901
|
result.push({ handler: mw, params });
|
|
861
902
|
}
|
|
862
903
|
}
|
|
863
|
-
// Collect middleware from orphan layouts (recursive)
|
|
904
|
+
// Collect middleware from orphan layouts (recursive). A bare cache()
|
|
905
|
+
// marker is both its layout's orphan and a chain entry of the routes
|
|
906
|
+
// after it; it is collected once, at its chain position (issue #918).
|
|
864
907
|
if (entry.layout && entry.layout.length > 0) {
|
|
865
908
|
for (const orphan of entry.layout) {
|
|
866
|
-
collect(orphan);
|
|
909
|
+
if (!chain.includes(orphan)) collect(orphan);
|
|
867
910
|
}
|
|
868
911
|
}
|
|
869
912
|
};
|
|
870
913
|
|
|
871
|
-
for (const entry of
|
|
914
|
+
for (const entry of chain) {
|
|
872
915
|
collect(entry);
|
|
873
916
|
}
|
|
874
917
|
|
|
@@ -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";
|
|
@@ -306,7 +307,18 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
306
307
|
// 12. Serialize segments using the cache serializer
|
|
307
308
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
308
309
|
const { encodeHandles } = await import("../cache/handle-snapshot.js");
|
|
309
|
-
|
|
310
|
+
// Flight encodes a component that throws (an async server component in
|
|
311
|
+
// the tree) as an error row and completes normally. Rethrown below like
|
|
312
|
+
// a handler throw, so the caller's policy decides instead of baking the
|
|
313
|
+
// error (#914).
|
|
314
|
+
const flightErrors: unknown[] = [];
|
|
315
|
+
const onFlightError = (error: unknown): void => {
|
|
316
|
+
flightErrors.push(error);
|
|
317
|
+
};
|
|
318
|
+
const serializedSegments = await serializeSegments(
|
|
319
|
+
nonLoaderSegments,
|
|
320
|
+
onFlightError,
|
|
321
|
+
);
|
|
310
322
|
|
|
311
323
|
// 13. Collect handle data per segment (skip segments with no handle data).
|
|
312
324
|
// Encoded through the Flight codec (not stored raw) so Promise/ReactNode
|
|
@@ -322,7 +334,9 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
322
334
|
handlesRecord[seg.id] = await resolveSegmentHandleValues(segHandles);
|
|
323
335
|
}
|
|
324
336
|
}
|
|
325
|
-
const handles = await encodeHandles(handlesRecord);
|
|
337
|
+
const handles = await encodeHandles(handlesRecord, onFlightError);
|
|
338
|
+
// Before intercept resolution, as a handler throw would be.
|
|
339
|
+
if (flightErrors.length > 0) throw flightErrors[0];
|
|
326
340
|
|
|
327
341
|
// Use the trie-level route key (e.g., "docs", "docs.article")
|
|
328
342
|
const routeName = matched.routeKey;
|
|
@@ -350,19 +364,23 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
350
364
|
intercept: InterceptEntry;
|
|
351
365
|
entry: EntryData;
|
|
352
366
|
}[] = [];
|
|
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
|
-
}
|
|
367
|
+
// The entry, then its orphans (nested ones included): the same traversal
|
|
368
|
+
// findInterceptForRoute uses; the build keeps ALL matches (not just the
|
|
369
|
+
// innermost) and skips when().
|
|
370
|
+
const collect = (source: EntryData): undefined => {
|
|
371
|
+
for (const ic of source.intercept) {
|
|
372
|
+
if (ic.routeName === matched.routeKey) {
|
|
373
|
+
foundIntercepts.push({ intercept: ic, entry: source });
|
|
364
374
|
}
|
|
365
375
|
}
|
|
376
|
+
return undefined;
|
|
377
|
+
};
|
|
378
|
+
let current: EntryData | null = manifestEntry;
|
|
379
|
+
let prev: EntryData | null = null;
|
|
380
|
+
while (current) {
|
|
381
|
+
collect(current);
|
|
382
|
+
findInOrphans(current, collect, prev);
|
|
383
|
+
prev = current;
|
|
366
384
|
current = current.parent;
|
|
367
385
|
}
|
|
368
386
|
|
|
@@ -427,6 +445,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
427
445
|
await handleStore.settled;
|
|
428
446
|
interceptSegments = await serializeSegments(
|
|
429
447
|
interceptResolvedSegments,
|
|
448
|
+
onFlightError,
|
|
430
449
|
);
|
|
431
450
|
const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
|
|
432
451
|
for (const seg of interceptResolvedSegments) {
|
|
@@ -439,13 +458,18 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
439
458
|
// The intercept artifact serves main + intercept segments together, so
|
|
440
459
|
// encode the MERGED handle map here (the sinks no longer merge raw
|
|
441
460
|
// records — they store this pre-encoded string as-is).
|
|
442
|
-
interceptHandles = await encodeHandles(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
461
|
+
interceptHandles = await encodeHandles(
|
|
462
|
+
{
|
|
463
|
+
...handlesRecord,
|
|
464
|
+
...interceptHandlesRecord,
|
|
465
|
+
},
|
|
466
|
+
onFlightError,
|
|
467
|
+
);
|
|
446
468
|
}
|
|
447
469
|
}
|
|
448
470
|
|
|
471
|
+
if (flightErrors.length > 0) throw flightErrors[0];
|
|
472
|
+
|
|
449
473
|
return {
|
|
450
474
|
segments: serializedSegments,
|
|
451
475
|
handles,
|
|
@@ -576,7 +600,12 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
576
600
|
|
|
577
601
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
578
602
|
const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
|
|
579
|
-
|
|
603
|
+
// Same error-row guard as matchForPrerender (#914).
|
|
604
|
+
const flightErrors: unknown[] = [];
|
|
605
|
+
const onFlightError = (error: unknown): void => {
|
|
606
|
+
flightErrors.push(error);
|
|
607
|
+
};
|
|
608
|
+
const [serialized] = await serializeSegments([segment], onFlightError);
|
|
580
609
|
|
|
581
610
|
// Collect handle data pushed during rendering and Flight-encode it (so
|
|
582
611
|
// Promise/ReactNode handle values survive the JSON build artifact). "" when
|
|
@@ -584,9 +613,14 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
584
613
|
const segHandles = handleStore.getDataForSegment(handlerId);
|
|
585
614
|
const handles =
|
|
586
615
|
Object.keys(segHandles).length > 0
|
|
587
|
-
? await encodeHandleValue(
|
|
616
|
+
? await encodeHandleValue(
|
|
617
|
+
await resolveSegmentHandleValues(segHandles),
|
|
618
|
+
onFlightError,
|
|
619
|
+
)
|
|
588
620
|
: "";
|
|
589
621
|
|
|
622
|
+
if (flightErrors.length > 0) throw flightErrors[0];
|
|
623
|
+
|
|
590
624
|
return { encoded: serialized.encoded, handles };
|
|
591
625
|
});
|
|
592
626
|
}
|
|
@@ -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
|
|
|
@@ -164,8 +164,10 @@ export interface RangoOptions<TEnv = any> {
|
|
|
164
164
|
*
|
|
165
165
|
* Must be a client component ("use client") that accepts { children }.
|
|
166
166
|
*
|
|
167
|
-
* If not provided, a default document
|
|
168
|
-
* `<
|
|
167
|
+
* If not provided, a default document is used: `Html.Meta` and `Html.Scripts`
|
|
168
|
+
* in `<head>`, `Html.Scripts position="body"` before `{children}` and
|
|
169
|
+
* `Html.ScrollRestoration` after it. Pass your own to set `getKey` or to
|
|
170
|
+
* leave back/forward scrolling to the browser.
|
|
169
171
|
*
|
|
170
172
|
* @example
|
|
171
173
|
* ```typescript
|
|
@@ -201,7 +203,8 @@ export interface RangoOptions<TEnv = any> {
|
|
|
201
203
|
|
|
202
204
|
/**
|
|
203
205
|
* Default not-found boundary fallback used when no notFoundBoundary is defined in the route tree
|
|
204
|
-
* If not provided,
|
|
206
|
+
* If not provided, notFound() renders the `notFound` option, else a plain
|
|
207
|
+
* `<h1>Not Found</h1>` (status 404)
|
|
205
208
|
*/
|
|
206
209
|
defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler;
|
|
207
210
|
|
|
@@ -211,7 +214,7 @@ export interface RangoOptions<TEnv = any> {
|
|
|
211
214
|
* This is rendered within your document/app shell with a 404 status code.
|
|
212
215
|
* Use this for a custom 404 page that maintains your app's look and feel.
|
|
213
216
|
*
|
|
214
|
-
* If not provided, a
|
|
217
|
+
* If not provided, a plain `<h1>Not Found</h1>` is rendered.
|
|
215
218
|
*
|
|
216
219
|
* Can be a static ReactNode or a function receiving the pathname.
|
|
217
220
|
*
|
|
@@ -368,7 +371,7 @@ export interface RangoOptions<TEnv = any> {
|
|
|
368
371
|
* When provided, enables:
|
|
369
372
|
* - ctx.theme and ctx.setTheme() in route handlers
|
|
370
373
|
* - useTheme() hook for client components
|
|
371
|
-
* - FOUC prevention via inline script in
|
|
374
|
+
* - FOUC prevention via inline script in `<Html.Meta />`
|
|
372
375
|
* - Automatic ThemeProvider wrapping in NavigationProvider
|
|
373
376
|
*
|
|
374
377
|
* @example
|