@rangojs/router 0.0.0-experimental.dfdb0387 → 0.0.0-experimental.e16b7c00
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 +120 -25
- package/dist/bin/rango.js +147 -57
- package/dist/vite/index.js +2106 -842
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +13 -8
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +188 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +47 -12
- package/skills/migrate-nextjs/SKILL.md +582 -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 +71 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +236 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +57 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +319 -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 +117 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +86 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +86 -11
- package/src/browser/navigation-client.ts +45 -25
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +61 -28
- package/src/browser/prefetch/cache.ts +124 -26
- package/src/browser/prefetch/fetch.ts +129 -37
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +18 -13
- package/src/browser/react/NavigationProvider.tsx +72 -31
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +22 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +64 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-reconciler.ts +10 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +21 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- package/src/build/route-types/scan-filter.ts +1 -1
- 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 +95 -1
- package/src/cache/cache-runtime.ts +79 -13
- package/src/cache/cache-scope.ts +77 -46
- package/src/cache/cache-tag.ts +135 -0
- package/src/cache/cf/cf-cache-store.ts +1067 -176
- package/src/cache/cf/index.ts +4 -1
- package/src/cache/document-cache.ts +59 -7
- package/src/cache/index.ts +6 -0
- package/src/cache/memory-segment-store.ts +158 -14
- package/src/cache/tag-invalidation.ts +206 -0
- package/src/cache/types.ts +27 -0
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +92 -182
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +4 -6
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +16 -4
- package/src/index.ts +65 -15
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -36
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +384 -257
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +100 -28
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +26 -41
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +8 -8
- package/src/router/loader-resolution.ts +19 -2
- package/src/router/manifest.ts +22 -13
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +1 -0
- package/src/router/match-middleware/cache-lookup.ts +46 -92
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +53 -32
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +15 -26
- package/src/router/middleware.ts +99 -84
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +3 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +25 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +27 -6
- package/src/router/segment-resolution/loader-cache.ts +8 -17
- package/src/router/segment-resolution/revalidation.ts +147 -106
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +23 -18
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +38 -70
- package/src/rsc/helpers.ts +72 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-route-handler.ts +54 -54
- package/src/rsc/rsc-rendering.ts +35 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +17 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +8 -2
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +132 -116
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +143 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +46 -44
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +1 -1
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +5 -1
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +68 -50
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +5 -6
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +35 -2
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +41 -7
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +101 -51
- 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 +67 -26
- 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 +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- 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 +214 -0
- package/src/vite/plugins/expose-action-id.ts +54 -30
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +496 -486
- package/src/vite/plugins/performance-tracks.ts +29 -25
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +116 -29
- package/src/vite/router-discovery.ts +750 -100
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +21 -6
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
package/src/router.ts
CHANGED
|
@@ -22,10 +22,9 @@ import type { UrlPatterns } from "./urls.js";
|
|
|
22
22
|
import type { UrlBuilder } from "./urls/pattern-types.js";
|
|
23
23
|
import { urls } from "./urls.js";
|
|
24
24
|
import {
|
|
25
|
-
EntryData,
|
|
26
|
-
InterceptSelectorContext,
|
|
25
|
+
type EntryData,
|
|
27
26
|
getContext,
|
|
28
|
-
|
|
27
|
+
RangoContext,
|
|
29
28
|
type MetricsStore,
|
|
30
29
|
} from "./server/context";
|
|
31
30
|
import { createHandleStore, type HandleStore } from "./server/handle-store.js";
|
|
@@ -91,13 +90,10 @@ import {
|
|
|
91
90
|
RouterRegistry,
|
|
92
91
|
nextRouterAutoId,
|
|
93
92
|
} from "./router/router-registry.js";
|
|
93
|
+
import type { RangoOptions, RootLayoutProps } from "./router/router-options.js";
|
|
94
94
|
import type {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} from "./router/router-options.js";
|
|
98
|
-
import type {
|
|
99
|
-
RSCRouter,
|
|
100
|
-
RSCRouterInternal,
|
|
95
|
+
Rango,
|
|
96
|
+
RangoInternal,
|
|
101
97
|
RouterRequestInput,
|
|
102
98
|
} from "./router/router-interfaces.js";
|
|
103
99
|
|
|
@@ -116,22 +112,22 @@ import {
|
|
|
116
112
|
// Re-export public types and values from extracted modules
|
|
117
113
|
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
|
|
118
114
|
export type {
|
|
119
|
-
|
|
115
|
+
RangoOptions,
|
|
120
116
|
RootLayoutProps,
|
|
121
117
|
SSRStreamMode,
|
|
122
118
|
SSROptions,
|
|
123
119
|
ResolveStreamingContext,
|
|
124
120
|
} from "./router/router-options.js";
|
|
125
121
|
export type {
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
Rango,
|
|
123
|
+
RangoInternal,
|
|
128
124
|
RouterRequestInput,
|
|
129
125
|
} from "./router/router-interfaces.js";
|
|
130
126
|
export { toInternal } from "./router/router-interfaces.js";
|
|
131
127
|
|
|
132
128
|
export function createRouter<TEnv = any>(
|
|
133
|
-
options:
|
|
134
|
-
):
|
|
129
|
+
options: RangoOptions<TEnv> = {},
|
|
130
|
+
): Rango<TEnv, {}> {
|
|
135
131
|
const {
|
|
136
132
|
id: userProvidedId,
|
|
137
133
|
$$id: injectedId,
|
|
@@ -159,6 +155,7 @@ export function createRouter<TEnv = any>(
|
|
|
159
155
|
timeouts: timeoutsOption,
|
|
160
156
|
onTimeout,
|
|
161
157
|
originCheck: originCheckOption,
|
|
158
|
+
viewTransition: viewTransitionOption = "auto",
|
|
162
159
|
} = options;
|
|
163
160
|
|
|
164
161
|
// Normalize basename: ensure leading slash, strip trailing slash.
|
|
@@ -538,6 +535,7 @@ export function createRouter<TEnv = any>(
|
|
|
538
535
|
findNearestNotFoundBoundary,
|
|
539
536
|
notFoundComponent: notFound,
|
|
540
537
|
callOnError,
|
|
538
|
+
viewTransitionDefault: viewTransitionOption,
|
|
541
539
|
};
|
|
542
540
|
|
|
543
541
|
// Match API dependencies
|
|
@@ -674,7 +672,7 @@ export function createRouter<TEnv = any>(
|
|
|
674
672
|
* The type system tracks accumulated routes through the builder chain
|
|
675
673
|
* Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
|
|
676
674
|
*/
|
|
677
|
-
const router:
|
|
675
|
+
const router: RangoInternal<TEnv, {}> = {
|
|
678
676
|
__brand: RSC_ROUTER_BRAND,
|
|
679
677
|
id: routerId,
|
|
680
678
|
basename,
|
|
@@ -722,7 +720,7 @@ export function createRouter<TEnv = any>(
|
|
|
722
720
|
};
|
|
723
721
|
|
|
724
722
|
let handlerResult: AllUseItems[] = [];
|
|
725
|
-
|
|
723
|
+
RangoContext.run(
|
|
726
724
|
{
|
|
727
725
|
manifest,
|
|
728
726
|
patterns: routePatterns,
|
|
@@ -1000,6 +998,13 @@ export function createRouter<TEnv = any>(
|
|
|
1000
998
|
// Expose basename for runtime manifest generation
|
|
1001
999
|
__basename: basename,
|
|
1002
1000
|
|
|
1001
|
+
// Expose router-level boundary defaults for build-time clientChunks
|
|
1002
|
+
// discovery (so a "use client" default boundary lands in app-fallback).
|
|
1003
|
+
// These are createRouter options, never pushed onto EntryData.
|
|
1004
|
+
__defaultErrorBoundary: defaultErrorBoundary,
|
|
1005
|
+
__defaultNotFoundBoundary: defaultNotFoundBoundary,
|
|
1006
|
+
__notFound: notFound,
|
|
1007
|
+
|
|
1003
1008
|
// RSC request handler (lazily created on first call)
|
|
1004
1009
|
fetch: (() => {
|
|
1005
1010
|
// Handler is created on first call and reused
|
|
@@ -1046,9 +1051,9 @@ export function createRouter<TEnv = any>(
|
|
|
1046
1051
|
|
|
1047
1052
|
// If urls option was provided, auto-register them
|
|
1048
1053
|
if (typeof urlsOption === "function") {
|
|
1049
|
-
return router.routes(urlsOption) as
|
|
1054
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1050
1055
|
} else if (urlsOption) {
|
|
1051
|
-
return router.routes(urlsOption) as
|
|
1056
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1052
1057
|
}
|
|
1053
1058
|
|
|
1054
1059
|
return router;
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* RSC rendering) so they can be standalone modules without closure coupling.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { RangoInternal } from "../router/router-interfaces.js";
|
|
10
10
|
import type { ErrorPhase } from "../types.js";
|
|
11
11
|
import type { InvokeOnErrorContext } from "../router/error-handling.js";
|
|
12
12
|
import type { RSCDependencies, LoadSSRModule } from "./types.js";
|
|
13
13
|
import type { SSRStreamMode } from "../router/router-options.js";
|
|
14
14
|
|
|
15
15
|
export interface HandlerContext<TEnv = unknown> {
|
|
16
|
-
router:
|
|
16
|
+
router: RangoInternal<TEnv, any>;
|
|
17
17
|
version: string;
|
|
18
18
|
renderToReadableStream: RSCDependencies["renderToReadableStream"];
|
|
19
19
|
decodeReply: RSCDependencies["decodeReply"];
|
package/src/rsc/handler.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { createElement } from "react";
|
|
11
|
-
import {
|
|
11
|
+
import { isRouteNotFoundError } from "../errors.js";
|
|
12
12
|
import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
|
|
13
13
|
import {
|
|
14
14
|
runWithRequestContext,
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
interceptRedirectForPartial,
|
|
32
32
|
buildRouteMiddlewareEntries,
|
|
33
33
|
} from "./helpers.js";
|
|
34
|
+
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
34
35
|
import {
|
|
35
36
|
handleResponseRoute,
|
|
36
37
|
type ResponseRouteMatch,
|
|
@@ -56,6 +57,8 @@ import {
|
|
|
56
57
|
getRouterTrie,
|
|
57
58
|
} from "../route-map-builder.js";
|
|
58
59
|
import type { HandlerContext } from "./handler-context.js";
|
|
60
|
+
import type { CacheErrorCategory } from "../cache/cache-error.js";
|
|
61
|
+
import type { SegmentCacheStore } from "../cache/types.js";
|
|
59
62
|
import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
|
|
60
63
|
import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
|
|
61
64
|
import {
|
|
@@ -64,7 +67,10 @@ import {
|
|
|
64
67
|
type ActionContinuation,
|
|
65
68
|
} from "./server-action.js";
|
|
66
69
|
import { handleLoaderFetch } from "./loader-fetch.js";
|
|
67
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
checkRequestOrigin,
|
|
72
|
+
ORIGIN_CHECK_PHASE_BY_MODE,
|
|
73
|
+
} from "./origin-guard.js";
|
|
68
74
|
import { handleRscRendering } from "./rsc-rendering.js";
|
|
69
75
|
import {
|
|
70
76
|
withTimeout,
|
|
@@ -81,6 +87,7 @@ import {
|
|
|
81
87
|
startSSRSetup,
|
|
82
88
|
getSSRSetup,
|
|
83
89
|
mayNeedSSR,
|
|
90
|
+
isRscRequest,
|
|
84
91
|
SSR_SETUP_VAR,
|
|
85
92
|
} from "./ssr-setup.js";
|
|
86
93
|
import {
|
|
@@ -128,6 +135,13 @@ export function createRSCHandler<
|
|
|
128
135
|
>(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
|
|
129
136
|
const { router, version = VERSION, nonce: nonceProvider } = options;
|
|
130
137
|
|
|
138
|
+
// Handler-owned registry of explicit per-scope stores from cache({ store }).
|
|
139
|
+
// Lives in the closure so it is scoped per handler (multi-router deployments
|
|
140
|
+
// get separate registries) and accumulates every explicit store this handler
|
|
141
|
+
// resolves across requests. updateTag()/revalidateTag() iterate it to reach
|
|
142
|
+
// stores not covered by the app-level ctx._cacheStore.
|
|
143
|
+
const explicitTaggedStores = new Set<SegmentCacheStore>();
|
|
144
|
+
|
|
131
145
|
// Use provided deps or default to @vitejs/plugin-rsc/rsc exports
|
|
132
146
|
const deps = options.deps ?? rscDeps;
|
|
133
147
|
const {
|
|
@@ -352,7 +366,7 @@ export function createRSCHandler<
|
|
|
352
366
|
// Resolve cache store configuration
|
|
353
367
|
// Priority: options.cache (handler override) > router.cache (router default)
|
|
354
368
|
// Store is enabled only if: config provided, enabled, and no ?__no_cache query param
|
|
355
|
-
let cacheStore
|
|
369
|
+
let cacheStore: SegmentCacheStore | undefined;
|
|
356
370
|
const cacheOption = options.cache ?? router.cache;
|
|
357
371
|
if (cacheOption && !url.searchParams.has("__no_cache")) {
|
|
358
372
|
const cacheConfig =
|
|
@@ -419,6 +433,7 @@ export function createRSCHandler<
|
|
|
419
433
|
url,
|
|
420
434
|
variables,
|
|
421
435
|
cacheStore,
|
|
436
|
+
explicitTaggedStores,
|
|
422
437
|
cacheProfiles: router.cacheProfiles,
|
|
423
438
|
executionContext: executionCtx,
|
|
424
439
|
themeConfig: router.themeConfig,
|
|
@@ -431,7 +446,7 @@ export function createRSCHandler<
|
|
|
431
446
|
// can surface non-fatal errors through the router's onError callback.
|
|
432
447
|
requestContext._reportBackgroundError = (
|
|
433
448
|
error: unknown,
|
|
434
|
-
category:
|
|
449
|
+
category: CacheErrorCategory,
|
|
435
450
|
) => {
|
|
436
451
|
callOnError(error, "cache", {
|
|
437
452
|
request,
|
|
@@ -533,7 +548,9 @@ export function createRSCHandler<
|
|
|
533
548
|
}
|
|
534
549
|
|
|
535
550
|
const fullTiming = timingParts.join(", ");
|
|
536
|
-
if (fullTiming
|
|
551
|
+
if (fullTiming && !isWebSocketUpgradeResponse(response)) {
|
|
552
|
+
response.headers.set("Server-Timing", fullTiming);
|
|
553
|
+
}
|
|
537
554
|
|
|
538
555
|
return response;
|
|
539
556
|
});
|
|
@@ -593,10 +610,7 @@ export function createRSCHandler<
|
|
|
593
610
|
routerId: router.id,
|
|
594
611
|
});
|
|
595
612
|
} catch (error) {
|
|
596
|
-
if (
|
|
597
|
-
error instanceof RouteNotFoundError ||
|
|
598
|
-
(error instanceof Error && error.name === "RouteNotFoundError")
|
|
599
|
-
) {
|
|
613
|
+
if (isRouteNotFoundError(error)) {
|
|
600
614
|
// Let the render path handle 404 — match()/matchPartial() will
|
|
601
615
|
// re-throw RouteNotFoundError and the catch block in
|
|
602
616
|
// executeRenderWithMiddleware renders the not-found page.
|
|
@@ -647,14 +661,7 @@ export function createRSCHandler<
|
|
|
647
661
|
}
|
|
648
662
|
|
|
649
663
|
// ---- 3. Origin guard (gate for action/loader/PE modes) ----
|
|
650
|
-
const originPhase
|
|
651
|
-
plan.mode === "action"
|
|
652
|
-
? "action"
|
|
653
|
-
: plan.mode === "loader"
|
|
654
|
-
? "loader"
|
|
655
|
-
: plan.mode === "pe-render"
|
|
656
|
-
? "pe-form"
|
|
657
|
-
: null;
|
|
664
|
+
const originPhase = ORIGIN_CHECK_PHASE_BY_MODE[plan.mode];
|
|
658
665
|
if (originPhase) {
|
|
659
666
|
const originResult = await checkRequestOrigin(
|
|
660
667
|
request,
|
|
@@ -804,7 +811,7 @@ export function createRSCHandler<
|
|
|
804
811
|
);
|
|
805
812
|
}
|
|
806
813
|
const response = responseOutcome.result;
|
|
807
|
-
if (plan.negotiated) {
|
|
814
|
+
if (plan.negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
808
815
|
response.headers.append("Vary", "Accept");
|
|
809
816
|
}
|
|
810
817
|
return response;
|
|
@@ -921,47 +928,17 @@ export function createRSCHandler<
|
|
|
921
928
|
);
|
|
922
929
|
}
|
|
923
930
|
|
|
924
|
-
//
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
plan.
|
|
931
|
-
|
|
932
|
-
request,
|
|
933
|
-
env,
|
|
934
|
-
url,
|
|
935
|
-
variables,
|
|
936
|
-
nonce,
|
|
937
|
-
handleStore,
|
|
938
|
-
isPartial,
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// PE that fell through (handleProgressiveEnhancement returned null)
|
|
943
|
-
// falls back to full render
|
|
944
|
-
if (plan.mode === "pe-render") {
|
|
945
|
-
return executeRenderWithMiddleware(
|
|
946
|
-
plan.route.routeMiddleware,
|
|
947
|
-
false,
|
|
948
|
-
plan.route.routeKey,
|
|
949
|
-
routeReverse,
|
|
950
|
-
request,
|
|
951
|
-
env,
|
|
952
|
-
url,
|
|
953
|
-
variables,
|
|
954
|
-
nonce,
|
|
955
|
-
handleStore,
|
|
956
|
-
false,
|
|
957
|
-
);
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// Redirect plan that wasn't handled above (full-page redirect — let
|
|
961
|
-
// the pipeline handle it via match() which returns { redirect: url })
|
|
931
|
+
// Full render, partial render, fallen-through PE, and full-page redirect all
|
|
932
|
+
// render through the same middleware-wrapped path. Only full/partial-render
|
|
933
|
+
// carry negotiation + the partial flag; pe/redirect render plainly.
|
|
934
|
+
const isPartial = plan.mode === "partial-render";
|
|
935
|
+
const negotiated =
|
|
936
|
+
plan.mode === "full-render" || plan.mode === "partial-render"
|
|
937
|
+
? plan.negotiated
|
|
938
|
+
: false;
|
|
962
939
|
return executeRenderWithMiddleware(
|
|
963
940
|
plan.route.routeMiddleware,
|
|
964
|
-
|
|
941
|
+
negotiated,
|
|
965
942
|
plan.route.routeKey,
|
|
966
943
|
routeReverse,
|
|
967
944
|
request,
|
|
@@ -970,7 +947,7 @@ export function createRSCHandler<
|
|
|
970
947
|
variables,
|
|
971
948
|
nonce,
|
|
972
949
|
handleStore,
|
|
973
|
-
|
|
950
|
+
isPartial,
|
|
974
951
|
);
|
|
975
952
|
}
|
|
976
953
|
|
|
@@ -1014,7 +991,7 @@ export function createRSCHandler<
|
|
|
1014
991
|
nonce,
|
|
1015
992
|
);
|
|
1016
993
|
}
|
|
1017
|
-
if (negotiated) {
|
|
994
|
+
if (negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
1018
995
|
response.headers.append("Vary", "Accept");
|
|
1019
996
|
}
|
|
1020
997
|
return response;
|
|
@@ -1050,10 +1027,7 @@ export function createRSCHandler<
|
|
|
1050
1027
|
}
|
|
1051
1028
|
|
|
1052
1029
|
// Render 404 page for unmatched routes
|
|
1053
|
-
|
|
1054
|
-
error instanceof RouteNotFoundError ||
|
|
1055
|
-
(error instanceof Error && error.name === "RouteNotFoundError");
|
|
1056
|
-
if (isRouteNotFound) {
|
|
1030
|
+
if (isRouteNotFoundError(error)) {
|
|
1057
1031
|
callOnError(error, "routing", {
|
|
1058
1032
|
request,
|
|
1059
1033
|
url,
|
|
@@ -1100,13 +1074,7 @@ export function createRSCHandler<
|
|
|
1100
1074
|
},
|
|
1101
1075
|
});
|
|
1102
1076
|
|
|
1103
|
-
|
|
1104
|
-
isPartial ||
|
|
1105
|
-
(!request.headers.get("accept")?.includes("text/html") &&
|
|
1106
|
-
!url.searchParams.has("__html")) ||
|
|
1107
|
-
url.searchParams.has("__rsc");
|
|
1108
|
-
|
|
1109
|
-
if (isRscRequest) {
|
|
1077
|
+
if (isRscRequest(request, url, isPartial)) {
|
|
1110
1078
|
return createResponseWithMergedHeaders(rscStream, {
|
|
1111
1079
|
status: 404,
|
|
1112
1080
|
headers: { "content-type": "text/x-component;charset=utf-8" },
|
package/src/rsc/helpers.ts
CHANGED
|
@@ -8,9 +8,50 @@ import {
|
|
|
8
8
|
_getRequestContext,
|
|
9
9
|
getLocationState,
|
|
10
10
|
} from "../server/request-context.js";
|
|
11
|
+
import type { RequestContext } from "../server/request-context.js";
|
|
11
12
|
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
13
|
+
import { isRedirectResponse } from "../response-utils.js";
|
|
12
14
|
import type { MiddlewareEntry, MiddlewareFn } from "../router/middleware.js";
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Copy stub headers from the request context onto a target Headers instance:
|
|
18
|
+
* append Set-Cookie entries, set everything else only if absent. Header
|
|
19
|
+
* mutation failures are swallowed so the same logic works against Response
|
|
20
|
+
* headers that may be immutable (e.g. Cloudflare protocol-switch responses).
|
|
21
|
+
*/
|
|
22
|
+
function applyStubHeaders(target: Headers, stub: Headers): void {
|
|
23
|
+
stub.forEach((value, name) => {
|
|
24
|
+
try {
|
|
25
|
+
if (name.toLowerCase() === "set-cookie") {
|
|
26
|
+
target.append(name, value);
|
|
27
|
+
} else if (!target.has(name)) {
|
|
28
|
+
target.set(name, value);
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Headers immutable — skip.
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Drain ctx._onResponseCallbacks onto a response. Swapping the array before
|
|
38
|
+
* iteration prevents re-entrant registrations from double-firing and matches
|
|
39
|
+
* the contract that each callback runs at most once per request.
|
|
40
|
+
*/
|
|
41
|
+
function drainOnResponseCallbacks(
|
|
42
|
+
ctx: RequestContext,
|
|
43
|
+
response: Response,
|
|
44
|
+
): Response {
|
|
45
|
+
const callbacks = ctx._onResponseCallbacks;
|
|
46
|
+
if (callbacks.length === 0) return response;
|
|
47
|
+
ctx._onResponseCallbacks = [];
|
|
48
|
+
let result = response;
|
|
49
|
+
for (const callback of callbacks) {
|
|
50
|
+
result = callback(result) ?? result;
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
14
55
|
/**
|
|
15
56
|
* Check if a request body has content to decode
|
|
16
57
|
*/
|
|
@@ -39,40 +80,23 @@ export function createResponseWithMergedHeaders(
|
|
|
39
80
|
return new Response(body, init);
|
|
40
81
|
}
|
|
41
82
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// merge points (e.g. executeMiddleware) do not duplicate them.
|
|
83
|
+
// Delete Set-Cookie from the stub after consuming so downstream merge
|
|
84
|
+
// points (e.g. executeMiddleware) don't duplicate them.
|
|
45
85
|
const mergedHeaders = new Headers(init.headers);
|
|
46
|
-
ctx.res.headers
|
|
47
|
-
if (name.toLowerCase() === "set-cookie") {
|
|
48
|
-
mergedHeaders.append(name, value);
|
|
49
|
-
} else if (!mergedHeaders.has(name)) {
|
|
50
|
-
// Only set if not already present in init.headers
|
|
51
|
-
mergedHeaders.set(name, value);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
86
|
+
applyStubHeaders(mergedHeaders, ctx.res.headers);
|
|
54
87
|
ctx.res.headers.delete("set-cookie");
|
|
55
88
|
|
|
56
|
-
//
|
|
57
|
-
//
|
|
89
|
+
// ctx.res.status overrides init.status when explicitly set (e.g. 404 for
|
|
90
|
+
// notFound, 500 for error). Default ctx.res.status is 200.
|
|
58
91
|
const status = ctx.res.status !== 200 ? ctx.res.status : init.status;
|
|
59
92
|
|
|
60
|
-
|
|
93
|
+
const response = new Response(body, {
|
|
61
94
|
...init,
|
|
62
95
|
status,
|
|
63
96
|
headers: mergedHeaders,
|
|
64
97
|
});
|
|
65
98
|
|
|
66
|
-
|
|
67
|
-
// Drain the array so that downstream callers (e.g. finalizeResponse)
|
|
68
|
-
// do not re-execute the same callbacks on this response.
|
|
69
|
-
const callbacks = ctx._onResponseCallbacks;
|
|
70
|
-
ctx._onResponseCallbacks = [];
|
|
71
|
-
for (const callback of callbacks) {
|
|
72
|
-
response = callback(response) ?? response;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return response;
|
|
99
|
+
return drainOnResponseCallbacks(ctx, response);
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
/**
|
|
@@ -122,10 +146,10 @@ export function interceptRedirectForPartial(
|
|
|
122
146
|
locationState?: Record<string, unknown>,
|
|
123
147
|
) => Response,
|
|
124
148
|
): Response | null {
|
|
125
|
-
|
|
126
|
-
if (!(response.status >= 300 && response.status < 400 && redirectUrl)) {
|
|
149
|
+
if (!isRedirectResponse(response)) {
|
|
127
150
|
return null;
|
|
128
151
|
}
|
|
152
|
+
const redirectUrl = response.headers.get("Location")!;
|
|
129
153
|
const locationState = getLocationState();
|
|
130
154
|
let intercepted: Response;
|
|
131
155
|
if (locationState) {
|
|
@@ -175,24 +199,29 @@ export function buildRouteMiddlewareEntries<TEnv>(
|
|
|
175
199
|
}
|
|
176
200
|
|
|
177
201
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
202
|
+
* Merge stub headers from the request context onto an existing Response in
|
|
203
|
+
* place, then drain onResponse callbacks. Used when a Response cannot flow
|
|
204
|
+
* through `new Response()` — status 101 is outside the constructor's
|
|
205
|
+
* 200-599 range, and the Cloudflare-specific `webSocket` property would be
|
|
206
|
+
* lost on reconstruction.
|
|
183
207
|
*/
|
|
184
|
-
export function
|
|
208
|
+
export function mergeStubHeadersAndFinalize(response: Response): Response {
|
|
185
209
|
const ctx = _getRequestContext();
|
|
186
|
-
if (!ctx
|
|
187
|
-
return response;
|
|
188
|
-
}
|
|
210
|
+
if (!ctx) return response;
|
|
189
211
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
applyStubHeaders(response.headers, ctx.res.headers);
|
|
213
|
+
ctx.res.headers.delete("set-cookie");
|
|
214
|
+
|
|
215
|
+
return drainOnResponseCallbacks(ctx, response);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Run onResponse callbacks on an existing Response. Used by code paths that
|
|
220
|
+
* bypass createResponseWithMergedHeaders (e.g. middleware short-circuits)
|
|
221
|
+
* but still need ctx.onResponse() callbacks to fire.
|
|
222
|
+
*/
|
|
223
|
+
export function finalizeResponse(response: Response): Response {
|
|
224
|
+
const ctx = _getRequestContext();
|
|
225
|
+
if (!ctx) return response;
|
|
226
|
+
return drainOnResponseCallbacks(ctx, response);
|
|
198
227
|
}
|
package/src/rsc/index.ts
CHANGED
package/src/rsc/origin-guard.ts
CHANGED
|
@@ -9,11 +9,29 @@
|
|
|
9
9
|
* navigations, bookmarks, and non-browser clients don't send Origin.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import type { RequestPlan } from "../router/request-classification.js";
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* Request phase that triggered the origin check.
|
|
14
16
|
*/
|
|
15
17
|
export type OriginCheckPhase = "action" | "loader" | "pe-form";
|
|
16
18
|
|
|
19
|
+
// Exhaustive over RequestPlan modes so a new mode must be classified here (the
|
|
20
|
+
// security gate) instead of silently falling through to no origin check.
|
|
21
|
+
export const ORIGIN_CHECK_PHASE_BY_MODE: Record<
|
|
22
|
+
RequestPlan["mode"],
|
|
23
|
+
OriginCheckPhase | null
|
|
24
|
+
> = {
|
|
25
|
+
action: "action",
|
|
26
|
+
loader: "loader",
|
|
27
|
+
"pe-render": "pe-form",
|
|
28
|
+
"full-render": null,
|
|
29
|
+
"partial-render": null,
|
|
30
|
+
response: null,
|
|
31
|
+
redirect: null,
|
|
32
|
+
"version-mismatch": null,
|
|
33
|
+
};
|
|
34
|
+
|
|
17
35
|
/**
|
|
18
36
|
* Context passed to the originCheck callback.
|
|
19
37
|
*/
|
|
@@ -116,14 +134,15 @@ export async function checkRequestOrigin<TEnv = any>(
|
|
|
116
134
|
// Disabled by explicit opt-out
|
|
117
135
|
if (config === false) return null;
|
|
118
136
|
|
|
119
|
-
// Default
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
137
|
+
// Default (true/undefined) becomes a callback returning boolean, so the
|
|
138
|
+
// Response|true|reject resolution below is written once.
|
|
139
|
+
const check: (
|
|
140
|
+
ctx: OriginCheckContext<TEnv>,
|
|
141
|
+
) => boolean | Response | Promise<boolean | Response> =
|
|
142
|
+
config === true || config === undefined
|
|
143
|
+
? () => defaultOriginCheck(request, url)
|
|
144
|
+
: config;
|
|
125
145
|
|
|
126
|
-
// Custom function — build context and call
|
|
127
146
|
const ctx: OriginCheckContext<TEnv> = {
|
|
128
147
|
request,
|
|
129
148
|
url,
|
|
@@ -133,9 +152,8 @@ export async function checkRequestOrigin<TEnv = any>(
|
|
|
133
152
|
defaultCheck: () => defaultOriginCheck(request, url),
|
|
134
153
|
};
|
|
135
154
|
|
|
136
|
-
const result = await
|
|
155
|
+
const result = await check(ctx);
|
|
137
156
|
|
|
138
157
|
if (result instanceof Response) return result;
|
|
139
|
-
|
|
140
|
-
return createForbiddenResponse(request);
|
|
158
|
+
return result === true ? null : createForbiddenResponse(request);
|
|
141
159
|
}
|
|
@@ -248,6 +248,8 @@ export async function handleProgressiveEnhancement<TEnv>(
|
|
|
248
248
|
segments: match.segments,
|
|
249
249
|
matched: match.matched,
|
|
250
250
|
diff: match.diff,
|
|
251
|
+
resolvedIds: match.resolvedIds,
|
|
252
|
+
params: match.params,
|
|
251
253
|
isPartial: false,
|
|
252
254
|
rootLayout: ctx.router.rootLayout,
|
|
253
255
|
handles: handleStore.stream(),
|
|
@@ -353,6 +355,8 @@ async function renderPeErrorBoundary<TEnv>(
|
|
|
353
355
|
segments: errorResult.segments,
|
|
354
356
|
matched: errorResult.matched,
|
|
355
357
|
diff: errorResult.diff,
|
|
358
|
+
resolvedIds: errorResult.resolvedIds,
|
|
359
|
+
params: errorResult.params,
|
|
356
360
|
isPartial: false,
|
|
357
361
|
isError: true,
|
|
358
362
|
rootLayout: ctx.router.rootLayout,
|