@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100
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/AGENTS.md +9 -0
- package/README.md +1037 -4
- package/dist/bin/rango.js +1619 -157
- package/dist/vite/index.js +5762 -2301
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +71 -63
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +367 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +176 -8
- package/skills/layout/SKILL.md +124 -3
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +474 -47
- package/skills/middleware/SKILL.md +207 -37
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +272 -1
- package/skills/prerender/SKILL.md +467 -65
- package/skills/rango/SKILL.md +89 -21
- package/skills/response-routes/SKILL.md +152 -91
- package/skills/route/SKILL.md +305 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +333 -86
- package/skills/use-cache/SKILL.md +324 -0
- package/skills/view-transitions/SKILL.md +212 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +136 -68
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +374 -561
- package/src/browser/navigation-client.ts +228 -70
- package/src/browser/navigation-store.ts +97 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +376 -315
- package/src/browser/prefetch/cache.ts +314 -0
- package/src/browser/prefetch/fetch.ts +282 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +191 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +152 -0
- package/src/browser/react/Link.tsx +255 -71
- package/src/browser/react/NavigationProvider.tsx +152 -24
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +55 -0
- package/src/browser/react/index.ts +15 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -120
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +78 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +99 -0
- package/src/browser/react/use-router.ts +83 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +85 -99
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +246 -64
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +158 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +84 -23
- package/src/build/generate-route-types.ts +39 -828
- package/src/build/index.ts +4 -5
- package/src/build/route-trie.ts +85 -32
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -307
- package/src/cache/cf/cf-cache-store.ts +573 -21
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +6 -1
- package/src/client.tsx +118 -302
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +55 -10
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +138 -21
- package/src/index.ts +206 -51
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +159 -13
- package/src/prerender.ts +397 -29
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +231 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1134 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +483 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition.ts +1 -1431
- package/src/route-map-builder.ts +162 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +66 -9
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +418 -86
- package/src/router/intercept-resolution.ts +35 -20
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +359 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +98 -32
- package/src/router/match-api.ts +196 -261
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +441 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +415 -86
- package/src/router/match-middleware/cache-store.ts +91 -29
- package/src/router/match-middleware/intercept-resolution.ts +48 -21
- package/src/router/match-middleware/segment-resolution.ts +73 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +154 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +209 -0
- package/src/router/middleware.ts +373 -371
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +292 -52
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +152 -39
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +756 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1407 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +111 -39
- package/src/router/types.ts +17 -9
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +642 -2011
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +864 -1114
- package/src/rsc/helpers.ts +181 -19
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +395 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +360 -0
- package/src/rsc/rsc-rendering.ts +256 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +360 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +52 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +187 -38
- package/src/server/context.ts +333 -59
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +603 -109
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +107 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +764 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +209 -0
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +167 -0
- package/src/types.ts +1 -1757
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +108 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1282
- package/src/use-loader.tsx +161 -81
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +376 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +486 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -2063
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +98 -0
- package/src/vite/plugins/client-ref-dedup.ts +131 -0
- package/src/vite/plugins/client-ref-hashing.ts +117 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +816 -0
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +336 -0
- package/src/vite/plugins/version-injector.ts +109 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +497 -0
- package/src/vite/router-discovery.ts +1423 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +161 -0
- package/src/vite/utils/prerender-utils.ts +222 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/vite/expose-prerender-handler-id.ts +0 -429
- package/src/vite/package-resolution.ts +0 -125
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -5,7 +5,24 @@ import type {
|
|
|
5
5
|
RscPayload,
|
|
6
6
|
RscBrowserDependencies,
|
|
7
7
|
} from "./types.js";
|
|
8
|
-
import { NetworkError, isNetworkError } from "../errors.js";
|
|
8
|
+
import { NetworkError, ServerRedirect, isNetworkError } from "../errors.js";
|
|
9
|
+
import {
|
|
10
|
+
browserDebugLog,
|
|
11
|
+
isBrowserDebugEnabled,
|
|
12
|
+
startBrowserTransaction,
|
|
13
|
+
} from "./logging.js";
|
|
14
|
+
import { getRangoState } from "./rango-state.js";
|
|
15
|
+
import {
|
|
16
|
+
extractRscHeaderUrl,
|
|
17
|
+
emptyResponse,
|
|
18
|
+
teeWithCompletion,
|
|
19
|
+
} from "./response-adapter.js";
|
|
20
|
+
import {
|
|
21
|
+
buildPrefetchKey,
|
|
22
|
+
buildSourceKey,
|
|
23
|
+
consumeInflightPrefetch,
|
|
24
|
+
consumePrefetch,
|
|
25
|
+
} from "./prefetch/cache.js";
|
|
9
26
|
|
|
10
27
|
/**
|
|
11
28
|
* Create a navigation client for fetching RSC payloads
|
|
@@ -13,32 +30,25 @@ import { NetworkError, isNetworkError } from "../errors.js";
|
|
|
13
30
|
* The client handles building URLs with RSC parameters and
|
|
14
31
|
* deserializing the response using the RSC runtime.
|
|
15
32
|
*
|
|
33
|
+
* Checks the in-memory prefetch cache before making a network request.
|
|
34
|
+
* Tries the source-scoped key first (populated when the server tagged
|
|
35
|
+
* the response as source-sensitive via `X-RSC-Prefetch-Scope: source`)
|
|
36
|
+
* and falls back to the Rango-state-keyed wildcard slot used for the
|
|
37
|
+
* common source-agnostic case.
|
|
38
|
+
*
|
|
16
39
|
* @param deps - RSC browser dependencies (createFromFetch)
|
|
17
40
|
* @returns NavigationClient instance
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* import { createFromFetch } from "@vitejs/plugin-rsc/browser";
|
|
22
|
-
*
|
|
23
|
-
* const client = createNavigationClient({ createFromFetch });
|
|
24
|
-
*
|
|
25
|
-
* const payload = await client.fetchPartial({
|
|
26
|
-
* targetUrl: "/shop/products",
|
|
27
|
-
* segmentIds: ["root", "shop"],
|
|
28
|
-
* previousUrl: "/",
|
|
29
|
-
* });
|
|
30
|
-
* ```
|
|
31
41
|
*/
|
|
32
42
|
export function createNavigationClient(
|
|
33
43
|
deps: Pick<RscBrowserDependencies, "createFromFetch">,
|
|
34
44
|
): NavigationClient {
|
|
35
|
-
|
|
36
45
|
return {
|
|
37
46
|
/**
|
|
38
47
|
* Fetch a partial RSC payload for navigation
|
|
39
48
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
49
|
+
* First checks the in-memory prefetch cache for a matching entry.
|
|
50
|
+
* If found, uses the cached response instantly. Otherwise sends
|
|
51
|
+
* current segment IDs to the server for diff-based rendering.
|
|
42
52
|
*
|
|
43
53
|
* @param options - Fetch options
|
|
44
54
|
* @returns RSC payload with segments and metadata, plus stream completion promise
|
|
@@ -54,18 +64,25 @@ export function createNavigationClient(
|
|
|
54
64
|
staleRevalidation,
|
|
55
65
|
interceptSourceUrl,
|
|
56
66
|
version,
|
|
67
|
+
routerId,
|
|
57
68
|
hmr,
|
|
58
69
|
} = options;
|
|
59
70
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
71
|
+
const debugEnabled = isBrowserDebugEnabled();
|
|
72
|
+
const tx = debugEnabled
|
|
73
|
+
? startBrowserTransaction(staleRevalidation ? "revalidate" : "navigate")
|
|
74
|
+
: null;
|
|
75
|
+
if (tx) {
|
|
76
|
+
browserDebugLog(tx, "request start", {
|
|
77
|
+
from: previousUrl,
|
|
78
|
+
to: targetUrl,
|
|
79
|
+
segments: segmentIds,
|
|
80
|
+
staleRevalidation: !!staleRevalidation,
|
|
81
|
+
});
|
|
66
82
|
}
|
|
67
83
|
|
|
68
|
-
// Build fetch URL with partial rendering params
|
|
84
|
+
// Build fetch URL with partial rendering params (used for both
|
|
85
|
+
// cache key lookup and actual fetch if cache misses)
|
|
69
86
|
const fetchUrl = new URL(targetUrl, window.location.origin);
|
|
70
87
|
fetchUrl.searchParams.set("_rsc_partial", "true");
|
|
71
88
|
fetchUrl.searchParams.set("_rsc_segments", segmentIds.join(","));
|
|
@@ -75,76 +92,217 @@ export function createNavigationClient(
|
|
|
75
92
|
if (version) {
|
|
76
93
|
fetchUrl.searchParams.set("_rsc_v", version);
|
|
77
94
|
}
|
|
95
|
+
if (routerId) {
|
|
96
|
+
fetchUrl.searchParams.set("_rsc_rid", routerId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check completed in-memory prefetch cache before making a network
|
|
100
|
+
// request. Try the source-scoped key first (populated when the server
|
|
101
|
+
// tagged the prefetch response as source-sensitive, e.g. intercepts,
|
|
102
|
+
// or when a Link opted in with `prefetchKey=":source"`), then fall
|
|
103
|
+
// back to the wildcard slot shared across source pages.
|
|
104
|
+
// Both keys embed the Rango state, so state rotation (deploy or
|
|
105
|
+
// server-action invalidation) auto-invalidates both scopes.
|
|
106
|
+
// Skip cache for stale revalidation (needs fresh data), HMR (needs
|
|
107
|
+
// fresh modules), and intercept contexts (source-dependent responses).
|
|
108
|
+
const canUsePrefetch = !staleRevalidation && !hmr && !interceptSourceUrl;
|
|
109
|
+
const rangoState = getRangoState();
|
|
110
|
+
const wildcardKey = buildPrefetchKey(rangoState, fetchUrl);
|
|
111
|
+
const cacheKey = buildSourceKey(rangoState, previousUrl, fetchUrl);
|
|
78
112
|
|
|
79
|
-
|
|
113
|
+
let cachedResponse: Response | null = null;
|
|
114
|
+
let hitKey: string | null = null;
|
|
115
|
+
if (canUsePrefetch) {
|
|
116
|
+
cachedResponse = consumePrefetch(cacheKey);
|
|
117
|
+
if (cachedResponse) {
|
|
118
|
+
hitKey = cacheKey;
|
|
119
|
+
} else {
|
|
120
|
+
cachedResponse = consumePrefetch(wildcardKey);
|
|
121
|
+
if (cachedResponse) hitKey = wildcardKey;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
80
124
|
|
|
125
|
+
let inflightResponsePromise: Promise<Response | null> | null = null;
|
|
126
|
+
if (canUsePrefetch && !cachedResponse) {
|
|
127
|
+
inflightResponsePromise = consumeInflightPrefetch(cacheKey);
|
|
128
|
+
if (inflightResponsePromise) {
|
|
129
|
+
hitKey = cacheKey;
|
|
130
|
+
} else {
|
|
131
|
+
inflightResponsePromise = consumeInflightPrefetch(wildcardKey);
|
|
132
|
+
if (inflightResponsePromise) hitKey = wildcardKey;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
81
135
|
// Track when the stream completes
|
|
82
136
|
let resolveStreamComplete: () => void;
|
|
83
137
|
const streamComplete = new Promise<void>((resolve) => {
|
|
84
138
|
resolveStreamComplete = resolve;
|
|
85
139
|
});
|
|
86
140
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Validate RSC control headers on any response (fresh, cached, or
|
|
143
|
+
* in-flight). Handles version-mismatch reloads and server redirects.
|
|
144
|
+
* Returns the response unchanged when no control header is present.
|
|
145
|
+
*/
|
|
146
|
+
const validateRscHeaders = (
|
|
147
|
+
response: Response,
|
|
148
|
+
source: string,
|
|
149
|
+
): Response | Promise<Response> => {
|
|
150
|
+
// Version mismatch — server wants a full page reload
|
|
151
|
+
const reload = extractRscHeaderUrl(response, "X-RSC-Reload");
|
|
152
|
+
if (reload === "blocked") {
|
|
153
|
+
resolveStreamComplete();
|
|
154
|
+
return emptyResponse();
|
|
155
|
+
}
|
|
156
|
+
if (reload) {
|
|
157
|
+
if (tx) {
|
|
158
|
+
browserDebugLog(tx, `version mismatch, reloading (${source})`, {
|
|
159
|
+
reloadUrl: reload.url,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
window.location.href = reload.url;
|
|
163
|
+
// Block further processing — page is reloading
|
|
104
164
|
return new Promise<Response>(() => {});
|
|
105
165
|
}
|
|
106
166
|
|
|
107
|
-
|
|
108
|
-
|
|
167
|
+
// Server-side redirect without state: the server returned 204 with
|
|
168
|
+
// X-RSC-Redirect instead of a 3xx (which fetch would auto-follow
|
|
169
|
+
// to a URL rendering full HTML). Throw ServerRedirect so the
|
|
170
|
+
// navigation bridge catches it and re-navigates with _skipCache.
|
|
171
|
+
const redirect = extractRscHeaderUrl(response, "X-RSC-Redirect");
|
|
172
|
+
if (redirect === "blocked") {
|
|
109
173
|
resolveStreamComplete();
|
|
110
|
-
return
|
|
174
|
+
return emptyResponse();
|
|
175
|
+
}
|
|
176
|
+
if (redirect) {
|
|
177
|
+
if (tx) {
|
|
178
|
+
browserDebugLog(tx, `server redirect (${source})`, {
|
|
179
|
+
redirectUrl: redirect.url,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
resolveStreamComplete();
|
|
183
|
+
throw new ServerRedirect(redirect.url, undefined);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return response;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/** Start a fresh navigation fetch (no cache / inflight hit). */
|
|
190
|
+
const doFreshFetch = (): Promise<Response> => {
|
|
191
|
+
if (tx) {
|
|
192
|
+
browserDebugLog(tx, "fetching", {
|
|
193
|
+
path: `${fetchUrl.pathname}${fetchUrl.search}`,
|
|
194
|
+
});
|
|
111
195
|
}
|
|
112
196
|
|
|
113
|
-
|
|
114
|
-
|
|
197
|
+
return fetch(fetchUrl, {
|
|
198
|
+
headers: {
|
|
199
|
+
"X-RSC-Router-Client-Path": previousUrl,
|
|
200
|
+
"X-Rango-State": getRangoState(),
|
|
201
|
+
...(tx && { "X-RSC-Router-Request-Id": tx.requestId }),
|
|
202
|
+
...(interceptSourceUrl && {
|
|
203
|
+
"X-RSC-Router-Intercept-Source": interceptSourceUrl,
|
|
204
|
+
}),
|
|
205
|
+
...(hmr && { "X-RSC-HMR": "1" }),
|
|
206
|
+
},
|
|
207
|
+
signal,
|
|
208
|
+
}).then((response) => {
|
|
209
|
+
const validated = validateRscHeaders(response, "fetch");
|
|
210
|
+
if (validated instanceof Promise) return validated;
|
|
115
211
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
212
|
+
return teeWithCompletion(
|
|
213
|
+
validated,
|
|
214
|
+
() => {
|
|
215
|
+
if (tx) browserDebugLog(tx, "stream complete");
|
|
216
|
+
resolveStreamComplete();
|
|
217
|
+
},
|
|
218
|
+
signal,
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
};
|
|
119
222
|
|
|
120
|
-
|
|
121
|
-
const onAbort = reader.cancel.bind(reader);
|
|
122
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
223
|
+
let responsePromise: Promise<Response>;
|
|
123
224
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
225
|
+
if (cachedResponse) {
|
|
226
|
+
if (tx) {
|
|
227
|
+
browserDebugLog(tx, "prefetch cache hit", {
|
|
228
|
+
key: hitKey,
|
|
229
|
+
wildcard: hitKey === wildcardKey,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
responsePromise = Promise.resolve(cachedResponse).then((response) => {
|
|
233
|
+
const validated = validateRscHeaders(response, "prefetch cache");
|
|
234
|
+
if (validated instanceof Promise) return validated;
|
|
235
|
+
|
|
236
|
+
return teeWithCompletion(
|
|
237
|
+
validated,
|
|
238
|
+
() => {
|
|
239
|
+
if (tx) browserDebugLog(tx, "stream complete (from cache)");
|
|
240
|
+
resolveStreamComplete();
|
|
241
|
+
},
|
|
242
|
+
signal,
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
} else if (inflightResponsePromise) {
|
|
246
|
+
if (tx) {
|
|
247
|
+
browserDebugLog(tx, "reusing inflight prefetch", {
|
|
248
|
+
key: hitKey,
|
|
249
|
+
wildcard: hitKey === wildcardKey,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const adoptedViaWildcard = hitKey === wildcardKey;
|
|
253
|
+
responsePromise = inflightResponsePromise.then(async (response) => {
|
|
254
|
+
if (!response) {
|
|
255
|
+
if (tx) {
|
|
256
|
+
browserDebugLog(tx, "inflight prefetch unavailable, refetching");
|
|
128
257
|
}
|
|
129
|
-
|
|
130
|
-
signal?.removeEventListener("abort", onAbort);
|
|
131
|
-
reader.releaseLock();
|
|
132
|
-
console.log("[STREAMING] RSC stream complete");
|
|
133
|
-
resolveStreamComplete();
|
|
258
|
+
return doFreshFetch();
|
|
134
259
|
}
|
|
135
|
-
})();
|
|
136
260
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
261
|
+
// Cross-source safety: an inflight promise adopted via the
|
|
262
|
+
// wildcard key may turn out to be source-scoped (server emitted
|
|
263
|
+
// `X-RSC-Prefetch-Scope: source`), which means it was built for
|
|
264
|
+
// a different source page. Discard and refetch.
|
|
265
|
+
if (
|
|
266
|
+
adoptedViaWildcard &&
|
|
267
|
+
response.headers.get("x-rsc-prefetch-scope") === "source"
|
|
268
|
+
) {
|
|
269
|
+
if (tx) {
|
|
270
|
+
browserDebugLog(
|
|
271
|
+
tx,
|
|
272
|
+
"wildcard inflight turned out source-scoped, refetching",
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return doFreshFetch();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const validated = validateRscHeaders(response, "inflight prefetch");
|
|
279
|
+
if (validated instanceof Promise) return validated;
|
|
280
|
+
|
|
281
|
+
return teeWithCompletion(
|
|
282
|
+
validated,
|
|
283
|
+
() => {
|
|
284
|
+
if (tx) {
|
|
285
|
+
browserDebugLog(tx, "stream complete (from inflight prefetch)");
|
|
286
|
+
}
|
|
287
|
+
resolveStreamComplete();
|
|
288
|
+
},
|
|
289
|
+
signal,
|
|
290
|
+
);
|
|
142
291
|
});
|
|
143
|
-
}
|
|
292
|
+
} else {
|
|
293
|
+
responsePromise = doFreshFetch();
|
|
294
|
+
}
|
|
144
295
|
|
|
145
296
|
try {
|
|
146
|
-
// Deserialize RSC payload
|
|
147
297
|
const payload = await deps.createFromFetch<RscPayload>(responsePromise);
|
|
298
|
+
|
|
299
|
+
if (tx) {
|
|
300
|
+
browserDebugLog(tx, "response received", {
|
|
301
|
+
isPartial: payload.metadata?.isPartial,
|
|
302
|
+
matchedCount: payload.metadata?.matched?.length ?? 0,
|
|
303
|
+
diffCount: payload.metadata?.diff?.length ?? 0,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
148
306
|
return { payload, streamComplete };
|
|
149
307
|
} catch (error) {
|
|
150
308
|
// Convert network-level errors to NetworkError for proper handling
|