@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
package/src/rsc/types.ts CHANGED
@@ -7,21 +7,33 @@
7
7
 
8
8
  import type { ResolvedSegment, SlotState } from "../types.js";
9
9
  import type { HandleData } from "../server/handle-store.js";
10
- import type { RSCRouter } from "../router.js";
10
+ import type { RangoInternal } from "../router/router-interfaces.js";
11
11
  import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
12
12
 
13
13
  /**
14
- * RSC payload sent to the client
14
+ * RSC payload sent to the client.
15
+ * The tree is always reconstructed from metadata.segments by consumers
16
+ * (SSR via renderSegments, browser via renderSegments in bridges).
15
17
  */
16
18
  export interface RscPayload {
17
- root: React.ReactNode | Promise<React.ReactNode>;
18
19
  metadata?: {
19
20
  pathname: string;
20
21
  segments: ResolvedSegment[];
22
+ /** Router instance ID. When this changes between navigations, the client
23
+ * discards cached segments and does a full tree replacement (app switch). */
24
+ routerId?: string;
21
25
  isPartial?: boolean;
22
26
  isError?: boolean;
23
27
  matched?: string[];
24
28
  diff?: string[];
29
+ /**
30
+ * All segment ids re-resolved on the server, including null-component
31
+ * ones excluded from `segments`/`diff`. Drives client-side handle-bucket
32
+ * cleanup. Superset of `diff`. See MatchResult.resolvedIds.
33
+ */
34
+ resolvedIds?: string[];
35
+ /** Merged route params from the matched route */
36
+ params?: Record<string, string>;
25
37
  slots?: Record<string, SlotState>;
26
38
  /** Root layout component for browser-side re-renders (client component reference) */
27
39
  rootLayout?: React.ComponentType<{ children: React.ReactNode }>;
@@ -29,15 +41,39 @@ export interface RscPayload {
29
41
  handles?: AsyncGenerator<HandleData, void, unknown>;
30
42
  /** RSC version string for cache invalidation */
31
43
  version?: string;
44
+ /** TTL in milliseconds for the client-side in-memory prefetch cache */
45
+ prefetchCacheTTL?: number;
46
+ /** Max entries in the client-side in-memory prefetch cache (FIFO eviction) */
47
+ prefetchCacheSize?: number;
48
+ /** Max concurrent speculative prefetch requests on the client */
49
+ prefetchConcurrency?: number;
50
+ /** Server-resolved rango state cookie name; the client reads it verbatim. */
51
+ stateCookieName?: string;
32
52
  /** Theme configuration for FOUC prevention */
33
53
  themeConfig?: ResolvedThemeConfig | null;
34
54
  /** Initial theme from cookie (for SSR hydration) */
35
55
  initialTheme?: Theme;
56
+ /** URL prefix for all routes (from createRouter({ basename })). */
57
+ basename?: string;
36
58
  /** Whether connection warmup is enabled */
37
59
  warmupEnabled?: boolean;
60
+ /**
61
+ * Whether the client should hydrate inside React.StrictMode. Carried on
62
+ * the initial full-render payload only; the browser entry reads it once at
63
+ * hydration. Absent on partial (navigation) payloads. Defaults to true on
64
+ * the client when omitted.
65
+ */
66
+ strictMode?: boolean;
67
+ /**
68
+ * Server-side redirect with optional state (for partial requests).
69
+ * `external: true` (from redirect(url, { external: true })) tells the client
70
+ * to hard-navigate to an off-host target instead of validating same-origin.
71
+ */
72
+ redirect?: { url: string; external?: boolean };
73
+ /** Server-set location state to include in history.pushState */
74
+ locationState?: Record<string, unknown>;
38
75
  };
39
76
  returnValue?: { ok: boolean; data: unknown };
40
- formState?: unknown;
41
77
  }
42
78
 
43
79
  /**
@@ -54,7 +90,10 @@ export interface RSCDependencies {
54
90
  */
55
91
  renderToReadableStream: <T>(
56
92
  payload: T,
57
- options?: { temporaryReferences?: unknown }
93
+ options?: {
94
+ temporaryReferences?: unknown;
95
+ onError?: (error: unknown) => string | void;
96
+ },
58
97
  ) => ReadableStream<Uint8Array>;
59
98
 
60
99
  /**
@@ -62,7 +101,7 @@ export interface RSCDependencies {
62
101
  */
63
102
  decodeReply: (
64
103
  body: FormData | string,
65
- options?: { temporaryReferences?: unknown }
104
+ options?: { temporaryReferences?: unknown },
66
105
  ) => Promise<unknown[]>;
67
106
 
68
107
  /**
@@ -87,7 +126,7 @@ export interface RSCDependencies {
87
126
  */
88
127
  decodeFormState: (
89
128
  actionResult: unknown,
90
- body: FormData
129
+ body: FormData,
91
130
  ) => Promise<ReactFormState | null>;
92
131
  }
