@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327
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 +301 -797
- package/dist/bin/rango.js +603 -145
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3750 -1160
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +96 -24
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +85 -6
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +228 -33
- package/skills/caching/SKILL.md +336 -19
- 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 +110 -4
- 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 +87 -56
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +12 -10
- package/skills/hooks/SKILL.md +73 -691
- 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 +129 -27
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +75 -19
- package/skills/layout/SKILL.md +40 -19
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +248 -10
- package/skills/middleware/SKILL.md +25 -13
- package/skills/migrate-nextjs/SKILL.md +205 -20
- package/skills/migrate-react-router/SKILL.md +59 -670
- 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 +29 -2
- package/skills/observability/SKILL.md +202 -0
- package/skills/parallel/SKILL.md +40 -10
- package/skills/ppr/SKILL.md +616 -0
- package/skills/prerender/SKILL.md +72 -60
- package/skills/rango/SKILL.md +318 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +138 -49
- package/skills/route/SKILL.md +117 -9
- package/skills/router-setup/SKILL.md +44 -9
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +776 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +28 -4
- 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 +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 -626
- 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 +74 -15
- package/skills/vercel/SKILL.md +128 -0
- 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 +0 -65
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +252 -158
- 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/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +144 -79
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +161 -73
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +183 -144
- package/src/browser/prefetch/cache.ts +242 -77
- package/src/browser/prefetch/fetch.ts +325 -69
- package/src/browser/prefetch/queue.ts +61 -12
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +58 -20
- package/src/browser/react/NavigationProvider.tsx +202 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- 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 +17 -14
- 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 +25 -3
- 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 +145 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +236 -65
- package/src/browser/types.ts +102 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +203 -154
- package/src/build/generate-route-types.ts +3 -1
- package/src/build/index.ts +11 -3
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -21
- 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 +456 -62
- 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 +128 -51
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +13 -21
- 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 +421 -58
- package/src/cache/cache-scope.ts +187 -96
- 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 +2202 -372
- 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 +180 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1127 -0
- package/src/client.rsc.tsx +41 -21
- package/src/client.tsx +33 -61
- 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 +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -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 +67 -37
- 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 +9 -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 +97 -12
- package/src/index.ts +98 -14
- 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 +32 -37
- package/src/prerender.ts +78 -10
- 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 +62 -0
- package/src/reverse.ts +65 -39
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +87 -59
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +27 -42
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +82 -59
- package/src/router/loader-resolution.ts +167 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +80 -55
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +211 -165
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- 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 +69 -79
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +298 -172
- 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 +181 -150
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +112 -67
- package/src/router/preview-match.ts +6 -2
- package/src/router/request-classification.ts +50 -69
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +115 -36
- package/src/router/router-options.ts +166 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +131 -86
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +139 -39
- package/src/router/segment-resolution/loader-mask.ts +67 -0
- package/src/router/segment-resolution/loader-snapshot.ts +251 -0
- package/src/router/segment-resolution/revalidation.ts +272 -320
- 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 +75 -0
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +105 -20
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +157 -71
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +291 -217
- 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 +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +124 -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 +99 -189
- package/src/rsc/rsc-rendering.ts +421 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +282 -116
- package/src/rsc/shell-capture.ts +1158 -0
- package/src/rsc/shell-serve.ts +150 -0
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +53 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +350 -149
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +208 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +152 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +395 -176
- package/src/ssr/index.tsx +458 -178
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +813 -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 +199 -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 +584 -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 +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 +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +19 -64
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -11
- package/src/urls/path-helper-types.ts +63 -17
- package/src/urls/path-helper.ts +22 -106
- package/src/urls/pattern-types.ts +72 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +98 -154
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +127 -86
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +96 -68
- 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 +45 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +210 -10
- 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 +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- 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 +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +384 -0
- 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 +138 -27
- package/src/vite/rango.ts +236 -138
- package/src/vite/router-discovery.ts +927 -136
- 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 +71 -43
- package/src/vite/utils/shared-utils.ts +142 -43
- 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
|
@@ -28,29 +28,49 @@ const DEFAULT_ACTION_STATE: TrackedActionState = {
|
|
|
28
28
|
// Maximum number of history entries to cache (URLs visited)
|
|
29
29
|
const HISTORY_CACHE_SIZE = 20;
|
|
30
30
|
|
|
31
|
-
// Cache entry:
|
|
32
|
-
// stale
|
|
31
|
+
// Cache entry:
|
|
32
|
+
// [url-key, segments, stale, handleData?, routerId?, navInstance?, handlesPending?]
|
|
33
|
+
// stale=true means the data may be outdated and should be revalidated on access.
|
|
34
|
+
// navInstance is the monotonic nav-instance token (see navInstance below): it
|
|
35
|
+
// identifies the per-commit visit that owns this entry. generateHistoryKey is
|
|
36
|
+
// URL-only, so A->B->A reuses the same key; the token lets a late async
|
|
37
|
+
// resolution tell its own visit's entry apart from a newer same-URL visit's, so
|
|
38
|
+
// a stale nav can never clobber a fresher one.
|
|
39
|
+
// handlesPending=true means the entry's handle data is INCOMPLETE (a deferred
|
|
40
|
+
// Meta was still pending when the user navigated away, so it never streamed). A
|
|
41
|
+
// popstate return must REVALIDATE WITH A FULL RE-RENDER (no client segment IDs)
|
|
42
|
+
// to re-stream the handles — a diff-only revalidation omits unchanged segments'
|
|
43
|
+
// handles, so the deferred Meta would never land. Cleared once the deferred Meta
|
|
44
|
+
// resolves while the entry is still owned.
|
|
33
45
|
type HistoryCacheEntry = [
|
|
34
46
|
string,
|
|
35
47
|
ResolvedSegment[],
|
|
36
48
|
boolean,
|
|
37
49
|
HandleData?,
|
|
38
50
|
string?,
|
|
51
|
+
number?,
|
|
52
|
+
boolean?,
|
|
39
53
|
];
|
|
40
54
|
|
|
41
55
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
56
|
+
* Clone the handleData CONTAINERS (the handle-name map and each segment map) so
|
|
57
|
+
* a cache entry is decoupled from the live map that eventController mutates — it
|
|
58
|
+
* adds/deletes segment keys and REPLACES bucket arrays in place. The bucket
|
|
59
|
+
* arrays themselves are shared by reference, NOT copied: a bucket array is only
|
|
60
|
+
* ever replaced wholesale (eventController.setHandleData reassigns it,
|
|
61
|
+
* resolveDeferredHandleValues builds a fresh one) and collect functions read it
|
|
62
|
+
* without mutating, so sharing is safe and skips an O(elements) copy on every
|
|
63
|
+
* cache write — the per-yield streaming hot path. This also preserves any
|
|
64
|
+
* non-serializable bucket contents (React elements, functions, etc.).
|
|
46
65
|
*/
|
|
47
|
-
function cloneHandleData(handleData: HandleData): HandleData {
|
|
66
|
+
export function cloneHandleData(handleData: HandleData): HandleData {
|
|
48
67
|
const cloned: HandleData = {};
|
|
49
68
|
for (const [handleKey, segmentMap] of Object.entries(handleData)) {
|
|
50
|
-
|
|
69
|
+
const clonedMap: Record<string, unknown[]> = {};
|
|
51
70
|
for (const [segmentId, dataArray] of Object.entries(segmentMap)) {
|
|
52
|
-
|
|
71
|
+
clonedMap[segmentId] = dataArray;
|
|
53
72
|
}
|
|
73
|
+
cloned[handleKey] = clonedMap;
|
|
54
74
|
}
|
|
55
75
|
return cloned;
|
|
56
76
|
}
|
|
@@ -130,14 +150,14 @@ export interface NavigationStoreConfig {
|
|
|
130
150
|
|
|
131
151
|
/**
|
|
132
152
|
* Enable cross-tab cache invalidation via BroadcastChannel (default: true)
|
|
133
|
-
* When cache is cleared (via server actions or
|
|
153
|
+
* When cache is cleared (via server actions or invalidateClientCache()),
|
|
134
154
|
* other tabs will also clear their cache
|
|
135
155
|
*/
|
|
136
156
|
crossTabSync?: boolean;
|
|
137
157
|
|
|
138
158
|
/**
|
|
139
159
|
* Auto-refresh when another tab mutates data on the same path (default: true)
|
|
140
|
-
* Triggered when cache is cleared via server actions or
|
|
160
|
+
* Triggered when cache is cleared via server actions or invalidateClientCache()
|
|
141
161
|
* Requires crossTabSync to be enabled
|
|
142
162
|
*/
|
|
143
163
|
crossTabAutoRefresh?: boolean;
|
|
@@ -239,6 +259,14 @@ export function createNavigationStore(
|
|
|
239
259
|
// Oldest entries (at front) are removed when over cacheSize limit
|
|
240
260
|
const historyCache: HistoryCacheEntry[] = [];
|
|
241
261
|
|
|
262
|
+
// Monotonic nav-instance token. Bumped each time a cache entry is created or
|
|
263
|
+
// replaced in cacheSegmentsForHistory (i.e. once per commit). Because
|
|
264
|
+
// generateHistoryKey is URL-only, two visits to the same URL share a key; this
|
|
265
|
+
// token gives each visit a distinct identity so a late async handle resolution
|
|
266
|
+
// can tell whether it still owns the live page / the target cache entry, and
|
|
267
|
+
// never overwrite a newer same-URL visit's state.
|
|
268
|
+
let navInstance = 0;
|
|
269
|
+
|
|
242
270
|
// Current history key (set on navigation, stored in history.state)
|
|
243
271
|
let currentHistoryKey = config?.initialHistoryKey || generateHistoryKey();
|
|
244
272
|
|
|
@@ -248,6 +276,10 @@ export function createNavigationStore(
|
|
|
248
276
|
config.initialHistoryKey,
|
|
249
277
|
config.initialSegments,
|
|
250
278
|
false,
|
|
279
|
+
undefined,
|
|
280
|
+
undefined,
|
|
281
|
+
++navInstance,
|
|
282
|
+
false,
|
|
251
283
|
]);
|
|
252
284
|
}
|
|
253
285
|
|
|
@@ -280,18 +312,17 @@ export function createNavigationStore(
|
|
|
280
312
|
/**
|
|
281
313
|
* Create a debounced function that batches rapid calls
|
|
282
314
|
*/
|
|
315
|
+
// A non-keyed notifier is the keyed one restricted to a single constant key;
|
|
316
|
+
// its own keyed instance means the "" key never collides with action keys.
|
|
283
317
|
function createDebouncedNotifier<T extends (...args: any[]) => void>(
|
|
284
318
|
fn: T,
|
|
285
319
|
ms: number = 20,
|
|
286
320
|
): T {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
fn(...args);
|
|
293
|
-
}, ms);
|
|
294
|
-
}) as T;
|
|
321
|
+
const keyed = createKeyedDebouncedNotifier(
|
|
322
|
+
(_key: string, ...args: any[]) => fn(...args),
|
|
323
|
+
ms,
|
|
324
|
+
);
|
|
325
|
+
return ((...args: Parameters<T>) => keyed("", ...args)) as T;
|
|
295
326
|
}
|
|
296
327
|
|
|
297
328
|
/**
|
|
@@ -336,12 +367,24 @@ export function createNavigationStore(
|
|
|
336
367
|
}
|
|
337
368
|
|
|
338
369
|
/**
|
|
339
|
-
* Mark
|
|
370
|
+
* Mark every history entry stale WITHOUT touching the prefetch caches or the
|
|
371
|
+
* rango state. Used by the jar-divergence observer: an external rotation has
|
|
372
|
+
* already changed the state value (so prefetch/HTTP entries strand under the
|
|
373
|
+
* retired key), and this tab must NOT re-rotate — only the history cache,
|
|
374
|
+
* which is not state-keyed, needs marking.
|
|
340
375
|
*/
|
|
341
|
-
function
|
|
376
|
+
function markHistoryStale(): void {
|
|
342
377
|
for (let i = 0; i < historyCache.length; i++) {
|
|
343
378
|
historyCache[i][2] = true;
|
|
344
379
|
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Mark all cache entries as stale (internal - does not broadcast). Also
|
|
384
|
+
* clears the prefetch caches, which rotates the rango state.
|
|
385
|
+
*/
|
|
386
|
+
function markCacheAsStaleInternal(): void {
|
|
387
|
+
markHistoryStale();
|
|
345
388
|
clearPrefetchCache();
|
|
346
389
|
}
|
|
347
390
|
|
|
@@ -554,6 +597,29 @@ export function createNavigationStore(
|
|
|
554
597
|
currentHistoryKey = key;
|
|
555
598
|
},
|
|
556
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Current nav-instance token: the instance of the most recently committed
|
|
602
|
+
* navigation (the value last written by cacheSegmentsForHistory). A late
|
|
603
|
+
* async handle resolution captures this at the start of its own nav and
|
|
604
|
+
* compares it back here to detect whether a NEWER navigation has since
|
|
605
|
+
* committed (token advanced), guarding against a stale nav writing a fresher
|
|
606
|
+
* nav's live state.
|
|
607
|
+
*/
|
|
608
|
+
getNavInstance(): number {
|
|
609
|
+
return navInstance;
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* The nav-instance token recorded on a specific cache entry, or undefined if
|
|
614
|
+
* no entry exists for that key. Because the history key is URL-only, this is
|
|
615
|
+
* how a late resolution tells "the entry I seeded is still mine" from "a
|
|
616
|
+
* newer same-URL visit replaced my entry".
|
|
617
|
+
*/
|
|
618
|
+
getCacheEntryInstance(historyKey: string): number | undefined {
|
|
619
|
+
const entry = historyCache.find(([key]) => key === historyKey);
|
|
620
|
+
return entry ? entry[5] : undefined;
|
|
621
|
+
},
|
|
622
|
+
|
|
557
623
|
/**
|
|
558
624
|
* Store segments for a history entry
|
|
559
625
|
* Updates existing entry if key exists, otherwise adds new entry
|
|
@@ -572,6 +638,11 @@ export function createNavigationStore(
|
|
|
572
638
|
? cloneHandleData(handleData)
|
|
573
639
|
: undefined;
|
|
574
640
|
|
|
641
|
+
// Each commit (create or replace) is a new nav instance. The bump happens
|
|
642
|
+
// here, exactly once per cacheSegmentsForHistory call, so getNavInstance()
|
|
643
|
+
// reflects the visit whose entry this is.
|
|
644
|
+
const instance = ++navInstance;
|
|
645
|
+
|
|
575
646
|
// Check if entry already exists and update it
|
|
576
647
|
const existingIndex = historyCache.findIndex(
|
|
577
648
|
([key]) => key === historyKey,
|
|
@@ -583,6 +654,8 @@ export function createNavigationStore(
|
|
|
583
654
|
false,
|
|
584
655
|
clonedHandleData,
|
|
585
656
|
currentRouterId,
|
|
657
|
+
instance,
|
|
658
|
+
false, // fresh commit: handles complete unless a deferred apply marks it
|
|
586
659
|
];
|
|
587
660
|
} else {
|
|
588
661
|
// Add new entry at the end (not stale)
|
|
@@ -592,6 +665,8 @@ export function createNavigationStore(
|
|
|
592
665
|
false,
|
|
593
666
|
clonedHandleData,
|
|
594
667
|
currentRouterId,
|
|
668
|
+
instance,
|
|
669
|
+
false,
|
|
595
670
|
]);
|
|
596
671
|
// Remove oldest entries if over limit
|
|
597
672
|
while (historyCache.length > cacheSize) {
|
|
@@ -610,6 +685,7 @@ export function createNavigationStore(
|
|
|
610
685
|
stale: boolean;
|
|
611
686
|
handleData?: HandleData;
|
|
612
687
|
routerId?: string;
|
|
688
|
+
handlesPending?: boolean;
|
|
613
689
|
}
|
|
614
690
|
| undefined {
|
|
615
691
|
const entry = historyCache.find(([key]) => key === historyKey);
|
|
@@ -619,6 +695,7 @@ export function createNavigationStore(
|
|
|
619
695
|
stale: entry[2],
|
|
620
696
|
handleData: entry[3],
|
|
621
697
|
routerId: entry[4],
|
|
698
|
+
handlesPending: entry[6],
|
|
622
699
|
};
|
|
623
700
|
},
|
|
624
701
|
|
|
@@ -630,11 +707,23 @@ export function createNavigationStore(
|
|
|
630
707
|
},
|
|
631
708
|
|
|
632
709
|
/**
|
|
633
|
-
* Update only the handleData for an existing
|
|
634
|
-
* Does nothing if the cache entry doesn't exist
|
|
635
|
-
*
|
|
710
|
+
* Update only the handleData (and optionally the stale flag) for an existing
|
|
711
|
+
* cache entry. Does nothing if the cache entry doesn't exist.
|
|
712
|
+
*
|
|
713
|
+
* Used to fix stale handleData after async handles processing AND to flip an
|
|
714
|
+
* entry's stale / handlesPending bits for the deferred-Meta
|
|
715
|
+
* invalidate+revalidate path: while a nav's Meta is deferred-pending its
|
|
716
|
+
* entry is marked stale + handlesPending (a popstate return then revalidates
|
|
717
|
+
* with a full re-render instead of serving the carry/seed as fresh), and once
|
|
718
|
+
* the deferred Meta resolves both are cleared. When a flag is omitted the
|
|
719
|
+
* entry's current value is preserved.
|
|
636
720
|
*/
|
|
637
|
-
updateCacheHandleData(
|
|
721
|
+
updateCacheHandleData(
|
|
722
|
+
historyKey: string,
|
|
723
|
+
handleData: HandleData,
|
|
724
|
+
stale?: boolean,
|
|
725
|
+
handlesPending?: boolean,
|
|
726
|
+
): void {
|
|
638
727
|
const existingIndex = historyCache.findIndex(
|
|
639
728
|
([key]) => key === historyKey,
|
|
640
729
|
);
|
|
@@ -645,13 +734,49 @@ export function createNavigationStore(
|
|
|
645
734
|
historyCache[existingIndex] = [
|
|
646
735
|
entry[0],
|
|
647
736
|
entry[1],
|
|
648
|
-
entry[2],
|
|
737
|
+
stale ?? entry[2], // set stale when provided, else preserve current
|
|
649
738
|
clonedHandleData,
|
|
650
739
|
entry[4], // preserve routerId
|
|
740
|
+
entry[5], // preserve navInstance (entry ownership identity)
|
|
741
|
+
handlesPending ?? entry[6], // set when provided, else preserve current
|
|
651
742
|
];
|
|
652
743
|
}
|
|
653
744
|
},
|
|
654
745
|
|
|
746
|
+
/**
|
|
747
|
+
* Owner-guarded handle-data write: locate the entry, and write ONLY when it
|
|
748
|
+
* is still owned by `ownerInstance` (the nav-instance token that seeded it).
|
|
749
|
+
* Folds the streaming hot path's separate getCacheEntryInstance() ownership
|
|
750
|
+
* probe and updateCacheHandleData() write into a SINGLE historyCache scan
|
|
751
|
+
* (processHandles calls this per yield). Semantics otherwise match
|
|
752
|
+
* updateCacheHandleData: no-op on a missing entry, clone the handleData
|
|
753
|
+
* containers, and preserve stale / handlesPending when the flag is omitted.
|
|
754
|
+
*/
|
|
755
|
+
updateCacheHandleDataIfOwned(
|
|
756
|
+
historyKey: string,
|
|
757
|
+
handleData: HandleData,
|
|
758
|
+
ownerInstance: number,
|
|
759
|
+
stale?: boolean,
|
|
760
|
+
handlesPending?: boolean,
|
|
761
|
+
): void {
|
|
762
|
+
const existingIndex = historyCache.findIndex(
|
|
763
|
+
([key]) => key === historyKey,
|
|
764
|
+
);
|
|
765
|
+
if (existingIndex === -1) return;
|
|
766
|
+
const entry = historyCache[existingIndex];
|
|
767
|
+
if (entry[5] !== ownerInstance) return;
|
|
768
|
+
const clonedHandleData = cloneHandleData(handleData);
|
|
769
|
+
historyCache[existingIndex] = [
|
|
770
|
+
entry[0],
|
|
771
|
+
entry[1],
|
|
772
|
+
stale ?? entry[2],
|
|
773
|
+
clonedHandleData,
|
|
774
|
+
entry[4],
|
|
775
|
+
entry[5],
|
|
776
|
+
handlesPending ?? entry[6],
|
|
777
|
+
];
|
|
778
|
+
},
|
|
779
|
+
|
|
655
780
|
/**
|
|
656
781
|
* Mark all cache entries as stale
|
|
657
782
|
* Called after server actions to indicate data may be outdated
|
|
@@ -660,6 +785,16 @@ export function createNavigationStore(
|
|
|
660
785
|
markCacheAsStaleInternal();
|
|
661
786
|
},
|
|
662
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Mark every history entry stale WITHOUT clearing the prefetch caches or
|
|
790
|
+
* rotating the rango state. The jar-divergence observer calls this after an
|
|
791
|
+
* external rotation has already changed the state value, so re-rotating
|
|
792
|
+
* here would ping-pong with the tab that rotated.
|
|
793
|
+
*/
|
|
794
|
+
markHistoryCacheStale(): void {
|
|
795
|
+
markHistoryStale();
|
|
796
|
+
},
|
|
797
|
+
|
|
663
798
|
/**
|
|
664
799
|
* Clear the history cache and broadcast to other tabs
|
|
665
800
|
* Use this for hard invalidation when data is definitely stale
|
|
@@ -676,14 +811,6 @@ export function createNavigationStore(
|
|
|
676
811
|
markStaleAndBroadcast();
|
|
677
812
|
},
|
|
678
813
|
|
|
679
|
-
/**
|
|
680
|
-
* Broadcast cache invalidation to other tabs without clearing local cache
|
|
681
|
-
* Used after consolidation fetch where local cache has fresh data
|
|
682
|
-
*/
|
|
683
|
-
broadcastCacheInvalidation(): void {
|
|
684
|
-
broadcastInvalidation();
|
|
685
|
-
},
|
|
686
|
-
|
|
687
814
|
/**
|
|
688
815
|
* Set the callback to invoke when cross-tab refresh is triggered
|
|
689
816
|
* Called by navigation bridge during initialization
|
|
@@ -800,42 +927,3 @@ export function createNavigationStore(
|
|
|
800
927
|
},
|
|
801
928
|
};
|
|
802
929
|
}
|
|
803
|
-
|
|
804
|
-
// Singleton store instance
|
|
805
|
-
let storeInstance: NavigationStore | null = null;
|
|
806
|
-
|
|
807
|
-
/**
|
|
808
|
-
* Initialize the global navigation store
|
|
809
|
-
*
|
|
810
|
-
* Should be called once during app initialization.
|
|
811
|
-
* Subsequent calls return the existing instance.
|
|
812
|
-
*/
|
|
813
|
-
export function initNavigationStore(
|
|
814
|
-
config?: NavigationStoreConfig,
|
|
815
|
-
): NavigationStore {
|
|
816
|
-
if (!storeInstance) {
|
|
817
|
-
storeInstance = createNavigationStore(config);
|
|
818
|
-
}
|
|
819
|
-
return storeInstance;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Get the global navigation store
|
|
824
|
-
*
|
|
825
|
-
* Throws if store hasn't been initialized.
|
|
826
|
-
*/
|
|
827
|
-
export function getNavigationStore(): NavigationStore {
|
|
828
|
-
if (!storeInstance) {
|
|
829
|
-
throw new Error(
|
|
830
|
-
"Navigation store not initialized. Call initNavigationStore first.",
|
|
831
|
-
);
|
|
832
|
-
}
|
|
833
|
-
return storeInstance;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/**
|
|
837
|
-
* Reset the store instance (for testing)
|
|
838
|
-
*/
|
|
839
|
-
export function resetNavigationStore(): void {
|
|
840
|
-
storeInstance = null;
|
|
841
|
-
}
|
|
@@ -11,9 +11,8 @@ import {
|
|
|
11
11
|
} from "./scroll-restoration.js";
|
|
12
12
|
import type { EventController, NavigationHandle } from "./event-controller.js";
|
|
13
13
|
import { debugLog } from "./logging.js";
|
|
14
|
-
import { buildHistoryState } from "./history-state.js";
|
|
14
|
+
import { buildHistoryState, pushHistoryWithIdx } from "./history-state.js";
|
|
15
15
|
|
|
16
|
-
// Re-export for consumers that import from navigation-transaction
|
|
17
16
|
export { resolveNavigationState } from "./history-state.js";
|
|
18
17
|
|
|
19
18
|
/** Check if a history state object contains location state keys. */
|
|
@@ -25,7 +24,6 @@ function hasLocationState(state: unknown): boolean {
|
|
|
25
24
|
);
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
// Polyfill Symbol.dispose for Safari and older browsers
|
|
29
27
|
if (typeof Symbol.dispose === "undefined") {
|
|
30
28
|
(Symbol as any).dispose = Symbol("Symbol.dispose");
|
|
31
29
|
}
|
|
@@ -114,7 +112,6 @@ export function createNavigationTransaction(
|
|
|
114
112
|
let committed = false;
|
|
115
113
|
const currentUrl = window.location.href;
|
|
116
114
|
|
|
117
|
-
// Start navigation in event controller (this sets loading state)
|
|
118
115
|
const handle = eventController.startNavigation(url, options);
|
|
119
116
|
|
|
120
117
|
/**
|
|
@@ -138,76 +135,50 @@ export function createNavigationTransaction(
|
|
|
138
135
|
|
|
139
136
|
const parsedUrl = new URL(url, window.location.origin);
|
|
140
137
|
|
|
141
|
-
// Generate history key from URL (with intercept suffix for separate caching)
|
|
142
138
|
const historyKey = generateHistoryKey(url, { intercept });
|
|
143
139
|
|
|
144
|
-
// For cache-only commits (stale revalidation), only update cache and return
|
|
145
|
-
// Don't touch store state or history - user may have navigated elsewhere
|
|
146
140
|
if (cacheOnly) {
|
|
147
141
|
const currentHandleData = eventController.getHandleState().data;
|
|
148
142
|
store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
|
|
149
|
-
// Complete the navigation handle so currentNavigation is cleared.
|
|
150
|
-
// Without this, the entry lingers and weakens state-machine invariants.
|
|
151
143
|
handle.complete(parsedUrl);
|
|
152
144
|
debugLog("[Browser] Cache-only commit, historyKey:", historyKey);
|
|
153
145
|
return { scroll: false };
|
|
154
146
|
}
|
|
155
147
|
|
|
156
|
-
// Save current scroll position before navigating
|
|
157
148
|
handleNavigationStart();
|
|
158
149
|
|
|
159
|
-
// Update segment state atomically
|
|
160
150
|
store.setSegmentIds(segmentIds);
|
|
161
151
|
store.setCurrentUrl(url);
|
|
162
152
|
store.setPath(parsedUrl.pathname);
|
|
163
153
|
|
|
164
154
|
store.setHistoryKey(historyKey);
|
|
165
155
|
|
|
166
|
-
// Cache segments with current handleData for this history entry
|
|
167
156
|
const currentHandleData = eventController.getHandleState().data;
|
|
168
157
|
store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
|
|
169
158
|
|
|
170
|
-
// For server actions, skip URL/history updates but still complete navigation
|
|
171
159
|
if (storeOnly) {
|
|
172
160
|
debugLog("[Browser] Store updated (action)");
|
|
173
|
-
// Complete navigation to clear loading state
|
|
174
161
|
handle.complete(parsedUrl);
|
|
175
162
|
return { scroll: false };
|
|
176
163
|
}
|
|
177
164
|
|
|
178
|
-
// Build history state - include user state, intercept info, and server-set state
|
|
179
165
|
const historyState = buildHistoryState(
|
|
180
166
|
opts.state,
|
|
181
167
|
{ intercept, sourceUrl: interceptSourceUrl },
|
|
182
168
|
serverState,
|
|
183
169
|
);
|
|
184
170
|
|
|
185
|
-
// Snapshot old state before pushState/replaceState overwrites it.
|
|
186
|
-
// Used to detect when location state is being cleared.
|
|
187
171
|
const oldState = window.history.state;
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
if (replace) {
|
|
191
|
-
window.history.replaceState(historyState, "", url);
|
|
192
|
-
} else {
|
|
193
|
-
window.history.pushState(historyState, "", url);
|
|
194
|
-
}
|
|
195
|
-
// Ensure new history entry has a scroll restoration key
|
|
173
|
+
pushHistoryWithIdx(historyState, url, replace ?? false);
|
|
196
174
|
ensureHistoryKey();
|
|
197
175
|
|
|
198
|
-
// Notify location state hooks when either old or new state carries
|
|
199
|
-
// location state. This covers both "set new state" and "clear old state"
|
|
200
|
-
// for same-page navigations where components don't remount.
|
|
201
176
|
if (hasLocationState(oldState) || hasLocationState(historyState)) {
|
|
202
177
|
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
203
178
|
}
|
|
204
179
|
|
|
205
|
-
// Complete the navigation in event controller (sets idle state, updates location)
|
|
206
180
|
handle.complete(parsedUrl);
|
|
207
181
|
|
|
208
|
-
// NOTE: Scroll is NOT handled here. The caller (partial-update.ts) handles
|
|
209
|
-
// scroll AFTER onUpdate() so React has the new content before we scroll.
|
|
210
|
-
|
|
211
182
|
debugLog(
|
|
212
183
|
"[Browser] Navigation committed, historyKey:",
|
|
213
184
|
historyKey,
|
|
@@ -221,10 +192,6 @@ export function createNavigationTransaction(
|
|
|
221
192
|
handle,
|
|
222
193
|
commit,
|
|
223
194
|
|
|
224
|
-
/**
|
|
225
|
-
* Create a bound transaction with pre-configured URL options
|
|
226
|
-
* segmentIds and segments provided at commit time (after they're resolved)
|
|
227
|
-
*/
|
|
228
195
|
with(
|
|
229
196
|
opts: Omit<CommitOptions, "segmentIds" | "segments">,
|
|
230
197
|
): BoundTransaction {
|
|
@@ -240,30 +207,16 @@ export function createNavigationTransaction(
|
|
|
240
207
|
segments: ResolvedSegment[],
|
|
241
208
|
overrides?: BoundCommitOverrides,
|
|
242
209
|
) => {
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
// Allow overrides to force replace (e.g., for intercepts)
|
|
247
|
-
const finalReplace =
|
|
248
|
-
overrides?.replace !== undefined ? overrides.replace : opts.replace;
|
|
249
|
-
// Intercept info: overrides take precedence, fallback to opts
|
|
250
|
-
const intercept =
|
|
251
|
-
overrides?.intercept !== undefined
|
|
252
|
-
? overrides.intercept
|
|
253
|
-
: opts.intercept;
|
|
210
|
+
const finalScroll = overrides?.scroll ?? opts.scroll;
|
|
211
|
+
const finalReplace = overrides?.replace ?? opts.replace;
|
|
212
|
+
const intercept = overrides?.intercept ?? opts.intercept;
|
|
254
213
|
const interceptSourceUrl =
|
|
255
|
-
overrides?.interceptSourceUrl
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
const cacheOnly =
|
|
260
|
-
overrides?.cacheOnly !== undefined
|
|
261
|
-
? overrides.cacheOnly
|
|
262
|
-
: opts.cacheOnly;
|
|
263
|
-
// User state: overrides take precedence, fallback to opts
|
|
214
|
+
overrides?.interceptSourceUrl ?? opts.interceptSourceUrl;
|
|
215
|
+
const cacheOnly = overrides?.cacheOnly ?? opts.cacheOnly;
|
|
216
|
+
// state is `unknown` (null is meaningful) so `??` would wrongly drop a
|
|
217
|
+
// null override; serverState always comes from overrides, never opts.
|
|
264
218
|
const state =
|
|
265
219
|
overrides?.state !== undefined ? overrides.state : opts.state;
|
|
266
|
-
// Server-set location state: only from overrides (set by partial-update)
|
|
267
220
|
const serverState = overrides?.serverState;
|
|
268
221
|
return commit({
|
|
269
222
|
...opts,
|
|
@@ -282,13 +235,10 @@ export function createNavigationTransaction(
|
|
|
282
235
|
},
|
|
283
236
|
|
|
284
237
|
[Symbol.dispose]() {
|
|
285
|
-
// Superseded: another navigation took over.
|
|
286
238
|
if (handle.signal.aborted) {
|
|
287
239
|
return;
|
|
288
240
|
}
|
|
289
241
|
|
|
290
|
-
// Failed (not committed): keep the target URL -- the error UI owns it.
|
|
291
|
-
// Just reset the event controller to idle.
|
|
292
242
|
if (!committed) {
|
|
293
243
|
handle[Symbol.dispose]();
|
|
294
244
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NetworkError, isNetworkError } from "../errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { RenderErrorThrower } from "../render-error-thrower.js";
|
|
3
3
|
import type { UpdateSubscriber } from "./types.js";
|
|
4
4
|
import { createElement, startTransition } from "react";
|
|
5
5
|
|
|
@@ -24,18 +24,18 @@ export function toNetworkError(
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Render an error into the segment tree via the onUpdate subscriber so the
|
|
28
|
+
* nearest error boundary catches it. Wrapped in startTransition; RenderErrorThrower
|
|
29
|
+
* throws during render (async rejections do not reach boundaries on their own).
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
function emitErrorToBoundary(
|
|
32
32
|
onUpdate: UpdateSubscriber,
|
|
33
|
-
error:
|
|
33
|
+
error: unknown,
|
|
34
34
|
pathname: string,
|
|
35
35
|
): void {
|
|
36
36
|
startTransition(() => {
|
|
37
37
|
onUpdate({
|
|
38
|
-
root: createElement(
|
|
38
|
+
root: createElement(RenderErrorThrower, { error }),
|
|
39
39
|
metadata: {
|
|
40
40
|
pathname,
|
|
41
41
|
segments: [],
|
|
@@ -45,6 +45,33 @@ export function emitNetworkError(
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Emit a NetworkError to the nearest error boundary (offline, failed fetch).
|
|
50
|
+
*/
|
|
51
|
+
export function emitNetworkError(
|
|
52
|
+
onUpdate: UpdateSubscriber,
|
|
53
|
+
error: NetworkError,
|
|
54
|
+
pathname: string,
|
|
55
|
+
): void {
|
|
56
|
+
emitErrorToBoundary(onUpdate, error, pathname);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Emit a navigation processing error to the nearest error boundary. Used when a
|
|
61
|
+
* navigation response cannot be processed (an undecodable Flight body, or any
|
|
62
|
+
* unanticipated failure while building the response) -- for both fresh and
|
|
63
|
+
* prefetched responses, since both funnel through the navigation catch. Without
|
|
64
|
+
* this, such a failure becomes an uncaught rejection that silently aborts the
|
|
65
|
+
* navigation instead of surfacing the route's error boundary.
|
|
66
|
+
*/
|
|
67
|
+
export function emitNavigationError(
|
|
68
|
+
onUpdate: UpdateSubscriber,
|
|
69
|
+
error: unknown,
|
|
70
|
+
pathname: string,
|
|
71
|
+
): void {
|
|
72
|
+
emitErrorToBoundary(onUpdate, error, pathname);
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
/**
|
|
49
76
|
* Check if an error is safe to suppress in background operations.
|
|
50
77
|
*
|