@rangojs/router 0.0.0-experimental.259 → 0.0.0-experimental.26

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 (225) hide show
  1. package/README.md +294 -28
  2. package/dist/bin/rango.js +355 -47
  3. package/dist/vite/index.js +1658 -1239
  4. package/package.json +3 -3
  5. package/skills/cache-guide/SKILL.md +9 -5
  6. package/skills/caching/SKILL.md +4 -4
  7. package/skills/document-cache/SKILL.md +2 -2
  8. package/skills/hooks/SKILL.md +40 -29
  9. package/skills/host-router/SKILL.md +218 -0
  10. package/skills/intercept/SKILL.md +79 -0
  11. package/skills/layout/SKILL.md +62 -2
  12. package/skills/loader/SKILL.md +229 -15
  13. package/skills/middleware/SKILL.md +109 -30
  14. package/skills/parallel/SKILL.md +57 -2
  15. package/skills/prerender/SKILL.md +189 -19
  16. package/skills/rango/SKILL.md +1 -2
  17. package/skills/response-routes/SKILL.md +3 -3
  18. package/skills/route/SKILL.md +44 -3
  19. package/skills/router-setup/SKILL.md +80 -3
  20. package/skills/theme/SKILL.md +5 -4
  21. package/skills/typesafety/SKILL.md +59 -16
  22. package/skills/use-cache/SKILL.md +16 -2
  23. package/src/__internal.ts +1 -1
  24. package/src/bin/rango.ts +56 -19
  25. package/src/browser/action-coordinator.ts +97 -0
  26. package/src/browser/event-controller.ts +29 -48
  27. package/src/browser/history-state.ts +80 -0
  28. package/src/browser/intercept-utils.ts +1 -1
  29. package/src/browser/link-interceptor.ts +19 -3
  30. package/src/browser/merge-segment-loaders.ts +9 -2
  31. package/src/browser/navigation-bridge.ts +66 -443
  32. package/src/browser/navigation-client.ts +34 -62
  33. package/src/browser/navigation-store.ts +4 -33
  34. package/src/browser/navigation-transaction.ts +295 -0
  35. package/src/browser/partial-update.ts +103 -151
  36. package/src/browser/prefetch/cache.ts +67 -0
  37. package/src/browser/prefetch/fetch.ts +137 -0
  38. package/src/browser/prefetch/observer.ts +65 -0
  39. package/src/browser/prefetch/policy.ts +42 -0
  40. package/src/browser/prefetch/queue.ts +88 -0
  41. package/src/browser/rango-state.ts +112 -0
  42. package/src/browser/react/Link.tsx +154 -44
  43. package/src/browser/react/NavigationProvider.tsx +32 -0
  44. package/src/browser/react/context.ts +6 -0
  45. package/src/browser/react/filter-segment-order.ts +11 -0
  46. package/src/browser/react/index.ts +2 -6
  47. package/src/browser/react/location-state-shared.ts +29 -11
  48. package/src/browser/react/location-state.ts +6 -4
  49. package/src/browser/react/nonce-context.ts +23 -0
  50. package/src/browser/react/shallow-equal.ts +27 -0
  51. package/src/browser/react/use-action.ts +23 -45
  52. package/src/browser/react/use-client-cache.ts +5 -3
  53. package/src/browser/react/use-handle.ts +21 -64
  54. package/src/browser/react/use-navigation.ts +7 -32
  55. package/src/browser/react/use-params.ts +5 -34
  56. package/src/browser/react/use-pathname.ts +2 -3
  57. package/src/browser/react/use-router.ts +3 -6
  58. package/src/browser/react/use-search-params.ts +2 -1
  59. package/src/browser/react/use-segments.ts +75 -114
  60. package/src/browser/response-adapter.ts +73 -0
  61. package/src/browser/rsc-router.tsx +46 -22
  62. package/src/browser/scroll-restoration.ts +10 -7
  63. package/src/browser/server-action-bridge.ts +458 -405
  64. package/src/browser/types.ts +21 -35
  65. package/src/browser/validate-redirect-origin.ts +29 -0
  66. package/src/build/generate-manifest.ts +38 -13
  67. package/src/build/generate-route-types.ts +4 -0
  68. package/src/build/index.ts +1 -0
  69. package/src/build/route-trie.ts +19 -3
  70. package/src/build/route-types/codegen.ts +13 -4
  71. package/src/build/route-types/include-resolution.ts +13 -0
  72. package/src/build/route-types/per-module-writer.ts +15 -3
  73. package/src/build/route-types/router-processing.ts +170 -18
  74. package/src/build/runtime-discovery.ts +13 -1
  75. package/src/cache/background-task.ts +34 -0
  76. package/src/cache/cache-key-utils.ts +44 -0
  77. package/src/cache/cache-policy.ts +125 -0
  78. package/src/cache/cache-runtime.ts +136 -123
  79. package/src/cache/cache-scope.ts +76 -83
  80. package/src/cache/cf/cf-cache-store.ts +12 -7
  81. package/src/cache/document-cache.ts +93 -69
  82. package/src/cache/handle-capture.ts +81 -0
  83. package/src/cache/index.ts +0 -15
  84. package/src/cache/memory-segment-store.ts +43 -69
  85. package/src/cache/profile-registry.ts +43 -8
  86. package/src/cache/read-through-swr.ts +134 -0
  87. package/src/cache/segment-codec.ts +140 -117
  88. package/src/cache/taint.ts +30 -3
  89. package/src/cache/types.ts +1 -115
  90. package/src/client.rsc.tsx +0 -1
  91. package/src/client.tsx +53 -76
  92. package/src/errors.ts +6 -1
  93. package/src/handle.ts +1 -1
  94. package/src/handles/MetaTags.tsx +5 -2
  95. package/src/host/cookie-handler.ts +8 -3
  96. package/src/host/index.ts +0 -3
  97. package/src/host/router.ts +14 -1
  98. package/src/href-client.ts +3 -1
  99. package/src/index.rsc.ts +53 -10
  100. package/src/index.ts +73 -43
  101. package/src/loader.rsc.ts +12 -4
  102. package/src/loader.ts +8 -0
  103. package/src/prerender/store.ts +60 -18
  104. package/src/prerender.ts +76 -18
  105. package/src/reverse.ts +11 -7
  106. package/src/root-error-boundary.tsx +30 -26
  107. package/src/route-definition/dsl-helpers.ts +9 -6
  108. package/src/route-definition/index.ts +0 -3
  109. package/src/route-definition/redirect.ts +15 -3
  110. package/src/route-map-builder.ts +38 -2
  111. package/src/route-name.ts +53 -0
  112. package/src/route-types.ts +7 -0
  113. package/src/router/content-negotiation.ts +1 -1
  114. package/src/router/debug-manifest.ts +16 -3
  115. package/src/router/handler-context.ts +96 -17
  116. package/src/router/intercept-resolution.ts +6 -4
  117. package/src/router/lazy-includes.ts +4 -0
  118. package/src/router/loader-resolution.ts +6 -11
  119. package/src/router/logging.ts +100 -3
  120. package/src/router/manifest.ts +32 -3
  121. package/src/router/match-api.ts +62 -54
  122. package/src/router/match-context.ts +3 -0
  123. package/src/router/match-handlers.ts +185 -11
  124. package/src/router/match-middleware/background-revalidation.ts +65 -85
  125. package/src/router/match-middleware/cache-lookup.ts +78 -10
  126. package/src/router/match-middleware/cache-store.ts +2 -0
  127. package/src/router/match-pipelines.ts +8 -43
  128. package/src/router/match-result.ts +0 -9
  129. package/src/router/metrics.ts +233 -13
  130. package/src/router/middleware-types.ts +34 -39
  131. package/src/router/middleware.ts +290 -130
  132. package/src/router/pattern-matching.ts +61 -10
  133. package/src/router/prerender-match.ts +36 -6
  134. package/src/router/preview-match.ts +7 -1
  135. package/src/router/revalidation.ts +61 -2
  136. package/src/router/router-context.ts +15 -0
  137. package/src/router/router-interfaces.ts +158 -40
  138. package/src/router/router-options.ts +223 -1
  139. package/src/router/router-registry.ts +5 -2
  140. package/src/router/segment-resolution/fresh.ts +165 -242
  141. package/src/router/segment-resolution/helpers.ts +263 -0
  142. package/src/router/segment-resolution/loader-cache.ts +102 -98
  143. package/src/router/segment-resolution/revalidation.ts +394 -272
  144. package/src/router/segment-resolution/static-store.ts +2 -2
  145. package/src/router/segment-resolution.ts +1 -3
  146. package/src/router/segment-wrappers.ts +3 -0
  147. package/src/router/telemetry-otel.ts +299 -0
  148. package/src/router/telemetry.ts +300 -0
  149. package/src/router/timeout.ts +148 -0
  150. package/src/router/trie-matching.ts +20 -2
  151. package/src/router/types.ts +7 -1
  152. package/src/router.ts +203 -18
  153. package/src/rsc/handler-context.ts +13 -2
  154. package/src/rsc/handler.ts +489 -438
  155. package/src/rsc/helpers.ts +125 -5
  156. package/src/rsc/index.ts +0 -20
  157. package/src/rsc/loader-fetch.ts +84 -42
  158. package/src/rsc/manifest-init.ts +3 -2
  159. package/src/rsc/origin-guard.ts +141 -0
  160. package/src/rsc/progressive-enhancement.ts +245 -19
  161. package/src/rsc/response-route-handler.ts +347 -0
  162. package/src/rsc/rsc-rendering.ts +47 -43
  163. package/src/rsc/runtime-warnings.ts +42 -0
  164. package/src/rsc/server-action.ts +166 -66
  165. package/src/rsc/ssr-setup.ts +128 -0
  166. package/src/rsc/types.ts +20 -2
  167. package/src/search-params.ts +38 -23
  168. package/src/server/context.ts +61 -7
  169. package/src/server/cookie-store.ts +190 -0
  170. package/src/server/fetchable-loader-store.ts +11 -6
  171. package/src/server/handle-store.ts +84 -12
  172. package/src/server/loader-registry.ts +11 -46
  173. package/src/server/request-context.ts +275 -49
  174. package/src/server.ts +6 -0
  175. package/src/ssr/index.tsx +67 -28
  176. package/src/static-handler.ts +7 -0
  177. package/src/theme/ThemeProvider.tsx +6 -1
  178. package/src/theme/index.ts +4 -18
  179. package/src/theme/theme-context.ts +1 -28
  180. package/src/theme/theme-script.ts +2 -1
  181. package/src/types/cache-types.ts +6 -1
  182. package/src/types/error-types.ts +3 -0
  183. package/src/types/global-namespace.ts +22 -0
  184. package/src/types/handler-context.ts +103 -16
  185. package/src/types/index.ts +1 -1
  186. package/src/types/loader-types.ts +9 -6
  187. package/src/types/route-config.ts +17 -26
  188. package/src/types/route-entry.ts +28 -0
  189. package/src/types/segments.ts +0 -5
  190. package/src/urls/include-helper.ts +49 -8
  191. package/src/urls/index.ts +1 -0
  192. package/src/urls/path-helper-types.ts +30 -12
  193. package/src/urls/path-helper.ts +17 -2
  194. package/src/urls/pattern-types.ts +21 -1
  195. package/src/urls/response-types.ts +29 -7
  196. package/src/urls/type-extraction.ts +23 -15
  197. package/src/use-loader.tsx +27 -9
  198. package/src/vite/discovery/bundle-postprocess.ts +32 -52
  199. package/src/vite/discovery/discover-routers.ts +52 -26
  200. package/src/vite/discovery/prerender-collection.ts +58 -41
  201. package/src/vite/discovery/route-types-writer.ts +7 -7
  202. package/src/vite/discovery/state.ts +7 -7
  203. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  204. package/src/vite/index.ts +10 -51
  205. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  206. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  207. package/src/vite/plugins/expose-internal-ids.ts +4 -3
  208. package/src/vite/plugins/refresh-cmd.ts +65 -0
  209. package/src/vite/plugins/use-cache-transform.ts +91 -3
  210. package/src/vite/plugins/version-plugin.ts +188 -18
  211. package/src/vite/rango.ts +61 -36
  212. package/src/vite/router-discovery.ts +173 -100
  213. package/src/vite/utils/prerender-utils.ts +81 -0
  214. package/src/vite/utils/shared-utils.ts +19 -9
  215. package/skills/testing/SKILL.md +0 -226
  216. package/src/browser/lru-cache.ts +0 -61
  217. package/src/browser/react/prefetch.ts +0 -27
  218. package/src/browser/request-controller.ts +0 -164
  219. package/src/cache/memory-store.ts +0 -253
  220. package/src/href-context.ts +0 -33
  221. package/src/route-definition/route-function.ts +0 -119
  222. package/src/router.gen.ts +0 -6
  223. package/src/static-handler.gen.ts +0 -5
  224. package/src/urls.gen.ts +0 -8
  225. /package/{CLAUDE.md → AGENTS.md} +0 -0
