@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
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 +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- 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 +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -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 +121 -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 +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- 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/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- 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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -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-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- 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 +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- 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 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- 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 +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- 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/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -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 +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- 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 +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- 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 +618 -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 +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -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 +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -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 +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- 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 +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- 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 -18
- 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 +192 -99
- 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 +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- 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 +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -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 +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- 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/router/middleware-cookies.ts +0 -55
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
import {
|
|
10
10
|
requireRequestContext,
|
|
11
11
|
setRequestContextParams,
|
|
12
|
-
getLocationState,
|
|
13
12
|
} from "../server/request-context.js";
|
|
14
|
-
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
15
13
|
import { appendMetric } from "../router/metrics.js";
|
|
16
|
-
import { getSSRSetup } from "./ssr-setup.js";
|
|
14
|
+
import { getSSRSetup, isRscRequest } from "./ssr-setup.js";
|
|
17
15
|
import type { RscPayload } from "./types.js";
|
|
16
|
+
import type { MatchResult } from "../types.js";
|
|
18
17
|
import {
|
|
19
18
|
createResponseWithMergedHeaders,
|
|
20
19
|
createSimpleRedirectResponse,
|
|
20
|
+
attachLocationStateIfPresent,
|
|
21
21
|
} from "./helpers.js";
|
|
22
22
|
import type { HandlerContext } from "./handler-context.js";
|
|
23
23
|
|
|
@@ -35,6 +35,29 @@ export async function handleRscRendering<TEnv>(
|
|
|
35
35
|
let payload: RscPayload;
|
|
36
36
|
let hasInterceptSlots = false;
|
|
37
37
|
|
|
38
|
+
// Shared by the partial-fallback and full-render paths. The partial-success
|
|
39
|
+
// payload below is intentionally different (omits rootLayout/theme, adds slots).
|
|
40
|
+
const buildFullPayload = (m: MatchResult): RscPayload => ({
|
|
41
|
+
metadata: {
|
|
42
|
+
pathname: url.pathname,
|
|
43
|
+
routerId: ctx.router.id,
|
|
44
|
+
basename: ctx.router.basename,
|
|
45
|
+
segments: m.segments,
|
|
46
|
+
matched: m.matched,
|
|
47
|
+
diff: m.diff,
|
|
48
|
+
resolvedIds: m.resolvedIds,
|
|
49
|
+
params: m.params,
|
|
50
|
+
isPartial: false,
|
|
51
|
+
rootLayout: ctx.router.rootLayout,
|
|
52
|
+
handles: handleStore.stream(),
|
|
53
|
+
version: ctx.version,
|
|
54
|
+
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
55
|
+
stateCookieName: ctx.router.resolvedStateCookieName,
|
|
56
|
+
themeConfig: ctx.router.themeConfig,
|
|
57
|
+
initialTheme: reqCtx.theme,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
38
61
|
if (isPartial) {
|
|
39
62
|
// Partial render (navigation)
|
|
40
63
|
const result = await ctx.router.matchPartial(request, { env });
|
|
@@ -51,22 +74,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
51
74
|
return createSimpleRedirectResponse(match.redirect);
|
|
52
75
|
}
|
|
53
76
|
|
|
54
|
-
payload =
|
|
55
|
-
metadata: {
|
|
56
|
-
pathname: url.pathname,
|
|
57
|
-
segments: match.segments,
|
|
58
|
-
matched: match.matched,
|
|
59
|
-
diff: match.diff,
|
|
60
|
-
params: match.params,
|
|
61
|
-
isPartial: false,
|
|
62
|
-
rootLayout: ctx.router.rootLayout,
|
|
63
|
-
handles: handleStore.stream(),
|
|
64
|
-
version: ctx.version,
|
|
65
|
-
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
66
|
-
themeConfig: ctx.router.themeConfig,
|
|
67
|
-
initialTheme: reqCtx.theme,
|
|
68
|
-
},
|
|
69
|
-
};
|
|
77
|
+
payload = buildFullPayload(match);
|
|
70
78
|
} else {
|
|
71
79
|
setRequestContextParams(result.params, result.routeName);
|
|
72
80
|
|
|
@@ -75,14 +83,23 @@ export async function handleRscRendering<TEnv>(
|
|
|
75
83
|
payload = {
|
|
76
84
|
metadata: {
|
|
77
85
|
pathname: url.pathname,
|
|
86
|
+
// routerId is serialized on every payload (including within-session
|
|
87
|
+
// ones) so the frontend can read the current app/router identity. It
|
|
88
|
+
// always equals the current app's id: a cross-app navigation is
|
|
89
|
+
// intercepted server-side (X-RSC-Reload) and never delivers a
|
|
90
|
+
// different-router payload to the client.
|
|
91
|
+
routerId: ctx.router.id,
|
|
78
92
|
segments: result.segments,
|
|
79
93
|
matched: result.matched,
|
|
80
94
|
diff: result.diff,
|
|
95
|
+
resolvedIds: result.resolvedIds,
|
|
81
96
|
params: result.params,
|
|
82
97
|
isPartial: true,
|
|
83
98
|
slots: result.slots,
|
|
84
99
|
handles: handleStore.stream(),
|
|
85
100
|
version: ctx.version,
|
|
101
|
+
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
102
|
+
stateCookieName: ctx.router.resolvedStateCookieName,
|
|
86
103
|
},
|
|
87
104
|
};
|
|
88
105
|
}
|
|
@@ -129,24 +146,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
129
146
|
{ headers: { "Content-Type": "application/json" } },
|
|
130
147
|
);
|
|
131
148
|
} else {
|
|
132
|
-
payload =
|
|
133
|
-
// Initial SSR can reconstruct the tree from segments + rootLayout,
|
|
134
|
-
// so we omit root to avoid sending the same structure twice.
|
|
135
|
-
|
|
136
|
-
metadata: {
|
|
137
|
-
pathname: url.pathname,
|
|
138
|
-
segments: match.segments,
|
|
139
|
-
matched: match.matched,
|
|
140
|
-
diff: match.diff,
|
|
141
|
-
params: match.params,
|
|
142
|
-
isPartial: false,
|
|
143
|
-
rootLayout: ctx.router.rootLayout,
|
|
144
|
-
handles: handleStore.stream(),
|
|
145
|
-
version: ctx.version,
|
|
146
|
-
themeConfig: ctx.router.themeConfig,
|
|
147
|
-
initialTheme: reqCtx.theme,
|
|
148
|
-
},
|
|
149
|
-
};
|
|
149
|
+
payload = buildFullPayload(match);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -154,11 +154,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
154
154
|
// SSR (full page) requests ignore location state since there's no history.state
|
|
155
155
|
// to write to on a fresh page load.
|
|
156
156
|
if (isPartial && payload.metadata) {
|
|
157
|
-
|
|
158
|
-
if (locationState) {
|
|
159
|
-
payload.metadata.locationState =
|
|
160
|
-
resolveLocationStateEntries(locationState);
|
|
161
|
-
}
|
|
157
|
+
attachLocationStateIfPresent(payload);
|
|
162
158
|
}
|
|
163
159
|
|
|
164
160
|
const metricsStore = reqCtx._metricsStore;
|
|
@@ -166,7 +162,11 @@ export async function handleRscRendering<TEnv>(
|
|
|
166
162
|
|
|
167
163
|
// Serialize to RSC stream
|
|
168
164
|
const rscSerializeStart = performance.now();
|
|
169
|
-
const rscStream = ctx.renderToReadableStream<RscPayload>(payload
|
|
165
|
+
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
166
|
+
onError: (error: unknown) => {
|
|
167
|
+
ctx.callOnError(error, "rendering", { request, url, env });
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
170
|
const rscSerializeDur = performance.now() - rscSerializeStart;
|
|
171
171
|
// This measures synchronous stream creation, not end-to-end stream consumption.
|
|
172
172
|
appendMetric(
|
|
@@ -176,23 +176,25 @@ export async function handleRscRendering<TEnv>(
|
|
|
176
176
|
rscSerializeDur,
|
|
177
177
|
);
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
// Partial requests (_rsc_partial) are always RSC -- they come from client-side
|
|
181
|
-
// navigation or prefetch fetch(). We cannot rely on Accept alone since some
|
|
182
|
-
// browsers may send Accept: text/html for non-HTML requests.
|
|
183
|
-
const isRscRequest =
|
|
184
|
-
isPartial ||
|
|
185
|
-
(!request.headers.get("accept")?.includes("text/html") &&
|
|
186
|
-
!url.searchParams.has("__html")) ||
|
|
187
|
-
url.searchParams.has("__rsc");
|
|
188
|
-
|
|
189
|
-
if (isRscRequest) {
|
|
179
|
+
if (isRscRequest(request, url, isPartial)) {
|
|
190
180
|
const renderDur = performance.now() - renderStart;
|
|
191
181
|
appendMetric(metricsStore, "render:total", renderStart, renderDur);
|
|
192
182
|
const rscHeaders: Record<string, string> = {
|
|
193
183
|
"content-type": "text/x-component;charset=utf-8",
|
|
194
184
|
vary: "accept, X-Rango-State, X-RSC-Router-Client-Path",
|
|
185
|
+
// Router identity, so the client can verify pre-decode (before importing
|
|
186
|
+
// chunks) that this content payload belongs to its app and refuse a
|
|
187
|
+
// foreign one (cache/proxy/bug). Control-only reload/redirect responses
|
|
188
|
+
// are deliberately NOT stamped. See browser/response-adapter.ts.
|
|
189
|
+
"X-RSC-Router-Id": ctx.router.id,
|
|
195
190
|
};
|
|
191
|
+
// Tell the client's prefetch cache to scope this response to its source
|
|
192
|
+
// URL (instead of the default source-agnostic wildcard). Intercept
|
|
193
|
+
// responses depend on the source page matching an intercept rule, so
|
|
194
|
+
// they must not be reused for navigations from other sources.
|
|
195
|
+
if (hasInterceptSlots) {
|
|
196
|
+
rscHeaders["x-rsc-prefetch-scope"] = "source";
|
|
197
|
+
}
|
|
196
198
|
// Enable browser HTTP caching for prefetch responses only.
|
|
197
199
|
// Requires X-Rango-Prefetch header (sent by Link prefetch fetch),
|
|
198
200
|
// non-intercept context (intercept responses depend on source page),
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createResponseWithMergedHeaders,
|
|
9
9
|
carryOverRedirectHeaders,
|
|
10
10
|
} from "./helpers.js";
|
|
11
|
+
import { isRedirectResponse } from "../response-utils.js";
|
|
11
12
|
|
|
12
13
|
// W3 -----------------------------------------------------------------------
|
|
13
14
|
|
|
@@ -18,16 +19,14 @@ import {
|
|
|
18
19
|
*/
|
|
19
20
|
export function extractRedirectResponse(value: unknown): Response | null {
|
|
20
21
|
if (!(value instanceof Response)) return null;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
22
|
+
if (!isRedirectResponse(value)) return null;
|
|
23
|
+
const location = value.headers.get("Location")!;
|
|
24
|
+
const redirect = createResponseWithMergedHeaders(null, {
|
|
25
|
+
status: value.status,
|
|
26
|
+
headers: { Location: location },
|
|
27
|
+
});
|
|
28
|
+
carryOverRedirectHeaders(value, redirect);
|
|
29
|
+
return redirect;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -40,3 +39,17 @@ export function warnNonRedirectPeResponse(): void {
|
|
|
40
39
|
`ignored — the page will re-render at the current URL instead.`,
|
|
41
40
|
);
|
|
42
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Warn when a non-redirect Response is returned (not thrown) from an action
|
|
45
|
+
* on the JS (fetch) path. A raw Response cannot be serialized into Flight, so
|
|
46
|
+
* it is discarded — mirroring the PE path. Use `throw redirect('/path')` for
|
|
47
|
+
* redirects.
|
|
48
|
+
*/
|
|
49
|
+
export function warnNonRedirectActionResponse(actionId: string): void {
|
|
50
|
+
console.warn(
|
|
51
|
+
`[@rangojs/router] Server action "${actionId}" returned a Response ` +
|
|
52
|
+
`that is not a redirect. Non-redirect Responses cannot be serialized ` +
|
|
53
|
+
`and are ignored. Use \`throw redirect('/path')\` for redirects.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
package/src/rsc/server-action.ts
CHANGED
|
@@ -18,31 +18,19 @@
|
|
|
18
18
|
import {
|
|
19
19
|
requireRequestContext,
|
|
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";
|
|
25
23
|
import type { RscPayload } from "./types.js";
|
|
26
24
|
import {
|
|
27
25
|
hasBodyContent,
|
|
28
26
|
createResponseWithMergedHeaders,
|
|
29
27
|
createSimpleRedirectResponse,
|
|
30
|
-
|
|
28
|
+
interceptRedirectForPartial,
|
|
29
|
+
attachLocationStateIfPresent,
|
|
31
30
|
} from "./helpers.js";
|
|
31
|
+
import { warnNonRedirectActionResponse } from "./runtime-warnings.js";
|
|
32
32
|
import type { HandlerContext } from "./handler-context.js";
|
|
33
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
|
-
}
|
|
45
|
-
|
|
46
34
|
/**
|
|
47
35
|
* Data flowing from action execution to the revalidation phase.
|
|
48
36
|
* When the action completes without redirect/error-boundary, the handler
|
|
@@ -97,7 +85,10 @@ export async function executeServerAction<TEnv>(
|
|
|
97
85
|
args = await ctx.decodeReply(body, { temporaryReferences });
|
|
98
86
|
}
|
|
99
87
|
} catch (error) {
|
|
100
|
-
|
|
88
|
+
// Keep the original error as `cause` for server-side logging, but do not
|
|
89
|
+
// interpolate it into the message: that string can surface to the client
|
|
90
|
+
// and may leak decode internals.
|
|
91
|
+
throw new Error("Failed to decode action arguments", {
|
|
101
92
|
cause: error,
|
|
102
93
|
});
|
|
103
94
|
}
|
|
@@ -109,51 +100,35 @@ export async function executeServerAction<TEnv>(
|
|
|
109
100
|
|
|
110
101
|
try {
|
|
111
102
|
loadedAction = await ctx.loadServerAction(actionId);
|
|
112
|
-
|
|
103
|
+
let data = await loadedAction!.apply(null, args);
|
|
113
104
|
|
|
114
|
-
// Intercept redirect
|
|
115
|
-
//
|
|
116
|
-
// and the revalidation step would run unnecessarily.
|
|
105
|
+
// Intercept redirect Responses: serializing one as the action returnValue
|
|
106
|
+
// would fail, and revalidation would run needlessly.
|
|
117
107
|
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;
|
|
108
|
+
const intercepted = interceptRedirectForPartial(
|
|
109
|
+
data,
|
|
110
|
+
ctx.createRedirectFlightResponse,
|
|
111
|
+
);
|
|
112
|
+
if (intercepted) return intercepted;
|
|
113
|
+
|
|
114
|
+
// Non-redirect Response returned (not thrown): a raw Response cannot be
|
|
115
|
+
// serialized into Flight. Discard it and re-render — mirroring the PE
|
|
116
|
+
// path (progressive-enhancement.ts) so JS and no-JS behave identically.
|
|
117
|
+
if (process.env.NODE_ENV !== "production") {
|
|
118
|
+
warnNonRedirectActionResponse(actionId);
|
|
133
119
|
}
|
|
120
|
+
data = undefined;
|
|
134
121
|
}
|
|
135
122
|
|
|
136
123
|
returnValue = { ok: true, data };
|
|
137
124
|
} catch (error) {
|
|
138
125
|
// Handle thrown redirect (e.g., throw redirect('/path'))
|
|
139
126
|
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
|
-
}
|
|
127
|
+
const intercepted = interceptRedirectForPartial(
|
|
128
|
+
error,
|
|
129
|
+
ctx.createRedirectFlightResponse,
|
|
130
|
+
);
|
|
131
|
+
if (intercepted) return intercepted;
|
|
157
132
|
|
|
158
133
|
// Non-redirect Response thrown from action — this will be treated
|
|
159
134
|
// as a regular error and routed to the error boundary. Warn in dev
|
|
@@ -208,10 +183,15 @@ export async function executeServerAction<TEnv>(
|
|
|
208
183
|
const payload: RscPayload = {
|
|
209
184
|
metadata: {
|
|
210
185
|
pathname: url.pathname,
|
|
186
|
+
// routerId exposed for the frontend (current app identity); see
|
|
187
|
+
// rsc-rendering.ts partial branch.
|
|
188
|
+
routerId: ctx.router.id,
|
|
211
189
|
segments: errorResult.segments,
|
|
212
190
|
isPartial: true,
|
|
213
191
|
matched: errorResult.matched,
|
|
214
192
|
diff: errorResult.diff,
|
|
193
|
+
resolvedIds: errorResult.resolvedIds,
|
|
194
|
+
params: errorResult.params,
|
|
215
195
|
isError: true,
|
|
216
196
|
handles: handleStore.stream(),
|
|
217
197
|
version: ctx.version,
|
|
@@ -225,28 +205,39 @@ export async function executeServerAction<TEnv>(
|
|
|
225
205
|
|
|
226
206
|
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
227
207
|
temporaryReferences,
|
|
208
|
+
onError: (error: unknown) => {
|
|
209
|
+
ctx.callOnError(error, "rendering", { request, url, env });
|
|
210
|
+
},
|
|
228
211
|
});
|
|
229
212
|
|
|
230
213
|
return createResponseWithMergedHeaders(rscStream, {
|
|
231
214
|
status: actionStatus,
|
|
232
|
-
headers: {
|
|
215
|
+
headers: {
|
|
216
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
217
|
+
// Router identity for the client's pre-decode integrity check (the
|
|
218
|
+
// action apply path has no post-decode guard). See response-adapter.
|
|
219
|
+
"X-RSC-Router-Id": ctx.router.id,
|
|
220
|
+
},
|
|
233
221
|
});
|
|
234
222
|
}
|
|
235
223
|
}
|
|
236
224
|
|
|
237
225
|
// Build continuation for the revalidation phase
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
226
|
+
const actionMeta = loadedAction as
|
|
227
|
+
| { $id?: string; $$id?: string }
|
|
228
|
+
| undefined;
|
|
229
|
+
const resolvedActionId = actionMeta?.$id ?? actionMeta?.$$id ?? actionId;
|
|
242
230
|
|
|
243
231
|
return {
|
|
244
232
|
returnValue,
|
|
245
233
|
actionStatus,
|
|
246
234
|
temporaryReferences,
|
|
247
235
|
actionContext: {
|
|
236
|
+
// Defensive copy of the already-parsed url (avoids re-parsing
|
|
237
|
+
// request.url). actionUrl is persisted into the continuation and later
|
|
238
|
+
// flows into matchPartial, so it must not alias the handler's live url.
|
|
248
239
|
actionId: resolvedActionId,
|
|
249
|
-
actionUrl: new URL(
|
|
240
|
+
actionUrl: new URL(url),
|
|
250
241
|
actionResult: returnValue.data,
|
|
251
242
|
formData: actionFormData,
|
|
252
243
|
},
|
|
@@ -285,8 +276,8 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
285
276
|
);
|
|
286
277
|
|
|
287
278
|
if (!matchResult) {
|
|
288
|
-
// matchPartial returns null when the route is a redirect or
|
|
289
|
-
//
|
|
279
|
+
// matchPartial returns null when the route is a redirect or no previous-URL
|
|
280
|
+
// context could be resolved. Check for redirect first.
|
|
290
281
|
const fullMatch = await ctx.router.match(request, { env });
|
|
291
282
|
setRequestContextParams(fullMatch.params, fullMatch.routeName);
|
|
292
283
|
|
|
@@ -297,14 +288,17 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
297
288
|
return createSimpleRedirectResponse(fullMatch.redirect);
|
|
298
289
|
}
|
|
299
290
|
|
|
300
|
-
// Non-redirect: this branch is only reachable when
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
291
|
+
// Non-redirect: this branch is only reachable when no previous URL could
|
|
292
|
+
// be resolved (neither X-RSC-Router-Client-Path nor a usable Referer), or
|
|
293
|
+
// the previous URL was unparseable (defensive). The client requires
|
|
294
|
+
// isPartial for action responses, so producing a full payload here would
|
|
295
|
+
// be rejected. Return 500 instead.
|
|
304
296
|
throw new Error(
|
|
305
297
|
`[RSC] matchPartial returned null for a non-redirect route ` +
|
|
306
298
|
`during action revalidation (${url.pathname}). This indicates ` +
|
|
307
|
-
`a malformed action request
|
|
299
|
+
`a malformed action request: no previous-URL context could be ` +
|
|
300
|
+
`resolved (neither X-RSC-Router-Client-Path nor a usable Referer), ` +
|
|
301
|
+
`or the previous URL was unparseable.`,
|
|
308
302
|
);
|
|
309
303
|
}
|
|
310
304
|
|
|
@@ -314,10 +308,15 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
314
308
|
const payload: RscPayload = {
|
|
315
309
|
metadata: {
|
|
316
310
|
pathname: url.pathname,
|
|
311
|
+
// routerId exposed for the frontend (current app identity); see
|
|
312
|
+
// rsc-rendering.ts partial branch.
|
|
313
|
+
routerId: ctx.router.id,
|
|
317
314
|
segments: matchResult.segments,
|
|
318
315
|
isPartial: true,
|
|
319
316
|
matched: matchResult.matched,
|
|
320
317
|
diff: matchResult.diff,
|
|
318
|
+
resolvedIds: matchResult.resolvedIds,
|
|
319
|
+
params: matchResult.params,
|
|
321
320
|
slots: matchResult.slots,
|
|
322
321
|
handles: handleStore.stream(),
|
|
323
322
|
version: ctx.version,
|
|
@@ -325,11 +324,14 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
325
324
|
returnValue,
|
|
326
325
|
};
|
|
327
326
|
|
|
328
|
-
|
|
327
|
+
attachLocationStateIfPresent(payload);
|
|
329
328
|
|
|
330
329
|
const renderStart = performance.now();
|
|
331
330
|
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
332
331
|
temporaryReferences,
|
|
332
|
+
onError: (error: unknown) => {
|
|
333
|
+
ctx.callOnError(error, "rendering", { request, url, env });
|
|
334
|
+
},
|
|
333
335
|
});
|
|
334
336
|
const rscSerializeDur = performance.now() - renderStart;
|
|
335
337
|
// This measures synchronous stream creation, not end-to-end stream consumption.
|
|
@@ -343,6 +345,11 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
343
345
|
|
|
344
346
|
return createResponseWithMergedHeaders(rscStream, {
|
|
345
347
|
status: actionStatus,
|
|
346
|
-
headers: {
|
|
348
|
+
headers: {
|
|
349
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
350
|
+
// Router identity for the client's pre-decode integrity check (the action
|
|
351
|
+
// apply path has no post-decode guard). See response-adapter.
|
|
352
|
+
"X-RSC-Router-Id": ctx.router.id,
|
|
353
|
+
},
|
|
347
354
|
});
|
|
348
355
|
}
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -77,7 +77,7 @@ export function getSSRSetup<TEnv>(
|
|
|
77
77
|
url: URL,
|
|
78
78
|
metricsStore: MetricsStore | undefined,
|
|
79
79
|
): Promise<SSRSetup> {
|
|
80
|
-
const early = _getRequestContext()?.
|
|
80
|
+
const early = _getRequestContext()?._variables?.[SSR_SETUP_VAR] as
|
|
81
81
|
| Promise<SSRSetup>
|
|
82
82
|
| undefined;
|
|
83
83
|
if (early) return early;
|
|
@@ -98,7 +98,7 @@ export function getSSRSetup<TEnv>(
|
|
|
98
98
|
* the isRscRequest decision in rsc-rendering.ts.
|
|
99
99
|
*
|
|
100
100
|
* Note: response/mime routes are excluded by the caller — this function
|
|
101
|
-
* runs after
|
|
101
|
+
* runs after classifyRequest() determines the request mode.
|
|
102
102
|
*/
|
|
103
103
|
export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
104
104
|
if (
|
|
@@ -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
|
+
}
|
package/src/rsc/types.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ResolvedSegment, SlotState } from "../types.js";
|
|
9
9
|
import type { HandleData } from "../server/handle-store.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { RangoInternal } from "../router/router-interfaces.js";
|
|
11
11
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -19,10 +19,19 @@ export interface RscPayload {
|
|
|
19
19
|
metadata?: {
|
|
20
20
|
pathname: string;
|
|
21
21
|
segments: ResolvedSegment[];
|
|
22
|
+
/** Router instance ID. When this changes between navigations, the client
|
|
23
|
+
* discards cached segments and does a full tree replacement (app switch). */
|
|
24
|
+
routerId?: string;
|
|
22
25
|
isPartial?: boolean;
|
|
23
26
|
isError?: boolean;
|
|
24
27
|
matched?: string[];
|
|
25
28
|
diff?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* All segment ids re-resolved on the server, including null-component
|
|
31
|
+
* ones excluded from `segments`/`diff`. Drives client-side handle-bucket
|
|
32
|
+
* cleanup. Superset of `diff`. See MatchResult.resolvedIds.
|
|
33
|
+
*/
|
|
34
|
+
resolvedIds?: string[];
|
|
26
35
|
/** Merged route params from the matched route */
|
|
27
36
|
params?: Record<string, string>;
|
|
28
37
|
slots?: Record<string, SlotState>;
|
|
@@ -34,19 +43,26 @@ export interface RscPayload {
|
|
|
34
43
|
version?: string;
|
|
35
44
|
/** TTL in milliseconds for the client-side in-memory prefetch cache */
|
|
36
45
|
prefetchCacheTTL?: number;
|
|
46
|
+
/** Server-resolved rango state cookie name; the client reads it verbatim. */
|
|
47
|
+
stateCookieName?: string;
|
|
37
48
|
/** Theme configuration for FOUC prevention */
|
|
38
49
|
themeConfig?: ResolvedThemeConfig | null;
|
|
39
50
|
/** Initial theme from cookie (for SSR hydration) */
|
|
40
51
|
initialTheme?: Theme;
|
|
52
|
+
/** URL prefix for all routes (from createRouter({ basename })). */
|
|
53
|
+
basename?: string;
|
|
41
54
|
/** Whether connection warmup is enabled */
|
|
42
55
|
warmupEnabled?: boolean;
|
|
43
|
-
/**
|
|
44
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Server-side redirect with optional state (for partial requests).
|
|
58
|
+
* `external: true` (from redirect(url, { external: true })) tells the client
|
|
59
|
+
* to hard-navigate to an off-host target instead of validating same-origin.
|
|
60
|
+
*/
|
|
61
|
+
redirect?: { url: string; external?: boolean };
|
|
45
62
|
/** Server-set location state to include in history.pushState */
|
|
46
63
|
locationState?: Record<string, unknown>;
|
|
47
64
|
};
|
|
48
65
|
returnValue?: { ok: boolean; data: unknown };
|
|
49
|
-
formState?: unknown;
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
/**
|
|
@@ -63,7 +79,10 @@ export interface RSCDependencies {
|
|
|
63
79
|
*/
|
|
64
80
|
renderToReadableStream: <T>(
|
|
65
81
|
payload: T,
|
|
66
|
-
options?: {
|
|
82
|
+
options?: {
|
|
83
|
+
temporaryReferences?: unknown;
|
|
84
|
+
onError?: (error: unknown) => string | void;
|
|
85
|
+
},
|
|
67
86
|
) => ReadableStream<Uint8Array>;
|
|
68
87
|
|
|
69
88
|
/**
|
|
@@ -171,7 +190,7 @@ export interface CreateRSCHandlerOptions<
|
|
|
171
190
|
/**
|
|
172
191
|
* The RSC router instance
|
|
173
192
|
*/
|
|
174
|
-
router:
|
|
193
|
+
router: RangoInternal<TEnv, TRoutes>;
|
|
175
194
|
|
|
176
195
|
/**
|
|
177
196
|
* RSC dependencies from @vitejs/plugin-rsc/rsc.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Runtime-safe detection of a test runner (Vitest), used to decide whether a
|
|
2
|
+
// create*() call with no plugin-injected $$id may fall back to a synthetic id (a
|
|
3
|
+
// bare test) or must fail loud (dev / a real build).
|
|
4
|
+
//
|
|
5
|
+
// `process` is absent in some target runtimes (the browser, certain edge/worker
|
|
6
|
+
// RSC environments), so probe it through `globalThis` with optional chaining —
|
|
7
|
+
// NEVER a bare `process.env.VITEST`, which would ReferenceError before the
|
|
8
|
+
// intended error is thrown. Unlike `process.env.NODE_ENV` (folded by the app's
|
|
9
|
+
// build `define`), `VITEST` is not folded, so this stays a small runtime check;
|
|
10
|
+
// it lives only on the create*() error path (id missing), which never runs in a
|
|
11
|
+
// correct production build.
|
|
12
|
+
//
|
|
13
|
+
// Vitest sets `VITEST` in every test process — the node project and the
|
|
14
|
+
// react-server forks alike (the RSC project forces NODE_ENV=production, so NODE_ENV
|
|
15
|
+
// cannot distinguish it from a real build; `VITEST` can). A real build never sets it.
|
|
16
|
+
export function isUnderTestRunner(): boolean {
|
|
17
|
+
return !!globalThis.process?.env?.VITEST;
|
|
18
|
+
}
|