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

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 (285) hide show
  1. package/README.md +196 -43
  2. package/dist/bin/rango.js +277 -99
  3. package/dist/testing/vitest.js +48 -0
  4. package/dist/vite/index.js +2779 -1064
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +57 -11
  8. package/skills/breadcrumbs/SKILL.md +3 -1
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +243 -21
  11. package/skills/caching/SKILL.md +155 -6
  12. package/skills/composability/SKILL.md +27 -2
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +229 -20
  16. package/skills/host-router/SKILL.md +45 -20
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +46 -4
  19. package/skills/layout/SKILL.md +28 -7
  20. package/skills/links/SKILL.md +249 -17
  21. package/skills/loader/SKILL.md +273 -53
  22. package/skills/middleware/SKILL.md +49 -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 +197 -6
  28. package/skills/prerender/SKILL.md +123 -100
  29. package/skills/rango/SKILL.md +242 -22
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +66 -9
  32. package/skills/route/SKILL.md +88 -4
  33. package/skills/router-setup/SKILL.md +90 -5
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/testing/SKILL.md +716 -0
  37. package/skills/typesafety/SKILL.md +329 -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 +117 -0
  42. package/src/__internal.ts +1 -1
  43. package/src/browser/action-coordinator.ts +53 -36
  44. package/src/browser/app-shell.ts +52 -0
  45. package/src/browser/app-version.ts +14 -0
  46. package/src/browser/event-controller.ts +91 -70
  47. package/src/browser/history-state.ts +21 -0
  48. package/src/browser/index.ts +3 -3
  49. package/src/browser/navigation-bridge.ts +102 -16
  50. package/src/browser/navigation-client.ts +164 -59
  51. package/src/browser/navigation-store.ts +75 -17
  52. package/src/browser/navigation-transaction.ts +21 -37
  53. package/src/browser/partial-update.ts +139 -38
  54. package/src/browser/prefetch/cache.ts +175 -15
  55. package/src/browser/prefetch/fetch.ts +180 -33
  56. package/src/browser/prefetch/queue.ts +123 -20
  57. package/src/browser/prefetch/resource-ready.ts +77 -0
  58. package/src/browser/rango-state.ts +53 -13
  59. package/src/browser/react/Link.tsx +81 -9
  60. package/src/browser/react/NavigationProvider.tsx +110 -33
  61. package/src/browser/react/context.ts +7 -2
  62. package/src/browser/react/filter-segment-order.ts +51 -7
  63. package/src/browser/react/index.ts +3 -0
  64. package/src/browser/react/location-state-shared.ts +175 -4
  65. package/src/browser/react/location-state.ts +39 -13
  66. package/src/browser/react/use-handle.ts +23 -64
  67. package/src/browser/react/use-navigation.ts +22 -2
  68. package/src/browser/react/use-params.ts +20 -8
  69. package/src/browser/react/use-reverse.ts +106 -0
  70. package/src/browser/react/use-router.ts +43 -10
  71. package/src/browser/react/use-segments.ts +11 -8
  72. package/src/browser/response-adapter.ts +25 -0
  73. package/src/browser/rsc-router.tsx +191 -74
  74. package/src/browser/scroll-restoration.ts +41 -14
  75. package/src/browser/segment-reconciler.ts +36 -9
  76. package/src/browser/segment-structure-assert.ts +2 -2
  77. package/src/browser/server-action-bridge.ts +31 -36
  78. package/src/browser/types.ts +57 -5
  79. package/src/build/collect-fallback-refs.ts +107 -0
  80. package/src/build/generate-manifest.ts +65 -40
  81. package/src/build/generate-route-types.ts +5 -0
  82. package/src/build/index.ts +2 -0
  83. package/src/build/route-trie.ts +52 -25
  84. package/src/build/route-types/codegen.ts +4 -4
  85. package/src/build/route-types/include-resolution.ts +9 -2
  86. package/src/build/route-types/per-module-writer.ts +7 -4
  87. package/src/build/route-types/router-processing.ts +278 -88
  88. package/src/build/route-types/scan-filter.ts +9 -2
  89. package/src/build/route-types/source-scan.ts +118 -0
  90. package/src/build/runtime-discovery.ts +9 -20
  91. package/src/cache/cache-runtime.ts +15 -11
  92. package/src/cache/cache-scope.ts +76 -49
  93. package/src/cache/cf/cf-cache-store.ts +501 -18
  94. package/src/cache/cf/index.ts +5 -1
  95. package/src/cache/document-cache.ts +17 -7
  96. package/src/cache/index.ts +1 -0
  97. package/src/cache/taint.ts +55 -0
  98. package/src/client.rsc.tsx +3 -0
  99. package/src/client.tsx +94 -238
  100. package/src/context-var.ts +72 -2
  101. package/src/debug.ts +2 -2
  102. package/src/decode-loader-results.ts +36 -0
  103. package/src/errors.ts +30 -1
  104. package/src/handle.ts +65 -12
  105. package/src/host/index.ts +2 -2
  106. package/src/host/router.ts +129 -57
  107. package/src/host/types.ts +31 -2
  108. package/src/host/utils.ts +1 -1
  109. package/src/href-client.ts +140 -20
  110. package/src/index.rsc.ts +12 -5
  111. package/src/index.ts +61 -11
  112. package/src/loader-store.ts +500 -0
  113. package/src/loader.rsc.ts +2 -5
  114. package/src/loader.ts +3 -10
  115. package/src/missing-id-error.ts +68 -0
  116. package/src/outlet-context.ts +1 -1
  117. package/src/prerender/store.ts +5 -4
  118. package/src/prerender.ts +141 -80
  119. package/src/response-utils.ts +37 -0
  120. package/src/reverse.ts +65 -15
  121. package/src/route-content-wrapper.tsx +6 -28
  122. package/src/route-definition/dsl-helpers.ts +435 -260
  123. package/src/route-definition/helper-factories.ts +29 -139
  124. package/src/route-definition/helpers-types.ts +110 -34
  125. package/src/route-definition/index.ts +3 -0
  126. package/src/route-definition/redirect.ts +11 -3
  127. package/src/route-definition/resolve-handler-use.ts +155 -0
  128. package/src/route-definition/use-item-types.ts +32 -0
  129. package/src/route-map-builder.ts +7 -1
  130. package/src/route-types.ts +37 -41
  131. package/src/router/basename.ts +14 -0
  132. package/src/router/content-negotiation.ts +113 -1
  133. package/src/router/error-handling.ts +1 -1
  134. package/src/router/find-match.ts +4 -2
  135. package/src/router/handler-context.ts +77 -38
  136. package/src/router/intercept-resolution.ts +15 -22
  137. package/src/router/lazy-includes.ts +12 -9
  138. package/src/router/loader-resolution.ts +174 -22
  139. package/src/router/logging.ts +5 -2
  140. package/src/router/manifest.ts +31 -16
  141. package/src/router/match-api.ts +128 -192
  142. package/src/router/match-handlers.ts +63 -20
  143. package/src/router/match-middleware/background-revalidation.ts +30 -2
  144. package/src/router/match-middleware/cache-lookup.ts +136 -106
  145. package/src/router/match-middleware/cache-store.ts +54 -10
  146. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  147. package/src/router/match-middleware/segment-resolution.ts +61 -5
  148. package/src/router/match-result.ts +125 -10
  149. package/src/router/metrics.ts +7 -2
  150. package/src/router/middleware-types.ts +21 -34
  151. package/src/router/middleware.ts +103 -90
  152. package/src/router/navigation-snapshot.ts +182 -0
  153. package/src/router/pattern-matching.ts +101 -17
  154. package/src/router/prerender-match.ts +110 -10
  155. package/src/router/preview-match.ts +32 -102
  156. package/src/router/request-classification.ts +286 -0
  157. package/src/router/revalidation.ts +58 -2
  158. package/src/router/route-snapshot.ts +245 -0
  159. package/src/router/router-context.ts +6 -1
  160. package/src/router/router-interfaces.ts +77 -28
  161. package/src/router/router-options.ts +76 -11
  162. package/src/router/router-registry.ts +2 -5
  163. package/src/router/segment-resolution/fresh.ts +223 -24
  164. package/src/router/segment-resolution/helpers.ts +29 -24
  165. package/src/router/segment-resolution/loader-cache.ts +1 -0
  166. package/src/router/segment-resolution/revalidation.ts +466 -285
  167. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  168. package/src/router/segment-wrappers.ts +2 -0
  169. package/src/router/substitute-pattern-params.ts +56 -0
  170. package/src/router/telemetry.ts +99 -0
  171. package/src/router/trie-matching.ts +18 -13
  172. package/src/router/types.ts +9 -0
  173. package/src/router/url-params.ts +49 -0
  174. package/src/router.ts +91 -23
  175. package/src/rsc/handler-context.ts +2 -2
  176. package/src/rsc/handler.ts +440 -381
  177. package/src/rsc/helpers.ts +91 -43
  178. package/src/rsc/index.ts +1 -1
  179. package/src/rsc/loader-fetch.ts +23 -3
  180. package/src/rsc/manifest-init.ts +5 -1
  181. package/src/rsc/origin-guard.ts +28 -10
  182. package/src/rsc/progressive-enhancement.ts +18 -2
  183. package/src/rsc/response-route-handler.ts +46 -53
  184. package/src/rsc/rsc-rendering.ts +41 -48
  185. package/src/rsc/runtime-warnings.ts +9 -10
  186. package/src/rsc/server-action.ts +25 -37
  187. package/src/rsc/ssr-setup.ts +18 -2
  188. package/src/rsc/types.ts +17 -3
  189. package/src/search-params.ts +4 -4
  190. package/src/segment-content-promise.ts +67 -0
  191. package/src/segment-loader-promise.ts +122 -0
  192. package/src/segment-system.tsx +219 -67
  193. package/src/serialize.ts +243 -0
  194. package/src/server/context.ts +277 -61
  195. package/src/server/cookie-store.ts +28 -4
  196. package/src/server/handle-store.ts +19 -0
  197. package/src/server/loader-registry.ts +9 -8
  198. package/src/server/request-context.ts +204 -60
  199. package/src/ssr/index.tsx +9 -1
  200. package/src/static-handler.ts +19 -7
  201. package/src/testing/cache-status.ts +166 -0
  202. package/src/testing/collect-handle.ts +63 -0
  203. package/src/testing/dispatch.ts +440 -0
  204. package/src/testing/dom.entry.ts +22 -0
  205. package/src/testing/e2e/fixture.ts +154 -0
  206. package/src/testing/e2e/index.ts +149 -0
  207. package/src/testing/e2e/matchers.ts +51 -0
  208. package/src/testing/e2e/page-helpers.ts +272 -0
  209. package/src/testing/e2e/parity.ts +306 -0
  210. package/src/testing/e2e/server.ts +183 -0
  211. package/src/testing/flight-matchers.ts +104 -0
  212. package/src/testing/flight-runtime.d.ts +21 -0
  213. package/src/testing/flight.entry.ts +22 -0
  214. package/src/testing/flight.ts +182 -0
  215. package/src/testing/generated-routes.ts +223 -0
  216. package/src/testing/index.ts +106 -0
  217. package/src/testing/internal/context.ts +255 -0
  218. package/src/testing/render-route.tsx +565 -0
  219. package/src/testing/run-loader.ts +296 -0
  220. package/src/testing/run-middleware.ts +179 -0
  221. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  222. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  223. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  224. package/src/testing/vitest-stubs/version.ts +5 -0
  225. package/src/testing/vitest.ts +183 -0
  226. package/src/types/cache-types.ts +4 -4
  227. package/src/types/global-namespace.ts +39 -26
  228. package/src/types/handler-context.ts +194 -72
  229. package/src/types/index.ts +1 -0
  230. package/src/types/loader-types.ts +41 -15
  231. package/src/types/request-scope.ts +126 -0
  232. package/src/types/route-entry.ts +19 -1
  233. package/src/types/segments.ts +37 -1
  234. package/src/urls/include-helper.ts +34 -67
  235. package/src/urls/index.ts +0 -3
  236. package/src/urls/path-helper-types.ts +50 -9
  237. package/src/urls/path-helper.ts +63 -63
  238. package/src/urls/pattern-types.ts +48 -19
  239. package/src/urls/response-types.ts +25 -22
  240. package/src/urls/type-extraction.ts +26 -116
  241. package/src/urls/urls-function.ts +1 -5
  242. package/src/use-loader.tsx +487 -44
  243. package/src/vite/debug.ts +185 -0
  244. package/src/vite/discovery/bundle-postprocess.ts +34 -37
  245. package/src/vite/discovery/discover-routers.ts +105 -51
  246. package/src/vite/discovery/discovery-errors.ts +194 -0
  247. package/src/vite/discovery/gate-state.ts +171 -0
  248. package/src/vite/discovery/prerender-collection.ts +188 -93
  249. package/src/vite/discovery/route-types-writer.ts +40 -84
  250. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  251. package/src/vite/discovery/state.ts +46 -6
  252. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  253. package/src/vite/index.ts +6 -0
  254. package/src/vite/plugin-types.ts +111 -72
  255. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  256. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  257. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  258. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  259. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  260. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  261. package/src/vite/plugins/expose-action-id.ts +55 -33
  262. package/src/vite/plugins/expose-id-utils.ts +24 -8
  263. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  264. package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
  265. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  266. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  267. package/src/vite/plugins/expose-internal-ids.ts +544 -317
  268. package/src/vite/plugins/performance-tracks.ts +92 -0
  269. package/src/vite/plugins/refresh-cmd.ts +88 -26
  270. package/src/vite/plugins/use-cache-transform.ts +65 -50
  271. package/src/vite/plugins/version-injector.ts +39 -23
  272. package/src/vite/plugins/version-plugin.ts +72 -3
  273. package/src/vite/plugins/virtual-entries.ts +2 -2
  274. package/src/vite/rango.ts +265 -226
  275. package/src/vite/router-discovery.ts +920 -137
  276. package/src/vite/utils/ast-handler-extract.ts +15 -15
  277. package/src/vite/utils/banner.ts +4 -4
  278. package/src/vite/utils/bundle-analysis.ts +4 -2
  279. package/src/vite/utils/client-chunks.ts +190 -0
  280. package/src/vite/utils/forward-user-plugins.ts +193 -0
  281. package/src/vite/utils/manifest-utils.ts +21 -5
  282. package/src/vite/utils/package-resolution.ts +41 -1
  283. package/src/vite/utils/prerender-utils.ts +38 -5
  284. package/src/vite/utils/shared-utils.ts +109 -27
  285. package/src/browser/action-response-classifier.ts +0 -99
