@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  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 +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,764 @@
1
+ import type { ReactNode } from "react";
2
+ import type { Handle } from "../handle.js";
3
+ import type { ContextVar } from "../context-var.js";
4
+ import type { MiddlewareFn } from "../router/middleware.js";
5
+ import type { Theme } from "../theme/types.js";
6
+ import type { ScopedReverseFunction } from "../reverse.js";
7
+ import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
8
+ import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
9
+ import type {
10
+ DefaultEnv,
11
+ DefaultHandlerRouteMap,
12
+ DefaultReverseRouteMap,
13
+ DefaultRouteName,
14
+ DefaultVars,
15
+ } from "./global-namespace.js";
16
+ import type {
17
+ ExtractParams,
18
+ RouteDefinition,
19
+ ResolvedRouteMap,
20
+ } from "./route-config.js";
21
+ import type { LoaderDefinition } from "./loader-types.js";
22
+ import type { UseItems, HandlerUseItem } from "../route-types.js";
23
+ import type { RequestScope } from "./request-scope.js";
24
+
25
+ // Re-export MiddlewareFn for internal/advanced use
26
+ export type { MiddlewareFn } from "../router/middleware.js";
27
+
28
+ /**
29
+ * Create a scoped view of a route map by stripping a name prefix.
30
+ * Useful for handlers in modules mounted via include() -- use the local
31
+ * route name instead of the fully qualified global name.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * // Given GeneratedRouteMap: { "blog.index": "/blog"; "blog.post": "/blog/:postId"; ... }
36
+ * type BlogRoutes = ScopedRouteMap<"blog">;
37
+ * // Resolves to: { "index": "/blog"; "post": "/blog/:postId" }
38
+ *
39
+ * const handler: Handler<"post", BlogRoutes> = (ctx) => {
40
+ * ctx.params.postId // string
41
+ * };
42
+ * ```
43
+ */
44
+ export type ScopedRouteMap<
45
+ TPrefix extends string,
46
+ TMap = RSCRouter.GeneratedRouteMap,
47
+ > = {
48
+ [K in keyof TMap as K extends `${TPrefix}.${infer Rest}`
49
+ ? Rest
50
+ : never]: TMap[K];
51
+ };
52
+
53
+ /**
54
+ * Extract the search schema from a route map entry.
55
+ * - string value (old format): no search schema -> {}
56
+ * - { path, search } value: extract search
57
+ * - { path, response } value (no search): -> {}
58
+ */
59
+ /** Extract params from a route map entry (string pattern or { path } object). */
60
+ type ExtractParamsFromEntry<TEntry, TFallback> = TEntry extends string
61
+ ? ExtractParams<TEntry>
62
+ : TEntry extends { readonly path: infer P extends string }
63
+ ? ExtractParams<P>
64
+ : TFallback;
65
+
66
+ /** Extract search schema from a route map entry. */
67
+ type ExtractSearchFromEntry<TMap, TKey> = TKey extends keyof TMap
68
+ ? TMap[TKey] extends { readonly search: infer S extends SearchSchema }
69
+ ? S
70
+ : {}
71
+ : {};
72
+
73
+ type IsEmptyObject<T> = keyof T extends never ? true : false;
74
+
75
+ type AutofillParamsFromEntry<TEntry> = TEntry extends string
76
+ ? string extends TEntry
77
+ ? Record<string, string>
78
+ : Partial<ExtractParams<TEntry>>
79
+ : TEntry extends { readonly path: infer P extends string }
80
+ ? string extends P
81
+ ? Record<string, string>
82
+ : Partial<ExtractParams<P>>
83
+ : Record<string, string>;
84
+
85
+ type AutofillSearchFromEntry<TMap, TKey> = TKey extends keyof TMap
86
+ ? TMap[TKey] extends { readonly search: infer S extends SearchSchema }
87
+ ? ResolveSearchSchema<S>
88
+ : Record<string, unknown>
89
+ : Record<string, unknown>;
90
+
91
+ type AutofillAwareReverseFunction<TLocalRoutes, TGlobalRoutes> =
92
+ ScopedReverseFunction<TLocalRoutes, TGlobalRoutes> & {
93
+ <TName extends keyof TGlobalRoutes & string>(
94
+ name: TName,
95
+ params?: AutofillParamsFromEntry<TGlobalRoutes[TName]>,
96
+ search?: AutofillSearchFromEntry<TGlobalRoutes, TName>,
97
+ ): string;
98
+ <TName extends keyof TLocalRoutes & string>(
99
+ name: `.${TName}`,
100
+ params?: AutofillParamsFromEntry<TLocalRoutes[TName]>,
101
+ search?: AutofillSearchFromEntry<TLocalRoutes, TName>,
102
+ ): string;
103
+ };
104
+
105
+ type StrictLocalParamsWithExtras<TEntry> =
106
+ IsEmptyObject<ExtractParamsFromEntry<TEntry, {}>> extends true
107
+ ? Record<string, string>
108
+ : ExtractParamsFromEntry<TEntry, {}> & Record<string, string>;
109
+
110
+ // HandlerContext.reverse is the only reverse surface with runtime param autofill
111
+ // from the current matched request. Middleware/loaders/request context do not
112
+ // have the same local-route guarantees, so they keep plain ScopedReverseFunction.
113
+ //
114
+ // When a handler has an explicit local route map, enforce that local route
115
+ // params declared by that map are present while still allowing extra mount
116
+ // params to be passed through. Global names remain autofill-friendly because
117
+ // parent include() params are often unknown at the module definition site.
118
+ type StrictLocalAutofillGlobalReverseFunction<TLocalRoutes, TGlobalRoutes> =
119
+ ScopedReverseFunction<TLocalRoutes, TGlobalRoutes> & {
120
+ <TName extends keyof TGlobalRoutes & string>(
121
+ name: TName,
122
+ params?: AutofillParamsFromEntry<TGlobalRoutes[TName]>,
123
+ search?: AutofillSearchFromEntry<TGlobalRoutes, TName>,
124
+ ): string;
125
+ <TName extends keyof TLocalRoutes & string>(
126
+ name: `.${TName}`,
127
+ params: StrictLocalParamsWithExtras<TLocalRoutes[TName]>,
128
+ search?: AutofillSearchFromEntry<TLocalRoutes, TName>,
129
+ ): string;
130
+ };
131
+
132
+ export type Handler<
133
+ T extends
134
+ | keyof DefaultHandlerRouteMap
135
+ | `.${keyof TRouteMap & string}`
136
+ | `/${string}`
137
+ | Record<string, any> = {},
138
+ TRouteMap extends {} = DefaultHandlerRouteMap,
139
+ TEnv = DefaultEnv,
140
+ > = ((
141
+ ctx: HandlerContext<
142
+ T extends `.${infer Local}`
143
+ ? Local extends keyof TRouteMap
144
+ ? ExtractParamsFromEntry<
145
+ TRouteMap[Local],
146
+ T extends string ? ExtractParams<T> : T
147
+ >
148
+ : T extends string
149
+ ? ExtractParams<T>
150
+ : T
151
+ : T extends keyof DefaultHandlerRouteMap
152
+ ? ExtractParamsFromEntry<
153
+ DefaultHandlerRouteMap[T],
154
+ T extends string ? ExtractParams<T> : T
155
+ >
156
+ : T extends string
157
+ ? ExtractParams<T>
158
+ : T,
159
+ TEnv,
160
+ T extends `.${infer Local}`
161
+ ? ExtractSearchFromEntry<TRouteMap, Local>
162
+ : ExtractSearchFromEntry<DefaultHandlerRouteMap, T>,
163
+ TRouteMap extends DefaultHandlerRouteMap ? never : TRouteMap
164
+ >,
165
+ ) => ReactNode | Promise<ReactNode> | Response | Promise<Response>) & {
166
+ /** Composable default DSL items merged when the handler is mounted. */
167
+ use?: () => UseItems<HandlerUseItem>;
168
+ };
169
+
170
+ /**
171
+ * Context passed to handlers (Hono-inspired type-safe context)
172
+ *
173
+ * Provides type-safe access to:
174
+ * - Route params (from URL pattern)
175
+ * - Cleaned route URL (`url`, `searchParams`, `pathname` — no `_rsc*` params)
176
+ * - Original request (`request` — raw transport URL, headers, method, body)
177
+ * - Platform bindings (env.DB, env.KV, env.SECRETS)
178
+ * - Middleware variables (`get("user")`, `get("permissions")`)
179
+ * - Getter/setter for variables (get('user'), set('user', ...))
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * const handler = (ctx: HandlerContext<{ slug: string }, AppEnv>) => {
184
+ * ctx.params.slug // Route param (string)
185
+ * ctx.env.DB // Binding (D1Database)
186
+ * ctx.get('user') // Variable (User | undefined)
187
+ * ctx.set('user', {...}) // Setter
188
+ * ctx.url // Clean URL (no _rsc* params)
189
+ * ctx.searchParams // Clean params (no _rsc* params)
190
+ * ctx.request // Raw transport request (original URL intact)
191
+ * }
192
+ * ```
193
+ */
194
+ export type HandlerContext<
195
+ TParams = {},
196
+ TEnv = DefaultEnv,
197
+ TSearch extends SearchSchema = {},
198
+ TRouteMap = never,
199
+ > = RequestScope<TEnv> & {
200
+ /**
201
+ * Route parameters extracted from the URL pattern.
202
+ * Type-safe when using Handler<"/path/:param"> or Handler<{ param: string }>.
203
+ */
204
+ params: TParams;
205
+ /** @internal Phantom property for params type invariance. Prevents mounting handlers on wrong routes. */
206
+ readonly _paramCheck?: (params: TParams) => TParams;
207
+ /**
208
+ * True during build-time pre-rendering, false at runtime.
209
+ * Build-time collection and dev on-demand prerender use `true`.
210
+ * Live request rendering, including passthrough fallback, uses `false`.
211
+ */
212
+ build: boolean;
213
+ /**
214
+ * True when running in Vite dev mode, false during production build or
215
+ * live request rendering. Use this to branch on runtime mode without
216
+ * changing build semantics (e.g., skip expensive operations in dev).
217
+ */
218
+ dev: boolean;
219
+ /**
220
+ * Typed search parameters parsed from URL query string via the route's
221
+ * search schema. Empty object when no schema is defined.
222
+ */
223
+ search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
224
+ /**
225
+ * Type-safe getter for middleware variables.
226
+ * Preferred way to read middleware-injected variables.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const user = ctx.get("user"); // Type-safe!
231
+ * ```
232
+ */
233
+ get: {
234
+ <T>(contextVar: ContextVar<T>): T | undefined;
235
+ } & (<K extends keyof DefaultVars>(key: K) => DefaultVars[K]);
236
+ /**
237
+ * Type-safe setter for middleware variables.
238
+ * Use in middleware to pass data to handlers.
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * ctx.set("user", { id: "123", name: "John" }); // Type-safe!
243
+ * ctx.set(MyVar, { ... }); // Type-safe via ContextVar token!
244
+ * ```
245
+ */
246
+ set: {
247
+ <T>(
248
+ contextVar: ContextVar<T>,
249
+ value: T,
250
+ options?: { cache?: boolean },
251
+ ): void;
252
+ } & (<K extends keyof DefaultVars>(
253
+ key: K,
254
+ value: DefaultVars[K],
255
+ options?: { cache?: boolean },
256
+ ) => void);
257
+ /**
258
+ * Response headers. Headers set here are merged into the final response.
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * route("product", (ctx) => {
263
+ * ctx.headers.set("Cache-Control", "s-maxage=60");
264
+ * return <ProductPage />;
265
+ * });
266
+ * ```
267
+ */
268
+ headers: Headers;
269
+ /**
270
+ * Access loader data or push handle data.
271
+ *
272
+ * Available in route handlers, layout handlers, middleware, server actions,
273
+ * and server components rendered within the request context.
274
+ *
275
+ * For loaders: Returns a promise that resolves to the loader data.
276
+ * Loaders are executed in parallel and memoized per request.
277
+ * Prefer DSL `loader()` + client `useLoader()` over `ctx.use(Loader)` —
278
+ * DSL loaders are always fresh and cache-safe. Use `ctx.use(Loader)` only
279
+ * when you need loader data in the handler itself (e.g., to set context
280
+ * variables or make routing decisions).
281
+ *
282
+ * For handles: Returns a push function to add data for this segment.
283
+ * Handle data accumulates across all matched route segments.
284
+ * Push accepts: direct value, Promise, or async callback (executed immediately).
285
+ *
286
+ * @example
287
+ * ```typescript
288
+ * // Loader escape hatch — use when handler needs the data directly
289
+ * route("product", async (ctx) => {
290
+ * const { product } = await ctx.use(ProductLoader);
291
+ * ctx.set(Product, product); // make available to children
292
+ * return <ProductPage />;
293
+ * });
294
+ *
295
+ * // Handle usage - direct value
296
+ * route("shop", (ctx) => {
297
+ * const push = ctx.use(Breadcrumbs);
298
+ * push({ label: "Shop", href: "/shop" });
299
+ * return <ShopPage />;
300
+ * });
301
+ *
302
+ * // Handle usage - Promise
303
+ * route("product", (ctx) => {
304
+ * const push = ctx.use(Breadcrumbs);
305
+ * push(fetchProductBreadcrumb(ctx.params.id));
306
+ * return <ProductPage />;
307
+ * });
308
+ *
309
+ * // Handle usage - async callback (executed immediately)
310
+ * route("product", (ctx) => {
311
+ * const push = ctx.use(Breadcrumbs);
312
+ * push(async () => {
313
+ * const product = await db.getProduct(ctx.params.id);
314
+ * return { label: product.name, href: `/product/${product.id}` };
315
+ * });
316
+ * return <ProductPage />;
317
+ * });
318
+ * ```
319
+ */
320
+ use: {
321
+ <T, TLoaderParams = any>(
322
+ loader: LoaderDefinition<T, TLoaderParams>,
323
+ ): Promise<T>;
324
+ <TData, TAccumulated = TData[]>(
325
+ handle: Handle<TData, TAccumulated>,
326
+ ): (data: TData | Promise<TData> | (() => Promise<TData>)) => void;
327
+ };
328
+ /**
329
+ * Current theme (from cookie or default).
330
+ * Only available when theme is enabled in router config.
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * route("settings", (ctx) => {
335
+ * const currentTheme = ctx.theme; // "light" | "dark" | "system" | undefined
336
+ * return <SettingsPage theme={currentTheme} />;
337
+ * });
338
+ * ```
339
+ */
340
+ theme?: Theme;
341
+ /**
342
+ * Set the theme (only available when theme is enabled in router config).
343
+ * Sets a cookie with the new theme value.
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * route("settings", async (ctx) => {
348
+ * if (ctx.request.method === "POST") {
349
+ * const formData = await ctx.request.formData();
350
+ * const newTheme = formData.get("theme") as Theme;
351
+ * ctx.setTheme?.(newTheme);
352
+ * }
353
+ * return <SettingsPage />;
354
+ * });
355
+ * ```
356
+ */
357
+ setTheme?: (theme: Theme) => void;
358
+ /**
359
+ * Attach location state entries to this response.
360
+ * State is delivered to the client via history.pushState and accessible
361
+ * through the useLocationState() hook.
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * route("product", (ctx) => {
366
+ * ctx.setLocationState(ServerInfo({ data: "value" }));
367
+ * return <ProductPage />;
368
+ * });
369
+ * ```
370
+ */
371
+ setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
372
+ /**
373
+ * The matched route name, if the route has an explicit name.
374
+ * Undefined for unnamed routes (those without a `name` option in path()).
375
+ * Includes the namespace prefix from include() (e.g., "blog.post").
376
+ *
377
+ * @example
378
+ * ```typescript
379
+ * route("product", (ctx) => {
380
+ * ctx.routeName // "product"
381
+ * return <ProductPage />;
382
+ * });
383
+ * ```
384
+ */
385
+ routeName?: DefaultRouteName;
386
+ /**
387
+ * Generate URLs from route names.
388
+ *
389
+ * - `.name` -- local route, resolved within current include() scope
390
+ * - `name` -- global route, from the named-routes definition
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * ctx.reverse(".article", { slug: "hello" }) // Local: magazine.article
395
+ * ctx.reverse(".index") // Local: magazine.index
396
+ * ctx.reverse("magazine.index") // Global: magazine.index
397
+ * ctx.reverse("blog.post", { slug: "hello" }) // Global: blog.post
398
+ * ```
399
+ */
400
+ reverse: [TRouteMap] extends [never]
401
+ ? AutofillAwareReverseFunction<
402
+ Record<string, string>,
403
+ DefaultReverseRouteMap
404
+ >
405
+ : StrictLocalAutofillGlobalReverseFunction<
406
+ TRouteMap,
407
+ DefaultReverseRouteMap
408
+ >;
409
+ };
410
+
411
+ /**
412
+ * Internal handler context with additional props for router internals.
413
+ * Use `HandlerContext` for user-facing code.
414
+ * @internal
415
+ */
416
+ export type InternalHandlerContext<
417
+ TParams = {},
418
+ TEnv = DefaultEnv,
419
+ TSearch extends SearchSchema = {},
420
+ > = HandlerContext<TParams, TEnv, TSearch> & {
421
+ /** @internal Stub response for collecting headers/cookies. */
422
+ res: Response;
423
+ /** @internal Shared variable backing store for ctx.get()/ctx.set(). */
424
+ _variables: Record<string, any>;
425
+ /** Prerender-only control flow helper, attached when the runtime context supports it. */
426
+ passthrough?: () => unknown;
427
+ /** Current segment ID for handle data attribution. */
428
+ _currentSegmentId?: string;
429
+ /** Response type tag (json, text, html, etc.) for cache key differentiation. */
430
+ _responseType?: string;
431
+ /** Route name for cache key scoping (prevents cross-route collisions). */
432
+ _routeName?: string;
433
+ };
434
+
435
+ /**
436
+ * Generic params type - flexible object with string keys
437
+ * Users can narrow this by explicitly typing their params:
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * [revalidate('post')]: (({ currentParams, nextParams }: RevalidateParams<{ slug: string }>) => {
442
+ * currentParams.slug // typed as string
443
+ * return currentParams.slug !== nextParams.slug;
444
+ * })
445
+ * ```
446
+ */
447
+ export type GenericParams = { [key: string]: string | undefined };
448
+
449
+ /**
450
+ * Helper type for revalidation handler params
451
+ * Allows inline type annotation for stricter param typing
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * [revalidate('post')]: (params: RevalidateParams<{ slug: string }>) => {
456
+ * params.currentParams.slug // typed as string
457
+ * return params.defaultShouldRevalidate;
458
+ * }
459
+ * ```
460
+ */
461
+ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
462
+ ShouldRevalidateFn<TParams, TEnv>
463
+ >[0];
464
+
465
+ /**
466
+ * Should revalidate function signature (inspired by React Router)
467
+ *
468
+ * Determines whether a route segment should re-render during partial navigation.
469
+ * Multiple revalidation functions can be defined per route - they execute in order.
470
+ *
471
+ * **Return Types:**
472
+ * - `boolean` - Hard decision: immediately returns this value (short-circuits)
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
476
+ *
477
+ * **Execution Flow:**
478
+ * 1. Start with built-in `defaultShouldRevalidate` (true if params changed)
479
+ * 2. Execute global revalidators first, then route-specific
480
+ * 3. Hard decision (boolean): stop immediately and use that value
481
+ * 4. Soft decision (object): update suggestion and continue to next revalidator
482
+ * 5. Defer (`void` / `null` / `undefined`): leave suggestion unchanged and continue
483
+ * 6. If no hard decision was returned: use the final running suggestion
484
+ *
485
+ * @param args.currentParams - Previous route params (generic by default, can be narrowed)
486
+ * @param args.currentUrl - Previous URL
487
+ * @param args.nextParams - Next route params (generic by default, can be narrowed)
488
+ * @param args.nextUrl - Next URL
489
+ * @param args.defaultShouldRevalidate - Current suggestion (updated by soft decisions)
490
+ * @param args.context - App context (db, user, etc.)
491
+ * @param args.actionResult - Result from action (future support)
492
+ * @param args.formData - Form data from action (future support)
493
+ * @param args.formMethod - HTTP method from action (future support)
494
+ *
495
+ * @returns Hard decision (boolean), soft suggestion (object), or defer
496
+ * (`void` / `null` / `undefined`) to keep the running suggestion as-is.
497
+ *
498
+ * @example
499
+ * ```typescript
500
+ * // Hard decision - definitive answer
501
+ * [revalidate('post')]: ({ currentParams, nextParams }) => {
502
+ * return currentParams.slug !== nextParams.slug; // boolean - short-circuits
503
+ * }
504
+ *
505
+ * // Soft decision - allows downstream revalidators to override
506
+ * [revalidate('*', 'global')]: ({ defaultShouldRevalidate }) => {
507
+ * return { defaultShouldRevalidate: true }; // object - continues to next
508
+ * }
509
+ *
510
+ * // Explicit typing for stricter params
511
+ * [revalidate('post')]: ((params: RevalidateParams<{ slug: string }>) => {
512
+ * return params.currentParams.slug !== params.nextParams.slug;
513
+ * })
514
+ * ```
515
+ */
516
+ /**
517
+ * Revalidation function called during client-side navigation to decide whether
518
+ * a segment (layout, route, parallel slot, or loader) should be re-rendered.
519
+ *
520
+ * Return `true` to re-render, `false` to skip (keep client's current version),
521
+ * `{ defaultShouldRevalidate: boolean }` to update the running suggestion for
522
+ * downstream revalidators, or nothing (`void` / `null` / `undefined`) to defer
523
+ * to the current suggestion without changing it.
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * // Re-render only when a cart action happened or browser signals staleness
528
+ * revalidate(({ actionId, stale }) =>
529
+ * actionId?.includes("cart") || stale || false
530
+ * )
531
+ *
532
+ * // Always re-render when params change (default behavior made explicit)
533
+ * revalidate(({ defaultShouldRevalidate }) => defaultShouldRevalidate)
534
+ * ```
535
+ */
536
+ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
537
+ /** Route params from the page being navigated away from. */
538
+ currentParams: TParams;
539
+ /** Full URL of the page being navigated away from. */
540
+ currentUrl: URL;
541
+ /** Route params for the navigation target. */
542
+ nextParams: TParams;
543
+ /** Full URL of the navigation target. */
544
+ nextUrl: URL;
545
+ /**
546
+ * The router's default revalidation decision for this segment.
547
+ * `true` when params changed or the segment is new to the client.
548
+ * Return this when you want default behavior plus your own conditions.
549
+ */
550
+ defaultShouldRevalidate: boolean;
551
+ /** Full handler context — access to `ctx.use()`, `ctx.env`, `ctx.params`, etc. */
552
+ context: HandlerContext<TParams, TEnv>;
553
+
554
+ // ── Segment metadata (which segment is being evaluated) ──────────────
555
+
556
+ /** The type of segment being revalidated. */
557
+ segmentType: "layout" | "route" | "parallel";
558
+ /** Layout name (e.g., `"root"`, `"shop"`, `"auth"`). Only set for layout segments. */
559
+ layoutName?: string;
560
+ /** Slot name (e.g., `"@sidebar"`, `"@modal"`). Only set for parallel segments. */
561
+ slotName?: string;
562
+
563
+ // ── Action context (populated when revalidation is triggered by a server action) ──
564
+
565
+ /**
566
+ * Identifier of the server action that triggered revalidation.
567
+ * `undefined` during normal navigation (no action involved).
568
+ *
569
+ * Format: `"src/<path>#<exportName>"` — the file path is the source path
570
+ * relative to the project root, followed by `#` and the exported function name.
571
+ *
572
+ * This is stable and can be used for path-based matching to revalidate
573
+ * when any action in a module or directory fires:
574
+ *
575
+ * @example
576
+ * ```ts
577
+ * // Match a specific action
578
+ * revalidate(({ actionId }) => actionId === "src/actions/cart.ts#addToCart")
579
+ *
580
+ * // Match any action in the cart module
581
+ * revalidate(({ actionId }) => actionId?.includes("cart") ?? false)
582
+ *
583
+ * // Match any action under src/apps/store/actions/
584
+ * revalidate(({ actionId }) => actionId?.startsWith("src/apps/store/actions/") ?? false)
585
+ * ```
586
+ */
587
+ actionId?: string;
588
+ /** URL where the action was executed (the page the user was on when they triggered the action). */
589
+ actionUrl?: URL;
590
+ /** Return value from the action execution. Can be used to conditionally revalidate based on the action's outcome. */
591
+ actionResult?: any;
592
+ /** FormData from the action request body. Only set for form-based actions (not inline `"use server"` actions). */
593
+ formData?: FormData;
594
+ /** HTTP method: `"GET"` for navigation, `"POST"` for server actions. */
595
+ method?: string;
596
+
597
+ // ── Route identity ───────────────────────────────────────────────────
598
+
599
+ /** Route name of the navigation target. Alias for `toRouteName`. */
600
+ routeName?: DefaultRouteName;
601
+ /**
602
+ * Route name being navigated away from.
603
+ * `undefined` for unnamed internal routes (those without a `name` option).
604
+ */
605
+ fromRouteName?: DefaultRouteName;
606
+ /**
607
+ * Route name being navigated to.
608
+ * `undefined` for unnamed internal routes (those without a `name` option).
609
+ */
610
+ toRouteName?: DefaultRouteName;
611
+
612
+ // ── Staleness signal ─────────────────────────────────────────────────
613
+
614
+ /**
615
+ * `true` when the browser signals that data may be stale — typically because
616
+ * a server action was executed in this or another tab (`_rsc_stale` header).
617
+ *
618
+ * This is NOT segment cache staleness (loaders are never segment-cached).
619
+ * Use this to decide whether loader data should be re-fetched after an
620
+ * action that may have mutated backend state.
621
+ */
622
+ stale?: boolean;
623
+ }) => boolean | { defaultShouldRevalidate: boolean } | null | void;
624
+
625
+ // MiddlewareFn is imported from "../router/middleware.js" and re-exported
626
+
627
+ /**
628
+ * Extract all route keys from a route definition (includes flattened nested routes)
629
+ */
630
+ export type RouteKeys<T extends RouteDefinition> = keyof ResolvedRouteMap<T> &
631
+ string;
632
+
633
+ /**
634
+ * Valid layout value - component or handler function
635
+ * Note: Arrays are not supported. Use separate layout() declarations with unique names instead.
636
+ */
637
+ type LayoutValue<TEnv = any> = ReactNode | Handler<any, any, TEnv>;
638
+
639
+ /**
640
+ * Helper to extract params from a route key using the resolved (flattened) route map
641
+ */
642
+ export type ExtractRouteParams<
643
+ T extends RouteDefinition,
644
+ K extends string,
645
+ > = K extends keyof ResolvedRouteMap<T>
646
+ ? ResolvedRouteMap<T>[K] extends string
647
+ ? ExtractParams<ResolvedRouteMap<T>[K]>
648
+ : GenericParams
649
+ : GenericParams;
650
+
651
+ /**
652
+ * Handlers object that maps route names to handler functions with type-safe string patterns
653
+ */
654
+ export type HandlersForRouteMap<T extends RouteDefinition, TEnv = any> = {
655
+ // Route handlers - type-safe params extracted from route patterns
656
+ [K in RouteKeys<T>]?: Handler<ExtractRouteParams<T, K & string>, any, TEnv>;
657
+ } & {
658
+ // Layout patterns: $layout.{routeName}.{layoutName}
659
+ [K in `$layout.${RouteKeys<T> | "*"}.${string}`]?: LayoutValue<TEnv>;
660
+ } & {
661
+ // Parallel route patterns: $parallel.{routeName}.{parallelName}
662
+ [K in `$parallel.${RouteKeys<T>}.${string}`]?: Record<
663
+ `@${string}`,
664
+ Handler<
665
+ K extends `$parallel.${infer RouteKey}.${string}`
666
+ ? RouteKey extends RouteKeys<T>
667
+ ? ExtractRouteParams<T, RouteKey & string>
668
+ : GenericParams
669
+ : GenericParams,
670
+ any,
671
+ TEnv
672
+ >
673
+ >;
674
+ } & {
675
+ // Global parallel routes (with '*') use GenericParams
676
+ [K in `$parallel.${"*"}.${string}`]?: Record<
677
+ `@${string}`,
678
+ Handler<GenericParams, any, TEnv>
679
+ >;
680
+ } & {
681
+ // Middleware patterns: $middleware.{routeName}.{middlewareName}
682
+ [K in `$middleware.${RouteKeys<T> | "*"}.${string}`]?: MiddlewareFn<
683
+ TEnv,
684
+ GenericParams
685
+ >[];
686
+ } & {
687
+ // Route revalidate patterns: $revalidate.route.{routeName}.{revalidateName}
688
+ [K in `$revalidate.route.${RouteKeys<T> | "*"}.${string}`]?: ShouldRevalidateFn<
689
+ GenericParams,
690
+ TEnv
691
+ >;
692
+ } & {
693
+ // Layout revalidate patterns: $revalidate.layout.{routeName}.{layoutName}.{revalidateName}
694
+ [K in `$revalidate.layout.${RouteKeys<T> | "*"}.${string}.${string}`]?: ShouldRevalidateFn<
695
+ GenericParams,
696
+ TEnv
697
+ >;
698
+ } & {
699
+ // Parallel revalidate patterns: $revalidate.parallel.{routeName}.{parallelName}.{slotName}.{revalidateName}
700
+ [K in `$revalidate.parallel.${RouteKeys<T> | "*"}.${string}.${string}.${string}`]?: ShouldRevalidateFn<
701
+ GenericParams,
702
+ TEnv
703
+ >;
704
+ };
705
+
706
+ /**
707
+ * Revalidation function with typed params
708
+ *
709
+ * @template T - Params object
710
+ * @template TEnv - Environment type
711
+ *
712
+ * @example
713
+ * ```typescript
714
+ * const revalidate: Revalidate<{ slug: string }> = ({ currentParams, nextParams }) => {
715
+ * return currentParams.slug !== nextParams.slug;
716
+ * }
717
+ * ```
718
+ */
719
+ export type Revalidate<
720
+ T = GenericParams,
721
+ TEnv = DefaultEnv,
722
+ > = ShouldRevalidateFn<T, TEnv>;
723
+
724
+ /**
725
+ * Middleware function with typed params and environment
726
+ *
727
+ * @template TParams - Params object (defaults to generic)
728
+ * @template TEnv - Environment type (defaults to global RSCRouter.Env)
729
+ *
730
+ * Note: Middleware cannot directly use route names for params typing because
731
+ * middleware is defined during router setup, before RegisteredRoutes is populated.
732
+ * Use ExtractParams<"/path/:id"> for typed params from a path pattern.
733
+ *
734
+ * @example
735
+ * ```typescript
736
+ * // Basic middleware (uses global RSCRouter.Env via module augmentation)
737
+ * const middleware: Middleware = async (ctx, next) => {
738
+ * ctx.set("user", { id: "123" }); // Type-safe!
739
+ * await next();
740
+ * }
741
+ *
742
+ * // With explicit params (most common)
743
+ * const middleware: Middleware<{ id: string }> = async (ctx, next) => {
744
+ * console.log(ctx.params.id);
745
+ * await next();
746
+ * }
747
+ *
748
+ * // With params from path pattern
749
+ * const middleware: Middleware<ExtractParams<"/products/:id">> = async (ctx, next) => {
750
+ * console.log(ctx.params.id);
751
+ * await next();
752
+ * }
753
+ *
754
+ * // With both params and explicit env
755
+ * const middleware: Middleware<{ id: string }, AppEnv> = async (ctx, next) => {
756
+ * ctx.set("user", { id: ctx.params.id });
757
+ * await next();
758
+ * }
759
+ * ```
760
+ */
761
+ export type Middleware<
762
+ TParams = GenericParams,
763
+ TEnv = DefaultEnv,
764
+ > = MiddlewareFn<TEnv, TParams>;