@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327

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 (440) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +301 -797
  3. package/dist/bin/rango.js +603 -145
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3750 -1160
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +96 -24
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +85 -6
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +228 -33
  12. package/skills/caching/SKILL.md +336 -19
  13. package/skills/catalog.json +271 -0
  14. package/skills/comparison/SKILL.md +50 -0
  15. package/skills/comparison/agents/openai.yaml +4 -0
  16. package/skills/comparison/references/framework-comparison.md +837 -0
  17. package/skills/composability/SKILL.md +110 -4
  18. package/skills/css/SKILL.md +76 -0
  19. package/skills/debug-manifest/SKILL.md +5 -3
  20. package/skills/defer-hydration/SKILL.md +235 -0
  21. package/skills/document-cache/SKILL.md +87 -56
  22. package/skills/fonts/SKILL.md +1 -1
  23. package/skills/handler-use/SKILL.md +12 -10
  24. package/skills/hooks/SKILL.md +73 -691
  25. package/skills/hooks/data.md +273 -0
  26. package/skills/hooks/handle-and-actions.md +103 -0
  27. package/skills/hooks/navigation.md +110 -0
  28. package/skills/hooks/outlets.md +41 -0
  29. package/skills/hooks/state.md +228 -0
  30. package/skills/hooks/urls.md +135 -0
  31. package/skills/host-router/SKILL.md +129 -27
  32. package/skills/i18n/SKILL.md +276 -0
  33. package/skills/intercept/SKILL.md +75 -19
  34. package/skills/layout/SKILL.md +40 -19
  35. package/skills/links/SKILL.md +247 -17
  36. package/skills/loader/SKILL.md +248 -10
  37. package/skills/middleware/SKILL.md +25 -13
  38. package/skills/migrate-nextjs/SKILL.md +205 -20
  39. package/skills/migrate-react-router/SKILL.md +59 -670
  40. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  41. package/skills/migrate-react-router/component-migration.md +196 -0
  42. package/skills/migrate-react-router/data-and-actions.md +225 -0
  43. package/skills/migrate-react-router/route-mapping.md +271 -0
  44. package/skills/mime-routes/SKILL.md +29 -2
  45. package/skills/observability/SKILL.md +202 -0
  46. package/skills/parallel/SKILL.md +40 -10
  47. package/skills/ppr/SKILL.md +616 -0
  48. package/skills/prerender/SKILL.md +72 -60
  49. package/skills/rango/SKILL.md +318 -26
  50. package/skills/react-compiler/SKILL.md +168 -0
  51. package/skills/response-routes/SKILL.md +138 -49
  52. package/skills/route/SKILL.md +117 -9
  53. package/skills/router-setup/SKILL.md +44 -9
  54. package/skills/scripts/SKILL.md +179 -0
  55. package/skills/server-actions/SKILL.md +776 -0
  56. package/skills/shell-manifest/SKILL.md +185 -0
  57. package/skills/streams-and-websockets/SKILL.md +283 -0
  58. package/skills/tailwind/SKILL.md +28 -4
  59. package/skills/testing/SKILL.md +130 -0
  60. package/skills/testing/bindings.md +103 -0
  61. package/skills/testing/cache-prerender.md +127 -0
  62. package/skills/testing/client-components.md +124 -0
  63. package/skills/testing/e2e-parity.md +125 -0
  64. package/skills/testing/flight.md +91 -0
  65. package/skills/testing/handles.md +131 -0
  66. package/skills/testing/loader.md +128 -0
  67. package/skills/testing/middleware.md +99 -0
  68. package/skills/testing/render-handler.md +122 -0
  69. package/skills/testing/response-routes.md +95 -0
  70. package/skills/testing/reverse-and-types.md +85 -0
  71. package/skills/testing/server-actions.md +107 -0
  72. package/skills/testing/server-tree.md +128 -0
  73. package/skills/testing/setup.md +123 -0
  74. package/skills/theme/SKILL.md +1 -1
  75. package/skills/typesafety/SKILL.md +45 -626
  76. package/skills/typesafety/env-and-bindings.md +254 -0
  77. package/skills/typesafety/generated-files-and-cli.md +335 -0
  78. package/skills/typesafety/params-and-search.md +153 -0
  79. package/skills/typesafety/route-types.md +209 -0
  80. package/skills/use-cache/SKILL.md +74 -15
  81. package/skills/vercel/SKILL.md +128 -0
  82. package/skills/view-transitions/SKILL.md +337 -0
  83. package/src/__augment-tests__/augment.ts +81 -0
  84. package/src/__augment-tests__/augmented.check.ts +116 -0
  85. package/src/__internal.ts +0 -65
  86. package/src/browser/action-coordinator.ts +53 -36
  87. package/src/browser/action-fence.ts +47 -0
  88. package/src/browser/app-shell.ts +39 -0
  89. package/src/browser/connection-warmup.ts +134 -0
  90. package/src/browser/cookie-name.ts +140 -0
  91. package/src/browser/event-controller.ts +252 -158
  92. package/src/browser/history-state.ts +21 -0
  93. package/src/browser/index.ts +3 -3
  94. package/src/browser/invalidate-client-cache.ts +52 -0
  95. package/src/browser/logging.ts +28 -0
  96. package/src/browser/merge-segment-loaders.ts +6 -4
  97. package/src/browser/navigation-bridge.ts +94 -25
  98. package/src/browser/navigation-client.ts +144 -79
  99. package/src/browser/navigation-store-handle.ts +38 -0
  100. package/src/browser/navigation-store.ts +161 -73
  101. package/src/browser/navigation-transaction.ts +9 -59
  102. package/src/browser/network-error-handler.ts +34 -7
  103. package/src/browser/partial-update.ts +183 -144
  104. package/src/browser/prefetch/cache.ts +242 -77
  105. package/src/browser/prefetch/fetch.ts +325 -69
  106. package/src/browser/prefetch/queue.ts +61 -12
  107. package/src/browser/rango-state.ts +158 -76
  108. package/src/browser/react/Link.tsx +58 -20
  109. package/src/browser/react/NavigationProvider.tsx +202 -120
  110. package/src/browser/react/ScrollRestoration.tsx +10 -6
  111. package/src/browser/react/filter-segment-order.ts +66 -7
  112. package/src/browser/react/index.ts +0 -48
  113. package/src/browser/react/location-state-shared.ts +178 -8
  114. package/src/browser/react/location-state.ts +39 -14
  115. package/src/browser/react/use-action.ts +6 -15
  116. package/src/browser/react/use-handle.ts +17 -14
  117. package/src/browser/react/use-href.tsx +8 -1
  118. package/src/browser/react/use-link-status.ts +33 -8
  119. package/src/browser/react/use-navigation.ts +32 -7
  120. package/src/browser/react/use-params.ts +20 -10
  121. package/src/browser/react/use-reverse.ts +106 -0
  122. package/src/browser/react/use-router.ts +25 -3
  123. package/src/browser/react/use-search-params.ts +0 -5
  124. package/src/browser/react/use-segments.ts +11 -21
  125. package/src/browser/response-adapter.ts +99 -8
  126. package/src/browser/rsc-router.tsx +145 -28
  127. package/src/browser/scroll-restoration.ts +37 -22
  128. package/src/browser/segment-reconciler.ts +31 -21
  129. package/src/browser/segment-structure-assert.ts +2 -2
  130. package/src/browser/server-action-bridge.ts +236 -65
  131. package/src/browser/types.ts +102 -9
  132. package/src/browser/validate-redirect-origin.ts +43 -16
  133. package/src/build/collect-fallback-refs.ts +107 -0
  134. package/src/build/generate-manifest.ts +203 -154
  135. package/src/build/generate-route-types.ts +3 -1
  136. package/src/build/index.ts +11 -3
  137. package/src/build/prefix-tree-utils.ts +123 -0
  138. package/src/build/route-trie.ts +152 -21
  139. package/src/build/route-types/ast-route-extraction.ts +15 -8
  140. package/src/build/route-types/codegen.ts +16 -5
  141. package/src/build/route-types/include-resolution.ts +456 -62
  142. package/src/build/route-types/param-extraction.ts +6 -3
  143. package/src/build/route-types/per-module-writer.ts +22 -6
  144. package/src/build/route-types/router-processing.ts +128 -51
  145. package/src/build/route-types/scan-filter.ts +1 -1
  146. package/src/build/route-types/source-scan.ts +216 -0
  147. package/src/build/runtime-discovery.ts +13 -21
  148. package/src/cache/cache-error.ts +104 -0
  149. package/src/cache/cache-key-utils.ts +58 -13
  150. package/src/cache/cache-policy.ts +108 -34
  151. package/src/cache/cache-runtime.ts +421 -58
  152. package/src/cache/cache-scope.ts +187 -96
  153. package/src/cache/cache-tag.ts +149 -0
  154. package/src/cache/cf/cf-base64.ts +33 -0
  155. package/src/cache/cf/cf-cache-constants.ts +127 -0
  156. package/src/cache/cf/cf-cache-store.ts +2202 -372
  157. package/src/cache/cf/cf-cache-types.ts +349 -0
  158. package/src/cache/cf/cf-kv-utils.ts +46 -0
  159. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  160. package/src/cache/cf/index.ts +6 -16
  161. package/src/cache/document-cache.ts +126 -41
  162. package/src/cache/handle-snapshot.ts +70 -0
  163. package/src/cache/index.ts +23 -20
  164. package/src/cache/memory-segment-store.ts +243 -37
  165. package/src/cache/profile-registry.ts +46 -31
  166. package/src/cache/read-through-swr.ts +56 -12
  167. package/src/cache/segment-codec.ts +13 -21
  168. package/src/cache/shell-snapshot.ts +417 -0
  169. package/src/cache/tag-invalidation.ts +230 -0
  170. package/src/cache/types.ts +180 -99
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1127 -0
  173. package/src/client.rsc.tsx +41 -21
  174. package/src/client.tsx +33 -61
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +108 -0
  177. package/src/component-utils.ts +19 -0
  178. package/src/components/DefaultDocument.tsx +8 -2
  179. package/src/context-var.ts +18 -6
  180. package/src/decode-loader-results.ts +52 -0
  181. package/src/defer.ts +185 -0
  182. package/src/deps/ssr.ts +0 -1
  183. package/src/encode-kv.ts +49 -0
  184. package/src/errors.ts +30 -4
  185. package/src/escape-script.ts +52 -0
  186. package/src/handle.ts +67 -37
  187. package/src/handles/MetaTags.tsx +24 -53
  188. package/src/handles/Scripts.tsx +183 -0
  189. package/src/handles/breadcrumbs.ts +35 -8
  190. package/src/handles/deferred-resolution.ts +127 -0
  191. package/src/handles/is-thenable.ts +18 -0
  192. package/src/handles/meta.ts +14 -40
  193. package/src/handles/script.ts +244 -0
  194. package/src/host/cookie-handler.ts +9 -60
  195. package/src/host/errors.ts +13 -22
  196. package/src/host/index.ts +9 -2
  197. package/src/host/pattern-matcher.ts +23 -52
  198. package/src/host/router.ts +107 -99
  199. package/src/host/testing.ts +40 -27
  200. package/src/host/types.ts +37 -4
  201. package/src/host/utils.ts +1 -1
  202. package/src/href-client.ts +137 -22
  203. package/src/index.rsc.ts +97 -12
  204. package/src/index.ts +98 -14
  205. package/src/internal-debug.ts +11 -10
  206. package/src/loader-store.ts +500 -0
  207. package/src/loader.rsc.ts +20 -13
  208. package/src/loader.ts +12 -11
  209. package/src/missing-id-error.ts +68 -0
  210. package/src/outlet-context.ts +1 -1
  211. package/src/outlet-provider.tsx +1 -5
  212. package/src/prerender/param-hash.ts +16 -16
  213. package/src/prerender/store.ts +32 -37
  214. package/src/prerender.ts +78 -10
  215. package/src/redirect-origin.ts +114 -0
  216. package/src/regex-escape.ts +8 -0
  217. package/src/render-error-thrower.tsx +20 -0
  218. package/src/response-utils.ts +62 -0
  219. package/src/reverse.ts +65 -39
  220. package/src/root-error-boundary.tsx +1 -19
  221. package/src/route-content-wrapper.tsx +19 -77
  222. package/src/route-definition/dsl-helpers.ts +304 -309
  223. package/src/route-definition/helper-factories.ts +28 -140
  224. package/src/route-definition/helpers-types.ts +87 -59
  225. package/src/route-definition/index.ts +1 -2
  226. package/src/route-definition/redirect.ts +44 -11
  227. package/src/route-definition/resolve-handler-use.ts +12 -1
  228. package/src/route-definition/use-item-types.ts +29 -0
  229. package/src/route-map-builder.ts +41 -20
  230. package/src/route-types.ts +19 -46
  231. package/src/router/basename.ts +14 -0
  232. package/src/router/content-negotiation.ts +73 -25
  233. package/src/router/error-handling.ts +45 -18
  234. package/src/router/find-match.ts +129 -30
  235. package/src/router/handler-context.ts +27 -42
  236. package/src/router/instrument.ts +355 -0
  237. package/src/router/intercept-resolution.ts +39 -20
  238. package/src/router/lazy-includes.ts +82 -59
  239. package/src/router/loader-resolution.ts +167 -72
  240. package/src/router/logging.ts +0 -6
  241. package/src/router/manifest.ts +74 -40
  242. package/src/router/match-api.ts +80 -55
  243. package/src/router/match-context.ts +0 -22
  244. package/src/router/match-handlers.ts +211 -165
  245. package/src/router/match-middleware/background-revalidation.ts +40 -24
  246. package/src/router/match-middleware/cache-lookup.ts +159 -285
  247. package/src/router/match-middleware/cache-store.ts +64 -52
  248. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  249. package/src/router/match-middleware/segment-resolution.ts +0 -22
  250. package/src/router/match-pipelines.ts +1 -42
  251. package/src/router/match-result.ts +69 -79
  252. package/src/router/metrics.ts +0 -34
  253. package/src/router/middleware-types.ts +7 -134
  254. package/src/router/middleware.ts +298 -172
  255. package/src/router/navigation-snapshot.ts +7 -56
  256. package/src/router/params-util.ts +23 -0
  257. package/src/router/parse-pattern.ts +115 -0
  258. package/src/router/pattern-matching.ts +181 -150
  259. package/src/router/prefetch-cache-ttl.ts +51 -0
  260. package/src/router/prefetch-limits.ts +37 -0
  261. package/src/router/prerender-match.ts +112 -67
  262. package/src/router/preview-match.ts +6 -2
  263. package/src/router/request-classification.ts +50 -69
  264. package/src/router/revalidation.ts +123 -73
  265. package/src/router/route-snapshot.ts +14 -3
  266. package/src/router/router-context.ts +6 -29
  267. package/src/router/router-interfaces.ts +115 -36
  268. package/src/router/router-options.ts +166 -5
  269. package/src/router/router-registry.ts +2 -5
  270. package/src/router/segment-resolution/fresh.ts +131 -86
  271. package/src/router/segment-resolution/helpers.ts +86 -6
  272. package/src/router/segment-resolution/loader-cache.ts +139 -39
  273. package/src/router/segment-resolution/loader-mask.ts +67 -0
  274. package/src/router/segment-resolution/loader-snapshot.ts +251 -0
  275. package/src/router/segment-resolution/revalidation.ts +272 -320
  276. package/src/router/segment-resolution/static-store.ts +19 -5
  277. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  278. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  279. package/src/router/segment-resolution.ts +5 -1
  280. package/src/router/segment-wrappers.ts +6 -5
  281. package/src/router/state-cookie-name.ts +33 -0
  282. package/src/router/substitute-pattern-params.ts +75 -0
  283. package/src/router/telemetry-otel.ts +160 -200
  284. package/src/router/telemetry.ts +105 -20
  285. package/src/router/timeout.ts +0 -20
  286. package/src/router/tracing.ts +215 -0
  287. package/src/router/trie-matching.ts +171 -59
  288. package/src/router/types.ts +9 -63
  289. package/src/router/url-params.ts +57 -0
  290. package/src/router.ts +157 -71
  291. package/src/rsc/full-payload.ts +70 -0
  292. package/src/rsc/handler-context.ts +3 -2
  293. package/src/rsc/handler.ts +291 -217
  294. package/src/rsc/helpers.ts +168 -46
  295. package/src/rsc/index.ts +2 -5
  296. package/src/rsc/json-route-result.ts +38 -0
  297. package/src/rsc/loader-fetch.ts +114 -38
  298. package/src/rsc/manifest-init.ts +29 -42
  299. package/src/rsc/nonce.ts +10 -1
  300. package/src/rsc/origin-guard.ts +39 -25
  301. package/src/rsc/progressive-enhancement.ts +124 -13
  302. package/src/rsc/redirect-guard.ts +100 -0
  303. package/src/rsc/response-cache-serve.ts +238 -0
  304. package/src/rsc/response-error.ts +79 -12
  305. package/src/rsc/response-route-handler.ts +99 -189
  306. package/src/rsc/rsc-rendering.ts +421 -76
  307. package/src/rsc/runtime-warnings.ts +23 -10
  308. package/src/rsc/server-action.ts +282 -116
  309. package/src/rsc/shell-capture.ts +1158 -0
  310. package/src/rsc/shell-serve.ts +150 -0
  311. package/src/rsc/ssr-setup.ts +16 -0
  312. package/src/rsc/transition-gate.ts +89 -0
  313. package/src/rsc/types.ts +53 -5
  314. package/src/runtime-env.ts +18 -0
  315. package/src/search-params.ts +35 -30
  316. package/src/segment-loader-promise.ts +49 -4
  317. package/src/segment-system.tsx +350 -149
  318. package/src/serialize.ts +243 -0
  319. package/src/server/context.ts +208 -51
  320. package/src/server/cookie-parse.ts +32 -0
  321. package/src/server/cookie-store.ts +152 -5
  322. package/src/server/handle-store.ts +21 -38
  323. package/src/server/loader-registry.ts +33 -42
  324. package/src/server/request-context.ts +395 -176
  325. package/src/ssr/index.tsx +458 -178
  326. package/src/ssr/ssr-root.tsx +228 -0
  327. package/src/static-handler.ts +10 -13
  328. package/src/testing/cache-status.ts +162 -0
  329. package/src/testing/collect-handle.ts +46 -0
  330. package/src/testing/dispatch.ts +813 -0
  331. package/src/testing/dom.entry.ts +22 -0
  332. package/src/testing/e2e/fixture.ts +188 -0
  333. package/src/testing/e2e/index.ts +128 -0
  334. package/src/testing/e2e/matchers.ts +35 -0
  335. package/src/testing/e2e/page-helpers.ts +272 -0
  336. package/src/testing/e2e/parity.ts +387 -0
  337. package/src/testing/e2e/server.ts +195 -0
  338. package/src/testing/flight-matchers.ts +97 -0
  339. package/src/testing/flight-normalize.ts +11 -0
  340. package/src/testing/flight-runtime.d.ts +57 -0
  341. package/src/testing/flight-tree.ts +682 -0
  342. package/src/testing/flight.entry.ts +52 -0
  343. package/src/testing/flight.ts +257 -0
  344. package/src/testing/generated-routes.ts +199 -0
  345. package/src/testing/index.ts +105 -0
  346. package/src/testing/internal/context.ts +371 -0
  347. package/src/testing/internal/flight-client-globals.ts +30 -0
  348. package/src/testing/internal/seed-vars.ts +54 -0
  349. package/src/testing/render-handler.ts +357 -0
  350. package/src/testing/render-route.tsx +584 -0
  351. package/src/testing/run-loader.ts +385 -0
  352. package/src/testing/run-middleware.ts +205 -0
  353. package/src/testing/run-transition-when.ts +164 -0
  354. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  355. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  356. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  357. package/src/testing/vitest-stubs/version.ts +5 -0
  358. package/src/testing/vitest.ts +305 -0
  359. package/src/theme/ThemeProvider.tsx +56 -84
  360. package/src/theme/ThemeScript.tsx +7 -9
  361. package/src/theme/constants.ts +52 -13
  362. package/src/theme/index.ts +0 -7
  363. package/src/theme/theme-context.ts +1 -5
  364. package/src/theme/theme-script.ts +22 -21
  365. package/src/theme/use-theme.ts +0 -3
  366. package/src/types/boundaries.ts +0 -35
  367. package/src/types/cache-types.ts +13 -4
  368. package/src/types/error-types.ts +30 -90
  369. package/src/types/global-namespace.ts +54 -41
  370. package/src/types/handler-context.ts +110 -62
  371. package/src/types/index.ts +3 -10
  372. package/src/types/loader-types.ts +11 -9
  373. package/src/types/request-scope.ts +112 -0
  374. package/src/types/route-config.ts +20 -52
  375. package/src/types/route-entry.ts +0 -6
  376. package/src/types/segments.ts +135 -14
  377. package/src/urls/include-helper.ts +19 -64
  378. package/src/urls/include-provider.ts +71 -0
  379. package/src/urls/index.ts +2 -11
  380. package/src/urls/path-helper-types.ts +63 -17
  381. package/src/urls/path-helper.ts +22 -106
  382. package/src/urls/pattern-types.ts +72 -19
  383. package/src/urls/response-types.ts +22 -29
  384. package/src/urls/type-extraction.ts +98 -154
  385. package/src/urls/urls-function.ts +1 -19
  386. package/src/use-loader.tsx +292 -107
  387. package/src/vercel/index.ts +11 -0
  388. package/src/vercel/tracing.ts +88 -0
  389. package/src/vite/debug.ts +185 -0
  390. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  391. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  392. package/src/vite/discovery/discover-routers.ts +127 -86
  393. package/src/vite/discovery/discovery-errors.ts +255 -0
  394. package/src/vite/discovery/gate-state.ts +171 -0
  395. package/src/vite/discovery/prerender-collection.ts +96 -68
  396. package/src/vite/discovery/route-types-writer.ts +40 -84
  397. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  398. package/src/vite/discovery/state.ts +45 -1
  399. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  400. package/src/vite/index.ts +4 -0
  401. package/src/vite/inject-client-debug.ts +88 -0
  402. package/src/vite/plugin-types.ts +210 -10
  403. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  404. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  405. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  406. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  407. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  408. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  409. package/src/vite/plugins/expose-action-id.ts +48 -95
  410. package/src/vite/plugins/expose-id-utils.ts +88 -55
  411. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  412. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  413. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  414. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  415. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  416. package/src/vite/plugins/performance-tracks.ts +26 -25
  417. package/src/vite/plugins/refresh-cmd.ts +1 -1
  418. package/src/vite/plugins/use-cache-transform.ts +73 -83
  419. package/src/vite/plugins/vercel-output.ts +384 -0
  420. package/src/vite/plugins/version-injector.ts +40 -29
  421. package/src/vite/plugins/version-plugin.ts +37 -40
  422. package/src/vite/plugins/virtual-entries.ts +138 -27
  423. package/src/vite/rango.ts +236 -138
  424. package/src/vite/router-discovery.ts +927 -136
  425. package/src/vite/utils/ast-handler-extract.ts +26 -35
  426. package/src/vite/utils/banner.ts +1 -1
  427. package/src/vite/utils/bundle-analysis.ts +10 -15
  428. package/src/vite/utils/client-chunks.ts +184 -0
  429. package/src/vite/utils/directive-prologue.ts +40 -0
  430. package/src/vite/utils/forward-user-plugins.ts +171 -0
  431. package/src/vite/utils/manifest-utils.ts +4 -59
  432. package/src/vite/utils/package-resolution.ts +20 -52
  433. package/src/vite/utils/prerender-utils.ts +71 -43
  434. package/src/vite/utils/shared-utils.ts +142 -43
  435. package/src/browser/action-response-classifier.ts +0 -99
  436. package/src/browser/react/use-client-cache.ts +0 -58
  437. package/src/browser/shallow.ts +0 -40
  438. package/src/handles/index.ts +0 -7
  439. package/src/network-error-thrower.tsx +0 -23
  440. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,194 @@
