@rangojs/router 0.5.2 → 0.6.0

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 (173) hide show
  1. package/dist/bin/rango.js +343 -125
  2. package/dist/types/browser/react/use-router.d.ts +10 -3
  3. package/dist/types/browser/react/use-search-params.d.ts +57 -10
  4. package/dist/types/browser/types.d.ts +22 -0
  5. package/dist/types/build/merge-full-manifests.d.ts +3 -0
  6. package/dist/types/build/route-trie.d.ts +4 -73
  7. package/dist/types/build/route-types/per-module-writer.d.ts +6 -4
  8. package/dist/types/build/route-types/router-processing.d.ts +2 -3
  9. package/dist/types/cache/cache-exec-scope.d.ts +31 -0
  10. package/dist/types/cache/taint.d.ts +12 -6
  11. package/dist/types/client-urls/client-root.d.ts +38 -0
  12. package/dist/types/client-urls/client-urls.d.ts +5 -0
  13. package/dist/types/client-urls/navigation.d.ts +38 -0
  14. package/dist/types/client-urls/revalidation-protocol.d.ts +25 -0
  15. package/dist/types/client-urls/server-projection.d.ts +62 -0
  16. package/dist/types/client-urls/types.d.ts +144 -0
  17. package/dist/types/client.d.ts +12 -4
  18. package/dist/types/client.rsc.d.ts +4 -1
  19. package/dist/types/decode-loader-results.d.ts +37 -0
  20. package/dist/types/errors.d.ts +1 -0
  21. package/dist/types/index.d.ts +1 -1
  22. package/dist/types/loader-redirect.d.ts +27 -0
  23. package/dist/types/outlet-context.d.ts +12 -0
  24. package/dist/types/outlet-provider.d.ts +3 -1
  25. package/dist/types/redirect-origin.d.ts +4 -0
  26. package/dist/types/route-content-wrapper.d.ts +42 -1
  27. package/dist/types/route-definition/helpers-types.d.ts +13 -2
  28. package/dist/types/router/error-handling.d.ts +35 -1
  29. package/dist/types/router/intercept-resolution.d.ts +12 -0
  30. package/dist/types/router/loader-resolution.d.ts +24 -2
  31. package/dist/types/router/revalidation.d.ts +7 -0
  32. package/dist/types/router/route-trie-builder.d.ts +77 -0
  33. package/dist/types/router/router-interfaces.d.ts +20 -0
  34. package/dist/types/router/segment-resolution/helpers.d.ts +1 -1
  35. package/dist/types/router/trie-matching.d.ts +1 -1
  36. package/dist/types/rsc/manifest-init.d.ts +5 -5
  37. package/dist/types/rsc/shell-capture.d.ts +9 -0
  38. package/dist/types/rsc/shell-serve.d.ts +11 -0
  39. package/dist/types/rsc/types.d.ts +30 -0
  40. package/dist/types/segment-system.d.ts +2 -0
  41. package/dist/types/server/context.d.ts +10 -0
  42. package/dist/types/server/handle-store.d.ts +34 -3
  43. package/dist/types/server/request-context.d.ts +11 -1
  44. package/dist/types/server.d.ts +1 -0
  45. package/dist/types/ssr/index.d.ts +22 -0
  46. package/dist/types/ssr/ssr-root.d.ts +10 -0
  47. package/dist/types/testing/dom.entry.d.ts +1 -1
  48. package/dist/types/testing/render-route.d.ts +16 -6
  49. package/dist/types/testing/run-loader.d.ts +9 -0
  50. package/dist/types/types/boundaries.d.ts +22 -0
  51. package/dist/types/types/index.d.ts +1 -1
  52. package/dist/types/types/loader-types.d.ts +57 -5
  53. package/dist/types/types/segments.d.ts +7 -0
  54. package/dist/types/urls/path-helper-types.d.ts +10 -4
  55. package/dist/types/vite/discovery/client-urls-projection.d.ts +53 -0
  56. package/dist/types/vite/discovery/discover-routers.d.ts +1 -1
  57. package/dist/types/vite/discovery/state.d.ts +8 -1
  58. package/dist/vite/index.js +5313 -2365
  59. package/package.json +1 -1
  60. package/skills/breadcrumbs/SKILL.md +39 -9
  61. package/skills/catalog.json +7 -1
  62. package/skills/client-urls/SKILL.md +338 -0
  63. package/skills/comparison/references/framework-comparison.md +23 -9
  64. package/skills/hooks/SKILL.md +2 -2
  65. package/skills/hooks/data.md +11 -2
  66. package/skills/hooks/handle-and-actions.md +7 -0
  67. package/skills/hooks/outlets.md +26 -5
  68. package/skills/hooks/urls.md +40 -3
  69. package/skills/loader/SKILL.md +132 -20
  70. package/skills/migrate-nextjs/SKILL.md +70 -10
  71. package/skills/migrate-react-router/SKILL.md +49 -13
  72. package/skills/migrate-react-router/component-migration.md +18 -13
  73. package/skills/migrate-react-router/data-and-actions.md +14 -3
  74. package/skills/migrate-react-router/route-mapping.md +15 -2
  75. package/skills/parallel/SKILL.md +32 -1
  76. package/skills/ppr/SKILL.md +16 -6
  77. package/skills/prerender/SKILL.md +8 -4
  78. package/skills/rango/SKILL.md +21 -17
  79. package/skills/react-compiler/SKILL.md +3 -3
  80. package/skills/route/SKILL.md +5 -2
  81. package/skills/router-setup/SKILL.md +16 -2
  82. package/skills/scripts/SKILL.md +16 -6
  83. package/skills/shell-manifest/SKILL.md +16 -7
  84. package/skills/testing/SKILL.md +2 -2
  85. package/skills/testing/client-components.md +6 -0
  86. package/skills/testing/handles.md +30 -8
  87. package/skills/testing/loader.md +51 -49
  88. package/skills/testing/middleware.md +1 -1
  89. package/skills/theme/SKILL.md +8 -5
  90. package/src/bin/rango.ts +7 -3
  91. package/src/browser/navigation-bridge.ts +6 -0
  92. package/src/browser/navigation-client.ts +5 -0
  93. package/src/browser/partial-update.ts +65 -13
  94. package/src/browser/react/use-router.ts +40 -11
  95. package/src/browser/react/use-search-params.ts +140 -17
  96. package/src/browser/rsc-router.tsx +59 -0
  97. package/src/browser/server-action-bridge.ts +26 -0
  98. package/src/browser/types.ts +22 -0
  99. package/src/build/merge-full-manifests.ts +161 -0
  100. package/src/build/route-trie.ts +9 -332
  101. package/src/build/route-types/include-resolution.ts +66 -11
  102. package/src/build/route-types/per-module-writer.ts +11 -6
  103. package/src/build/route-types/router-processing.ts +184 -153
  104. package/src/build/runtime-discovery.ts +23 -12
  105. package/src/cache/cache-exec-scope.ts +47 -0
  106. package/src/cache/cache-runtime.ts +24 -25
  107. package/src/cache/taint.ts +28 -9
  108. package/src/client-urls/client-root.tsx +168 -0
  109. package/src/client-urls/client-urls.ts +698 -0
  110. package/src/client-urls/navigation.ts +237 -0
  111. package/src/client-urls/revalidation-protocol.ts +56 -0
  112. package/src/client-urls/server-projection.ts +579 -0
  113. package/src/client-urls/types.ts +195 -0
  114. package/src/client.rsc.tsx +12 -0
  115. package/src/client.tsx +49 -6
  116. package/src/decode-loader-results.ts +113 -0
  117. package/src/errors.ts +14 -0
  118. package/src/handles/deferred-resolution.ts +14 -7
  119. package/src/index.ts +1 -0
  120. package/src/loader-redirect.tsx +64 -0
  121. package/src/outlet-context.ts +12 -0
  122. package/src/outlet-provider.tsx +15 -1
  123. package/src/redirect-origin.ts +29 -0
  124. package/src/route-content-wrapper.tsx +96 -3
  125. package/src/route-definition/dsl-helpers.ts +28 -3
  126. package/src/route-definition/helpers-types.ts +13 -0
  127. package/src/route-definition/redirect.ts +17 -18
  128. package/src/router/error-handling.ts +65 -11
  129. package/src/router/intercept-resolution.ts +29 -0
  130. package/src/router/loader-resolution.ts +261 -28
  131. package/src/router/match-result.ts +7 -0
  132. package/src/router/revalidation.ts +24 -11
  133. package/src/router/route-trie-builder.ts +334 -0
  134. package/src/router/router-interfaces.ts +38 -0
  135. package/src/router/segment-resolution/fresh.ts +47 -0
  136. package/src/router/segment-resolution/helpers.ts +9 -11
  137. package/src/router/segment-resolution/loader-cache.ts +14 -24
  138. package/src/router/segment-resolution/revalidation.ts +20 -1
  139. package/src/router/trie-matching.ts +3 -3
  140. package/src/router.ts +46 -1
  141. package/src/rsc/full-payload.ts +6 -0
  142. package/src/rsc/handler.ts +10 -7
  143. package/src/rsc/loader-fetch.ts +2 -2
  144. package/src/rsc/manifest-init.ts +28 -9
  145. package/src/rsc/rsc-rendering.ts +15 -1
  146. package/src/rsc/shell-capture.ts +12 -0
  147. package/src/rsc/shell-serve.ts +15 -2
  148. package/src/rsc/ssr-setup.ts +10 -1
  149. package/src/rsc/types.ts +31 -2
  150. package/src/segment-system.tsx +83 -26
  151. package/src/server/context.ts +10 -0
  152. package/src/server/cookie-store.ts +19 -19
  153. package/src/server/handle-store.ts +185 -48
  154. package/src/server/request-context.ts +30 -6
  155. package/src/server.ts +7 -0
  156. package/src/ssr/index.tsx +37 -2
  157. package/src/ssr/ssr-root.tsx +29 -2
  158. package/src/testing/dom.entry.ts +1 -1
  159. package/src/testing/render-route.tsx +22 -8
  160. package/src/testing/run-loader.ts +51 -13
  161. package/src/types/boundaries.ts +19 -0
  162. package/src/types/index.ts +1 -0
  163. package/src/types/loader-types.ts +60 -5
  164. package/src/types/segments.ts +7 -0
  165. package/src/urls/include-helper.ts +22 -4
  166. package/src/urls/path-helper-types.ts +14 -1
  167. package/src/use-loader.tsx +67 -6
  168. package/src/vite/discovery/client-urls-projection.ts +322 -0
  169. package/src/vite/discovery/discover-routers.ts +43 -17
  170. package/src/vite/discovery/state.ts +11 -1
  171. package/src/vite/discovery/virtual-module-codegen.ts +20 -0
  172. package/src/vite/plugins/virtual-entries.ts +12 -3
  173. package/src/vite/router-discovery.ts +163 -12
