@rangojs/router 0.0.0-experimental.9 → 0.0.0-experimental.a5f27bd5

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.
Files changed (299) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1531 -155
  4. package/dist/vite/index.js +4440 -2170
  5. package/package.json +60 -54
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +6 -4
  13. package/skills/hooks/SKILL.md +333 -71
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +74 -15
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +15 -11
  21. package/skills/parallel/SKILL.md +78 -1
  22. package/skills/prerender/SKILL.md +405 -45
  23. package/skills/rango/SKILL.md +85 -21
  24. package/skills/response-routes/SKILL.md +144 -91
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/theme/SKILL.md +9 -8
  28. package/skills/typesafety/SKILL.md +316 -87
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +102 -4
  31. package/src/bin/rango.ts +312 -15
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +87 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +24 -4
  38. package/src/browser/logging.ts +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +285 -553
  41. package/src/browser/navigation-client.ts +123 -73
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +261 -309
  46. package/src/browser/prefetch/cache.ts +154 -0
  47. package/src/browser/prefetch/fetch.ts +135 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +48 -0
  50. package/src/browser/prefetch/queue.ts +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +182 -70
  53. package/src/browser/react/NavigationProvider.tsx +51 -11
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +6 -1
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +29 -70
  65. package/src/browser/react/use-link-status.ts +6 -5
  66. package/src/browser/react/use-navigation.ts +22 -63
  67. package/src/browser/react/use-params.ts +65 -0
  68. package/src/browser/react/use-pathname.ts +47 -0
  69. package/src/browser/react/use-router.ts +63 -0
  70. package/src/browser/react/use-search-params.ts +56 -0
  71. package/src/browser/react/use-segments.ts +80 -97
  72. package/src/browser/response-adapter.ts +73 -0
  73. package/src/browser/rsc-router.tsx +106 -27
  74. package/src/browser/scroll-restoration.ts +92 -16
  75. package/src/browser/segment-reconciler.ts +216 -0
  76. package/src/browser/segment-structure-assert.ts +16 -0
  77. package/src/browser/server-action-bridge.ts +504 -599
  78. package/src/browser/shallow.ts +6 -1
  79. package/src/browser/types.ts +107 -47
  80. package/src/browser/validate-redirect-origin.ts +29 -0
  81. package/src/build/generate-manifest.ts +82 -21
  82. package/src/build/generate-route-types.ts +36 -752
  83. package/src/build/index.ts +6 -5
  84. package/src/build/route-trie.ts +39 -13
  85. package/src/build/route-types/ast-helpers.ts +25 -0
  86. package/src/build/route-types/ast-route-extraction.ts +98 -0
  87. package/src/build/route-types/codegen.ts +102 -0
  88. package/src/build/route-types/include-resolution.ts +411 -0
  89. package/src/build/route-types/param-extraction.ts +48 -0
  90. package/src/build/route-types/per-module-writer.ts +128 -0
  91. package/src/build/route-types/router-processing.ts +469 -0
  92. package/src/build/route-types/scan-filter.ts +78 -0
  93. package/src/build/runtime-discovery.ts +231 -0
  94. package/src/cache/background-task.ts +34 -0
  95. package/src/cache/cache-key-utils.ts +44 -0
  96. package/src/cache/cache-policy.ts +125 -0
  97. package/src/cache/cache-runtime.ts +338 -0
  98. package/src/cache/cache-scope.ts +120 -301
  99. package/src/cache/cf/cf-cache-store.ts +119 -7
  100. package/src/cache/cf/index.ts +8 -2
  101. package/src/cache/document-cache.ts +101 -72
  102. package/src/cache/handle-capture.ts +81 -0
  103. package/src/cache/handle-snapshot.ts +41 -0
  104. package/src/cache/index.ts +0 -15
  105. package/src/cache/memory-segment-store.ts +191 -13
  106. package/src/cache/profile-registry.ts +73 -0
  107. package/src/cache/read-through-swr.ts +134 -0
  108. package/src/cache/segment-codec.ts +256 -0
  109. package/src/cache/taint.ts +98 -0
  110. package/src/cache/types.ts +72 -122
  111. package/src/client.rsc.tsx +3 -1
  112. package/src/client.tsx +84 -126
  113. package/src/component-utils.ts +4 -4
  114. package/src/components/DefaultDocument.tsx +5 -1
  115. package/src/context-var.ts +86 -0
  116. package/src/debug.ts +17 -7
  117. package/src/errors.ts +77 -7
  118. package/src/handle.ts +15 -10
  119. package/src/handles/MetaTags.tsx +73 -20
  120. package/src/handles/breadcrumbs.ts +66 -0
  121. package/src/handles/index.ts +1 -0
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +21 -15
  124. package/src/host/errors.ts +8 -8
  125. package/src/host/index.ts +4 -7
  126. package/src/host/pattern-matcher.ts +27 -27
  127. package/src/host/router.ts +61 -39
  128. package/src/host/testing.ts +8 -8
  129. package/src/host/types.ts +15 -7
  130. package/src/host/utils.ts +1 -1
  131. package/src/href-client.ts +65 -45
  132. package/src/index.rsc.ts +133 -21
  133. package/src/index.ts +164 -52
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +25 -143
  136. package/src/loader.ts +27 -10
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +4 -2
  140. package/src/prerender/store.ts +158 -13
  141. package/src/prerender.ts +333 -26
  142. package/src/reverse.ts +184 -121
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +7 -4
  145. package/src/route-definition/dsl-helpers.ts +934 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1431
  151. package/src/route-map-builder.ts +156 -123
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +48 -9
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +158 -0
  158. package/src/router/handler-context.ts +374 -81
  159. package/src/router/intercept-resolution.ts +24 -16
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +83 -32
  164. package/src/router/match-api.ts +118 -119
  165. package/src/router/match-context.ts +4 -2
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +336 -84
  169. package/src/router/match-middleware/cache-store.ts +43 -24
  170. package/src/router/match-middleware/intercept-resolution.ts +45 -20
  171. package/src/router/match-middleware/segment-resolution.ts +16 -8
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -28
  174. package/src/router/metrics.ts +235 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +324 -367
  178. package/src/router/pattern-matching.ts +197 -41
  179. package/src/router/prerender-match.ts +402 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1239 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -1315
  192. package/src/router/segment-wrappers.ts +289 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +96 -29
  197. package/src/router/types.ts +16 -9
  198. package/src/router.ts +590 -1983
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +661 -1015
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +0 -20
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +237 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +38 -11
  215. package/src/search-params.ts +230 -0
  216. package/src/segment-system.tsx +25 -13
  217. package/src/server/context.ts +173 -48
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +430 -70
  223. package/src/server.ts +35 -155
  224. package/src/ssr/index.tsx +100 -31
  225. package/src/static-handler.ts +114 -0
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +687 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1757
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -1282
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -1963
  261. package/src/vite/plugin-types.ts +131 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +254 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -43
  289. package/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/router.gen.ts +0 -6
  294. package/src/urls.gen.ts +0 -8
  295. package/src/vite/expose-handle-id.ts +0 -209
  296. package/src/vite/expose-loader-id.ts +0 -426
  297. package/src/vite/expose-location-state-id.ts +0 -177
  298. package/src/vite/expose-prerender-handler-id.ts +0 -429
  299. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -6,6 +6,7 @@
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
@@ -15,6 +16,7 @@ export interface ParsedSegment {
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
  /**
@@ -37,11 +39,22 @@ export function parsePattern(pattern: string): ParsedSegment[] {
37
39
  // - :param(a|b)
38
40
  // - :param(a|b)?
39
41
  // - *
40
- const segmentRegex = /\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?|(\*)|([^/]+))/g;
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 [, , paramName, , constraint, optional, wildcard, staticText] = match;
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 @@ export 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 @@ export 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 @@ export 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
@@ -239,7 +293,7 @@ export interface LazyEvaluationNeeded<TEnv = any> {
239
293
  * Type guard to check if result is a lazy evaluation needed response
240
294
  */
241
295
  export function isLazyEvaluationNeeded<TEnv>(
242
- result: RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null
296
+ result: RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null,
243
297
  ): result is LazyEvaluationNeeded<TEnv> {
244
298
  return result !== null && "lazyEntry" in result;
245
299
  }
@@ -260,22 +314,33 @@ export function enableMatchDebug(enabled: boolean): void {
260
314
  }
261
315
 
262
316
  export function getMatchDebugStats(): MatchDebugStats {
263
- return { entriesChecked: debugStats.entriesChecked, entriesSkipped: debugStats.entriesSkipped, routesChecked: debugStats.routesChecked };
317
+ return {
318
+ entriesChecked: debugStats.entriesChecked,
319
+ entriesSkipped: debugStats.entriesSkipped,
320
+ routesChecked: debugStats.routesChecked,
321
+ };
264
322
  }
265
323
 
266
324
  export function findMatch<TEnv>(
267
325
  pathname: string,
268
- routesEntries: RouteEntry<TEnv>[]
326
+ routesEntries: RouteEntry<TEnv>[],
269
327
  ): RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null {
270
- if (debugEnabled) {
328
+ const effectiveDebug = debugEnabled || isRouterDebugEnabled();
329
+
330
+ if (effectiveDebug) {
271
331
  debugStats = { entriesChecked: 0, entriesSkipped: 0, routesChecked: 0 };
272
- console.log(`[findMatch] pathname="${pathname}", entries=${routesEntries.length}`);
332
+ debugLog("findMatch", "start", { pathname, entries: routesEntries.length });
273
333
  for (const e of routesEntries) {
274
- console.log(` entry: prefix="${e.prefix}", staticPrefix="${e.staticPrefix}", routes=${Object.keys(e.routes).length}`);
334
+ debugLog("findMatch", "entry", {
335
+ prefix: e.prefix,
336
+ staticPrefix: e.staticPrefix,
337
+ routeCount: Object.keys(e.routes).length,
338
+ });
275
339
  }
276
340
  }
277
341
 
278
- const pathnameHasTrailingSlash = pathname.length > 1 && pathname.endsWith("/");
342
+ const pathnameHasTrailingSlash =
343
+ pathname.length > 1 && pathname.endsWith("/");
279
344
  // Try alternate pathname for redirect matching
280
345
  const alternatePathname = pathnameHasTrailingSlash
281
346
  ? pathname.slice(0, -1)
@@ -285,9 +350,12 @@ export function findMatch<TEnv>(
285
350
  // Short-circuit: skip entry if pathname doesn't start with static prefix
286
351
  // staticPrefix is pre-computed at registration time, so this is O(1)
287
352
  if (entry.staticPrefix && !pathname.startsWith(entry.staticPrefix)) {
288
- if (debugEnabled) {
353
+ if (effectiveDebug) {
289
354
  debugStats.entriesSkipped++;
290
- console.log(` SKIP entry prefix="${entry.prefix}" (staticPrefix="${entry.staticPrefix}" doesn't match)`);
355
+ debugLog("findMatch", "skipped entry", {
356
+ prefix: entry.prefix,
357
+ staticPrefix: entry.staticPrefix,
358
+ });
291
359
  }
292
360
  continue;
293
361
  }
@@ -295,20 +363,22 @@ export function findMatch<TEnv>(
295
363
  // Check if this is a lazy entry that needs evaluation
296
364
  // When staticPrefix matches but routes are not yet populated, signal caller to evaluate
297
365
  if (entry.lazy && !entry.lazyEvaluated) {
298
- if (debugEnabled) {
299
- console.log(` LAZY entry needs evaluation: staticPrefix="${entry.staticPrefix}"`);
366
+ if (effectiveDebug) {
367
+ debugLog("findMatch", "lazy entry requires evaluation", {
368
+ staticPrefix: entry.staticPrefix,
369
+ });
300
370
  }
301
371
  return { lazyEntry: entry };
302
372
  }
303
373
 
304
- if (debugEnabled) {
374
+ if (effectiveDebug) {
305
375
  debugStats.entriesChecked++;
306
376
  }
307
377
 
308
378
  const routeEntries = Object.entries(entry.routes);
309
379
 
310
380
  for (const [routeKey, pattern] of routeEntries) {
311
- if (debugEnabled) {
381
+ if (effectiveDebug) {
312
382
  debugStats.routesChecked++;
313
383
  }
314
384
 
@@ -322,11 +392,20 @@ export function findMatch<TEnv>(
322
392
  fullPattern = entry.prefix + pattern;
323
393
  }
324
394
 
325
- const { regex, paramNames, optionalParams, hasTrailingSlash } = compilePattern(fullPattern);
395
+ const { regex, paramNames, optionalParams, hasTrailingSlash } =
396
+ getCompiledPattern(fullPattern);
326
397
 
327
398
  // Get trailing slash mode for this route (per-route config or pattern-based)
328
- const trailingSlashMode: TrailingSlashMode | undefined = entry.trailingSlash?.[routeKey];
399
+ const trailingSlashMode: TrailingSlashMode | undefined =
400
+ entry.trailingSlash?.[routeKey];
329
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
+ : {};
330
409
 
331
410
  // Try exact match first
332
411
  const match = regex.exec(pathname);
@@ -336,21 +415,51 @@ export function findMatch<TEnv>(
336
415
  params[name] = match[index + 1] ?? "";
337
416
  });
338
417
 
339
- if (debugEnabled) {
340
- console.log(` MATCH: routeKey="${routeKey}", pattern="${fullPattern}"`);
341
- console.log(` Stats: entriesChecked=${debugStats.entriesChecked}, entriesSkipped=${debugStats.entriesSkipped}, routesChecked=${debugStats.routesChecked}`);
418
+ if (effectiveDebug) {
419
+ debugLog("findMatch", "matched route", {
420
+ routeKey,
421
+ pattern: fullPattern,
422
+ stats: { ...debugStats },
423
+ });
342
424
  }
343
425
 
344
426
  // Check if trailing slash mode requires redirect even on exact match
345
- if (trailingSlashMode === "always" && !pathnameHasTrailingSlash && pathname !== "/") {
427
+ if (
428
+ trailingSlashMode === "always" &&
429
+ !pathnameHasTrailingSlash &&
430
+ pathname !== "/"
431
+ ) {
346
432
  // Mode says always have trailing slash, but pathname doesn't have it
347
- return { entry, routeKey, params, optionalParams, redirectTo: pathname + "/" };
433
+ return {
434
+ entry,
435
+ routeKey,
436
+ params,
437
+ optionalParams,
438
+ redirectTo: pathname + "/",
439
+ ...prFlag,
440
+ ...ptFlag,
441
+ };
348
442
  } else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
349
443
  // Mode says never have trailing slash, but pathname has it
350
- return { entry, routeKey, params, optionalParams, redirectTo: pathname.slice(0, -1) };
444
+ return {
445
+ entry,
446
+ routeKey,
447
+ params,
448
+ optionalParams,
449
+ redirectTo: pathname.slice(0, -1),
450
+ ...prFlag,
451
+ ...ptFlag,
452
+ };
351
453
  }
352
454
 
353
- return { entry, routeKey, params, optionalParams };
455
+ return {
456
+ entry,
457
+ routeKey,
458
+ params,
459
+ optionalParams,
460
+ ...prFlag,
461
+ ...ptFlag,
462
+ };
354
463
  }
355
464
 
356
465
  // Try alternate pathname (opposite trailing slash)
@@ -364,24 +473,71 @@ export function findMatch<TEnv>(
364
473
  // Determine redirect behavior based on mode
365
474
  if (trailingSlashMode === "ignore") {
366
475
  // Match without redirect
367
- return { entry, routeKey, params, optionalParams };
476
+ return {
477
+ entry,
478
+ routeKey,
479
+ params,
480
+ optionalParams,
481
+ ...prFlag,
482
+ ...ptFlag,
483
+ };
368
484
  } else if (trailingSlashMode === "never") {
369
485
  // Redirect to no trailing slash
370
486
  if (pathnameHasTrailingSlash) {
371
- return { entry, routeKey, params, optionalParams, redirectTo: alternatePathname };
487
+ return {
488
+ entry,
489
+ routeKey,
490
+ params,
491
+ optionalParams,
492
+ redirectTo: alternatePathname,
493
+ ...prFlag,
494
+ ...ptFlag,
495
+ };
372
496
  }
373
- return { entry, routeKey, params, optionalParams };
497
+ return {
498
+ entry,
499
+ routeKey,
500
+ params,
501
+ optionalParams,
502
+ ...prFlag,
503
+ ...ptFlag,
504
+ };
374
505
  } else if (trailingSlashMode === "always") {
375
506
  // Redirect to with trailing slash
376
507
  if (!pathnameHasTrailingSlash) {
377
- return { entry, routeKey, params, optionalParams, redirectTo: alternatePathname };
508
+ return {
509
+ entry,
510
+ routeKey,
511
+ params,
512
+ optionalParams,
513
+ redirectTo: alternatePathname,
514
+ ...prFlag,
515
+ ...ptFlag,
516
+ };
378
517
  }
379
- return { entry, routeKey, params, optionalParams };
518
+ return {
519
+ entry,
520
+ routeKey,
521
+ params,
522
+ optionalParams,
523
+ ...prFlag,
524
+ ...ptFlag,
525
+ };
380
526
  } else {
381
527
  // No explicit mode - use pattern-based detection
382
528
  // Redirect to canonical form (what the pattern defines)
383
- const canonicalPath = hasTrailingSlash ? alternatePathname : pathname.slice(0, -1);
384
- return { entry, routeKey, params, optionalParams, redirectTo: canonicalPath };
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
+ };
385
541
  }
386
542
  }
387
543
  }