1
+ import type { Plugin } from "vite";
2
+
3
+ const VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
4
+ const NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
5
+ const CF_PREFIX = "cloudflare:";
6
+
7
+ /**
8
+ * `globalThis` key the `cloudflare:workers` stub reads to populate its
9
+ * `env` export. Router discovery sets this to the resolved `buildEnv`
10
+ * proxy (from `wrangler.getPlatformProxy()` when `buildEnv: "auto"` is
11
+ * configured, or a user-supplied object otherwise) before importing the
12
+ * worker entry, and clears it after discovery disposes the proxy. When
13
+ * unset, the stub's `env` falls back to `{}`.
14
+ *
15
+ * Using `globalThis` is the only cross-module bridge that works here:
16
+ * the stub's `load` hook returns source text, not a live closure, but
17
+ * the stub module is evaluated in the same Node process as the
18
+ * discovery plugin — so reading a global at module-evaluation time
19
+ * reaches whatever the plugin assigned there. A symbol key would be
20
+ * cleaner in-process but awkward to name from the stub source.
21
+ *
22
+ * @internal
23
+ */
24
+ export const BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
25
+
26
+ const SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
27
+
28
+ const IMPORT_NODE_TYPES = new Set([
29
+ "ImportDeclaration",
30
+ "ImportExpression",
31
+ "ExportNamedDeclaration",
32
+ "ExportAllDeclaration",
33
+ ]);
34
+
35
+ const STUBS: Record<string, string> = {
36
+ "cloudflare:workers": `
37
+ export class DurableObject { constructor(_ctx, _env) {} }
38
+ export class WorkerEntrypoint { constructor(_ctx, _env) {} }
39
+ export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
40
+ export class RpcTarget {}
41
+ export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
42
+ export default {};
43
+ `,
44
+ "cloudflare:email": `
45
+ export class EmailMessage { constructor(_from, _to, _raw) {} }
46
+ export default {};
47
+ `,
48
+ "cloudflare:sockets": `
49
+ export function connect() { return {}; }
50
+ export default {};
51
+ `,
52
+ "cloudflare:workflows": `
53
+ export class NonRetryableError extends Error {
54
+ constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
55
+ }
56
+ export default {};
57
+ `,
58
+ };
59
+
60
+ const FALLBACK_STUB = `export default {};\n`;
61
+
62
+ interface AstNode {
63
+ type: string;
64
+ start?: number;
65
+ end?: number;
66
+ source?: AstNode | null;
67
+ value?: unknown;
68
+ [key: string]: unknown;
69
+ }
70
+
71
+ /**
72
+ * Stubs `cloudflare:*` imports for the discovery-time Node Vite server.
73
+ *
74
+ * Discovery only evaluates user module top-level code — it never invokes
75
+ * DurableObject / WorkerEntrypoint / Workflow handlers — so empty base
76
+ * classes are enough for `class X extends DurableObject {}` declarations
77
+ * to load in Node, where `cloudflare:*` is otherwise unresolvable.
78
+ *
79
+ * Interception point: a transform hook parses source with Rollup's
80
+ * plugin-context parser (`this.parse`) and rewrites only real import
81
+ * specifier spans (`import ... from "cloudflare:xxx"`,
82
+ * `import("cloudflare:xxx")`, `export ... from "cloudflare:xxx"`) to a
83
+ * plain virtual module name (`virtual:rango-cloudflare-stub-xxx`).
84
+ * This must be done in transform because Vite's module runner routes
85
+ * URL-scheme specifiers straight to Node's native ESM loader without
86
+ * consulting plugin `resolveId` hooks. Using the AST (instead of a
87
+ * text regex or a permissive lexer) guarantees that strings,
88
+ * comments, and template literals that merely contain import-like
89
+ * text are never mutated — the walker only looks at the four import
90
+ * node types.
91
+ *
92
+ * The transform runs on user source AND on compiled node_modules
93
+ * output: real-world CF packages (e.g. the Cloudflare Agents SDK)
94
+ * ship compiled JS that contains `import ... from "cloudflare:email"`
95
+ * and similar, so excluding node_modules would leave those imports
96
+ * unrewritten. Cost is small because the early exit (`code.includes`)
97
+ * skips files with no cloudflare: mention.
98
+ *
99
+ * The plugin intentionally runs at Vite's default ordering (no
100
+ * `enforce: "pre"`) so TS/JSX has already been compiled to plain JS
101
+ * by the time `this.parse` runs — acorn doesn't understand
102
+ * non-standard syntax.
103
+ *
104
+ * `cloudflare:workers`, `cloudflare:email`, `cloudflare:sockets`, and
105
+ * `cloudflare:workflows` each get curated stubs with the well-known
106
+ * symbols that appear in top-level `extends` positions. Any other
107
+ * `cloudflare:*` specifier falls back to an empty default export —
108
+ * discovery never executes the handlers, so an empty module is safe
109
+ * for anything the graph pulls in transitively.
110
+ *
111
+ * Only registered in the discovery temp server, not the user's runtime
112
+ * config.
113
+ * @internal
114
+ */
115
+ export function createCloudflareProtocolStubPlugin(): Plugin {
116
+ return {
117
+ name: "@rangojs/router:cloudflare-protocol-stub",
118
+ transform(code, id) {
119
+ const cleanId = id.split("?")[0] ?? id;
120
+ if (!SOURCE_EXT_RE.test(cleanId)) return null;
121
+ if (!code.includes(CF_PREFIX)) return null;
122
+
123
+ let ast: AstNode;
124
+ try {
125
+ ast = this.parse(code, { lang: "tsx" }) as unknown as AstNode;
126
+ } catch {
127
+ // Malformed source — let a downstream plugin surface the parse error.
128
+ return null;
129
+ }
130
+
131
+ const hits: Array<{ start: number; end: number; value: string }> = [];
132
+ walk(ast, (node) => {
133
+ if (!IMPORT_NODE_TYPES.has(node.type)) return;
134
+ const source = node.source;
135
+ if (!source || source.type !== "Literal") return;
136
+ if (typeof source.value !== "string") return;
137
+ if (!source.value.startsWith(CF_PREFIX)) return;
138
+ if (typeof source.start !== "number" || typeof source.end !== "number")
139
+ return;
140
+ hits.push({
141
+ start: source.start,
142
+ end: source.end,
143
+ value: source.value,
144
+ });
145
+ });
146
+
147
+ if (hits.length === 0) return null;
148
+
149
+ hits.sort((a, b) => b.start - a.start);
150
+ let out = code;
151
+ for (const hit of hits) {
152
+ const submodule = hit.value.slice(CF_PREFIX.length);
153
+ const quote = code[hit.start] === "'" ? "'" : '"';
154
+ out =
155
+ out.slice(0, hit.start) +
156
+ quote +
157
+ VIRTUAL_PREFIX +
158
+ submodule +
159
+ quote +
160
+ out.slice(hit.end);
161
+ }
162
+ return { code: out, map: null };
163
+ },
164
+ resolveId(id) {
165
+ if (id.startsWith(VIRTUAL_PREFIX)) {
166
+ return "\0" + id;
167
+ }
168
+ return null;
169
+ },
170
+ load(id) {
171
+ if (!id.startsWith(NULL_PREFIX)) return null;
172
+ const submodule = id.slice(NULL_PREFIX.length);
173
+ const specifier = CF_PREFIX + submodule;
174
+ return STUBS[specifier] ?? FALLBACK_STUB;
175
+ },
176
+ };
177
+ }
178
+
179
+ function walk(node: unknown, visit: (n: AstNode) => void): void {
180
+ if (!node || typeof node !== "object") return;
181
+ if (Array.isArray(node)) {
182
+ for (const child of node) walk(child, visit);
183
+ return;
184
+ }
185
+ const n = node as AstNode;
186
+ if (typeof n.type !== "string") return;
187
+ visit(n);
188
+ for (const key in n) {
189
+ if (key === "loc" || key === "start" || key === "end" || key === "range") {
190
+ continue;
191
+ }
192
+ walk(n[key], visit);
193
+ }
194
+ }
@@ -3,10 +3,10 @@ import MagicString from "magic-string";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs";
5
5
  import { normalizePath } from "./expose-id-utils.js";
