@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3373 -1176
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +77 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +70 -5
- package/skills/router-setup/SKILL.md +65 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +131 -30
- package/src/browser/navigation-client.ts +186 -100
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +165 -112
- package/src/browser/prefetch/cache.ts +205 -62
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +295 -119
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +114 -24
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -72
- package/src/browser/types.ts +102 -12
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +99 -13
- package/src/index.ts +139 -19
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +143 -69
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +52 -31
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +268 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +170 -276
- 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 +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +266 -169
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +115 -35
- package/src/router/router-options.ts +172 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +162 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +351 -321
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +157 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +655 -529
- 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 +122 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +131 -14
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +109 -74
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -115
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +29 -9
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +236 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +224 -52
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +401 -173
- package/src/ssr/index.tsx +24 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +125 -71
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +214 -132
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +64 -170
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +118 -114
- package/src/vite/router-discovery.ts +941 -142
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- 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
|
@@ -4,14 +4,15 @@ import type {
|
|
|
4
4
|
RscPayload,
|
|
5
5
|
} from "./types.js";
|
|
6
6
|
import { createPartialUpdater } from "./partial-update.js";
|
|
7
|
-
import {
|
|
7
|
+
import { enterActionFence, exitActionFence } from "./action-fence.js";
|
|
8
|
+
import { KEEP_CACHE_HEADER } from "./cookie-name.js";
|
|
8
9
|
import { createNavigationTransaction } from "./navigation-transaction.js";
|
|
9
10
|
import {
|
|
10
11
|
reconcileSegments,
|
|
11
12
|
reconcileErrorSegments,
|
|
12
13
|
} from "./segment-reconciler.js";
|
|
13
14
|
import { startTransition } from "react";
|
|
14
|
-
import type { EventController } from "./event-controller.js";
|
|
15
|
+
import type { EventController, ActionHandle } from "./event-controller.js";
|
|
15
16
|
import {
|
|
16
17
|
toNetworkError,
|
|
17
18
|
emitNetworkError,
|
|
@@ -22,14 +23,20 @@ import {
|
|
|
22
23
|
isBrowserDebugEnabled,
|
|
23
24
|
startBrowserTransaction,
|
|
24
25
|
} from "./logging.js";
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
validateRedirectOrigin,
|
|
28
|
+
validateExternalRedirect,
|
|
29
|
+
} from "./validate-redirect-origin.js";
|
|
26
30
|
import {
|
|
27
31
|
extractRscHeaderUrl,
|
|
28
32
|
emptyResponse,
|
|
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";
|
|
39
|
+
import { getAppVersion } from "./app-version.js";
|
|
33
40
|
|
|
34
41
|
// Polyfill Symbol.dispose/asyncDispose for Safari and older browsers
|
|
35
42
|
if (typeof Symbol.dispose === "undefined") {
|
|
@@ -44,8 +51,6 @@ if (typeof Symbol.asyncDispose === "undefined") {
|
|
|
44
51
|
*/
|
|
45
52
|
export interface ServerActionBridgeConfigWithController extends ServerActionBridgeConfig {
|
|
46
53
|
eventController: EventController;
|
|
47
|
-
/** RSC version from initial payload metadata */
|
|
48
|
-
version?: string;
|
|
49
54
|
/** Callback to trigger SPA navigation (for action redirects) */
|
|
50
55
|
onNavigate?: (
|
|
51
56
|
url: string,
|
|
@@ -53,6 +58,27 @@ export interface ServerActionBridgeConfigWithController extends ServerActionBrid
|
|
|
53
58
|
) => Promise<void>;
|
|
54
59
|
}
|
|
55
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
|
+
|
|
56
82
|
/**
|
|
57
83
|
* Create a server action bridge for handling RSC server actions
|
|
58
84
|
*
|
|
@@ -76,10 +102,29 @@ export function createServerActionBridge(
|
|
|
76
102
|
deps,
|
|
77
103
|
onUpdate,
|
|
78
104
|
renderSegments,
|
|
79
|
-
version,
|
|
80
105
|
onNavigate,
|
|
81
106
|
} = config;
|
|
82
107
|
|
|
108
|
+
// SPA-navigate when onNavigate is set, else hard-reload. state is omitted (not
|
|
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).
|
|
113
|
+
async function dispatchRedirect(url: string, state?: unknown): Promise<void> {
|
|
114
|
+
if (onNavigate) {
|
|
115
|
+
await onNavigate(url, {
|
|
116
|
+
...(state !== undefined ? { state } : {}),
|
|
117
|
+
replace: true,
|
|
118
|
+
_skipCache: true,
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
const safe = validateRedirectOrigin(url, window.location.origin);
|
|
122
|
+
if (safe) {
|
|
123
|
+
window.location.href = safe;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
83
128
|
let isRegistered = false;
|
|
84
129
|
|
|
85
130
|
const fetchPartialUpdate = createPartialUpdater({
|
|
@@ -87,7 +132,7 @@ export function createServerActionBridge(
|
|
|
87
132
|
client,
|
|
88
133
|
onUpdate,
|
|
89
134
|
renderSegments,
|
|
90
|
-
|
|
135
|
+
getVersion: getAppVersion,
|
|
91
136
|
});
|
|
92
137
|
|
|
93
138
|
/**
|
|
@@ -141,14 +186,57 @@ export function createServerActionBridge(
|
|
|
141
186
|
const locationKey = window.history.state?.key;
|
|
142
187
|
log("action start", { id, argsCount: args.length });
|
|
143
188
|
|
|
144
|
-
// Start action in event controller - handles lifecycle tracking
|
|
145
|
-
|
|
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
|
+
};
|
|
146
231
|
try {
|
|
147
232
|
const segmentState = store.getSegmentState();
|
|
148
233
|
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
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();
|
|
152
240
|
|
|
153
241
|
// Create temporary references for serialization
|
|
154
242
|
const temporaryReferences = deps.createTemporaryReferenceSet();
|
|
@@ -166,9 +254,15 @@ export function createServerActionBridge(
|
|
|
166
254
|
segmentState.currentSegmentIds.join(","),
|
|
167
255
|
);
|
|
168
256
|
// Add version param for version mismatch detection
|
|
257
|
+
const version = getAppVersion();
|
|
169
258
|
if (version) {
|
|
170
259
|
url.searchParams.set("_rsc_v", version);
|
|
171
260
|
}
|
|
261
|
+
// Add router ID for app switch detection
|
|
262
|
+
const rid = store.getRouterId?.();
|
|
263
|
+
if (rid) {
|
|
264
|
+
url.searchParams.set("_rsc_rid", rid);
|
|
265
|
+
}
|
|
172
266
|
|
|
173
267
|
// Encode arguments
|
|
174
268
|
const encodedBody = await deps.encodeReply(args, { temporaryReferences });
|
|
@@ -200,14 +294,6 @@ export function createServerActionBridge(
|
|
|
200
294
|
const onHandleAbort = () => fetchAbort.abort();
|
|
201
295
|
handle.signal.addEventListener("abort", onHandleAbort, { once: true });
|
|
202
296
|
|
|
203
|
-
// Dev-only: create debug channel for React Performance Tracks
|
|
204
|
-
const debugId = (import.meta as any).hot
|
|
205
|
-
? crypto.randomUUID()
|
|
206
|
-
: undefined;
|
|
207
|
-
const debugChannel = debugId
|
|
208
|
-
? createClientDebugChannel(debugId)
|
|
209
|
-
: undefined;
|
|
210
|
-
|
|
211
297
|
// Send action request with stream tracking
|
|
212
298
|
const responsePromise = fetch(url, {
|
|
213
299
|
method: "POST",
|
|
@@ -215,32 +301,41 @@ export function createServerActionBridge(
|
|
|
215
301
|
"rsc-action": id,
|
|
216
302
|
"X-RSC-Router-Client-Path": segmentState.currentUrl,
|
|
217
303
|
...(tx && { "X-RSC-Router-Request-Id": tx.requestId }),
|
|
218
|
-
// Send intercept source URL so server can maintain intercept context
|
|
219
304
|
...(interceptSourceUrl && {
|
|
220
305
|
"X-RSC-Router-Intercept-Source": interceptSourceUrl,
|
|
221
306
|
}),
|
|
222
|
-
...(debugId && { [DEBUG_ID_HEADER]: debugId }),
|
|
223
307
|
},
|
|
224
308
|
body: encodedBody,
|
|
225
309
|
signal: fetchAbort.signal,
|
|
226
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;
|
|
227
317
|
// Response arrived — disconnect fetch abort from handle abort so
|
|
228
318
|
// abortAllActions() doesn't disrupt the in-progress Flight stream.
|
|
229
319
|
handle.signal.removeEventListener("abort", onHandleAbort);
|
|
230
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
|
+
|
|
231
326
|
// Check for version mismatch - server wants us to reload
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
327
|
+
const reloadResult = handleReloadHeader(response, {
|
|
328
|
+
onBlocked: resolveStreamComplete,
|
|
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
|
+
},
|
|
337
|
+
});
|
|
338
|
+
if (reloadResult) return reloadResult;
|
|
244
339
|
|
|
245
340
|
// Simple redirect from action (no state, no RSC payload).
|
|
246
341
|
// Short-circuits before createFromFetch — no Flight deserialization needed.
|
|
@@ -250,14 +345,11 @@ export function createServerActionBridge(
|
|
|
250
345
|
if (redirect && redirect !== "blocked" && !handle.signal.aborted) {
|
|
251
346
|
log("action simple redirect", { url: redirect.url });
|
|
252
347
|
handle.complete(undefined);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
} else {
|
|
259
|
-
window.location.href = redirect.url;
|
|
260
|
-
}
|
|
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();
|
|
352
|
+
await dispatchRedirect(redirect.url);
|
|
261
353
|
return new Promise<Response>(() => {});
|
|
262
354
|
}
|
|
263
355
|
if (redirect === "blocked") {
|
|
@@ -265,6 +357,29 @@ export function createServerActionBridge(
|
|
|
265
357
|
return emptyResponse();
|
|
266
358
|
}
|
|
267
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
|
+
|
|
268
383
|
// Start streaming immediately when response arrives
|
|
269
384
|
if (!handle.signal.aborted) {
|
|
270
385
|
streamingToken = handle.startStreaming();
|
|
@@ -282,7 +397,6 @@ export function createServerActionBridge(
|
|
|
282
397
|
try {
|
|
283
398
|
payload = await deps.createFromFetch<RscPayload>(responsePromise, {
|
|
284
399
|
temporaryReferences,
|
|
285
|
-
...(debugChannel && { debugChannel }),
|
|
286
400
|
});
|
|
287
401
|
} catch (error) {
|
|
288
402
|
// Clean up streaming token on error (may be null if fetch failed before .then() ran)
|
|
@@ -320,7 +434,6 @@ export function createServerActionBridge(
|
|
|
320
434
|
matchedCount: payload.metadata?.matched?.length ?? 0,
|
|
321
435
|
diffCount: payload.metadata?.diff?.length ?? 0,
|
|
322
436
|
});
|
|
323
|
-
|
|
324
437
|
// Guard: if the action was aborted while streaming (e.g., user navigated
|
|
325
438
|
// away or abortAllActions fired), bail out before any reconcile/render/cache
|
|
326
439
|
// writes to avoid overwriting the current UI with stale action results.
|
|
@@ -337,6 +450,27 @@ export function createServerActionBridge(
|
|
|
337
450
|
// Check handle.signal.aborted to avoid redirecting from a stale action
|
|
338
451
|
// when the user has already navigated away.
|
|
339
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
|
+
}
|
|
340
474
|
const redirectUrl = validateRedirectOrigin(
|
|
341
475
|
metadata.redirect.url,
|
|
342
476
|
window.location.origin,
|
|
@@ -348,18 +482,9 @@ export function createServerActionBridge(
|
|
|
348
482
|
handle.complete(returnValue?.data);
|
|
349
483
|
return returnValue?.data;
|
|
350
484
|
}
|
|
351
|
-
const redirectState = metadata.locationState;
|
|
352
485
|
log("action redirect", { url: redirectUrl });
|
|
353
486
|
handle.complete(returnValue?.data);
|
|
354
|
-
|
|
355
|
-
await onNavigate(redirectUrl, {
|
|
356
|
-
state: redirectState,
|
|
357
|
-
replace: true,
|
|
358
|
-
_skipCache: true,
|
|
359
|
-
});
|
|
360
|
-
} else {
|
|
361
|
-
window.location.href = redirectUrl;
|
|
362
|
-
}
|
|
487
|
+
await dispatchRedirect(redirectUrl, metadata.locationState);
|
|
363
488
|
return returnValue?.data;
|
|
364
489
|
}
|
|
365
490
|
|
|
@@ -526,6 +651,20 @@ export function createServerActionBridge(
|
|
|
526
651
|
currentInterceptSource: store.getInterceptSourceUrl(),
|
|
527
652
|
});
|
|
528
653
|
|
|
654
|
+
// Apply server-set location state exhaustively here, as a successful-
|
|
655
|
+
// response effect — the terminal switch below decides rendering/refetch,
|
|
656
|
+
// not whether this metadata survives (every refetch path is storeOnly and
|
|
657
|
+
// never writes history.state). Gated on NOT navigated-away: the classifier
|
|
658
|
+
// treats either a pathname OR history-key change as diversion, so this
|
|
659
|
+
// both honors the "diverted state is dropped" contract AND prevents a
|
|
660
|
+
// cross-entry write (mergeLocationState writes the CURRENT entry, which a
|
|
661
|
+
// navigated-away action no longer owns). Done before the switch (and
|
|
662
|
+
// before the normal branch's async renderSegments) so a slow render racing
|
|
663
|
+
// a navigation cannot drop it.
|
|
664
|
+
if (scenario.type !== "navigated-away") {
|
|
665
|
+
applyActionLocationState(handle, metadata?.locationState);
|
|
666
|
+
}
|
|
667
|
+
|
|
529
668
|
switch (scenario.type) {
|
|
530
669
|
case "navigated-away": {
|
|
531
670
|
log("user navigated away during action", {
|
|
@@ -537,8 +676,9 @@ export function createServerActionBridge(
|
|
|
537
676
|
handle.clearConsolidation();
|
|
538
677
|
|
|
539
678
|
if (scenario.historyKeyChanged) {
|
|
540
|
-
|
|
541
|
-
|
|
679
|
+
// Invalidation is deferred to finalizeAction(); here we only trigger
|
|
680
|
+
// the revalidation refetch of the new route (suppressed on keep).
|
|
681
|
+
if (!scenario.onInterceptRoute && !keepCache) {
|
|
542
682
|
refetchRoute().catch((error) => {
|
|
543
683
|
if (isBackgroundSuppressible(error)) return;
|
|
544
684
|
console.error(
|
|
@@ -550,11 +690,14 @@ export function createServerActionBridge(
|
|
|
550
690
|
break;
|
|
551
691
|
}
|
|
552
692
|
|
|
553
|
-
// Same history key but different pathname - safe to refetch current
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
693
|
+
// Same history key but different pathname - safe to refetch current
|
|
694
|
+
// route. Invalidation is deferred to finalizeAction(); here we only
|
|
695
|
+
// trigger the revalidation refetch (suppressed on keep).
|
|
696
|
+
if (!keepCache) {
|
|
697
|
+
await refetchRoute({
|
|
698
|
+
interceptSourceUrl: store.getInterceptSourceUrl(),
|
|
699
|
+
});
|
|
700
|
+
}
|
|
558
701
|
break;
|
|
559
702
|
}
|
|
560
703
|
|
|
@@ -562,8 +705,11 @@ export function createServerActionBridge(
|
|
|
562
705
|
console.warn(
|
|
563
706
|
`[Browser] Missing segments after action (HMR detected), refetching...`,
|
|
564
707
|
);
|
|
708
|
+
// Repair (not revalidation), so ungated on keepCache: a keep action
|
|
709
|
+
// resolving last must discharge a directive-free sibling's repair.
|
|
710
|
+
// See the keep row in docs/design/rango-state-cookie.md (the all-keep
|
|
711
|
+
// edge, and the benign re-mark-stale-after-refetch end-state delta).
|
|
565
712
|
await refetchRoute({ interceptSourceUrl });
|
|
566
|
-
store.broadcastCacheInvalidation();
|
|
567
713
|
break;
|
|
568
714
|
}
|
|
569
715
|
|
|
@@ -571,7 +717,8 @@ export function createServerActionBridge(
|
|
|
571
717
|
log("consolidation fetch needed", {
|
|
572
718
|
segmentIds: scenario.segmentIds,
|
|
573
719
|
});
|
|
574
|
-
//
|
|
720
|
+
// Location state already applied above (pre-switch). Calculate
|
|
721
|
+
// segments to send (exclude the ones we want fresh).
|
|
575
722
|
const currentSegmentIds = store.getSegmentState().currentSegmentIds;
|
|
576
723
|
const segmentsToSend = currentSegmentIds.filter(
|
|
577
724
|
(sid) => !scenario.segmentIds.includes(sid),
|
|
@@ -580,11 +727,11 @@ export function createServerActionBridge(
|
|
|
580
727
|
// Clear consolidation tracking before fetch
|
|
581
728
|
handle.clearConsolidation();
|
|
582
729
|
|
|
730
|
+
// Ungated on keepCache, same as hmr-missing above (see the keep row).
|
|
583
731
|
await refetchRoute({
|
|
584
732
|
segments: segmentsToSend,
|
|
585
733
|
interceptSourceUrl,
|
|
586
734
|
});
|
|
587
|
-
store.broadcastCacheInvalidation();
|
|
588
735
|
break;
|
|
589
736
|
}
|
|
590
737
|
|
|
@@ -595,6 +742,8 @@ export function createServerActionBridge(
|
|
|
595
742
|
// Only update store if history key hasn't changed (user didn't navigate away)
|
|
596
743
|
const currentKeyNow = store.getHistoryKey();
|
|
597
744
|
if (currentKeyNow === currentKey) {
|
|
745
|
+
// Location state already applied above (pre-switch); this action's
|
|
746
|
+
// UI render is skipped because a later sibling consolidates.
|
|
598
747
|
store.setSegmentIds(matched);
|
|
599
748
|
const currentHandleData = eventController.getHandleState().data;
|
|
600
749
|
store.cacheSegmentsForHistory(
|
|
@@ -634,13 +783,7 @@ export function createServerActionBridge(
|
|
|
634
783
|
onUpdate({ root: newTree, metadata: metadata! });
|
|
635
784
|
});
|
|
636
785
|
|
|
637
|
-
//
|
|
638
|
-
const actionLocationState = metadata?.locationState;
|
|
639
|
-
if (actionLocationState) {
|
|
640
|
-
mergeLocationState(actionLocationState);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Update store state
|
|
786
|
+
// Location state already applied above (pre-switch). Update store.
|
|
644
787
|
store.setSegmentIds(matched);
|
|
645
788
|
const currentHandleData = eventController.getHandleState().data;
|
|
646
789
|
store.cacheSegmentsForHistory(
|
|
@@ -648,7 +791,9 @@ export function createServerActionBridge(
|
|
|
648
791
|
fullSegments,
|
|
649
792
|
currentHandleData,
|
|
650
793
|
);
|
|
651
|
-
|
|
794
|
+
// Invalidation deferred to finalizeAction() (runs after this caches
|
|
795
|
+
// the fresh segments), suppressed when the action called
|
|
796
|
+
// keepClientCache().
|
|
652
797
|
break;
|
|
653
798
|
}
|
|
654
799
|
}
|
|
@@ -656,6 +801,11 @@ export function createServerActionBridge(
|
|
|
656
801
|
handle.complete(returnData);
|
|
657
802
|
return returnData;
|
|
658
803
|
} finally {
|
|
804
|
+
// The single deferred invalidation + fence release for this action. Runs
|
|
805
|
+
// for every terminal that settles (normal, navigated-away, error, abort,
|
|
806
|
+
// intercept, concurrent); the SPA-redirect paths above already ran it.
|
|
807
|
+
// Latched, so it fires exactly once.
|
|
808
|
+
finalizeAction();
|
|
659
809
|
handle[Symbol.dispose]();
|
|
660
810
|
}
|
|
661
811
|
}
|