@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430
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 +5 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4474 -867
- package/package.json +60 -51
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +50 -21
- 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 +167 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +89 -30
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +78 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +318 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -64
- 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 +285 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +258 -308
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +185 -73
- package/src/browser/react/NavigationProvider.tsx +51 -11
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -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 +32 -79
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +107 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +109 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +265 -0
- 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 +411 -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 +469 -0
- package/src/build/route-types/scan-filter.ts +78 -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 +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -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 +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +106 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +15 -29
- 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 +119 -29
- package/src/index.rsc.ts +153 -19
- package/src/index.ts +211 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +211 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +59 -8
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +148 -35
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -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 +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +692 -4257
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +25 -13
- package/src/server/context.ts +182 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +430 -70
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +100 -31
- package/src/static-handler.ts +114 -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 +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -1133
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -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 +510 -0
- package/src/vite/router-discovery.ts +785 -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/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -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/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- 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/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -5,7 +5,19 @@ 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 { buildPrefetchKey, consumePrefetch } from "./prefetch/cache.js";
|
|
9
21
|
|
|
10
22
|
/**
|
|
11
23
|
* Create a navigation client for fetching RSC payloads
|
|
@@ -13,21 +25,12 @@ import { NetworkError, isNetworkError } from "../errors.js";
|
|
|
13
25
|
* The client handles building URLs with RSC parameters and
|
|
14
26
|
* deserializing the response using the RSC runtime.
|
|
15
27
|
*
|
|
28
|
+
* Checks the in-memory prefetch cache before making a network request.
|
|
29
|
+
* The cache key is source-dependent (includes the previous URL) so
|
|
30
|
+
* prefetch responses match the exact diff the server would produce.
|
|
31
|
+
*
|
|
16
32
|
* @param deps - RSC browser dependencies (createFromFetch)
|
|
17
33
|
* @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
34
|
*/
|
|
32
35
|
export function createNavigationClient(
|
|
33
36
|
deps: Pick<RscBrowserDependencies, "createFromFetch">,
|
|
@@ -36,8 +39,9 @@ export function createNavigationClient(
|
|
|
36
39
|
/**
|
|
37
40
|
* Fetch a partial RSC payload for navigation
|
|
38
41
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
42
|
+
* First checks the in-memory prefetch cache for a matching entry.
|
|
43
|
+
* If found, uses the cached response instantly. Otherwise sends
|
|
44
|
+
* current segment IDs to the server for diff-based rendering.
|
|
41
45
|
*
|
|
42
46
|
* @param options - Fetch options
|
|
43
47
|
* @returns RSC payload with segments and metadata, plus stream completion promise
|
|
@@ -53,17 +57,24 @@ export function createNavigationClient(
|
|
|
53
57
|
staleRevalidation,
|
|
54
58
|
interceptSourceUrl,
|
|
55
59
|
version,
|
|
60
|
+
hmr,
|
|
56
61
|
} = options;
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
63
|
+
const debugEnabled = isBrowserDebugEnabled();
|
|
64
|
+
const tx = debugEnabled
|
|
65
|
+
? startBrowserTransaction(staleRevalidation ? "revalidate" : "navigate")
|
|
66
|
+
: null;
|
|
67
|
+
if (tx) {
|
|
68
|
+
browserDebugLog(tx, "request start", {
|
|
69
|
+
from: previousUrl,
|
|
70
|
+
to: targetUrl,
|
|
71
|
+
segments: segmentIds,
|
|
72
|
+
staleRevalidation: !!staleRevalidation,
|
|
73
|
+
});
|
|
64
74
|
}
|
|
65
75
|
|
|
66
|
-
// Build fetch URL with partial rendering params
|
|
76
|
+
// Build fetch URL with partial rendering params (used for both
|
|
77
|
+
// cache key lookup and actual fetch if cache misses)
|
|
67
78
|
const fetchUrl = new URL(targetUrl, window.location.origin);
|
|
68
79
|
fetchUrl.searchParams.set("_rsc_partial", "true");
|
|
69
80
|
fetchUrl.searchParams.set("_rsc_segments", segmentIds.join(","));
|
|
@@ -74,7 +85,16 @@ export function createNavigationClient(
|
|
|
74
85
|
fetchUrl.searchParams.set("_rsc_v", version);
|
|
75
86
|
}
|
|
76
87
|
|
|
77
|
-
|
|
88
|
+
// Check in-memory prefetch cache before making a network request.
|
|
89
|
+
// The cache key includes the source URL (previousUrl) because the
|
|
90
|
+
// server's diff response depends on the source page context.
|
|
91
|
+
// Skip cache for stale revalidation (needs fresh data), HMR (needs
|
|
92
|
+
// fresh modules), and intercept contexts (source-dependent responses).
|
|
93
|
+
const cacheKey = buildPrefetchKey(previousUrl, fetchUrl);
|
|
94
|
+
const cachedResponse =
|
|
95
|
+
!staleRevalidation && !hmr && !interceptSourceUrl
|
|
96
|
+
? consumePrefetch(cacheKey)
|
|
97
|
+
: null;
|
|
78
98
|
|
|
79
99
|
// Track when the stream completes
|
|
80
100
|
let resolveStreamComplete: () => void;
|
|
@@ -82,66 +102,99 @@ export function createNavigationClient(
|
|
|
82
102
|
resolveStreamComplete = resolve;
|
|
83
103
|
});
|
|
84
104
|
|
|
85
|
-
|
|
86
|
-
const responsePromise = fetch(fetchUrl, {
|
|
87
|
-
headers: {
|
|
88
|
-
"X-RSC-Router-Client-Path": previousUrl,
|
|
89
|
-
...(interceptSourceUrl && {
|
|
90
|
-
"X-RSC-Router-Intercept-Source": interceptSourceUrl,
|
|
91
|
-
}),
|
|
92
|
-
},
|
|
93
|
-
signal,
|
|
94
|
-
}).then((response) => {
|
|
95
|
-
// Check for version mismatch - server wants us to reload
|
|
96
|
-
const reloadUrl = response.headers.get("X-RSC-Reload");
|
|
97
|
-
if (reloadUrl) {
|
|
98
|
-
console.log(`[Browser] Version mismatch - reloading: ${reloadUrl}`);
|
|
99
|
-
window.location.href = reloadUrl;
|
|
100
|
-
// Return a never-resolving promise to prevent further processing
|
|
101
|
-
return new Promise<Response>(() => {});
|
|
102
|
-
}
|
|
105
|
+
let responsePromise: Promise<Response>;
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
if (cachedResponse) {
|
|
108
|
+
if (tx) {
|
|
109
|
+
browserDebugLog(tx, "prefetch cache hit", { key: cacheKey });
|
|
110
|
+
}
|
|
111
|
+
// Cached response body is already fully buffered (arrayBuffer),
|
|
112
|
+
// so stream completion is immediate.
|
|
113
|
+
responsePromise = Promise.resolve(cachedResponse).then((response) => {
|
|
114
|
+
return teeWithCompletion(
|
|
115
|
+
response,
|
|
116
|
+
() => {
|
|
117
|
+
if (tx) browserDebugLog(tx, "stream complete (from cache)");
|
|
118
|
+
resolveStreamComplete();
|
|
119
|
+
},
|
|
120
|
+
signal,
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
if (tx) {
|
|
125
|
+
browserDebugLog(tx, "fetching", {
|
|
126
|
+
path: `${fetchUrl.pathname}${fetchUrl.search}`,
|
|
127
|
+
});
|
|
108
128
|
}
|
|
109
129
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
130
|
+
responsePromise = fetch(fetchUrl, {
|
|
131
|
+
headers: {
|
|
132
|
+
"X-RSC-Router-Client-Path": previousUrl,
|
|
133
|
+
"X-Rango-State": getRangoState(),
|
|
134
|
+
...(tx && { "X-RSC-Router-Request-Id": tx.requestId }),
|
|
135
|
+
...(interceptSourceUrl && {
|
|
136
|
+
"X-RSC-Router-Intercept-Source": interceptSourceUrl,
|
|
137
|
+
}),
|
|
138
|
+
...(hmr && { "X-RSC-HMR": "1" }),
|
|
139
|
+
},
|
|
140
|
+
signal,
|
|
141
|
+
}).then((response) => {
|
|
142
|
+
// Check for version mismatch - server wants us to reload
|
|
143
|
+
const reload = extractRscHeaderUrl(response, "X-RSC-Reload");
|
|
144
|
+
if (reload === "blocked") {
|
|
145
|
+
resolveStreamComplete();
|
|
146
|
+
return emptyResponse();
|
|
147
|
+
}
|
|
148
|
+
if (reload) {
|
|
149
|
+
if (tx) {
|
|
150
|
+
browserDebugLog(tx, "version mismatch, reloading", {
|
|
151
|
+
reloadUrl: reload.url,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
window.location.href = reload.url;
|
|
155
|
+
return new Promise<Response>(() => {});
|
|
156
|
+
}
|
|
120
157
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
// Server-side redirect without state: the server returned 204 with
|
|
159
|
+
// X-RSC-Redirect instead of a 3xx (which fetch would auto-follow
|
|
160
|
+
// to a URL rendering full HTML). Throw ServerRedirect so the
|
|
161
|
+
// navigation bridge catches it and re-navigates with _skipCache.
|
|
162
|
+
const redirect = extractRscHeaderUrl(response, "X-RSC-Redirect");
|
|
163
|
+
if (redirect === "blocked") {
|
|
164
|
+
resolveStreamComplete();
|
|
165
|
+
return emptyResponse();
|
|
166
|
+
}
|
|
167
|
+
if (redirect) {
|
|
168
|
+
if (tx) {
|
|
169
|
+
browserDebugLog(tx, "server redirect", {
|
|
170
|
+
redirectUrl: redirect.url,
|
|
171
|
+
});
|
|
125
172
|
}
|
|
126
|
-
} finally {
|
|
127
|
-
signal?.removeEventListener("abort", onAbort);
|
|
128
|
-
reader.releaseLock();
|
|
129
|
-
console.log("[STREAMING] RSC stream complete");
|
|
130
173
|
resolveStreamComplete();
|
|
174
|
+
throw new ServerRedirect(redirect.url, undefined);
|
|
131
175
|
}
|
|
132
|
-
})();
|
|
133
176
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
177
|
+
return teeWithCompletion(
|
|
178
|
+
response,
|
|
179
|
+
() => {
|
|
180
|
+
if (tx) browserDebugLog(tx, "stream complete");
|
|
181
|
+
resolveStreamComplete();
|
|
182
|
+
},
|
|
183
|
+
signal,
|
|
184
|
+
);
|
|
139
185
|
});
|
|
140
|
-
}
|
|
186
|
+
}
|
|
141
187
|
|
|
142
188
|
try {
|
|
143
189
|
// Deserialize RSC payload
|
|
144
190
|
const payload = await deps.createFromFetch<RscPayload>(responsePromise);
|
|
191
|
+
if (tx) {
|
|
192
|
+
browserDebugLog(tx, "response received", {
|
|
193
|
+
isPartial: payload.metadata?.isPartial,
|
|
194
|
+
matchedCount: payload.metadata?.matched?.length ?? 0,
|
|
195
|
+
diffCount: payload.metadata?.diff?.length ?? 0,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
145
198
|
return { payload, streamComplete };
|
|
146
199
|
} catch (error) {
|
|
147
200
|
// Convert network-level errors to NetworkError for proper handling
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ActionStateListener,
|
|
13
13
|
HandleData,
|
|
14
14
|
} from "./types.js";
|
|
15
|
+
import { clearPrefetchCache } from "./prefetch/cache.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Default action state (idle with no payload)
|
|
@@ -88,7 +89,7 @@ export interface HistoryKeyOptions {
|
|
|
88
89
|
*/
|
|
89
90
|
export function generateHistoryKey(
|
|
90
91
|
url?: string,
|
|
91
|
-
options?: HistoryKeyOptions
|
|
92
|
+
options?: HistoryKeyOptions,
|
|
92
93
|
): string {
|
|
93
94
|
if (!url) {
|
|
94
95
|
url = typeof window !== "undefined" ? window.location.href : "/";
|
|
@@ -182,7 +183,7 @@ function createLocation(loc: { href: string }): NavigationLocation {
|
|
|
182
183
|
* ```
|
|
183
184
|
*/
|
|
184
185
|
export function createNavigationStore(
|
|
185
|
-
config?: NavigationStoreConfig
|
|
186
|
+
config?: NavigationStoreConfig,
|
|
186
187
|
): NavigationStore {
|
|
187
188
|
// Default location from window or config
|
|
188
189
|
const defaultLocation: NavigationLocation =
|
|
@@ -270,7 +271,7 @@ export function createNavigationStore(
|
|
|
270
271
|
*/
|
|
271
272
|
function createDebouncedNotifier<T extends (...args: any[]) => void>(
|
|
272
273
|
fn: T,
|
|
273
|
-
ms: number = 20
|
|
274
|
+
ms: number = 20,
|
|
274
275
|
): T {
|
|
275
276
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
276
277
|
return ((...args: Parameters<T>) => {
|
|
@@ -297,7 +298,7 @@ export function createNavigationStore(
|
|
|
297
298
|
setTimeout(() => {
|
|
298
299
|
timeouts.delete(key);
|
|
299
300
|
fn(key, ...args);
|
|
300
|
-
}, ms)
|
|
301
|
+
}, ms),
|
|
301
302
|
);
|
|
302
303
|
}) as T;
|
|
303
304
|
}
|
|
@@ -312,7 +313,7 @@ export function createNavigationStore(
|
|
|
312
313
|
if (listeners) {
|
|
313
314
|
listeners.forEach((listener) => listener(state));
|
|
314
315
|
}
|
|
315
|
-
}
|
|
316
|
+
},
|
|
316
317
|
);
|
|
317
318
|
|
|
318
319
|
/**
|
|
@@ -320,6 +321,7 @@ export function createNavigationStore(
|
|
|
320
321
|
*/
|
|
321
322
|
function clearCacheInternal(): void {
|
|
322
323
|
historyCache.length = 0;
|
|
324
|
+
clearPrefetchCache();
|
|
323
325
|
}
|
|
324
326
|
|
|
325
327
|
/**
|
|
@@ -329,13 +331,13 @@ export function createNavigationStore(
|
|
|
329
331
|
for (let i = 0; i < historyCache.length; i++) {
|
|
330
332
|
historyCache[i][2] = true;
|
|
331
333
|
}
|
|
334
|
+
clearPrefetchCache();
|
|
332
335
|
}
|
|
333
336
|
|
|
334
337
|
/**
|
|
335
338
|
* Clear the history cache and broadcast to other tabs
|
|
336
339
|
*/
|
|
337
340
|
function clearCacheAndBroadcast(): void {
|
|
338
|
-
console.log("[Browser] Clearing cache and broadcasting to other tabs");
|
|
339
341
|
clearCacheInternal();
|
|
340
342
|
broadcastInvalidation();
|
|
341
343
|
}
|
|
@@ -344,9 +346,6 @@ export function createNavigationStore(
|
|
|
344
346
|
* Mark cache as stale and broadcast to other tabs
|
|
345
347
|
*/
|
|
346
348
|
function markStaleAndBroadcast(): void {
|
|
347
|
-
console.log(
|
|
348
|
-
"[Browser] Marking cache as stale and broadcasting to other tabs"
|
|
349
|
-
);
|
|
350
349
|
markCacheAsStaleInternal();
|
|
351
350
|
broadcastInvalidation();
|
|
352
351
|
}
|
|
@@ -369,14 +368,6 @@ export function createNavigationStore(
|
|
|
369
368
|
path: currentPath,
|
|
370
369
|
segmentIds: currentSegmentIds,
|
|
371
370
|
});
|
|
372
|
-
console.log(
|
|
373
|
-
"[Browser] Broadcast sent for path:",
|
|
374
|
-
currentPath,
|
|
375
|
-
"segments:",
|
|
376
|
-
currentSegmentIds.join(", ")
|
|
377
|
-
);
|
|
378
|
-
} else {
|
|
379
|
-
console.warn("[Browser] No BroadcastChannel available");
|
|
380
371
|
}
|
|
381
372
|
}
|
|
382
373
|
|
|
@@ -393,7 +384,7 @@ export function createNavigationStore(
|
|
|
393
384
|
// Check for shared segments between tabs
|
|
394
385
|
// Routes sharing any segment (layout, loader, etc.) should invalidate together
|
|
395
386
|
const hasSharedSegment = mutatedSegmentIds.some((id) =>
|
|
396
|
-
currentSegmentIds.includes(id)
|
|
387
|
+
currentSegmentIds.includes(id),
|
|
397
388
|
);
|
|
398
389
|
|
|
399
390
|
if (!hasSharedSegment) {
|
|
@@ -401,34 +392,21 @@ export function createNavigationStore(
|
|
|
401
392
|
return;
|
|
402
393
|
}
|
|
403
394
|
|
|
404
|
-
console.log(
|
|
405
|
-
"[Browser] Cache marked stale by another tab, shared segments:",
|
|
406
|
-
mutatedSegmentIds
|
|
407
|
-
.filter((id) => currentSegmentIds.includes(id))
|
|
408
|
-
.join(", ")
|
|
409
|
-
);
|
|
410
395
|
markCacheAsStaleInternal();
|
|
411
396
|
|
|
412
397
|
// Auto-refresh if enabled and callback is registered
|
|
413
398
|
if (crossTabAutoRefresh && crossTabRefreshCallback) {
|
|
414
399
|
// If idle, refresh immediately. If loading, wait for idle then refresh.
|
|
415
400
|
if (navState.state === "idle") {
|
|
416
|
-
console.log("[Browser] Cross-tab refresh triggered (idle)");
|
|
417
401
|
crossTabRefreshCallback();
|
|
418
402
|
} else if (!pendingCrossTabRefresh) {
|
|
419
403
|
// Only queue one refresh, ignore subsequent events while loading
|
|
420
404
|
pendingCrossTabRefresh = true;
|
|
421
|
-
console.log(
|
|
422
|
-
"[Browser] Navigation in progress, deferring cross-tab refresh"
|
|
423
|
-
);
|
|
424
405
|
// Subscribe to state changes, refresh when idle
|
|
425
406
|
const listener: StateListener = () => {
|
|
426
407
|
if (navState.state === "idle") {
|
|
427
408
|
stateListeners.delete(listener);
|
|
428
409
|
pendingCrossTabRefresh = false;
|
|
429
|
-
console.log(
|
|
430
|
-
"[Browser] Cross-tab refresh triggered (deferred)"
|
|
431
|
-
);
|
|
432
410
|
crossTabRefreshCallback?.();
|
|
433
411
|
}
|
|
434
412
|
};
|
|
@@ -574,7 +552,7 @@ export function createNavigationStore(
|
|
|
574
552
|
cacheSegmentsForHistory(
|
|
575
553
|
historyKey: string,
|
|
576
554
|
segments: ResolvedSegment[],
|
|
577
|
-
handleData?: HandleData
|
|
555
|
+
handleData?: HandleData,
|
|
578
556
|
): void {
|
|
579
557
|
// Shallow clone handleData arrays to avoid reference sharing between cache entries
|
|
580
558
|
// We only clone the structure (objects and arrays), not the data items themselves,
|
|
@@ -585,10 +563,15 @@ export function createNavigationStore(
|
|
|
585
563
|
|
|
586
564
|
// Check if entry already exists and update it
|
|
587
565
|
const existingIndex = historyCache.findIndex(
|
|
588
|
-
([key]) => key === historyKey
|
|
566
|
+
([key]) => key === historyKey,
|
|
589
567
|
);
|
|
590
568
|
if (existingIndex !== -1) {
|
|
591
|
-
historyCache[existingIndex] = [
|
|
569
|
+
historyCache[existingIndex] = [
|
|
570
|
+
historyKey,
|
|
571
|
+
segments,
|
|
572
|
+
false,
|
|
573
|
+
clonedHandleData,
|
|
574
|
+
];
|
|
592
575
|
} else {
|
|
593
576
|
// Add new entry at the end (not stale)
|
|
594
577
|
historyCache.push([historyKey, segments, false, clonedHandleData]);
|
|
@@ -604,8 +587,10 @@ export function createNavigationStore(
|
|
|
604
587
|
* Returns { segments, stale, handleData } or undefined if not cached
|
|
605
588
|
*/
|
|
606
589
|
getCachedSegments(
|
|
607
|
-
historyKey: string
|
|
608
|
-
):
|
|
590
|
+
historyKey: string,
|
|
591
|
+
):
|
|
592
|
+
| { segments: ResolvedSegment[]; stale: boolean; handleData?: HandleData }
|
|
593
|
+
| undefined {
|
|
609
594
|
const entry = historyCache.find(([key]) => key === historyKey);
|
|
610
595
|
if (!entry) return undefined;
|
|
611
596
|
return { segments: entry[1], stale: entry[2], handleData: entry[3] };
|
|
@@ -625,13 +610,18 @@ export function createNavigationStore(
|
|
|
625
610
|
*/
|
|
626
611
|
updateCacheHandleData(historyKey: string, handleData: HandleData): void {
|
|
627
612
|
const existingIndex = historyCache.findIndex(
|
|
628
|
-
([key]) => key === historyKey
|
|
613
|
+
([key]) => key === historyKey,
|
|
629
614
|
);
|
|
630
615
|
if (existingIndex !== -1) {
|
|
631
616
|
const entry = historyCache[existingIndex];
|
|
632
617
|
// Shallow clone handleData arrays to avoid reference sharing
|
|
633
618
|
const clonedHandleData = cloneHandleData(handleData);
|
|
634
|
-
historyCache[existingIndex] = [
|
|
619
|
+
historyCache[existingIndex] = [
|
|
620
|
+
entry[0],
|
|
621
|
+
entry[1],
|
|
622
|
+
entry[2],
|
|
623
|
+
clonedHandleData,
|
|
624
|
+
];
|
|
635
625
|
}
|
|
636
626
|
},
|
|
637
627
|
|
|
@@ -640,14 +630,7 @@ export function createNavigationStore(
|
|
|
640
630
|
* Called after server actions to indicate data may be outdated
|
|
641
631
|
*/
|
|
642
632
|
markCacheAsStale(): void {
|
|
643
|
-
|
|
644
|
-
historyCache[i][2] = true;
|
|
645
|
-
}
|
|
646
|
-
console.log(
|
|
647
|
-
"[Browser] Marked",
|
|
648
|
-
historyCache.length,
|
|
649
|
-
"cache entries as stale"
|
|
650
|
-
);
|
|
633
|
+
markCacheAsStaleInternal();
|
|
651
634
|
},
|
|
652
635
|
|
|
653
636
|
/**
|
|
@@ -745,7 +728,7 @@ export function createNavigationStore(
|
|
|
745
728
|
*/
|
|
746
729
|
setActionState(
|
|
747
730
|
actionId: string,
|
|
748
|
-
partial: Partial<TrackedActionState
|
|
731
|
+
partial: Partial<TrackedActionState>,
|
|
749
732
|
): void {
|
|
750
733
|
const current = actionStates.get(actionId) ?? { ...DEFAULT_ACTION_STATE };
|
|
751
734
|
const updated: TrackedActionState = {
|
|
@@ -763,7 +746,7 @@ export function createNavigationStore(
|
|
|
763
746
|
*/
|
|
764
747
|
subscribeToAction(
|
|
765
748
|
actionId: string,
|
|
766
|
-
listener: ActionStateListener
|
|
749
|
+
listener: ActionStateListener,
|
|
767
750
|
): () => void {
|
|
768
751
|
let listeners = actionListeners.get(actionId);
|
|
769
752
|
if (!listeners) {
|
|
@@ -793,7 +776,7 @@ let storeInstance: NavigationStore | null = null;
|
|
|
793
776
|
* Subsequent calls return the existing instance.
|
|
794
777
|
*/
|
|
795
778
|
export function initNavigationStore(
|
|
796
|
-
config?: NavigationStoreConfig
|
|
779
|
+
config?: NavigationStoreConfig,
|
|
797
780
|
): NavigationStore {
|
|
798
781
|
if (!storeInstance) {
|
|
799
782
|
storeInstance = createNavigationStore(config);
|
|
@@ -809,7 +792,7 @@ export function initNavigationStore(
|
|
|
809
792
|
export function getNavigationStore(): NavigationStore {
|
|
810
793
|
if (!storeInstance) {
|
|
811
794
|
throw new Error(
|
|
812
|
-
"Navigation store not initialized. Call initNavigationStore first."
|
|
795
|
+
"Navigation store not initialized. Call initNavigationStore first.",
|
|
813
796
|
);
|
|
814
797
|
}
|
|
815
798
|
return storeInstance;
|