@rangojs/router 0.0.0-experimental.18 → 0.0.0-experimental.19

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 (177) hide show
  1. package/README.md +46 -8
  2. package/dist/bin/rango.js +105 -18
  3. package/dist/vite/index.js +227 -93
  4. package/package.json +15 -14
  5. package/skills/hooks/SKILL.md +1 -1
  6. package/skills/intercept/SKILL.md +79 -0
  7. package/skills/layout/SKILL.md +62 -2
  8. package/skills/loader/SKILL.md +94 -1
  9. package/skills/middleware/SKILL.md +81 -0
  10. package/skills/parallel/SKILL.md +57 -2
  11. package/skills/prerender/SKILL.md +187 -17
  12. package/skills/route/SKILL.md +42 -1
  13. package/skills/router-setup/SKILL.md +77 -0
  14. package/src/__internal.ts +1 -1
  15. package/src/bin/rango.ts +38 -19
  16. package/src/browser/action-coordinator.ts +97 -0
  17. package/src/browser/event-controller.ts +25 -27
  18. package/src/browser/history-state.ts +80 -0
  19. package/src/browser/intercept-utils.ts +1 -1
  20. package/src/browser/link-interceptor.ts +0 -3
  21. package/src/browser/merge-segment-loaders.ts +9 -2
  22. package/src/browser/navigation-bridge.ts +46 -13
  23. package/src/browser/navigation-client.ts +32 -61
  24. package/src/browser/navigation-store.ts +1 -31
  25. package/src/browser/navigation-transaction.ts +46 -207
  26. package/src/browser/partial-update.ts +102 -150
  27. package/src/browser/{prefetch-cache.ts → prefetch/cache.ts} +23 -4
  28. package/src/browser/{prefetch-fetch.ts → prefetch/fetch.ts} +36 -8
  29. package/src/browser/prefetch/policy.ts +42 -0
  30. package/src/browser/{prefetch-queue.ts → prefetch/queue.ts} +10 -3
  31. package/src/browser/react/Link.tsx +28 -23
  32. package/src/browser/react/NavigationProvider.tsx +9 -1
  33. package/src/browser/react/index.ts +2 -6
  34. package/src/browser/react/location-state-shared.ts +1 -1
  35. package/src/browser/react/location-state.ts +2 -0
  36. package/src/browser/react/nonce-context.ts +23 -0
  37. package/src/browser/react/use-action.ts +9 -1
  38. package/src/browser/react/use-handle.ts +3 -25
  39. package/src/browser/react/use-params.ts +2 -4
  40. package/src/browser/react/use-pathname.ts +2 -3
  41. package/src/browser/react/use-router.ts +1 -1
  42. package/src/browser/react/use-search-params.ts +2 -1
  43. package/src/browser/react/use-segments.ts +7 -60
  44. package/src/browser/response-adapter.ts +73 -0
  45. package/src/browser/rsc-router.tsx +29 -23
  46. package/src/browser/scroll-restoration.ts +10 -7
  47. package/src/browser/server-action-bridge.ts +115 -96
  48. package/src/browser/types.ts +1 -31
  49. package/src/browser/validate-redirect-origin.ts +29 -0
  50. package/src/build/generate-manifest.ts +5 -0
  51. package/src/build/generate-route-types.ts +2 -0
  52. package/src/build/route-types/codegen.ts +13 -4
  53. package/src/build/route-types/include-resolution.ts +13 -0
  54. package/src/build/route-types/per-module-writer.ts +15 -3
  55. package/src/build/route-types/router-processing.ts +45 -3
  56. package/src/build/runtime-discovery.ts +13 -1
  57. package/src/cache/background-task.ts +34 -0
  58. package/src/cache/cache-key-utils.ts +44 -0
  59. package/src/cache/cache-policy.ts +125 -0
  60. package/src/cache/cache-runtime.ts +132 -96
  61. package/src/cache/cache-scope.ts +71 -73
  62. package/src/cache/cf/cf-cache-store.ts +9 -4
  63. package/src/cache/document-cache.ts +72 -47
  64. package/src/cache/handle-capture.ts +81 -0
  65. package/src/cache/memory-segment-store.ts +18 -7
  66. package/src/cache/profile-registry.ts +43 -8
  67. package/src/cache/read-through-swr.ts +134 -0
  68. package/src/cache/segment-codec.ts +101 -112
  69. package/src/cache/taint.ts +26 -0
  70. package/src/client.tsx +53 -30
  71. package/src/errors.ts +6 -1
  72. package/src/handle.ts +1 -1
  73. package/src/handles/MetaTags.tsx +5 -2
  74. package/src/host/cookie-handler.ts +8 -3
  75. package/src/host/router.ts +14 -1
  76. package/src/href-client.ts +3 -1
  77. package/src/index.rsc.ts +33 -1
  78. package/src/index.ts +27 -0
  79. package/src/loader.rsc.ts +12 -4
  80. package/src/loader.ts +8 -0
  81. package/src/prerender/store.ts +4 -3
  82. package/src/prerender.ts +76 -18
  83. package/src/reverse.ts +11 -7
  84. package/src/root-error-boundary.tsx +30 -26
  85. package/src/route-definition/dsl-helpers.ts +9 -6
  86. package/src/route-definition/redirect.ts +15 -3
  87. package/src/route-map-builder.ts +38 -2
  88. package/src/route-name.ts +53 -0
  89. package/src/route-types.ts +7 -0
  90. package/src/router/content-negotiation.ts +1 -1
  91. package/src/router/debug-manifest.ts +16 -3
  92. package/src/router/handler-context.ts +94 -15
  93. package/src/router/intercept-resolution.ts +6 -4
  94. package/src/router/lazy-includes.ts +4 -0
  95. package/src/router/loader-resolution.ts +1 -0
  96. package/src/router/logging.ts +100 -3
  97. package/src/router/manifest.ts +32 -3
  98. package/src/router/match-api.ts +61 -7
  99. package/src/router/match-context.ts +3 -0
  100. package/src/router/match-handlers.ts +185 -11
  101. package/src/router/match-middleware/background-revalidation.ts +65 -85
  102. package/src/router/match-middleware/cache-lookup.ts +69 -4
  103. package/src/router/match-middleware/cache-store.ts +2 -0
  104. package/src/router/match-pipelines.ts +8 -43
  105. package/src/router/middleware-types.ts +7 -0
  106. package/src/router/middleware.ts +93 -8
  107. package/src/router/pattern-matching.ts +41 -5
  108. package/src/router/prerender-match.ts +34 -6
  109. package/src/router/preview-match.ts +7 -1
  110. package/src/router/revalidation.ts +61 -2
  111. package/src/router/router-context.ts +15 -0
  112. package/src/router/router-interfaces.ts +34 -0
  113. package/src/router/router-options.ts +200 -0
  114. package/src/router/segment-resolution/fresh.ts +123 -30
  115. package/src/router/segment-resolution/helpers.ts +19 -0
  116. package/src/router/segment-resolution/loader-cache.ts +37 -146
  117. package/src/router/segment-resolution/revalidation.ts +358 -94
  118. package/src/router/segment-wrappers.ts +3 -0
  119. package/src/router/telemetry-otel.ts +299 -0
  120. package/src/router/telemetry.ts +300 -0
  121. package/src/router/timeout.ts +148 -0
  122. package/src/router/types.ts +7 -1
  123. package/src/router.ts +155 -11
  124. package/src/rsc/handler-context.ts +11 -0
  125. package/src/rsc/handler.ts +380 -88
  126. package/src/rsc/helpers.ts +25 -16
  127. package/src/rsc/loader-fetch.ts +84 -42
  128. package/src/rsc/origin-guard.ts +141 -0
  129. package/src/rsc/progressive-enhancement.ts +232 -19
  130. package/src/rsc/response-route-handler.ts +37 -26
  131. package/src/rsc/rsc-rendering.ts +12 -5
  132. package/src/rsc/runtime-warnings.ts +42 -0
  133. package/src/rsc/server-action.ts +134 -58
  134. package/src/rsc/types.ts +8 -0
  135. package/src/search-params.ts +22 -10
  136. package/src/server/context.ts +53 -5
  137. package/src/server/fetchable-loader-store.ts +11 -6
  138. package/src/server/handle-store.ts +66 -9
  139. package/src/server/loader-registry.ts +11 -46
  140. package/src/server/request-context.ts +90 -9
  141. package/src/ssr/index.tsx +63 -27
  142. package/src/static-handler.ts +7 -0
  143. package/src/theme/ThemeProvider.tsx +6 -1
  144. package/src/theme/index.ts +1 -6
  145. package/src/theme/theme-context.ts +1 -28
  146. package/src/theme/theme-script.ts +2 -1
  147. package/src/types/cache-types.ts +5 -0
  148. package/src/types/error-types.ts +3 -0
  149. package/src/types/global-namespace.ts +9 -0
  150. package/src/types/handler-context.ts +35 -13
  151. package/src/types/loader-types.ts +7 -0
  152. package/src/types/route-entry.ts +28 -0
  153. package/src/urls/include-helper.ts +49 -8
  154. package/src/urls/index.ts +1 -0
  155. package/src/urls/path-helper-types.ts +30 -12
  156. package/src/urls/path-helper.ts +17 -2
  157. package/src/urls/pattern-types.ts +21 -1
  158. package/src/urls/response-types.ts +27 -2
  159. package/src/urls/type-extraction.ts +23 -15
  160. package/src/use-loader.tsx +12 -4
  161. package/src/vite/discovery/bundle-postprocess.ts +12 -7
  162. package/src/vite/discovery/discover-routers.ts +30 -18
  163. package/src/vite/discovery/prerender-collection.ts +24 -27
  164. package/src/vite/discovery/route-types-writer.ts +7 -7
  165. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  166. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  167. package/src/vite/plugins/use-cache-transform.ts +91 -3
  168. package/src/vite/rango.ts +3 -3
  169. package/src/vite/router-discovery.ts +99 -36
  170. package/src/vite/utils/prerender-utils.ts +21 -0
  171. package/src/vite/utils/shared-utils.ts +3 -1
  172. package/src/browser/request-controller.ts +0 -164
  173. package/src/href-context.ts +0 -33
  174. package/src/router.gen.ts +0 -6
  175. package/src/static-handler.gen.ts +0 -5
  176. package/src/urls.gen.ts +0 -8
  177. /package/src/browser/{prefetch-observer.ts → prefetch/observer.ts} +0 -0
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { createContext, useContext, type Context } from "react";
13
- import type { ResolvedThemeConfig, ThemeContextValue } from "./types.js";
13
+ import type { ThemeContextValue } from "./types.js";
14
14
 
