@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c

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 (392) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +126 -16
  3. package/dist/bin/rango.js +319 -95
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +2724 -1053
  6. package/package.json +68 -14
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +64 -2
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +224 -32
  11. package/skills/caching/SKILL.md +279 -17
  12. package/skills/composability/SKILL.md +27 -3
  13. package/skills/css/SKILL.md +76 -0
  14. package/skills/debug-manifest/SKILL.md +4 -2
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +11 -9
  17. package/skills/hooks/SKILL.md +243 -29
  18. package/skills/host-router/SKILL.md +83 -23
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +68 -19
  21. package/skills/layout/SKILL.md +13 -9
  22. package/skills/links/SKILL.md +190 -23
  23. package/skills/loader/SKILL.md +235 -9
  24. package/skills/middleware/SKILL.md +18 -10
  25. package/skills/migrate-nextjs/SKILL.md +43 -19
  26. package/skills/migrate-react-router/SKILL.md +8 -2
  27. package/skills/mime-routes/SKILL.md +28 -1
  28. package/skills/observability/SKILL.md +172 -0
  29. package/skills/parallel/SKILL.md +18 -7
  30. package/skills/prerender/SKILL.md +65 -60
  31. package/skills/rango/SKILL.md +251 -24
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +115 -48
  34. package/skills/route/SKILL.md +46 -5
  35. package/skills/router-setup/SKILL.md +30 -8
  36. package/skills/scripts/SKILL.md +179 -0
  37. package/skills/server-actions/SKILL.md +775 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +130 -0
  40. package/skills/testing/bindings.md +103 -0
  41. package/skills/testing/cache-prerender.md +127 -0
  42. package/skills/testing/client-components.md +124 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +91 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +122 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +123 -0
  54. package/skills/typesafety/SKILL.md +322 -29
  55. package/skills/use-cache/SKILL.md +57 -14
  56. package/skills/view-transitions/SKILL.md +337 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +0 -65
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/connection-warmup.ts +134 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +192 -150
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/navigation-bridge.ts +94 -25
  70. package/src/browser/navigation-client.ts +121 -84
  71. package/src/browser/navigation-store-handle.ts +38 -0
  72. package/src/browser/navigation-store.ts +115 -67
  73. package/src/browser/navigation-transaction.ts +9 -59
  74. package/src/browser/network-error-handler.ts +34 -7
  75. package/src/browser/partial-update.ts +147 -128
  76. package/src/browser/prefetch/cache.ts +107 -56
  77. package/src/browser/prefetch/fetch.ts +204 -34
  78. package/src/browser/prefetch/queue.ts +6 -3
  79. package/src/browser/rango-state.ts +158 -76
  80. package/src/browser/react/Link.tsx +30 -7
  81. package/src/browser/react/NavigationProvider.tsx +283 -118
  82. package/src/browser/react/ScrollRestoration.tsx +10 -6
  83. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  84. package/src/browser/react/filter-segment-order.ts +66 -7
  85. package/src/browser/react/index.ts +0 -48
  86. package/src/browser/react/location-state-shared.ts +178 -8
  87. package/src/browser/react/location-state.ts +39 -14
  88. package/src/browser/react/use-action.ts +6 -15
  89. package/src/browser/react/use-handle.ts +17 -14
  90. package/src/browser/react/use-href.tsx +8 -1
  91. package/src/browser/react/use-link-status.ts +33 -8
  92. package/src/browser/react/use-navigation.ts +10 -5
  93. package/src/browser/react/use-params.ts +11 -11
  94. package/src/browser/react/use-reverse.ts +106 -0
  95. package/src/browser/react/use-router.ts +25 -3
  96. package/src/browser/react/use-search-params.ts +0 -5
  97. package/src/browser/react/use-segments.ts +11 -21
  98. package/src/browser/response-adapter.ts +99 -8
  99. package/src/browser/rsc-router.tsx +91 -24
  100. package/src/browser/scroll-restoration.ts +30 -17
  101. package/src/browser/segment-structure-assert.ts +2 -2
  102. package/src/browser/server-action-bridge.ts +214 -55
  103. package/src/browser/types.ts +80 -9
  104. package/src/browser/validate-redirect-origin.ts +43 -16
  105. package/src/build/collect-fallback-refs.ts +107 -0
  106. package/src/build/generate-manifest.ts +60 -35
  107. package/src/build/generate-route-types.ts +2 -1
  108. package/src/build/index.ts +8 -2
  109. package/src/build/prefix-tree-utils.ts +123 -0
  110. package/src/build/route-trie.ts +117 -14
  111. package/src/build/route-types/ast-route-extraction.ts +15 -8
  112. package/src/build/route-types/codegen.ts +16 -5
  113. package/src/build/route-types/include-resolution.ts +117 -23
  114. package/src/build/route-types/param-extraction.ts +6 -3
  115. package/src/build/route-types/per-module-writer.ts +22 -6
  116. package/src/build/route-types/router-processing.ts +55 -28
  117. package/src/build/route-types/scan-filter.ts +1 -1
  118. package/src/build/route-types/source-scan.ts +216 -0
  119. package/src/build/runtime-discovery.ts +9 -20
  120. package/src/cache/cache-error.ts +104 -0
  121. package/src/cache/cache-key-utils.ts +29 -13
  122. package/src/cache/cache-policy.ts +108 -34
  123. package/src/cache/cache-runtime.ts +224 -41
  124. package/src/cache/cache-scope.ts +188 -82
  125. package/src/cache/cache-tag.ts +103 -0
  126. package/src/cache/cf/cf-base64.ts +33 -0
  127. package/src/cache/cf/cf-cache-constants.ts +127 -0
  128. package/src/cache/cf/cf-cache-store.ts +1989 -378
  129. package/src/cache/cf/cf-cache-types.ts +349 -0
  130. package/src/cache/cf/cf-kv-utils.ts +46 -0
  131. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  132. package/src/cache/cf/index.ts +6 -16
  133. package/src/cache/document-cache.ts +89 -21
  134. package/src/cache/handle-snapshot.ts +70 -0
  135. package/src/cache/index.ts +10 -20
  136. package/src/cache/memory-segment-store.ts +136 -37
  137. package/src/cache/profile-registry.ts +46 -31
  138. package/src/cache/read-through-swr.ts +56 -12
  139. package/src/cache/segment-codec.ts +9 -17
  140. package/src/cache/tag-invalidation.ts +230 -0
  141. package/src/cache/types.ts +37 -100
  142. package/src/client.rsc.tsx +44 -21
  143. package/src/client.tsx +36 -61
  144. package/src/cloudflare/index.ts +11 -0
  145. package/src/cloudflare/tracing.ts +109 -0
  146. package/src/component-utils.ts +19 -0
  147. package/src/components/DefaultDocument.tsx +8 -2
  148. package/src/context-var.ts +18 -6
  149. package/src/decode-loader-results.ts +52 -0
  150. package/src/defer.ts +196 -0
  151. package/src/deps/ssr.ts +0 -1
  152. package/src/encode-kv.ts +49 -0
  153. package/src/errors.ts +30 -4
  154. package/src/escape-script.ts +52 -0
  155. package/src/handle.ts +31 -23
  156. package/src/handles/MetaTags.tsx +62 -19
  157. package/src/handles/Scripts.tsx +183 -0
  158. package/src/handles/breadcrumbs.ts +37 -8
  159. package/src/handles/is-thenable.ts +19 -0
  160. package/src/handles/meta.ts +51 -40
  161. package/src/handles/script.ts +244 -0
  162. package/src/host/cookie-handler.ts +9 -60
  163. package/src/host/errors.ts +0 -24
  164. package/src/host/index.ts +8 -2
  165. package/src/host/pattern-matcher.ts +23 -52
  166. package/src/host/router.ts +107 -99
  167. package/src/host/testing.ts +40 -27
  168. package/src/host/types.ts +37 -4
  169. package/src/host/utils.ts +1 -1
  170. package/src/href-client.ts +137 -22
  171. package/src/index.rsc.ts +96 -12
  172. package/src/index.ts +94 -14
  173. package/src/internal-debug.ts +11 -10
  174. package/src/loader-store.ts +500 -0
  175. package/src/loader.rsc.ts +20 -13
  176. package/src/loader.ts +12 -11
  177. package/src/missing-id-error.ts +68 -0
  178. package/src/outlet-context.ts +1 -1
  179. package/src/outlet-provider.tsx +1 -5
  180. package/src/prerender/param-hash.ts +16 -16
  181. package/src/prerender/store.ts +32 -37
  182. package/src/prerender.ts +61 -6
  183. package/src/redirect-origin.ts +100 -0
  184. package/src/regex-escape.ts +8 -0
  185. package/src/render-error-thrower.tsx +20 -0
  186. package/src/response-utils.ts +34 -0
  187. package/src/reverse.ts +65 -40
  188. package/src/root-error-boundary.tsx +1 -19
  189. package/src/route-content-wrapper.tsx +19 -77
  190. package/src/route-definition/dsl-helpers.ts +304 -309
  191. package/src/route-definition/helper-factories.ts +28 -140
  192. package/src/route-definition/helpers-types.ts +82 -55
  193. package/src/route-definition/index.ts +1 -2
  194. package/src/route-definition/redirect.ts +44 -11
  195. package/src/route-definition/resolve-handler-use.ts +12 -1
  196. package/src/route-definition/use-item-types.ts +29 -0
  197. package/src/route-map-builder.ts +0 -16
  198. package/src/route-types.ts +19 -46
  199. package/src/router/basename.ts +14 -0
  200. package/src/router/content-negotiation.ts +73 -25
  201. package/src/router/error-handling.ts +45 -18
  202. package/src/router/find-match.ts +44 -23
  203. package/src/router/handler-context.ts +27 -43
  204. package/src/router/instrument.ts +350 -0
  205. package/src/router/intercept-resolution.ts +39 -20
  206. package/src/router/lazy-includes.ts +10 -47
  207. package/src/router/loader-resolution.ts +155 -72
  208. package/src/router/logging.ts +0 -6
  209. package/src/router/manifest.ts +18 -29
  210. package/src/router/match-api.ts +9 -24
  211. package/src/router/match-context.ts +0 -22
  212. package/src/router/match-handlers.ts +58 -58
  213. package/src/router/match-middleware/background-revalidation.ts +40 -24
  214. package/src/router/match-middleware/cache-lookup.ts +159 -285
  215. package/src/router/match-middleware/cache-store.ts +64 -52
  216. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  217. package/src/router/match-middleware/segment-resolution.ts +0 -22
  218. package/src/router/match-pipelines.ts +1 -42
  219. package/src/router/match-result.ts +44 -74
  220. package/src/router/metrics.ts +0 -34
  221. package/src/router/middleware-types.ts +7 -134
  222. package/src/router/middleware.ts +247 -166
  223. package/src/router/navigation-snapshot.ts +0 -51
  224. package/src/router/params-util.ts +23 -0
  225. package/src/router/pattern-matching.ts +85 -94
  226. package/src/router/prefetch-cache-ttl.ts +51 -0
  227. package/src/router/prerender-match.ts +104 -65
  228. package/src/router/preview-match.ts +3 -1
  229. package/src/router/request-classification.ts +28 -62
  230. package/src/router/revalidation.ts +123 -73
  231. package/src/router/route-snapshot.ts +0 -1
  232. package/src/router/router-context.ts +3 -28
  233. package/src/router/router-interfaces.ts +83 -35
  234. package/src/router/router-options.ts +136 -5
  235. package/src/router/router-registry.ts +2 -5
  236. package/src/router/segment-resolution/fresh.ts +97 -84
  237. package/src/router/segment-resolution/helpers.ts +86 -6
  238. package/src/router/segment-resolution/loader-cache.ts +76 -39
  239. package/src/router/segment-resolution/revalidation.ts +272 -320
  240. package/src/router/segment-resolution/static-store.ts +19 -5
  241. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  242. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  243. package/src/router/segment-resolution.ts +5 -1
  244. package/src/router/segment-wrappers.ts +6 -5
  245. package/src/router/state-cookie-name.ts +33 -0
  246. package/src/router/substitute-pattern-params.ts +56 -0
  247. package/src/router/telemetry-otel.ts +161 -199
  248. package/src/router/telemetry.ts +96 -19
  249. package/src/router/timeout.ts +0 -20
  250. package/src/router/tracing.ts +206 -0
  251. package/src/router/trie-matching.ts +162 -64
  252. package/src/router/types.ts +9 -63
  253. package/src/router/url-params.ts +0 -5
  254. package/src/router.ts +110 -55
  255. package/src/rsc/handler-context.ts +3 -2
  256. package/src/rsc/handler.ts +264 -220
  257. package/src/rsc/helpers.ts +100 -6
  258. package/src/rsc/index.ts +2 -5
  259. package/src/rsc/json-route-result.ts +38 -0
  260. package/src/rsc/loader-fetch.ts +114 -38
  261. package/src/rsc/manifest-init.ts +28 -41
  262. package/src/rsc/origin-guard.ts +39 -25
  263. package/src/rsc/progressive-enhancement.ts +117 -11
  264. package/src/rsc/redirect-guard.ts +99 -0
  265. package/src/rsc/response-cache-serve.ts +238 -0
  266. package/src/rsc/response-error.ts +79 -12
  267. package/src/rsc/response-route-handler.ts +88 -188
  268. package/src/rsc/rsc-rendering.ts +98 -76
  269. package/src/rsc/runtime-warnings.ts +23 -10
  270. package/src/rsc/server-action.ts +281 -117
  271. package/src/rsc/ssr-setup.ts +16 -0
  272. package/src/rsc/transition-gate.ts +89 -0
  273. package/src/rsc/types.ts +23 -5
  274. package/src/runtime-env.ts +18 -0
  275. package/src/search-params.ts +35 -30
  276. package/src/segment-loader-promise.ts +31 -4
  277. package/src/segment-system.tsx +254 -143
  278. package/src/serialize.ts +243 -0
  279. package/src/server/context.ts +163 -51
  280. package/src/server/cookie-parse.ts +32 -0
  281. package/src/server/cookie-store.ts +80 -5
  282. package/src/server/handle-store.ts +21 -38
  283. package/src/server/loader-registry.ts +33 -42
  284. package/src/server/request-context.ts +287 -178
  285. package/src/ssr/index.tsx +21 -16
  286. package/src/static-handler.ts +10 -13
  287. package/src/testing/cache-status.ts +162 -0
  288. package/src/testing/collect-handle.ts +40 -0
  289. package/src/testing/dispatch.ts +701 -0
  290. package/src/testing/dom.entry.ts +22 -0
  291. package/src/testing/e2e/fixture.ts +188 -0
  292. package/src/testing/e2e/index.ts +128 -0
  293. package/src/testing/e2e/matchers.ts +35 -0
  294. package/src/testing/e2e/page-helpers.ts +272 -0
  295. package/src/testing/e2e/parity.ts +387 -0
  296. package/src/testing/e2e/server.ts +195 -0
  297. package/src/testing/flight-matchers.ts +97 -0
  298. package/src/testing/flight-normalize.ts +11 -0
  299. package/src/testing/flight-runtime.d.ts +57 -0
  300. package/src/testing/flight-tree.ts +682 -0
  301. package/src/testing/flight.entry.ts +52 -0
  302. package/src/testing/flight.ts +257 -0
  303. package/src/testing/generated-routes.ts +183 -0
  304. package/src/testing/index.ts +105 -0
  305. package/src/testing/internal/context.ts +371 -0
  306. package/src/testing/internal/flight-client-globals.ts +30 -0
  307. package/src/testing/internal/seed-vars.ts +54 -0
  308. package/src/testing/render-handler.ts +357 -0
  309. package/src/testing/render-route.tsx +581 -0
  310. package/src/testing/run-loader.ts +385 -0
  311. package/src/testing/run-middleware.ts +205 -0
  312. package/src/testing/run-transition-when.ts +164 -0
  313. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  314. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  315. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  316. package/src/testing/vitest-stubs/version.ts +5 -0
  317. package/src/testing/vitest.ts +305 -0
  318. package/src/theme/ThemeProvider.tsx +20 -58
  319. package/src/theme/ThemeScript.tsx +7 -9
  320. package/src/theme/constants.ts +52 -13
  321. package/src/theme/index.ts +0 -7
  322. package/src/theme/theme-context.ts +1 -5
  323. package/src/theme/theme-script.ts +22 -21
  324. package/src/theme/use-theme.ts +0 -3
  325. package/src/types/boundaries.ts +0 -35
  326. package/src/types/cache-types.ts +13 -4
  327. package/src/types/error-types.ts +30 -90
  328. package/src/types/global-namespace.ts +54 -41
  329. package/src/types/handler-context.ts +110 -62
  330. package/src/types/index.ts +3 -10
  331. package/src/types/loader-types.ts +11 -9
  332. package/src/types/request-scope.ts +112 -0
  333. package/src/types/route-config.ts +6 -50
  334. package/src/types/route-entry.ts +0 -6
  335. package/src/types/segments.ts +135 -14
  336. package/src/urls/include-helper.ts +9 -56
  337. package/src/urls/index.ts +1 -11
  338. package/src/urls/path-helper-types.ts +29 -12
  339. package/src/urls/path-helper.ts +17 -106
  340. package/src/urls/pattern-types.ts +36 -19
  341. package/src/urls/response-types.ts +22 -29
  342. package/src/urls/type-extraction.ts +58 -139
  343. package/src/urls/urls-function.ts +1 -19
  344. package/src/use-loader.tsx +292 -107
  345. package/src/vite/debug.ts +185 -0
  346. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  347. package/src/vite/discovery/discover-routers.ts +126 -85
  348. package/src/vite/discovery/discovery-errors.ts +194 -0
  349. package/src/vite/discovery/gate-state.ts +171 -0
  350. package/src/vite/discovery/prerender-collection.ts +96 -68
  351. package/src/vite/discovery/route-types-writer.ts +40 -84
  352. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  353. package/src/vite/discovery/state.ts +44 -0
  354. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  355. package/src/vite/index.ts +2 -0
  356. package/src/vite/inject-client-debug.ts +36 -0
  357. package/src/vite/plugin-types.ts +126 -8
  358. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  359. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  360. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  361. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  362. package/src/vite/plugins/expose-action-id.ts +48 -95
  363. package/src/vite/plugins/expose-id-utils.ts +88 -55
  364. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  365. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  366. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  367. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  368. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  369. package/src/vite/plugins/performance-tracks.ts +26 -25
  370. package/src/vite/plugins/refresh-cmd.ts +1 -1
  371. package/src/vite/plugins/use-cache-transform.ts +73 -83
  372. package/src/vite/plugins/version-injector.ts +40 -29
  373. package/src/vite/plugins/version-plugin.ts +37 -40
  374. package/src/vite/plugins/virtual-entries.ts +39 -25
  375. package/src/vite/rango.ts +109 -118
  376. package/src/vite/router-discovery.ts +718 -119
  377. package/src/vite/utils/ast-handler-extract.ts +26 -35
  378. package/src/vite/utils/banner.ts +1 -1
  379. package/src/vite/utils/bundle-analysis.ts +10 -15
  380. package/src/vite/utils/client-chunks.ts +184 -0
  381. package/src/vite/utils/directive-prologue.ts +40 -0
  382. package/src/vite/utils/forward-user-plugins.ts +171 -0
  383. package/src/vite/utils/manifest-utils.ts +4 -59
  384. package/src/vite/utils/package-resolution.ts +20 -52
  385. package/src/vite/utils/prerender-utils.ts +54 -39
  386. package/src/vite/utils/shared-utils.ts +90 -41
  387. package/src/browser/action-response-classifier.ts +0 -99
  388. package/src/browser/react/use-client-cache.ts +0 -58
  389. package/src/browser/shallow.ts +0 -40
  390. package/src/handles/index.ts +0 -7
  391. package/src/network-error-thrower.tsx +0 -23
  392. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Debug logging for the Rango Vite plugin.