@@ -7,24 +7,6 @@ export type DocumentProps = {
7
7
  children: ReactNode;
8
8
  };
9
9
 
10
- /**
11
- * @deprecated RouterEnv is no longer needed. Pass bindings directly as TEnv
12
- * to createRouter<TEnv>() and declare RSCRouter.Vars for variables.
13
- *
14
- * Migration:
15
- * // Before:
16
- * type AppEnv = RouterEnv<AppBindings, AppVariables>;
17
- * createRouter<AppEnv>();
18
- *
19
- * // After:
20
- * createRouter<AppBindings>();
21
- * declare global { namespace RSCRouter { interface Vars extends AppVariables {} } }
22
- */
23
- export interface RouterEnv<TBindings = {}, TVariables = {}> {
24
- Bindings: TBindings;
25
- Variables: TVariables;
26
- }
27
-
28
10
  /**
29
11
  * Parse constraint values into a union type
30
12
  * "a|b|c" -> "a" | "b" | "c"
@@ -42,17 +24,26 @@ type ParseConstraint<T extends string> =
42
24
  * - :param(a|b)? -> { name: "param", optional: true, type: "a" | "b" }
43
25
  */
44
26
  type ExtractParamInfo<T extends string> =
45
- // Optional + constrained: :param(a|b)?
46
- T extends `${infer Name}(${infer Constraint})?`
27
+ // Optional + constrained (with optional suffix): :param(a|b)?suffix
28
+ T extends `${infer Name}(${infer Constraint})?${string}`
47
29
  ? { name: Name; optional: true; type: ParseConstraint<Constraint> }
