@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. package/src/router/middleware-cookies.ts +0 -55
@@ -11,122 +11,40 @@ import {
11
11
  when,
12
12
  errorBoundary,
13
13
  notFoundBoundary,
14
- loaderFn,
15
- loadingFn,
16
- transitionFn,
17
- routeFn,
14
+ route,
15
+ loader,
16
+ loading,
17
+ transition,
18
18
  } from "./dsl-helpers.js";
19
19
  import RootLayout from "../server/root-layout";
20
20
  import { invariant } from "../errors";
21
21
 
22
- /*
23
- * Create revalidate helper
24
- */
25
- const createRevalidateHelper = <TEnv>(): RouteHelpers<
26
- any,
27
- TEnv
28
- >["revalidate"] => {
29
- return revalidate as RouteHelpers<any, TEnv>["revalidate"];
30
- };
31
-
32
- /**
33
- * Create errorBoundary helper
34
- */
35
- const createErrorBoundaryHelper = <TEnv>(): RouteHelpers<
36
- any,
37
- TEnv
38
- >["errorBoundary"] => {
39
- return errorBoundary as RouteHelpers<any, TEnv>["errorBoundary"];
40
- };
41
-
42
- /**
43
- * Create notFoundBoundary helper
44
- */
45
- const createNotFoundBoundaryHelper = <TEnv>(): RouteHelpers<
46
- any,
47
- TEnv
48
- >["notFoundBoundary"] => {
49
- return notFoundBoundary as RouteHelpers<any, TEnv>["notFoundBoundary"];
50
- };
51
-
52
22
  /**
53
- * Create middleware helper
23
+ * Assemble the RouteHelpers object. The helpers are the DSL functions
24
+ * themselves; the single cast erases the phantom generics (and the extra
25
+ * `route` key) that the per-router RouteHelpers<T, TEnv> type carries but the
26
+ * runtime functions do not.
54
27
  */
55
- const createMiddlewareHelper = <TEnv>(): RouteHelpers<
56
- any,
28
+ function buildRouteHelpers<T extends RouteDefinition, TEnv>(): RouteHelpers<
29
+ T,
57
30
  TEnv
58
- >["middleware"] => {
59
- return middleware as RouteHelpers<any, TEnv>["middleware"];
60
- };
61
-
62
- /**
63
- * Create parallel helper
64
- */
65
- const createParallelHelper = <TEnv>(): RouteHelpers<any, TEnv>["parallel"] => {
66
- return parallel as RouteHelpers<any, TEnv>["parallel"];
67
- };
68
-
69
- /**
70
- * Create intercept helper
71
- */
72
- const createInterceptHelper = <
73
- const T extends RouteDefinition,
74
- TEnv,
75
- >(): RouteHelpers<T, TEnv>["intercept"] => {
76
- return intercept as RouteHelpers<T, TEnv>["intercept"];
77
- };
78
-
79
- /**
80
- * Create loader helper
81
- */
82
- const createLoaderHelper = <TEnv>(): RouteHelpers<any, TEnv>["loader"] => {
83
- return loaderFn as RouteHelpers<any, TEnv>["loader"];
84
- };
85
-
86
- /**
87
- * Create loading helper
88
- */
89
- const createLoadingHelper = (): RouteHelpers<any, any>["loading"] => {
90
- return loadingFn;
91
- };
92
-
93
- /**
94
- * Create route helper
95
- */
96
- const createRouteHelper = <
97
- const T extends RouteDefinition,
98
- TEnv,
99
- >(): RouteHelpers<T, TEnv>["route"] => {
100
- return routeFn as unknown as RouteHelpers<T, TEnv>["route"];
101
- };
102
-
103
- /**
104
- * Create layout helper
105
- */
106
- const createLayoutHelper = <TEnv>(): RouteHelpers<any, TEnv>["layout"] => {
107
- return layout as RouteHelpers<any, TEnv>["layout"];
108
- };
109
-
110
- /**
111
- * Create when helper for intercept conditions
112
- */
113
- const createWhenHelper = (): RouteHelpers<any, any>["when"] => {
114
- return when;
115
- };
116
-
117
- /**
118
- * Create cache helper for cache configuration
119
- */
120
- const createCacheHelper = (): RouteHelpers<any, any>["cache"] => {
121
- return cache;
122
- };
123
-
124
- /**
125
- * Create transition helper
126
- */
127
- const createTransitionHelper = (): RouteHelpers<any, any>["transition"] => {
128
- return transitionFn as RouteHelpers<any, any>["transition"];
129
- };
31
+ > {
32
+ return {
33
+ route,
34
+ layout,
35
+ parallel,
36
+ intercept,
37
+ middleware,
38
+ revalidate,
39
+ loader,
40
+ loading,
41
+ errorBoundary,
42
+ notFoundBoundary,
43
+ when,
44
+ cache,
45
+ transition,
46
+ } as unknown as RouteHelpers<T, TEnv>;
47
+ }
130
48
 
