@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1c0bdfad

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 (406) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +291 -61
  3. package/dist/bin/rango.js +544 -143
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3744 -1329
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +67 -13
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +312 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +247 -23
  12. package/skills/caching/SKILL.md +322 -19
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/debug-manifest/SKILL.md +4 -2
  16. package/skills/document-cache/SKILL.md +78 -55
  17. package/skills/handler-use/SKILL.md +364 -0
  18. package/skills/hooks/SKILL.md +282 -60
  19. package/skills/host-router/SKILL.md +278 -0
  20. package/skills/i18n/SKILL.md +276 -0
  21. package/skills/intercept/SKILL.md +50 -6
  22. package/skills/layout/SKILL.md +35 -9
  23. package/skills/links/SKILL.md +249 -17
  24. package/skills/loader/SKILL.md +297 -31
  25. package/skills/middleware/SKILL.md +52 -13
  26. package/skills/migrate-nextjs/SKILL.md +584 -0
  27. package/skills/migrate-react-router/SKILL.md +771 -0
  28. package/skills/mime-routes/SKILL.md +28 -1
  29. package/skills/observability/SKILL.md +172 -0
  30. package/skills/parallel/SKILL.md +203 -7
  31. package/skills/prerender/SKILL.md +155 -111
  32. package/skills/rango/SKILL.md +251 -23
  33. package/skills/react-compiler/SKILL.md +168 -0
  34. package/skills/response-routes/SKILL.md +123 -48
  35. package/skills/route/SKILL.md +104 -9
  36. package/skills/router-setup/SKILL.md +124 -11
  37. package/skills/scripts/SKILL.md +179 -0
  38. package/skills/server-actions/SKILL.md +775 -0
  39. package/skills/streams-and-websockets/SKILL.md +283 -0
  40. package/skills/tailwind/SKILL.md +27 -3
  41. package/skills/testing/SKILL.md +125 -222
  42. package/skills/testing/bindings.md +103 -0
  43. package/skills/testing/cache-prerender.md +127 -0
  44. package/skills/testing/client-components.md +124 -0
  45. package/skills/testing/e2e-parity.md +125 -0
  46. package/skills/testing/flight.md +91 -0
  47. package/skills/testing/handles.md +129 -0
  48. package/skills/testing/loader.md +128 -0
  49. package/skills/testing/middleware.md +99 -0
  50. package/skills/testing/render-handler.md +121 -0
  51. package/skills/testing/response-routes.md +95 -0
  52. package/skills/testing/reverse-and-types.md +84 -0
  53. package/skills/testing/server-actions.md +107 -0
  54. package/skills/testing/server-tree.md +128 -0
  55. package/skills/testing/setup.md +123 -0
  56. package/skills/typesafety/SKILL.md +357 -52
  57. package/skills/use-cache/SKILL.md +46 -14
  58. package/skills/view-transitions/SKILL.md +294 -0
  59. package/src/__augment-tests__/augment.ts +81 -0
  60. package/src/__augment-tests__/augmented.check.ts +116 -0
  61. package/src/__internal.ts +67 -40
  62. package/src/bin/rango.ts +18 -0
  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 +197 -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/link-interceptor.ts +4 -0
  74. package/src/browser/navigation-bridge.ts +200 -30
  75. package/src/browser/navigation-client.ts +217 -58
  76. package/src/browser/navigation-store-handle.ts +38 -0
  77. package/src/browser/navigation-store.ts +76 -67
  78. package/src/browser/navigation-transaction.ts +18 -66
  79. package/src/browser/network-error-handler.ts +34 -7
  80. package/src/browser/partial-update.ts +187 -112
  81. package/src/browser/prefetch/cache.ts +312 -30
  82. package/src/browser/prefetch/fetch.ts +344 -47
  83. package/src/browser/prefetch/policy.ts +6 -0
  84. package/src/browser/prefetch/queue.ts +126 -20
  85. package/src/browser/prefetch/resource-ready.ts +77 -0
  86. package/src/browser/rango-state.ts +158 -76
  87. package/src/browser/react/Link.tsx +125 -18
  88. package/src/browser/react/NavigationProvider.tsx +135 -120
  89. package/src/browser/react/ScrollRestoration.tsx +10 -6
  90. package/src/browser/react/context.ts +7 -2
  91. package/src/browser/react/filter-segment-order.ts +66 -7
  92. package/src/browser/react/index.ts +0 -48
  93. package/src/browser/react/location-state-shared.ts +178 -8
  94. package/src/browser/react/location-state.ts +39 -14
  95. package/src/browser/react/use-action.ts +6 -15
  96. package/src/browser/react/use-handle.ts +23 -69
  97. package/src/browser/react/use-href.tsx +8 -1
  98. package/src/browser/react/use-link-status.ts +33 -8
  99. package/src/browser/react/use-navigation.ts +32 -7
  100. package/src/browser/react/use-params.ts +20 -10
  101. package/src/browser/react/use-reverse.ts +106 -0
  102. package/src/browser/react/use-router.ts +46 -11
  103. package/src/browser/react/use-search-params.ts +0 -5
  104. package/src/browser/react/use-segments.ts +11 -21
  105. package/src/browser/response-adapter.ts +80 -5
  106. package/src/browser/rsc-router.tsx +226 -75
  107. package/src/browser/scroll-restoration.ts +54 -42
  108. package/src/browser/segment-reconciler.ts +36 -9
  109. package/src/browser/segment-structure-assert.ts +2 -2
  110. package/src/browser/server-action-bridge.ts +619 -442
  111. package/src/browser/types.ts +115 -11
  112. package/src/browser/validate-redirect-origin.ts +43 -16
  113. package/src/build/collect-fallback-refs.ts +107 -0
  114. package/src/build/generate-manifest.ts +65 -40
  115. package/src/build/generate-route-types.ts +7 -1
  116. package/src/build/index.ts +8 -2
  117. package/src/build/prefix-tree-utils.ts +123 -0
  118. package/src/build/route-trie.ts +182 -37
  119. package/src/build/route-types/ast-route-extraction.ts +15 -8
  120. package/src/build/route-types/codegen.ts +16 -5
  121. package/src/build/route-types/include-resolution.ts +125 -24
  122. package/src/build/route-types/param-extraction.ts +6 -3
  123. package/src/build/route-types/per-module-writer.ts +22 -6
  124. package/src/build/route-types/router-processing.ts +392 -106
  125. package/src/build/route-types/scan-filter.ts +9 -2
  126. package/src/build/route-types/source-scan.ts +216 -0
  127. package/src/build/runtime-discovery.ts +9 -20
  128. package/src/cache/cache-error.ts +104 -0
  129. package/src/cache/cache-key-utils.ts +29 -13
  130. package/src/cache/cache-policy.ts +108 -34
  131. package/src/cache/cache-runtime.ts +214 -48
  132. package/src/cache/cache-scope.ts +236 -89
  133. package/src/cache/cache-tag.ts +103 -0
  134. package/src/cache/cf/cf-base64.ts +33 -0
  135. package/src/cache/cf/cf-cache-constants.ts +127 -0
  136. package/src/cache/cf/cf-cache-store.ts +2224 -171
  137. package/src/cache/cf/cf-cache-types.ts +349 -0
  138. package/src/cache/cf/cf-kv-utils.ts +46 -0
  139. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  140. package/src/cache/cf/index.ts +11 -17
  141. package/src/cache/document-cache.ts +89 -27
  142. package/src/cache/handle-snapshot.ts +70 -0
  143. package/src/cache/index.ts +11 -20
  144. package/src/cache/memory-segment-store.ts +136 -37
  145. package/src/cache/profile-registry.ts +31 -31
  146. package/src/cache/read-through-swr.ts +41 -11
  147. package/src/cache/segment-codec.ts +9 -17
  148. package/src/cache/tag-invalidation.ts +230 -0
  149. package/src/cache/taint.ts +55 -0
  150. package/src/cache/types.ts +37 -100
  151. package/src/client.rsc.tsx +45 -21
  152. package/src/client.tsx +120 -336
  153. package/src/cloudflare/index.ts +11 -0
  154. package/src/cloudflare/tracing.ts +109 -0
  155. package/src/component-utils.ts +19 -0
  156. package/src/components/DefaultDocument.tsx +8 -2
  157. package/src/context-var.ts +84 -2
  158. package/src/debug.ts +2 -2
  159. package/src/decode-loader-results.ts +52 -0
  160. package/src/defer.ts +196 -0
  161. package/src/deps/ssr.ts +0 -1
  162. package/src/encode-kv.ts +49 -0
  163. package/src/errors.ts +30 -4
  164. package/src/escape-script.ts +52 -0
  165. package/src/handle.ts +70 -22
  166. package/src/handles/MetaTags.tsx +56 -19
  167. package/src/handles/Scripts.tsx +183 -0
  168. package/src/handles/breadcrumbs.ts +95 -0
  169. package/src/handles/is-thenable.ts +19 -0
  170. package/src/handles/meta.ts +51 -40
  171. package/src/handles/script.ts +244 -0
  172. package/src/host/cookie-handler.ts +9 -60
  173. package/src/host/errors.ts +0 -24
  174. package/src/host/index.ts +8 -5
  175. package/src/host/pattern-matcher.ts +23 -52
  176. package/src/host/router.ts +107 -99
  177. package/src/host/testing.ts +40 -27
  178. package/src/host/types.ts +37 -4
  179. package/src/host/utils.ts +1 -1
  180. package/src/href-client.ts +137 -22
  181. package/src/index.rsc.ts +79 -29
  182. package/src/index.ts +149 -65
  183. package/src/internal-debug.ts +11 -10
  184. package/src/loader-store.ts +500 -0
  185. package/src/loader.rsc.ts +20 -13
  186. package/src/loader.ts +12 -11
  187. package/src/missing-id-error.ts +68 -0
  188. package/src/outlet-context.ts +1 -1
  189. package/src/outlet-provider.tsx +1 -5
  190. package/src/prerender/param-hash.ts +16 -16
  191. package/src/prerender/store.ts +63 -26
  192. package/src/prerender.ts +198 -82
  193. package/src/redirect-origin.ts +100 -0
  194. package/src/regex-escape.ts +8 -0
  195. package/src/render-error-thrower.tsx +20 -0
  196. package/src/response-utils.ts +62 -0
  197. package/src/reverse.ts +65 -15
  198. package/src/root-error-boundary.tsx +1 -19
  199. package/src/route-content-wrapper.tsx +7 -72
  200. package/src/route-definition/dsl-helpers.ts +469 -276
  201. package/src/route-definition/helper-factories.ts +29 -139
  202. package/src/route-definition/helpers-types.ts +113 -37
  203. package/src/route-definition/index.ts +3 -3
  204. package/src/route-definition/redirect.ts +53 -12
  205. package/src/route-definition/resolve-handler-use.ts +161 -0
  206. package/src/route-definition/use-item-types.ts +32 -0
  207. package/src/route-map-builder.ts +7 -17
  208. package/src/route-types.ts +37 -41
  209. package/src/router/basename.ts +14 -0
  210. package/src/router/content-negotiation.ts +164 -17
  211. package/src/router/error-handling.ts +45 -18
  212. package/src/router/find-match.ts +45 -22
  213. package/src/router/handler-context.ts +110 -39
  214. package/src/router/instrument.ts +350 -0
  215. package/src/router/intercept-resolution.ts +50 -24
  216. package/src/router/lazy-includes.ts +19 -53
  217. package/src/router/loader-resolution.ts +274 -56
  218. package/src/router/logging.ts +5 -8
  219. package/src/router/manifest.ts +49 -45
  220. package/src/router/match-api.ts +121 -205
  221. package/src/router/match-context.ts +0 -22
  222. package/src/router/match-handlers.ts +58 -58
  223. package/src/router/match-middleware/background-revalidation.ts +33 -6
  224. package/src/router/match-middleware/cache-lookup.ts +214 -263
  225. package/src/router/match-middleware/cache-store.ts +73 -33
  226. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  227. package/src/router/match-middleware/segment-resolution.ts +52 -18
  228. package/src/router/match-pipelines.ts +1 -42
  229. package/src/router/match-result.ts +104 -49
  230. package/src/router/metrics.ts +217 -26
  231. package/src/router/middleware-types.ts +24 -110
  232. package/src/router/middleware.ts +384 -197
  233. package/src/router/navigation-snapshot.ts +131 -0
  234. package/src/router/params-util.ts +23 -0
  235. package/src/router/pattern-matching.ts +148 -91
  236. package/src/router/prefetch-cache-ttl.ts +51 -0
  237. package/src/router/prerender-match.ts +199 -56
  238. package/src/router/preview-match.ts +32 -102
  239. package/src/router/request-classification.ts +276 -0
  240. package/src/router/revalidation.ts +144 -74
  241. package/src/router/route-snapshot.ts +244 -0
  242. package/src/router/router-context.ts +8 -28
  243. package/src/router/router-interfaces.ts +129 -36
  244. package/src/router/router-options.ts +185 -23
  245. package/src/router/router-registry.ts +2 -5
  246. package/src/router/segment-resolution/fresh.ts +281 -76
  247. package/src/router/segment-resolution/helpers.ts +116 -31
  248. package/src/router/segment-resolution/loader-cache.ts +63 -37
  249. package/src/router/segment-resolution/revalidation.ts +493 -391
  250. package/src/router/segment-resolution/static-store.ts +19 -5
  251. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  252. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  253. package/src/router/segment-resolution.ts +5 -1
  254. package/src/router/segment-wrappers.ts +8 -5
  255. package/src/router/state-cookie-name.ts +33 -0
  256. package/src/router/substitute-pattern-params.ts +56 -0
  257. package/src/router/telemetry-otel.ts +161 -199
  258. package/src/router/telemetry.ts +96 -19
  259. package/src/router/timeout.ts +0 -20
  260. package/src/router/tracing.ts +206 -0
  261. package/src/router/trie-matching.ts +180 -58
  262. package/src/router/types.ts +10 -63
  263. package/src/router/url-params.ts +44 -0
  264. package/src/router.ts +182 -54
  265. package/src/rsc/handler-context.ts +3 -2
  266. package/src/rsc/handler.ts +702 -460
  267. package/src/rsc/helpers.ts +168 -46
  268. package/src/rsc/index.ts +2 -25
  269. package/src/rsc/json-route-result.ts +38 -0
  270. package/src/rsc/loader-fetch.ts +127 -31
  271. package/src/rsc/manifest-init.ts +33 -42
  272. package/src/rsc/origin-guard.ts +39 -25
  273. package/src/rsc/progressive-enhancement.ts +98 -19
  274. package/src/rsc/redirect-guard.ts +99 -0
  275. package/src/rsc/response-cache-serve.ts +238 -0
  276. package/src/rsc/response-error.ts +79 -12
  277. package/src/rsc/response-route-handler.ts +99 -189
  278. package/src/rsc/rsc-rendering.ts +126 -106
  279. package/src/rsc/runtime-warnings.ts +23 -10
  280. package/src/rsc/server-action.ts +269 -114
  281. package/src/rsc/ssr-setup.ts +144 -0
  282. package/src/rsc/types.ts +34 -6
  283. package/src/runtime-env.ts +18 -0
  284. package/src/search-params.ts +49 -41
  285. package/src/segment-content-promise.ts +67 -0
  286. package/src/segment-loader-promise.ts +149 -0
  287. package/src/segment-system.tsx +281 -129
  288. package/src/serialize.ts +243 -0
  289. package/src/server/context.ts +317 -63
  290. package/src/server/cookie-parse.ts +32 -0
  291. package/src/server/cookie-store.ts +80 -5
  292. package/src/server/handle-store.ts +40 -38
  293. package/src/server/loader-registry.ts +26 -46
  294. package/src/server/request-context.ts +425 -177
  295. package/src/server.ts +6 -0
  296. package/src/ssr/index.tsx +25 -16
  297. package/src/static-handler.ts +27 -18
  298. package/src/testing/cache-status.ts +162 -0
  299. package/src/testing/collect-handle.ts +40 -0
  300. package/src/testing/dispatch.ts +701 -0
  301. package/src/testing/dom.entry.ts +22 -0
  302. package/src/testing/e2e/fixture.ts +188 -0
  303. package/src/testing/e2e/index.ts +128 -0
  304. package/src/testing/e2e/matchers.ts +35 -0
  305. package/src/testing/e2e/page-helpers.ts +272 -0
  306. package/src/testing/e2e/parity.ts +387 -0
  307. package/src/testing/e2e/server.ts +195 -0
  308. package/src/testing/flight-matchers.ts +97 -0
  309. package/src/testing/flight-normalize.ts +11 -0
  310. package/src/testing/flight-runtime.d.ts +57 -0
  311. package/src/testing/flight-tree.ts +682 -0
  312. package/src/testing/flight.entry.ts +52 -0
  313. package/src/testing/flight.ts +257 -0
  314. package/src/testing/generated-routes.ts +183 -0
  315. package/src/testing/index.ts +99 -0
  316. package/src/testing/internal/context.ts +371 -0
  317. package/src/testing/internal/flight-client-globals.ts +30 -0
  318. package/src/testing/internal/seed-vars.ts +54 -0
  319. package/src/testing/render-handler.ts +343 -0
  320. package/src/testing/render-route.tsx +581 -0
  321. package/src/testing/run-loader.ts +385 -0
  322. package/src/testing/run-middleware.ts +205 -0
  323. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  324. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  325. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  326. package/src/testing/vitest-stubs/version.ts +5 -0
  327. package/src/testing/vitest.ts +305 -0
  328. package/src/theme/ThemeProvider.tsx +20 -58
  329. package/src/theme/ThemeScript.tsx +7 -9
  330. package/src/theme/constants.ts +52 -13
  331. package/src/theme/index.ts +3 -19
  332. package/src/theme/theme-context.ts +1 -5
  333. package/src/theme/theme-script.ts +22 -21
  334. package/src/theme/use-theme.ts +0 -3
  335. package/src/types/boundaries.ts +0 -35
  336. package/src/types/cache-types.ts +17 -8
  337. package/src/types/error-types.ts +30 -90
  338. package/src/types/global-namespace.ts +54 -41
  339. package/src/types/handler-context.ts +236 -88
  340. package/src/types/index.ts +1 -10
  341. package/src/types/loader-types.ts +44 -15
  342. package/src/types/request-scope.ts +112 -0
  343. package/src/types/route-config.ts +10 -45
  344. package/src/types/route-entry.ts +19 -7
  345. package/src/types/segments.ts +37 -19
  346. package/src/urls/include-helper.ts +33 -70
  347. package/src/urls/index.ts +1 -11
  348. package/src/urls/path-helper-types.ts +58 -11
  349. package/src/urls/path-helper.ts +57 -111
  350. package/src/urls/pattern-types.ts +48 -19
  351. package/src/urls/response-types.ts +25 -22
  352. package/src/urls/type-extraction.ts +58 -139
  353. package/src/urls/urls-function.ts +1 -18
  354. package/src/use-loader.tsx +346 -89
  355. package/src/vite/debug.ts +185 -0
  356. package/src/vite/discovery/bundle-postprocess.ts +64 -91
  357. package/src/vite/discovery/discover-routers.ts +147 -88
  358. package/src/vite/discovery/discovery-errors.ts +194 -0
  359. package/src/vite/discovery/gate-state.ts +171 -0
  360. package/src/vite/discovery/prerender-collection.ts +247 -145
  361. package/src/vite/discovery/route-types-writer.ts +40 -84
  362. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  363. package/src/vite/discovery/state.ts +61 -13
  364. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  365. package/src/vite/index.ts +10 -3
  366. package/src/vite/inject-client-debug.ts +36 -0
  367. package/src/vite/plugin-types.ts +155 -65
  368. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  369. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  370. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  371. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  372. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  373. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  374. package/src/vite/plugins/expose-action-id.ts +49 -98
  375. package/src/vite/plugins/expose-id-utils.ts +96 -51
  376. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  377. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  378. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  379. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  380. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  381. package/src/vite/plugins/performance-tracks.ts +89 -0
  382. package/src/vite/plugins/refresh-cmd.ts +127 -0
  383. package/src/vite/plugins/use-cache-transform.ts +73 -83
  384. package/src/vite/plugins/version-injector.ts +21 -25
  385. package/src/vite/plugins/version-plugin.ts +46 -37
  386. package/src/vite/plugins/virtual-entries.ts +13 -18
  387. package/src/vite/rango.ts +241 -287
  388. package/src/vite/router-discovery.ts +956 -149
  389. package/src/vite/utils/ast-handler-extract.ts +26 -35
  390. package/src/vite/utils/banner.ts +4 -4
  391. package/src/vite/utils/bundle-analysis.ts +10 -15
  392. package/src/vite/utils/client-chunks.ts +184 -0
  393. package/src/vite/utils/directive-prologue.ts +40 -0
  394. package/src/vite/utils/forward-user-plugins.ts +171 -0
  395. package/src/vite/utils/manifest-utils.ts +4 -59
  396. package/src/vite/utils/package-resolution.ts +20 -52
  397. package/src/vite/utils/prerender-utils.ts +141 -34
  398. package/src/vite/utils/shared-utils.ts +92 -42
  399. package/CLAUDE.md +0 -5
  400. package/src/browser/action-response-classifier.ts +0 -99
  401. package/src/browser/react/use-client-cache.ts +0 -58
  402. package/src/browser/shallow.ts +0 -40
  403. package/src/handles/index.ts +0 -6
  404. package/src/network-error-thrower.tsx +0 -23
  405. package/src/route-definition/route-function.ts +0 -119
  406. package/src/router/middleware-cookies.ts +0 -55
