@rangojs/router 0.0.0-experimental.8678bb02 → 0.0.0-experimental.87

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 (147) hide show
  1. package/README.md +126 -38
  2. package/dist/bin/rango.js +130 -47
  3. package/dist/vite/index.js +847 -384
  4. package/dist/vite/index.js.bak +5448 -0
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +5 -5
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/handler-use/SKILL.md +362 -0
  9. package/skills/hooks/SKILL.md +28 -20
  10. package/skills/intercept/SKILL.md +20 -0
  11. package/skills/layout/SKILL.md +22 -0
  12. package/skills/links/SKILL.md +91 -17
  13. package/skills/loader/SKILL.md +35 -2
  14. package/skills/middleware/SKILL.md +34 -3
  15. package/skills/migrate-nextjs/SKILL.md +560 -0
  16. package/skills/migrate-react-router/SKILL.md +765 -0
  17. package/skills/parallel/SKILL.md +59 -0
  18. package/skills/prerender/SKILL.md +110 -68
  19. package/skills/rango/SKILL.md +24 -22
  20. package/skills/response-routes/SKILL.md +8 -0
  21. package/skills/route/SKILL.md +24 -0
  22. package/skills/router-setup/SKILL.md +35 -0
  23. package/skills/streams-and-websockets/SKILL.md +283 -0
  24. package/skills/typesafety/SKILL.md +3 -1
  25. package/src/__internal.ts +1 -1
  26. package/src/browser/app-shell.ts +52 -0
  27. package/src/browser/app-version.ts +14 -0
  28. package/src/browser/navigation-bridge.ts +87 -6
  29. package/src/browser/navigation-client.ts +128 -77
  30. package/src/browser/navigation-store.ts +68 -9
  31. package/src/browser/partial-update.ts +60 -7
  32. package/src/browser/prefetch/cache.ts +129 -21
  33. package/src/browser/prefetch/fetch.ts +156 -18
  34. package/src/browser/prefetch/queue.ts +36 -5
  35. package/src/browser/rango-state.ts +53 -13
  36. package/src/browser/react/Link.tsx +72 -8
  37. package/src/browser/react/NavigationProvider.tsx +57 -11
  38. package/src/browser/react/context.ts +7 -2
  39. package/src/browser/react/use-handle.ts +9 -58
  40. package/src/browser/react/use-navigation.ts +22 -2
  41. package/src/browser/react/use-params.ts +11 -1
  42. package/src/browser/react/use-router.ts +29 -9
  43. package/src/browser/rsc-router.tsx +60 -9
  44. package/src/browser/scroll-restoration.ts +10 -8
  45. package/src/browser/segment-reconciler.ts +36 -14
  46. package/src/browser/server-action-bridge.ts +8 -18
  47. package/src/browser/types.ts +33 -5
  48. package/src/build/generate-manifest.ts +6 -6
  49. package/src/build/generate-route-types.ts +3 -0
  50. package/src/build/route-trie.ts +50 -24
  51. package/src/build/route-types/include-resolution.ts +8 -1
  52. package/src/build/route-types/router-processing.ts +211 -72
  53. package/src/build/route-types/scan-filter.ts +8 -1
  54. package/src/cache/cf/cf-cache-store.ts +5 -7
  55. package/src/client.tsx +84 -230
  56. package/src/deps/browser.ts +0 -1
  57. package/src/handle.ts +40 -0
  58. package/src/index.rsc.ts +6 -1
  59. package/src/index.ts +49 -6
  60. package/src/outlet-context.ts +1 -1
  61. package/src/prerender/store.ts +5 -4
  62. package/src/prerender.ts +138 -77
  63. package/src/response-utils.ts +28 -0
  64. package/src/reverse.ts +27 -2
  65. package/src/route-definition/dsl-helpers.ts +210 -35
  66. package/src/route-definition/helpers-types.ts +61 -14
  67. package/src/route-definition/index.ts +3 -0
  68. package/src/route-definition/redirect.ts +9 -1
  69. package/src/route-definition/resolve-handler-use.ts +155 -0
  70. package/src/route-types.ts +18 -0
  71. package/src/router/content-negotiation.ts +100 -1
  72. package/src/router/handler-context.ts +70 -17
  73. package/src/router/intercept-resolution.ts +9 -4
  74. package/src/router/lazy-includes.ts +6 -6
  75. package/src/router/loader-resolution.ts +153 -21
  76. package/src/router/manifest.ts +22 -13
  77. package/src/router/match-api.ts +127 -192
  78. package/src/router/match-middleware/cache-lookup.ts +28 -8
  79. package/src/router/match-middleware/segment-resolution.ts +53 -0
  80. package/src/router/match-result.ts +82 -4
  81. package/src/router/middleware-types.ts +2 -28
  82. package/src/router/middleware.ts +32 -7
  83. package/src/router/navigation-snapshot.ts +182 -0
  84. package/src/router/pattern-matching.ts +60 -9
  85. package/src/router/prerender-match.ts +110 -10
  86. package/src/router/preview-match.ts +30 -102
  87. package/src/router/request-classification.ts +310 -0
  88. package/src/router/route-snapshot.ts +245 -0
  89. package/src/router/router-interfaces.ts +36 -4
  90. package/src/router/router-options.ts +37 -11
  91. package/src/router/segment-resolution/fresh.ts +70 -5
  92. package/src/router/segment-resolution/revalidation.ts +87 -9
  93. package/src/router/trie-matching.ts +10 -4
  94. package/src/router/url-params.ts +49 -0
  95. package/src/router.ts +54 -7
  96. package/src/rsc/handler.ts +478 -399
  97. package/src/rsc/helpers.ts +69 -41
  98. package/src/rsc/loader-fetch.ts +18 -3
  99. package/src/rsc/manifest-init.ts +5 -1
  100. package/src/rsc/progressive-enhancement.ts +14 -3
  101. package/src/rsc/response-route-handler.ts +14 -1
  102. package/src/rsc/rsc-rendering.ts +15 -2
  103. package/src/rsc/server-action.ts +10 -2
  104. package/src/rsc/ssr-setup.ts +2 -2
  105. package/src/rsc/types.ts +6 -4
  106. package/src/segment-content-promise.ts +67 -0
  107. package/src/segment-loader-promise.ts +122 -0
  108. package/src/segment-system.tsx +11 -61
  109. package/src/server/context.ts +65 -5
  110. package/src/server/handle-store.ts +19 -0
  111. package/src/server/loader-registry.ts +9 -8
  112. package/src/server/request-context.ts +142 -55
  113. package/src/ssr/index.tsx +3 -0
  114. package/src/static-handler.ts +18 -6
  115. package/src/types/cache-types.ts +4 -4
  116. package/src/types/handler-context.ts +17 -43
  117. package/src/types/loader-types.ts +37 -11
  118. package/src/types/request-scope.ts +126 -0
  119. package/src/types/route-entry.ts +12 -1
  120. package/src/types/segments.ts +1 -1
  121. package/src/urls/include-helper.ts +24 -14
  122. package/src/urls/path-helper-types.ts +39 -6
  123. package/src/urls/path-helper.ts +47 -12
  124. package/src/urls/pattern-types.ts +12 -0
  125. package/src/urls/response-types.ts +18 -16
  126. package/src/use-loader.tsx +77 -5
  127. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  128. package/src/vite/discovery/discover-routers.ts +5 -1
  129. package/src/vite/discovery/prerender-collection.ts +128 -74
  130. package/src/vite/discovery/state.ts +13 -4
  131. package/src/vite/index.ts +4 -0
  132. package/src/vite/plugin-types.ts +60 -5
  133. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  134. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  135. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  136. package/src/vite/plugins/expose-id-utils.ts +12 -0
  137. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  138. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  139. package/src/vite/plugins/performance-tracks.ts +64 -206
  140. package/src/vite/plugins/refresh-cmd.ts +88 -26
  141. package/src/vite/rango.ts +40 -18
  142. package/src/vite/router-discovery.ts +237 -37
  143. package/src/vite/utils/banner.ts +1 -1
  144. package/src/vite/utils/package-resolution.ts +1 -1
  145. package/src/vite/utils/prerender-utils.ts +37 -5
  146. package/src/vite/utils/shared-utils.ts +3 -2
  147. package/src/browser/debug-channel.ts +0 -93
