@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1c0bdfad
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 +17 -0
- package/README.md +291 -61
- package/dist/bin/rango.js +544 -143
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3744 -1329
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +67 -13
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +312 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +322 -19
- package/skills/composability/SKILL.md +27 -2
- 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 +282 -60
- package/skills/host-router/SKILL.md +278 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +50 -6
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +297 -31
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +155 -111
- package/skills/rango/SKILL.md +251 -23
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +104 -9
- package/skills/router-setup/SKILL.md +124 -11
- 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 +125 -222
- 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 +121 -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 +357 -52
- package/skills/use-cache/SKILL.md +46 -14
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/bin/rango.ts +18 -0
- 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 +197 -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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +200 -30
- package/src/browser/navigation-client.ts +217 -58
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +187 -112
- package/src/browser/prefetch/cache.ts +312 -30
- package/src/browser/prefetch/fetch.ts +344 -47
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +125 -18
- package/src/browser/react/NavigationProvider.tsx +135 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- 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 +80 -5
- package/src/browser/rsc-router.tsx +226 -75
- package/src/browser/scroll-restoration.ts +54 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +619 -442
- package/src/browser/types.ts +115 -11
- 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 +7 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +182 -37
- 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 +392 -106
- 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 +214 -48
- package/src/cache/cache-scope.ts +236 -89
- 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 +2224 -171
- 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 +11 -17
- package/src/cache/document-cache.ts +89 -27
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +11 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +31 -31
- package/src/cache/read-through-swr.ts +41 -11
- 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 +45 -21
- package/src/client.tsx +120 -336
- 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/debug.ts +2 -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 +56 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +95 -0
- 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 -5
- 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 +79 -29
- package/src/index.ts +149 -65
- 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 +63 -26
- 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 +7 -72
- package/src/route-definition/dsl-helpers.ts +469 -276
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +53 -12
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- 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 +45 -22
- package/src/router/handler-context.ts +110 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +50 -24
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +33 -6
- package/src/router/match-middleware/cache-lookup.ts +214 -263
- package/src/router/match-middleware/cache-store.ts +73 -33
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -49
- package/src/router/metrics.ts +217 -26
- package/src/router/middleware-types.ts +24 -110
- package/src/router/middleware.ts +384 -197
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +148 -91
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +199 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +144 -74
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +8 -28
- package/src/router/router-interfaces.ts +129 -36
- package/src/router/router-options.ts +185 -23
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +281 -76
- package/src/router/segment-resolution/helpers.ts +116 -31
- package/src/router/segment-resolution/loader-cache.ts +63 -37
- package/src/router/segment-resolution/revalidation.ts +493 -391
- 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 +36 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +8 -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 +180 -58
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +182 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +702 -460
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -25
- 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 +98 -19
- 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 +126 -106
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +269 -114
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/types.ts +34 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +49 -41
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +281 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +317 -63
- 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 +26 -46
- package/src/server/request-context.ts +425 -177
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +25 -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 +99 -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 +343 -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/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 +3 -19
- 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 +236 -88
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +10 -45
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -19
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- 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 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +64 -91
- package/src/vite/discovery/discover-routers.ts +147 -88
- 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 +247 -145
- 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 +61 -13
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +10 -3
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +155 -65
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- 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 +49 -98
- 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 +127 -0
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +46 -37
- package/src/vite/plugins/virtual-entries.ts +13 -18
- package/src/vite/rango.ts +241 -287
- package/src/vite/router-discovery.ts +956 -149
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +4 -4
- 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 +141 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/CLAUDE.md +0 -5
- 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 -6
- package/src/network-error-thrower.tsx +0 -23
- package/src/route-definition/route-function.ts +0 -119
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -117,6 +117,7 @@ export function setupLinkInterception(
|
|
|
117
117
|
// Read navigation options from data attributes (set by Link component)
|
|
118
118
|
const scrollAttr = link.getAttribute("data-scroll");
|
|
119
119
|
const replaceAttr = link.getAttribute("data-replace");
|
|
120
|
+
const revalidateAttr = link.getAttribute("data-revalidate");
|
|
120
121
|
|
|
121
122
|
const navigateOptions: NavigateOptions = {};
|
|
122
123
|
if (scrollAttr === "false") {
|
|
@@ -125,6 +126,9 @@ export function setupLinkInterception(
|
|
|
125
126
|
if (replaceAttr === "true") {
|
|
126
127
|
navigateOptions.replace = true;
|
|
127
128
|
}
|
|
129
|
+
if (revalidateAttr === "false") {
|
|
130
|
+
navigateOptions.revalidate = false;
|
|
131
|
+
}
|
|
128
132
|
|
|
129
133
|
onNavigate(href, navigateOptions);
|
|
130
134
|
};
|
|
@@ -4,12 +4,21 @@ import type {
|
|
|
4
4
|
NavigateOptionsInternal,
|
|
5
5
|
ResolvedSegment,
|
|
6
6
|
} from "./types.js";
|
|
7
|
+
import { setAppVersion } from "./app-version.js";
|
|
8
|
+
import { isActionFenceActive } from "./action-fence.js";
|
|
9
|
+
import { getRangoState } from "./rango-state.js";
|
|
7
10
|
import * as React from "react";
|
|
8
11
|
import { startTransition } from "react";
|
|
9
12
|
import {
|
|
10
13
|
createNavigationTransaction,
|
|
11
14
|
resolveNavigationState,
|
|
12
15
|
} from "./navigation-transaction.js";
|
|
16
|
+
import { buildHistoryState, pushHistoryWithIdx } from "./history-state.js";
|
|
17
|
+
import {
|
|
18
|
+
handleNavigationStart,
|
|
19
|
+
handleNavigationEnd,
|
|
20
|
+
ensureHistoryKey,
|
|
21
|
+
} from "./scroll-restoration.js";
|
|
13
22
|
|
|
14
23
|
// addTransitionType is only available in React experimental
|
|
15
24
|
const addTransitionType: ((type: string) => void) | undefined =
|
|
@@ -18,12 +27,12 @@ const addTransitionType: ((type: string) => void) | undefined =
|
|
|
18
27
|
import { setupLinkInterception } from "./link-interceptor.js";
|
|
19
28
|
import { createPartialUpdater } from "./partial-update.js";
|
|
20
29
|
import { generateHistoryKey } from "./navigation-store.js";
|
|
21
|
-
import { handleNavigationEnd } from "./scroll-restoration.js";
|
|
22
30
|
import type { EventController } from "./event-controller.js";
|
|
23
31
|
import { isInterceptOnlyCache } from "./intercept-utils.js";
|
|
24
32
|
import {
|
|
25
33
|
toNetworkError,
|
|
26
34
|
emitNetworkError,
|
|
35
|
+
emitNavigationError,
|
|
27
36
|
isBackgroundSuppressible,
|
|
28
37
|
} from "./network-error-handler.js";
|
|
29
38
|
import { debugLog } from "./logging.js";
|
|
@@ -35,11 +44,6 @@ if (typeof Symbol.dispose === "undefined") {
|
|
|
35
44
|
(Symbol as any).dispose = Symbol("Symbol.dispose");
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
/** Get IDs of non-loader segments (layouts, routes, parallels). */
|
|
39
|
-
function getNonLoaderSegmentIds(segments: ResolvedSegment[]): string[] {
|
|
40
|
-
return segments.filter((s) => s.type !== "loader").map((s) => s.id);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
47
|
export { createNavigationTransaction };
|
|
44
48
|
|
|
45
49
|
/**
|
|
@@ -47,7 +51,7 @@ export { createNavigationTransaction };
|
|
|
47
51
|
*/
|
|
48
52
|
export interface NavigationBridgeConfigWithController extends NavigationBridgeConfig {
|
|
49
53
|
eventController: EventController;
|
|
50
|
-
/** RSC version from initial payload metadata */
|
|
54
|
+
/** RSC version from initial payload metadata. */
|
|
51
55
|
version?: string;
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -67,8 +71,8 @@ export interface NavigationBridgeConfigWithController extends NavigationBridgeCo
|
|
|
67
71
|
export function createNavigationBridge(
|
|
68
72
|
config: NavigationBridgeConfigWithController,
|
|
69
73
|
): NavigationBridge {
|
|
70
|
-
const { store, client, eventController, onUpdate, renderSegments
|
|
71
|
-
|
|
74
|
+
const { store, client, eventController, onUpdate, renderSegments } = config;
|
|
75
|
+
let version = config.version;
|
|
72
76
|
|
|
73
77
|
// Create shared partial updater
|
|
74
78
|
const fetchPartialUpdate = createPartialUpdater({
|
|
@@ -76,7 +80,7 @@ export function createNavigationBridge(
|
|
|
76
80
|
client,
|
|
77
81
|
onUpdate,
|
|
78
82
|
renderSegments,
|
|
79
|
-
version,
|
|
83
|
+
getVersion: () => version,
|
|
80
84
|
});
|
|
81
85
|
|
|
82
86
|
return {
|
|
@@ -114,6 +118,81 @@ export function createNavigationBridge(
|
|
|
114
118
|
return;
|
|
115
119
|
}
|
|
116
120
|
|
|
121
|
+
// Shallow navigation: skip RSC fetch when revalidate is false
|
|
122
|
+
// and the pathname hasn't changed (search param / hash only change).
|
|
123
|
+
if (
|
|
124
|
+
options?.revalidate === false &&
|
|
125
|
+
targetUrl.pathname === new URL(window.location.href).pathname
|
|
126
|
+
) {
|
|
127
|
+
// Preserve intercept context from the current history entry so that
|
|
128
|
+
// popstate uses the correct cache key (:intercept suffix) and restores
|
|
129
|
+
// the right full-page vs modal semantics.
|
|
130
|
+
const currentHistoryState = window.history.state;
|
|
131
|
+
const isIntercept = currentHistoryState?.intercept === true;
|
|
132
|
+
const interceptSourceUrl = isIntercept
|
|
133
|
+
? currentHistoryState?.sourceUrl
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
136
|
+
const historyKey = generateHistoryKey(url, { intercept: isIntercept });
|
|
137
|
+
|
|
138
|
+
// Copy current segments to the new history key so back/forward restores instantly
|
|
139
|
+
const currentKey = store.getHistoryKey();
|
|
140
|
+
const currentCache = store.getCachedSegments(currentKey);
|
|
141
|
+
if (currentCache?.segments) {
|
|
142
|
+
const currentHandleData = eventController.getHandleState().data;
|
|
143
|
+
store.cacheSegmentsForHistory(
|
|
144
|
+
historyKey,
|
|
145
|
+
currentCache.segments,
|
|
146
|
+
currentHandleData,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Save current scroll position before changing URL
|
|
151
|
+
handleNavigationStart();
|
|
152
|
+
|
|
153
|
+
// Snapshot old state before pushState/replaceState overwrites it
|
|
154
|
+
const oldState = window.history.state;
|
|
155
|
+
|
|
156
|
+
// Update browser URL (carry intercept context into history state)
|
|
157
|
+
const historyState = buildHistoryState(
|
|
158
|
+
resolvedState,
|
|
159
|
+
{
|
|
160
|
+
intercept: isIntercept || undefined,
|
|
161
|
+
sourceUrl: interceptSourceUrl,
|
|
162
|
+
},
|
|
163
|
+
{},
|
|
164
|
+
);
|
|
165
|
+
pushHistoryWithIdx(historyState, url, options?.replace ?? false);
|
|
166
|
+
|
|
167
|
+
// Ensure new history entry has a scroll restoration key
|
|
168
|
+
ensureHistoryKey();
|
|
169
|
+
|
|
170
|
+
// Notify useLocationState() hooks when state changes
|
|
171
|
+
const hasOldState =
|
|
172
|
+
oldState &&
|
|
173
|
+
typeof oldState === "object" &&
|
|
174
|
+
("state" in oldState ||
|
|
175
|
+
Object.keys(oldState).some((k) => k.startsWith("__rsc_ls_")));
|
|
176
|
+
const hasNewState =
|
|
177
|
+
historyState &&
|
|
178
|
+
("state" in historyState ||
|
|
179
|
+
Object.keys(historyState).some((k) => k.startsWith("__rsc_ls_")));
|
|
180
|
+
if (hasOldState || hasNewState) {
|
|
181
|
+
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Update store history key so future navigations reference the right cache
|
|
185
|
+
store.setHistoryKey(historyKey);
|
|
186
|
+
store.setCurrentUrl(url);
|
|
187
|
+
|
|
188
|
+
// Notify hooks — location updates, state stays idle
|
|
189
|
+
eventController.setLocation(targetUrl);
|
|
190
|
+
|
|
191
|
+
// Handle post-navigation scroll
|
|
192
|
+
handleNavigationEnd({ scroll: options.scroll });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
117
196
|
// Only abort pending requests when navigating to a different route
|
|
118
197
|
// Same-route navigation (e.g., /todos -> /todos) should not cancel in-flight actions
|
|
119
198
|
const currentPath = new URL(window.location.href).pathname;
|
|
@@ -181,18 +260,24 @@ export function createNavigationBridge(
|
|
|
181
260
|
// 2. routes that CAN be intercepted - we don't know if this navigation will intercept
|
|
182
261
|
// 3. when leaving intercept - we need fresh non-intercept segments from server
|
|
183
262
|
// 4. redirect-with-state - force re-render so hooks read fresh state
|
|
263
|
+
// 5. stale cache - server action invalidated it, need fresh data with loading state
|
|
184
264
|
const hasUsableCache =
|
|
185
265
|
cachedSegments &&
|
|
186
266
|
cachedSegments.length > 0 &&
|
|
187
267
|
!isInterceptOnlyCache(cachedSegments) &&
|
|
188
268
|
!hasInterceptCache &&
|
|
189
269
|
!isLeavingIntercept &&
|
|
270
|
+
!cached?.stale &&
|
|
190
271
|
!options?._skipCache;
|
|
191
272
|
|
|
192
|
-
|
|
273
|
+
// Forward navigations always await fetchPartialUpdate before rendering,
|
|
274
|
+
// so useNavigation should always report "loading". skipLoadingState is
|
|
275
|
+
// only used for popstate background revalidation (line ~526) where
|
|
276
|
+
// cached content renders instantly without a network wait.
|
|
277
|
+
const tx = createNavigationTransaction(store, eventController, url, {
|
|
193
278
|
...options,
|
|
194
279
|
state: resolvedState,
|
|
195
|
-
skipLoadingState:
|
|
280
|
+
skipLoadingState: false,
|
|
196
281
|
});
|
|
197
282
|
|
|
198
283
|
// REVALIDATE: Fetch fresh data from server
|
|
@@ -200,7 +285,7 @@ export function createNavigationBridge(
|
|
|
200
285
|
await fetchPartialUpdate(
|
|
201
286
|
url,
|
|
202
287
|
hasUsableCache
|
|
203
|
-
?
|
|
288
|
+
? cachedSegments!.map((s) => s.id)
|
|
204
289
|
: options?._skipCache
|
|
205
290
|
? [] // Action redirect: send no segments so server renders everything fresh
|
|
206
291
|
: undefined,
|
|
@@ -224,7 +309,7 @@ export function createNavigationBridge(
|
|
|
224
309
|
);
|
|
225
310
|
} catch (error) {
|
|
226
311
|
// Server-side redirect with location state: the current transaction's
|
|
227
|
-
//
|
|
312
|
+
// cleanup resets loading state. Re-navigate to the redirect
|
|
228
313
|
// target carrying the server-set state into history.pushState.
|
|
229
314
|
if (error instanceof ServerRedirect) {
|
|
230
315
|
const redirectUrl = validateRedirectOrigin(
|
|
@@ -241,8 +326,15 @@ export function createNavigationBridge(
|
|
|
241
326
|
} as NavigateOptionsInternal);
|
|
242
327
|
}
|
|
243
328
|
|
|
244
|
-
|
|
245
|
-
|
|
329
|
+
// Aborted, or superseded by a newer navigation. A superseded nav may
|
|
330
|
+
// reject with a non-AbortError (e.g. a Flight decode that fails after its
|
|
331
|
+
// signal was aborted), so check the signal too -- otherwise we would
|
|
332
|
+
// render a boundary that clobbers the newer navigation's content.
|
|
333
|
+
if (
|
|
334
|
+
(error instanceof DOMException && error.name === "AbortError") ||
|
|
335
|
+
tx.handle.signal.aborted
|
|
336
|
+
) {
|
|
337
|
+
debugLog("[Browser] Navigation aborted or superseded");
|
|
246
338
|
return;
|
|
247
339
|
}
|
|
248
340
|
|
|
@@ -259,7 +351,15 @@ export function createNavigationBridge(
|
|
|
259
351
|
return;
|
|
260
352
|
}
|
|
261
353
|
|
|
262
|
-
|
|
354
|
+
// A response we could not process (undecodable Flight body, or an
|
|
355
|
+
// unanticipated failure building the response). Surface the route's
|
|
356
|
+
// error boundary rather than let the rejection abort the navigation
|
|
357
|
+
// silently. Prefetched responses funnel here too: a failed warm-prefetch
|
|
358
|
+
// payload rejects on consumption and propagates to this catch.
|
|
359
|
+
console.error("[Browser] Unprocessable navigation response:", error);
|
|
360
|
+
emitNavigationError(onUpdate, error, url);
|
|
361
|
+
} finally {
|
|
362
|
+
tx[Symbol.dispose]();
|
|
263
363
|
}
|
|
264
364
|
},
|
|
265
365
|
|
|
@@ -269,7 +369,7 @@ export function createNavigationBridge(
|
|
|
269
369
|
async refresh(): Promise<void> {
|
|
270
370
|
eventController.abortNavigation();
|
|
271
371
|
|
|
272
|
-
|
|
372
|
+
const tx = createNavigationTransaction(
|
|
273
373
|
store,
|
|
274
374
|
eventController,
|
|
275
375
|
window.location.href,
|
|
@@ -286,6 +386,14 @@ export function createNavigationBridge(
|
|
|
286
386
|
tx.with({ url: window.location.href, replace: true, scroll: false }),
|
|
287
387
|
);
|
|
288
388
|
} catch (error) {
|
|
389
|
+
// Aborted or superseded: bail without rendering a boundary (see navigate()).
|
|
390
|
+
if (
|
|
391
|
+
(error instanceof DOMException && error.name === "AbortError") ||
|
|
392
|
+
tx.handle.signal.aborted
|
|
393
|
+
) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
289
397
|
const networkError = toNetworkError(error, {
|
|
290
398
|
url: window.location.href,
|
|
291
399
|
operation: "revalidation",
|
|
@@ -298,7 +406,14 @@ export function createNavigationBridge(
|
|
|
298
406
|
emitNetworkError(onUpdate, networkError, window.location.href);
|
|
299
407
|
return;
|
|
300
408
|
}
|
|
301
|
-
|
|
409
|
+
|
|
410
|
+
// refresh() shares the fetchPartialUpdate chokepoint with navigate()/
|
|
411
|
+
// popstate, so an unprocessable response must surface the error boundary
|
|
412
|
+
// here too rather than become an uncaught rejection.
|
|
413
|
+
console.error("[Browser] Unprocessable refresh response:", error);
|
|
414
|
+
emitNavigationError(onUpdate, error, window.location.href);
|
|
415
|
+
} finally {
|
|
416
|
+
tx[Symbol.dispose]();
|
|
302
417
|
}
|
|
303
418
|
},
|
|
304
419
|
|
|
@@ -328,6 +443,15 @@ export function createNavigationBridge(
|
|
|
328
443
|
eventController.abortAllActions();
|
|
329
444
|
}
|
|
330
445
|
|
|
446
|
+
// Popstate that exits an intercept to a non-intercept destination. The
|
|
447
|
+
// fallback fetch path below needs `leave-intercept` mode so it filters
|
|
448
|
+
// the cached @modal segment from the request and forces a re-render —
|
|
449
|
+
// otherwise a cache-miss popstate whose server response has an empty
|
|
450
|
+
// diff hits the "no changes" branch in partial-update and the modal
|
|
451
|
+
// stays on screen.
|
|
452
|
+
const isLeavingIntercept =
|
|
453
|
+
!isIntercept && currentInterceptSource !== null;
|
|
454
|
+
|
|
331
455
|
// Compute history key from URL (with intercept suffix if applicable)
|
|
332
456
|
const historyKey = generateHistoryKey(url, { intercept: isIntercept });
|
|
333
457
|
|
|
@@ -351,11 +475,22 @@ export function createNavigationBridge(
|
|
|
351
475
|
// Helper to check if streaming is in progress
|
|
352
476
|
const isStreaming = () => eventController.getState().isStreaming;
|
|
353
477
|
|
|
478
|
+
// Surface any external rotation of the rango state cookie (a server
|
|
479
|
+
// Set-Cookie, a sibling tab, a cookie clear) BEFORE reading the stale bit.
|
|
480
|
+
// The divergence observer only runs inside getRangoState() — fetch-time —
|
|
481
|
+
// so a popstate-first interaction would otherwise serve a pre-mutation
|
|
482
|
+
// page as fresh and never fetch to trigger the observer. Reading here lets
|
|
483
|
+
// the observer mark the history cache stale so getCachedSegments sees it.
|
|
484
|
+
getRangoState();
|
|
485
|
+
|
|
354
486
|
// Check if we can restore from history cache
|
|
355
487
|
const cached = store.getCachedSegments(historyKey);
|
|
356
488
|
const cachedSegments = cached?.segments;
|
|
357
489
|
const cachedHandleData = cached?.handleData;
|
|
358
|
-
|
|
490
|
+
// While an action is in flight the fence persists no stale flag, so OR it
|
|
491
|
+
// in here: a popstate during the flight serves the cached entry AND
|
|
492
|
+
// revalidates (SWR) instead of serving it as fresh.
|
|
493
|
+
const isStale = (cached?.stale ?? false) || isActionFenceActive();
|
|
359
494
|
|
|
360
495
|
if (cachedSegments && cachedSegments.length > 0) {
|
|
361
496
|
// Update store to point to this history entry
|
|
@@ -364,6 +499,12 @@ export function createNavigationBridge(
|
|
|
364
499
|
store.setCurrentUrl(url);
|
|
365
500
|
store.setPath(new URL(url).pathname);
|
|
366
501
|
|
|
502
|
+
// Restore router identity from cache so subsequent navigations
|
|
503
|
+
// don't falsely detect an app switch.
|
|
504
|
+
if (cached?.routerId) {
|
|
505
|
+
store.setRouterId?.(cached.routerId);
|
|
506
|
+
}
|
|
507
|
+
|
|
367
508
|
// Render from cache - force await to skip loading fallbacks
|
|
368
509
|
try {
|
|
369
510
|
const root = await renderSegments(cachedSegments, {
|
|
@@ -389,8 +530,16 @@ export function createNavigationBridge(
|
|
|
389
530
|
cachedHandleData,
|
|
390
531
|
params: cachedParams,
|
|
391
532
|
},
|
|
533
|
+
scroll: { restore: true, isStreaming },
|
|
392
534
|
};
|
|
393
|
-
|
|
535
|
+
// Intercept-driven popstate (entering OR leaving an intercept) only
|
|
536
|
+
// mutates the parallel slot; the main outlet shows the same content.
|
|
537
|
+
// Skip startViewTransition in those cases — same rationale as the
|
|
538
|
+
// intercept guard in partial-update.ts's hasTransition computation.
|
|
539
|
+
const hasTransition =
|
|
540
|
+
!isIntercept &&
|
|
541
|
+
!isLeavingIntercept &&
|
|
542
|
+
cachedSegments.some((s) => s.transition);
|
|
394
543
|
if (hasTransition) {
|
|
395
544
|
startTransition(() => {
|
|
396
545
|
if (addTransitionType) {
|
|
@@ -402,14 +551,11 @@ export function createNavigationBridge(
|
|
|
402
551
|
onUpdate(popstateUpdate);
|
|
403
552
|
}
|
|
404
553
|
|
|
405
|
-
// Restore scroll position for back/forward navigation
|
|
406
|
-
handleNavigationEnd({ restore: true, isStreaming });
|
|
407
|
-
|
|
408
554
|
// SWR: If stale, trigger background revalidation
|
|
409
555
|
if (isStale) {
|
|
410
556
|
debugLog("[Browser] Cache is stale, background revalidating...");
|
|
411
557
|
// Background revalidation - don't await, just fire and forget
|
|
412
|
-
const segmentIds =
|
|
558
|
+
const segmentIds = cachedSegments.map((s) => s.id);
|
|
413
559
|
|
|
414
560
|
const tx = createNavigationTransaction(
|
|
415
561
|
store,
|
|
@@ -457,7 +603,7 @@ export function createNavigationBridge(
|
|
|
457
603
|
}
|
|
458
604
|
|
|
459
605
|
// Fetch if not cached
|
|
460
|
-
|
|
606
|
+
const tx = createNavigationTransaction(store, eventController, url, {
|
|
461
607
|
replace: true,
|
|
462
608
|
});
|
|
463
609
|
|
|
@@ -474,13 +620,22 @@ export function createNavigationBridge(
|
|
|
474
620
|
intercept: isIntercept,
|
|
475
621
|
interceptSourceUrl,
|
|
476
622
|
}),
|
|
477
|
-
isIntercept
|
|
623
|
+
isIntercept
|
|
624
|
+
? { type: "navigate", interceptSourceUrl }
|
|
625
|
+
: isLeavingIntercept
|
|
626
|
+
? { type: "leave-intercept" }
|
|
627
|
+
: undefined,
|
|
478
628
|
);
|
|
479
629
|
// Restore scroll position after fetch completes
|
|
480
630
|
handleNavigationEnd({ restore: true, isStreaming });
|
|
481
631
|
} catch (error) {
|
|
482
|
-
|
|
483
|
-
|
|
632
|
+
// Aborted or superseded by a newer navigation: bail without clobbering
|
|
633
|
+
// content with a boundary (see navigate()).
|
|
634
|
+
if (
|
|
635
|
+
(error instanceof DOMException && error.name === "AbortError") ||
|
|
636
|
+
tx.handle.signal.aborted
|
|
637
|
+
) {
|
|
638
|
+
debugLog("[Browser] Popstate navigation aborted or superseded");
|
|
484
639
|
return;
|
|
485
640
|
}
|
|
486
641
|
|
|
@@ -497,7 +652,12 @@ export function createNavigationBridge(
|
|
|
497
652
|
return;
|
|
498
653
|
}
|
|
499
654
|
|
|
500
|
-
|
|
655
|
+
// Unprocessable response on a back/forward navigation: surface the
|
|
656
|
+
// error boundary instead of an uncaught rejection (see navigate()).
|
|
657
|
+
console.error("[Browser] Unprocessable popstate response:", error);
|
|
658
|
+
emitNavigationError(onUpdate, error, url);
|
|
659
|
+
} finally {
|
|
660
|
+
tx[Symbol.dispose]();
|
|
501
661
|
}
|
|
502
662
|
},
|
|
503
663
|
|
|
@@ -549,6 +709,16 @@ export function createNavigationBridge(
|
|
|
549
709
|
window.removeEventListener("pageshow", handlePageShow);
|
|
550
710
|
};
|
|
551
711
|
},
|
|
712
|
+
|
|
713
|
+
getVersion(): string | undefined {
|
|
714
|
+
return version;
|
|
715
|
+
},
|
|
716
|
+
|
|
717
|
+
updateVersion(newVersion: string): void {
|
|
718
|
+
version = newVersion;
|
|
719
|
+
setAppVersion(newVersion);
|
|
720
|
+
store.clearHistoryCache();
|
|
721
|
+
},
|
|
552
722
|
};
|
|
553
723
|
}
|
|
554
724
|
|