131
49
  /**
132
50
  * Branded type for route handlers that carries the route type info.
@@ -152,21 +70,7 @@ export function map<const T extends RouteDefinition, TEnv = DefaultEnv>(
152
70
  "map() expects a builder function as its argument",
153
71
  );
154
72
  // Create helpers
155
- const helpers: RouteHelpers<T, TEnv> = {
156
- route: createRouteHelper<T, TEnv>(),
157
- layout: createLayoutHelper<TEnv>(),
158
- parallel: createParallelHelper<TEnv>(),
159
- intercept: createInterceptHelper<T, TEnv>(),
160
- middleware: createMiddlewareHelper<TEnv>(),
161
- revalidate: createRevalidateHelper<TEnv>(),
162
- loader: createLoaderHelper<TEnv>(),
163
- loading: createLoadingHelper(),
164
- errorBoundary: createErrorBoundaryHelper<TEnv>(),
165
- notFoundBoundary: createNotFoundBoundaryHelper<TEnv>(),
166
- when: createWhenHelper(),
167
- cache: createCacheHelper(),
168
- transition: createTransitionHelper(),
169
- };
73
+ const helpers = buildRouteHelpers<T, TEnv>();
170
74
 
171
75
  return [layout(RootLayout, () => builder(helpers))].flat(3);
172
76
  };
@@ -182,19 +86,5 @@ export function createRouteHelpers<
182
86
  T extends RouteDefinition,
183
87
  TEnv,
184
88
  >(): RouteHelpers<T, TEnv> {
185
- return {
186
- route: createRouteHelper<T, TEnv>(),
187
- layout: createLayoutHelper<TEnv>(),
188
- parallel: createParallelHelper<TEnv>(),
189
- intercept: createInterceptHelper<T, TEnv>(),
190
- middleware: createMiddlewareHelper<TEnv>(),
191
- revalidate: createRevalidateHelper<TEnv>(),
192
- loader: createLoaderHelper<TEnv>(),
193
- loading: createLoadingHelper(),
194
- errorBoundary: createErrorBoundaryHelper<TEnv>(),
195
- notFoundBoundary: createNotFoundBoundaryHelper<TEnv>(),
196
- when: createWhenHelper(),
197
- cache: createCacheHelper(),
198
- transition: createTransitionHelper(),
199
- };
89
+ return buildRouteHelpers<T, TEnv>();
200
90
  }
@@ -198,10 +198,10 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
198
198
  use?: () => UseItems<InterceptUseItem>,
199
199
  ): InterceptItem;
200
200
  // Global: unprefixed, params inferred from global route map
201
- <K extends keyof RSCRouter.GeneratedRouteMap & string>(
201
+ <K extends keyof Rango.GeneratedRouteMap & string>(
202
202
  slotName: `@${string}`,
203
203
  routeName: K,
204
- handler: ReactNode | Handler<K, RSCRouter.GeneratedRouteMap, TEnv>,
204
+ handler: ReactNode | Handler<K, Rango.GeneratedRouteMap, TEnv>,
205
205
  use?: () => UseItems<InterceptUseItem>,
206
206
  ): InterceptItem;
207
207
  };
@@ -250,8 +250,10 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
250
250
  * )
251
251
  *
252
252
  * // Revalidate after specific actions (actionId format: "path/to/file.ts#exportName")
253
+ * // Use `|| undefined` (defer), not `?? false` (hard short-circuit), so the
254
+ * // chain and the segment default still apply when there is no match.
253
255
  * revalidate(({ actionId }) =>
254
- * actionId?.includes("Cart") ?? false
256
+ * actionId?.includes("Cart") || undefined
255
257
  * )
256
258
  *
257
259
  * // Soft decision (suggest but allow override)
@@ -274,7 +276,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
274
276
  *
275
277
  * // With loader-specific revalidation (match by file or export name)
276
278
  * loader(CartLoader, () => [
277
- * revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
279
+ * revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
278
280
  * ])
279
281
  *
280
282
  * // Consume in client components with useLoader()
@@ -439,18 +441,36 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
439
441
  cache: {
440
442
  (): CacheItem;
441
443
  (children: () => UseItems<AllUseItems>): CacheItem;
442
- (profileName: string): CacheItem;
443
- (profileName: string, use: () => UseItems<AllUseItems>): CacheItem;
444
444
  (
445
- options: PartialCacheOptions | false,
445
+ options: PartialCacheOptions<TEnv> | false,
446
446
  use?: () => UseItems<AllUseItems>,
447
447
  ): CacheItem;
448
448
  };
449
449
  /**
450
- * Attach a ViewTransition boundary to the current segment or a group of routes
451
- *
452
- * Wraps segment content with React's `<ViewTransition>` component.
453
- * Only takes effect when React experimental is used (no-op on stable React).
450
+ * Opt a route (or group of routes) into transition-driven navigation.
451
+ *
452
+ * `transition()` does two independent things, and you choose how far to go:
453
+ * 1. startTransition (ALL React versions): the navigation commit is driven
454
+ * through React's startTransition, so a same-route nav (same route,
455
+ * different params, e.g. /product/1 -> /product/2) holds the previous
456
+ * content while the new loader resolves instead of flashing the route's
457
+ * loading() skeleton (see segment-system.tsx inTransitionScope). This is
458
+ * also the precondition for any view-transition animation.
459
+ * 2. <ViewTransition> (experimental React only): the segment content is also
460
+ * wrapped in React's <ViewTransition>, so the held swap cross-fades/morphs.
461
+ * Layered on by default; pass { viewTransition: false } to keep #1 without
462
+ * the router boundary (and place your own <ViewTransition> instead).
463
+ *
464
+ * A view transition cannot fire without a startTransition, so the meaningful
465
+ * choices are (see skills/view-transitions for the full matrix):
466
+ * - no transition() -> neither (remount + skeleton)
467
+ * - transition({ viewTransition: false }) -> startTransition only (hold)
468
+ * - transition({}) / transition({ enter… }) -> startTransition + ViewTransition
469
+ *
470
+ * Precedence: a bare transition({}) inherits createRouter({ viewTransition })
471
+ * (default "auto"); an explicit per-route `viewTransition` always wins. So
472
+ * transition({}) is startTransition + ViewTransition under the default and
473
+ * startTransition only when the router sets viewTransition: false.
454
474
  *
455
475
  * ```typescript
456
476
  * // Attach to a single route
@@ -464,16 +484,19 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
464
484
  * path("/about", AboutPage),
465
485
  * ])
466
486
  *
467
- * // Direction-aware transitions
468
- * transition({
469
- * enter: { "navigation": "slide-right", "navigation-back": "slide-left" },
470
- * exit: { "navigation": "slide-left", "navigation-back": "slide-right" },
471
- * })
487
+ * // Hold content + drive view transitions, but place no router boundary:
488
+ * path("/product/:id", ProductPage, { name: "product" }, () => [
489
+ * transition({ viewTransition: false }),
490
+ * ])
472
491
  * ```
473
- * @param config - ViewTransition configuration (enter, exit, update, share, default, name)
492
+ * @param config - ViewTransition configuration (enter, exit, update, share,
493
+ * default, name) plus `viewTransition: "auto" | false` to toggle the router
494
+ * boundary (createRouter({ viewTransition }) sets the app-wide default)
474
495
  * @param children - Optional callback returning child routes to wrap
475
496
  */
