@rangojs/router 0.0.0-experimental.0f44aca1
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 +5 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +5214 -0
- package/package.json +176 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +220 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +645 -0
- package/src/browser/navigation-client.ts +215 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +550 -0
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +360 -0
- package/src/browser/react/NavigationProvider.tsx +386 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +431 -0
- package/src/browser/scroll-restoration.ts +400 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +538 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +540 -0
- package/src/cache/cf/index.ts +25 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +43 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +275 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +267 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +192 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +316 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1239 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1002 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +914 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +365 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive Enhancement Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles no-JS form submissions. When JavaScript is disabled, React renders
|
|
5
|
+
* forms with hidden fields ($ACTION_REF_*, $ACTION_KEY) containing the action
|
|
6
|
+
* reference. We detect these and return HTML instead of RSC stream.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
requireRequestContext,
|
|
11
|
+
setRequestContextParams,
|
|
12
|
+
} from "../server/request-context.js";
|
|
13
|
+
import { getSSRSetup } from "./ssr-setup.js";
|
|
14
|
+
import type { MiddlewareFn } from "../router/middleware.js";
|
|
15
|
+
import { executeMiddleware } from "../router/middleware.js";
|
|
16
|
+
import type { RscPayload, ReactFormState } from "./types.js";
|
|
17
|
+
import {
|
|
18
|
+
createResponseWithMergedHeaders,
|
|
19
|
+
finalizeResponse,
|
|
20
|
+
buildRouteMiddlewareEntries,
|
|
21
|
+
} from "./helpers.js";
|
|
22
|
+
import type { HandlerContext } from "./handler-context.js";
|
|
23
|
+
import {
|
|
24
|
+
extractRedirectResponse,
|
|
25
|
+
warnNonRedirectPeResponse,
|
|
26
|
+
} from "./runtime-warnings.js";
|
|
27
|
+
|
|
28
|
+
export interface PeRouteMiddlewareInfo {
|
|
29
|
+
routeMiddleware?: Array<{
|
|
30
|
+
handler: MiddlewareFn;
|
|
31
|
+
params: Record<string, string>;
|
|
32
|
+
}>;
|
|
33
|
+
variables: Record<string, any>;
|
|
34
|
+
routeReverse?: (
|
|
35
|
+
name: string,
|
|
36
|
+
params?: Record<string, string>,
|
|
37
|
+
search?: Record<string, unknown>,
|
|
38
|
+
) => string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function handleProgressiveEnhancement<TEnv>(
|
|
42
|
+
ctx: HandlerContext<TEnv>,
|
|
43
|
+
request: Request,
|
|
44
|
+
env: TEnv,
|
|
45
|
+
url: URL,
|
|
46
|
+
isAction: boolean,
|
|
47
|
+
handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
48
|
+
nonce: string | undefined,
|
|
49
|
+
routeMwInfo?: PeRouteMiddlewareInfo,
|
|
50
|
+
): Promise<Response | null> {
|
|
51
|
+
const contentType = request.headers.get("content-type") || "";
|
|
52
|
+
const isFormSubmission =
|
|
53
|
+
contentType.includes("multipart/form-data") ||
|
|
54
|
+
contentType.includes("application/x-www-form-urlencoded");
|
|
55
|
+
|
|
56
|
+
if (request.method !== "POST" || isAction || !isFormSubmission) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Clone the request to read FormData without consuming it.
|
|
61
|
+
// Wrap in try-catch so malformed POST bodies are reported as action
|
|
62
|
+
// errors, not routing errors from the outer catch in handler.ts.
|
|
63
|
+
let formData: FormData;
|
|
64
|
+
try {
|
|
65
|
+
formData = await request.clone().formData();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Attempt error boundary rendering so the user sees a meaningful page.
|
|
68
|
+
const errorHtml = await renderPeErrorBoundary(
|
|
69
|
+
ctx,
|
|
70
|
+
request,
|
|
71
|
+
env,
|
|
72
|
+
url,
|
|
73
|
+
error,
|
|
74
|
+
handleStore,
|
|
75
|
+
nonce,
|
|
76
|
+
);
|
|
77
|
+
if (errorHtml) {
|
|
78
|
+
ctx.callOnError(error, "action", {
|
|
79
|
+
request,
|
|
80
|
+
url,
|
|
81
|
+
env,
|
|
82
|
+
handledByBoundary: true,
|
|
83
|
+
});
|
|
84
|
+
return errorHtml;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
ctx.callOnError(error, "action", {
|
|
88
|
+
request,
|
|
89
|
+
url,
|
|
90
|
+
env,
|
|
91
|
+
handledByBoundary: false,
|
|
92
|
+
});
|
|
93
|
+
console.error("[RSC] Progressive enhancement form parse error:", error);
|
|
94
|
+
return createResponseWithMergedHeaders(null, { status: 400 });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Look for React's progressive enhancement hidden fields
|
|
98
|
+
let isDirectAction = false;
|
|
99
|
+
let isUseActionState = false;
|
|
100
|
+
let directActionId: string | null = null;
|
|
101
|
+
|
|
102
|
+
formData.forEach((_value, key) => {
|
|
103
|
+
if (key.startsWith("$ACTION_ID_")) {
|
|
104
|
+
isDirectAction = true;
|
|
105
|
+
directActionId = key.slice("$ACTION_ID_".length);
|
|
106
|
+
} else if (key.startsWith("$ACTION_REF_")) {
|
|
107
|
+
isUseActionState = true;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!isDirectAction && !isUseActionState) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Execute action and return HTML
|
|
116
|
+
let actionResult: unknown = undefined;
|
|
117
|
+
let reactFormState: ReactFormState | null = null;
|
|
118
|
+
|
|
119
|
+
if (isUseActionState) {
|
|
120
|
+
// Decode and extract action identity before execution so error
|
|
121
|
+
// handlers can report actionId even when the action throws.
|
|
122
|
+
let useActionStateId: string | undefined;
|
|
123
|
+
try {
|
|
124
|
+
const boundAction = await ctx.decodeAction(formData);
|
|
125
|
+
// React's custom .bind() preserves $$id on server references.
|
|
126
|
+
useActionStateId = (boundAction as { $$id?: string }).$$id ?? undefined;
|
|
127
|
+
actionResult = await boundAction();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
// Handle thrown redirect (e.g., throw redirect('/path'))
|
|
130
|
+
const redirectResponse = extractRedirectResponse(error);
|
|
131
|
+
if (redirectResponse) return redirectResponse;
|
|
132
|
+
|
|
133
|
+
// Attempt error boundary rendering for the PE path
|
|
134
|
+
const errorHtml = await renderPeErrorBoundary(
|
|
135
|
+
ctx,
|
|
136
|
+
request,
|
|
137
|
+
env,
|
|
138
|
+
url,
|
|
139
|
+
error,
|
|
140
|
+
handleStore,
|
|
141
|
+
nonce,
|
|
142
|
+
useActionStateId,
|
|
143
|
+
);
|
|
144
|
+
if (errorHtml) return errorHtml;
|
|
145
|
+
|
|
146
|
+
ctx.callOnError(error, "action", {
|
|
147
|
+
request,
|
|
148
|
+
url,
|
|
149
|
+
env,
|
|
150
|
+
actionId: useActionStateId,
|
|
151
|
+
handledByBoundary: false,
|
|
152
|
+
});
|
|
153
|
+
console.error("[RSC] Progressive enhancement action error:", error);
|
|
154
|
+
}
|
|
155
|
+
} else if (isDirectAction && directActionId) {
|
|
156
|
+
const temporaryReferences = ctx.createTemporaryReferenceSet();
|
|
157
|
+
|
|
158
|
+
let args: unknown[] = [];
|
|
159
|
+
try {
|
|
160
|
+
args = await ctx.decodeReply(formData, { temporaryReferences });
|
|
161
|
+
} catch {
|
|
162
|
+
args = [formData];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const loadedAction = await ctx.loadServerAction(directActionId);
|
|
167
|
+
actionResult = await loadedAction.apply(null, args);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
// Handle thrown redirect (e.g., throw redirect('/path'))
|
|
170
|
+
const redirectResponse = extractRedirectResponse(error);
|
|
171
|
+
if (redirectResponse) return redirectResponse;
|
|
172
|
+
|
|
173
|
+
// Attempt error boundary rendering for the PE path
|
|
174
|
+
const errorHtml = await renderPeErrorBoundary(
|
|
175
|
+
ctx,
|
|
176
|
+
request,
|
|
177
|
+
env,
|
|
178
|
+
url,
|
|
179
|
+
error,
|
|
180
|
+
handleStore,
|
|
181
|
+
nonce,
|
|
182
|
+
directActionId,
|
|
183
|
+
);
|
|
184
|
+
if (errorHtml) return errorHtml;
|
|
185
|
+
|
|
186
|
+
ctx.callOnError(error, "action", {
|
|
187
|
+
request,
|
|
188
|
+
url,
|
|
189
|
+
env,
|
|
190
|
+
actionId: directActionId,
|
|
191
|
+
handledByBoundary: false,
|
|
192
|
+
});
|
|
193
|
+
console.error("[RSC] Progressive enhancement action error:", error);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle Response returned from action during PE.
|
|
198
|
+
// In the JS path, executeServerAction intercepts redirect Responses and
|
|
199
|
+
// short-circuits. The PE path must handle them too.
|
|
200
|
+
if (actionResult instanceof Response) {
|
|
201
|
+
const redirectResponse = extractRedirectResponse(actionResult);
|
|
202
|
+
if (redirectResponse) return redirectResponse;
|
|
203
|
+
// W3: Non-redirect Response — discard it so it doesn't flow into
|
|
204
|
+
// decodeFormState or the re-render payload.
|
|
205
|
+
if (process.env.NODE_ENV !== "production") {
|
|
206
|
+
warnNonRedirectPeResponse();
|
|
207
|
+
}
|
|
208
|
+
actionResult = undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Decode form state for useActionState progressive enhancement
|
|
212
|
+
try {
|
|
213
|
+
reactFormState = await ctx.decodeFormState(actionResult, formData);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
ctx.callOnError(error, "action", {
|
|
216
|
+
request,
|
|
217
|
+
url,
|
|
218
|
+
env,
|
|
219
|
+
handledByBoundary: false,
|
|
220
|
+
});
|
|
221
|
+
console.error("[RSC] Failed to decode form state:", error);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Re-render the page and return HTML.
|
|
225
|
+
// Route middleware wraps the render so context variables, headers, and
|
|
226
|
+
// cookies set by route middleware are available during re-render — matching
|
|
227
|
+
// the behavior of JS-enabled requests.
|
|
228
|
+
const renderPage = async (): Promise<Response> => {
|
|
229
|
+
const renderRequest = new Request(url.toString(), {
|
|
230
|
+
method: "GET",
|
|
231
|
+
headers: new Headers({ accept: "text/html" }),
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const match = await ctx.router.match(renderRequest, { env });
|
|
235
|
+
|
|
236
|
+
if (match.redirect) {
|
|
237
|
+
return createResponseWithMergedHeaders(null, {
|
|
238
|
+
status: 308,
|
|
239
|
+
headers: { Location: match.redirect },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const payload: RscPayload = {
|
|
244
|
+
metadata: {
|
|
245
|
+
pathname: url.pathname,
|
|
246
|
+
segments: match.segments,
|
|
247
|
+
matched: match.matched,
|
|
248
|
+
diff: match.diff,
|
|
249
|
+
isPartial: false,
|
|
250
|
+
rootLayout: ctx.router.rootLayout,
|
|
251
|
+
handles: handleStore.stream(),
|
|
252
|
+
version: ctx.version,
|
|
253
|
+
themeConfig: ctx.router.themeConfig,
|
|
254
|
+
warmupEnabled: ctx.router.warmupEnabled,
|
|
255
|
+
initialTheme: requireRequestContext().theme,
|
|
256
|
+
},
|
|
257
|
+
formState: actionResult,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const rscStream = ctx.renderToReadableStream<RscPayload>(payload);
|
|
261
|
+
// metricsStore=undefined is safe: the handler already stashed the early
|
|
262
|
+
// SSR setup promise on request variables, so getSSRSetup returns it
|
|
263
|
+
// without falling back to a fresh startSSRSetup.
|
|
264
|
+
const [ssrModule, streamMode] = await getSSRSetup(
|
|
265
|
+
ctx,
|
|
266
|
+
request,
|
|
267
|
+
env,
|
|
268
|
+
url,
|
|
269
|
+
undefined,
|
|
270
|
+
);
|
|
271
|
+
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
272
|
+
formState: reactFormState,
|
|
273
|
+
nonce,
|
|
274
|
+
streamMode,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return createResponseWithMergedHeaders(htmlStream, {
|
|
278
|
+
headers: { "content-type": "text/html;charset=utf-8" },
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Execute route middleware wrapping the render, if any.
|
|
283
|
+
// finalizeResponse drains onResponse callbacks that middleware short-circuits
|
|
284
|
+
// may leave behind (executeMiddleware does not finalize them itself).
|
|
285
|
+
if (routeMwInfo?.routeMiddleware && routeMwInfo.routeMiddleware.length > 0) {
|
|
286
|
+
return finalizeResponse(
|
|
287
|
+
await executeMiddleware(
|
|
288
|
+
buildRouteMiddlewareEntries(routeMwInfo.routeMiddleware),
|
|
289
|
+
request,
|
|
290
|
+
env,
|
|
291
|
+
routeMwInfo.variables,
|
|
292
|
+
renderPage,
|
|
293
|
+
routeMwInfo.routeReverse,
|
|
294
|
+
),
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return renderPage();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Attempt to render an error boundary as full HTML for the PE path.
|
|
303
|
+
* Returns null if no error boundary is found (caller falls through to
|
|
304
|
+
* normal page re-render).
|
|
305
|
+
*/
|
|
306
|
+
async function renderPeErrorBoundary<TEnv>(
|
|
307
|
+
ctx: HandlerContext<TEnv>,
|
|
308
|
+
request: Request,
|
|
309
|
+
env: TEnv,
|
|
310
|
+
url: URL,
|
|
311
|
+
error: unknown,
|
|
312
|
+
handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
313
|
+
nonce: string | undefined,
|
|
314
|
+
actionId?: string | null,
|
|
315
|
+
): Promise<Response | null> {
|
|
316
|
+
let errorResult;
|
|
317
|
+
try {
|
|
318
|
+
errorResult = await ctx.router.matchError(request, { env }, error, "route");
|
|
319
|
+
} catch (matchErr) {
|
|
320
|
+
ctx.callOnError(error, "action", {
|
|
321
|
+
request,
|
|
322
|
+
url,
|
|
323
|
+
env,
|
|
324
|
+
actionId: actionId ?? undefined,
|
|
325
|
+
handledByBoundary: false,
|
|
326
|
+
});
|
|
327
|
+
throw matchErr;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!errorResult) return null;
|
|
331
|
+
|
|
332
|
+
ctx.callOnError(error, "action", {
|
|
333
|
+
request,
|
|
334
|
+
url,
|
|
335
|
+
env,
|
|
336
|
+
actionId: actionId ?? undefined,
|
|
337
|
+
handledByBoundary: true,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
setRequestContextParams(errorResult.params, errorResult.routeName);
|
|
341
|
+
|
|
342
|
+
const payload: RscPayload = {
|
|
343
|
+
metadata: {
|
|
344
|
+
pathname: url.pathname,
|
|
345
|
+
segments: errorResult.segments,
|
|
346
|
+
matched: errorResult.matched,
|
|
347
|
+
diff: errorResult.diff,
|
|
348
|
+
isPartial: false,
|
|
349
|
+
isError: true,
|
|
350
|
+
rootLayout: ctx.router.rootLayout,
|
|
351
|
+
handles: handleStore.stream(),
|
|
352
|
+
version: ctx.version,
|
|
353
|
+
themeConfig: ctx.router.themeConfig,
|
|
354
|
+
warmupEnabled: ctx.router.warmupEnabled,
|
|
355
|
+
initialTheme: requireRequestContext().theme,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const rscStream = ctx.renderToReadableStream<RscPayload>(payload);
|
|
360
|
+
// metricsStore=undefined is safe: the handler already stashed the early
|
|
361
|
+
// SSR setup promise on request variables, so getSSRSetup returns it
|
|
362
|
+
// without falling back to a fresh startSSRSetup.
|
|
363
|
+
const [ssrModule, streamMode] = await getSSRSetup(
|
|
364
|
+
ctx,
|
|
365
|
+
request,
|
|
366
|
+
env,
|
|
367
|
+
url,
|
|
368
|
+
undefined,
|
|
369
|
+
);
|
|
370
|
+
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
371
|
+
nonce,
|
|
372
|
+
streamMode,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return createResponseWithMergedHeaders(htmlStream, {
|
|
376
|
+
status: 500,
|
|
377
|
+
headers: { "content-type": "text/html;charset=utf-8" },
|
|
378
|
+
});
|
|
379
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Error Payload Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds a ResponseError object from a caught error, controlling
|
|
5
|
+
* what information is exposed based on error type and environment.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { RouterError } from "../errors.js";
|
|
9
|
+
import type { ResponseError } from "../urls.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build a ResponseError payload from a caught error.
|
|
13
|
+
* RouterError messages are always exposed (developer-crafted).
|
|
14
|
+
* Standard Error messages are hidden in production.
|
|
15
|
+
*/
|
|
16
|
+
export function createResponseErrorPayload(
|
|
17
|
+
error: unknown,
|
|
18
|
+
isDev: boolean,
|
|
19
|
+
): ResponseError {
|
|
20
|
+
if (error instanceof RouterError) {
|
|
21
|
+
return {
|
|
22
|
+
message: error.message,
|
|
23
|
+
code: error.code,
|
|
24
|
+
...(error.type ? { type: error.type } : {}),
|
|
25
|
+
...(isDev && error.stack ? { stack: error.stack } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (error instanceof Error) {
|
|
29
|
+
return {
|
|
30
|
+
message: isDev ? error.message : "Internal Server Error",
|
|
31
|
+
...(isDev && error.stack ? { stack: error.stack } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
message: isDev ? String(error) : "Internal Server Error",
|
|
36
|
+
};
|
|
37
|
+
}
|