48
- : // Constrained only: :param(a|b)
49
- T extends `${infer Name}(${infer Constraint})`
30
+ : // Constrained (with optional suffix): :param(a|b)suffix
31
+ T extends `${infer Name}(${infer Constraint})${string}`
50
32
  ? { name: Name; optional: false; type: ParseConstraint<Constraint> }
51
- : // Optional only: :param?
52
- T extends `${infer Name}?`
33
+ : // Optional (with optional suffix): :param?suffix
34
+ T extends `${infer Name}?${string}`
53
35
  ? { name: Name; optional: true; type: string }
54
- : // Required: :param
55
- { name: T; optional: false; type: string };
36
+ : // Param with dot-suffix: :param.html
37
+ T extends `${infer Name}.${string}`
38
+ ? { name: Name; optional: false; type: string }
39
+ : // Param with dash-suffix: :param-slug
40
+ T extends `${infer Name}-${string}`
41
+ ? { name: Name; optional: false; type: string }
42
+ : // Param with tilde-suffix: :param~v2
43
+ T extends `${infer Name}~${string}`
44
+ ? { name: Name; optional: false; type: string }
45
+ : // Required: :param (no suffix)
46
+ { name: T; optional: false; type: string };
56
47
 
57
48
  /**
58
49
  * Build param object from info
@@ -8,6 +8,10 @@ export interface LazyIncludeContext {
8
8
  urlPrefix: string;
9
9
  namePrefix: string | undefined;
10
10
  parent: unknown; // EntryData - avoid circular import
11
+ cacheProfiles?: Record<
12
+ string,
13
+ import("../cache/profile-registry.js").CacheProfile
14
+ >;
11
15
  }
12
16
 
13
17
  /**
@@ -37,6 +41,14 @@ export interface RouteEntry<TEnv = any> {
37
41
  * If not specified for a route, defaults to pattern-based detection
38
42
  */