15
15
  /**
16
16
  * React context for theme state
@@ -19,33 +19,6 @@ import type { ResolvedThemeConfig, ThemeContextValue } from "./types.js";
19
19
  export const ThemeContext: Context<ThemeContextValue | null> =
20
20
  createContext<ThemeContextValue | null>(null);
21
21
 
22
- /**
23
- * SSR module-level state for theme config.
24
- * Populated by initThemeConfigSync before React renders.
25
- * Used by MetaTags during SSR to render the theme script.
26
- */
27
- let ssrThemeConfig: ResolvedThemeConfig | null = null;
28
-
29
- /**
30
- * Initialize theme config synchronously for SSR.
31
- * Called before rendering to populate state for MetaTags.
32
- *
33
- * @param config - Theme config from router, or null if theme is disabled
34
- */
35
- export function initThemeConfigSync(config: ResolvedThemeConfig | null): void {
36
- ssrThemeConfig = config;
37
- }
38
-
39
- /**
40
- * Get theme config for SSR/hydration.
41
- * Used by MetaTags to render the theme script.
42
- *
43
- * @returns Theme config if available, null otherwise
44
- */
45
- export function getSSRThemeConfig(): ResolvedThemeConfig | null {
46
- return ssrThemeConfig;
47
- }
48
-
49
22
  /**
50
23
  * Get theme context (internal use)
51
24
  * Returns null if theme is not enabled
@@ -40,7 +40,8 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
40
40
  for (var i = 0; i < cookies.length; i++) {
41
41
  var cookie = cookies[i].trim();
42
42
  if (cookie.indexOf(storageKey + '=') === 0) {
43
- return decodeURIComponent(cookie.substring(storageKey.length + 1));
43
+ try { return decodeURIComponent(cookie.substring(storageKey.length + 1)); }
44
+ catch (e) { return cookie.substring(storageKey.length + 1); }
44
45
  }
45
46
  }
46
47
  // Fall back to localStorage
@@ -145,6 +145,11 @@ export interface CacheOptions<TEnv = unknown> {
145
145
  * Tags for cache invalidation.
146
146
  * Can be a static array or a function that returns tags.
147
147
  *
148
+ * Note: Tags are passed through to the store but built-in stores
149
+ * (MemorySegmentCacheStore, CFCacheStore) do not yet index or
150
+ * invalidate by tag. Effective tag-based invalidation requires a
151
+ * custom store implementation with secondary indices.
152
+ *
148
153
  * @example
149
154
  * ```typescript
150
155
  * // Static tags
@@ -10,6 +10,7 @@
10
10
  * - "rendering": Invoked during SSR rendering errors (ssr/index.tsx, separate callback)
11
11
  * - "action": Invoked when server action execution fails (rsc/handler.ts, router.ts)
12
12
  * - "revalidation": Invoked when revalidation fails (router.ts, conditional with action)
13
+ * - "origin": Invoked when cross-origin request validation rejects a request (rsc/handler.ts)
13
14
  * - "unknown": Fallback for unclassified errors (not currently invoked)
14
15
  */
