@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.
- package/README.md +294 -28
- package/dist/bin/rango.js +355 -47
- package/dist/vite/index.js +1658 -1239
- package/package.json +3 -3
- package/skills/cache-guide/SKILL.md +9 -5
- package/skills/caching/SKILL.md +4 -4
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/hooks/SKILL.md +40 -29
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +79 -0
- package/skills/layout/SKILL.md +62 -2
- package/skills/loader/SKILL.md +229 -15
- package/skills/middleware/SKILL.md +109 -30
- package/skills/parallel/SKILL.md +57 -2
- package/skills/prerender/SKILL.md +189 -19
- package/skills/rango/SKILL.md +1 -2
- package/skills/response-routes/SKILL.md +3 -3
- package/skills/route/SKILL.md +44 -3
- package/skills/router-setup/SKILL.md +80 -3
- package/skills/theme/SKILL.md +5 -4
- package/skills/typesafety/SKILL.md +59 -16
- package/skills/use-cache/SKILL.md +16 -2
- package/src/__internal.ts +1 -1
- package/src/bin/rango.ts +56 -19
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/event-controller.ts +29 -48
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +1 -1
- package/src/browser/link-interceptor.ts +19 -3
- package/src/browser/merge-segment-loaders.ts +9 -2
- package/src/browser/navigation-bridge.ts +66 -443
- package/src/browser/navigation-client.ts +34 -62
- package/src/browser/navigation-store.ts +4 -33
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/partial-update.ts +103 -151
- package/src/browser/prefetch/cache.ts +67 -0
- package/src/browser/prefetch/fetch.ts +137 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +154 -44
- package/src/browser/react/NavigationProvider.tsx +32 -0
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +2 -6
- package/src/browser/react/location-state-shared.ts +29 -11
- package/src/browser/react/location-state.ts +6 -4
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +23 -45
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +21 -64
- package/src/browser/react/use-navigation.ts +7 -32
- package/src/browser/react/use-params.ts +5 -34
- package/src/browser/react/use-pathname.ts +2 -3
- package/src/browser/react/use-router.ts +3 -6
- package/src/browser/react/use-search-params.ts +2 -1
- package/src/browser/react/use-segments.ts +75 -114
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +46 -22
- package/src/browser/scroll-restoration.ts +10 -7
- package/src/browser/server-action-bridge.ts +458 -405
- package/src/browser/types.ts +21 -35
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +38 -13
- package/src/build/generate-route-types.ts +4 -0
- package/src/build/index.ts +1 -0
- package/src/build/route-trie.ts +19 -3
- 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 +170 -18
- 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 +136 -123
- package/src/cache/cache-scope.ts +76 -83
- package/src/cache/cf/cf-cache-store.ts +12 -7
- package/src/cache/document-cache.ts +93 -69
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +43 -69
- package/src/cache/profile-registry.ts +43 -8
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +140 -117
- package/src/cache/taint.ts +30 -3
- package/src/cache/types.ts +1 -115
- package/src/client.rsc.tsx +0 -1
- package/src/client.tsx +53 -76
- 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/index.ts +0 -3
- package/src/host/router.ts +14 -1
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +53 -10
- package/src/index.ts +73 -43
- package/src/loader.rsc.ts +12 -4
- package/src/loader.ts +8 -0
- package/src/prerender/store.ts +60 -18
- 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/index.ts +0 -3
- 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 +96 -17
- package/src/router/intercept-resolution.ts +6 -4
- package/src/router/lazy-includes.ts +4 -0
- package/src/router/loader-resolution.ts +6 -11
- package/src/router/logging.ts +100 -3
- package/src/router/manifest.ts +32 -3
- package/src/router/match-api.ts +62 -54
- 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 +78 -10
- package/src/router/match-middleware/cache-store.ts +2 -0
- package/src/router/match-pipelines.ts +8 -43
- package/src/router/match-result.ts +0 -9
- package/src/router/metrics.ts +233 -13
- package/src/router/middleware-types.ts +34 -39
- package/src/router/middleware.ts +290 -130
- package/src/router/pattern-matching.ts +61 -10
- package/src/router/prerender-match.ts +36 -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 +158 -40
- package/src/router/router-options.ts +223 -1
- package/src/router/router-registry.ts +5 -2
- package/src/router/segment-resolution/fresh.ts +165 -242
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +102 -98
- package/src/router/segment-resolution/revalidation.ts +394 -272
- package/src/router/segment-resolution/static-store.ts +2 -2
- package/src/router/segment-resolution.ts +1 -3
- 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/trie-matching.ts +20 -2
- package/src/router/types.ts +7 -1
- package/src/router.ts +203 -18
- package/src/rsc/handler-context.ts +13 -2
- package/src/rsc/handler.ts +489 -438
- package/src/rsc/helpers.ts +125 -5
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +84 -42
- package/src/rsc/manifest-init.ts +3 -2
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +245 -19
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +47 -43
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +166 -66
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +20 -2
- package/src/search-params.ts +38 -23
- package/src/server/context.ts +61 -7
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +84 -12
- package/src/server/loader-registry.ts +11 -46
- package/src/server/request-context.ts +275 -49
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +67 -28
- package/src/static-handler.ts +7 -0
- package/src/theme/ThemeProvider.tsx +6 -1
- package/src/theme/index.ts +4 -18
- package/src/theme/theme-context.ts +1 -28
- package/src/theme/theme-script.ts +2 -1
- package/src/types/cache-types.ts +6 -1
- package/src/types/error-types.ts +3 -0
- package/src/types/global-namespace.ts +22 -0
- package/src/types/handler-context.ts +103 -16
- package/src/types/index.ts +1 -1
- package/src/types/loader-types.ts +9 -6
- package/src/types/route-config.ts +17 -26
- package/src/types/route-entry.ts +28 -0
- package/src/types/segments.ts +0 -5
- 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 +29 -7
- package/src/urls/type-extraction.ts +23 -15
- package/src/use-loader.tsx +27 -9
- package/src/vite/discovery/bundle-postprocess.ts +32 -52
- package/src/vite/discovery/discover-routers.ts +52 -26
- package/src/vite/discovery/prerender-collection.ts +58 -41
- package/src/vite/discovery/route-types-writer.ts +7 -7
- package/src/vite/discovery/state.ts +7 -7
- package/src/vite/discovery/virtual-module-codegen.ts +5 -2
- package/src/vite/index.ts +10 -51
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +3 -3
- package/src/vite/plugins/expose-internal-ids.ts +4 -3
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +91 -3
- package/src/vite/plugins/version-plugin.ts +188 -18
- package/src/vite/rango.ts +61 -36
- package/src/vite/router-discovery.ts +173 -100
- package/src/vite/utils/prerender-utils.ts +81 -0
- package/src/vite/utils/shared-utils.ts +19 -9
- package/skills/testing/SKILL.md +0 -226
- package/src/browser/lru-cache.ts +0 -61
- package/src/browser/react/prefetch.ts +0 -27
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/route-definition/route-function.ts +0 -119
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- /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
|
|
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
|
|
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
|
-
: //
|
|
55
|
-
|
|
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
|
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
|
}
|
package/src/types/segments.ts
CHANGED
|
@@ -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
|
|
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,6 +1,30 @@
|
|
|
1
|
-
import type { CookieOptions } from "../router/middleware.js";
|
|
2
1
|
import type { ContextVar } from "../context-var.js";
|
|
3
|
-
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
|
+
};
|
|
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
|
|
57
|
-
*
|
|
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:
|
|
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 {
|
|
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
|
package/src/use-loader.tsx
CHANGED
|
@@ -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 (!
|
|
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.
|
|
124
|
-
url.searchParams.set("_rsc_loader",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
214
|
-
// value or undefined). Caller should check error state
|
|
215
|
-
return
|
|
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
|
-
|
|
233
|
+
if (requestId === requestIdRef.current) {
|
|
234
|
+
setIsLoading(false);
|
|
235
|
+
}
|
|
218
236
|
}
|
|
219
237
|
},
|
|
220
238
|
[throwOnError],
|