@rangojs/router 0.24.3 → 0.25.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 +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/types.d.ts +8 -0
- package/dist/types/client-urls/client-root.d.ts +4 -1
- package/dist/types/client-urls/client-urls.d.ts +28 -2
- package/dist/types/client-urls/navigation.d.ts +6 -8
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +20 -5
- package/dist/types/client-urls/types.d.ts +35 -17
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -6
- package/dist/types/router/router-options.d.ts +4 -7
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +19 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +10 -1
- package/dist/types/rsc/shell-capture.d.ts +7 -0
- package/dist/types/rsc/types.d.ts +5 -0
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/context.d.ts +64 -4
- package/dist/types/server/request-context.d.ts +41 -4
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +41 -4
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +4 -3
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/urls/path-helper.d.ts +5 -0
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6816 -6547
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/caching/SKILL.md +3 -1
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +195 -32
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +32 -10
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +43 -8
- package/skills/prerender/SKILL.md +1 -2
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +85 -21
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +119 -98
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-reverse.ts +4 -17
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +2 -0
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/types.ts +8 -0
- package/src/cache/vercel/vercel-cache-store.ts +6 -1
- package/src/client-urls/client-root.tsx +10 -5
- package/src/client-urls/client-urls.ts +246 -166
- package/src/client-urls/navigation.ts +45 -38
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +286 -75
- package/src/client-urls/types.ts +40 -16
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +27 -7
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +6 -8
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -7
- package/src/router/router-options.ts +4 -7
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +37 -2
- package/src/router/tracing.ts +2 -2
- package/src/router.ts +2 -9
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +83 -41
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +22 -0
- package/src/rsc/shell-capture.ts +11 -0
- package/src/rsc/types.ts +5 -0
- package/src/segment-system.tsx +125 -119
- package/src/server/context.ts +96 -5
- package/src/server/handle-store.ts +3 -2
- package/src/server/request-context.ts +42 -3
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/dispatch.ts +4 -5
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +77 -4
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +4 -3
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/urls/path-helper.ts +16 -3
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
|
@@ -4,6 +4,8 @@ import type { RouteMatchResult } from "./pattern-matching.js";
|
|
|
4
4
|
|
|
5
5
|
export interface NavigationSnapshot {
|
|
6
6
|
prevUrl: URL;
|
|
7
|
+
/** The visitor's URL of the page left, when `prevUrl` is its rewrite target. */
|
|
8
|
+
prevVisitorUrl: URL | null;
|
|
7
9
|
prevParams: Record<string, string>;
|
|
8
10
|
prevMatch: RouteMatchResult | null;
|
|
9
11
|
|
|
@@ -95,6 +97,35 @@ export function getInterceptSourceHeader(request: Request): string | null {
|
|
|
95
97
|
return requestHeaders(request).get("X-RSC-Router-Intercept-Source");
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
/**
|
|
101
|
+
* The URL the page left rendered, when middleware rewrote it there (#1144):
|
|
102
|
+
* `_rsc_rt`, the payload's `rewriteTarget` the browser sends back while
|
|
103
|
+
* `X-RSC-Router-Client-Path` names the visitor's URL. Matching the visitor's
|
|
104
|
+
* URL would find the rewriting route. Same origin only. It names the page
|
|
105
|
+
* left, never what renders, so a forged value runs or skips no middleware.
|
|
106
|
+
*/
|
|
107
|
+
function getShownRouteUrl(url: URL): URL | null {
|
|
108
|
+
const shown = url.searchParams.get("_rsc_rt");
|
|
109
|
+
if (!shown) return null;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = new URL(shown, url.origin);
|
|
112
|
+
return parsed.origin === url.origin ? formSearch(parsed) : null;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* `url` with its query as URLSearchParams writes it, as the request's own URL
|
|
120
|
+
* has it: setting and stripping `_rsc*` params rewrote that one (`?q=a%2Cb`),
|
|
121
|
+
* while a URL the browser names keeps the address bar's (`?q=a,b`). Compared
|
|
122
|
+
* as sent, every navigation from such a page was a search change.
|
|
123
|
+
*/
|
|
124
|
+
function formSearch(url: URL): URL {
|
|
125
|
+
url.search = String(url.searchParams);
|
|
126
|
+
return url;
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
export async function resolveNavigation(
|
|
99
130
|
request: Request,
|
|
100
131
|
url: URL,
|
|
@@ -112,12 +143,15 @@ export async function resolveNavigation(
|
|
|
112
143
|
return null;
|
|
113
144
|
}
|
|
114
145
|
|
|
115
|
-
let
|
|
146
|
+
let visitor: URL;
|
|
116
147
|
try {
|
|
117
|
-
|
|
148
|
+
visitor = formSearch(new URL(previousUrl, url.origin));
|
|
118
149
|
} catch {
|
|
119
150
|
return null;
|
|
120
151
|
}
|
|
152
|
+
// An intercept's request diffs from its source, which _rsc_rt does not name.
|
|
153
|
+
const shown = interceptSourceUrl ? null : getShownRouteUrl(url);
|
|
154
|
+
const prevUrl = shown ?? visitor;
|
|
121
155
|
|
|
122
156
|
let interceptContextUrl: URL;
|
|
123
157
|
try {
|
|
@@ -154,6 +188,7 @@ export async function resolveNavigation(
|
|
|
154
188
|
|
|
155
189
|
return {
|
|
156
190
|
prevUrl,
|
|
191
|
+
prevVisitorUrl: shown && visitor,
|
|
157
192
|
prevParams,
|
|
158
193
|
prevMatch,
|
|
159
194
|
interceptContextUrl,
|
|
@@ -176,6 +211,7 @@ export function createNavigationSnapshot(
|
|
|
176
211
|
const defaultUrl = new URL("http://localhost/");
|
|
177
212
|
return {
|
|
178
213
|
prevUrl: defaultUrl,
|
|
214
|
+
prevVisitorUrl: null,
|
|
179
215
|
prevParams: {},
|
|
180
216
|
prevMatch: null,
|
|
181
217
|
interceptContextUrl: defaultUrl,
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolve the prefetch cache TTL once, at router init, into the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Cache-Control
|
|
2
|
+
* Resolve the prefetch cache TTL once, at router init, into the values the
|
|
3
|
+
* rest of the router consumes: seconds, and milliseconds for the client-side
|
|
4
|
+
* in-memory prefetch cache (shipped in payload metadata). Prefetch responses
|
|
5
|
+
* carry no Cache-Control: the browser HTTP cache would keep a prefetch whose
|
|
6
|
+
* render failed after its 200 went out (#1170).
|
|
6
7
|
*
|
|
7
8
|
* `false` disables prefetch caching (seconds 0). A non-finite input
|
|
8
9
|
* (NaN/Infinity, e.g. from an env-derived number that failed to parse) is
|
|
9
|
-
* treated as the default rather than
|
|
10
|
-
*
|
|
11
|
-
* which would silently disable caching on EVERY prefetch response. Negative
|
|
12
|
-
* finite values clamp to 0 (disabled).
|
|
10
|
+
* treated as the default rather than disabling the cache. Negative finite
|
|
11
|
+
* values clamp to 0 (disabled).
|
|
13
12
|
*
|
|
14
13
|
* Policy note: this is the prefetch-TTL policy specifically. Other finite-number
|
|
15
14
|
* guards in the codebase deliberately differ — defer.ts treats Infinity as an
|
|
@@ -17,12 +16,10 @@
|
|
|
17
16
|
* at config time. They are NOT the same guard, so don't unify them.
|
|
18
17
|
*/
|
|
19
18
|
export interface ResolvedPrefetchCacheTTL {
|
|
20
|
-
/** TTL in seconds
|
|
19
|
+
/** TTL in seconds. */
|
|
21
20
|
seconds: number;
|
|
22
21
|
/** TTL in milliseconds for the client-side in-memory prefetch cache. */
|
|
23
22
|
ms: number;
|
|
24
|
-
/** Cache-Control header value, or false when caching is disabled. */
|
|
25
|
-
cacheControl: string | false;
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
const DEFAULT_PREFETCH_CACHE_TTL_SECONDS = 300;
|
|
@@ -38,14 +35,9 @@ export function resolvePrefetchCacheTTL(
|
|
|
38
35
|
} else if (typeof rawTTL === "number" && Number.isFinite(rawTTL)) {
|
|
39
36
|
seconds = Math.max(0, Math.floor(rawTTL));
|
|
40
37
|
} else {
|
|
41
|
-
// Non-finite (NaN/Infinity): fall back to the default
|
|
42
|
-
// malformed max-age=NaN/Infinity that CDNs and browsers reject.
|
|
38
|
+
// Non-finite (NaN/Infinity): fall back to the default.
|
|
43
39
|
seconds = DEFAULT_PREFETCH_CACHE_TTL_SECONDS;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
return {
|
|
47
|
-
seconds,
|
|
48
|
-
ms: seconds * 1000,
|
|
49
|
-
cacheControl: seconds === 0 ? false : `private, max-age=${seconds}`,
|
|
50
|
-
};
|
|
42
|
+
return { seconds, ms: seconds * 1000 };
|
|
51
43
|
}
|
|
@@ -16,6 +16,7 @@ import { detectPrerenderPassthrough } from "../prerender.js";
|
|
|
16
16
|
import { isRouteRootScoped } from "../route-map-builder.js";
|
|
17
17
|
import { setupBuildUse } from "./loader-resolution.js";
|
|
18
18
|
import { findInOrphans } from "./error-handling.js";
|
|
19
|
+
import { interceptSlotId } from "./intercept-resolution.js";
|
|
19
20
|
import { loadManifest } from "./manifest.js";
|
|
20
21
|
import { traverseBack } from "./pattern-matching.js";
|
|
21
22
|
import type { RouterContext } from "./router-context.js";
|
|
@@ -487,7 +488,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
487
488
|
// artifact. Re-pinned per iteration so multiple intercepts targeting
|
|
488
489
|
// the same route each get their own id (mirrors fresh.ts parallel-slot
|
|
489
490
|
// pinning).
|
|
490
|
-
const interceptSegmentId =
|
|
491
|
+
const interceptSegmentId = interceptSlotId(parentEntry, intercept);
|
|
491
492
|
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
492
493
|
interceptSegmentId;
|
|
493
494
|
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* Evaluates whether segments should revalidate based on params, actions, and custom functions.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
ResolvedSegment,
|
|
9
|
+
HandlerContext,
|
|
10
|
+
ShouldRevalidateFn,
|
|
11
|
+
} from "../types";
|
|
8
12
|
import type { ActionContext } from "./types";
|
|
9
13
|
import {
|
|
10
14
|
debugLog,
|
|
@@ -17,6 +21,22 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
17
21
|
import { paramsEqual } from "./params-util.js";
|
|
18
22
|
import { makeIsAction } from "./is-action.js";
|
|
19
23
|
|
|
24
|
+
const REVALIDATE_ALWAYS: ShouldRevalidateFn<any, any> = () => true;
|
|
25
|
+
const REVALIDATE_NEVER: ShouldRevalidateFn<any, any> = () => false;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The predicate `revalidate(true)` / `revalidate(false)` stands for: the
|
|
29
|
+
* constant hard decision of `revalidate(() => true)` / `() => false`.
|
|
30
|
+
* Booleans are converted where they enter (the urls() helper, clientUrls()
|
|
31
|
+
* materialization, runRevalidate), so evaluation only ever sees functions.
|
|
32
|
+
*/
|
|
33
|
+
export function toShouldRevalidateFn(
|
|
34
|
+
fn: ShouldRevalidateFn<any, any> | boolean,
|
|
35
|
+
): ShouldRevalidateFn<any, any> {
|
|
36
|
+
if (typeof fn !== "boolean") return fn;
|
|
37
|
+
return fn ? REVALIDATE_ALWAYS : REVALIDATE_NEVER;
|
|
38
|
+
}
|
|
39
|
+
|
|
20
40
|
/**
|
|
21
41
|
* Options for revalidation evaluation
|
|
22
42
|
*/
|
|
@@ -85,7 +105,9 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
85
105
|
} = options;
|
|
86
106
|
const nextParams = segment.params || {};
|
|
87
107
|
const paramsChanged = !paramsEqual(nextParams, prevParams);
|
|
88
|
-
const
|
|
108
|
+
const reqCtx = _getRequestContext();
|
|
109
|
+
const searchChanged =
|
|
110
|
+
prevUrl.search !== nextUrl.search || reqCtx?._visitorMoved === true;
|
|
89
111
|
|
|
90
112
|
function pushTrace(
|
|
91
113
|
defaultVal: boolean,
|
|
@@ -197,7 +219,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
197
219
|
|
|
198
220
|
const toRouteName =
|
|
199
221
|
routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
|
|
200
|
-
const reqCtx = _getRequestContext();
|
|
201
222
|
const prevRouteKey = reqCtx?._prevRouteKey;
|
|
202
223
|
const fromRouteName =
|
|
203
224
|
prevRouteKey && !isAutoGeneratedRouteName(prevRouteKey)
|
|
@@ -25,7 +25,7 @@ import type { ResolveSegmentOptions } from "./segment-resolution.js";
|
|
|
25
25
|
* Revalidation context passed to segment resolution
|
|
26
26
|
*/
|
|
27
27
|
export interface RevalidationContext {
|
|
28
|
-
clientSegmentIds:
|
|
28
|
+
clientSegmentIds: ReadonlySet<string>;
|
|
29
29
|
prevParams: Record<string, string>;
|
|
30
30
|
request: Request;
|
|
31
31
|
prevUrl: URL;
|
|
@@ -37,7 +37,7 @@ export interface RevalidationContext {
|
|
|
37
37
|
actionResult?: any;
|
|
38
38
|
formData?: FormData;
|
|
39
39
|
};
|
|
40
|
-
stale
|
|
40
|
+
stale?: boolean;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -159,7 +159,7 @@ export interface RouterContext<TEnv = any> {
|
|
|
159
159
|
handlerContext: HandlerContext<any, TEnv>,
|
|
160
160
|
belongsToRoute: boolean,
|
|
161
161
|
revalidationContext?: RevalidationContext,
|
|
162
|
-
options?: {
|
|
162
|
+
options?: { background?: boolean },
|
|
163
163
|
) => Promise<ResolvedSegment[]>;
|
|
164
164
|
|
|
165
165
|
collectWithMarkers?: <T>(
|
|
@@ -250,23 +250,10 @@ export interface RouterContext<TEnv = any> {
|
|
|
250
250
|
resolveInterceptLoadersOnly?: (
|
|
251
251
|
intercept: InterceptEntry,
|
|
252
252
|
entry: EntryData,
|
|
253
|
-
params: Record<string, string>,
|
|
254
253
|
handlerContext: HandlerContext<any, TEnv>,
|
|
255
254
|
belongsToRoute: boolean,
|
|
256
|
-
revalidationContext:
|
|
257
|
-
|
|
258
|
-
prevParams: Record<string, string>;
|
|
259
|
-
request: Request;
|
|
260
|
-
prevUrl: URL;
|
|
261
|
-
nextUrl: URL;
|
|
262
|
-
routeKey: string;
|
|
263
|
-
actionContext?: any;
|
|
264
|
-
stale?: boolean;
|
|
265
|
-
},
|
|
266
|
-
) => Promise<{
|
|
267
|
-
loaderDataPromise: Promise<any[]> | any[];
|
|
268
|
-
loaderIds: string[];
|
|
269
|
-
} | null>;
|
|
255
|
+
revalidationContext: RevalidationContext,
|
|
256
|
+
) => Promise<ResolvedSegment[]>;
|
|
270
257
|
}
|
|
271
258
|
|
|
272
259
|
const routerContext = new AsyncLocalStorage<RouterContext<any>>();
|
|
@@ -382,13 +382,6 @@ export interface RangoInternal<
|
|
|
382
382
|
import("../cache/profile-registry.js").CacheProfile
|
|
383
383
|
>;
|
|
384
384
|
|
|
385
|
-
/**
|
|
386
|
-
* Cache-Control header value for prefetch responses.
|
|
387
|
-
* False means no caching of prefetch responses.
|
|
388
|
-
* Derived from prefetchCacheTTL.
|
|
389
|
-
*/
|
|
390
|
-
readonly prefetchCacheControl: string | false;
|
|
391
|
-
|
|
392
385
|
/**
|
|
393
386
|
* TTL in milliseconds for the client-side in-memory prefetch cache.
|
|
394
387
|
* 0 means caching is disabled.
|
|
@@ -553,15 +553,12 @@ export interface RangoOptions<TEnv = any> {
|
|
|
553
553
|
version?: string;
|
|
554
554
|
|
|
555
555
|
/**
|
|
556
|
-
* TTL (in seconds) for the in-memory prefetch cache
|
|
557
|
-
* Cache-Control header on prefetch responses.
|
|
556
|
+
* TTL (in seconds) for the client-side in-memory prefetch cache.
|
|
558
557
|
*
|
|
559
558
|
* Controls how long prefetch responses are kept in the client-side
|
|
560
|
-
* in-memory cache
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
* intercept targets gets no prefetch `Cache-Control`: whether its
|
|
564
|
-
* response is the modal or the full page depends on the source page.
|
|
559
|
+
* in-memory cache. Prefetch responses carry no `Cache-Control`, so the
|
|
560
|
+
* browser's HTTP cache does not keep them: a prefetch whose render failed
|
|
561
|
+
* after its 200 went out would otherwise answer later requests.
|
|
565
562
|
*
|
|
566
563
|
* The cache is automatically invalidated on server actions regardless
|
|
567
564
|
* of TTL, so this is primarily a staleness safety net.
|
|
@@ -38,6 +38,7 @@ import { _getRequestContext } from "../../server/request-context.js";
|
|
|
38
38
|
import { getRouterContext } from "../router-context.js";
|
|
39
39
|
import { trackStreamedHandler } from "./streamed-handler-boundary.js";
|
|
40
40
|
import { observeSegmentHandler } from "../instrument.js";
|
|
41
|
+
import { withReverseScope } from "../handler-context.js";
|
|
41
42
|
import {
|
|
42
43
|
track,
|
|
43
44
|
RangoContext,
|
|
@@ -177,6 +178,7 @@ export async function resolveLoaders<TEnv>(
|
|
|
177
178
|
bakeLane || loaderEntry.bake === true ? segmentId : null,
|
|
178
179
|
),
|
|
179
180
|
loader.$$id,
|
|
181
|
+
loaderEntry.reverseScope,
|
|
180
182
|
),
|
|
181
183
|
entry,
|
|
182
184
|
segmentId,
|
|
@@ -227,6 +229,7 @@ export async function resolveLoaders<TEnv>(
|
|
|
227
229
|
bakeLane || loaderEntry.bake === true ? segmentId : null,
|
|
228
230
|
),
|
|
229
231
|
loader.$$id,
|
|
232
|
+
loaderEntry.reverseScope,
|
|
230
233
|
),
|
|
231
234
|
entry,
|
|
232
235
|
segmentId,
|
|
@@ -646,6 +649,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
646
649
|
>;
|
|
647
650
|
|
|
648
651
|
const slotsToResolve = slotNames ?? (Object.keys(slots) as `@${string}`[]);
|
|
652
|
+
const slotContext = withReverseScope(context, parallelEntry.reverseScope);
|
|
649
653
|
|
|
650
654
|
for (const slot of slotsToResolve) {
|
|
651
655
|
// Try static lookup first — in production, handler bodies are evicted
|
|
@@ -683,7 +687,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
683
687
|
`${parentShortCode}.${slot}`,
|
|
684
688
|
`${parallelEntry.id}.${slot}`,
|
|
685
689
|
handler,
|
|
686
|
-
|
|
690
|
+
slotContext,
|
|
687
691
|
)
|
|
688
692
|
: handler;
|
|
689
693
|
if (result instanceof Promise) {
|
|
@@ -708,7 +712,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
708
712
|
`${parentShortCode}.${slot}`,
|
|
709
713
|
`${parallelEntry.id}.${slot}`,
|
|
710
714
|
handler,
|
|
711
|
-
|
|
715
|
+
slotContext,
|
|
712
716
|
)
|
|
713
717
|
: handler;
|
|
714
718
|
doneParallelHandler();
|
|
@@ -30,6 +30,7 @@ import type { SegmentResolutionDeps } from "../types.js";
|
|
|
30
30
|
import { debugLog } from "../logging.js";
|
|
31
31
|
import { tryStaticLookup } from "./static-store.js";
|
|
32
32
|
import { observeSegmentHandler } from "../instrument.js";
|
|
33
|
+
import { withReverseScope } from "../handler-context.js";
|
|
33
34
|
import type { TelemetrySink } from "../telemetry.js";
|
|
34
35
|
import { resolveSink, safeEmit, getRequestId } from "../telemetry.js";
|
|
35
36
|
|
|
@@ -176,7 +177,12 @@ export async function resolveLayoutComponent<TEnv>(
|
|
|
176
177
|
// the span so a handler that returns a Response (redirect control flow, which it
|
|
177
178
|
// rethrows) is not recorded as a span error — mirrors the route-handler sites.
|
|
178
179
|
return handleHandlerResult(
|
|
179
|
-
await observeSegmentHandler(
|
|
180
|
+
await observeSegmentHandler(
|
|
181
|
+
entry.shortCode,
|
|
182
|
+
entry.id,
|
|
183
|
+
handler,
|
|
184
|
+
withReverseScope(context, entry.reverseScope),
|
|
185
|
+
),
|
|
180
186
|
);
|
|
181
187
|
}
|
|
182
188
|
|
|
@@ -30,12 +30,14 @@ import {
|
|
|
30
30
|
getParallelSlotEntries,
|
|
31
31
|
isPprEntry,
|
|
32
32
|
type EntryData,
|
|
33
|
+
type InterceptEntry,
|
|
33
34
|
type LoaderEntry,
|
|
34
35
|
} from "../../server/context.js";
|
|
35
36
|
import { requestHeaders } from "../../server/request-headers.js";
|
|
36
37
|
import type { HandlerContext, InternalHandlerContext } from "../../types.js";
|
|
37
38
|
import type { CacheScope } from "../../cache/cache-scope.js";
|
|
38
39
|
import { FILL_PARAM, PREFETCH_HEADER } from "../../prefetch-markers.js";
|
|
40
|
+
import { isInterceptSegment } from "../../browser/intercept-utils.js";
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* What a request says it is, read once when its context is created
|
|
@@ -271,40 +273,64 @@ export function resolveDeferralScope(
|
|
|
271
273
|
return scope;
|
|
272
274
|
}
|
|
273
275
|
|
|
276
|
+
const interceptScopes = new WeakMap<InterceptEntry, DeferralScope>();
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The deferral scope of a request that resolves `intercept`: its own flagged
|
|
280
|
+
* loaders, nothing of the target chain. The chain's layouts below the
|
|
281
|
+
* declaring entry render in that request and its `matched` leaves them out
|
|
282
|
+
* (match-result.ts), so a hole there would stand for a segment outside the
|
|
283
|
+
* page. A flagged loading() inside intercept() defers nothing: the slot is
|
|
284
|
+
* never a unit. Memoized on the entry (replaced with its manifest on HMR).
|
|
285
|
+
*/
|
|
286
|
+
export function resolveInterceptDeferralScope(
|
|
287
|
+
intercept: InterceptEntry,
|
|
288
|
+
): DeferralScope {
|
|
289
|
+
const cached = interceptScopes.get(intercept);
|
|
290
|
+
if (cached) return cached;
|
|
291
|
+
const flagged = intercept.loader.filter((l) => l.prefetch === false);
|
|
292
|
+
const scope: DeferralScope =
|
|
293
|
+
flagged.length === 0
|
|
294
|
+
? EMPTY_SCOPE
|
|
295
|
+
: {
|
|
296
|
+
loaders: new Map(flagged.map((l) => [l, true as const])),
|
|
297
|
+
loaderIds: new Set(flagged.map((l) => l.loader.$$id)),
|
|
298
|
+
units: new Map(),
|
|
299
|
+
};
|
|
300
|
+
interceptScopes.set(intercept, scope);
|
|
301
|
+
return scope;
|
|
302
|
+
}
|
|
303
|
+
|
|
274
304
|
/**
|
|
275
305
|
* Which half of the feature a partial request is, if either (`fill` and
|
|
276
306
|
* `prefetch` are requestKind's answer). Both halves are GETs: a request of
|
|
277
307
|
* another method that carries either marker is a normal request. A prefetch
|
|
278
308
|
* defers only on a plain GET navigation: an action re-renders what it
|
|
279
|
-
* changed,
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* runs the full match): each one states its own reason.
|
|
309
|
+
* changed, and a shell capture is never a prefetch whatever the request that
|
|
310
|
+
* scheduled it sent. The conditions overlap today (an action is a POST, a
|
|
311
|
+
* capture runs the full match): each one states its own reason.
|
|
283
312
|
*/
|
|
284
313
|
export function partialDeferralMode(request: {
|
|
285
314
|
fill: boolean;
|
|
286
315
|
prefetch: boolean;
|
|
287
316
|
method: string;
|
|
288
317
|
isAction: boolean;
|
|
289
|
-
isIntercept: boolean;
|
|
290
318
|
isShellCapture: boolean;
|
|
291
319
|
}): PrefetchDeferral["mode"] {
|
|
292
320
|
if (request.method !== "GET") return undefined;
|
|
293
321
|
if (request.fill) return "fill";
|
|
294
|
-
return request.prefetch &&
|
|
295
|
-
!request.isAction &&
|
|
296
|
-
!request.isIntercept &&
|
|
297
|
-
!request.isShellCapture
|
|
322
|
+
return request.prefetch && !request.isAction && !request.isShellCapture
|
|
298
323
|
? "prefetch"
|
|
299
324
|
: undefined;
|
|
300
325
|
}
|
|
301
326
|
|
|
302
|
-
const NOTHING_HELD: ReadonlySet<string> = new Set();
|
|
327
|
+
export const NOTHING_HELD: ReadonlySet<string> = new Set();
|
|
303
328
|
|
|
304
329
|
/**
|
|
305
330
|
* The plan for one match, or undefined when the tree declares no flag and the
|
|
306
331
|
* request is not a fill (the common case: nothing to carry). `held` is the
|
|
307
|
-
* client's segment ids as the request listed them.
|
|
332
|
+
* client's segment ids as the request listed them. `scope` is the chain's
|
|
333
|
+
* unless the request resolves an intercept (resolveInterceptDeferralScope).
|
|
308
334
|
*/
|
|
309
335
|
export function planPrefetchDeferral(
|
|
310
336
|
entries: readonly EntryData[],
|
|
@@ -312,8 +338,8 @@ export function planPrefetchDeferral(
|
|
|
312
338
|
matched: { pr?: boolean; od?: boolean },
|
|
313
339
|
cacheScope: CacheScope | null | undefined,
|
|
314
340
|
held: ReadonlySet<string> = NOTHING_HELD,
|
|
341
|
+
scope: DeferralScope = resolveDeferralScope(entries),
|
|
315
342
|
): PrefetchDeferral | undefined {
|
|
316
|
-
const scope = resolveDeferralScope(entries);
|
|
317
343
|
if (scope === EMPTY_SCOPE && mode !== "fill") return undefined;
|
|
318
344
|
|
|
319
345
|
const leaf = entries[entries.length - 1];
|
|
@@ -386,6 +412,30 @@ export function fillSends(
|
|
|
386
412
|
return isFillRequest(ctx) ? !held.has(segmentId) : undefined;
|
|
387
413
|
}
|
|
388
414
|
|
|
415
|
+
/**
|
|
416
|
+
* A fill that resolves an intercept sends only what the client lacks of the
|
|
417
|
+
* intercept: a target-chain entry (by id) or stored segment it does not hold
|
|
418
|
+
* lies outside the intercept's `matched` (match-result.ts), so the fill skips
|
|
419
|
+
* it. The intercept's own segments are never skipped.
|
|
420
|
+
*/
|
|
421
|
+
export function interceptFillSkips(
|
|
422
|
+
match: {
|
|
423
|
+
handlerContext: HandlerContext<any, any>;
|
|
424
|
+
isIntercept: boolean;
|
|
425
|
+
clientSegmentSet: ReadonlySet<string>;
|
|
426
|
+
},
|
|
427
|
+
segment: string | { id: string; namespace?: string },
|
|
428
|
+
): boolean {
|
|
429
|
+
if (!match.isIntercept) return false;
|
|
430
|
+
if (typeof segment !== "string") {
|
|
431
|
+
if (isInterceptSegment(segment)) return false;
|
|
432
|
+
segment = segment.id;
|
|
433
|
+
}
|
|
434
|
+
return (
|
|
435
|
+
fillSends(match.handlerContext, segment, match.clientSegmentSet) === true
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
389
439
|
const RECORD_OPEN: RecordUse = { read: true, write: true, refresh: true };
|
|
390
440
|
|
|
391
441
|
/** PrefetchDeferral.record, or all of it open when the match has no plan. */
|