@rangojs/router 0.0.0-experimental.18 → 0.0.0-experimental.19

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 (177) hide show
  1. package/README.md +46 -8
  2. package/dist/bin/rango.js +105 -18
  3. package/dist/vite/index.js +227 -93
  4. package/package.json +15 -14
  5. package/skills/hooks/SKILL.md +1 -1
  6. package/skills/intercept/SKILL.md +79 -0
  7. package/skills/layout/SKILL.md +62 -2
  8. package/skills/loader/SKILL.md +94 -1
  9. package/skills/middleware/SKILL.md +81 -0
  10. package/skills/parallel/SKILL.md +57 -2
  11. package/skills/prerender/SKILL.md +187 -17
  12. package/skills/route/SKILL.md +42 -1
  13. package/skills/router-setup/SKILL.md +77 -0
  14. package/src/__internal.ts +1 -1
  15. package/src/bin/rango.ts +38 -19
  16. package/src/browser/action-coordinator.ts +97 -0
  17. package/src/browser/event-controller.ts +25 -27
  18. package/src/browser/history-state.ts +80 -0
  19. package/src/browser/intercept-utils.ts +1 -1
  20. package/src/browser/link-interceptor.ts +0 -3
  21. package/src/browser/merge-segment-loaders.ts +9 -2
  22. package/src/browser/navigation-bridge.ts +46 -13
  23. package/src/browser/navigation-client.ts +32 -61
  24. package/src/browser/navigation-store.ts +1 -31
  25. package/src/browser/navigation-transaction.ts +46 -207
  26. package/src/browser/partial-update.ts +102 -150
  27. package/src/browser/{prefetch-cache.ts → prefetch/cache.ts} +23 -4
  28. package/src/browser/{prefetch-fetch.ts → prefetch/fetch.ts} +36 -8
  29. package/src/browser/prefetch/policy.ts +42 -0
  30. package/src/browser/{prefetch-queue.ts → prefetch/queue.ts} +10 -3
  31. package/src/browser/react/Link.tsx +28 -23
  32. package/src/browser/react/NavigationProvider.tsx +9 -1
  33. package/src/browser/react/index.ts +2 -6
  34. package/src/browser/react/location-state-shared.ts +1 -1
  35. package/src/browser/react/location-state.ts +2 -0
  36. package/src/browser/react/nonce-context.ts +23 -0
  37. package/src/browser/react/use-action.ts +9 -1
  38. package/src/browser/react/use-handle.ts +3 -25
  39. package/src/browser/react/use-params.ts +2 -4
  40. package/src/browser/react/use-pathname.ts +2 -3
  41. package/src/browser/react/use-router.ts +1 -1
  42. package/src/browser/react/use-search-params.ts +2 -1
  43. package/src/browser/react/use-segments.ts +7 -60
  44. package/src/browser/response-adapter.ts +73 -0
  45. package/src/browser/rsc-router.tsx +29 -23
  46. package/src/browser/scroll-restoration.ts +10 -7
  47. package/src/browser/server-action-bridge.ts +115 -96
  48. package/src/browser/types.ts +1 -31
  49. package/src/browser/validate-redirect-origin.ts +29 -0
  50. package/src/build/generate-manifest.ts +5 -0
  51. package/src/build/generate-route-types.ts +2 -0
  52. package/src/build/route-types/codegen.ts +13 -4
  53. package/src/build/route-types/include-resolution.ts +13 -0
  54. package/src/build/route-types/per-module-writer.ts +15 -3
  55. package/src/build/route-types/router-processing.ts +45 -3
  56. package/src/build/runtime-discovery.ts +13 -1
  57. package/src/cache/background-task.ts +34 -0
  58. package/src/cache/cache-key-utils.ts +44 -0
  59. package/src/cache/cache-policy.ts +125 -0
  60. package/src/cache/cache-runtime.ts +132 -96
  61. package/src/cache/cache-scope.ts +71 -73
  62. package/src/cache/cf/cf-cache-store.ts +9 -4
  63. package/src/cache/document-cache.ts +72 -47
  64. package/src/cache/handle-capture.ts +81 -0
  65. package/src/cache/memory-segment-store.ts +18 -7
  66. package/src/cache/profile-registry.ts +43 -8
  67. package/src/cache/read-through-swr.ts +134 -0
  68. package/src/cache/segment-codec.ts +101 -112
  69. package/src/cache/taint.ts +26 -0
  70. package/src/client.tsx +53 -30
  71. package/src/errors.ts +6 -1
  72. package/src/handle.ts +1 -1
  73. package/src/handles/MetaTags.tsx +5 -2
  74. package/src/host/cookie-handler.ts +8 -3
  75. package/src/host/router.ts +14 -1
  76. package/src/href-client.ts +3 -1
  77. package/src/index.rsc.ts +33 -1
  78. package/src/index.ts +27 -0
  79. package/src/loader.rsc.ts +12 -4
  80. package/src/loader.ts +8 -0
  81. package/src/prerender/store.ts +4 -3
  82. package/src/prerender.ts +76 -18
  83. package/src/reverse.ts +11 -7
  84. package/src/root-error-boundary.tsx +30 -26
  85. package/src/route-definition/dsl-helpers.ts +9 -6
  86. package/src/route-definition/redirect.ts +15 -3
  87. package/src/route-map-builder.ts +38 -2
  88. package/src/route-name.ts +53 -0
  89. package/src/route-types.ts +7 -0
  90. package/src/router/content-negotiation.ts +1 -1
  91. package/src/router/debug-manifest.ts +16 -3
  92. package/src/router/handler-context.ts +94 -15
  93. package/src/router/intercept-resolution.ts +6 -4
  94. package/src/router/lazy-includes.ts +4 -0
  95. package/src/router/loader-resolution.ts +1 -0
  96. package/src/router/logging.ts +100 -3
  97. package/src/router/manifest.ts +32 -3
  98. package/src/router/match-api.ts +61 -7
  99. package/src/router/match-context.ts +3 -0
  100. package/src/router/match-handlers.ts +185 -11
  101. package/src/router/match-middleware/background-revalidation.ts +65 -85
  102. package/src/router/match-middleware/cache-lookup.ts +69 -4
  103. package/src/router/match-middleware/cache-store.ts +2 -0
  104. package/src/router/match-pipelines.ts +8 -43
  105. package/src/router/middleware-types.ts +7 -0
  106. package/src/router/middleware.ts +93 -8
  107. package/src/router/pattern-matching.ts +41 -5
  108. package/src/router/prerender-match.ts +34 -6
  109. package/src/router/preview-match.ts +7 -1
  110. package/src/router/revalidation.ts +61 -2
  111. package/src/router/router-context.ts +15 -0
  112. package/src/router/router-interfaces.ts +34 -0
  113. package/src/router/router-options.ts +200 -0
  114. package/src/router/segment-resolution/fresh.ts +123 -30
  115. package/src/router/segment-resolution/helpers.ts +19 -0
  116. package/src/router/segment-resolution/loader-cache.ts +37 -146
  117. package/src/router/segment-resolution/revalidation.ts +358 -94
  118. package/src/router/segment-wrappers.ts +3 -0
  119. package/src/router/telemetry-otel.ts +299 -0
  120. package/src/router/telemetry.ts +300 -0
  121. package/src/router/timeout.ts +148 -0
  122. package/src/router/types.ts +7 -1
  123. package/src/router.ts +155 -11
  124. package/src/rsc/handler-context.ts +11 -0
  125. package/src/rsc/handler.ts +380 -88
  126. package/src/rsc/helpers.ts +25 -16
  127. package/src/rsc/loader-fetch.ts +84 -42
  128. package/src/rsc/origin-guard.ts +141 -0
  129. package/src/rsc/progressive-enhancement.ts +232 -19
  130. package/src/rsc/response-route-handler.ts +37 -26
  131. package/src/rsc/rsc-rendering.ts +12 -5
  132. package/src/rsc/runtime-warnings.ts +42 -0
  133. package/src/rsc/server-action.ts +134 -58
  134. package/src/rsc/types.ts +8 -0
  135. package/src/search-params.ts +22 -10
  136. package/src/server/context.ts +53 -5
  137. package/src/server/fetchable-loader-store.ts +11 -6
  138. package/src/server/handle-store.ts +66 -9
  139. package/src/server/loader-registry.ts +11 -46
  140. package/src/server/request-context.ts +90 -9
  141. package/src/ssr/index.tsx +63 -27
  142. package/src/static-handler.ts +7 -0
  143. package/src/theme/ThemeProvider.tsx +6 -1
  144. package/src/theme/index.ts +1 -6
  145. package/src/theme/theme-context.ts +1 -28
  146. package/src/theme/theme-script.ts +2 -1
  147. package/src/types/cache-types.ts +5 -0
  148. package/src/types/error-types.ts +3 -0
  149. package/src/types/global-namespace.ts +9 -0
  150. package/src/types/handler-context.ts +35 -13
  151. package/src/types/loader-types.ts +7 -0
  152. package/src/types/route-entry.ts +28 -0
  153. package/src/urls/include-helper.ts +49 -8
  154. package/src/urls/index.ts +1 -0
  155. package/src/urls/path-helper-types.ts +30 -12
  156. package/src/urls/path-helper.ts +17 -2
  157. package/src/urls/pattern-types.ts +21 -1
  158. package/src/urls/response-types.ts +27 -2
  159. package/src/urls/type-extraction.ts +23 -15
  160. package/src/use-loader.tsx +12 -4
  161. package/src/vite/discovery/bundle-postprocess.ts +12 -7
  162. package/src/vite/discovery/discover-routers.ts +30 -18
  163. package/src/vite/discovery/prerender-collection.ts +24 -27
  164. package/src/vite/discovery/route-types-writer.ts +7 -7
  165. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  166. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  167. package/src/vite/plugins/use-cache-transform.ts +91 -3
  168. package/src/vite/rango.ts +3 -3
  169. package/src/vite/router-discovery.ts +99 -36
  170. package/src/vite/utils/prerender-utils.ts +21 -0
  171. package/src/vite/utils/shared-utils.ts +3 -1
  172. package/src/browser/request-controller.ts +0 -164
  173. package/src/href-context.ts +0 -33
  174. package/src/router.gen.ts +0 -6
  175. package/src/static-handler.gen.ts +0 -5
  176. package/src/urls.gen.ts +0 -8
  177. /package/src/browser/{prefetch-observer.ts → prefetch/observer.ts} +0 -0