93
132
 
@@ -107,6 +146,14 @@ export interface SSRRenderOptions {
107
146
  * Nonce for Content Security Policy (CSP)
108
147
  */
109
148
  nonce?: string;
149
+
150
+ /**
151
+ * SSR stream mode.
152
+ *
153
+ * - `"stream"` (default) — start flushing HTML immediately.
154
+ * - `"allReady"` — await `stream.allReady` before returning.
155
+ */
156
+ streamMode?: import("../router/router-options.js").SSRStreamMode;
110
157
  }
111
158
 
112
159
  /**
@@ -115,7 +162,33 @@ export interface SSRRenderOptions {
115
162
  export interface SSRModule {
116
163
  renderHTML: (
117
164
  rscStream: ReadableStream<Uint8Array>,
118
- options?: SSRRenderOptions
165
+ options?: SSRRenderOptions,
166
+ ) => Promise<ReadableStream<Uint8Array>>;
167
+
168
+ /**
169
+ * PPR shell CAPTURE strategy (Axis 2). Prerenders the loader-masked shell over
170
+ * the Flight stream, aborts once quiescent, and returns the prelude bytes plus
171
+ * the postponed resume state — or null when the prelude degraded and must not
172
+ * be stored. Present only when the SSR virtual entry wires
173
+ * createShellCaptureHandler; the render layer feature-detects it. See
174
+ * docs/design/ppr-shell-resume.md.
175
+ */
176
+ captureShellHTML?: (
177
+ rscStream: ReadableStream<Uint8Array>,
178
+ options: { quiesce: Promise<void>; maxWaitMs?: number },
179
+ ) => Promise<{ prelude: Uint8Array; postponed: string | null } | null>;
180
+
181
+ /**
182
+ * PPR shell RESUME strategy (Axis 2). Produces the per-request live portion of
183
+ * the document: resumes fizz over a fresh SsrRoot to emit only the postponed
184
+ * holes (or, for the DATA variant with postponed === null, just the fresh Flight
185
+ * payload scripts). The caller prepends the stored prelude bytes to form the
186
+ * composite response. Present only when the SSR virtual entry wires
187
+ * createShellResumeHandler; the render layer feature-detects it.
188
+ */
189
+ resumeShellHTML?: (
190
+ rscStream: ReadableStream<Uint8Array>,
191
+ options: { postponed: string | null; nonce?: string },
119
192
  ) => Promise<ReadableStream<Uint8Array>>;
120
193
  }
121
194
 
@@ -141,7 +214,7 @@ export interface HandlerCacheConfig {
141
214
  */
142
215
  export type NonceProvider<TEnv = unknown> = (
143
216
  request: Request,
144
- env: TEnv
217
+ env: TEnv,
145
218
  ) => string | true | Promise<string | true>;
146
219
 
147
220
  /**
@@ -154,7 +227,7 @@ export interface CreateRSCHandlerOptions<
154
227
  /**
155
228
  * The RSC router instance
156
229
  */
157
- router: RSCRouter<TEnv, TRoutes>;
230
+ router: RangoInternal<TEnv, TRoutes>;
158
231
 
159
232
  /**
160
233
  * RSC dependencies from @vitejs/plugin-rsc/rsc.
@@ -213,7 +286,8 @@ export interface CreateRSCHandlerOptions<
213
286
  * - Undefined to disable nonce (default)
214
287
  *
215
288
  * The nonce will be applied to inline scripts injected by the RSC payload.
216
- * It's also available to middleware via `ctx.get('nonce')`.
289
+ * It's also available to middleware via the typed `nonce` token:
290
+ * `import { nonce } from "@rangojs/router"; ctx.get(nonce)`
217
291
  *
218
292
  * @example Auto-generate nonce
219
293
  * ```tsx
@@ -230,7 +304,16 @@ export interface CreateRSCHandlerOptions<
230
304
  * nonce: (request, env) => env.nonce,
231
305
  * });
232
306
  * ```
307
+ *
308
+ * @example Access nonce in middleware
309
+ * ```tsx
310
+ * import { nonce } from "@rangojs/router";
311
+ *
312
+ * const cspMiddleware: Middleware = async (ctx, next) => {
313
+ * const value = ctx.get(nonce); // string | undefined
314
+ * await next();
315
+ * };
316
+ * ```
233
317
  */
234
318
  nonce?: NonceProvider<TEnv>;
235
-
236
319
  }
