@rangojs/router 0.0.0-experimental.135c6902 → 0.0.0-experimental.136
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 +8 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3386 -1111
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +78 -25
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +281 -11
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +289 -53
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +914 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +144 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +101 -5
- package/skills/router-setup/SKILL.md +117 -10
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/typesafety/SKILL.md +332 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +131 -30
- package/src/browser/navigation-client.ts +199 -86
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +174 -109
- package/src/browser/prefetch/cache.ts +222 -69
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +113 -32
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +300 -122
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +125 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -61
- package/src/browser/types.ts +115 -12
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +239 -52
- package/src/cache/cache-scope.ts +234 -87
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +99 -13
- package/src/index.ts +139 -19
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +149 -74
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +83 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +54 -25
- package/src/router/match-middleware/cache-lookup.ts +205 -271
- package/src/router/match-middleware/cache-store.ts +81 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +93 -39
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +268 -171
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -27
- package/src/router/router-interfaces.ts +129 -35
- package/src/router/router-options.ts +202 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +197 -92
- package/src/router/segment-resolution/helpers.ts +115 -30
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +392 -338
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +174 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +660 -518
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +133 -13
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +115 -73
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -113
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +36 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +269 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +235 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +442 -173
- package/src/ssr/index.tsx +24 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +234 -82
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +214 -132
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- 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 +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +119 -111
- package/src/vite/router-discovery.ts +941 -142
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -8,19 +8,25 @@ import type {
|
|
|
8
8
|
import { NetworkError, ServerRedirect, isNetworkError } from "../errors.js";
|
|
9
9
|
import {
|
|
10
10
|
browserDebugLog,
|
|
11
|
+
debugLog,
|
|
11
12
|
isBrowserDebugEnabled,
|
|
12
13
|
startBrowserTransaction,
|
|
13
14
|
} from "./logging.js";
|
|
14
15
|
import { getRangoState } from "./rango-state.js";
|
|
16
|
+
import { isActionFenceActive } from "./action-fence.js";
|
|
15
17
|
import {
|
|
16
18
|
extractRscHeaderUrl,
|
|
17
19
|
emptyResponse,
|
|
20
|
+
handleReloadHeader,
|
|
18
21
|
teeWithCompletion,
|
|
22
|
+
isForeignRouterId,
|
|
19
23
|
} from "./response-adapter.js";
|
|
20
24
|
import {
|
|
21
25
|
buildPrefetchKey,
|
|
26
|
+
buildSourceKey,
|
|
22
27
|
consumeInflightPrefetch,
|
|
23
28
|
consumePrefetch,
|
|
29
|
+
type DecodedPrefetch,
|
|
24
30
|
} from "./prefetch/cache.js";
|
|
25
31
|
|
|
26
32
|
/**
|
|
@@ -30,8 +36,10 @@ import {
|
|
|
30
36
|
* deserializing the response using the RSC runtime.
|
|
31
37
|
*
|
|
32
38
|
* Checks the in-memory prefetch cache before making a network request.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
39
|
+
* Tries the source-scoped key first (populated when the server tagged
|
|
40
|
+
* the response as source-sensitive via `X-RSC-Prefetch-Scope: source`)
|
|
41
|
+
* and falls back to the Rango-state-keyed wildcard slot used for the
|
|
42
|
+
* common source-agnostic case.
|
|
35
43
|
*
|
|
36
44
|
* @param deps - RSC browser dependencies (createFromFetch)
|
|
37
45
|
* @returns NavigationClient instance
|
|
@@ -61,6 +69,7 @@ export function createNavigationClient(
|
|
|
61
69
|
staleRevalidation,
|
|
62
70
|
interceptSourceUrl,
|
|
63
71
|
version,
|
|
72
|
+
routerId,
|
|
64
73
|
hmr,
|
|
65
74
|
} = options;
|
|
66
75
|
|
|
@@ -88,25 +97,116 @@ export function createNavigationClient(
|
|
|
88
97
|
if (version) {
|
|
89
98
|
fetchUrl.searchParams.set("_rsc_v", version);
|
|
90
99
|
}
|
|
100
|
+
if (routerId) {
|
|
101
|
+
fetchUrl.searchParams.set("_rsc_rid", routerId);
|
|
102
|
+
}
|
|
91
103
|
|
|
92
|
-
// Check completed in-memory prefetch cache before making a network
|
|
93
|
-
//
|
|
94
|
-
//
|
|
104
|
+
// Check completed in-memory prefetch cache before making a network
|
|
105
|
+
// request. Try the source-scoped key first (populated when the server
|
|
106
|
+
// tagged the prefetch response as source-sensitive, e.g. intercepts,
|
|
107
|
+
// or when a Link opted in with `prefetchKey=":source"`), then fall
|
|
108
|
+
// back to the wildcard slot shared across source pages.
|
|
109
|
+
// Both keys embed the Rango state, so state rotation (deploy or
|
|
110
|
+
// server-action invalidation) auto-invalidates both scopes.
|
|
95
111
|
// Skip cache for stale revalidation (needs fresh data), HMR (needs
|
|
96
112
|
// fresh modules), and intercept contexts (source-dependent responses).
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
// Suspend prefetch consumption while an action is in flight: a queued
|
|
114
|
+
// prefetch holds pre-mutation data and must not be served until the
|
|
115
|
+
// action's response decides whether anything changed.
|
|
116
|
+
const canUsePrefetch =
|
|
117
|
+
!staleRevalidation &&
|
|
118
|
+
!hmr &&
|
|
119
|
+
!interceptSourceUrl &&
|
|
120
|
+
!isActionFenceActive();
|
|
121
|
+
const rangoState = getRangoState();
|
|
122
|
+
const wildcardKey = buildPrefetchKey(rangoState, fetchUrl);
|
|
123
|
+
const cacheKey = buildSourceKey(rangoState, previousUrl, fetchUrl);
|
|
124
|
+
|
|
125
|
+
let cachedEntry: DecodedPrefetch | null = null;
|
|
126
|
+
let hitKey: string | null = null;
|
|
127
|
+
if (canUsePrefetch) {
|
|
128
|
+
cachedEntry = consumePrefetch(cacheKey);
|
|
129
|
+
if (cachedEntry) {
|
|
130
|
+
hitKey = cacheKey;
|
|
131
|
+
} else {
|
|
132
|
+
cachedEntry = consumePrefetch(wildcardKey);
|
|
133
|
+
if (cachedEntry) hitKey = wildcardKey;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let inflightEntryPromise: Promise<DecodedPrefetch | null> | null = null;
|
|
138
|
+
if (canUsePrefetch && !cachedEntry) {
|
|
139
|
+
inflightEntryPromise = consumeInflightPrefetch(cacheKey);
|
|
140
|
+
if (inflightEntryPromise) {
|
|
141
|
+
hitKey = cacheKey;
|
|
142
|
+
} else {
|
|
143
|
+
inflightEntryPromise = consumeInflightPrefetch(wildcardKey);
|
|
144
|
+
if (inflightEntryPromise) hitKey = wildcardKey;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
104
147
|
// Track when the stream completes
|
|
105
148
|
let resolveStreamComplete: () => void;
|
|
106
149
|
const streamComplete = new Promise<void>((resolve) => {
|
|
107
150
|
resolveStreamComplete = resolve;
|
|
108
151
|
});
|
|
109
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Validate RSC control headers on any response (fresh, cached, or
|
|
155
|
+
* in-flight). Handles version-mismatch reloads and server redirects.
|
|
156
|
+
* Returns the response unchanged when no control header is present.
|
|
157
|
+
*/
|
|
158
|
+
const validateRscHeaders = (
|
|
159
|
+
response: Response,
|
|
160
|
+
source: string,
|
|
161
|
+
): Response | Promise<Response> => {
|
|
162
|
+
// Version mismatch — server wants a full page reload
|
|
163
|
+
const reloadResult = handleReloadHeader(response, {
|
|
164
|
+
onBlocked: resolveStreamComplete,
|
|
165
|
+
onReload: (url) => {
|
|
166
|
+
if (tx) {
|
|
167
|
+
browserDebugLog(tx, `version mismatch, reloading (${source})`, {
|
|
168
|
+
reloadUrl: url,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
if (reloadResult) return reloadResult;
|
|
174
|
+
|
|
175
|
+
// Server-side redirect without state: the server returned 204 with
|
|
176
|
+
// X-RSC-Redirect instead of a 3xx (which fetch would auto-follow
|
|
177
|
+
// to a URL rendering full HTML). Throw ServerRedirect so the
|
|
178
|
+
// navigation bridge catches it and re-navigates with _skipCache.
|
|
179
|
+
const redirect = extractRscHeaderUrl(response, "X-RSC-Redirect");
|
|
180
|
+
if (redirect === "blocked") {
|
|
181
|
+
resolveStreamComplete();
|
|
182
|
+
return emptyResponse();
|
|
183
|
+
}
|
|
184
|
+
if (redirect) {
|
|
185
|
+
if (tx) {
|
|
186
|
+
browserDebugLog(tx, `server redirect (${source})`, {
|
|
187
|
+
redirectUrl: redirect.url,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
resolveStreamComplete();
|
|
191
|
+
throw new ServerRedirect(redirect.url, undefined);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Integrity check (pre-decode): refuse a foreign app's content response
|
|
195
|
+
// before createFromFetch imports its chunks. Ordered AFTER the reload
|
|
196
|
+
// and redirect handlers — control responses are never stamped with
|
|
197
|
+
// X-RSC-Router-Id, so they are steered first and never reach here.
|
|
198
|
+
if (isForeignRouterId(response, routerId)) {
|
|
199
|
+
if (tx) {
|
|
200
|
+
browserDebugLog(tx, `router id mismatch, reloading (${source})`);
|
|
201
|
+
}
|
|
202
|
+
resolveStreamComplete();
|
|
203
|
+
window.location.href = targetUrl;
|
|
204
|
+
return new Promise<Response>(() => {});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return response;
|
|
208
|
+
};
|
|
209
|
+
|
|
110
210
|
/** Start a fresh navigation fetch (no cache / inflight hit). */
|
|
111
211
|
const doFreshFetch = (): Promise<Response> => {
|
|
112
212
|
if (tx) {
|
|
@@ -116,6 +216,11 @@ export function createNavigationClient(
|
|
|
116
216
|
}
|
|
117
217
|
|
|
118
218
|
return fetch(fetchUrl, {
|
|
219
|
+
// During an action's flight the state is not rotated, so the old
|
|
220
|
+
// X-Rango-State still matches the Vary-keyed HTTP-cache entry; bypass
|
|
221
|
+
// it so a genuine mid-action navigation fetches fresh instead of being
|
|
222
|
+
// served the stale prefetched bytes.
|
|
223
|
+
...(isActionFenceActive() && { cache: "no-store" as RequestCache }),
|
|
119
224
|
headers: {
|
|
120
225
|
"X-RSC-Router-Client-Path": previousUrl,
|
|
121
226
|
"X-Rango-State": getRangoState(),
|
|
@@ -127,43 +232,11 @@ export function createNavigationClient(
|
|
|
127
232
|
},
|
|
128
233
|
signal,
|
|
129
234
|
}).then((response) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (reload === "blocked") {
|
|
133
|
-
resolveStreamComplete();
|
|
134
|
-
return emptyResponse();
|
|
135
|
-
}
|
|
136
|
-
if (reload) {
|
|
137
|
-
if (tx) {
|
|
138
|
-
browserDebugLog(tx, "version mismatch, reloading", {
|
|
139
|
-
reloadUrl: reload.url,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
window.location.href = reload.url;
|
|
143
|
-
return new Promise<Response>(() => {});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Server-side redirect without state: the server returned 204 with
|
|
147
|
-
// X-RSC-Redirect instead of a 3xx (which fetch would auto-follow
|
|
148
|
-
// to a URL rendering full HTML). Throw ServerRedirect so the
|
|
149
|
-
// navigation bridge catches it and re-navigates with _skipCache.
|
|
150
|
-
const redirect = extractRscHeaderUrl(response, "X-RSC-Redirect");
|
|
151
|
-
if (redirect === "blocked") {
|
|
152
|
-
resolveStreamComplete();
|
|
153
|
-
return emptyResponse();
|
|
154
|
-
}
|
|
155
|
-
if (redirect) {
|
|
156
|
-
if (tx) {
|
|
157
|
-
browserDebugLog(tx, "server redirect", {
|
|
158
|
-
redirectUrl: redirect.url,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
resolveStreamComplete();
|
|
162
|
-
throw new ServerRedirect(redirect.url, undefined);
|
|
163
|
-
}
|
|
235
|
+
const validated = validateRscHeaders(response, "fetch");
|
|
236
|
+
if (validated instanceof Promise) return validated;
|
|
164
237
|
|
|
165
238
|
return teeWithCompletion(
|
|
166
|
-
|
|
239
|
+
validated,
|
|
167
240
|
() => {
|
|
168
241
|
if (tx) browserDebugLog(tx, "stream complete");
|
|
169
242
|
resolveStreamComplete();
|
|
@@ -173,54 +246,90 @@ export function createNavigationClient(
|
|
|
173
246
|
});
|
|
174
247
|
};
|
|
175
248
|
|
|
176
|
-
|
|
249
|
+
// A warm prefetch hit returns its eagerly-decoded payload directly: the
|
|
250
|
+
// route's chunks were imported during the prefetch, so this click runs
|
|
251
|
+
// no decode and no network. Only the fresh path runs createFromFetch and
|
|
252
|
+
// resolves the local streamComplete (via doFreshFetch's teeWithCompletion
|
|
253
|
+
// and the control-header short-circuits in validateRscHeaders).
|
|
254
|
+
const freshResult = (): {
|
|
255
|
+
payload: Promise<RscPayload>;
|
|
256
|
+
streamComplete: Promise<void>;
|
|
257
|
+
} => ({
|
|
258
|
+
payload: deps.createFromFetch<RscPayload>(doFreshFetch()),
|
|
259
|
+
streamComplete,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
let payloadPromise: Promise<RscPayload>;
|
|
263
|
+
let streamCompletePromise: Promise<void>;
|
|
264
|
+
// True only for a prefetch-cache hit whose stream had already fully drained
|
|
265
|
+
// (complete === true). A still-streaming hit and the fresh path stay false,
|
|
266
|
+
// so only a fully-warmed prefetch commits in a transition (no fallback flash).
|
|
267
|
+
let fullyPrefetched = false;
|
|
177
268
|
|
|
178
|
-
if (
|
|
269
|
+
if (cachedEntry) {
|
|
179
270
|
if (tx) {
|
|
180
|
-
browserDebugLog(tx, "prefetch cache hit", {
|
|
271
|
+
browserDebugLog(tx, "prefetch cache hit (warm)", {
|
|
272
|
+
key: hitKey,
|
|
273
|
+
wildcard: hitKey === wildcardKey,
|
|
274
|
+
});
|
|
181
275
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (tx) browserDebugLog(tx, "stream complete (from cache)");
|
|
189
|
-
resolveStreamComplete();
|
|
190
|
-
},
|
|
191
|
-
signal,
|
|
192
|
-
);
|
|
193
|
-
});
|
|
194
|
-
} else if (inflightResponsePromise) {
|
|
276
|
+
payloadPromise = cachedEntry.payload;
|
|
277
|
+
streamCompletePromise = cachedEntry.streamComplete;
|
|
278
|
+
// Only a hit whose stream already fully drained is "fully prefetched";
|
|
279
|
+
// a still-streaming hit must keep streaming its fallbacks like a cold load.
|
|
280
|
+
fullyPrefetched = cachedEntry.complete;
|
|
281
|
+
} else if (inflightEntryPromise) {
|
|
195
282
|
if (tx) {
|
|
196
|
-
browserDebugLog(tx, "reusing inflight prefetch", {
|
|
283
|
+
browserDebugLog(tx, "reusing inflight prefetch", {
|
|
284
|
+
key: hitKey,
|
|
285
|
+
wildcard: hitKey === wildcardKey,
|
|
286
|
+
});
|
|
197
287
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return doFreshFetch();
|
|
288
|
+
const adoptedViaWildcard = hitKey === wildcardKey;
|
|
289
|
+
const entry = await inflightEntryPromise;
|
|
290
|
+
if (!entry) {
|
|
291
|
+
if (tx) {
|
|
292
|
+
browserDebugLog(tx, "inflight prefetch unavailable, refetching");
|
|
204
293
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
294
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
295
|
+
freshResult());
|
|
296
|
+
} else if (adoptedViaWildcard && entry.scope === "source") {
|
|
297
|
+
// A wildcard-adopted inflight that turned out source-scoped was
|
|
298
|
+
// built for a different source page. Discard and refetch.
|
|
299
|
+
if (tx) {
|
|
300
|
+
browserDebugLog(
|
|
301
|
+
tx,
|
|
302
|
+
"wildcard inflight turned out source-scoped, refetching",
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
306
|
+
freshResult());
|
|
307
|
+
} else {
|
|
308
|
+
payloadPromise = entry.payload;
|
|
309
|
+
streamCompletePromise = entry.streamComplete;
|
|
310
|
+
// Adopted inflight is normally still streaming (false), but read the
|
|
311
|
+
// flag in case it completed between publish and adoption.
|
|
312
|
+
fullyPrefetched = entry.complete;
|
|
313
|
+
}
|
|
217
314
|
} else {
|
|
218
|
-
|
|
315
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
316
|
+
freshResult());
|
|
219
317
|
}
|
|
220
318
|
|
|
221
319
|
try {
|
|
222
|
-
//
|
|
223
|
-
|
|
320
|
+
// [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. Times how long the RSC
|
|
321
|
+
// payload ROOT takes to resolve: ~full-stream duration means the root
|
|
322
|
+
// model is not flushed early (server/runtime buffering, e.g. wrangler
|
|
323
|
+
// dev gzip); fast means the block, if any, is downstream in render.
|
|
324
|
+
const vtDebugStart = isBrowserDebugEnabled() ? performance.now() : 0;
|
|
325
|
+
const payload = await payloadPromise;
|
|
326
|
+
if (isBrowserDebugEnabled()) {
|
|
327
|
+
debugLog("[VT-DIAG] payloadResolved", {
|
|
328
|
+
ms: Math.round(performance.now() - vtDebugStart),
|
|
329
|
+
isPartial: payload.metadata?.isPartial,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
224
333
|
if (tx) {
|
|
225
334
|
browserDebugLog(tx, "response received", {
|
|
226
335
|
isPartial: payload.metadata?.isPartial,
|
|
@@ -228,7 +337,11 @@ export function createNavigationClient(
|
|
|
228
337
|
diffCount: payload.metadata?.diff?.length ?? 0,
|
|
229
338
|
});
|
|
230
339
|
}
|
|
231
|
-
return {
|
|
340
|
+
return {
|
|
341
|
+
payload,
|
|
342
|
+
streamComplete: streamCompletePromise,
|
|
343
|
+
fullyPrefetched,
|
|
344
|
+
};
|
|
232
345
|
} catch (error) {
|
|
233
346
|
// Convert network-level errors to NetworkError for proper handling
|
|
234
347
|
if (isNetworkError(error)) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A module-level handle to the active navigation store.
|
|
3
|
+
*
|
|
4
|
+
* The boot path (`rsc-router.tsx`) calls `createNavigationStore()` directly;
|
|
5
|
+
* there is no global store singleton. This handle is the live reference for
|
|
6
|
+
* code that needs the store but does not
|
|
7
|
+
* receive it by argument: the jar-divergence observer (below) and the client
|
|
8
|
+
* seat of `invalidateClientCache()` (added later).
|
|
9
|
+
*
|
|
10
|
+
* Dependency-light on purpose: it imports only `setRangoStateObserver` and the
|
|
11
|
+
* store type, so pulling it into the default root entry does not drag the
|
|
12
|
+
* navigation store into bundles that previously lacked it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { setRangoStateObserver } from "./rango-state.js";
|
|
16
|
+
import type { NavigationStore } from "./types.js";
|
|
17
|
+
|
|
18
|
+
let registeredStore: NavigationStore | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register the active navigation store at boot, and wire the jar-divergence
|
|
22
|
+
* observer: when a per-request cookie read detects an EXTERNAL rotation (a
|
|
23
|
+
* sibling tab, a server `Set-Cookie`, or a cookie clear), mark this tab's
|
|
24
|
+
* history cache stale. The history cache is not state-keyed, so the value
|
|
25
|
+
* rotation alone does not reach it. No broadcast, no prefetch clear, no
|
|
26
|
+
* re-rotation — the value already changed externally.
|
|
27
|
+
*/
|
|
28
|
+
export function registerNavigationStore(store: NavigationStore): void {
|
|
29
|
+
registeredStore = store;
|
|
30
|
+
setRangoStateObserver(() => {
|
|
31
|
+
registeredStore?.markHistoryCacheStale();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The active navigation store, or null before boot has registered it. */
|
|
36
|
+
export function getRegisteredStore(): NavigationStore | null {
|
|
37
|
+
return registeredStore;
|
|
38
|
+
}
|