@rangojs/router 0.0.0-experimental.77 → 0.0.0-experimental.77ed8945

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 (239) hide show
  1. package/README.md +120 -25
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/vite/index.js +2103 -861
  4. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  5. package/package.json +13 -8
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +220 -30
  10. package/skills/caching/SKILL.md +116 -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 +229 -20
  16. package/skills/host-router/SKILL.md +66 -20
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +26 -4
  19. package/skills/layout/SKILL.md +6 -7
  20. package/skills/links/SKILL.md +247 -17
  21. package/skills/loader/SKILL.md +219 -9
  22. package/skills/middleware/SKILL.md +47 -12
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +27 -0
  26. package/skills/observability/SKILL.md +137 -0
  27. package/skills/parallel/SKILL.md +12 -6
  28. package/skills/prerender/SKILL.md +14 -33
  29. package/skills/rango/SKILL.md +238 -22
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +122 -47
  32. package/skills/route/SKILL.md +33 -4
  33. package/skills/router-setup/SKILL.md +3 -3
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/tailwind/SKILL.md +27 -3
  37. package/skills/typesafety/SKILL.md +319 -27
  38. package/skills/use-cache/SKILL.md +34 -5
  39. package/skills/view-transitions/SKILL.md +294 -0
  40. package/src/__augment-tests__/augment.ts +81 -0
  41. package/src/__augment-tests__/augmented.check.ts +116 -0
  42. package/src/browser/action-coordinator.ts +53 -36
  43. package/src/browser/app-shell.ts +39 -0
  44. package/src/browser/event-controller.ts +86 -70
  45. package/src/browser/history-state.ts +21 -0
  46. package/src/browser/index.ts +3 -3
  47. package/src/browser/navigation-bridge.ts +29 -9
  48. package/src/browser/navigation-client.ts +99 -77
  49. package/src/browser/navigation-store.ts +7 -8
  50. package/src/browser/navigation-transaction.ts +10 -28
  51. package/src/browser/partial-update.ts +60 -40
  52. package/src/browser/prefetch/cache.ts +196 -49
  53. package/src/browser/prefetch/fetch.ts +203 -59
  54. package/src/browser/prefetch/queue.ts +36 -5
  55. package/src/browser/rango-state.ts +37 -13
  56. package/src/browser/react/Link.tsx +18 -13
  57. package/src/browser/react/NavigationProvider.tsx +75 -31
  58. package/src/browser/react/filter-segment-order.ts +51 -7
  59. package/src/browser/react/index.ts +3 -0
  60. package/src/browser/react/location-state-shared.ts +175 -4
  61. package/src/browser/react/location-state.ts +39 -13
  62. package/src/browser/react/use-handle.ts +17 -9
  63. package/src/browser/react/use-navigation.ts +22 -2
  64. package/src/browser/react/use-params.ts +20 -8
  65. package/src/browser/react/use-reverse.ts +106 -0
  66. package/src/browser/react/use-router.ts +23 -2
  67. package/src/browser/react/use-segments.ts +11 -8
  68. package/src/browser/response-adapter.ts +52 -1
  69. package/src/browser/rsc-router.tsx +71 -22
  70. package/src/browser/scroll-restoration.ts +22 -14
  71. package/src/browser/segment-reconciler.ts +10 -14
  72. package/src/browser/segment-structure-assert.ts +2 -2
  73. package/src/browser/server-action-bridge.ts +44 -30
  74. package/src/browser/types.ts +12 -2
  75. package/src/build/collect-fallback-refs.ts +107 -0
  76. package/src/build/generate-manifest.ts +60 -35
  77. package/src/build/generate-route-types.ts +2 -0
  78. package/src/build/index.ts +8 -1
  79. package/src/build/prefix-tree-utils.ts +123 -0
  80. package/src/build/route-trie.ts +45 -1
  81. package/src/build/route-types/codegen.ts +4 -4
  82. package/src/build/route-types/include-resolution.ts +1 -1
  83. package/src/build/route-types/per-module-writer.ts +7 -4
  84. package/src/build/route-types/router-processing.ts +55 -14
  85. package/src/build/route-types/scan-filter.ts +1 -1
  86. package/src/build/route-types/source-scan.ts +118 -0
  87. package/src/build/runtime-discovery.ts +9 -20
  88. package/src/cache/cache-runtime.ts +17 -5
  89. package/src/cache/cache-scope.ts +51 -49
  90. package/src/cache/cf/cf-cache-store.ts +502 -32
  91. package/src/cache/cf/index.ts +3 -0
  92. package/src/cache/handle-snapshot.ts +103 -0
  93. package/src/cache/index.ts +3 -0
  94. package/src/cache/memory-segment-store.ts +3 -2
  95. package/src/cache/types.ts +10 -6
  96. package/src/client.rsc.tsx +3 -0
  97. package/src/client.tsx +96 -205
  98. package/src/context-var.ts +5 -5
  99. package/src/decode-loader-results.ts +36 -0
  100. package/src/errors.ts +30 -4
  101. package/src/handle.ts +4 -6
  102. package/src/host/index.ts +2 -2
  103. package/src/host/router.ts +129 -57
  104. package/src/host/types.ts +31 -2
  105. package/src/host/utils.ts +1 -1
  106. package/src/href-client.ts +140 -21
  107. package/src/index.rsc.ts +10 -6
  108. package/src/index.ts +17 -8
  109. package/src/loader-store.ts +500 -0
  110. package/src/loader.rsc.ts +2 -5
  111. package/src/loader.ts +3 -10
  112. package/src/missing-id-error.ts +68 -0
  113. package/src/outlet-context.ts +1 -1
  114. package/src/prerender/store.ts +9 -7
  115. package/src/prerender.ts +4 -4
  116. package/src/response-utils.ts +37 -0
  117. package/src/reverse.ts +65 -39
  118. package/src/route-content-wrapper.tsx +6 -28
  119. package/src/route-definition/dsl-helpers.ts +253 -265
  120. package/src/route-definition/helper-factories.ts +29 -139
  121. package/src/route-definition/helpers-types.ts +43 -15
  122. package/src/route-definition/resolve-handler-use.ts +6 -0
  123. package/src/route-definition/use-item-types.ts +32 -0
  124. package/src/route-types.ts +26 -41
  125. package/src/router/content-negotiation.ts +15 -2
  126. package/src/router/error-handling.ts +1 -1
  127. package/src/router/find-match.ts +54 -6
  128. package/src/router/handler-context.ts +21 -41
  129. package/src/router/intercept-resolution.ts +4 -18
  130. package/src/router/lazy-includes.ts +41 -22
  131. package/src/router/loader-resolution.ts +82 -36
  132. package/src/router/manifest.ts +41 -19
  133. package/src/router/match-api.ts +4 -3
  134. package/src/router/match-handlers.ts +1 -0
  135. package/src/router/match-middleware/cache-lookup.ts +57 -95
  136. package/src/router/match-middleware/cache-store.ts +3 -2
  137. package/src/router/match-result.ts +53 -32
  138. package/src/router/metrics.ts +1 -1
  139. package/src/router/middleware-types.ts +15 -26
  140. package/src/router/middleware.ts +99 -84
  141. package/src/router/pattern-matching.ts +116 -19
  142. package/src/router/prerender-match.ts +40 -15
  143. package/src/router/preview-match.ts +3 -1
  144. package/src/router/request-classification.ts +40 -37
  145. package/src/router/revalidation.ts +58 -2
  146. package/src/router/router-interfaces.ts +51 -35
  147. package/src/router/router-options.ts +25 -1
  148. package/src/router/router-registry.ts +2 -5
  149. package/src/router/segment-resolution/fresh.ts +27 -6
  150. package/src/router/segment-resolution/revalidation.ts +147 -106
  151. package/src/router/segment-resolution/static-store.ts +19 -5
  152. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  153. package/src/router/substitute-pattern-params.ts +56 -0
  154. package/src/router/trie-matching.ts +40 -16
  155. package/src/router/types.ts +8 -0
  156. package/src/router/url-params.ts +49 -0
  157. package/src/router.ts +37 -25
  158. package/src/rsc/handler-context.ts +2 -2
  159. package/src/rsc/handler.ts +58 -77
  160. package/src/rsc/helpers.ts +72 -43
  161. package/src/rsc/index.ts +1 -1
  162. package/src/rsc/manifest-init.ts +28 -41
  163. package/src/rsc/origin-guard.ts +30 -10
  164. package/src/rsc/progressive-enhancement.ts +4 -0
  165. package/src/rsc/response-error.ts +79 -12
  166. package/src/rsc/response-route-handler.ts +76 -61
  167. package/src/rsc/rsc-rendering.ts +45 -51
  168. package/src/rsc/runtime-warnings.ts +9 -10
  169. package/src/rsc/server-action.ts +33 -39
  170. package/src/rsc/ssr-setup.ts +16 -0
  171. package/src/rsc/types.ts +8 -2
  172. package/src/search-params.ts +4 -4
  173. package/src/segment-content-promise.ts +67 -0
  174. package/src/segment-loader-promise.ts +122 -0
  175. package/src/segment-system.tsx +132 -116
  176. package/src/serialize.ts +243 -0
  177. package/src/server/context.ts +175 -53
  178. package/src/server/cookie-store.ts +28 -4
  179. package/src/server/request-context.ts +57 -51
  180. package/src/ssr/index.tsx +5 -1
  181. package/src/static-handler.ts +1 -1
  182. package/src/types/global-namespace.ts +39 -26
  183. package/src/types/handler-context.ts +68 -50
  184. package/src/types/index.ts +1 -0
  185. package/src/types/loader-types.ts +11 -9
  186. package/src/types/request-scope.ts +126 -0
  187. package/src/types/route-entry.ts +11 -0
  188. package/src/types/segments.ts +35 -2
  189. package/src/urls/include-helper.ts +34 -67
  190. package/src/urls/index.ts +1 -5
  191. package/src/urls/path-helper-types.ts +17 -3
  192. package/src/urls/path-helper.ts +17 -52
  193. package/src/urls/pattern-types.ts +36 -19
  194. package/src/urls/response-types.ts +22 -29
  195. package/src/urls/type-extraction.ts +58 -139
  196. package/src/urls/urls-function.ts +1 -5
  197. package/src/use-loader.tsx +413 -42
  198. package/src/vite/debug.ts +185 -0
  199. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  200. package/src/vite/discovery/discover-routers.ts +106 -75
  201. package/src/vite/discovery/discovery-errors.ts +194 -0
  202. package/src/vite/discovery/gate-state.ts +171 -0
  203. package/src/vite/discovery/prerender-collection.ts +72 -31
  204. package/src/vite/discovery/route-types-writer.ts +40 -84
  205. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  206. package/src/vite/discovery/state.ts +33 -0
  207. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  208. package/src/vite/index.ts +2 -0
  209. package/src/vite/plugin-types.ts +67 -0
  210. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  211. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  212. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  213. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  214. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  215. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  216. package/src/vite/plugins/expose-action-id.ts +54 -30
  217. package/src/vite/plugins/expose-id-utils.ts +12 -8
  218. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  219. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  220. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  221. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  222. package/src/vite/plugins/expose-internal-ids.ts +496 -486
  223. package/src/vite/plugins/performance-tracks.ts +29 -25
  224. package/src/vite/plugins/use-cache-transform.ts +65 -50
  225. package/src/vite/plugins/version-injector.ts +39 -23
  226. package/src/vite/plugins/version-plugin.ts +59 -2
  227. package/src/vite/plugins/virtual-entries.ts +2 -2
  228. package/src/vite/rango.ts +116 -29
  229. package/src/vite/router-discovery.ts +753 -104
  230. package/src/vite/utils/ast-handler-extract.ts +15 -15
  231. package/src/vite/utils/banner.ts +1 -1
  232. package/src/vite/utils/bundle-analysis.ts +4 -2
  233. package/src/vite/utils/client-chunks.ts +190 -0
  234. package/src/vite/utils/forward-user-plugins.ts +193 -0
  235. package/src/vite/utils/manifest-utils.ts +8 -59
  236. package/src/vite/utils/package-resolution.ts +41 -1
  237. package/src/vite/utils/prerender-utils.ts +5 -4
  238. package/src/vite/utils/shared-utils.ts +107 -26
  239. package/src/browser/action-response-classifier.ts +0 -99
