@rangojs/router 0.22.1 → 0.24.0
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/dist/bin/rango.js +10 -4
- package/dist/testing/vitest.js +31 -1
- package/dist/types/browser/defer.d.ts +48 -0
- package/dist/types/browser/document-revealed.d.ts +17 -0
- package/dist/types/browser/event-controller.d.ts +7 -1
- package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
- package/dist/types/browser/navigation-transaction.d.ts +21 -7
- package/dist/types/browser/partial-update.d.ts +39 -8
- package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
- package/dist/types/browser/react/context.d.ts +3 -2
- package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
- package/dist/types/browser/response-adapter.d.ts +7 -0
- package/dist/types/browser/types.d.ts +24 -10
- package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/router-processing.d.ts +1 -1
- package/dist/types/build/route-types/scan-filter.d.ts +22 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
- package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
- package/dist/types/cache/document-cache-policy.d.ts +24 -0
- package/dist/types/cache/document-cache.d.ts +2 -10
- package/dist/types/client-urls/client-urls.d.ts +1 -1
- package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
- package/dist/types/client-urls/navigation.d.ts +0 -37
- package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
- package/dist/types/client-urls/server-projection.d.ts +29 -1
- package/dist/types/client-urls/types.d.ts +34 -69
- package/dist/types/client.d.ts +0 -1
- package/dist/types/context-var.d.ts +12 -0
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/handles/deferred-resolution.d.ts +2 -1
- package/dist/types/handles/pushed-by.d.ts +31 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.rsc.d.ts +2 -0
- package/dist/types/loader-store.d.ts +8 -0
- package/dist/types/prefetch-markers.d.ts +11 -0
- package/dist/types/response-utils.d.ts +7 -0
- package/dist/types/reverse.d.ts +8 -8
- package/dist/types/route-definition/helpers-types.d.ts +27 -0
- package/dist/types/route-definition/rewrite.d.ts +56 -0
- package/dist/types/route-map-builder.d.ts +7 -2
- package/dist/types/router/basename.d.ts +7 -0
- package/dist/types/router/handler-context.d.ts +1 -5
- package/dist/types/router/internal-params.d.ts +8 -0
- package/dist/types/router/is-action.d.ts +2 -1
- package/dist/types/router/middleware-types.d.ts +15 -0
- package/dist/types/router/revalidation.d.ts +1 -2
- package/dist/types/router/route-trie-builder.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
- package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
- package/dist/types/rsc/full-payload.d.ts +11 -0
- package/dist/types/rsc/handler.d.ts +33 -0
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/loader-fetch.d.ts +16 -0
- package/dist/types/rsc/rewrite-passes.d.ts +74 -0
- package/dist/types/rsc/ssr-setup.d.ts +3 -1
- package/dist/types/server/context.d.ts +21 -1
- package/dist/types/server/handle-store.d.ts +9 -1
- package/dist/types/server/request-context.d.ts +52 -2
- package/dist/types/server/rewrite-trail.d.ts +49 -0
- package/dist/types/server/slot-accessors.d.ts +29 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +4 -4
- package/dist/types/testing/internal/action-id.d.ts +10 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
- package/dist/types/testing/internal/test-action.d.ts +8 -0
- package/dist/types/testing/render-route.d.ts +8 -0
- package/dist/types/testing/run-loader.d.ts +2 -1
- package/dist/types/testing/run-middleware.d.ts +20 -0
- package/dist/types/testing/run-revalidate.d.ts +74 -0
- package/dist/types/testing/run-transition-when.d.ts +2 -3
- package/dist/types/testing/serve-shell-request.d.ts +40 -1
- package/dist/types/testing/vitest.d.ts +11 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/boundaries.d.ts +9 -0
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +30 -0
- package/dist/types/types/request-scope.d.ts +7 -0
- package/dist/types/types/segments.d.ts +21 -4
- package/dist/types/urls/path-helper-types.d.ts +25 -4
- package/dist/types/urls/type-extraction.d.ts +6 -2
- package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
- package/dist/types/vite/discovery/state.d.ts +14 -4
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
- package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
- package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
- package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
- package/dist/types/vite/router-discovery.d.ts +10 -0
- package/dist/vite/index.js +974 -379
- package/package.json +1 -1
- package/skills/caching/SKILL.md +54 -5
- package/skills/catalog.json +3 -3
- package/skills/client-urls/SKILL.md +197 -24
- package/skills/cloudflare/SKILL.md +14 -9
- package/skills/hooks/SKILL.md +16 -3
- package/skills/hooks/data.md +3 -1
- package/skills/links/SKILL.md +24 -0
- package/skills/loader/SKILL.md +199 -2
- package/skills/middleware/SKILL.md +183 -2
- package/skills/migrate-nextjs/SKILL.md +2 -2
- package/skills/migrate-react-router/SKILL.md +10 -7
- package/skills/migrate-react-router/data-and-actions.md +1 -1
- package/skills/ppr/SKILL.md +22 -0
- package/skills/prerender/SKILL.md +22 -8
- package/skills/rango/SKILL.md +13 -7
- package/skills/route/SKILL.md +139 -0
- package/skills/server-actions/SKILL.md +27 -0
- package/skills/testing/SKILL.md +3 -2
- package/skills/testing/client-components.md +2 -1
- package/skills/testing/middleware.md +31 -10
- package/skills/testing/predicates.md +67 -32
- package/skills/testing/response-routes.md +1 -0
- package/skills/testing/serve-shell-request.md +107 -19
- package/skills/testing/setup.md +2 -2
- package/skills/typesafety/route-types.md +2 -2
- package/skills/use-cache/SKILL.md +4 -2
- package/skills/view-transitions/SKILL.md +26 -0
- package/src/__augment-tests__/augmented.check.ts +20 -0
- package/src/browser/defer.ts +82 -0
- package/src/browser/document-revealed.ts +52 -0
- package/src/browser/event-controller.ts +46 -32
- package/src/browser/merge-segment-loaders.ts +31 -18
- package/src/browser/navigation-bridge.ts +82 -16
- package/src/browser/navigation-client.ts +48 -36
- package/src/browser/navigation-store.ts +13 -0
- package/src/browser/navigation-transaction.ts +25 -8
- package/src/browser/partial-update.ts +554 -146
- package/src/browser/prefetch/fetch.ts +2 -1
- package/src/browser/react/Link.tsx +7 -9
- package/src/browser/react/NavigationProvider.tsx +88 -49
- package/src/browser/react/context.ts +3 -2
- package/src/browser/react/filter-segment-order.ts +13 -2
- package/src/browser/react/use-router.ts +2 -4
- package/src/browser/response-adapter.ts +20 -0
- package/src/browser/rsc-router.tsx +17 -45
- package/src/browser/server-action-bridge.ts +25 -63
- package/src/browser/types.ts +24 -10
- package/src/browser/validate-redirect-origin.ts +16 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/router-processing.ts +3 -6
- package/src/build/route-types/scan-filter.ts +45 -11
- package/src/cache/cf/cf-cache-store.ts +59 -21
- package/src/cache/cf/cf-cache-types.ts +24 -2
- package/src/cache/document-cache-policy.ts +131 -0
- package/src/cache/document-cache.ts +21 -107
- package/src/client-urls/client-urls.ts +22 -6
- package/src/client-urls/hoisted-revalidate.ts +25 -0
- package/src/client-urls/navigation.ts +0 -96
- package/src/client-urls/revalidate-registry.ts +30 -0
- package/src/client-urls/server-projection.ts +151 -65
- package/src/client-urls/types.ts +39 -71
- package/src/client.tsx +9 -5
- package/src/context-var.ts +32 -2
- package/src/errors.ts +34 -0
- package/src/handles/deferred-resolution.ts +28 -5
- package/src/handles/pushed-by.ts +148 -0
- package/src/index.rsc.ts +8 -0
- package/src/index.ts +7 -0
- package/src/loader-store.ts +18 -7
- package/src/prefetch-markers.ts +13 -0
- package/src/response-utils.ts +19 -0
- package/src/reverse.ts +17 -14
- package/src/route-content-wrapper.tsx +18 -4
- package/src/route-definition/dsl-helpers.ts +27 -4
- package/src/route-definition/helpers-types.ts +27 -1
- package/src/route-definition/redirect.ts +2 -15
- package/src/route-definition/rewrite.ts +146 -0
- package/src/route-map-builder.ts +14 -11
- package/src/router/basename.ts +20 -0
- package/src/router/handler-context.ts +3 -13
- package/src/router/intercept-resolution.ts +35 -16
- package/src/router/internal-params.ts +16 -0
- package/src/router/is-action.ts +2 -1
- package/src/router/loader-resolution.ts +85 -4
- package/src/router/match-api.ts +36 -1
- package/src/router/match-middleware/background-revalidation.ts +16 -4
- package/src/router/match-middleware/cache-lookup.ts +35 -7
- package/src/router/match-middleware/cache-store.ts +15 -1
- package/src/router/match-middleware/intercept-resolution.ts +4 -2
- package/src/router/match-result.ts +14 -2
- package/src/router/middleware-types.ts +16 -0
- package/src/router/middleware.ts +127 -49
- package/src/router/navigation-snapshot.ts +2 -1
- package/src/router/revalidation.ts +1 -1
- package/src/router/route-trie-builder.ts +28 -1
- package/src/router/segment-resolution/fresh.ts +22 -18
- package/src/router/segment-resolution/loader-cache.ts +2 -1
- package/src/router/segment-resolution/loader-mask.ts +5 -3
- package/src/router/segment-resolution/loader-snapshot.ts +18 -4
- package/src/router/segment-resolution/mask-nested.ts +8 -4
- package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
- package/src/router/segment-resolution/revalidation.ts +215 -82
- package/src/rsc/full-payload.ts +37 -5
- package/src/rsc/handler.ts +82 -55
- package/src/rsc/helpers.ts +24 -0
- package/src/rsc/loader-fetch.ts +79 -24
- package/src/rsc/response-cache-serve.ts +18 -2
- package/src/rsc/response-route-handler.ts +4 -7
- package/src/rsc/rewrite-passes.ts +203 -0
- package/src/rsc/rsc-rendering.ts +54 -9
- package/src/rsc/shell-capture.ts +2 -1
- package/src/rsc/ssr-setup.ts +11 -5
- package/src/segment-loader-promise.ts +23 -20
- package/src/segment-system.tsx +60 -26
- package/src/server/context.ts +39 -5
- package/src/server/cookie-parse.ts +36 -12
- package/src/server/cookie-store.ts +7 -3
- package/src/server/handle-store.ts +152 -25
- package/src/server/request-context.ts +175 -64
- package/src/server/request-headers.ts +4 -14
- package/src/server/rewrite-trail.ts +190 -0
- package/src/server/slot-accessors.ts +57 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +118 -16
- package/src/testing/action-form.ts +2 -11
- package/src/testing/dispatch.ts +138 -67
- package/src/testing/e2e/page-helpers.ts +3 -1
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +7 -3
- package/src/testing/internal/action-id.ts +16 -1
- package/src/testing/internal/client-urls-projection.ts +70 -0
- package/src/testing/internal/test-action.ts +25 -0
- package/src/testing/render-route.tsx +16 -3
- package/src/testing/run-loader.ts +4 -1
- package/src/testing/run-middleware.ts +56 -11
- package/src/testing/run-revalidate.ts +142 -0
- package/src/testing/run-transition-when.ts +7 -8
- package/src/testing/serve-shell-request.ts +131 -8
- package/src/testing/vitest.ts +67 -1
- package/src/thenable-status.ts +11 -0
- package/src/types/boundaries.ts +12 -0
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +30 -0
- package/src/types/request-scope.ts +8 -0
- package/src/types/segments.ts +26 -4
- package/src/urls/path-helper-types.ts +27 -5
- package/src/urls/type-extraction.ts +30 -10
- package/src/use-loader.tsx +18 -13
- package/src/vite/discovery/client-urls-projection.ts +20 -21
- package/src/vite/discovery/discover-routers.ts +94 -26
- package/src/vite/discovery/state.ts +28 -4
- package/src/vite/discovery/virtual-module-codegen.ts +22 -17
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
- package/src/vite/plugins/expose-internal-ids.ts +2 -3
- package/src/vite/plugins/hoist/literal.ts +417 -0
- package/src/vite/plugins/hoist/scope.ts +370 -0
- package/src/vite/plugins/hoist/source.ts +84 -0
- package/src/vite/plugins/transition-when-hoist.ts +113 -779
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +116 -53
- package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
- package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
- package/dist/types/testing/run-client-revalidate.d.ts +0 -43
- package/src/client-urls/revalidate-chain.ts +0 -83
- package/src/client-urls/revalidation-protocol.ts +0 -56
- package/src/testing/run-client-revalidate.ts +0 -92
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
14
|
import { parseCookiesFromHeader } from "./cookie-parse.js";
|
|
15
|
+
import { defineHidden, defineSlot, slotAccessor } from "./slot-accessors.js";
|
|
15
16
|
import type { CacheErrorCategory } from "../cache/cache-error.js";
|
|
16
17
|
import type { CookieOptions } from "../router/middleware.js";
|
|
17
18
|
import {
|
|
@@ -31,6 +32,7 @@ import type {
|
|
|
31
32
|
import type { Handle } from "../handle.js";
|
|
32
33
|
import {
|
|
33
34
|
type ContextVar,
|
|
35
|
+
assignOwn,
|
|
34
36
|
contextGet,
|
|
35
37
|
contextSet,
|
|
36
38
|
isNonCacheable,
|
|
@@ -78,6 +80,7 @@ import {
|
|
|
78
80
|
readGuardedTheme,
|
|
79
81
|
} from "./cookie-store.js";
|
|
80
82
|
import { requestHeaders } from "./request-headers.js";
|
|
83
|
+
import { requestKind } from "../router/segment-resolution/prefetch-deferral.js";
|
|
81
84
|
import {
|
|
82
85
|
createReverseFunction,
|
|
83
86
|
stripInternalParams,
|
|
@@ -93,6 +96,10 @@ import {
|
|
|
93
96
|
type ExecutionStart,
|
|
94
97
|
} from "../cache/invalidation-order.js";
|
|
95
98
|
import { invariant } from "../errors.js";
|
|
99
|
+
import {
|
|
100
|
+
defineRequestRewrittenFrom,
|
|
101
|
+
defineRewrittenFrom,
|
|
102
|
+
} from "./rewrite-trail.js";
|
|
96
103
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
97
104
|
|
|
98
105
|
export interface RenderForegroundCursor {
|
|
@@ -713,6 +720,29 @@ export interface RequestContext<
|
|
|
713
720
|
*/
|
|
714
721
|
_payloadVisitorTheme?: boolean;
|
|
715
722
|
|
|
723
|
+
/**
|
|
724
|
+
* @internal Set by the partial render when its payload carries deferred
|
|
725
|
+
* units (`prefetch: false`): what one page's prefetch left for the fill.
|
|
726
|
+
* The document cache does not store that response, whatever Cache-Control
|
|
727
|
+
* it ends up with (docs/design/prefetch-false.md, R7).
|
|
728
|
+
*/
|
|
729
|
+
_payloadDeferred?: boolean;
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* @internal What the request says it is: a `<Link>` prefetch or a fill
|
|
733
|
+
* (`prefetch: false`), read once at creation
|
|
734
|
+
* (segment-resolution/prefetch-deferral.ts requestKind).
|
|
735
|
+
*/
|
|
736
|
+
_requestKind?: "prefetch" | "fill";
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* @internal Set when a render shows the visitor's URL of a rewritten
|
|
740
|
+
* request: the payload or SSR seed (renderedLocation, rsc/full-payload.ts)
|
|
741
|
+
* or a `ctx.rewrittenFrom` read (server/rewrite-trail.ts). The document
|
|
742
|
+
* cache does not store that response: it is keyed by the target.
|
|
743
|
+
*/
|
|
744
|
+
_payloadVisitorLocation?: boolean;
|
|
745
|
+
|
|
716
746
|
/**
|
|
717
747
|
* Attach location state entries to the current response.
|
|
718
748
|
*
|
|
@@ -1012,6 +1042,9 @@ export type PublicRequestContext<
|
|
|
1012
1042
|
| "_readTheme"
|
|
1013
1043
|
| "_documentCacheRender"
|
|
1014
1044
|
| "_payloadVisitorTheme"
|
|
1045
|
+
| "_payloadDeferred"
|
|
1046
|
+
| "_requestKind"
|
|
1047
|
+
| "_payloadVisitorLocation"
|
|
1015
1048
|
| "_locationState"
|
|
1016
1049
|
| "_routeName"
|
|
1017
1050
|
| "_prevRouteKey"
|
|
@@ -1069,6 +1102,60 @@ export const UNTRACKED_BACKGROUND_TASK: unique symbol = Symbol.for(
|
|
|
1069
1102
|
// AsyncLocalStorage instance for request context
|
|
1070
1103
|
const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
1071
1104
|
|
|
1105
|
+
// The request context's accessors: shared descriptors (slot-accessors.ts).
|
|
1106
|
+
const RES_SLOT: unique symbol = Symbol("rango.requestContext.res");
|
|
1107
|
+
const RENDER_BARRIER_SLOT: unique symbol = Symbol(
|
|
1108
|
+
"rango.requestContext.renderBarrier",
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
const RES_DESCRIPTOR: PropertyDescriptor = {
|
|
1112
|
+
...slotAccessor(RES_SLOT, true),
|
|
1113
|
+
set(_: Response): void {
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
"ctx.res is read-only. Use ctx.header() to set response headers, or cookies() for cookie mutations.",
|
|
1116
|
+
);
|
|
1117
|
+
},
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
// getRequestContext().theme is the visitor's cookie, guarded like cookies()
|
|
1121
|
+
// (#971); before it was a plain read that a ppr shell, a cache() entry or a
|
|
1122
|
+
// "use cache" entry stored for every visitor. Non-enumerable like the
|
|
1123
|
+
// handler ctx.theme (handler-context.ts THEME_DESCRIPTOR): serializing the
|
|
1124
|
+
// context (React dev debug info) must not count as a read. The guard reads
|
|
1125
|
+
// the ambient context, as cookies() does, and falls back to the receiver.
|
|
1126
|
+
const REQUEST_THEME_DESCRIPTOR: PropertyDescriptor = {
|
|
1127
|
+
get(this: RequestContext<any>): Theme | undefined {
|
|
1128
|
+
return readGuardedTheme(this, "getRequestContext().theme");
|
|
1129
|
+
},
|
|
1130
|
+
enumerable: false,
|
|
1131
|
+
configurable: true,
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
// A fresh context enumerates _renderBarrier like its other fields (the
|
|
1135
|
+
// fetchable-loader ctx spreads it, loader-fetch.ts); a derived one never did.
|
|
1136
|
+
const RENDER_BARRIER_DESCRIPTOR: PropertyDescriptor = slotAccessor(
|
|
1137
|
+
RENDER_BARRIER_SLOT,
|
|
1138
|
+
true,
|
|
1139
|
+
);
|
|
1140
|
+
const DERIVED_RENDER_BARRIER_DESCRIPTOR: PropertyDescriptor = {
|
|
1141
|
+
...RENDER_BARRIER_DESCRIPTOR,
|
|
1142
|
+
enumerable: false,
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Define `ctx.res` as an own accessor that reads `get`. Assigning it throws
|
|
1147
|
+
* the read-only error. A context derived from another (Object.create) gets
|
|
1148
|
+
* its own response this way; copying the descriptor would read the
|
|
1149
|
+
* prototype's slot.
|
|
1150
|
+
*/
|
|
1151
|
+
export function bindRes(
|
|
1152
|
+
ctx: RequestContext<any, any>,
|
|
1153
|
+
get: () => Response,
|
|
1154
|
+
): void {
|
|
1155
|
+
defineSlot(ctx, RES_SLOT, get);
|
|
1156
|
+
Object.defineProperty(ctx, "res", RES_DESCRIPTOR);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1072
1159
|
/**
|
|
1073
1160
|
* Capture the current async context (RequestContext._runAtRequestEntry), taken
|
|
1074
1161
|
* on handler entry before any router scope, so a nested dispatch (a
|
|
@@ -1398,10 +1485,10 @@ export function createRequestContext<TEnv>(
|
|
|
1398
1485
|
}
|
|
1399
1486
|
const result: Record<string, string> = {};
|
|
1400
1487
|
for (const key of Object.keys(parsed)) {
|
|
1401
|
-
if (!deleted.has(key)) result
|
|
1488
|
+
if (!deleted.has(key)) assignOwn(result, key, parsed[key]);
|
|
1402
1489
|
}
|
|
1403
1490
|
for (const [k, v] of mutations) {
|
|
1404
|
-
if (v !== null) result
|
|
1491
|
+
if (v !== null) assignOwn(result, k, v);
|
|
1405
1492
|
}
|
|
1406
1493
|
return result;
|
|
1407
1494
|
};
|
|
@@ -1445,12 +1532,18 @@ export function createRequestContext<TEnv>(
|
|
|
1445
1532
|
};
|
|
1446
1533
|
|
|
1447
1534
|
const cleanUrl = stripInternalParams(url);
|
|
1535
|
+
const originalUrl = new URL(request.url);
|
|
1448
1536
|
|
|
1449
|
-
|
|
1537
|
+
// Data members only: the accessors and hidden members are defined after it.
|
|
1538
|
+
const fields: Omit<
|
|
1539
|
+
RequestContext<TEnv>,
|
|
1540
|
+
"res" | "_renderBarrier" | "_readTheme" | "_readCookie" | "_readCookies"
|
|
1541
|
+
> = {
|
|
1450
1542
|
env,
|
|
1451
1543
|
request,
|
|
1452
1544
|
url: cleanUrl,
|
|
1453
|
-
originalUrl
|
|
1545
|
+
originalUrl,
|
|
1546
|
+
_requestKind: requestKind(request, originalUrl),
|
|
1454
1547
|
pathname: url.pathname,
|
|
1455
1548
|
searchParams: cleanUrl.searchParams,
|
|
1456
1549
|
_variables: variables,
|
|
@@ -1476,15 +1569,6 @@ export function createRequestContext<TEnv>(
|
|
|
1476
1569
|
}) as RequestContext<TEnv>["set"],
|
|
1477
1570
|
params: {} as Record<string, string>,
|
|
1478
1571
|
|
|
1479
|
-
get res(): Response {
|
|
1480
|
-
return stubResponse;
|
|
1481
|
-
},
|
|
1482
|
-
set res(_: Response) {
|
|
1483
|
-
throw new Error(
|
|
1484
|
-
"ctx.res is read-only. Use ctx.header() to set response headers, or cookies() for cookie mutations.",
|
|
1485
|
-
);
|
|
1486
|
-
},
|
|
1487
|
-
|
|
1488
1572
|
// The visitor's cookies, guarded like cookies() (#976): before, a plain
|
|
1489
1573
|
// read that a ppr shell, a cache() entry or a "use cache" entry stored
|
|
1490
1574
|
// for every visitor.
|
|
@@ -1498,10 +1582,6 @@ export function createRequestContext<TEnv>(
|
|
|
1498
1582
|
return effectiveCookies();
|
|
1499
1583
|
},
|
|
1500
1584
|
|
|
1501
|
-
// Non-enumerable, defined after this literal.
|
|
1502
|
-
_readCookie: effectiveCookie,
|
|
1503
|
-
_readCookies: effectiveCookies,
|
|
1504
|
-
|
|
1505
1585
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
1506
1586
|
assertNotInsideCacheExec(ctx, "setCookie");
|
|
1507
1587
|
stubResponse.headers.append(
|
|
@@ -1690,8 +1770,6 @@ export function createRequestContext<TEnv>(
|
|
|
1690
1770
|
this._onResponseCallbacks.push(callback);
|
|
1691
1771
|
},
|
|
1692
1772
|
|
|
1693
|
-
// theme: a guarded, non-enumerable getter defined after this literal.
|
|
1694
|
-
_readTheme: getTheme,
|
|
1695
1773
|
setTheme: themeConfig
|
|
1696
1774
|
? (theme: Theme) => {
|
|
1697
1775
|
assertNotInsideCacheExec(ctx, "setTheme");
|
|
@@ -1717,14 +1795,15 @@ export function createRequestContext<TEnv>(
|
|
|
1717
1795
|
_renderDiagnosticsEnabled: undefined,
|
|
1718
1796
|
_handlerStart: undefined,
|
|
1719
1797
|
|
|
1720
|
-
_renderBarrier: null as any,
|
|
1721
1798
|
_resolveRenderBarrier: null as any,
|
|
1722
1799
|
_renderBarrierSegmentOrder: undefined,
|
|
1723
1800
|
|
|
1724
1801
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
1725
1802
|
};
|
|
1803
|
+
const ctx = fields as RequestContext<TEnv>;
|
|
1726
1804
|
|
|
1727
|
-
|
|
1805
|
+
bindRes(ctx, () => stubResponse);
|
|
1806
|
+
wireRenderBarrier(ctx, handleStore, RENDER_BARRIER_DESCRIPTOR);
|
|
1728
1807
|
|
|
1729
1808
|
ctx.use = createUseFunction({
|
|
1730
1809
|
handleStore,
|
|
@@ -1732,41 +1811,70 @@ export function createRequestContext<TEnv>(
|
|
|
1732
1811
|
getContext: () => ctx,
|
|
1733
1812
|
});
|
|
1734
1813
|
|
|
1735
|
-
//
|
|
1736
|
-
//
|
|
1737
|
-
//
|
|
1738
|
-
//
|
|
1739
|
-
//
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
theme: {
|
|
1746
|
-
get(this: RequestContext<TEnv>): Theme | undefined {
|
|
1747
|
-
return readGuardedTheme(this, "getRequestContext().theme");
|
|
1748
|
-
},
|
|
1749
|
-
enumerable: false,
|
|
1750
|
-
configurable: true,
|
|
1751
|
-
},
|
|
1752
|
-
_readTheme: { enumerable: false },
|
|
1753
|
-
_readCookie: { enumerable: false },
|
|
1754
|
-
_readCookies: { enumerable: false },
|
|
1755
|
-
});
|
|
1814
|
+
// theme: REQUEST_THEME_DESCRIPTOR. _readTheme is the unguarded read:
|
|
1815
|
+
// non-enumerable too, so a spread copy of the context (the fetchable-loader
|
|
1816
|
+
// ctx, loader-fetch.ts) cannot carry it. The same holds for the unguarded
|
|
1817
|
+
// cookie reads (#976). Added here, not flipped from enumerable: changing an
|
|
1818
|
+
// existing property's attributes costs V8 a map copy per request.
|
|
1819
|
+
Object.defineProperty(ctx, "theme", REQUEST_THEME_DESCRIPTOR);
|
|
1820
|
+
defineRequestRewrittenFrom(ctx);
|
|
1821
|
+
defineHidden(ctx, "_readTheme", getTheme);
|
|
1822
|
+
defineHidden(ctx, "_readCookie", effectiveCookie);
|
|
1823
|
+
defineHidden(ctx, "_readCookies", effectiveCookies);
|
|
1756
1824
|
|
|
1757
1825
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
1758
1826
|
// Non-enumerable: a self-reference would make the context circular for
|
|
1759
1827
|
// JSON.stringify, spreads and React's dev debug-info serialization of props.
|
|
1760
|
-
|
|
1761
|
-
value: ctx,
|
|
1762
|
-
enumerable: false,
|
|
1763
|
-
configurable: true,
|
|
1764
|
-
writable: true,
|
|
1765
|
-
});
|
|
1828
|
+
defineHidden(ctx, "_requestRoot", ctx);
|
|
1766
1829
|
ctx._requestStart = executionStart();
|
|
1767
1830
|
return ctx;
|
|
1768
1831
|
}
|
|
1769
1832
|
|
|
1833
|
+
/**
|
|
1834
|
+
* Move a request context to a rewrite target for the next pass of the
|
|
1835
|
+
* request (rsc/rewrite-passes.ts, `throw rewrite()`). The context object
|
|
1836
|
+
* stays the same, so what the visitor's pass set is still there: variables
|
|
1837
|
+
* (the rewrite trail among them), the response stub (status, headers,
|
|
1838
|
+
* cookies), location state, theme, `dynamic()`, metrics, request tags,
|
|
1839
|
+
* onResponse callbacks, the handle store.
|
|
1840
|
+
*
|
|
1841
|
+
* Moved to the target: the request (`rawUrl`, the visitor's `_rsc*` params
|
|
1842
|
+
* included), url, originalUrl, pathname, searchParams and the request kind.
|
|
1843
|
+
* Cleared for the target's match: params, route name, reverse, the
|
|
1844
|
+
* classified route, the request mode, and the document cache's render mark
|
|
1845
|
+
* (its middleware runs again for the target). The loader memo behind
|
|
1846
|
+
* `getRequestContext().use()` is new: a loader that middleware started in
|
|
1847
|
+
* the visitor's pass read the visitor's URL. A pass never renders before it
|
|
1848
|
+
* rewrites: router/middleware.ts refuses a rewrite thrown after a next()
|
|
1849
|
+
* unless that next() rejected with a Rewrite (a downstream middleware
|
|
1850
|
+
* rewrote before its own next()), so there is no render state to reset.
|
|
1851
|
+
*/
|
|
1852
|
+
export function rebaseRequestContext(
|
|
1853
|
+
ctx: RequestContext<any>,
|
|
1854
|
+
request: Request,
|
|
1855
|
+
rawUrl: URL,
|
|
1856
|
+
): void {
|
|
1857
|
+
const cleanUrl = stripInternalParams(rawUrl);
|
|
1858
|
+
ctx.request = guardRequestHeaders(request);
|
|
1859
|
+
ctx.url = cleanUrl;
|
|
1860
|
+
ctx.originalUrl = rawUrl;
|
|
1861
|
+
ctx.pathname = cleanUrl.pathname;
|
|
1862
|
+
ctx.searchParams = cleanUrl.searchParams;
|
|
1863
|
+
ctx._requestKind = requestKind(request, rawUrl);
|
|
1864
|
+
ctx.params = {};
|
|
1865
|
+
ctx._routeName = undefined;
|
|
1866
|
+
ctx.routeName = undefined;
|
|
1867
|
+
ctx.reverse = createReverseFunction(getGlobalRouteMap(), undefined, {});
|
|
1868
|
+
ctx._classifiedRoute = undefined;
|
|
1869
|
+
ctx._requestMode = undefined;
|
|
1870
|
+
ctx._documentCacheRender = undefined;
|
|
1871
|
+
ctx.use = createUseFunction({
|
|
1872
|
+
handleStore: ctx._handleStore,
|
|
1873
|
+
loaderPromises: new Map(),
|
|
1874
|
+
getContext: () => ctx,
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1770
1878
|
/**
|
|
1771
1879
|
* Wire a fresh render barrier onto `ctx`, closure-bound to THIS ctx and THIS
|
|
1772
1880
|
* handle store. Called by createRequestContext for every fresh context, and by
|
|
@@ -1783,10 +1891,14 @@ export function createRequestContext<TEnv>(
|
|
|
1783
1891
|
* read the foreground's handle snapshot; the capture's fresh `_handleStore`
|
|
1784
1892
|
* was invisible, so foreground per-request handle data could bake into the
|
|
1785
1893
|
* shared shell.
|
|
1894
|
+
*
|
|
1895
|
+
* A derived context's `_renderBarrier` is non-enumerable (the default
|
|
1896
|
+
* descriptor); a fresh context's is enumerable like its other fields.
|
|
1786
1897
|
*/
|
|
1787
1898
|
export function wireRenderBarrier(
|
|
1788
1899
|
ctx: RequestContext<any, any>,
|
|
1789
1900
|
handleStore: HandleStore,
|
|
1901
|
+
descriptor: PropertyDescriptor = DERIVED_RENDER_BARRIER_DESCRIPTOR,
|
|
1790
1902
|
): void {
|
|
1791
1903
|
// Reset the whole barrier family as OWN properties. No-op for a fresh
|
|
1792
1904
|
// context; for the derived capture context this shadows the foreground's
|
|
@@ -1833,25 +1945,23 @@ export function wireRenderBarrier(
|
|
|
1833
1945
|
}
|
|
1834
1946
|
if (resolveBarrier) resolveBarrier();
|
|
1835
1947
|
};
|
|
1948
|
+
defineSlot(ctx, RENDER_BARRIER_SLOT, (): Promise<void> => {
|
|
1949
|
+
const p = barrierResolved
|
|
1950
|
+
? Promise.resolve()
|
|
1951
|
+
: new Promise<void>((resolve) => {
|
|
1952
|
+
resolveBarrier = resolve;
|
|
1953
|
+
});
|
|
1954
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
1955
|
+
value: p,
|
|
1956
|
+
writable: false,
|
|
1957
|
+
configurable: false,
|
|
1958
|
+
});
|
|
1959
|
+
return p;
|
|
1960
|
+
});
|
|
1836
1961
|
// defineProperty, not assignment: on a derived context the prototype's
|
|
1837
1962
|
// _renderBarrier may already be a non-writable data property (the getter
|
|
1838
1963
|
// pins it after first access), which would reject a plain assignment.
|
|
1839
|
-
Object.defineProperty(ctx, "_renderBarrier",
|
|
1840
|
-
get() {
|
|
1841
|
-
const p = barrierResolved
|
|
1842
|
-
? Promise.resolve()
|
|
1843
|
-
: new Promise<void>((resolve) => {
|
|
1844
|
-
resolveBarrier = resolve;
|
|
1845
|
-
});
|
|
1846
|
-
Object.defineProperty(ctx, "_renderBarrier", {
|
|
1847
|
-
value: p,
|
|
1848
|
-
writable: false,
|
|
1849
|
-
configurable: false,
|
|
1850
|
-
});
|
|
1851
|
-
return p;
|
|
1852
|
-
},
|
|
1853
|
-
configurable: true,
|
|
1854
|
-
});
|
|
1964
|
+
Object.defineProperty(ctx, "_renderBarrier", descriptor);
|
|
1855
1965
|
}
|
|
1856
1966
|
|
|
1857
1967
|
// Capture the Max-Age value so it can be parsed numerically. A leading zero
|
|
@@ -2083,6 +2193,7 @@ export function createUseFunction<TEnv>(
|
|
|
2083
2193
|
);
|
|
2084
2194
|
},
|
|
2085
2195
|
};
|
|
2196
|
+
defineRewrittenFrom(loaderCtx, ctx);
|
|
2086
2197
|
// "use cache" keys it by route, like the request ctx it reads (#940).
|
|
2087
2198
|
(loaderCtx as any)[NOCACHE_SYMBOL] = true;
|
|
2088
2199
|
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* keeps the object a real Request, and those operations never call it.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { defineHidden } from "./slot-accessors.js";
|
|
14
|
+
|
|
13
15
|
/** The Headers each shadowed request's getter returns. */
|
|
14
16
|
const RAW_HEADERS = new WeakMap<Request, Headers>();
|
|
15
17
|
|
|
@@ -93,12 +95,12 @@ export function shadowRequestHeaders(
|
|
|
93
95
|
configurable: true,
|
|
94
96
|
});
|
|
95
97
|
const clone = request.clone;
|
|
96
|
-
|
|
98
|
+
defineHidden(request, "clone", function (this: Request): Request {
|
|
97
99
|
return shadowRequestHeaders(clone.call(this), beforeRead);
|
|
98
100
|
});
|
|
99
101
|
const inspect = (request as unknown as Record<symbol, unknown>)[INSPECT];
|
|
100
102
|
if (typeof inspect === "function") {
|
|
101
|
-
|
|
103
|
+
defineHidden(
|
|
102
104
|
request,
|
|
103
105
|
INSPECT,
|
|
104
106
|
function (this: Request, ...args: unknown[]) {
|
|
@@ -109,18 +111,6 @@ export function shadowRequestHeaders(
|
|
|
109
111
|
return request;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
function defineMethod(
|
|
113
|
-
target: object,
|
|
114
|
-
key: PropertyKey,
|
|
115
|
-
value: (...args: never[]) => unknown,
|
|
116
|
-
): void {
|
|
117
|
-
Object.defineProperty(target, key, {
|
|
118
|
-
value,
|
|
119
|
-
configurable: true,
|
|
120
|
-
writable: true,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
114
|
/** The getter `key` resolves to on `target`'s prototype chain, if any. */
|
|
125
115
|
function inheritedGetter(
|
|
126
116
|
target: object,
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The URLs a rewritten request moved through (`throw rewrite()`), and what
|
|
3
|
+
* each context sees of them.
|
|
4
|
+
*
|
|
5
|
+
* One source: the trail lives in the request's variables under a token no
|
|
6
|
+
* user code can reach, so a middleware cannot reset the loop count. The
|
|
7
|
+
* visitor's URL is its first entry (`ctx.rewrittenFrom`); the hops are its
|
|
8
|
+
* length minus one.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { contextGet, contextSet, createVar } from "../context-var.js";
|
|
12
|
+
import { RewriteLoopError } from "../errors.js";
|
|
13
|
+
import { documentCacheStoresRender } from "../cache/document-cache-policy.js";
|
|
14
|
+
import { guardIdentityRead, type IdentityReadWording } from "./context.js";
|
|
15
|
+
import { stripInternalParams } from "../router/internal-params.js";
|
|
16
|
+
import type { RequestContext } from "./request-context.js";
|
|
17
|
+
|
|
18
|
+
/** Most rewrites one request may take; the next one is a RewriteLoopError. */
|
|
19
|
+
export const MAX_REWRITE_HOPS = 5;
|
|
20
|
+
|
|
21
|
+
/** Clean hrefs, the visitor's first. */
|
|
22
|
+
const REWRITE_TRAIL = createVar<readonly string[]>();
|
|
23
|
+
|
|
24
|
+
type TrailContext = Pick<RequestContext<any>, "_variables">;
|
|
25
|
+
|
|
26
|
+
function pathKey(url: URL): string {
|
|
27
|
+
return url.pathname + url.search;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The trail of a rewritten request; undefined when nothing rewrote. */
|
|
31
|
+
export function rewriteTrail(
|
|
32
|
+
reqCtx: TrailContext | undefined,
|
|
33
|
+
): readonly string[] | undefined {
|
|
34
|
+
return reqCtx?._variables
|
|
35
|
+
? (contextGet(reqCtx._variables, REWRITE_TRAIL) as
|
|
36
|
+
| readonly string[]
|
|
37
|
+
| undefined)
|
|
38
|
+
: undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Count one hop from `from` to `to` (clean URLs) and return its number.
|
|
43
|
+
* Throws RewriteLoopError past {@link MAX_REWRITE_HOPS}, or when `to` is the
|
|
44
|
+
* URL the request is on (pathname and search).
|
|
45
|
+
*/
|
|
46
|
+
export function recordRewriteHop(
|
|
47
|
+
reqCtx: TrailContext,
|
|
48
|
+
from: URL,
|
|
49
|
+
to: URL,
|
|
50
|
+
): number {
|
|
51
|
+
const trail = [...(rewriteTrail(reqCtx) ?? [from.href]), to.href];
|
|
52
|
+
const hops = trail.length - 1;
|
|
53
|
+
if (pathKey(to) === pathKey(from) || hops > MAX_REWRITE_HOPS) {
|
|
54
|
+
throw new RewriteLoopError(
|
|
55
|
+
trail.map((href) => pathKey(new URL(href))),
|
|
56
|
+
MAX_REWRITE_HOPS,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
contextSet(reqCtx._variables, REWRITE_TRAIL, trail);
|
|
60
|
+
return hops;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type AudienceContext = Pick<
|
|
64
|
+
RequestContext<any>,
|
|
65
|
+
"_shellCaptureRun" | "_documentCacheRender" | "res"
|
|
66
|
+
>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A render other visitors may be served: a PPR shell capture, or a render
|
|
70
|
+
* the document cache stores (its response opted in before next()). It shows
|
|
71
|
+
* what a direct visit to its URL shows: no visitor theme, no visitor URL.
|
|
72
|
+
*/
|
|
73
|
+
export function rendersForSharedAudience(reqCtx: AudienceContext): boolean {
|
|
74
|
+
return reqCtx._shellCaptureRun === true || documentCacheStoresRender(reqCtx);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The URL the visitor is on: the first URL of a rewritten request, else
|
|
79
|
+
* `url` with the internal `_rsc*` params stripped. A reload the router asks
|
|
80
|
+
* the browser for points here.
|
|
81
|
+
*/
|
|
82
|
+
export function visitorUrl(reqCtx: TrailContext | undefined, url: URL): URL {
|
|
83
|
+
const first = rewriteTrail(reqCtx)?.[0];
|
|
84
|
+
return first ? new URL(first) : stripInternalParams(url);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const REWRITTEN_FROM_FIX =
|
|
88
|
+
"It is the visitor's URL, which the shared entry would serve to every " +
|
|
89
|
+
"visitor. Read it in middleware and pass what the page needs through a " +
|
|
90
|
+
"variable the cache key covers.";
|
|
91
|
+
|
|
92
|
+
const REWRITTEN_FROM_READ: IdentityReadWording = {
|
|
93
|
+
verb: "read",
|
|
94
|
+
fix: {
|
|
95
|
+
useCache:
|
|
96
|
+
"ctx.rewrittenFrom is not in the cache key. Read it before the cached " +
|
|
97
|
+
"function and pass it in as an argument.",
|
|
98
|
+
cacheScope: REWRITTEN_FROM_FIX,
|
|
99
|
+
capture: REWRITTEN_FROM_FIX,
|
|
100
|
+
warning: REWRITTEN_FROM_FIX,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Who reads `rewrittenFrom`: "render" for the contexts a render reads
|
|
106
|
+
* (handler, loader, response route, getRequestContext()), "middleware" for a
|
|
107
|
+
* middleware ctx, which runs once per request and is never stored.
|
|
108
|
+
*/
|
|
109
|
+
export type RewrittenFromReader = "render" | "middleware";
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The read behind every `ctx.rewrittenFrom`. Guarded like cookies(): inside
|
|
113
|
+
* a `cache()` boundary or a `"use cache"` function it throws, and a loader's
|
|
114
|
+
* own `cache()` fill records it. A render other visitors may be served sees
|
|
115
|
+
* `undefined`, as on a direct visit; any other render that sees the visitor's
|
|
116
|
+
* URL marks `_payloadVisitorLocation`, so the document cache does not store
|
|
117
|
+
* it.
|
|
118
|
+
*/
|
|
119
|
+
function readRewrittenFrom(
|
|
120
|
+
reqCtx: RequestContext<any> | undefined,
|
|
121
|
+
reader: RewrittenFromReader,
|
|
122
|
+
): string | undefined {
|
|
123
|
+
if (!reqCtx) return undefined;
|
|
124
|
+
if (reader === "render" && rendersForSharedAudience(reqCtx)) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
guardIdentityRead(reqCtx, "ctx.rewrittenFrom", REWRITTEN_FROM_READ);
|
|
128
|
+
const href = rewriteTrail(reqCtx)?.[0];
|
|
129
|
+
if (href && reader === "render") reqCtx._payloadVisitorLocation = true;
|
|
130
|
+
return href;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Each context's own copy of the URL, kept while the visitor URL holds. */
|
|
134
|
+
const COPIES = new WeakMap<object, URL>();
|
|
135
|
+
/** The request context a handler/loader/middleware ctx reads through. */
|
|
136
|
+
const SOURCES = new WeakMap<object, RequestContext<any> | undefined>();
|
|
137
|
+
const READERS = new WeakMap<object, RewrittenFromReader>();
|
|
138
|
+
|
|
139
|
+
function copyFor(holder: object, href: string | undefined): URL | undefined {
|
|
140
|
+
if (!href) return undefined;
|
|
141
|
+
const memo = COPIES.get(holder);
|
|
142
|
+
if (memo && memo.href === href) return memo;
|
|
143
|
+
const copy = new URL(href);
|
|
144
|
+
COPIES.set(holder, copy);
|
|
145
|
+
return copy;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Non-enumerable, like `ctx.theme`: serializing a ctx (React's dev debug
|
|
150
|
+
* info) must not count as a read.
|
|
151
|
+
*/
|
|
152
|
+
const SCOPED_DESCRIPTOR: PropertyDescriptor = {
|
|
153
|
+
get(this: object): URL | undefined {
|
|
154
|
+
const href = readRewrittenFrom(
|
|
155
|
+
SOURCES.get(this),
|
|
156
|
+
READERS.get(this) ?? "render",
|
|
157
|
+
);
|
|
158
|
+
return copyFor(this, href);
|
|
159
|
+
},
|
|
160
|
+
enumerable: false,
|
|
161
|
+
configurable: true,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const REQUEST_DESCRIPTOR: PropertyDescriptor = {
|
|
165
|
+
get(this: RequestContext<any>): URL | undefined {
|
|
166
|
+
return copyFor(this, readRewrittenFrom(this, "render"));
|
|
167
|
+
},
|
|
168
|
+
enumerable: false,
|
|
169
|
+
configurable: true,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Define `ctx.rewrittenFrom` on a handler, loader, response-route or
|
|
174
|
+
* middleware ctx, read through `source` (the request context it belongs
|
|
175
|
+
* to). Each ctx gets its own copy of the URL.
|
|
176
|
+
*/
|
|
177
|
+
export function defineRewrittenFrom(
|
|
178
|
+
ctx: object,
|
|
179
|
+
source: RequestContext<any> | undefined,
|
|
180
|
+
reader: RewrittenFromReader = "render",
|
|
181
|
+
): void {
|
|
182
|
+
SOURCES.set(ctx, source);
|
|
183
|
+
if (reader !== "render") READERS.set(ctx, reader);
|
|
184
|
+
Object.defineProperty(ctx, "rewrittenFrom", SCOPED_DESCRIPTOR);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Define `rewrittenFrom` on a request context, read through itself. */
|
|
188
|
+
export function defineRequestRewrittenFrom(ctx: RequestContext<any>): void {
|
|
189
|
+
Object.defineProperty(ctx, "rewrittenFrom", REQUEST_DESCRIPTOR);
|
|
190
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property definitions for per-request objects that keep V8 fast properties
|
|
3
|
+
* (#1109): the request context, the middleware ctx, the handle store and a
|
|
4
|
+
* guarded request.
|
|
5
|
+
*
|
|
6
|
+
* V8 puts an object in dictionary mode, where every property read is a hash
|
|
7
|
+
* lookup for the object's whole lifetime, when an object literal declares a
|
|
8
|
+
* get/set member, when a data property is redefined as an accessor after
|
|
9
|
+
* creation, or when an accessor is defined with a function its shape
|
|
10
|
+
* transition has not recorded: a closure created per request is a new
|
|
11
|
+
* function every time. So an accessor on a per-request object is a
|
|
12
|
+
* module-level descriptor, and one that needs per-object state reads a
|
|
13
|
+
* per-object closure from a non-enumerable symbol slot (slotAccessor +
|
|
14
|
+
* defineSlot). A derived object (Object.create(obj)) reads the slot through
|
|
15
|
+
* its prototype; the descriptor copied onto another object reads that
|
|
16
|
+
* object's slot instead. context-fast-properties.test.ts pins all of it.
|
|
17
|
+
*
|
|
18
|
+
* Dependency-free leaf: handle-store.ts is imported by request-context.ts,
|
|
19
|
+
* so these cannot live in either.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
type SlotHolder = Record<symbol, () => unknown>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* An accessor descriptor whose getter calls the receiver's `slot` closure.
|
|
26
|
+
* Call it once at module level and share the result.
|
|
27
|
+
*/
|
|
28
|
+
export function slotAccessor(
|
|
29
|
+
slot: symbol,
|
|
30
|
+
enumerable: boolean,
|
|
31
|
+
): PropertyDescriptor {
|
|
32
|
+
return {
|
|
33
|
+
get(this: SlotHolder): unknown {
|
|
34
|
+
return this[slot]();
|
|
35
|
+
},
|
|
36
|
+
enumerable,
|
|
37
|
+
configurable: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Define `slot` on `target` as a non-enumerable, read-only data property. */
|
|
42
|
+
export function defineSlot(target: object, slot: symbol, value: unknown): void {
|
|
43
|
+
Object.defineProperty(target, slot, { value, configurable: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Define `key` on `target` as a non-enumerable, writable data property. */
|
|
47
|
+
export function defineHidden(
|
|
48
|
+
target: object,
|
|
49
|
+
key: PropertyKey,
|
|
50
|
+
value: unknown,
|
|
51
|
+
): void {
|
|
52
|
+
Object.defineProperty(target, key, {
|
|
53
|
+
value,
|
|
54
|
+
writable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
});
|
|
57
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -58,3 +58,7 @@ export {
|
|
|
58
58
|
setClientUrlProjection,
|
|
59
59
|
type ClientUrlProjection,
|
|
60
60
|
} from "./client-urls/server-projection.js";
|
|
61
|
+
|
|
62
|
+
// Hoisted clientUrls() revalidate() predicates: the generated
|
|
63
|
+
// `<file>?rango-revalidate` modules register them in the RSC realm.
|
|
64
|
+
export { registerClientUrlRevalidators } from "./client-urls/revalidate-registry.js";
|