@@ -8,10 +8,21 @@ export interface LazyIncludeContext {
8
8
  urlPrefix: string;
9
9
  namePrefix: string | undefined;
10
10
  parent: unknown; // EntryData - avoid circular import
11
+ /** Counter snapshot from pattern extraction for consistent shortCode indices */
12
+ counters?: Record<string, number>;
11
13
  cacheProfiles?: Record<
12
14
  string,
13
15
  import("../cache/profile-registry.js").CacheProfile
14
16
  >;
17
+ /** Root scope flag for dot-local reverse resolution */
18
+ rootScoped?: boolean;
19
+ /**
20
+ * Positional include scope token composed from the parent scope plus this
21
+ * include's sibling index (`${parentScope}I${idx}`). Applied to direct-
22
+ * descendant shortCodes during lazy evaluation so routes inside the
23
+ * include cannot collide with siblings declared outside it.
24
+ */
25
+ includeScope?: string;
15
26
  }
16
27
 
17
28
  /**
@@ -55,6 +66,13 @@ export interface RouteEntry<TEnv = any> {
55
66
  | Promise<() => Array<AllUseItems>>;
56
67
  mountIndex: number;
57
68
 
69
+ /**
70
+ * Router ID that owns this entry. Used to namespace the manifest cache
71
+ * so multi-router setups (host routing) don't share cached EntryData
72
+ * across routers with overlapping mountIndex + routeKey combinations.
73
+ */
74
+ routerId?: string;
75
+
58
76
  /**
59
77
  * Route keys in this entry that have pre-render handlers.
60
78
  * Used by the non-trie match path to set the `pr` flag.
@@ -62,7 +80,7 @@ export interface RouteEntry<TEnv = any> {
62
80
  prerenderRouteKeys?: Set<string>;
63
81
 
64
82
  /**
65
- * Route keys in this entry that use `{ passthrough: true }`.
83
+ * Route keys in this entry that are wrapped with `Passthrough()`.
66
84
  * Used by the non-trie match path to set the `pt` flag.
67
85
  */