@@ -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)
@@ -259,7 +261,12 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
259
261
  * ({ defaultShouldRevalidate: true })
260
262
  * )
261
263
  * ```
262
- * @param fn - Function that returns boolean (hard) or { defaultShouldRevalidate } (soft)
264
+ * @param fn - Function returning either:
265
+ * - `boolean` (hard decision — short-circuits the chain),
266
+ * - `{ defaultShouldRevalidate: boolean }` (soft — updates the suggestion
267
+ * for downstream revalidators),
268
+ * - or nothing / `null` / `undefined` (defer — leaves the suggestion
269
+ * unchanged and continues to the next revalidator).
263
270
  */
264
271
  revalidate: (fn: ShouldRevalidateFn<any, TEnv>) => RevalidateItem;
265
272
  /**
@@ -269,7 +276,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
269
276
  *
270
277
  * // With loader-specific revalidation (match by file or export name)
271
278
  * loader(CartLoader, () => [
272
- * revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
279
+ * revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
273
280
  * ])
274
281
  *
275
282
  * // Consume in client components with useLoader()
@@ -442,10 +449,30 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
442
449
  ): CacheItem;
443
450
  };
444
451
  /**
445
- * Attach a ViewTransition boundary to the current segment or a group of routes
446
- *
447
- * Wraps segment content with React's `<ViewTransition>` component.
448
- * Only takes effect when React experimental is used (no-op on stable React).
452
+ * Opt a route (or group of routes) into transition-driven navigation.
453
+ *
454
+ * `transition()` does two independent things, and you choose how far to go:
455
+ * 1. startTransition (ALL React versions): the navigation commit is driven
456
+ * through React's startTransition, so a same-route nav (same route,
457
+ * different params, e.g. /product/1 -> /product/2) holds the previous
458
+ * content while the new loader resolves instead of flashing the route's
459
+ * loading() skeleton (see segment-system.tsx inTransitionScope). This is
460
+ * also the precondition for any view-transition animation.
461
+ * 2. <ViewTransition> (experimental React only): the segment content is also
462
+ * wrapped in React's <ViewTransition>, so the held swap cross-fades/morphs.
463
+ * Layered on by default; pass { viewTransition: false } to keep #1 without
464
+ * the router boundary (and place your own <ViewTransition> instead).
465
+ *
466
+ * A view transition cannot fire without a startTransition, so the meaningful
467
+ * choices are (see skills/view-transitions for the full matrix):
468
+ * - no transition() -> neither (remount + skeleton)
469
+ * - transition({ viewTransition: false }) -> startTransition only (hold)
470
+ * - transition({}) / transition({ enter… }) -> startTransition + ViewTransition
471
+ *
472
+ * Precedence: a bare transition({}) inherits createRouter({ viewTransition })
473
+ * (default "auto"); an explicit per-route `viewTransition` always wins. So
474
+ * transition({}) is startTransition + ViewTransition under the default and
475
+ * startTransition only when the router sets viewTransition: false.
449
476
  *
450
477
  * ```typescript