@@ -1454,7 +1454,7 @@ function useCacheTransform() {
1454
1454
  transformWrapExport
1455
1455
  );
1456
1456
  }
1457
- return transformFunctionLevelUseCache(
1457
+ const functionResult = transformFunctionLevelUseCache(
1458
1458
  code,
1459
1459
  ast,
1460
1460
  filePath,
@@ -1462,21 +1462,33 @@ function useCacheTransform() {
1462
1462
  isBuild,
1463
1463
  transformHoistInlineDirective
1464
1464
  );
1465
+ warnOnNearMissDirectives(ast, id, this.warn.bind(this));
1466
+ if (functionResult) return functionResult;
1465
1467
  }
1466
1468
  };
1467
1469
  }
1468
1470
  function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLayoutOrTemplate, transformWrapExport) {
1471
+ const nonFunctionExports = [];
1469
1472
  const { exportNames, output } = transformWrapExport(code, ast, {
1470
1473
  runtime: (value, name) => {
1471
1474
  const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1472
1475
  return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, "default")`;
1473
1476
  },
1474
1477
  rejectNonAsyncFunction: false,
1475
- filter: (name) => {
1478
+ filter: (name, meta) => {
1476
1479
  if (name === "default" && isLayoutOrTemplate) return false;
1480
+ if (meta.isFunction === false) {
1481
+ nonFunctionExports.push(name);
1482
+ return false;
1483
+ }
1477
1484
  return true;
1478
1485
  }
1479
1486
  });
1487
+ if (nonFunctionExports.length > 0) {
1488
+ throw new Error(
1489
+ `[rango:use-cache] File-level "use cache" in ${sourceId} cannot wrap non-function export${nonFunctionExports.length > 1 ? "s" : ""}: ${nonFunctionExports.map((n) => `"${n}"`).join(", ")}. Only function exports can be cached. Either remove "use cache" from the file level and add it inside individual functions, or move the non-function exports to a separate module.`
1490
+ );
1491
+ }
1480
1492
  if (exportNames.length === 0) {
1481
1493
  const s = new MagicString5(code);
1482
1494
  const directive2 = findFileLevelDirective(ast);
@@ -1513,7 +1525,7 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
1513
1525
  function transformFunctionLevelUseCache(code, ast, filePath, sourceId, isBuild, transformHoistInlineDirective) {
1514
1526
  try {
1515
1527
  const { output, names } = transformHoistInlineDirective(code, ast, {
1516
- directive: /^use cache(:\s*\w+)?$/,
1528
+ directive: /^use cache(:\s*[\w-]+)?$/,
1517
1529
  runtime: (value, name, meta) => {
1518
1530
  const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1519
1531
  const profileMatch = meta.directiveMatch[1];
@@ -1543,6 +1555,35 @@ function findFileLevelDirective(ast) {
1543
1555
  }
1544
1556
  return null;
1545
1557
  }
1558
+ var VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
1559
+ var NEAR_MISS_RE = /^use cache:\s*.+$/;
1560
+ function warnOnNearMissDirectives(ast, fileId, warn) {
1561
+ const visit = (node) => {
1562
+ if (!node || typeof node !== "object") return;
1563
+ if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
1564
+ const value = node.expression.value;
1565
+ if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !VALID_DIRECTIVE_RE.test(value)) {
1566
+ const profilePart = value.slice("use cache:".length).trim();
1567
+ warn(
1568
+ `[rango:use-cache] "${value}" in ${fileId} has an invalid profile name "${profilePart}". Profile names must match [a-zA-Z0-9_-]+. This directive will be ignored.`
1569
+ );
1570
+ }
1571
+ }
1572
+ for (const key of Object.keys(node)) {
1573
+ const child = node[key];
1574
+ if (Array.isArray(child)) {
1575
+ for (const item of child) {
1576
+ visit(item);
1577
+ }
1578
+ } else if (child && typeof child === "object" && child.type) {
1579
+ visit(child);
1580
+ }
1581
+ }
1582
+ };
1583
+ for (const node of ast.body ?? []) {
1584
+ visit(node);
1585
+ }
1586
+ }
1546
1587
 
1547
1588
  // src/vite/plugins/virtual-entries.ts
1548
1589
  var VIRTUAL_ENTRY_BROWSER = `
@@ -1657,7 +1698,7 @@ import { resolve } from "node:path";
1657
1698
  // package.json
1658
1699
  var package_default = {
1659
1700
  name: "@rangojs/router",
1660
- version: "0.0.0-experimental.18",
1701
+ version: "0.0.0-experimental.19",
1661
1702
  description: "Django-inspired RSC router with composable URL patterns",
1662
1703
  keywords: [
1663
1704
  "react",
@@ -1970,15 +2011,27 @@ function extractRouteFromCallExpression(node) {
1970
2011
  };
1971
2012
  }
1972
2013
 
2014
+ // src/route-name.ts
2015
+ var AUTO_GENERATED_ROUTE_PREFIX = "$path_";
2016
+ var INTERNAL_INCLUDE_SCOPE_PREFIX = "$prefix_";
2017
+ function isAutoGeneratedRouteName(name) {
2018
+ return name.split(".").some((segment) => {
2019
+ return segment.startsWith(AUTO_GENERATED_ROUTE_PREFIX) || segment.startsWith(INTERNAL_INCLUDE_SCOPE_PREFIX);
2020
+ });
2021
+ }
2022
+
1973
2023
  // src/build/route-types/codegen.ts
1974
2024
  function generateRouteTypesSource(routeManifest, searchSchemas) {
1975
- const entries = Object.entries(routeManifest).sort(
1976
- ([a], [b]) => a.localeCompare(b)
1977
- );
2025
+ const entries = Object.entries(routeManifest).filter(([name]) => !isAutoGeneratedRouteName(name)).sort(([a], [b]) => a.localeCompare(b));
2026
+ const filteredSearchSchemas = searchSchemas ? Object.fromEntries(
2027
+ Object.entries(searchSchemas).filter(
2028
+ ([name]) => !isAutoGeneratedRouteName(name)
2029
+ )
2030
+ ) : void 0;
1978
2031
  const objectBody = entries.map(([name, pattern]) => {
1979
2032
  const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
1980
2033
  const params = extractParamsFromPattern(pattern);
1981
- const search = searchSchemas?.[name];
2034
+ const search = filteredSearchSchemas?.[name];
1982
2035
  return formatRouteEntry(key, pattern, params, search);
1983
2036
  }).join("\n");
1984
2037
  return `// Auto-generated by @rangojs/router - do not edit
@@ -2244,6 +2297,9 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2244
2297
  diagnosticsOut
2245
2298
  );
2246
2299
  }
2300
+ if (namePrefix === null) {
2301
+ continue;
2302
+ }
2247
2303
  for (const [name, pattern] of Object.entries(childResult.routes)) {
2248
2304
  const prefixedName = namePrefix ? `${namePrefix}.${name}` : name;
2249
2305
  let prefixedPattern;
@@ -2294,6 +2350,7 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2294
2350
  searchSchemas,
2295
2351
  diagnosticsOut
2296
2352
  );
2353
+ visited.delete(key);
2297
2354
  return { routes, searchSchemas };
2298
2355
  }
2299
2356
 
@@ -2301,6 +2358,17 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2301
2358
  import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, unlinkSync } from "node:fs";
2302
2359
  import { join as join2, dirname as dirname2, resolve as resolve3, basename as pathBasename } from "node:path";
2303
2360
  import ts5 from "typescript";
2361
+ function countPublicRouteEntries(source) {
2362
+ const matches = source.matchAll(/^\s+(?:"([^"]+)"|([a-zA-Z_$][^:]*)):\s*["{]/gm) ?? [];
2363
+ let count = 0;
2364
+ for (const match of matches) {
2365
+ const routeName = match[1] || match[2];
2366
+ if (routeName && !isAutoGeneratedRouteName(routeName.trim())) {
2367
+ count++;
2368
+ }
2369
+ }
2370
+ return count;
2371
+ }
2304
2372
  function extractUrlsVariableFromRouter(code) {
2305
2373
  const sourceFile = ts5.createSourceFile(
2306
2374
  "router.tsx",
@@ -2439,8 +2507,10 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2439
2507
  );
2440
2508
  if (existing !== source) {
2441
2509
  if (opts?.preserveIfLarger && existing) {
2442
- const existingCount = (existing.match(/^\s+["a-zA-Z_$][^:]*:\s*["{]/gm) || []).length;
2443
- const newCount = Object.keys(result.routes).length;
2510
+ const existingCount = countPublicRouteEntries(existing);
2511
+ const newCount = Object.keys(result.routes).filter(
2512
+ (name) => !isAutoGeneratedRouteName(name)
2513
+ ).length;
2444
2514
  if (existingCount > newCount) {
2445
2515
  continue;
2446
2516
  }
@@ -2684,7 +2754,8 @@ function createVirtualEntriesPlugin(entries, routerPathRef) {
2684
2754
  return virtualModules[virtualId];
2685
2755
  }
2686
2756
  if (virtualId === VIRTUAL_IDS.rsc && routerPathRef?.path) {
2687
- const absoluteRouterPath = routerPathRef.path.startsWith(".") ? "/" + routerPathRef.path.slice(2) : routerPathRef.path;
2757
+ const raw = routerPathRef.path.startsWith(".") ? "/" + routerPathRef.path.slice(2) : routerPathRef.path;
2758
+ const absoluteRouterPath = raw.replaceAll("\\", "/");
2688
2759
  return getVirtualEntryRSC(absoluteRouterPath);
2689
2760
  }
2690
2761
  }
@@ -2881,7 +2952,7 @@ function createVirtualStubPlugin() {
2881
2952
  }
2882
2953
 
2883
2954
  // src/vite/plugins/client-ref-hashing.ts
2884
- import { relative as relative2, posix } from "node:path";
2955
+ import { relative as relative2 } from "node:path";
2885
2956
  import { createHash as createHash2 } from "node:crypto";
2886
2957
  var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
2887
2958
  var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
@@ -2894,10 +2965,10 @@ function computeProductionHash(projectRoot, refKey) {
2894
2965
  const absPath = decodeURIComponent(
2895
2966
  refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
2896
2967
  );
2897
- toHash = posix.normalize(relative2(projectRoot, absPath));
2968
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
2898
2969
  } else if (refKey.startsWith(FS_PREFIX)) {
2899
2970
  const absPath = refKey.slice(FS_PREFIX.length - 1);
2900
- toHash = posix.normalize(relative2(projectRoot, absPath));
2971
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
2901
2972
  } else if (refKey.startsWith("/")) {
2902
2973
  toHash = refKey.slice(1);
2903
2974
  } else {
@@ -3143,6 +3214,18 @@ function escapeRegExp2(str) {
3143
3214
  function encodePathParam(value) {
3144
3215
  return String(value).split("/").map((segment) => encodeURIComponent(segment)).join("/");
3145
3216
  }
3217
+ function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3218
+ let result = pattern;
3219
+ for (const [key, value] of Object.entries(params)) {
3220
+ const escaped = escapeRegExp2(key);
3221
+ result = result.replace(
3222
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3223
+ encode(value)
3224
+ );
3225
+ result = result.replace(`*${key}`, encode(value));
3226
+ }
3227
+ return result;
3228
+ }
3146
3229
  async function runWithConcurrency(items, concurrency, fn) {
3147
3230
  const limit = Math.max(1, Math.min(concurrency, items.length));
3148
3231
  if (limit <= 1) {
@@ -3215,18 +3298,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3215
3298
  const getParamsReverse = (name, params) => {
3216
3299
  const pattern = allRoutes[name];
3217
3300
  if (!pattern) throw new Error(`Unknown route: "${name}"`);
3218
- let result = pattern;
3219
- if (params) {
3220
- for (const [key, value] of Object.entries(params)) {
3221
- const escaped = escapeRegExp2(key);
3222
- result = result.replace(
3223
- new RegExp(`:${escaped}(\\([^)]*\\))?`),
3224
- encodeURIComponent(value)
3225
- );
3226
- result = result.replace(`*${key}`, encodeURIComponent(value));
3227
- }
3228
- }
3229
- return result;
3301
+ if (!params) return pattern;
3302
+ return substituteRouteParams(pattern, params);
3230
3303
  };
3231
3304
  for (const { manifest } of allManifests) {
3232
3305
  if (!manifest.prerenderRoutes) continue;
@@ -3234,15 +3307,17 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3234
3307
  for (const routeName of manifest.prerenderRoutes) {
3235
3308
  const pattern = manifest.routeManifest[routeName];
3236
3309
  if (!pattern) continue;
3310
+ const def = defs[routeName];
3311
+ const isPassthroughRoute = !!def?.options?.passthrough;
3237
3312
  const hasDynamic = pattern.includes(":") || pattern.includes("*");
3238
3313
  if (!hasDynamic) {
3239
3314
  entries.push({
3240
3315
  urlPath: pattern.replace(/\/$/, "") || "/",
3241
3316
  routeName,
3242
- concurrency: 1
3317
+ concurrency: 1,
3318
+ isPassthroughRoute
3243
3319
  });
3244
3320
  } else {
3245
- const def = defs[routeName];
3246
3321
  if (def?.getParams) {
3247
3322
  try {
3248
3323
  const buildVars = {};
@@ -3257,18 +3332,11 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3257
3332
  const concurrency = def.options?.concurrency ?? 1;
3258
3333
  const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3259
3334
  for (const params of paramsList) {
3260
- let url = pattern;
3261
- for (const [key, value] of Object.entries(
3262
- params
3263
- )) {
3264
- const encoded = encodePathParam(value);
3265
- const escaped = escapeRegExp2(key);
3266
- url = url.replace(
3267
- new RegExp(`:${escaped}(\\([^)]*\\))?`),
3268
- encoded
3269
- );
3270
- url = url.replace(`*${key}`, encoded);
3271
- }
3335
+ let url = substituteRouteParams(
3336
+ pattern,
3337
+ params,
3338
+ encodePathParam
3339
+ );
3272
3340
  if (url.includes("*")) {
3273
3341
  const wildcardValue = params["*"] ?? params.splat;
3274
3342
  if (wildcardValue !== void 0) {
@@ -3279,7 +3347,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3279
3347
  urlPath: url.replace(/\/$/, "") || "/",
3280
3348
  routeName,
3281
3349
  concurrency,
3282
- ...hasBuildVars ? { buildVars } : {}
3350
+ ...hasBuildVars ? { buildVars } : {},
3351
+ isPassthroughRoute
3283
3352
  });
3284
3353
  }
3285
3354
  } catch (err) {
@@ -3335,9 +3404,18 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3335
3404
  const result = await routerInstance.matchForPrerender(
3336
3405
  entry.urlPath,
3337
3406
  {},
3338
- entry.buildVars
3407
+ entry.buildVars,
3408
+ entry.isPassthroughRoute
3339
3409
  );
3340
3410
  if (!result) continue;
3411
+ if (result.passthrough) {
3412
+ const elapsed2 = (performance.now() - startUrl).toFixed(0);
3413
+ console.log(
3414
+ `[rsc-router] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
3415
+ );
3416
+ doneCount++;
3417
+ break;
3418
+ }
3341
3419
  const paramHash = hashParams(result.params || {});
3342
3420
  collectedData[`${result.routeName}/${paramHash}`] = {
3343
3421
  segments: result.segments,
@@ -3537,12 +3615,12 @@ async function discoverRouters(state, rscEnv) {
3537
3615
  }
3538
3616
  const buildMod = await rscEnv.runner.import("@rangojs/router/build");
3539
3617
  const generateManifestFull = buildMod.generateManifestFull;
3540
- state.mergedRouteManifest = {};
3541
- state.mergedPrecomputedEntries = [];
3542
- state.perRouterManifests = [];
3543
- state.perRouterManifestDataMap = /* @__PURE__ */ new Map();
3544
- state.perRouterPrecomputedMap = /* @__PURE__ */ new Map();
3545
- state.perRouterTrieMap = /* @__PURE__ */ new Map();
3618
+ const newMergedRouteManifest = {};
3619
+ const newMergedPrecomputedEntries = [];
3620
+ const newPerRouterManifests = [];
3621
+ const newPerRouterManifestDataMap = /* @__PURE__ */ new Map();
3622
+ const newPerRouterPrecomputedMap = /* @__PURE__ */ new Map();
3623
+ const newPerRouterTrieMap = /* @__PURE__ */ new Map();
3546
3624
  let mergedRouteAncestry = {};
3547
3625
  let mergedRouteTrailingSlash = {};
3548
3626
  let routerMountIndex = 0;
@@ -3559,7 +3637,7 @@ async function discoverRouters(state, rscEnv) {
3559
3637
  (p) => !p.includes(":") && !p.includes("*")
3560
3638
  ).length;
3561
3639
  const dynamicRoutes = routeCount - staticRoutes;
3562
- Object.assign(state.mergedRouteManifest, manifest.routeManifest);
3640
+ Object.assign(newMergedRouteManifest, manifest.routeManifest);
3563
3641
  let factoryOnlyPrefixes;
3564
3642
  if (router.__sourceFile) {
3565
3643
  const staticParsed = buildCombinedRouteMapForRouterFile(
@@ -3577,7 +3655,7 @@ async function discoverRouters(state, rscEnv) {
3577
3655
  }
3578
3656
  if (factoryOnlyPrefixes.size === 0) factoryOnlyPrefixes = void 0;
3579
3657
  }
3580
- state.perRouterManifests.push({
3658
+ newPerRouterManifests.push({
3581
3659
  id,
3582
3660
  routeManifest: manifest.routeManifest,
3583
3661
  routeSearchSchemas: manifest.routeSearchSchemas,
@@ -3593,16 +3671,16 @@ async function discoverRouters(state, rscEnv) {
3593
3671
  flattenLeafEntries(
3594
3672
  manifest.prefixTree,
3595
3673
  manifest.routeManifest,
3596
- state.mergedPrecomputedEntries
3674
+ newMergedPrecomputedEntries
3597
3675
  );
3598
- state.perRouterManifestDataMap.set(id, manifest.routeManifest);
3676
+ newPerRouterManifestDataMap.set(id, manifest.routeManifest);
3599
3677
  const routerPrecomputed = [];
3600
3678
  flattenLeafEntries(
3601
3679
  manifest.prefixTree,
3602
3680
  manifest.routeManifest,
3603
3681
  routerPrecomputed
3604
3682
  );
3605
- state.perRouterPrecomputedMap.set(id, routerPrecomputed);
3683
+ newPerRouterPrecomputedMap.set(id, routerPrecomputed);
3606
3684
  console.log(
3607
3685
  `[rsc-router] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
3608
3686
  );
@@ -3617,7 +3695,8 @@ async function discoverRouters(state, rscEnv) {
3617
3695
  );
3618
3696
  }
3619
3697
  }
3620
- if (state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0) {
3698
+ let newMergedRouteTrie = null;
3699
+ if (Object.keys(newMergedRouteManifest).length > 0) {
3621
3700
  const buildRouteTrie = buildMod.buildRouteTrie;
3622
3701
  if (buildRouteTrie && mergedRouteAncestry) {
3623
3702
  const routeToStaticPrefix = {};
@@ -3647,8 +3726,8 @@ async function discoverRouters(state, rscEnv) {
3647
3726
  Object.assign(mergedResponseTypeRoutes, manifest.responseTypeRoutes);
3648
3727
  }
3649
3728
  }
3650
- state.mergedRouteTrie = buildRouteTrie(
3651
- state.mergedRouteManifest,
3729
+ newMergedRouteTrie = buildRouteTrie(
3730
+ newMergedRouteManifest,
3652
3731
  mergedRouteAncestry,
3653
3732
  routeToStaticPrefix,
3654
3733
  Object.keys(mergedRouteTrailingSlash).length > 0 ? mergedRouteTrailingSlash : void 0,
@@ -3675,10 +3754,17 @@ async function discoverRouters(state, rscEnv) {
3675
3754
  perRouterPassthroughNames && perRouterPassthroughNames.size > 0 ? perRouterPassthroughNames : void 0,
3676
3755
  manifest.responseTypeRoutes && Object.keys(manifest.responseTypeRoutes).length > 0 ? manifest.responseTypeRoutes : void 0
3677
3756
  );
3678
- state.perRouterTrieMap.set(id, perRouterTrie);
3757
+ newPerRouterTrieMap.set(id, perRouterTrie);
3679
3758
  }
3680
3759
  }
3681
3760
  }
3761
+ state.mergedRouteManifest = newMergedRouteManifest;
3762
+ state.mergedPrecomputedEntries = newMergedPrecomputedEntries;
3763
+ state.perRouterManifests = newPerRouterManifests;
3764
+ state.perRouterManifestDataMap = newPerRouterManifestDataMap;
3765
+ state.perRouterPrecomputedMap = newPerRouterPrecomputedMap;
3766
+ state.perRouterTrieMap = newPerRouterTrieMap;
3767
+ state.mergedRouteTrie = newMergedRouteTrie;
3682
3768
  await expandPrerenderRoutes(state, rscEnv, registry, allManifests);
3683
3769
  await renderStaticHandlers(state, rscEnv, registry);
3684
3770
  return serverMod;
@@ -3690,7 +3776,7 @@ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsS
3690
3776
  function filterUserNamedRoutes(manifest) {
3691
3777
  const filtered = {};
3692
3778
  for (const [name, pattern] of Object.entries(manifest)) {
3693
- if (!name.startsWith("$")) {
3779
+ if (!isAutoGeneratedRouteName(name)) {
3694
3780
  filtered[name] = pattern;
3695
3781
  }
3696
3782
  }
@@ -3813,7 +3899,7 @@ function supplementGenFilesWithRuntimeRoutes(state) {
3813
3899
  ...staticParsed.searchSchemas
3814
3900
  };
3815
3901
  for (const [name, pattern] of Object.entries(routeManifest)) {
3816
- if (name.startsWith("$")) continue;
3902
+ if (isAutoGeneratedRouteName(name)) continue;
3817
3903
  const dotIdx = name.indexOf(".");
3818
3904
  if (dotIdx <= 0) continue;
3819
3905
  const prefix = name.substring(0, dotIdx + 1);
@@ -3858,7 +3944,7 @@ function generateRoutesManifestModule(state) {
3858
3944
  const genPath = join4(
3859
3945
  routerDir,
3860
3946
  `${routerBasename}.named-routes.gen.js`
3861
- );
3947
+ ).replaceAll("\\", "/");
3862
3948
  const varName = `_r${varIdx++}`;
3863
3949
  genFileImports.push(
3864
3950
  `import { NamedRoutes as ${varName} } from ${JSON.stringify(genPath)};`
@@ -3952,7 +4038,10 @@ function generatePerRouterModule(state, routerId) {
3952
4038
  /\.(tsx?|jsx?)$/,
3953
4039
  ""
3954
4040
  );
3955
- const genPath = join4(routerDir, `${routerBasename}.named-routes.gen.js`);
4041
+ const genPath = join4(
4042
+ routerDir,
4043
+ `${routerBasename}.named-routes.gen.js`
4044
+ ).replaceAll("\\", "/");
3956
4045
  lines.push(`import { NamedRoutes as _r } from ${JSON.stringify(genPath)};`);
3957
4046
  lines.push(
3958
4047
  `function __flat(r) { const o = {}; for (const [k, v] of Object.entries(r)) o[k] = typeof v === "string" ? v : v.path; return o; }`
@@ -4034,7 +4123,7 @@ function postprocessBundle(state) {
4034
4123
  state.staticHandlerChunkInfo = null;
4035
4124
  if (hasPrerenderData && existsSync5(rscEntryPath)) {
4036
4125
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4037
- if (!rscCode.includes("__PRERENDER_MANIFEST")) {
4126
+ if (!rscCode.includes("__prerender-manifest.js")) {
4038
4127
  try {
4039
4128
  const assetsDir = resolve6(state.projectRoot, "dist/rsc/assets");
4040
4129
  mkdirSync(assetsDir, { recursive: true });
@@ -4089,11 +4178,11 @@ globalThis.__PRERENDER_MANIFEST = __pm;
4089
4178
  for (const [handlerId, { encoded, handles }] of Object.entries(
4090
4179
  state.staticCollectedData
4091
4180
  )) {
4092
- const contentHash = createHash4("sha256").update(encoded).digest("hex").slice(0, 8);
4093
- const assetFileName = `__st-${contentHash}.js`;
4094
- const assetPath = resolve6(assetsDir, assetFileName);
4095
4181
  const hasHandles = Object.keys(handles).length > 0;
4096
4182
  const exportValue = hasHandles ? JSON.stringify({ encoded, handles }) : JSON.stringify(encoded);
4183
+ const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
4184
+ const assetFileName = `__st-${contentHash}.js`;
4185
+ const assetPath = resolve6(assetsDir, assetFileName);
4097
4186
  const assetCode = `export default ${exportValue};
4098
4187
  `;
4099
4188
  writeFileSync3(assetPath, assetCode);
@@ -4293,34 +4382,10 @@ ${err.stack}`
4293
4382
  if (serverMod?.setManifestReadyPromise) {
4294
4383
  serverMod.setManifestReadyPromise(discoveryPromise);
4295
4384
  }
4296
- const serverModAfterDiscovery = await discoverRouters(s, rscEnv);
4297
- mainRegistry = serverModAfterDiscovery?.RouterRegistry ?? null;
4385
+ await discoverRouters(s, rscEnv);
4298
4386
  s.devServerOrigin = getDevServerOrigin();
4299
4387
  writeRouteTypesFiles(s);
4300
- if (s.mergedRouteManifest && serverMod?.setCachedManifest) {
4301
- serverMod.setCachedManifest(s.mergedRouteManifest);
4302
- }
4303
- if (s.mergedPrecomputedEntries && s.mergedPrecomputedEntries.length > 0 && serverMod?.setPrecomputedEntries) {
4304
- serverMod.setPrecomputedEntries(s.mergedPrecomputedEntries);
4305
- }
4306
- if (s.mergedRouteTrie && serverMod?.setRouteTrie) {
4307
- serverMod.setRouteTrie(s.mergedRouteTrie);
4308
- }
4309
- if (serverMod?.setRouterManifest) {
4310
- for (const [routerId, manifest] of s.perRouterManifestDataMap) {
4311
- serverMod.setRouterManifest(routerId, manifest);
4312
- }
4313
- }
4314
- if (serverMod?.setRouterTrie) {
4315
- for (const [routerId, trie] of s.perRouterTrieMap) {
4316
- serverMod.setRouterTrie(routerId, trie);
4317
- }
4318
- }
4319
- if (serverMod?.setRouterPrecomputedEntries) {
4320
- for (const [routerId, entries] of s.perRouterPrecomputedMap) {
4321
- serverMod.setRouterPrecomputedEntries(routerId, entries);
4322
- }
4323
- }
4388
+ await propagateDiscoveryState(rscEnv);
4324
4389
  } catch (err) {
4325
4390
  console.warn(
4326
4391
  `[rsc-router] Router discovery failed: ${err.message}
@@ -4334,6 +4399,38 @@ ${err.stack}`
4334
4399
  setTimeout(() => discover().then(resolve8, resolve8), 0);
4335
4400
  });
4336
4401
  let mainRegistry = null;
4402
+ const propagateDiscoveryState = async (rscEnv) => {
4403
+ const serverMod = await rscEnv.runner.import("@rangojs/router/server");
4404
+ if (!serverMod) return;
4405
+ if (serverMod.clearAllRouterData) {
4406
+ serverMod.clearAllRouterData();
4407
+ }
4408
+ mainRegistry = serverMod.RouterRegistry ?? null;
4409
+ if (s.mergedRouteManifest && serverMod.setCachedManifest) {
4410
+ serverMod.setCachedManifest(s.mergedRouteManifest);
4411
+ }
4412
+ if (s.mergedPrecomputedEntries && s.mergedPrecomputedEntries.length > 0 && serverMod.setPrecomputedEntries) {
4413
+ serverMod.setPrecomputedEntries(s.mergedPrecomputedEntries);
4414
+ }
4415
+ if (s.mergedRouteTrie && serverMod.setRouteTrie) {
4416
+ serverMod.setRouteTrie(s.mergedRouteTrie);
4417
+ }
4418
+ if (serverMod.setRouterManifest) {
4419
+ for (const [routerId, manifest] of s.perRouterManifestDataMap) {
4420
+ serverMod.setRouterManifest(routerId, manifest);
4421
+ }
4422
+ }
4423
+ if (serverMod.setRouterTrie) {
4424
+ for (const [routerId, trie] of s.perRouterTrieMap) {
4425
+ serverMod.setRouterTrie(routerId, trie);
4426
+ }
4427
+ }
4428
+ if (serverMod.setRouterPrecomputedEntries) {
4429
+ for (const [routerId, entries] of s.perRouterPrecomputedMap) {
4430
+ serverMod.setRouterPrecomputedEntries(routerId, entries);
4431
+ }
4432
+ }
4433
+ };
4337
4434
  server.middlewares.use("/__rsc_prerender", async (req, res) => {
4338
4435
  if (s.discoveryDone) await s.discoveryDone;
4339
4436
  const url = new URL(req.url || "/", "http://localhost");
@@ -4356,11 +4453,20 @@ ${err.stack}`
4356
4453
  return;
4357
4454
  }
4358
4455
  const wantIntercept = url.searchParams.get("intercept") === "1";
4456
+ const wantRouteName = url.searchParams.get("routeName");
4457
+ const wantPassthrough = url.searchParams.get("passthrough") === "1";
4359
4458
  for (const [, routerInstance] of registry) {
4360
4459
  if (!routerInstance.matchForPrerender) continue;
4361
4460
  try {
4362
- const result = await routerInstance.matchForPrerender(pathname, {});
4461
+ const result = await routerInstance.matchForPrerender(
4462
+ pathname,
4463
+ {},
4464
+ void 0,
4465
+ wantPassthrough
4466
+ );
4363
4467
  if (!result) continue;
4468
+ if (result.passthrough) continue;
4469
+ if (wantRouteName && result.routeName !== wantRouteName) continue;
4364
4470
  res.setHeader("content-type", "application/json");
4365
4471
  let payload;
4366
4472
  if (wantIntercept && result.interceptSegments?.length) {
@@ -4397,10 +4503,29 @@ ${err.stack}`
4397
4503
  const maybeHandleGeneratedRouteFileMutation = (filePath) => {
4398
4504
  if (!isGeneratedRouteFile(filePath)) return false;
4399
4505
  if (consumeSelfGenWrite(s, filePath)) return true;
4506
+ const hasRunner = !!server.environments?.rsc?.runner;
4507
+ if (!hasRunner) return true;
4400
4508
  regenerateGeneratedRouteFiles();
4401
4509
  return true;
4402
4510
  };
4403
4511
  let routeChangeTimer;
4512
+ let runtimeRediscoveryInProgress = false;
4513
+ const refreshRuntimeDiscovery = async () => {
4514
+ const rscEnv = server.environments?.rsc;
4515
+ if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
4516
+ runtimeRediscoveryInProgress = true;
4517
+ try {
4518
+ await discoverRouters(s, rscEnv);
4519
+ writeRouteTypesFiles(s);
4520
+ await propagateDiscoveryState(rscEnv);
4521
+ } catch (err) {
4522
+ console.warn(
4523
+ `[rsc-router] Runtime re-discovery failed: ${err.message}`
4524
+ );
4525
+ } finally {
4526
+ runtimeRediscoveryInProgress = false;
4527
+ }
4528
+ };
4404
4529
  const scheduleRouteRegeneration = () => {
4405
4530
  clearTimeout(routeChangeTimer);
4406
4531
  routeChangeTimer = setTimeout(() => {
@@ -4415,6 +4540,13 @@ ${err.stack}`
4415
4540
  `[rsc-router] Route regeneration error: ${err.message}`
4416
4541
  );
4417
4542
  }
4543
+ if (s.perRouterManifests.length > 0) {
4544
+ refreshRuntimeDiscovery().catch((err) => {
4545
+ console.warn(
4546
+ `[rsc-router] Runtime re-discovery error: ${err.message}`
4547
+ );
4548
+ });
4549
+ }
4418
4550
  }, 100);
4419
4551
  };
4420
4552
  const handleRouteFileChange = (filePath) => {
@@ -4441,6 +4573,8 @@ ${err.stack}`
4441
4573
  server.watcher.on("change", handleRouteFileChange);
4442
4574
  server.watcher.on("unlink", (filePath) => {
4443
4575
  if (!isGeneratedRouteFile(filePath)) return;
4576
+ const hasRunner = !!server.environments?.rsc?.runner;
4577
+ if (!hasRunner) return;
4444
4578
  regenerateGeneratedRouteFiles();
4445
4579
  });
4446
4580
  }
@@ -4699,7 +4833,7 @@ async function rango(options) {
4699
4833
  const candidates = findRouterFiles(root, filter);
4700
4834
  if (candidates.length === 1) {
4701
4835
  const abs = candidates[0];
4702
- routerRef.path = abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs;
4836
+ routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
4703
4837
  } else if (candidates.length > 1) {
4704
4838
  const list = candidates.map(
4705
4839
  (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)