@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847
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/AGENTS.md +9 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1531 -212
- package/dist/vite/index.js +3995 -2489
- package/package.json +57 -52
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +85 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/hooks/SKILL.md +328 -70
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +62 -15
- package/skills/loader/SKILL.md +368 -42
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +14 -10
- package/skills/parallel/SKILL.md +137 -1
- package/skills/prerender/SKILL.md +366 -28
- package/skills/rango/SKILL.md +85 -21
- package/skills/response-routes/SKILL.md +136 -83
- package/skills/route/SKILL.md +195 -21
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +240 -102
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +266 -558
- package/src/browser/navigation-client.ts +132 -75
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +303 -309
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +144 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +190 -70
- package/src/browser/react/NavigationProvider.tsx +78 -11
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- 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 +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +29 -70
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +188 -57
- package/src/browser/scroll-restoration.ts +117 -44
- package/src/browser/segment-reconciler.ts +221 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +488 -606
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +116 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +63 -21
- package/src/build/generate-route-types.ts +36 -1038
- package/src/build/index.ts +2 -5
- package/src/build/route-trie.ts +38 -12
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- 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 +342 -0
- package/src/cache/cache-scope.ts +122 -303
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +84 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +12 -7
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +104 -40
- package/src/index.ts +122 -67
- package/src/internal-debug.ts +9 -3
- package/src/loader.rsc.ts +18 -93
- package/src/loader.ts +26 -9
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +121 -17
- package/src/prerender.ts +325 -20
- package/src/reverse.ts +144 -124
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +959 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1450
- package/src/route-map-builder.ts +87 -133
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +41 -6
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +324 -116
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +179 -133
- package/src/router/logging.ts +112 -6
- package/src/router/manifest.ts +58 -19
- package/src/router/match-api.ts +89 -88
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +86 -89
- package/src/router/match-middleware/cache-lookup.ts +295 -49
- package/src/router/match-middleware/cache-store.ts +56 -13
- package/src/router/match-middleware/intercept-resolution.ts +45 -22
- package/src/router/match-middleware/segment-resolution.ts +20 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +44 -21
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +327 -369
- package/src/router/pattern-matching.ts +169 -31
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +105 -14
- package/src/router/router-context.ts +40 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +677 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1296 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1354
- package/src/router/segment-wrappers.ts +291 -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 +96 -29
- package/src/router/types.ts +15 -9
- package/src/router.ts +642 -2366
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +639 -1027
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +66 -54
- package/src/segment-system.tsx +165 -17
- package/src/server/context.ts +237 -54
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +438 -71
- package/src/server.ts +26 -164
- package/src/ssr/index.tsx +101 -31
- package/src/static-handler.ts +22 -4
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +773 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1323
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -2259
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -47
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +8 -43
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +181 -9
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- 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/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -5,60 +5,184 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { HandlerContext, InternalHandlerContext } from "../types";
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import { getSearchSchema } from "../route-map-builder.js";
|
|
8
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
9
|
+
import { getSearchSchema, isRouteRootScoped } from "../route-map-builder.js";
|
|
11
10
|
import { parseSearchParams, serializeSearchParams } from "../search-params.js";
|
|
11
|
+
import { contextGet, contextSet } from "../context-var.js";
|
|
12
|
+
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
13
|
+
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
14
|
+
import { PRERENDER_PASSTHROUGH } from "../prerender.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Strip internal _rsc* query params from a URL.
|
|
18
|
+
* Returns a new URL with only user-facing params.
|
|
19
|
+
*/
|
|
20
|
+
export function stripInternalParams(url: URL): URL {
|
|
21
|
+
const clean = new URL(url);
|
|
22
|
+
for (const key of [...clean.searchParams.keys()]) {
|
|
23
|
+
if (key.startsWith("_rsc")) {
|
|
24
|
+
clean.searchParams.delete(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return clean;
|
|
28
|
+
}
|
|
12
29
|
|
|
13
30
|
/**
|
|
14
31
|
* Resolve route name with namespace prefix support.
|
|
15
|
-
* Supports local names
|
|
32
|
+
* Supports local names (dot-prefixed) and absolute names (global lookup).
|
|
33
|
+
*
|
|
34
|
+
* @param rootScoped - Explicit override for root-scope check. When undefined,
|
|
35
|
+
* falls back to the global scope registry, then to a heuristic.
|
|
16
36
|
*/
|
|
17
37
|
function resolveRouteName(
|
|
18
38
|
name: string,
|
|
19
39
|
routeMap: Record<string, string>,
|
|
20
|
-
currentRoutePrefix?: string
|
|
40
|
+
currentRoutePrefix?: string,
|
|
41
|
+
rootScoped?: boolean,
|
|
21
42
|
): string | undefined {
|
|
22
|
-
// 1.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
43
|
+
// 1. Dot-prefixed (".article", ".author.posts") — local resolution only.
|
|
44
|
+
// Resolves within the current include() scope using the mount prefix.
|
|
45
|
+
if (name.startsWith(".")) {
|
|
46
|
+
const lookupName = name.slice(1);
|
|
47
|
+
if (!currentRoutePrefix) return undefined;
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return routeMap[name];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// 3. Local name - try with current prefix first, then fall back to direct lookup
|
|
33
|
-
if (currentRoutePrefix) {
|
|
34
|
-
// Extract the prefix from current route name
|
|
35
|
-
// e.g., "blog.posts.detail" → prefix is "blog.posts"
|
|
49
|
+
// Extract the include prefix from current route name
|
|
50
|
+
// e.g., "magazine.author" -> prefix is "magazine"
|
|
36
51
|
const lastDot = currentRoutePrefix.lastIndexOf(".");
|
|
37
|
-
const prefix =
|
|
52
|
+
const prefix =
|
|
53
|
+
lastDot > 0
|
|
54
|
+
? currentRoutePrefix.substring(0, lastDot)
|
|
55
|
+
: currentRoutePrefix;
|
|
38
56
|
|
|
39
|
-
// Try prefixed name
|
|
40
|
-
const prefixedName = `${prefix}.${
|
|
57
|
+
// Try prefixed name at current level
|
|
58
|
+
const prefixedName = `${prefix}.${lookupName}`;
|
|
41
59
|
if (routeMap[prefixedName] !== undefined) {
|
|
42
60
|
return routeMap[prefixedName];
|
|
43
61
|
}
|
|
44
62
|
|
|
45
|
-
//
|
|
46
|
-
// e.g., for "blog.posts.detail", try "blog.posts.index", then "blog.index"
|
|
63
|
+
// Walk up parent prefixes for nested includes
|
|
47
64
|
let currentPrefix = prefix;
|
|
48
65
|
while (currentPrefix.includes(".")) {
|
|
49
66
|
const parentDot = currentPrefix.lastIndexOf(".");
|
|
50
67
|
currentPrefix = currentPrefix.substring(0, parentDot);
|
|
51
|
-
const parentPrefixedName = `${currentPrefix}.${
|
|
68
|
+
const parentPrefixedName = `${currentPrefix}.${lookupName}`;
|
|
52
69
|
if (routeMap[parentPrefixedName] !== undefined) {
|
|
53
70
|
return routeMap[parentPrefixedName];
|
|
54
71
|
}
|
|
55
72
|
}
|
|
73
|
+
|
|
74
|
+
// Fallback: try bare name at root scope only.
|
|
75
|
+
// Routes inside { name: "" } mounts are at root scope — their dot-local
|
|
76
|
+
// names can fall back to bare names (e.g., "sub.detail" reaching "flatIndex").
|
|
77
|
+
// Routes inside named mounts (e.g., { name: "magazine" }) are NOT at root
|
|
78
|
+
// scope — dot-local must not leak into unrelated global names.
|
|
79
|
+
//
|
|
80
|
+
// Resolution order: explicit param > scope registry > heuristic.
|
|
81
|
+
const isRootScoped =
|
|
82
|
+
rootScoped ??
|
|
83
|
+
isRouteRootScoped(currentRoutePrefix) ??
|
|
84
|
+
!currentRoutePrefix.includes(".");
|
|
85
|
+
if (isRootScoped) {
|
|
86
|
+
if (routeMap[lookupName] !== undefined) {
|
|
87
|
+
return routeMap[lookupName];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return undefined;
|
|
56
92
|
}
|
|
57
93
|
|
|
58
|
-
//
|
|
94
|
+
// 2. Unprefixed ("magazine.index", "blog.post") — global resolution only.
|
|
95
|
+
// Direct lookup in the full named-routes map.
|
|
59
96
|
return routeMap[name];
|
|
60
97
|
}
|
|
61
98
|
|
|
99
|
+
function createPrerenderPassthroughFn(
|
|
100
|
+
build: boolean,
|
|
101
|
+
isPassthroughRoute: boolean,
|
|
102
|
+
): () => typeof PRERENDER_PASSTHROUGH {
|
|
103
|
+
return () => {
|
|
104
|
+
if (!build) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
"ctx.passthrough() can only be called during build-time prerendering.",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (!isPassthroughRoute) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
"ctx.passthrough() is only available on routes declared with " +
|
|
112
|
+
"{ passthrough: true }. Remove the passthrough() call or add " +
|
|
113
|
+
"{ passthrough: true } to the Prerender options.",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return PRERENDER_PASSTHROUGH;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a reverse function for URL generation from route names.
|
|
122
|
+
* Used by both HandlerContext and MiddlewareContext.
|
|
123
|
+
*
|
|
124
|
+
* When currentParams is provided, those params are used as defaults for URL
|
|
125
|
+
* generation. This enables auto-filling mount params from include() prefixes:
|
|
126
|
+
* inner handlers can call ctx.reverse(".sibling") without explicitly passing
|
|
127
|
+
* params that are already known from the current URL match.
|
|
128
|
+
* Explicitly passed hrefParams take priority over currentParams.
|
|
129
|
+
*/
|
|
130
|
+
export function createReverseFunction(
|
|
131
|
+
routeMap: Record<string, string>,
|
|
132
|
+
currentRoutePrefix?: string,
|
|
133
|
+
currentParams?: Record<string, string>,
|
|
134
|
+
rootScoped?: boolean,
|
|
135
|
+
): (
|
|
136
|
+
name: string,
|
|
137
|
+
hrefParams?: Record<string, string>,
|
|
138
|
+
search?: Record<string, unknown>,
|
|
139
|
+
) => string {
|
|
140
|
+
return (name, hrefParams, search) => {
|
|
141
|
+
// Resolve route name with namespace support
|
|
142
|
+
const pattern = resolveRouteName(
|
|
143
|
+
name,
|
|
144
|
+
routeMap,
|
|
145
|
+
currentRoutePrefix,
|
|
146
|
+
rootScoped,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (pattern === undefined) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Unknown route: "${name}"${currentRoutePrefix ? ` (current route: ${currentRoutePrefix})` : ""}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let result = pattern;
|
|
156
|
+
|
|
157
|
+
// Merge current request params as defaults, explicit params override
|
|
158
|
+
const effectiveParams = currentParams
|
|
159
|
+
? { ...currentParams, ...hrefParams }
|
|
160
|
+
: hrefParams;
|
|
161
|
+
|
|
162
|
+
// Substitute params (strip constraint and optional syntax: :param(a|b)? -> value)
|
|
163
|
+
if (effectiveParams) {
|
|
164
|
+
result = result.replace(
|
|
165
|
+
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\??/g,
|
|
166
|
+
(_, key) => {
|
|
167
|
+
const value = effectiveParams[key];
|
|
168
|
+
if (value === undefined) {
|
|
169
|
+
throw new Error(`Missing param "${key}" for route "${name}"`);
|
|
170
|
+
}
|
|
171
|
+
return encodeURIComponent(value);
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Append search params as query string
|
|
177
|
+
if (search) {
|
|
178
|
+
const qs = serializeSearchParams(search);
|
|
179
|
+
if (qs) result += `?${qs}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
62
186
|
/**
|
|
63
187
|
* Create HandlerContext with typed env/var/get/set
|
|
64
188
|
*/
|
|
@@ -68,55 +192,69 @@ export function createHandlerContext<TEnv>(
|
|
|
68
192
|
searchParams: URLSearchParams,
|
|
69
193
|
pathname: string,
|
|
70
194
|
url: URL,
|
|
71
|
-
bindings:
|
|
195
|
+
bindings: TEnv = {} as TEnv,
|
|
72
196
|
routeMap: Record<string, string> = {},
|
|
73
|
-
routeName?: string
|
|
197
|
+
routeName?: string,
|
|
198
|
+
responseType?: string,
|
|
199
|
+
isPassthroughRoute: boolean = false,
|
|
74
200
|
): InternalHandlerContext<any, TEnv> {
|
|
75
201
|
// Get variables from request context - this is the unified context
|
|
76
202
|
// shared between middleware and route handlers
|
|
77
|
-
const requestContext =
|
|
203
|
+
const requestContext = _getRequestContext();
|
|
78
204
|
const variables: any = requestContext?.var ?? {};
|
|
79
205
|
|
|
80
|
-
// Filter system parameters (starting with _rsc) from searchParams
|
|
81
|
-
// This ensures handlers only see user-facing query params
|
|
82
|
-
const cleanSearchParams = new URLSearchParams();
|
|
83
|
-
searchParams.forEach((value, key) => {
|
|
84
|
-
if (!key.startsWith("_rsc")) {
|
|
85
|
-
cleanSearchParams.set(key, value);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Create clean URL without system params
|
|
90
|
-
const cleanUrl = new URL(url);
|
|
91
|
-
cleanUrl.search = cleanSearchParams.toString();
|
|
92
|
-
|
|
93
206
|
// If route has a search schema, parse URLSearchParams into typed object
|
|
94
207
|
const searchSchema = routeName ? getSearchSchema(routeName) : undefined;
|
|
95
208
|
const resolvedSearchParams = searchSchema
|
|
96
|
-
? parseSearchParams(
|
|
97
|
-
:
|
|
209
|
+
? parseSearchParams(searchParams, searchSchema)
|
|
210
|
+
: searchParams;
|
|
98
211
|
|
|
99
212
|
// Get stub response from request context for setting headers
|
|
100
|
-
const stubResponse =
|
|
213
|
+
const stubResponse =
|
|
214
|
+
requestContext?.res ?? new Response(null, { status: 200 });
|
|
101
215
|
|
|
102
|
-
|
|
216
|
+
// Guard mutating Headers methods so they throw inside "use cache" functions.
|
|
217
|
+
// Uses lazy `ctx` reference (assigned below) — only the specific handler ctx
|
|
218
|
+
// is stamped by cache-runtime, not the shared request context.
|
|
219
|
+
const MUTATING_HEADERS_METHODS = new Set(["set", "append", "delete"]);
|
|
220
|
+
let ctx: InternalHandlerContext<any, TEnv>;
|
|
221
|
+
const guardedHeaders = new Proxy(stubResponse.headers, {
|
|
222
|
+
get(target, prop, receiver) {
|
|
223
|
+
const value = Reflect.get(target, prop, receiver);
|
|
224
|
+
if (typeof value === "function") {
|
|
225
|
+
if (MUTATING_HEADERS_METHODS.has(prop as string)) {
|
|
226
|
+
return (...args: any[]) => {
|
|
227
|
+
assertNotInsideCacheExec(ctx, "headers");
|
|
228
|
+
return value.apply(target, args);
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return value.bind(target);
|
|
232
|
+
}
|
|
233
|
+
return value;
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
ctx = {
|
|
103
238
|
params,
|
|
239
|
+
build: false,
|
|
104
240
|
request,
|
|
105
|
-
searchParams
|
|
241
|
+
searchParams,
|
|
242
|
+
search: searchSchema ? resolvedSearchParams : {},
|
|
106
243
|
pathname,
|
|
107
|
-
url
|
|
244
|
+
url,
|
|
245
|
+
originalUrl: new URL(request.url),
|
|
108
246
|
env: bindings,
|
|
109
247
|
var: variables,
|
|
110
|
-
get: ((
|
|
248
|
+
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as HandlerContext<
|
|
111
249
|
any,
|
|
112
250
|
TEnv
|
|
113
251
|
>["get"],
|
|
114
|
-
set: ((
|
|
115
|
-
|
|
252
|
+
set: ((keyOrVar: any, value: any) => {
|
|
253
|
+
assertNotInsideCacheExec(ctx, "set");
|
|
254
|
+
contextSet(variables, keyOrVar, value);
|
|
116
255
|
}) as HandlerContext<any, TEnv>["set"],
|
|
117
|
-
_originalRequest: request, // Raw request for advanced use
|
|
118
256
|
res: stubResponse, // Stub response for setting headers
|
|
119
|
-
headers:
|
|
257
|
+
headers: guardedHeaders, // Guarded shorthand for res.headers
|
|
120
258
|
// Placeholder use() - will be replaced with actual implementation during request
|
|
121
259
|
use: () => {
|
|
122
260
|
throw new Error("ctx.use() called before loaders were initialized");
|
|
@@ -124,73 +262,53 @@ export function createHandlerContext<TEnv>(
|
|
|
124
262
|
// Theme support (when enabled via router config)
|
|
125
263
|
theme: requestContext?.theme,
|
|
126
264
|
setTheme: requestContext?.setTheme,
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (name.startsWith("/")) {
|
|
131
|
-
let result = name;
|
|
132
|
-
if (hrefParams) {
|
|
133
|
-
result = result.replace(/:([^/]+)/g, (_, key) => {
|
|
134
|
-
const value = hrefParams[key];
|
|
135
|
-
if (value === undefined) {
|
|
136
|
-
throw new Error(`Missing param "${key}" for path "${name}"`);
|
|
137
|
-
}
|
|
138
|
-
return encodeURIComponent(value);
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
if (search) {
|
|
142
|
-
const qs = serializeSearchParams(search);
|
|
143
|
-
if (qs) result += `?${qs}`;
|
|
144
|
-
}
|
|
145
|
-
return result;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Resolve route name with namespace support
|
|
149
|
-
const pattern = resolveRouteName(name, routeMap, routeName);
|
|
150
|
-
|
|
151
|
-
if (pattern === undefined) {
|
|
265
|
+
// Location state support (delegates to request context)
|
|
266
|
+
setLocationState(entries) {
|
|
267
|
+
if (!requestContext) {
|
|
152
268
|
throw new Error(
|
|
153
|
-
|
|
269
|
+
"setLocationState() is not available outside a request context",
|
|
154
270
|
);
|
|
155
271
|
}
|
|
156
|
-
|
|
157
|
-
let result = pattern;
|
|
158
|
-
|
|
159
|
-
// Substitute params
|
|
160
|
-
if (hrefParams) {
|
|
161
|
-
result = result.replace(/:([^/]+)/g, (_, key) => {
|
|
162
|
-
const value = hrefParams[key];
|
|
163
|
-
if (value === undefined) {
|
|
164
|
-
throw new Error(`Missing param "${key}" for route "${name}"`);
|
|
165
|
-
}
|
|
166
|
-
return encodeURIComponent(value);
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Append search params as query string
|
|
171
|
-
if (search) {
|
|
172
|
-
const qs = serializeSearchParams(search);
|
|
173
|
-
if (qs) result += `?${qs}`;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return result;
|
|
272
|
+
requestContext.setLocationState(entries);
|
|
177
273
|
},
|
|
274
|
+
routeName: (routeName && !isAutoGeneratedRouteName(routeName)
|
|
275
|
+
? routeName
|
|
276
|
+
: undefined) as HandlerContext["routeName"],
|
|
277
|
+
// Scoped reverse for URL generation (auto-fills current request params).
|
|
278
|
+
// Resolve rootScoped eagerly so the reverse function is self-contained
|
|
279
|
+
// and does not depend on the global rootScopeRoutes registry at call time.
|
|
280
|
+
reverse: createReverseFunction(
|
|
281
|
+
routeMap,
|
|
282
|
+
routeName,
|
|
283
|
+
params,
|
|
284
|
+
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
285
|
+
),
|
|
286
|
+
passthrough: createPrerenderPassthroughFn(false, isPassthroughRoute),
|
|
287
|
+
_responseType: responseType,
|
|
288
|
+
_routeName: routeName,
|
|
178
289
|
};
|
|
290
|
+
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
291
|
+
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
292
|
+
return ctx;
|
|
179
293
|
}
|
|
180
294
|
|
|
181
295
|
/**
|
|
182
|
-
* Create a
|
|
296
|
+
* Create a PrerenderContext for Prerender() handlers at build time.
|
|
183
297
|
*
|
|
184
|
-
* Returns
|
|
185
|
-
* and use(handle) work
|
|
186
|
-
*
|
|
298
|
+
* Returns an InternalHandlerContext where params, pathname, url, searchParams,
|
|
299
|
+
* search, reverse, and use(handle) work. Request-time properties
|
|
300
|
+
* (request, env, headers, cookies, var, get, set, res) throw with a clear error.
|
|
187
301
|
*/
|
|
188
|
-
export function
|
|
302
|
+
export function createPrerenderContext<TEnv>(
|
|
189
303
|
params: Record<string, string>,
|
|
190
304
|
pathname: string,
|
|
191
|
-
|
|
305
|
+
routeMap: Record<string, string>,
|
|
306
|
+
routeName?: string,
|
|
307
|
+
buildVars?: Record<string, any>,
|
|
308
|
+
isPassthroughRoute?: boolean,
|
|
192
309
|
): InternalHandlerContext<any, TEnv> {
|
|
193
310
|
const syntheticUrl = new URL(`http://prerender${pathname}`);
|
|
311
|
+
const variables = buildVars ?? {};
|
|
194
312
|
|
|
195
313
|
function throwUnavailable(prop: string): never {
|
|
196
314
|
throw new Error(
|
|
@@ -201,29 +319,109 @@ export function createBuildContext<TEnv>(
|
|
|
201
319
|
|
|
202
320
|
return {
|
|
203
321
|
params,
|
|
322
|
+
build: true,
|
|
204
323
|
get request(): Request {
|
|
205
324
|
return throwUnavailable("request");
|
|
206
325
|
},
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
},
|
|
326
|
+
searchParams: syntheticUrl.searchParams,
|
|
327
|
+
search: {},
|
|
210
328
|
pathname,
|
|
211
329
|
url: syntheticUrl,
|
|
330
|
+
originalUrl: syntheticUrl,
|
|
212
331
|
get env(): TEnv {
|
|
213
332
|
return throwUnavailable("env");
|
|
214
333
|
},
|
|
215
334
|
get var(): any {
|
|
216
335
|
return throwUnavailable("var");
|
|
217
336
|
},
|
|
218
|
-
get: (() =>
|
|
219
|
-
|
|
337
|
+
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
338
|
+
set: ((keyOrVar: any, value: any) => {
|
|
339
|
+
contextSet(variables, keyOrVar, value);
|
|
220
340
|
}) as any,
|
|
221
|
-
|
|
222
|
-
throwUnavailable("
|
|
223
|
-
}
|
|
224
|
-
get
|
|
341
|
+
get res(): Response {
|
|
342
|
+
return throwUnavailable("res");
|
|
343
|
+
},
|
|
344
|
+
get headers(): Headers {
|
|
345
|
+
return throwUnavailable("headers");
|
|
346
|
+
},
|
|
347
|
+
// Placeholder use() - replaced by setupBuildUse
|
|
348
|
+
use: () => {
|
|
349
|
+
throw new Error("ctx.use() called before build context was initialized");
|
|
350
|
+
},
|
|
351
|
+
theme: undefined,
|
|
352
|
+
setTheme: undefined,
|
|
353
|
+
routeName: (routeName && !isAutoGeneratedRouteName(routeName)
|
|
354
|
+
? routeName
|
|
355
|
+
: undefined) as HandlerContext["routeName"],
|
|
356
|
+
setLocationState: () => {
|
|
357
|
+
throwUnavailable("setLocationState");
|
|
358
|
+
},
|
|
359
|
+
reverse: createReverseFunction(
|
|
360
|
+
routeMap,
|
|
361
|
+
routeName,
|
|
362
|
+
params,
|
|
363
|
+
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
364
|
+
),
|
|
365
|
+
passthrough: createPrerenderPassthroughFn(
|
|
366
|
+
true,
|
|
367
|
+
isPassthroughRoute === true,
|
|
368
|
+
),
|
|
369
|
+
_routeName: routeName,
|
|
370
|
+
} as InternalHandlerContext<any, TEnv>;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Create a StaticContext for Static() handlers at build time.
|
|
375
|
+
*
|
|
376
|
+
* Returns an InternalHandlerContext where only reverse and use(handle) work.
|
|
377
|
+
* Static handlers have no URL, no params, no pathname — everything else throws.
|
|
378
|
+
*/
|
|
379
|
+
export function createStaticContext<TEnv>(
|
|
380
|
+
routeMap: Record<string, string>,
|
|
381
|
+
routeName?: string,
|
|
382
|
+
): InternalHandlerContext<any, TEnv> {
|
|
383
|
+
const variables: Record<string, any> = {};
|
|
384
|
+
|
|
385
|
+
function throwUnavailable(prop: string): never {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Property "${prop}" is not available in Static() handlers. ` +
|
|
388
|
+
`Static handlers render content without request context.`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
get params(): any {
|
|
394
|
+
return throwUnavailable("params");
|
|
395
|
+
},
|
|
396
|
+
build: true,
|
|
397
|
+
get request(): Request {
|
|
225
398
|
return throwUnavailable("request");
|
|
226
399
|
},
|
|
400
|
+
get searchParams(): URLSearchParams {
|
|
401
|
+
return throwUnavailable("searchParams");
|
|
402
|
+
},
|
|
403
|
+
get search(): any {
|
|
404
|
+
return throwUnavailable("search");
|
|
405
|
+
},
|
|
406
|
+
get pathname(): string {
|
|
407
|
+
return throwUnavailable("pathname");
|
|
408
|
+
},
|
|
409
|
+
get url(): URL {
|
|
410
|
+
return throwUnavailable("url");
|
|
411
|
+
},
|
|
412
|
+
get originalUrl(): URL {
|
|
413
|
+
return throwUnavailable("originalUrl");
|
|
414
|
+
},
|
|
415
|
+
get env(): TEnv {
|
|
416
|
+
return throwUnavailable("env");
|
|
417
|
+
},
|
|
418
|
+
get var(): any {
|
|
419
|
+
return throwUnavailable("var");
|
|
420
|
+
},
|
|
421
|
+
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
422
|
+
set: ((keyOrVar: any, value: any) => {
|
|
423
|
+
contextSet(variables, keyOrVar, value);
|
|
424
|
+
}) as any,
|
|
227
425
|
get res(): Response {
|
|
228
426
|
return throwUnavailable("res");
|
|
229
427
|
},
|
|
@@ -236,8 +434,18 @@ export function createBuildContext<TEnv>(
|
|
|
236
434
|
},
|
|
237
435
|
theme: undefined,
|
|
238
436
|
setTheme: undefined,
|
|
239
|
-
|
|
240
|
-
|
|
437
|
+
routeName: (routeName && !isAutoGeneratedRouteName(routeName)
|
|
438
|
+
? routeName
|
|
439
|
+
: undefined) as HandlerContext["routeName"],
|
|
440
|
+
setLocationState: () => {
|
|
441
|
+
throwUnavailable("setLocationState");
|
|
241
442
|
},
|
|
443
|
+
reverse: createReverseFunction(
|
|
444
|
+
routeMap,
|
|
445
|
+
routeName,
|
|
446
|
+
undefined,
|
|
447
|
+
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
448
|
+
),
|
|
449
|
+
_routeName: routeName,
|
|
242
450
|
} as InternalHandlerContext<any, TEnv>;
|
|
243
451
|
}
|
|
@@ -15,6 +15,8 @@ import type { HandlerContext, ResolvedSegment } from "../types";
|
|
|
15
15
|
import { evaluateRevalidation } from "./revalidation.js";
|
|
16
16
|
import { getRequestContext } from "../server/request-context.js";
|
|
17
17
|
import { executeInterceptMiddleware } from "./middleware.js";
|
|
18
|
+
import { createReverseFunction } from "./handler-context.js";
|
|
19
|
+
import { getGlobalRouteMap } from "../route-map-builder.js";
|
|
18
20
|
import { handleHandlerResult } from "./segment-resolution.js";
|
|
19
21
|
import type { SegmentResolutionDeps } from "./types.js";
|
|
20
22
|
import { debugLog } from "./logging.js";
|
|
@@ -133,6 +135,7 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
133
135
|
params,
|
|
134
136
|
context.var as Record<string, any>,
|
|
135
137
|
requestCtx.res,
|
|
138
|
+
createReverseFunction(getGlobalRouteMap()),
|
|
136
139
|
);
|
|
137
140
|
if (middlewareResponse) throw middlewareResponse;
|
|
138
141
|
}
|
|
@@ -185,6 +188,7 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
185
188
|
context,
|
|
186
189
|
actionContext,
|
|
187
190
|
stale,
|
|
191
|
+
traceSource: "intercept-loader",
|
|
188
192
|
});
|
|
189
193
|
|
|
190
194
|
if (!shouldRevalidate) {
|
|
@@ -352,6 +356,7 @@ export async function resolveInterceptLoadersOnly<TEnv>(
|
|
|
352
356
|
context,
|
|
353
357
|
actionContext,
|
|
354
358
|
stale,
|
|
359
|
+
traceSource: "intercept-loader",
|
|
355
360
|
});
|
|
356
361
|
|
|
357
362
|
if (!shouldRevalidate) {
|
|
@@ -381,10 +386,12 @@ export async function resolveInterceptLoadersOnly<TEnv>(
|
|
|
381
386
|
return null;
|
|
382
387
|
}
|
|
383
388
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
389
|
+
// Match fresh-path semantics: only defer (no await) when loading is truthy.
|
|
390
|
+
// `loading: false` means "no loading UI, await loaders before render" —
|
|
391
|
+
// same as the fresh path's `if (interceptEntry.loading && ...)` check.
|
|
392
|
+
const loaderDataPromise = interceptEntry.loading
|
|
393
|
+
? Promise.all(loaderPromises)
|
|
394
|
+
: await Promise.all(loaderPromises);
|
|
388
395
|
|
|
389
396
|
return { loaderDataPromise, loaderIds };
|
|
390
397
|
}
|