@rangojs/router 0.24.4 → 0.25.0
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/README.md +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/types.d.ts +8 -0
- package/dist/types/client-urls/client-urls.d.ts +6 -2
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +9 -5
- package/dist/types/client-urls/types.d.ts +29 -16
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -6
- package/dist/types/router/router-options.d.ts +4 -7
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +3 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +10 -1
- package/dist/types/rsc/shell-capture.d.ts +7 -0
- package/dist/types/rsc/types.d.ts +5 -0
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/context.d.ts +64 -4
- package/dist/types/server/request-context.d.ts +41 -4
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +41 -4
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +4 -3
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6816 -6547
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/caching/SKILL.md +3 -1
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +185 -32
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +32 -10
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +43 -8
- package/skills/prerender/SKILL.md +1 -2
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +85 -21
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +112 -84
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +2 -0
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/types.ts +8 -0
- package/src/cache/vercel/vercel-cache-store.ts +6 -1
- package/src/client-urls/client-urls.ts +202 -153
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +137 -66
- package/src/client-urls/types.ts +30 -15
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +27 -7
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +6 -8
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -7
- package/src/router/router-options.ts +4 -7
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +3 -2
- package/src/router/tracing.ts +2 -2
- package/src/router.ts +2 -9
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +83 -41
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +22 -0
- package/src/rsc/shell-capture.ts +11 -0
- package/src/rsc/types.ts +5 -0
- package/src/segment-system.tsx +125 -119
- package/src/server/context.ts +96 -5
- package/src/server/handle-store.ts +3 -2
- package/src/server/request-context.ts +42 -3
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/dispatch.ts +4 -5
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +77 -4
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +4 -3
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
package/src/rsc/helpers.ts
CHANGED
|
@@ -25,6 +25,8 @@ import type { MiddlewareEntry, MiddlewareFn } from "../router/middleware.js";
|
|
|
25
25
|
import { formatCacheSignalHeader } from "../router/telemetry.js";
|
|
26
26
|
import type { RscPayload } from "./types.js";
|
|
27
27
|
import { rewriteTrail, visitorUrl } from "../server/rewrite-trail.js";
|
|
28
|
+
import { guardRequestHeaders } from "../server/cookie-store.js";
|
|
29
|
+
import { bodylessRequest } from "../server/request-headers.js";
|
|
28
30
|
import type { HandlerContext } from "./handler-context.js";
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -380,6 +382,17 @@ export function buildRouteMiddlewareEntries<TEnv>(
|
|
|
380
382
|
}));
|
|
381
383
|
}
|
|
382
384
|
|
|
385
|
+
/**
|
|
386
|
+
* The request route middleware sees around the render after a server action
|
|
387
|
+
* or a no-JS form post: the page's GET (URL, headers without body framing or
|
|
388
|
+
* content-type, signal, workerd `cf`; no body). Route middleware never wraps the action,
|
|
389
|
+
* only that render, so it decides as for a GET of the page: a rewrite it
|
|
390
|
+
* skips for mutations still moves the render to its target (#1145).
|
|
391
|
+
*/
|
|
392
|
+
export function actionRenderRequest(request: Request): Request {
|
|
393
|
+
return guardRequestHeaders(bodylessRequest(request.url, request, "GET"));
|
|
394
|
+
}
|
|
395
|
+
|
|
383
396
|
/**
|
|
384
397
|
* Merge stub headers from the request context onto an existing Response in
|
|
385
398
|
* place, then drain onResponse callbacks. Used when a Response cannot flow
|
package/src/rsc/manifest-init.ts
CHANGED
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
ensureRouterManifest,
|
|
9
10
|
getGlobalRouteMap,
|
|
11
|
+
getRouterManifest,
|
|
12
|
+
getRouterTrie,
|
|
10
13
|
hasCachedManifest,
|
|
14
|
+
isRouterTrieAuthoritative,
|
|
11
15
|
setCachedManifest,
|
|
12
16
|
setRouterManifest,
|
|
13
17
|
setRouterTrie,
|
|
@@ -15,6 +19,55 @@ import {
|
|
|
15
19
|
} from "../route-map-builder.js";
|
|
16
20
|
import { mergeFullManifests } from "../build/merge-full-manifests.js";
|
|
17
21
|
import { isTransitionWhenValidationEnabled } from "../transition-when-ref.js";
|
|
22
|
+
import type { TrieNode } from "../router/route-trie-builder.js";
|
|
23
|
+
|
|
24
|
+
// A zero-route router builds no trie; recording that keeps it from rebuilding.
|
|
25
|
+
const NO_TRIE: unique symbol = Symbol("rango.noTrie");
|
|
26
|
+
|
|
27
|
+
// The trie each router object last installed. Router ids need not be unique
|
|
28
|
+
// across instances (a test helper calling createRouter({ id }) per test), and
|
|
29
|
+
// the per-id trie map cannot tell which instance a trie came from.
|
|
30
|
+
const installedTries = new WeakMap<object, TrieNode | typeof NO_TRIE>();
|
|
31
|
+
|
|
32
|
+
/** True when the trie installed for `router.id` was built from this router. */
|
|
33
|
+
export function isRouterTrieBuiltFrom(router: { id?: string }): boolean {
|
|
34
|
+
if (router.id === undefined) return false;
|
|
35
|
+
const installed = installedTries.get(router);
|
|
36
|
+
return (
|
|
37
|
+
installed !== undefined &&
|
|
38
|
+
installed === (getRouterTrie(router.id) ?? NO_TRIE)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The router fields ensureRouterTrie reads. */
|
|
43
|
+
export interface TrieSource {
|
|
44
|
+
id?: string;
|
|
45
|
+
urlpatterns?: unknown;
|
|
46
|
+
routeMap: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Install the trie `router` matches through, as a request finds it: the build
|
|
51
|
+
* manifest's trie when one is registered for its id, otherwise one built from
|
|
52
|
+
* its routes the way the dev handler builds it (rsc/handler.ts). Ids can repeat
|
|
53
|
+
* across router objects (tests), and another object's trie names other routes,
|
|
54
|
+
* so a trie this object did not build is rebuilt. Seeds the per-router manifest
|
|
55
|
+
* from `router.routeMap` when neither source set one, for reverse(). testing's
|
|
56
|
+
* dispatch matches through it, never the regex fallback alone (#1104, #1107).
|
|
57
|
+
*/
|
|
58
|
+
export async function ensureRouterTrie(router: TrieSource): Promise<void> {
|
|
59
|
+
const id = router.id;
|
|
60
|
+
if (!id) return;
|
|
61
|
+
if (!isRouterTrieAuthoritative(id) && !isRouterTrieBuiltFrom(router)) {
|
|
62
|
+
await ensureRouterManifest(id);
|
|
63
|
+
if (router.urlpatterns && !isRouterTrieAuthoritative(id)) {
|
|
64
|
+
await buildRouterTrieFromUrlpatterns(router);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!getRouterManifest(id)) {
|
|
68
|
+
setRouterManifest(id, router.routeMap as Record<string, string>);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
18
71
|
|
|
19
72
|
/**
|
|
20
73
|
* Build a fresh route trie from the router's URL-pattern mounts and store it in
|
|
@@ -63,9 +116,9 @@ export async function buildRouterTrieFromUrlpatterns(
|
|
|
63
116
|
// are no routes.
|
|
64
117
|
const { buildPerRouterTrie } = await import("../build/route-trie.js");
|
|
65
118
|
const trie = buildPerRouterTrie(generated);
|
|
66
|
-
if (trie)
|
|
67
|
-
|
|
68
|
-
|
|
119
|
+
if (trie) setRouterTrie(router.id, trie);
|
|
120
|
+
// Re-read: setRouterTrie stores a copy (nullPrototypeStaticChildren).
|
|
121
|
+
installedTries.set(router, trie ? getRouterTrie(router.id)! : NO_TRIE);
|
|
69
122
|
setRouterManifest(router.id, generated.routeManifest);
|
|
70
123
|
|
|
71
124
|
// Match the production discovery path: precompute leaf-include entries so the
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A prefetch whose render reported an error is not kept (#1170).
|
|
3
|
+
*
|
|
4
|
+
* WHY: its 200 left with the headers before a streamed part failed, so the
|
|
5
|
+
* client stored the error boundary as a healthy prefetch, served it to a
|
|
6
|
+
* click and respawned it. The status cannot change any more, and the body
|
|
7
|
+
* cannot end with an error: workerd delivers an errored body as a clean end,
|
|
8
|
+
* and in Chromium the error drops the rows a click that adopted the prefetch
|
|
9
|
+
* has not decoded yet (a Flight "network error" in place of the page).
|
|
10
|
+
*
|
|
11
|
+
* So the body ends cleanly with one more row, PREFETCH_FAILED_ROW, which the
|
|
12
|
+
* Flight decoder skips and teeWithCompletion (browser/response-adapter.ts)
|
|
13
|
+
* reads as a stream that did not end cleanly: the client evicts the prefetch
|
|
14
|
+
* and never respawns it. Prefetch responses carry no Cache-Control, so the
|
|
15
|
+
* browser HTTP cache keeps none of it either.
|
|
16
|
+
*
|
|
17
|
+
* The errors are the render's `_renderErrors`: a Flight error row, a streamed
|
|
18
|
+
* handler recovered into its boundary, a loader that threw (redirect() and
|
|
19
|
+
* notFound() are not errors). Each is recorded before Flight writes the row
|
|
20
|
+
* that carries it, so the list is complete once Flight closes the stream.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { PREFETCH_FAILED_ROW } from "../prefetch-markers.js";
|
|
24
|
+
|
|
25
|
+
export function markPrefetchOnRenderError(
|
|
26
|
+
response: Response,
|
|
27
|
+
renderErrors: readonly unknown[],
|
|
28
|
+
): Response {
|
|
29
|
+
if (!response.body) return response;
|
|
30
|
+
const marked = new TransformStream<Uint8Array, Uint8Array>({
|
|
31
|
+
flush(controller) {
|
|
32
|
+
if (renderErrors.length > 0) {
|
|
33
|
+
controller.enqueue(new TextEncoder().encode(PREFETCH_FAILED_ROW));
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return new Response(response.body.pipeThrough(marked), response);
|
|
38
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* reference. We detect these and return HTML instead of RSC stream.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { requestHeaders } from "../server/request-headers.js";
|
|
9
|
+
import { bodylessHeaders, requestHeaders } from "../server/request-headers.js";
|
|
10
10
|
import {
|
|
11
11
|
getRequestContext,
|
|
12
12
|
setRequestContextParams,
|
|
@@ -16,10 +16,12 @@ import type { MiddlewareFn } from "../router/middleware.js";
|
|
|
16
16
|
import { executeMiddleware } from "../router/middleware.js";
|
|
17
17
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
18
18
|
import { attachTransitionWhen } from "./attach-transition-when.js";
|
|
19
|
-
import { payloadInitialTheme } from "./full-payload.js";
|
|
19
|
+
import { payloadInitialTheme, payloadLocation } from "./full-payload.js";
|
|
20
20
|
import { resolvedHandleStream } from "../handles/deferred-resolution.js";
|
|
21
21
|
import type { RscPayload, ReactFormState } from "./types.js";
|
|
22
|
+
import type { MatchResult } from "../types.js";
|
|
22
23
|
import {
|
|
24
|
+
actionRenderRequest,
|
|
23
25
|
createResponseWithMergedHeaders,
|
|
24
26
|
finalizeResponse,
|
|
25
27
|
buildRouteMiddlewareEntries,
|
|
@@ -78,17 +80,30 @@ export async function handleProgressiveEnhancement<TEnv>(
|
|
|
78
80
|
// renders and the re-render), so a PE request prints ONE tree. A non-PE form
|
|
79
81
|
// POST (no $ACTION fields) drives no plan and stays silent.
|
|
80
82
|
const trace = INTERNAL_RANGO_DEBUG ? createRoutineTrace("pe") : undefined;
|
|
83
|
+
const input: PeRequestInput<TEnv> = {
|
|
84
|
+
ctx,
|
|
85
|
+
request,
|
|
86
|
+
env,
|
|
87
|
+
url,
|
|
88
|
+
handleStore,
|
|
89
|
+
nonce,
|
|
90
|
+
routeMwInfo,
|
|
91
|
+
trace,
|
|
92
|
+
};
|
|
81
93
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
);
|
|
94
|
+
const reqCtx = getRequestContext();
|
|
95
|
+
// A rewrite pass after the form's action ran: re-render, never run it again.
|
|
96
|
+
const ran = reqCtx._actionRan;
|
|
97
|
+
if (ran?.kind === "form") {
|
|
98
|
+
return await rerenderAfterFormAction(
|
|
99
|
+
input,
|
|
100
|
+
await outcomeForRewritePass(input, ran.outcome),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const outcome = await runFormAction(input);
|
|
104
|
+
if (!outcome || outcome instanceof Response) return outcome;
|
|
105
|
+
reqCtx._actionRan = { kind: "form", outcome };
|
|
106
|
+
return await rerenderAfterFormAction(input, outcome);
|
|
92
107
|
} finally {
|
|
93
108
|
if (trace && trace.entries.length > 0) {
|
|
94
109
|
console.log(
|
|
@@ -98,16 +113,27 @@ export async function handleProgressiveEnhancement<TEnv>(
|
|
|
98
113
|
}
|
|
99
114
|
}
|
|
100
115
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
/** One PE request's inputs, shared by the action run and the re-render. */
|
|
117
|
+
interface PeRequestInput<TEnv> {
|
|
118
|
+
ctx: HandlerContext<TEnv>;
|
|
119
|
+
request: Request;
|
|
120
|
+
env: TEnv;
|
|
121
|
+
url: URL;
|
|
122
|
+
handleStore: ReturnType<typeof getRequestContext>["_handleStore"];
|
|
123
|
+
nonce: string | undefined;
|
|
124
|
+
routeMwInfo: PeRouteMiddlewareInfo | undefined;
|
|
125
|
+
trace: RoutineTrace | undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Run the no-JS form's action, outside route middleware. Returns the outcome
|
|
130
|
+
* the re-render renders from, a Response that ends the request (a redirect, a
|
|
131
|
+
* 400 for an unparseable body), or null for a POST that is not a form action.
|
|
132
|
+
*/
|
|
133
|
+
async function runFormAction<TEnv>(
|
|
134
|
+
input: PeRequestInput<TEnv>,
|
|
135
|
+
): Promise<FormActionOutcome | Response | null> {
|
|
136
|
+
const { ctx, request, env, url } = input;
|
|
111
137
|
// Clone the request to read FormData without consuming it.
|
|
112
138
|
// Wrap in try-catch so malformed POST bodies are reported as action
|
|
113
139
|
// errors, not routing errors from the outer catch in handler.ts.
|
|
@@ -116,32 +142,10 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
116
142
|
formData = await request.clone().formData();
|
|
117
143
|
} catch (error) {
|
|
118
144
|
// Attempt error boundary rendering so the user sees a meaningful page.
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
env,
|
|
123
|
-
url,
|
|
124
|
-
error,
|
|
125
|
-
handleStore,
|
|
126
|
-
nonce,
|
|
127
|
-
trace,
|
|
128
|
-
);
|
|
129
|
-
if (errorHtml) {
|
|
130
|
-
ctx.callOnError(error, "action", {
|
|
131
|
-
request,
|
|
132
|
-
url,
|
|
133
|
-
env,
|
|
134
|
-
handledByBoundary: true,
|
|
135
|
-
});
|
|
136
|
-
return errorHtml;
|
|
145
|
+
const failure = await matchPeFailure(input, error, undefined, false);
|
|
146
|
+
if (failure.boundary) {
|
|
147
|
+
return { reactFormState: null, directActionId: null, failure };
|
|
137
148
|
}
|
|
138
|
-
|
|
139
|
-
ctx.callOnError(error, "action", {
|
|
140
|
-
request,
|
|
141
|
-
url,
|
|
142
|
-
env,
|
|
143
|
-
handledByBoundary: false,
|
|
144
|
-
});
|
|
145
149
|
console.error("[RSC] Progressive enhancement form parse error:", error);
|
|
146
150
|
return createResponseWithMergedHeaders(null, { status: 400 });
|
|
147
151
|
}
|
|
@@ -167,13 +171,7 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
167
171
|
// Execute action and return HTML
|
|
168
172
|
let actionResult: unknown = undefined;
|
|
169
173
|
let reactFormState: ReactFormState | null = null;
|
|
170
|
-
|
|
171
|
-
// When an action throws and NO error boundary matches, the PE path re-renders
|
|
172
|
-
// the page (below) the same way it would after a successful action. Without
|
|
173
|
-
// this the re-render serves HTTP 200, diverging from the JS path which serves
|
|
174
|
-
// 500 (server-action.ts sets actionStatus=500 for the same boundaryless error).
|
|
175
|
-
// 500 is carried only into the final HTML response, never the redirect branch.
|
|
176
|
-
let boundarylessErrorStatus: number | undefined;
|
|
174
|
+
let failure: PeFailure | undefined;
|
|
177
175
|
|
|
178
176
|
if (isUseActionState) {
|
|
179
177
|
// Decode and extract action identity before execution so error
|
|
@@ -192,33 +190,7 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
192
190
|
// Handle thrown redirect (e.g., throw redirect('/path'))
|
|
193
191
|
const redirectResponse = extractRedirectResponse(error);
|
|
194
192
|
if (redirectResponse) return redirectResponse;
|
|
195
|
-
|
|
196
|
-
// Attempt error boundary rendering for the PE path
|
|
197
|
-
const errorHtml = await renderPeErrorBoundary(
|
|
198
|
-
ctx,
|
|
199
|
-
request,
|
|
200
|
-
env,
|
|
201
|
-
url,
|
|
202
|
-
error,
|
|
203
|
-
handleStore,
|
|
204
|
-
nonce,
|
|
205
|
-
trace,
|
|
206
|
-
useActionStateId,
|
|
207
|
-
true, // an action ran and threw
|
|
208
|
-
);
|
|
209
|
-
if (errorHtml) return errorHtml;
|
|
210
|
-
|
|
211
|
-
ctx.callOnError(error, "action", {
|
|
212
|
-
request,
|
|
213
|
-
url,
|
|
214
|
-
env,
|
|
215
|
-
actionId: useActionStateId,
|
|
216
|
-
handledByBoundary: false,
|
|
217
|
-
});
|
|
218
|
-
console.error("[RSC] Progressive enhancement action error:", error);
|
|
219
|
-
// No boundary matched — the fall-through re-render must carry 500 to match
|
|
220
|
-
// the JS path's boundaryless-error status (server-action.ts).
|
|
221
|
-
boundarylessErrorStatus = 500;
|
|
193
|
+
failure = await matchPeFailure(input, error, useActionStateId, true);
|
|
222
194
|
}
|
|
223
195
|
} else if (isDirectAction && directActionId) {
|
|
224
196
|
const temporaryReferences = ctx.createTemporaryReferenceSet();
|
|
@@ -247,36 +219,17 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
247
219
|
// Handle thrown redirect (e.g., throw redirect('/path'))
|
|
248
220
|
const redirectResponse = extractRedirectResponse(error);
|
|
249
221
|
if (redirectResponse) return redirectResponse;
|
|
250
|
-
|
|
251
|
-
// Attempt error boundary rendering for the PE path
|
|
252
|
-
const errorHtml = await renderPeErrorBoundary(
|
|
253
|
-
ctx,
|
|
254
|
-
request,
|
|
255
|
-
env,
|
|
256
|
-
url,
|
|
257
|
-
error,
|
|
258
|
-
handleStore,
|
|
259
|
-
nonce,
|
|
260
|
-
trace,
|
|
261
|
-
directActionId,
|
|
262
|
-
true, // an action ran and threw
|
|
263
|
-
);
|
|
264
|
-
if (errorHtml) return errorHtml;
|
|
265
|
-
|
|
266
|
-
ctx.callOnError(error, "action", {
|
|
267
|
-
request,
|
|
268
|
-
url,
|
|
269
|
-
env,
|
|
270
|
-
actionId: directActionId,
|
|
271
|
-
handledByBoundary: false,
|
|
272
|
-
});
|
|
273
|
-
console.error("[RSC] Progressive enhancement action error:", error);
|
|
274
|
-
// No boundary matched — the fall-through re-render must carry 500 to match
|
|
275
|
-
// the JS path's boundaryless-error status (server-action.ts).
|
|
276
|
-
boundarylessErrorStatus = 500;
|
|
222
|
+
failure = await matchPeFailure(input, error, directActionId, true);
|
|
277
223
|
}
|
|
278
224
|
}
|
|
279
225
|
|
|
226
|
+
if (failure?.boundary) {
|
|
227
|
+
return { reactFormState: null, directActionId, failure };
|
|
228
|
+
}
|
|
229
|
+
if (failure) {
|
|
230
|
+
console.error("[RSC] Progressive enhancement action error:", failure.error);
|
|
231
|
+
}
|
|
232
|
+
|
|
280
233
|
// Handle Response returned from action during PE.
|
|
281
234
|
// In the JS path, executeServerAction intercepts redirect Responses and
|
|
282
235
|
// short-circuits. The PE path must handle them too.
|
|
@@ -304,6 +257,58 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
304
257
|
console.error("[RSC] Failed to decode form state:", error);
|
|
305
258
|
}
|
|
306
259
|
|
|
260
|
+
return { reactFormState, directActionId, failure };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** What a no-JS form post's action left for the re-render (`_actionRan`). */
|
|
264
|
+
export interface FormActionOutcome {
|
|
265
|
+
reactFormState: ReactFormState | null;
|
|
266
|
+
directActionId: string | null;
|
|
267
|
+
/** The action threw, or the form body did not parse. */
|
|
268
|
+
failure?: PeFailure;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* A failed no-JS form post and the error boundary matched for it. The boundary
|
|
273
|
+
* renders in place of the page, inside route middleware as the JS path renders
|
|
274
|
+
* its own (server-action.ts `errorBoundary`). With none, the page re-renders
|
|
275
|
+
* with 500, the JS path's status for the same boundaryless error.
|
|
276
|
+
*/
|
|
277
|
+
export interface PeFailure {
|
|
278
|
+
error: unknown;
|
|
279
|
+
actionId: string | null | undefined;
|
|
280
|
+
boundary: MatchResult | null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The outcome for a rewrite pass: a failure's boundary is re-matched on the
|
|
285
|
+
* target, since pass 1 matched (or missed) the rewriting route's
|
|
286
|
+
* (`continuationForRewritePass` in server-action.ts).
|
|
287
|
+
*/
|
|
288
|
+
async function outcomeForRewritePass<TEnv>(
|
|
289
|
+
input: PeRequestInput<TEnv>,
|
|
290
|
+
outcome: FormActionOutcome,
|
|
291
|
+
): Promise<FormActionOutcome> {
|
|
292
|
+
const { failure } = outcome;
|
|
293
|
+
if (!failure) return outcome;
|
|
294
|
+
const boundary = await input.ctx.router.matchError(
|
|
295
|
+
input.request,
|
|
296
|
+
{ env: input.env },
|
|
297
|
+
failure.error,
|
|
298
|
+
"route",
|
|
299
|
+
);
|
|
300
|
+
return { ...outcome, failure: { ...failure, boundary } };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** The re-render after a no-JS form post's action, wrapped in route middleware. */
|
|
304
|
+
async function rerenderAfterFormAction<TEnv>(
|
|
305
|
+
input: PeRequestInput<TEnv>,
|
|
306
|
+
outcome: FormActionOutcome,
|
|
307
|
+
): Promise<Response> {
|
|
308
|
+
const { ctx, request, env, url, handleStore, nonce, routeMwInfo, trace } =
|
|
309
|
+
input;
|
|
310
|
+
const { failure } = outcome;
|
|
311
|
+
|
|
307
312
|
// Re-render the page and return HTML.
|
|
308
313
|
// Route middleware wraps the render so context variables, headers, and
|
|
309
314
|
// cookies set by route middleware are available during re-render — matching
|
|
@@ -313,11 +318,7 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
313
318
|
// custom headers) so loaders that read request headers/cookies behave
|
|
314
319
|
// identically under PE and the JS action path. Drop body-framing headers
|
|
315
320
|
// from the bodyless GET and force the HTML accept.
|
|
316
|
-
const headers =
|
|
317
|
-
headers.delete("content-type");
|
|
318
|
-
headers.delete("content-length");
|
|
319
|
-
headers.delete("content-encoding");
|
|
320
|
-
headers.delete("transfer-encoding");
|
|
321
|
+
const headers = bodylessHeaders(request);
|
|
321
322
|
headers.set("accept", "text/html");
|
|
322
323
|
const renderRequest = new Request(url.toString(), {
|
|
323
324
|
method: "GET",
|
|
@@ -340,14 +341,17 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
340
341
|
renderRequest,
|
|
341
342
|
handleStore,
|
|
342
343
|
nonce,
|
|
343
|
-
|
|
344
|
-
boundarylessErrorStatus,
|
|
345
|
-
directActionId,
|
|
344
|
+
...outcome,
|
|
346
345
|
}),
|
|
347
346
|
{ trace, owner: getRequestContext() },
|
|
348
347
|
);
|
|
349
348
|
};
|
|
350
349
|
|
|
350
|
+
const boundary = failure?.boundary;
|
|
351
|
+
const render = boundary
|
|
352
|
+
? () => renderPeErrorBoundary(input, failure?.actionId, boundary)
|
|
353
|
+
: renderPage;
|
|
354
|
+
|
|
351
355
|
// Execute route middleware wrapping the render, if any.
|
|
352
356
|
// finalizeResponse drains onResponse callbacks that middleware short-circuits
|
|
353
357
|
// may leave behind (executeMiddleware does not finalize them itself).
|
|
@@ -355,19 +359,19 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
355
359
|
return finalizeResponse(
|
|
356
360
|
await executeMiddleware(
|
|
357
361
|
buildRouteMiddlewareEntries(routeMwInfo.routeMiddleware),
|
|
358
|
-
request,
|
|
362
|
+
actionRenderRequest(request),
|
|
359
363
|
env,
|
|
360
364
|
routeMwInfo.variables,
|
|
361
|
-
|
|
365
|
+
render,
|
|
362
366
|
routeMwInfo.routeReverse,
|
|
363
367
|
),
|
|
364
368
|
);
|
|
365
369
|
}
|
|
366
370
|
|
|
367
|
-
return
|
|
371
|
+
return render();
|
|
368
372
|
}
|
|
369
373
|
|
|
370
|
-
interface PeRenderInput<TEnv> {
|
|
374
|
+
interface PeRenderInput<TEnv> extends FormActionOutcome {
|
|
371
375
|
ctx: HandlerContext<TEnv>;
|
|
372
376
|
request: Request;
|
|
373
377
|
env: TEnv;
|
|
@@ -375,9 +379,6 @@ interface PeRenderInput<TEnv> {
|
|
|
375
379
|
renderRequest: Request;
|
|
376
380
|
handleStore: ReturnType<typeof getRequestContext>["_handleStore"];
|
|
377
381
|
nonce: string | undefined;
|
|
378
|
-
reactFormState: ReactFormState | null;
|
|
379
|
-
boundarylessErrorStatus: number | undefined;
|
|
380
|
-
directActionId: string | null;
|
|
381
382
|
}
|
|
382
383
|
|
|
383
384
|
/** PE re-render: match the bodyless GET mirror, then render full HTML. */
|
|
@@ -413,13 +414,13 @@ function renderPeResponse<TEnv>(
|
|
|
413
414
|
handleStore,
|
|
414
415
|
nonce,
|
|
415
416
|
reactFormState,
|
|
416
|
-
|
|
417
|
+
failure,
|
|
417
418
|
directActionId,
|
|
418
419
|
} = input;
|
|
419
420
|
|
|
420
421
|
const payload: RscPayload = {
|
|
421
422
|
metadata: {
|
|
422
|
-
|
|
423
|
+
...payloadLocation(getRequestContext(), url),
|
|
423
424
|
routerId: ctx.router.id,
|
|
424
425
|
basename: ctx.router.basename,
|
|
425
426
|
segments: attachTransitionWhen(match.segments, getRequestContext()),
|
|
@@ -461,13 +462,10 @@ function renderPeResponse<TEnv>(
|
|
|
461
462
|
url,
|
|
462
463
|
payload,
|
|
463
464
|
init: {
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
...(boundarylessErrorStatus !== undefined
|
|
469
|
-
? { status: boundarylessErrorStatus }
|
|
470
|
-
: {}),
|
|
465
|
+
// A failure reaching the page render matched no boundary: 500, like
|
|
466
|
+
// the JS path. The redirect branch in peRenderPlan returns before
|
|
467
|
+
// this, so a redirect re-render keeps its 308.
|
|
468
|
+
...(failure ? { status: 500 } : {}),
|
|
471
469
|
headers: { "content-type": "text/html;charset=utf-8" },
|
|
472
470
|
},
|
|
473
471
|
tracking: stageTracking,
|
|
@@ -487,80 +485,22 @@ function renderPeResponse<TEnv>(
|
|
|
487
485
|
);
|
|
488
486
|
}
|
|
489
487
|
|
|
490
|
-
interface PeErrorBoundaryInput<TEnv> {
|
|
491
|
-
ctx: HandlerContext<TEnv>;
|
|
492
|
-
request: Request;
|
|
493
|
-
env: TEnv;
|
|
494
|
-
url: URL;
|
|
495
|
-
error: unknown;
|
|
496
|
-
handleStore: ReturnType<typeof getRequestContext>["_handleStore"];
|
|
497
|
-
nonce: string | undefined;
|
|
498
|
-
actionId: string | null | undefined;
|
|
499
|
-
actionRan: boolean;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
type PeErrorMatch<TEnv> = NonNullable<
|
|
503
|
-
Awaited<ReturnType<HandlerContext<TEnv>["router"]["matchError"]>>
|
|
504
|
-
>;
|
|
505
|
-
|
|
506
488
|
/**
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
489
|
+
* Match an error boundary for a failed PE post and report the error, handled
|
|
490
|
+
* or not. A matchError failure reports the ORIGINAL error as unhandled and
|
|
491
|
+
* rethrows the match failure.
|
|
510
492
|
*/
|
|
511
|
-
async function
|
|
512
|
-
|
|
513
|
-
request: Request,
|
|
514
|
-
env: TEnv,
|
|
515
|
-
url: URL,
|
|
493
|
+
async function matchPeFailure<TEnv>(
|
|
494
|
+
input: PeRequestInput<TEnv>,
|
|
516
495
|
error: unknown,
|
|
517
|
-
|
|
518
|
-
nonce: string | undefined,
|
|
519
|
-
trace: RoutineTrace | undefined,
|
|
520
|
-
actionId?: string | null,
|
|
496
|
+
actionId: string | null | undefined,
|
|
521
497
|
// True when an action actually ran and threw (vs a malformed form body, where
|
|
522
498
|
// no action executed). Drives _inActionRevalidation for JS/PE parity — it must
|
|
523
499
|
// NOT be inferred from actionId, since a useActionState bound action can run
|
|
524
500
|
// and throw with no $$id (actionId === undefined) yet still be an action error.
|
|
525
|
-
actionRan
|
|
526
|
-
): Promise<
|
|
527
|
-
|
|
528
|
-
peErrorBoundaryPlan({
|
|
529
|
-
ctx,
|
|
530
|
-
request,
|
|
531
|
-
env,
|
|
532
|
-
url,
|
|
533
|
-
error,
|
|
534
|
-
handleStore,
|
|
535
|
-
nonce,
|
|
536
|
-
actionId,
|
|
537
|
-
actionRan,
|
|
538
|
-
}),
|
|
539
|
-
{ trace, owner: getRequestContext() },
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/** PE error boundary: match a boundary for the thrown error, then render it. */
|
|
544
|
-
function* peErrorBoundaryPlan<TEnv>(
|
|
545
|
-
input: PeErrorBoundaryInput<TEnv>,
|
|
546
|
-
): RoutinePlan<Response | null> {
|
|
547
|
-
const boundary = yield* step("match:error", () =>
|
|
548
|
-
matchPeErrorBoundary(input),
|
|
549
|
-
);
|
|
550
|
-
if (!boundary) return null;
|
|
551
|
-
return yield* step("render", () => renderPeErrorResponse(input, boundary));
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Match an error boundary for the PE path, owning the reporting protocol:
|
|
556
|
-
* a matchError failure reports the ORIGINAL error as unhandled and rethrows
|
|
557
|
-
* the match failure; a miss returns null; a hit reports handled and stamps
|
|
558
|
-
* params + action gate context before the render.
|
|
559
|
-
*/
|
|
560
|
-
async function matchPeErrorBoundary<TEnv>(
|
|
561
|
-
input: PeErrorBoundaryInput<TEnv>,
|
|
562
|
-
): Promise<PeErrorMatch<TEnv> | null> {
|
|
563
|
-
const { ctx, request, env, url, error, actionId, actionRan } = input;
|
|
501
|
+
actionRan: boolean,
|
|
502
|
+
): Promise<PeFailure> {
|
|
503
|
+
const { ctx, request, env, url } = input;
|
|
564
504
|
|
|
565
505
|
// JS/PE parity for an action-triggered error re-render: a stale
|
|
566
506
|
// `foregroundOnAction` cache entry inside the error boundary must foreground
|
|
@@ -572,9 +512,9 @@ async function matchPeErrorBoundary<TEnv>(
|
|
|
572
512
|
getRequestContext()._inActionRevalidation = true;
|
|
573
513
|
}
|
|
574
514
|
|
|
575
|
-
let
|
|
515
|
+
let boundary: MatchResult | null;
|
|
576
516
|
try {
|
|
577
|
-
|
|
517
|
+
boundary = await ctx.router.matchError(request, { env }, error, "route");
|
|
578
518
|
} catch (matchErr) {
|
|
579
519
|
ctx.callOnError(error, "action", {
|
|
580
520
|
request,
|
|
@@ -586,31 +526,41 @@ async function matchPeErrorBoundary<TEnv>(
|
|
|
586
526
|
throw matchErr;
|
|
587
527
|
}
|
|
588
528
|
|
|
589
|
-
if (!errorResult) return null;
|
|
590
|
-
|
|
591
529
|
ctx.callOnError(error, "action", {
|
|
592
530
|
request,
|
|
593
531
|
url,
|
|
594
532
|
env,
|
|
595
533
|
actionId: actionId ?? undefined,
|
|
596
|
-
handledByBoundary:
|
|
534
|
+
handledByBoundary: !!boundary,
|
|
597
535
|
});
|
|
536
|
+
return { error, actionId, boundary };
|
|
537
|
+
}
|
|
598
538
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
539
|
+
/** Render a failed PE post's boundary, inside route middleware. */
|
|
540
|
+
function renderPeErrorBoundary<TEnv>(
|
|
541
|
+
input: PeRequestInput<TEnv>,
|
|
542
|
+
actionId: string | null | undefined,
|
|
543
|
+
boundary: MatchResult,
|
|
544
|
+
): Promise<Response> {
|
|
545
|
+
return runRoutine(
|
|
546
|
+
step("render", () => renderPeErrorResponse(input, actionId, boundary)),
|
|
547
|
+
{ trace: input.trace, owner: getRequestContext() },
|
|
548
|
+
);
|
|
602
549
|
}
|
|
603
550
|
|
|
604
551
|
/** Render the matched PE error boundary as a full HTML document. */
|
|
605
552
|
function renderPeErrorResponse<TEnv>(
|
|
606
|
-
input:
|
|
607
|
-
|
|
553
|
+
input: PeRequestInput<TEnv>,
|
|
554
|
+
actionId: string | null | undefined,
|
|
555
|
+
errorResult: MatchResult,
|
|
608
556
|
): Promise<Response> {
|
|
609
|
-
const { ctx, request, env, url, handleStore, nonce
|
|
557
|
+
const { ctx, request, env, url, handleStore, nonce } = input;
|
|
558
|
+
|
|
559
|
+
setRequestContextParams(errorResult.params, errorResult.routeName);
|
|
610
560
|
|
|
611
561
|
const payload: RscPayload = {
|
|
612
562
|
metadata: {
|
|
613
|
-
|
|
563
|
+
...payloadLocation(getRequestContext(), url),
|
|
614
564
|
routerId: ctx.router.id,
|
|
615
565
|
basename: ctx.router.basename,
|
|
616
566
|
segments: attachTransitionWhen(errorResult.segments, getRequestContext()),
|