15
16
  export type ErrorPhase =
@@ -21,8 +22,10 @@ export type ErrorPhase =
21
22
  | "rendering" // During RSC/SSR rendering (SSR handler uses separate callback)
22
23
  | "action" // During server action execution
23
24
  | "revalidation" // During revalidation evaluation
25
+ | "cache" // During "use cache" background operations (stale revalidation, async cache writes)
24
26
  | "prerender" // During build-time pre-rendering (Vite closeBundle)
25
27
  | "static" // During build-time static handler rendering (Vite closeBundle)
28
+ | "origin" // During cross-origin request validation (CSRF protection)
26
29
  | "unknown"; // Fallback for unclassified errors
27
30
 
28
31
  /**
@@ -89,3 +89,12 @@ export type DefaultEnv = keyof RSCRouter.Env extends never
89
89
  export type DefaultVars = keyof RSCRouter.Vars extends never
90
90
  ? Record<string, any>
91
91
  : RSCRouter.Vars;
92
+
93
+ /**
94
+ * Default route name type for public `routeName` on contexts.
95
+ * When GeneratedRouteMap is augmented, narrows to the known route names.
96
+ * Otherwise falls back to `string` for untyped usage.
97
+ */
98
+ export type DefaultRouteName = keyof RSCRouter.GeneratedRouteMap extends never
99
+ ? string
100
+ : keyof RSCRouter.GeneratedRouteMap & string;
@@ -10,6 +10,7 @@ import type {
10
10
  DefaultEnv,
11
11
  DefaultHandlerRouteMap,
12
12
  DefaultReverseRouteMap,
13
+ DefaultRouteName,
13
14
  DefaultVars,
14
15
  } from "./global-namespace.js";
