@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.
- package/README.md +46 -8
- package/dist/bin/rango.js +105 -18
- package/dist/vite/index.js +227 -93
- package/package.json +15 -14
- package/skills/hooks/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +79 -0
- package/skills/layout/SKILL.md +62 -2
- package/skills/loader/SKILL.md +94 -1
- package/skills/middleware/SKILL.md +81 -0
- package/skills/parallel/SKILL.md +57 -2
- package/skills/prerender/SKILL.md +187 -17
- package/skills/route/SKILL.md +42 -1
- package/skills/router-setup/SKILL.md +77 -0
- package/src/__internal.ts +1 -1
- package/src/bin/rango.ts +38 -19
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/event-controller.ts +25 -27
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +1 -1
- package/src/browser/link-interceptor.ts +0 -3
- package/src/browser/merge-segment-loaders.ts +9 -2
- package/src/browser/navigation-bridge.ts +46 -13
- package/src/browser/navigation-client.ts +32 -61
- package/src/browser/navigation-store.ts +1 -31
- package/src/browser/navigation-transaction.ts +46 -207
- package/src/browser/partial-update.ts +102 -150
- package/src/browser/{prefetch-cache.ts → prefetch/cache.ts} +23 -4
- package/src/browser/{prefetch-fetch.ts → prefetch/fetch.ts} +36 -8
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/{prefetch-queue.ts → prefetch/queue.ts} +10 -3
- package/src/browser/react/Link.tsx +28 -23
- package/src/browser/react/NavigationProvider.tsx +9 -1
- package/src/browser/react/index.ts +2 -6
- package/src/browser/react/location-state-shared.ts +1 -1
- package/src/browser/react/location-state.ts +2 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/use-action.ts +9 -1
- package/src/browser/react/use-handle.ts +3 -25
- package/src/browser/react/use-params.ts +2 -4
- package/src/browser/react/use-pathname.ts +2 -3
- package/src/browser/react/use-router.ts +1 -1
- package/src/browser/react/use-search-params.ts +2 -1
- package/src/browser/react/use-segments.ts +7 -60
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +29 -23
- package/src/browser/scroll-restoration.ts +10 -7
- package/src/browser/server-action-bridge.ts +115 -96
- package/src/browser/types.ts +1 -31
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +5 -0
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/route-types/codegen.ts +13 -4
- package/src/build/route-types/include-resolution.ts +13 -0
- package/src/build/route-types/per-module-writer.ts +15 -3
- package/src/build/route-types/router-processing.ts +45 -3
- package/src/build/runtime-discovery.ts +13 -1
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +132 -96
- package/src/cache/cache-scope.ts +71 -73
- package/src/cache/cf/cf-cache-store.ts +9 -4
- package/src/cache/document-cache.ts +72 -47
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/memory-segment-store.ts +18 -7
- package/src/cache/profile-registry.ts +43 -8
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +101 -112
- package/src/cache/taint.ts +26 -0
- package/src/client.tsx +53 -30
- package/src/errors.ts +6 -1
- package/src/handle.ts +1 -1
- package/src/handles/MetaTags.tsx +5 -2
- package/src/host/cookie-handler.ts +8 -3
- package/src/host/router.ts +14 -1
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +33 -1
- package/src/index.ts +27 -0
- package/src/loader.rsc.ts +12 -4
- package/src/loader.ts +8 -0
- package/src/prerender/store.ts +4 -3
- package/src/prerender.ts +76 -18
- package/src/reverse.ts +11 -7
- package/src/root-error-boundary.tsx +30 -26
- package/src/route-definition/dsl-helpers.ts +9 -6
- package/src/route-definition/redirect.ts +15 -3
- package/src/route-map-builder.ts +38 -2
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +7 -0
- package/src/router/content-negotiation.ts +1 -1
- package/src/router/debug-manifest.ts +16 -3
- package/src/router/handler-context.ts +94 -15
- package/src/router/intercept-resolution.ts +6 -4
- package/src/router/lazy-includes.ts +4 -0
- package/src/router/loader-resolution.ts +1 -0
- package/src/router/logging.ts +100 -3
- package/src/router/manifest.ts +32 -3
- package/src/router/match-api.ts +61 -7
- package/src/router/match-context.ts +3 -0
- package/src/router/match-handlers.ts +185 -11
- package/src/router/match-middleware/background-revalidation.ts +65 -85
- package/src/router/match-middleware/cache-lookup.ts +69 -4
- package/src/router/match-middleware/cache-store.ts +2 -0
- package/src/router/match-pipelines.ts +8 -43
- package/src/router/middleware-types.ts +7 -0
- package/src/router/middleware.ts +93 -8
- package/src/router/pattern-matching.ts +41 -5
- package/src/router/prerender-match.ts +34 -6
- package/src/router/preview-match.ts +7 -1
- package/src/router/revalidation.ts +61 -2
- package/src/router/router-context.ts +15 -0
- package/src/router/router-interfaces.ts +34 -0
- package/src/router/router-options.ts +200 -0
- package/src/router/segment-resolution/fresh.ts +123 -30
- package/src/router/segment-resolution/helpers.ts +19 -0
- package/src/router/segment-resolution/loader-cache.ts +37 -146
- package/src/router/segment-resolution/revalidation.ts +358 -94
- package/src/router/segment-wrappers.ts +3 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/types.ts +7 -1
- package/src/router.ts +155 -11
- package/src/rsc/handler-context.ts +11 -0
- package/src/rsc/handler.ts +380 -88
- package/src/rsc/helpers.ts +25 -16
- package/src/rsc/loader-fetch.ts +84 -42
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +232 -19
- package/src/rsc/response-route-handler.ts +37 -26
- package/src/rsc/rsc-rendering.ts +12 -5
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +134 -58
- package/src/rsc/types.ts +8 -0
- package/src/search-params.ts +22 -10
- package/src/server/context.ts +53 -5
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +66 -9
- package/src/server/loader-registry.ts +11 -46
- package/src/server/request-context.ts +90 -9
- package/src/ssr/index.tsx +63 -27
- package/src/static-handler.ts +7 -0
- package/src/theme/ThemeProvider.tsx +6 -1
- package/src/theme/index.ts +1 -6
- package/src/theme/theme-context.ts +1 -28
- package/src/theme/theme-script.ts +2 -1
- package/src/types/cache-types.ts +5 -0
- package/src/types/error-types.ts +3 -0
- package/src/types/global-namespace.ts +9 -0
- package/src/types/handler-context.ts +35 -13
- package/src/types/loader-types.ts +7 -0
- package/src/types/route-entry.ts +28 -0
- package/src/urls/include-helper.ts +49 -8
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +30 -12
- package/src/urls/path-helper.ts +17 -2
- package/src/urls/pattern-types.ts +21 -1
- package/src/urls/response-types.ts +27 -2
- package/src/urls/type-extraction.ts +23 -15
- package/src/use-loader.tsx +12 -4
- package/src/vite/discovery/bundle-postprocess.ts +12 -7
- package/src/vite/discovery/discover-routers.ts +30 -18
- package/src/vite/discovery/prerender-collection.ts +24 -27
- package/src/vite/discovery/route-types-writer.ts +7 -7
- package/src/vite/discovery/virtual-module-codegen.ts +5 -2
- package/src/vite/plugins/client-ref-hashing.ts +3 -3
- package/src/vite/plugins/use-cache-transform.ts +91 -3
- package/src/vite/rango.ts +3 -3
- package/src/vite/router-discovery.ts +99 -36
- package/src/vite/utils/prerender-utils.ts +21 -0
- package/src/vite/utils/shared-utils.ts +3 -1
- package/src/browser/request-controller.ts +0 -164
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- /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 {
|
|
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
|
package/src/types/cache-types.ts
CHANGED
|
@@ -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
|
package/src/types/error-types.ts
CHANGED
|
@@ -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
|
-
* -
|
|
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
|
-
*
|
|
206
|
-
*
|
|
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
|
-
*
|
|
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(
|
|
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
|
-
|
|
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
|
-
/**
|
|
424
|
-
|
|
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?:
|
|
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>;
|
package/src/types/route-entry.ts
CHANGED
|
@@ -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
|
|
17
|
-
const namePrefix =
|
|
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
|
|
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
|
|
107
|
-
?
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
@@ -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 =
|
|
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
|
|
209
|
+
* Include nested URL patterns under a URL prefix.
|
|
209
210
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
package/src/urls/path-helper.ts
CHANGED
|
@@ -6,8 +6,14 @@ import type {
|
|
|
6
6
|
RouteUseItem,
|
|
7
7
|
UseItems,
|
|
8
8
|
} from "../route-types.js";
|
|
9
|
-
import {
|
|
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 {
|
|
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
|
-
/**
|
|
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 {
|
|
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:
|
|
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 {
|
|
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
|
|
160
|
-
?
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
:
|
|
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
|
|
243
|
-
?
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|