@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

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 (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  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 +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -17,7 +17,12 @@ import {
17
17
  export interface FixtureOptions {
18
18
  /** Absolute or cwd-relative path to the consumer app under test. */
19
19
  root: string;
20
- mode?: "dev" | "build";
20
+ /**
21
+ * Server mode. Required: omitting it would spawn no server in beforeAll and
22
+ * surface only as a bare "Invalid URL" from `url()` at first use, so we fail
23
+ * eagerly at useFixture time instead.
24
+ */
25
+ mode: "dev" | "build";
21
26
  /** Override the server command (default: `pnpm dev` for dev, `pnpm preview` for build). */
22
27
  command?: string;
23
28
  /** Override the build command (default: `pnpm build`). */
@@ -32,7 +37,7 @@ export interface FixtureOptions {
32
37
  }
33
38
 
34
39
  export interface Fixture {
35
- mode: "dev" | "build" | undefined;
40
+ mode: "dev" | "build";
36
41
  root: string;
37
42
  /** Resolve a path against the running server's base URL. */
38
43
  url: (url?: string) => string;
@@ -49,6 +54,11 @@ export function createUseFixture(
49
54
  test: TestType<any, any>,
50
55
  ): (options: FixtureOptions) => Fixture {
51
56
  return function useFixture(options: FixtureOptions): Fixture {
57
+ if (options.mode !== "dev" && options.mode !== "build") {
58
+ throw new Error(
59
+ `useFixture: mode is required: 'dev' | 'build' (got ${JSON.stringify(options.mode)}).`,
60
+ );
61
+ }
52
62
  let cleanup: (() => Promise<void>) | undefined;
53
63
  let baseURL!: string;
54
64
 
@@ -79,6 +89,14 @@ export function createUseFixture(
79
89
  ...options.cliOptions,
80
90
  env: cliEnv,
81
91
  });
92
+ // Assign cleanup immediately after spawn, before any await that can
93
+ // throw (findPort/waitForReady). Otherwise a beforeAll failure leaves
94
+ // afterAll's cleanup a no-op and orphans the dev server (plus workerd
95
+ // children) for the rest of the run.
96
+ cleanup = async () => {
97
+ proc!.kill();
98
+ await proc!.done;
99
+ };
82
100
  const port = await proc.findPort();
83
101
  baseURL = `http://localhost:${port}`;
84
102
  const readyUrl = options.readyPath
@@ -96,10 +114,6 @@ export function createUseFixture(
96
114
  if (options.isolatedServer) {
97
115
  await warmupDevServer(readyUrl);
98
116
  }
99
- cleanup = async () => {
100
- proc!.kill();
101
- await proc!.done;
102
- };
103
117
  }
104
118
 
105
119
  if (options.mode === "build") {
@@ -112,7 +126,25 @@ export function createUseFixture(
112
126
  ...options.cliOptions,
113
127
  env: cliEnv,
114
128
  });
115
- await buildProc.done;
129
+ // The build is a finite process, but assign cleanup before the await
130
+ // so a hung build is still killed if beforeAll is torn down.
131
+ cleanup = async () => {
132
+ buildProc.kill();
133
+ await buildProc.done;
134
+ };
135
+ // Fail loudly on a nonzero build exit. Without this a failed build is
136
+ // swallowed and preview serves the previous stale dist/ (green
137
+ // production tests against old code), or times out with "Server not
138
+ // ready" that never mentions the build failure. The captured build
139
+ // output (stdout/stderr above) precedes this throw.
140
+ const code = await buildProc.exitCode;
141
+ if (code !== 0) {
142
+ throw new Error(
143
+ `Build failed with exit code ${code} for "${options.root}" ` +
144
+ `(command: ${options.buildCommand ?? "pnpm build"}). ` +
145
+ `See the captured build output above for the cause.`,
146
+ );
147
+ }
116
148
  }
117
149
  proc = runCli({
118
150
  command: options.command ?? `pnpm preview`,
@@ -121,6 +153,12 @@ export function createUseFixture(
121
153
  ...options.cliOptions,
122
154
  env: cliEnv,
123
155
  });
156
+ // Switch cleanup to the preview process immediately after spawn, before
157
+ // findPort/waitForReady can throw and orphan it.
158
+ cleanup = async () => {
159
+ proc!.kill();
160
+ await proc!.done;
161
+ };
124
162
  const port = await proc.findPort();
125
163
  baseURL = `http://localhost:${port}`;
126
164
  const buildReadyUrl = options.readyPath
@@ -130,10 +168,6 @@ export function createUseFixture(
130
168
  stdout: proc!.stdout(),
131
169
  stderr: proc!.stderr(),
132
170
  }));
133
- cleanup = async () => {
134
- proc!.kill();
135
- await proc!.done;
136
- };
137
171
  }
138
172
  });