15
16
  import type {
@@ -166,14 +167,12 @@ export type Handler<
166
167
  *
167
168
  * Provides type-safe access to:
168
169
  * - Route params (from URL pattern)
169
- * - Request data (request, searchParams, pathname, url)
170
+ * - Cleaned route URL (`url`, `searchParams`, `pathname` — no `_rsc*` params)
171
+ * - Original request (`request` — raw transport URL, headers, method, body)
170
172
  * - Platform bindings (env.DB, env.KV, env.SECRETS)
171
173
  * - Middleware variables (var.user, var.permissions)
172
174
  * - Getter/setter for variables (get('user'), set('user', ...))
173
175
  *
174
- * **Note:** System parameters (query params starting with `_rsc`) are automatically
175
- * filtered from `url`, `searchParams`, and `request.url` for cleaner access.
176
- *
177
176
  * @example
178
177
  * ```typescript
179
178
  * const handler = (ctx: HandlerContext<{ slug: string }, AppEnv>) => {
@@ -184,6 +183,7 @@ export type Handler<
184
183
  * ctx.set('user', {...}) // Setter
185
184
  * ctx.url // Clean URL (no _rsc* params)
186
185
  * ctx.searchParams // Clean params (no _rsc* params)
186
+ * ctx.request // Raw transport request (original URL intact)
187
187
  * }
188
188
  * ```
189
189
  */
@@ -202,13 +202,15 @@ export type HandlerContext<
202
202
  readonly _paramCheck?: (params: TParams) => TParams;
203
203
  /**
204
204
  * True during build-time pre-rendering, false at runtime.
205
- * In dev mode, Prerender handlers run live so build is false.
206
- * In production passthrough (live fallback), build is also false.
205
+ * Build-time collection and dev on-demand prerender use `true`.
206
+ * Live request rendering, including passthrough fallback, uses `false`.
207
207
  */
208
208
  build: boolean;
209
209
  /**
210
- * The incoming Request object.
211
- * System params (`_rsc*`) are filtered from the URL for cleaner access.
210
+ * The original incoming Request object (transport URL intact).
211
+ * Use `ctx.url` / `ctx.searchParams` for application logic those have
212
+ * internal `_rsc*` params stripped. `ctx.request` preserves the raw URL
213
+ * for cases where you need original headers, method, or body.
212
214
  */
213
215
  request: Request;
214
216
  /**
@@ -379,12 +381,26 @@ export type HandlerContext<
379
381
  * @example
380
382
  * ```typescript
381
383
  * route("product", (ctx) => {
382
- * ctx.setLocationState([ServerInfo({ data: "value" })]);
384
+ * ctx.setLocationState(ServerInfo({ data: "value" }));
385
+ * return <ProductPage />;
386
+ * });
387
+ * ```
388
+ */
389
+ setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
390
+ /**
391
+ * The matched route name, if the route has an explicit name.
392
+ * Undefined for unnamed routes (those without a `name` option in path()).
393
+ * Includes the namespace prefix from include() (e.g., "blog.post").
394
+ *
395
+ * @example
396
+ * ```typescript
397
+ * route("product", (ctx) => {
398
+ * ctx.routeName // "product"
383
399
  * return <ProductPage />;
384
400
  * });
385
401
  * ```
386
402
  */
387
- setLocationState(entries: LocationStateEntry[]): void;
403
+ routeName?: DefaultRouteName;
388
404
  /**
389
405
  * Generate URLs from route names.
390
406
  *
@@ -420,12 +436,14 @@ export type InternalHandlerContext<
420
436
  TEnv = DefaultEnv,
421
437
  TSearch extends SearchSchema = {},
422
438
  > = HandlerContext<TParams, TEnv, TSearch> & {
423
- /** Raw request with all system parameters intact. */
424
- _originalRequest: Request;
439
+ /** Prerender-only control flow helper, attached when the runtime context supports it. */
440
+ passthrough?: () => unknown;
425
441
  /** Current segment ID for handle data attribution. */
426
442
  _currentSegmentId?: string;
427
443
  /** Response type tag (json, text, html, etc.) for cache key differentiation. */
428
444
  _responseType?: string;
445
+ /** Route name for cache key scoping (prevents cross-route collisions). */
446
+ _routeName?: string;
429
447
  };
