@rangojs/router 0.0.0-experimental.77 → 0.0.0-experimental.77ed8945
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 +2103 -861
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +13 -8
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +66 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +26 -4
- package/skills/layout/SKILL.md +6 -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 +562 -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 +12 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +238 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +33 -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/tailwind/SKILL.md +27 -3
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +39 -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 +29 -9
- package/src/browser/navigation-client.ts +99 -77
- package/src/browser/navigation-store.ts +7 -8
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +60 -40
- package/src/browser/prefetch/cache.ts +196 -49
- package/src/browser/prefetch/fetch.ts +203 -59
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +37 -13
- package/src/browser/react/Link.tsx +18 -13
- package/src/browser/react/NavigationProvider.tsx +75 -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 +23 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +71 -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 +44 -30
- package/src/browser/types.ts +12 -2
- 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 +8 -1
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +45 -1
- 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-runtime.ts +17 -5
- package/src/cache/cache-scope.ts +51 -49
- package/src/cache/cf/cf-cache-store.ts +502 -32
- package/src/cache/cf/index.ts +3 -0
- package/src/cache/handle-snapshot.ts +103 -0
- package/src/cache/index.ts +3 -0
- package/src/cache/memory-segment-store.ts +3 -2
- package/src/cache/types.ts +10 -6
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +96 -205
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -4
- 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 -21
- package/src/index.rsc.ts +10 -6
- package/src/index.ts +17 -8
- 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/store.ts +9 -7
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -39
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +253 -265
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +43 -15
- 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/find-match.ts +54 -6
- package/src/router/handler-context.ts +21 -41
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +41 -22
- package/src/router/loader-resolution.ts +82 -36
- package/src/router/manifest.ts +41 -19
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +1 -0
- package/src/router/match-middleware/cache-lookup.ts +57 -95
- 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 +116 -19
- package/src/router/prerender-match.ts +40 -15
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +40 -37
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +51 -35
- 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/revalidation.ts +147 -106
- package/src/router/segment-resolution/static-store.ts +19 -5
- 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 +40 -16
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +37 -25
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +58 -77
- package/src/rsc/helpers.ts +72 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +30 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -61
- package/src/rsc/rsc-rendering.ts +45 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +33 -39
- 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 +175 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +57 -51
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +1 -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 +11 -9
- 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 +1 -5
- package/src/urls/path-helper-types.ts +17 -3
- 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 +58 -139
- 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 +106 -75
- 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 +72 -31
- 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 +753 -104
- 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 +8 -59
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +5 -4
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { TrieNode, TrieLeaf } from "../build/route-trie.js";
|
|
9
|
+
import { safeDecodeURIComponent } from "./url-params.js";
|
|
9
10
|
|
|
10
11
|
export interface TrieMatchResult {
|
|
11
12
|
/** Route name */
|
|
@@ -14,10 +15,10 @@ export interface TrieMatchResult {
|
|
|
14
15
|
sp: string;
|
|
15
16
|
/** Matched route params */
|
|
16
17
|
params: Record<string, string>;
|
|
17
|
-
/** Optional param names
|
|
18
|
+
/** Optional param names declared on the route. Absent params are omitted
|
|
19
|
+
* from `params` (read as `undefined`), matching the
|
|
20
|
+
* `ExtractParams<"/:locale?/...">` type. */
|
|
18
21
|
optionalParams?: string[];
|
|
19
|
-
/** Ancestry shortCodes for layout pruning */
|
|
20
|
-
ancestry: string[];
|
|
21
22
|
/** Redirect target if trailing slash requires it */
|
|
22
23
|
redirectTo?: string;
|
|
23
24
|
/** Route has pre-rendered data available */
|
|
@@ -60,6 +61,19 @@ export function tryTrieMatch(
|
|
|
60
61
|
pathnameHasTrailingSlash,
|
|
61
62
|
);
|
|
62
63
|
}
|
|
64
|
+
// A root-level wildcard ("/*") matches "/" with an empty remainder, the
|
|
65
|
+
// same value the regex matcher produces for the bare prefix. Without this
|
|
66
|
+
// the trie misses, the regex fallback runs, and its no-config branch emits
|
|
67
|
+
// a corrupt slice-off redirect. The static terminal still wins above.
|
|
68
|
+
if (trie.w) {
|
|
69
|
+
return validateAndBuild(
|
|
70
|
+
trie.w,
|
|
71
|
+
[],
|
|
72
|
+
"",
|
|
73
|
+
pathname,
|
|
74
|
+
pathnameHasTrailingSlash,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
63
77
|
return null;
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -102,6 +116,15 @@ function walkTrie(
|
|
|
102
116
|
if (node.r) {
|
|
103
117
|
return { leaf: node.r, paramValues: [...paramValues] };
|
|
104
118
|
}
|
|
119
|
+
// A wildcard at this node matches the bare prefix with an empty remainder
|
|
120
|
+
// (e.g. "/files" against "/files/*"), mirroring the regex matcher's `*=""`.
|
|
121
|
+
// walkTrie otherwise only reaches node.w in the index<length branch below,
|
|
122
|
+
// so without this a request to the wildcard's own prefix misses the trie
|
|
123
|
+
// and the regex fallback emits a corrupt redirect. A static terminal
|
|
124
|
+
// (node.r) still wins.
|
|
125
|
+
if (node.w) {
|
|
126
|
+
return { leaf: node.w, paramValues: [...paramValues], wildcardValue: "" };
|
|
127
|
+
}
|
|
105
128
|
return null;
|
|
106
129
|
}
|
|
107
130
|
|
|
@@ -173,20 +196,25 @@ function validateAndBuild(
|
|
|
173
196
|
originalPathname: string,
|
|
174
197
|
pathnameHasTrailingSlash: boolean,
|
|
175
198
|
): TrieMatchResult | null {
|
|
176
|
-
// Build named params by zipping leaf.pa with positional paramValues
|
|
199
|
+
// Build named params by zipping leaf.pa with positional paramValues.
|
|
200
|
+
// Params are URL-decoded at this boundary so ctx.params holds the values
|
|
201
|
+
// apps expect (matching Express/React Router) and round-trip cleanly
|
|
202
|
+
// through ctx.reverse.
|
|
177
203
|
const params: Record<string, string> = {};
|
|
178
204
|
if (leaf.pa) {
|
|
179
205
|
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
180
|
-
params[leaf.pa[i]] = paramValues[i];
|
|
206
|
+
params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
|
|
181
207
|
}
|
|
182
208
|
}
|
|
183
209
|
|
|
184
210
|
// Add wildcard param (wildcard leaves have pn from TrieNode.w type)
|
|
185
211
|
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
186
|
-
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
212
|
+
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
213
|
+
safeDecodeURIComponent(wildcardValue);
|
|
187
214
|
}
|
|
188
215
|
|
|
189
|
-
// Validate constraints
|
|
216
|
+
// Validate constraints against decoded values so constraint lists can be
|
|
217
|
+
// written in decoded form (e.g. ["en-GB", "en US"]).
|
|
190
218
|
if (leaf.cv) {
|
|
191
219
|
for (const paramName in leaf.cv) {
|
|
192
220
|
const allowed = leaf.cv[paramName]!;
|
|
@@ -197,14 +225,11 @@ function validateAndBuild(
|
|
|
197
225
|
}
|
|
198
226
|
}
|
|
199
227
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
228
|
+
// Optional params that weren't matched are left absent from `params` so
|
|
229
|
+
// `ctx.params.locale` reads as `undefined`, matching the
|
|
230
|
+
// `ExtractParams<"/:locale?/...">` type (`{ locale?: string }`). Both
|
|
231
|
+
// internal consumers — the constraint check above and `reverse()` —
|
|
232
|
+
// already treat missing/undefined as the absent form.
|
|
208
233
|
|
|
209
234
|
// Trailing slash handling
|
|
210
235
|
const tsMode = leaf.ts as "never" | "always" | "ignore" | undefined;
|
|
@@ -224,7 +249,6 @@ function validateAndBuild(
|
|
|
224
249
|
routeKey: leaf.n,
|
|
225
250
|
sp: leaf.sp,
|
|
226
251
|
params,
|
|
227
|
-
ancestry: leaf.a,
|
|
228
252
|
};
|
|
229
253
|
|
|
230
254
|
if (leaf.op) result.optionalParams = leaf.op;
|
package/src/router/types.ts
CHANGED
|
@@ -98,6 +98,14 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
98
98
|
) => ReactNode | NotFoundBoundaryHandler | null;
|
|
99
99
|
notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
100
100
|
callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
|
|
101
|
+
/**
|
|
102
|
+
* Router-level default for the per-segment `transition({ viewTransition })`
|
|
103
|
+
* flag, from createRouter({ viewTransition }). Resolved into each segment's
|
|
104
|
+
* transition config during resolution (only `false` is stamped) so the render
|
|
105
|
+
* gate reads the boundary decision off the segment on both server and client.
|
|
106
|
+
* Undefined is treated as "auto" (wrap).
|
|
107
|
+
*/
|
|
108
|
+
viewTransitionDefault?: "auto" | false;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL param encode/decode at the route boundary.
|
|
3
|
+
*
|
|
4
|
+
* Extraction (decode): regex/trie matchers keep param values URL-encoded;
|
|
5
|
+
* `safeDecodeURIComponent` turns them back into raw strings so `ctx.params`
|
|
6
|
+
* matches the contract apps expect (Express/React Router/Fastify/Koa) and
|
|
7
|
+
* round-trips through reverse stay stable. Malformed %-encoding is
|
|
8
|
+
* preserved as-is so a broken URL doesn't crash matching.
|
|
9
|
+
*
|
|
10
|
+
* Reversal (encode): `encodePathSegment` escapes only what RFC 3986
|
|
11
|
+
* requires for a path segment — `/`, `?`, `#`, space, control chars,
|
|
12
|
+
* non-ASCII — and leaves pchar sub-delims (`@ : $ & + , ; =` and friends)
|
|
13
|
+
* readable. `encodeURIComponent` over-encodes for path segments, which
|
|
14
|
+
* makes generated URLs harder for humans to read in the address bar
|
|
15
|
+
* (e.g. mailbox IDs like `ivo@example.com` would become
|
|
16
|
+
* `ivo%40example.com` even though `@` is path-legal).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export function safeDecodeURIComponent(raw: string): string {
|
|
20
|
+
if (raw === "" || raw.indexOf("%") === -1) return raw;
|
|
21
|
+
try {
|
|
22
|
+
return decodeURIComponent(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// encodeURIComponent over-encodes for path segments. After running it,
|
|
29
|
+
// un-encode the pchar sub-delims + (`:` / `@`) so the resulting URL
|
|
30
|
+
// keeps human-readable characters that are legal in a path segment.
|
|
31
|
+
// Everything dangerous — `/ ? # %` and space/control/non-ASCII — stays
|
|
32
|
+
// encoded.
|
|
33
|
+
const PATH_SAFE_ESCAPES: Record<string, string> = {
|
|
34
|
+
"%3A": ":",
|
|
35
|
+
"%40": "@",
|
|
36
|
+
"%24": "$",
|
|
37
|
+
"%26": "&",
|
|
38
|
+
"%2B": "+",
|
|
39
|
+
"%2C": ",",
|
|
40
|
+
"%3B": ";",
|
|
41
|
+
"%3D": "=",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function encodePathSegment(value: string): string {
|
|
45
|
+
return encodeURIComponent(value).replace(
|
|
46
|
+
/%(?:3A|40|24|26|2B|2C|3B|3D)/gi,
|
|
47
|
+
(match) => PATH_SAFE_ESCAPES[match.toUpperCase()] ?? match,
|
|
48
|
+
);
|
|
49
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -21,11 +21,11 @@ import type { AllUseItems } from "./route-types.js";
|
|
|
21
21
|
import type { UrlPatterns } from "./urls.js";
|
|
22
22
|
import type { UrlBuilder } from "./urls/pattern-types.js";
|
|
23
23
|
import { urls } from "./urls.js";
|
|
24
|
+
import { buildPrecomputedByPrefix } from "./build/prefix-tree-utils.js";
|
|
24
25
|
import {
|
|
25
|
-
EntryData,
|
|
26
|
-
InterceptSelectorContext,
|
|
26
|
+
type EntryData,
|
|
27
27
|
getContext,
|
|
28
|
-
|
|
28
|
+
RangoContext,
|
|
29
29
|
type MetricsStore,
|
|
30
30
|
} from "./server/context";
|
|
31
31
|
import { createHandleStore, type HandleStore } from "./server/handle-store.js";
|
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
} from "./router/middleware.js";
|
|
72
72
|
import {
|
|
73
73
|
extractStaticPrefix,
|
|
74
|
+
joinPrefix,
|
|
74
75
|
traverseBack,
|
|
75
76
|
} from "./router/pattern-matching.js";
|
|
76
77
|
import { resolveSink, safeEmit, getRequestId } from "./router/telemetry.js";
|
|
@@ -91,13 +92,10 @@ import {
|
|
|
91
92
|
RouterRegistry,
|
|
92
93
|
nextRouterAutoId,
|
|
93
94
|
} from "./router/router-registry.js";
|
|
95
|
+
import type { RangoOptions, RootLayoutProps } from "./router/router-options.js";
|
|
94
96
|
import type {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} from "./router/router-options.js";
|
|
98
|
-
import type {
|
|
99
|
-
RSCRouter,
|
|
100
|
-
RSCRouterInternal,
|
|
97
|
+
Rango,
|
|
98
|
+
RangoInternal,
|
|
101
99
|
RouterRequestInput,
|
|
102
100
|
} from "./router/router-interfaces.js";
|
|
103
101
|
|
|
@@ -116,22 +114,22 @@ import {
|
|
|
116
114
|
// Re-export public types and values from extracted modules
|
|
117
115
|
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
|
|
118
116
|
export type {
|
|
119
|
-
|
|
117
|
+
RangoOptions,
|
|
120
118
|
RootLayoutProps,
|
|
121
119
|
SSRStreamMode,
|
|
122
120
|
SSROptions,
|
|
123
121
|
ResolveStreamingContext,
|
|
124
122
|
} from "./router/router-options.js";
|
|
125
123
|
export type {
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
Rango,
|
|
125
|
+
RangoInternal,
|
|
128
126
|
RouterRequestInput,
|
|
129
127
|
} from "./router/router-interfaces.js";
|
|
130
128
|
export { toInternal } from "./router/router-interfaces.js";
|
|
131
129
|
|
|
132
130
|
export function createRouter<TEnv = any>(
|
|
133
|
-
options:
|
|
134
|
-
):
|
|
131
|
+
options: RangoOptions<TEnv> = {},
|
|
132
|
+
): Rango<TEnv, {}> {
|
|
135
133
|
const {
|
|
136
134
|
id: userProvidedId,
|
|
137
135
|
$$id: injectedId,
|
|
@@ -159,6 +157,7 @@ export function createRouter<TEnv = any>(
|
|
|
159
157
|
timeouts: timeoutsOption,
|
|
160
158
|
onTimeout,
|
|
161
159
|
originCheck: originCheckOption,
|
|
160
|
+
viewTransition: viewTransitionOption = "auto",
|
|
162
161
|
} = options;
|
|
163
162
|
|
|
164
163
|
// Normalize basename: ensure leading slash, strip trailing slash.
|
|
@@ -366,9 +365,11 @@ export function createRouter<TEnv = any>(
|
|
|
366
365
|
getRouterPrecomputedEntries(routerId) ?? getPrecomputedEntries();
|
|
367
366
|
if (current !== precomputedSource) {
|
|
368
367
|
precomputedSource = current;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
368
|
+
// buildPrecomputedByPrefix drops any staticPrefix owned by more than one
|
|
369
|
+
// leaf include instead of collapsing it last-wins (which would mis-assign
|
|
370
|
+
// one include's routes to another's entry and 500 a valid sibling route).
|
|
371
|
+
// Such shared-prefix includes resolve via the handler path instead.
|
|
372
|
+
precomputedByPrefix = current ? buildPrecomputedByPrefix(current) : null;
|
|
372
373
|
}
|
|
373
374
|
return precomputedByPrefix;
|
|
374
375
|
}
|
|
@@ -538,6 +539,7 @@ export function createRouter<TEnv = any>(
|
|
|
538
539
|
findNearestNotFoundBoundary,
|
|
539
540
|
notFoundComponent: notFound,
|
|
540
541
|
callOnError,
|
|
542
|
+
viewTransitionDefault: viewTransitionOption,
|
|
541
543
|
};
|
|
542
544
|
|
|
543
545
|
// Match API dependencies
|
|
@@ -674,7 +676,7 @@ export function createRouter<TEnv = any>(
|
|
|
674
676
|
* The type system tracks accumulated routes through the builder chain
|
|
675
677
|
* Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
|
|
676
678
|
*/
|
|
677
|
-
const router:
|
|
679
|
+
const router: RangoInternal<TEnv, {}> = {
|
|
678
680
|
__brand: RSC_ROUTER_BRAND,
|
|
679
681
|
id: routerId,
|
|
680
682
|
basename,
|
|
@@ -722,7 +724,7 @@ export function createRouter<TEnv = any>(
|
|
|
722
724
|
};
|
|
723
725
|
|
|
724
726
|
let handlerResult: AllUseItems[] = [];
|
|
725
|
-
|
|
727
|
+
RangoContext.run(
|
|
726
728
|
{
|
|
727
729
|
manifest,
|
|
728
730
|
patterns: routePatterns,
|
|
@@ -834,10 +836,13 @@ export function createRouter<TEnv = any>(
|
|
|
834
836
|
|
|
835
837
|
// Create placeholder RouteEntry for each lazy include
|
|
836
838
|
for (const lazyInclude of lazyIncludes) {
|
|
837
|
-
// Compute the full URL prefix (combining parent prefix if any)
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
839
|
+
// Compute the full URL prefix (combining parent prefix if any). Use the
|
|
840
|
+
// slash-collapsing join so a trailing-slash parent prefix does not
|
|
841
|
+
// produce a double-slash staticPrefix the trie's sp can never match.
|
|
842
|
+
const fullPrefix = joinPrefix(
|
|
843
|
+
lazyInclude.context.urlPrefix,
|
|
844
|
+
lazyInclude.prefix,
|
|
845
|
+
);
|
|
841
846
|
|
|
842
847
|
const lazyEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
|
|
843
848
|
prefix: "",
|
|
@@ -1000,6 +1005,13 @@ export function createRouter<TEnv = any>(
|
|
|
1000
1005
|
// Expose basename for runtime manifest generation
|
|
1001
1006
|
__basename: basename,
|
|
1002
1007
|
|
|
1008
|
+
// Expose router-level boundary defaults for build-time clientChunks
|
|
1009
|
+
// discovery (so a "use client" default boundary lands in app-fallback).
|
|
1010
|
+
// These are createRouter options, never pushed onto EntryData.
|
|
1011
|
+
__defaultErrorBoundary: defaultErrorBoundary,
|
|
1012
|
+
__defaultNotFoundBoundary: defaultNotFoundBoundary,
|
|
1013
|
+
__notFound: notFound,
|
|
1014
|
+
|
|
1003
1015
|
// RSC request handler (lazily created on first call)
|
|
1004
1016
|
fetch: (() => {
|
|
1005
1017
|
// Handler is created on first call and reused
|
|
@@ -1046,9 +1058,9 @@ export function createRouter<TEnv = any>(
|
|
|
1046
1058
|
|
|
1047
1059
|
// If urls option was provided, auto-register them
|
|
1048
1060
|
if (typeof urlsOption === "function") {
|
|
1049
|
-
return router.routes(urlsOption) as
|
|
1061
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1050
1062
|
} else if (urlsOption) {
|
|
1051
|
-
return router.routes(urlsOption) as
|
|
1063
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1052
1064
|
}
|
|
1053
1065
|
|
|
1054
1066
|
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,7 @@ import {
|
|
|
56
57
|
getRouterTrie,
|
|
57
58
|
} from "../route-map-builder.js";
|
|
58
59
|
import type { HandlerContext } from "./handler-context.js";
|
|
60
|
+
import type { SegmentCacheStore } from "../cache/types.js";
|
|
59
61
|
import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
|
|
60
62
|
import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
|
|
61
63
|
import {
|
|
@@ -64,7 +66,10 @@ import {
|
|
|
64
66
|
type ActionContinuation,
|
|
65
67
|
} from "./server-action.js";
|
|
66
68
|
import { handleLoaderFetch } from "./loader-fetch.js";
|
|
67
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
checkRequestOrigin,
|
|
71
|
+
ORIGIN_CHECK_PHASE_BY_MODE,
|
|
72
|
+
} from "./origin-guard.js";
|
|
68
73
|
import { handleRscRendering } from "./rsc-rendering.js";
|
|
69
74
|
import {
|
|
70
75
|
withTimeout,
|
|
@@ -81,6 +86,7 @@ import {
|
|
|
81
86
|
startSSRSetup,
|
|
82
87
|
getSSRSetup,
|
|
83
88
|
mayNeedSSR,
|
|
89
|
+
isRscRequest,
|
|
84
90
|
SSR_SETUP_VAR,
|
|
85
91
|
} from "./ssr-setup.js";
|
|
86
92
|
import {
|
|
@@ -122,6 +128,22 @@ import {
|
|
|
122
128
|
* });
|
|
123
129
|
* ```
|
|
124
130
|
*/
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Response that tells the client to do a full document navigation. Shared by
|
|
134
|
+
* the terminal reload plans (version-mismatch and app-switch): an empty 200
|
|
135
|
+
* carrying X-RSC-Reload, which the client turns into window.location.href.
|
|
136
|
+
*/
|
|
137
|
+
function createReloadResponse(reloadUrl: string) {
|
|
138
|
+
return createResponseWithMergedHeaders(null, {
|
|
139
|
+
status: 200,
|
|
140
|
+
headers: {
|
|
141
|
+
"X-RSC-Reload": reloadUrl,
|
|
142
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
125
147
|
export function createRSCHandler<
|
|
126
148
|
TEnv = unknown,
|
|
127
149
|
TRoutes extends Record<string, string> = Record<string, string>,
|
|
@@ -352,7 +374,7 @@ export function createRSCHandler<
|
|
|
352
374
|
// Resolve cache store configuration
|
|
353
375
|
// Priority: options.cache (handler override) > router.cache (router default)
|
|
354
376
|
// Store is enabled only if: config provided, enabled, and no ?__no_cache query param
|
|
355
|
-
let cacheStore
|
|
377
|
+
let cacheStore: SegmentCacheStore | undefined;
|
|
356
378
|
const cacheOption = options.cache ?? router.cache;
|
|
357
379
|
if (cacheOption && !url.searchParams.has("__no_cache")) {
|
|
358
380
|
const cacheConfig =
|
|
@@ -533,7 +555,9 @@ export function createRSCHandler<
|
|
|
533
555
|
}
|
|
534
556
|
|
|
535
557
|
const fullTiming = timingParts.join(", ");
|
|
536
|
-
if (fullTiming
|
|
558
|
+
if (fullTiming && !isWebSocketUpgradeResponse(response)) {
|
|
559
|
+
response.headers.set("Server-Timing", fullTiming);
|
|
560
|
+
}
|
|
537
561
|
|
|
538
562
|
return response;
|
|
539
563
|
});
|
|
@@ -593,10 +617,7 @@ export function createRSCHandler<
|
|
|
593
617
|
routerId: router.id,
|
|
594
618
|
});
|
|
595
619
|
} catch (error) {
|
|
596
|
-
if (
|
|
597
|
-
error instanceof RouteNotFoundError ||
|
|
598
|
-
(error instanceof Error && error.name === "RouteNotFoundError")
|
|
599
|
-
) {
|
|
620
|
+
if (isRouteNotFoundError(error)) {
|
|
600
621
|
// Let the render path handle 404 — match()/matchPartial() will
|
|
601
622
|
// re-throw RouteNotFoundError and the catch block in
|
|
602
623
|
// executeRenderWithMiddleware renders the not-found page.
|
|
@@ -637,24 +658,18 @@ export function createRSCHandler<
|
|
|
637
658
|
console.log(
|
|
638
659
|
`[RSC] Version mismatch: client=${url.searchParams.get("_rsc_v")}, server=${version}. Forcing reload.`,
|
|
639
660
|
);
|
|
640
|
-
return
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
661
|
+
return createReloadResponse(plan.reloadUrl);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (plan.mode === "app-switch") {
|
|
665
|
+
// Cross-app SPA navigation crossed a host-router app boundary. Force a
|
|
666
|
+
// real document navigation so the target app's document is re-established
|
|
667
|
+
// (stylesheets, theme, warmup, prefetch-TTL). See request-classification.
|
|
668
|
+
return createReloadResponse(plan.reloadUrl);
|
|
647
669
|
}
|
|
648
670
|
|
|
649
671
|
// ---- 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;
|
|
672
|
+
const originPhase = ORIGIN_CHECK_PHASE_BY_MODE[plan.mode];
|
|
658
673
|
if (originPhase) {
|
|
659
674
|
const originResult = await checkRequestOrigin(
|
|
660
675
|
request,
|
|
@@ -804,7 +819,7 @@ export function createRSCHandler<
|
|
|
804
819
|
);
|
|
805
820
|
}
|
|
806
821
|
const response = responseOutcome.result;
|
|
807
|
-
if (plan.negotiated) {
|
|
822
|
+
if (plan.negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
808
823
|
response.headers.append("Vary", "Accept");
|
|
809
824
|
}
|
|
810
825
|
return response;
|
|
@@ -921,47 +936,17 @@ export function createRSCHandler<
|
|
|
921
936
|
);
|
|
922
937
|
}
|
|
923
938
|
|
|
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 })
|
|
939
|
+
// Full render, partial render, fallen-through PE, and full-page redirect all
|
|
940
|
+
// render through the same middleware-wrapped path. Only full/partial-render
|
|
941
|
+
// carry negotiation + the partial flag; pe/redirect render plainly.
|
|
942
|
+
const isPartial = plan.mode === "partial-render";
|
|
943
|
+
const negotiated =
|
|
944
|
+
plan.mode === "full-render" || plan.mode === "partial-render"
|
|
945
|
+
? plan.negotiated
|
|
946
|
+
: false;
|
|
962
947
|
return executeRenderWithMiddleware(
|
|
963
948
|
plan.route.routeMiddleware,
|
|
964
|
-
|
|
949
|
+
negotiated,
|
|
965
950
|
plan.route.routeKey,
|
|
966
951
|
routeReverse,
|
|
967
952
|
request,
|
|
@@ -970,7 +955,7 @@ export function createRSCHandler<
|
|
|
970
955
|
variables,
|
|
971
956
|
nonce,
|
|
972
957
|
handleStore,
|
|
973
|
-
|
|
958
|
+
isPartial,
|
|
974
959
|
);
|
|
975
960
|
}
|
|
976
961
|
|
|
@@ -1014,7 +999,7 @@ export function createRSCHandler<
|
|
|
1014
999
|
nonce,
|
|
1015
1000
|
);
|
|
1016
1001
|
}
|
|
1017
|
-
if (negotiated) {
|
|
1002
|
+
if (negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
1018
1003
|
response.headers.append("Vary", "Accept");
|
|
1019
1004
|
}
|
|
1020
1005
|
return response;
|
|
@@ -1050,10 +1035,7 @@ export function createRSCHandler<
|
|
|
1050
1035
|
}
|
|
1051
1036
|
|
|
1052
1037
|
// Render 404 page for unmatched routes
|
|
1053
|
-
|
|
1054
|
-
error instanceof RouteNotFoundError ||
|
|
1055
|
-
(error instanceof Error && error.name === "RouteNotFoundError");
|
|
1056
|
-
if (isRouteNotFound) {
|
|
1038
|
+
if (isRouteNotFoundError(error)) {
|
|
1057
1039
|
callOnError(error, "routing", {
|
|
1058
1040
|
request,
|
|
1059
1041
|
url,
|
|
@@ -1100,16 +1082,15 @@ export function createRSCHandler<
|
|
|
1100
1082
|
},
|
|
1101
1083
|
});
|
|
1102
1084
|
|
|
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) {
|
|
1085
|
+
if (isRscRequest(request, url, isPartial)) {
|
|
1110
1086
|
return createResponseWithMergedHeaders(rscStream, {
|
|
1111
1087
|
status: 404,
|
|
1112
|
-
headers: {
|
|
1088
|
+
headers: {
|
|
1089
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
1090
|
+
// Router identity for the client's pre-decode integrity check; a
|
|
1091
|
+
// same-app 404 matches and applies in place. See response-adapter.
|
|
1092
|
+
"X-RSC-Router-Id": router.id,
|
|
1093
|
+
},
|
|
1113
1094
|
});
|
|
1114
1095
|
}
|
|
1115
1096
|
|