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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +126 -16
  3. package/dist/bin/rango.js +319 -95
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +2724 -1053
  6. package/package.json +68 -14
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +64 -2
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +224 -32
  11. package/skills/caching/SKILL.md +279 -17
  12. package/skills/composability/SKILL.md +27 -3
  13. package/skills/css/SKILL.md +76 -0
  14. package/skills/debug-manifest/SKILL.md +4 -2
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +11 -9
  17. package/skills/hooks/SKILL.md +243 -29
  18. package/skills/host-router/SKILL.md +83 -23
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +68 -19
  21. package/skills/layout/SKILL.md +13 -9
  22. package/skills/links/SKILL.md +190 -23
  23. package/skills/loader/SKILL.md +235 -9
  24. package/skills/middleware/SKILL.md +18 -10
  25. package/skills/migrate-nextjs/SKILL.md +43 -19
  26. package/skills/migrate-react-router/SKILL.md +8 -2
  27. package/skills/mime-routes/SKILL.md +28 -1
  28. package/skills/observability/SKILL.md +172 -0
  29. package/skills/parallel/SKILL.md +18 -7
  30. package/skills/prerender/SKILL.md +65 -60
  31. package/skills/rango/SKILL.md +251 -24
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +115 -48
  34. package/skills/route/SKILL.md +46 -5
  35. package/skills/router-setup/SKILL.md +30 -8
  36. package/skills/scripts/SKILL.md +179 -0
  37. package/skills/server-actions/SKILL.md +775 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +130 -0
  40. package/skills/testing/bindings.md +103 -0
  41. package/skills/testing/cache-prerender.md +127 -0
  42. package/skills/testing/client-components.md +124 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +91 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +122 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +123 -0
  54. package/skills/typesafety/SKILL.md +322 -29
  55. package/skills/use-cache/SKILL.md +57 -14
  56. package/skills/view-transitions/SKILL.md +337 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +0 -65
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/connection-warmup.ts +134 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +192 -150
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/navigation-bridge.ts +94 -25
  70. package/src/browser/navigation-client.ts +121 -84
  71. package/src/browser/navigation-store-handle.ts +38 -0
  72. package/src/browser/navigation-store.ts +115 -67
  73. package/src/browser/navigation-transaction.ts +9 -59
  74. package/src/browser/network-error-handler.ts +34 -7
  75. package/src/browser/partial-update.ts +147 -128
  76. package/src/browser/prefetch/cache.ts +107 -56
  77. package/src/browser/prefetch/fetch.ts +204 -34
  78. package/src/browser/prefetch/queue.ts +6 -3
  79. package/src/browser/rango-state.ts +158 -76
  80. package/src/browser/react/Link.tsx +30 -7
  81. package/src/browser/react/NavigationProvider.tsx +283 -118
  82. package/src/browser/react/ScrollRestoration.tsx +10 -6
  83. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  84. package/src/browser/react/filter-segment-order.ts +66 -7
  85. package/src/browser/react/index.ts +0 -48
  86. package/src/browser/react/location-state-shared.ts +178 -8
  87. package/src/browser/react/location-state.ts +39 -14
  88. package/src/browser/react/use-action.ts +6 -15
  89. package/src/browser/react/use-handle.ts +17 -14
  90. package/src/browser/react/use-href.tsx +8 -1
  91. package/src/browser/react/use-link-status.ts +33 -8
  92. package/src/browser/react/use-navigation.ts +10 -5
  93. package/src/browser/react/use-params.ts +11 -11
  94. package/src/browser/react/use-reverse.ts +106 -0
  95. package/src/browser/react/use-router.ts +25 -3
  96. package/src/browser/react/use-search-params.ts +0 -5
  97. package/src/browser/react/use-segments.ts +11 -21
  98. package/src/browser/response-adapter.ts +99 -8
  99. package/src/browser/rsc-router.tsx +91 -24
  100. package/src/browser/scroll-restoration.ts +30 -17
  101. package/src/browser/segment-structure-assert.ts +2 -2
  102. package/src/browser/server-action-bridge.ts +214 -55
  103. package/src/browser/types.ts +80 -9
  104. package/src/browser/validate-redirect-origin.ts +43 -16
  105. package/src/build/collect-fallback-refs.ts +107 -0
  106. package/src/build/generate-manifest.ts +60 -35
  107. package/src/build/generate-route-types.ts +2 -1
  108. package/src/build/index.ts +8 -2
  109. package/src/build/prefix-tree-utils.ts +123 -0
  110. package/src/build/route-trie.ts +117 -14
  111. package/src/build/route-types/ast-route-extraction.ts +15 -8
  112. package/src/build/route-types/codegen.ts +16 -5
  113. package/src/build/route-types/include-resolution.ts +117 -23
  114. package/src/build/route-types/param-extraction.ts +6 -3
  115. package/src/build/route-types/per-module-writer.ts +22 -6
  116. package/src/build/route-types/router-processing.ts +55 -28
  117. package/src/build/route-types/scan-filter.ts +1 -1
  118. package/src/build/route-types/source-scan.ts +216 -0
  119. package/src/build/runtime-discovery.ts +9 -20
  120. package/src/cache/cache-error.ts +104 -0
  121. package/src/cache/cache-key-utils.ts +29 -13
  122. package/src/cache/cache-policy.ts +108 -34
  123. package/src/cache/cache-runtime.ts +224 -41
  124. package/src/cache/cache-scope.ts +188 -82
  125. package/src/cache/cache-tag.ts +103 -0
  126. package/src/cache/cf/cf-base64.ts +33 -0
  127. package/src/cache/cf/cf-cache-constants.ts +127 -0
  128. package/src/cache/cf/cf-cache-store.ts +1989 -378
  129. package/src/cache/cf/cf-cache-types.ts +349 -0
  130. package/src/cache/cf/cf-kv-utils.ts +46 -0
  131. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  132. package/src/cache/cf/index.ts +6 -16
  133. package/src/cache/document-cache.ts +89 -21
  134. package/src/cache/handle-snapshot.ts +70 -0
  135. package/src/cache/index.ts +10 -20
  136. package/src/cache/memory-segment-store.ts +136 -37
  137. package/src/cache/profile-registry.ts +46 -31
  138. package/src/cache/read-through-swr.ts +56 -12
  139. package/src/cache/segment-codec.ts +9 -17
  140. package/src/cache/tag-invalidation.ts +230 -0
  141. package/src/cache/types.ts +37 -100
  142. package/src/client.rsc.tsx +44 -21
  143. package/src/client.tsx +36 -61
  144. package/src/cloudflare/index.ts +11 -0
  145. package/src/cloudflare/tracing.ts +109 -0
  146. package/src/component-utils.ts +19 -0
  147. package/src/components/DefaultDocument.tsx +8 -2
  148. package/src/context-var.ts +18 -6
  149. package/src/decode-loader-results.ts +52 -0
  150. package/src/defer.ts +196 -0
  151. package/src/deps/ssr.ts +0 -1
  152. package/src/encode-kv.ts +49 -0
  153. package/src/errors.ts +30 -4
  154. package/src/escape-script.ts +52 -0
  155. package/src/handle.ts +31 -23
  156. package/src/handles/MetaTags.tsx +62 -19
  157. package/src/handles/Scripts.tsx +183 -0
  158. package/src/handles/breadcrumbs.ts +37 -8
  159. package/src/handles/is-thenable.ts +19 -0
  160. package/src/handles/meta.ts +51 -40
  161. package/src/handles/script.ts +244 -0
  162. package/src/host/cookie-handler.ts +9 -60
  163. package/src/host/errors.ts +0 -24
  164. package/src/host/index.ts +8 -2
  165. package/src/host/pattern-matcher.ts +23 -52
  166. package/src/host/router.ts +107 -99
  167. package/src/host/testing.ts +40 -27
  168. package/src/host/types.ts +37 -4
  169. package/src/host/utils.ts +1 -1
  170. package/src/href-client.ts +137 -22
  171. package/src/index.rsc.ts +96 -12
  172. package/src/index.ts +94 -14
  173. package/src/internal-debug.ts +11 -10
  174. package/src/loader-store.ts +500 -0
  175. package/src/loader.rsc.ts +20 -13
  176. package/src/loader.ts +12 -11
  177. package/src/missing-id-error.ts +68 -0
  178. package/src/outlet-context.ts +1 -1
  179. package/src/outlet-provider.tsx +1 -5
  180. package/src/prerender/param-hash.ts +16 -16
  181. package/src/prerender/store.ts +32 -37
  182. package/src/prerender.ts +61 -6
  183. package/src/redirect-origin.ts +100 -0
  184. package/src/regex-escape.ts +8 -0
  185. package/src/render-error-thrower.tsx +20 -0
  186. package/src/response-utils.ts +34 -0
  187. package/src/reverse.ts +65 -40
  188. package/src/root-error-boundary.tsx +1 -19
  189. package/src/route-content-wrapper.tsx +19 -77
  190. package/src/route-definition/dsl-helpers.ts +304 -309
  191. package/src/route-definition/helper-factories.ts +28 -140
  192. package/src/route-definition/helpers-types.ts +82 -55
  193. package/src/route-definition/index.ts +1 -2
  194. package/src/route-definition/redirect.ts +44 -11
  195. package/src/route-definition/resolve-handler-use.ts +12 -1
  196. package/src/route-definition/use-item-types.ts +29 -0
  197. package/src/route-map-builder.ts +0 -16
  198. package/src/route-types.ts +19 -46
  199. package/src/router/basename.ts +14 -0
  200. package/src/router/content-negotiation.ts +73 -25
  201. package/src/router/error-handling.ts +45 -18
  202. package/src/router/find-match.ts +44 -23
  203. package/src/router/handler-context.ts +27 -43
  204. package/src/router/instrument.ts +350 -0
  205. package/src/router/intercept-resolution.ts +39 -20
  206. package/src/router/lazy-includes.ts +10 -47
  207. package/src/router/loader-resolution.ts +155 -72
  208. package/src/router/logging.ts +0 -6
  209. package/src/router/manifest.ts +18 -29
  210. package/src/router/match-api.ts +9 -24
  211. package/src/router/match-context.ts +0 -22
  212. package/src/router/match-handlers.ts +58 -58
  213. package/src/router/match-middleware/background-revalidation.ts +40 -24
  214. package/src/router/match-middleware/cache-lookup.ts +159 -285
  215. package/src/router/match-middleware/cache-store.ts +64 -52
  216. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  217. package/src/router/match-middleware/segment-resolution.ts +0 -22
  218. package/src/router/match-pipelines.ts +1 -42
  219. package/src/router/match-result.ts +44 -74
  220. package/src/router/metrics.ts +0 -34
  221. package/src/router/middleware-types.ts +7 -134
  222. package/src/router/middleware.ts +247 -166
  223. package/src/router/navigation-snapshot.ts +0 -51
  224. package/src/router/params-util.ts +23 -0
  225. package/src/router/pattern-matching.ts +85 -94
  226. package/src/router/prefetch-cache-ttl.ts +51 -0
  227. package/src/router/prerender-match.ts +104 -65
  228. package/src/router/preview-match.ts +3 -1
  229. package/src/router/request-classification.ts +28 -62
  230. package/src/router/revalidation.ts +123 -73
  231. package/src/router/route-snapshot.ts +0 -1
  232. package/src/router/router-context.ts +3 -28
  233. package/src/router/router-interfaces.ts +83 -35
  234. package/src/router/router-options.ts +136 -5
  235. package/src/router/router-registry.ts +2 -5
  236. package/src/router/segment-resolution/fresh.ts +97 -84
  237. package/src/router/segment-resolution/helpers.ts +86 -6
  238. package/src/router/segment-resolution/loader-cache.ts +76 -39
  239. package/src/router/segment-resolution/revalidation.ts +272 -320
  240. package/src/router/segment-resolution/static-store.ts +19 -5
  241. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  242. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  243. package/src/router/segment-resolution.ts +5 -1
  244. package/src/router/segment-wrappers.ts +6 -5
  245. package/src/router/state-cookie-name.ts +33 -0
  246. package/src/router/substitute-pattern-params.ts +56 -0
  247. package/src/router/telemetry-otel.ts +161 -199
  248. package/src/router/telemetry.ts +96 -19
  249. package/src/router/timeout.ts +0 -20
  250. package/src/router/tracing.ts +206 -0
  251. package/src/router/trie-matching.ts +162 -64
  252. package/src/router/types.ts +9 -63
  253. package/src/router/url-params.ts +0 -5
  254. package/src/router.ts +110 -55
  255. package/src/rsc/handler-context.ts +3 -2
  256. package/src/rsc/handler.ts +264 -220
  257. package/src/rsc/helpers.ts +100 -6
  258. package/src/rsc/index.ts +2 -5
  259. package/src/rsc/json-route-result.ts +38 -0
  260. package/src/rsc/loader-fetch.ts +114 -38
  261. package/src/rsc/manifest-init.ts +28 -41
  262. package/src/rsc/origin-guard.ts +39 -25
  263. package/src/rsc/progressive-enhancement.ts +117 -11
  264. package/src/rsc/redirect-guard.ts +99 -0
  265. package/src/rsc/response-cache-serve.ts +238 -0
  266. package/src/rsc/response-error.ts +79 -12
  267. package/src/rsc/response-route-handler.ts +88 -188
  268. package/src/rsc/rsc-rendering.ts +98 -76
  269. package/src/rsc/runtime-warnings.ts +23 -10
  270. package/src/rsc/server-action.ts +281 -117
  271. package/src/rsc/ssr-setup.ts +16 -0
  272. package/src/rsc/transition-gate.ts +89 -0
  273. package/src/rsc/types.ts +23 -5
  274. package/src/runtime-env.ts +18 -0
  275. package/src/search-params.ts +35 -30
  276. package/src/segment-loader-promise.ts +31 -4
  277. package/src/segment-system.tsx +254 -143
  278. package/src/serialize.ts +243 -0
  279. package/src/server/context.ts +163 -51
  280. package/src/server/cookie-parse.ts +32 -0
  281. package/src/server/cookie-store.ts +80 -5
  282. package/src/server/handle-store.ts +21 -38
  283. package/src/server/loader-registry.ts +33 -42
  284. package/src/server/request-context.ts +287 -178
  285. package/src/ssr/index.tsx +21 -16
  286. package/src/static-handler.ts +10 -13
  287. package/src/testing/cache-status.ts +162 -0
  288. package/src/testing/collect-handle.ts +40 -0
  289. package/src/testing/dispatch.ts +701 -0
  290. package/src/testing/dom.entry.ts +22 -0
  291. package/src/testing/e2e/fixture.ts +188 -0
  292. package/src/testing/e2e/index.ts +128 -0
  293. package/src/testing/e2e/matchers.ts +35 -0
  294. package/src/testing/e2e/page-helpers.ts +272 -0
  295. package/src/testing/e2e/parity.ts +387 -0
  296. package/src/testing/e2e/server.ts +195 -0
  297. package/src/testing/flight-matchers.ts +97 -0
  298. package/src/testing/flight-normalize.ts +11 -0
  299. package/src/testing/flight-runtime.d.ts +57 -0
  300. package/src/testing/flight-tree.ts +682 -0
  301. package/src/testing/flight.entry.ts +52 -0
  302. package/src/testing/flight.ts +257 -0
  303. package/src/testing/generated-routes.ts +183 -0
  304. package/src/testing/index.ts +105 -0
  305. package/src/testing/internal/context.ts +371 -0
  306. package/src/testing/internal/flight-client-globals.ts +30 -0
  307. package/src/testing/internal/seed-vars.ts +54 -0
  308. package/src/testing/render-handler.ts +357 -0
  309. package/src/testing/render-route.tsx +581 -0
  310. package/src/testing/run-loader.ts +385 -0
  311. package/src/testing/run-middleware.ts +205 -0
  312. package/src/testing/run-transition-when.ts +164 -0
  313. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  314. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  315. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  316. package/src/testing/vitest-stubs/version.ts +5 -0
  317. package/src/testing/vitest.ts +305 -0
  318. package/src/theme/ThemeProvider.tsx +20 -58
  319. package/src/theme/ThemeScript.tsx +7 -9
  320. package/src/theme/constants.ts +52 -13
  321. package/src/theme/index.ts +0 -7
  322. package/src/theme/theme-context.ts +1 -5
  323. package/src/theme/theme-script.ts +22 -21
  324. package/src/theme/use-theme.ts +0 -3
  325. package/src/types/boundaries.ts +0 -35
  326. package/src/types/cache-types.ts +13 -4
  327. package/src/types/error-types.ts +30 -90
  328. package/src/types/global-namespace.ts +54 -41
  329. package/src/types/handler-context.ts +110 -62
  330. package/src/types/index.ts +3 -10
  331. package/src/types/loader-types.ts +11 -9
  332. package/src/types/request-scope.ts +112 -0
  333. package/src/types/route-config.ts +6 -50
  334. package/src/types/route-entry.ts +0 -6
  335. package/src/types/segments.ts +135 -14
  336. package/src/urls/include-helper.ts +9 -56
  337. package/src/urls/index.ts +1 -11
  338. package/src/urls/path-helper-types.ts +29 -12
  339. package/src/urls/path-helper.ts +17 -106
  340. package/src/urls/pattern-types.ts +36 -19
  341. package/src/urls/response-types.ts +22 -29
  342. package/src/urls/type-extraction.ts +58 -139
  343. package/src/urls/urls-function.ts +1 -19
  344. package/src/use-loader.tsx +292 -107
  345. package/src/vite/debug.ts +185 -0
  346. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  347. package/src/vite/discovery/discover-routers.ts +126 -85
  348. package/src/vite/discovery/discovery-errors.ts +194 -0
  349. package/src/vite/discovery/gate-state.ts +171 -0
  350. package/src/vite/discovery/prerender-collection.ts +96 -68
  351. package/src/vite/discovery/route-types-writer.ts +40 -84
  352. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  353. package/src/vite/discovery/state.ts +44 -0
  354. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  355. package/src/vite/index.ts +2 -0
  356. package/src/vite/inject-client-debug.ts +36 -0
  357. package/src/vite/plugin-types.ts +126 -8
  358. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  359. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  360. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  361. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  362. package/src/vite/plugins/expose-action-id.ts +48 -95
  363. package/src/vite/plugins/expose-id-utils.ts +88 -55
  364. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  365. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  366. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  367. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  368. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  369. package/src/vite/plugins/performance-tracks.ts +26 -25
  370. package/src/vite/plugins/refresh-cmd.ts +1 -1
  371. package/src/vite/plugins/use-cache-transform.ts +73 -83
  372. package/src/vite/plugins/version-injector.ts +40 -29
  373. package/src/vite/plugins/version-plugin.ts +37 -40
  374. package/src/vite/plugins/virtual-entries.ts +39 -25
  375. package/src/vite/rango.ts +109 -118
  376. package/src/vite/router-discovery.ts +718 -119
  377. package/src/vite/utils/ast-handler-extract.ts +26 -35
  378. package/src/vite/utils/banner.ts +1 -1
  379. package/src/vite/utils/bundle-analysis.ts +10 -15
  380. package/src/vite/utils/client-chunks.ts +184 -0
  381. package/src/vite/utils/directive-prologue.ts +40 -0
  382. package/src/vite/utils/forward-user-plugins.ts +171 -0
  383. package/src/vite/utils/manifest-utils.ts +4 -59
  384. package/src/vite/utils/package-resolution.ts +20 -52
  385. package/src/vite/utils/prerender-utils.ts +54 -39
  386. package/src/vite/utils/shared-utils.ts +90 -41
  387. package/src/browser/action-response-classifier.ts +0 -99
  388. package/src/browser/react/use-client-cache.ts +0 -58
  389. package/src/browser/shallow.ts +0 -40
  390. package/src/handles/index.ts +0 -7
  391. package/src/network-error-thrower.tsx +0 -23
  392. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Shared internals for the consumer testing primitives.
