@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
|
@@ -13,14 +13,30 @@
|
|
|
13
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
14
|
import type { CookieOptions } from "../router/middleware.js";
|
|
15
15
|
import type { LoaderDefinition, LoaderContext } from "../types.js";
|
|
16
|
+
import type { ScopedReverseFunction } from "../reverse.js";
|
|
17
|
+
import type {
|
|
18
|
+
DefaultEnv,
|
|
19
|
+
DefaultReverseRouteMap,
|
|
20
|
+
DefaultRouteName,
|
|
21
|
+
} from "../types/global-namespace.js";
|
|
16
22
|
import type { Handle } from "../handle.js";
|
|
23
|
+
import { type ContextVar, contextGet, contextSet } from "../context-var.js";
|
|
17
24
|
import { createHandleStore, type HandleStore } from "./handle-store.js";
|
|
18
25
|
import { isHandle } from "../handle.js";
|
|
19
|
-
import { track } from "./context.js";
|
|
26
|
+
import { track, type MetricsStore } from "./context.js";
|
|
20
27
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
21
28
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
22
29
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
23
30
|
import { THEME_COOKIE } from "../theme/constants.js";
|
|
31
|
+
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
32
|
+
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
33
|
+
import {
|
|
34
|
+
createReverseFunction,
|
|
35
|
+
stripInternalParams,
|
|
36
|
+
} from "../router/handler-context.js";
|
|
37
|
+
import { getGlobalRouteMap, isRouteRootScoped } from "../route-map-builder.js";
|
|
38
|
+
import { invariant } from "../errors.js";
|
|
39
|
+
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
24
40
|
|
|
25
41
|
/**
|
|
26
42
|
* Unified request context available via getRequestContext()
|
|
@@ -29,46 +45,61 @@ import { THEME_COOKIE } from "../theme/constants.js";
|
|
|
29
45
|
* Use this when you need access to request data outside of route handlers.
|
|
30
46
|
*/
|
|
31
47
|
export interface RequestContext<
|
|
32
|
-
TEnv =
|
|
48
|
+
TEnv = DefaultEnv,
|
|
33
49
|
TParams = Record<string, string>,
|
|
34
50
|
> {
|
|
35
51
|
/** Platform bindings (Cloudflare env, etc.) */
|
|
36
52
|
env: TEnv;
|
|
37
53
|
/** Original HTTP request */
|
|
38
54
|
request: Request;
|
|
39
|
-
/** Parsed URL (
|
|
55
|
+
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
40
56
|
url: URL;
|
|
57
|
+
/**
|
|
58
|
+
* The original request URL with all parameters intact, including
|
|
59
|
+
* internal `_rsc*` transport params.
|
|
60
|
+
*/
|
|
61
|
+
originalUrl: URL;
|
|
41
62
|
/** URL pathname */
|
|
42
63
|
pathname: string;
|
|
43
|
-
/** URL search params (
|
|
64
|
+
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
44
65
|
searchParams: URLSearchParams;
|
|
45
66
|
/** Variables set by middleware (same as ctx.var) */
|
|
46
67
|
var: Record<string, any>;
|
|
47
68
|
/** Get a variable set by middleware */
|
|
48
|
-
get:
|
|
69
|
+
get: {
|
|
70
|
+
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
71
|
+
<K extends string>(key: K): any;
|
|
72
|
+
};
|
|
49
73
|
/** Set a variable (shared with middleware and handlers) */
|
|
50
|
-
set:
|
|
74
|
+
set: {
|
|
75
|
+
<T>(contextVar: ContextVar<T>, value: T): void;
|
|
76
|
+
<K extends string>(key: K, value: any): void;
|
|
77
|
+
};
|
|
51
78
|
/**
|
|
52
79
|
* Route params (populated after route matching)
|
|
53
80
|
* Initially empty, then set to matched params
|
|
54
81
|
*/
|
|
55
82
|
params: TParams;
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
* Headers set here are merged into the final response
|
|
59
|
-
*/
|
|
60
|
-
res: Response;
|
|
83
|
+
/** @internal Stub response for collecting headers/cookies. Use ctx.headers or ctx.header() instead. */
|
|
84
|
+
readonly res: Response;
|
|
61
85
|
|
|
62
|
-
/** Get a cookie value
|
|
86
|
+
/** @internal Get a cookie value (effective: request + response mutations). Use cookies().get() instead. */
|
|
63
87
|
cookie(name: string): string | undefined;
|
|
64
|
-
/** Get all cookies
|
|
88
|
+
/** @internal Get all cookies (effective merged view). Use cookies().getAll() instead. */
|
|
65
89
|
cookies(): Record<string, string>;
|
|
66
|
-
/** Set a cookie on the response */
|
|
90
|
+
/** @internal Set a cookie on the response. Use cookies().set() instead. */
|
|
67
91
|
setCookie(name: string, value: string, options?: CookieOptions): void;
|
|
68
|
-
/** Delete a cookie */
|
|
69
|
-
deleteCookie(
|
|
92
|
+
/** @internal Delete a cookie. Use cookies().delete() instead. */
|
|
93
|
+
deleteCookie(
|
|
94
|
+
name: string,
|
|
95
|
+
options?: Pick<CookieOptions, "domain" | "path">,
|
|
96
|
+
): void;
|
|
70
97
|
/** Set a response header */
|
|
71
98
|
header(name: string, value: string): void;
|
|
99
|
+
/** Set the response status code */
|
|
100
|
+
setStatus(status: number): void;
|
|
101
|
+
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
102
|
+
_setStatus(status: number): void;
|
|
72
103
|
|
|
73
104
|
/**
|
|
74
105
|
* Access loader data or push handle data.
|
|
@@ -90,10 +121,12 @@ export interface RequestContext<
|
|
|
90
121
|
* ```
|
|
91
122
|
*/
|
|
92
123
|
use: {
|
|
93
|
-
<T, TLoaderParams = any>(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
<T, TLoaderParams = any>(
|
|
125
|
+
loader: LoaderDefinition<T, TLoaderParams>,
|
|
126
|
+
): Promise<T>;
|
|
127
|
+
<TData, TAccumulated = TData[]>(
|
|
128
|
+
handle: Handle<TData, TAccumulated>,
|
|
129
|
+
): (data: TData | Promise<TData> | (() => Promise<TData>)) => void;
|
|
97
130
|
};
|
|
98
131
|
|
|
99
132
|
/** HTTP method (GET, POST, PUT, PATCH, DELETE, etc.) */
|
|
@@ -105,6 +138,12 @@ export interface RequestContext<
|
|
|
105
138
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
106
139
|
_cacheStore?: SegmentCacheStore;
|
|
107
140
|
|
|
141
|
+
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
142
|
+
_cacheProfiles?: Record<
|
|
143
|
+
string,
|
|
144
|
+
import("../cache/profile-registry.js").CacheProfile
|
|
145
|
+
>;
|
|
146
|
+
|
|
108
147
|
/**
|
|
109
148
|
* Schedule work to run after the response is sent.
|
|
110
149
|
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
@@ -178,8 +217,99 @@ export interface RequestContext<
|
|
|
178
217
|
|
|
179
218
|
/** @internal Theme configuration (null if theme not enabled) */
|
|
180
219
|
_themeConfig?: ResolvedThemeConfig | null;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Attach location state entries to the current response.
|
|
223
|
+
*
|
|
224
|
+
* For partial (SPA) requests, the state is included in the RSC payload
|
|
225
|
+
* metadata and merged into history.pushState on the client. For redirect
|
|
226
|
+
* responses, the state travels through the redirect payload so the target
|
|
227
|
+
* page can read it via useLocationState.
|
|
228
|
+
*
|
|
229
|
+
* Multiple calls accumulate entries.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* ctx.setLocationState(Flash({ text: "Item saved!" }));
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
237
|
+
|
|
238
|
+
/** @internal Accumulated location state entries */
|
|
239
|
+
_locationState?: LocationStateEntry[];
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The matched route name, if the route has an explicit name.
|
|
243
|
+
* Undefined before route matching or for unnamed routes.
|
|
244
|
+
* Includes the namespace prefix from include() (e.g., "blog.post").
|
|
245
|
+
*/
|
|
246
|
+
routeName?: DefaultRouteName;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generate URLs from route names.
|
|
250
|
+
* Uses the global route map. After route matching, scoped (`.name`) resolution
|
|
251
|
+
* works within the matched include() scope.
|
|
252
|
+
*/
|
|
253
|
+
reverse: ScopedReverseFunction<
|
|
254
|
+
Record<string, string>,
|
|
255
|
+
DefaultReverseRouteMap
|
|
256
|
+
>;
|
|
257
|
+
|
|
258
|
+
/** @internal Route name from route matching, used for scoped reverse resolution */
|
|
259
|
+
_routeName?: string;
|
|
260
|
+
|
|
261
|
+
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
262
|
+
_prevRouteKey?: string;
|
|
263
|
+
|
|
264
|
+
/** @internal Per-request error dedup set for onError reporting */
|
|
265
|
+
_reportedErrors: WeakSet<object>;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @internal Report a non-fatal background error through the router's
|
|
269
|
+
* onError callback. Wired by the RSC handler / router during request
|
|
270
|
+
* creation. Cache-runtime and other subsystems call this to surface
|
|
271
|
+
* errors without failing the response.
|
|
272
|
+
*/
|
|
273
|
+
_reportBackgroundError?: (error: unknown, category: string) => void;
|
|
274
|
+
|
|
275
|
+
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
276
|
+
_debugPerformance?: boolean;
|
|
277
|
+
|
|
278
|
+
/** @internal Request-scoped performance metrics store */
|
|
279
|
+
_metricsStore?: MetricsStore;
|
|
181
280
|
}
|
|
182
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Public view of RequestContext, without internal methods and fields.
|
|
284
|
+
*
|
|
285
|
+
* This is the type exported to library consumers. Internal code should
|
|
286
|
+
* use the full RequestContext interface directly.
|
|
287
|
+
*/
|
|
288
|
+
export type PublicRequestContext<
|
|
289
|
+
TEnv = DefaultEnv,
|
|
290
|
+
TParams = Record<string, string>,
|
|
291
|
+
> = Omit<
|
|
292
|
+
RequestContext<TEnv, TParams>,
|
|
293
|
+
| "cookie"
|
|
294
|
+
| "cookies"
|
|
295
|
+
| "setCookie"
|
|
296
|
+
| "deleteCookie"
|
|
297
|
+
| "_handleStore"
|
|
298
|
+
| "_cacheStore"
|
|
299
|
+
| "_cacheProfiles"
|
|
300
|
+
| "_onResponseCallbacks"
|
|
301
|
+
| "_themeConfig"
|
|
302
|
+
| "_locationState"
|
|
303
|
+
| "_routeName"
|
|
304
|
+
| "_prevRouteKey"
|
|
305
|
+
| "_reportedErrors"
|
|
306
|
+
| "_reportBackgroundError"
|
|
307
|
+
| "_debugPerformance"
|
|
308
|
+
| "_metricsStore"
|
|
309
|
+
| "_setStatus"
|
|
310
|
+
| "res"
|
|
311
|
+
>;
|
|
312
|
+
|
|
183
313
|
// AsyncLocalStorage instance for request context
|
|
184
314
|
const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
185
315
|
|
|
@@ -189,16 +319,33 @@ const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
|
189
319
|
*/
|
|
190
320
|
export function runWithRequestContext<TEnv, T>(
|
|
191
321
|
context: RequestContext<TEnv>,
|
|
192
|
-
fn: () => T
|
|
322
|
+
fn: () => T,
|
|
193
323
|
): T {
|
|
194
324
|
return requestContextStorage.run(context, fn);
|
|
195
325
|
}
|
|
196
326
|
|
|
197
327
|
/**
|
|
198
328
|
* Get the current request context
|
|
199
|
-
*
|
|
329
|
+
* Throws if called outside of a request context
|
|
200
330
|
*/
|
|
201
|
-
export function getRequestContext<TEnv =
|
|
331
|
+
export function getRequestContext<TEnv = DefaultEnv>(): RequestContext<TEnv> {
|
|
332
|
+
const ctx = requestContextStorage.getStore() as
|
|
333
|
+
| RequestContext<TEnv>
|
|
334
|
+
| undefined;
|
|
335
|
+
invariant(
|
|
336
|
+
ctx,
|
|
337
|
+
"getRequestContext() called outside of a request context. " +
|
|
338
|
+
"This function must be called from within a route handler, loader, middleware, " +
|
|
339
|
+
"server action, or server component.",
|
|
340
|
+
);
|
|
341
|
+
return ctx;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @internal Get the request context without throwing — for internal code that
|
|
346
|
+
* may run outside a request context (cache stores, optional handle lookups, etc.)
|
|
347
|
+
*/
|
|
348
|
+
export function _getRequestContext<TEnv = DefaultEnv>():
|
|
202
349
|
| RequestContext<TEnv>
|
|
203
350
|
| undefined {
|
|
204
351
|
return requestContextStorage.getStore() as RequestContext<TEnv> | undefined;
|
|
@@ -206,28 +353,67 @@ export function getRequestContext<TEnv = unknown>():
|
|
|
206
353
|
|
|
207
354
|
/**
|
|
208
355
|
* Update params on the current request context
|
|
209
|
-
* Called after route matching to populate route params
|
|
356
|
+
* Called after route matching to populate route params and route name
|
|
210
357
|
*/
|
|
211
|
-
export function setRequestContextParams(
|
|
358
|
+
export function setRequestContextParams(
|
|
359
|
+
params: Record<string, string>,
|
|
360
|
+
routeName?: string,
|
|
361
|
+
): void {
|
|
212
362
|
const ctx = requestContextStorage.getStore();
|
|
213
363
|
if (ctx) {
|
|
214
364
|
ctx.params = params;
|
|
365
|
+
if (routeName !== undefined) {
|
|
366
|
+
ctx._routeName = routeName;
|
|
367
|
+
ctx.routeName = (
|
|
368
|
+
routeName && !isAutoGeneratedRouteName(routeName)
|
|
369
|
+
? routeName
|
|
370
|
+
: undefined
|
|
371
|
+
) as DefaultRouteName | undefined;
|
|
372
|
+
}
|
|
373
|
+
// Update reverse with scoped resolution now that route is known
|
|
374
|
+
ctx.reverse = createReverseFunction(
|
|
375
|
+
getGlobalRouteMap(),
|
|
376
|
+
routeName,
|
|
377
|
+
params,
|
|
378
|
+
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
379
|
+
);
|
|
215
380
|
}
|
|
216
381
|
}
|
|
217
382
|
|
|
218
383
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
384
|
+
* Store the previous route key on the request context.
|
|
385
|
+
* Called during partial-match context creation to make the navigation source
|
|
386
|
+
* route key available for revalidation and intercept evaluation.
|
|
387
|
+
* @internal
|
|
221
388
|
*/
|
|
222
|
-
export function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
);
|
|
389
|
+
export function setRequestContextPrevRouteKey(
|
|
390
|
+
prevRouteKey: string | undefined,
|
|
391
|
+
): void {
|
|
392
|
+
const ctx = requestContextStorage.getStore();
|
|
393
|
+
if (ctx && prevRouteKey !== undefined) {
|
|
394
|
+
ctx._prevRouteKey = prevRouteKey;
|
|
229
395
|
}
|
|
230
|
-
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get accumulated location state entries from the current request context.
|
|
400
|
+
* Returns undefined if no state has been set.
|
|
401
|
+
*
|
|
402
|
+
* @internal Used by the RSC handler to include state in payload metadata.
|
|
403
|
+
*/
|
|
404
|
+
export function getLocationState(): LocationStateEntry[] | undefined {
|
|
405
|
+
const ctx = getRequestContext();
|
|
406
|
+
return ctx?._locationState;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get the current request context, throwing if not available
|
|
411
|
+
* @deprecated Use getRequestContext() directly — it now throws if outside context
|
|
412
|
+
*/
|
|
413
|
+
export function requireRequestContext<
|
|
414
|
+
TEnv = DefaultEnv,
|
|
415
|
+
>(): RequestContext<TEnv> {
|
|
416
|
+
return getRequestContext<TEnv>();
|
|
231
417
|
}
|
|
232
418
|
|
|
233
419
|
/**
|
|
@@ -246,8 +432,15 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
246
432
|
request: Request;
|
|
247
433
|
url: URL;
|
|
248
434
|
variables: Record<string, any>;
|
|
435
|
+
/** Optional initial response stub headers/status to seed effective cookie reads */
|
|
436
|
+
initialResponse?: Response;
|
|
249
437
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
250
438
|
cacheStore?: SegmentCacheStore;
|
|
439
|
+
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
440
|
+
cacheProfiles?: Record<
|
|
441
|
+
string,
|
|
442
|
+
import("../cache/profile-registry.js").CacheProfile
|
|
443
|
+
>;
|
|
251
444
|
/** Optional Cloudflare execution context for waitUntil support */
|
|
252
445
|
executionContext?: ExecutionContext;
|
|
253
446
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
@@ -263,20 +456,37 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
263
456
|
* - Passed to handlers as ctx
|
|
264
457
|
*/
|
|
265
458
|
export function createRequestContext<TEnv>(
|
|
266
|
-
options: CreateRequestContextOptions<TEnv
|
|
459
|
+
options: CreateRequestContextOptions<TEnv>,
|
|
267
460
|
): RequestContext<TEnv> {
|
|
268
|
-
const {
|
|
461
|
+
const {
|
|
462
|
+
env,
|
|
463
|
+
request,
|
|
464
|
+
url,
|
|
465
|
+
variables,
|
|
466
|
+
initialResponse,
|
|
467
|
+
cacheStore,
|
|
468
|
+
cacheProfiles,
|
|
469
|
+
executionContext,
|
|
470
|
+
themeConfig,
|
|
471
|
+
} = options;
|
|
269
472
|
const cookieHeader = request.headers.get("Cookie");
|
|
270
473
|
let parsedCookies: Record<string, string> | null = null;
|
|
271
474
|
|
|
272
|
-
// Create stub response for collecting headers/cookies
|
|
273
|
-
|
|
475
|
+
// Create stub response for collecting headers/cookies.
|
|
476
|
+
// All cookie/header mutations go here; cookie reads derive from it.
|
|
477
|
+
let stubResponse = initialResponse
|
|
478
|
+
? new Response(null, {
|
|
479
|
+
status: initialResponse.status,
|
|
480
|
+
statusText: initialResponse.statusText,
|
|
481
|
+
headers: new Headers(initialResponse.headers),
|
|
482
|
+
})
|
|
483
|
+
: new Response(null, { status: 200 });
|
|
274
484
|
|
|
275
485
|
// Create handle store and loader memoization for this request
|
|
276
486
|
const handleStore = createHandleStore();
|
|
277
487
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
278
488
|
|
|
279
|
-
// Lazy parse cookies
|
|
489
|
+
// Lazy parse cookies from the original Cookie header
|
|
280
490
|
const getParsedCookies = (): Record<string, string> => {
|
|
281
491
|
if (!parsedCookies) {
|
|
282
492
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -284,11 +494,35 @@ export function createRequestContext<TEnv>(
|
|
|
284
494
|
return parsedCookies;
|
|
285
495
|
};
|
|
286
496
|
|
|
497
|
+
// Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
|
|
498
|
+
let responseCookieCache: Map<string, string | null> | null = null;
|
|
499
|
+
const getResponseCookies = (): Map<string, string | null> => {
|
|
500
|
+
if (!responseCookieCache) {
|
|
501
|
+
responseCookieCache = parseResponseCookies(stubResponse);
|
|
502
|
+
}
|
|
503
|
+
return responseCookieCache;
|
|
504
|
+
};
|
|
505
|
+
const invalidateResponseCookieCache = () => {
|
|
506
|
+
responseCookieCache = null;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Effective cookie read: response stub Set-Cookie wins, then original header.
|
|
510
|
+
// The stub IS the source of truth for same-request mutations.
|
|
511
|
+
const effectiveCookie = (name: string): string | undefined => {
|
|
512
|
+
const mutations = getResponseCookies();
|
|
513
|
+
if (mutations.has(name)) {
|
|
514
|
+
const v = mutations.get(name);
|
|
515
|
+
return v === null ? undefined : v;
|
|
516
|
+
}
|
|
517
|
+
return getParsedCookies()[name];
|
|
518
|
+
};
|
|
519
|
+
|
|
287
520
|
// Theme helpers (only used when themeConfig is provided)
|
|
288
521
|
const getTheme = (): Theme | undefined => {
|
|
289
522
|
if (!themeConfig) return undefined;
|
|
290
523
|
|
|
291
|
-
|
|
524
|
+
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
525
|
+
const stored = effectiveCookie(themeConfig.storageKey);
|
|
292
526
|
if (stored) {
|
|
293
527
|
// Validate stored value
|
|
294
528
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
@@ -306,65 +540,117 @@ export function createRequestContext<TEnv>(
|
|
|
306
540
|
|
|
307
541
|
// Validate theme value
|
|
308
542
|
if (theme !== "system" && !themeConfig.themes.includes(theme)) {
|
|
309
|
-
console.warn(
|
|
543
|
+
console.warn(
|
|
544
|
+
`[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`,
|
|
545
|
+
);
|
|
310
546
|
return;
|
|
311
547
|
}
|
|
312
548
|
|
|
313
|
-
//
|
|
549
|
+
// Write to stub — effectiveCookie() will pick it up on next read
|
|
314
550
|
stubResponse.headers.append(
|
|
315
551
|
"Set-Cookie",
|
|
316
552
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
317
553
|
path: THEME_COOKIE.path,
|
|
318
554
|
maxAge: THEME_COOKIE.maxAge,
|
|
319
555
|
sameSite: THEME_COOKIE.sameSite,
|
|
320
|
-
})
|
|
556
|
+
}),
|
|
321
557
|
);
|
|
558
|
+
invalidateResponseCookieCache();
|
|
322
559
|
};
|
|
323
560
|
|
|
561
|
+
// Strip internal _rsc* params so userland sees a clean URL.
|
|
562
|
+
const cleanUrl = stripInternalParams(url);
|
|
563
|
+
|
|
324
564
|
// Build the context object first (without use), then add use
|
|
325
565
|
const ctx: RequestContext<TEnv> = {
|
|
326
566
|
env,
|
|
327
567
|
request,
|
|
328
|
-
url,
|
|
568
|
+
url: cleanUrl,
|
|
569
|
+
originalUrl: new URL(request.url),
|
|
329
570
|
pathname: url.pathname,
|
|
330
|
-
searchParams:
|
|
571
|
+
searchParams: cleanUrl.searchParams,
|
|
331
572
|
var: variables,
|
|
332
|
-
get:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
573
|
+
get: ((keyOrVar: any) =>
|
|
574
|
+
contextGet(variables, keyOrVar)) as RequestContext<TEnv>["get"],
|
|
575
|
+
set: ((keyOrVar: any, value: any) => {
|
|
576
|
+
assertNotInsideCacheExec(ctx, "set");
|
|
577
|
+
contextSet(variables, keyOrVar, value);
|
|
578
|
+
}) as RequestContext<TEnv>["set"],
|
|
336
579
|
params: {} as Record<string, string>,
|
|
337
|
-
|
|
580
|
+
|
|
581
|
+
get res(): Response {
|
|
582
|
+
return stubResponse;
|
|
583
|
+
},
|
|
584
|
+
set res(_: Response) {
|
|
585
|
+
throw new Error(
|
|
586
|
+
"ctx.res is read-only. Use ctx.header() to set response headers, or cookies() for cookie mutations.",
|
|
587
|
+
);
|
|
588
|
+
},
|
|
338
589
|
|
|
339
590
|
cookie(name: string): string | undefined {
|
|
340
|
-
return
|
|
591
|
+
return effectiveCookie(name);
|
|
341
592
|
},
|
|
342
593
|
|
|
343
594
|
cookies(): Record<string, string> {
|
|
344
|
-
|
|
595
|
+
const parsed = getParsedCookies();
|
|
596
|
+
const mutations = getResponseCookies();
|
|
597
|
+
if (mutations.size === 0) return { ...parsed };
|
|
598
|
+
// Build result without delete (avoids V8 dictionary-mode de-opt)
|
|
599
|
+
const deleted = new Set<string>();
|
|
600
|
+
for (const [k, v] of mutations) {
|
|
601
|
+
if (v === null) deleted.add(k);
|
|
602
|
+
}
|
|
603
|
+
const result: Record<string, string> = {};
|
|
604
|
+
for (const key of Object.keys(parsed)) {
|
|
605
|
+
if (!deleted.has(key)) result[key] = parsed[key];
|
|
606
|
+
}
|
|
607
|
+
for (const [k, v] of mutations) {
|
|
608
|
+
if (v !== null) result[k] = v;
|
|
609
|
+
}
|
|
610
|
+
return result;
|
|
345
611
|
},
|
|
346
612
|
|
|
347
613
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
614
|
+
assertNotInsideCacheExec(ctx, "setCookie");
|
|
348
615
|
stubResponse.headers.append(
|
|
349
616
|
"Set-Cookie",
|
|
350
|
-
serializeCookieValue(name, value, options)
|
|
617
|
+
serializeCookieValue(name, value, options),
|
|
351
618
|
);
|
|
619
|
+
invalidateResponseCookieCache();
|
|
352
620
|
},
|
|
353
621
|
|
|
354
622
|
deleteCookie(
|
|
355
623
|
name: string,
|
|
356
|
-
options?: Pick<CookieOptions, "domain" | "path"
|
|
624
|
+
options?: Pick<CookieOptions, "domain" | "path">,
|
|
357
625
|
): void {
|
|
626
|
+
assertNotInsideCacheExec(ctx, "deleteCookie");
|
|
358
627
|
stubResponse.headers.append(
|
|
359
628
|
"Set-Cookie",
|
|
360
|
-
serializeCookieValue(name, "", { ...options, maxAge: 0 })
|
|
629
|
+
serializeCookieValue(name, "", { ...options, maxAge: 0 }),
|
|
361
630
|
);
|
|
631
|
+
invalidateResponseCookieCache();
|
|
362
632
|
},
|
|
363
633
|
|
|
364
634
|
header(name: string, value: string): void {
|
|
635
|
+
assertNotInsideCacheExec(ctx, "header");
|
|
365
636
|
stubResponse.headers.set(name, value);
|
|
366
637
|
},
|
|
367
638
|
|
|
639
|
+
setStatus(status: number): void {
|
|
640
|
+
assertNotInsideCacheExec(ctx, "setStatus");
|
|
641
|
+
stubResponse = new Response(null, {
|
|
642
|
+
status,
|
|
643
|
+
headers: stubResponse.headers,
|
|
644
|
+
});
|
|
645
|
+
},
|
|
646
|
+
|
|
647
|
+
_setStatus(status: number): void {
|
|
648
|
+
stubResponse = new Response(null, {
|
|
649
|
+
status,
|
|
650
|
+
headers: stubResponse.headers,
|
|
651
|
+
});
|
|
652
|
+
},
|
|
653
|
+
|
|
368
654
|
// Placeholder - will be replaced below
|
|
369
655
|
use: null as any,
|
|
370
656
|
|
|
@@ -372,6 +658,7 @@ export function createRequestContext<TEnv>(
|
|
|
372
658
|
|
|
373
659
|
_handleStore: handleStore,
|
|
374
660
|
_cacheStore: cacheStore,
|
|
661
|
+
_cacheProfiles: cacheProfiles,
|
|
375
662
|
|
|
376
663
|
waitUntil(fn: () => Promise<void>): void {
|
|
377
664
|
if (executionContext?.waitUntil) {
|
|
@@ -379,20 +666,44 @@ export function createRequestContext<TEnv>(
|
|
|
379
666
|
executionContext.waitUntil(fn());
|
|
380
667
|
} else {
|
|
381
668
|
// Node.js / dev: fire-and-forget with error logging
|
|
382
|
-
fn().catch((err) =>
|
|
669
|
+
fn().catch((err) =>
|
|
670
|
+
console.error("[waitUntil] Background task failed:", err),
|
|
671
|
+
);
|
|
383
672
|
}
|
|
384
673
|
},
|
|
385
674
|
|
|
386
675
|
_onResponseCallbacks: [],
|
|
387
676
|
|
|
388
677
|
onResponse(callback: (response: Response) => Response): void {
|
|
678
|
+
assertNotInsideCacheExec(ctx, "onResponse");
|
|
389
679
|
this._onResponseCallbacks.push(callback);
|
|
390
680
|
},
|
|
391
681
|
|
|
392
682
|
// Theme properties (only set when themeConfig is provided)
|
|
393
|
-
theme
|
|
394
|
-
|
|
683
|
+
get theme() {
|
|
684
|
+
return themeConfig ? getTheme() : undefined;
|
|
685
|
+
},
|
|
686
|
+
setTheme: themeConfig
|
|
687
|
+
? (theme: Theme) => {
|
|
688
|
+
assertNotInsideCacheExec(ctx, "setTheme");
|
|
689
|
+
setTheme(theme);
|
|
690
|
+
}
|
|
691
|
+
: undefined,
|
|
395
692
|
_themeConfig: themeConfig,
|
|
693
|
+
|
|
694
|
+
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void {
|
|
695
|
+
assertNotInsideCacheExec(ctx, "setLocationState");
|
|
696
|
+
const arr = Array.isArray(entries) ? entries : [entries];
|
|
697
|
+
this._locationState = this._locationState
|
|
698
|
+
? [...this._locationState, ...arr]
|
|
699
|
+
: arr;
|
|
700
|
+
},
|
|
701
|
+
_locationState: undefined,
|
|
702
|
+
|
|
703
|
+
_reportedErrors: new WeakSet<object>(),
|
|
704
|
+
_metricsStore: undefined,
|
|
705
|
+
|
|
706
|
+
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
396
707
|
};
|
|
397
708
|
|
|
398
709
|
// Now create use() with access to ctx
|
|
@@ -402,14 +713,53 @@ export function createRequestContext<TEnv>(
|
|
|
402
713
|
getContext: () => ctx,
|
|
403
714
|
});
|
|
404
715
|
|
|
716
|
+
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
717
|
+
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
405
718
|
return ctx;
|
|
406
719
|
}
|
|
407
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Parse Set-Cookie headers from a response into effective cookie state.
|
|
723
|
+
* Returns a map of cookie name -> value (string) or name -> null (deleted).
|
|
724
|
+
* Last-write-wins: later Set-Cookie entries for the same name overwrite earlier ones.
|
|
725
|
+
* Max-Age=0 is treated as a delete.
|
|
726
|
+
*/
|
|
727
|
+
const MAX_AGE_ZERO_RE = /;\s*Max-Age\s*=\s*0/i;
|
|
728
|
+
|
|
729
|
+
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
730
|
+
const result = new Map<string, string | null>();
|
|
731
|
+
const setCookies = response.headers.getSetCookie();
|
|
732
|
+
|
|
733
|
+
for (const header of setCookies) {
|
|
734
|
+
// First segment before ';' is the name=value pair
|
|
735
|
+
const semiIdx = header.indexOf(";");
|
|
736
|
+
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
737
|
+
const eqIdx = pair.indexOf("=");
|
|
738
|
+
if (eqIdx === -1) continue;
|
|
739
|
+
|
|
740
|
+
let name: string;
|
|
741
|
+
let value: string;
|
|
742
|
+
try {
|
|
743
|
+
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
744
|
+
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
745
|
+
} catch {
|
|
746
|
+
// Malformed encoding — skip this entry
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Max-Age=0 means the cookie is being deleted
|
|
751
|
+
const isDeleted = MAX_AGE_ZERO_RE.test(header);
|
|
752
|
+
result.set(name, isDeleted ? null : value);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
|
|
408
758
|
/**
|
|
409
759
|
* Parse cookies from Cookie header
|
|
410
760
|
*/
|
|
411
761
|
function parseCookiesFromHeader(
|
|
412
|
-
cookieHeader: string | null
|
|
762
|
+
cookieHeader: string | null,
|
|
413
763
|
): Record<string, string> {
|
|
414
764
|
if (!cookieHeader) return {};
|
|
415
765
|
|
|
@@ -419,7 +769,13 @@ function parseCookiesFromHeader(
|
|
|
419
769
|
for (const pair of pairs) {
|
|
420
770
|
const [name, ...rest] = pair.trim().split("=");
|
|
421
771
|
if (name) {
|
|
422
|
-
|
|
772
|
+
const raw = rest.join("=");
|
|
773
|
+
try {
|
|
774
|
+
cookies[name] = decodeURIComponent(raw);
|
|
775
|
+
} catch {
|
|
776
|
+
// Malformed percent-encoded value (e.g. %zz, %2) - fall back to raw value
|
|
777
|
+
cookies[name] = raw;
|
|
778
|
+
}
|
|
423
779
|
}
|
|
424
780
|
}
|
|
425
781
|
|
|
@@ -432,7 +788,7 @@ function parseCookiesFromHeader(
|
|
|
432
788
|
function serializeCookieValue(
|
|
433
789
|
name: string,
|
|
434
790
|
value: string,
|
|
435
|
-
options: CookieOptions = {}
|
|
791
|
+
options: CookieOptions = {},
|
|
436
792
|
): string {
|
|
437
793
|
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
438
794
|
|
|
@@ -464,7 +820,7 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
464
820
|
* - For handles: returns a push function to add handle data
|
|
465
821
|
*/
|
|
466
822
|
export function createUseFunction<TEnv>(
|
|
467
|
-
options: CreateUseFunctionOptions<TEnv
|
|
823
|
+
options: CreateUseFunctionOptions<TEnv>,
|
|
468
824
|
): RequestContext["use"] {
|
|
469
825
|
const { handleStore, loaderPromises, getContext } = options;
|
|
470
826
|
|
|
@@ -478,16 +834,19 @@ export function createUseFunction<TEnv>(
|
|
|
478
834
|
if (!segmentId) {
|
|
479
835
|
throw new Error(
|
|
480
836
|
`Handle "${handle.$$id}" used outside of handler context. ` +
|
|
481
|
-
`Handles must be used within route/layout handlers
|
|
837
|
+
`Handles must be used within route/layout handlers.`,
|
|
482
838
|
);
|
|
483
839
|
}
|
|
484
840
|
|
|
485
841
|
// Return a push function bound to this handle and segment
|
|
486
|
-
return (
|
|
842
|
+
return (
|
|
843
|
+
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>),
|
|
844
|
+
) => {
|
|
487
845
|
// If it's a function, call it immediately to get the promise
|
|
488
|
-
const valueOrPromise =
|
|
489
|
-
|
|
490
|
-
|
|
846
|
+
const valueOrPromise =
|
|
847
|
+
typeof dataOrFn === "function"
|
|
848
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
849
|
+
: dataOrFn;
|
|
491
850
|
|
|
492
851
|
// Push directly - promises will be serialized by RSC and streamed
|
|
493
852
|
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
@@ -513,7 +872,7 @@ export function createUseFunction<TEnv>(
|
|
|
513
872
|
|
|
514
873
|
if (!loaderFn) {
|
|
515
874
|
throw new Error(
|
|
516
|
-
`Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build
|
|
875
|
+
`Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build.`,
|
|
517
876
|
);
|
|
518
877
|
}
|
|
519
878
|
|
|
@@ -522,25 +881,33 @@ export function createUseFunction<TEnv>(
|
|
|
522
881
|
// Create loader context with recursive use() support
|
|
523
882
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
524
883
|
params: ctx.params,
|
|
884
|
+
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
525
885
|
request: ctx.request,
|
|
526
886
|
searchParams: ctx.searchParams,
|
|
887
|
+
search: (ctx as any).search ?? {},
|
|
527
888
|
pathname: ctx.pathname,
|
|
528
889
|
url: ctx.url,
|
|
529
890
|
env: ctx.env as any,
|
|
530
891
|
var: ctx.var as any,
|
|
531
892
|
get: ctx.get as any,
|
|
532
893
|
use: <TDep, TDepParams = any>(
|
|
533
|
-
dep: LoaderDefinition<TDep, TDepParams
|
|
894
|
+
dep: LoaderDefinition<TDep, TDepParams>,
|
|
534
895
|
): Promise<TDep> => {
|
|
535
896
|
// Recursive call - will start dep loader if not already started
|
|
536
897
|
return ctx.use(dep);
|
|
537
898
|
},
|
|
538
899
|
method: "GET",
|
|
539
900
|
body: undefined,
|
|
901
|
+
reverse: createReverseFunction(
|
|
902
|
+
getGlobalRouteMap(),
|
|
903
|
+
ctx._routeName,
|
|
904
|
+
ctx.params as Record<string, string>,
|
|
905
|
+
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
906
|
+
),
|
|
540
907
|
};
|
|
541
908
|
|
|
542
909
|
// Start loader execution with tracking
|
|
543
|
-
const doneLoader = track(`loader:${loader.$$id}
|
|
910
|
+
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
544
911
|
const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
|
|
545
912
|
doneLoader();
|
|
546
913
|
});
|