@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
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./merge-segment-loaders.js";
|
|
7
7
|
import { assertSegmentStructure } from "./segment-structure-assert.js";
|
|
8
8
|
import { splitInterceptSegments } from "./intercept-utils.js";
|
|
9
|
-
import { debugLog } from "./logging.js";
|
|
9
|
+
import { debugLog, IS_BROWSER_DEBUG } from "./logging.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Determines the merging behavior for segment reconciliation.
|
|
@@ -87,15 +87,17 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
87
87
|
input.cachedSegments.forEach((s) => cachedSegments.set(s.id, s));
|
|
88
88
|
|
|
89
89
|
const diffSet = new Set(diff);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
if (IS_BROWSER_DEBUG) {
|
|
91
|
+
debugLog(
|
|
92
|
+
`[reconcile] actor=${actor}, matched=${matched.length}, diff=${diff.length}`,
|
|
93
|
+
);
|
|
94
|
+
debugLog(
|
|
95
|
+
`[reconcile] server segments: ${[...serverSegments.keys()].join(", ")}`,
|
|
96
|
+
);
|
|
97
|
+
debugLog(
|
|
98
|
+
`[reconcile] cached segments: ${[...cachedSegments.keys()].join(", ")}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
99
101
|
|
|
100
102
|
const segments = matched
|
|
101
103
|
.map((segId: string) => {
|
|
@@ -106,9 +108,11 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
106
108
|
const inDiff = diffSet.has(segId);
|
|
107
109
|
// Merge partial loader data when server returns fewer loaders than cached
|
|
108
110
|
if (shouldMergeLoaders && needsLoaderMerge(fromServer, fromCache)) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
if (IS_BROWSER_DEBUG) {
|
|
112
|
+
debugLog(
|
|
113
|
+
`[reconcile] ${segId}: MERGE loaders (server partial, ${inDiff ? "in diff" : "not in diff"})`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
112
116
|
return mergeSegmentLoaders(fromServer, fromCache);
|
|
113
117
|
}
|
|
114
118
|
|
|
@@ -159,14 +163,18 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
159
163
|
// above fails to preserve a value it should have.
|
|
160
164
|
assertSegmentStructure(fromCache, merged, context);
|
|
161
165
|
|
|
166
|
+
if (IS_BROWSER_DEBUG) {
|
|
167
|
+
debugLog(
|
|
168
|
+
`[reconcile] ${segId}: SERVER+CACHE merge (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, component=${fromServer.component === null ? "null→cached" : "server"})`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return merged;
|
|
172
|
+
}
|
|
173
|
+
if (IS_BROWSER_DEBUG) {
|
|
162
174
|
debugLog(
|
|
163
|
-
`[reconcile] ${segId}: SERVER
|
|
175
|
+
`[reconcile] ${segId}: SERVER only (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, no cache entry)`,
|
|
164
176
|
);
|
|
165
|
-
return merged;
|
|
166
177
|
}
|
|
167
|
-
debugLog(
|
|
168
|
-
`[reconcile] ${segId}: SERVER only (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, no cache entry)`,
|
|
169
|
-
);
|
|
170
178
|
return fromServer;
|
|
171
179
|
}
|
|
172
180
|
|
|
@@ -180,9 +188,11 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
180
188
|
return fromCache;
|
|
181
189
|
}
|
|
182
190
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
if (IS_BROWSER_DEBUG) {
|
|
192
|
+
debugLog(
|
|
193
|
+
`[reconcile] ${segId}: CACHE only (not from server, type=${fromCache.type}, component=${fromCache.component != null ? "yes" : "null"})`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
186
196
|
|
|
187
197
|
// Return the cached segment as-is, regardless of actor. We used to clear
|
|
188
198
|
// truthy `loading` here to prevent a stale Suspense fallback from
|
|
@@ -4,13 +4,15 @@ import type {
|
|
|
4
4
|
RscPayload,
|
|
5
5
|
} from "./types.js";
|
|
6
6
|
import { createPartialUpdater } from "./partial-update.js";
|
|
7
|
+
import { enterActionFence, exitActionFence } from "./action-fence.js";
|
|
8
|
+
import { KEEP_CACHE_HEADER } from "./cookie-name.js";
|
|
7
9
|
import { createNavigationTransaction } from "./navigation-transaction.js";
|
|
8
10
|
import {
|
|
9
11
|
reconcileSegments,
|
|
10
12
|
reconcileErrorSegments,
|
|
11
13
|
} from "./segment-reconciler.js";
|
|
12
14
|
import { startTransition } from "react";
|
|
13
|
-
import type { EventController } from "./event-controller.js";
|
|
15
|
+
import type { EventController, ActionHandle } from "./event-controller.js";
|
|
14
16
|
import {
|
|
15
17
|
toNetworkError,
|
|
16
18
|
emitNetworkError,
|
|
@@ -21,12 +23,16 @@ import {
|
|
|
21
23
|
isBrowserDebugEnabled,
|
|
22
24
|
startBrowserTransaction,
|
|
23
25
|
} from "./logging.js";
|
|
24
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
validateRedirectOrigin,
|
|
28
|
+
validateExternalRedirect,
|
|
29
|
+
} from "./validate-redirect-origin.js";
|
|
25
30
|
import {
|
|
26
31
|
extractRscHeaderUrl,
|
|
27
32
|
emptyResponse,
|
|
28
33
|
handleReloadHeader,
|
|
29
34
|
teeWithCompletion,
|
|
35
|
+
isForeignRouterId,
|
|
30
36
|
} from "./response-adapter.js";
|
|
31
37
|
import { mergeLocationState } from "./history-state.js";
|
|
32
38
|
import { classifyActionOutcome } from "./action-coordinator.js";
|
|
@@ -52,6 +58,27 @@ export interface ServerActionBridgeConfigWithController extends ServerActionBrid
|
|
|
52
58
|
) => Promise<void>;
|
|
53
59
|
}
|
|
54
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Merge an action's location-state payload into history.state, restricted to
|
|
63
|
+
* the keys this action is entitled to write. claimLocationState enforces
|
|
64
|
+
* last-initiated-wins for same-key concurrent writes (a later-initiated sibling
|
|
65
|
+
* keeps its value even if this action's response settles afterward); distinct
|
|
66
|
+
* keys from every concurrent action survive. Arbitration is scoped to the
|
|
67
|
+
* action's cohort (captured at startAction), so a newer action on another entry
|
|
68
|
+
* cannot suppress this one. No-op when nothing was set or all keys were already
|
|
69
|
+
* claimed by a later-initiated sibling in the cohort.
|
|
70
|
+
*/
|
|
71
|
+
function applyActionLocationState(
|
|
72
|
+
handle: ActionHandle,
|
|
73
|
+
locationState: Record<string, unknown> | undefined,
|
|
74
|
+
): void {
|
|
75
|
+
if (!locationState) return;
|
|
76
|
+
const winning = handle.claimLocationState(locationState);
|
|
77
|
+
if (Object.keys(winning).length > 0) {
|
|
78
|
+
mergeLocationState(winning);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
55
82
|
/**
|
|
56
83
|
* Create a server action bridge for handling RSC server actions
|
|
57
84
|
*
|
|
@@ -80,6 +107,9 @@ export function createServerActionBridge(
|
|
|
80
107
|
|
|
81
108
|
// SPA-navigate when onNavigate is set, else hard-reload. state is omitted (not
|
|
82
109
|
// passed as undefined) to match the header path's prior call shape.
|
|
110
|
+
// Callers pass an already same-origin-validated url; the hard-reload fallback
|
|
111
|
+
// re-validates defensively so this leaf cannot become an open redirect if a
|
|
112
|
+
// future caller forgets (the SPA path validates inside the navigation bridge).
|
|
83
113
|
async function dispatchRedirect(url: string, state?: unknown): Promise<void> {
|
|
84
114
|
if (onNavigate) {
|
|
85
115
|
await onNavigate(url, {
|
|
@@ -88,7 +118,10 @@ export function createServerActionBridge(
|
|
|
88
118
|
_skipCache: true,
|
|
89
119
|
});
|
|
90
120
|
} else {
|
|
91
|
-
window.location.
|
|
121
|
+
const safe = validateRedirectOrigin(url, window.location.origin);
|
|
122
|
+
if (safe) {
|
|
123
|
+
window.location.href = safe;
|
|
124
|
+
}
|
|
92
125
|
}
|
|
93
126
|
}
|
|
94
127
|
|
|
@@ -153,14 +186,57 @@ export function createServerActionBridge(
|
|
|
153
186
|
const locationKey = window.history.state?.key;
|
|
154
187
|
log("action start", { id, argsCount: args.length });
|
|
155
188
|
|
|
156
|
-
// Start action in event controller - handles lifecycle tracking
|
|
157
|
-
|
|
189
|
+
// Start action in event controller - handles lifecycle tracking. The
|
|
190
|
+
// current history key is the action's cohort: location-state arbitration is
|
|
191
|
+
// scoped to it so a later action on a different entry cannot suppress this
|
|
192
|
+
// one (and vice versa).
|
|
193
|
+
const handle = eventController.startAction(id, args, locationKey);
|
|
194
|
+
// Whether the action's response carried the keepClientCache() directive.
|
|
195
|
+
// Set when the response arrives; gates the deferred invalidation below.
|
|
196
|
+
let keepCache = false;
|
|
197
|
+
// Whether a Response actually settled from the network (the server saw the
|
|
198
|
+
// request). Set true as the first statement in the fetch .then() below.
|
|
199
|
+
// Gates the automatic invalidation: a pre-dispatch failure (encodeReply
|
|
200
|
+
// throw or a fetch rejection — server unreachable/DNS/connection refused)
|
|
201
|
+
// leaves this false, so finalizeAction() must NOT invalidate or broadcast —
|
|
202
|
+
// nothing reached the server, so nothing could have mutated. A failed Flight
|
|
203
|
+
// DECODE after the response arrived keeps it true (the mutation may have
|
|
204
|
+
// committed, so invalidating the now-possibly-stale client cache is correct).
|
|
205
|
+
let responseReceived = false;
|
|
206
|
+
// Single deferred invalidation + fence release, run exactly ONCE however the
|
|
207
|
+
// action terminates (normal, redirect, error, abort, intercept, concurrent).
|
|
208
|
+
// This replaces main's eager clear at action start: every directive-free
|
|
209
|
+
// action invalidates once; keepClientCache() suppresses only the automatic
|
|
210
|
+
// invalidation, so a concurrent directive-free action still invalidates via
|
|
211
|
+
// its own latch. Latched so the finally AND the early SPA-redirect returns
|
|
212
|
+
// (whose Flight stream never settles) can both call it safely.
|
|
213
|
+
let actionFinalized = false;
|
|
214
|
+
// skipInvalidation: the version-mismatch reload terminal released nothing
|
|
215
|
+
// server-side, so it releases the fence without invalidating.
|
|
216
|
+
const finalizeAction = (skipInvalidation = false): void => {
|
|
217
|
+
if (actionFinalized) return;
|
|
218
|
+
actionFinalized = true;
|
|
219
|
+
// finally so a throw in invalidation cannot leak the fence (latch is set).
|
|
220
|
+
try {
|
|
221
|
+
// responseReceived gates the automatic invalidation: a pre-dispatch
|
|
222
|
+
// failure (serialize throw / fetch reject) never reached the server, so
|
|
223
|
+
// marking the cache stale + broadcasting cross-tab would be spurious.
|
|
224
|
+
if (responseReceived && !keepCache && !skipInvalidation) {
|
|
225
|
+
store.markCacheAsStaleAndBroadcast();
|
|
226
|
+
}
|
|
227
|
+
} finally {
|
|
228
|
+
exitActionFence();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
158
231
|
try {
|
|
159
232
|
const segmentState = store.getSegmentState();
|
|
160
233
|
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
store
|
|
234
|
+
// Raise the action fence (replaces the old eager clear). Nothing is wiped,
|
|
235
|
+
// rotated, or broadcast yet: navigations during the flight fetch fresh
|
|
236
|
+
// (no-store) and popstate is treated as SWR, but the decision to
|
|
237
|
+
// invalidate is deferred to the response so a no-op action (keepClientCache)
|
|
238
|
+
// can leave the caches and the jar untouched.
|
|
239
|
+
enterActionFence();
|
|
164
240
|
|
|
165
241
|
// Create temporary references for serialization
|
|
166
242
|
const temporaryReferences = deps.createTemporaryReferenceSet();
|
|
@@ -232,15 +308,32 @@ export function createServerActionBridge(
|
|
|
232
308
|
body: encodedBody,
|
|
233
309
|
signal: fetchAbort.signal,
|
|
234
310
|
}).then(async (response) => {
|
|
311
|
+
// A settled fetch promise means the request reached the server and a
|
|
312
|
+
// Response came back (true for 2xx, 4xx, AND 5xx — fetch only rejects
|
|
313
|
+
// on network-layer failure, never on HTTP status). Record it as the
|
|
314
|
+
// first statement so every downstream terminal can invalidate; a
|
|
315
|
+
// pre-dispatch failure never gets here and stays gated out.
|
|
316
|
+
responseReceived = true;
|
|
235
317
|
// Response arrived — disconnect fetch abort from handle abort so
|
|
236
318
|
// abortAllActions() doesn't disrupt the in-progress Flight stream.
|
|
237
319
|
handle.signal.removeEventListener("abort", onHandleAbort);
|
|
238
320
|
|
|
321
|
+
// Did the action call keepClientCache()? If so the deferred invalidation
|
|
322
|
+
// below is suppressed for THIS action (a concurrent directive-free
|
|
323
|
+
// action still invalidates via its own response).
|
|
324
|
+
keepCache = response.headers.get(KEEP_CACHE_HEADER) === "1";
|
|
325
|
+
|
|
239
326
|
// Check for version mismatch - server wants us to reload
|
|
240
327
|
const reloadResult = handleReloadHeader(response, {
|
|
241
328
|
onBlocked: resolveStreamComplete,
|
|
242
|
-
onReload: (url) =>
|
|
243
|
-
log("version mismatch on action, reloading", { reloadUrl: url })
|
|
329
|
+
onReload: (url) => {
|
|
330
|
+
log("version mismatch on action, reloading", { reloadUrl: url });
|
|
331
|
+
// Never-settling terminal (navigates away), so the finally never
|
|
332
|
+
// runs: release the fence here. skipInvalidation — the mismatch
|
|
333
|
+
// short-circuits the action server-side, so nothing mutated and a
|
|
334
|
+
// broadcast would only risk hard-reloading a sibling mid-task.
|
|
335
|
+
finalizeAction(true);
|
|
336
|
+
},
|
|
244
337
|
});
|
|
245
338
|
if (reloadResult) return reloadResult;
|
|
246
339
|
|
|
@@ -252,6 +345,10 @@ export function createServerActionBridge(
|
|
|
252
345
|
if (redirect && redirect !== "blocked" && !handle.signal.aborted) {
|
|
253
346
|
log("action simple redirect", { url: redirect.url });
|
|
254
347
|
handle.complete(undefined);
|
|
348
|
+
// This path returns a never-settling promise, so the finally never
|
|
349
|
+
// runs: invalidate + release the fence here (the mutation committed
|
|
350
|
+
// and we're navigating away). Latched, so the finally is a no-op.
|
|
351
|
+
finalizeAction();
|
|
255
352
|
await dispatchRedirect(redirect.url);
|
|
256
353
|
return new Promise<Response>(() => {});
|
|
257
354
|
}
|
|
@@ -260,6 +357,29 @@ export function createServerActionBridge(
|
|
|
260
357
|
return emptyResponse();
|
|
261
358
|
}
|
|
262
359
|
|
|
360
|
+
// Integrity check (pre-decode): a foreign app's action response must
|
|
361
|
+
// not be decoded + applied here. This is the one decode-and-apply path
|
|
362
|
+
// the post-decode partial-update guard does NOT cover (the action
|
|
363
|
+
// bridge has its own createFromFetch -> onUpdate). Ordered after the
|
|
364
|
+
// reload/redirect handlers, which steer control responses first.
|
|
365
|
+
// Reloads via window.location.reload() rather than navigating to a
|
|
366
|
+
// target (as the navigation-client guard does): an action has no
|
|
367
|
+
// navigation target, so reloading the current URL re-syncs the
|
|
368
|
+
// document against the server-applied action effect.
|
|
369
|
+
if (
|
|
370
|
+
!handle.signal.aborted &&
|
|
371
|
+
isForeignRouterId(response, store.getRouterId?.())
|
|
372
|
+
) {
|
|
373
|
+
log("action router id mismatch, reloading to re-sync");
|
|
374
|
+
handle.complete(undefined);
|
|
375
|
+
resolveStreamComplete();
|
|
376
|
+
// Never-settling return: release the fence before the reload (the
|
|
377
|
+
// reload resets module state anyway, but stay balanced). Latched.
|
|
378
|
+
finalizeAction();
|
|
379
|
+
window.location.reload();
|
|
380
|
+
return new Promise<Response>(() => {});
|
|
381
|
+
}
|
|
382
|
+
|
|
263
383
|
// Start streaming immediately when response arrives
|
|
264
384
|
if (!handle.signal.aborted) {
|
|
265
385
|
streamingToken = handle.startStreaming();
|
|
@@ -330,6 +450,27 @@ export function createServerActionBridge(
|
|
|
330
450
|
// Check handle.signal.aborted to avoid redirecting from a stale action
|
|
331
451
|
// when the user has already navigated away.
|
|
332
452
|
if (metadata?.redirect && !handle.signal.aborted) {
|
|
453
|
+
// Explicit off-host redirect (redirect(url, { external: true })):
|
|
454
|
+
// hard-navigate, but still scheme-validate (http/https only). external
|
|
455
|
+
// waives the same-origin check, NOT scheme safety, so a forged payload
|
|
456
|
+
// carrying a javascript:/data: URL cannot script via location.assign.
|
|
457
|
+
if (metadata.redirect.external) {
|
|
458
|
+
const externalUrl = validateExternalRedirect(
|
|
459
|
+
metadata.redirect.url,
|
|
460
|
+
window.location.origin,
|
|
461
|
+
);
|
|
462
|
+
if (!externalUrl) {
|
|
463
|
+
log("blocked external action redirect payload", {
|
|
464
|
+
url: metadata.redirect.url,
|
|
465
|
+
});
|
|
466
|
+
handle.complete(returnValue?.data);
|
|
467
|
+
return returnValue?.data;
|
|
468
|
+
}
|
|
469
|
+
log("external action redirect", { url: externalUrl });
|
|
470
|
+
handle.complete(returnValue?.data);
|
|
471
|
+
window.location.assign(externalUrl);
|
|
472
|
+
return returnValue?.data;
|
|
473
|
+
}
|
|
333
474
|
const redirectUrl = validateRedirectOrigin(
|
|
334
475
|
metadata.redirect.url,
|
|
335
476
|
window.location.origin,
|
|
@@ -417,18 +558,15 @@ export function createServerActionBridge(
|
|
|
417
558
|
return undefined;
|
|
418
559
|
}
|
|
419
560
|
|
|
420
|
-
// Update UI with error boundary
|
|
421
|
-
startTransition(() => {
|
|
422
|
-
onUpdate({ root: errorTree, metadata: metadata! });
|
|
423
|
-
});
|
|
424
|
-
|
|
425
561
|
// Update segment tracking to exclude error segment IDs
|
|
426
562
|
const errorSegmentIds = new Set(diff);
|
|
427
563
|
const segmentIdsAfterError = segmentState.currentSegmentIds.filter(
|
|
428
564
|
(id) => !errorSegmentIds.has(id),
|
|
429
565
|
);
|
|
430
566
|
|
|
431
|
-
//
|
|
567
|
+
// Cache (and bump the nav instance) BEFORE the UI update so a deferred
|
|
568
|
+
// handle pushed by the error-boundary render still applies — see the
|
|
569
|
+
// "normal" case below for why caching after onUpdate dropped it.
|
|
432
570
|
store.setSegmentIds(segmentIdsAfterError);
|
|
433
571
|
const currentHandleData = eventController.getHandleState().data;
|
|
434
572
|
store.cacheSegmentsForHistory(
|
|
@@ -437,6 +575,11 @@ export function createServerActionBridge(
|
|
|
437
575
|
currentHandleData,
|
|
438
576
|
);
|
|
439
577
|
|
|
578
|
+
// Update UI with error boundary
|
|
579
|
+
startTransition(() => {
|
|
580
|
+
onUpdate({ root: errorTree, metadata: metadata! });
|
|
581
|
+
});
|
|
582
|
+
|
|
440
583
|
// Throw the error so the action promise rejects
|
|
441
584
|
if (returnValue && !returnValue.ok) {
|
|
442
585
|
throw returnValue.data;
|
|
@@ -510,6 +653,20 @@ export function createServerActionBridge(
|
|
|
510
653
|
currentInterceptSource: store.getInterceptSourceUrl(),
|
|
511
654
|
});
|
|
512
655
|
|
|
656
|
+
// Apply server-set location state exhaustively here, as a successful-
|
|
657
|
+
// response effect — the terminal switch below decides rendering/refetch,
|
|
658
|
+
// not whether this metadata survives (every refetch path is storeOnly and
|
|
659
|
+
// never writes history.state). Gated on NOT navigated-away: the classifier
|
|
660
|
+
// treats either a pathname OR history-key change as diversion, so this
|
|
661
|
+
// both honors the "diverted state is dropped" contract AND prevents a
|
|
662
|
+
// cross-entry write (mergeLocationState writes the CURRENT entry, which a
|
|
663
|
+
// navigated-away action no longer owns). Done before the switch (and
|
|
664
|
+
// before the normal branch's async renderSegments) so a slow render racing
|
|
665
|
+
// a navigation cannot drop it.
|
|
666
|
+
if (scenario.type !== "navigated-away") {
|
|
667
|
+
applyActionLocationState(handle, metadata?.locationState);
|
|
668
|
+
}
|
|
669
|
+
|
|
513
670
|
switch (scenario.type) {
|
|
514
671
|
case "navigated-away": {
|
|
515
672
|
log("user navigated away during action", {
|
|
@@ -521,8 +678,9 @@ export function createServerActionBridge(
|
|
|
521
678
|
handle.clearConsolidation();
|
|
522
679
|
|
|
523
680
|
if (scenario.historyKeyChanged) {
|
|
524
|
-
|
|
525
|
-
|
|
681
|
+
// Invalidation is deferred to finalizeAction(); here we only trigger
|
|
682
|
+
// the revalidation refetch of the new route (suppressed on keep).
|
|
683
|
+
if (!scenario.onInterceptRoute && !keepCache) {
|
|
526
684
|
refetchRoute().catch((error) => {
|
|
527
685
|
if (isBackgroundSuppressible(error)) return;
|
|
528
686
|
console.error(
|
|
@@ -534,11 +692,14 @@ export function createServerActionBridge(
|
|
|
534
692
|
break;
|
|
535
693
|
}
|
|
536
694
|
|
|
537
|
-
// Same history key but different pathname - safe to refetch current
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
695
|
+
// Same history key but different pathname - safe to refetch current
|
|
696
|
+
// route. Invalidation is deferred to finalizeAction(); here we only
|
|
697
|
+
// trigger the revalidation refetch (suppressed on keep).
|
|
698
|
+
if (!keepCache) {
|
|
699
|
+
await refetchRoute({
|
|
700
|
+
interceptSourceUrl: store.getInterceptSourceUrl(),
|
|
701
|
+
});
|
|
702
|
+
}
|
|
542
703
|
break;
|
|
543
704
|
}
|
|
544
705
|
|
|
@@ -546,8 +707,11 @@ export function createServerActionBridge(
|
|
|
546
707
|
console.warn(
|
|
547
708
|
`[Browser] Missing segments after action (HMR detected), refetching...`,
|
|
548
709
|
);
|
|
710
|
+
// Repair (not revalidation), so ungated on keepCache: a keep action
|
|
711
|
+
// resolving last must discharge a directive-free sibling's repair.
|
|
712
|
+
// See the keep row in docs/design/rango-state-cookie.md (the all-keep
|
|
713
|
+
// edge, and the benign re-mark-stale-after-refetch end-state delta).
|
|
549
714
|
await refetchRoute({ interceptSourceUrl });
|
|
550
|
-
store.broadcastCacheInvalidation();
|
|
551
715
|
break;
|
|
552
716
|
}
|
|
553
717
|
|
|
@@ -555,7 +719,8 @@ export function createServerActionBridge(
|
|
|
555
719
|
log("consolidation fetch needed", {
|
|
556
720
|
segmentIds: scenario.segmentIds,
|
|
557
721
|
});
|
|
558
|
-
//
|
|
722
|
+
// Location state already applied above (pre-switch). Calculate
|
|
723
|
+
// segments to send (exclude the ones we want fresh).
|
|
559
724
|
const currentSegmentIds = store.getSegmentState().currentSegmentIds;
|
|
560
725
|
const segmentsToSend = currentSegmentIds.filter(
|
|
561
726
|
(sid) => !scenario.segmentIds.includes(sid),
|
|
@@ -564,11 +729,11 @@ export function createServerActionBridge(
|
|
|
564
729
|
// Clear consolidation tracking before fetch
|
|
565
730
|
handle.clearConsolidation();
|
|
566
731
|
|
|
732
|
+
// Ungated on keepCache, same as hmr-missing above (see the keep row).
|
|
567
733
|
await refetchRoute({
|
|
568
734
|
segments: segmentsToSend,
|
|
569
735
|
interceptSourceUrl,
|
|
570
736
|
});
|
|
571
|
-
store.broadcastCacheInvalidation();
|
|
572
737
|
break;
|
|
573
738
|
}
|
|
574
739
|
|
|
@@ -579,6 +744,8 @@ export function createServerActionBridge(
|
|
|
579
744
|
// Only update store if history key hasn't changed (user didn't navigate away)
|
|
580
745
|
const currentKeyNow = store.getHistoryKey();
|
|
581
746
|
if (currentKeyNow === currentKey) {
|
|
747
|
+
// Location state already applied above (pre-switch); this action's
|
|
748
|
+
// UI render is skipped because a later sibling consolidates.
|
|
582
749
|
store.setSegmentIds(matched);
|
|
583
750
|
const currentHandleData = eventController.getHandleState().data;
|
|
584
751
|
store.cacheSegmentsForHistory(
|
|
@@ -614,17 +781,17 @@ export function createServerActionBridge(
|
|
|
614
781
|
break;
|
|
615
782
|
}
|
|
616
783
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
//
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
// Update store
|
|
784
|
+
// Cache (and bump the nav instance) BEFORE the UI update, matching the
|
|
785
|
+
// navigation commit order (navigation-transaction.ts:157). processHandles
|
|
786
|
+
// is spawned by onUpdate and captures the current nav instance up front;
|
|
787
|
+
// a deferred handle value resolves asynchronously and is only applied
|
|
788
|
+
// while stillLive() (its captured instance still owns the page). Caching
|
|
789
|
+
// AFTER onUpdate bumped the instance out from under that in-flight
|
|
790
|
+
// resolve, so stillLive() turned false and the resolved handle snapshot
|
|
791
|
+
// was dropped on the action-revalidation path (sync siblings sharing the
|
|
792
|
+
// yield are held atomically, so they were dropped too).
|
|
793
|
+
//
|
|
794
|
+
// Location state already applied above (pre-switch). Update store.
|
|
628
795
|
store.setSegmentIds(matched);
|
|
629
796
|
const currentHandleData = eventController.getHandleState().data;
|
|
630
797
|
store.cacheSegmentsForHistory(
|
|
@@ -632,7 +799,13 @@ export function createServerActionBridge(
|
|
|
632
799
|
fullSegments,
|
|
633
800
|
currentHandleData,
|
|
634
801
|
);
|
|
635
|
-
|
|
802
|
+
|
|
803
|
+
startTransition(() => {
|
|
804
|
+
onUpdate({ root: newTree, metadata: metadata! });
|
|
805
|
+
});
|
|
806
|
+
// Invalidation deferred to finalizeAction() (runs after this caches
|
|
807
|
+
// the fresh segments), suppressed when the action called
|
|
808
|
+
// keepClientCache().
|
|
636
809
|
break;
|
|
637
810
|
}
|
|
638
811
|
}
|
|
@@ -640,6 +813,11 @@ export function createServerActionBridge(
|
|
|
640
813
|
handle.complete(returnData);
|
|
641
814
|
return returnData;
|
|
642
815
|
} finally {
|
|
816
|
+
// The single deferred invalidation + fence release for this action. Runs
|
|
817
|
+
// for every terminal that settles (normal, navigated-away, error, abort,
|
|
818
|
+
// intercept, concurrent); the SPA-redirect paths above already ran it.
|
|
819
|
+
// Latched, so it fires exactly once.
|
|
820
|
+
finalizeAction();
|
|
643
821
|
handle[Symbol.dispose]();
|
|
644
822
|
}
|
|
645
823
|
}
|