@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
|
@@ -109,10 +109,12 @@ import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
|
109
109
|
import { getContext } from "../../server/context.js";
|
|
110
110
|
import { executionStart } from "../../cache/tag-invalidation.js";
|
|
111
111
|
import {
|
|
112
|
+
bindRes,
|
|
112
113
|
createRequestContext,
|
|
113
114
|
runWithRequestContext,
|
|
114
115
|
type RequestContext,
|
|
115
116
|
} from "../../server/request-context.js";
|
|
117
|
+
import { recordUse } from "../segment-resolution/prefetch-deferral.js";
|
|
116
118
|
|
|
117
119
|
/**
|
|
118
120
|
* Re-render a cached route in the background and write it with
|
|
@@ -165,9 +167,7 @@ export async function rerenderAndCacheRoute<TEnv>(
|
|
|
165
167
|
themeConfig: requestCtx._themeConfig,
|
|
166
168
|
});
|
|
167
169
|
const renderCtx: RequestContext<TEnv> = Object.assign(
|
|
168
|
-
Object.create(requestCtx,
|
|
169
|
-
res: Object.getOwnPropertyDescriptor(sink, "res")!,
|
|
170
|
-
}),
|
|
170
|
+
Object.create(requestCtx),
|
|
171
171
|
{
|
|
172
172
|
_handleStore: handleStore,
|
|
173
173
|
_metricsStore: undefined,
|
|
@@ -184,6 +184,8 @@ export async function rerenderAndCacheRoute<TEnv>(
|
|
|
184
184
|
_setKeepCacheDirective: sink._setKeepCacheDirective,
|
|
185
185
|
},
|
|
186
186
|
);
|
|
187
|
+
// ctx.res reads the sink's stub, not the live request's.
|
|
188
|
+
bindRes(renderCtx, () => sink.res);
|
|
187
189
|
const store = Object.assign(Object.create(ctx.Store), { metrics: undefined });
|
|
188
190
|
const runInStore = <T>(fn: () => T): T =>
|
|
189
191
|
getContext().runWithStore(
|
|
@@ -281,7 +283,17 @@ export function withBackgroundRevalidation<TEnv>(
|
|
|
281
283
|
// Only trigger background revalidation if:
|
|
282
284
|
// 1. Cache was hit and stale
|
|
283
285
|
// 2. Cache scope exists
|
|
284
|
-
|
|
286
|
+
// 3. The request may refresh the record (RecordUse.refresh): a fill may
|
|
287
|
+
// not (prefetch: false). It runs no handler of a segment the client
|
|
288
|
+
// holds, and the re-render runs every handler of the route. The record
|
|
289
|
+
// stays stale until a request that reads it arrives: the prefetch
|
|
290
|
+
// before the fill already was one.
|
|
291
|
+
if (
|
|
292
|
+
!state.shouldRevalidate ||
|
|
293
|
+
!state.cacheHit ||
|
|
294
|
+
!ctx.cacheScope ||
|
|
295
|
+
!recordUse(ctx.handlerContext).refresh
|
|
296
|
+
) {
|
|
285
297
|
return;
|
|
286
298
|
}
|
|
287
299
|
|
|
@@ -99,6 +99,10 @@ import { observeEvent } from "../instrument.js";
|
|
|
99
99
|
import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
|
|
100
100
|
import { treeHasStreaming } from "./segment-resolution.js";
|
|
101
101
|
import { loaderPins } from "../segment-resolution/loader-cache.js";
|
|
102
|
+
import {
|
|
103
|
+
fillSends,
|
|
104
|
+
recordUse,
|
|
105
|
+
} from "../segment-resolution/prefetch-deferral.js";
|
|
102
106
|
import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
|
|
103
107
|
import {
|
|
104
108
|
isStoredEntryStale,
|
|
@@ -345,14 +349,20 @@ async function* yieldFromStore<TEnv>(
|
|
|
345
349
|
for (const segment of segments) {
|
|
346
350
|
if (
|
|
347
351
|
!ctx.isFullMatch &&
|
|
348
|
-
!paramsChanged &&
|
|
349
352
|
ctx.clientSegmentSet.has(segment.id) &&
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
)
|
|
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:")
|
|
354
357
|
) {
|
|
355
|
-
|
|
358
|
+
// A fill keeps it (fillSends); otherwise it is sent again when the
|
|
359
|
+
// params changed or an action re-sends the route's own segments.
|
|
360
|
+
const sends =
|
|
361
|
+
fillSends(ctx.handlerContext, segment.id, ctx.clientSegmentSet) ??
|
|
362
|
+
(paramsChanged ||
|
|
363
|
+
(resendRouteSegments &&
|
|
364
|
+
(segment.type === "route" || segment.belongsToRoute === true)));
|
|
365
|
+
if (!sends) keepClientSegment(segment);
|
|
356
366
|
}
|
|
357
367
|
yield segment;
|
|
358
368
|
}
|
|
@@ -725,7 +735,15 @@ export function withCacheLookup<TEnv>(
|
|
|
725
735
|
throw new ShellRecordUnavailableError(tailMarker.fixedDocKey);
|
|
726
736
|
}
|
|
727
737
|
|
|
728
|
-
|
|
738
|
+
// A prefetch that defers a layout above the boundary (prefetch: false)
|
|
739
|
+
// emits nothing the record holds: reading it would replay the handle
|
|
740
|
+
// pushes of segments the response does not carry. withCacheStore skips
|
|
741
|
+
// the write for the same request.
|
|
742
|
+
if (
|
|
743
|
+
ctx.isAction ||
|
|
744
|
+
!ctx.cacheScope?.enabled ||
|
|
745
|
+
!recordUse(ctx.handlerContext).read
|
|
746
|
+
) {
|
|
729
747
|
yield* source;
|
|
730
748
|
if (ms) {
|
|
731
749
|
ms.metrics.push({
|
|
@@ -919,6 +937,16 @@ export function withCacheLookup<TEnv>(
|
|
|
919
937
|
continue;
|
|
920
938
|
}
|
|
921
939
|
|
|
940
|
+
// A fill keeps a held segment outright (fillSends).
|
|
941
|
+
if (
|
|
942
|
+
fillSends(ctx.handlerContext, segment.id, ctx.clientSegmentSet) ===
|
|
943
|
+
false
|
|
944
|
+
) {
|
|
945
|
+
keepClientSegment(segment);
|
|
946
|
+
yield segment;
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
|
|
922
950
|
const entryInfo = entryRevalidateMap?.get(segment.id);
|
|
923
951
|
|
|
924
952
|
const searchChanged = ctx.prevUrl.search !== ctx.url.search;
|
|
@@ -114,6 +114,7 @@ import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
|
114
114
|
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
115
115
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
116
116
|
import { rerenderAndCacheRoute } from "./background-revalidation.js";
|
|
117
|
+
import { recordUse } from "../segment-resolution/prefetch-deferral.js";
|
|
117
118
|
|
|
118
119
|
/**
|
|
119
120
|
* Creates cache store middleware
|
|
@@ -149,11 +150,15 @@ export function withCacheStore<TEnv>(
|
|
|
149
150
|
// segments were collected into allSegments either way).
|
|
150
151
|
recordShellCaptureDocRecord(ctx, state, allSegments);
|
|
151
152
|
|
|
153
|
+
// prefetch: false (prefetch-deferral.ts). A prefetch that deferred a
|
|
154
|
+
// layout above the boundary did not read the record (withCacheLookup)
|
|
155
|
+
// and could only write an incomplete one.
|
|
152
156
|
if (
|
|
153
157
|
!ctx.cacheScope?.enabled ||
|
|
154
158
|
ctx.isAction ||
|
|
155
159
|
state.cacheHit ||
|
|
156
|
-
ctx.request.method !== "GET"
|
|
160
|
+
ctx.request.method !== "GET" ||
|
|
161
|
+
!recordUse(ctx.handlerContext).write
|
|
157
162
|
) {
|
|
158
163
|
if (ms) {
|
|
159
164
|
ms.metrics.push({
|
|
@@ -192,6 +197,15 @@ export function withCacheStore<TEnv>(
|
|
|
192
197
|
cacheScope.covers(s.id, s.namespace),
|
|
193
198
|
);
|
|
194
199
|
|
|
200
|
+
// The client holds part of what the record covers, so writing it takes
|
|
201
|
+
// the proactive re-render below, which a request that may not refresh
|
|
202
|
+
// the record (RecordUse.refresh) does not start. That is a fill: it
|
|
203
|
+
// renders what the client is missing and nothing else, and writes like
|
|
204
|
+
// any other request only when that is everything the record covers. The
|
|
205
|
+
// stale-hit refresh skips it for the same reason
|
|
206
|
+
// (withBackgroundRevalidation).
|
|
207
|
+
if (hasNullComponents && !recordUse(ctx.handlerContext).refresh) return;
|
|
208
|
+
|
|
195
209
|
const requestCtx = getRequestContext();
|
|
196
210
|
if (!requestCtx) return;
|
|
197
211
|
|
|
@@ -260,7 +260,9 @@ async function handleCacheHitIntercept<TEnv>(
|
|
|
260
260
|
interceptMainSegment.loaderIds = freshLoaderResult.loaderIds;
|
|
261
261
|
debugLog(
|
|
262
262
|
"matchPartial.intercept",
|
|
263
|
-
|
|
263
|
+
freshLoaderResult.loaderIds.length > 0
|
|
264
|
+
? "cache hit with fresh intercept loaders"
|
|
265
|
+
: "cache hit, every intercept loader held",
|
|
264
266
|
{
|
|
265
267
|
routeName: ctx.localRouteName,
|
|
266
268
|
slotName,
|
|
@@ -270,7 +272,7 @@ async function handleCacheHitIntercept<TEnv>(
|
|
|
270
272
|
} else {
|
|
271
273
|
debugLog(
|
|
272
274
|
"matchPartial.intercept",
|
|
273
|
-
"cache hit
|
|
275
|
+
"cache hit, intercept has no loaders",
|
|
274
276
|
{
|
|
275
277
|
routeName: ctx.localRouteName,
|
|
276
278
|
slotName,
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
* 4. Render slot content if slots present
|
|
109
109
|
*/
|
|
110
110
|
import type { MatchResult, ResolvedSegment } from "../types.js";
|
|
111
|
+
import { isPlaceholder } from "../types/segments.js";
|
|
111
112
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
112
113
|
import { debugLog } from "./logging.js";
|
|
113
114
|
import { appendMetric } from "./metrics.js";
|
|
@@ -229,12 +230,23 @@ export function buildMatchResult<TEnv>(
|
|
|
229
230
|
allIds = [...new Set(allIds)];
|
|
230
231
|
|
|
231
232
|
// One pass over allSegments: keep renderable segments and collect the
|
|
232
|
-
//
|
|
233
|
+
// ids that ran (resolvedIds) together. A partial match resolves only the
|
|
234
|
+
// loaders it runs (revalidation.ts), so each loader segment here ran:
|
|
235
|
+
// the browser replaces that loader's handle pushes, and keeps those of a
|
|
236
|
+
// loader it skipped (handles/pushed-by.ts).
|
|
233
237
|
const clientIdSet = new Set(ctx.clientSegmentIds);
|
|
234
238
|
segmentsToRender = [];
|
|
235
239
|
resolvedIds = [];
|
|
236
240
|
for (const s of allSegments) {
|
|
237
|
-
|
|
241
|
+
// A deferred loader (prefetch: false) did not run in this response:
|
|
242
|
+
// its pushes arrive with the fill, which lists it then.
|
|
243
|
+
if (s._handlerRan || (s.type === "loader" && !isPlaceholder(s))) {
|
|
244
|
+
resolvedIds.push(s.id);
|
|
245
|
+
}
|
|
246
|
+
// A deferred unit (prefetch: false) has a null component and is sent:
|
|
247
|
+
// only a segment the client does not hold is ever deferred, and
|
|
248
|
+
// deferral reads these same ids (prefetch-deferral.ts
|
|
249
|
+
// PrefetchDeferral.held), not the set the match forces renders with.
|
|
238
250
|
if (
|
|
239
251
|
s.component !== null ||
|
|
240
252
|
s.type === "loader" ||
|
|
@@ -8,6 +8,7 @@ import type { GlobalReverseFunction } from "../reverse.js";
|
|
|
8
8
|
import type { Theme } from "../theme/types.js";
|
|
9
9
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
10
10
|
import type { RequestScope } from "../types/request-scope.js";
|
|
11
|
+
import type { Rewrite } from "../route-definition/rewrite.js";
|
|
11
12
|
|
|
12
13
|
type GetVariableFn = {
|
|
13
14
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -81,6 +82,21 @@ export interface MiddlewareContext<
|
|
|
81
82
|
|
|
82
83
|
/** Global route names only; see {@link GlobalReverseFunction}. */
|
|
83
84
|
reverse: GlobalReverseFunction<DefaultReverseRouteMap>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether `value` is a `rewrite()` thrown downstream. A middleware that
|
|
88
|
+
* wraps `next()` in try/catch rethrows it, so the request re-dispatches:
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* try {
|
|
92
|
+
* return await next();
|
|
93
|
+
* } catch (error) {
|
|
94
|
+
* if (ctx.isRewrite(error)) throw error;
|
|
95
|
+
* return new Response("Error", { status: 500 });
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
isRewrite(value: unknown): value is Rewrite;
|
|
84
100
|
}
|
|
85
101
|
|
|
86
102
|
export type MiddlewareFn<
|
package/src/router/middleware.ts
CHANGED
|
@@ -32,7 +32,14 @@ import {
|
|
|
32
32
|
import { getCacheExecScope } from "../cache/cache-exec-scope.js";
|
|
33
33
|
import { assertNonCacheableReadAllowed } from "../server/context.js";
|
|
34
34
|
import { readGuardedTheme } from "../server/cookie-store.js";
|
|
35
|
+
import { defineSlot, slotAccessor } from "../server/slot-accessors.js";
|
|
35
36
|
import type { Theme } from "../theme/types.js";
|
|
37
|
+
import {
|
|
38
|
+
isRewrite,
|
|
39
|
+
rewriteNotSupported,
|
|
40
|
+
type Rewrite,
|
|
41
|
+
} from "../route-definition/rewrite.js";
|
|
42
|
+
import { defineRewrittenFrom } from "../server/rewrite-trail.js";
|
|
36
43
|
|
|
37
44
|
// Re-export types consumed through this module's path.
|
|
38
45
|
export type {
|
|
@@ -43,19 +50,54 @@ export type {
|
|
|
43
50
|
} from "./middleware-types.js";
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* request-context getters: a spread of the ctx (inside a "use cache"
|
|
50
|
-
* function, say) does not read it. One shared descriptor, so every ctx keeps
|
|
51
|
-
* the same property shape.
|
|
53
|
+
* The middleware ctx's accessors: shared descriptors (slot-accessors.ts).
|
|
54
|
+
* routeName, _routeName, theme and setTheme read only the request context;
|
|
55
|
+
* headers calls the per-ctx closure in HEADERS_SLOT.
|
|
52
56
|
*/
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const HEADERS_SLOT: unique symbol = Symbol("rango.middlewareContext.headers");
|
|
58
|
+
|
|
59
|
+
const MIDDLEWARE_ACCESSORS: Record<
|
|
60
|
+
"routeName" | "_routeName" | "headers" | "setTheme" | "theme",
|
|
61
|
+
PropertyDescriptor
|
|
62
|
+
> = {
|
|
63
|
+
// Re-derived from the request context on each access, so global middleware
|
|
64
|
+
// sees the matched route name after await next().
|
|
65
|
+
routeName: {
|
|
66
|
+
get(): string | undefined {
|
|
67
|
+
const raw = _getRequestContext()?._routeName;
|
|
68
|
+
return raw && !isAutoGeneratedRouteName(raw) ? raw : undefined;
|
|
69
|
+
},
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
},
|
|
73
|
+
// The matched route key, auto-generated names included (the handler ctx's
|
|
74
|
+
// _routeName); undefined before routing.
|
|
75
|
+
_routeName: {
|
|
76
|
+
get(): string | undefined {
|
|
77
|
+
return _getRequestContext()?._routeName;
|
|
78
|
+
},
|
|
79
|
+
enumerable: true,
|
|
80
|
+
configurable: true,
|
|
81
|
+
},
|
|
82
|
+
headers: slotAccessor(HEADERS_SLOT, true),
|
|
83
|
+
setTheme: {
|
|
84
|
+
get(): MiddlewareContext<unknown>["setTheme"] {
|
|
85
|
+
return _getRequestContext()?.setTheme;
|
|
86
|
+
},
|
|
87
|
+
enumerable: true,
|
|
88
|
+
configurable: true,
|
|
89
|
+
},
|
|
90
|
+
// The visitor's theme cookie, guarded like the handler ctx.theme and
|
|
91
|
+
// cookies() (#971, #946), so a read inside a "use cache" function the
|
|
92
|
+
// middleware calls throws. Non-enumerable like the handler and
|
|
93
|
+
// request-context getters: a spread of the ctx does not read it.
|
|
94
|
+
theme: {
|
|
95
|
+
get(): Theme | undefined {
|
|
96
|
+
return readGuardedTheme(_getRequestContext(), "ctx.theme");
|
|
97
|
+
},
|
|
98
|
+
enumerable: false,
|
|
99
|
+
configurable: true,
|
|
56
100
|
},
|
|
57
|
-
enumerable: false,
|
|
58
|
-
configurable: true,
|
|
59
101
|
};
|
|
60
102
|
|
|
61
103
|
const MIDDLEWARE_METRIC_DEPTH = 1;
|
|
@@ -252,15 +294,20 @@ export function createMiddlewareContext<TEnv>(
|
|
|
252
294
|
|
|
253
295
|
// reqCtx captured once above: the request-scoped platform fields
|
|
254
296
|
// (originalUrl, executionContext, waitUntil) are immutable per request,
|
|
255
|
-
// so snapshotting beats re-reading ALS on every access. The
|
|
256
|
-
//
|
|
257
|
-
//
|
|
297
|
+
// so snapshotting beats re-reading ALS on every access. The accessors
|
|
298
|
+
// (routeName, _routeName, theme, setTheme: MIDDLEWARE_ACCESSORS) read the
|
|
299
|
+
// request context on each access because those can change during
|
|
300
|
+
// `await next()`.
|
|
258
301
|
//
|
|
259
302
|
// Request-scoped like the handler ctx (issue #940): the NOCACHE_SYMBOL brand
|
|
260
303
|
// below makes a "use cache" function taking it key by host, _routeName,
|
|
261
304
|
// pathname, params and search; set/header/headers throw inside that
|
|
262
305
|
// function, and non-cacheable get reads run the shared guard.
|
|
263
|
-
|
|
306
|
+
// Data members only: the accessors are MIDDLEWARE_ACCESSORS.
|
|
307
|
+
const fields: Omit<
|
|
308
|
+
MiddlewareContext<TEnv>,
|
|
309
|
+
"routeName" | "headers" | "setTheme"
|
|
310
|
+
> = {
|
|
264
311
|
request,
|
|
265
312
|
url,
|
|
266
313
|
originalUrl: reqCtx?.originalUrl ?? new URL(request.url),
|
|
@@ -269,39 +316,12 @@ export function createMiddlewareContext<TEnv>(
|
|
|
269
316
|
env: env as MiddlewareContext<TEnv>["env"],
|
|
270
317
|
params,
|
|
271
318
|
build: reqCtx?.build ?? false,
|
|
319
|
+
isRewrite,
|
|
272
320
|
dynamic(): void {
|
|
273
321
|
reqCtx?.dynamic();
|
|
274
322
|
},
|
|
275
323
|
executionContext: reqCtx?.executionContext,
|
|
276
324
|
waitUntil: reqCtx ? reqCtx.waitUntil.bind(reqCtx) : fireAndForgetWaitUntil,
|
|
277
|
-
// Getter: re-derives from request context on each access so that global
|
|
278
|
-
// middleware sees the matched route name after await next().
|
|
279
|
-
get routeName(): MiddlewareContext<TEnv>["routeName"] {
|
|
280
|
-
const reqCtx = _getRequestContext();
|
|
281
|
-
const raw = reqCtx?._routeName;
|
|
282
|
-
return (
|
|
283
|
-
raw && !isAutoGeneratedRouteName(raw) ? raw : undefined
|
|
284
|
-
) as MiddlewareContext<TEnv>["routeName"];
|
|
285
|
-
},
|
|
286
|
-
|
|
287
|
-
// The matched route key, auto-generated names included (the handler
|
|
288
|
-
// ctx's _routeName); undefined before routing.
|
|
289
|
-
get _routeName(): string | undefined {
|
|
290
|
-
return _getRequestContext()?._routeName;
|
|
291
|
-
},
|
|
292
|
-
|
|
293
|
-
// Guarded only inside a "use cache" body: the stamp covers a miss that
|
|
294
|
-
// took this ctx as an argument, the exec scope a stale refresh (which does
|
|
295
|
-
// not stamp its args, cache-runtime.ts). A write there is lost on a hit.
|
|
296
|
-
get headers(): Headers {
|
|
297
|
-
const headers = getResponse().headers;
|
|
298
|
-
return (INSIDE_CACHE_EXEC as symbol) in ctx ||
|
|
299
|
-
getCacheExecScope() !== undefined
|
|
300
|
-
? guardHeaderWrites(headers, () =>
|
|
301
|
-
assertNotInsideCacheExec(ctx, "headers"),
|
|
302
|
-
)
|
|
303
|
-
: headers;
|
|
304
|
-
},
|
|
305
325
|
|
|
306
326
|
get: ((keyOrVar: any) => {
|
|
307
327
|
// "use cache" only: intercept middleware runs inside a cache()
|
|
@@ -338,11 +358,6 @@ export function createMiddlewareContext<TEnv>(
|
|
|
338
358
|
responseHolder.response.headers.set(name, value);
|
|
339
359
|
},
|
|
340
360
|
|
|
341
|
-
// theme: MIDDLEWARE_THEME_DESCRIPTOR, defined after this literal.
|
|
342
|
-
get setTheme(): MiddlewareContext<TEnv>["setTheme"] {
|
|
343
|
-
return _getRequestContext()?.setTheme;
|
|
344
|
-
},
|
|
345
|
-
|
|
346
361
|
setLocationState(entries) {
|
|
347
362
|
const reqCtx = _getRequestContext();
|
|
348
363
|
if (!reqCtx) {
|
|
@@ -376,11 +391,46 @@ export function createMiddlewareContext<TEnv>(
|
|
|
376
391
|
}
|
|
377
392
|
},
|
|
378
393
|
};
|
|
379
|
-
|
|
394
|
+
const ctx = fields as MiddlewareContext<TEnv>;
|
|
395
|
+
// Guarded only inside a "use cache" body: the stamp covers a miss that
|
|
396
|
+
// took this ctx as an argument, the exec scope a stale refresh (which does
|
|
397
|
+
// not stamp its args, cache-runtime.ts). A write there is lost on a hit.
|
|
398
|
+
defineSlot(ctx, HEADERS_SLOT, (): Headers => {
|
|
399
|
+
const headers = getResponse().headers;
|
|
400
|
+
return (INSIDE_CACHE_EXEC as symbol) in ctx ||
|
|
401
|
+
getCacheExecScope() !== undefined
|
|
402
|
+
? guardHeaderWrites(headers, () =>
|
|
403
|
+
assertNotInsideCacheExec(ctx, "headers"),
|
|
404
|
+
)
|
|
405
|
+
: headers;
|
|
406
|
+
});
|
|
407
|
+
Object.defineProperty(ctx, "routeName", MIDDLEWARE_ACCESSORS.routeName);
|
|
408
|
+
Object.defineProperty(ctx, "_routeName", MIDDLEWARE_ACCESSORS._routeName);
|
|
409
|
+
Object.defineProperty(ctx, "headers", MIDDLEWARE_ACCESSORS.headers);
|
|
410
|
+
Object.defineProperty(ctx, "setTheme", MIDDLEWARE_ACCESSORS.setTheme);
|
|
411
|
+
Object.defineProperty(ctx, "theme", MIDDLEWARE_ACCESSORS.theme);
|
|
412
|
+
defineRewrittenFrom(ctx, reqCtx, "middleware");
|
|
380
413
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
381
414
|
return ctx;
|
|
382
415
|
}
|
|
383
416
|
|
|
417
|
+
/** A middleware threw a Rewrite after its next() started the downstream. */
|
|
418
|
+
function rewriteAfterNextError(handler: Function, rewrite: Rewrite): Error {
|
|
419
|
+
return new Error(
|
|
420
|
+
`Middleware "${handler.name || "(anonymous)"}" threw rewrite() after ` +
|
|
421
|
+
`calling next(). Throw rewrite() before await next().`,
|
|
422
|
+
{ cause: rewrite },
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** A middleware returned a Rewrite: only a thrown one re-dispatches. */
|
|
427
|
+
function returnedRewriteError(handler: Function): Error {
|
|
428
|
+
return new Error(
|
|
429
|
+
`Middleware "${handler.name || "(anonymous)"}" returned a rewrite(). ` +
|
|
430
|
+
`Throw it instead: \`throw rewrite(...)\`, don't return it.`,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
384
434
|
/**
|
|
385
435
|
* Match middleware entries against a pathname
|
|
386
436
|
* Returns entries that match, with extracted params
|
|
@@ -584,6 +634,12 @@ export async function executeMiddleware<TEnv>(
|
|
|
584
634
|
// downstream chain and overwrite responseHolder.response.
|
|
585
635
|
let nextPromise: Promise<Response> | null = null;
|
|
586
636
|
let nextResolvedAt: number | undefined;
|
|
637
|
+
// Whether next() rejected with a Rewrite: only then did nothing render
|
|
638
|
+
// (a downstream middleware rewrote before its own next()). Any other
|
|
639
|
+
// rejection can come after the route rendered (a route middleware that
|
|
640
|
+
// throws after its next(), the render's own error), so a rewrite thrown
|
|
641
|
+
// after it would re-dispatch onto the used handle store.
|
|
642
|
+
let nextRejectedWithRewrite = false;
|
|
587
643
|
const wrappedNext = (): Promise<Response> => {
|
|
588
644
|
if (nextPromise) {
|
|
589
645
|
throw new Error(
|
|
@@ -599,6 +655,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
599
655
|
},
|
|
600
656
|
(err) => {
|
|
601
657
|
nextResolvedAt = performance.now();
|
|
658
|
+
nextRejectedWithRewrite = isRewrite(err);
|
|
602
659
|
throw err;
|
|
603
660
|
},
|
|
604
661
|
);
|
|
@@ -612,6 +669,11 @@ export async function executeMiddleware<TEnv>(
|
|
|
612
669
|
// middleware-over-middleware and the core handler underneath. Pass-through
|
|
613
670
|
// when neither surface is active.
|
|
614
671
|
let result: Response | void;
|
|
672
|
+
// A thrown rewrite() is control flow like a thrown Response: held past
|
|
673
|
+
// the span (which settles as success) and rethrown below, so it unwinds
|
|
674
|
+
// to the dispatcher (rsc/handler.ts) with no further code of this
|
|
675
|
+
// middleware run.
|
|
676
|
+
let rewrite: Rewrite | undefined;
|
|
615
677
|
try {
|
|
616
678
|
result = await observePhase(PHASES.middleware(metricLabel), async () => {
|
|
617
679
|
try {
|
|
@@ -627,6 +689,10 @@ export async function executeMiddleware<TEnv>(
|
|
|
627
689
|
// this convention (segment-resolution/helpers.ts keeps result handling
|
|
628
690
|
// outside the span). Real errors propagate past the span.
|
|
629
691
|
if (error instanceof Response) return error;
|
|
692
|
+
if (isRewrite(error)) {
|
|
693
|
+
rewrite = error;
|
|
694
|
+
return undefined;
|
|
695
|
+
}
|
|
630
696
|
throw error;
|
|
631
697
|
}
|
|
632
698
|
});
|
|
@@ -635,6 +701,16 @@ export async function executeMiddleware<TEnv>(
|
|
|
635
701
|
// error paths (idempotent guard in finishMiddleware).
|
|
636
702
|
finishMiddleware();
|
|
637
703
|
}
|
|
704
|
+
// A rewrite after this middleware's next() started the downstream chain
|
|
705
|
+
// (resolved, still running, or failed) would leave that render running
|
|
706
|
+
// against the context the next pass moves, or re-dispatch onto what it
|
|
707
|
+
// already pushed, and drop the response it built. Only a rewrite after
|
|
708
|
+
// next() rejected with a Rewrite (nothing rendered) unwinds.
|
|
709
|
+
if (rewrite && nextPromise && !nextRejectedWithRewrite) {
|
|
710
|
+
throw rewriteAfterNextError(entry.handler, rewrite);
|
|
711
|
+
}
|
|
712
|
+
if (rewrite) throw rewrite;
|
|
713
|
+
if (isRewrite(result)) throw returnedRewriteError(entry.handler);
|
|
638
714
|
|
|
639
715
|
// Record post-next() processing time when middleware did work after
|
|
640
716
|
// the downstream chain resolved (e.g. adding headers, logging).
|
|
@@ -805,10 +881,12 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
805
881
|
// runner settles rango.middleware as success, not STATUS_ERROR (same
|
|
806
882
|
// reasoning as executeMiddleware's main chain). Real errors propagate.
|
|
807
883
|
if (error instanceof Response) return error;
|
|
884
|
+
if (isRewrite(error)) throw rewriteNotSupported(error, "intercept");
|
|
808
885
|
throw error;
|
|
809
886
|
}
|
|
810
887
|
},
|
|
811
888
|
);
|
|
889
|
+
if (isRewrite(result)) throw returnedRewriteError(middleware);
|
|
812
890
|
|
|
813
891
|
if (result instanceof Response) {
|
|
814
892
|
earlyResponse = result;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isLoaderSegmentId } from "../browser/react/filter-segment-order.js";
|
|
1
2
|
import { requestHeaders } from "../server/request-headers.js";
|
|
2
3
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
3
4
|
|
|
@@ -145,7 +146,7 @@ export async function resolveNavigation(
|
|
|
145
146
|
|
|
146
147
|
const filteredSegmentIds = clientSegmentIds.filter((id) => {
|
|
147
148
|
if (id.includes(".@")) return false;
|
|
148
|
-
if (
|
|
149
|
+
if (isLoaderSegmentId(id)) return false;
|
|
149
150
|
return true;
|
|
150
151
|
});
|
|
151
152
|
|
|
@@ -20,7 +20,7 @@ import { makeIsAction } from "./is-action.js";
|
|
|
20
20
|
/**
|
|
21
21
|
* Options for revalidation evaluation
|
|
22
22
|
*/
|
|
23
|
-
interface EvaluateRevalidationOptions<TEnv> {
|
|
23
|
+
export interface EvaluateRevalidationOptions<TEnv> {
|
|
24
24
|
/** Current segment to evaluate */
|
|
25
25
|
segment: ResolvedSegment;
|
|
26
26
|
/** Previous route params (from route match, not segment) */
|
|
@@ -153,6 +153,31 @@ function sortSuffixParams(node: TrieNode): void {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Give every static-children map in the trie a null prototype, in place, and
|
|
158
|
+
* return the trie (#1109). The walker indexes `node.s` with raw URL segments,
|
|
159
|
+
* so a map with Object.prototype would answer `/constructor` or `/__proto__`
|
|
160
|
+
* with an inherited member. The builder creates null-prototype maps; a trie
|
|
161
|
+
* that arrives as JSON (the build manifest) or across the dev runner boundary
|
|
162
|
+
* gets them here. Object.setPrototypeOf keeps a JSON-parsed map's fast
|
|
163
|
+
* properties, where Object.create(null) starts in dictionary mode.
|
|
164
|
+
*/
|
|
165
|
+
export function nullPrototypeStaticChildren(node: TrieNode): TrieNode {
|
|
166
|
+
if (node.s) {
|
|
167
|
+
Object.setPrototypeOf(node.s, null);
|
|
168
|
+
for (const child of Object.values(node.s)) {
|
|
169
|
+
nullPrototypeStaticChildren(child);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (node.xp) {
|
|
173
|
+
for (const child of Object.values(node.xp)) {
|
|
174
|
+
nullPrototypeStaticChildren(child.c);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (node.p) nullPrototypeStaticChildren(node.p.c);
|
|
178
|
+
return node;
|
|
179
|
+
}
|
|
180
|
+
|
|
156
181
|
/**
|
|
157
182
|
* Insert a route into the trie. Optional params expand into two branches at
|
|
158
183
|
* registration time (skip-first, then present), so each terminal lives at the
|
|
@@ -271,7 +296,9 @@ function insertSegments(
|
|
|
271
296
|
const segment = segments[index];
|
|
272
297
|
|
|
273
298
|
if (segment.type === "static") {
|
|
274
|
-
|
|
299
|
+
// Null prototype: a segment named `constructor` or `__proto__` is an own
|
|
300
|
+
// key, never an inherited member to descend into and write a leaf on.
|
|
301
|
+
if (!node.s) node.s = Object.create(null) as Record<string, TrieNode>;
|
|
275
302
|
if (!node.s[segment.value]) node.s[segment.value] = {};
|
|
276
303
|
insertSegments(
|
|
277
304
|
node.s[segment.value],
|
|
@@ -164,17 +164,19 @@ export async function resolveLoaders<TEnv>(
|
|
|
164
164
|
// key off it.
|
|
165
165
|
...stampAwaitBeforeFlush(loaderEntry),
|
|
166
166
|
loaderData: deps.wrapLoaderPromise(
|
|
167
|
-
runInsideLoaderScope(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
167
|
+
runInsideLoaderScope(
|
|
168
|
+
() =>
|
|
169
|
+
resolveLoaderData(
|
|
170
|
+
loaderEntry,
|
|
171
|
+
ctx,
|
|
172
|
+
ctx.pathname,
|
|
173
|
+
// The bake key rides for flagged loaders too: ssr:false
|
|
174
|
+
// bakes at capture regardless of the entry's loading() lane
|
|
175
|
+
// (loader-cache.ts capture branch) and its HIT-tail seed
|
|
176
|
+
// overlay needs the same key.
|
|
177
|
+
bakeLane || loaderEntry.bake === true ? segmentId : null,
|
|
178
|
+
),
|
|
179
|
+
loader.$$id,
|
|
178
180
|
),
|
|
179
181
|
entry,
|
|
180
182
|
segmentId,
|
|
@@ -216,13 +218,15 @@ export async function resolveLoaders<TEnv>(
|
|
|
216
218
|
const { loader } = loaderEntry;
|
|
217
219
|
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
218
220
|
const wrapped = deps.wrapLoaderPromise(
|
|
219
|
-
runInsideLoaderScope(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
221
|
+
runInsideLoaderScope(
|
|
222
|
+
() =>
|
|
223
|
+
resolveLoaderData(
|
|
224
|
+
loaderEntry,
|
|
225
|
+
ctx,
|
|
226
|
+
ctx.pathname,
|
|
227
|
+
bakeLane || loaderEntry.bake === true ? segmentId : null,
|
|
228
|
+
),
|
|
229
|
+
loader.$$id,
|
|
226
230
|
),
|
|
227
231
|
entry,
|
|
228
232
|
segmentId,
|
|
@@ -118,6 +118,7 @@ import {
|
|
|
118
118
|
isShellCaptureActive,
|
|
119
119
|
createMaskedLoaderPromise,
|
|
120
120
|
} from "./loader-mask.js";
|
|
121
|
+
import { loaderOwner } from "./prefetch-deferral.js";
|
|
121
122
|
// Lazy-loaded to avoid pulling @vitejs/plugin-rsc/rsc into modules that
|
|
122
123
|
// import segment-resolution but never use loader caching.
|
|
123
124
|
let _serializeResult: typeof import("../../cache/segment-codec.js").serializeResult;
|
|
@@ -658,7 +659,7 @@ function executeLoaderData<TEnv>(
|
|
|
658
659
|
// Handle pushes: the store and the owning segment are read synchronously
|
|
659
660
|
// at kickoff, as createLoaderExecutor does for the body's own pushes.
|
|
660
661
|
const handleStore = _getRequestContext()?._handleStore;
|
|
661
|
-
const owningSegmentId =
|
|
662
|
+
const owningSegmentId = loaderOwner(ctx, loaderId);
|
|
662
663
|
// Record only pushes from THIS loader's body and the loaders it awaits via
|
|
663
664
|
// ctx.use: the handler and sibling loaders push into the same store
|
|
664
665
|
// concurrently. A dependency can still run on a HIT for another reader
|