68
86
  passthroughRouteKeys?: Set<string>;
@@ -10,7 +10,10 @@ export type ViewTransitionClass = Record<string, string> | string;
10
10
 
11
11
  /**
12
12
  * Configuration for React's <ViewTransition> component.
13
- * Maps directly to ViewTransitionProps (minus children/ref/callbacks).
13
+ *
14
+ * The phase fields (enter/exit/update/share/default/name) map directly to
15
+ * ViewTransitionProps (minus children/ref/callbacks). The `viewTransition`
16
+ * field is router-specific and is stripped before the config reaches React.
14
17
  */
15
18
  export interface TransitionConfig {
16
19
  enter?: ViewTransitionClass;
@@ -19,6 +22,20 @@ export interface TransitionConfig {
19
22
  share?: ViewTransitionClass;
20
23
  default?: ViewTransitionClass;
21
24
  name?: string;
25
+ /**
26
+ * Whether the router wraps this segment's content in its own
27
+ * <ViewTransition> boundary.
28
+ *
29
+ * - "auto" (default): the router places the boundary, producing the
30
+ * router-owned cross-fade described by the phase fields above.
31
+ * - false: the router places no boundary. The navigation commit is still
32
+ * driven through startTransition (so loaders hold instead of flashing a
33
+ * skeleton, and consumer-placed <ViewTransition> elements still animate),
34
+ * but the router contributes no cross-fade of its own.
35
+ *
36
+ * When unset, inherits the createRouter({ viewTransition }) default.
37
+ */
38
+ viewTransition?: "auto" | false;
22
39
  }
23
40
 
24
41
  /**
@@ -50,7 +67,9 @@ export interface ResolvedSegment {
50
67
  parallelName?: string; // For parallels: the parallel group name (used to match with revalidations)
51
68
  // Loader-specific fields
52
69
  loaderId?: string; // For loaders: the loader $$id identifier
70
+ _inherited?: boolean; // For inherited loaders: dedup marker for buildMatchResult
53
71
  loaderData?: any; // For loaders: the resolved data from loader execution
72
+ parallelLoading?: ReactNode; // For parallel-owned loaders: the parallel's loading fallback
54
73
  // Intercept loader fields (for streaming loader data in parallel segments)
55
74
  loaderDataPromise?: Promise<any[]> | any[]; // Loader data promise or resolved array
56
75
  loaderIds?: string[]; // IDs ($$id) of loaders for this segment
@@ -60,6 +79,14 @@ export interface ResolvedSegment {
60
79
  notFoundInfo?: NotFoundInfo; // For notFound segments: the not found information
61
80
  // Mount path from include() scope, used for MountContext.Provider wrapping
62
81
  mountPath?: string;
82
+ /**
83
+ * @internal Server-side marker: true when the segment's handler actually ran
84
+ * this request (not skipped via the revalidate cache path). Used by
85
+ * match-result.ts to populate `MatchResult.resolvedIds` for client-side
86
+ * handle-bucket cleanup. Stripped from the wire payload before serialization
87
+ * — never reaches the client.
88
+ */
89
+ _handlerRan?: boolean;
63
90
  }