139
173
 
@@ -1,8 +1,3 @@
1
- // Public entry for the consumer e2e harness. `createRangoE2E({ test, expect })`
2
- // wires the server fixture, page helpers, parity helpers, and matchers around
3
- // the consumer's Playwright `test`/`expect` objects so this module never
4
- // imports `@playwright/test` at runtime (type-only imports are erased).
5
-
6
1
  import type { Expect, TestType } from "@playwright/test";
7
2
  import {
8
3
  createUseFixture,
@@ -35,14 +30,9 @@ import {
35
30
  } from "./parity.js";
36
31
  import { createRangoMatchers, type RangoMatchers } from "./matchers.js";
37
32
 
38
- // Cache-status helpers are pure (cache-status.ts imports only TYPES), so they
39
- // are safe to surface from this Playwright-runnable entry. Importing them from
40
- // the `@rangojs/router/testing` barrel does NOT work in a plain Playwright
41
- // runner — the barrel transitively pulls the build-only `@rangojs/router:version`
42
- // virtual via the route-manifest path. Asserting cache status on a real
43
- // response is an e2e activity, so this is their Playwright-safe home.
44
33
  export {
45
34
  assertCacheStatus,
35
+ assertCacheDecision,
46
36
  parseCacheHeader,
47
37
  createCacheSink,
48
38
  filterCacheDecisions,
@@ -50,9 +40,6 @@ export {
50
40
  type ExpectedCacheStatus,
51
41
  type CacheStatusTarget,
52
42
  } from "../cache-status.js";
53
-
54
- // Re-export standalone helpers and all public types so the barrel can re-export
55
- // them from a single module.
56
43
  export {
57
44
  testId,
58
45
  waitForHydration,
@@ -87,7 +74,6 @@ export interface RangoE2E extends PageHelpers, Parity {
87
74
  useFixture: (options: FixtureOptions) => Fixture;
88
75
  testNoJs: TestType<any, any>;
89
76
  rangoMatchers: RangoMatchers;
90
- // Standalone helpers, re-surfaced for convenience.
91
77
  testId: typeof testId;
92
78
  waitForHydration: typeof waitForHydration;
93
79
  waitForNavigation: typeof waitForNavigation;
@@ -102,12 +88,6 @@ export interface RangoE2E extends PageHelpers, Parity {
102
88
  measureTime: typeof measureTime;
103
89
  }
104
90
 
105
- /**
106
- * Wire the full e2e harness around a consumer's Playwright `test`/`expect`.
107
- *
108
- * @param defaultRoot - fallback app root for `parityDescribe` when a call omits
109
- * `options.root`.
110
- */
111
91
  export function createRangoE2E({
112
92
  test,
113
93
  expect,
@@ -132,7 +112,6 @@ export function createRangoE2E({
132
112
  rangoMatchers,
133
113
  ...parity,
134
114
  ...pageHelpers,
135
- // Standalone helpers.
136
115
  testId,
137
116
  waitForHydration,
138
117
  waitForNavigation,
@@ -1,6 +1,3 @@
1
- // Custom Playwright matchers for Rango assertions. Returned as an object
2
- // suitable for `expect.extend(...)`. v1 ships only `toHaveRangoPathname`.
3
-
4
1
  import type { Expect, Page } from "@playwright/test";
5
2
 
6
3
  interface MatcherResult {
@@ -12,17 +9,6 @@ export interface RangoMatchers {
12
9
  toHaveRangoPathname: (page: Page, expected: string) => MatcherResult;
13
10
  }
14
11
 
15
- /**
16
- * Build the matcher object for `expect.extend(createRangoMatchers(expect))`.
17
- *
18
- * `toHaveRangoPathname(page, expected)` asserts that the pathname of the page's
19
- * current URL equals `expected`.
20
- *
21
- * TODO: `toHaveSegments` / `toHaveParams` are intentionally not implemented.
22
- * They require a client-emitted signal (the active segment chain / resolved
23
- * params exposed on the page) that does not exist yet; implementing them by
24
- * scraping the DOM would be a guess. Add them once the router emits that signal.
25
- */
26
12
  export function createRangoMatchers(_expect: Expect): RangoMatchers {
27
13
  return {
28
14
  toHaveRangoPathname(page: Page, expected: string): MatcherResult {
@@ -39,8 +25,6 @@ export function createRangoMatchers(_expect: Expect): RangoMatchers {
39
25
  };
40
26
  }
41
27
 
42
- // Type augmentation so consumers can call `await expect(page).toHaveRangoPathname("/x")`
43
- // after `expect.extend(rangoMatchers)`, without re-declaring the matcher.
44
28
  declare global {
45
29
  // eslint-disable-next-line @typescript-eslint/no-namespace
46
30
  namespace PlaywrightTest {
@@ -6,6 +6,7 @@
6
6
 
7
7
  import type { Expect, Page, TestType } from "@playwright/test";
8
8
  import type { Fixture, FixtureOptions } from "./fixture.js";
9
+ import { DEFAULT_STATE_COOKIE_PREFIX } from "../../browser/cookie-name.js";
9
10
 
10
11
  export interface ParityDescribeOptions extends Partial<
11
12
  Omit<FixtureOptions, "mode">
@@ -30,6 +31,15 @@ export interface ExpectParityOptions {
30
31
  * transport being snapshotted.
31
32
  */
32
33
  waitFor?: (page: Page) => Promise<void>;
34
+ /**
35
+ * Cookie NAMES to exclude from the JS-vs-no-JS jar comparison, matched exactly
36
+ * (string) or by pattern (RegExp). The rango state cookie (default prefix
37
+ * `rango-state`) is ALWAYS excluded — it is written/rotated only by the client
38
+ * runtime, so it is JS-only by design and never appears in the no-JS jar. Use
39
+ * this for a custom `stateCookiePrefix` (e.g. `[/^myapp-state_/]`) or any other
40
+ * volatile/JS-only cookie (analytics, CSRF) that should not break parity.
41
+ */
42
+ ignoreCookies?: ReadonlyArray<string | RegExp>;
33
43
  }
34
44
 
35
45
  export interface Parity {
@@ -76,6 +86,31 @@ export interface Parity {
76
86
  * are therefore NOT compared — a PE/JS divergence in an HttpOnly cookie will
77
87
  * not be caught here. Assert on those via `read_network_requests` / response
78
88
  * Set-Cookie headers in a dedicated test, not expectParity.
89
+ *
90
+ * Submit-intent requirements (the `submit` path runs the intent TWICE against
91
+ * the same server, and compares whole cookie jars from two contexts):
92
+ * - DOUBLE EXECUTION: the JS path submits, then the no-JS pass reloads the
93
+ * same `originUrl` in a fresh context and submits AGAIN. Both hit the one
94
+ * running server, so a non-idempotent action runs twice. The no-JS snapshot
95
+ * then observes BOTH mutations unless the mutated state is per-session /
96
+ * per-context — e.g. an add-to-cart count only reaches the same value on
97
+ * both transports if the cart is session-scoped (the JS context and the
98
+ * fresh no-JS context are distinct sessions). A globally-shared counter
99
+ * would read N after the JS submit and N+1 after the no-JS submit and
100
+ * false-mismatch. Make the action's observable state session/context-scoped,
101
+ * or assert the submit path some other way.
102
+ * - COOKIE JAR vs DELTA: the cookie check compares the WHOLE `document.cookie`
103
+ * of two different contexts. The JS context carries every cookie accumulated
104
+ * before the intent (consent, analytics, prior navigations); the fresh no-JS
105
+ * context starts empty. Unrelated pre-existing cookies therefore false-
106
+ * mismatch — expectParity compares jars, not the per-submit cookie delta.
107
+ * Keep the JS context's pre-intent cookie state minimal, or assert the
108
+ * specific Set-Cookie elsewhere.
109
+ * - RANGO STATE COOKIE: the rango state cookie (default prefix `rango-state`)
110
+ * is written/rotated only by the client runtime, so it is JS-only by design
111
+ * and would always diverge — it is excluded from the comparison
112
+ * automatically. A custom `stateCookiePrefix` (or any other volatile/JS-only
113
+ * cookie) is excluded via `opts.ignoreCookies`.
79
114
  */
80
115
  expectParity: (
81
116
  page: Page,
@@ -178,8 +213,16 @@ async function settleSubmit(
178
213
  `\`observe\`, or pass \`waitFor\` to express the precise post-submit wait.`,
179
214
  );
180
215
  }
181
- // Landed but never stabilized within the ceiling: fall through and snapshot
182
- // the last-read state; the parity equality assertion surfaces any mismatch.
216
+ // Landed but never stabilized within the ceiling: warn (so a slow/flaky action
217
+ // is visible rather than silently snapshotting a mid-flight DOM), then fall
218
+ // through and snapshot the last-read state — the parity equality assertion
219
+ // still surfaces any JS-vs-no-JS mismatch. A throw here would risk failing a
220
+ // slow-but-correct submit, so this stays a warning.
221
+ console.warn(
222
+ `expectParity: the observed testids [${observe.join(", ")}] did not stabilize ` +
223
+ `within 5s; snapshotting the last-read state. If this is flaky, pass ` +
224
+ `\`waitFor\` to express the precise post-submit wait.`,
225
+ );
183
226
  }
184
227
 
185
228
  async function snapshot(
@@ -188,7 +231,13 @@ async function snapshot(
188
231
  ): Promise<ParitySnapshot> {
189
232
  const testIds: Record<string, string | null> = {};
190
233
  for (const id of observe) {
191
- testIds[id] = await page.locator(`[data-testid="${id}"]`).textContent();
234
+ // Match readObserved: .first() (a testid may repeat) + .catch (a missing
235
+ // testid yields null, not an unhandled rejection).
236
+ testIds[id] = await page
237
+ .locator(`[data-testid="${id}"]`)
238
+ .first()
239
+ .textContent()
240
+ .catch(() => null);
192
241
  }
193
242
  return {
194
243
  testIds,
@@ -197,6 +246,35 @@ async function snapshot(
197
246
  };
198
247
  }
199
248
 
249
+ // Reduce a `document.cookie` string to the cookies that should match across the
250
+ // JS and no-JS transports. The rango state cookie (default prefix `rango-state`)
251
+ // is always dropped — it is written/rotated only by the client runtime, so it
252
+ // exists in the JS jar but never the no-JS one. `ignore` drops additional names
253
+ // (a custom stateCookiePrefix, or other JS-only/volatile cookies) by exact
254
+ // string or RegExp match. Returns a normalized, sorted `name=value; ...` string.
255
+ function parityCookies(
256
+ cookieString: string,
257
+ ignore: ReadonlyArray<string | RegExp>,
258
+ ): string {
259
+ const isIgnored = (name: string): boolean => {
260
+ if (name.startsWith(DEFAULT_STATE_COOKIE_PREFIX)) return true;
261
+ return ignore.some((m) =>
262
+ typeof m === "string" ? m === name : m.test(name),
263
+ );
264
+ };
265
+ return cookieString
266
+ .split(";")
267
+ .map((c) => c.trim())
268
+ .filter((c) => c.length > 0)
269
+ .filter((c) => {
270
+ const eq = c.indexOf("=");
271
+ const name = eq === -1 ? c : c.slice(0, eq);
272
+ return !isIgnored(name);
273
+ })
274
+ .sort()
275
+ .join("; ");
276
+ }
277
+
200
278
  export function createParity({
201
279
  test: _test,
202
280
  expect,
@@ -296,7 +374,10 @@ export function createParity({
296
374
  return url.pathname + url.search + url.hash;
297
375
  };
298
376
  expect(locationOf(noJsSnapshot.url)).toEqual(locationOf(jsSnapshot.url));
299
- expect(noJsSnapshot.cookies).toEqual(jsSnapshot.cookies);
377
+ const ignore = opts.ignoreCookies ?? [];
378
+ expect(parityCookies(noJsSnapshot.cookies, ignore)).toEqual(
379
+ parityCookies(jsSnapshot.cookies, ignore),
380
+ );
300
381
  } finally {
301
382
  await noJsContext.close();
302
383
  }
@@ -12,6 +12,12 @@ export type { SpawnOptions };
12
12
  export interface RunCliHandle {
13
13
  proc: ReturnType<typeof x>["process"];
14
14
  done: Promise<void>;
15
+ /**
16
+ * Resolves with the process's exit code (null if killed by signal) when it
17
+ * exits. Unlike `done`, callers can branch on a nonzero code. Used to fail
18
+ * the build step loudly; the long-running serve processes never inspect it.
19
+ */
20
+ exitCode: Promise<number | null>;
15
21
  findPort: (timeoutMs?: number) => Promise<number>;
16
22
  kill: () => void;
17
23
  stdout: () => string;
@@ -49,11 +55,16 @@ export function runCli(
49
55
  console.log(styleText("magenta", label), data.toString());
50
56
  }
51
57
  });
58
+ let resolveExitCode!: (code: number | null) => void;
59
+ const exitCode = new Promise<number | null>((resolve) => {
60
+ resolveExitCode = resolve;
61
+ });
52
62
  const done = new Promise<void>((resolve) => {
53
63
  child.on("exit", (code) => {
54
64
  if (code !== 0 && code !== 143 && process.platform !== "win32") {
55
65
  console.log(styleText("magenta", `${label}`), `exit code ${code}`);
56
66
  }
67
+ resolveExitCode(code);
57
68
  resolve();
58
69
  });
59
70
  });
@@ -106,6 +117,7 @@ export function runCli(
106
117
  return {
107
118
  proc: child,
108
119
  done,
120
+ exitCode,
109
121
  findPort,
110
122
  kill,
111
123
  stdout: () => stdout,
@@ -29,21 +29,19 @@
29
29
  */
30
30
 
31
31
  import { expect } from "vitest";
32
- import { normalizeFlight } from "./flight.js";
32
+ // Import from the serializer-free module, NOT ./flight.js: that module
33
+ // top-level imports the vendored react-server-dom serializer, which throws when
34
+ // loaded outside the `react-server` condition. flight-matchers must be
35
+ // importable under the plain node condition (a consumer's shared setupFiles
36
+ // does `expect.extend(flightMatchers)`), so it cannot transitively pull in the
37
+ // serializer.
38
+ import { normalizeFlight } from "./flight-normalize.js";
33
39
 
34
40
  interface MatcherResult {
35
41
  pass: boolean;
36
42
  message: () => string;
37
43
  }
38
44
 
39
- /**
40
- * Matcher object for `expect.extend(flightMatchers)`.
41
- *
42
- * - `toMatchFlight(received, expected)` — `received` is a rendered Flight
43
- * string; passes if its normalized form contains `expected`.
44
- * - `toMatchFlightSnapshot(received)` — delegates to vitest's snapshot on the
45
- * normalized Flight string.
46
- */
47
45
  export const flightMatchers: {
48
46
  toMatchFlight(received: string, expected: string): MatcherResult;
49
47
  toMatchFlightSnapshot(received: string): MatcherResult;
@@ -72,12 +70,7 @@ export const flightMatchers: {
72
70
  },
73
71
 
74
72
  toMatchFlightSnapshot(received: string): MatcherResult {
75
- // Delegate to vitest's snapshot engine on the normalized string. The
76
- // snapshot is keyed by the current test file/title (vitest tracks this via
77
- // the active test context), not by this call site, so delegating through a
78
- // freshly imported `expect` is reliable.
79
73
  expect(normalizeFlight(received)).toMatchSnapshot();
80
- // toMatchSnapshot throws on mismatch; reaching here means it passed.
81
74
  return {
82
75
  pass: true,
83
76
  message: () => "Flight snapshot matched.",
@@ -0,0 +1,11 @@
1
+ // Volatile leading reference row: `:N<timestamp>` (dev only).
2
+ const REFERENCE_ROW_RE = /^:N[\d.]+\n/;
3
+ // Absolute file:// paths in dev stack rows. Pattern matches frames
4
+ // `["Component","file:///path",<line>,<col>...]` and scrubs the path only.
5
+ const FILE_URL_RE = /file:\/\/[^"\\]+(?=",\d+,\d+)/g;
6
+
7
+ export function normalizeFlight(flight: string): string {
8
+ return flight
9
+ .replace(REFERENCE_ROW_RE, "")
10
+ .replace(FILE_URL_RE, "file://<path>");
11
+ }
@@ -18,4 +18,40 @@ declare module "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge" {
18
18
  clientManifest: unknown,
19
19
  options?: { onError?: (error: unknown) => string | void },
20
20
  ): ReadableStream<Uint8Array>;
21
+
22
+ /**
23
+ * Tag a value as a client reference. Mutates `impl` in place (defining
24
+ * `$$typeof`/`$$id`/`$$async`) and returns it, so a server tree that imports
25
+ * the same module binding renders it as a client boundary (an `I` row) rather
26
+ * than inlining it. `$$id` becomes `${id}#${exportName}`.
27
+ */
28
+ export function registerClientReference<T>(
29
+ impl: T,
30
+ id: string,
31
+ exportName: string,
32
+ ): T;
33
+ }
34
+
35
+ /**
36
+ * Vendored react-server-dom CLIENT deserializer wrappers shipped inside
37
+ * @vitejs/plugin-rsc. Used by renderServerTree to turn a Flight wire string
38
+ * back into an inspectable React element tree. These run in the same
39
+ * `react-server`-condition worker as the serializer (deserialize-only never
40
+ * renders, so the client React/react-dom imports they pull are inert).
41
+ */
42
+ declare module "@vitejs/plugin-rsc/react/browser" {
43
+ export function createFromReadableStream<T = unknown>(
44
+ stream: ReadableStream<Uint8Array>,
45
+ options?: { temporaryReferences?: unknown },
46
+ ): Promise<T>;
47
+ }
48
+
49
+ declare module "@vitejs/plugin-rsc/core/browser" {
50
+ /**
51
+ * Install the module loader the client deserializer resolves client
52
+ * references through. Init-once per worker (first call wins).
53
+ */
54
+ export function setRequireModule(options: {
55
+ load: (id: string) => unknown;
56
+ }): void;
21
57
  }