@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- 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 +131 -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 +85 -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/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- 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 +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -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 +2170 -377
- 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 +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- 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 +35 -15
- 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 +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -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 +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- 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 +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- 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 +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- 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
|
@@ -5,8 +5,8 @@ import type {
|
|
|
5
5
|
ResolvedSegment,
|
|
6
6
|
} from "./types.js";
|
|
7
7
|
import { setAppVersion } from "./app-version.js";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { isActionFenceActive } from "./action-fence.js";
|
|
9
|
+
import { getRangoState } from "./rango-state.js";
|
|
10
10
|
import * as React from "react";
|
|
11
11
|
import { startTransition } from "react";
|
|
12
12
|
import {
|
|
@@ -32,6 +32,7 @@ import { isInterceptOnlyCache } from "./intercept-utils.js";
|
|
|
32
32
|
import {
|
|
33
33
|
toNetworkError,
|
|
34
34
|
emitNetworkError,
|
|
35
|
+
emitNavigationError,
|
|
35
36
|
isBackgroundSuppressible,
|
|
36
37
|
} from "./network-error-handler.js";
|
|
37
38
|
import { debugLog } from "./logging.js";
|
|
@@ -50,13 +51,8 @@ export { createNavigationTransaction };
|
|
|
50
51
|
*/
|
|
51
52
|
export interface NavigationBridgeConfigWithController extends NavigationBridgeConfig {
|
|
52
53
|
eventController: EventController;
|
|
53
|
-
/** RSC version from initial payload metadata
|
|
54
|
+
/** RSC version from initial payload metadata. */
|
|
54
55
|
version?: string;
|
|
55
|
-
/**
|
|
56
|
-
* Live app-shell ref. When supplied, the bridge reads version/basename
|
|
57
|
-
* from this ref so cross-app navigations propagate correctly.
|
|
58
|
-
*/
|
|
59
|
-
appShellRef?: AppShellRef;
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
/**
|
|
@@ -75,46 +71,9 @@ export interface NavigationBridgeConfigWithController extends NavigationBridgeCo
|
|
|
75
71
|
export function createNavigationBridge(
|
|
76
72
|
config: NavigationBridgeConfigWithController,
|
|
77
73
|
): NavigationBridge {
|
|
78
|
-
const {
|
|
79
|
-
store,
|
|
80
|
-
client,
|
|
81
|
-
eventController,
|
|
82
|
-
onUpdate,
|
|
83
|
-
renderSegments,
|
|
84
|
-
appShellRef,
|
|
85
|
-
} = config;
|
|
74
|
+
const { store, client, eventController, onUpdate, renderSegments } = config;
|
|
86
75
|
let version = config.version;
|
|
87
76
|
|
|
88
|
-
/**
|
|
89
|
-
* Replace the active app-shell snapshot atomically. Called by the partial
|
|
90
|
-
* updater when a response's routerId indicates the navigation crossed
|
|
91
|
-
* into a different app. Runs the local-only side-effects tied to
|
|
92
|
-
* app-shell fields (app version, rango-state namespace) so the new app
|
|
93
|
-
* owns them after the swap. Theme, warmup, and prefetch TTL are
|
|
94
|
-
* document-lifetime and are NOT touched here.
|
|
95
|
-
*/
|
|
96
|
-
function applyAppShell(next: AppShell): void {
|
|
97
|
-
if (appShellRef) {
|
|
98
|
-
appShellRef.update(next);
|
|
99
|
-
}
|
|
100
|
-
if (next.version !== undefined) {
|
|
101
|
-
version = next.version;
|
|
102
|
-
setAppVersion(next.version);
|
|
103
|
-
// Use the local-only setter — initRangoState writes the shared
|
|
104
|
-
// localStorage key and fires a storage event in other tabs still in
|
|
105
|
-
// the old app. setRangoStateLocal only mutates this tab's in-memory
|
|
106
|
-
// cache and rebinds it to the target app's routerId-scoped key,
|
|
107
|
-
// preserving the "local-only, no broadcast/rotation" contract for
|
|
108
|
-
// smooth app-switch transitions.
|
|
109
|
-
setRangoStateLocal(next.version, next.routerId);
|
|
110
|
-
}
|
|
111
|
-
// Cross-app: prior cache entries belong to a different app's segments.
|
|
112
|
-
// Drop them locally only — do NOT broadcast invalidation or rotate the
|
|
113
|
-
// shared X-Rango-State token, since other tabs still in the old app are
|
|
114
|
-
// unaffected by this tab's transition.
|
|
115
|
-
store.clearHistoryCacheLocal();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
77
|
// Create shared partial updater
|
|
119
78
|
const fetchPartialUpdate = createPartialUpdater({
|
|
120
79
|
store,
|
|
@@ -122,7 +81,6 @@ export function createNavigationBridge(
|
|
|
122
81
|
onUpdate,
|
|
123
82
|
renderSegments,
|
|
124
83
|
getVersion: () => version,
|
|
125
|
-
applyAppShell,
|
|
126
84
|
});
|
|
127
85
|
|
|
128
86
|
return {
|
|
@@ -258,17 +216,20 @@ export function createNavigationBridge(
|
|
|
258
216
|
store.setInterceptSourceUrl(null);
|
|
259
217
|
}
|
|
260
218
|
|
|
261
|
-
// Before navigating away, update the source page's cache with the latest
|
|
262
|
-
// This ensures the cache has correct handleData even if handles
|
|
219
|
+
// Before navigating away, update the source page's cache with the latest
|
|
220
|
+
// handleData. This ensures the cache has correct handleData even if handles
|
|
221
|
+
// were streaming. Use updateCacheHandleData (not cacheSegmentsForHistory):
|
|
222
|
+
// the source page's segments are unchanged, so this is a handleData refresh,
|
|
223
|
+
// not a commit. Critically it PRESERVES the entry's stale flag — when the
|
|
224
|
+
// source page has a deferred Meta still pending, its entry was marked stale
|
|
225
|
+
// (invalidate-on-pending) so a popstate return revalidates; re-committing it
|
|
226
|
+
// here would reset stale to false and serve the carried (pre-resolution)
|
|
227
|
+
// title as fresh. It also leaves the nav-instance token intact.
|
|
263
228
|
const sourceHistoryKey = store.getHistoryKey();
|
|
264
229
|
const sourceCached = store.getCachedSegments(sourceHistoryKey);
|
|
265
230
|
if (sourceCached?.segments && sourceCached.segments.length > 0) {
|
|
266
231
|
const currentHandleData = eventController.getHandleState().data;
|
|
267
|
-
store.
|
|
268
|
-
sourceHistoryKey,
|
|
269
|
-
sourceCached.segments,
|
|
270
|
-
currentHandleData,
|
|
271
|
-
);
|
|
232
|
+
store.updateCacheHandleData(sourceHistoryKey, currentHandleData);
|
|
272
233
|
}
|
|
273
234
|
|
|
274
235
|
// Check if we have cached segments for target URL
|
|
@@ -368,8 +329,15 @@ export function createNavigationBridge(
|
|
|
368
329
|
} as NavigateOptionsInternal);
|
|
369
330
|
}
|
|
370
331
|
|
|
371
|
-
|
|
372
|
-
|
|
332
|
+
// Aborted, or superseded by a newer navigation. A superseded nav may
|
|
333
|
+
// reject with a non-AbortError (e.g. a Flight decode that fails after its
|
|
334
|
+
// signal was aborted), so check the signal too -- otherwise we would
|
|
335
|
+
// render a boundary that clobbers the newer navigation's content.
|
|
336
|
+
if (
|
|
337
|
+
(error instanceof DOMException && error.name === "AbortError") ||
|
|
338
|
+
tx.handle.signal.aborted
|
|
339
|
+
) {
|
|
340
|
+
debugLog("[Browser] Navigation aborted or superseded");
|
|
373
341
|
return;
|
|
374
342
|
}
|
|
375
343
|
|
|
@@ -386,7 +354,13 @@ export function createNavigationBridge(
|
|
|
386
354
|
return;
|
|
387
355
|
}
|
|
388
356
|
|
|
389
|
-
|
|
357
|
+
// A response we could not process (undecodable Flight body, or an
|
|
358
|
+
// unanticipated failure building the response). Surface the route's
|
|
359
|
+
// error boundary rather than let the rejection abort the navigation
|
|
360
|
+
// silently. Prefetched responses funnel here too: a failed warm-prefetch
|
|
361
|
+
// payload rejects on consumption and propagates to this catch.
|
|
362
|
+
console.error("[Browser] Unprocessable navigation response:", error);
|
|
363
|
+
emitNavigationError(onUpdate, error, url);
|
|
390
364
|
} finally {
|
|
391
365
|
tx[Symbol.dispose]();
|
|
392
366
|
}
|
|
@@ -415,6 +389,14 @@ export function createNavigationBridge(
|
|
|
415
389
|
tx.with({ url: window.location.href, replace: true, scroll: false }),
|
|
416
390
|
);
|
|
417
391
|
} catch (error) {
|
|
392
|
+
// Aborted or superseded: bail without rendering a boundary (see navigate()).
|
|
393
|
+
if (
|
|
394
|
+
(error instanceof DOMException && error.name === "AbortError") ||
|
|
395
|
+
tx.handle.signal.aborted
|
|
396
|
+
) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
418
400
|
const networkError = toNetworkError(error, {
|
|
419
401
|
url: window.location.href,
|
|
420
402
|
operation: "revalidation",
|
|
@@ -427,7 +409,12 @@ export function createNavigationBridge(
|
|
|
427
409
|
emitNetworkError(onUpdate, networkError, window.location.href);
|
|
428
410
|
return;
|
|
429
411
|
}
|
|
430
|
-
|
|
412
|
+
|
|
413
|
+
// refresh() shares the fetchPartialUpdate chokepoint with navigate()/
|
|
414
|
+
// popstate, so an unprocessable response must surface the error boundary
|
|
415
|
+
// here too rather than become an uncaught rejection.
|
|
416
|
+
console.error("[Browser] Unprocessable refresh response:", error);
|
|
417
|
+
emitNavigationError(onUpdate, error, window.location.href);
|
|
431
418
|
} finally {
|
|
432
419
|
tx[Symbol.dispose]();
|
|
433
420
|
}
|
|
@@ -491,11 +478,22 @@ export function createNavigationBridge(
|
|
|
491
478
|
// Helper to check if streaming is in progress
|
|
492
479
|
const isStreaming = () => eventController.getState().isStreaming;
|
|
493
480
|
|
|
481
|
+
// Surface any external rotation of the rango state cookie (a server
|
|
482
|
+
// Set-Cookie, a sibling tab, a cookie clear) BEFORE reading the stale bit.
|
|
483
|
+
// The divergence observer only runs inside getRangoState() — fetch-time —
|
|
484
|
+
// so a popstate-first interaction would otherwise serve a pre-mutation
|
|
485
|
+
// page as fresh and never fetch to trigger the observer. Reading here lets
|
|
486
|
+
// the observer mark the history cache stale so getCachedSegments sees it.
|
|
487
|
+
getRangoState();
|
|
488
|
+
|
|
494
489
|
// Check if we can restore from history cache
|
|
495
490
|
const cached = store.getCachedSegments(historyKey);
|
|
496
491
|
const cachedSegments = cached?.segments;
|
|
497
492
|
const cachedHandleData = cached?.handleData;
|
|
498
|
-
|
|
493
|
+
// While an action is in flight the fence persists no stale flag, so OR it
|
|
494
|
+
// in here: a popstate during the flight serves the cached entry AND
|
|
495
|
+
// revalidates (SWR) instead of serving it as fresh.
|
|
496
|
+
const isStale = (cached?.stale ?? false) || isActionFenceActive();
|
|
499
497
|
|
|
500
498
|
if (cachedSegments && cachedSegments.length > 0) {
|
|
501
499
|
// Update store to point to this history entry
|
|
@@ -559,8 +557,19 @@ export function createNavigationBridge(
|
|
|
559
557
|
// SWR: If stale, trigger background revalidation
|
|
560
558
|
if (isStale) {
|
|
561
559
|
debugLog("[Browser] Cache is stale, background revalidating...");
|
|
562
|
-
// Background revalidation - don't await, just fire and forget
|
|
563
|
-
|
|
560
|
+
// Background revalidation - don't await, just fire and forget.
|
|
561
|
+
// When the entry's handles are incomplete (a deferred Meta was still
|
|
562
|
+
// pending when the user navigated away — see handlesPending), send NO
|
|
563
|
+
// segment IDs so the server returns a FULL re-render with the handle
|
|
564
|
+
// stream. A normal stale revalidation sends the cached IDs and the
|
|
565
|
+
// server returns a diff-only payload that omits unchanged segments'
|
|
566
|
+
// handles, so a deferred Meta would never re-stream and the title
|
|
567
|
+
// would stay the pre-resolution carry. handlesPending is set only for
|
|
568
|
+
// the deferred-Meta-aborted case, so action/cross-tab SWR keeps the
|
|
569
|
+
// cheap diff path.
|
|
570
|
+
const segmentIds = cached?.handlesPending
|
|
571
|
+
? []
|
|
572
|
+
: cachedSegments.map((s) => s.id);
|
|
564
573
|
|
|
565
574
|
const tx = createNavigationTransaction(
|
|
566
575
|
store,
|
|
@@ -634,8 +643,13 @@ export function createNavigationBridge(
|
|
|
634
643
|
// Restore scroll position after fetch completes
|
|
635
644
|
handleNavigationEnd({ restore: true, isStreaming });
|
|
636
645
|
} catch (error) {
|
|
637
|
-
|
|
638
|
-
|
|
646
|
+
// Aborted or superseded by a newer navigation: bail without clobbering
|
|
647
|
+
// content with a boundary (see navigate()).
|
|
648
|
+
if (
|
|
649
|
+
(error instanceof DOMException && error.name === "AbortError") ||
|
|
650
|
+
tx.handle.signal.aborted
|
|
651
|
+
) {
|
|
652
|
+
debugLog("[Browser] Popstate navigation aborted or superseded");
|
|
639
653
|
return;
|
|
640
654
|
}
|
|
641
655
|
|
|
@@ -652,7 +666,10 @@ export function createNavigationBridge(
|
|
|
652
666
|
return;
|
|
653
667
|
}
|
|
654
668
|
|
|
655
|
-
|
|
669
|
+
// Unprocessable response on a back/forward navigation: surface the
|
|
670
|
+
// error boundary instead of an uncaught rejection (see navigate()).
|
|
671
|
+
console.error("[Browser] Unprocessable popstate response:", error);
|
|
672
|
+
emitNavigationError(onUpdate, error, url);
|
|
656
673
|
} finally {
|
|
657
674
|
tx[Symbol.dispose]();
|
|
658
675
|
}
|
|
@@ -716,10 +733,6 @@ export function createNavigationBridge(
|
|
|
716
733
|
setAppVersion(newVersion);
|
|
717
734
|
store.clearHistoryCache();
|
|
718
735
|
},
|
|
719
|
-
|
|
720
|
-
updateAppShell(next: AppShell): void {
|
|
721
|
-
applyAppShell(next);
|
|
722
|
-
},
|
|
723
736
|
};
|
|
724
737
|
}
|
|
725
738
|
|
|
@@ -8,21 +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,
|
|
18
20
|
handleReloadHeader,
|
|
19
21
|
teeWithCompletion,
|
|
22
|
+
isForeignRouterId,
|
|
20
23
|
} from "./response-adapter.js";
|
|
21
24
|
import {
|
|
22
25
|
buildPrefetchKey,
|
|
23
26
|
buildSourceKey,
|
|
24
27
|
consumeInflightPrefetch,
|
|
25
28
|
consumePrefetch,
|
|
29
|
+
type DecodedPrefetch,
|
|
26
30
|
} from "./prefetch/cache.js";
|
|
27
31
|
|
|
28
32
|
/**
|
|
@@ -106,31 +110,38 @@ export function createNavigationClient(
|
|
|
106
110
|
// server-action invalidation) auto-invalidates both scopes.
|
|
107
111
|
// Skip cache for stale revalidation (needs fresh data), HMR (needs
|
|
108
112
|
// fresh modules), and intercept contexts (source-dependent responses).
|
|
109
|
-
|
|
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();
|
|
110
121
|
const rangoState = getRangoState();
|
|
111
122
|
const wildcardKey = buildPrefetchKey(rangoState, fetchUrl);
|
|
112
123
|
const cacheKey = buildSourceKey(rangoState, previousUrl, fetchUrl);
|
|
113
124
|
|
|
114
|
-
let
|
|
125
|
+
let cachedEntry: DecodedPrefetch | null = null;
|
|
115
126
|
let hitKey: string | null = null;
|
|
116
127
|
if (canUsePrefetch) {
|
|
117
|
-
|
|
118
|
-
if (
|
|
128
|
+
cachedEntry = consumePrefetch(cacheKey);
|
|
129
|
+
if (cachedEntry) {
|
|
119
130
|
hitKey = cacheKey;
|
|
120
131
|
} else {
|
|
121
|
-
|
|
122
|
-
if (
|
|
132
|
+
cachedEntry = consumePrefetch(wildcardKey);
|
|
133
|
+
if (cachedEntry) hitKey = wildcardKey;
|
|
123
134
|
}
|
|
124
135
|
}
|
|
125
136
|
|
|
126
|
-
let
|
|
127
|
-
if (canUsePrefetch && !
|
|
128
|
-
|
|
129
|
-
if (
|
|
137
|
+
let inflightEntryPromise: Promise<DecodedPrefetch | null> | null = null;
|
|
138
|
+
if (canUsePrefetch && !cachedEntry) {
|
|
139
|
+
inflightEntryPromise = consumeInflightPrefetch(cacheKey);
|
|
140
|
+
if (inflightEntryPromise) {
|
|
130
141
|
hitKey = cacheKey;
|
|
131
142
|
} else {
|
|
132
|
-
|
|
133
|
-
if (
|
|
143
|
+
inflightEntryPromise = consumeInflightPrefetch(wildcardKey);
|
|
144
|
+
if (inflightEntryPromise) hitKey = wildcardKey;
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
// Track when the stream completes
|
|
@@ -180,6 +191,19 @@ export function createNavigationClient(
|
|
|
180
191
|
throw new ServerRedirect(redirect.url, undefined);
|
|
181
192
|
}
|
|
182
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
|
+
|
|
183
207
|
return response;
|
|
184
208
|
};
|
|
185
209
|
|
|
@@ -192,9 +216,18 @@ export function createNavigationClient(
|
|
|
192
216
|
}
|
|
193
217
|
|
|
194
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 }),
|
|
195
224
|
headers: {
|
|
196
225
|
"X-RSC-Router-Client-Path": previousUrl,
|
|
197
|
-
|
|
226
|
+
// Reuse the single per-operation read (see rangoState above): the
|
|
227
|
+
// cache-key lookup and this header must agree on one value, and the
|
|
228
|
+
// cookie read has side effects (external-rotation notify) we do not
|
|
229
|
+
// want to fire twice per navigation.
|
|
230
|
+
"X-Rango-State": rangoState,
|
|
198
231
|
...(tx && { "X-RSC-Router-Request-Id": tx.requestId }),
|
|
199
232
|
...(interceptSourceUrl && {
|
|
200
233
|
"X-RSC-Router-Intercept-Source": interceptSourceUrl,
|
|
@@ -217,29 +250,39 @@ export function createNavigationClient(
|
|
|
217
250
|
});
|
|
218
251
|
};
|
|
219
252
|
|
|
220
|
-
|
|
253
|
+
// A warm prefetch hit returns its eagerly-decoded payload directly: the
|
|
254
|
+
// route's chunks were imported during the prefetch, so this click runs
|
|
255
|
+
// no decode and no network. Only the fresh path runs createFromFetch and
|
|
256
|
+
// resolves the local streamComplete (via doFreshFetch's teeWithCompletion
|
|
257
|
+
// and the control-header short-circuits in validateRscHeaders).
|
|
258
|
+
const freshResult = (): {
|
|
259
|
+
payload: Promise<RscPayload>;
|
|
260
|
+
streamComplete: Promise<void>;
|
|
261
|
+
} => ({
|
|
262
|
+
payload: deps.createFromFetch<RscPayload>(doFreshFetch()),
|
|
263
|
+
streamComplete,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
let payloadPromise: Promise<RscPayload>;
|
|
267
|
+
let streamCompletePromise: Promise<void>;
|
|
268
|
+
// True only for a prefetch-cache hit whose stream had already fully drained
|
|
269
|
+
// (complete === true). A still-streaming hit and the fresh path stay false,
|
|
270
|
+
// so only a fully-warmed prefetch commits in a transition (no fallback flash).
|
|
271
|
+
let fullyPrefetched = false;
|
|
221
272
|
|
|
222
|
-
if (
|
|
273
|
+
if (cachedEntry) {
|
|
223
274
|
if (tx) {
|
|
224
|
-
browserDebugLog(tx, "prefetch cache hit", {
|
|
275
|
+
browserDebugLog(tx, "prefetch cache hit (warm)", {
|
|
225
276
|
key: hitKey,
|
|
226
277
|
wildcard: hitKey === wildcardKey,
|
|
227
278
|
});
|
|
228
279
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
() => {
|
|
236
|
-
if (tx) browserDebugLog(tx, "stream complete (from cache)");
|
|
237
|
-
resolveStreamComplete();
|
|
238
|
-
},
|
|
239
|
-
signal,
|
|
240
|
-
);
|
|
241
|
-
});
|
|
242
|
-
} else if (inflightResponsePromise) {
|
|
280
|
+
payloadPromise = cachedEntry.payload;
|
|
281
|
+
streamCompletePromise = cachedEntry.streamComplete;
|
|
282
|
+
// Only a hit whose stream already fully drained is "fully prefetched";
|
|
283
|
+
// a still-streaming hit must keep streaming its fallbacks like a cold load.
|
|
284
|
+
fullyPrefetched = cachedEntry.complete;
|
|
285
|
+
} else if (inflightEntryPromise) {
|
|
243
286
|
if (tx) {
|
|
244
287
|
browserDebugLog(tx, "reusing inflight prefetch", {
|
|
245
288
|
key: hitKey,
|
|
@@ -247,51 +290,49 @@ export function createNavigationClient(
|
|
|
247
290
|
});
|
|
248
291
|
}
|
|
249
292
|
const adoptedViaWildcard = hitKey === wildcardKey;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
return doFreshFetch();
|
|
293
|
+
const entry = await inflightEntryPromise;
|
|
294
|
+
if (!entry) {
|
|
295
|
+
if (tx) {
|
|
296
|
+
browserDebugLog(tx, "inflight prefetch unavailable, refetching");
|
|
256
297
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
//
|
|
261
|
-
// a different source page. Discard and refetch.
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
browserDebugLog(
|
|
268
|
-
tx,
|
|
269
|
-
"wildcard inflight turned out source-scoped, refetching",
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
return doFreshFetch();
|
|
298
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
299
|
+
freshResult());
|
|
300
|
+
} else if (adoptedViaWildcard && entry.scope === "source") {
|
|
301
|
+
// A wildcard-adopted inflight that turned out source-scoped was
|
|
302
|
+
// built for a different source page. Discard and refetch.
|
|
303
|
+
if (tx) {
|
|
304
|
+
browserDebugLog(
|
|
305
|
+
tx,
|
|
306
|
+
"wildcard inflight turned out source-scoped, refetching",
|
|
307
|
+
);
|
|
273
308
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
resolveStreamComplete();
|
|
285
|
-
},
|
|
286
|
-
signal,
|
|
287
|
-
);
|
|
288
|
-
});
|
|
309
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
310
|
+
freshResult());
|
|
311
|
+
} else {
|
|
312
|
+
payloadPromise = entry.payload;
|
|
313
|
+
streamCompletePromise = entry.streamComplete;
|
|
314
|
+
// Adopted inflight is normally still streaming (false), but read the
|
|
315
|
+
// flag in case it completed between publish and adoption.
|
|
316
|
+
fullyPrefetched = entry.complete;
|
|
317
|
+
}
|
|
289
318
|
} else {
|
|
290
|
-
|
|
319
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
320
|
+
freshResult());
|
|
291
321
|
}
|
|
292
322
|
|
|
293
323
|
try {
|
|
294
|
-
|
|
324
|
+
// [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. Times how long the RSC
|
|
325
|
+
// payload ROOT takes to resolve: ~full-stream duration means the root
|
|
326
|
+
// model is not flushed early (server/runtime buffering, e.g. wrangler
|
|
327
|
+
// dev gzip); fast means the block, if any, is downstream in render.
|
|
328
|
+
const vtDebugStart = isBrowserDebugEnabled() ? performance.now() : 0;
|
|
329
|
+
const payload = await payloadPromise;
|
|
330
|
+
if (isBrowserDebugEnabled()) {
|
|
331
|
+
debugLog("[VT-DIAG] payloadResolved", {
|
|
332
|
+
ms: Math.round(performance.now() - vtDebugStart),
|
|
333
|
+
isPartial: payload.metadata?.isPartial,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
295
336
|
|
|
296
337
|
if (tx) {
|
|
297
338
|
browserDebugLog(tx, "response received", {
|
|
@@ -300,7 +341,11 @@ export function createNavigationClient(
|
|
|
300
341
|
diffCount: payload.metadata?.diff?.length ?? 0,
|
|
301
342
|
});
|
|
302
343
|
}
|
|
303
|
-
return {
|
|
344
|
+
return {
|
|
345
|
+
payload,
|
|
346
|
+
streamComplete: streamCompletePromise,
|
|
347
|
+
fullyPrefetched,
|
|
348
|
+
};
|
|
304
349
|
} catch (error) {
|
|
305
350
|
// Convert network-level errors to NetworkError for proper handling
|
|
306
351
|
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
|
+
}
|