@@ -0,0 +1,18 @@
1
+ // Runtime-safe detection of a test runner (Vitest), used to decide whether a
2
+ // create*() call with no plugin-injected $$id may fall back to a synthetic id (a
3
+ // bare test) or must fail loud (dev / a real build).
4
+ //
5
+ // `process` is absent in some target runtimes (the browser, certain edge/worker
6
+ // RSC environments), so probe it through `globalThis` with optional chaining —
7
+ // NEVER a bare `process.env.VITEST`, which would ReferenceError before the
8
+ // intended error is thrown. Unlike `process.env.NODE_ENV` (folded by the app's
9
+ // build `define`), `VITEST` is not folded, so this stays a small runtime check;
10
+ // it lives only on the create*() error path (id missing), which never runs in a
11
+ // correct production build.
12
+ //
13
+ // Vitest sets `VITEST` in every test process — the node project and the
14
+ // react-server forks alike (the RSC project forces NODE_ENV=production, so NODE_ENV
15
+ // cannot distinguish it from a real build; `VITEST` can). A real build never sets it.
16
+ export function isUnderTestRunner(): boolean {
17
+ return !!globalThis.process?.env?.VITEST;
18
+ }
@@ -2,13 +2,21 @@
2
2
  * Search parameter schema types and runtime utilities.
3
3
  *
4
4
  * Provides a lightweight schema system for typed query parameters.
5
- * When a route defines a `search` schema, ctx.searchParams becomes
6
- * a typed object with parsed values instead of raw URLSearchParams.
5
+ * When a route defines a `search` schema, ctx.search provides a typed
6
+ * object with parsed values. ctx.searchParams always remains a standard
7
+ * URLSearchParams instance.
7
8
  */
8
9
 
9
- // ============================================================================
10
- // Schema Types
11
- // ============================================================================
10
+ import { encodeKV } from "./encode-kv.js";
11
+
12
+ /**
13
+ * Decimal-number grammar for `"number"` search params: optional sign, digits
14
+ * with optional fraction, optional exponent. Deliberately excludes hex (`0x`),
15
+ * `Infinity`, and empty/whitespace so `Number()`'s lenient coercions
16
+ * (`Number("")===0`, `Number("0x10")===16`, `Number("Infinity")===Infinity`)
17
+ * do not slip non-decimal values into typed search.
18
+ */
19
+ const DECIMAL_NUMBER_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
12
20
 
13
21
  /** Supported scalar types for search params (append ? for optional). */
14
22
  export type SearchSchemaValue =
@@ -22,10 +30,6 @@ export type SearchSchemaValue =
22
30
  /** A search schema maps param names to their type descriptors. */
23
31
  export type SearchSchema = Record<string, SearchSchemaValue>;
24
32
 
25
- // ============================================================================
26
- // Type-Level Schema Resolution
27
- // ============================================================================
28
-
29
33
  /** Strip trailing `?` from a schema value to get the base type. */
30
34
  type BaseType<T extends string> = T extends `${infer B}?` ? B : T;
31
35
 
@@ -54,27 +58,37 @@ type Simplify<T> = { [K in keyof T]: T[K] };
54
58
  /**
55
59
  * Resolve a SearchSchema to its typed object.
56
60
  *
61
+ * Both required and optional params resolve to `T | undefined` at the handler
62
+ * level. The required/optional distinction is a consumer-facing contract
63
+ * (e.g., for href() and reverse() autocomplete) — it tells callers which
64
+ * params the route expects, but the handler must still check for undefined
65
+ * since the framework cannot trust the client to send all required params.
66
+ *
57
67
  * @example
58
68
  * type S = { q: "string"; page: "number?"; sort: "string?" };
59
69
  * type R = ResolveSearchSchema<S>;
60
- * // { q: string; page?: number; sort?: string }
70
+ * // { q: string | undefined; page?: number; sort?: string }
61
71
  */