39
43
  trailingSlash?: Record<string, TrailingSlashMode>;
44
+ /**
45
+ * Supported handler shapes:
46
+ * - sync: () => Array<AllUseItems>
47
+ * - lazy import: () => Promise<{ default: () => Array<AllUseItems> }>
48
+ * - lazy function: () => Promise<() => Array<AllUseItems>>
49
+ *
50
+ * Direct Promise<Array> is NOT supported and rejected at runtime.
51
+ */
40
52
  handler: () =>
41
53
  | Array<AllUseItems>
42
54
  | Promise<{ default: () => Array<AllUseItems> }>
@@ -49,6 +61,12 @@ export interface RouteEntry<TEnv = any> {
49
61
  */
50
62
  prerenderRouteKeys?: Set<string>;
51
63
 
64
+ /**
65
+ * Route keys in this entry that use `{ passthrough: true }`.
66
+ * Used by the non-trie match path to set the `pt` flag.
67
+ */
68
+ passthroughRouteKeys?: Set<string>;
69
+
52
70
  // === Lazy evaluation fields ===
53
71
 
54
72
  /**
@@ -71,4 +89,14 @@ export interface RouteEntry<TEnv = any> {
71
89
  * For lazy entries: whether patterns have been evaluated
72
90
  */
73
91
  lazyEvaluated?: boolean;
92
+
93
+ /**
94
+ * Cache profiles for DSL-time cache("profileName") resolution.
95
+ * Set on all entries (lazy and non-lazy) so loadManifest() can
96
+ * propagate them into the HelperContext Store.
97
+ */
98
+ cacheProfiles?: Record<
99
+ string,
100
+ import("../cache/profile-registry.js").CacheProfile
101
+ >;
74
102
  }