64
91
 
65
92
  /**
@@ -114,6 +141,15 @@ export interface MatchResult {
114
141
  segments: ResolvedSegment[];
115
142
  matched: string[];
116
143
  diff: string[];
144
+ /**
145
+ * Every segment id whose handler actually ran on the server this request,
146
+ * including ones with `component === null` that get filtered out of
147
+ * `segments`/`diff` to avoid wasted bytes. Drives the client's handle-
148
+ * cleanup pass — a slot that re-resolves and pushes nothing must clear
149
+ * its previous handle bucket, but `diff` doesn't carry it because the
150
+ * segment payload doesn't either. A superset of `diff`.
151
+ */
152
+ resolvedIds: string[];
117
153
  /**
118
154
  * Merged route params from all matched segments
119
155
  * Available for use by the handler after route matching
@@ -1,10 +1,8 @@
1
1
  import type { AllUseItems, IncludeItem } from "../route-types.js";
2
2
  import {
3
- getContext,
4
- runWithPrefixes,
5
3
  getUrlPrefix,
6
4
  getNamePrefix,
7
- getRootScoped,
5
+ requireDslContext,
8
6
  } from "../server/context";
9
7
  import {
10
8
  INTERNAL_INCLUDE_SCOPE_PREFIX,
@@ -27,28 +25,10 @@ function allocateInternalIncludeScopeId(
27
25
  }
28
26
 
29
27
  /**
30
- * Process an IncludeItem by executing its nested patterns with prefixes
31
- * This expands the include into actual route registrations
32
- */
33
- function processIncludeItem(item: IncludeItem): AllUseItems[] {
34
- const { prefix, patterns } = item;
35
- const namePrefix =
36
- (item as IncludeItem & { _lazyContext?: { namePrefix?: string } })
37
- ._lazyContext?.namePrefix ?? item.options?.name;
38
-
39
- // Execute the nested patterns' handler with URL and name prefixes
40
- // The urlPrefix being set tells nested urls() to skip RootLayout wrapping
41
- return runWithPrefixes(prefix, namePrefix, () => {
42
- // Call the nested patterns' handler - this registers routes with prefixed patterns/names
43
- return (patterns as UrlPatterns).handler();
44
- });
45
- }
46
-
47
- /**
48
- * Recursively process items, expanding any IncludeItems
49
- * Returns items with IncludeItems expanded into actual route items
28
+ * Recursively walk items, recursing into layout children.
50
29
  *
51
- * Lazy includes are kept as-is (not expanded) for the router to handle later.
30
+ * All includes are lazy and kept as-is; the router expands them on the first
31
+ * matching request.
52
32
  */