62
- export type ResolveSearchSchema<T extends SearchSchema> = Simplify<{
63
- [K in RequiredKeys<T> & string]: ResolveBaseType<BaseType<T[K]>>;
64
- } & {
65
- [K in OptionalKeys<T> & string]?: ResolveBaseType<BaseType<T[K]>>;
66
- }>;
72
+ export type ResolveSearchSchema<T extends SearchSchema> = Simplify<
73
+ {
74
+ [K in RequiredKeys<T> & string]:
75
+ | ResolveBaseType<BaseType<T[K]>>
76
+ | undefined;
77
+ } & {
78
+ [K in OptionalKeys<T> & string]?: ResolveBaseType<BaseType<T[K]>>;
79
+ }
80
+ >;
67
81
 
68
82
  // ============================================================================
69
83
  // Route-Level Type Extraction
70
84
  // ============================================================================
71
85
 
72
86
  /** Resolve the global route map from RegisteredRoutes or GeneratedRouteMap. */
73
- type GlobalRouteMap = keyof RSCRouter.RegisteredRoutes extends never
74
- ? keyof RSCRouter.GeneratedRouteMap extends never
87
+ type GlobalRouteMap = keyof Rango.RegisteredRoutes extends never
88
+ ? keyof Rango.GeneratedRouteMap extends never
75
89
  ? Record<string, string>
76
- : RSCRouter.GeneratedRouteMap
77
- : RSCRouter.RegisteredRoutes;
90
+ : Rango.GeneratedRouteMap
91
+ : Rango.RegisteredRoutes;
78
92
 
79
93
  /**
80
94
  * Extract the resolved search params type for a named route.
@@ -87,19 +101,17 @@ type GlobalRouteMap = keyof RSCRouter.RegisteredRoutes extends never
87
101
  * // { q: string; page?: number }
88
102
  * ```
89
103
  */
90
- export type RouteSearchParams<
91
- TName extends string,
92
- TRouteMap = never,
93
- > = [TRouteMap] extends [never]
104
+ export type RouteSearchParams<TName extends string, TRouteMap = never> = [
105
+ TRouteMap,
106
+ ] extends [never]
94
107
  ? ExtractAndResolveSearch<GlobalRouteMap, TName>
95
108
  : ExtractAndResolveSearch<TRouteMap, TName>;
96
109
 
97
- type ExtractAndResolveSearch<TRouteMap, TName> =
98
- TName extends keyof TRouteMap
99
- ? TRouteMap[TName] extends { readonly search: infer S extends SearchSchema }
100
- ? ResolveSearchSchema<S>
101
- : {}
102
- : {};
110
+ type ExtractAndResolveSearch<TRouteMap, TName> = TName extends keyof TRouteMap
111
+ ? TRouteMap[TName] extends { readonly search: infer S extends SearchSchema }
112
+ ? ResolveSearchSchema<S>
113
+ : {}
114
+ : {};
103
115
 
104
116
  /**
105
117
  * Extract the route params type for a named route.
@@ -112,50 +124,62 @@ type ExtractAndResolveSearch<TRouteMap, TName> =
112
124
  * // { slug: string }
113
125
  * ```
114
126
  */
115
- export type RouteParams<
116
- TName extends string,
117
- TRouteMap = never,
118
- > = [TRouteMap] extends [never]
127
+ export type RouteParams<TName extends string, TRouteMap = never> = [
128
+ TRouteMap,
129
+ ] extends [never]
119
130
  ? ExtractRouteParamsFromMap<GlobalRouteMap, TName>
120
131
  : ExtractRouteParamsFromMap<TRouteMap, TName>;
121
132
 
122
- type ExtractRouteParamsFromMap<TRouteMap, TName> =
123
- TName extends keyof TRouteMap
124
- ? TRouteMap[TName] extends string
125
- ? ExtractParamsFromPattern<TRouteMap[TName]>
126
- : TRouteMap[TName] extends { readonly path: infer P extends string }
127
- ? ExtractParamsFromPattern<P>
128
- : {}
129
- : {};
133
+ type ExtractRouteParamsFromMap<TRouteMap, TName> = TName extends keyof TRouteMap
134
+ ? TRouteMap[TName] extends string
135
+ ? ExtractParamsFromPattern<TRouteMap[TName]>
136
+ : TRouteMap[TName] extends { readonly path: infer P extends string }
137
+ ? ExtractParamsFromPattern<P>
138
+ : {}
139
+ : {};
140
+
141
+ /** Parse "a|b|c" into "a" | "b" | "c" */
142
+ type ParseConstraint<T extends string> =
143
+ T extends `${infer First}|${infer Rest}` ? First | ParseConstraint<Rest> : T;
130
144
 
131
145
  /** Minimal inline param extraction (avoids importing from types.ts to prevent circular deps). */