3
+ *
4
+ * Thin wrapper over the `debug` package (the same one Vite uses for its
5
+ * own `vite:*` namespaces). Enable with either:
6
+ *
7
+ * DEBUG='rango:*' vite dev
8
+ * vite --debug rango:* # vite prepends `vite:`, we bridge it
9
+ *
10
+ * Returns `undefined` when no matching namespace is enabled, so call sites
11
+ * can guard expensive diagnostics with a simple truthiness check:
12
+ *
13
+ * const debug = createRangoDebugger(NS.routes);
14
+ * if (debug) debug("built manifest (%d routes) in %dms", n, ms);
15
+ *
16
+ * Back-compat: INTERNAL_RANGO_DEBUG=1 still enables all rango namespaces.
17
+ *
18
+ * Vite CLI note: `vite --debug <feat>` rewrites to `DEBUG=vite:<feat>` — it
19
+ * always prefixes with `vite:` and cannot enable bare `rango:*` namespaces.
20
+ * We work around this by registering a shadow `vite:rango:*` instance for
21
+ * each debugger, so either invocation works.
22
+ */
23
+
24
+ import debugFactory from "debug";
25
+
26
+ /**
27
+ * Canonical debug namespaces. Import as `NS.xxx` instead of string literals
28
+ * so typos become type errors and the full set lives in one place.
29
+ */
30
+ export const NS = {
31
+ config: "rango:config",
32
+ discovery: "rango:discovery",
33
+ routes: "rango:routes",
34
+ prerender: "rango:prerender",
35
+ build: "rango:build",
36
+ dev: "rango:dev",
37
+ transform: "rango:transform",
38
+ chunks: "rango:chunks",
39
+ } as const;
40
+
41
+ // Back-compat: the legacy INTERNAL_RANGO_DEBUG env var enabled per-site
42
+ // console.logs in this plugin. Map it to `rango:*` so those call sites can
43
+ // be migrated to the `debug` pipeline without breaking existing setups.
44
+ // Uses debug.enable() rather than mutating process.env because the `debug`
45
+ // package already snapshotted DEBUG when it was imported above.
46
+ if (process.env.INTERNAL_RANGO_DEBUG) {
47
+ const existing = debugFactory.disable();
48
+ debugFactory.enable(existing ? `${existing},rango:*` : "rango:*");
49
+ }
50
+
51
+ export type Debugger = (formatter: string, ...args: unknown[]) => void;
52
+
53
+ export function createRangoDebugger(namespace: string): Debugger | undefined {
54
+ const primary = debugFactory(namespace);
55
+ // Shadow namespace so `vite --debug rango:*` (which expands to
56
+ // DEBUG=vite:rango:*) and `vite --debug` (DEBUG=vite:*) both pick us up.
57
+ const shadow = debugFactory(`vite:${namespace}`);
58
+ if (primary.enabled) return primary as Debugger;
59
+ if (shadow.enabled) return shadow as Debugger;
60
+ return undefined;
61
+ }
62
+
63
+ /**
64
+ * Measure an async block and log its duration via `debug`. No-ops (still
65
+ * runs `fn`) when the namespace is disabled, so production cost is a single
66
+ * `.enabled` check per call.
67
+ *
68
+ * await timed(debug, "discover routers", () => discoverRouters(state));
69
+ */
70
+ export async function timed<T>(
71
+ debug: Debugger | undefined,
72
+ label: string,
73
+ fn: () => T | Promise<T>,
74
+ ): Promise<T> {
75
+ if (!debug) return await fn();
76
+ const start = performance.now();
77
+ try {
78
+ return await fn();
79
+ } finally {
80
+ debug("%s (%sms)", label, (performance.now() - start).toFixed(1));
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Synchronous variant of `timed`. Use for sync call sites — wrapping them
86
+ * with the async `timed` would create a floating promise that discards any
87
+ * throw, bypassing the surrounding try/catch.
88
+ */
89
+ export function timedSync<T>(
90
+ debug: Debugger | undefined,
91
+ label: string,
92
+ fn: () => T,
93
+ ): T {
94
+ if (!debug) return fn();
95
+ const start = performance.now();
96
+ try {
97
+ return fn();
98
+ } finally {
99
+ debug("%s (%sms)", label, (performance.now() - start).toFixed(1));
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Aggregate counter for high-frequency call sites (typically Vite
105
+ * `transform` hooks that run on many files). Per-call logging would
106
+ * drown real signal; this collects totals and reports once on flush.
107
+ *
108
+ * const counter = createCounter(debug, "use-cache-transform");
109
+ * // inside transform():
110
+ * return counter?.time(id, () => doWork()) ?? doWork();
111
+ * // or manually:
112
+ * counter?.record(id, ms);
113
+ * // flush on buildEnd (counter resets, so multi-env builds each get
114
+ * // their own summary line):
115
+ * counter?.flush();
116
+ *
117
+ * Returns `undefined` when the namespace is disabled so call sites pay
118
+ * nothing when off.
119
+ */
120
+ export interface Counter {
121
+ record(file: string, ms: number): void;
122
+ /**
123
+ * Convenience: time a sync or async block and record it. Propagates
124
+ * throws; records regardless of outcome. Returns the function's result.
125
+ */
126
+ time<T>(file: string, fn: () => T): T;
127
+ time<T>(file: string, fn: () => Promise<T>): Promise<T>;
128
+ flush(): void;
129
+ }
130
+
131
+ export function createCounter(
132
+ debug: Debugger | undefined,
133
+ label: string,
134
+ ): Counter | undefined {
135
+ if (!debug) return undefined;
136
+ let n = 0;
137
+ let totalMs = 0;
138
+ let slowestMs = 0;
139
+ let slowestFile = "";
140
+ const record = (file: string, ms: number): void => {
141
+ n++;
142
+ totalMs += ms;
143
+ if (ms > slowestMs) {
144
+ slowestMs = ms;
145
+ slowestFile = file;
146
+ }
147
+ };
148
+ return {
149
+ record,
150
+ time<T>(file: string, fn: () => T | Promise<T>): T | Promise<T> {
151
+ const start = performance.now();
152
+ let out: T | Promise<T>;
153
+ try {
154
+ out = fn();
155
+ } catch (err) {
156
+ record(file, performance.now() - start);
157
+ throw err;
158
+ }
159
+ if (out && typeof (out as any).then === "function") {
160
+ return (out as Promise<T>).finally(() =>
161
+ record(file, performance.now() - start),
162
+ );
163
+ }
164
+ record(file, performance.now() - start);
165
+ return out;
166
+ },
167
+ flush(): void {
168
+ if (n === 0) return;
169
+ debug(
170
+ "%s: %d files, %sms total, slowest %sms %s",
171
+ label,
172
+ n,
173
+ totalMs.toFixed(1),
174
+ slowestMs.toFixed(1),
175
+ slowestFile,
176
+ );
177
+ // Reset so buildEnd firing once per environment (Vite 6+ multi-env)
178
+ // gives one log line per env rather than silently dropping later data.
179
+ n = 0;
180
+ totalMs = 0;
181
+ slowestMs = 0;
182
+ slowestFile = "";
183
+ },
184
+ };
185
+ }
@@ -9,6 +9,7 @@ import { resolve } from "node:path";
9
9
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
10
10
  import { evictHandlerCode } from "../utils/bundle-analysis.js";
11
11
  import { copyStagedBuildAssets } from "../utils/prerender-utils.js";
12
+ import { jsonParseExpression } from "../utils/manifest-utils.js";
12
13
  import type { DiscoveryState } from "./state.js";
13
14
 
14
15
  /**
@@ -71,12 +72,12 @@ export function postprocessBundle(state: DiscoveryState): void {
71
72
  writeFileSync(chunkPath, result.code);
72
73
  const savedKB = (result.savedBytes / 1024).toFixed(1);
73
74
  console.log(
74
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
75
+ `[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
75
76
  );
76
77
  }
77
78
  } catch (replaceErr: any) {
78
79
  console.warn(
79
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`,
80
+ `[rango] Failed to evict ${target.label}: ${replaceErr.message}`,
80
81
  );
81
82
  }
82
83
  }
@@ -104,7 +105,7 @@ export function postprocessBundle(state: DiscoveryState): void {
104
105
  }
105
106
 
106
107
  const manifestCode = [
107
- `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
108
+ `const m=${jsonParseExpression(manifestMap)};`,
108
109
  `export function loadPrerenderAsset(s){return import(s)}`,
109
110
  `export default m;`,
110
111
  "",
@@ -121,11 +122,11 @@ export function postprocessBundle(state: DiscoveryState): void {
121
122
 
122
123
  const totalKB = (totalBytes / 1024).toFixed(1);
123
124
  console.log(
124
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries!).length} entries)`,
125
+ `[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries!).length} entries)`,
125
126
  );
126
127
  } catch (err: any) {
127
128
  throw new Error(
128
- `[rsc-router] Failed to write prerender assets: ${err.message}`,
129
+ `[rango] Failed to write prerender assets: ${err.message}`,
129
130
  );
130
131
  }
131
132
  }
@@ -169,11 +170,11 @@ export function postprocessBundle(state: DiscoveryState): void {
169
170
 
170
171
  const totalKB = (totalBytes / 1024).toFixed(1);
171
172
  console.log(
172
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries!).length} entries)`,
173
+ `[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries!).length} entries)`,
173
174
  );
174
175
  } catch (err: any) {
175
176
  throw new Error(
176
- `[rsc-router] Failed to write static assets: ${err.message}`,
177
+ `[rango] Failed to write static assets: ${err.message}`,
177
178
  );
178
179
  }
179
180
  }
@@ -11,6 +11,16 @@ import {
11
11
  formatNestedRouterConflictError,
12
12
  findNestedRouterConflict,
13
13
  } from "../../build/generate-route-types.js";
14
+ // Pure data transforms over generateManifestFull's output. Imported directly
15
+ // from source (not the public ./build barrel, and not the runner) because they
16
+ // are realm-independent: buildRouteTrie/buildPerRouterTrie operate on plain
17
+ // manifest data, and collectFallbackClientRefs keys on the global-registry
18
+ // Symbol.for("react.client.reference"), so it detects client references in a
19
+ // boundary tree regardless of which realm imported the walker. Only
20
+ // generateManifestFull must stay on the runner (it invokes user handlers via
21
+ // RangoContext from the runner realm) — see the runner.import below.
22
+ import { buildRouteTrie, buildPerRouterTrie } from "../../build/route-trie.js";
23
+ import { collectFallbackClientRefs } from "../../build/collect-fallback-refs.js";
14
24
  import {
15
25
  flattenLeafEntries,
16
26
  buildRouteToStaticPrefix,
@@ -20,6 +30,15 @@ import {
20
30
  expandPrerenderRoutes,
21
31
  renderStaticHandlers,
22
32
  } from "./prerender-collection.js";
33
+ import {
34
+ resolveHostRouterHandlers,
35
+ DiscoveryError,
36
+ type CaughtDiscoveryError,
37
+ } from "./discovery-errors.js";
38
+ import { createRangoDebugger, timed, NS } from "../debug.js";
39
+ import { computeProductionHash } from "../plugins/client-ref-hashing.js";
40
+
41
+ const debug = createRangoDebugger(NS.discovery);
23
42
 
24
43
  /**
25
44
  * Import the user's entry via RSC runner, generate manifests for each
@@ -38,41 +57,40 @@ export async function discoverRouters(
38
57
  // Import the entry file via RSC environment.
39
58
  // For node preset: this is the router file (createRouter() registers in RouterRegistry).
40
59
  // For cloudflare preset: this is the worker entry (which imports the router).
41
- await rscEnv.runner.import(state.resolvedEntryPath);
60
+ await timed(debug, "inner: import entry", () =>
61
+ rscEnv.runner.import(state.resolvedEntryPath),
62
+ );
42
63
 
43
64
  // Import the router package to access the registry
44
- const serverMod = await rscEnv.runner.import("@rangojs/router/server");
65
+ const serverMod = await timed(
66
+ debug,
67
+ "inner: import @rangojs/router/server",
68
+ () => rscEnv.runner.import("@rangojs/router/server"),
69
+ );
45
70
  let registry: Map<string, any> = serverMod.RouterRegistry;
46
71
 
47
72
  if (!registry || registry.size === 0) {
48
73
  // No RSC routers found directly. Check for host routers with lazy handlers
49
74
  // that need to be resolved to trigger sub-app createRouter() calls.
75
+ //
76
+ // Handler failures are collected rather than swallowed: when the registry
77
+ // is still empty afterwards, these errors (typically a sub-app whose router
78
+ // module failed to import) are the most likely cause and are surfaced in
79
+ // the terminal "No routers found" error below.
80
+ const discoveryErrors: CaughtDiscoveryError[] = [];
50
81
  try {
51
82
  const hostRegistry: Map<string, any> | undefined =
52
83
  serverMod.HostRouterRegistry;
53
84
 
54
85
  if (hostRegistry && hostRegistry.size > 0) {
55
86
  console.log(
56
- `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
87
+ `[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
57
88
  );
58
89
 
59
- for (const [, entry] of hostRegistry) {
60
- for (const route of entry.routes) {
61
- if (typeof route.handler === "function") {
62
- try {
63
- await route.handler();
64
- } catch {
65
- // Lazy handler may fail in temp server context, that's OK
66
- }
67
- }
68
- }
69
- if (entry.fallback && typeof entry.fallback.handler === "function") {
70
- try {
71
- await entry.fallback.handler();
72
- } catch {
73
- // Fallback handler may fail in temp server context
74
- }
75
- }
90
+ const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
91
+ discoveryErrors.push(...handlerErrors);
92
+ for (const { context, error } of handlerErrors) {
93
+ debug?.("caught error while resolving %s: %O", context, error);
76
94
  }
77
95
 
78
96
  // Re-read RouterRegistry - sub-app createRouter() calls should have populated it
@@ -87,22 +105,31 @@ export async function discoverRouters(
87
105
  registry = freshRegistry;
88
106
  }
89
107
  }
90
- } catch {
91
- // Host-router discovery is best-effort; skip if unavailable
108
+ } catch (error) {
109
+ // Host-router discovery is best-effort; record the failure so it can be
110
+ // surfaced if no routers are found.
111
+ discoveryErrors.push({ context: "host-router discovery", error });
92
112
  }
93
113
 
94
114
  // If still no routers after host router resolution, fail
95
115
  if (!registry || registry.size === 0) {
96
- throw new Error(
97
- `[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`,
98
- );
116
+ throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
99
117
  }
100
118
  }
101
119
 
102
- // Import build utilities for manifest generation
103
- const buildMod = await rscEnv.runner.import("@rangojs/router/build");
120
+ // generateManifestFull must run in the RSC runner realm: it invokes the
121
+ // user's urlpatterns.handler() via RangoContext, consuming router instances
122
+ // from the runner. The trie/fallback-ref builders are pure transforms over
123
+ // its output and are imported directly from source above.
124
+ const buildMod = await timed(
125
+ debug,
126
+ "inner: import @rangojs/router/build",
127
+ () => rscEnv.runner.import("@rangojs/router/build"),
128
+ );
104
129
  const generateManifestFull = buildMod.generateManifestFull;
105
130
 
131
+ debug?.("inner: found %d router(s) in registry", registry.size);
132
+
106
133
  const nestedRouterConflict = findNestedRouterConflict(
107
134
  [...registry.values()]
108
135
  .map((router) => router.__sourceFile)
@@ -130,6 +157,30 @@ export async function discoverRouters(
130
157
  // Collect all manifests for trie building (avoid re-running generateManifest)
131
158
  const allManifests: Array<{ id: string; manifest: any }> = [];
132
159
 
160
+ // Built-in clientChunks context (present only when the built-in strategy is
161
+ // active). Collect the production hashes of "use client" error/notFound
162
+ // fallback modules so the strategy can route them into app-fallback.
163
+ const clientChunkCtx = state.opts?.clientChunkCtx;
164
+ const collectClientFallbackRef = clientChunkCtx
165
+ ? (refKey: string) =>
166
+ clientChunkCtx.fallbackRefs.add(
167
+ computeProductionHash(state.projectRoot, refKey),
168
+ )
169
+ : undefined;
170
+ // Router-level boundary defaults (`createRouter({ defaultErrorBoundary, ... })`)
171
+ // are NOT in EntryData, so generateManifestFull's walk misses them. Collect any
172
+ // "use client" default boundary directly off the router instance. The value is
173
+ // commonly a handler function wrapping the client boundary in server providers,
174
+ // so collectFallbackClientRefs invokes + walks the tree. The walker keys on the
175
+ // global-registry Symbol.for("react.client.reference"), so it detects client
176
+ // references in a runner-realm boundary tree even when imported here directly.
177
+ const collectFromBoundaryNode = (node: unknown): void => {
178
+ if (collectClientFallbackRef) {
179
+ collectFallbackClientRefs(node, collectClientFallbackRef);
180
+ }
181
+ };
182
+
183
+ const manifestGenStart = debug ? performance.now() : 0;
133
184
  for (const [id, router] of registry) {
134
185
  if (!router.urlpatterns || !generateManifestFull) {
135
186
  continue;
@@ -138,10 +189,23 @@ export async function discoverRouters(
138
189
  const manifest = generateManifestFull(
139
190
  router.urlpatterns,
140
191
  routerMountIndex,
141
- router.__basename ? { urlPrefix: router.__basename } : undefined,
192
+ {
193
+ ...(router.__basename ? { urlPrefix: router.__basename } : {}),
194
+ ...(collectClientFallbackRef ? { collectClientFallbackRef } : {}),
195
+ },
142
196
  );
143
197
  routerMountIndex++;
144
198
  allManifests.push({ id, manifest });
199
+
200
+ // Router-level "use client" boundary defaults -> app-fallback (the
201
+ // route-tree errorBoundary()/notFoundBoundary() helpers are already
202
+ // collected inside generateManifestFull via collectClientFallbackRef).
203
+ if (collectClientFallbackRef) {
204
+ collectFromBoundaryNode(router.__defaultErrorBoundary);
205
+ collectFromBoundaryNode(router.__defaultNotFoundBoundary);
206
+ collectFromBoundaryNode(router.__notFound);
207
+ }
208
+
145
209
  const routeCount = Object.keys(manifest.routeManifest).length;
146
210
  const staticRoutes = Object.values(manifest.routeManifest).filter(
147
211
  (p: any) => !p.includes(":") && !p.includes("*"),
@@ -193,24 +257,23 @@ export async function discoverRouters(
193
257
  // Flatten prefix tree leaf nodes into precomputed entries.
194
258
  // Leaf nodes (no children) can have their routes used directly by
195
259
  // evaluateLazyEntry() without running the handler at runtime.
260
+ // Walk once into a per-router array, then fold it into the merged array;
261
+ // the merged and per-router entries are identical, so a second walk is
262
+ // redundant. Append order is preserved within and across routers.
263
+ const routerPrecomputed: PrecomputedEntry[] = [];
196
264
  flattenLeafEntries(
197
265
  manifest.prefixTree,
198
266
  manifest.routeManifest,
199
- newMergedPrecomputedEntries,
267
+ routerPrecomputed,
200
268
  );
269
+ newMergedPrecomputedEntries.push(...routerPrecomputed);
201
270
 
202
271
  // Store per-router manifest and precomputed entries for isolated virtual modules.
203
272
  newPerRouterManifestDataMap.set(id, manifest.routeManifest);
204
- const routerPrecomputed: PrecomputedEntry[] = [];
205
- flattenLeafEntries(
206
- manifest.prefixTree,
207
- manifest.routeManifest,
208
- routerPrecomputed,
209
- );
210
273
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
211
274
 
212
275
  console.log(
213
- `[rsc-router] Router "${id}" -> ${routeCount} routes ` +
276
+ `[rango] Router "${id}" -> ${routeCount} routes ` +
214
277
  `(${staticRoutes} static, ${dynamicRoutes} dynamic)`,
215
278
  );
216
279
  }
@@ -226,7 +289,7 @@ export async function discoverRouters(
226
289
  );
227
290
  if (autoIds.length > 1) {
228
291
  console.warn(
229
- `[rsc-router] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
292
+ `[rango] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
230
293
  `In multi-router setups, each createRouter() must have an explicit \`id\` option ` +
231
294
  `to ensure per-router manifest data is matched correctly at runtime. ` +
232
295
  `Example: createRouter({ id: "site", ... })`,
@@ -234,11 +297,17 @@ export async function discoverRouters(
234
297
  }
235
298
  }
236
299
 
300
+ debug?.(
301
+ "inner: generated manifests for %d router(s) (%sms)",
302
+ allManifests.length,
303
+ (performance.now() - manifestGenStart).toFixed(1),
304
+ );
305
+
237
306
  // Build route trie from merged manifest + ancestry
238
307
  let newMergedRouteTrie: any = null;
308
+ const trieStart = debug ? performance.now() : 0;
239
309
  if (Object.keys(newMergedRouteManifest).length > 0) {
240
- const buildRouteTrie = buildMod.buildRouteTrie;
241
- if (buildRouteTrie && mergedRouteAncestry) {
310
+ if (mergedRouteAncestry) {
242
311
  // Build routeToStaticPrefix from saved manifests
243
312
  const routeToStaticPrefix: Record<string, string> = {};
244
313
  for (const { manifest } of allManifests) {
@@ -271,64 +340,36 @@ export async function discoverRouters(
271
340
  }
272
341
  }
273
342
 
343
+ // buildRouteTrie reads these via ?.has / ?.[] — empty is observationally
344
+ // identical to undefined, so no empty->undefined coercion is needed.
274
345
  newMergedRouteTrie = buildRouteTrie(
275
346
  newMergedRouteManifest,
276
347
  mergedRouteAncestry,
277
348
  routeToStaticPrefix,
278
- Object.keys(mergedRouteTrailingSlash).length > 0
279
- ? mergedRouteTrailingSlash
280
- : undefined,
281
- prerenderRouteNames.size > 0 ? prerenderRouteNames : undefined,
282
- passthroughRouteNames.size > 0 ? passthroughRouteNames : undefined,
283
- Object.keys(mergedResponseTypeRoutes).length > 0
284
- ? mergedResponseTypeRoutes
285
- : undefined,
349
+ mergedRouteTrailingSlash,
350
+ prerenderRouteNames,
351
+ passthroughRouteNames,
352
+ mergedResponseTypeRoutes,
286
353
  );
287
354
 
288
- // Build per-router tries for multi-router isolation.
355
+ // Build per-router tries for multi-router isolation. Uses the single
356
+ // shared buildPerRouterTrie so the production serialized trie is built by
357
+ // exactly the same code as the dev/HMR runtime rebuild (manifest-init.ts).
358
+ // Returns null for route-less manifests (route-trie.ts).
289
359
  for (const { id, manifest } of allManifests) {
290
- if (
291
- !manifest._routeAncestry ||
292
- Object.keys(manifest._routeAncestry).length === 0
293
- )
294
- continue;
295
- const perRouterStaticPrefix: Record<string, string> = {};
296
- for (const name of Object.keys(manifest.routeManifest)) {
297
- perRouterStaticPrefix[name] = "";
360
+ const perRouterTrie = buildPerRouterTrie(manifest);
361
+ if (perRouterTrie) {
362
+ newPerRouterTrieMap.set(id, perRouterTrie);
298
363
  }
299
- buildRouteToStaticPrefix(manifest.prefixTree, perRouterStaticPrefix);
300
-
301
- const perRouterPrerenderNames = manifest.prerenderRoutes
302
- ? new Set<string>(manifest.prerenderRoutes)
303
- : undefined;
304
- const perRouterPassthroughNames = manifest.passthroughRoutes
305
- ? new Set<string>(manifest.passthroughRoutes)
306
- : undefined;
307
-
308
- const perRouterTrie = buildRouteTrie(
309
- manifest.routeManifest,
310
- manifest._routeAncestry,
311
- perRouterStaticPrefix,
312
- manifest.routeTrailingSlash &&
313
- Object.keys(manifest.routeTrailingSlash).length > 0
314
- ? manifest.routeTrailingSlash
315
- : undefined,
316
- perRouterPrerenderNames && perRouterPrerenderNames.size > 0
317
- ? perRouterPrerenderNames
318
- : undefined,
319
- perRouterPassthroughNames && perRouterPassthroughNames.size > 0
320
- ? perRouterPassthroughNames
321
- : undefined,
322
- manifest.responseTypeRoutes &&
323
- Object.keys(manifest.responseTypeRoutes).length > 0
324
- ? manifest.responseTypeRoutes
325
- : undefined,
326
- );
327
- newPerRouterTrieMap.set(id, perRouterTrie);
328
364
  }
329
365
  }
330
366
  }
331
367
 
368
+ debug?.(
369
+ "inner: trie build done (%sms)",
370
+ (performance.now() - trieStart).toFixed(1),
371
+ );
372
+
332
373
  // Commit all local state to the shared discovery state atomically.
333
374
  // This ensures a failed re-discovery (e.g. from a transient module
334
375
  // evaluation error) preserves the last known-good state.