430
448
 
431
449
  /**
@@ -522,7 +540,11 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
522
540
  actionResult?: any; // Return value from action execution
523
541
  formData?: FormData; // FormData from action request
524
542
  method?: string; // Request method: 'GET' for navigation, 'POST' for actions
525
- routeName?: string; // Route name where action was executed (e.g., "products.detail")
543
+ routeName?: DefaultRouteName; // Route name of the navigation target (alias for toRouteName)
544
+ // Named-route identity for both ends of a navigation transition.
545
+ // Undefined for unnamed internal routes (those without a `name` option).
546
+ fromRouteName?: DefaultRouteName; // Route name being navigated away from
547
+ toRouteName?: DefaultRouteName; // Route name being navigated to
526
548
  // Stale cache revalidation (SWR pattern):
527
549
  stale?: boolean; // True if this is a stale cache revalidation request
528
550
  }) => boolean | { defaultShouldRevalidate: boolean };
@@ -40,6 +40,13 @@ export type LoaderContext<
40
40
  TSearch extends SearchSchema = {},
41
41
  > = {
42
42
  params: TParams;
43
+ /**
44
+ * Route params extracted from the URL pattern match (server-side only).
45
+ * Unlike `params`, these cannot be overridden by client-provided loader params.
46
+ * Use this when you need trusted, server-matched route params for auth or
47
+ * resource scoping.
48
+ */
49
+ routeParams: Record<string, string>;
43
50
  request: Request;
44
51
  searchParams: URLSearchParams;
45
52
  search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
@@ -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
  }
@@ -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,5 +1,30 @@
1
1
  import type { ContextVar } from "../context-var.js";
2
- 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
+ };
3
28
 
4
29
  /**
5
30
  * Symbol marking a route as a response route (non-RSC).
@@ -71,7 +96,7 @@ export interface ResponseHandlerContext<
71
96
  url: URL;
72
97
  /** The pathname portion of the request URL. */
73
98
  pathname: string;
74
- reverse: (name: string, params?: Record<string, string>) => string;
99
+ reverse: ResponseReverseFunction;
75
100
  /** Read a variable set by middleware via ctx.set(key, value) or ctx.set(ContextVar, value). */
76
101
  get: {
77
102
  <T>(contextVar: ContextVar<T>): T | undefined;
@@ -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