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