@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
@@ -96,7 +96,7 @@ export function Static<TParams extends Record<string, any>>(
96
96
 
97
97
  if (!id) {
98
98
  throw new Error(
99
- "[rsc-router] Static: missing $$id. " +
99
+ "[rango] Static: missing $$id. " +
100
100
  "Ensure the exposeInternalIds Vite plugin is configured.",
101
101
  );
102
102
  }
@@ -7,7 +7,7 @@
7
7
  * ```typescript
8
8
  * // In env.ts or env.d.ts
9
9
  * declare global {
10
- * namespace RSCRouter {
10
+ * namespace Rango {
11
11
  * interface Env extends AppBindings {}
12
12
  * interface Vars extends AppVariables {}
13
13
  * }
@@ -18,7 +18,7 @@
18
18
  * ```
19
19
  */
20
20
  declare global {
21
- namespace RSCRouter {
21
+ namespace Rango {
22
22
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
23
23
  interface Env {
24
24
  // Empty by default - users augment with their bindings (e.g., { DB: D1Database })
@@ -44,13 +44,25 @@ declare global {
44
44
  }
45
45
 
46
46
  /**
47
- * Get registered routes or fallback to generic Record<string, string>
48
- * When RSCRouter.RegisteredRoutes is augmented, provides autocomplete for route names
49
- * When not augmented, allows any string (no autocomplete)
47
+ * Route map for path-validation surfaces (`href`, `ValidPaths`, `PathResponse`).
48
+ *
49
+ * Resolution order:
50
+ * 1. `RegisteredRoutes` — manual `extends typeof router.routeMap`; richest map,
51
+ * the only one carrying response-route payload metadata.
52
+ * 2. `GeneratedRouteMap` — auto-wired by `router.named-routes.gen.ts`; path +
53
+ * search only. Lets `rango generate` alone enable `href()`/`ValidPaths` path
54
+ * checking without a manual `RegisteredRoutes` declaration.
55
+ * 3. `Record<string, string>` — permissive fallback when nothing is registered.
56
+ *
57
+ * Referencing `GeneratedRouteMap` here is cycle-safe: it is declared in the
58
+ * standalone gen file with no imports, unlike `RegisteredRoutes extends typeof
59
+ * router.routeMap`.
50
60
  */
51
- export type GetRegisteredRoutes = keyof RSCRouter.RegisteredRoutes extends never
52
- ? Record<string, string>
53
- : RSCRouter.RegisteredRoutes;
61
+ export type GetRegisteredRoutes = keyof Rango.RegisteredRoutes extends never
62
+ ? keyof Rango.GeneratedRouteMap extends never
63
+ ? Record<string, string>
64
+ : Rango.GeneratedRouteMap
65
+ : Rango.RegisteredRoutes;
54
66
 
55
67
  /**
56
68
  * Default route map for reverse() surfaces.
@@ -58,12 +70,11 @@ export type GetRegisteredRoutes = keyof RSCRouter.RegisteredRoutes extends never
58
70
  * cycles, but falls back to RegisteredRoutes for manual augmentation and then to
59
71
  * a permissive record when no route types are available.
60
72
  */
61
- export type DefaultReverseRouteMap =
62
- keyof RSCRouter.GeneratedRouteMap extends never
63
- ? keyof RSCRouter.RegisteredRoutes extends never
64
- ? Record<string, string>
65
- : RSCRouter.RegisteredRoutes
66
- : RSCRouter.GeneratedRouteMap;
73
+ export type DefaultReverseRouteMap = keyof Rango.GeneratedRouteMap extends never
74
+ ? keyof Rango.RegisteredRoutes extends never
75
+ ? Record<string, string>
76
+ : Rango.RegisteredRoutes
77
+ : Rango.GeneratedRouteMap;
67
78
 
68
79
  /**
69
80
  * Default route map for Handler type.
@@ -71,30 +82,32 @@ export type DefaultReverseRouteMap =
71
82
  * circular dependencies: router.tsx -> urls.tsx -> handler.tsx -> RegisteredRoutes -> router.tsx.
72
83
  * GeneratedRouteMap is declared in a standalone gen file with no imports.
73
84
  */
74
- export type DefaultHandlerRouteMap =
75
- keyof RSCRouter.GeneratedRouteMap extends never
76
- ? {}
77
- : RSCRouter.GeneratedRouteMap;
85
+ export type DefaultHandlerRouteMap = keyof Rango.GeneratedRouteMap extends never
86
+ ? {}
87
+ : Rango.GeneratedRouteMap;
78
88
 
79
89
  /**
80
- * Default environment type - uses global augmentation if available, any otherwise
90
+ * Default environment type - uses global augmentation if available.
91
+ *
92
+ * Falls back to `unknown` (not `any`) when `Rango.Env` is not augmented, so
93
+ * an app that forgets to register its bindings gets a compile error on
94
+ * `ctx.env.SOMETHING` instead of silently losing all env type-checking. Augment
95
+ * `Rango.Env` to type `ctx.env`.
81
96
  */
82
- export type DefaultEnv = keyof RSCRouter.Env extends never
83
- ? any
84
- : RSCRouter.Env;
97
+ export type DefaultEnv = keyof Rango.Env extends never ? unknown : Rango.Env;
85
98
 
86
99
  /**
87
100
  * Default variables type - uses global augmentation if available, Record<string, any> otherwise
88
101
  */
89
- export type DefaultVars = keyof RSCRouter.Vars extends never
102
+ export type DefaultVars = keyof Rango.Vars extends never
90
103
  ? Record<string, any>
91
- : RSCRouter.Vars;
104
+ : Rango.Vars;
92
105
 
93
106
  /**
94
107
  * Default route name type for public `routeName` on contexts.
95
108
  * When GeneratedRouteMap is augmented, narrows to the known route names.
96
109
  * Otherwise falls back to `string` for untyped usage.
97
110
  */
98
- export type DefaultRouteName = keyof RSCRouter.GeneratedRouteMap extends never
111
+ export type DefaultRouteName = keyof Rango.GeneratedRouteMap extends never
99
112
  ? string
100
- : keyof RSCRouter.GeneratedRouteMap & string;
113
+ : keyof Rango.GeneratedRouteMap & string;
@@ -20,6 +20,7 @@ import type {
20
20
  } from "./route-config.js";
21
21
  import type { LoaderDefinition } from "./loader-types.js";
22
22
  import type { UseItems, HandlerUseItem } from "../route-types.js";
23
+ import type { RequestScope } from "./request-scope.js";
23
24
 
24
25
  // Re-export MiddlewareFn for internal/advanced use
25
26
  export type { MiddlewareFn } from "../router/middleware.js";
@@ -42,7 +43,7 @@ export type { MiddlewareFn } from "../router/middleware.js";
42
43
  */
43
44
  export type ScopedRouteMap<
44
45
  TPrefix extends string,
45
- TMap = RSCRouter.GeneratedRouteMap,
46
+ TMap = Rango.GeneratedRouteMap,
46
47
  > = {
47
48
  [K in keyof TMap as K extends `${TPrefix}.${infer Rest}`
48
49
  ? Rest
@@ -195,7 +196,7 @@ export type HandlerContext<
195
196
  TEnv = DefaultEnv,
196
197
  TSearch extends SearchSchema = {},
197
198
  TRouteMap = never,
198
- > = {
199
+ > = RequestScope<TEnv> & {
199
200
  /**
200
201
  * Route parameters extracted from the URL pattern.
201
202
  * Type-safe when using Handler<"/path/:param"> or Handler<{ param: string }>.
@@ -215,44 +216,11 @@ export type HandlerContext<
215
216
  * changing build semantics (e.g., skip expensive operations in dev).
216
217
  */
217
218
  dev: boolean;
218
- /**
219
- * The original incoming Request object (transport URL intact).
220
- * Use `ctx.url` / `ctx.searchParams` for application logic — those have
221
- * internal `_rsc*` params stripped. `ctx.request` preserves the raw URL
222
- * for cases where you need original headers, method, or body.
223
- */
224
- request: Request;
225
- /**
226
- * Query parameters from the URL (system params like `_rsc*` are filtered).
227
- * Always a standard URLSearchParams instance.
228
- */
229
- searchParams: URLSearchParams;
230
219
  /**
231
220
  * Typed search parameters parsed from URL query string via the route's
232
221
  * search schema. Empty object when no schema is defined.
233
222
  */
234
223
  search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
235
- /**
236
- * The pathname portion of the request URL.
237
- */
238
- pathname: string;
239
- /**
240
- * The full URL object (with internal `_rsc*` params stripped).
241
- * Use this for application logic — routing, link generation, display.
242
- */
243
- url: URL;
244
- /**
245
- * The original request URL with all parameters intact, including
246
- * internal `_rsc*` transport params. Use `ctx.url` for application
247
- * logic — this is only needed for advanced cases like debugging
248
- * or custom cache keying.
249
- */
250
- originalUrl: URL;
251
- /**
252
- * Platform bindings (DB, KV, secrets, etc.).
253
- * Access resources like `ctx.env.DB`, `ctx.env.KV`.
254
- */
255
- env: TEnv;
256
224
  /**
257
225
  * Type-safe getter for middleware variables.
258
226
  * Preferred way to read middleware-injected variables.
@@ -503,13 +471,16 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
503
471
  * **Return Types:**
504
472
  * - `boolean` - Hard decision: immediately returns this value (short-circuits)
505
473
  * - `{ defaultShouldRevalidate: boolean }` - Soft decision: updates suggestion for next revalidator
474
+ * - `void` / `null` / `undefined` - Defer to the current suggestion (no opinion); the
475
+ * loop continues to the next revalidator without changing the running default
506
476
  *
507
477
  * **Execution Flow:**
508
478
  * 1. Start with built-in `defaultShouldRevalidate` (true if params changed)
509
479
  * 2. Execute global revalidators first, then route-specific
510
480
  * 3. Hard decision (boolean): stop immediately and use that value
511
481
  * 4. Soft decision (object): update suggestion and continue to next revalidator
512
- * 5. If all return soft decisions: use the final suggestion
482
+ * 5. Defer (`void` / `null` / `undefined`): leave suggestion unchanged and continue
483
+ * 6. If no hard decision was returned: use the final running suggestion
513
484
  *
514
485
  * @param args.currentParams - Previous route params (generic by default, can be narrowed)
515
486
  * @param args.currentUrl - Previous URL
@@ -521,7 +492,8 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
521
492
  * @param args.formData - Form data from action (future support)
522
493
  * @param args.formMethod - HTTP method from action (future support)
523
494
  *
524
- * @returns Hard decision (boolean) or soft suggestion (object)
495
+ * @returns Hard decision (boolean), soft suggestion (object), or defer
496
+ * (`void` / `null` / `undefined`) to keep the running suggestion as-is.
525
497
  *
526
498
  * @example
527
499
  * ```typescript
@@ -541,19 +513,34 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
541
513
  * })
542
514
  * ```
543
515
  */
516
+ /**
517
+ * A reference to a server action, used by `isAction()` in a revalidate predicate.
518
+ *
519
+ * Either a directly imported action (`import { addToCart }`) or a namespace
520
+ * import of an action module (`import * as CartActions`). Matching resolves the
521
+ * action's build-injected id (`path#export`) — the same identity the router uses
522
+ * for `actionId` — so a renamed or moved action breaks at compile time instead
523
+ * of silently failing to match.
524
+ */
525
+ export type ActionRef =
526
+ | ((...args: never[]) => unknown)
527
+ | Record<string, unknown>;
528
+
544
529
  /**
545
530
  * Revalidation function called during client-side navigation to decide whether
546
531
  * a segment (layout, route, parallel slot, or loader) should be re-rendered.
547
532
  *
548
533
  * Return `true` to re-render, `false` to skip (keep client's current version),
549
- * or `{ defaultShouldRevalidate: boolean }` to override the default for
550
- * downstream segments.
534
+ * `{ defaultShouldRevalidate: boolean }` to update the running suggestion for
535
+ * downstream revalidators, or nothing (`void` / `null` / `undefined`) to defer
536
+ * to the current suggestion without changing it.
551
537
  *
552
538
  * @example
553
539
  * ```ts
554
- * // Re-render only when a cart action happened or browser signals staleness
540
+ * // Re-render when a cart action happened or the browser signals staleness;
541
+ * // defer otherwise (|| undefined) so the segment default still applies
555
542
  * revalidate(({ actionId, stale }) =>
556
- * actionId?.includes("cart") || stale || false
543
+ * actionId?.includes("cart") || stale || undefined
557
544
  * )
558
545
  *
559
546
  * // Always re-render when params change (default behavior made explicit)
@@ -580,8 +567,11 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
580
567
 
581
568
  // ── Segment metadata (which segment is being evaluated) ──────────────
582
569
 
583
- /** The type of segment being revalidated. */
584
- segmentType: "layout" | "route" | "parallel";
570
+ /**
571
+ * The type of segment being revalidated. `"loader"` is passed to revalidate
572
+ * functions attached to a `loader(Fn, () => [revalidate(...)])` registration.
573
+ */
574
+ segmentType: "layout" | "route" | "parallel" | "loader";
585
575
  /** Layout name (e.g., `"root"`, `"shop"`, `"auth"`). Only set for layout segments. */
586
576
  layoutName?: string;
587
577
  /** Slot name (e.g., `"@sidebar"`, `"@modal"`). Only set for parallel segments. */
@@ -597,21 +587,49 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
597
587
  * relative to the project root, followed by `#` and the exported function name.
598
588
  *
599
589
  * This is stable and can be used for path-based matching to revalidate
600
- * when any action in a module or directory fires:
590
+ * when any action in a module or directory fires. Prefer `|| undefined`
591
+ * (defer to the segment default / downstream revalidators) over `?? false`
592
+ * (hard short-circuit that suppresses the default and ends the chain):
601
593
  *
602
594
  * @example
603
595
  * ```ts
604
596
  * // Match a specific action
605
- * revalidate(({ actionId }) => actionId === "src/actions/cart.ts#addToCart")
597
+ * revalidate(({ actionId }) => actionId === "src/actions/cart.ts#addToCart" || undefined)
606
598
  *
607
599
  * // Match any action in the cart module
608
- * revalidate(({ actionId }) => actionId?.includes("cart") ?? false)
600
+ * revalidate(({ actionId }) => actionId?.includes("cart") || undefined)
609
601
  *
610
602
  * // Match any action under src/apps/store/actions/
611
- * revalidate(({ actionId }) => actionId?.startsWith("src/apps/store/actions/") ?? false)
603
+ * revalidate(({ actionId }) => actionId?.startsWith("src/apps/store/actions/") || undefined)
612
604
  * ```
613
605
  */
614
606
  actionId?: string;
607
+ /**
608
+ * Typed, rename-safe action matching. Returns `true` when the action that
609
+ * triggered this revalidation is one of the given references — or, for a
610
+ * namespace import (`import * as CartActions`), any export of that module —
611
+ * and `false` otherwise (including plain navigation with no action).
612
+ *
613
+ * Prefer this over hand-written `actionId` substring matches: it resolves the
614
+ * action's stable `path#export` id from the imported reference, so a rename is
615
+ * a type error in one place instead of silent drift across consumers. It
616
+ * resolves the reference the same way the action boundary derives `actionId`
617
+ * (`$id ?? $$id`), so it matches in both dev and production.
618
+ *
619
+ * Returns a raw boolean, so for the common "revalidate on match, else defer"
620
+ * intent combine with `|| undefined`:
621
+ *
622
+ * @example
623
+ * ```ts
624
+ * import { addToCart, removeFromCart } from "./actions/cart";
625
+ * import * as CartActions from "./actions/cart";
626
+ *
627
+ * revalidate((ctx) => ctx.isAction(addToCart) || undefined); // one action
628
+ * revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
629
+ * revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any in the module
630
+ * ```
631
+ */
632
+ isAction: (...actions: ActionRef[]) => boolean;
615
633
  /** URL where the action was executed (the page the user was on when they triggered the action). */
616
634
  actionUrl?: URL;
617
635
  /** Return value from the action execution. Can be used to conditionally revalidate based on the action's outcome. */
@@ -647,7 +665,7 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
647
665
  * action that may have mutated backend state.
648
666
  */
649
667
  stale?: boolean;
650
- }) => boolean | { defaultShouldRevalidate: boolean };
668
+ }) => boolean | { defaultShouldRevalidate: boolean } | null | void;
651
669
 
652
670
  // MiddlewareFn is imported from "../router/middleware.js" and re-exported
653
671
 
@@ -752,7 +770,7 @@ export type Revalidate<
752
770
  * Middleware function with typed params and environment
753
771
  *
754
772
  * @template TParams - Params object (defaults to generic)
755
- * @template TEnv - Environment type (defaults to global RSCRouter.Env)
773
+ * @template TEnv - Environment type (defaults to global Rango.Env)
756
774
  *
757
775
  * Note: Middleware cannot directly use route names for params typing because
758
776
  * middleware is defined during router setup, before RegisteredRoutes is populated.
@@ -760,7 +778,7 @@ export type Revalidate<
760
778
  *
761
779
  * @example
762
780
  * ```typescript
763
- * // Basic middleware (uses global RSCRouter.Env via module augmentation)
781
+ * // Basic middleware (uses global Rango.Env via module augmentation)
764
782
  * const middleware: Middleware = async (ctx, next) => {
765
783
  * ctx.set("user", { id: "123" }); // Type-safe!
766
784
  * await next();
@@ -42,6 +42,7 @@ export type {
42
42
  GenericParams,
43
43
  RevalidateParams,
44
44
  ShouldRevalidateFn,
45
+ ActionRef,
45
46
  RouteKeys,
46
47
  ExtractRouteParams,
47
48
  HandlersForRouteMap,
@@ -3,11 +3,13 @@ import type { Handle } from "../handle.js";
3
3
  import type { MiddlewareFn } from "../router/middleware.js";
4
4
  import type { ScopedReverseFunction } from "../reverse.js";
5
5
  import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
6
+ import type { UseItems, LoaderUseItem } from "../route-types.js";
6
7
  import type {
7
8
  DefaultEnv,
8
9
  DefaultReverseRouteMap,
9
10
  DefaultVars,
10
11
  } from "./global-namespace.js";
12
+ import type { RequestScope } from "./request-scope.js";
11
13
 
12
14
  /**
13
15
  * Context passed to loader functions during execution
@@ -39,7 +41,7 @@ export type LoaderContext<
39
41
  TEnv = DefaultEnv,
40
42
  TBody = unknown,
41
43
  TSearch extends SearchSchema = {},
42
- > = {
44
+ > = RequestScope<TEnv> & {
43
45
  params: TParams;
44
46
  /**
45
47
  * Route params extracted from the URL pattern match (server-side only).
@@ -48,12 +50,7 @@ export type LoaderContext<
48
50
  * resource scoping.
49
51
  */
50
52
  routeParams: Record<string, string>;
51
- request: Request;
52
- searchParams: URLSearchParams;
53
53
  search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
54
- pathname: string;
55
- url: URL;
56
- env: TEnv;
57
54
  get: {
58
55
  <T>(contextVar: ContextVar<T>): T | undefined;
59
56
  } & (<K extends keyof DefaultVars>(key: K) => DefaultVars[K]);
@@ -75,9 +72,12 @@ export type LoaderContext<
75
72
  * **Experimental.** Wait for all non-loader segments to settle.
76
73
  *
77
74
  * After the returned promise resolves, handle data is available via
78
- * `ctx.use(handle)`. Only supported in DSL loaders on non-streaming
79
- * trees (no `loading()`). Throws if called from a handler-invoked
80
- * loader or when the tree uses streaming.
75
+ * `ctx.use(handle)`. Supported in DSL loaders, including on streaming
76
+ * trees that use `loading()` the barrier waits for the streaming
77
+ * handlers to finish pushing before it resolves. Throws if called from a
78
+ * handler-invoked loader, or if a handler is already awaiting this loader
79
+ * via `ctx.use()` (that would deadlock — use a loader-to-loader
80
+ * dependency instead).
81
81
  *
82
82
  * @example
83
83
  * ```typescript
@@ -207,4 +207,6 @@ export type LoaderDefinition<
207
207
  __brand: "loader";
208
208
  $$id: string; // Injected by Vite plugin (exposeInternalIds) - unique identifier
209
209
  fn?: LoaderFn<T, TParams, any>; // Optional - server-side only, stored in registry for RSC
210
+ /** Composable default DSL items merged when the loader is mounted. */
211
+ use?: () => UseItems<LoaderUseItem>;
210
212
  };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * RequestScope: the fields every user-facing context shares.
3
+ *
4
+ * A handler, middleware, loader, response handler, and the ALS-bound
5
+ * RequestContext are all different phases of the same request, and they
6
+ * all carry the same set of request-scoped capabilities: the raw Request,
7
+ * the parsed URL pair (`url` is cleaned of internal `_rsc*` params,
8
+ * `originalUrl` retains them), pathname/searchParams, platform bindings
9
+ * (`env`), and two escape hatches for work that outlives the response
10
+ * (`waitUntil`) or needs the raw Cloudflare runtime object
11
+ * (`executionContext`).
12
+ *
13
+ * Each public context type intersects `RequestScope<TEnv>` with its own
14
+ * phase-specific fields (e.g. `params`/`reverse` on HandlerContext,
15
+ * `headers`/`header()` on MiddlewareContext). That keeps platform surface
16
+ * in one place and lets the next runtime escape hatch we need land in
17
+ * one file instead of four.
18
+ */
19
+
20
+ import type { DefaultEnv } from "./global-namespace.js";
21
+
22
+ /**
23
+ * Minimal subset of Cloudflare Workers' ExecutionContext that the router
24
+ * uses. Defined locally so the package does not depend on
25
+ * `@cloudflare/workers-types`. Consumers that want the full type can cast.
26
+ *
27
+ * On non-Cloudflare runtimes (Node, dev server, tests), this is undefined
28
+ * — portable apps should prefer `ctx.waitUntil(...)`, which degrades
29
+ * gracefully. `ctx.executionContext` is the escape hatch for libraries
30
+ * (MCP, Durable Object routing, etc.) that type their arguments as the
31
+ * raw ExecutionContext.
32
+ */
33
+ export interface ExecutionContext {
34
+ waitUntil(promise: Promise<any>): void;
35
+ passThroughOnException(): void;
36
+ }
37
+
38
+ /**
39
+ * Fallback `waitUntil` body used when no Cloudflare `ExecutionContext`
40
+ * is available (Node, dev, tests). Runs the work fire-and-forget and
41
+ * logs errors so they don't silently swallow.
42
+ *
43
+ * Exported so every `waitUntil` call site degrades identically instead
44
+ * of inventing its own fallback policy.
45
+ */
46
+ export function fireAndForgetWaitUntil(fn: () => Promise<void>): void {
47
+ fn().catch((err) =>
48
+ console.error("[waitUntil] Background task failed:", err),
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Fields present on every user-facing request context.
54
+ *
55
+ * @template TEnv - Platform bindings type (Cloudflare env, etc.).
56
+ */
57
+ export interface RequestScope<TEnv = DefaultEnv> {
58
+ /**
59
+ * The original incoming Request object (transport URL intact).
60
+ * Use `url` / `searchParams` for application logic — those have
61
+ * internal `_rsc*` params stripped. `request` preserves the raw URL
62
+ * when you need original headers, method, or body.
63
+ */
64
+ request: Request;
65
+
66
+ /**
67
+ * The request URL with internal `_rsc*` transport params stripped.
68
+ * Use this for routing, link generation, and display.
69
+ */
70
+ url: URL;
71
+
72
+ /**
73
+ * The original request URL with all parameters intact, including
74
+ * internal `_rsc*` transport params. Use `url` for application logic
75
+ * — this is only needed for advanced cases like debugging or custom
76
+ * cache keying.
77
+ */
78
+ originalUrl: URL;
79
+
80
+ /** URL pathname (same as `url.pathname`). */
81
+ pathname: string;
82
+
83
+ /**
84
+ * Query parameters from the URL (system params like `_rsc*` are
85
+ * filtered). Always a standard `URLSearchParams` instance.
86
+ */
87
+ searchParams: URLSearchParams;
88
+
89
+ /**
90
+ * Platform bindings (DB, KV, secrets, etc.). On Cloudflare Workers
91
+ * these are the `env` object passed to the Worker's `fetch()` handler.
92
+ */
93
+ env: TEnv;
94
+
95
+ /**
96
+ * Schedule work to run after the response is sent.
97
+ * On Cloudflare Workers, delegates to `executionContext.waitUntil()`.
98
+ * On Node / dev / tests, runs as fire-and-forget with error logging.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * ctx.waitUntil(async () => {
103
+ * await cacheStore.set(key, data, ttl);
104
+ * });
105
+ * ```
106
+ */
107
+ waitUntil(fn: () => Promise<void>): void;
108
+
109
+ /**
110
+ * Raw Cloudflare Workers `ExecutionContext`, when running on a
111
+ * Cloudflare-compatible runtime. Undefined elsewhere.
112
+ *
113
+ * Escape hatch for libraries that type their arguments as
114
+ * `ExecutionContext` (MCP `fetch`, `routeAgentRequest`, etc.).
115
+ * For the common "do work after the response" case, prefer
116
+ * `ctx.waitUntil(...)` — it is platform-neutral.
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * path.any("/mcp", (ctx) =>
121
+ * emailMcp.fetch(ctx.request, ctx.env, ctx.executionContext!),
122
+ * );
123
+ * ```
124
+ */
125
+ executionContext?: ExecutionContext;
126
+ }
@@ -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
  /**
@@ -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
  /**
@@ -56,13 +73,20 @@ export interface ResolvedSegment {
56
73
  // Intercept loader fields (for streaming loader data in parallel segments)
57
74
  loaderDataPromise?: Promise<any[]> | any[]; // Loader data promise or resolved array
58
75
  loaderIds?: string[]; // IDs ($$id) of loaders for this segment
59
- parallelLoaderSources?: any[]; // Internal: preserves stable aggregate promise across renders
60
76
  // Error-specific fields
61
77
  error?: ErrorInfo; // For error segments: the error information
62
78
  // NotFound-specific fields
63
79
  notFoundInfo?: NotFoundInfo; // For notFound segments: the not found information
64
80
  // Mount path from include() scope, used for MountContext.Provider wrapping
65
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;
66
90
  }
67
91
 
68
92
  /**
@@ -117,6 +141,15 @@ export interface MatchResult {
117
141
  segments: ResolvedSegment[];
118
142
  matched: string[];
119
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[];
120
153
  /**
121
154
  * Merged route params from all matched segments
122
155
  * Available for use by the handler after route matching