132
146
  type ExtractParamsFromPattern<T extends string> =
133
147
  T extends `${string}:${infer Param}/${infer Rest}`
134
- ? Param extends `${infer Name}?`
135
- ? { [K in Name]?: string } & ExtractParamsFromPattern<`/${Rest}`>
136
- : Param extends `${infer Name}(${string})`
137
- ? { [K in Name]: string } & ExtractParamsFromPattern<`/${Rest}`>
138
- : { [K in Param]: string } & ExtractParamsFromPattern<`/${Rest}`>
148
+ ? Param extends `${infer Name}(${infer C})?`
149
+ ? {
150
+ [K in Name]?: ParseConstraint<C>;
151
+ } & ExtractParamsFromPattern<`/${Rest}`>
152
+ : Param extends `${infer Name}(${infer C})`
153
+ ? {
154
+ [K in Name]: ParseConstraint<C>;
155
+ } & ExtractParamsFromPattern<`/${Rest}`>
156
+ : Param extends `${infer Name}?`
157
+ ? { [K in Name]?: string } & ExtractParamsFromPattern<`/${Rest}`>
158
+ : { [K in Param]: string } & ExtractParamsFromPattern<`/${Rest}`>
139
159
  : T extends `${string}:${infer Param}`
140
- ? Param extends `${infer Name}?`
141
- ? { [K in Name]?: string }
142
- : Param extends `${infer Name}(${string})`
143
- ? { [K in Name]: string }
144
- : { [K in Param]: string }
160
+ ? Param extends `${infer Name}(${infer C})?`
161
+ ? { [K in Name]?: ParseConstraint<C> }
162
+ : Param extends `${infer Name}(${infer C})`
163
+ ? { [K in Name]: ParseConstraint<C> }
164
+ : Param extends `${infer Name}?`
165
+ ? { [K in Name]?: string }
166
+ : { [K in Param]: string }
145
167
  : {};
146
168
 
147
- // ============================================================================
148
- // Runtime Parser
149
- // ============================================================================
150
-
151
169
  /**
152
170
  * Parse URLSearchParams into a typed object using the given schema.
153
171
  *
154
172
  * - `"string"` / `"string?"` - kept as-is
155
- * - `"number"` / `"number?"` - coerced via `Number()`; NaN treated as missing
173
+ * - `"number"` / `"number?"` - parsed as a finite decimal number. Accepts an
174
+ * optional sign, digits with optional fraction, and optional exponent
175
+ * (e.g. `42`, `-3.5`, `1e3`). Empty/whitespace-only, non-decimal forms
176
+ * (`0x10`), and non-finite (`Infinity`) are treated as missing (omitted),
177
+ * NOT coerced to `0`/`16`/`Infinity`.
156
178
  * - `"boolean"` / `"boolean?"` - `"true"` / `"1"` -> true, `"false"` / `"0"` / `""` -> false
157
179
  *
158
- * Missing required params are set to their zero value (empty string / 0 / false).
180
+ * Missing params (both required and optional) are omitted from the result
181
+ * (undefined). The required/optional distinction is a consumer-facing contract
182
+ * only — the handler must check for undefined.
159
183
  */