@@ -41,8 +41,11 @@ import {
41
41
  } from "./helpers.js";
42
42
  import { getRouterContext } from "../router-context.js";
43
43
  import { resolveSink, safeEmit } from "../telemetry.js";
44
- import { track } from "../../server/context.js";
45
- import { RSCRouterContext } from "../../server/context.js";
44
+ import {
45
+ track,
46
+ RSCRouterContext,
47
+ runInsideLoaderScope,
48
+ } from "../../server/context.js";
46
49
 
47
50
  // ---------------------------------------------------------------------------
48
51
  // Telemetry helpers
@@ -233,7 +236,9 @@ export async function resolveLoadersWithRevalidation<TEnv>(
233
236
  params: ctx.params,
234
237
  loaderId: loader.$$id,
235
238
  loaderData: deps.wrapLoaderPromise(
236
- resolveLoaderData(loaderEntry, ctx, ctx.pathname),
239
+ runInsideLoaderScope(() =>
240
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
241
+ ),
237
242
  entry,
238
243
  segmentId,
239
244
  ctx.pathname,
@@ -314,6 +319,39 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
314
319
  const childBelongsToRoute = belongsToRoute || entry.type === "route";
315
320
  for (const layoutEntry of entry.layout) {
316
321
  await collectEntryLoaders(layoutEntry, childBelongsToRoute);
322
+ // Inherit route loaders for orphan layouts with parallels.
323
+ // Resolve directly — do NOT re-enter collectEntryLoaders with the
324
+ // route entry, as that would re-iterate route.layout and loop.
325
+ if (
326
+ entry.type === "route" &&
327
+ entry.loader &&
328
+ entry.loader.length > 0 &&
329
+ Object.keys(layoutEntry.parallel).length > 0
330
+ ) {
331
+ const inherited = await resolveLoadersWithRevalidation(
332
+ entry,
333
+ context,
334
+ childBelongsToRoute,
335
+ clientSegmentIds,
336
+ prevParams,
337
+ request,
338
+ prevUrl,
339
+ nextUrl,
340
+ routeKey,
341
+ deps,
342
+ actionContext,
343
+ layoutEntry.shortCode,
344
+ stale,
345
+ );
346
+ for (const seg of inherited.segments) {
347
+ if (!seenIds.has(seg.id)) {
348
+ seenIds.add(seg.id);
349
+ seg._inherited = true;
350
+ allLoaderSegments.push(seg);
351
+ }
352
+ }
353
+ allMatchedIds.push(...inherited.matchedIds);
354
+ }
317
355
  }
318
356
  }
319
357
 
@@ -683,13 +721,20 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
683
721
  return staticComponent;
684
722
  }