451
478
  * // Attach to a single route
@@ -459,13 +486,14 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
459
486
  * path("/about", AboutPage),
460
487
  * ])
461
488
  *
462
- * // Direction-aware transitions
463
- * transition({
464
- * enter: { "navigation": "slide-right", "navigation-back": "slide-left" },
465
- * exit: { "navigation": "slide-left", "navigation-back": "slide-right" },
466
- * })
489
+ * // Hold content + drive view transitions, but place no router boundary:
490
+ * path("/product/:id", ProductPage, { name: "product" }, () => [
491
+ * transition({ viewTransition: false }),
492
+ * ])
467
493
  * ```
468
- * @param config - ViewTransition configuration (enter, exit, update, share, default, name)
494
+ * @param config - ViewTransition configuration (enter, exit, update, share,
495
+ * default, name) plus `viewTransition: "auto" | false` to toggle the router
496
+ * boundary (createRouter({ viewTransition }) sets the app-wide default)
469
497
  * @param children - Optional callback returning child routes to wrap
470
498
  */
471
499
  transition: {
@@ -21,6 +21,10 @@ export function resolveHandlerUse(handler: unknown): (() => any[]) | undefined {
21
21
  if (isStaticHandler(handler)) {
22
22
  return (handler as any).use;
23
23
  }
24
+ // Loader definitions from createLoader() — branded objects with optional .use
25
+ if (typeof handler === "object" && (handler as any).__brand === "loader") {
26
+ return (handler as any).use;
27
+ }
24
28
  // Plain handler function
25
29
  if (typeof handler === "function") {
26
30
  return (handler as any).use;
@@ -99,6 +103,8 @@ const MOUNT_SITE_ALLOWED_TYPES: Record<string, Set<string>> = {
99
103
  "when",
100
104
  "transition",
101
105
  ]),
106
+ // LoaderUseItem — only revalidate + cache can attach to a loader entry
107
+ loader: new Set(["revalidate", "cache"]),
102
108
  };
103
109
 
104
110
  /**
@@ -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
+ );
@@ -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)
@@ -176,8 +155,14 @@ export type IncludeItem = {
176
155
  >;
177
156
  /** Root scope flag for dot-local reverse resolution */
178
157
  rootScoped?: boolean;
158
+ /**
159
+ * Positional include scope token composed from the parent scope plus this
160
+ * include's sibling index (`${parentScope}I${idx}`). Applied to direct-
161
+ * descendant shortCodes during lazy evaluation so routes inside the
162
+ * include cannot collide with siblings declared outside it.
163
+ */
164
+ includeScope?: string;
179
165
  };
180
- [IncludeBrand]: void;
181
166
  };
182
167
 
183
168
  /**
@@ -135,8 +135,8 @@ export interface NegotiationResult {
135
135
  manifestEntry: EntryData;
136
136
  /** Route middleware for the winning variant */
137
137
  routeMiddleware: CollectedMiddleware[];
138
- /** Always true negotiation occurred */
139
- negotiated: true;
138
+ /** True when negotiation selected a variant; false for a plain response route. */
139
+ negotiated: boolean;
140
140
  }
141
141
 
142
142
  /**
@@ -155,6 +155,19 @@ export async function negotiateRoute(
155
155
  ): Promise<NegotiationResult | null> {
156
156
  const { matched, manifestEntry, routeMiddleware, responseType } = snapshot;
157
157
  if (!matched.negotiateVariants || matched.negotiateVariants.length === 0) {
158
+ // No variants: a plain response route still yields a result (negotiated:false)
159
+ // so callers don't re-derive it; RSC routes (no responseType/handler) -> null.
160
+ const handler =
161
+ manifestEntry.type === "route" ? manifestEntry.handler : undefined;
162
+ if (responseType && handler) {
163
+ return {
164
+ responseType,
165
+ handler: handler as Function,
166
+ manifestEntry,
167
+ routeMiddleware,
168
+ negotiated: false,
169
+ };
170
+ }
158
171
  return null;
159
172
  }
160
173
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Router Error Handling Utilities
3
3
  *
4
- * Error boundary and not-found boundary handling for RSC Router.
4
+ * Error boundary and not-found boundary handling for Rango.
5
5
  * Also includes the shared invokeOnError utility for error callback invocation.
6
6
  */
7
7
 
@@ -1,5 +1,5 @@
1
1
  import { tryTrieMatch } from "./trie-matching.js";
2
- import { getRouteTrie, getRouterTrie } from "../route-map-builder.js";
2
+ import { getRouterTrie } from "../route-map-builder.js";
3
3
  import {
4
4
  findMatch as findRouteMatch,
5
5
  isLazyEvaluationNeeded,
@@ -8,6 +8,19 @@ import {
8
8
  import type { MetricsStore } from "../server/context";
9
9
  import type { RouteEntry } from "../types";
10
10
 
11
+ // Return a shallow copy with an independent `params` object. The single-entry
12
+ // cache below is module-lifetime and keyed only on pathname, so the same result
13
+ // object is handed to every same-pathname request in the isolate. ctx.params
14
+ // aliases this `params` (see request-context), so without an own copy a handler
15
+ // that mutates ctx.params would corrupt the cached entry for later requests.
16
+ // `entry` and the flags are intentionally shared by reference: they are
17
+ // read-only, and entry identity is compared in match-api (prevMatch.entry).
18
+ function cloneMatchResult<TEnv>(
19
+ r: RouteMatchResult<TEnv> | null,
20
+ ): RouteMatchResult<TEnv> | null {
21
+ return r ? { ...r, params: { ...r.params } } : null;
22
+ }
23
+
11
24
  export interface FindMatchDeps<TEnv = any> {
12
25
  routesEntries: RouteEntry<TEnv>[];
13
26
  evaluateLazyEntry: (entry: RouteEntry<TEnv>) => void;
@@ -35,9 +48,10 @@ export function createFindMatch<TEnv = any>(
35
48
  pathname: string,
36
49
  ms?: MetricsStore,
37
50
  ): RouteMatchResult<TEnv> | null {
38
- // Return cached result if same pathname (avoids double-match per request)
51
+ // Return cached result if same pathname (avoids double-match per request).
52
+ // Clone so a caller mutating ctx.params cannot corrupt the shared cache.
39
53
  if (lastFindMatchPathname === pathname) {
40
- return lastFindMatchResult;
54
+ return cloneMatchResult(lastFindMatchResult);
41
55
  }
42
56
 
43
57
  // Helper to push sub-metrics
@@ -56,12 +70,19 @@ export function createFindMatch<TEnv = any>(
56
70
  // routers and must not be used — in multi-router setups (host routing)
57
71
  // overlapping paths like "/" would match the wrong app's route.
58
72
  const routeTrie = getRouterTrie(deps.routerId);
73
+ // Whether the trie produced a match for this pathname (independent of
74
+ // whether the owning RouteEntry was resolvable yet). Used to suppress the
75
+ // R3 dev warning below: if the trie DID match but we fell through to the
76
+ // regex fallback only because a lazy entry was not spliced in yet, that is
77
+ // not a trie gap.
78
+ let trieMatched = false;
59
79
  if (routeTrie) {
60
80
  const trieStart = performance.now();
61
81
  const trieResult = tryTrieMatch(routeTrie, pathname);
62
82
  pushMetric?.("match:trie", trieStart);
63
83
 
64
84
  if (trieResult) {
85
+ trieMatched = true;
65
86
  // Find the RouteEntry that contains this route.
66
87
  // Multiple entries can share the same staticPrefix (e.g., several
67
88
  // include("/", patterns) calls all produce staticPrefix=""). Evaluate
@@ -114,7 +135,6 @@ export function createFindMatch<TEnv = any>(
114
135
  params: trieResult.params,
115
136
  optionalParams: new Set(trieResult.optionalParams || []),
116
137
  redirectTo: trieResult.redirectTo,
117
- ancestry: trieResult.ancestry,
118
138
  ...(trieResult.pr ? { pr: true } : {}),
119
139
  ...(trieResult.pt ? { pt: true } : {}),
120
140
  ...(trieResult.responseType
@@ -125,7 +145,7 @@ export function createFindMatch<TEnv = any>(
125
145
  : {}),
126
146
  ...(trieResult.rscFirst ? { rscFirst: true } : {}),
127
147
  };
128
- return lastFindMatchResult;
148
+ return cloneMatchResult(lastFindMatchResult);
129
149
  }
130
150
  }
131
151
  }
@@ -153,8 +173,36 @@ export function createFindMatch<TEnv = any>(
153
173
  }
154
174
  pushMetric?.("match:regex-fallback", regexStart);
155
175
 
176
+ // The trie is the single source of truth and is built before findMatch in
177
+ // both dev (handler rebuild) and production (ensureRouterManifest). If the
178
+ // trie was present yet the regex fallback resolved a real match, the trie
179
+ // has a gap (e.g. a route shape it cannot represent) and dev/prod could
180
+ // diverge if the trie were ever absent. Surface it in dev; folded out in
181
+ // production builds.
182
+ //
183
+ // Suppress when the trie DID match (`trieMatched`): that path falls through
184
+ // to the regex fallback only on the first request to a not-yet-spliced lazy
185
+ // entry (e.g. a 2+-level nested include whose deeper parent has not been
186
+ // evaluated). The trie knew the route; runtime lazy discovery simply lagged.
187
+ // That is the supported lazy-include flow, not a trie gap, so warning on it
188
+ // is a false positive (it manufactures bug reports and erodes the signal).
189
+ if (
190
+ process.env.NODE_ENV !== "production" &&
191
+ routeTrie &&
192
+ !trieMatched &&
193
+ result &&
194
+ !isLazyEvaluationNeeded(result)
195
+ ) {
196
+ console.warn(
197
+ `[@rangojs/router] Route "${pathname}" resolved via the regex fallback ` +
198
+ `even though the route trie was present. The trie should be the single ` +
199
+ `matching source of truth; this indicates a trie gap. Please report this ` +
200
+ `with your route configuration.`,
201
+ );
202
+ }
203
+
156
204
  lastFindMatchPathname = pathname;
157
205
  lastFindMatchResult = result;
158
- return result;
206
+ return cloneMatchResult(result);
159
207
  };
160
208
  }