@rangojs/router 0.24.4 → 0.26.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/README.md +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +0 -2
- package/dist/types/cache/cf/cf-cache-types.d.ts +0 -16
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/handle-snapshot.d.ts +2 -2
- package/dist/types/cache/types.d.ts +16 -6
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +1 -15
- package/dist/types/client-urls/client-urls.d.ts +6 -2
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +9 -5
- package/dist/types/client-urls/types.d.ts +29 -16
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/handles/deferred-resolution.d.ts +3 -2
- package/dist/types/handles/meta.d.ts +5 -0
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/prerender/build-shell-capture.d.ts +1 -2
- package/dist/types/prerender/writable-store.d.ts +2 -3
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -11
- package/dist/types/router/router-options.d.ts +4 -27
- package/dist/types/router/segment-resolution/mask-nested.d.ts +5 -3
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +3 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/router-versions.d.ts +8 -6
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +14 -4
- package/dist/types/rsc/shell-capture.d.ts +23 -2
- package/dist/types/rsc/types.d.ts +5 -8
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/build-version-table.d.ts +10 -13
- package/dist/types/server/context.d.ts +90 -6
- package/dist/types/server/handle-store.d.ts +17 -6
- package/dist/types/server/request-context.d.ts +45 -7
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/build-versions.d.ts +0 -3
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/internal/seed-vars.d.ts +4 -4
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +42 -5
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +10 -4
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6819 -6555
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/cache-guide/SKILL.md +4 -8
- package/skills/caching/SKILL.md +6 -6
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +185 -32
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/host-router/SKILL.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +42 -19
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +103 -52
- package/skills/prerender/SKILL.md +4 -5
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/router-setup/SKILL.md +0 -2
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/render-handler.md +17 -17
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +86 -22
- package/skills/testing/server-actions.md +16 -16
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +112 -84
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +6 -7
- package/src/cache/cf/cf-cache-types.ts +0 -17
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/handle-snapshot.ts +2 -2
- package/src/cache/types.ts +16 -6
- package/src/cache/vercel/vercel-cache-store.ts +8 -19
- package/src/client-urls/client-urls.ts +202 -153
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +137 -66
- package/src/client-urls/types.ts +30 -15
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/handles/deferred-resolution.ts +3 -2
- package/src/handles/meta.ts +5 -0
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/prerender/build-shell-capture.ts +1 -2
- package/src/prerender/writable-store.ts +2 -3
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +41 -21
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +15 -10
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -13
- package/src/router/router-options.ts +4 -28
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/loader-cache.ts +4 -3
- package/src/router/segment-resolution/mask-nested.ts +5 -3
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +3 -2
- package/src/router/tracing.ts +2 -2
- package/src/router-versions.ts +7 -7
- package/src/router.ts +5 -17
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +86 -48
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +26 -3
- package/src/rsc/shell-capture.ts +137 -51
- package/src/rsc/types.ts +5 -9
- package/src/segment-system.tsx +125 -119
- package/src/server/build-version-table.ts +12 -24
- package/src/server/context.ts +160 -7
- package/src/server/handle-store.ts +80 -12
- package/src/server/request-context.ts +46 -6
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/build-versions.ts +0 -3
- package/src/testing/dispatch.ts +5 -8
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/internal/seed-vars.ts +4 -4
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +79 -9
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +10 -4
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/shell-prerender-phase.ts +1 -5
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
package/src/server/context.ts
CHANGED
|
@@ -92,6 +92,12 @@ export type EntryPropCommon = {
|
|
|
92
92
|
cache?: EntryCacheConfig;
|
|
93
93
|
/** URL prefix from include() scope, used for MountContext on client */
|
|
94
94
|
mountPath?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Include scope the entry's handler and loaders resolve `ctx.reverse(".name")`
|
|
97
|
+
* in. Structural entries only (layout, cache, parallel, wrapping
|
|
98
|
+
* middleware); a route resolves from its own name. See ReverseScope.
|
|
99
|
+
*/
|
|
100
|
+
reverseScope?: ReverseScope;
|
|
95
101
|
/** clientUrls() group key (PathOptions.clientGroup); route entries only. */
|
|
96
102
|
clientGroup?: string;
|
|
97
103
|
};
|
|
@@ -124,6 +130,11 @@ export type EntryPropRender = {
|
|
|
124
130
|
* on both DSL evaluations, whatever `ssr` did to `loading`.
|
|
125
131
|
*/
|
|
126
132
|
loadingPrefetch?: false;
|
|
133
|
+
/**
|
|
134
|
+
* loading(fallback, { ssr: false }), on both DSL evaluations; a document
|
|
135
|
+
* then sets `loading` to false (applyDocumentModeLoading).
|
|
136
|
+
*/
|
|
137
|
+
loadingSsr?: false;
|
|
127
138
|
transition?: TransitionConfig;
|
|
128
139
|
};
|
|
129
140
|
|
|
@@ -145,7 +156,8 @@ export type LoaderEntry = {
|
|
|
145
156
|
* Resolved at DSL-evaluation time from ctx.isSSR — entries are cached
|
|
146
157
|
* per-isSSR (router/manifest.ts cache key), so the flag is already
|
|
147
158
|
* request-mode-correct when resolveLoaders (fresh.ts) reads it and never
|
|
148
|
-
* appears on navigation-lane entries.
|
|
159
|
+
* appears on navigation-lane entries. Set by documentModeLoader, which
|
|
160
|
+
* getDocumentModeEntry also applies to a lazy include's captured ancestors.
|
|
149
161
|
*/
|
|
150
162
|
awaitBeforeFlush?: true;
|
|
151
163
|
/**
|
|
@@ -163,6 +175,13 @@ export type LoaderEntry = {
|
|
|
163
175
|
* (router/segment-resolution/prefetch-deferral.ts).
|
|
164
176
|
*/
|
|
165
177
|
prefetch?: false;
|
|
178
|
+
/**
|
|
179
|
+
* The declaring layout's / parallel's / cache()'s include scope, for the
|
|
180
|
+
* loader body's `ctx.reverse(".name")` (its DSL kickoff carries it,
|
|
181
|
+
* runInsideLoaderScope). Unset on a route's loaders: they resolve from the
|
|
182
|
+
* route's name.
|
|
183
|
+
*/
|
|
184
|
+
reverseScope?: ReverseScope;
|
|
166
185
|
};
|
|
167
186
|
|
|
168
187
|
/**
|
|
@@ -233,7 +252,6 @@ export type InterceptEntry = {
|
|
|
233
252
|
slotName: `@${string}`; // e.g., "@modal"
|
|
234
253
|
routeName: string; // e.g., "card"
|
|
235
254
|
handler: ReactNode | Handler<any, any, any>;
|
|
236
|
-
middleware: MiddlewareFn<any, any>[];
|
|
237
255
|
loader: LoaderEntry[];
|
|
238
256
|
loading?: ReactNode | false;
|
|
239
257
|
transition?: TransitionConfig;
|
|
@@ -327,10 +345,22 @@ export type EntryData =
|
|
|
327
345
|
EntryPropSegments &
|
|
328
346
|
EntryPropRender);
|
|
329
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Where an include()'s handler runs in the route tree. Hidden include-scope
|
|
350
|
+
* names (`$prefix_*`) derive from it, never from counter state, so build
|
|
351
|
+
* discovery and runtime expansion name every include the same (#1104).
|
|
352
|
+
*/
|
|
353
|
+
export interface IncludeScopePosition {
|
|
354
|
+
/** Sibling indexes of the enclosing includes, "_"-joined ("" at a mount root). */
|
|
355
|
+
scopePath?: string;
|
|
356
|
+
/** The scopePath tail since the nearest include that added a name segment. */
|
|
357
|
+
hiddenScopePath?: string;
|
|
358
|
+
}
|
|
359
|
+
|
|
330
360
|
/**
|
|
331
361
|
* Tracked include info for build-time manifest generation
|
|
332
362
|
*/
|
|
333
|
-
export interface TrackedInclude {
|
|
363
|
+
export interface TrackedInclude extends IncludeScopePosition {
|
|
334
364
|
prefix: string;
|
|
335
365
|
fullPrefix: string;
|
|
336
366
|
namePrefix?: string;
|
|
@@ -352,7 +382,7 @@ export type CachedHeaderScope = {
|
|
|
352
382
|
/**
|
|
353
383
|
* Context stored in AsyncLocalStorage
|
|
354
384
|
*/
|
|
355
|
-
interface HelperContext {
|
|
385
|
+
interface HelperContext extends IncludeScopePosition {
|
|
356
386
|
manifest: Map<string, EntryData>;
|
|
357
387
|
namespace: string;
|
|
358
388
|
parent: EntryData | null;
|
|
@@ -364,7 +394,8 @@ interface HelperContext {
|
|
|
364
394
|
* different routers don't clobber each other (route-map-builder.ts). */
|
|
365
395
|
routerId?: string;
|
|
366
396
|
metrics?: MetricsStore;
|
|
367
|
-
/** True when rendering for SSR (document requests)
|
|
397
|
+
/** True when rendering for SSR (document requests). DSL decisions on it
|
|
398
|
+
* go through the document-mode helpers (documentModeLoader). */
|
|
368
399
|
isSSR?: boolean;
|
|
369
400
|
/** URL patterns map for path() routes (route name -> pattern) */
|
|
370
401
|
patterns?: Map<string, string>;
|
|
@@ -597,6 +628,8 @@ export const getContext = (): {
|
|
|
597
628
|
trackedIncludes: store.trackedIncludes,
|
|
598
629
|
cacheProfiles: store.cacheProfiles,
|
|
599
630
|
includeScope: store.includeScope,
|
|
631
|
+
scopePath: store.scopePath,
|
|
632
|
+
hiddenScopePath: store.hiddenScopePath,
|
|
600
633
|
// cachedHeaderScope (and insideCacheScope) deliberately NOT copied —
|
|
601
634
|
// the header guard's middleware exemption depends on the latch dying
|
|
602
635
|
// with the funnel scope (see assertCachedHeaderWriteAllowed).
|
|
@@ -641,6 +674,8 @@ export const getContext = (): {
|
|
|
641
674
|
rootScoped: store?.rootScoped,
|
|
642
675
|
trackedIncludes: store?.trackedIncludes,
|
|
643
676
|
cacheProfiles: store?.cacheProfiles,
|
|
677
|
+
scopePath: store?.scopePath,
|
|
678
|
+
hiddenScopePath: store?.hiddenScopePath,
|
|
644
679
|
},
|
|
645
680
|
callback,
|
|
646
681
|
);
|
|
@@ -763,6 +798,54 @@ export function getRootScoped(): boolean {
|
|
|
763
798
|
return store?.rootScoped ?? true;
|
|
764
799
|
}
|
|
765
800
|
|
|
801
|
+
/**
|
|
802
|
+
* The include scope a segment's dot-local `ctx.reverse(".name")` resolves in.
|
|
803
|
+
*
|
|
804
|
+
* One handler ctx serves every segment of a request and is built from the
|
|
805
|
+
* matched route's name (match-api.ts), so a parent module's layout resolved
|
|
806
|
+
* `.settings` from the matched CHILD module's prefix first: on
|
|
807
|
+
* `/acme/web/issues` the account layout's `.settings` hit the project
|
|
808
|
+
* module's route. Structural entries (layout, cache, parallel, wrapping
|
|
809
|
+
* middleware) and the loaders they declare capture the scope where the DSL
|
|
810
|
+
* defined them. Their handler gets a ctx whose `reverse` carries it
|
|
811
|
+
* (withReverseScope, router/handler-context.ts), and their loaders' DSL
|
|
812
|
+
* kickoff carries it in the loader scope (runInsideLoaderScope) for the
|
|
813
|
+
* loader ctx the executor builds. Global names and mount-param autofill do
|
|
814
|
+
* not change.
|
|
815
|
+
*/
|
|
816
|
+
export interface ReverseScope {
|
|
817
|
+
/** Include name prefix ("account.project"); undefined outside named includes. */
|
|
818
|
+
readonly prefix: string | undefined;
|
|
819
|
+
/** getRootScoped() where the segment was defined: bare-name fallback allowed. */
|
|
820
|
+
readonly rootScoped: boolean;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/** The ReverseScope of the DSL position being evaluated. */
|
|
824
|
+
export function getReverseScope(): ReverseScope {
|
|
825
|
+
return { prefix: getNamePrefix(), rootScoped: getRootScoped() };
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* A createReverseFunction result (router/handler-context.ts). The internal
|
|
830
|
+
* 4th argument resolves dot names in a segment's ReverseScope instead of the
|
|
831
|
+
* route's name.
|
|
832
|
+
*/
|
|
833
|
+
export type ScopableReverse = (
|
|
834
|
+
name: string,
|
|
835
|
+
hrefParams?: Record<string, string>,
|
|
836
|
+
search?: Record<string, unknown>,
|
|
837
|
+
segmentScope?: ReverseScope,
|
|
838
|
+
) => string;
|
|
839
|
+
|
|
840
|
+
/** `reverse` with dot names resolved in `scope`, if any. */
|
|
841
|
+
export function scopeReverse(
|
|
842
|
+
reverse: ScopableReverse,
|
|
843
|
+
scope: ReverseScope | undefined,
|
|
844
|
+
): ScopableReverse {
|
|
845
|
+
if (scope === undefined) return reverse;
|
|
846
|
+
return (name, hrefParams, search) => reverse(name, hrefParams, search, scope);
|
|
847
|
+
}
|
|
848
|
+
|
|
766
849
|
/**
|
|
767
850
|
* Stamp build-time scope identity onto a Static() definition at mount time.
|
|
768
851
|
* The bake collector (prerender-collection.ts) renders defs OUTSIDE any
|
|
@@ -817,6 +900,61 @@ export function getIsolatedLazyParent(
|
|
|
817
900
|
};
|
|
818
901
|
}
|
|
819
902
|
|
|
903
|
+
/*
|
|
904
|
+
* The DSL's document-only decisions. A document (isSSR) evaluation applies
|
|
905
|
+
* them in dsl-helpers.ts; getDocumentModeEntry applies the same ones to a
|
|
906
|
+
* chain built without isSSR. A new ctx.isSSR decision gets a helper here,
|
|
907
|
+
* called from both.
|
|
908
|
+
*/
|
|
909
|
+
|
|
910
|
+
/** loader(Def, { ssr: false }): a document awaits it before the first flush. */
|
|
911
|
+
export function documentModeLoader(entry: LoaderEntry): LoaderEntry {
|
|
912
|
+
return entry.bake ? { ...entry, awaitBeforeFlush: true } : entry;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/** loading(fallback, { ssr: false }): a document renders no fallback. */
|
|
916
|
+
export function applyDocumentModeLoading(entry: EntryPropRender): void {
|
|
917
|
+
if (entry.loadingSsr === false) entry.loading = false;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const documentModeEntries = new WeakMap<EntryData, EntryData>();
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* `entry`, its ancestors, orphan layouts and parallels as a document (isSSR)
|
|
924
|
+
* DSL evaluation builds them, from an evaluation without isSSR.
|
|
925
|
+
*
|
|
926
|
+
* include() captures its parent chain on router.ts routes() or
|
|
927
|
+
* lazy-includes.ts runExpansion, neither of which has isSSR, and loadManifest
|
|
928
|
+
* reuses it for every request. Handed to a document as is, every ancestor
|
|
929
|
+
* outside the include kept its navigation-mode decisions (#1179). Intercepts
|
|
930
|
+
* stay as they are: a document never resolves one.
|
|
931
|
+
*
|
|
932
|
+
* Memoized per entry, so an ancestor several includes share maps to one copy.
|
|
933
|
+
*/
|
|
934
|
+
export function getDocumentModeEntry(entry: EntryData): EntryData {
|
|
935
|
+
const known = documentModeEntries.get(entry);
|
|
936
|
+
if (known) return known;
|
|
937
|
+
const copy = { ...entry };
|
|
938
|
+
documentModeEntries.set(entry, copy);
|
|
939
|
+
documentModeEntries.set(copy, copy);
|
|
940
|
+
copy.loader = entry.loader.map(documentModeLoader);
|
|
941
|
+
applyDocumentModeLoading(copy);
|
|
942
|
+
copy.layout = entry.layout.map(getDocumentModeEntry);
|
|
943
|
+
const parallel: ParallelEntries = {};
|
|
944
|
+
for (const slot of Object.keys(entry.parallel) as `@${string}`[]) {
|
|
945
|
+
const slotEntry = entry.parallel[slot];
|
|
946
|
+
if (slotEntry) {
|
|
947
|
+
parallel[slot] = getDocumentModeEntry(slotEntry) as ParallelEntryData;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
copy.parallel = parallel;
|
|
951
|
+
if (entry.parent) copy.parent = getDocumentModeEntry(entry.parent);
|
|
952
|
+
if (entry.orphanOwner) {
|
|
953
|
+
copy.orphanOwner = getDocumentModeEntry(entry.orphanOwner);
|
|
954
|
+
}
|
|
955
|
+
return copy;
|
|
956
|
+
}
|
|
957
|
+
|
|
820
958
|
export function getParallelEntries(
|
|
821
959
|
parallels: ParallelEntries | EntryData[] | undefined,
|
|
822
960
|
): ParallelEntryData[] {
|
|
@@ -909,6 +1047,8 @@ interface LoaderScope {
|
|
|
909
1047
|
active: true;
|
|
910
1048
|
/** $$id of the DSL loader this scope resolves (runInsideLoaderScope). */
|
|
911
1049
|
loaderId?: string;
|
|
1050
|
+
/** The declaring entry's ReverseScope (LoaderEntry.reverseScope). */
|
|
1051
|
+
reverseScope?: ReverseScope;
|
|
912
1052
|
}
|
|
913
1053
|
const loaderScopeALS: AsyncLocalStorage<LoaderScope> = ((globalThis as any)[
|
|
914
1054
|
LOADER_SCOPE_KEY
|
|
@@ -1472,8 +1612,21 @@ export function assertCachedHeaderWriteAllowed(
|
|
|
1472
1612
|
* are bypassed because loaders are always fresh (never cached) and
|
|
1473
1613
|
* their side effects (setCookie, header, etc.) are safe.
|
|
1474
1614
|
*/
|
|
1475
|
-
export function runInsideLoaderScope<T>(
|
|
1476
|
-
|
|
1615
|
+
export function runInsideLoaderScope<T>(
|
|
1616
|
+
fn: () => T,
|
|
1617
|
+
loaderId?: string,
|
|
1618
|
+
reverseScope?: ReverseScope,
|
|
1619
|
+
): T {
|
|
1620
|
+
return loaderScopeALS.run({ active: true, loaderId, reverseScope }, fn);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* The ReverseScope of the DSL loader kickoff around this call: a loader the
|
|
1625
|
+
* kickoff starts, and one its body starts with ctx.use(), resolve dot names
|
|
1626
|
+
* in it. A handler's ctx.use(Loader) runs outside any kickoff: none.
|
|
1627
|
+
*/
|
|
1628
|
+
export function getLoaderReverseScope(): ReverseScope | undefined {
|
|
1629
|
+
return loaderScopeALS.getStore()?.reverseScope;
|
|
1477
1630
|
}
|
|
1478
1631
|
|
|
1479
1632
|
/**
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isInsideLoaderScope,
|
|
5
5
|
} from "./context.js";
|
|
6
6
|
import { holdsThenable } from "../router/segment-resolution/mask-nested.js";
|
|
7
|
+
import { isThenable } from "../handles/is-thenable.js";
|
|
7
8
|
import { pushedByKey } from "../handles/pushed-by.js";
|
|
8
9
|
import { defineSlot } from "./slot-accessors.js";
|
|
9
10
|
|
|
@@ -47,8 +48,9 @@ function createLateHandlePushError(
|
|
|
47
48
|
): Error {
|
|
48
49
|
const error = new Error(
|
|
49
50
|
`Handle "${handleName}" for segment "${segmentId}" was pushed after handle collection completed. ` +
|
|
50
|
-
`This usually means an async
|
|
51
|
-
`Push
|
|
51
|
+
`This usually means an async component pushed it after an await. ` +
|
|
52
|
+
`Push from the route or layout handler, or reserve the slot there with ` +
|
|
53
|
+
`ctx.use(Handle).defer() and resolve it from the component.`,
|
|
52
54
|
);
|
|
53
55
|
error.name = "LateHandlePushError";
|
|
54
56
|
return error;
|
|
@@ -253,12 +255,14 @@ export interface HandleStore {
|
|
|
253
255
|
): void;
|
|
254
256
|
|
|
255
257
|
/**
|
|
256
|
-
* push() a record's copy of `loaderId`'s
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
258
|
+
* push() a record's copy of `loaderId`'s push that stands (restoreHandles):
|
|
259
|
+
* the record supplies the loader's value too. From then on this request
|
|
260
|
+
* drops every thenable-free push the loader makes (live, or replayed from
|
|
261
|
+
* a cache entry, anywhere inside its body), and keeps the ones holding a
|
|
262
|
+
* thenable (holdsThenable: what the record could not keep), placed among
|
|
263
|
+
* the restored copies in the loader's push order. A top-level promise is
|
|
264
|
+
* judged by its settled value, as the capture judged it (#1182): dropped
|
|
265
|
+
* when it settles thenable-free or rejects, else kept once it settles.
|
|
262
266
|
*/
|
|
263
267
|
pushRestored(
|
|
264
268
|
handleName: string,
|
|
@@ -277,6 +281,16 @@ export interface HandleStore {
|
|
|
277
281
|
*/
|
|
278
282
|
setRecordAuthority(authority: (loaderId: string) => RecordAuthority): void;
|
|
279
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Capture only (shell-capture.ts deriveShellCaptureContext): the slot
|
|
286
|
+
* holding `value` becomes a DSL-loader push without an owner, so the
|
|
287
|
+
* record and the prelude leave it out (getDataForSegment's
|
|
288
|
+
* `excludeLoaderPushes`). The capture owns a bake-lane loader's promise
|
|
289
|
+
* when it is pushed, and calls this once the promise settled to a value
|
|
290
|
+
* that holds one (#1182).
|
|
291
|
+
*/
|
|
292
|
+
unrecordPush(handleName: string, segmentId: string, value: unknown): void;
|
|
293
|
+
|
|
280
294
|
/**
|
|
281
295
|
* `loaderId`'s run in this request settled (resolved or rejected), its
|
|
282
296
|
* pushes made (loader-resolution.ts createLoaderExecutor). Its placeholders
|
|
@@ -665,6 +679,39 @@ export function createHandleStore(): HandleStore {
|
|
|
665
679
|
return after;
|
|
666
680
|
}
|
|
667
681
|
|
|
682
|
+
// A restored loader's top-level promise push, judged by its settled value
|
|
683
|
+
// as push(await value) would be: thenable-free, the record's copy stands
|
|
684
|
+
// (the capture recorded that value, #1182); holding a promise, it is kept
|
|
685
|
+
// where the loader's pushes go, once it settles; rejected, it is dropped.
|
|
686
|
+
// Tracked on the auxiliary lane, so the late channel stays open for it.
|
|
687
|
+
function judgeRestoredPromise(
|
|
688
|
+
store: HandleStore,
|
|
689
|
+
handleName: string,
|
|
690
|
+
segmentId: string,
|
|
691
|
+
value: unknown,
|
|
692
|
+
loaderId: string,
|
|
693
|
+
tag: SlotTag,
|
|
694
|
+
): void {
|
|
695
|
+
const judged = Promise.resolve(value).then(
|
|
696
|
+
(settled) => {
|
|
697
|
+
if (!holdsThenable(settled)) {
|
|
698
|
+
const existing = data[handleName]?.[segmentId];
|
|
699
|
+
if (existing) matchRestoredSlot(existing, loaderId);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const values = ((data[handleName] ??= {})[segmentId] ??= []);
|
|
703
|
+
insertSlot(values, restoredInsertIndex(values, loaderId), value, tag);
|
|
704
|
+
if (tag.replayOf !== undefined) {
|
|
705
|
+
addPending(pendingReplays, tag.replayOf, values);
|
|
706
|
+
}
|
|
707
|
+
version++;
|
|
708
|
+
signalEmission();
|
|
709
|
+
},
|
|
710
|
+
() => {},
|
|
711
|
+
);
|
|
712
|
+
store.trackAuxiliary(judged);
|
|
713
|
+
}
|
|
714
|
+
|
|
668
715
|
function replayPush(
|
|
669
716
|
store: HandleStore,
|
|
670
717
|
replay: NonNullable<typeof replaying>,
|
|
@@ -859,10 +906,22 @@ export function createHandleStore(): HandleStore {
|
|
|
859
906
|
restoredBy = restoringLoader(replayOf);
|
|
860
907
|
}
|
|
861
908
|
}
|
|
862
|
-
if (restoredBy !== undefined
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
909
|
+
if (restoredBy !== undefined) {
|
|
910
|
+
if (isThenable(value)) {
|
|
911
|
+
judgeRestoredPromise(this, handleName, segmentId, value, restoredBy, {
|
|
912
|
+
loader: (loaderPush ?? isInsideLoaderScope()) || undefined,
|
|
913
|
+
replayOf,
|
|
914
|
+
liveOf: replayOf === undefined ? restoredBy : undefined,
|
|
915
|
+
owner,
|
|
916
|
+
by: getHandlePushOwner() ?? replayOf ?? owner,
|
|
917
|
+
});
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (!holdsThenable(value)) {
|
|
921
|
+
const existing = data[handleName]?.[segmentId];
|
|
922
|
+
if (existing) matchRestoredSlot(existing, restoredBy);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
866
925
|
}
|
|
867
926
|
|
|
868
927
|
if (!data[handleName]) {
|
|
@@ -976,6 +1035,15 @@ export function createHandleStore(): HandleStore {
|
|
|
976
1035
|
authority = of;
|
|
977
1036
|
},
|
|
978
1037
|
|
|
1038
|
+
unrecordPush(handleName: string, segmentId: string, value: unknown): void {
|
|
1039
|
+
const values = data[handleName]?.[segmentId];
|
|
1040
|
+
if (!values) return;
|
|
1041
|
+
const i = values.indexOf(value);
|
|
1042
|
+
if (i < 0) return;
|
|
1043
|
+
const tags = tagsFor(values);
|
|
1044
|
+
tags[i] = { ...tags[i], loader: true, owner: undefined };
|
|
1045
|
+
},
|
|
1046
|
+
|
|
979
1047
|
settleLoaderRun(loaderId: string): void {
|
|
980
1048
|
if (completed) return;
|
|
981
1049
|
const hole = isHole(loaderId);
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
serializeStateCookie,
|
|
24
24
|
} from "../browser/cookie-name.js";
|
|
25
25
|
import type { LoaderDefinition, LoaderContext } from "../types.js";
|
|
26
|
-
import type {
|
|
26
|
+
import type { AutofillAwareReverseFunction } from "../types/handler-context.js";
|
|
27
27
|
import type {
|
|
28
28
|
DefaultEnv,
|
|
29
29
|
DefaultReverseRouteMap,
|
|
@@ -65,6 +65,8 @@ import {
|
|
|
65
65
|
} from "../theme/constants.js";
|
|
66
66
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
67
67
|
import type { RoutineTrace } from "../rsc/routine-plan.js";
|
|
68
|
+
import type { ActionContinuation } from "../rsc/server-action.js";
|
|
69
|
+
import type { FormActionOutcome } from "../rsc/progressive-enhancement.js";
|
|
68
70
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
69
71
|
import {
|
|
70
72
|
assertCachedHeaderWriteAllowed,
|
|
@@ -345,6 +347,13 @@ export interface RequestContext<
|
|
|
345
347
|
*/
|
|
346
348
|
_shellCaptureRun?: boolean;
|
|
347
349
|
|
|
350
|
+
/**
|
|
351
|
+
* @internal Set beside `_shellCaptureRun` when the shell is keyed by the
|
|
352
|
+
* visitor's URL (`rewrite(...).shell("visitor")`): the capture renders the
|
|
353
|
+
* visitor's location and `ctx.rewrittenFrom`; the theme stays the default.
|
|
354
|
+
*/
|
|
355
|
+
_shellCaptureForVisitor?: boolean;
|
|
356
|
+
|
|
348
357
|
/**
|
|
349
358
|
* @internal Bake-lane loader containers collected DURING a shell capture:
|
|
350
359
|
* segment-key -> the loader's (pre-wrap) result promise. Populated by
|
|
@@ -359,9 +368,10 @@ export interface RequestContext<
|
|
|
359
368
|
|
|
360
369
|
/**
|
|
361
370
|
* @internal Set on a capture's derived context when a loader pushed a
|
|
362
|
-
* value the record cannot keep (
|
|
363
|
-
*
|
|
364
|
-
* `runs: 1`, so a HIT still
|
|
371
|
+
* value the record cannot keep (masked nested promises, a pushed promise
|
|
372
|
+
* that settled to one, a live-lane loader's push, or a push outside a
|
|
373
|
+
* loader body): the loader records then carry `runs: 1`, so a HIT still
|
|
374
|
+
* runs their bodies for those pushes.
|
|
365
375
|
*/
|
|
366
376
|
_shellCaptureUnrecordedLoaderPush?: true;
|
|
367
377
|
|
|
@@ -774,8 +784,15 @@ export interface RequestContext<
|
|
|
774
784
|
* Generate URLs from route names.
|
|
775
785
|
* Uses the global route map. After route matching, scoped (`.name`) resolution
|
|
776
786
|
* works within the matched include() scope.
|
|
777
|
-
|
|
778
|
-
|
|
787
|
+
*
|
|
788
|
+
* Params are optional in the type because the fill depends on the phase.
|
|
789
|
+
* After the route match (route middleware, handlers, loaders, server actions)
|
|
790
|
+
* missing params come from the matched route's params; explicit params win.
|
|
791
|
+
* In global middleware before `next()` nothing is matched and the fill is
|
|
792
|
+
* empty, so a route with a required param throws `Missing param "<name>" for
|
|
793
|
+
* route "<route>"` at runtime (a param-less route still resolves).
|
|
794
|
+
*/
|
|
795
|
+
reverse: AutofillAwareReverseFunction<
|
|
779
796
|
Record<string, string>,
|
|
780
797
|
DefaultReverseRouteMap
|
|
781
798
|
>;
|
|
@@ -786,6 +803,13 @@ export interface RequestContext<
|
|
|
786
803
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
787
804
|
_prevRouteKey?: string;
|
|
788
805
|
|
|
806
|
+
/**
|
|
807
|
+
* @internal Leaving a rewritten page, the visitor's URL moved
|
|
808
|
+
* (router/match-api.ts): the default revalidation counts it as a search
|
|
809
|
+
* change, since the render may read `ctx.rewrittenFrom`.
|
|
810
|
+
*/
|
|
811
|
+
_visitorMoved?: boolean;
|
|
812
|
+
|
|
789
813
|
/**
|
|
790
814
|
* @internal True while the post-action revalidation render is running (set by
|
|
791
815
|
* revalidateAfterAction). The "use cache" runtime reads this to prefer
|
|
@@ -796,6 +820,19 @@ export interface RequestContext<
|
|
|
796
820
|
*/
|
|
797
821
|
_inActionRevalidation?: boolean;
|
|
798
822
|
|
|
823
|
+
/**
|
|
824
|
+
* @internal The outcome of the action this request ran (a JS server
|
|
825
|
+
* action's continuation, a no-JS form post's form state), set before route
|
|
826
|
+
* middleware wraps the re-render. While it is unset, applyRewrite
|
|
827
|
+
* (rsc/rewrite-passes.ts) moves a mutation with its body, so the action
|
|
828
|
+
* runs on the target's pass, or refuses it (`disallowMutations()`, or a
|
|
829
|
+
* body middleware read); once set, the request moves to the target, whose
|
|
830
|
+
* pass re-renders from it instead of running the action again (#1145).
|
|
831
|
+
*/
|
|
832
|
+
_actionRan?:
|
|
833
|
+
| { kind: "action"; continuation: ActionContinuation }
|
|
834
|
+
| { kind: "form"; outcome: FormActionOutcome };
|
|
835
|
+
|
|
799
836
|
/**
|
|
800
837
|
* @internal Render barrier for experimental `rendered()` API.
|
|
801
838
|
* Resolves when all non-loader segments have settled and handle data
|
|
@@ -1017,6 +1054,7 @@ export type PublicRequestContext<
|
|
|
1017
1054
|
| "_onDemandProducer"
|
|
1018
1055
|
| "_searchParamsFilter"
|
|
1019
1056
|
| "_shellCaptureRun"
|
|
1057
|
+
| "_shellCaptureForVisitor"
|
|
1020
1058
|
| "_shellImplicitCache"
|
|
1021
1059
|
| "_shellLoaderSeed"
|
|
1022
1060
|
| "_shellCaptureLoaderRecords"
|
|
@@ -1048,7 +1086,9 @@ export type PublicRequestContext<
|
|
|
1048
1086
|
| "_locationState"
|
|
1049
1087
|
| "_routeName"
|
|
1050
1088
|
| "_prevRouteKey"
|
|
1089
|
+
| "_visitorMoved"
|
|
1051
1090
|
| "_inActionRevalidation"
|
|
1091
|
+
| "_actionRan"
|
|
1052
1092
|
| "_reportedErrors"
|
|
1053
1093
|
| "_renderErrors"
|
|
1054
1094
|
| "_recoveredHandlerErrors"
|
|
@@ -45,6 +45,38 @@ export function requestHeaders(request: Request): Headers {
|
|
|
45
45
|
return RAW_HEADERS.get(request) ?? request.headers;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* A copy of `request`'s headers for a bodyless mirror of it (the PE
|
|
50
|
+
* re-render, route middleware around it, a rewrite pass after an action),
|
|
51
|
+
* without the body-framing ones and `content-type`, so a body check gated on
|
|
52
|
+
* the content-type passes over a request that has no body.
|
|
53
|
+
*/
|
|
54
|
+
export function bodylessHeaders(request: Request): Headers {
|
|
55
|
+
const headers = new Headers(requestHeaders(request));
|
|
56
|
+
headers.delete("content-length");
|
|
57
|
+
headers.delete("content-encoding");
|
|
58
|
+
headers.delete("transfer-encoding");
|
|
59
|
+
headers.delete("content-type");
|
|
60
|
+
return headers;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A bodyless mirror of `request` at `url`: `method`, its
|
|
65
|
+
* {@link bodylessHeaders}, the signal and workerd's `cf`.
|
|
66
|
+
*/
|
|
67
|
+
export function bodylessRequest(
|
|
68
|
+
url: string | URL,
|
|
69
|
+
request: Request,
|
|
70
|
+
method: string,
|
|
71
|
+
): Request {
|
|
72
|
+
return new Request(url, {
|
|
73
|
+
method,
|
|
74
|
+
headers: bodylessHeaders(request),
|
|
75
|
+
signal: request.signal,
|
|
76
|
+
cf: (request as { cf?: unknown }).cf,
|
|
77
|
+
} as RequestInit);
|
|
78
|
+
}
|
|
79
|
+
|
|
48
80
|
/**
|
|
49
81
|
* Shadow `request.headers` with an own, non-enumerable getter that calls
|
|
50
82
|
* `beforeRead` and returns the request's own Headers (stable per request, so
|
|
@@ -11,8 +11,15 @@
|
|
|
11
11
|
import { contextGet, contextSet, createVar } from "../context-var.js";
|
|
12
12
|
import { RewriteLoopError } from "../errors.js";
|
|
13
13
|
import { documentCacheStoresRender } from "../cache/document-cache-policy.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
guardIdentityRead,
|
|
16
|
+
recordLoaderIdentityRead,
|
|
17
|
+
refuseInCacheScope,
|
|
18
|
+
type IdentityReadWording,
|
|
19
|
+
} from "./context.js";
|
|
20
|
+
import { isInsideIdentityExempt } from "../cache/cache-exec-scope.js";
|
|
15
21
|
import { stripInternalParams } from "../router/internal-params.js";
|
|
22
|
+
import type { RewriteShell } from "../route-definition/rewrite.js";
|
|
16
23
|
import type { RequestContext } from "./request-context.js";
|
|
17
24
|
|
|
18
25
|
/** Most rewrites one request may take; the next one is a RewriteLoopError. */
|
|
@@ -23,7 +30,8 @@ const REWRITE_TRAIL = createVar<readonly string[]>();
|
|
|
23
30
|
|
|
24
31
|
type TrailContext = Pick<RequestContext<any>, "_variables">;
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
/** A URL's path and search: what a rewrite hop, a target and a visitor's move compare. */
|
|
34
|
+
export function pathKey(url: URL): string {
|
|
27
35
|
return url.pathname + url.search;
|
|
28
36
|
}
|
|
29
37
|
|
|
@@ -60,6 +68,25 @@ export function recordRewriteHop(
|
|
|
60
68
|
return hops;
|
|
61
69
|
}
|
|
62
70
|
|
|
71
|
+
/** The `.shell()` choice of the hop that reached the route that renders. */
|
|
72
|
+
const REWRITE_SHELL = createVar<RewriteShell | undefined>();
|
|
73
|
+
|
|
74
|
+
export function recordRewriteShell(
|
|
75
|
+
reqCtx: TrailContext,
|
|
76
|
+
audience: RewriteShell | undefined,
|
|
77
|
+
): void {
|
|
78
|
+
contextSet(reqCtx._variables, REWRITE_SHELL, audience);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The last hop's `rewrite(...).shell()` choice; undefined: none. */
|
|
82
|
+
export function rewriteShellAudience(
|
|
83
|
+
reqCtx: TrailContext | undefined,
|
|
84
|
+
): RewriteShell | undefined {
|
|
85
|
+
return reqCtx?._variables
|
|
86
|
+
? (contextGet(reqCtx._variables, REWRITE_SHELL) as RewriteShell | undefined)
|
|
87
|
+
: undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
63
90
|
type AudienceContext = Pick<
|
|
64
91
|
RequestContext<any>,
|
|
65
92
|
"_shellCaptureRun" | "_documentCacheRender" | "res"
|
|
@@ -68,7 +95,8 @@ type AudienceContext = Pick<
|
|
|
68
95
|
/**
|
|
69
96
|
* A render other visitors may be served: a PPR shell capture, or a render
|
|
70
97
|
* the document cache stores (its response opted in before next()). It shows
|
|
71
|
-
*
|
|
98
|
+
* no visitor theme, and no visitor URL unless it is a visitor-keyed shell
|
|
99
|
+
* capture (`_shellCaptureForVisitor`).
|
|
72
100
|
*/
|
|
73
101
|
export function rendersForSharedAudience(reqCtx: AudienceContext): boolean {
|
|
74
102
|
return reqCtx._shellCaptureRun === true || documentCacheStoresRender(reqCtx);
|
|
@@ -112,9 +140,9 @@ export type RewrittenFromReader = "render" | "middleware";
|
|
|
112
140
|
* The read behind every `ctx.rewrittenFrom`. Guarded like cookies(): inside
|
|
113
141
|
* a `cache()` boundary or a `"use cache"` function it throws, and a loader's
|
|
114
142
|
* own `cache()` fill records it. A render other visitors may be served sees
|
|
115
|
-
* `undefined`, as on a direct visit
|
|
116
|
-
* URL
|
|
117
|
-
* it.
|
|
143
|
+
* `undefined`, as on a direct visit, except a visitor-keyed shell capture,
|
|
144
|
+
* whose key covers the URL; any other render that sees the visitor's URL
|
|
145
|
+
* marks `_payloadVisitorLocation`, so the document cache does not store it.
|
|
118
146
|
*/
|
|
119
147
|
function readRewrittenFrom(
|
|
120
148
|
reqCtx: RequestContext<any> | undefined,
|
|
@@ -122,7 +150,15 @@ function readRewrittenFrom(
|
|
|
122
150
|
): string | undefined {
|
|
123
151
|
if (!reqCtx) return undefined;
|
|
124
152
|
if (reader === "render" && rendersForSharedAudience(reqCtx)) {
|
|
125
|
-
return undefined;
|
|
153
|
+
if (!reqCtx._shellCaptureForVisitor) return undefined;
|
|
154
|
+
// guardIdentityRead minus the capture guard: a cache() boundary, a "use
|
|
155
|
+
// cache" body or a loader's own cache() inside the capture is keyed
|
|
156
|
+
// without the URL.
|
|
157
|
+
if (!isInsideIdentityExempt()) {
|
|
158
|
+
refuseInCacheScope("ctx.rewrittenFrom", REWRITTEN_FROM_READ, reqCtx);
|
|
159
|
+
recordLoaderIdentityRead("ctx.rewrittenFrom", REWRITTEN_FROM_READ.verb);
|
|
160
|
+
}
|
|
161
|
+
return rewriteTrail(reqCtx)?.[0];
|
|
126
162
|
}
|
|
127
163
|
guardIdentityRead(reqCtx, "ctx.rewrittenFrom", REWRITTEN_FROM_READ);
|
|
128
164
|
const href = rewriteTrail(reqCtx)?.[0];
|
|
@@ -145,15 +181,25 @@ function copyFor(holder: object, href: string | undefined): URL | undefined {
|
|
|
145
181
|
return copy;
|
|
146
182
|
}
|
|
147
183
|
|
|
184
|
+
/**
|
|
185
|
+
* The ctx defineRewrittenFrom ran on: `holder`, or the one it derives from
|
|
186
|
+
* (a layout's ctx is an Object.create child of the route's, withReverseScope).
|
|
187
|
+
*/
|
|
188
|
+
function definedOn(holder: object): object {
|
|
189
|
+
while (!SOURCES.has(holder)) holder = Object.getPrototypeOf(holder);
|
|
190
|
+
return holder;
|
|
191
|
+
}
|
|
192
|
+
|
|
148
193
|
/**
|
|
149
194
|
* Non-enumerable, like `ctx.theme`: serializing a ctx (React's dev debug
|
|
150
195
|
* info) must not count as a read.
|
|
151
196
|
*/
|
|
152
197
|
const SCOPED_DESCRIPTOR: PropertyDescriptor = {
|
|
153
198
|
get(this: object): URL | undefined {
|
|
199
|
+
const owner = definedOn(this);
|
|
154
200
|
const href = readRewrittenFrom(
|
|
155
|
-
SOURCES.get(
|
|
156
|
-
READERS.get(
|
|
201
|
+
SOURCES.get(owner),
|
|
202
|
+
READERS.get(owner) ?? "render",
|
|
157
203
|
);
|
|
158
204
|
return copyFor(this, href);
|
|
159
205
|
},
|