6
+ import { createRangoDebugger, createCounter, NS } from "../debug.js";
7
+
8
+ const debug = createRangoDebugger(NS.transform);
6
9
 
7
- /**
8
- * Type for the RSC plugin's manager API
9
- */
10
10
  interface RscPluginManager {
11
11
  serverReferenceMetaMap: Record<
12
12
  string,
@@ -23,14 +23,9 @@ interface RscPluginApi {
23
23
  manager: RscPluginManager;
24
24
  }
25
25
 
26
- /**
27
- * Get the RSC plugin's API from Vite config
28
- */
29
26
  function getRscPluginApi(config: ResolvedConfig): RscPluginApi | undefined {
30
- // Try by name first
31
27
  let plugin = config.plugins.find((p) => p.name === "rsc:minimal");
32
28
 
33
- // Fallback: find by API structure if name lookup fails
34
29
  if (!plugin) {
35
30
  plugin = config.plugins.find(
36
31
  (p) =>
@@ -39,7 +34,7 @@ function getRscPluginApi(config: ResolvedConfig): RscPluginApi | undefined {
39
34
  );
40
35
  if (plugin) {
41
36
  console.warn(
42
- `[rsc-router:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). ` +
37
+ `[rango:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). ` +
43
38
  `Consider updating the name lookup if the plugin was renamed.`,
44
39
  );
45
40
  }
@@ -59,13 +54,11 @@ function getRscPluginApi(config: ResolvedConfig): RscPluginApi | undefined {
59
54
  function isUseServerModule(filePath: string): boolean {
60
55
  try {
61
56
  const content = fs.readFileSync(filePath, "utf-8");
62
- // Remove leading comments and whitespace to find the first meaningful content
63
57
  const trimmed = content
64
- .replace(/^\s*\/\/[^\n]*\n/gm, "") // Remove single-line comments
65
- .replace(/^\s*\/\*[\s\S]*?\*\/\s*/gm, "") // Remove multi-line comments
58
+ .replace(/^\s*\/\/[^\n]*\n/gm, "")
59
+ .replace(/^\s*\/\*[\s\S]*?\*\/\s*/gm, "")
66
60
  .trimStart();
67
61
 
68
- // Check if the file starts with "use server" directive
69
62
  return (
70
63
  trimmed.startsWith('"use server"') || trimmed.startsWith("'use server'")
71
64
  );
@@ -74,18 +67,6 @@ function isUseServerModule(filePath: string): boolean {
74
67
  }
75
68
  }
76
69
 
77
- /**
78
- * Transform code to expose action IDs on createServerReference calls.
79
- * Wraps each call with an IIFE that attaches $id to the returned function.
80
- *
81
- * @param code - The source code to transform
82
- * @param sourceId - The source file identifier (for sourcemap)
83
- * @param hashToFileMap - Optional mapping from hash to file path (for server bundles)
84
- */
85
- /**
86
- * Apply createServerReference wrapping to a MagicString instance.
87
- * Returns true if any changes were made.
88
- */
89
70
  function applyServerReferenceWrapping(
90
71
  code: string,
91
72
  s: MagicString,
@@ -95,11 +76,6 @@ function applyServerReferenceWrapping(
95
76
  return false;
96
77
  }
97
78
 
98
- // Match: createServerReference("hash#actionName", ...) or $$ReactClient.createServerReference(...)
99
- // The RSC plugin uses $$ReactClient namespace in transformed code.
100
- // Note: [^)]* cannot handle nested parens in trailing args. This is safe in practice
101
- // because the RSC plugin always generates simple variable references (e.g., callServer)
102
- // as the second argument, never nested function calls.
103
79
  const pattern =
104
80
  /((?:\$\$\w+\.)?createServerReference)\(("[^"]+#[^"]+")([^)]*)\)/g;
105
81
 
@@ -112,23 +88,19 @@ function applyServerReferenceWrapping(
112
88
  const start = match.index;
113
89
  const end = start + fullMatch.length;
114
90
 
115
- // Parse the ID to potentially replace hash with file path
116
91
  let finalIdArg = idArg;
117
92
  if (hashToFileMap) {
118
- // idArg is like '"hash#actionName"', extract the parts
119
93
  const idValue = idArg.slice(1, -1); // Remove quotes
120
94
  const hashMatch = idValue.match(/^([^#]+)#(.+)$/);
121
95
  if (hashMatch) {
122
96
  const [, hash, actionName] = hashMatch;
123
97
  const filePath = hashToFileMap.get(hash);
124
98
  if (filePath) {
125
- // Replace hash with file path for server-side
126
99
  finalIdArg = `"${filePath}#${actionName}"`;
127
100
  }
128
101
  }
129
102
  }
130
103
 
131
- // Wrap the createServerReference call to attach $$id to the returned function
132
104
  const replacement = `(function(fn) { fn.$$id = ${finalIdArg}; return fn; })(${fnCall}(${idArg}${rest}))`;
133
105
  s.overwrite(start, end, replacement);
134
106
  }
@@ -152,29 +124,6 @@ function transformServerReferences(
152
124
  };
153
125
  }
154
126
 
155
- /**
156
- * Transform registerServerReference calls in server bundles to use file paths instead of hashes.
157
- * Pattern: registerServerReference(fn, "hash", "exportName")
158
- * React's registerServerReference sets $$id = hash + "#" + exportName
159
- * By replacing the hash with file path, $$id will contain the file path for revalidation matching.
160
- *
161
- * Only actions from module-level "use server" files are transformed.
162
- * Inline actions (defined in RSC components with "use server" inside a function) are NOT in
163
- * hashToFileMap and keep their hashed IDs. This is intentional for client security:
164
- * - Module-level "use server" files: shared action modules, file path helps revalidation
165
- * - Inline actions: one-off actions in RSC, hash ID prevents file path exposure to client
166
- *
167
- * @param code - The source code to transform
168
- * @param sourceId - The source file identifier (for sourcemap)
169
- * @param hashToFileMap - Mapping from hash to file path (only module-level "use server" files)
170
- */
171
- /**
172
- * Apply registerServerReference wrapping to a MagicString instance.
173
- * Returns true if any changes were made.
174
- *
175
- * Only actions from module-level "use server" files are transformed.
176
- * Inline actions keep their hashed IDs for client security.
177
- */
178
127
  function applyRegisterReferenceWrapping(
179
128
  code: string,
180
129
  s: MagicString,
@@ -184,8 +133,6 @@ function applyRegisterReferenceWrapping(
184
133
  return false;
185
134
  }
186
135
 
187
- // Match: registerServerReference(fn, "hash", "exportName")
188
- // The hash is the second argument, exportName is the third
189
136
  const pattern =
190
137
  /registerServerReference\(([^,]+),\s*"([^"]+)",\s*"([^"]+)"\)/g;
191
138
 
@@ -197,15 +144,9 @@ function applyRegisterReferenceWrapping(
197
144
  const start = match.index;
198
145
  const end = start + fullMatch.length;
199
146
 
200
- // Look up the file path for this hash
201
147
  const filePath = hashToFileMap.get(hash);
202
148
  if (filePath) {
203
149
  hasChanges = true;
204
- // WRAP the call to add $id property with file path
205
- // Keep the original hash for React's action registry (so loadServerAction works)
206
- // Add $id (single dollar) with file path for revalidation matching
207
- // Note: We use $id instead of $$id because React's registerServerReference
208
- // sets $$id as a non-writable property
209
150
  const filePathId = `${filePath}#${exportName}`;
210
151
  const replacement = `(function(fn) { fn.$id = "${filePathId}"; return fn; })(registerServerReference(${fnArg}, "${hash}", "${exportName}"))`;
211
152
  s.overwrite(start, end, replacement);
@@ -254,6 +195,8 @@ export function exposeActionId(): Plugin {
254
195
  let isBuild = false;
255
196
  let hashToFileMap: Map<string, string> | undefined;
256
197
  let rscPluginApi: RscPluginApi | undefined;
198
+ const counterTransform = createCounter(debug, "expose-action-id transform");
199
+ const counterRender = createCounter(debug, "expose-action-id renderChunk");
257
200
 
258
201
  return {
259
202
  name: "@rangojs/router:expose-action-id",
@@ -268,6 +211,11 @@ export function exposeActionId(): Plugin {
268
211
  rscPluginApi = getRscPluginApi(config);
269
212
  },
270
213
 
214
+ buildEnd() {
215
+ counterTransform?.flush();
216
+ counterRender?.flush();
217
+ },
218
+
271
219
  buildStart() {
272
220
  // Verify RSC plugin is present at build start (after all config hooks have run)
273
221
  // This allows rsc-router:rsc-integration to dynamically add the RSC plugin
@@ -277,7 +225,7 @@ export function exposeActionId(): Plugin {
277
225
 
278
226
  if (!rscPluginApi) {
279
227
  throw new Error(
280
- "[rsc-router] Could not find @vitejs/plugin-rsc. " +
228
+ "[rango] Could not find @vitejs/plugin-rsc. " +
281
229
  "@rangojs/router requires the Vite RSC plugin, which is included automatically by rango().",
282
230
  );
283
231
  }
@@ -324,40 +272,45 @@ export function exposeActionId(): Plugin {
324
272
  return;
325
273
  }
326
274
 
327
- // Dev mode: no hash-to-file mapping needed (IDs are already file paths)
328
- return transformServerReferences(code, id);
275
+ const start = counterTransform ? performance.now() : 0;
276
+ try {
277
+ return transformServerReferences(code, id);
278
+ } finally {
279
+ counterTransform?.record(id, performance.now() - start);
280
+ }
329
281
  },
330
282
 
331
- // Build mode: renderChunk runs after all transforms and bundling complete
332
283
  renderChunk(code, chunk) {
333
- // Only RSC bundle should get file paths for revalidation matching
334
- // SSR bundle must NOT use file paths because client components run there
335
- // and need to match the client bundle during hydration (otherwise: error #418)
336
- const isRscEnv = this.environment?.name === "rsc";
337
-
338
- // Only use file path mapping for RSC environment
339
- const effectiveMap = isRscEnv ? hashToFileMap : undefined;
340
-
341
- // For RSC bundles, both createServerReference and registerServerReference
342
- // may need transforming. Use a single MagicString for correct sourcemaps.
343
- if (isRscEnv && hashToFileMap) {
344
- const s = new MagicString(code);
345
- const changed1 = applyServerReferenceWrapping(code, s, effectiveMap);
346
- const changed2 = applyRegisterReferenceWrapping(code, s, hashToFileMap);
347
- if (changed1 || changed2) {
348
- return {
349
- code: s.toString(),
350
- map: s.generateMap({
351
- source: chunk.fileName,
352
- includeContent: true,
353
- }),
354
- };
284
+ const start = counterRender ? performance.now() : 0;
285
+ try {
286
+ const isRscEnv = this.environment?.name === "rsc";
287
+
288
+ const effectiveMap = isRscEnv ? hashToFileMap : undefined;
289
+
290
+ if (isRscEnv && hashToFileMap) {
291
+ const s = new MagicString(code);
292
+ const changed1 = applyServerReferenceWrapping(code, s, effectiveMap);
293
+ const changed2 = applyRegisterReferenceWrapping(
294
+ code,
295
+ s,
296
+ hashToFileMap,
297
+ );
298
+ if (changed1 || changed2) {
299
+ return {
300
+ code: s.toString(),
301
+ map: s.generateMap({
302
+ source: chunk.fileName,
303
+ includeContent: true,
304
+ }),
305
+ };
306
+ }
307
+ return null;
355
308
  }
356
- return null;
357
- }
358
309
 
359
- // Non-RSC environments: only transform createServerReference calls
360
- return transformServerReferences(code, chunk.fileName, effectiveMap);
310
+ return transformServerReferences(code, chunk.fileName, effectiveMap);
311
+ } finally {
312
+ counterRender?.record(chunk.fileName, performance.now() - start);
313
+ }
361
314
  },
362
315
  };
363
316
  }
@@ -8,21 +8,12 @@ export function normalizePath(p: string): string {
8
8
  return p.split(path.sep).join("/");
9
9
  }
10
10
 
11
- /**
12
- * Generate a short hash for an ID.
13
- * Uses first 8 chars of SHA-256 hash for uniqueness while keeping IDs short.
14
- * Appends export name for easier debugging in production: "abc123#ExportName"
15
- */
16
11
  export function hashId(filePath: string, exportName: string): string {
17
12
  const input = `${filePath}#${exportName}`;
18
13
  const hash = crypto.createHash("sha256").update(input).digest("hex");
19
14
  return `${hash.slice(0, 8)}#${exportName}`;
20
15
  }
21
16
 
22
- /**
23
- * Build a stable ID for an export binding. Uses hashed IDs in production
24
- * builds (short + opaque) and readable path#name IDs in dev.
25
- */
26
17
  export function makeStubId(
27
18
  filePath: string,
28
19
  exportName: string,
@@ -31,19 +22,12 @@ export function makeStubId(
31
22
  return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
32
23
  }
33
24
 
34
- /**
35
- * Generate an 8-char hex hash for an inline static handler call site.
36
- * Uses file path and line number (plus optional index for same-line collisions).
37
- */
38
25
  export function hashInlineId(
39
26
  filePath: string,
40
- lineNumber: number,
41
- index?: number,
27
+ fnName: string,
28
+ index: number,
42
29
  ): string {
43
- const input =
44
- index !== undefined && index > 0
45
- ? `${filePath}:${lineNumber}:${index}`
46
- : `${filePath}:${lineNumber}`;
30
+ const input = `${filePath}:${fnName}:${index}`;
47
31
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
48
32
  }
49
33
 
@@ -57,11 +41,6 @@ export interface DetectedImports {
57
41
  any: boolean;
58
42
  }
59
43
 
60
- /**
61
- * Build a map from local binding name to exported names by walking
62
- * ExportNamedDeclaration nodes. Handles `export const X`, `export { X }`,
63
- * and `export { X as Y }`. Skips re-exports (`export { X } from "..."`).
64
- */
65
44
  export function buildExportMap(program: any): Map<string, string[]> {
66
45
  const exportMap = new Map<string, string[]>();
67
46
 
@@ -101,10 +80,6 @@ export function buildExportMap(program: any): Map<string, string[]> {
101
80
  return exportMap;
102
81
  }
103
82
 
104
- /**
105
- * Single-pass detection of all create* imports from @rangojs/router.
106
- * Returns which create functions are imported so we can skip unnecessary transforms.
107
- */
108
83
  export function detectImports(code: string): DetectedImports {
109
84
  // Extract all import declarations from @rangojs/router in one scan
110
85
  const importPattern =
@@ -131,10 +106,6 @@ export function detectImports(code: string): DetectedImports {
131
106
  if (/\bcreateRouter\b/.test(imports)) result.router = true;
132
107
  }
133
108
 
134
- // createRouter has a stricter check: only from "@rangojs/router" (not sub-paths).
135
- // NOTE: This is intentional — detectImports is used as a fast pre-filter in
136
- // exposeInternalIds (which does NOT handle router transforms). The separate
137
- // exposeRouterId plugin handles createRouter and DOES accept the /server subpath.
138
109
  if (result.router) {
139
110
  result.router =
140
111
  /import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router["']/.test(
@@ -153,11 +124,6 @@ export function detectImports(code: string): DetectedImports {
153
124
  return result;
154
125
  }
155
126
 
156
- /**
157
- * Skip past a string literal, template literal, or comment starting at pos.
158
- * Returns the index after the closing delimiter, or pos if not at a
159
- * string/comment start. Handles escape sequences and nested ${} in templates.
160
- */
161
127
  export function skipStringOrComment(code: string, pos: number): number {
162
128
  const ch = code[pos];
163
129
 
@@ -215,11 +181,6 @@ export function skipStringOrComment(code: string, pos: number): number {
215
181
  return pos;
216
182
  }
217
183
 
218
- /**
219
- * Find the matching closing paren starting after an already-opened paren.
220
- * Skips strings, template literals, and comments so parens inside them
221
- * don't affect depth tracking. Returns the index after the closing paren.
222
- */
223
184
  export function findMatchingParen(code: string, startPos: number): number {
224
185
  let depth = 1;
225
186
  let i = startPos;
@@ -236,10 +197,6 @@ export function findMatchingParen(code: string, startPos: number): number {
236
197
  return i;
237
198
  }
238
199
 
239
- /**
240
- * Count the number of top-level arguments in a function call.
241
- * Skips nested parens, brackets, braces, strings, and comments.
242
- */
243
200
  export function countArgs(
244
201
  code: string,
245
202
  startPos: number,
@@ -253,7 +210,16 @@ export function countArgs(
253
210
  while (i < endPos) {
254
211
  const skipped = skipStringOrComment(code, i);
255
212
  if (skipped > i) {
256
- hasContent = true;
213
+ // A comment is transparent: `createHandle(/* meta */)` has ZERO real
214
+ // arguments, so a comment-only region must NOT set hasContent (else the
215
+ // fallback path would count it as 1 arg and inject `, "id"` over an elided
216
+ // first slot — a syntax error). A string/template literal IS content.
217
+ const ch = code[i];
218
+ const isComment =
219
+ ch === "/" &&
220
+ i + 1 < code.length &&
221
+ (code[i + 1] === "/" || code[i + 1] === "*");
222
+ if (!isComment) hasContent = true;
257
223
  i = skipped;
258
224
  continue;
259
225
  }
@@ -275,10 +241,6 @@ export function countArgs(
275
241
  return hasContent ? argCount + 1 : 0;
276
242
  }
277
243
 
278
- /**
279
- * Find the end of a statement: skip whitespace and optional semicolon after
280
- * a closing paren position.
281
- */
282
244
  export function findStatementEnd(code: string, pos: number): number {
283
245
  let i = pos;
284
246
  while (i < code.length && /\s/.test(code[i])) {
@@ -290,10 +252,81 @@ export function findStatementEnd(code: string, pos: number): number {
290
252
  return i;
291
253
  }
292
254
 
255
+ export { escapeRegExp } from "../../regex-escape.js";
256
+
293
257
  /**
294
- * Escape special regex characters in a string so it can be safely
295
- * interpolated into a RegExp pattern.
258
+ * Given an index pointing just past a `create*` callee identifier, return the
259
+ * index of the call's opening `(`, accounting for an optional generic type
260
+ * argument list `<...>` with arbitrarily nested `<>`. Returns -1 when the next
261
+ * non-trivia token is neither `<` nor `(` (i.e. not a call site).
262
+ *
263
+ * Replaces the single-level `<[^>]*>` regex assumption, which stopped at the
264
+ * first `>` and so never reached `(` for `createRouter<Config<Env>>(`.
265
+ *
266
+ * Whitespace, comments, and strings between the callee and `(` are skipped via
267
+ * skipStringOrComment. The `<...>` scan only balances angle brackets; it does
268
+ * not attempt to parse full TS type syntax (sufficient for locating the paren).
296
269
  */
297
- export function escapeRegExp(input: string): string {
298
- return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
270
+ export function findCallParenAfterGenerics(
271
+ code: string,
272
+ afterCalleeIndex: number,
273
+ ): number {
274
+ let i = afterCalleeIndex;
275
+ // Skip leading whitespace/comments/strings before `<` or `(`.
276
+ while (i < code.length) {
277
+ const skipped = skipStringOrComment(code, i);
278
+ if (skipped > i) {
279
+ i = skipped;
280
+ continue;
281
+ }
282
+ if (/\s/.test(code[i])) {
283
+ i++;
284
+ continue;
285
+ }
286
+ break;
287
+ }
288
+
289
+ if (i >= code.length) return -1;
290
+
291
+ if (code[i] === "(") return i;
292
+
293
+ if (code[i] === "<") {
294
+ let depth = 0;
295
+ while (i < code.length) {
296
+ const skipped = skipStringOrComment(code, i);
297
+ if (skipped > i) {
298
+ i = skipped;
299
+ continue;
300
+ }
301
+ const ch = code[i];
302
+ if (ch === "<") {
303
+ depth++;
304
+ } else if (ch === ">") {
305
+ depth--;
306
+ if (depth === 0) {
307
+ i++;
308
+ break;
309
+ }
310
+ }
311
+ i++;
312
+ }
313
+ if (depth !== 0) return -1;
314
+ // After the balanced generic list, skip trivia and expect `(`.
315
+ while (i < code.length) {
316
+ const skipped = skipStringOrComment(code, i);
317
+ if (skipped > i) {
318
+ i = skipped;
319
+ continue;
320
+ }
321
+ if (/\s/.test(code[i])) {
322
+ i++;
323
+ continue;
324
+ }
325
+ break;
326
+ }
327
+ if (i < code.length && code[i] === "(") return i;
328
+ return -1;
329
+ }
330
+
331
+ return -1;
299
332
  }