package/dist/bin/rango.js CHANGED
@@ -22,10 +22,10 @@ function extractParamsFromPattern(pattern) {
22
22
  function formatRouteEntry(key, pattern, _params, search) {
23
23
  const hasSearch = search && Object.keys(search).length > 0;
24
24
  if (!hasSearch) {
25
- return ` ${key}: "${pattern}",`;
25
+ return ` ${key}: ${JSON.stringify(pattern)},`;
26
26
  }
27
- const searchBody = Object.entries(search).map(([k, v]) => `${k}: "${v}"`).join(", ");
28
- return ` ${key}: { path: "${pattern}", search: { ${searchBody} } },`;
27
+ const searchBody = Object.entries(search).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
28
+ return ` ${key}: { path: ${JSON.stringify(pattern)}, search: { ${searchBody} } },`;
29
29
  }
30
30
  var init_param_extraction = __esm({
31
31
  "src/build/route-types/param-extraction.ts"() {
@@ -59,8 +59,8 @@ var init_ast_helpers = __esm({
59
59
 
60
60
  // src/build/route-types/ast-route-extraction.ts
61
61
  import ts2 from "typescript";
62
- function extractRoutesFromSource(code) {
63
- const sourceFile = ts2.createSourceFile(
62
+ function extractRoutesFromSource(code, sourceFileArg) {
63
+ const sourceFile = sourceFileArg ?? ts2.createSourceFile(
64
64
  "input.tsx",
65
65
  code,
66
66
  ts2.ScriptTarget.Latest,
@@ -139,7 +139,7 @@ function generatePerModuleTypesSource(routes) {
139
139
  const valid = routes.filter(({ name }) => {
140
140
  if (!name || /["'\\`\n\r]/.test(name)) {
141
141
  console.warn(
142
- `[rsc-router] Skipping route with invalid name: ${JSON.stringify(name)}`
142
+ `[rango] Skipping route with invalid name: ${JSON.stringify(name)}`
143
143
  );
144
144
  return false;
145
145
  }
@@ -149,7 +149,7 @@ function generatePerModuleTypesSource(routes) {
149
149
  for (const { name, pattern, params, search } of valid) {
150
150
  if (deduped.has(name)) {
151
151
  console.warn(
152
- `[rsc-router] Duplicate route name "${name}" \u2014 keeping first definition`
152
+ `[rango] Duplicate route name "${name}" \u2014 keeping first definition`
153
153
  );
154
154
  continue;
155
155
  }
@@ -185,9 +185,13 @@ export const NamedRoutes = {
185
185
  ${objectBody}
186
186
  } as const;
187
187
 
188
+ // Aliased so the augmentation below does not pay a homomorphic mapped-type
189
+ // instantiation per route; \`as const\` already makes the members readonly.
190
+ type NamedRoutesShape = typeof NamedRoutes;
191
+
188
192
  declare global {
189
- namespace RSCRouter {
190
- interface GeneratedRouteMap extends Readonly<typeof NamedRoutes> {}
193
+ namespace Rango {
194
+ interface GeneratedRouteMap extends NamedRoutesShape {}
191
195
  }
192
196
  }
193
197
  `;
@@ -211,14 +215,15 @@ function findTsFiles(dir, filter) {
211
215
  entries = readdirSync(dir, { withFileTypes: true });
212
216
  } catch (err) {
213
217
  console.warn(
214
- `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
218
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
215
219
  );
216
220
  return results;
217
221
  }
218
222
  for (const entry of entries) {
219
223
  const fullPath = join(dir, entry.name);
220
224
  if (entry.isDirectory()) {
221
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
225
+ if (entry.name === "node_modules" || entry.name.startsWith(".") || entry.name === "dist" || entry.name === "build" || entry.name === "coverage")
226
+ continue;
222
227
  results.push(...findTsFiles(fullPath, filter));
223
228
  } else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx")) && !entry.name.includes(".gen.")) {
224
229
  if (filter && !filter(fullPath)) continue;
@@ -237,6 +242,33 @@ var init_scan_filter = __esm({
237
242
  import { readFileSync, existsSync } from "node:fs";
238
243
  import { dirname, resolve } from "node:path";
239
244
  import ts3 from "typescript";
245
+ function createScanMemo() {
246
+ return { files: /* @__PURE__ */ new Map(), blockSourceFiles: /* @__PURE__ */ new Map() };
247
+ }
248
+ function parseBlock(memo, block) {
249
+ if (memo) {
250
+ const cached = memo.blockSourceFiles.get(block);
251
+ if (cached) return cached;
252
+ }
253
+ const sf = ts3.createSourceFile(
254
+ "input.tsx",
255
+ block,
256
+ ts3.ScriptTarget.Latest,
257
+ true,
258
+ ts3.ScriptKind.TSX
259
+ );
260
+ if (memo) memo.blockSourceFiles.set(block, sf);
261
+ return sf;
262
+ }
263
+ function readSourceMemoized(memo, realPath) {
264
+ if (memo) {
265
+ const cached = memo.files.get(realPath);
266
+ if (cached !== void 0) return cached;
267
+ }
268
+ const source = readFileSync(realPath, "utf-8");
269
+ if (memo) memo.files.set(realPath, source);
270
+ return source;
271
+ }
240
272
  function extractNamePrefixFromInclude(node) {
241
273
  if (node.arguments.length >= 3) {
242
274
  const thirdArg = node.arguments[2];
@@ -252,8 +284,8 @@ function extractNamePrefixFromInclude(node) {
252
284
  }
253
285
  return null;
254
286
  }
255
- function extractIncludesWithDiagnostics(code) {
256
- const sourceFile = ts3.createSourceFile(
287
+ function extractIncludesWithDiagnostics(code, sourceFileArg) {
288
+ const sourceFile = sourceFileArg ?? ts3.createSourceFile(
257
289
  "input.tsx",
258
290
  code,
259
291
  ts3.ScriptTarget.Latest,
@@ -307,7 +339,7 @@ function extractIncludesWithDiagnostics(code) {
307
339
  return { resolved, unresolvable };
308
340
  }
309
341
  function resolveImportedVariable(code, localName) {
310
- const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
342
+ const importRegex = /import\s*(?:[\w$]+\s*,\s*)?\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
311
343
  let match;
312
344
  while ((match = importRegex.exec(code)) !== null) {
313
345
  const imports = match[1];
@@ -344,8 +376,8 @@ function resolveImportPath(importSpec, fromFile) {
344
376
  }
345
377
  return null;
346
378
  }
347
- function extractUrlsBlockForVariable(code, varName) {
348
- const sourceFile = ts3.createSourceFile(
379
+ function extractUrlsBlockForVariable(code, varName, sourceFileArg) {
380
+ const sourceFile = sourceFileArg ?? ts3.createSourceFile(
349
381
  "input.tsx",
350
382
  code,
351
383
  ts3.ScriptTarget.Latest,
@@ -367,16 +399,20 @@ function extractUrlsBlockForVariable(code, varName) {
367
399
  visit(sourceFile);
368
400
  return result;
369
401
  }
370
- function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSchemasOut, diagnosticsOut) {
402
+ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSchemasOut, diagnosticsOut, memo) {
371
403
  const routeMap = {};
372
- const localRoutes = extractRoutesFromSource(block);
404
+ const blockSourceFile = parseBlock(memo, block);
405
+ const localRoutes = extractRoutesFromSource(block, blockSourceFile);
373
406
  for (const { name, pattern, search } of localRoutes) {
374
407
  routeMap[name] = pattern;
375
408
  if (search && searchSchemasOut) {
376
409
  searchSchemasOut[name] = search;
377
410
  }
378
411
  }
379
- const { resolved: includes, unresolvable } = extractIncludesWithDiagnostics(block);
412
+ const { resolved: includes, unresolvable } = extractIncludesWithDiagnostics(
413
+ block,
414
+ blockSourceFile
415
+ );
380
416
  if (diagnosticsOut) {
381
417
  for (const entry of unresolvable) {
382
418
  diagnosticsOut.push({ ...entry, sourceFile: filePath });
@@ -403,12 +439,15 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
403
439
  targetFile,
404
440
  imported.exportedName,
405
441
  visited,
406
- diagnosticsOut
442
+ diagnosticsOut,
443
+ void 0,
444
+ memo
407
445
  );
408
446
  } else {
409
447
  const sameFileBlock = extractUrlsBlockForVariable(
410
448
  fullSource,
411
- variableName
449
+ variableName,
450
+ parseBlock(memo, fullSource)
412
451
  );
413
452
  if (!sameFileBlock) {
414
453
  if (diagnosticsOut) {
@@ -426,7 +465,9 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
426
465
  filePath,
427
466
  variableName,
428
467
  visited,
429
- diagnosticsOut
468
+ diagnosticsOut,
469
+ void 0,
470
+ memo
430
471
  );
431
472
  }
432
473
  if (namePrefix === null) {
@@ -450,24 +491,31 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
450
491
  }
451
492
  return routeMap;
452
493
  }
453
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
494
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock, memo) {
454
495
  visited = visited ?? /* @__PURE__ */ new Set();
496
+ memo = memo ?? createScanMemo();
455
497
  const realPath = resolve(filePath);
456
498
  const key = variableName ? `${realPath}:${variableName}` : realPath;
457
499
  if (visited.has(key)) {
458
- console.warn(`[rsc-router] Circular include detected, skipping: ${key}`);
500
+ console.warn(`[rango] Circular include detected, skipping: ${key}`);
459
501
  return { routes: {}, searchSchemas: {} };
460
502
  }
461
503
  visited.add(key);
462
504
  let source;
463
505
  try {
464
- source = readFileSync(realPath, "utf-8");
506
+ source = readSourceMemoized(memo, realPath);
465
507
  } catch {
466
508
  return { routes: {}, searchSchemas: {} };
467
509
  }
468
510
  let block;
469
- if (variableName) {
470
- const extracted = extractUrlsBlockForVariable(source, variableName);
511
+ if (inlineBlock) {
512
+ block = inlineBlock;
513
+ } else if (variableName) {
514
+ const extracted = extractUrlsBlockForVariable(
515
+ source,
516
+ variableName,
517
+ parseBlock(memo, source)
518
+ );
471
519
  if (!extracted) return { routes: {}, searchSchemas: {} };
472
520
  block = extracted;
473
521
  } else {
@@ -480,7 +528,8 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
480
528
  realPath,
481
529
  visited,
482
530
  searchSchemas,
483
- diagnosticsOut
531
+ diagnosticsOut,
532
+ memo
484
533
  );
485
534
  visited.delete(key);
486
535
  return { routes, searchSchemas };
@@ -525,8 +574,16 @@ function writePerModuleRouteTypesForFile(filePath) {
525
574
  let routes;
526
575
  if (varNames.length > 0) {
527
576
  routes = [];
577
+ const memo = createScanMemo();
528
578
  for (const varName of varNames) {
529
- const { routes: routeMap, searchSchemas } = buildCombinedRouteMapWithSearch(filePath, varName);
579
+ const { routes: routeMap, searchSchemas } = buildCombinedRouteMapWithSearch(
580
+ filePath,
581
+ varName,
582
+ void 0,
583
+ void 0,
584
+ void 0,
585
+ memo
586
+ );
530
587
  for (const [name, pattern] of Object.entries(routeMap)) {
531
588
  const params = extractParamsFromPattern(pattern);
532
589
  routes.push({
@@ -540,12 +597,12 @@ function writePerModuleRouteTypesForFile(filePath) {
540
597
  } else {
541
598
  routes = extractRoutesFromSource(source);
542
599
  }
543
- const genPath = filePath.replace(/\.(tsx?)$/, ".gen.ts");
600
+ const genPath = filePath.replace(/\.(tsx?|jsx?)$/, ".gen.ts");
544
601
  if (routes.length === 0) {
545
602
  if (varNames.length > 0 && !existsSync2(genPath)) {
546
603
  writeFileSync(genPath, generatePerModuleTypesSource([]));
547
604
  console.log(
548
- `[rsc-router] Generated route types (placeholder) -> ${genPath}`
605
+ `[rango] Generated route types (placeholder) -> ${genPath}`
549
606
  );
550
607
  }
551
608
  return;
@@ -554,11 +611,11 @@ function writePerModuleRouteTypesForFile(filePath) {
554
611
  const existing = existsSync2(genPath) ? readFileSync2(genPath, "utf-8") : null;
555
612
  if (existing !== genSource) {
556
613
  writeFileSync(genPath, genSource);
557
- console.log(`[rsc-router] Generated route types -> ${genPath}`);
614
+ console.log(`[rango] Generated route types -> ${genPath}`);
558
615
  }
559
616
  } catch (err) {
560
617
  console.warn(
561
- `[rsc-router] Failed to generate route types for ${filePath}: ${err.message}`
618
+ `[rango] Failed to generate route types for ${filePath}: ${err.message}`
562
619
  );
563
620
  }
564
621
  }
@@ -573,9 +630,146 @@ var init_per_module_writer = __esm({
573
630
  }
574
631
  });
575
632
 
633
+ // src/build/route-types/source-scan.ts
634
+ function isLineTerminator(ch) {
635
+ const c = ch.charCodeAt(0);
636
+ return c === 10 || c === 13 || c === 8232 || c === 8233;
637
+ }
638
+ function isRegexPositionAt(code, slashPos, prevChar) {
639
+ if (prevChar === void 0) return true;
640
+ if (prevChar === ")" || prevChar === "]" || prevChar === "}") return false;
641
+ if (!/[\w$]/.test(prevChar)) return true;
642
+ let k = slashPos - 1;
643
+ while (k >= 0 && /\s/.test(code[k])) k--;
644
+ const wordEnd = k + 1;
645
+ while (k >= 0 && /[\w$]/.test(code[k])) k--;
646
+ return REGEX_PRECEDING_KEYWORDS.has(code.slice(k + 1, wordEnd));
647
+ }
648
+ function makeCodeClassifier(code) {
649
+ const n = code.length;
650
+ let i = 0;
651
+ let skipStart = -1;
652
+ let skipEnd = -1;
653
+ let lastSig;
654
+ return (q) => {
655
+ if (q >= skipStart && q < skipEnd) return false;
656
+ while (i < n && i <= q) {
657
+ const c = code[i];
658
+ const d = i + 1 < n ? code[i + 1] : "";
659
+ let end = -1;
660
+ let transparent = false;
661
+ if (c === "/" && d === "/") {
662
+ let j = i + 2;
663
+ while (j < n && !isLineTerminator(code[j])) j++;
664
+ end = j;
665
+ transparent = true;
666
+ } else if (c === "/" && d === "*") {
667
+ let j = i + 2;
668
+ while (j < n && !(code[j] === "*" && code[j + 1] === "/")) j++;
669
+ end = Math.min(n, j + 2);
670
+ transparent = true;
671
+ } else if (c === '"' || c === "'" || c === "`") {
672
+ let j = i + 1;
673
+ while (j < n) {
674
+ if (code[j] === "\\") {
675
+ j += 2;
676
+ continue;
677
+ }
678
+ if (code[j] === c) {
679
+ j++;
680
+ break;
681
+ }
682
+ j++;
683
+ }
684
+ end = j;
685
+ } else if (c === "/" && d !== "/" && d !== "*" && isRegexPositionAt(code, i, lastSig)) {
686
+ let j = i + 1;
687
+ let inClass = false;
688
+ let closed = false;
689
+ while (j < n && !isLineTerminator(code[j])) {
690
+ const r = code[j];
691
+ if (r === "\\") {
692
+ j += 2;
693
+ continue;
694
+ }
695
+ if (r === "[") inClass = true;
696
+ else if (r === "]") inClass = false;
697
+ else if (r === "/" && !inClass) {
698
+ j++;
699
+ closed = true;
700
+ break;
701
+ }
702
+ j++;
703
+ }
704
+ if (closed) {
705
+ while (j < n && /[a-z]/.test(code[j])) j++;
706
+ end = j;
707
+ }
708
+ }
709
+ if (end >= 0) {
710
+ if (q < end) {
711
+ skipStart = i;
712
+ skipEnd = end;
713
+ return false;
714
+ }
715
+ i = end;
716
+ if (!transparent) lastSig = "x";
717
+ } else {
718
+ if (!/\s/.test(c)) lastSig = c;
719
+ i++;
720
+ }
721
+ }
722
+ return true;
723
+ };
724
+ }
725
+ function firstCodeMatchIndex(code, pattern) {
726
+ const inCode = makeCodeClassifier(code);
727
+ pattern.lastIndex = 0;
728
+ let m;
729
+ while ((m = pattern.exec(code)) !== null) {
730
+ if (inCode(m.index)) return m.index;
731
+ if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
732
+ }
733
+ return -1;
734
+ }
735
+ var REGEX_PRECEDING_KEYWORDS;
736
+ var init_source_scan = __esm({
737
+ "src/build/route-types/source-scan.ts"() {
738
+ "use strict";
739
+ REGEX_PRECEDING_KEYWORDS = /* @__PURE__ */ new Set([
740
+ "return",
741
+ "typeof",
742
+ "instanceof",
743
+ "in",
744
+ "of",
745
+ "new",
746
+ "delete",
747
+ "void",
748
+ "do",
749
+ "else",
750
+ "yield",
751
+ "await",
752
+ "case",
753
+ "throw"
754
+ ]);
755
+ }
756
+ });
757
+
576
758
  // src/build/route-types/router-processing.ts
577
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync } from "node:fs";
578
- import { join as join2, dirname as dirname2, resolve as resolve2, basename as pathBasename } from "node:path";
759
+ import {
760
+ readFileSync as readFileSync3,
761
+ writeFileSync as writeFileSync2,
762
+ existsSync as existsSync3,
763
+ unlinkSync,
764
+ readdirSync as readdirSync2
765
+ } from "node:fs";
766
+ import {
767
+ join as join2,
768
+ dirname as dirname2,
769
+ resolve as resolve2,
770
+ sep,
771
+ basename as pathBasename
772
+ } from "node:path";
579
773
  import ts5 from "typescript";
580
774
  function countPublicRouteEntries(source) {
581
775
  const matches = source.matchAll(/^\s+(?:"([^"]+)"|([a-zA-Z_$][^:]*)):\s*["{]/gm) ?? [];
@@ -588,7 +782,78 @@ function countPublicRouteEntries(source) {
588
782
  }
589
783
  return count;
590
784
  }
591
- function extractUrlsVariableFromRouter(code) {
785
+ function isRoutableSourceFile(name) {
786
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
787
+ }
788
+ function findRouterFilesRecursive(dir, filter, results) {
789
+ let entries;
790
+ try {
791
+ entries = readdirSync2(dir, { withFileTypes: true });
792
+ } catch (err) {
793
+ console.warn(
794
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
795
+ );
796
+ return;
797
+ }
798
+ const childDirs = [];
799
+ const routerFilesInDir = [];
800
+ for (const entry of entries) {
801
+ const fullPath = join2(dir, entry.name);
802
+ if (entry.isDirectory()) {
803
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === "__tests__" || entry.name === "__mocks__" || entry.name.startsWith("."))
804
+ continue;
805
+ childDirs.push(fullPath);
806
+ continue;
807
+ }
808
+ if (!isRoutableSourceFile(entry.name)) continue;
809
+ if (filter && !filter(fullPath)) continue;
810
+ try {
811
+ const source = readFileSync3(fullPath, "utf-8");
812
+ if (ROUTER_CALL_PATTERN.test(source) && firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0) {
813
+ routerFilesInDir.push(fullPath);
814
+ }
815
+ } catch {
816
+ continue;
817
+ }
818
+ }
819
+ if (routerFilesInDir.length > 0) {
820
+ results.push(...routerFilesInDir);
821
+ return;
822
+ }
823
+ for (const childDir of childDirs) {
824
+ findRouterFilesRecursive(childDir, filter, results);
825
+ }
826
+ }
827
+ function findNestedRouterConflict(routerFiles) {
828
+ const routerDirs = [
829
+ ...new Set(routerFiles.map((filePath) => dirname2(resolve2(filePath))))
830
+ ].sort((a, b) => a.length - b.length);
831
+ for (let i = 0; i < routerDirs.length; i++) {
832
+ const ancestorDir = routerDirs[i];
833
+ const prefix = ancestorDir.endsWith(sep) ? ancestorDir : `${ancestorDir}${sep}`;
834
+ for (let j = i + 1; j < routerDirs.length; j++) {
835
+ const nestedDir = routerDirs[j];
836
+ if (!nestedDir.startsWith(prefix)) continue;
837
+ const ancestorFile = routerFiles.find(
838
+ (filePath) => dirname2(resolve2(filePath)) === ancestorDir
839
+ );
840
+ const nestedFile = routerFiles.find(
841
+ (filePath) => dirname2(resolve2(filePath)) === nestedDir
842
+ );
843
+ if (ancestorFile && nestedFile) {
844
+ return { ancestor: ancestorFile, nested: nestedFile };
845
+ }
846
+ }
847
+ }
848
+ return null;
849
+ }
850
+ function formatNestedRouterConflictError(conflict, prefix = "[rango]") {
851
+ return `${prefix} Nested router roots are not supported.
852
+ Router root: ${conflict.ancestor}
853
+ Nested router: ${conflict.nested}
854
+ Move the nested router into a sibling directory or configure it as a separate app root.`;
855
+ }
856
+ function extractUrlsFromRouter(code) {
592
857
  const sourceFile = ts5.createSourceFile(
593
858
  "router.tsx",
594
859
  code,
@@ -602,24 +867,70 @@ function extractUrlsVariableFromRouter(code) {
602
867
  const callee = node.expression;
603
868
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
604
869
  }
870
+ function isInlineBuilder(node) {
871
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
872
+ }
873
+ function isRoutesOnCreateRouter(node) {
874
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
875
+ return false;
876
+ let inner = node.expression.expression;
877
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
878
+ inner = inner.expression.expression;
879
+ }
880
+ return isCreateRouterCall(inner);
881
+ }
605
882
  function visit(node) {
606
883
  if (result) return;
607
- if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
608
- let inner = node.expression.expression;
609
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
610
- inner = inner.expression.expression;
611
- }
612
- if (isCreateRouterCall(inner)) {
613
- result = node.arguments[0].text;
614
- return;
884
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
885
+ const arg = node.arguments[0];
886
+ if (ts5.isIdentifier(arg)) {
887
+ result = { kind: "variable", name: arg.text };
888
+ } else if (isInlineBuilder(arg)) {
889
+ result = { kind: "inline", block: arg.getText(sourceFile) };
615
890
  }
891
+ return;
616
892
  }
617
893
  if (isCreateRouterCall(node)) {
618
894
  const callExpr = node;
619
- for (const arg of callExpr.arguments) {
895
+ for (const callArg of callExpr.arguments) {
896
+ if (ts5.isObjectLiteralExpression(callArg)) {
897
+ for (const prop of callArg.properties) {
898
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
899
+ if (ts5.isIdentifier(prop.initializer)) {
900
+ result = { kind: "variable", name: prop.initializer.text };
901
+ } else if (isInlineBuilder(prop.initializer)) {
902
+ result = {
903
+ kind: "inline",
904
+ block: prop.initializer.getText(sourceFile)
905
+ };
906
+ }
907
+ return;
908
+ }
909
+ }
910
+ }
911
+ }
912
+ }
913
+ ts5.forEachChild(node, visit);
914
+ }
915
+ visit(sourceFile);
916
+ return result;
917
+ }
918
+ function extractBasenameFromRouter(code) {
919
+ const sourceFile = ts5.createSourceFile(
920
+ "router.tsx",
921
+ code,
922
+ ts5.ScriptTarget.Latest,
923
+ true,
924
+ ts5.ScriptKind.TSX
925
+ );
926
+ let result;
927
+ function visit(node) {
928
+ if (result !== void 0) return;
929
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
930
+ for (const arg of node.arguments) {
620
931
  if (ts5.isObjectLiteralExpression(arg)) {
621
932
  for (const prop of arg.properties) {
622
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
933
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
623
934
  result = prop.initializer.text;
624
935
  return;
625
936
  }
@@ -632,6 +943,38 @@ function extractUrlsVariableFromRouter(code) {
632
943
  visit(sourceFile);
633
944
  return result;
634
945
  }
946
+ function applyBasenameToRoutes(result, basename) {
947
+ const prefixed = {};
948
+ for (const [name, pattern] of Object.entries(result.routes)) {
949
+ if (pattern === "/") {
950
+ prefixed[name] = basename;
951
+ } else if (basename.endsWith("/") && pattern.startsWith("/")) {
952
+ prefixed[name] = basename + pattern.slice(1);
953
+ } else {
954
+ prefixed[name] = basename + pattern;
955
+ }
956
+ }
957
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
958
+ }
959
+ function genFileTsPath(sourceFile) {
960
+ const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
961
+ return join2(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
962
+ }
963
+ function resolveSearchSchemas(publicRouteNames, runtimeSchemas, sourceFile) {
964
+ if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
965
+ return runtimeSchemas;
966
+ }
967
+ const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
968
+ if (Object.keys(staticParsed.searchSchemas).length === 0) {
969
+ return runtimeSchemas;
970
+ }
971
+ const filtered = {};
972
+ for (const name of publicRouteNames) {
973
+ const schema = staticParsed.searchSchemas[name];
974
+ if (schema) filtered[name] = schema;
975
+ }
976
+ return Object.keys(filtered).length > 0 ? filtered : runtimeSchemas;
977
+ }
635
978
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
636
979
  let routerSource;
637
980
  try {
@@ -639,19 +982,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
639
982
  } catch {
640
983
  return { routes: {}, searchSchemas: {} };
641
984
  }
642
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
643
- if (!urlsVarName) {
985
+ const extraction = extractUrlsFromRouter(routerSource);
986
+ if (!extraction) {
644
987
  return { routes: {}, searchSchemas: {} };
645
988
  }
646
- const imported = resolveImportedVariable(routerSource, urlsVarName);
647
- if (imported) {
648
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
649
- if (!targetFile) {
650
- return { routes: {}, searchSchemas: {} };
989
+ const rawBasename = extractBasenameFromRouter(routerSource);
990
+ const basename = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
991
+ let result;
992
+ if (extraction.kind === "inline") {
993
+ result = buildCombinedRouteMapWithSearch(
994
+ routerFilePath,
995
+ void 0,
996
+ void 0,
997
+ void 0,
998
+ extraction.block
999
+ );
1000
+ } else {
1001
+ const imported = resolveImportedVariable(routerSource, extraction.name);
1002
+ if (imported) {
1003
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
1004
+ if (!targetFile) {
1005
+ return { routes: {}, searchSchemas: {} };
1006
+ }
1007
+ result = buildCombinedRouteMapWithSearch(
1008
+ targetFile,
1009
+ imported.exportedName
1010
+ );
1011
+ } else {
1012
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
651
1013
  }
652
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
653
1014
  }
654
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
1015
+ if (basename) {
1016
+ result = applyBasenameToRoutes(result, basename);
1017
+ }
1018
+ return result;
655
1019
  }
656
1020
  function detectUnresolvableIncludes(routerFilePath) {
657
1021
  const realPath = resolve2(routerFilePath);
@@ -661,9 +1025,20 @@ function detectUnresolvableIncludes(routerFilePath) {
661
1025
  } catch {
662
1026
  return [];
663
1027
  }
664
- const urlsVarName = extractUrlsVariableFromRouter(source);
665
- if (!urlsVarName) return [];
666
- const imported = resolveImportedVariable(source, urlsVarName);
1028
+ const extraction = extractUrlsFromRouter(source);
1029
+ if (!extraction) return [];
1030
+ const diagnostics = [];
1031
+ if (extraction.kind === "inline") {
1032
+ buildCombinedRouteMapWithSearch(
1033
+ realPath,
1034
+ void 0,
1035
+ /* @__PURE__ */ new Set(),
1036
+ diagnostics,
1037
+ extraction.block
1038
+ );
1039
+ return diagnostics;
1040
+ }
1041
+ const imported = resolveImportedVariable(source, extraction.name);
667
1042
  let targetFile;
668
1043
  let exportedName;
669
1044
  if (imported) {
@@ -683,9 +1058,8 @@ function detectUnresolvableIncludes(routerFilePath) {
683
1058
  exportedName = imported.exportedName;
684
1059
  } else {
685
1060
  targetFile = realPath;
686
- exportedName = urlsVarName;
1061
+ exportedName = extraction.name;
687
1062
  }
688
- const diagnostics = [];
689
1063
  buildCombinedRouteMapWithSearch(
690
1064
  targetFile,
691
1065
  exportedName,
@@ -711,19 +1085,8 @@ function detectUnresolvableIncludesForUrlsFile(filePath) {
711
1085
  return diagnostics;
712
1086
  }
713
1087
  function findRouterFiles(root, filter) {
714
- const files = findTsFiles(root, filter);
715
1088
  const result = [];
716
- for (const filePath of files) {
717
- if (filePath.includes(".gen.")) continue;
718
- try {
719
- const source = readFileSync3(filePath, "utf-8");
720
- if (/\bcreateRouter\s*[<(]/.test(source)) {
721
- result.push(filePath);
722
- }
723
- } catch {
724
- continue;
725
- }
726
- }
1089
+ findRouterFilesRecursive(root, filter, result);
727
1090
  return result;
728
1091
  }
729
1092
  function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
@@ -732,46 +1095,34 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
732
1095
  if (existsSync3(oldCombinedPath)) {
733
1096
  unlinkSync(oldCombinedPath);
734
1097
  console.log(
735
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
1098
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
736
1099
  );
737
1100
  }
738
1101
  } catch {
739
1102
  }
740
1103
  const routerFilePaths = knownRouterFiles ?? findRouterFiles(root);
741
1104
  if (routerFilePaths.length === 0) return;
1105
+ const nestedRouterConflict = findNestedRouterConflict(routerFilePaths);
1106
+ if (nestedRouterConflict) {
1107
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
1108
+ }
742
1109
  for (const routerFilePath of routerFilePaths) {
743
- let routerSource;
744
- try {
745
- routerSource = readFileSync3(routerFilePath, "utf-8");
746
- } catch {
747
- continue;
748
- }
749
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
750
- if (!urlsVarName) continue;
751
- let result;
752
- const imported = resolveImportedVariable(routerSource, urlsVarName);
753
- if (imported) {
754
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
755
- if (!targetFile) continue;
756
- result = buildCombinedRouteMapWithSearch(
757
- targetFile,
758
- imported.exportedName
759
- );
760
- } else {
761
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
1110
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
1111
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
1112
+ let routerSource;
1113
+ try {
1114
+ routerSource = readFileSync3(routerFilePath, "utf-8");
1115
+ } catch {
1116
+ continue;
1117
+ }
1118
+ if (!extractUrlsFromRouter(routerSource)) continue;
762
1119
  }
763
- const routerBasename = pathBasename(routerFilePath).replace(
764
- /\.(tsx?|jsx?)$/,
765
- ""
766
- );
767
- const outPath = join2(
768
- dirname2(routerFilePath),
769
- `${routerBasename}.named-routes.gen.ts`
770
- );
1120
+ const outPath = genFileTsPath(routerFilePath);
771
1121
  const existing = existsSync3(outPath) ? readFileSync3(outPath, "utf-8") : null;
772
1122
  if (Object.keys(result.routes).length === 0) {
773
1123
  if (!existing) {
774
1124
  const emptySource = generateRouteTypesSource({});
1125
+ opts?.onWrite?.(outPath, emptySource);
775
1126
  writeFileSync2(outPath, emptySource);
776
1127
  }
777
1128
  continue;
@@ -791,21 +1142,25 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
791
1142
  continue;
792
1143
  }
793
1144
  }
1145
+ opts?.onWrite?.(outPath, source);
794
1146
  writeFileSync2(outPath, source);
795
1147
  console.log(
796
- `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
1148
+ `[rango] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
797
1149
  );
798
1150
  }
799
1151
  }
800
1152
  }
1153
+ var ROUTER_CALL_PATTERN, ROUTER_CALL_PATTERN_G;
801
1154
  var init_router_processing = __esm({
802
1155
  "src/build/route-types/router-processing.ts"() {
803
1156
  "use strict";
804
1157
  init_codegen();
805
- init_scan_filter();
1158
+ init_source_scan();
806
1159
  init_include_resolution();
807
1160
  init_per_module_writer();
808
1161
  init_route_name();
1162
+ ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
1163
+ ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
809
1164
  }
810
1165
  });
811
1166
 
@@ -843,7 +1198,7 @@ import {
843
1198
  import { createElement, StrictMode } from "react";
844
1199
  import { hydrateRoot } from "react-dom/client";
845
1200
  import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
846
- import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
1201
+ import { initBrowserApp, Rango } from "@rangojs/router/browser";
847
1202
 
848
1203
  async function initializeApp() {
849
1204
  const deps = {
@@ -854,11 +1209,16 @@ async function initializeApp() {
854
1209
  createTemporaryReferenceSet,
855
1210
  };
856
1211
 
857
- await initBrowserApp({ rscStream, deps });
1212
+ // initBrowserApp resolves the initial payload and returns the browser app
1213
+ // context, including strictMode (default true) from createRouter. StrictMode
1214
+ // is the default; createRouter({ strictMode: false }) ships the opt-out in the
1215
+ // payload metadata. StrictMode emits no DOM, so toggling never changes markup.
1216
+ const { strictMode } = await initBrowserApp({ rscStream, deps });
858
1217
 
1218
+ const app = createElement(Rango);
859
1219
  hydrateRoot(
860
1220
  document,
861
- createElement(StrictMode, null, createElement(RSCRouter))
1221
+ strictMode === false ? app : createElement(StrictMode, null, app)
862
1222
  );
863
1223
  }
864
1224
 
@@ -887,12 +1247,37 @@ export const renderHTML = createSSRHandler({
887
1247
  }
888
1248
  });
889
1249
 
1250
+ // src/vite/utils/directive-prologue.ts
1251
+ import { parseAst } from "vite";
1252
+ function hasUseClientDirective(source) {
1253
+ let program;
1254
+ try {
1255
+ program = parseAst(source, { lang: "tsx" });
1256
+ } catch {
1257
+ return false;
1258
+ }
1259
+ for (const node of program.body ?? []) {
1260
+ if (node?.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
1261
+ if (node.expression.value === "use client") return true;
1262
+ continue;
1263
+ }
1264
+ break;
1265
+ }
1266
+ return false;
1267
+ }
1268
+ var init_directive_prologue = __esm({
1269
+ "src/vite/utils/directive-prologue.ts"() {
1270
+ "use strict";
1271
+ }
1272
+ });
1273
+
890
1274
  // src/vite/plugins/version-plugin.ts
891
1275
  var version_plugin_exports = {};
892
1276
  __export(version_plugin_exports, {
893
- createVersionPlugin: () => createVersionPlugin
1277
+ createVersionPlugin: () => createVersionPlugin,
1278
+ isViteDepCachePath: () => isViteDepCachePath
894
1279
  });
895
- import { parseAst } from "vite";
1280
+ import { parseAst as parseAst2 } from "vite";
896
1281
  function isCodeModule(id) {
897
1282
  return /\.(tsx?|jsx?)($|\?)/.test(id);
898
1283
  }
@@ -900,23 +1285,13 @@ function normalizeModuleId(id) {
900
1285
  return id.split("?", 1)[0];
901
1286
  }
902
1287
  function getClientModuleSignature(source) {
1288
+ if (!hasUseClientDirective(source)) return void 0;
903
1289
  let program;
904
1290
  try {
905
- program = parseAst(source, { jsx: true });
1291
+ program = parseAst2(source, { lang: "tsx" });
906
1292
  } catch {
907
1293
  return void 0;
908
1294
  }
909
- let isUseClient = false;
910
- for (const node of program.body ?? []) {
911
- if (node?.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
912
- if (node.expression.value === "use client") {
913
- isUseClient = true;
914
- }
915
- continue;
916
- }
917
- break;
918
- }
919
- if (!isUseClient) return void 0;
920
1295
  const exports = /* @__PURE__ */ new Set();
921
1296
  let hasDefault = false;
922
1297
  let hasExportAll = false;
@@ -985,10 +1360,12 @@ function createVersionPlugin() {
985
1360
  let currentVersion = buildVersion;
986
1361
  let isDev = false;
987
1362
  let server = null;
1363
+ let resolvedCacheDir;
988
1364
  const clientModuleSignatures = /* @__PURE__ */ new Map();
1365
+ let versionCounter = 0;
989
1366
  const bumpVersion = (reason) => {
990
- currentVersion = Date.now().toString(16);
991
- console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
1367
+ currentVersion = Date.now().toString(16) + String(++versionCounter);
1368
+ console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
992
1369
  const rscEnv = server?.environments?.rsc;
993
1370
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
994
1371
  "\0" + VIRTUAL_IDS.version
@@ -1002,6 +1379,7 @@ function createVersionPlugin() {
1002
1379
  enforce: "pre",
1003
1380
  configResolved(config) {
1004
1381
  isDev = config.command === "serve";
1382
+ resolvedCacheDir = config.cacheDir ? String(config.cacheDir).replace(/\\/g, "/") : void 0;
1005
1383
  },
1006
1384
  configureServer(devServer) {
1007
1385
  server = devServer;
@@ -1038,11 +1416,14 @@ function createVersionPlugin() {
1038
1416
  }
1039
1417
  return null;
1040
1418
  },
1041
- // Track RSC module changes and update version
1042
1419
  async hotUpdate(ctx) {
1043
1420
  if (!isDev) return;
1044
1421
  const isRscModule = this.environment?.name === "rsc";
1045
1422
  if (!isRscModule) return;
1423
+ if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
1424
+ if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
1425
+ return;
1426
+ }
1046
1427
  if (isCodeModule(ctx.file)) {
1047
1428
  const filePath = normalizeModuleId(ctx.file);
1048
1429
  const previousSignature = clientModuleSignatures.get(filePath);
@@ -1069,10 +1450,22 @@ function createVersionPlugin() {
1069
1450
  }
1070
1451
  };
1071
1452
  }
1453
+ function isViteDepCachePath(filePath, cacheDir) {
1454
+ if (!filePath) return false;
1455
+ const normalized = filePath.replace(/\\/g, "/");
1456
+ if (cacheDir) {
1457
+ const normalizedCacheDir = cacheDir.replace(/\\/g, "/").replace(/\/+$/, "");
1458
+ if (normalized === normalizedCacheDir || normalized.startsWith(normalizedCacheDir + "/")) {
1459
+ return true;
1460
+ }
1461
+ }
1462
+ return /\/node_modules\/\.vite[^/]*\//.test(normalized) || normalized.includes("/.vite-isolated/");
1463
+ }
1072
1464
  var init_version_plugin = __esm({
1073
1465
  "src/vite/plugins/version-plugin.ts"() {
1074
1466
  "use strict";
1075
1467
  init_virtual_entries();
1468
+ init_directive_prologue();
1076
1469
  }
1077
1470
  });
1078
1471
 
@@ -1114,7 +1507,7 @@ var runtime_discovery_exports = {};
1114
1507
  __export(runtime_discovery_exports, {
1115
1508
  discoverAndWriteRouteTypes: () => discoverAndWriteRouteTypes
1116
1509
  });
1117
- import { dirname as dirname3, join as join3, basename, resolve as resolve3 } from "node:path";
1510
+ import { resolve as resolve3 } from "node:path";
1118
1511
  import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
1119
1512
  async function discoverAndWriteRouteTypes(opts) {
1120
1513
  let createViteServer;
@@ -1224,22 +1617,12 @@ This means createRouter() stack trace parsing matched an internal frame.
1224
1617
  Set an explicit \`id\` on createRouter() or check the call site.`
1225
1618
  );
1226
1619
  }
1227
- if (!routeSearchSchemas || Object.keys(routeSearchSchemas).length === 0) {
1228
- const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
1229
- if (Object.keys(staticParsed.searchSchemas).length > 0) {
1230
- const filtered = {};
1231
- for (const name of Object.keys(routeManifest)) {
1232
- const schema = staticParsed.searchSchemas[name];
1233
- if (schema) filtered[name] = schema;
1234
- }
1235
- if (Object.keys(filtered).length > 0) {
1236
- routeSearchSchemas = filtered;
1237
- }
1238
- }
1239
- }
1240
- const routerDir = dirname3(sourceFile);
1241
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
1242
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
1620
+ routeSearchSchemas = resolveSearchSchemas(
1621
+ Object.keys(routeManifest),
1622
+ routeSearchSchemas,
1623
+ sourceFile
1624
+ );
1625
+ const outPath = genFileTsPath(sourceFile);
1243
1626
  const source = generateRouteTypesSource(
1244
1627
  routeManifest,
1245
1628
  routeSearchSchemas && Object.keys(routeSearchSchemas).length > 0 ? routeSearchSchemas : void 0
@@ -1276,7 +1659,7 @@ var init_runtime_discovery = __esm({
1276
1659
 
1277
1660
  // src/bin/rango.ts
1278
1661
  init_generate_route_types();
1279
- import { resolve as resolve4, dirname as dirname4 } from "node:path";
1662
+ import { resolve as resolve4, dirname as dirname3 } from "node:path";
1280
1663
  import { readFileSync as readFileSync5, statSync, existsSync as existsSync5 } from "node:fs";
1281
1664
  var [command, ...rawArgs] = process.argv.slice(2);
1282
1665
  if (command === "generate") {
@@ -1347,12 +1730,12 @@ Examples:
1347
1730
  );
1348
1731
  }
1349
1732
  function findProjectRoot(fromPath) {
1350
- let dir = dirname4(resolve4(fromPath));
1351
- while (dir !== dirname4(dir)) {
1733
+ let dir = dirname3(resolve4(fromPath));
1734
+ while (dir !== dirname3(dir)) {
1352
1735
  if (existsSync5(resolve4(dir, "package.json")) || existsSync5(resolve4(dir, "vite.config.ts")) || existsSync5(resolve4(dir, "vite.config.js"))) {
1353
1736
  return dir;
1354
1737
  }
1355
- dir = dirname4(dir);
1738
+ dir = dirname3(dir);
1356
1739
  }
1357
1740
  return process.cwd();
1358
1741
  }
@@ -1428,6 +1811,15 @@ function runStaticGeneration(args, mode) {
1428
1811
  formatDiagnostics(uniqueDiagnostics);
1429
1812
  console.warn("");
1430
1813
  }
1814
+ const nestedRouterConflict = findNestedRouterConflict(routerFiles);
1815
+ if (nestedRouterConflict) {
1816
+ console.error(
1817
+ `
1818
+ ${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}
1819
+ `
1820
+ );
1821
+ process.exit(1);
1822
+ }
1431
1823
  for (const urlsFile of urlsFiles) {
1432
1824
  writePerModuleRouteTypesForFile(urlsFile);
1433
1825
  }
@@ -1471,6 +1863,15 @@ async function runRuntimeDiscovery(args, configFile) {
1471
1863
  console.error("[rango] No router files found in the provided paths");
1472
1864
  process.exit(1);
1473
1865
  }
1866
+ const nestedRouterConflict = findNestedRouterConflict(routerEntries);
1867
+ if (nestedRouterConflict) {
1868
+ console.error(
1869
+ `
1870
+ ${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}
1871
+ `
1872
+ );
1873
+ process.exit(1);
1874
+ }
1474
1875
  let discoverAndWriteRouteTypes2;
1475
1876
  try {
1476
1877
  const mod = await Promise.resolve().then(() => (init_runtime_discovery(), runtime_discovery_exports));