@rangojs/router 0.24.4 → 0.25.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-shell-frame.d.ts +2 -0
- package/dist/types/cache/types.d.ts +8 -0
- 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/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/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 -6
- package/dist/types/router/router-options.d.ts +4 -7
- 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/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 +10 -1
- package/dist/types/rsc/shell-capture.d.ts +7 -0
- package/dist/types/rsc/types.d.ts +5 -0
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/context.d.ts +64 -4
- package/dist/types/server/request-context.d.ts +41 -4
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- 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/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 +41 -4
- 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 +4 -3
- 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 +6816 -6547
- 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/caching/SKILL.md +3 -1
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +185 -32
- 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/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 +32 -10
- 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 +43 -8
- package/skills/prerender/SKILL.md +1 -2
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- 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/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +85 -21
- package/skills/ux-architecture/SKILL.md +980 -0
- 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 +2 -0
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/types.ts +8 -0
- package/src/cache/vercel/vercel-cache-store.ts +6 -1
- 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/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/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +27 -7
- 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 +6 -8
- 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 -7
- package/src/router/router-options.ts +4 -7
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- 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.ts +2 -9
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +83 -41
- 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 +22 -0
- package/src/rsc/shell-capture.ts +11 -0
- package/src/rsc/types.ts +5 -0
- package/src/segment-system.tsx +125 -119
- package/src/server/context.ts +96 -5
- package/src/server/handle-store.ts +3 -2
- package/src/server/request-context.ts +42 -3
- 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/dispatch.ts +4 -5
- 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/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 +77 -4
- 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 +4 -3
- 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/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
|
@@ -64,12 +64,15 @@ import {
|
|
|
64
64
|
} from "./segment-resolution/prefetch-deferral.js";
|
|
65
65
|
import {
|
|
66
66
|
assertNonCacheableReadAllowed,
|
|
67
|
+
getLoaderReverseScope,
|
|
67
68
|
isInsideLoaderScope,
|
|
69
|
+
scopeReverse,
|
|
68
70
|
runInsideLoaderBodyScope,
|
|
69
71
|
runInsideLoaderRun,
|
|
70
72
|
isInsidePushCallbackScope,
|
|
71
73
|
runInsidePushCallbackScope,
|
|
72
74
|
type LoaderRunIdentity,
|
|
75
|
+
type ScopableReverse,
|
|
73
76
|
} from "../server/context.js";
|
|
74
77
|
import { debugLog } from "./logging.js";
|
|
75
78
|
|
|
@@ -179,6 +182,8 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
179
182
|
// The token is the loader's $$id (hash#export in prod, pathfrag#export in dev),
|
|
180
183
|
// not a clean display name.
|
|
181
184
|
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
185
|
+
// Captured now: the rejection can settle outside the request's ALS frame.
|
|
186
|
+
const renderErrors = _getRequestContext()?._renderErrors;
|
|
182
187
|
|
|
183
188
|
return Promise.resolve(promise)
|
|
184
189
|
.then(
|
|
@@ -275,6 +280,11 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
275
280
|
};
|
|
276
281
|
}
|
|
277
282
|
|
|
283
|
+
// The error renders in place inside a 200 as a value, which neither
|
|
284
|
+
// Flight nor Fizz reports: record it like their errors, so a writer
|
|
285
|
+
// that reads _renderErrors (the document cache) does not store it.
|
|
286
|
+
renderErrors?.push(error);
|
|
287
|
+
|
|
278
288
|
// Find nearest error boundary
|
|
279
289
|
const fallback = findNearestErrorBoundary(entry);
|
|
280
290
|
|
|
@@ -543,6 +553,11 @@ function createLoaderExecutor<TEnv>(
|
|
|
543
553
|
// (runInsideLoaderScope in fresh.ts). Handler-invoked loaders are NOT
|
|
544
554
|
// inside loader scope. This determines whether rendered() is allowed.
|
|
545
555
|
const isDslLoader = isInsideLoaderScope();
|
|
556
|
+
// The body resolves ctx.reverse(".name") in its DSL declaration's module
|
|
557
|
+
// (a layout's / parallel's / cache()'s loader). A route's loader, and one
|
|
558
|
+
// a handler starts with ctx.use(), resolve from the route. One run per
|
|
559
|
+
// request: the first declaration to start it wins.
|
|
560
|
+
const reverseScope = getLoaderReverseScope();
|
|
546
561
|
|
|
547
562
|
// Owning segment for ctx.handle() attribution, captured SYNCHRONOUSLY at
|
|
548
563
|
// kickoff: segment resolution sets _currentSegmentId while resolving the
|
|
@@ -568,6 +583,7 @@ function createLoaderExecutor<TEnv>(
|
|
|
568
583
|
InternalHandlerContext<any, TEnv>,
|
|
569
584
|
| "_routeName"
|
|
570
585
|
| "_responseType"
|
|
586
|
+
| "_reverseScope"
|
|
571
587
|
| "_currentSegmentId"
|
|
572
588
|
| "_claimLoaderPushes"
|
|
573
589
|
| "_runLoaderIsolated"
|
|
@@ -648,9 +664,13 @@ function createLoaderExecutor<TEnv>(
|
|
|
648
664
|
}) as LoaderContext["use"],
|
|
649
665
|
method: "GET",
|
|
650
666
|
body: undefined,
|
|
651
|
-
reverse:
|
|
667
|
+
reverse: scopeReverse(
|
|
668
|
+
ctx.reverse as ScopableReverse,
|
|
669
|
+
reverseScope,
|
|
670
|
+
) as LoaderContext["reverse"],
|
|
652
671
|
_routeName: internal._routeName,
|
|
653
672
|
_responseType: internal._responseType,
|
|
673
|
+
_reverseScope: reverseScope,
|
|
654
674
|
_currentSegmentId: owningSegmentId,
|
|
655
675
|
_claimLoaderPushes: internal._claimLoaderPushes,
|
|
656
676
|
_runLoaderIsolated: internal._runLoaderIsolated,
|
package/src/router/manifest.ts
CHANGED
|
@@ -11,10 +11,8 @@ import MapRootLayout from "../server/root-layout";
|
|
|
11
11
|
import { joinPrefix } from "./pattern-matching.js";
|
|
12
12
|
import type { RouteEntry } from "../types";
|
|
13
13
|
import type { UrlPatterns } from "../urls";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
resolveIncludeModule,
|
|
17
|
-
} from "../urls/include-provider.js";
|
|
14
|
+
import { isIncludeProvider } from "../urls/include-provider.js";
|
|
15
|
+
import { resolveEntryPatterns } from "./lazy-includes.js";
|
|
18
16
|
import { VERSION } from "@rangojs/router:version";
|
|
19
17
|
|
|
20
18
|
// Tags an error thrown while dynamically importing an async include's module
|
|
@@ -167,6 +165,9 @@ export async function loadManifest(
|
|
|
167
165
|
// undefined) so a prior lazy build's scope cannot leak into a later
|
|
168
166
|
// non-lazy build on the same ALS-backed Store.
|
|
169
167
|
Store.includeScope = lazyContext?.includeScope;
|
|
168
|
+
// Same rule for the include's tree position (hidden include names).
|
|
169
|
+
Store.scopePath = lazyContext?.scopePath;
|
|
170
|
+
Store.hiddenScopePath = lazyContext?.hiddenScopePath;
|
|
170
171
|
|
|
171
172
|
const handlerExecStart = performance.now();
|
|
172
173
|
const useItems = await getContext().runWithStore(
|
|
@@ -190,10 +191,7 @@ export async function loadManifest(
|
|
|
190
191
|
// cache the resolved patterns on the entry so later renders reuse them.
|
|
191
192
|
if (isIncludeProvider(entry.lazyPatterns)) {
|
|
192
193
|
try {
|
|
193
|
-
|
|
194
|
-
await entry.lazyPatterns(),
|
|
195
|
-
entry.staticPrefix,
|
|
196
|
-
) as unknown as UrlPatterns<any>;
|
|
194
|
+
await resolveEntryPatterns(entry);
|
|
197
195
|
} catch (err) {
|
|
198
196
|
// A failed dynamic import of a REAL, matched route's module is a
|
|
199
197
|
// server error, not a missing route. Tag it so the outer catch
|
package/src/router/match-api.ts
CHANGED
|
@@ -54,10 +54,12 @@ import {
|
|
|
54
54
|
type RouteSnapshot,
|
|
55
55
|
} from "./route-snapshot.js";
|
|
56
56
|
import { resolveNavigation } from "./navigation-snapshot.js";
|
|
57
|
+
import { pathKey, visitorUrl } from "../server/rewrite-trail.js";
|
|
57
58
|
import { withLayoutLoaderCopies } from "../client-urls/layout-loader-copies.js";
|
|
58
59
|
import {
|
|
59
60
|
partialDeferralMode,
|
|
60
61
|
planPrefetchDeferral,
|
|
62
|
+
resolveInterceptDeferralScope,
|
|
61
63
|
requestKind,
|
|
62
64
|
} from "./segment-resolution/prefetch-deferral.js";
|
|
63
65
|
|
|
@@ -375,6 +377,12 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
375
377
|
}
|
|
376
378
|
|
|
377
379
|
setRequestContextPrevRouteKey(nav.effectiveFromMatch?.routeKey);
|
|
380
|
+
// Two visitor URLs can share a rewrite target, and the render may read
|
|
381
|
+
// ctx.rewrittenFrom: a move of the visitor's URL counts as a change.
|
|
382
|
+
if (reqCtx && nav.prevVisitorUrl) {
|
|
383
|
+
reqCtx._visitorMoved =
|
|
384
|
+
pathKey(visitorUrl(reqCtx, cleanUrl)) !== pathKey(nav.prevVisitorUrl);
|
|
385
|
+
}
|
|
378
386
|
|
|
379
387
|
const interceptSelectorContext: InterceptSelectorContext = {
|
|
380
388
|
// The source's params come from matching its URL (the intercept source
|
|
@@ -484,7 +492,6 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
484
492
|
prefetch: kind === "prefetch",
|
|
485
493
|
method: request.method,
|
|
486
494
|
isAction,
|
|
487
|
-
isIntercept,
|
|
488
495
|
isShellCapture: reqCtx?._shellCaptureRun === true,
|
|
489
496
|
}),
|
|
490
497
|
matched,
|
|
@@ -492,6 +499,9 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
492
499
|
// What the page holds, not the copy forced above: see
|
|
493
500
|
// prefetch-deferral.ts.
|
|
494
501
|
held,
|
|
502
|
+
interceptResult
|
|
503
|
+
? resolveInterceptDeferralScope(interceptResult.intercept)
|
|
504
|
+
: undefined,
|
|
495
505
|
);
|
|
496
506
|
(handlerContext as InternalHandlerContext<any, TEnv>)._prefetchDeferral =
|
|
497
507
|
deferral;
|
|
@@ -230,10 +230,8 @@ export async function rerenderAndCacheRoute<TEnv>(
|
|
|
230
230
|
handlerContext,
|
|
231
231
|
true, // belongsToRoute
|
|
232
232
|
undefined, // no revalidationContext: render fresh
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
// short-circuit Response would abort the write.
|
|
236
|
-
{ skipMiddleware: true },
|
|
233
|
+
// A handler error rejects, so the failure is never cached.
|
|
234
|
+
{ background: true },
|
|
237
235
|
),
|
|
238
236
|
)),
|
|
239
237
|
);
|
|
@@ -101,6 +101,7 @@ import { treeHasStreaming } from "./segment-resolution.js";
|
|
|
101
101
|
import { loaderPins } from "../segment-resolution/loader-cache.js";
|
|
102
102
|
import {
|
|
103
103
|
fillSends,
|
|
104
|
+
interceptFillSkips,
|
|
104
105
|
recordUse,
|
|
105
106
|
} from "../segment-resolution/prefetch-deferral.js";
|
|
106
107
|
import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
|
|
@@ -127,6 +128,7 @@ import { ShellRecordUnavailableError } from "../../cache/shell-snapshot.js";
|
|
|
127
128
|
import { prerenderStoreShortCircuits } from "../navigation-snapshot.js";
|
|
128
129
|
import { paramsEqual } from "../params-util.js";
|
|
129
130
|
import { requestHeaders } from "../../server/request-headers.js";
|
|
131
|
+
import { isInterceptSegment } from "../../browser/intercept-utils.js";
|
|
130
132
|
|
|
131
133
|
// Lazily initialized prerender store singleton and dynamically imported deps.
|
|
132
134
|
// Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
|
|
@@ -207,6 +209,7 @@ async function* resolveFreshLoadersAndYield<TEnv>(
|
|
|
207
209
|
resolveLoadersOnlyWithRevalidation: RouterContext<TEnv>["resolveLoadersOnlyWithRevalidation"],
|
|
208
210
|
): AsyncGenerator<ResolvedSegment> {
|
|
209
211
|
const loaderStart = performance.now();
|
|
212
|
+
entries = entries.filter((e) => !interceptFillSkips(ctx, e.shortCode));
|
|
210
213
|
|
|
211
214
|
if (ctx.isFullMatch) {
|
|
212
215
|
if (resolveLoadersOnly) {
|
|
@@ -347,14 +350,8 @@ async function* yieldFromStore<TEnv>(
|
|
|
347
350
|
!ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
|
|
348
351
|
const resendRouteSegments = replaceable === true && ctx.isAction;
|
|
349
352
|
for (const segment of segments) {
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
ctx.clientSegmentSet.has(segment.id) &&
|
|
353
|
-
// A stored intercept segment is re-sent, as on the runtime cache path:
|
|
354
|
-
// its loaders run (handleCacheHitIntercept), and a kept (null) one would
|
|
355
|
-
// be left out of the response with its loaders' new data.
|
|
356
|
-
!segment.namespace?.startsWith("intercept:")
|
|
357
|
-
) {
|
|
353
|
+
if (interceptFillSkips(ctx, segment)) continue;
|
|
354
|
+
if (!ctx.isFullMatch && ctx.clientSegmentSet.has(segment.id)) {
|
|
358
355
|
// A fill keeps it (fillSends); otherwise it is sent again when the
|
|
359
356
|
// params changed or an action re-sends the route's own segments.
|
|
360
357
|
const sends =
|
|
@@ -912,10 +909,15 @@ export function withCacheLookup<TEnv>(
|
|
|
912
909
|
const entryRevalidateMap = canCheckSegmentRevalidation
|
|
913
910
|
? buildEntryRevalidateMap(ctx.entries)
|
|
914
911
|
: undefined;
|
|
912
|
+
const searchChanged =
|
|
913
|
+
ctx.prevUrl.search !== ctx.url.search ||
|
|
914
|
+
pipelineReqCtx?._visitorMoved === true;
|
|
915
|
+
const routeParamsChanged = !paramsEqual(ctx.matched.params, ctx.prevParams);
|
|
915
916
|
|
|
916
917
|
yield* liveSegments;
|
|
917
918
|
|
|
918
919
|
for (const segment of cacheResult.segments) {
|
|
920
|
+
if (interceptFillSkips(ctx, segment)) continue;
|
|
919
921
|
if (!ctx.clientSegmentSet.has(segment.id)) {
|
|
920
922
|
if (isTraceActive()) {
|
|
921
923
|
pushRevalidationTraceEntry({
|
|
@@ -932,12 +934,8 @@ export function withCacheLookup<TEnv>(
|
|
|
932
934
|
continue;
|
|
933
935
|
}
|
|
934
936
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
continue;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// A fill keeps a held segment outright (fillSends).
|
|
937
|
+
// A fill keeps a held segment outright (fillSends), an intercept's
|
|
938
|
+
// included.
|
|
941
939
|
if (
|
|
942
940
|
fillSends(ctx.handlerContext, segment.id, ctx.clientSegmentSet) ===
|
|
943
941
|
false
|
|
@@ -946,14 +944,12 @@ export function withCacheLookup<TEnv>(
|
|
|
946
944
|
yield segment;
|
|
947
945
|
continue;
|
|
948
946
|
}
|
|
947
|
+
if (isInterceptSegment(segment)) {
|
|
948
|
+
yield segment;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
949
951
|
|
|
950
952
|
const entryInfo = entryRevalidateMap?.get(segment.id);
|
|
951
|
-
|
|
952
|
-
const searchChanged = ctx.prevUrl.search !== ctx.url.search;
|
|
953
|
-
const routeParamsChanged = !paramsEqual(
|
|
954
|
-
ctx.matched.params,
|
|
955
|
-
ctx.prevParams,
|
|
956
|
-
);
|
|
957
953
|
const shouldDefaultRevalidate =
|
|
958
954
|
(searchChanged || routeParamsChanged) &&
|
|
959
955
|
(segment.type === "route" ||
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* v
|
|
30
30
|
* +----------------------+ +----------------------------+
|
|
31
31
|
* | Fresh intercept? |yes>| resolveInterceptEntry() |
|
|
32
|
-
* | (!cacheHit or | | -
|
|
32
|
+
* | (!cacheHit or | | - loaders, UI |
|
|
33
33
|
* | no intercept segs) | +----------------------------+
|
|
34
34
|
* +----------------------+ |
|
|
35
35
|
* | no v
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
*
|
|
60
60
|
* 1. Fresh intercept (no cache):
|
|
61
61
|
* - Full resolution of intercept entry
|
|
62
|
-
* - Resolves
|
|
62
|
+
* - Resolves loaders and component
|
|
63
63
|
* - Yields all intercept segments
|
|
64
64
|
*
|
|
65
65
|
* 2. Cache hit with intercept:
|
|
@@ -106,6 +106,7 @@ import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
|
106
106
|
import { getRouterContext } from "../router-context.js";
|
|
107
107
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
108
108
|
import { debugLog } from "../logging.js";
|
|
109
|
+
import { isInterceptSegment } from "../../browser/intercept-utils.js";
|
|
109
110
|
|
|
110
111
|
/**
|
|
111
112
|
* Creates intercept resolution middleware
|
|
@@ -151,7 +152,7 @@ export function withInterceptResolution<TEnv>(
|
|
|
151
152
|
|
|
152
153
|
if (skipInterceptResolution) {
|
|
153
154
|
if (ctx.interceptResult && state.cacheHit && ctx.isIntercept) {
|
|
154
|
-
await handleCacheHitIntercept(ctx, state, segments);
|
|
155
|
+
yield* await handleCacheHitIntercept(ctx, state, segments);
|
|
155
156
|
}
|
|
156
157
|
if (ms) {
|
|
157
158
|
ms.metrics.push({
|
|
@@ -212,77 +213,57 @@ export function withInterceptResolution<TEnv>(
|
|
|
212
213
|
};
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
/**
|
|
217
|
+
* A cache or prerender hit that carries the intercept's slot: the slot keeps
|
|
218
|
+
* its stored component and layout, and its loaders, never stored, resolve as
|
|
219
|
+
* loader segments of it. Returns those segments for the caller to yield.
|
|
220
|
+
*/
|
|
215
221
|
async function handleCacheHitIntercept<TEnv>(
|
|
216
222
|
ctx: MatchContext<TEnv>,
|
|
217
223
|
state: MatchPipelineState,
|
|
218
224
|
segments: ResolvedSegment[],
|
|
219
|
-
): Promise<
|
|
220
|
-
if (!ctx.interceptResult) return;
|
|
225
|
+
): Promise<ResolvedSegment[]> {
|
|
226
|
+
if (!ctx.interceptResult) return [];
|
|
221
227
|
|
|
222
228
|
const { resolveInterceptLoadersOnly } = getRouterContext<TEnv>();
|
|
223
229
|
|
|
224
230
|
const slotName = ctx.interceptResult.intercept.slotName;
|
|
225
231
|
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
ctx.interceptResult!.intercept,
|
|
236
|
-
ctx.interceptResult!.entry,
|
|
237
|
-
ctx.matched.params,
|
|
238
|
-
ctx.handlerContext,
|
|
239
|
-
true, // belongsToRoute
|
|
240
|
-
{
|
|
241
|
-
clientSegmentIds: ctx.clientSegmentSet,
|
|
242
|
-
prevParams: ctx.prevParams,
|
|
243
|
-
request: ctx.request,
|
|
244
|
-
prevUrl: ctx.prevUrl,
|
|
245
|
-
nextUrl: ctx.url,
|
|
246
|
-
routeKey: ctx.routeKey,
|
|
247
|
-
actionContext: ctx.actionContext,
|
|
248
|
-
stale: ctx.stale,
|
|
249
|
-
},
|
|
250
|
-
),
|
|
251
|
-
);
|
|
232
|
+
const cachedSlot = segments.filter(isInterceptSegment);
|
|
233
|
+
// A record stored before 0.25.0 carries the loaders' aggregate on the slot;
|
|
234
|
+
// the loader segments below replace it. Build versions hash the router's
|
|
235
|
+
// code, so only a `version` pinned across the upgrade (createRouter's or
|
|
236
|
+
// the store's) still reads one.
|
|
237
|
+
for (const slot of cachedSlot) {
|
|
238
|
+
delete slot.loaderDataPromise;
|
|
239
|
+
delete slot.loaderIds;
|
|
240
|
+
}
|
|
252
241
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
interceptMainSegment.loaderIds = freshLoaderResult.loaderIds;
|
|
261
|
-
debugLog(
|
|
262
|
-
"matchPartial.intercept",
|
|
263
|
-
freshLoaderResult.loaderIds.length > 0
|
|
264
|
-
? "cache hit with fresh intercept loaders"
|
|
265
|
-
: "cache hit, every intercept loader held",
|
|
242
|
+
const loaderSegments = resolveInterceptLoadersOnly
|
|
243
|
+
? await ctx.Store.run(() =>
|
|
244
|
+
resolveInterceptLoadersOnly(
|
|
245
|
+
ctx.interceptResult!.intercept,
|
|
246
|
+
ctx.interceptResult!.entry,
|
|
247
|
+
ctx.handlerContext,
|
|
248
|
+
true, // belongsToRoute
|
|
266
249
|
{
|
|
267
|
-
|
|
268
|
-
|
|
250
|
+
clientSegmentIds: ctx.clientSegmentSet,
|
|
251
|
+
prevParams: ctx.prevParams,
|
|
252
|
+
request: ctx.request,
|
|
253
|
+
prevUrl: ctx.prevUrl,
|
|
254
|
+
nextUrl: ctx.url,
|
|
255
|
+
routeKey: ctx.routeKey,
|
|
256
|
+
actionContext: ctx.actionContext,
|
|
257
|
+
stale: ctx.stale,
|
|
269
258
|
},
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
"cache hit, intercept has no loaders",
|
|
276
|
-
{
|
|
277
|
-
routeName: ctx.localRouteName,
|
|
278
|
-
slotName,
|
|
279
|
-
},
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
259
|
+
),
|
|
260
|
+
)
|
|
261
|
+
: [];
|
|
262
|
+
const interceptSegments = [...cachedSlot, ...loaderSegments];
|
|
263
|
+
state.interceptSegments = interceptSegments;
|
|
284
264
|
state.slots[slotName] = {
|
|
285
265
|
active: true,
|
|
286
266
|
segments: interceptSegments,
|
|
287
267
|
};
|
|
268
|
+
return loaderSegments;
|
|
288
269
|
}
|
|
@@ -109,10 +109,14 @@
|
|
|
109
109
|
*/
|
|
110
110
|
import type { MatchResult, ResolvedSegment } from "../types.js";
|
|
111
111
|
import { isPlaceholder } from "../types/segments.js";
|
|
112
|
+
import { loaderParentId } from "../browser/react/filter-segment-order.js";
|
|
112
113
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
113
114
|
import { debugLog } from "./logging.js";
|
|
114
115
|
import { appendMetric } from "./metrics.js";
|
|
115
|
-
import {
|
|
116
|
+
import {
|
|
117
|
+
collectInterceptTargetNames,
|
|
118
|
+
interceptSegmentIds,
|
|
119
|
+
} from "./intercept-resolution.js";
|
|
116
120
|
|
|
117
121
|
export async function collectSegments(
|
|
118
122
|
generator: AsyncGenerator<ResolvedSegment>,
|
|
@@ -192,6 +196,25 @@ function deduplicateLoaderSegments(
|
|
|
192
196
|
return { segments: result, removedIds };
|
|
193
197
|
}
|
|
194
198
|
|
|
199
|
+
/**
|
|
200
|
+
* An intercept's `matched`: the client's ids without any of the slot's (its
|
|
201
|
+
* id and every loader segment on it, another intercept's into the same slot
|
|
202
|
+
* included), then the slot's (interceptSegmentIds). One order for the
|
|
203
|
+
* prefetch, its fill, an action and a revisit: a fill whose `matched` differs
|
|
204
|
+
* from its tree's is rendered as a new tree (browser/partial-update.ts
|
|
205
|
+
* fillHoles).
|
|
206
|
+
*/
|
|
207
|
+
function interceptMatched(
|
|
208
|
+
clientIds: readonly string[],
|
|
209
|
+
slotIds: readonly string[],
|
|
210
|
+
): string[] {
|
|
211
|
+
// loaderParentId leaves a non-loader id as it is: the slot's own id goes too.
|
|
212
|
+
return [
|
|
213
|
+
...clientIds.filter((id) => loaderParentId(id) !== slotIds[0]),
|
|
214
|
+
...slotIds,
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
|
|
195
218
|
export function buildMatchResult<TEnv>(
|
|
196
219
|
allSegments: ResolvedSegment[],
|
|
197
220
|
ctx: MatchContext<TEnv>,
|
|
@@ -223,7 +246,13 @@ export function buildMatchResult<TEnv>(
|
|
|
223
246
|
} else {
|
|
224
247
|
allIds = ctx.interceptResult
|
|
225
248
|
? ctx.clientSegmentIds.length > 0
|
|
226
|
-
?
|
|
249
|
+
? interceptMatched(
|
|
250
|
+
ctx.clientSegmentIds,
|
|
251
|
+
interceptSegmentIds(
|
|
252
|
+
ctx.interceptResult.entry,
|
|
253
|
+
ctx.interceptResult.intercept,
|
|
254
|
+
),
|
|
255
|
+
)
|
|
227
256
|
: allSegments.map((s) => s.id)
|
|
228
257
|
: [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
|
|
229
258
|
|
package/src/router/middleware.ts
CHANGED
|
@@ -34,11 +34,7 @@ import { assertNonCacheableReadAllowed } from "../server/context.js";
|
|
|
34
34
|
import { readGuardedTheme } from "../server/cookie-store.js";
|
|
35
35
|
import { defineSlot, slotAccessor } from "../server/slot-accessors.js";
|
|
36
36
|
import type { Theme } from "../theme/types.js";
|
|
37
|
-
import {
|
|
38
|
-
isRewrite,
|
|
39
|
-
rewriteNotSupported,
|
|
40
|
-
type Rewrite,
|
|
41
|
-
} from "../route-definition/rewrite.js";
|
|
37
|
+
import { isRewrite, type Rewrite } from "../route-definition/rewrite.js";
|
|
42
38
|
import { defineRewrittenFrom } from "../server/rewrite-trail.js";
|
|
43
39
|
|
|
44
40
|
// Re-export types consumed through this module's path.
|
|
@@ -324,8 +320,8 @@ export function createMiddlewareContext<TEnv>(
|
|
|
324
320
|
waitUntil: reqCtx ? reqCtx.waitUntil.bind(reqCtx) : fireAndForgetWaitUntil,
|
|
325
321
|
|
|
326
322
|
get: ((keyOrVar: any) => {
|
|
327
|
-
// "use cache" only:
|
|
328
|
-
//
|
|
323
|
+
// "use cache" only: middleware runs before any cache() subtree
|
|
324
|
+
// renders, so the cache() read guard never applies to it.
|
|
329
325
|
if (
|
|
330
326
|
isNonCacheable(variables, keyOrVar) &&
|
|
331
327
|
getCacheExecScope() !== undefined
|
|
@@ -517,13 +513,11 @@ function mergeReqCtxStub(
|
|
|
517
513
|
// Clone `base` with stub headers merged into a fresh Headers (the clone keeps
|
|
518
514
|
// the body mutable for post-next() modifications). Set-Cookie is always
|
|
519
515
|
// appended; other headers obey stubOverridesNonCookie (see mergeStubHeaders).
|
|
520
|
-
//
|
|
521
|
-
// short-circuit path passes false (its reqCtx headers are not merged here),
|
|
522
|
-
// which is the one deliberate divergence between the call sites.
|
|
516
|
+
// RequestContext stub cookies/headers are folded in last.
|
|
523
517
|
function mergeResponse(
|
|
524
518
|
base: Response,
|
|
525
519
|
stub: Headers,
|
|
526
|
-
opts: { stubOverridesNonCookie: boolean
|
|
520
|
+
opts: { stubOverridesNonCookie: boolean },
|
|
527
521
|
): Response {
|
|
528
522
|
const mergedHeaders = new Headers(base.headers);
|
|
529
523
|
// The reserved external-redirect marker is never a trust signal and must never
|
|
@@ -532,9 +526,7 @@ function mergeResponse(
|
|
|
532
526
|
// guard would not touch) to the client. The opt-in is the out-of-band brand.
|
|
533
527
|
mergedHeaders.delete(EXTERNAL_REDIRECT_MARKER);
|
|
534
528
|
mergeStubHeaders(mergedHeaders, stub, opts.stubOverridesNonCookie);
|
|
535
|
-
|
|
536
|
-
mergeReqCtxStub(mergedHeaders, _getRequestContext());
|
|
537
|
-
}
|
|
529
|
+
mergeReqCtxStub(mergedHeaders, _getRequestContext());
|
|
538
530
|
const merged = new Response(base.body, {
|
|
539
531
|
status: base.status,
|
|
540
532
|
statusText: base.statusText,
|
|
@@ -597,7 +589,6 @@ export async function executeMiddleware<TEnv>(
|
|
|
597
589
|
// and reqCtx stub headers are merged in.
|
|
598
590
|
responseHolder.response = mergeResponse(response, stubResponse.headers, {
|
|
599
591
|
stubOverridesNonCookie: true,
|
|
600
|
-
mergeReqCtx: true,
|
|
601
592
|
});
|
|
602
593
|
|
|
603
594
|
return responseHolder.response;
|
|
@@ -740,7 +731,6 @@ export async function executeMiddleware<TEnv>(
|
|
|
740
731
|
// (stubOverridesNonCookie=false); reqCtx stub headers still merge in.
|
|
741
732
|
const merged = mergeResponse(result, stubResponse.headers, {
|
|
742
733
|
stubOverridesNonCookie: false,
|
|
743
|
-
mergeReqCtx: true,
|
|
744
734
|
});
|
|
745
735
|
responseHolder.response = merged;
|
|
746
736
|
return merged;
|
|
@@ -796,137 +786,6 @@ export async function executeMiddleware<TEnv>(
|
|
|
796
786
|
return finalResponse;
|
|
797
787
|
}
|
|
798
788
|
|
|
799
|
-
/**
|
|
800
|
-
* Execute middleware for intercepts (simplified execution)
|
|
801
|
-
*
|
|
802
|
-
* Intercepts use a shared stubResponse from the request context. This function:
|
|
803
|
-
* - Runs middleware in sequence with a simple next() chain
|
|
804
|
-
* - Returns Response if any middleware short-circuits (returns OR throws a Response, or redirects, BEFORE next())
|
|
805
|
-
* - Returns null if all middleware calls next() - headers set after next() remain on stubResponse
|
|
806
|
-
*
|
|
807
|
-
* @param middlewares - Array of middleware functions
|
|
808
|
-
* @param request - Original request
|
|
809
|
-
* @param env - Environment bindings
|
|
810
|
-
* @param params - Route params
|
|
811
|
-
* @param variables - Shared variables object
|
|
812
|
-
* @param stubResponse - Response from request context for collecting headers/cookies
|
|
813
|
-
*/
|
|
814
|
-
export async function executeInterceptMiddleware<TEnv>(
|
|
815
|
-
middlewares: MiddlewareFn<TEnv>[],
|
|
816
|
-
request: Request,
|
|
817
|
-
env: TEnv,
|
|
818
|
-
params: Record<string, string>,
|
|
819
|
-
variables: Record<string, any>,
|
|
820
|
-
stubResponse: Response,
|
|
821
|
-
reverse?: (
|
|
822
|
-
name: string,
|
|
823
|
-
params?: Record<string, string>,
|
|
824
|
-
search?: Record<string, unknown>,
|
|
825
|
-
) => string,
|
|
826
|
-
): Promise<Response | null> {
|
|
827
|
-
if (middlewares.length === 0) {
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
let index = 0;
|
|
832
|
-
let earlyResponse: Response | null = null;
|
|
833
|
-
|
|
834
|
-
// Use provided stubResponse - headers/cookies set here will be merged by the caller
|
|
835
|
-
const responseHolder: ResponseHolder = { response: stubResponse };
|
|
836
|
-
|
|
837
|
-
const next = async (): Promise<Response> => {
|
|
838
|
-
if (index >= middlewares.length || earlyResponse) {
|
|
839
|
-
return stubResponse;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
const ordinal = index;
|
|
843
|
-
const middleware = middlewares[index++];
|
|
844
|
-
const ctx = createMiddlewareContext(
|
|
845
|
-
request,
|
|
846
|
-
env,
|
|
847
|
-
params,
|
|
848
|
-
variables,
|
|
849
|
-
responseHolder,
|
|
850
|
-
reverse,
|
|
851
|
-
);
|
|
852
|
-
|
|
853
|
-
let nextCalled = false;
|
|
854
|
-
const guardedNext = (): Promise<Response> => {
|
|
855
|
-
if (nextCalled) {
|
|
856
|
-
throw new Error(
|
|
857
|
-
`[@rangojs/router] Intercept middleware called next() more than once.`,
|
|
858
|
-
);
|
|
859
|
-
}
|
|
860
|
-
nextCalled = true;
|
|
861
|
-
return next();
|
|
862
|
-
};
|
|
863
|
-
|
|
864
|
-
// Span-wrap each intercept middleware as rango.middleware (metric:false —
|
|
865
|
-
// intercept runs inside the render phase already metered by render:total, so
|
|
866
|
-
// it contributes a span but no separate perf metric). Bare MiddlewareFns
|
|
867
|
-
// have no pattern, so the label is scoped to "*" like a pattern-less entry.
|
|
868
|
-
const label = getMiddlewareMetricLabel(
|
|
869
|
-
{ handler: middleware, pattern: null } as MiddlewareEntry<TEnv>,
|
|
870
|
-
ordinal,
|
|
871
|
-
);
|
|
872
|
-
|
|
873
|
-
const result: Response | void = await observePhase(
|
|
874
|
-
PHASES.middleware(label),
|
|
875
|
-
async () => {
|
|
876
|
-
try {
|
|
877
|
-
return await middleware(ctx, guardedNext);
|
|
878
|
-
} catch (error) {
|
|
879
|
-
// Thrown Response is short-circuit control flow, parity with the
|
|
880
|
-
// explicit-return path below. Absorb it INSIDE the span so the tracing
|
|
881
|
-
// runner settles rango.middleware as success, not STATUS_ERROR (same
|
|
882
|
-
// reasoning as executeMiddleware's main chain). Real errors propagate.
|
|
883
|
-
if (error instanceof Response) return error;
|
|
884
|
-
if (isRewrite(error)) throw rewriteNotSupported(error, "intercept");
|
|
885
|
-
throw error;
|
|
886
|
-
}
|
|
887
|
-
},
|
|
888
|
-
);
|
|
889
|
-
if (isRewrite(result)) throw returnedRewriteError(middleware);
|
|
890
|
-
|
|
891
|
-
if (result instanceof Response) {
|
|
892
|
-
earlyResponse = result;
|
|
893
|
-
return result;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
return stubResponse;
|
|
897
|
-
};
|
|
898
|
-
|
|
899
|
-
await next();
|
|
900
|
-
|
|
901
|
-
// Return early response if middleware short-circuited (returned Response BEFORE next())
|
|
902
|
-
if (earlyResponse) {
|
|
903
|
-
// Capture in const for TypeScript narrowing (earlyResponse is `let` which loses narrowing in callbacks)
|
|
904
|
-
const response: Response = earlyResponse;
|
|
905
|
-
|
|
906
|
-
// Merge any headers/cookies set on stub into the early response
|
|
907
|
-
let hasStubHeaders = false;
|
|
908
|
-
stubResponse.headers.forEach(() => {
|
|
909
|
-
hasStubHeaders = true;
|
|
910
|
-
});
|
|
911
|
-
|
|
912
|
-
if (hasStubHeaders) {
|
|
913
|
-
// Only fill in missing headers — the returned Response's explicit headers
|
|
914
|
-
// take precedence (stubOverridesNonCookie=false), matching executeMiddleware.
|
|
915
|
-
// mergeReqCtx=false: the intercept path deliberately does NOT merge reqCtx
|
|
916
|
-
// stub headers here (pinned by intercept-middleware-headers.test.ts).
|
|
917
|
-
return mergeResponse(response, stubResponse.headers, {
|
|
918
|
-
stubOverridesNonCookie: false,
|
|
919
|
-
mergeReqCtx: false,
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
return response;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
// All middleware completed without short-circuit
|
|
926
|
-
// Headers/cookies set on stubResponse will be merged into the final response by the caller
|
|
927
|
-
return null;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
789
|
/**
|
|
931
790
|
* Execute middleware chain for loaders (simpler signature)
|
|
932
791
|
*
|