53
33
  export function processItems(items: readonly AllUseItems[]): AllUseItems[] {
54
34
  const result: AllUseItems[] = [];
@@ -57,26 +37,8 @@ export function processItems(items: readonly AllUseItems[]): AllUseItems[] {
57
37
  if (!item) continue;
58
38
 
59
39
  if (item.type === "include") {
60
- const includeItem = item as IncludeItem & {
61
- _expanded?: AllUseItems[];
62
- lazy?: boolean;
63
- };
64
-
65
- // Lazy includes are NOT expanded here - kept for router to handle
66
- if (includeItem.lazy) {
67
- result.push(item);
68
- continue;
69
- }
70
-
71
- // Eager includes are already expanded during include() call
72
- if (includeItem._expanded) {
73
- // Items were expanded immediately - just process them recursively
74
- result.push(...processItems(includeItem._expanded));
75
- } else {
76
- // Fallback for legacy include items without _expanded
77
- const expanded = processIncludeItem(item as IncludeItem);
78
- result.push(...processItems(expanded));
79
- }
40
+ // All includes are lazy; the router expands them on first matching request.
41
+ result.push(item);
80
42
  } else if (item.type === "layout" && (item as any).uses) {
81
43
  // Process nested items in layout
82
44
  const layoutItem = item as any;
@@ -93,13 +55,9 @@ export function processItems(items: readonly AllUseItems[]): AllUseItems[] {
93
55
  /**
94
56
  * Create include() helper for composing URL patterns
95
57
  *
96
- * By default, include() IMMEDIATELY expands the nested patterns. This ensures
97
- * that routes from included patterns inherit the correct parent context
98
- * (the layout they're included in).
99
- *
100
- * With `lazy: true`, patterns are NOT expanded at definition time. Instead,
101
- * they're evaluated on first request that matches the prefix. This improves
102
- * cold start time for apps with many routes.
58
+ * All includes are lazy: the nested patterns are NOT expanded at definition
59
+ * time. Instead they are evaluated on the first request that matches the
60
+ * prefix, which improves cold start time for apps with many routes.
103
61
  */
104
62
  export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
105
63
  return (
@@ -107,9 +65,7 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
107
65
  patterns: UrlPatterns<TEnv>,
108
66
  options?: IncludeOptions,
109
67
  ): IncludeItem => {
110
- const store = getContext();
111
- const ctx = store.getStore();
112
- if (!ctx) throw new Error("include() must be called inside urls()");
68
+ const { ctx } = requireDslContext("include() must be called inside urls()");
113
69
 
114
70
  const explicitName = options?.name;
115
71
  const hasExplicitName = hasExplicitNameOption(options);
@@ -149,22 +105,32 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
149
105
  });
150
106
  }
151
107
 
152
- // Snapshot parent's counters so lazy manifest generation starts
153
- // at the correct index, preventing shortCode collisions with
154
- // sibling entries (e.g., BlogLayout and ArticlesLayout under NavLayout).
155
- const capturedCounters = { ...ctx.counters };
156
-
157
- // Reserve a layout slot in the parent's counter so sibling lazy includes
158
- // produce different shortCode indices for their root layout.
159
- // Without this, consecutive include() calls capture identical counters
160
- // and their first child layouts get the same shortCode (e.g., both M0L0L0),
161
- // causing the client partial-update diff to see no changes on navigation.
108
+ // Allocate an include-scope token for this include() call. The token is
109
+ // appended to the parent's shortCode prefix whenever the include's
110
+ // direct-descendant shortCodes are generated (see getShortCode in
111
+ // context.ts), partitioning the parent's counter namespace so routes
112
+ // inside an include cannot collide with siblings declared outside it.
113
+ //
114
+ // Scopes compose: a nested include inside an outer include with scope
115
+ // "I0" allocates against the `${parent.shortCode}I0_include` counter
116
+ // and produces scope "I0I0", "I0I1", etc.
117
+ const parentScope = ctx.includeScope ?? "";
118
+ let includeScope = parentScope;
162
119
  if (capturedParent?.shortCode) {
163
- const layoutCounterKey = `${capturedParent.shortCode}_layout`;
164
- ctx.counters[layoutCounterKey] ??= 0;
165
- ctx.counters[layoutCounterKey]++;
120
+ const includeCounterKey = `${capturedParent.shortCode}${parentScope}_include`;
121
+ ctx.counters[includeCounterKey] ??= 0;
122
+ const includeIdx = ctx.counters[includeCounterKey];
123
+ ctx.counters[includeCounterKey] = includeIdx + 1;
124
+ includeScope = `${parentScope}I${includeIdx}`;
166
125
  }
167
126
 
127
+ // Snapshot parent's counters AFTER allocating the include scope so lazy
128
+ // manifest generation starts with the same counter state this include
129
+ // observed — its descendants still get fresh per-scope counters because
130
+ // they key off `${parent.shortCode}${includeScope}_*` (not shared with
131
+ // siblings outside the include).
132
+ const capturedCounters = { ...ctx.counters };
133
+
168
134
  // Compute rootScoped at capture time, mirroring the logic in runWithPrefixes.
169
135
  // This ensures lazy evaluation restores the correct scope state.
170
136
  const parentRootScoped = ctx.rootScoped;
@@ -191,6 +157,7 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
191
157
  counters: capturedCounters,
192
158
  cacheProfiles: ctx.cacheProfiles,
193
159
  rootScoped: capturedRootScoped,
160
+ includeScope,
194
161
  },
195
162
  } as IncludeItem;