@@ -124,11 +124,6 @@ export interface MatchResult {
124
124
  * Used by ctx.reverse() for local name resolution.
125
125
  */
126
126
  routeName?: string;
127
- /**
128
- * Server-Timing header value (only present when debugPerformance is enabled)
129
- * Can be added to response headers for DevTools integration
130
- */
131
- serverTiming?: string;
132
127
  /**
133
128
  * State of named slots for this route match
134
129
  * Key is slot name (e.g., "@modal"), value is slot state
@@ -4,17 +4,37 @@ import {
4
4
  runWithPrefixes,
5
5
  getUrlPrefix,
6
6
  getNamePrefix,
7
+ getRootScoped,
7
8
  } from "../server/context";
9
+ import {
10
+ INTERNAL_INCLUDE_SCOPE_PREFIX,
11
+ validateUserRouteName,
12
+ } from "../route-name.js";
8
13
  import type { UrlPatterns, IncludeOptions } from "./pattern-types.js";
9
14
  import type { IncludeFn } from "./path-helper-types.js";
10
15
 
16
+ function hasExplicitNameOption(options: IncludeOptions | undefined): boolean {
17
+ return !!options && Object.prototype.hasOwnProperty.call(options, "name");
18
+ }
19
+
20
+ function allocateInternalIncludeScopeId(
21
+ counters: Record<string, number>,
22
+ ): string {
23
+ const key = "__include_scope__";
24
+ const index = counters[key] ?? 0;
25
+ counters[key] = index + 1;
26
+ return `${INTERNAL_INCLUDE_SCOPE_PREFIX}${index}`;
27
+ }
28
+
11
29
  /**
12
30
  * Process an IncludeItem by executing its nested patterns with prefixes
13
31
  * This expands the include into actual route registrations
14
32
  */
15
33
  function processIncludeItem(item: IncludeItem): AllUseItems[] {
16
- const { prefix, patterns, options } = item;
17
- const namePrefix = options?.name;
34
+ const { prefix, patterns } = item;
35
+ const namePrefix =
36
+ (item as IncludeItem & { _lazyContext?: { namePrefix?: string } })
37
+ ._lazyContext?.namePrefix ?? item.options?.name;
18
38
 
19
39
  // Execute the nested patterns' handler with URL and name prefixes
20
40
  // The urlPrefix being set tells nested urls() to skip RootLayout wrapping
@@ -91,7 +111,11 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
91
111
  const ctx = store.getStore();
92
112
  if (!ctx) throw new Error("include() must be called inside urls()");
93
113
 
94
- const namePrefix = options?.name;
114
+ const explicitName = options?.name;
115
+ const hasExplicitName = hasExplicitNameOption(options);
116
+ if (hasExplicitName && explicitName) {
117
+ validateUserRouteName(explicitName);
118
+ }
95
119
  const name = `$include_${prefix.replace(/[/:*?]/g, "_")}`;
96
120
 
97
121
  // Capture context for deferred evaluation
@@ -103,11 +127,16 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
103
127
  ? capturedUrlPrefix + prefix.slice(1)
104
128
  : capturedUrlPrefix + prefix
105
129
  : prefix;
106
- const fullNamePrefix = namePrefix
107
- ? capturedNamePrefix
108
- ? `${capturedNamePrefix}.${namePrefix}`
109
- : namePrefix
110
- : capturedNamePrefix;
130
+ const internalScope = !hasExplicitName
131
+ ? allocateInternalIncludeScopeId(ctx.counters)
132
+ : undefined;
133
+ const nextSegment = hasExplicitName ? explicitName : internalScope;
134
+ const fullNamePrefix =
135
+ nextSegment !== undefined && nextSegment !== ""
136
+ ? capturedNamePrefix
137
+ ? `${capturedNamePrefix}.${nextSegment}`
138
+ : nextSegment
139
+ : capturedNamePrefix;
111
140
 
112
141
  // Track this include for build-time manifest generation
113
142
  if (ctx.trackedIncludes) {
@@ -136,6 +165,16 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
136
165
  ctx.counters[layoutCounterKey]++;
137
166
  }
138
167
 
168
+ // Compute rootScoped at capture time, mirroring the logic in runWithPrefixes.
169
+ // This ensures lazy evaluation restores the correct scope state.
170
+ const parentRootScoped = ctx.rootScoped;
171
+ const capturedRootScoped =
172
+ nextSegment === ""
173
+ ? (parentRootScoped ?? true)
174
+ : nextSegment !== undefined
175
+ ? (parentRootScoped ?? false)
176
+ : parentRootScoped;
177
+
139
178
  // All includes are lazy - patterns are evaluated on first matching request
140
179
  // This improves cold start time significantly for large route sets
141
180
  return {
@@ -150,6 +189,8 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
150
189
  namePrefix: fullNamePrefix,
151
190
  parent: capturedParent,
152
191
  counters: capturedCounters,
192
+ cacheProfiles: ctx.cacheProfiles,
193
+ rootScoped: capturedRootScoped,
153
194
  },
154
195
  } as IncludeItem;
155
196
  };
