@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c
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 +126 -16
- package/dist/bin/rango.js +319 -95
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2724 -1053
- 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 +11 -9
- package/skills/hooks/SKILL.md +243 -29
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +68 -19
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +190 -23
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +18 -10
- package/skills/migrate-nextjs/SKILL.md +43 -19
- package/skills/migrate-react-router/SKILL.md +8 -2
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +18 -7
- package/skills/prerender/SKILL.md +65 -60
- package/skills/rango/SKILL.md +251 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +115 -48
- package/skills/route/SKILL.md +46 -5
- package/skills/router-setup/SKILL.md +30 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -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 +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 +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 +94 -25
- package/src/browser/navigation-client.ts +121 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +115 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +147 -128
- package/src/browser/prefetch/cache.ts +107 -56
- package/src/browser/prefetch/fetch.ts +204 -34
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +30 -7
- package/src/browser/react/NavigationProvider.tsx +283 -118
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- 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 +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 +10 -5
- package/src/browser/react/use-params.ts +11 -11
- 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 +91 -24
- package/src/browser/scroll-restoration.ts +30 -17
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +214 -55
- package/src/browser/types.ts +80 -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 +60 -35
- package/src/build/generate-route-types.ts +2 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +117 -14
- 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 +117 -23
- 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 +55 -28
- 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 +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 +36 -61
- 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 +31 -23
- 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 +96 -12
- package/src/index.ts +94 -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 +61 -6
- 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 +34 -0
- package/src/reverse.ts +65 -40
- 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 +82 -55
- 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 +0 -16
- 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 +44 -23
- package/src/router/handler-context.ts +27 -43
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +10 -47
- package/src/router/loader-resolution.ts +155 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +9 -24
- 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 +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 +44 -74
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +247 -166
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +85 -94
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +104 -65
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +83 -35
- package/src/router/router-options.ts +136 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +97 -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 +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 +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 +162 -64
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +110 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +264 -220
- package/src/rsc/helpers.ts +100 -6
- 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 +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +117 -11
- 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 +88 -188
- package/src/rsc/rsc-rendering.ts +98 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +281 -117
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +23 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +31 -4
- package/src/segment-system.tsx +254 -143
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +163 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +287 -178
- package/src/ssr/index.tsx +21 -16
- package/src/static-handler.ts +10 -13
- 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 +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 +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +29 -12
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +126 -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 +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 +44 -0
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +2 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +126 -8
- 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-stub.ts +1 -21
- 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/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 +109 -118
- package/src/vite/router-discovery.ts +718 -119
- 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 +54 -39
- package/src/vite/utils/shared-utils.ts +90 -41
- 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
package/src/rsc/server-action.ts
CHANGED
|
@@ -16,37 +16,36 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
getRequestContext,
|
|
20
20
|
setRequestContextParams,
|
|
21
|
-
getLocationState,
|
|
22
21
|
} from "../server/request-context.js";
|
|
23
|
-
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
24
22
|
import { appendMetric } from "../router/metrics.js";
|
|
23
|
+
import { observePhase, PHASES } from "../router/instrument.js";
|
|
24
|
+
import { gateTransitions } from "./transition-gate.js";
|
|
25
25
|
import type { RscPayload } from "./types.js";
|
|
26
26
|
import {
|
|
27
27
|
hasBodyContent,
|
|
28
28
|
createResponseWithMergedHeaders,
|
|
29
29
|
createSimpleRedirectResponse,
|
|
30
|
-
|
|
30
|
+
interceptRedirectForPartial,
|
|
31
|
+
attachLocationStateIfPresent,
|
|
31
32
|
} from "./helpers.js";
|
|
33
|
+
import { warnNonRedirectActionResponse } from "./runtime-warnings.js";
|
|
32
34
|
import type { HandlerContext } from "./handler-context.js";
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Attach location state set during the action to a payload's metadata.
|
|
36
|
-
* No-op if no location state was set.
|
|
37
|
-
*/
|
|
38
|
-
function attachLocationState(payload: RscPayload): void {
|
|
39
|
-
const locationState = getLocationState();
|
|
40
|
-
if (locationState) {
|
|
41
|
-
payload.metadata!.locationState =
|
|
42
|
-
resolveLocationStateEntries(locationState);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
35
|
+
import type { MatchResult } from "../types.js";
|
|
45
36
|
|
|
46
37
|
/**
|
|
47
38
|
* Data flowing from action execution to the revalidation phase.
|
|
48
|
-
* When the action completes without redirect
|
|
49
|
-
*
|
|
39
|
+
* When the action completes without redirect, the handler passes this to route
|
|
40
|
+
* middleware → revalidateAfterAction.
|
|
41
|
+
*
|
|
42
|
+
* `errorBoundary` carries the matched error-boundary result when the action
|
|
43
|
+
* threw and a boundary matched. The error-boundary render is then performed in
|
|
44
|
+
* the revalidation phase so it runs INSIDE the same route-middleware wrapper as
|
|
45
|
+
* a successful revalidation — route middleware (context vars, headers, cookies)
|
|
46
|
+
* must apply to the error render too, matching the module doc's "identical to a
|
|
47
|
+
* normal render". When `errorBoundary` is set, `actionContext` is unused (the
|
|
48
|
+
* boundary is already matched; no matchPartial is run).
|
|
50
49
|
*/
|
|
51
50
|
export interface ActionContinuation {
|
|
52
51
|
returnValue: { ok: boolean; data: unknown };
|
|
@@ -60,6 +59,7 @@ export interface ActionContinuation {
|
|
|
60
59
|
actionResult: unknown;
|
|
61
60
|
formData?: FormData;
|
|
62
61
|
};
|
|
62
|
+
errorBoundary?: MatchResult;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
@@ -75,7 +75,7 @@ export async function executeServerAction<TEnv>(
|
|
|
75
75
|
env: TEnv,
|
|
76
76
|
url: URL,
|
|
77
77
|
actionId: string,
|
|
78
|
-
handleStore: ReturnType<typeof
|
|
78
|
+
handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
|
|
79
79
|
): Promise<Response | ActionContinuation> {
|
|
80
80
|
const temporaryReferences = ctx.createTemporaryReferenceSet();
|
|
81
81
|
|
|
@@ -89,17 +89,82 @@ export async function executeServerAction<TEnv>(
|
|
|
89
89
|
? await request.formData()
|
|
90
90
|
: await request.text();
|
|
91
91
|
|
|
92
|
-
if (body instanceof FormData) {
|
|
93
|
-
actionFormData = body;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
92
|
if (hasBodyContent(body)) {
|
|
97
93
|
args = await ctx.decodeReply(body, { temporaryReferences });
|
|
98
94
|
}
|
|
95
|
+
|
|
96
|
+
// Surface the action's FormData to shouldRevalidate({ formData }) for a JS
|
|
97
|
+
// server action, matching the PE path (progressive-enhancement.ts populates
|
|
98
|
+
// formData from request.formData()). A form-driven action is invoked as
|
|
99
|
+
// action(formData) (direct) or action(prevState, formData) (useActionState),
|
|
100
|
+
// so the FormData arrives INSIDE the decoded args. Use the LAST FormData arg:
|
|
101
|
+
// for useActionState the submitted form is the final arg, and a prior state
|
|
102
|
+
// that is itself a FormData would otherwise be picked first.
|
|
103
|
+
//
|
|
104
|
+
// The raw request body is NOT usable here: encodeReply wraps a FormData arg
|
|
105
|
+
// in a multipart envelope whose keys are Flight-encoded (e.g. `_1_name`,
|
|
106
|
+
// `0`), so request.formData() would hand shouldRevalidate a FormData with
|
|
107
|
+
// internal keys instead of the consumer's `name`. The decoded arg has the
|
|
108
|
+
// original keys.
|
|
109
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
110
|
+
if (args[i] instanceof FormData) {
|
|
111
|
+
actionFormData = args[i] as FormData;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
99
115
|
} catch (error) {
|
|
100
|
-
|
|
116
|
+
// Keep the original error as `cause` for server-side logging, but do not
|
|
117
|
+
// interpolate it into the message: that string can surface to the client
|
|
118
|
+
// and may leak decode internals.
|
|
119
|
+
const decodeError = new Error("Failed to decode action arguments", {
|
|
101
120
|
cause: error,
|
|
102
121
|
});
|
|
122
|
+
|
|
123
|
+
// Produce a router-controlled response instead of re-throwing into the
|
|
124
|
+
// host (which would surface as an opaque 500). This mirrors the no-JS PE
|
|
125
|
+
// path, where a malformed form body renders the route error boundary or
|
|
126
|
+
// returns an explicit 400 (progressive-enhancement.ts). Attempt boundary
|
|
127
|
+
// rendering first; if a boundary matches, defer the render to the
|
|
128
|
+
// revalidation phase (errorBoundary continuation) so it runs inside route
|
|
129
|
+
// middleware, identical to the action-threw path below. Otherwise return a
|
|
130
|
+
// plain 400 — the JS and no-JS paths now converge on the same outcome.
|
|
131
|
+
let decodeBoundary: MatchResult | undefined;
|
|
132
|
+
try {
|
|
133
|
+
decodeBoundary =
|
|
134
|
+
(await ctx.router.matchError(request, { env }, decodeError, "route")) ??
|
|
135
|
+
undefined;
|
|
136
|
+
} catch {
|
|
137
|
+
// matchError itself failed — fall through to the plain 400 below.
|
|
138
|
+
decodeBoundary = undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
ctx.callOnError(decodeError, "action", {
|
|
142
|
+
request,
|
|
143
|
+
url,
|
|
144
|
+
env,
|
|
145
|
+
actionId,
|
|
146
|
+
handledByBoundary: !!decodeBoundary,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (decodeBoundary) {
|
|
150
|
+
return {
|
|
151
|
+
returnValue: { ok: false, data: decodeError },
|
|
152
|
+
// 400: malformed action request, matching the PE explicit-400 status
|
|
153
|
+
// class (the action-threw path uses 500; a decode failure is a bad
|
|
154
|
+
// request, not an action runtime error).
|
|
155
|
+
actionStatus: 400,
|
|
156
|
+
temporaryReferences,
|
|
157
|
+
actionContext: {
|
|
158
|
+
actionId,
|
|
159
|
+
actionUrl: new URL(url),
|
|
160
|
+
actionResult: decodeError,
|
|
161
|
+
formData: actionFormData,
|
|
162
|
+
},
|
|
163
|
+
errorBoundary: decodeBoundary,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return createResponseWithMergedHeaders(null, { status: 400 });
|
|
103
168
|
}
|
|
104
169
|
|
|
105
170
|
// Execute the server action
|
|
@@ -109,51 +174,37 @@ export async function executeServerAction<TEnv>(
|
|
|
109
174
|
|
|
110
175
|
try {
|
|
111
176
|
loadedAction = await ctx.loadServerAction(actionId);
|
|
112
|
-
|
|
177
|
+
let data = await loadedAction!.apply(null, args);
|
|
113
178
|
|
|
114
|
-
// Intercept redirect
|
|
115
|
-
//
|
|
116
|
-
// and the revalidation step would run unnecessarily.
|
|
179
|
+
// Intercept redirect Responses: serializing one as the action returnValue
|
|
180
|
+
// would fail, and revalidation would run needlessly.
|
|
117
181
|
if (data instanceof Response) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
redirect = createSimpleRedirectResponse(redirectUrl);
|
|
130
|
-
}
|
|
131
|
-
carryOverRedirectHeaders(data, redirect);
|
|
132
|
-
return redirect;
|
|
182
|
+
const intercepted = interceptRedirectForPartial(
|
|
183
|
+
data,
|
|
184
|
+
ctx.createRedirectFlightResponse,
|
|
185
|
+
);
|
|
186
|
+
if (intercepted) return intercepted;
|
|
187
|
+
|
|
188
|
+
// Non-redirect Response returned (not thrown): a raw Response cannot be
|
|
189
|
+
// serialized into Flight. Discard it and re-render — mirroring the PE
|
|
190
|
+
// path (progressive-enhancement.ts) so JS and no-JS behave identically.
|
|
191
|
+
if (process.env.NODE_ENV !== "production") {
|
|
192
|
+
warnNonRedirectActionResponse(actionId);
|
|
133
193
|
}
|
|
194
|
+
data = undefined;
|
|
134
195
|
}
|
|
135
196
|
|
|
136
197
|
returnValue = { ok: true, data };
|
|
137
198
|
} catch (error) {
|
|
199
|
+
let actionResultData: unknown = error;
|
|
200
|
+
|
|
138
201
|
// Handle thrown redirect (e.g., throw redirect('/path'))
|
|
139
202
|
if (error instanceof Response) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
let redirect: Response;
|
|
146
|
-
if (locationState) {
|
|
147
|
-
redirect = ctx.createRedirectFlightResponse(
|
|
148
|
-
redirectUrl,
|
|
149
|
-
resolveLocationStateEntries(locationState),
|
|
150
|
-
);
|
|
151
|
-
} else {
|
|
152
|
-
redirect = createSimpleRedirectResponse(redirectUrl);
|
|
153
|
-
}
|
|
154
|
-
carryOverRedirectHeaders(error, redirect);
|
|
155
|
-
return redirect;
|
|
156
|
-
}
|
|
203
|
+
const intercepted = interceptRedirectForPartial(
|
|
204
|
+
error,
|
|
205
|
+
ctx.createRedirectFlightResponse,
|
|
206
|
+
);
|
|
207
|
+
if (intercepted) return intercepted;
|
|
157
208
|
|
|
158
209
|
// Non-redirect Response thrown from action — this will be treated
|
|
159
210
|
// as a regular error and routed to the error boundary. Warn in dev
|
|
@@ -166,9 +217,18 @@ export async function executeServerAction<TEnv>(
|
|
|
166
217
|
`Use \`throw redirect('/path')\` for redirects.`,
|
|
167
218
|
);
|
|
168
219
|
}
|
|
220
|
+
|
|
221
|
+
// A raw Response cannot be serialized into Flight; storing it as the
|
|
222
|
+
// action returnValue.data would make the error payload serialization
|
|
223
|
+
// throw and mask the boundary render. Replace it with a serializable
|
|
224
|
+
// error (mirrors the discard of a returned non-redirect Response above).
|
|
225
|
+
// matchError/onError still receive the original Response.
|
|
226
|
+
actionResultData = new Error(
|
|
227
|
+
`Server action "${actionId}" threw a non-redirect Response (status ${error.status})`,
|
|
228
|
+
);
|
|
169
229
|
}
|
|
170
230
|
|
|
171
|
-
returnValue = { ok: false, data:
|
|
231
|
+
returnValue = { ok: false, data: actionResultData };
|
|
172
232
|
actionStatus = 500;
|
|
173
233
|
|
|
174
234
|
// Try to render error boundary.
|
|
@@ -203,55 +263,46 @@ export async function executeServerAction<TEnv>(
|
|
|
203
263
|
});
|
|
204
264
|
|
|
205
265
|
if (errorResult) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
diff: errorResult.diff,
|
|
216
|
-
params: errorResult.params,
|
|
217
|
-
isError: true,
|
|
218
|
-
handles: handleStore.stream(),
|
|
219
|
-
version: ctx.version,
|
|
220
|
-
},
|
|
266
|
+
// Defer the error-boundary render to the revalidation phase so it runs
|
|
267
|
+
// inside the same route-middleware wrapper as a successful revalidation
|
|
268
|
+
// (handler.ts executeRenderWithMiddleware). Building + returning the
|
|
269
|
+
// Response here would bypass route middleware: context vars, headers, and
|
|
270
|
+
// cookies set by route middleware would NOT apply to the error render,
|
|
271
|
+
// diverging from the success path and from the module doc's "identical to
|
|
272
|
+
// a normal render". The boundary is already matched; the render in
|
|
273
|
+
// revalidateAfterAction uses errorResult directly (no matchPartial).
|
|
274
|
+
return {
|
|
221
275
|
returnValue,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// Intentionally omit attachLocationState for error payloads:
|
|
225
|
-
// location state is a success-only semantic. Error boundary responses
|
|
226
|
-
// update the error UI but should not mutate browser history state.
|
|
227
|
-
|
|
228
|
-
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
276
|
+
actionStatus,
|
|
229
277
|
temporaryReferences,
|
|
230
|
-
|
|
231
|
-
|
|
278
|
+
// actionContext is unused on the errorBoundary path (no matchPartial).
|
|
279
|
+
actionContext: {
|
|
280
|
+
actionId,
|
|
281
|
+
actionUrl: new URL(url),
|
|
282
|
+
actionResult: returnValue.data,
|
|
283
|
+
formData: actionFormData,
|
|
232
284
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return createResponseWithMergedHeaders(rscStream, {
|
|
236
|
-
status: actionStatus,
|
|
237
|
-
headers: { "content-type": "text/x-component;charset=utf-8" },
|
|
238
|
-
});
|
|
285
|
+
errorBoundary: errorResult,
|
|
286
|
+
};
|
|
239
287
|
}
|
|
240
288
|
}
|
|
241
289
|
|
|
242
290
|
// Build continuation for the revalidation phase
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
291
|
+
const actionMeta = loadedAction as
|
|
292
|
+
| { $id?: string; $$id?: string }
|
|
293
|
+
| undefined;
|
|
294
|
+
const resolvedActionId = actionMeta?.$id ?? actionMeta?.$$id ?? actionId;
|
|
247
295
|
|
|
248
296
|
return {
|
|
249
297
|
returnValue,
|
|
250
298
|
actionStatus,
|
|
251
299
|
temporaryReferences,
|
|
252
300
|
actionContext: {
|
|
301
|
+
// Defensive copy of the already-parsed url (avoids re-parsing
|
|
302
|
+
// request.url). actionUrl is persisted into the continuation and later
|
|
303
|
+
// flows into matchPartial, so it must not alias the handler's live url.
|
|
253
304
|
actionId: resolvedActionId,
|
|
254
|
-
actionUrl: new URL(
|
|
305
|
+
actionUrl: new URL(url),
|
|
255
306
|
actionResult: returnValue.data,
|
|
256
307
|
formData: actionFormData,
|
|
257
308
|
},
|
|
@@ -270,19 +321,122 @@ export async function executeServerAction<TEnv>(
|
|
|
270
321
|
* provide. Redirects are the only non-partial outcome and are handled via
|
|
271
322
|
* X-RSC-Redirect headers before Flight deserialization.
|
|
272
323
|
*/
|
|
273
|
-
export
|
|
324
|
+
export function revalidateAfterAction<TEnv>(
|
|
274
325
|
ctx: HandlerContext<TEnv>,
|
|
275
326
|
request: Request,
|
|
276
327
|
env: TEnv,
|
|
277
328
|
url: URL,
|
|
278
|
-
handleStore: ReturnType<typeof
|
|
329
|
+
handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
|
|
279
330
|
continuation: ActionContinuation,
|
|
280
331
|
): Promise<Response> {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
332
|
+
// Instrument the action-revalidation render through the unified phase API,
|
|
333
|
+
// exactly like a normal navigation render (handleRscRendering). It records
|
|
334
|
+
// "render:total" AND opens "rango.render" from one boundary covering
|
|
335
|
+
// matchPartial -> serialize, so the revalidation loaders' rango.loader spans
|
|
336
|
+
// nest under a rango.render parent instead of dangling at the request root.
|
|
337
|
+
return observePhase(PHASES.render, () =>
|
|
338
|
+
revalidateAfterActionInner(
|
|
339
|
+
ctx,
|
|
340
|
+
request,
|
|
341
|
+
env,
|
|
342
|
+
url,
|
|
343
|
+
handleStore,
|
|
344
|
+
continuation,
|
|
345
|
+
),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function revalidateAfterActionInner<TEnv>(
|
|
350
|
+
ctx: HandlerContext<TEnv>,
|
|
351
|
+
request: Request,
|
|
352
|
+
env: TEnv,
|
|
353
|
+
url: URL,
|
|
354
|
+
handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
|
|
355
|
+
continuation: ActionContinuation,
|
|
356
|
+
): Promise<Response> {
|
|
357
|
+
const {
|
|
358
|
+
returnValue,
|
|
359
|
+
actionStatus,
|
|
360
|
+
temporaryReferences,
|
|
361
|
+
actionContext,
|
|
362
|
+
errorBoundary,
|
|
363
|
+
} = continuation;
|
|
364
|
+
const reqCtx = getRequestContext();
|
|
284
365
|
const metricsStore = reqCtx._metricsStore;
|
|
285
366
|
|
|
367
|
+
// Expose the action that triggered this revalidation to the transition({ when })
|
|
368
|
+
// gate (covers both the error-boundary and success gate calls below). Mirrors
|
|
369
|
+
// the action fields a revalidate() predicate sees.
|
|
370
|
+
reqCtx._gateActionId = actionContext?.actionId;
|
|
371
|
+
reqCtx._gateActionUrl = actionContext?.actionUrl;
|
|
372
|
+
reqCtx._gateActionResult = actionContext?.actionResult;
|
|
373
|
+
reqCtx._gateFormData = actionContext?.formData;
|
|
374
|
+
|
|
375
|
+
// Mark the rest of this request as an action revalidation render. The "use
|
|
376
|
+
// cache" runtime reads this to re-execute a stale entry in the foreground
|
|
377
|
+
// (fresh data in the action response) rather than serving stale + revalidating
|
|
378
|
+
// in the background. See registerCachedFunction in cache/cache-runtime.ts.
|
|
379
|
+
reqCtx._inActionRevalidation = true;
|
|
380
|
+
|
|
381
|
+
// Action threw and a boundary matched: render the (already-matched) error
|
|
382
|
+
// boundary here so it runs inside the route-middleware wrapper, exactly like
|
|
383
|
+
// the success branch below. setRequestContextParams + the payload mirror the
|
|
384
|
+
// pre-deferral render that executeServerAction used to do inline.
|
|
385
|
+
if (errorBoundary) {
|
|
386
|
+
setRequestContextParams(errorBoundary.params, errorBoundary.routeName);
|
|
387
|
+
|
|
388
|
+
const errorPayload: RscPayload = {
|
|
389
|
+
metadata: {
|
|
390
|
+
pathname: url.pathname,
|
|
391
|
+
// routerId exposed for the frontend (current app identity); see
|
|
392
|
+
// rsc-rendering.ts partial branch.
|
|
393
|
+
routerId: ctx.router.id,
|
|
394
|
+
segments: gateTransitions(
|
|
395
|
+
errorBoundary.segments,
|
|
396
|
+
reqCtx,
|
|
397
|
+
ctx.router.onError,
|
|
398
|
+
),
|
|
399
|
+
isPartial: true,
|
|
400
|
+
matched: errorBoundary.matched,
|
|
401
|
+
diff: errorBoundary.diff,
|
|
402
|
+
resolvedIds: errorBoundary.resolvedIds,
|
|
403
|
+
params: errorBoundary.params,
|
|
404
|
+
isError: true,
|
|
405
|
+
handles: handleStore.stream(),
|
|
406
|
+
version: ctx.version,
|
|
407
|
+
},
|
|
408
|
+
returnValue,
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// Intentionally omit attachLocationState for error payloads: location state
|
|
412
|
+
// is a success-only semantic. Error boundary responses update the error UI
|
|
413
|
+
// but should not mutate browser history state.
|
|
414
|
+
|
|
415
|
+
const errorStart = performance.now();
|
|
416
|
+
const errorStream = ctx.renderToReadableStream<RscPayload>(errorPayload, {
|
|
417
|
+
temporaryReferences,
|
|
418
|
+
onError: (error: unknown) => {
|
|
419
|
+
ctx.callOnError(error, "rendering", { request, url, env });
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
appendMetric(
|
|
423
|
+
metricsStore,
|
|
424
|
+
"rsc-serialize",
|
|
425
|
+
errorStart,
|
|
426
|
+
performance.now() - errorStart,
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
return createResponseWithMergedHeaders(errorStream, {
|
|
430
|
+
status: actionStatus,
|
|
431
|
+
headers: {
|
|
432
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
433
|
+
// Router identity for the client's pre-decode integrity check (the
|
|
434
|
+
// action apply path has no post-decode guard). See response-adapter.
|
|
435
|
+
"X-RSC-Router-Id": ctx.router.id,
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
286
440
|
const matchResult = await ctx.router.matchPartial(
|
|
287
441
|
request,
|
|
288
442
|
{ env },
|
|
@@ -290,8 +444,8 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
290
444
|
);
|
|
291
445
|
|
|
292
446
|
if (!matchResult) {
|
|
293
|
-
// matchPartial returns null when the route is a redirect or
|
|
294
|
-
//
|
|
447
|
+
// matchPartial returns null when the route is a redirect or no previous-URL
|
|
448
|
+
// context could be resolved. Check for redirect first.
|
|
295
449
|
const fullMatch = await ctx.router.match(request, { env });
|
|
296
450
|
setRequestContextParams(fullMatch.params, fullMatch.routeName);
|
|
297
451
|
|
|
@@ -302,14 +456,17 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
302
456
|
return createSimpleRedirectResponse(fullMatch.redirect);
|
|
303
457
|
}
|
|
304
458
|
|
|
305
|
-
// Non-redirect: this branch is only reachable when
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
459
|
+
// Non-redirect: this branch is only reachable when no previous URL could
|
|
460
|
+
// be resolved (neither X-RSC-Router-Client-Path nor a usable Referer), or
|
|
461
|
+
// the previous URL was unparseable (defensive). The client requires
|
|
462
|
+
// isPartial for action responses, so producing a full payload here would
|
|
463
|
+
// be rejected. Return 500 instead.
|
|
309
464
|
throw new Error(
|
|
310
465
|
`[RSC] matchPartial returned null for a non-redirect route ` +
|
|
311
466
|
`during action revalidation (${url.pathname}). This indicates ` +
|
|
312
|
-
`a malformed action request
|
|
467
|
+
`a malformed action request: no previous-URL context could be ` +
|
|
468
|
+
`resolved (neither X-RSC-Router-Client-Path nor a usable Referer), ` +
|
|
469
|
+
`or the previous URL was unparseable.`,
|
|
313
470
|
);
|
|
314
471
|
}
|
|
315
472
|
|
|
@@ -319,11 +476,18 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
319
476
|
const payload: RscPayload = {
|
|
320
477
|
metadata: {
|
|
321
478
|
pathname: url.pathname,
|
|
479
|
+
// routerId exposed for the frontend (current app identity); see
|
|
480
|
+
// rsc-rendering.ts partial branch.
|
|
322
481
|
routerId: ctx.router.id,
|
|
323
|
-
segments:
|
|
482
|
+
segments: gateTransitions(
|
|
483
|
+
matchResult.segments,
|
|
484
|
+
reqCtx,
|
|
485
|
+
ctx.router.onError,
|
|
486
|
+
),
|
|
324
487
|
isPartial: true,
|
|
325
488
|
matched: matchResult.matched,
|
|
326
489
|
diff: matchResult.diff,
|
|
490
|
+
resolvedIds: matchResult.resolvedIds,
|
|
327
491
|
params: matchResult.params,
|
|
328
492
|
slots: matchResult.slots,
|
|
329
493
|
handles: handleStore.stream(),
|
|
@@ -332,7 +496,7 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
332
496
|
returnValue,
|
|
333
497
|
};
|
|
334
498
|
|
|
335
|
-
|
|
499
|
+
attachLocationStateIfPresent(payload);
|
|
336
500
|
|
|
337
501
|
const renderStart = performance.now();
|
|
338
502
|
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
@@ -343,16 +507,16 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
343
507
|
});
|
|
344
508
|
const rscSerializeDur = performance.now() - renderStart;
|
|
345
509
|
// This measures synchronous stream creation, not end-to-end stream consumption.
|
|
510
|
+
// render:total is recorded by the observePhase wrapper in revalidateAfterAction.
|
|
346
511
|
appendMetric(metricsStore, "rsc-serialize", renderStart, rscSerializeDur);
|
|
347
|
-
appendMetric(
|
|
348
|
-
metricsStore,
|
|
349
|
-
"render:total",
|
|
350
|
-
renderStart,
|
|
351
|
-
performance.now() - renderStart,
|
|
352
|
-
);
|
|
353
512
|
|
|
354
513
|
return createResponseWithMergedHeaders(rscStream, {
|
|
355
514
|
status: actionStatus,
|
|
356
|
-
headers: {
|
|
515
|
+
headers: {
|
|
516
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
517
|
+
// Router identity for the client's pre-decode integrity check (the action
|
|
518
|
+
// apply path has no post-decode guard). See response-adapter.
|
|
519
|
+
"X-RSC-Router-Id": ctx.router.id,
|
|
520
|
+
},
|
|
357
521
|
});
|
|
358
522
|
}
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -126,3 +126,19 @@ export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
|
126
126
|
|
|
127
127
|
return true;
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
// Final render-time decision: is the response an RSC stream (vs HTML)? Distinct
|
|
131
|
+
// from mayNeedSSR, which is a conservative pre-classifier (it treats a missing
|
|
132
|
+
// Accept header as needing SSR; this treats it as RSC).
|
|
133
|
+
export function isRscRequest(
|
|
134
|
+
request: Request,
|
|
135
|
+
url: URL,
|
|
136
|
+
isPartial: boolean,
|
|
137
|
+
): boolean {
|
|
138
|
+
return (
|
|
139
|
+
isPartial ||
|
|
140
|
+
(!request.headers.get("accept")?.includes("text/html") &&
|
|
141
|
+
!url.searchParams.has("__html")) ||
|
|
142
|
+
url.searchParams.has("__rsc")
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { MatchResult } from "../types.js";
|
|
2
|
+
import type { TransitionWhenContext } from "../types/segments.js";
|
|
3
|
+
import type { getRequestContext } from "../server/request-context.js";
|
|
4
|
+
import { invokeOnError } from "../router/error-handling.js";
|
|
5
|
+
import type { OnErrorCallback } from "../types/error-types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Apply transition({ when }) gates to a payload's segments.
|
|
9
|
+
*
|
|
10
|
+
* The predicates were collected during resolution (keyed by segment id) and
|
|
11
|
+
* stripped from the serialized config; here — after handlers ran and outside any
|
|
12
|
+
* cache scope — we evaluate each and drop the segment's transition when the
|
|
13
|
+
* predicate does not hold, so the navigation streams its loading fallback
|
|
14
|
+
* instead of holding the previous content. A predicate that throws is reported
|
|
15
|
+
* to the router's onError (phase "rendering") and then treated as "do not hold"
|
|
16
|
+
* (conservative), so a buggy predicate degrades to no transition rather than
|
|
17
|
+
* failing the response.
|
|
18
|
+
*
|
|
19
|
+
* Mutating the segments here is safe: the segment cache stores a serialized copy
|
|
20
|
+
* (segment-codec), written during match() BEFORE this gate runs, so dropping a
|
|
21
|
+
* transition never corrupts a cache entry. The flip side is that a cache hit
|
|
22
|
+
* skips resolution, collects no predicate, and replays the cached transition
|
|
23
|
+
* as-is (it was serialized before the gate) — combining transition({ when })
|
|
24
|
+
* with cache() on the same segment freezes the gate to its cached state, so
|
|
25
|
+
* avoid caching a route whose transition decision is request-dependent.
|
|
26
|
+
*
|
|
27
|
+
* Returns the same array (mutated) for inline use at the payload's `segments`
|
|
28
|
+
* field.
|
|
29
|
+
*/
|
|
30
|
+
export function gateTransitions(
|
|
31
|
+
segments: MatchResult["segments"],
|
|
32
|
+
ctx: ReturnType<typeof getRequestContext>,
|
|
33
|
+
onError?: OnErrorCallback,
|
|
34
|
+
): MatchResult["segments"] {
|
|
35
|
+
const predicates = ctx._transitionWhen;
|
|
36
|
+
if (predicates && predicates.length) {
|
|
37
|
+
for (const { id, when } of predicates) {
|
|
38
|
+
let drop: boolean;
|
|
39
|
+
try {
|
|
40
|
+
// Assemble the ShouldRevalidateFn-shaped predicate context from the
|
|
41
|
+
// request context. Source fields (currentUrl/currentParams/fromRouteName)
|
|
42
|
+
// were stashed at match time from the navigation snapshot; action fields
|
|
43
|
+
// at the action-bearing gate call sites. nextUrl/nextParams/toRouteName/
|
|
44
|
+
// method/get/env come straight off ctx (setRequestContextParams ran
|
|
45
|
+
// before the gate). Source/action fields are undefined when absent —
|
|
46
|
+
// never fabricated (see TransitionWhenContext).
|
|
47
|
+
const whenCtx: TransitionWhenContext = {
|
|
48
|
+
currentUrl: ctx._gateCurrentUrl,
|
|
49
|
+
currentParams: ctx._gateCurrentParams,
|
|
50
|
+
fromRouteName:
|
|
51
|
+
ctx._prevRouteKey as TransitionWhenContext["fromRouteName"],
|
|
52
|
+
nextUrl: ctx.url,
|
|
53
|
+
nextParams: ctx.params,
|
|
54
|
+
toRouteName: ctx.routeName,
|
|
55
|
+
actionId: ctx._gateActionId,
|
|
56
|
+
actionUrl: ctx._gateActionUrl,
|
|
57
|
+
actionResult: ctx._gateActionResult,
|
|
58
|
+
formData: ctx._gateFormData,
|
|
59
|
+
method: ctx.request.method,
|
|
60
|
+
get: ctx.get,
|
|
61
|
+
env: ctx.env,
|
|
62
|
+
};
|
|
63
|
+
drop = when(whenCtx) === false;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// A throwing predicate must not fail the response: report it and treat
|
|
66
|
+
// the transition as gated off (do not hold). invokeOnError no-ops when
|
|
67
|
+
// onError is undefined.
|
|
68
|
+
drop = true;
|
|
69
|
+
invokeOnError(
|
|
70
|
+
onError,
|
|
71
|
+
error,
|
|
72
|
+
"rendering",
|
|
73
|
+
{
|
|
74
|
+
request: ctx.request,
|
|
75
|
+
url: ctx.url,
|
|
76
|
+
params: ctx.params,
|
|
77
|
+
segmentId: id,
|
|
78
|
+
},
|
|
79
|
+
"RSC",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (drop) {
|
|
83
|
+
const seg = segments.find((s) => s.id === id);
|
|
84
|
+
if (seg) seg.transition = undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return segments;
|
|
89
|
+
}
|