@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,228 @@
1
+ import React from "react";
2
+ import { renderSegments } from "../segment-system.js";
3
+ import {
4
+ filterSegmentOrder,
5
+ filterRouteSegmentIds,
6
+ } from "../browser/react/filter-segment-order.js";
7
+ import { ThemeProvider } from "../theme/ThemeProvider.js";
8
+ import { NonceContext } from "../browser/react/nonce-context.js";
9
+ import { NavigationStoreContext } from "../browser/react/context.js";
10
+ import type { NavigationStoreContextValue } from "../browser/react/context.js";
11
+ import type { HandleData } from "../browser/types.js";
12
+ import type { ResolvedSegment } from "../types.js";
13
+ import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
14
+ import type {
15
+ EventController,
16
+ DerivedNavigationState,
17
+ } from "../browser/event-controller.js";
18
+
19
+ /**
20
+ * createFromReadableStream from @rangojs/router/internal/deps/ssr.
21
+ * Deserializes the Flight branch used to build the SSR VDOM.
22
+ */
23
+ export type CreateFromReadableStream = <T>(
24
+ stream: ReadableStream<Uint8Array>,
25
+ ) => Promise<T>;
26
+
27
+ /**
28
+ * RSC payload type (minimal interface for SSR)
29
+ */
30
+ export interface RscPayload {
31
+ metadata?: {
32
+ segments?: ResolvedSegment[];
33
+ rootLayout?: React.ComponentType<{ children: React.ReactNode }>;
34
+ handles?: AsyncGenerator<HandleData, void, unknown>;
35
+ matched?: string[];
36
+ pathname?: string;
37
+ params?: Record<string, string>;
38
+ basename?: string;
39
+ themeConfig?: ResolvedThemeConfig | null;
40
+ initialTheme?: Theme;
41
+ version?: string;
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Consume an async generator and return a Promise that resolves with the final value.
47
+ * Used for SSR where we need to await all handle data before rendering.
48
+ */
49
+ async function consumeAsyncGenerator(
50
+ generator: AsyncGenerator<HandleData, void, unknown>,
51
+ ): Promise<HandleData> {
52
+ let lastData: HandleData = {};
53
+ for await (const data of generator) {
54
+ lastData = data;
55
+ }
56
+ return lastData;
57
+ }
58
+
59
+ /**
60
+ * Create a minimal event controller for SSR.
61
+ * This provides the correct pathname so useNavigation returns the right value during SSR.
62
+ */
63
+ function createSsrEventController(opts: {
64
+ pathname: string;
65
+ params?: Record<string, string>;
66
+ handleData?: HandleData;
67
+ matched?: string[];
68
+ }): EventController {
69
+ const location = new URL(opts.pathname, "http://localhost");
70
+ let params = opts.params ?? {};
71
+ const rawMatched = opts.matched ?? [];
72
+ const handleState = {
73
+ data: opts.handleData ?? {},
74
+ segmentOrder: filterSegmentOrder(rawMatched),
75
+ routeSegmentIds: filterRouteSegmentIds(rawMatched),
76
+ };
77
+ const state: DerivedNavigationState = {
78
+ state: "idle",
79
+ isStreaming: false,
80
+ isNavigating: false,
81
+ location,
82
+ pendingUrl: null,
83
+ inflightActions: [],
84
+ };
85
+
86
+ return {
87
+ getState: () => state,
88
+ getLocation: () => location,
89
+ subscribe: () => () => {},
90
+ getActionState: () => ({
91
+ state: "idle",
92
+ actionId: null,
93
+ payload: null,
94
+ error: null,
95
+ result: null,
96
+ }),
97
+ subscribeToAction: () => () => {},
98
+ subscribeToHandles: () => () => {},
99
+ setHandleData: () => {},
100
+ getHandleState: () => handleState,
101
+ setRouteSegmentIds: () => {},
102
+ setParams: (nextParams) => {
103
+ params = nextParams;
104
+ },
105
+ getParams: () => params,
106
+ setLocation: () => {},
107
+ startNavigation: () => {
108
+ throw new Error("Navigation not supported during SSR");
109
+ },
110
+ abortNavigation: () => {},
111
+ startAction: () => {
112
+ throw new Error("Actions not supported during SSR");
113
+ },
114
+ abortAllActions: () => {},
115
+ getCurrentNavigation: () => null,
116
+ getInflightActions: () => new Map(),
117
+ hadAnyConcurrentActions: () => false,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Options for {@link createSsrRootComponent}.
123
+ */
124
+ export interface SsrRootOptions {
125
+ /** createFromReadableStream for the SSR branch of the Flight stream. */
126
+ createFromReadableStream: CreateFromReadableStream;
127
+ /** The Flight stream branch to deserialize into the SSR VDOM. */
128
+ rscStream: ReadableStream<Uint8Array>;
129
+ /** Nonce for CSP; propagated to NonceContext. */
130
+ nonce?: string;
131
+ }
132
+
133
+ /**
134
+ * Build the closure component that deserializes the Flight payload, consumes
135
+ * handles to completion, builds the segment tree, and wraps it in the
136
+ * NavigationStore / Nonce / Theme providers.
137
+ *
138
+ * The full-fizz path (renderHTML), the shell prerender pass (capture), and the
139
+ * shell resume pass all render this identical tree. `resume` requires the tree
140
+ * above the postponed holes to match the prerendered tree; rendering the same
141
+ * builder over the same replayed segments is what makes that hold. A fresh
142
+ * component instance per pass is fine — replay matches structure, not function
143
+ * identity.
144
+ *
145
+ * The memo slots (payload/handles/context/root) are closure-scoped to the
146
+ * returned instance, so each render pass memoizes independently. renderSegments
147
+ * is async: React.use() on a fresh promise would suspend and replay SsrRoot,
148
+ * re-running the whole segment-tree build unless the promise is memoized.
149
+ */
150
+ export function createSsrRootComponent(opts: SsrRootOptions): React.FC {
151
+ const { createFromReadableStream, rscStream, nonce } = opts;
152
+
153
+ let payload: Promise<RscPayload> | undefined;
154
+ let handlesPromise: Promise<HandleData> | undefined;
155
+ let ssrContextValue: NavigationStoreContextValue | undefined;
156
+ let rootPromise: Promise<React.ReactNode> | undefined;
157
+
158
+ return function SsrRoot() {
159
+ payload ??= createFromReadableStream<RscPayload>(rscStream);
160
+ const resolved = React.use(payload);
161
+
162
+ const themeConfig = resolved.metadata?.themeConfig ?? null;
163
+ const pathname = resolved.metadata?.pathname ?? "/";
164
+
165
+ // Await handles before creating SSR event controller so hooks can
166
+ // read request-local handle data via NavigationStoreContext.
167
+ // The handles property is an async generator that yields on each push
168
+ // Memoize the promise since async generators can only be iterated once
169
+ let handleData: HandleData = {};
170
+ if (resolved.metadata?.handles) {
171
+ handlesPromise ??= consumeAsyncGenerator(resolved.metadata.handles);
172
+ handleData = React.use(handlesPromise);
173
+ }
174
+
175
+ // Create SSR context with request-local pathname/params/handles.
176
+ ssrContextValue ??= {
177
+ store: null as any,
178
+ eventController: createSsrEventController({
179
+ pathname,
180
+ params: resolved.metadata?.params,
181
+ handleData,
182
+ matched: resolved.metadata?.matched,
183
+ }),
184
+ navigate: async () => {},
185
+ refresh: async () => {},
186
+ version: resolved.metadata?.version,
187
+ basename: resolved.metadata?.basename,
188
+ };
189
+
190
+ // Build content tree from segments.
191
+ // Order must match NavigationProvider: NavigationStoreContext > NonceContext > ThemeProvider > content
192
+ // Memoize like payload/handles above: renderSegments is async, so
193
+ // React.use() on a fresh promise suspends and replays SsrRoot, which
194
+ // would re-run the entire segment-tree build on every initial render.
195
+ rootPromise ??= Promise.resolve(
196
+ renderSegments(resolved.metadata?.segments ?? [], {
197
+ rootLayout: resolved.metadata?.rootLayout,
198
+ }),
199
+ );
200
+ let content: React.ReactNode = React.use(rootPromise);
201
+
202
+ // Wrap content with ThemeProvider if theme is enabled
203
+ if (themeConfig) {
204
+ content = (
205
+ <ThemeProvider
206
+ config={themeConfig}
207
+ initialTheme={resolved.metadata?.initialTheme}
208
+ >
209
+ {content}
210
+ </ThemeProvider>
211
+ );
212
+ }
213
+
214
+ // Wrap with NonceContext so client components (e.g. MetaTags) can
215
+ // apply CSP nonces to inline scripts during SSR. Always present to
216
+ // match the browser-side NavigationProvider tree shape for hydration.
217
+ content = (
218
+ <NonceContext.Provider value={nonce}>{content}</NonceContext.Provider>
219
+ );
220
+
221
+ // Wrap with NavigationStoreContext for useNavigation hook
222
+ return (
223
+ <NavigationStoreContext.Provider value={ssrContextValue!}>
224
+ {content}
225
+ </NavigationStoreContext.Provider>
226
+ );
227
+ };
228
+ }
@@ -31,46 +31,78 @@
31
31
  * ```
32
32
  */
33
33
  import type { ReactNode } from "react";
34
- import type { Handler, HandlerContext } from "./types.js";
35
- import type { PrerenderOptions } from "./prerender.js";
34
+ import type { Handler } from "./types.js";
35
+ import type { StaticBuildContext } from "./prerender.js";
36
+ import type { UseItems, HandlerUseItem } from "./route-types.js";
37
+ import { isCachedFunction } from "./cache/taint.js";
38
+ import { isUnderTestRunner } from "./runtime-env.js";
36
39
 
37
- // -- Types ------------------------------------------------------------------
40
+ export interface StaticHandlerOptions {
41
+ /**
42
+ * Keep handler in server bundle for live fallback (default: false).
43
+ * false: handler replaced with stub, source-only APIs excluded from bundle.
44
+ * true: handler stays in bundle, renders live at request time.
45
+ */
46
+ passthrough?: boolean;
47
+ }
38
48
 
39
- export interface StaticHandlerDefinition<TParams extends Record<string, any> = any> {
49
+ export interface StaticHandlerDefinition<
50
+ TParams extends Record<string, any> = any,
51
+ > {
40
52
  readonly __brand: "staticHandler";
41
53
  /** Auto-generated unique ID (injected by Vite plugin). */
42
54
  $$id: string;
43
55
  /** In dev mode, the actual handler function that layout/path/parallel can call. */
44
56
  handler: Handler<TParams>;
45
57
  /** Static handler options (passthrough support). */
46
- options?: PrerenderOptions;
58
+ options?: StaticHandlerOptions;
59
+ /** Composable default DSL items merged when the handler is mounted. */
60
+ use?: () => UseItems<HandlerUseItem>;
47
61
  }
48
62
 
49
- // -- Function ---------------------------------------------------------------
63
+ // Process-stable fallback id counter (mirrors createHandle/createLoader/Prerender).
64
+ // Only assigned in bare unit tests where the Vite plugin did not inject an id.
65
+ let runtimeStaticIdCounter = 0;
50
66
 
51
67
  export function Static<TParams extends Record<string, any> = {}>(
52
- handler: (ctx: HandlerContext<TParams>) => ReactNode | Promise<ReactNode>,
53
- options?: PrerenderOptions,
68
+ handler: (ctx: StaticBuildContext) => ReactNode | Promise<ReactNode>,
69
+ options?: StaticHandlerOptions,
54
70
  __injectedId?: string,
55
71
  ): StaticHandlerDefinition<TParams>;
56
72
 
57
- // -- Implementation ---------------------------------------------------------
58
-
59
73
  export function Static<TParams extends Record<string, any>>(
60
74
  handler: Function,
61
- optionsOrId?: PrerenderOptions | string,
75
+ optionsOrId?: StaticHandlerOptions | string,
62
76
  maybeId?: string,
63
77
  ): StaticHandlerDefinition<TParams> {
64
- let options: PrerenderOptions | undefined;
78
+ if (isCachedFunction(handler)) {
79
+ throw new Error(
80
+ 'A "use cache" function cannot be used as a Static() handler. ' +
81
+ "Static handlers are rendered once at build time. Remove the " +
82
+ '"use cache" directive — Static already provides caching.',
83
+ );
84
+ }
85
+
86
+ let options: StaticHandlerOptions | undefined;
65
87
  let id: string;
66
88
 
67
89
  if (typeof optionsOrId === "string") {
68
90
  id = optionsOrId;
69
91
  } else {
70
- options = optionsOrId as PrerenderOptions | undefined;
92
+ options = optionsOrId as StaticHandlerOptions | undefined;
71
93
  id = maybeId ?? "";
72
94
  }
73
95
 
96
+ if (!id && !isUnderTestRunner()) {
97
+ throw new Error(
98
+ "[rango] Static: missing $$id. Use `export const X = Static(...)` and " +
99
+ "ensure the exposeInternalIds Vite plugin is configured.",
100
+ );
101
+ }
102
+ if (!id) {
103
+ id = `__rango_runtime_static_${runtimeStaticIdCounter++}`;
104
+ }
105
+
74
106
  return {
75
107
  __brand: "staticHandler" as const,
76
108
  $$id: id,
@@ -79,11 +111,6 @@ export function Static<TParams extends Record<string, any>>(
79
111
  };
80
112
  }
81
113
 
82
- // -- Type guard -------------------------------------------------------------
83
-
84
- /**
85
- * Type guard to check if a value is a StaticHandlerDefinition.
86
- */
87
114
  export function isStaticHandler(
88
115
  value: unknown,
89
116
  ): value is StaticHandlerDefinition {
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Cache-status testing primitives for @rangojs/router consumers.
3
+ *
4
+ * Two complementary paths, both DEVELOPMENT/TEST ONLY:
5
+ *
6
+ * 1. Header path — `parseCacheHeader` / `assertCacheStatus` read the
7
+ * `X-Rango-Cache` response header. The header is emitted only when the
8
+ * router's debug cache signal gate is on (the `debugCacheSignal` option or
9
+ * `RANGO_TEST_SIGNALS=1`). With the gate off there is no header and these
10
+ * helpers throw a clear "header missing" error.
11
+ *
12
+ * 2. Telemetry path — `createCacheSink` returns a `{ sink, events }` pair the
13
+ * consumer wires via `createRouter({ telemetry: sink })`. This has ZERO
14
+ * production surface: no header, just structured `cache.decision` events
15
+ * (which carry the same coarse `segments` cache signal). Assert with
16
+ * `assertCacheDecision(events, routeKey, expected)` (the one-call counterpart
17
+ * of `assertCacheStatus`) or filter raw via `filterCacheDecisions`.
18
+ *
19
+ * Both paths report the SAME coarse route-level signal — pick by TRANSPORT, not
20
+ * by meaning: the header is the only signal a black-box Playwright `Response`
21
+ * carries (needs the debug gate ON); the sink is the only zero-production-surface
22
+ * option and the only one exposing per-segment `shouldRevalidate`.
23
+ *
24
+ * v1 cache status is COARSE (route-level): the router reports a single entry
25
+ * keyed by the route key (the route NAME), not per individual segment.
26
+ *
27
+ * Import path: from a Vitest unit/integration test use `@rangojs/router/testing`;
28
+ * from a Playwright e2e use `@rangojs/router/testing/e2e` (the barrel pulls a
29
+ * build-only virtual that does not resolve in a plain Playwright runner).
30
+ */
31
+
32
+ import type {
33
+ CacheDecisionEvent,
34
+ CacheSegmentStatus,
35
+ TelemetryEvent,
36
+ TelemetrySink,
37
+ } from "../router/telemetry.js";
38
+
39
+ const CACHE_HEADER = "X-Rango-Cache";
40
+
41
+ /** Expected cache status passed to assertCacheStatus. */
42
+ export type ExpectedCacheStatus = CacheSegmentStatus;
43
+
44
+ /** A target carrying response headers (a Response or a `{ headers }` object). */
45
+ export type CacheStatusTarget = Response | { headers: Headers };
46
+
47
+ export function parseCacheHeader(
48
+ headerValue: string | null | undefined,
49
+ ): Record<string, string> {
50
+ const result: Record<string, string> = {};
51
+ if (!headerValue) return result;
52
+ for (const rawEntry of headerValue.split(",")) {
53
+ const entry = rawEntry.trim();
54
+ if (entry.length === 0) continue;
55
+ const eq = entry.indexOf("=");
56
+ if (eq === -1) continue;
57
+ const id = entry.slice(0, eq).trim();
58
+ const status = entry.slice(eq + 1).trim();
59
+ if (id.length === 0 || status.length === 0) continue;
60
+ result[id] = status;
61
+ }
62
+ return result;
63
+ }
64
+
65
+ function getHeaders(target: CacheStatusTarget): Headers {
66
+ return target.headers;
67
+ }
68
+
69
+ export function assertCacheStatus(
70
+ target: CacheStatusTarget,
71
+ segment: string,
72
+ expected: ExpectedCacheStatus,
73
+ ): void {
74
+ const headerValue = getHeaders(target).get(CACHE_HEADER);
75
+ if (headerValue === null) {
76
+ throw new Error(
77
+ `assertCacheStatus: response has no ${CACHE_HEADER} header. ` +
78
+ `Enable the debug cache signal via createRouter({ debugCacheSignal: true }) ` +
79
+ `or RANGO_TEST_SIGNALS=1.`,
80
+ );
81
+ }
82
+ const map = parseCacheHeader(headerValue);
83
+ const actual = map[segment];
84
+ if (actual === undefined) {
85
+ const known = Object.keys(map);
86
+ throw new Error(
87
+ `assertCacheStatus: segment "${segment}" not found in ${CACHE_HEADER} ` +
88
+ `("${headerValue}"). Known segments: ${
89
+ known.length > 0 ? known.join(", ") : "(none)"
90
+ }.`,
91
+ );
92
+ }
93
+ if (actual !== expected) {
94
+ throw new Error(
95
+ `assertCacheStatus: segment "${segment}" expected "${expected}" but got "${actual}".`,
96
+ );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * A telemetry sink paired with the array it records events into.
102
+ */
103
+ export interface CacheSink {
104
+ /** Wire into `createRouter({ telemetry: sink })`. */
105
+ sink: TelemetrySink;
106
+ /** All telemetry events captured so far, in emit order. */
107
+ events: TelemetryEvent[];
108
+ }
109
+
110
+ export function createCacheSink(): CacheSink {
111
+ const events: TelemetryEvent[] = [];
112
+ const sink: TelemetrySink = {
113
+ emit(event: TelemetryEvent): void {
114
+ events.push(event);
115
+ },
116
+ };
117
+ return { sink, events };
118
+ }
119
+
120
+ export function filterCacheDecisions(
121
+ events: readonly TelemetryEvent[],
122
+ ): CacheDecisionEvent[] {
123
+ return events.filter(
124
+ (e): e is CacheDecisionEvent => e.type === "cache.decision",
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Telemetry-path counterpart of {@link assertCacheStatus}: assert a captured
130
+ * `cache.decision` event reported `expected` for the segment keyed by `routeKey`
131
+ * (the route NAME, the same coarse key the header path uses). Throws an
132
+ * actionable error when no matching segment was captured, or on a mismatch.
133
+ *
134
+ * Pairs with {@link createCacheSink}: wire `createRouter({ telemetry: sink })`,
135
+ * drive an RSC request, then assert against the recorded `events`. This is the
136
+ * zero-production-surface path (no header to enable). NOTE: `events` accumulates
137
+ * across requests, so the FIRST matching segment wins — slice or recreate the
138
+ * sink between requests for the same `routeKey`.
139
+ */
140
+ export function assertCacheDecision(
141
+ events: readonly TelemetryEvent[],
142
+ routeKey: string,
143
+ expected: ExpectedCacheStatus,
144
+ ): void {
145
+ const segments = filterCacheDecisions(events).flatMap(
146
+ (d) => d.segments ?? [],
147
+ );
148
+ const seg = segments.find((s) => s.id === routeKey);
149
+ if (seg === undefined) {
150
+ const known = segments.map((s) => s.id);
151
+ throw new Error(
152
+ `assertCacheDecision: no cache.decision segment for routeKey "${routeKey}". ` +
153
+ `Seen: ${known.length > 0 ? known.join(", ") : "(none)"}. Wire ` +
154
+ `createRouter({ telemetry: createCacheSink().sink }) and drive an RSC request.`,
155
+ );
156
+ }
157
+ if (seg.cacheStatus !== expected) {
158
+ throw new Error(
159
+ `assertCacheDecision: routeKey "${routeKey}" expected "${expected}" but got "${seg.cacheStatus}".`,
160
+ );
161
+ }
162
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * collectHandle — unit-test a handle's `collect`/accumulator function directly.
3
+ *
4
+ * A handle's collect function (the `createHandle(collect)` argument that maps the
5
+ * per-segment pushed values into the accumulated result) is otherwise not
6
+ * directly reachable: createHandle keeps it in a private registry keyed by the
7
+ * handle's `$$id` and returns only `{ __brand, $$id }`. This primitive runs that
8
+ * REAL registered collect on per-segment values you provide and returns the
9
+ * accumulated result — so the mapper/accumulator is unit-testable without a full
10
+ * route match.
11
+ *
12
+ * It relies on createHandle registering the collect even in a bare test (it
13
+ * assigns a runtime fallback id when the Vite plugin did not inject one). If a
14
+ * handle's module was never imported (so createHandle never ran), the collect is
15
+ * unregistered and this falls back to the default identity collect — with a
16
+ * warning, since a CUSTOM collect that failed to register silently returns the
17
+ * wrong shape.
18
+ */
19
+
20
+ import { getCollectFn, type Handle } from "../handle.js";
21
+
22
+ export function collectHandle<TData, TAccumulated>(
23
+ handle: Handle<TData, TAccumulated>,
24
+ segments: ReadonlyArray<ReadonlyArray<TData>>,
25
+ ): TAccumulated {
26
+ const collectFn = getCollectFn(handle.$$id) as
27
+ | ((segments: TData[][]) => TAccumulated)
28
+ | undefined;
29
+
30
+ // Drop empty arrays matching production behavior (segment count/indices).
31
+ const nonEmpty = segments.filter((seg) => seg.length > 0) as TData[][];
32
+
33
+ // No registered collect (the handle's module was not imported): fall back to the
34
+ // default identity collect — the per-segment arrays as-is, mirroring production
35
+ // collectHandleData. Warn, because a handle with a CUSTOM collect would silently
36
+ // get the wrong shape (the runtime can't tell it from an intended default).
37
+ if (!collectFn) {
38
+ console.warn(
39
+ `[rango] collectHandle: handle "${handle.$$id}" has no registered collect — ` +
40
+ `falling back to the identity (per-segment data as-is). Import the handle's ` +
41
+ `module so createHandle() runs if you expected a custom collect.`,
42
+ );
43
+ return nonEmpty as unknown as TAccumulated;
44
+ }
45
+ return collectFn(nonEmpty);
46
+ }