package/dist/bin/rango.js CHANGED
@@ -274,6 +274,10 @@ function readSourceMemoized(memo, realPath) {
274
274
  if (memo) memo.files.set(realPath, source);
275
275
  return source;
276
276
  }
277
+ function isUrlsDefinitionCall(node) {
278
+ const callee = node.expression;
279
+ return ts3.isIdentifier(callee) && (callee.text === "urls" || callee.text === "clientUrls");
280
+ }
277
281
  function extractNamePrefixFromInclude(node) {
278
282
  if (node.arguments.length >= 3) {
279
283
  const thirdArg = node.arguments[2];
@@ -366,11 +370,8 @@ function resolveDefaultExportTarget(code, sourceFile) {
366
370
  const expr = node.expression;
367
371
  if (ts3.isIdentifier(expr)) {
368
372
  result = { variableName: expr.text };
369
- } else if (ts3.isCallExpression(expr)) {
370
- const callee = expr.expression;
371
- if (ts3.isIdentifier(callee) && callee.text === "urls") {
372
- result = { inlineBlock: expr.getText(sourceFile) };
373
- }
373
+ } else if (ts3.isCallExpression(expr) && isUrlsDefinitionCall(expr)) {
374
+ result = { inlineBlock: expr.getText(sourceFile) };
374
375
  }
375
376
  return;
376
377
  }
@@ -441,6 +442,13 @@ function extractIncludesWithDiagnostics(code, sourceFileArg) {
441
442
  return { resolved, unresolvable };
442
443
  }
443
444
  function resolveImportedVariable(code, localName) {
445
+ const defaultImportRegex = /import\s+([\w$]+)\s*(?:,\s*\{[^}]*\})?\s+from\s*["']([^"']+)["']/g;
446
+ let defaultMatch;
447
+ while ((defaultMatch = defaultImportRegex.exec(code)) !== null) {
448
+ if (defaultMatch[1] === localName) {
449
+ return { specifier: defaultMatch[2], exportedName: "default" };
450
+ }
451
+ }
444
452
  const importRegex = /import\s*(?:[\w$]+\s*,\s*)?\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
445
453
  let match;
446
454
  while ((match = importRegex.exec(code)) !== null) {
@@ -490,8 +498,7 @@ function extractUrlsBlockForVariable(code, varName, sourceFileArg) {
490
498
  function visit(node) {
491
499
  if (result) return;
492
500
  if (ts3.isVariableDeclaration(node) && ts3.isIdentifier(node.name) && node.name.text === varName && node.initializer && ts3.isCallExpression(node.initializer)) {
493
- const callee = node.initializer.expression;
494
- if (ts3.isIdentifier(callee) && callee.text === "urls") {
501
+ if (isUrlsDefinitionCall(node.initializer)) {
495
502
  result = node.initializer.getText(sourceFile);
496
503
  return;
497
504
  }
@@ -707,6 +714,48 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
707
714
  let block;
708
715
  if (inlineBlock) {
709
716
  block = inlineBlock;
717
+ } else if (variableName === "default") {
718
+ const defaultTarget = resolveDefaultExportTarget(
719
+ source,
720
+ parseBlock(memo, source)
721
+ );
722
+ if (!defaultTarget) {
723
+ visited.delete(key);
724
+ return { routes: {}, searchSchemas: {} };
725
+ }
726
+ if (defaultTarget.inlineBlock) {
727
+ block = defaultTarget.inlineBlock;
728
+ } else {
729
+ const targetName = defaultTarget.variableName;
730
+ const imported = resolveImportedVariable(source, targetName);
731
+ if (imported) {
732
+ const targetFile = resolveImportPath(imported.specifier, realPath);
733
+ if (!targetFile) {
734
+ visited.delete(key);
735
+ return { routes: {}, searchSchemas: {} };
736
+ }
737
+ const result = buildCombinedRouteMapWithSearch(
738
+ targetFile,
739
+ imported.exportedName,
740
+ visited,
741
+ diagnosticsOut,
742
+ void 0,
743
+ memo
744
+ );
745
+ visited.delete(key);
746
+ return result;
747
+ }
748
+ const extracted = extractUrlsBlockForVariable(
749
+ source,
750
+ targetName,
751
+ parseBlock(memo, source)
752
+ );
753
+ if (!extracted) {
754
+ visited.delete(key);
755
+ return { routes: {}, searchSchemas: {} };
756
+ }
757
+ block = extracted;
758
+ }
710
759
  } else if (variableName) {
711
760
  const extracted = extractUrlsBlockForVariable(
712
761
  source,
@@ -754,7 +803,7 @@ function findUrlsVariableNames(code) {
754
803
  function visit(node) {
755
804
  if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.initializer && ts4.isCallExpression(node.initializer)) {
756
805
  const callee = node.initializer.expression;
757
- if (ts4.isIdentifier(callee) && callee.text === "urls") {
806
+ if (ts4.isIdentifier(callee) && (callee.text === "urls" || callee.text === "clientUrls")) {
758
807
  names.push(node.name.text);
759
808
  }
760
809
  }
@@ -766,7 +815,7 @@ function findUrlsVariableNames(code) {
766
815
  function writePerModuleRouteTypesForFile(filePath) {
767
816
  try {
768
817
  const source = readFileSync2(filePath, "utf-8");
769
- if (!source.includes("urls(")) return;
818
+ if (!source.includes("urls(") && !source.includes("clientUrls(")) return;
770
819
  const varNames = findUrlsVariableNames(source);
771
820
  let routes;
772
821
  if (varNames.length > 0) {
@@ -1057,7 +1106,33 @@ Router root: ${conflict.ancestor}
1057
1106
  Nested router: ${conflict.nested}
1058
1107
  Move the nested router into a sibling directory or configure it as a separate app root.`;
1059
1108
  }
1060
- function extractUrlsFromRouter(code) {
1109
+ function isCreateRouterCall(node) {
1110
+ if (!ts5.isCallExpression(node)) return false;
1111
+ const callee = node.expression;
1112
+ return ts5.isIdentifier(callee) && callee.text === "createRouter";
1113
+ }
1114
+ function extractUrlsArgument(node, sourceFile) {
1115
+ if (ts5.isIdentifier(node)) {
1116
+ return { kind: "variable", name: node.text };
1117
+ }
1118
+ if (ts5.isArrowFunction(node) || ts5.isFunctionExpression(node)) {
1119
+ return { kind: "inline", block: node.getText(sourceFile) };
1120
+ }
1121
+ return null;
1122
+ }
1123
+ function collectCreateRouterCallChain(endpoint) {
1124
+ const calls = [];
1125
+ let current = endpoint;
1126
+ while (true) {
1127
+ calls.push(current);
1128
+ if (isCreateRouterCall(current)) return calls.reverse();
1129
+ if (!ts5.isPropertyAccessExpression(current.expression)) return null;
1130
+ const receiver = current.expression.expression;
1131
+ if (!ts5.isCallExpression(receiver)) return null;
1132
+ current = receiver;
1133
+ }
1134
+ }
1135
+ function extractAllUrlsFromRouter(code) {
1061
1136
  const sourceFile = ts5.createSourceFile(
1062
1137
  "router.tsx",
1063
1138
  code,
@@ -1066,58 +1141,45 @@ function extractUrlsFromRouter(code) {
1066
1141
  ts5.ScriptKind.TSX
1067
1142
  );
1068
1143
  let result = null;
1069
- function isCreateRouterCall(node) {
1070
- if (!ts5.isCallExpression(node)) return false;
1071
- const callee = node.expression;
1072
- return ts5.isIdentifier(callee) && callee.text === "createRouter";
1073
- }
1074
- function isInlineBuilder(node) {
1075
- return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
1076
- }
1077
- function isRoutesOnCreateRouter(node) {
1078
- if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
1079
- return false;
1080
- let inner = node.expression.expression;
1081
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
1082
- inner = inner.expression.expression;
1083
- }
1084
- return isCreateRouterCall(inner);
1085
- }
1086
1144
  function visit(node) {
1087
1145
  if (result) return;
1088
- if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
1089
- const arg = node.arguments[0];
1090
- if (ts5.isIdentifier(arg)) {
1091
- result = { kind: "variable", name: arg.text };
1092
- } else if (isInlineBuilder(arg)) {
1093
- result = { kind: "inline", block: arg.getText(sourceFile) };
1094
- }
1095
- return;
1096
- }
1097
- if (isCreateRouterCall(node)) {
1098
- const callExpr = node;
1099
- for (const callArg of callExpr.arguments) {
1100
- if (ts5.isObjectLiteralExpression(callArg)) {
1146
+ if (ts5.isCallExpression(node)) {
1147
+ const chain = collectCreateRouterCallChain(node);
1148
+ if (chain) {
1149
+ const extracted = [];
1150
+ const createRouterCall = chain[0];
1151
+ for (const callArg of createRouterCall.arguments) {
1152
+ if (!ts5.isObjectLiteralExpression(callArg)) continue;
1101
1153
  for (const prop of callArg.properties) {
1102
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
1103
- if (ts5.isIdentifier(prop.initializer)) {
1104
- result = { kind: "variable", name: prop.initializer.text };
1105
- } else if (isInlineBuilder(prop.initializer)) {
1106
- result = {
1107
- kind: "inline",
1108
- block: prop.initializer.getText(sourceFile)
1109
- };
1110
- }
1111
- return;
1154
+ if (!ts5.isPropertyAssignment(prop) || !ts5.isIdentifier(prop.name) || prop.name.text !== "urls") {
1155
+ continue;
1112
1156
  }
1157
+ const urls = extractUrlsArgument(prop.initializer, sourceFile);
1158
+ if (urls) extracted.push(urls);
1159
+ break;
1113
1160
  }
1114
1161
  }
1162
+ for (const call of chain.slice(1)) {
1163
+ if (!ts5.isPropertyAccessExpression(call.expression) || call.expression.name.text !== "routes" || call.arguments.length === 0) {
1164
+ continue;
1165
+ }
1166
+ const urls = extractUrlsArgument(call.arguments[0], sourceFile);
1167
+ if (urls) extracted.push(urls);
1168
+ }
1169
+ if (extracted.length > 0) {
1170
+ result = extracted;
1171
+ return;
1172
+ }
1115
1173
  }
1116
1174
  }
1117
1175
  ts5.forEachChild(node, visit);
1118
1176
  }
1119
1177
  visit(sourceFile);
1120
- return result;
1178
+ return result ?? [];
1179
+ }
1180
+ function extractUrlsFromRouter(code) {
1181
+ const extractions = extractAllUrlsFromRouter(code);
1182
+ return extractions[extractions.length - 1] ?? null;
1121
1183
  }
1122
1184
  function extractBasenameFromRouter(code) {
1123
1185
  const sourceFile = ts5.createSourceFile(
@@ -1160,6 +1222,59 @@ function applyBasenameToRoutes(result, basename) {
1160
1222
  }
1161
1223
  return { routes: prefixed, searchSchemas: result.searchSchemas };
1162
1224
  }
1225
+ function resolveRouterUrlsExtraction(routerFilePath, routerSource, extraction, diagnosticsOut, memo) {
1226
+ if (extraction.kind === "inline") {
1227
+ return buildCombinedRouteMapWithSearch(
1228
+ routerFilePath,
1229
+ void 0,
1230
+ /* @__PURE__ */ new Set(),
1231
+ diagnosticsOut,
1232
+ extraction.block,
1233
+ memo
1234
+ );
1235
+ }
1236
+ const imported = resolveImportedVariable(routerSource, extraction.name);
1237
+ if (imported) {
1238
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
1239
+ if (!targetFile) {
1240
+ diagnosticsOut?.push({
1241
+ pathPrefix: "/",
1242
+ namePrefix: null,
1243
+ reason: "file-not-found",
1244
+ sourceFile: routerFilePath,
1245
+ detail: `import "${imported.specifier}" resolved to no file`
1246
+ });
1247
+ return { routes: {}, searchSchemas: {} };
1248
+ }
1249
+ return buildCombinedRouteMapWithSearch(
1250
+ targetFile,
1251
+ imported.exportedName,
1252
+ /* @__PURE__ */ new Set(),
1253
+ diagnosticsOut,
1254
+ void 0,
1255
+ memo
1256
+ );
1257
+ }
1258
+ return buildCombinedRouteMapWithSearch(
1259
+ routerFilePath,
1260
+ extraction.name,
1261
+ /* @__PURE__ */ new Set(),
1262
+ diagnosticsOut,
1263
+ void 0,
1264
+ memo
1265
+ );
1266
+ }
1267
+ function mergeRouteMaps(target, source) {
1268
+ for (const [name, pattern] of Object.entries(source.routes)) {
1269
+ target.routes[name] = pattern;
1270
+ const search = source.searchSchemas[name];
1271
+ if (search) {
1272
+ target.searchSchemas[name] = search;
1273
+ } else {
1274
+ delete target.searchSchemas[name];
1275
+ }
1276
+ }
1277
+ }
1163
1278
  function genFileTsPath(sourceFile) {
1164
1279
  const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
1165
1280
  return join2(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
@@ -1186,38 +1301,28 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
1186
1301
  } catch {
1187
1302
  return { routes: {}, searchSchemas: {} };
1188
1303
  }
1189
- const extraction = extractUrlsFromRouter(routerSource);
1190
- if (!extraction) {
1304
+ const extractions = extractAllUrlsFromRouter(routerSource);
1305
+ if (extractions.length === 0) {
1191
1306
  return { routes: {}, searchSchemas: {} };
1192
1307
  }
1193
1308
  const rawBasename = extractBasenameFromRouter(routerSource);
1194
1309
  const basename = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
1195
- let result;
1196
- if (extraction.kind === "inline") {
1197
- result = buildCombinedRouteMapWithSearch(
1198
- routerFilePath,
1199
- void 0,
1200
- void 0,
1201
- void 0,
1202
- extraction.block
1310
+ const result = { routes: {}, searchSchemas: {} };
1311
+ const memo = createScanMemo();
1312
+ for (const extraction of extractions) {
1313
+ mergeRouteMaps(
1314
+ result,
1315
+ resolveRouterUrlsExtraction(
1316
+ routerFilePath,
1317
+ routerSource,
1318
+ extraction,
1319
+ void 0,
1320
+ memo
1321
+ )
1203
1322
  );
1204
- } else {
1205
- const imported = resolveImportedVariable(routerSource, extraction.name);
1206
- if (imported) {
1207
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
1208
- if (!targetFile) {
1209
- return { routes: {}, searchSchemas: {} };
1210
- }
1211
- result = buildCombinedRouteMapWithSearch(
1212
- targetFile,
1213
- imported.exportedName
1214
- );
1215
- } else {
1216
- result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
1217
- }
1218
1323
  }
1219
1324
  if (basename) {
1220
- result = applyBasenameToRoutes(result, basename);
1325
+ return applyBasenameToRoutes(result, basename);
1221
1326
  }
1222
1327
  return result;
1223
1328
  }
@@ -1229,47 +1334,19 @@ function detectUnresolvableIncludes(routerFilePath) {
1229
1334
  } catch {
1230
1335
  return [];
1231
1336
  }
1232
- const extraction = extractUrlsFromRouter(source);
1233
- if (!extraction) return [];
1337
+ const extractions = extractAllUrlsFromRouter(source);
1338
+ if (extractions.length === 0) return [];
1234
1339
  const diagnostics = [];
1235
- if (extraction.kind === "inline") {
1236
- buildCombinedRouteMapWithSearch(
1340
+ const memo = createScanMemo();
1341
+ for (const extraction of extractions) {
1342
+ resolveRouterUrlsExtraction(
1237
1343
  realPath,
1238
- void 0,
1239
- /* @__PURE__ */ new Set(),
1344
+ source,
1345
+ extraction,
1240
1346
  diagnostics,
1241
- extraction.block
1347
+ memo
1242
1348
  );
1243
- return diagnostics;
1244
- }
1245
- const imported = resolveImportedVariable(source, extraction.name);
1246
- let targetFile;
1247
- let exportedName;
1248
- if (imported) {
1249
- const resolved = resolveImportPath(imported.specifier, realPath);
1250
- if (!resolved) {
1251
- return [
1252
- {
1253
- pathPrefix: "/",
1254
- namePrefix: null,
1255
- reason: "file-not-found",
1256
- sourceFile: realPath,
1257
- detail: `import "${imported.specifier}" resolved to no file`
1258
- }
1259
- ];
1260
- }
1261
- targetFile = resolved;
1262
- exportedName = imported.exportedName;
1263
- } else {
1264
- targetFile = realPath;
1265
- exportedName = extraction.name;
1266
1349
  }
1267
- buildCombinedRouteMapWithSearch(
1268
- targetFile,
1269
- exportedName,
1270
- /* @__PURE__ */ new Set(),
1271
- diagnostics
1272
- );
1273
1350
  return diagnostics;
1274
1351
  }
1275
1352
  function detectUnresolvableIncludesForUrlsFile(filePath) {
@@ -1390,6 +1467,138 @@ var init_generate_route_types = __esm({
1390
1467
  }
1391
1468
  });
1392
1469
 
1470
+ // src/build/merge-full-manifests.ts
1471
+ function clonePrefixNode(node) {
1472
+ return {
1473
+ staticPrefix: node.staticPrefix,
1474
+ fullPrefix: node.fullPrefix,
1475
+ ...node.namePrefix === void 0 ? {} : { namePrefix: node.namePrefix },
1476
+ children: clonePrefixTree(node.children),
1477
+ routes: [...node.routes]
1478
+ };
1479
+ }
1480
+ function clonePrefixTree(tree) {
1481
+ return Object.fromEntries(
1482
+ Object.entries(tree).map(([prefix, node]) => [
1483
+ prefix,
1484
+ clonePrefixNode(node)
1485
+ ])
1486
+ );
1487
+ }
1488
+ function removeRoutesFromPrefixTree(tree, routeNames) {
1489
+ return Object.fromEntries(
1490
+ Object.entries(tree).map(([prefix, node]) => [
1491
+ prefix,
1492
+ {
1493
+ staticPrefix: node.staticPrefix,
1494
+ fullPrefix: node.fullPrefix,
1495
+ ...node.namePrefix === void 0 ? {} : { namePrefix: node.namePrefix },
1496
+ children: removeRoutesFromPrefixTree(node.children, routeNames),
1497
+ routes: node.routes.filter((name) => !routeNames.has(name))
1498
+ }
1499
+ ])
1500
+ );
1501
+ }
1502
+ function mergePrefixTrees(earlier, later) {
1503
+ const merged = clonePrefixTree(earlier);
1504
+ for (const [prefix, laterNode] of Object.entries(later)) {
1505
+ const earlierNode = merged[prefix];
1506
+ if (!earlierNode) {
1507
+ merged[prefix] = clonePrefixNode(laterNode);
1508
+ continue;
1509
+ }
1510
+ merged[prefix] = {
1511
+ staticPrefix: laterNode.staticPrefix,
1512
+ fullPrefix: laterNode.fullPrefix,
1513
+ ...laterNode.namePrefix === void 0 ? {} : { namePrefix: laterNode.namePrefix },
1514
+ children: mergePrefixTrees(earlierNode.children, laterNode.children),
1515
+ routes: [.../* @__PURE__ */ new Set([...earlierNode.routes, ...laterNode.routes])]
1516
+ };
1517
+ }
1518
+ return merged;
1519
+ }
1520
+ function mergeRouteRecord(earlier, later, overwrittenRoutes, cloneValue) {
1521
+ const merged = {};
1522
+ for (const [name, value] of Object.entries(earlier ?? {})) {
1523
+ if (!overwrittenRoutes.has(name)) merged[name] = cloneValue(value);
1524
+ }
1525
+ for (const [name, value] of Object.entries(later ?? {})) {
1526
+ merged[name] = cloneValue(value);
1527
+ }
1528
+ return Object.keys(merged).length > 0 ? merged : void 0;
1529
+ }
1530
+ function mergeRouteList(earlier, later, overwrittenRoutes) {
1531
+ const merged = [
1532
+ ...(earlier ?? []).filter((name) => !overwrittenRoutes.has(name)),
1533
+ ...later ?? []
1534
+ ];
1535
+ const unique = [...new Set(merged)];
1536
+ return unique.length > 0 ? unique : void 0;
1537
+ }
1538
+ function mergeFullManifests(manifests) {
1539
+ let merged = {
1540
+ prefixTree: {},
1541
+ routeManifest: {}
1542
+ };
1543
+ for (const manifest of manifests) {
1544
+ const overwrittenRoutes = new Set(Object.keys(manifest.routeManifest));
1545
+ const prefixTreeWithoutOverwrites = removeRoutesFromPrefixTree(
1546
+ merged.prefixTree,
1547
+ overwrittenRoutes
1548
+ );
1549
+ merged = {
1550
+ prefixTree: mergePrefixTrees(
1551
+ prefixTreeWithoutOverwrites,
1552
+ manifest.prefixTree
1553
+ ),
1554
+ routeManifest: {
1555
+ ...merged.routeManifest,
1556
+ ...manifest.routeManifest
1557
+ },
1558
+ routeTrailingSlash: mergeRouteRecord(
1559
+ merged.routeTrailingSlash,
1560
+ manifest.routeTrailingSlash,
1561
+ overwrittenRoutes,
1562
+ (value) => value
1563
+ ),
1564
+ prerenderRoutes: mergeRouteList(
1565
+ merged.prerenderRoutes,
1566
+ manifest.prerenderRoutes,
1567
+ overwrittenRoutes
1568
+ ),
1569
+ passthroughRoutes: mergeRouteList(
1570
+ merged.passthroughRoutes,
1571
+ manifest.passthroughRoutes,
1572
+ overwrittenRoutes
1573
+ ),
1574
+ responseTypeRoutes: mergeRouteRecord(
1575
+ merged.responseTypeRoutes,
1576
+ manifest.responseTypeRoutes,
1577
+ overwrittenRoutes,
1578
+ (value) => value
1579
+ ),
1580
+ routeSearchSchemas: mergeRouteRecord(
1581
+ merged.routeSearchSchemas,
1582
+ manifest.routeSearchSchemas,
1583
+ overwrittenRoutes,
1584
+ (value) => ({ ...value })
1585
+ ),
1586
+ _prerenderDefs: mergeRouteRecord(
1587
+ merged._prerenderDefs,
1588
+ manifest._prerenderDefs,
1589
+ overwrittenRoutes,
1590
+ (value) => value
1591
+ )
1592
+ };
1593
+ }
1594
+ return merged;
1595
+ }
1596
+ var init_merge_full_manifests = __esm({
1597
+ "src/build/merge-full-manifests.ts"() {
1598
+ "use strict";
1599
+ }
1600
+ });
1601
+
1393
1602
  // src/vite/plugins/virtual-entries.ts
1394
1603
  function getVirtualVersionContent(version) {
1395
1604
  return `export const VERSION = ${JSON.stringify(version)};`;
@@ -1798,20 +2007,28 @@ async function discoverAndWriteRouteTypes(opts) {
1798
2007
  );
1799
2008
  }
1800
2009
  const buildMod = await rscEnv.runner.import("@rangojs/router/build");
1801
- const generateManifest = buildMod.generateManifest;
1802
- if (!generateManifest) {
1803
- throw new Error("generateManifest not found in @rangojs/router/build");
2010
+ const generateManifestFull = buildMod.generateManifestFull;
2011
+ if (!generateManifestFull) {
2012
+ throw new Error(
2013
+ "generateManifestFull not found in @rangojs/router/build"
2014
+ );
1804
2015
  }
1805
2016
  const outputFiles = [];
1806
2017
  let totalRouteCount = 0;
1807
- let routerMountIndex = 0;
2018
+ let globalMountIndex = 0;
2019
+ let routerCount = 0;
1808
2020
  for (const [id, router] of registry) {
1809
- if (!router.urlpatterns) continue;
1810
- const manifest = await generateManifest(
1811
- router.urlpatterns,
1812
- routerMountIndex
1813
- );
1814
- routerMountIndex++;
2021
+ const mounts = router.__urlpatternMounts ?? (router.urlpatterns ? [{ patterns: router.urlpatterns }] : []);
2022
+ if (mounts.length === 0) continue;
2023
+ const mountManifests = [];
2024
+ for (const mount of mounts) {
2025
+ mountManifests.push(
2026
+ await generateManifestFull(mount.patterns, globalMountIndex)
2027
+ );
2028
+ globalMountIndex++;
2029
+ }
2030
+ const manifest = mergeFullManifests(mountManifests);
2031
+ routerCount++;
1815
2032
  const rawManifest = manifest.routeManifest;
1816
2033
  const routeManifest = {};
1817
2034
  for (const [name, pattern] of Object.entries(rawManifest)) {
@@ -1856,7 +2073,7 @@ Set an explicit \`id\` on createRouter() or check the call site.`
1856
2073
  );
1857
2074
  }
1858
2075
  return {
1859
- routerCount: routerMountIndex,
2076
+ routerCount,
1860
2077
  routeCount: totalRouteCount,
1861
2078
  outputFiles
1862
2079
  };
@@ -1870,6 +2087,7 @@ var init_runtime_discovery = __esm({
1870
2087
  "src/build/runtime-discovery.ts"() {
1871
2088
  "use strict";
1872
2089
  init_generate_route_types();
2090
+ init_merge_full_manifests();
1873
2091
  init_route_name();
1874
2092
  }
1875
2093
  });
@@ -1982,7 +2200,7 @@ function runStaticGeneration(args, mode) {
1982
2200
  if (/\bcreateRouter\s*[<(]/.test(source)) {
1983
2201
  routerFiles.push(filePath);
1984
2202
  }
1985
- if (source.includes("urls(")) {
2203
+ if (source.includes("urls(") || source.includes("clientUrls(")) {
1986
2204
  urlsFiles.push(filePath);
1987
2205
  }
1988
2206
  } catch (err) {
@@ -2070,7 +2288,7 @@ async function runRuntimeDiscovery(args, configFile) {
2070
2288
  if (/\bcreateRouter\s*[<(]/.test(source)) {
2071
2289
  routerEntries.push(filePath);
2072
2290
  }
2073
- if (source.includes("urls(")) {
2291
+ if (source.includes("urls(") || source.includes("clientUrls(")) {
2074
2292
  writePerModuleRouteTypesForFile(filePath);
2075
2293
  }
2076
2294
  } catch {
@@ -2,8 +2,8 @@ import type { RouterInstance } from "../types.js";
2
2
  /**
3
3
  * Hook to access router actions (push, replace, refresh, prefetch, back, forward).
4
4
  *
5
- * Returns a STABLE reference that never changes, so components using
6
- * useRouter() do not re-render on navigation state changes.
5
+ * Returns a STABLE reference per component, so components using useRouter()
6
+ * do not re-render on navigation state changes.
7
7
  * For reactive navigation state, use useNavigation() instead.
8
8
  *
9
9
  * Methods read `basename` from the context on each call. It is set once from
@@ -11,10 +11,17 @@ import type { RouterInstance } from "../types.js";
11
11
  * is a full document load (X-RSC-Reload), so the target app mounts fresh with
12
12
  * its own basename.
13
13
  *
14
+ * RELATIVE paths (no leading `/`, no scheme/query/hash) resolve against the
15
+ * current include() mount: `router.push("cart")` inside `include("/shop")`
16
+ * navigates to `/shop/cart`. Absolute paths stay APP-absolute — unlike
17
+ * basename, the mount is scoped, and absolute pushes legitimately target
18
+ * outside it, so they are never auto-prefixed.
19
+ *
14
20
  * @example
15
21
  * ```tsx
16
22
  * const router = useRouter();
17
- * router.push("/products");
23
+ * router.push("/products"); // app-absolute
24
+ * router.push("cart"); // mount-relative
18
25
  * router.replace("/login", { scroll: false });
19
26
  * router.prefetch("/dashboard");
20
27
  * router.back();