@rangojs/router 0.0.0-experimental.5 → 0.0.0-experimental.50
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 +1606 -0
- package/dist/vite/index.js +4567 -769
- package/package.json +77 -58
- 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 +167 -0
- package/skills/hooks/SKILL.md +334 -72
- 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 +89 -30
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +204 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +318 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- 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 +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +282 -557
- package/src/browser/navigation-client.ts +157 -71
- 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 -310
- 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 +193 -73
- package/src/browser/react/NavigationProvider.tsx +160 -13
- 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 +24 -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 +32 -79
- package/src/browser/react/use-href.tsx +2 -2
- 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 -55
- 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 +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +118 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +265 -0
- 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 +167 -309
- 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 +106 -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 +108 -2
- package/src/handle.ts +15 -29
- 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 +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +153 -19
- package/src/index.ts +211 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -147
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- 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 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +59 -8
- 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 +374 -81
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +154 -35
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +440 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +27 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +55 -33
- 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 +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +41 -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 -0
- 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 +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +665 -4182
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- 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 +230 -0
- package/src/segment-system.tsx +172 -21
- package/src/server/context.ts +266 -58
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +439 -73
- package/src/server.ts +35 -128
- package/src/ssr/index.tsx +101 -31
- package/src/static-handler.ts +114 -0
- 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 -1623
- 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 -802
- 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 -782
- 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 -53
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- 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} +27 -16
- 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/utils/ast-handler-extract.ts +517 -0
- 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/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/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/warmup/connection-warmup.tsx +0 -94
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -13,13 +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";
|
|
27
|
+
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
20
28
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
21
29
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
22
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";
|
|
23
40
|
|
|
24
41
|
/**
|
|
25
42
|
* Unified request context available via getRequestContext()
|
|
@@ -28,46 +45,61 @@ import { THEME_COOKIE } from "../theme/constants.js";
|
|
|
28
45
|
* Use this when you need access to request data outside of route handlers.
|
|
29
46
|
*/
|
|
30
47
|
export interface RequestContext<
|
|
31
|
-
TEnv =
|
|
48
|
+
TEnv = DefaultEnv,
|
|
32
49
|
TParams = Record<string, string>,
|
|
33
50
|
> {
|
|
34
51
|
/** Platform bindings (Cloudflare env, etc.) */
|
|
35
52
|
env: TEnv;
|
|
36
53
|
/** Original HTTP request */
|
|
37
54
|
request: Request;
|
|
38
|
-
/** Parsed URL (
|
|
55
|
+
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
39
56
|
url: URL;
|
|
57
|
+
/**
|
|
58
|
+
* The original request URL with all parameters intact, including
|
|
59
|
+
* internal `_rsc*` transport params.
|
|
60
|
+
*/
|
|
61
|
+
originalUrl: URL;
|
|
40
62
|
/** URL pathname */
|
|
41
63
|
pathname: string;
|
|
42
|
-
/** URL search params (
|
|
64
|
+
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
43
65
|
searchParams: URLSearchParams;
|
|
44
66
|
/** Variables set by middleware (same as ctx.var) */
|
|
45
67
|
var: Record<string, any>;
|
|
46
68
|
/** Get a variable set by middleware */
|
|
47
|
-
get:
|
|
69
|
+
get: {
|
|
70
|
+
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
71
|
+
<K extends string>(key: K): any;
|
|
72
|
+
};
|
|
48
73
|
/** Set a variable (shared with middleware and handlers) */
|
|
49
|
-
set:
|
|
74
|
+
set: {
|
|
75
|
+
<T>(contextVar: ContextVar<T>, value: T): void;
|
|
76
|
+
<K extends string>(key: K, value: any): void;
|
|
77
|
+
};
|
|
50
78
|
/**
|
|
51
79
|
* Route params (populated after route matching)
|
|
52
80
|
* Initially empty, then set to matched params
|
|
53
81
|
*/
|
|
54
82
|
params: TParams;
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
* Headers set here are merged into the final response
|
|
58
|
-
*/
|
|
59
|
-
res: Response;
|
|
83
|
+
/** @internal Stub response for collecting headers/cookies. Use ctx.headers or ctx.header() instead. */
|
|
84
|
+
readonly res: Response;
|
|
60
85
|
|
|
61
|
-
/** Get a cookie value
|
|
86
|
+
/** @internal Get a cookie value (effective: request + response mutations). Use cookies().get() instead. */
|
|
62
87
|
cookie(name: string): string | undefined;
|
|
63
|
-
/** Get all cookies
|
|
88
|
+
/** @internal Get all cookies (effective merged view). Use cookies().getAll() instead. */
|
|
64
89
|
cookies(): Record<string, string>;
|
|
65
|
-
/** Set a cookie on the response */
|
|
90
|
+
/** @internal Set a cookie on the response. Use cookies().set() instead. */
|
|
66
91
|
setCookie(name: string, value: string, options?: CookieOptions): void;
|
|
67
|
-
/** Delete a cookie */
|
|
68
|
-
deleteCookie(
|
|
92
|
+
/** @internal Delete a cookie. Use cookies().delete() instead. */
|
|
93
|
+
deleteCookie(
|
|
94
|
+
name: string,
|
|
95
|
+
options?: Pick<CookieOptions, "domain" | "path">,
|
|
96
|
+
): void;
|
|
69
97
|
/** Set a response header */
|
|
70
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;
|
|
71
103
|
|
|
72
104
|
/**
|
|
73
105
|
* Access loader data or push handle data.
|
|
@@ -89,10 +121,12 @@ export interface RequestContext<
|
|
|
89
121
|
* ```
|
|
90
122
|
*/
|
|
91
123
|
use: {
|
|
92
|
-
<T, TLoaderParams = any>(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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;
|
|
96
130
|
};
|
|
97
131
|
|
|
98
132
|
/** HTTP method (GET, POST, PUT, PATCH, DELETE, etc.) */
|
|
@@ -104,6 +138,12 @@ export interface RequestContext<
|
|
|
104
138
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
105
139
|
_cacheStore?: SegmentCacheStore;
|
|
106
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
|
+
|
|
107
147
|
/**
|
|
108
148
|
* Schedule work to run after the response is sent.
|
|
109
149
|
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
@@ -177,8 +217,99 @@ export interface RequestContext<
|
|
|
177
217
|
|
|
178
218
|
/** @internal Theme configuration (null if theme not enabled) */
|
|
179
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;
|
|
180
280
|
}
|
|
181
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
|
+
|
|
182
313
|
// AsyncLocalStorage instance for request context
|
|
183
314
|
const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
184
315
|
|
|
@@ -188,16 +319,33 @@ const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
|
188
319
|
*/
|
|
189
320
|
export function runWithRequestContext<TEnv, T>(
|
|
190
321
|
context: RequestContext<TEnv>,
|
|
191
|
-
fn: () => T
|
|
322
|
+
fn: () => T,
|
|
192
323
|
): T {
|
|
193
324
|
return requestContextStorage.run(context, fn);
|
|
194
325
|
}
|
|
195
326
|
|
|
196
327
|
/**
|
|
197
328
|
* Get the current request context
|
|
198
|
-
*
|
|
329
|
+
* Throws if called outside of a request context
|
|
199
330
|
*/
|
|
200
|
-
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>():
|
|
201
349
|
| RequestContext<TEnv>
|
|
202
350
|
| undefined {
|
|
203
351
|
return requestContextStorage.getStore() as RequestContext<TEnv> | undefined;
|
|
@@ -205,28 +353,67 @@ export function getRequestContext<TEnv = unknown>():
|
|
|
205
353
|
|
|
206
354
|
/**
|
|
207
355
|
* Update params on the current request context
|
|
208
|
-
* Called after route matching to populate route params
|
|
356
|
+
* Called after route matching to populate route params and route name
|
|
209
357
|
*/
|
|
210
|
-
export function setRequestContextParams(
|
|
358
|
+
export function setRequestContextParams(
|
|
359
|
+
params: Record<string, string>,
|
|
360
|
+
routeName?: string,
|
|
361
|
+
): void {
|
|
211
362
|
const ctx = requestContextStorage.getStore();
|
|
212
363
|
if (ctx) {
|
|
213
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
|
+
);
|
|
214
380
|
}
|
|
215
381
|
}
|
|
216
382
|
|
|
217
383
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
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
|
|
220
388
|
*/
|
|
221
|
-
export function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
);
|
|
389
|
+
export function setRequestContextPrevRouteKey(
|
|
390
|
+
prevRouteKey: string | undefined,
|
|
391
|
+
): void {
|
|
392
|
+
const ctx = requestContextStorage.getStore();
|
|
393
|
+
if (ctx && prevRouteKey !== undefined) {
|
|
394
|
+
ctx._prevRouteKey = prevRouteKey;
|
|
228
395
|
}
|
|
229
|
-
|
|
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>();
|
|
230
417
|
}
|
|
231
418
|
|
|
232
419
|
/**
|
|
@@ -245,8 +432,15 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
245
432
|
request: Request;
|
|
246
433
|
url: URL;
|
|
247
434
|
variables: Record<string, any>;
|
|
435
|
+
/** Optional initial response stub headers/status to seed effective cookie reads */
|
|
436
|
+
initialResponse?: Response;
|
|
248
437
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
249
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
|
+
>;
|
|
250
444
|
/** Optional Cloudflare execution context for waitUntil support */
|
|
251
445
|
executionContext?: ExecutionContext;
|
|
252
446
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
@@ -262,20 +456,37 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
262
456
|
* - Passed to handlers as ctx
|
|
263
457
|
*/
|
|
264
458
|
export function createRequestContext<TEnv>(
|
|
265
|
-
options: CreateRequestContextOptions<TEnv
|
|
459
|
+
options: CreateRequestContextOptions<TEnv>,
|
|
266
460
|
): RequestContext<TEnv> {
|
|
267
|
-
const {
|
|
461
|
+
const {
|
|
462
|
+
env,
|
|
463
|
+
request,
|
|
464
|
+
url,
|
|
465
|
+
variables,
|
|
466
|
+
initialResponse,
|
|
467
|
+
cacheStore,
|
|
468
|
+
cacheProfiles,
|
|
469
|
+
executionContext,
|
|
470
|
+
themeConfig,
|
|
471
|
+
} = options;
|
|
268
472
|
const cookieHeader = request.headers.get("Cookie");
|
|
269
473
|
let parsedCookies: Record<string, string> | null = null;
|
|
270
474
|
|
|
271
|
-
// Create stub response for collecting headers/cookies
|
|
272
|
-
|
|
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 });
|
|
273
484
|
|
|
274
485
|
// Create handle store and loader memoization for this request
|
|
275
486
|
const handleStore = createHandleStore();
|
|
276
487
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
277
488
|
|
|
278
|
-
// Lazy parse cookies
|
|
489
|
+
// Lazy parse cookies from the original Cookie header
|
|
279
490
|
const getParsedCookies = (): Record<string, string> => {
|
|
280
491
|
if (!parsedCookies) {
|
|
281
492
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -283,11 +494,35 @@ export function createRequestContext<TEnv>(
|
|
|
283
494
|
return parsedCookies;
|
|
284
495
|
};
|
|
285
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
|
+
|
|
286
520
|
// Theme helpers (only used when themeConfig is provided)
|
|
287
521
|
const getTheme = (): Theme | undefined => {
|
|
288
522
|
if (!themeConfig) return undefined;
|
|
289
523
|
|
|
290
|
-
|
|
524
|
+
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
525
|
+
const stored = effectiveCookie(themeConfig.storageKey);
|
|
291
526
|
if (stored) {
|
|
292
527
|
// Validate stored value
|
|
293
528
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
@@ -305,65 +540,117 @@ export function createRequestContext<TEnv>(
|
|
|
305
540
|
|
|
306
541
|
// Validate theme value
|
|
307
542
|
if (theme !== "system" && !themeConfig.themes.includes(theme)) {
|
|
308
|
-
console.warn(
|
|
543
|
+
console.warn(
|
|
544
|
+
`[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`,
|
|
545
|
+
);
|
|
309
546
|
return;
|
|
310
547
|
}
|
|
311
548
|
|
|
312
|
-
//
|
|
549
|
+
// Write to stub — effectiveCookie() will pick it up on next read
|
|
313
550
|
stubResponse.headers.append(
|
|
314
551
|
"Set-Cookie",
|
|
315
552
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
316
553
|
path: THEME_COOKIE.path,
|
|
317
554
|
maxAge: THEME_COOKIE.maxAge,
|
|
318
555
|
sameSite: THEME_COOKIE.sameSite,
|
|
319
|
-
})
|
|
556
|
+
}),
|
|
320
557
|
);
|
|
558
|
+
invalidateResponseCookieCache();
|
|
321
559
|
};
|
|
322
560
|
|
|
561
|
+
// Strip internal _rsc* params so userland sees a clean URL.
|
|
562
|
+
const cleanUrl = stripInternalParams(url);
|
|
563
|
+
|
|
323
564
|
// Build the context object first (without use), then add use
|
|
324
565
|
const ctx: RequestContext<TEnv> = {
|
|
325
566
|
env,
|
|
326
567
|
request,
|
|
327
|
-
url,
|
|
568
|
+
url: cleanUrl,
|
|
569
|
+
originalUrl: new URL(request.url),
|
|
328
570
|
pathname: url.pathname,
|
|
329
|
-
searchParams:
|
|
571
|
+
searchParams: cleanUrl.searchParams,
|
|
330
572
|
var: variables,
|
|
331
|
-
get:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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"],
|
|
335
579
|
params: {} as Record<string, string>,
|
|
336
|
-
|
|
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
|
+
},
|
|
337
589
|
|
|
338
590
|
cookie(name: string): string | undefined {
|
|
339
|
-
return
|
|
591
|
+
return effectiveCookie(name);
|
|
340
592
|
},
|
|
341
593
|
|
|
342
594
|
cookies(): Record<string, string> {
|
|
343
|
-
|
|
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;
|
|
344
611
|
},
|
|
345
612
|
|
|
346
613
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
614
|
+
assertNotInsideCacheExec(ctx, "setCookie");
|
|
347
615
|
stubResponse.headers.append(
|
|
348
616
|
"Set-Cookie",
|
|
349
|
-
serializeCookieValue(name, value, options)
|
|
617
|
+
serializeCookieValue(name, value, options),
|
|
350
618
|
);
|
|
619
|
+
invalidateResponseCookieCache();
|
|
351
620
|
},
|
|
352
621
|
|
|
353
622
|
deleteCookie(
|
|
354
623
|
name: string,
|
|
355
|
-
options?: Pick<CookieOptions, "domain" | "path"
|
|
624
|
+
options?: Pick<CookieOptions, "domain" | "path">,
|
|
356
625
|
): void {
|
|
626
|
+
assertNotInsideCacheExec(ctx, "deleteCookie");
|
|
357
627
|
stubResponse.headers.append(
|
|
358
628
|
"Set-Cookie",
|
|
359
|
-
serializeCookieValue(name, "", { ...options, maxAge: 0 })
|
|
629
|
+
serializeCookieValue(name, "", { ...options, maxAge: 0 }),
|
|
360
630
|
);
|
|
631
|
+
invalidateResponseCookieCache();
|
|
361
632
|
},
|
|
362
633
|
|
|
363
634
|
header(name: string, value: string): void {
|
|
635
|
+
assertNotInsideCacheExec(ctx, "header");
|
|
364
636
|
stubResponse.headers.set(name, value);
|
|
365
637
|
},
|
|
366
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
|
+
|
|
367
654
|
// Placeholder - will be replaced below
|
|
368
655
|
use: null as any,
|
|
369
656
|
|
|
@@ -371,6 +658,7 @@ export function createRequestContext<TEnv>(
|
|
|
371
658
|
|
|
372
659
|
_handleStore: handleStore,
|
|
373
660
|
_cacheStore: cacheStore,
|
|
661
|
+
_cacheProfiles: cacheProfiles,
|
|
374
662
|
|
|
375
663
|
waitUntil(fn: () => Promise<void>): void {
|
|
376
664
|
if (executionContext?.waitUntil) {
|
|
@@ -378,20 +666,44 @@ export function createRequestContext<TEnv>(
|
|
|
378
666
|
executionContext.waitUntil(fn());
|
|
379
667
|
} else {
|
|
380
668
|
// Node.js / dev: fire-and-forget with error logging
|
|
381
|
-
fn().catch((err) =>
|
|
669
|
+
fn().catch((err) =>
|
|
670
|
+
console.error("[waitUntil] Background task failed:", err),
|
|
671
|
+
);
|
|
382
672
|
}
|
|
383
673
|
},
|
|
384
674
|
|
|
385
675
|
_onResponseCallbacks: [],
|
|
386
676
|
|
|
387
677
|
onResponse(callback: (response: Response) => Response): void {
|
|
678
|
+
assertNotInsideCacheExec(ctx, "onResponse");
|
|
388
679
|
this._onResponseCallbacks.push(callback);
|
|
389
680
|
},
|
|
390
681
|
|
|
391
682
|
// Theme properties (only set when themeConfig is provided)
|
|
392
|
-
theme
|
|
393
|
-
|
|
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,
|
|
394
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, {}),
|
|
395
707
|
};
|
|
396
708
|
|
|
397
709
|
// Now create use() with access to ctx
|
|
@@ -401,14 +713,53 @@ export function createRequestContext<TEnv>(
|
|
|
401
713
|
getContext: () => ctx,
|
|
402
714
|
});
|
|
403
715
|
|
|
716
|
+
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
717
|
+
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
404
718
|
return ctx;
|
|
405
719
|
}
|
|
406
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
|
+
|
|
407
758
|
/**
|
|
408
759
|
* Parse cookies from Cookie header
|
|
409
760
|
*/
|
|
410
761
|
function parseCookiesFromHeader(
|
|
411
|
-
cookieHeader: string | null
|
|
762
|
+
cookieHeader: string | null,
|
|
412
763
|
): Record<string, string> {
|
|
413
764
|
if (!cookieHeader) return {};
|
|
414
765
|
|
|
@@ -418,7 +769,13 @@ function parseCookiesFromHeader(
|
|
|
418
769
|
for (const pair of pairs) {
|
|
419
770
|
const [name, ...rest] = pair.trim().split("=");
|
|
420
771
|
if (name) {
|
|
421
|
-
|
|
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
|
+
}
|
|
422
779
|
}
|
|
423
780
|
}
|
|
424
781
|
|
|
@@ -431,7 +788,7 @@ function parseCookiesFromHeader(
|
|
|
431
788
|
function serializeCookieValue(
|
|
432
789
|
name: string,
|
|
433
790
|
value: string,
|
|
434
|
-
options: CookieOptions = {}
|
|
791
|
+
options: CookieOptions = {},
|
|
435
792
|
): string {
|
|
436
793
|
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
437
794
|
|
|
@@ -463,7 +820,7 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
463
820
|
* - For handles: returns a push function to add handle data
|
|
464
821
|
*/
|
|
465
822
|
export function createUseFunction<TEnv>(
|
|
466
|
-
options: CreateUseFunctionOptions<TEnv
|
|
823
|
+
options: CreateUseFunctionOptions<TEnv>,
|
|
467
824
|
): RequestContext["use"] {
|
|
468
825
|
const { handleStore, loaderPromises, getContext } = options;
|
|
469
826
|
|
|
@@ -477,16 +834,19 @@ export function createUseFunction<TEnv>(
|
|
|
477
834
|
if (!segmentId) {
|
|
478
835
|
throw new Error(
|
|
479
836
|
`Handle "${handle.$$id}" used outside of handler context. ` +
|
|
480
|
-
`Handles must be used within route/layout handlers
|
|
837
|
+
`Handles must be used within route/layout handlers.`,
|
|
481
838
|
);
|
|
482
839
|
}
|
|
483
840
|
|
|
484
841
|
// Return a push function bound to this handle and segment
|
|
485
|
-
return (
|
|
842
|
+
return (
|
|
843
|
+
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>),
|
|
844
|
+
) => {
|
|
486
845
|
// If it's a function, call it immediately to get the promise
|
|
487
|
-
const valueOrPromise =
|
|
488
|
-
|
|
489
|
-
|
|
846
|
+
const valueOrPromise =
|
|
847
|
+
typeof dataOrFn === "function"
|
|
848
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
849
|
+
: dataOrFn;
|
|
490
850
|
|
|
491
851
|
// Push directly - promises will be serialized by RSC and streamed
|
|
492
852
|
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
@@ -504,8 +864,6 @@ export function createUseFunction<TEnv>(
|
|
|
504
864
|
// Get loader function - either from loader object or fetchable registry
|
|
505
865
|
let loaderFn = loader.fn;
|
|
506
866
|
if (!loaderFn) {
|
|
507
|
-
// Lazy import to avoid circular dependency
|
|
508
|
-
const { getFetchableLoader } = require("../loader.rsc.js");
|
|
509
867
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
510
868
|
if (fetchable) {
|
|
511
869
|
loaderFn = fetchable.fn;
|
|
@@ -514,7 +872,7 @@ export function createUseFunction<TEnv>(
|
|
|
514
872
|
|
|
515
873
|
if (!loaderFn) {
|
|
516
874
|
throw new Error(
|
|
517
|
-
`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.`,
|
|
518
876
|
);
|
|
519
877
|
}
|
|
520
878
|
|
|
@@ -523,25 +881,33 @@ export function createUseFunction<TEnv>(
|
|
|
523
881
|
// Create loader context with recursive use() support
|
|
524
882
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
525
883
|
params: ctx.params,
|
|
884
|
+
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
526
885
|
request: ctx.request,
|
|
527
886
|
searchParams: ctx.searchParams,
|
|
887
|
+
search: (ctx as any).search ?? {},
|
|
528
888
|
pathname: ctx.pathname,
|
|
529
889
|
url: ctx.url,
|
|
530
890
|
env: ctx.env as any,
|
|
531
891
|
var: ctx.var as any,
|
|
532
892
|
get: ctx.get as any,
|
|
533
893
|
use: <TDep, TDepParams = any>(
|
|
534
|
-
dep: LoaderDefinition<TDep, TDepParams
|
|
894
|
+
dep: LoaderDefinition<TDep, TDepParams>,
|
|
535
895
|
): Promise<TDep> => {
|
|
536
896
|
// Recursive call - will start dep loader if not already started
|
|
537
897
|
return ctx.use(dep);
|
|
538
898
|
},
|
|
539
899
|
method: "GET",
|
|
540
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
|
+
),
|
|
541
907
|
};
|
|
542
908
|
|
|
543
909
|
// Start loader execution with tracking
|
|
544
|
-
const doneLoader = track(`loader:${loader.$$id}
|
|
910
|
+
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
545
911
|
const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
|
|
546
912
|
doneLoader();
|
|
547
913
|
});
|