package/src/urls/index.ts CHANGED
@@ -11,6 +11,7 @@ export {
11
11
  // Pattern types
12
12
  export type {
13
13
  UnnamedRoute,
14
+ LocalOnlyInclude,
14
15
  PathOptions,
15
16
  PathDefinition,
16
17
  UrlPatterns,
@@ -47,6 +47,7 @@ import type {
47
47
  } from "./response-types.js";
48
48
  import type {
49
49
  UnnamedRoute,
50
+ LocalOnlyInclude,
50
51
  PathOptions,
51
52
  UrlPatterns,
52
53
  IncludeOptions,
@@ -149,7 +150,7 @@ export type TextResponsePathFn<TEnv> = <
149
150
  export type IncludeFn<TEnv> = <
150
151
  TRoutes extends Record<string, any>,
151
152
  const TUrlPrefix extends string,
152
- const TNamePrefix extends string = never,
153
+ const TNamePrefix extends string = LocalOnlyInclude,
153
154
  TResponses extends Record<string, unknown> = Record<string, unknown>,
154
155
  >(
155
156
  prefix: TUrlPrefix,
@@ -205,14 +206,24 @@ export type PathHelpers<TEnv> = {
205
206
  };
206
207
 
207
208
  /**
208
- * Include nested URL patterns with optional name prefix
209
+ * Include nested URL patterns under a URL prefix.
209
210
  *
210
- * ```typescript
211
- * // Without name - routes keep local names
212
- * include("/blog", blogPatterns)
211
+ * The `name` option controls how child route names appear in the
212
+ * global route map and generated types:
213
213
  *
214
- * // With name - routes are prefixed (e.g., "index" -> "blog.index")
214
+ * ```typescript
215
+ * // Named — children become "blog.index", "blog.post", etc.
216
+ * // Visible in generated types and globally reversible.
215
217
  * include("/blog", blogPatterns, { name: "blog" })
218
+ *
219
+ * // Flattened — children merge into the parent namespace as-is.
220
+ * // Equivalent to defining those routes inline at the include site.
221
+ * include("/blog", blogPatterns, { name: "" })
222
+ *
223
+ * // Local-only (default) — children are scoped privately.
224
+ * // Hidden from generated types and global reverse resolution.
225
+ * // Only dot-local reverse (reverse(".child")) works inside.
226
+ * include("/blog", blogPatterns)
216
227
  * ```
217
228
  */
218
229
  include: IncludeFn<TEnv>;
@@ -234,12 +245,19 @@ export type PathHelpers<TEnv> = {
234
245
  * Define an intercepting route for soft navigation
235
246
  * Note: routeName must match a named path() in this urlpatterns
236
247
  */
237
- intercept: (
238
- slotName: `@${string}`,
239
- routeName: string,
240
- handler: ReactNode | Handler<any, any, TEnv>,
241
- use?: () => InterceptUseItem[],
242
- ) => InterceptItem;
248
+ intercept: keyof RSCRouter.GeneratedRouteMap extends never
249
+ ? (
250
+ slotName: `@${string}`,
251
+ routeName: string,
252
+ handler: ReactNode | Handler<any, any, TEnv>,
253
+ use?: () => InterceptUseItem[],
254
+ ) => InterceptItem
255
+ : (
256
+ slotName: `@${string}`,
257
+ routeName: (keyof RSCRouter.GeneratedRouteMap & string) | `.${string}`,
258
+ handler: ReactNode | Handler<any, any, TEnv>,
259
+ use?: () => InterceptUseItem[],
260
+ ) => InterceptItem;
243
261
 
244
262
  /**
245
263
  * Attach middleware to the current route/layout
@@ -6,8 +6,14 @@ import type {
6
6
  RouteUseItem,
7
7
  UseItems,
8
8
  } from "../route-types.js";
9
- import { getContext, getUrlPrefix, getNamePrefix } from "../server/context";
9
+ import {
10
+ getContext,
11
+ getUrlPrefix,
12
+ getNamePrefix,
13
+ getRootScoped,
14
+ } from "../server/context";
10
15
  import { invariant } from "../errors";
16
+ import { validateUserRouteName } from "../route-name.js";
11
17
  import {
12
18
  isPrerenderHandler,
13
19
  type PrerenderHandlerDefinition,
@@ -16,7 +22,10 @@ import {
16
22
  isStaticHandler,
17
23
  type StaticHandlerDefinition,
18
24
  } from "../static-handler.js";
19
- import { registerSearchSchema } from "../route-map-builder.js";
25
+ import {
26
+ registerSearchSchema,
27
+ registerRouteRootScope,
28
+ } from "../route-map-builder.js";
20
29
  import { RESPONSE_TYPE } from "./response-types.js";
21
30
  import type { PathOptions } from "./pattern-types.js";
22
31
  import type {
@@ -143,6 +152,9 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
143
152
  // Generate route name - use provided name or generate from pattern
144
153
  const localName =
145
154
  options?.name || `$path_${pattern.replace(/[/:*?]/g, "_")}`;
155
+ if (options?.name) {
156
+ validateUserRouteName(options.name);
157
+ }
146
158
  // Apply name prefix if set (from include())
147
159
  const routeName = applyNamePrefix(namePrefix, localName);
148
160
 
@@ -222,6 +234,9 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
222
234
  // Register route entry with prefixed name
223
235
  ctx.manifest.set(routeName, entry);
224
236
 
237
+ // Register root-scope flag for dot-local reverse resolution
238
+ registerRouteRootScope(routeName, getRootScoped());
239
+
225
240
  // Also store pattern in a separate map for URL generation
226
241
  if (ctx.patterns) {
227
242
  ctx.patterns.set(routeName, prefixedPattern);
@@ -15,6 +15,16 @@ import { RESPONSE_TYPE } from "./response-types.js";
15
15
  */
16
16
  export type UnnamedRoute = "$unnamed";
17
17
 
18
+ /**
19
+ * Sentinel type for include() mounts that stay local to the mounted module.
20
+ * This keeps child route names out of the parent/global type map while still
21
+ * allowing the mounted module to use its own local route names internally.
22
+ *
23
+ * Branded with a symbol key so it cannot be accidentally produced by user code.
24
+ */
25
+ declare const LOCAL_ONLY_BRAND: unique symbol;
26
+ export type LocalOnlyInclude = string & { [LOCAL_ONLY_BRAND]: void };
27
+
18
28
  /**
19
29
  * Options for path() function
20
30
  */
@@ -70,6 +80,16 @@ export interface UrlPatterns<
70
80
  * Options for include()
71
81
  */
72
82
  export interface IncludeOptions<TNamePrefix extends string = string> {
73
- /** Name prefix for all routes in this pattern set */
83
+ /**
84
+ * Name prefix for all routes in this pattern set.
85
+ *
86
+ * - `{ name: "blog" }` — children become `blog.index`, `blog.detail`, etc.
87
+ * Visible in generated route types and resolvable globally via `reverse("blog.index")`.
88
+ * - `{ name: "" }` — children merge into the parent namespace with no prefix.
89
+ * Equivalent to defining the routes inline at the include site.
90
+ * - Omitted — children live in a private local scope, hidden from the
91
+ * generated route map and global reverse resolution. Only dot-local
92
+ * reverse (e.g. `reverse(".child")`) works from inside the module.
93
+ */
74
94
  name?: TNamePrefix;
75
95
  }
@@ -1,6 +1,30 @@
1
- import type { CookieOptions } from "../router/middleware.js";
2
1
  import type { ContextVar } from "../context-var.js";
3
- import type { DefaultVars } from "../types/global-namespace.js";
2
+ import type { ReverseFunction } from "../reverse.js";
3
+ import type {
4
+ DefaultReverseRouteMap,
5
+ DefaultVars,
6
+ } from "../types/global-namespace.js";
7
+
8
+ /**
9
+ * Reverse function for response handler contexts.
10
+ * Global names get autocomplete and param validation from the generated route map.
11
+ * Local `.name` calls are accepted but not validated (scope unknown at type level).
12
+ */
13
+ type ResponseReverseFunction = [DefaultReverseRouteMap] extends [
14
+ Record<string, string>,
15
+ ]
16
+ ? (
17
+ name: string,
18
+ params?: Record<string, string>,
19
+ search?: Record<string, unknown>,
20
+ ) => string
21
+ : ReverseFunction<DefaultReverseRouteMap> & {
22
+ (
23
+ name: `.${string}`,
24
+ params?: Record<string, string>,
25
+ search?: Record<string, unknown>,
26
+ ): string;
27
+ };
4
28
 
5
29
  /**
6
30
  * Symbol marking a route as a response route (non-RSC).
@@ -53,8 +77,8 @@ export type TextResponseHandler<
53
77
 
54
78
  /**
55
79
  * Lighter handler context for response routes.
56
- * No ctx.use() (no loaders). Supports setting response headers and cookies
57
- * without constructing a full Response object.
80
+ * No ctx.use() (no loaders). Supports setting response headers via ctx.header().
81
+ * Use the standalone cookies() function for cookie mutations.
58
82
  */
59
83
  export interface ResponseHandlerContext<
60
84
  TParams = Record<string, string>,
@@ -72,13 +96,11 @@ export interface ResponseHandlerContext<
72
96
  url: URL;
73
97
  /** The pathname portion of the request URL. */
74
98
  pathname: string;
75
- reverse: (name: string, params?: Record<string, string>) => string;
99
+ reverse: ResponseReverseFunction;
76
100
  /** Read a variable set by middleware via ctx.set(key, value) or ctx.set(ContextVar, value). */
77
101
  get: {
78
102
  <T>(contextVar: ContextVar<T>): T | undefined;
79
103
  } & (<K extends keyof DefaultVars>(key: K) => DefaultVars[K]);
80
104
  /** Set a response header. Merged into the auto-wrapped or pass-through Response. */
81
105
  header: (name: string, value: string) => void;
82
- /** Set a cookie on the response. */
83
- setCookie: (name: string, value: string, options?: CookieOptions) => void;
84
106
  }
@@ -6,7 +6,11 @@ import type {
6
6
  TypedCacheItem,
7
7
  TypedTransitionItem,
8
8
  } from "../route-types.js";
9
- import type { UnnamedRoute, UrlPatterns } from "./pattern-types.js";
9
+ import type {
10
+ LocalOnlyInclude,
11
+ UnnamedRoute,
12
+ UrlPatterns,
13
+ } from "./pattern-types.js";
10
14
 
11
15
  // ============================================================================
12
16
  // Route Type Extraction Utilities
@@ -156,13 +160,15 @@ type ExtractRoutesFromItem<T, D extends number = 40> = [D] extends [never]
156
160
  infer TNamePrefix,
157
161
  infer TUrlPrefix
158
162
  >
159
- ? TNamePrefix extends string
160
- ? TUrlPrefix extends string
161
- ? PrefixRoutes<PrefixPatterns<TRoutes, TUrlPrefix>, TNamePrefix>
162
- : PrefixRoutes<TRoutes, TNamePrefix>
163
- : TUrlPrefix extends string
164
- ? PrefixPatterns<TRoutes, TUrlPrefix>
165
- : TRoutes
163
+ ? TNamePrefix extends LocalOnlyInclude
164
+ ? {}
165
+ : TNamePrefix extends string
166
+ ? TUrlPrefix extends string
167
+ ? PrefixRoutes<PrefixPatterns<TRoutes, TUrlPrefix>, TNamePrefix>
168
+ : PrefixRoutes<TRoutes, TNamePrefix>
169
+ : TUrlPrefix extends string
170
+ ? PrefixPatterns<TRoutes, TUrlPrefix>
171
+ : TRoutes
166
172
  : // TypedLayoutItem: extract child routes from phantom type
167
173
  T extends TypedLayoutItem<infer TChildRoutes>
168
174
  ? TChildRoutes
@@ -239,13 +245,15 @@ type ExtractResponsesFromItem<T, D extends number = 40> = [D] extends [never]
239
245
  : { [K in TName]: TData }
240
246
  : {}
241
247
  : T extends TypedIncludeItem<any, infer TNamePrefix, any, infer TResponses>
242
- ? TNamePrefix extends string
243
- ? TResponses extends Record<string, unknown>
244
- ? PrefixKeys<TResponses, TNamePrefix>
245
- : {}
246
- : TResponses extends Record<string, unknown>
247
- ? TResponses
248
- : {}
248
+ ? TNamePrefix extends LocalOnlyInclude
249
+ ? {}
250
+ : TNamePrefix extends string
251
+ ? TResponses extends Record<string, unknown>
252
+ ? PrefixKeys<TResponses, TNamePrefix>
253
+ : {}
254
+ : TResponses extends Record<string, unknown>
255
+ ? TResponses
256
+ : {}
249
257
  : T extends TypedLayoutItem<any, infer TChildResponses>
250
258
  ? TChildResponses extends Record<string, unknown>
251
259
  ? TChildResponses
@@ -88,6 +88,7 @@ function useLoaderInternal<T>(
88
88
  const [fetchedData, setFetchedData] = useState<T | undefined>(undefined);
89
89
  const [isLoading, setIsLoading] = useState(false);
90
90
  const [error, setError] = useState<Error | null>(null);
91
+ const requestIdRef = useRef(0);
91
92
 
92
93
  // Track context data changes to reset fetched data on navigation
93
94
  const prevContextDataRef = useRef(contextData);
@@ -105,12 +106,23 @@ function useLoaderInternal<T>(
105
106
 
106
107
  const throwOnError = options?.throwOnError ?? true;
107
108
 
109
+ // Refs for values used inside load() that should NOT cause callback identity
110
+ // churn. loader.$$id can change if a reusable component receives a different
111
+ // loader without remounting; data changes on every navigation. Refs keep the
112
+ // callback stable while always reading the latest values.
113
+ const loaderIdRef = useRef(loader.$$id);
114
+ loaderIdRef.current = loader.$$id;
115
+ const dataRef = useRef(data);
116
+ dataRef.current = data;
117
+
108
118
  // Load function for fetching data via the ?_rsc_loader endpoint.
109
119
  // Supports GET (data fetching) and POST/PUT/PATCH/DELETE (mutations).
110
120
  const load = useCallback(
111
121
  async (loadOptions?: LoadOptions): Promise<T> => {
122
+ const requestId = ++requestIdRef.current;
123
+ const loaderId = loaderIdRef.current;
112
124
  // Verify the loader has $$id
113
- if (!loader.$$id) {
125
+ if (!loaderId) {
114
126
  throw new Error(
115
127
  `Loader is missing $$id. Make sure the exposeLoaderId Vite plugin is enabled.`,
116
128
  );
@@ -120,8 +132,8 @@ function useLoaderInternal<T>(
120
132
  setError(null);
121
133
 
122
134
  try {
123
- const url = new URL(window.location.pathname, window.location.origin);
124
- url.searchParams.set("_rsc_loader", loader.$$id);
135
+ const url = new URL(window.location.href);
136
+ url.searchParams.set("_rsc_loader", loaderId);
125
137
 
126
138
  const method = loadOptions?.method ?? "GET";
127
139
  const isBodyMethod = method !== "GET";
@@ -202,19 +214,25 @@ function useLoaderInternal<T>(
202
214
  }
203
215
 
204
216
  const result = payload.loaderResult;
205
- setFetchedData(result);
217
+ if (requestId === requestIdRef.current) {
218
+ setFetchedData(result);
219
+ }
206
220
  return result;
207
221
  } catch (e) {
208
222
  const err = e instanceof Error ? e : new Error(String(e));
209
- setError(err);
223
+ if (requestId === requestIdRef.current) {
224
+ setError(err);
225
+ }
210
226
  if (throwOnError) {
211
227
  throw err;
212
228
  }
213
- // When throwOnError is false, return the current data (previous successful
214
- // value or undefined). Caller should check error state for error handling.
215
- return data as T;
229
+ // When throwOnError is false, return the latest data snapshot (previous
230
+ // successful value or undefined). Caller should check error state.
231
+ return dataRef.current as T;
216
232
  } finally {
217
- setIsLoading(false);
233
+ if (requestId === requestIdRef.current) {
234
+ setIsLoading(false);
235
+ }
218
236
  }
219
237
  },
220
238
  [throwOnError],