@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
|
@@ -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
|
|
@@ -120,12 +120,8 @@ export async function resolveLoaders<TEnv>(
|
|
|
120
120
|
// loading-disabled entries consult it), so normal requests are unchanged.
|
|
121
121
|
const emitStreaming = !loadingDisabled || isShellCaptureActive();
|
|
122
122
|
|
|
123
|
-
// PPR
|
|
124
|
-
//
|
|
125
|
-
// executed at capture (container bakes, nested pending promises hole at the
|
|
126
|
-
// consumer's Suspense) and overlay-pinned from the shell snapshot on a HIT.
|
|
127
|
-
// Renderable loading() keeps the LIVE lane (masked at capture, always
|
|
128
|
-
// fresh). Computed per entry; resolveLoaderData applies the policy.
|
|
123
|
+
// PPR bake/seed key input only; lane rule: see resolveLoaderData
|
|
124
|
+
// (loader-cache.ts). Unflagged loaders mask at capture regardless of the key.
|
|
129
125
|
const bakeLane = !entryLoadingMasksLoaders(entry.loading);
|
|
130
126
|
|
|
131
127
|
// Error context for wrapLoaderPromise: without it, a throwing DSL loader never
|
|
@@ -146,9 +142,9 @@ export async function resolveLoaders<TEnv>(
|
|
|
146
142
|
// barrier), and the loader body can call rendered() before the await below
|
|
147
143
|
// is reached.
|
|
148
144
|
//
|
|
149
|
-
// Shell-capture renders await too: flagged loaders
|
|
150
|
-
// (
|
|
151
|
-
//
|
|
145
|
+
// Shell-capture renders await too: flagged loaders execute at capture
|
|
146
|
+
// (lane rule: see resolveLoaderData, loader-cache.ts), so their handle
|
|
147
|
+
// pushes must land in the prelude.
|
|
152
148
|
const awaitedIndices = registerAwaitBeforeFlushIds(loaderEntries);
|
|
153
149
|
|
|
154
150
|
// Streaming loaders: promises kick off now, settle during RSC serialization.
|
|
@@ -276,6 +272,16 @@ export interface ResolveSegmentOptions {
|
|
|
276
272
|
* so error boundaries keep catching at request time.
|
|
277
273
|
*/
|
|
278
274
|
throwOnError?: boolean;
|
|
275
|
+
/**
|
|
276
|
+
* The matched route's full entry chain. resolveAllSegments defaults it to
|
|
277
|
+
* its `entries`; withCacheLookup passes it when it resolves only the entries
|
|
278
|
+
* above a cache() boundary. An orphan list can hold an entry of the chain: a
|
|
279
|
+
* bare cache() marker sits in its layout's layout[] (it wraps that layout's
|
|
280
|
+
* routes) and is the parent of the routes after it. For those routes it
|
|
281
|
+
* resolves once, as a chain entry, below the cache() header latch (issue
|
|
282
|
+
* #918: resolved twice on a MISS, once and discarded on a HIT).
|
|
283
|
+
*/
|
|
284
|
+
chain?: readonly EntryData[];
|
|
279
285
|
}
|
|
280
286
|
|
|
281
287
|
/**
|
|
@@ -349,6 +355,7 @@ export async function resolveSegment<TEnv>(
|
|
|
349
355
|
}
|
|
350
356
|
|
|
351
357
|
for (const orphan of entry.layout) {
|
|
358
|
+
if (options?.chain?.includes(orphan)) continue;
|
|
352
359
|
const orphanSegments = await resolveOrphanLayout(
|
|
353
360
|
orphan,
|
|
354
361
|
params,
|
|
@@ -578,6 +585,26 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
578
585
|
resolvedParallelEntries.add(parallelEntry.id);
|
|
579
586
|
}
|
|
580
587
|
|
|
588
|
+
// Routeless entries nested in this one (a layout() after a bare cache()
|
|
589
|
+
// marker, a wrapper inside a routeless wrapper) wrap the same content one
|
|
590
|
+
// level deeper. Issue #918: they were never rendered. None is in the
|
|
591
|
+
// matched chain: that would put this orphan in it, and resolveSegment
|
|
592
|
+
// skips those.
|
|
593
|
+
for (const nested of orphan.layout) {
|
|
594
|
+
segments.push(
|
|
595
|
+
...(await resolveOrphanLayout(
|
|
596
|
+
nested,
|
|
597
|
+
params,
|
|
598
|
+
context,
|
|
599
|
+
loaderPromises,
|
|
600
|
+
belongsToRoute,
|
|
601
|
+
deps,
|
|
602
|
+
options,
|
|
603
|
+
routeKey,
|
|
604
|
+
)),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
581
608
|
return segments;
|
|
582
609
|
}
|
|
583
610
|
|
|
@@ -736,6 +763,10 @@ export async function resolveAllSegments<TEnv>(
|
|
|
736
763
|
): Promise<ResolvedSegment[]> {
|
|
737
764
|
const allSegments: ResolvedSegment[] = [];
|
|
738
765
|
const seenIds = new Set<string>();
|
|
766
|
+
const segmentOptions: ResolveSegmentOptions = {
|
|
767
|
+
...options,
|
|
768
|
+
chain: options?.chain ?? entries,
|
|
769
|
+
};
|
|
739
770
|
|
|
740
771
|
// ppr routes are document-scoped cached territory: the whole chain (root
|
|
741
772
|
// layout down to the page) bakes into the shared shell, so the header-write
|
|
@@ -760,8 +791,9 @@ export async function resolveAllSegments<TEnv>(
|
|
|
760
791
|
// Set ALS flag when entering a cache() boundary so that ctx.get()
|
|
761
792
|
// can guard non-cacheable variable reads. Also latch the header-write
|
|
762
793
|
// scope (response-level side effects — headers/cookies/status).
|
|
763
|
-
// Persists for all descendant entries.
|
|
764
|
-
|
|
794
|
+
// Persists for all descendant entries. A route entry carries `cache` when
|
|
795
|
+
// cache() is among its own children (dsl-helpers.ts cache()).
|
|
796
|
+
if (entry.cache) {
|
|
765
797
|
const store = RangoContext.getStore();
|
|
766
798
|
if (store) store.insideCacheScope = true;
|
|
767
799
|
latchCachedHeaderScope("cache", routeKey);
|
|
@@ -779,7 +811,7 @@ export async function resolveAllSegments<TEnv>(
|
|
|
779
811
|
loaderPromises,
|
|
780
812
|
deps,
|
|
781
813
|
false,
|
|
782
|
-
|
|
814
|
+
segmentOptions,
|
|
783
815
|
),
|
|
784
816
|
(seg) => [seg],
|
|
785
817
|
deps,
|