@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +105 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +357 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
@@ -15,6 +15,7 @@ import {
15
15
  import ts from "typescript";
16
16
  import { generateRouteTypesSource } from "./codegen.js";
17
17
  import type { ScanFilter } from "./scan-filter.js";
18
+ import { firstCodeMatchIndex } from "./source-scan.js";
18
19
  import {
19
20
  resolveImportedVariable,
20
21
  resolveImportPath,
@@ -38,6 +39,8 @@ function countPublicRouteEntries(source: string): number {
38
39
  }
39
40
 
40
41
  const ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
42
+ // Global variant for the code-region scan (firstCodeMatchIndex sets lastIndex).
43
+ const ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
41
44
 
42
45
  function isRoutableSourceFile(name: string): boolean {
43
46
  return (
@@ -61,7 +64,7 @@ function findRouterFilesRecursive(
61
64
  entries = readdirSync(dir, { withFileTypes: true });
62
65
  } catch (err) {
63
66
  console.warn(
64
- `[rsc-router] Failed to scan directory ${dir}: ${(err as Error).message}`,
67
+ `[rango] Failed to scan directory ${dir}: ${(err as Error).message}`,
65
68
  );
66
69
  return;
67
70
  }
@@ -90,7 +93,17 @@ function findRouterFilesRecursive(
90
93
 
91
94
  try {
92
95
  const source = readFileSync(fullPath, "utf-8");
93
- if (ROUTER_CALL_PATTERN.test(source)) {
96
+ // Fast path: most files contain no `createRouter(` at all, so the cheap
97
+ // raw regex short-circuits before the code-region scan. Only a file that
98
+ // mentions the token (real call OR a comment/string mention) is rescanned
99
+ // over code regions — allocation-free, never building a stripped copy —
100
+ // so a mention inside a comment or string is not mistaken for a real
101
+ // router file (which previously triggered a spurious "Multiple routers
102
+ // found" error).
103
+ if (
104
+ ROUTER_CALL_PATTERN.test(source) &&
105
+ firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0
106
+ ) {
94
107
  routerFilesInDir.push(fullPath);
95
108
  }
96
109
  } catch {
@@ -142,7 +155,7 @@ export function findNestedRouterConflict(
142
155
 
143
156
  export function formatNestedRouterConflictError(
144
157
  conflict: { ancestor: string; nested: string },
145
- prefix = "[rsc-router]",
158
+ prefix = "[rango]",
146
159
  ): string {
147
160
  return (
148
161
  `${prefix} Nested router roots are not supported.\n` +
@@ -157,13 +170,26 @@ export function formatNestedRouterConflictError(
157
170
  // ---------------------------------------------------------------------------
158
171
 
159
172
  /**
160
- * Extract the url patterns variable from a router file using AST.
161
- * Detects two patterns:
173
+ * Result of extracting URL patterns from a router file.
174
+ * - "variable": a named variable reference (e.g., `.routes(patterns)` or `urls: patterns`)
175
+ * - "inline": an inline builder function (e.g., `.routes(({ path }) => [...])` or `urls: ({ path }) => [...]`)
176
+ */
177
+ export type UrlsExtractionResult =
178
+ | { kind: "variable"; name: string }
179
+ | { kind: "inline"; block: string };
180
+
181
+ /**
182
+ * Extract the url patterns from a router file using AST.
183
+ * Detects four patterns:
162
184
  * 1. createRouter(...).routes(variableName)
163
185
  * 2. createRouter({ urls: variableName, ... })
164
- * Returns the local variable name.
186
+ * 3. createRouter(...).routes(({ path, ... }) => [...])
187
+ * 4. createRouter({ urls: ({ path, ... }) => [...], ... })
188
+ * Returns either a variable name or an inline code block.
165
189
  */
166
- export function extractUrlsVariableFromRouter(code: string): string | null {
190
+ export function extractUrlsFromRouter(
191
+ code: string,
192
+ ): UrlsExtractionResult | null {
167
193
  const sourceFile = ts.createSourceFile(
168
194
  "router.tsx",
169
195
  code,
@@ -171,7 +197,7 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
171
197
  true,
172
198
  ts.ScriptKind.TSX,
173
199
  );
174
- let result: string | null = null;
200
+ let result: UrlsExtractionResult | null = null;
175
201
 
176
202
  function isCreateRouterCall(node: ts.Node): boolean {
177
203
  if (!ts.isCallExpression(node)) return false;
@@ -179,44 +205,108 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
179
205
  return ts.isIdentifier(callee) && callee.text === "createRouter";
180
206
  }
181
207
 
208
+ /** Check if a node is an arrow/function expression (inline builder). */
209
+ function isInlineBuilder(node: ts.Node): boolean {
210
+ return ts.isArrowFunction(node) || ts.isFunctionExpression(node);
211
+ }
212
+
213
+ /** Check if a .routes() call chains from createRouter(). */
214
+ function isRoutesOnCreateRouter(node: ts.CallExpression): boolean {
215
+ if (
216
+ !ts.isPropertyAccessExpression(node.expression) ||
217
+ node.expression.name.text !== "routes"
218
+ )
219
+ return false;
220
+ let inner: ts.Expression = node.expression.expression;
221
+ while (
222
+ ts.isCallExpression(inner) &&
223
+ ts.isPropertyAccessExpression(inner.expression)
224
+ ) {
225
+ inner = inner.expression.expression;
226
+ }
227
+ return isCreateRouterCall(inner);
228
+ }
229
+
182
230
  function visit(node: ts.Node) {
183
231
  if (result) return;
184
232
 
185
- // Pattern 1: createRouter(...).routes(variableName)
186
- // The AST shape is CallExpression(.routes) -> PropertyAccessExpression -> CallExpression(createRouter)
233
+ // Pattern 1 & 3: createRouter(...).routes(variableName | builder)
187
234
  if (
188
235
  ts.isCallExpression(node) &&
189
- ts.isPropertyAccessExpression(node.expression) &&
190
- node.expression.name.text === "routes" &&
191
236
  node.arguments.length >= 1 &&
192
- ts.isIdentifier(node.arguments[0])
237
+ isRoutesOnCreateRouter(node)
193
238
  ) {
194
- // Walk up the chain: createRouter().middleware(...).routes(x) etc.
195
- // The innermost call should be createRouter(...)
196
- let inner: ts.Expression = node.expression.expression;
197
- while (
198
- ts.isCallExpression(inner) &&
199
- ts.isPropertyAccessExpression(inner.expression)
200
- ) {
201
- inner = inner.expression.expression;
202
- }
203
- if (isCreateRouterCall(inner)) {
204
- result = (node.arguments[0] as ts.Identifier).text;
205
- return;
239
+ const arg = node.arguments[0];
240
+ if (ts.isIdentifier(arg)) {
241
+ result = { kind: "variable", name: arg.text };
242
+ } else if (isInlineBuilder(arg)) {
243
+ result = { kind: "inline", block: arg.getText(sourceFile) };
206
244
  }
245
+ return;
207
246
  }
208
247
 
209
- // Pattern 2: createRouter({ urls: variableName, ... })
248
+ // Pattern 2 & 4: createRouter({ urls: variableName | builder, ... })
210
249
  if (isCreateRouterCall(node)) {
211
250
  const callExpr = node as ts.CallExpression;
212
- for (const arg of callExpr.arguments) {
251
+ for (const callArg of callExpr.arguments) {
252
+ if (ts.isObjectLiteralExpression(callArg)) {
253
+ for (const prop of callArg.properties) {
254
+ if (
255
+ ts.isPropertyAssignment(prop) &&
256
+ ts.isIdentifier(prop.name) &&
257
+ prop.name.text === "urls"
258
+ ) {
259
+ if (ts.isIdentifier(prop.initializer)) {
260
+ result = { kind: "variable", name: prop.initializer.text };
261
+ } else if (isInlineBuilder(prop.initializer)) {
262
+ result = {
263
+ kind: "inline",
264
+ block: prop.initializer.getText(sourceFile),
265
+ };
266
+ }
267
+ return;
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ ts.forEachChild(node, visit);
275
+ }
276
+
277
+ visit(sourceFile);
278
+ return result;
279
+ }
280
+
281
+ /**
282
+ * Extract the `basename` string literal from createRouter({ basename: "..." }).
283
+ * Returns the basename value or undefined if not present.
284
+ */
285
+ export function extractBasenameFromRouter(code: string): string | undefined {
286
+ const sourceFile = ts.createSourceFile(
287
+ "router.tsx",
288
+ code,
289
+ ts.ScriptTarget.Latest,
290
+ true,
291
+ ts.ScriptKind.TSX,
292
+ );
293
+ let result: string | undefined;
294
+
295
+ function visit(node: ts.Node) {
296
+ if (result !== undefined) return;
297
+ if (
298
+ ts.isCallExpression(node) &&
299
+ ts.isIdentifier(node.expression) &&
300
+ node.expression.text === "createRouter"
301
+ ) {
302
+ for (const arg of node.arguments) {
213
303
  if (ts.isObjectLiteralExpression(arg)) {
214
304
  for (const prop of arg.properties) {
215
305
  if (
216
306
  ts.isPropertyAssignment(prop) &&
217
307
  ts.isIdentifier(prop.name) &&
218
- prop.name.text === "urls" &&
219
- ts.isIdentifier(prop.initializer)
308
+ prop.name.text === "basename" &&
309
+ ts.isStringLiteral(prop.initializer)
220
310
  ) {
221
311
  result = prop.initializer.text;
222
312
  return;
@@ -225,7 +315,6 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
225
315
  }
226
316
  }
227
317
  }
228
-
229
318
  ts.forEachChild(node, visit);
230
319
  }
231
320
 
@@ -233,9 +322,64 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
233
322
  return result;
234
323
  }
235
324
 
325
+ /** Apply a basename prefix to all route patterns in a result set. */
326
+ function applyBasenameToRoutes(
327
+ result: {
328
+ routes: Record<string, string>;
329
+ searchSchemas: Record<string, Record<string, string>>;
330
+ },
331
+ basename: string,
332
+ ): {
333
+ routes: Record<string, string>;
334
+ searchSchemas: Record<string, Record<string, string>>;
335
+ } {
336
+ const prefixed: Record<string, string> = {};
337
+ for (const [name, pattern] of Object.entries(result.routes)) {
338
+ if (pattern === "/") {
339
+ prefixed[name] = basename;
340
+ } else if (basename.endsWith("/") && pattern.startsWith("/")) {
341
+ prefixed[name] = basename + pattern.slice(1);
342
+ } else {
343
+ prefixed[name] = basename + pattern;
344
+ }
345
+ }
346
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
347
+ }
348
+
349
+ // Filesystem path of the generated route-types file for a router source file.
350
+ // Native separators — matches the self-gen-tracking Map key the watcher compares.
351
+ export function genFileTsPath(sourceFile: string): string {
352
+ const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
353
+ return join(dirname(sourceFile), `${base}.named-routes.gen.ts`);
354
+ }
355
+
356
+ // Search schemas for the gen file: prefer the runtime manifest's; when it omits
357
+ // them (some module-runner flows) fall back to static parsing filtered to the
358
+ // public route-name set. Returns the runtime value unchanged otherwise.
359
+ export function resolveSearchSchemas(
360
+ publicRouteNames: string[],
361
+ runtimeSchemas: Record<string, Record<string, string>> | undefined,
362
+ sourceFile: string,
363
+ ): Record<string, Record<string, string>> | undefined {
364
+ if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
365
+ return runtimeSchemas;
366
+ }
367
+ const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
368
+ if (Object.keys(staticParsed.searchSchemas).length === 0) {
369
+ return runtimeSchemas;
370
+ }
371
+ const filtered: Record<string, Record<string, string>> = {};
372
+ for (const name of publicRouteNames) {
373
+ const schema = staticParsed.searchSchemas[name];
374
+ if (schema) filtered[name] = schema;
375
+ }
376
+ return Object.keys(filtered).length > 0 ? filtered : runtimeSchemas;
377
+ }
378
+
236
379
  /**
237
380
  * Resolve routes and search schemas from a router source file by following the
238
- * variable passed to `.routes(...)` or `urls: ...` in createRouter options.
381
+ * variable passed to `.routes(...)` or `urls: ...` in createRouter options,
382
+ * or by parsing an inline builder function directly.
239
383
  */
240
384
  export function buildCombinedRouteMapForRouterFile(routerFilePath: string): {
241
385
  routes: Record<string, string>;
@@ -248,21 +392,54 @@ export function buildCombinedRouteMapForRouterFile(routerFilePath: string): {
248
392
  return { routes: {}, searchSchemas: {} };
249
393
  }
250
394
 
251
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
252
- if (!urlsVarName) {
395
+ const extraction = extractUrlsFromRouter(routerSource);
396
+ if (!extraction) {
253
397
  return { routes: {}, searchSchemas: {} };
254
398
  }
255
399
 
256
- const imported = resolveImportedVariable(routerSource, urlsVarName);
257
- if (imported) {
258
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
259
- if (!targetFile) {
260
- return { routes: {}, searchSchemas: {} };
400
+ // Detect basename from createRouter({ basename: "..." })
401
+ const rawBasename = extractBasenameFromRouter(routerSource);
402
+ const basename = rawBasename
403
+ ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "")
404
+ : undefined;
405
+
406
+ let result: {
407
+ routes: Record<string, string>;
408
+ searchSchemas: Record<string, Record<string, string>>;
409
+ };
410
+
411
+ // Inline builder: extract routes directly from the function body
412
+ if (extraction.kind === "inline") {
413
+ result = buildCombinedRouteMapWithSearch(
414
+ routerFilePath,
415
+ undefined,
416
+ undefined,
417
+ undefined,
418
+ extraction.block,
419
+ );
420
+ } else {
421
+ // Variable reference: follow imports or same-file declaration
422
+ const imported = resolveImportedVariable(routerSource, extraction.name);
423
+ if (imported) {
424
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
425
+ if (!targetFile) {
426
+ return { routes: {}, searchSchemas: {} };
427
+ }
428
+ result = buildCombinedRouteMapWithSearch(
429
+ targetFile,
430
+ imported.exportedName,
431
+ );
432
+ } else {
433
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
261
434
  }
262
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
263
435
  }
264
436
 
265
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
437
+ // Apply basename prefix to all extracted route patterns
438
+ if (basename) {
439
+ result = applyBasenameToRoutes(result, basename);
440
+ }
441
+
442
+ return result;
266
443
  }
267
444
 
268
445
  // ---------------------------------------------------------------------------
@@ -285,12 +462,26 @@ export function detectUnresolvableIncludes(
285
462
  return [];
286
463
  }
287
464
 
288
- // Extract the urls variable from the router file
289
- const urlsVarName = extractUrlsVariableFromRouter(source);
290
- if (!urlsVarName) return [];
465
+ // Extract the urls source from the router file
466
+ const extraction = extractUrlsFromRouter(source);
467
+ if (!extraction) return [];
291
468
 
292
- // Resolve where the urls variable comes from
293
- const imported = resolveImportedVariable(source, urlsVarName);
469
+ const diagnostics: UnresolvableInclude[] = [];
470
+
471
+ if (extraction.kind === "inline") {
472
+ // Inline builder: parse directly
473
+ buildCombinedRouteMapWithSearch(
474
+ realPath,
475
+ undefined,
476
+ new Set(),
477
+ diagnostics,
478
+ extraction.block,
479
+ );
480
+ return diagnostics;
481
+ }
482
+
483
+ // Variable reference: resolve where it comes from
484
+ const imported = resolveImportedVariable(source, extraction.name);
294
485
  let targetFile: string;
295
486
  let exportedName: string | undefined;
296
487
 
@@ -312,10 +503,9 @@ export function detectUnresolvableIncludes(
312
503
  } else {
313
504
  // Same-file urls() definition
314
505
  targetFile = realPath;
315
- exportedName = urlsVarName;
506
+ exportedName = extraction.name;
316
507
  }
317
508
 
318
- const diagnostics: UnresolvableInclude[] = [];
319
509
  buildCombinedRouteMapWithSearch(
320
510
  targetFile,
321
511
  exportedName,
@@ -375,7 +565,10 @@ export function findRouterFiles(root: string, filter?: ScanFilter): string[] {
375
565
  export function writeCombinedRouteTypes(
376
566
  root: string,
377
567
  knownRouterFiles?: string[],
378
- opts?: { preserveIfLarger?: boolean },
568
+ opts?: {
569
+ preserveIfLarger?: boolean;
570
+ onWrite?: (outPath: string, content: string) => void;
571
+ },
379
572
  ): void {
380
573
  // Delete old combined named-routes.gen.ts if it exists (stale from older versions)
381
574
  try {
@@ -383,7 +576,7 @@ export function writeCombinedRouteTypes(
383
576
  if (existsSync(oldCombinedPath)) {
384
577
  unlinkSync(oldCombinedPath);
385
578
  console.log(
386
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`,
579
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`,
387
580
  );
388
581
  }
389
582
  } catch {}
@@ -397,54 +590,31 @@ export function writeCombinedRouteTypes(
397
590
  }
398
591
 
399
592
  for (const routerFilePath of routerFilePaths) {
400
- let routerSource: string;
401
- try {
402
- routerSource = readFileSync(routerFilePath, "utf-8");
403
- } catch {
404
- continue;
405
- }
406
- // Extract the urls variable name from .routes(varName) or urls: varName
407
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
408
- if (!urlsVarName) continue;
409
-
410
- // Resolve the variable to its source module
411
- let result: {
412
- routes: Record<string, string>;
413
- searchSchemas: Record<string, Record<string, string>>;
414
- };
415
-
416
- const imported = resolveImportedVariable(routerSource, urlsVarName);
417
- if (imported) {
418
- // Variable is imported from another module
419
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
420
- if (!targetFile) continue;
421
- result = buildCombinedRouteMapWithSearch(
422
- targetFile,
423
- imported.exportedName,
424
- );
425
- } else {
426
- // Variable is defined in the same file
427
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
593
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
594
+ if (
595
+ Object.keys(result.routes).length === 0 &&
596
+ Object.keys(result.searchSchemas).length === 0
597
+ ) {
598
+ // Check if the file even has a createRouter call — if not, skip entirely.
599
+ // If it does, fall through to write an empty placeholder below.
600
+ let routerSource: string;
601
+ try {
602
+ routerSource = readFileSync(routerFilePath, "utf-8");
603
+ } catch {
604
+ continue;
605
+ }
606
+ if (!extractUrlsFromRouter(routerSource)) continue;
428
607
  }
429
608
 
430
- const routerBasename = pathBasename(routerFilePath).replace(
431
- /\.(tsx?|jsx?)$/,
432
- "",
433
- );
434
- const outPath = join(
435
- dirname(routerFilePath),
436
- `${routerBasename}.named-routes.gen.ts`,
437
- );
609
+ const outPath = genFileTsPath(routerFilePath);
438
610
  const existing = existsSync(outPath)
439
611
  ? readFileSync(outPath, "utf-8")
440
612
  : null;
441
613
 
442
- // When the static parser can't extract routes (e.g. callback-style urls()),
443
- // write an empty placeholder so the build-time transform's injected import
444
- // resolves. Runtime discovery will overwrite this with the real routes.
445
614
  if (Object.keys(result.routes).length === 0) {
446
615
  if (!existing) {
447
616
  const emptySource = generateRouteTypesSource({});
617
+ opts?.onWrite?.(outPath, emptySource);
448
618
  writeFileSync(outPath, emptySource);
449
619
  }
450
620
  continue;
@@ -456,11 +626,6 @@ export function writeCombinedRouteTypes(
456
626
  hasSearchSchemas ? result.searchSchemas : undefined,
457
627
  );
458
628
  if (existing !== source) {
459
- // On initial dev startup, don't overwrite a file from runtime discovery
460
- // (which has all dynamic routes) with a smaller set from the static
461
- // parser. The static parser can't see routes generated by Array.from()
462
- // or other dynamic code. During HMR (file watcher), always write so
463
- // newly added routes appear immediately.
464
629
  if (opts?.preserveIfLarger && existing) {
465
630
  const existingCount = countPublicRouteEntries(existing);
466
631
  const newCount = Object.keys(result.routes).filter(
@@ -470,9 +635,10 @@ export function writeCombinedRouteTypes(
470
635
  continue;
471
636
  }
472
637
  }
638
+ opts?.onWrite?.(outPath, source);
473
639
  writeFileSync(outPath, source);
474
640
  console.log(
475
- `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`,
641
+ `[rango] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`,
476
642
  );
477
643
  }
478
644
  }
@@ -54,14 +54,21 @@ export function findTsFiles(dir: string, filter?: ScanFilter): string[] {
54
54
  entries = readdirSync(dir, { withFileTypes: true });
55
55
  } catch (err) {
56
56
  console.warn(
57
- `[rsc-router] Failed to scan directory ${dir}: ${(err as Error).message}`,
57
+ `[rango] Failed to scan directory ${dir}: ${(err as Error).message}`,
58
58
  );
59
59
  return results;
60
60
  }
61
61
  for (const entry of entries) {
62
62
  const fullPath = join(dir, entry.name);
63
63
  if (entry.isDirectory()) {
64
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
64
+ if (
65
+ entry.name === "node_modules" ||
66
+ entry.name.startsWith(".") ||
67
+ entry.name === "dist" ||
68
+ entry.name === "build" ||
69
+ entry.name === "coverage"
70
+ )
71
+ continue;
65
72
  results.push(...findTsFiles(fullPath, filter));
66
73
  } else if (
67
74
  (entry.name.endsWith(".ts") ||