160
184
  export function parseSearchParams<T extends SearchSchema>(
161
185
  searchParams: URLSearchParams,
@@ -169,25 +193,24 @@ export function parseSearchParams<T extends SearchSchema>(
169
193
  const raw = searchParams.get(key);
170
194
 
171
195
  if (raw === null) {
172
- if (!isOptional) {
173
- // Required param missing: use zero value
174
- if (baseType === "string") result[key] = "";
175
- else if (baseType === "number") result[key] = 0;
176
- else if (baseType === "boolean") result[key] = false;
177
- }
178
- // Optional params are omitted (undefined)
196
+ // Missing params are omitted (undefined) regardless of required/optional
179
197
  continue;
180
198
  }
181
199
 
182
200
  if (baseType === "string") {
183
201
  result[key] = raw;
184
202
  } else if (baseType === "number") {
185
- const num = Number(raw);
186
- if (Number.isNaN(num)) {
187
- if (!isOptional) result[key] = 0;
188
- } else {
189
- result[key] = num;
203
+ // Trim, then require a valid decimal numeral and a finite result.
204
+ // Empty/whitespace, hex (0x10), and Infinity are treated as missing
205
+ // (omitted) — not coerced to 0/16/Infinity by Number()'s lenient rules.
206
+ const trimmed = raw.trim();
207
+ if (trimmed !== "" && DECIMAL_NUMBER_RE.test(trimmed)) {
208
+ const num = Number(trimmed);
209
+ if (Number.isFinite(num)) {
210
+ result[key] = num;
211
+ }
190
212
  }
213
+ // Anything else treated as missing (undefined)
191
214
  } else if (baseType === "boolean") {
192
215
  result[key] = raw === "true" || raw === "1";
193
216
  }
@@ -196,23 +219,17 @@ export function parseSearchParams<T extends SearchSchema>(
196
219
  return result as ResolveSearchSchema<T>;
197
220
  }
198
221
 
199
- // ============================================================================
200
- // Runtime Serializer
201
- // ============================================================================
202
-
203
222
  /**
204
223
  * Serialize a typed search params object to a query string (without leading `?`).
205
- * Skips `undefined` and `null` values.
224
+ * Skips `undefined` and `null` values. Preserves insertion order (no sort).
206
225
  */
207
- export function serializeSearchParams(
208
- params: Record<string, unknown>,
209
- ): string {
210
- const parts: string[] = [];
226
+ export function serializeSearchParams(params: Record<string, unknown>): string {
227
+ // Pre-filter null/undefined and coerce values to strings here so encodeKV
228
+ // (which never inspects values) reproduces this call site's exact output.
229
+ const pairs: [string, string][] = [];
211
230
  for (const [key, value] of Object.entries(params)) {
212
231
  if (value === undefined || value === null) continue;
213
- parts.push(
214
- `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,
215
- );
232
+ pairs.push([key, String(value)]);
216
233
  }
217
- return parts.join("&");
234
+ return encodeKV(pairs);
218
235
  }
@@ -0,0 +1,67 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ /**
4
+ * Stable Promise wrappers keyed on the component itself. Objects (React
5
+ * elements, functions, lazy payloads) land in a WeakMap so entries GC when
6
+ * the underlying component is released; primitives (string, number, boolean,
7
+ * null) land in a Map so memoization still applies to text-/null-backed
8
+ * segments like those in partial-update flows. Keeping this cache outside
9
+ * the segment eliminates preservation fields on ResolvedSegment — it survives
10
+ * reconciliation naturally because the component ref is what's stable.
11
+ *
12
+ * Browser-only. On the server each SSR render needs a fresh pending promise
13
+ * so Suspense can emit the loading fallback HTML before content streams. A
14
+ * shared already-resolved promise has `.status === "fulfilled"` attached by
15
+ * React on its first observation — subsequent `use()` calls return
16
+ * synchronously without suspending, so the Suspense fallback never makes it
17
+ * into the initial HTML. Route-definition components share refs across
18
+ * requests, so a global cache would leak tracked state between renders.
19
+ */
20
+ const IS_BROWSER = typeof window !== "undefined";
21
+ const objectContentCache = IS_BROWSER
22
+ ? new WeakMap<object, Promise<ReactNode>>()
23
+ : null;
24
+ const primitiveContentCache = IS_BROWSER
25
+ ? new Map<unknown, Promise<ReactNode>>()
26
+ : null;
27
+
28
+ /**
29
+ * Return a stable Promise wrapping `component`, memoized on the component ref.
30
+ *
31
+ * A fresh `Promise.resolve(component)` each render would suspend for one
32
+ * microtask and briefly commit the loading fallback inside Suspender — the
33
+ * intercept / parallel-slot flicker this indirection prevents. Reusing the
34
+ * same Promise ref keeps React's `use()` in "known fulfilled" state after
35
+ * the first observation.
36
+ *
37
+ * @internal
38
+ */
39
+ export function getMemoizedContentPromise(
40
+ component: ReactNode,
41
+ ): Promise<ReactNode> {
42
+ if (component instanceof Promise) {
43
+ return component as Promise<ReactNode>;
44
+ }
45
+
46
+ if (!objectContentCache || !primitiveContentCache) {
47
+ return Promise.resolve(component);
48
+ }
49
+
50
+ if (component !== null && typeof component === "object") {
51
+ const cached = objectContentCache.get(component);
52
+ if (cached) {
53
+ return cached;
54
+ }
55
+ const promise = Promise.resolve(component);
56
+ objectContentCache.set(component, promise);
57
+ return promise;
58
+ }
59
+
60
+ const cached = primitiveContentCache.get(component);
61
+ if (cached) {
62
+ return cached;
63
+ }
64
+ const promise = Promise.resolve(component);
65
+ primitiveContentCache.set(component, promise);
66
+ return promise;
67
+ }