196
163
  };
package/src/urls/index.ts CHANGED
@@ -13,7 +13,6 @@ export type {
13
13
  UnnamedRoute,
14
14
  LocalOnlyInclude,
15
15
  PathOptions,
16
- PathDefinition,
17
16
  UrlPatterns,
18
17
  IncludeOptions,
19
18
  } from "./pattern-types.js";
@@ -22,8 +21,6 @@ export type {
22
21
  export type {
23
22
  ExtractRoutes,
24
23
  ExtractResponses,
25
- ExtractRouteNames,
26
- ExtractPathParams,
27
24
  ResponseError,
28
25
  ResponseEnvelope,
29
26
  RouteResponse,
@@ -37,7 +37,10 @@ import type {
37
37
  UseItems,
38
38
  } from "../route-types.js";
39
39
  import type { SearchSchema } from "../search-params.js";
40
- import type { PrerenderHandlerDefinition } from "../prerender.js";
40
+ import type {
41
+ PrerenderHandlerDefinition,
42
+ PassthroughHandlerDefinition,
43
+ } from "../prerender.js";
41
44
  import type { StaticHandlerDefinition } from "../static-handler.js";
42
45
  import type { InterceptWhenFn } from "../server/context";
43
46
  import type {
@@ -70,6 +73,7 @@ export type PathFn<TEnv> = <
70
73
  ctx: HandlerContext<TParams, TEnv, TSearch>,
71
74
  ) => ReactNode | Promise<ReactNode> | Response | Promise<Response>)
72
75
  | PrerenderHandlerDefinition<TParams>
76
+ | PassthroughHandlerDefinition<TParams, TEnv>
73
77
  | StaticHandlerDefinition<TParams>,
74
78
  optionsOrUse?: PathOptions<TName, TSearch> | (() => UseItems<RouteUseItem>),
75
79
  use?: () => UseItems<RouteUseItem>,
@@ -229,12 +233,27 @@ export type PathHelpers<TEnv> = {
229
233
  include: IncludeFn<TEnv>;
230
234
 
231
235
  /**
232
- * Define parallel routes that render simultaneously in named slots
236
+ * Define parallel routes that render simultaneously in named slots.
237
+ *
238
+ * A slot value can be a Handler / ReactNode / StaticHandlerDefinition
239
+ * (legacy form, broadcast use applies to every slot) or a slot descriptor
240
+ * `{ handler, use? }` whose `use` is scoped to that slot only. Per-slot
241
+ * merge order is `handler.use` → shared `use` → slot-local `use`, with
242
+ * narrowest scope winning for last-write-wins items like `loading()`.
233
243
  */
234
244
  parallel: <
235
245
  TSlots extends Record<
236
246
  `@${string}`,
237
- Handler<any, any, TEnv> | ReactNode | StaticHandlerDefinition
247
+ | Handler<any, any, TEnv>
248
+ | ReactNode
249
+ | StaticHandlerDefinition
250
+ | {
251
+ handler:
252
+ | Handler<any, any, TEnv>
253
+ | ReactNode
254
+ | StaticHandlerDefinition;
255
+ use?: () => ParallelUseItem[];
256
+ }
238
257
  >,
239
258
  >(
240
259
  slots: TSlots,
@@ -245,7 +264,7 @@ export type PathHelpers<TEnv> = {
245
264
  * Define an intercepting route for soft navigation
246
265
  * Note: routeName must match a named path() in this urlpatterns
247
266
  */
248
- intercept: keyof RSCRouter.GeneratedRouteMap extends never
267
+ intercept: keyof Rango.GeneratedRouteMap extends never
249
268
  ? (
250
269
  slotName: `@${string}`,
251
270
  routeName: string,
@@ -254,15 +273,26 @@ export type PathHelpers<TEnv> = {
254
273
  ) => InterceptItem
255
274
  : (
256
275
  slotName: `@${string}`,
257
- routeName: (keyof RSCRouter.GeneratedRouteMap & string) | `.${string}`,
276
+ routeName: (keyof Rango.GeneratedRouteMap & string) | `.${string}`,
258
277
  handler: ReactNode | Handler<any, any, TEnv>,
259
278
  use?: () => InterceptUseItem[],
260
279
  ) => InterceptItem;
261
280
 
262
281
  /**
263
- * Attach middleware to the current route/layout
282
+ * Attach middleware to the current route/layout, or wrap child segments
264
283
  */
265
- middleware: (...fns: MiddlewareFn<TEnv>[]) => MiddlewareItem;
284
+ middleware: {
285
+ (fn: MiddlewareFn<TEnv>): MiddlewareItem;
286
+ (
287
+ fn: MiddlewareFn<TEnv>,
288
+ children: () => UseItems<LayoutUseItem>,
289
+ ): MiddlewareItem;
290
+ (fns: MiddlewareFn<TEnv>[]): MiddlewareItem;
291
+ (
292
+ fns: MiddlewareFn<TEnv>[],
293
+ children: () => UseItems<LayoutUseItem>,
294
+ ): MiddlewareItem;
295
+ };
266
296
 
267
297
  /**
268
298
  * Control when a segment should revalidate during navigation
@@ -280,7 +310,10 @@ export type PathHelpers<TEnv> = {
280
310
  /**
281
311
  * Attach a loading component to the current route/layout
282
312
  */
283
- loading: (component: ReactNode, options?: { ssr?: boolean }) => LoadingItem;
313
+ loading: (
314
+ component: ReactNode | (() => ReactNode),
315
+ options?: { ssr?: boolean },
316
+ ) => LoadingItem;
284
317
 
285
318
  /**
286
319
  * Attach an error boundary to catch errors in this segment
@@ -317,7 +350,15 @@ export type PathHelpers<TEnv> = {
317
350
  };
318
351
 
319
352
  /**
320
- * Attach a ViewTransition boundary to the current segment or a group of routes
353
+ * Opt a route (or group of routes) into transition-driven navigation.
354
+ *
355
+ * Two independent layers: (1) startTransition, on all React versions, holds
356
+ * the previous content across a same-route nav (no skeleton flash) and is the
357
+ * precondition for any view transition; (2) on experimental React, an
358
+ * additional `<ViewTransition>` boundary cross-fades/morphs the swap. Pass
359
+ * `{ viewTransition: false }` to keep #1 without the router boundary. A view
360
+ * transition cannot fire without a startTransition. See
361
+ * skills/view-transitions for the startTransition x ViewTransition matrix.
321
362
  */
322
363
  transition: {
323
364
  (): TransitionItem;
@@ -1,21 +1,17 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { Handler } from "../types.js";
3
- import type {
4
- AllUseItems,
5
- RouteItem,
6
- RouteUseItem,
7
- UseItems,
8
- } from "../route-types.js";
3
+ import type { RouteItem, RouteUseItem, UseItems } from "../route-types.js";
9
4
  import {
10
- getContext,
11
5
  getUrlPrefix,
12
6
  getNamePrefix,
13
7
  getRootScoped,
8
+ requireDslContext,
14
9
  } from "../server/context";
15
- import { invariant } from "../errors";
10
+ import { invariant, DataNotFoundError } from "../errors";
16
11
  import { validateUserRouteName } from "../route-name.js";
17
12
  import {
18
13
  isPrerenderHandler,
14
+ isPassthroughHandler,
19
15
  type PrerenderHandlerDefinition,
20
16
  } from "../prerender.js";
21
17
  import {
@@ -34,35 +30,14 @@ import type {
34
30
  JsonResponsePathFn,
35
31
  TextResponsePathFn,
36
32
  } from "./path-helper-types.js";
37
-
38
- /**
39
- * Check if a value is a valid use item
40
- */
41
- const isValidUseItem = (item: any): item is AllUseItems | undefined | null => {
42
- return (
43
- typeof item === "undefined" ||
44
- item === null ||
45
- (item &&
46
- typeof item === "object" &&
47
- "type" in item &&
48
- [
49
- "layout",
50
- "route",
51
- "middleware",
52
- "revalidate",
53
- "parallel",
54
- "intercept",
55
- "loader",
56
- "loading",
57
- "errorBoundary",
58
- "notFoundBoundary",
59
- "when",
60
- "cache",
61
- "transition",
62
- "include",
63
- ].includes(item.type))
64
- );
65
- };
33
+ import {
34
+ resolveHandlerUse,
35
+ mergeHandlerUse,
36
+ } from "../route-definition/resolve-handler-use.js";
37
+ import {
38
+ emptySegmentBase,
39
+ runAndValidateUseItems,
40
+ } from "../route-definition/dsl-helpers.js";
66
41
 
67
42
  /**
68
43
  * Apply URL prefix to a pattern
@@ -107,9 +82,9 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
107
82
  optionsOrUse?: PathOptions | (() => UseItems<RouteUseItem>),
108
83
  maybeUse?: () => UseItems<RouteUseItem>,
109
84
  ): RouteItem => {
110
- const store = getContext();
111
- const ctx = store.getStore();
112
- if (!ctx) throw new Error("path() must be called inside urls()");
85
+ const { store, ctx } = requireDslContext(
86
+ "path() must be called inside urls()",
87
+ );
113
88
 
114
89
  invariant(
115
90
  !ctx.parent || ctx.parent.type !== "parallel",
@@ -142,6 +117,12 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
142
117
  use = maybeUse;
143
118
  }
144
119
 
120
+ // Merge handler.use() defaults with explicit use()
121
+ // Response routes (path.json, path.text, etc.) only allow middleware + cache
122
+ const handlerUseFn = resolveHandlerUse(handler);
123
+ const mountSite = resolveResponseType(options) ? "response" : "path";
124
+ const mergedUse = mergeHandlerUse(handlerUseFn, use, mountSite);
125
+
145
126
  // Get prefixes from context (set by include())
146
127
  const urlPrefix = getUrlPrefix();
147
128
  const namePrefix = getNamePrefix();
@@ -176,16 +157,34 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
176
157
  }
177
158
 
178
159
  // Ensure handler is always a function (wrap ReactNode or extract from prerender/static def)
160
+ // For prerender stubs (production builds where handler code is evicted),
161
+ // handler.handler is undefined — provide a notFound fallback so requests
162
+ // for non-prerendered params get 404 instead of "handler is not a function".
179
163
  const wrappedHandler: Handler<any, any, TEnv> =
180
164
  typeof handler === "function"
181
165
  ? (handler as Handler<any, any, TEnv>)
182
- : isPrerenderHandler(handler)
183
- ? (handler.handler as Handler<any, any, TEnv>)
184
- : isStaticHandler(handler)
185
- ? (handler.handler as Handler<any, any, TEnv>)
186
- : () => handler;
166
+ : isPassthroughHandler(handler)
167
+ ? typeof handler.prerenderDef.handler === "function"
168
+ ? (handler.prerenderDef.handler as Handler<any, any, TEnv>)
169
+ : () => {
170
+ throw new DataNotFoundError(
171
+ "No prerender data found for this route",
172
+ );
173
+ }
174
+ : isPrerenderHandler(handler)
175
+ ? typeof handler.handler === "function"
176
+ ? (handler.handler as Handler<any, any, TEnv>)
177
+ : () => {
178
+ throw new DataNotFoundError(
179
+ "No prerender data found for this route",
180
+ );
181
+ }
182
+ : isStaticHandler(handler)
183
+ ? (handler.handler as Handler<any, any, TEnv>)
184
+ : () => handler;
187
185
 
188
186
  const entry = {
187
+ ...emptySegmentBase(),
189
188
  id: namespace,
190
189
  shortCode: store.getShortCode("route"),
191
190
  type: "route" as const,
@@ -193,22 +192,20 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
193
192
  handler: wrappedHandler,
194
193
  // Store the PREFIXED pattern for route matching
195
194
  pattern: prefixedPattern,
196
- loading: undefined,
197
- middleware: [],
198
- revalidate: [],
199
- errorBoundary: [],
200
- notFoundBoundary: [],
201
- layout: [],
202
- parallel: [],
203
- intercept: [],
204
- loader: [],
205
195
  ...(urlPrefix ? { mountPath: urlPrefix } : {}),
206
- ...(isPrerenderHandler(handler)
196
+ ...(isPassthroughHandler(handler)
207
197
  ? {
208
198
  isPrerender: true as const,
209
- prerenderDef: handler as PrerenderHandlerDefinition,
199
+ prerenderDef: handler.prerenderDef as PrerenderHandlerDefinition,
200
+ isPassthrough: true as const,
201
+ liveHandler: handler.liveHandler as Handler<any, any, TEnv>,
210
202
  }
211
- : {}),
203
+ : isPrerenderHandler(handler)
204
+ ? {
205
+ isPrerender: true as const,
206
+ prerenderDef: handler as PrerenderHandlerDefinition,
207
+ }
208
+ : {}),
212
209
  ...(isStaticHandler(handler)
213
210
  ? {
214
211
  isStaticPrerender: true as const,
@@ -264,12 +261,15 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
264
261
  registerSearchSchema(routeName, options.search);
265
262
  }
266
263
 
267
- // Run use callback if provided
268
- if (use && typeof use === "function") {
269
- const result = store.run(namespace, entry, use)?.flat(3);
270
- invariant(
271
- Array.isArray(result) && result.every((item) => isValidUseItem(item)),
272
- `path() use() callback must return an array of use items [${namespace}]`,
264
+ // Run merged use callback (handler.use defaults + explicit use) if present
265
+ if (mergedUse) {
266
+ const result = runAndValidateUseItems(
267
+ store,
268
+ namespace,
269
+ entry,
270
+ mergedUse,
271
+ "path",
272
+ "use",
273
273
  );
274
274
  return { name: namespace, type: "route", uses: result } as RouteItem;
275
275
  }