@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4474 -867
- package/package.json +60 -51
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +50 -21
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +89 -30
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +78 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +318 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +285 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +258 -308
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +185 -73
- package/src/browser/react/NavigationProvider.tsx +51 -11
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +32 -79
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +107 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +109 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +106 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +15 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +153 -19
- package/src/index.ts +211 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +211 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +59 -8
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +148 -35
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +692 -4257
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +25 -13
- package/src/server/context.ts +182 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +430 -70
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +100 -31
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -1133
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
import type { RouteEntry, TrailingSlashMode } from "../types";
|
|
8
8
|
import type { EntryData } from "../server/context";
|
|
9
|
+
import { debugLog, isRouterDebugEnabled } from "./logging.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Parsed segment info
|
|
12
13
|
*/
|
|
13
|
-
interface ParsedSegment {
|
|
14
|
+
export interface ParsedSegment {
|
|
14
15
|
type: "static" | "param" | "wildcard";
|
|
15
16
|
value: string; // static text, param name, or "*"
|
|
16
17
|
optional: boolean;
|
|
17
18
|
constraint?: string[]; // enum values like ["en", "gb"]
|
|
19
|
+
suffix?: string; // literal text after param in same segment (e.g., ".html")
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -28,7 +30,7 @@ interface ParsedSegment {
|
|
|
28
30
|
* - Optional + Constrained: /:locale(en|gb)?
|
|
29
31
|
* - Wildcard: /*
|
|
30
32
|
*/
|
|
31
|
-
function parsePattern(pattern: string): ParsedSegment[] {
|
|
33
|
+
export function parsePattern(pattern: string): ParsedSegment[] {
|
|
32
34
|
const segments: ParsedSegment[] = [];
|
|
33
35
|
// Match: /segment where segment can be:
|
|
34
36
|
// - static text
|
|
@@ -37,11 +39,22 @@ function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
37
39
|
// - :param(a|b)
|
|
38
40
|
// - :param(a|b)?
|
|
39
41
|
// - *
|
|
40
|
-
const segmentRegex =
|
|
42
|
+
const segmentRegex =
|
|
43
|
+
/\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([^/]*)|(\*)|([^/]+))/g;
|
|
41
44
|
|
|
42
45
|
let match;
|
|
43
46
|
while ((match = segmentRegex.exec(pattern)) !== null) {
|
|
44
|
-
const [
|
|
47
|
+
const [
|
|
48
|
+
,
|
|
49
|
+
,
|
|
50
|
+
paramName,
|
|
51
|
+
,
|
|
52
|
+
constraint,
|
|
53
|
+
optional,
|
|
54
|
+
suffix,
|
|
55
|
+
wildcard,
|
|
56
|
+
staticText,
|
|
57
|
+
] = match;
|
|
45
58
|
|
|
46
59
|
if (wildcard) {
|
|
47
60
|
segments.push({ type: "wildcard", value: "*", optional: false });
|
|
@@ -51,6 +64,7 @@ function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
51
64
|
value: paramName,
|
|
52
65
|
optional: optional === "?",
|
|
53
66
|
constraint: constraint ? constraint.split("|") : undefined,
|
|
67
|
+
suffix: suffix || undefined,
|
|
54
68
|
});
|
|
55
69
|
} else if (staticText) {
|
|
56
70
|
segments.push({ type: "static", value: staticText, optional: false });
|
|
@@ -60,6 +74,48 @@ function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
60
74
|
return segments;
|
|
61
75
|
}
|
|
62
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Compiled pattern result containing regex, param metadata, and trailing slash info.
|
|
79
|
+
*/
|
|
80
|
+
export interface CompiledPattern {
|
|
81
|
+
regex: RegExp;
|
|
82
|
+
paramNames: string[];
|
|
83
|
+
optionalParams: Set<string>;
|
|
84
|
+
hasTrailingSlash: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Module-level cache for compiled patterns. Route patterns are a finite set
|
|
88
|
+
// defined at build time, so this map is bounded by the number of routes.
|
|
89
|
+
const compiledPatternCache = new Map<string, CompiledPattern>();
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get a compiled pattern from cache or compile and cache it.
|
|
93
|
+
* Avoids O(routes) regex compilations per request in the fallback path.
|
|
94
|
+
*/
|
|
95
|
+
export function getCompiledPattern(pattern: string): CompiledPattern {
|
|
96
|
+
let compiled = compiledPatternCache.get(pattern);
|
|
97
|
+
if (compiled) return compiled;
|
|
98
|
+
compiled = compilePattern(pattern);
|
|
99
|
+
compiledPatternCache.set(pattern, compiled);
|
|
100
|
+
return compiled;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Return the current size of the compiled pattern cache.
|
|
105
|
+
* Exposed for testing.
|
|
106
|
+
*/
|
|
107
|
+
export function getPatternCacheSize(): number {
|
|
108
|
+
return compiledPatternCache.size;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Clear the compiled pattern cache.
|
|
113
|
+
* Exposed for testing.
|
|
114
|
+
*/
|
|
115
|
+
export function clearPatternCache(): void {
|
|
116
|
+
compiledPatternCache.clear();
|
|
117
|
+
}
|
|
118
|
+
|
|
63
119
|
/**
|
|
64
120
|
* Compile a route pattern to regex
|
|
65
121
|
*
|
|
@@ -77,12 +133,7 @@ function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
77
133
|
* compilePattern("/:locale(en|gb)/blog") // matches /en/blog or /gb/blog
|
|
78
134
|
* compilePattern("/:locale(en|gb)?/blog") // matches /blog, /en/blog, or /gb/blog
|
|
79
135
|
*/
|
|
80
|
-
export function compilePattern(pattern: string): {
|
|
81
|
-
regex: RegExp;
|
|
82
|
-
paramNames: string[];
|
|
83
|
-
optionalParams: Set<string>;
|
|
84
|
-
hasTrailingSlash: boolean;
|
|
85
|
-
} {
|
|
136
|
+
export function compilePattern(pattern: string): CompiledPattern {
|
|
86
137
|
// Detect if pattern has trailing slash (but not just "/")
|
|
87
138
|
const hasTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
88
139
|
// Remove trailing slash for parsing (we'll add it back to regex if needed)
|
|
@@ -100,16 +151,19 @@ export function compilePattern(pattern: string): {
|
|
|
100
151
|
regexPattern += "/(.*)";
|
|
101
152
|
} else if (segment.type === "param") {
|
|
102
153
|
paramNames.push(segment.value);
|
|
154
|
+
const suffixPattern = segment.suffix ? escapeRegex(segment.suffix) : "";
|
|
103
155
|
const valuePattern = segment.constraint
|
|
104
|
-
? `(${segment.constraint.join("|")})`
|
|
105
|
-
:
|
|
156
|
+
? `(${segment.constraint.map(escapeRegex).join("|")})`
|
|
157
|
+
: segment.suffix
|
|
158
|
+
? "([^/]+?)"
|
|
159
|
+
: "([^/]+)";
|
|
106
160
|
|
|
107
161
|
if (segment.optional) {
|
|
108
162
|
optionalParams.add(segment.value);
|
|
109
163
|
// Optional: make the whole /segment optional
|
|
110
|
-
regexPattern += `(?:/${valuePattern})?`;
|
|
164
|
+
regexPattern += `(?:/${valuePattern}${suffixPattern})?`;
|
|
111
165
|
} else {
|
|
112
|
-
regexPattern += `/${valuePattern}`;
|
|
166
|
+
regexPattern += `/${valuePattern}${suffixPattern}`;
|
|
113
167
|
}
|
|
114
168
|
} else {
|
|
115
169
|
// Static segment
|
|
@@ -214,6 +268,18 @@ export interface RouteMatchResult<TEnv = any> {
|
|
|
214
268
|
params: Record<string, string>;
|
|
215
269
|
optionalParams: Set<string>;
|
|
216
270
|
redirectTo?: string;
|
|
271
|
+
/** Ancestry shortCodes for layout pruning (from trie match) */
|
|
272
|
+
ancestry?: string[];
|
|
273
|
+
/** Route has pre-rendered data available (from trie) */
|
|
274
|
+
pr?: true;
|
|
275
|
+
/** Passthrough: handler kept for live fallback on unknown params (from trie) */
|
|
276
|
+
pt?: true;
|
|
277
|
+
/** Response type for non-RSC routes (json, text, image, any) */
|
|
278
|
+
responseType?: string;
|
|
279
|
+
/** Negotiate variants: response-type routes sharing this path */
|
|
280
|
+
negotiateVariants?: Array<{ routeKey: string; responseType: string }>;
|
|
281
|
+
/** RSC-first: RSC route was defined before response-type variants */
|
|
282
|
+
rscFirst?: true;
|
|
217
283
|
}
|
|
218
284
|
|
|
219
285
|
/**
|
|
@@ -227,7 +293,7 @@ export interface LazyEvaluationNeeded<TEnv = any> {
|
|
|
227
293
|
* Type guard to check if result is a lazy evaluation needed response
|
|
228
294
|
*/
|
|
229
295
|
export function isLazyEvaluationNeeded<TEnv>(
|
|
230
|
-
result: RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null
|
|
296
|
+
result: RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null,
|
|
231
297
|
): result is LazyEvaluationNeeded<TEnv> {
|
|
232
298
|
return result !== null && "lazyEntry" in result;
|
|
233
299
|
}
|
|
@@ -248,22 +314,33 @@ export function enableMatchDebug(enabled: boolean): void {
|
|
|
248
314
|
}
|
|
249
315
|
|
|
250
316
|
export function getMatchDebugStats(): MatchDebugStats {
|
|
251
|
-
return {
|
|
317
|
+
return {
|
|
318
|
+
entriesChecked: debugStats.entriesChecked,
|
|
319
|
+
entriesSkipped: debugStats.entriesSkipped,
|
|
320
|
+
routesChecked: debugStats.routesChecked,
|
|
321
|
+
};
|
|
252
322
|
}
|
|
253
323
|
|
|
254
324
|
export function findMatch<TEnv>(
|
|
255
325
|
pathname: string,
|
|
256
|
-
routesEntries: RouteEntry<TEnv>[]
|
|
326
|
+
routesEntries: RouteEntry<TEnv>[],
|
|
257
327
|
): RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null {
|
|
258
|
-
|
|
328
|
+
const effectiveDebug = debugEnabled || isRouterDebugEnabled();
|
|
329
|
+
|
|
330
|
+
if (effectiveDebug) {
|
|
259
331
|
debugStats = { entriesChecked: 0, entriesSkipped: 0, routesChecked: 0 };
|
|
260
|
-
|
|
332
|
+
debugLog("findMatch", "start", { pathname, entries: routesEntries.length });
|
|
261
333
|
for (const e of routesEntries) {
|
|
262
|
-
|
|
334
|
+
debugLog("findMatch", "entry", {
|
|
335
|
+
prefix: e.prefix,
|
|
336
|
+
staticPrefix: e.staticPrefix,
|
|
337
|
+
routeCount: Object.keys(e.routes).length,
|
|
338
|
+
});
|
|
263
339
|
}
|
|
264
340
|
}
|
|
265
341
|
|
|
266
|
-
const pathnameHasTrailingSlash =
|
|
342
|
+
const pathnameHasTrailingSlash =
|
|
343
|
+
pathname.length > 1 && pathname.endsWith("/");
|
|
267
344
|
// Try alternate pathname for redirect matching
|
|
268
345
|
const alternatePathname = pathnameHasTrailingSlash
|
|
269
346
|
? pathname.slice(0, -1)
|
|
@@ -273,9 +350,12 @@ export function findMatch<TEnv>(
|
|
|
273
350
|
// Short-circuit: skip entry if pathname doesn't start with static prefix
|
|
274
351
|
// staticPrefix is pre-computed at registration time, so this is O(1)
|
|
275
352
|
if (entry.staticPrefix && !pathname.startsWith(entry.staticPrefix)) {
|
|
276
|
-
if (
|
|
353
|
+
if (effectiveDebug) {
|
|
277
354
|
debugStats.entriesSkipped++;
|
|
278
|
-
|
|
355
|
+
debugLog("findMatch", "skipped entry", {
|
|
356
|
+
prefix: entry.prefix,
|
|
357
|
+
staticPrefix: entry.staticPrefix,
|
|
358
|
+
});
|
|
279
359
|
}
|
|
280
360
|
continue;
|
|
281
361
|
}
|
|
@@ -283,20 +363,22 @@ export function findMatch<TEnv>(
|
|
|
283
363
|
// Check if this is a lazy entry that needs evaluation
|
|
284
364
|
// When staticPrefix matches but routes are not yet populated, signal caller to evaluate
|
|
285
365
|
if (entry.lazy && !entry.lazyEvaluated) {
|
|
286
|
-
if (
|
|
287
|
-
|
|
366
|
+
if (effectiveDebug) {
|
|
367
|
+
debugLog("findMatch", "lazy entry requires evaluation", {
|
|
368
|
+
staticPrefix: entry.staticPrefix,
|
|
369
|
+
});
|
|
288
370
|
}
|
|
289
371
|
return { lazyEntry: entry };
|
|
290
372
|
}
|
|
291
373
|
|
|
292
|
-
if (
|
|
374
|
+
if (effectiveDebug) {
|
|
293
375
|
debugStats.entriesChecked++;
|
|
294
376
|
}
|
|
295
377
|
|
|
296
378
|
const routeEntries = Object.entries(entry.routes);
|
|
297
379
|
|
|
298
380
|
for (const [routeKey, pattern] of routeEntries) {
|
|
299
|
-
if (
|
|
381
|
+
if (effectiveDebug) {
|
|
300
382
|
debugStats.routesChecked++;
|
|
301
383
|
}
|
|
302
384
|
|
|
@@ -310,11 +392,20 @@ export function findMatch<TEnv>(
|
|
|
310
392
|
fullPattern = entry.prefix + pattern;
|
|
311
393
|
}
|
|
312
394
|
|
|
313
|
-
const { regex, paramNames, optionalParams, hasTrailingSlash } =
|
|
395
|
+
const { regex, paramNames, optionalParams, hasTrailingSlash } =
|
|
396
|
+
getCompiledPattern(fullPattern);
|
|
314
397
|
|
|
315
398
|
// Get trailing slash mode for this route (per-route config or pattern-based)
|
|
316
|
-
const trailingSlashMode: TrailingSlashMode | undefined =
|
|
399
|
+
const trailingSlashMode: TrailingSlashMode | undefined =
|
|
400
|
+
entry.trailingSlash?.[routeKey];
|
|
317
401
|
|
|
402
|
+
// Prerender flag from entry metadata (set by urls() for prerender handlers)
|
|
403
|
+
const prFlag = entry.prerenderRouteKeys?.has(routeKey)
|
|
404
|
+
? { pr: true as const }
|
|
405
|
+
: {};
|
|
406
|
+
const ptFlag = entry.passthroughRouteKeys?.has(routeKey)
|
|
407
|
+
? { pt: true as const }
|
|
408
|
+
: {};
|
|
318
409
|
|
|
319
410
|
// Try exact match first
|
|
320
411
|
const match = regex.exec(pathname);
|
|
@@ -324,21 +415,51 @@ export function findMatch<TEnv>(
|
|
|
324
415
|
params[name] = match[index + 1] ?? "";
|
|
325
416
|
});
|
|
326
417
|
|
|
327
|
-
if (
|
|
328
|
-
|
|
329
|
-
|
|
418
|
+
if (effectiveDebug) {
|
|
419
|
+
debugLog("findMatch", "matched route", {
|
|
420
|
+
routeKey,
|
|
421
|
+
pattern: fullPattern,
|
|
422
|
+
stats: { ...debugStats },
|
|
423
|
+
});
|
|
330
424
|
}
|
|
331
425
|
|
|
332
426
|
// Check if trailing slash mode requires redirect even on exact match
|
|
333
|
-
if (
|
|
427
|
+
if (
|
|
428
|
+
trailingSlashMode === "always" &&
|
|
429
|
+
!pathnameHasTrailingSlash &&
|
|
430
|
+
pathname !== "/"
|
|
431
|
+
) {
|
|
334
432
|
// Mode says always have trailing slash, but pathname doesn't have it
|
|
335
|
-
return {
|
|
433
|
+
return {
|
|
434
|
+
entry,
|
|
435
|
+
routeKey,
|
|
436
|
+
params,
|
|
437
|
+
optionalParams,
|
|
438
|
+
redirectTo: pathname + "/",
|
|
439
|
+
...prFlag,
|
|
440
|
+
...ptFlag,
|
|
441
|
+
};
|
|
336
442
|
} else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
|
|
337
443
|
// Mode says never have trailing slash, but pathname has it
|
|
338
|
-
return {
|
|
444
|
+
return {
|
|
445
|
+
entry,
|
|
446
|
+
routeKey,
|
|
447
|
+
params,
|
|
448
|
+
optionalParams,
|
|
449
|
+
redirectTo: pathname.slice(0, -1),
|
|
450
|
+
...prFlag,
|
|
451
|
+
...ptFlag,
|
|
452
|
+
};
|
|
339
453
|
}
|
|
340
454
|
|
|
341
|
-
return {
|
|
455
|
+
return {
|
|
456
|
+
entry,
|
|
457
|
+
routeKey,
|
|
458
|
+
params,
|
|
459
|
+
optionalParams,
|
|
460
|
+
...prFlag,
|
|
461
|
+
...ptFlag,
|
|
462
|
+
};
|
|
342
463
|
}
|
|
343
464
|
|
|
344
465
|
// Try alternate pathname (opposite trailing slash)
|
|
@@ -352,24 +473,71 @@ export function findMatch<TEnv>(
|
|
|
352
473
|
// Determine redirect behavior based on mode
|
|
353
474
|
if (trailingSlashMode === "ignore") {
|
|
354
475
|
// Match without redirect
|
|
355
|
-
return {
|
|
476
|
+
return {
|
|
477
|
+
entry,
|
|
478
|
+
routeKey,
|
|
479
|
+
params,
|
|
480
|
+
optionalParams,
|
|
481
|
+
...prFlag,
|
|
482
|
+
...ptFlag,
|
|
483
|
+
};
|
|
356
484
|
} else if (trailingSlashMode === "never") {
|
|
357
485
|
// Redirect to no trailing slash
|
|
358
486
|
if (pathnameHasTrailingSlash) {
|
|
359
|
-
return {
|
|
487
|
+
return {
|
|
488
|
+
entry,
|
|
489
|
+
routeKey,
|
|
490
|
+
params,
|
|
491
|
+
optionalParams,
|
|
492
|
+
redirectTo: alternatePathname,
|
|
493
|
+
...prFlag,
|
|
494
|
+
...ptFlag,
|
|
495
|
+
};
|
|
360
496
|
}
|
|
361
|
-
return {
|
|
497
|
+
return {
|
|
498
|
+
entry,
|
|
499
|
+
routeKey,
|
|
500
|
+
params,
|
|
501
|
+
optionalParams,
|
|
502
|
+
...prFlag,
|
|
503
|
+
...ptFlag,
|
|
504
|
+
};
|
|
362
505
|
} else if (trailingSlashMode === "always") {
|
|
363
506
|
// Redirect to with trailing slash
|
|
364
507
|
if (!pathnameHasTrailingSlash) {
|
|
365
|
-
return {
|
|
508
|
+
return {
|
|
509
|
+
entry,
|
|
510
|
+
routeKey,
|
|
511
|
+
params,
|
|
512
|
+
optionalParams,
|
|
513
|
+
redirectTo: alternatePathname,
|
|
514
|
+
...prFlag,
|
|
515
|
+
...ptFlag,
|
|
516
|
+
};
|
|
366
517
|
}
|
|
367
|
-
return {
|
|
518
|
+
return {
|
|
519
|
+
entry,
|
|
520
|
+
routeKey,
|
|
521
|
+
params,
|
|
522
|
+
optionalParams,
|
|
523
|
+
...prFlag,
|
|
524
|
+
...ptFlag,
|
|
525
|
+
};
|
|
368
526
|
} else {
|
|
369
527
|
// No explicit mode - use pattern-based detection
|
|
370
528
|
// Redirect to canonical form (what the pattern defines)
|
|
371
|
-
const canonicalPath = hasTrailingSlash
|
|
372
|
-
|
|
529
|
+
const canonicalPath = hasTrailingSlash
|
|
530
|
+
? alternatePathname
|
|
531
|
+
: pathname.slice(0, -1);
|
|
532
|
+
return {
|
|
533
|
+
entry,
|
|
534
|
+
routeKey,
|
|
535
|
+
params,
|
|
536
|
+
optionalParams,
|
|
537
|
+
redirectTo: canonicalPath,
|
|
538
|
+
...prFlag,
|
|
539
|
+
...ptFlag,
|
|
540
|
+
};
|
|
373
541
|
}
|
|
374
542
|
}
|
|
375
543
|
}
|