685
723
  const routeEntry = entry as Extract<EntryData, { type: "route" }>;
724
+ // For Passthrough routes at runtime, use the live handler instead of
725
+ // the build handler. At build time (context.build === true), always
726
+ // use the build handler from routeEntry.handler.
727
+ const handler =
728
+ !context.build && routeEntry.liveHandler
729
+ ? routeEntry.liveHandler
730
+ : routeEntry.handler;
686
731
  if (!routeEntry.loading) {
687
- const result = handleHandlerResult(await routeEntry.handler(context));
732
+ const result = handleHandlerResult(await handler(context));
688
733
  doneHandler();
689
734
  return result;
690
735
  }
691
736
  if (!actionContext) {
692
- const result = handleHandlerResult(routeEntry.handler(context));
737
+ const result = handleHandlerResult(handler(context));
693
738
  if (result instanceof Promise) {
694
739
  result.finally(doneHandler).catch(() => {});
695
740
  const tracked = deps.trackHandler(result, {
@@ -712,9 +757,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
712
757
  debugLog("segment.action", "resolving action route with awaited value", {
713
758
  entryId: entry.id,
714
759
  });
715
- const actionResult = handleHandlerResult(
716
- await routeEntry.handler(context),
717
- );
760
+ const actionResult = handleHandlerResult(await handler(context));
718
761
  doneHandler();
719
762
  return {
720
763
  content: Promise.resolve(actionResult),
@@ -830,6 +873,7 @@ export async function resolveSegmentWithRevalidation<TEnv>(
830
873
  deps,
831
874
  actionContext,
832
875
  stale,
876
+ entry,
833
877
  );
834
878
  segments.push(...orphanResult.segments);
835
879
  matchedIds.push(...orphanResult.matchedIds);
@@ -941,6 +985,8 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
941
985
  deps: SegmentResolutionDeps<TEnv>,
942
986
  actionContext?: ActionContext,
943
987
  stale?: boolean,
988
+ /** Parent route entry — its loaders are inherited so parallel slots can access them. */
989
+ parentRouteEntry?: EntryData,
944
990
  ): Promise<SegmentRevalidationResult> {
945
991
  invariant(
946
992
  orphan.type === "layout" || orphan.type === "cache",
@@ -968,6 +1014,37 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
968
1014
  segments.push(...loaderResult.segments);
969
1015
  matchedIds.push(...loaderResult.matchedIds);
970
1016
 
1017
+ // Inherit parent route's loaders so parallel slots inside this layout
1018
+ // can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
1019
+ if (
1020
+ parentRouteEntry &&
1021
+ parentRouteEntry.loader &&
1022
+ parentRouteEntry.loader.length > 0 &&
1023
+ Object.keys(orphan.parallel).length > 0
1024
+ ) {
1025
+ const inheritedResult = await resolveLoadersWithRevalidation(
1026
+ parentRouteEntry,
1027
+ context,
1028
+ belongsToRoute,
1029
+ clientSegmentIds,
1030
+ prevParams,
1031
+ request,
1032
+ prevUrl,
1033
+ nextUrl,
1034
+ routeKey,
1035
+ deps,
1036
+ actionContext,
1037
+ orphan.shortCode,
1038
+ stale,
1039
+ );
1040
+ // Tag as inherited so buildMatchResult can deduplicate when safe
1041
+ for (const s of inheritedResult.segments) {
1042
+ s._inherited = true;
1043
+ }
1044
+ segments.push(...inheritedResult.segments);
1045
+ matchedIds.push(...inheritedResult.matchedIds);
1046
+ }
1047
+
971
1048
  // Handler-first: resolve orphan layout handler before its parallels
972
1049
  // so ctx.set() values are visible to parallel children.
973
1050
  matchedIds.push(orphan.shortCode);
@@ -1054,6 +1131,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1054
1131
  );
1055
1132
 
1056
1133
  if (!resolvedParallelEntries.has(parallelEntry.id)) {
1134
+ // shortCodeOverride must match the parent layout, not the parallel entry.
1057
1135
  const loaderResult = await resolveLoadersWithRevalidation(
1058
1136
  parallelEntry,
1059
1137
  context,
@@ -1066,7 +1144,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1066
1144
  routeKey,
1067
1145
  deps,
1068
1146
  actionContext,
1069
- undefined,
1147
+ orphan.shortCode,
1070
1148
  stale,
1071
1149
  );
1072
1150
  segments.push(...loaderResult.segments);
@@ -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 */
@@ -173,20 +174,25 @@ function validateAndBuild(
173
174
  originalPathname: string,
174
175
  pathnameHasTrailingSlash: boolean,
175
176
  ): TrieMatchResult | null {
176
- // Build named params by zipping leaf.pa with positional paramValues
177
+ // Build named params by zipping leaf.pa with positional paramValues.
178
+ // Params are URL-decoded at this boundary so ctx.params holds the values
179
+ // apps expect (matching Express/React Router) and round-trip cleanly
180
+ // through ctx.reverse.
177
181
  const params: Record<string, string> = {};
178
182
  if (leaf.pa) {
179
183
  for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
180
- params[leaf.pa[i]] = paramValues[i];
184
+ params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
181
185
  }
182
186
  }
183
187
 
184
188
  // Add wildcard param (wildcard leaves have pn from TrieNode.w type)
185
189
  if (wildcardValue !== undefined && "pn" in leaf) {
186
- params[(leaf as TrieLeaf & { pn: string }).pn] = wildcardValue;
190
+ params[(leaf as TrieLeaf & { pn: string }).pn] =
191
+ safeDecodeURIComponent(wildcardValue);
187
192
  }
188
193
 
189
- // Validate constraints
194
+ // Validate constraints against decoded values so constraint lists can be
195
+ // written in decoded form (e.g. ["en-GB", "en US"]).
190
196
  if (leaf.cv) {
191
197
  for (const paramName in leaf.cv) {
192
198
  const allowed = leaf.cv[paramName]!;
@@ -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,9 +19,10 @@ 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
  RSCRouterContext,
27
28
  type MetricsStore,
@@ -133,6 +134,7 @@ export function createRouter<TEnv = any>(
133
134
  const {
134
135
  id: userProvidedId,
135
136
  $$id: injectedId,
137
+ basename: basenameOption,
136
138
  debugPerformance = false,
137
139
  document: documentOption,
138
140
  defaultErrorBoundary,
@@ -158,6 +160,13 @@ export function createRouter<TEnv = any>(
158
160
  originCheck: originCheckOption,
159
161
  } = options;
160
162
 
163
+ // Normalize basename: ensure leading slash, strip trailing slash.
164
+ // A bare "/" is equivalent to no basename.
165
+ const basename =
166
+ basenameOption && basenameOption.replace(/^\/+|\/+$/g, "")
167
+ ? "/" + basenameOption.replace(/^\/+|\/+$/g, "")
168
+ : undefined;
169
+
161
170
  // Resolve telemetry sink (no-op when not configured)
162
171
  const telemetry = resolveSink(telemetrySink);
163
172
 
@@ -615,6 +624,8 @@ export function createRouter<TEnv = any>(
615
624
  params: Record<string, string>,
616
625
  buildVars?: Record<string, any>,
617
626
  isPassthroughRoute?: boolean,
627
+ buildEnv?: TEnv,
628
+ devMode?: boolean,
618
629
  ) {
619
630
  return _matchForPrerender(
620
631
  pathname,
@@ -622,6 +633,8 @@ export function createRouter<TEnv = any>(
622
633
  prerenderDeps,
623
634
  buildVars,
624
635
  isPassthroughRoute,
636
+ buildEnv,
637
+ devMode,
625
638
  );
626
639
  }
627
640
 
@@ -629,12 +642,16 @@ export function createRouter<TEnv = any>(
629
642
  handler: Function,
630
643
  handlerId: string,
631
644
  routeName?: string,
645
+ buildEnv?: TEnv,
646
+ devMode?: boolean,
632
647
  ) {
633
648
  return _renderStaticSegment<TEnv>(
634
649
  handler,
635
650
  handlerId,
636
651
  mergedRouteMap,
637
652
  routeName,
653
+ buildEnv,
654
+ devMode,
638
655
  );
639
656
  }
640
657
 
@@ -659,8 +676,15 @@ export function createRouter<TEnv = any>(
659
676
  const router: RSCRouterInternal<TEnv, {}> = {
660
677
  __brand: RSC_ROUTER_BRAND,
661
678
  id: routerId,
679
+ basename,
680
+
681
+ routes(patternsOrBuilder: UrlPatterns<TEnv> | UrlBuilder<TEnv>): any {
682
+ // Wrap builder functions in urls() automatically
683
+ const urlPatterns: UrlPatterns<TEnv> =
684
+ typeof patternsOrBuilder === "function"
685
+ ? (urls(patternsOrBuilder) as UrlPatterns<TEnv>)
686
+ : patternsOrBuilder;
662
687
 
663
- routes(urlPatterns: UrlPatterns<TEnv>): any {
664
688
  // Store reference for runtime manifest generation
665
689
  storedUrlPatterns = urlPatterns;
666
690
  const currentMountIndex = mountIndex++;
@@ -708,6 +732,10 @@ export function createRouter<TEnv = any>(
708
732
  counters: {},
709
733
  mountIndex: currentMountIndex,
710
734
  cacheProfiles: resolvedCacheProfiles,
735
+ // basename sets the initial URL prefix so all path() patterns
736
+ // are registered with the prefix (e.g. "/admin" + "/users" = "/admin/users").
737
+ // No namePrefix — route names stay unprefixed.
738
+ ...(basename ? { urlPrefix: basename } : {}),
711
739
  },
712
740
  () => {
713
741
  handlerResult = urlPatterns.handler() as AllUseItems[];
@@ -727,7 +755,7 @@ export function createRouter<TEnv = any>(
727
755
  if (entry.type === "route" && entry.isPrerender) {
728
756
  if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
729
757
  prerenderRouteKeys.add(name);
730
- if (entry.prerenderDef?.options?.passthrough === true) {
758
+ if (entry.isPassthrough === true) {
731
759
  if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
732
760
  passthroughRouteKeys.add(name);
733
761
  }
@@ -856,8 +884,18 @@ export function createRouter<TEnv = any>(
856
884
  patternOrMiddleware: string | MiddlewareFn<TEnv>,
857
885
  middleware?: MiddlewareFn<TEnv>,
858
886
  ): any {
859
- // Global middleware - no mount prefix
860
- addMiddleware(patternOrMiddleware, middleware, null);
887
+ // Auto-prefix pattern with basename so router-level middleware
888
+ // patterns are router-relative (e.g. "/users/*" matches "/app/users/*").
889
+ if (basename && typeof patternOrMiddleware === "string") {
890
+ const pattern = patternOrMiddleware;
891
+ const prefixed =
892
+ pattern === "/*" || pattern === "*"
893
+ ? `${basename}/*`
894
+ : `${basename}${pattern}`;
895
+ addMiddleware(prefixed, middleware, null);
896
+ } else {
897
+ addMiddleware(patternOrMiddleware, middleware, null);
898
+ }
861
899
  return router;
862
900
  },
863
901
 
@@ -958,6 +996,9 @@ export function createRouter<TEnv = any>(
958
996
  // Expose source file for per-router type generation
959
997
  __sourceFile,
960
998
 
999
+ // Expose basename for runtime manifest generation
1000
+ __basename: basename,
1001
+
961
1002
  // RSC request handler (lazily created on first call)
962
1003
  fetch: (() => {
963
1004
  // Handler is created on first call and reused
@@ -991,6 +1032,10 @@ export function createRouter<TEnv = any>(
991
1032
  };
992
1033
  })(),
993
1034
 
1035
+ // Low-level route matching for request classification
1036
+ findMatch: (pathname: string, metricsStore?: any) =>
1037
+ findMatch(pathname, metricsStore),
1038
+
994
1039
  // Debug utility for manifest inspection
995
1040
  debugManifest: () => buildDebugManifest<TEnv>(routesEntries),
996
1041
  };
@@ -999,7 +1044,9 @@ export function createRouter<TEnv = any>(
999
1044
  RouterRegistry.set(routerId, router);
1000
1045
 
1001
1046
  // If urls option was provided, auto-register them
1002
- if (urlsOption) {
1047
+ if (typeof urlsOption === "function") {
1048
+ return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
1049
+ } else if (urlsOption) {
1003
1050
  return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
1004
1051
  }
1005
1052