@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc
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 +196 -43
- package/dist/bin/rango.js +277 -99
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +2779 -1064
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +243 -21
- package/skills/caching/SKILL.md +155 -6
- 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 +249 -17
- package/skills/loader/SKILL.md +273 -53
- package/skills/middleware/SKILL.md +49 -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 +197 -6
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +88 -4
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +716 -0
- package/skills/typesafety/SKILL.md +329 -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 +117 -0
- package/src/__internal.ts +1 -1
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +91 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +102 -16
- package/src/browser/navigation-client.ts +164 -59
- package/src/browser/navigation-store.ts +75 -17
- package/src/browser/navigation-transaction.ts +21 -37
- package/src/browser/partial-update.ts +139 -38
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +110 -33
- package/src/browser/react/context.ts +7 -2
- 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 +23 -64
- 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 +43 -10
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +191 -74
- package/src/browser/scroll-restoration.ts +41 -14
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +31 -36
- package/src/browser/types.ts +57 -5
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +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 +9 -2
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -88
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +76 -49
- package/src/cache/cf/cf-cache-store.ts +501 -18
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +94 -238
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +65 -12
- 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 +12 -5
- package/src/index.ts +61 -11
- 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 +5 -4
- package/src/prerender.ts +141 -80
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +435 -260
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +110 -34
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +113 -1
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +77 -38
- package/src/router/intercept-resolution.ts +15 -22
- package/src/router/lazy-includes.ts +12 -9
- package/src/router/loader-resolution.ts +174 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -192
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +136 -106
- package/src/router/match-middleware/cache-store.ts +54 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +125 -10
- package/src/router/metrics.ts +7 -2
- package/src/router/middleware-types.ts +21 -34
- package/src/router/middleware.ts +103 -90
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +286 -0
- package/src/router/revalidation.ts +58 -2
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +77 -28
- package/src/router/router-options.ts +76 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +223 -24
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +466 -285
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +9 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +91 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +440 -381
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +18 -2
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +41 -48
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +25 -37
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +17 -3
- 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 +219 -67
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +277 -61
- package/src/server/cookie-store.ts +28 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +204 -60
- package/src/ssr/index.tsx +9 -1
- package/src/static-handler.ts +19 -7
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +255 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +179 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +183 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +194 -72
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +37 -1
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +50 -9
- package/src/urls/path-helper.ts +63 -63
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +487 -44
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +34 -37
- package/src/vite/discovery/discover-routers.ts +105 -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 +188 -93
- 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 +46 -6
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +6 -0
- package/src/vite/plugin-types.ts +111 -72
- 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 +55 -33
- package/src/vite/plugins/expose-id-utils.ts +24 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
- 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 +544 -317
- package/src/vite/plugins/performance-tracks.ts +92 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- 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 +72 -3
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +265 -226
- package/src/vite/router-discovery.ts +920 -137
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +4 -4
- 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 +38 -5
- package/src/vite/utils/shared-utils.ts +109 -27
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View-transition boundary default resolution.
|
|
3
|
+
*
|
|
4
|
+
* Kept in its own module (rather than helpers.ts) because several resolution
|
|
5
|
+
* tests mock helpers.ts with an explicit export list; a shared util here is
|
|
6
|
+
* never mocked, so the fresh and revalidation paths always get the real
|
|
7
|
+
* implementation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { EntryData } from "../../server/context";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the effective `viewTransition` for a segment's transition config.
|
|
14
|
+
*
|
|
15
|
+
* The per-segment value (set via the transition() DSL) always wins. When it is
|
|
16
|
+
* unset, the router-level createRouter({ viewTransition }) default is stamped
|
|
17
|
+
* in so the render gate reads the boundary decision off the segment — server
|
|
18
|
+
* and client, via the serialized segment — without the router option being
|
|
19
|
+
* threaded to the client. Only `false` is ever stamped; an unset (or "auto")
|
|
20
|
+
* value is left untouched because it already means "wrap" at the gate, which
|
|
21
|
+
* also avoids needless object allocation and payload growth. Used by both the
|
|
22
|
+
* fresh and revalidation resolution paths.
|
|
23
|
+
*/
|
|
24
|
+
export function applyViewTransitionDefault(
|
|
25
|
+
transition: EntryData["transition"],
|
|
26
|
+
viewTransitionDefault: "auto" | false | undefined,
|
|
27
|
+
): EntryData["transition"] {
|
|
28
|
+
if (!transition) return transition;
|
|
29
|
+
if (
|
|
30
|
+
transition.viewTransition === undefined &&
|
|
31
|
+
viewTransitionDefault === false
|
|
32
|
+
) {
|
|
33
|
+
return { ...transition, viewTransition: false };
|
|
34
|
+
}
|
|
35
|
+
return transition;
|
|
36
|
+
}
|
|
@@ -204,6 +204,7 @@ export function createSegmentWrappers<TEnv = any>(
|
|
|
204
204
|
interceptResult: { intercept: InterceptEntry; entry: EntryData } | null,
|
|
205
205
|
localRouteName: string,
|
|
206
206
|
pathname: string,
|
|
207
|
+
stale?: boolean,
|
|
207
208
|
): ReturnType<typeof _resolveAllSegmentsWithRevalidation> {
|
|
208
209
|
return _resolveAllSegmentsWithRevalidation(
|
|
209
210
|
entries,
|
|
@@ -221,6 +222,7 @@ export function createSegmentWrappers<TEnv = any>(
|
|
|
221
222
|
localRouteName,
|
|
222
223
|
pathname,
|
|
223
224
|
segmentDeps,
|
|
225
|
+
stale,
|
|
224
226
|
);
|
|
225
227
|
}
|
|
226
228
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { encodePathSegment } from "./url-params.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Substitute `:param` placeholders in a route pattern with values from
|
|
5
|
+
* `params`. Two-pass: optional params (`:name?`) first so absent values
|
|
6
|
+
* collapse cleanly, then required params (throws on missing). Constraint
|
|
7
|
+
* syntax (`:name(en|gb)`) is stripped from the result. Trailing-slash
|
|
8
|
+
* patterns like `/blog/` are preserved unless an optional segment was
|
|
9
|
+
* actually omitted.
|
|
10
|
+
*
|
|
11
|
+
* Shared by `ctx.reverse()` (server), `createReverse()` (typed runtime
|
|
12
|
+
* helper), and `useReverse()` (client hook). The behavior must stay
|
|
13
|
+
* identical across all three call sites.
|
|
14
|
+
*/
|
|
15
|
+
export function substitutePatternParams(
|
|
16
|
+
pattern: string,
|
|
17
|
+
params: Record<string, string | undefined>,
|
|
18
|
+
routeName: string,
|
|
19
|
+
): string {
|
|
20
|
+
let result = pattern;
|
|
21
|
+
let hadOmittedOptional = false;
|
|
22
|
+
|
|
23
|
+
result = result.replace(
|
|
24
|
+
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(\?)/g,
|
|
25
|
+
(_match, key) => {
|
|
26
|
+
const value = params[key as string];
|
|
27
|
+
// The matcher omits absent optional params (so `value` is `undefined`
|
|
28
|
+
// here), but caller-supplied params or `getParams()` shapes may still
|
|
29
|
+
// pass `""` explicitly. Treat both as the absent form.
|
|
30
|
+
if (value === undefined || value === "") {
|
|
31
|
+
hadOmittedOptional = true;
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
return encodePathSegment(value);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
result = result.replace(
|
|
39
|
+
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(?!\?)/g,
|
|
40
|
+
(_match, key) => {
|
|
41
|
+
const value = params[key as string];
|
|
42
|
+
if (value === undefined) {
|
|
43
|
+
throw new Error(`Missing param "${key}" for route "${routeName}"`);
|
|
44
|
+
}
|
|
45
|
+
return encodePathSegment(value);
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (hadOmittedOptional) {
|
|
50
|
+
const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
51
|
+
result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
|
|
52
|
+
if (hadTrailingSlash && !result.endsWith("/")) result += "/";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
package/src/router/telemetry.ts
CHANGED
|
@@ -90,6 +90,34 @@ export interface HandlerErrorEvent extends BaseEvent {
|
|
|
90
90
|
params?: Record<string, string>;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Per-segment (or coarse route-level) cache status carried on the
|
|
95
|
+
* cache.decision telemetry event and the X-Rango-Cache debug header.
|
|
96
|
+
*
|
|
97
|
+
* v1 is COARSE: the router's pipeline tracks cache decisions at the
|
|
98
|
+
* route/entry level (cacheHit/cacheSource/shouldRevalidate), not per
|
|
99
|
+
* individual segment. The `segments` array therefore contains a single
|
|
100
|
+
* route-level entry keyed by the route key. The shape is forward-compatible
|
|
101
|
+
* with genuine per-segment status if the pipeline later exposes it.
|
|
102
|
+
*/
|
|
103
|
+
export type CacheSegmentStatus =
|
|
104
|
+
| "hit"
|
|
105
|
+
| "miss"
|
|
106
|
+
| "stale"
|
|
107
|
+
| "prerendered"
|
|
108
|
+
| "passthrough";
|
|
109
|
+
|
|
110
|
+
export interface CacheSegmentSignal {
|
|
111
|
+
/** Segment id (v1: the route key, since status is route-level). */
|
|
112
|
+
id: string;
|
|
113
|
+
/** Segment type (v1: "route" for the coarse route-level entry). */
|
|
114
|
+
type: string;
|
|
115
|
+
/** Resolved cache status for this segment. */
|
|
116
|
+
cacheStatus: CacheSegmentStatus;
|
|
117
|
+
/** Whether stale-while-revalidate was triggered for this segment. */
|
|
118
|
+
shouldRevalidate?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
export interface CacheDecisionEvent extends BaseEvent {
|
|
94
122
|
type: "cache.decision";
|
|
95
123
|
pathname: string;
|
|
@@ -98,6 +126,12 @@ export interface CacheDecisionEvent extends BaseEvent {
|
|
|
98
126
|
/** Whether stale-while-revalidate was triggered */
|
|
99
127
|
shouldRevalidate: boolean;
|
|
100
128
|
source?: "runtime" | "prerender";
|
|
129
|
+
/**
|
|
130
|
+
* Optional per-segment (v1: coarse route-level) cache status. Present only
|
|
131
|
+
* when telemetry or the debug cache signal is enabled. Optional so existing
|
|
132
|
+
* sinks are unaffected.
|
|
133
|
+
*/
|
|
134
|
+
segments?: CacheSegmentSignal[];
|
|
101
135
|
}
|
|
102
136
|
|
|
103
137
|
export interface RevalidationDecisionEvent extends BaseEvent {
|
|
@@ -140,6 +174,71 @@ export type TelemetryEvent =
|
|
|
140
174
|
| RequestTimeoutEvent
|
|
141
175
|
| OriginCheckRejectedEvent;
|
|
142
176
|
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Cache signal derivation (coarse, route-level)
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Derive the coarse, route-level cache status from pipeline cache state.
|
|
183
|
+
*
|
|
184
|
+
* v1 mapping (route-level — see CacheSegmentSignal):
|
|
185
|
+
* - prerender hit -> "prerendered"
|
|
186
|
+
* - runtime hit + shouldRevalidate (SWR) -> "stale"
|
|
187
|
+
* - runtime hit -> "hit"
|
|
188
|
+
* - no hit -> "miss"
|
|
189
|
+
*
|
|
190
|
+
* Note: "passthrough" is a build-time prerender concept (a route opts out of
|
|
191
|
+
* being prerendered for some params). At runtime a passthrough route renders
|
|
192
|
+
* fresh and is indistinguishable from a normal miss in the pipeline state, so
|
|
193
|
+
* v1 reports it as "miss". The "passthrough" status remains in the type union
|
|
194
|
+
* for forward compatibility.
|
|
195
|
+
*/
|
|
196
|
+
export function deriveCacheStatus(state: {
|
|
197
|
+
cacheHit: boolean;
|
|
198
|
+
cacheSource?: "runtime" | "prerender";
|
|
199
|
+
shouldRevalidate?: boolean;
|
|
200
|
+
}): CacheSegmentStatus {
|
|
201
|
+
if (state.cacheHit) {
|
|
202
|
+
if (state.cacheSource === "prerender") return "prerendered";
|
|
203
|
+
if (state.shouldRevalidate) return "stale";
|
|
204
|
+
return "hit";
|
|
205
|
+
}
|
|
206
|
+
return "miss";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build the coarse route-level cache signal array (a single entry keyed by
|
|
211
|
+
* the route key). Used for both the cache.decision telemetry event and the
|
|
212
|
+
* X-Rango-Cache debug header.
|
|
213
|
+
*/
|
|
214
|
+
export function buildCacheSignalSegments(
|
|
215
|
+
routeKey: string,
|
|
216
|
+
state: {
|
|
217
|
+
cacheHit: boolean;
|
|
218
|
+
cacheSource?: "runtime" | "prerender";
|
|
219
|
+
shouldRevalidate?: boolean;
|
|
220
|
+
},
|
|
221
|
+
): CacheSegmentSignal[] {
|
|
222
|
+
return [
|
|
223
|
+
{
|
|
224
|
+
id: routeKey,
|
|
225
|
+
type: "route",
|
|
226
|
+
cacheStatus: deriveCacheStatus(state),
|
|
227
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Serialize cache signal segments into the X-Rango-Cache header value:
|
|
234
|
+
* `<segId>=<status>, <segId2>=<status2>`.
|
|
235
|
+
*/
|
|
236
|
+
export function formatCacheSignalHeader(
|
|
237
|
+
segments: CacheSegmentSignal[],
|
|
238
|
+
): string {
|
|
239
|
+
return segments.map((s) => `${s.id}=${s.cacheStatus}`).join(", ");
|
|
240
|
+
}
|
|
241
|
+
|
|
143
242
|
// ---------------------------------------------------------------------------
|
|
144
243
|
// Sink interface
|
|
145
244
|
// ---------------------------------------------------------------------------
|
|
@@ -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,7 +15,9 @@ 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
22
|
/** Ancestry shortCodes for layout pruning */
|
|
20
23
|
ancestry: string[];
|
|
@@ -173,20 +176,25 @@ function validateAndBuild(
|
|
|
173
176
|
originalPathname: string,
|
|
174
177
|
pathnameHasTrailingSlash: boolean,
|
|
175
178
|
): TrieMatchResult | null {
|
|
176
|
-
// Build named params by zipping leaf.pa with positional paramValues
|
|
179
|
+
// Build named params by zipping leaf.pa with positional paramValues.
|
|
180
|
+
// Params are URL-decoded at this boundary so ctx.params holds the values
|
|
181
|
+
// apps expect (matching Express/React Router) and round-trip cleanly
|
|
182
|
+
// through ctx.reverse.
|
|
177
183
|
const params: Record<string, string> = {};
|
|
178
184
|
if (leaf.pa) {
|
|
179
185
|
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
180
|
-
params[leaf.pa[i]] = paramValues[i];
|
|
186
|
+
params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
|
|
181
187
|
}
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
// Add wildcard param (wildcard leaves have pn from TrieNode.w type)
|
|
185
191
|
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
186
|
-
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
192
|
+
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
193
|
+
safeDecodeURIComponent(wildcardValue);
|
|
187
194
|
}
|
|
188
195
|
|
|
189
|
-
// Validate constraints
|
|
196
|
+
// Validate constraints against decoded values so constraint lists can be
|
|
197
|
+
// written in decoded form (e.g. ["en-GB", "en US"]).
|
|
190
198
|
if (leaf.cv) {
|
|
191
199
|
for (const paramName in leaf.cv) {
|
|
192
200
|
const allowed = leaf.cv[paramName]!;
|
|
@@ -197,14 +205,11 @@ function validateAndBuild(
|
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
+
// Optional params that weren't matched are left absent from `params` so
|
|
209
|
+
// `ctx.params.locale` reads as `undefined`, matching the
|
|
210
|
+
// `ExtractParams<"/:locale?/...">` type (`{ locale?: string }`). Both
|
|
211
|
+
// internal consumers — the constraint check above and `reverse()` —
|
|
212
|
+
// already treat missing/undefined as the absent form.
|
|
208
213
|
|
|
209
214
|
// Trailing slash handling
|
|
210
215
|
const tsMode = leaf.ts as "never" | "always" | "ignore" | undefined;
|
package/src/router/types.ts
CHANGED
|
@@ -96,7 +96,16 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
96
96
|
findNearestNotFoundBoundary: (
|
|
97
97
|
entry: EntryData | null,
|
|
98
98
|
) => ReactNode | NotFoundBoundaryHandler | null;
|
|
99
|
+
notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
99
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;
|
|
100
109
|
}
|
|
101
110
|
|
|
102
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
|
@@ -19,11 +19,12 @@ import {
|
|
|
19
19
|
import MapRootLayout from "./server/root-layout.js";
|
|
20
20
|
import type { AllUseItems } from "./route-types.js";
|
|
21
21
|
import type { UrlPatterns } from "./urls.js";
|
|
22
|
+
import type { UrlBuilder } from "./urls/pattern-types.js";
|
|
23
|
+
import { urls } from "./urls.js";
|
|
22
24
|
import {
|
|
23
|
-
EntryData,
|
|
24
|
-
InterceptSelectorContext,
|
|
25
|
+
type EntryData,
|
|
25
26
|
getContext,
|
|
26
|
-
|
|
27
|
+
RangoContext,
|
|
27
28
|
type MetricsStore,
|
|
28
29
|
} from "./server/context";
|
|
29
30
|
import { createHandleStore, type HandleStore } from "./server/handle-store.js";
|
|
@@ -55,6 +56,7 @@ import { buildDebugManifest } from "./router/debug-manifest.js";
|
|
|
55
56
|
|
|
56
57
|
import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
|
|
57
58
|
import { createHandlerContext } from "./router/handler-context.js";
|
|
59
|
+
import { normalizeBasename } from "./router/basename.js";
|
|
58
60
|
import {
|
|
59
61
|
setupLoaderAccess,
|
|
60
62
|
setupLoaderAccessSilent,
|
|
@@ -89,13 +91,10 @@ import {
|
|
|
89
91
|
RouterRegistry,
|
|
90
92
|
nextRouterAutoId,
|
|
91
93
|
} from "./router/router-registry.js";
|
|
94
|
+
import type { RangoOptions, RootLayoutProps } from "./router/router-options.js";
|
|
92
95
|
import type {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
} from "./router/router-options.js";
|
|
96
|
-
import type {
|
|
97
|
-
RSCRouter,
|
|
98
|
-
RSCRouterInternal,
|
|
96
|
+
Rango,
|
|
97
|
+
RangoInternal,
|
|
99
98
|
RouterRequestInput,
|
|
100
99
|
} from "./router/router-interfaces.js";
|
|
101
100
|
|
|
@@ -114,25 +113,26 @@ import {
|
|
|
114
113
|
// Re-export public types and values from extracted modules
|
|
115
114
|
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
|
|
116
115
|
export type {
|
|
117
|
-
|
|
116
|
+
RangoOptions,
|
|
118
117
|
RootLayoutProps,
|
|
119
118
|
SSRStreamMode,
|
|
120
119
|
SSROptions,
|
|
121
120
|
ResolveStreamingContext,
|
|
122
121
|
} from "./router/router-options.js";
|
|
123
122
|
export type {
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
Rango,
|
|
124
|
+
RangoInternal,
|
|
126
125
|
RouterRequestInput,
|
|
127
126
|
} from "./router/router-interfaces.js";
|
|
128
127
|
export { toInternal } from "./router/router-interfaces.js";
|
|
129
128
|
|
|
130
129
|
export function createRouter<TEnv = any>(
|
|
131
|
-
options:
|
|
132
|
-
):
|
|
130
|
+
options: RangoOptions<TEnv> = {},
|
|
131
|
+
): Rango<TEnv, {}> {
|
|
133
132
|
const {
|
|
134
133
|
id: userProvidedId,
|
|
135
134
|
$$id: injectedId,
|
|
135
|
+
basename: basenameOption,
|
|
136
136
|
debugPerformance = false,
|
|
137
137
|
document: documentOption,
|
|
138
138
|
defaultErrorBoundary,
|
|
@@ -156,8 +156,24 @@ export function createRouter<TEnv = any>(
|
|
|
156
156
|
timeouts: timeoutsOption,
|
|
157
157
|
onTimeout,
|
|
158
158
|
originCheck: originCheckOption,
|
|
159
|
+
viewTransition: viewTransitionOption = "auto",
|
|
160
|
+
debugCacheSignal: debugCacheSignalOption = false,
|
|
159
161
|
} = options;
|
|
160
162
|
|
|
163
|
+
// Debug cache signal gate (DEVELOPMENT/TEST ONLY). Enabled by the
|
|
164
|
+
// debugCacheSignal option OR the RANGO_TEST_SIGNALS=1 env flag. When off,
|
|
165
|
+
// no X-Rango-Cache header is emitted and output is byte-identical.
|
|
166
|
+
const cacheSignalEnabled =
|
|
167
|
+
debugCacheSignalOption ||
|
|
168
|
+
(typeof process !== "undefined" &&
|
|
169
|
+
(process as { env?: Record<string, string | undefined> }).env
|
|
170
|
+
?.RANGO_TEST_SIGNALS === "1");
|
|
171
|
+
|
|
172
|
+
// Normalize basename: ensure leading slash, strip trailing slash.
|
|
173
|
+
// A bare "/" is equivalent to no basename. Shared with the testing
|
|
174
|
+
// primitives via normalizeBasename so they can never drift.
|
|
175
|
+
const basename = normalizeBasename(basenameOption);
|
|
176
|
+
|
|
161
177
|
// Resolve telemetry sink (no-op when not configured)
|
|
162
178
|
const telemetry = resolveSink(telemetrySink);
|
|
163
179
|
|
|
@@ -526,7 +542,9 @@ export function createRouter<TEnv = any>(
|
|
|
526
542
|
trackHandler,
|
|
527
543
|
findNearestErrorBoundary,
|
|
528
544
|
findNearestNotFoundBoundary,
|
|
545
|
+
notFoundComponent: notFound,
|
|
529
546
|
callOnError,
|
|
547
|
+
viewTransitionDefault: viewTransitionOption,
|
|
530
548
|
};
|
|
531
549
|
|
|
532
550
|
// Match API dependencies
|
|
@@ -560,6 +578,7 @@ export function createRouter<TEnv = any>(
|
|
|
560
578
|
mergedRouteMap,
|
|
561
579
|
nextMountIndex: () => mountIndex++,
|
|
562
580
|
getPrecomputedByPrefix,
|
|
581
|
+
routerId,
|
|
563
582
|
};
|
|
564
583
|
|
|
565
584
|
function evaluateLazyEntry(entry: RouteEntry<TEnv>): void {
|
|
@@ -613,6 +632,8 @@ export function createRouter<TEnv = any>(
|
|
|
613
632
|
params: Record<string, string>,
|
|
614
633
|
buildVars?: Record<string, any>,
|
|
615
634
|
isPassthroughRoute?: boolean,
|
|
635
|
+
buildEnv?: TEnv,
|
|
636
|
+
devMode?: boolean,
|
|
616
637
|
) {
|
|
617
638
|
return _matchForPrerender(
|
|
618
639
|
pathname,
|
|
@@ -620,6 +641,8 @@ export function createRouter<TEnv = any>(
|
|
|
620
641
|
prerenderDeps,
|
|
621
642
|
buildVars,
|
|
622
643
|
isPassthroughRoute,
|
|
644
|
+
buildEnv,
|
|
645
|
+
devMode,
|
|
623
646
|
);
|
|
624
647
|
}
|
|
625
648
|
|
|
@@ -627,12 +650,16 @@ export function createRouter<TEnv = any>(
|
|
|
627
650
|
handler: Function,
|
|
628
651
|
handlerId: string,
|
|
629
652
|
routeName?: string,
|
|
653
|
+
buildEnv?: TEnv,
|
|
654
|
+
devMode?: boolean,
|
|
630
655
|
) {
|
|
631
656
|
return _renderStaticSegment<TEnv>(
|
|
632
657
|
handler,
|
|
633
658
|
handlerId,
|
|
634
659
|
mergedRouteMap,
|
|
635
660
|
routeName,
|
|
661
|
+
buildEnv,
|
|
662
|
+
devMode,
|
|
636
663
|
);
|
|
637
664
|
}
|
|
638
665
|
|
|
@@ -645,6 +672,7 @@ export function createRouter<TEnv = any>(
|
|
|
645
672
|
findMatch,
|
|
646
673
|
findInterceptForRoute,
|
|
647
674
|
telemetry: telemetrySink,
|
|
675
|
+
cacheSignalEnabled,
|
|
648
676
|
});
|
|
649
677
|
|
|
650
678
|
const { match, matchPartial, matchError, previewMatch } = matchHandlers;
|
|
@@ -654,11 +682,18 @@ export function createRouter<TEnv = any>(
|
|
|
654
682
|
* The type system tracks accumulated routes through the builder chain
|
|
655
683
|
* Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
|
|
656
684
|
*/
|
|
657
|
-
const router:
|
|
685
|
+
const router: RangoInternal<TEnv, {}> = {
|
|
658
686
|
__brand: RSC_ROUTER_BRAND,
|
|
659
687
|
id: routerId,
|
|
688
|
+
basename,
|
|
689
|
+
|
|
690
|
+
routes(patternsOrBuilder: UrlPatterns<TEnv> | UrlBuilder<TEnv>): any {
|
|
691
|
+
// Wrap builder functions in urls() automatically
|
|
692
|
+
const urlPatterns: UrlPatterns<TEnv> =
|
|
693
|
+
typeof patternsOrBuilder === "function"
|
|
694
|
+
? (urls(patternsOrBuilder) as UrlPatterns<TEnv>)
|
|
695
|
+
: patternsOrBuilder;
|
|
660
696
|
|
|
661
|
-
routes(urlPatterns: UrlPatterns<TEnv>): any {
|
|
662
697
|
// Store reference for runtime manifest generation
|
|
663
698
|
storedUrlPatterns = urlPatterns;
|
|
664
699
|
const currentMountIndex = mountIndex++;
|
|
@@ -689,13 +724,13 @@ export function createRouter<TEnv = any>(
|
|
|
689
724
|
errorBoundary: [],
|
|
690
725
|
notFoundBoundary: [],
|
|
691
726
|
layout: [],
|
|
692
|
-
parallel:
|
|
727
|
+
parallel: {},
|
|
693
728
|
intercept: [],
|
|
694
729
|
loader: [],
|
|
695
730
|
};
|
|
696
731
|
|
|
697
732
|
let handlerResult: AllUseItems[] = [];
|
|
698
|
-
|
|
733
|
+
RangoContext.run(
|
|
699
734
|
{
|
|
700
735
|
manifest,
|
|
701
736
|
patterns: routePatterns,
|
|
@@ -706,6 +741,10 @@ export function createRouter<TEnv = any>(
|
|
|
706
741
|
counters: {},
|
|
707
742
|
mountIndex: currentMountIndex,
|
|
708
743
|
cacheProfiles: resolvedCacheProfiles,
|
|
744
|
+
// basename sets the initial URL prefix so all path() patterns
|
|
745
|
+
// are registered with the prefix (e.g. "/admin" + "/users" = "/admin/users").
|
|
746
|
+
// No namePrefix — route names stay unprefixed.
|
|
747
|
+
...(basename ? { urlPrefix: basename } : {}),
|
|
709
748
|
},
|
|
710
749
|
() => {
|
|
711
750
|
handlerResult = urlPatterns.handler() as AllUseItems[];
|
|
@@ -725,7 +764,7 @@ export function createRouter<TEnv = any>(
|
|
|
725
764
|
if (entry.type === "route" && entry.isPrerender) {
|
|
726
765
|
if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
|
|
727
766
|
prerenderRouteKeys.add(name);
|
|
728
|
-
if (entry.
|
|
767
|
+
if (entry.isPassthrough === true) {
|
|
729
768
|
if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
|
|
730
769
|
passthroughRouteKeys.add(name);
|
|
731
770
|
}
|
|
@@ -751,6 +790,7 @@ export function createRouter<TEnv = any>(
|
|
|
751
790
|
trailingSlash: trailingSlashConfig,
|
|
752
791
|
handler: urlPatterns.handler,
|
|
753
792
|
mountIndex: currentMountIndex,
|
|
793
|
+
routerId,
|
|
754
794
|
cacheProfiles: resolvedCacheProfiles,
|
|
755
795
|
...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
|
|
756
796
|
...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
|
|
@@ -770,6 +810,7 @@ export function createRouter<TEnv = any>(
|
|
|
770
810
|
trailingSlash: trailingSlashConfig,
|
|
771
811
|
handler: urlPatterns.handler,
|
|
772
812
|
mountIndex: currentMountIndex,
|
|
813
|
+
routerId,
|
|
773
814
|
cacheProfiles: resolvedCacheProfiles,
|
|
774
815
|
...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
|
|
775
816
|
...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
|
|
@@ -813,6 +854,7 @@ export function createRouter<TEnv = any>(
|
|
|
813
854
|
trailingSlash: trailingSlashConfig,
|
|
814
855
|
handler: urlPatterns.handler,
|
|
815
856
|
mountIndex: mountIndex++,
|
|
857
|
+
routerId,
|
|
816
858
|
// Lazy evaluation fields
|
|
817
859
|
lazy: true,
|
|
818
860
|
lazyPatterns: lazyInclude.patterns,
|
|
@@ -851,8 +893,18 @@ export function createRouter<TEnv = any>(
|
|
|
851
893
|
patternOrMiddleware: string | MiddlewareFn<TEnv>,
|
|
852
894
|
middleware?: MiddlewareFn<TEnv>,
|
|
853
895
|
): any {
|
|
854
|
-
//
|
|
855
|
-
|
|
896
|
+
// Auto-prefix pattern with basename so router-level middleware
|
|
897
|
+
// patterns are router-relative (e.g. "/users/*" matches "/app/users/*").
|
|
898
|
+
if (basename && typeof patternOrMiddleware === "string") {
|
|
899
|
+
const pattern = patternOrMiddleware;
|
|
900
|
+
const prefixed =
|
|
901
|
+
pattern === "/*" || pattern === "*"
|
|
902
|
+
? `${basename}/*`
|
|
903
|
+
: `${basename}${pattern}`;
|
|
904
|
+
addMiddleware(prefixed, middleware, null);
|
|
905
|
+
} else {
|
|
906
|
+
addMiddleware(patternOrMiddleware, middleware, null);
|
|
907
|
+
}
|
|
856
908
|
return router;
|
|
857
909
|
},
|
|
858
910
|
|
|
@@ -953,6 +1005,16 @@ export function createRouter<TEnv = any>(
|
|
|
953
1005
|
// Expose source file for per-router type generation
|
|
954
1006
|
__sourceFile,
|
|
955
1007
|
|
|
1008
|
+
// Expose basename for runtime manifest generation
|
|
1009
|
+
__basename: basename,
|
|
1010
|
+
|
|
1011
|
+
// Expose router-level boundary defaults for build-time clientChunks
|
|
1012
|
+
// discovery (so a "use client" default boundary lands in app-fallback).
|
|
1013
|
+
// These are createRouter options, never pushed onto EntryData.
|
|
1014
|
+
__defaultErrorBoundary: defaultErrorBoundary,
|
|
1015
|
+
__defaultNotFoundBoundary: defaultNotFoundBoundary,
|
|
1016
|
+
__notFound: notFound,
|
|
1017
|
+
|
|
956
1018
|
// RSC request handler (lazily created on first call)
|
|
957
1019
|
fetch: (() => {
|
|
958
1020
|
// Handler is created on first call and reused
|
|
@@ -986,6 +1048,10 @@ export function createRouter<TEnv = any>(
|
|
|
986
1048
|
};
|
|
987
1049
|
})(),
|
|
988
1050
|
|
|
1051
|
+
// Low-level route matching for request classification
|
|
1052
|
+
findMatch: (pathname: string, metricsStore?: any) =>
|
|
1053
|
+
findMatch(pathname, metricsStore),
|
|
1054
|
+
|
|
989
1055
|
// Debug utility for manifest inspection
|
|
990
1056
|
debugManifest: () => buildDebugManifest<TEnv>(routesEntries),
|
|
991
1057
|
};
|
|
@@ -994,8 +1060,10 @@ export function createRouter<TEnv = any>(
|
|
|
994
1060
|
RouterRegistry.set(routerId, router);
|
|
995
1061
|
|
|
996
1062
|
// If urls option was provided, auto-register them
|
|
997
|
-
if (urlsOption) {
|
|
998
|
-
return router.routes(urlsOption) as
|
|
1063
|
+
if (typeof urlsOption === "function") {
|
|
1064
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1065
|
+
} else if (urlsOption) {
|
|
1066
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
999
1067
|
}
|
|
1000
1068
|
|
|
1001
1069
|
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"];
|