3
+ *
4
+ * Builds a real RequestContext via the same createRequestContext the RSC
5
+ * handler uses, with test-friendly defaults, so loaders and middleware run
6
+ * with production-fidelity context (cookies, headers, get/set, use, reverse)
7
+ * instead of a hand-rolled mock.
8
+ */
9
+
10
+ import {
11
+ createRequestContext,
12
+ runWithRequestContext,
13
+ type RequestContext,
14
+ } from "../../server/request-context.js";
15
+ import { drainOnResponseCallbacks } from "../../rsc/helpers.js";
16
+ import { resolveLocationStateEntries } from "../../browser/react/location-state-shared.js";
17
+ import { createReverseFunction } from "../../router/handler-context.js";
18
+ import { normalizeBasename } from "../../router/basename.js";
19
+ import {
20
+ seedVariables,
21
+ resolveSeededStateCookieName,
22
+ type VarsInit,
23
+ type StateCookieSeed,
24
+ } from "./seed-vars.js";
25
+ import type { ThemeConfig } from "../../theme/types.js";
26
+ import { resolveThemeConfig } from "../../theme/constants.js";
27
+ import type { SegmentCacheStore } from "../../cache/types.js";
28
+ import type { CacheProfile } from "../../cache/profile-registry.js";
29
+
30
+ const DEFAULT_ORIGIN = "http://localhost/";
31
+
32
+ export type { VarsInit, StateCookieSeed };
33
+ export { seedVariables };
34
+
35
+ /** Normalize a Request | string | undefined into a concrete Request. */
36
+ export function toRequest(
37
+ request: Request | string | undefined,
38
+ init?: RequestInit,
39
+ ): Request {
40
+ if (request instanceof Request) return request;
41
+ return typeof request === "string"
42
+ ? new Request(new URL(request, DEFAULT_ORIGIN), init)
43
+ : new Request(DEFAULT_ORIGIN, init);
44
+ }
45
+
46
+ export interface CreateTestContextOptions<TEnv> {
47
+ env?: TEnv;
48
+ request?: Request | string;
49
+ requestInit?: RequestInit;
50
+ /** Backing store for ctx.get()/ctx.set(); pre-seeded from `vars`. */
51
+ variables?: Record<string, unknown>;
52
+ /** Variables a prior middleware would have set (object or [key, value] list). */
53
+ vars?: VarsInit;
54
+ /** Route name -> pattern map enabling ctx.reverse() without global state. */
55
+ routeMap?: Record<string, string>;
56
+ routeName?: string;
57
+ params?: Record<string, string>;
58
+ /**
59
+ * Router basename for this request (what the RSC handler stores on the
60
+ * context). Drives redirect() prefixing. Normalized exactly like
61
+ * createRouter({ basename }) (leading slash forced, trailing stripped, bare
62
+ * "/" -> undefined) so passing the same value your router takes yields the
63
+ * same redirect Location. Defaults to undefined (no basename).
64
+ */
65
+ basename?: string;
66
+ /**
67
+ * Cache store backing `use cache` functions invoked during the test, the
68
+ * same shape `createRouter({ cache })` resolves. Without it,
69
+ * registerCachedFunction bypasses (it checks for a store FIRST), so a cached
70
+ * function runs uncached and its taint/profile guards never fire. Wire one
71
+ * (e.g. `new MemorySegmentCacheStore()`) to exercise real cache behavior.
72
+ */
73
+ cacheStore?: SegmentCacheStore;
74
+ /**
75
+ * Cache profiles in the `createRouter({ cacheProfiles })` shape. Required for
76
+ * a `use cache: "profileName"` function to resolve its profile (an unknown
77
+ * profile throws), once a `cacheStore` is wired.
78
+ */
79
+ cacheProfiles?: Record<string, CacheProfile>;
80
+ /**
81
+ * Theme config in the same shape `createRouter({ theme })` takes (resolved
82
+ * internally). Without it `ctx.theme`/`ctx.setTheme` are inert (undefined),
83
+ * mirroring an app with no theme configured. Pass one (e.g. `true`, or
84
+ * `{ themes: [...] }`) to exercise a handler that reads them.
85
+ */
86
+ theme?: ThemeConfig | true;
87
+ /**
88
+ * Customize the rango state cookie that `invalidateClientCache()` rotates.
89
+ * The name is ALWAYS seeded (default `rango-state_router_0`) so a call to
90
+ * `invalidateClientCache()` rotates and emits the `Set-Cookie` exactly as in
91
+ * production, rather than silently no-opping. Override `prefix`/`routerId` to
92
+ * match your `createRouter({ stateCookiePrefix, id })` so the test asserts the
93
+ * same name, or `version` (the build identifier prefixed to the rotated
94
+ * `{version}:{timestamp}` value, default `"0"`). Assert
95
+ * `response.headers.getSetCookie()` against the resolved `stateCookieName`
96
+ * (returned by `runInRequestContext`).
97
+ */
98
+ stateCookie?: StateCookieSeed;
99
+ }
100
+
101
+ /**
102
+ * The seeded RequestContext with its `reverse` RELAXED to accept any route NAME
103
+ * from the `routeMap` you passed, rather than the global `Rango.GeneratedRouteMap`
104
+ * union — so reversing a test-only route name is not a type error (it works at
105
+ * runtime; the names come from your `routeMap`). Mirrors runLoader's
106
+ * `TestLoaderContext.reverse`. Everything else is the real `RequestContext`.
107
+ */
108
+ export type TestRequestContextObject<TEnv> = Omit<
109
+ RequestContext<TEnv>,
110
+ "reverse"
111
+ > & {
112
+ reverse: (
113
+ name: string,
114
+ params?: Record<string, string>,
115
+ search?: Record<string, unknown>,
116
+ ) => string;
117
+ };
118
+
119
+ export interface TestRequestContext<TEnv> {
120
+ ctx: TestRequestContextObject<TEnv>;
121
+ request: Request;
122
+ url: URL;
123
+ variables: Record<string, unknown>;
124
+ /**
125
+ * The resolved rango state cookie name seeded into the context (default
126
+ * `rango-state_router_0`, or composed from `opts.stateCookie`). The name a
127
+ * call to `invalidateClientCache()` rotates.
128
+ */
129
+ stateCookieName: string;
130
+ }
131
+
132
+ /**
133
+ * Create a real RequestContext for unit-testing loaders/middleware.
134
+ *
135
+ * The returned `ctx` must be ENTERED before use — wrap your call in
136
+ * `runWithRequestContext(ctx, fn)` (re-exported from `@rangojs/router/testing`)
137
+ * so that cookie/header mutations and `getRequestContext()` resolve. For the
138
+ * common case prefer {@link runInRequestContext}, which builds AND enters the
139
+ * context in a single call.
140
+ */
141
+ export function createTestRequestContext<TEnv>(
142
+ opts: CreateTestContextOptions<TEnv> = {},
143
+ ): TestRequestContext<TEnv> {
144
+ const request = toRequest(opts.request, opts.requestInit);
145
+ const url = new URL(request.url);
146
+ const variables = seedVariables(opts.variables ?? {}, opts.vars);
147
+ const stateCookieName = resolveSeededStateCookieName(opts.stateCookie);
148
+ const ctx = createRequestContext<TEnv>({
149
+ env: (opts.env ?? {}) as TEnv,
150
+ request,
151
+ url,
152
+ variables,
153
+ themeConfig:
154
+ opts.theme === undefined ? undefined : resolveThemeConfig(opts.theme),
155
+ cacheStore: opts.cacheStore,
156
+ cacheProfiles: opts.cacheProfiles,
157
+ stateCookieName,
158
+ version: opts.stateCookie?.version,
159
+ });
160
+ if (opts.basename !== undefined)
161
+ ctx._basename = normalizeBasename(opts.basename);
162
+ if (opts.params) ctx.params = opts.params;
163
+ if (opts.routeMap) {
164
+ ctx._routeName = opts.routeName;
165
+ ctx.reverse = createReverseFunction(
166
+ opts.routeMap,
167
+ opts.routeName,
168
+ opts.params ?? {},
169
+ ) as RequestContext<TEnv>["reverse"];
170
+ }
171
+ return {
172
+ ctx: ctx as unknown as TestRequestContextObject<TEnv>,
173
+ request,
174
+ url,
175
+ variables,
176
+ stateCookieName,
177
+ };
178
+ }
179
+
180
+ /**
181
+ * What a run accumulated on the request context, surfaced as PUBLIC values so a
182
+ * test never has to cast through the `@internal` `ctx.res` / `ctx.cookies()` to
183
+ * assert what an action produced.
184
+ */
185
+ export interface RunInRequestContextResult<T> {
186
+ /**
187
+ * The value `fn` returned (awaited), or `undefined` if `fn` threw — in which
188
+ * case the thrown value is on {@link thrown}. The snapshot below is captured
189
+ * either way.
190
+ */
191
+ result: T | undefined;
192
+ /**
193
+ * The value `fn` threw, or `undefined` if it returned normally. Commonly a
194
+ * `Response` from `throw redirect(...)` / `throw notFound()` — the dominant
195
+ * cookie+flash case is an action that sets them then throws a redirect — so
196
+ * this (and the snapshot below) is observable WITHOUT wrapping the action in
197
+ * your own try/catch. NOTE: the value is captured, NOT re-thrown; assert on it
198
+ * for a throwing action.
199
+ */
200
+ thrown: unknown;
201
+ /**
202
+ * A Response carrying the status, headers, and Set-Cookie the run set (via
203
+ * `cookies().set()`, `ctx.header()`, etc.). Assert Set-Cookie with
204
+ * `response.headers.getSetCookie()`. When `fn` threw a `Response` (a redirect),
205
+ * THIS is that Response with the accumulated Set-Cookie/headers merged in
206
+ * (mirroring how the framework merges them in production), so a redirect's
207
+ * Location AND the cookies it set are both observable here.
208
+ */
209
+ response: Response;
210
+ /**
211
+ * The effective cookie view after the run: request cookies merged with
212
+ * anything the run set or deleted (last-write-wins), as `{ name: value }`.
213
+ */
214
+ cookies: Record<string, string>;
215
+ /**
216
+ * The response headers the run set (via `ctx.header(...)`, plus a thrown
217
+ * redirect's `Location`), as a plain `{ name: value }` object — the same view
218
+ * as `response.headers`, but assertable like `cookies`/`locationState`.
219
+ * EXCLUDES `set-cookie` (use `cookies`, or `response.headers.getSetCookie()`).
220
+ * Header names are lowercased (HTTP headers are case-insensitive).
221
+ */
222
+ headers: Record<string, string>;
223
+ /**
224
+ * Location state the run set via `ctx.setLocationState()` / `redirect({ state })`,
225
+ * resolved to the flat `{ key: value }` shape the client reads off
226
+ * `history.state` (empty object when none) — so a post-action flash ("Saved!")
227
+ * is assertable at the unit layer.
228
+ */
229
+ locationState: Record<string, unknown>;
230
+ /**
231
+ * The resolved rango state cookie name seeded into the run (default
232
+ * `rango-state_router_0`, or composed from `opts.stateCookie`). Assert an
233
+ * action's `invalidateClientCache()` rotation against it without recomputing:
234
+ * `response.headers.getSetCookie().some((c) => c.startsWith(stateCookieName + "="))`.
235
+ */
236
+ stateCookieName: string;
237
+ }
238
+
239
+ export function snapshotRunEffects<TEnv>(ctx: RequestContext<TEnv>): {
240
+ cookies: Record<string, string>;
241
+ locationState: Record<string, unknown>;
242
+ } {
243
+ return {
244
+ cookies: { ...ctx.cookies() },
245
+ locationState: resolveLocationStateEntries(ctx._locationState ?? []),
246
+ };
247
+ }
248
+
249
+ export function headersToObject(headers: Headers): Record<string, string> {
250
+ const out: Record<string, string> = {};
251
+ headers.forEach((value, name) => {
252
+ if (name.toLowerCase() === "set-cookie") return;
253
+ out[name] = value;
254
+ });
255
+ return out;
256
+ }
257
+
258
+ export function buildRunResponse<TEnv>(
259
+ ctx: RequestContext<TEnv>,
260
+ thrown: unknown,
261
+ ): Response {
262
+ const stub = ctx.res;
263
+ let response: Response;
264
+ if (thrown instanceof Response) {
265
+ const headers = new Headers(thrown.headers);
266
+ for (const cookie of stub.headers.getSetCookie()) {
267
+ headers.append("set-cookie", cookie);
268
+ }
269
+ stub.headers.forEach((value, name) => {
270
+ if (name.toLowerCase() === "set-cookie") return;
271
+ if (!headers.has(name)) headers.set(name, value);
272
+ });
273
+ response = new Response(null, { status: thrown.status, headers });
274
+ } else {
275
+ response = new Response(null, {
276
+ status: stub.status,
277
+ headers: stub.headers,
278
+ });
279
+ }
280
+ // Mirror production response finalization: every response-finalization path
281
+ // drains ctx.onResponse() callbacks (createResponseWithMergedHeaders /
282
+ // finalizeResponse). buildRunResponse runs AFTER runWithRequestContext has
283
+ // exited, so _getRequestContext() (and finalizeResponse) would no-op — drain
284
+ // ctx._onResponseCallbacks explicitly. Reuses the SAME production drain
285
+ // (swap-before-iterate + external-redirect brand preservation) so a callback's
286
+ // header mutations / returned replacement Response are reflected on the result
287
+ // the harness surfaces. rsc/helpers is plugin-rsc-free on its eager graph
288
+ // (dispatch.ts in this same testing barrel already statically imports from it).
289
+ // drainOnResponseCallbacks is typed against the default-env RequestContext (it
290
+ // touches only the env-agnostic _onResponseCallbacks); the harness ctx is
291
+ // RequestContext<TEnv> — assignable in the router's own tsc but not when a
292
+ // consumer pins a concrete Env, so cast to the param type.
293
+ return drainOnResponseCallbacks(
294
+ ctx as Parameters<typeof drainOnResponseCallbacks>[0],
295
+ response,
296
+ );
297
+ }
298
+
299
+ export function buildRunSnapshot<TEnv>(
300
+ ctx: RequestContext<TEnv>,
301
+ thrown: unknown,
302
+ stateCookieName: string,
303
+ ): {
304
+ thrown: unknown;
305
+ response: Response;
306
+ cookies: Record<string, string>;
307
+ headers: Record<string, string>;
308
+ locationState: Record<string, unknown>;
309
+ stateCookieName: string;
310
+ } {
311
+ const { cookies, locationState } = snapshotRunEffects(ctx);
312
+ const response = buildRunResponse(ctx, thrown);
313
+ const headers = headersToObject(response.headers);
314
+ return { thrown, response, cookies, headers, locationState, stateCookieName };
315
+ }
316
+
317
+ /**
318
+ * Build a seeded RequestContext (via {@link createTestRequestContext}) and run
319
+ * `fn` inside it, so code under test that calls `getRequestContext()`,
320
+ * `cookies()`, or reads/mutates request headers resolves exactly as in
321
+ * production.
322
+ *
323
+ * This is the entry point for the advanced cases the unit wrappers
324
+ * (`runLoader` / `runMiddleware`) do not model — most notably a server ACTION
325
+ * that authenticates off the request cookie or sets a session cookie / flash:
326
+ * an action has no loader context, so `runLoader` is the wrong shape, yet it
327
+ * still needs a real request context to read the cookie and resolve
328
+ * `getRequestContext()`.
329
+ *
330
+ * Returns `{ result, thrown, response, cookies, headers, locationState }` so the
331
+ * action's OUTPUT (Set-Cookie, response headers, flash) is assertable without
332
+ * casting through the `@internal` `ctx.res` / `ctx.cookies()`. `fn` may be async — the context stays
333
+ * active across its awaits (AsyncLocalStorage), and the snapshot is captured
334
+ * whether `fn` returns OR throws. The throw path matters: the most common
335
+ * cookie+flash case is an auth action that sets a cookie + flash then
336
+ * `throw redirect(...)` on success — the thrown redirect is on `thrown` (NOT
337
+ * re-thrown) and its Location plus the cookies are on `response`/`cookies`.
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * const { result, cookies, response, thrown } = await runInRequestContext(
342
+ * () => loginAction(input), // sets a session cookie, then `throw redirect("/app")`
343
+ * {
344
+ * env,
345
+ * request: new Request("https://app.test/", {
346
+ * headers: { Cookie: "sid=abc" },
347
+ * }),
348
+ * },
349
+ * );
350
+ * expect(cookies.session).toBe("new-token");
351
+ * expect(headers.location).toBe("/app"); // response headers as a plain object
352
+ * expect((thrown as Response).headers.get("Location")).toBe("/app");
353
+ * expect(response.headers.getSetCookie()).toContainEqual(
354
+ * expect.stringContaining("session="),
355
+ * );
356
+ * ```
357
+ */
358
+ export async function runInRequestContext<T, TEnv = unknown>(
359
+ fn: (ctx: RequestContext<TEnv>) => T | Promise<T>,
360
+ opts: CreateTestContextOptions<TEnv> = {},
361
+ ): Promise<RunInRequestContextResult<T>> {
362
+ const { ctx, stateCookieName } = createTestRequestContext<TEnv>(opts);
363
+ let result: T | undefined;
364
+ let thrown: unknown;
365
+ try {
366
+ result = (await runWithRequestContext(ctx, () => fn(ctx))) as T;
367
+ } catch (error) {
368
+ thrown = error;
369
+ }
370
+ return { result, ...buildRunSnapshot(ctx, thrown, stateCookieName) };
371
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Side-effect module: define the webpack-style globals the vendored
3
+ * react-server-dom CLIENT deserializer reads at module-eval time.
4
+ *
5
+ * In a real app the plugin-rsc Vite plugin rewrites `__webpack_require__` ->
6
+ * `__vite_rsc_require__` and `__webpack_require__.u` -> `({}).u`
7
+ * (@vitejs/plugin-rsc `core/plugin.js`). That transform does NOT run in a bare
8
+ * Vitest process, so the vendored client's free `__webpack_require__` /
9
+ * `__webpack_chunk_load__` references would be undefined. We provide minimal
10
+ * shims: `__webpack_require__` routes to the loader installed via
11
+ * `setRequireModule`, and `__webpack_chunk_load__` is a no-op (renderServerTree
12
+ * serializes with empty `chunks`, so no chunk fetch ever happens).
13
+ *
14
+ * MUST be imported (for side effect) BEFORE `@vitejs/plugin-rsc/react/browser`,
15
+ * which is why flight-tree.ts lists it first.
16
+ */
17
+ const g = globalThis as unknown as {
18
+ __webpack_require__?: ((id: string) => unknown) & { u?: unknown };
19
+ __webpack_chunk_load__?: (chunkId: string) => Promise<unknown>;
20
+ __vite_rsc_client_require__?: (id: string) => unknown;
21
+ };
22
+
23
+ if (!g.__webpack_require__) {
24
+ g.__webpack_require__ = (id: string) => g.__vite_rsc_client_require__!(id);
25
+ }
26
+ if (!g.__webpack_chunk_load__) {
27
+ g.__webpack_chunk_load__ = async () => {};
28
+ }
29
+
30
+ export {};
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Variable seeding shared by the node/DOM testing tier (internal/context.ts)
3
+ * AND the react-server Flight tier (flight.ts). Depends only on the
4
+ * dependency-free `context-var` module and the env-agnostic state-cookie-name
5
+ * composition (no window/document), so it is safe to import under the
6
+ * `react-server` condition (unlike internal/context.ts, which pulls
7
+ * client/browser modules).
8
+ */
9
+ import { contextSet, type ContextVar } from "../../context-var.js";
10
+ import { resolveStateCookieName } from "../../router/state-cookie-name.js";
11
+
12
+ export interface StateCookieSeed {
13
+ /**
14
+ * Cookie-name prefix, sanitized then composed with `routerId` exactly like
15
+ * `createRouter({ stateCookiePrefix })`. Defaults to `"rango-state"`.
16
+ */
17
+ prefix?: string;
18
+ /**
19
+ * Router id; the resolved name is `{sanitizedPrefix}_{sanitizedRouterId}`.
20
+ * Defaults to `"router_0"` (the name a single default router resolves to), so
21
+ * the default name is `rango-state_router_0`.
22
+ */
23
+ routerId?: string;
24
+ /**
25
+ * Build version used as the rotated value's prefix (`{version}:{timestamp}`).
26
+ * Defaults to `"0"` (resolved inside createRequestContext).
27
+ */
28
+ version?: string;
29
+ }
30
+
31
+ export function resolveSeededStateCookieName(seed?: StateCookieSeed): string {
32
+ return resolveStateCookieName(seed?.prefix, seed?.routerId ?? "router_0");
33
+ }
34
+
35
+ export type VarsInit =
36
+ | Record<string, unknown>
37
+ | ReadonlyArray<readonly [ContextVar<unknown> | string, unknown]>;
38
+
39
+ export function seedVariables(
40
+ variables: Record<string, unknown>,
41
+ vars?: VarsInit,
42
+ ): Record<string, unknown> {
43
+ if (!vars) return variables;
44
+ const entries: Iterable<readonly [ContextVar<unknown> | string, unknown]> =
45
+ Symbol.iterator in (vars as object)
46
+ ? (vars as ReadonlyArray<
47
+ readonly [ContextVar<unknown> | string, unknown]
48
+ >)
49
+ : Object.entries(vars as Record<string, unknown>);
50
+ for (const [key, value] of entries) {
51
+ contextSet(variables, key as ContextVar<unknown>, value);
52
+ }
53
+ return variables;
54
+ }