476
497
  transition: {
498
+ (): TransitionItem;
499
+ (children: () => UseItems<AllUseItems>): TransitionItem;
477
500
  (config: TransitionConfig): TransitionItem;
478
501
  (
479
502
  config: TransitionConfig,
@@ -4,6 +4,7 @@ import {
4
4
  getRequestContext,
5
5
  _getRequestContext,
6
6
  } from "../server/request-context.js";
7
+ import { markExternalRedirect } from "../redirect-origin.js";
7
8
 
8
9
  /**
9
10
  * Create a soft redirect Response for middleware short-circuit
@@ -39,6 +40,11 @@ import {
39
40
  * status: 303,
40
41
  * state: [Flash({ text: "Session expired" })],
41
42
  * });
43
+ *
44
+ * // Off-host redirect (opt out of the same-origin guard). Without
45
+ * // `external: true`, a cross-origin target is blocked and replaced with the
46
+ * // app root, matching the client's open-redirect protection.
47
+ * return redirect('https://accounts.example.com/oauth', { external: true });
42
48
  * ```
43
49
  */
44
50
  export function redirect(url: string, status?: number): Response;
@@ -47,13 +53,18 @@ export function redirect(
47
53
  options: {
48
54
  status?: number;
49
55
  state?: LocationStateEntry | LocationStateEntry[];
56
+ external?: boolean;
50
57
  },
51
58
  ): Response;
52
59
  export function redirect(
53
60
  url: string,
54
61
  statusOrOptions?:
55
62
  | number
56
- | { status?: number; state?: LocationStateEntry | LocationStateEntry[] },
63
+ | {
64
+ status?: number;
65
+ state?: LocationStateEntry | LocationStateEntry[];
66
+ external?: boolean;
67
+ },
57
68
  ): Response {
58
69
  const status =
59
70
  typeof statusOrOptions === "number"
@@ -61,6 +72,8 @@ export function redirect(
61
72
  : (statusOrOptions?.status ?? 302);
62
73
  const state =
63
74
  typeof statusOrOptions === "object" ? statusOrOptions?.state : undefined;
75
+ const external =
76
+ typeof statusOrOptions === "object" ? statusOrOptions?.external : undefined;
64
77
 
65
78
  if (state) {
66
79
  const ctx = requireRequestContext();
@@ -85,17 +98,38 @@ export function redirect(
85
98
  }
86
99
 
87
100
  // Auto-prefix root-relative URLs with basename for app-local redirects.
101
+ // Treat the URL as already-prefixed when the basename is followed by a path
102
+ // separator, a query, a fragment, or end-of-string, so "/admin?tab=x" and
103
+ // "/admin#frag" are not double-prefixed into "/admin/admin?tab=x".
88
104
  const bn = _getRequestContext()?._basename;
89
105
  let resolvedUrl = url;
90
- if (bn && url.startsWith("/") && !url.startsWith(bn + "/") && url !== bn) {
106
+ if (
107
+ bn &&
108
+ url.startsWith("/") &&
109
+ url !== bn &&
110
+ !url.startsWith(bn + "/") &&
111
+ !url.startsWith(bn + "?") &&
112
+ !url.startsWith(bn + "#")
113
+ ) {
91
114
  resolvedUrl = url === "/" ? bn : bn + url;
92
115
  }
93
116
 
94
- return new Response(null, {
95
- status,
96
- headers: {
97
- Location: resolvedUrl,
98
- "X-RSC-Redirect": "soft",
99
- },
100
- });
117
+ const headers: Record<string, string> = {
118
+ Location: resolvedUrl,
119
+ "X-RSC-Redirect": "soft",
120
+ };
121
+
122
+ const response = new Response(null, { status, headers });
123
+
124
+ // Mark an explicit off-host redirect with an out-of-band brand so the
125
+ // same-origin guard (rsc/redirect-guard.ts) lets it through. The brand is a
126
+ // WeakSet membership on this Response object -- NOT a wire header -- so the
127
+ // opt-in cannot be forged by an attacker-controlled upstream response a
128
+ // proxy-style response route might copy. The internal redirect-rebuild paths
129
+ // transfer the brand; the guard reads and clears it (see markExternalRedirect).
130
+ if (external) {
131
+ markExternalRedirect(response);
132
+ }
133
+
134
+ return response;
101
135
  }
@@ -139,6 +139,12 @@ export function mergeHandlerUse(
139
139
  mountSite: string,
140
140
  ): (() => any[]) | undefined {
141
141
  if (!handlerUse && !explicitUse) return undefined;
142
+ // Validation asymmetry (intentional, pre-1.0): only handler.use() items are
143
+ // checked against the mount-site allow-list (validateHandlerUseItems below).
144
+ // Explicit use() items pass through unvalidated on both the explicit-only
145
+ // branch here and the merged branch, so a structurally-valid-but-prohibited
146
+ // item (e.g. middleware() inside a parallel slot) is not rejected at this seam.
147
+ // Documented rather than enforced for now; revisit before 1.0 (#569).
142
148
  if (!handlerUse) return explicitUse;
143
149
  if (!explicitUse) {
144
150
  return () => {
@@ -0,0 +1,32 @@
1
+ import type { AllUseItems, WhenItem } from "../route-types.js";
2
+
3
+ /**
4
+ * The set of valid use-item `type` discriminants — the single runtime source of
5
+ * truth for "is this a well-formed use item?" shape validation.
6
+ *
7
+ * Declared via a `Record<...>` so that adding a member to the union without
8
+ * updating this map is a compile error. `when` is included because when() items
9
+ * are valid inside intercept() even though WhenItem is not part of AllUseItems
10
+ * (it lives only in InterceptUseItem). This is shape validation only; per-mount-
11
+ * site rules remain the narrower hand-written tables in resolve-handler-use.ts.
12
+ */
13
+ const USE_ITEM_TYPES: Record<AllUseItems["type"] | WhenItem["type"], true> = {
14
+ layout: true,
15
+ route: true,
16
+ middleware: true,
17
+ revalidate: true,
18
+ parallel: true,
19
+ intercept: true,
20
+ loader: true,
21
+ loading: true,
22
+ errorBoundary: true,
23
+ notFoundBoundary: true,
24
+ when: true,
25
+ cache: true,
26
+ transition: true,
27
+ include: true,
28
+ };
29
+
30
+ export const ALL_USE_ITEM_TYPES: ReadonlySet<string> = new Set(
31
+ Object.keys(USE_ITEM_TYPES),
32
+ );
@@ -8,15 +8,10 @@
8
8
  * See docs/manifests.md for the full data flow.
9
9
  */
10
10
 
11
- // Singleton route map instance - populated incrementally as routes are encountered
12
11
  let globalRouteMap: Record<string, string> = {};
13
12
 
14
- // Cached complete manifest - includes all routes (including lazy includes)
15
- // Set from runtime cache or build-time import
16
13
  let cachedManifest: Record<string, string> | null = null;
17
14
 
18
- // Pre-computed route entries from build-time prefix tree leaf nodes.
19
- // Used by evaluateLazyEntry() to skip running the handler for route matching.
20
15
  let cachedPrecomputedEntries: Array<{
21
16
  staticPrefix: string;
22
17
  routes: Record<string, string>;
@@ -43,7 +38,6 @@ export function registerRouteMap(map: Record<string, string>): void {
43
38
  * @internal
44
39
  */
45
40
  export function getGlobalRouteMap(): Record<string, string> {
46
- // Cached manifest is complete (includes lazy routes), so prefer it
47
41
  if (cachedManifest) {
48
42
  return cachedManifest;
49
43
  }
@@ -231,10 +225,6 @@ export function waitForManifestReady(): Promise<void> | null {
231
225
  return manifestReadyPromise;
232
226
  }
233
227
 
234
- // ============================================================================
235
- // Route Scope Registry
236
- // ============================================================================
237
-
238
228
  // Tracks whether each route is at root scope (no named include boundary above).
239
229
  // Used by dot-local reverse resolution to decide whether bare-name fallback
240
230
  // is allowed after scoped lookups are exhausted.
@@ -259,14 +249,8 @@ export function isRouteRootScoped(routeName: string): boolean | undefined {
259
249
  return rootScopeRoutes.get(routeName);
260
250
  }
261
251
 
262
- // ============================================================================
263
- // Search Schema Registry
264
- // ============================================================================
265
-
266
252
  import type { SearchSchema } from "./search-params.js";
267
253
 
268
- // Global search schema map: route name -> search schema descriptor.
269
- // Populated by path() when a search option is provided.
270
254
  const globalSearchSchemas: Map<string, SearchSchema> = new Map();
271
255
 
272
256
  export function registerSearchSchema(
@@ -5,47 +5,43 @@
5
5
  */
6
6
 
7
7
  /**
8
- * Branded return types for route helpers
8
+ * Brand for UrlPatterns nominal typing (see pattern-types.ts). The route-item
9
+ * types below are discriminated by their `type` literal, so they carry no brand.
9
10
  */
10
- export declare const LayoutBrand: unique symbol;
11
- export declare const RouteBrand: unique symbol;
12
- export declare const ParallelBrand: unique symbol;
13
- export declare const InterceptBrand: unique symbol;
14
- export declare const MiddlewareBrand: unique symbol;
15
- export declare const RevalidateBrand: unique symbol;
16
- export declare const LoaderBrand: unique symbol;
17
- export declare const LoadingBrand: unique symbol;
18
- export declare const ErrorBoundaryBrand: unique symbol;
19
- export declare const NotFoundBoundaryBrand: unique symbol;
20
- export declare const WhenBrand: unique symbol;
21
- export declare const CacheBrand: unique symbol;
22
- export declare const TransitionBrand: unique symbol;
23
- export declare const IncludeBrand: unique symbol;
24
11
  export declare const UrlPatternsBrand: unique symbol;
25
12
 
26
13
  export type LayoutItem = {
27
14
  name: string;
28
15
  type: "layout";
29
16
  uses?: AllUseItems[];
30
- [LayoutBrand]: void;
31
17
  };
32
18
 
33
19
  /**
34
- * Typed layout item that carries child routes as phantom type
35
- * Used for type inference in urls() API
20
+ * Phantom inference fields attached to wrapper items (layout/cache/transition)
21
+ * so the urls() type extractor can read their child routes/responses. The fields
22
+ * never exist at runtime.
36
23
  */
37
- export type TypedLayoutItem<
24
+ type WithChildren<
25
+ TBase,
38
26
  TChildRoutes extends Record<string, any> = Record<string, string>,
39
27
  TChildResponses extends Record<string, unknown> = Record<string, unknown>,
40
- > = LayoutItem & {
28
+ > = TBase & {
41
29
  readonly __childRoutes?: TChildRoutes;
42
30
  readonly __childResponses?: TChildResponses;
43
31
  };
32
+
33
+ /**
34
+ * Typed layout item that carries child routes as phantom type
35
+ * Used for type inference in urls() API
36
+ */
37
+ export type TypedLayoutItem<
38
+ TChildRoutes extends Record<string, any> = Record<string, string>,
39
+ TChildResponses extends Record<string, unknown> = Record<string, unknown>,
40
+ > = WithChildren<LayoutItem, TChildRoutes, TChildResponses>;
44
41
  export type RouteItem = {
45
42
  name: string;
46
43
  type: "route";
47
44
  uses?: AllUseItems[];
48
- [RouteBrand]: void;
49
45
  };
50
46
 
51
47
  /**
@@ -67,64 +63,53 @@ export type ParallelItem = {
67
63
  name: string;
68
64
  type: "parallel";
69
65
  uses?: ParallelUseItem[];
70
- [ParallelBrand]: void;
71
66
  };
72
67
  export type InterceptItem = {
73
68
  name: string;
74
69
  type: "intercept";
75
70
  uses?: InterceptUseItem[];
76
- [InterceptBrand]: void;
77
71
  };
78
72
  export type LoaderItem = {
79
73
  name: string;
80
74
  type: "loader";
81
75
  uses?: LoaderUseItem[];
82
- [LoaderBrand]: void;
83
76
  };
84
77
  export type MiddlewareItem = {
85
78
  name: string;
86
79
  type: "middleware";
87
80
  uses?: AllUseItems[];
88
- [MiddlewareBrand]: void;
89
81
  };
90
82
  export type RevalidateItem = {
91
83
  name: string;
92
84
  type: "revalidate";
93
85
  uses?: AllUseItems[];
94
- [RevalidateBrand]: void;
95
86
  };
96
87
  export type LoadingItem = {
97
88
  name: string;
98
89
  type: "loading";
99
- [LoadingBrand]: void;
100
90
  };
101
91
  export type ErrorBoundaryItem = {
102
92
  name: string;
103
93
  type: "errorBoundary";
104
94
  uses?: AllUseItems[];
105
- [ErrorBoundaryBrand]: void;
106
95
  };
107
96
  export type NotFoundBoundaryItem = {
108
97
  name: string;
109
98
  type: "notFoundBoundary";
110
99
  uses?: AllUseItems[];
111
- [NotFoundBoundaryBrand]: void;
112
100
  };
113
101
  export type WhenItem = {
114
102
  name: string;
115
103
  type: "when";
116
- [WhenBrand]: void;
117
104
  };
118
105
  export type CacheItem = {
119
106
  name: string;
120
107
  type: "cache";
121
108
  uses?: AllUseItems[];
122
- [CacheBrand]: void;
123
109
  };
124
110
  export type TransitionItem = {
125
111
  name: string;
126
112
  type: "transition";
127
- [TransitionBrand]: void;
128
113
  };
129
114
 
130
115
  /**
@@ -134,10 +119,7 @@ export type TransitionItem = {
134
119
  export type TypedTransitionItem<
135
120
  TChildRoutes extends Record<string, any> = Record<string, string>,
136
121
  TChildResponses extends Record<string, unknown> = Record<string, unknown>,
137
- > = TransitionItem & {
138
- readonly __childRoutes?: TChildRoutes;
139
- readonly __childResponses?: TChildResponses;
140
- };
122
+ > = WithChildren<TransitionItem, TChildRoutes, TChildResponses>;
141
123
 
142
124
  /**
143
125
  * Typed cache item that carries child routes as phantom type
@@ -146,10 +128,7 @@ export type TypedTransitionItem<
146
128
  export type TypedCacheItem<
147
129
  TChildRoutes extends Record<string, any> = Record<string, string>,
148
130
  TChildResponses extends Record<string, unknown> = Record<string, unknown>,
149
- > = CacheItem & {
150
- readonly __childRoutes?: TChildRoutes;
151
- readonly __childResponses?: TChildResponses;
152
- };
131
+ > = WithChildren<CacheItem, TChildRoutes, TChildResponses>;
153
132
 
154
133
  /**
155
134
  * Include item for URL pattern composition (used by urls() API)
@@ -184,7 +163,6 @@ export type IncludeItem = {
184
163
  */
185
164
  includeScope?: string;
186
165
  };
187
- [IncludeBrand]: void;
188
166
  };
189
167
 
190
168
  /**
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Normalize a router basename to its canonical form: a single leading slash,
3
+ * no trailing slash, and `undefined` for an empty or bare-"/" value.
4
+ *
5
+ * This is the single source of truth used by both createRouter() (so the RSC
6
+ * handler stores a canonical basename on the request context) and the testing
7
+ * primitives (so a consumer can pass the same un-normalized string their
8
+ * createRouter() accepts and observe the same redirect() prefixing).
9
+ */
10
+ export function normalizeBasename(basename?: string): string | undefined {
11
+ if (!basename) return undefined;
12
+ const trimmed = basename.replace(/^\/+|\/+$/g, "");
13
+ return trimmed ? "/" + trimmed : undefined;
14
+ }