@rangojs/router 0.27.0 → 0.28.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 +1 -1
- package/dist/types/__internal.d.ts +7 -1
- package/dist/types/browser/event-controller.d.ts +2 -1
- package/dist/types/browser/history-state.d.ts +3 -9
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/segment-reconciler.d.ts +6 -4
- package/dist/types/browser/types.d.ts +2 -1
- package/dist/types/browser/wire-protocol.d.ts +25 -0
- package/dist/types/cache/cache-key-utils.d.ts +11 -5
- package/dist/types/cache/cache-scope.d.ts +34 -22
- package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
- package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
- package/dist/types/cache/handle-snapshot.d.ts +9 -4
- package/dist/types/cache/segment-codec.d.ts +14 -0
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/tag-invalidation.d.ts +10 -0
- package/dist/types/cache/taint.d.ts +5 -3
- package/dist/types/cache/types.d.ts +7 -3
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
- package/dist/types/prefetch-markers.d.ts +5 -14
- package/dist/types/prerender/store.d.ts +31 -0
- package/dist/types/prerender/warm-request.d.ts +3 -3
- package/dist/types/prerender.d.ts +2 -2
- package/dist/types/route-name.d.ts +5 -0
- package/dist/types/router/error-handling.d.ts +4 -4
- package/dist/types/router/instrument.d.ts +4 -3
- package/dist/types/router/intercept-resolution.d.ts +25 -9
- package/dist/types/router/match-context.d.ts +4 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +10 -20
- package/dist/types/router/match-middleware/cache-lookup.d.ts +34 -86
- package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
- package/dist/types/router/prerender-match.d.ts +21 -6
- package/dist/types/router/preview-match.d.ts +2 -7
- package/dist/types/router/request-classification.d.ts +5 -0
- package/dist/types/router/router-context.d.ts +7 -46
- package/dist/types/router/router-interfaces.d.ts +8 -14
- package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
- package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +93 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution.d.ts +1 -2
- package/dist/types/router/segment-wrappers.d.ts +11 -48
- package/dist/types/router/telemetry.d.ts +2 -0
- package/dist/types/router/types.d.ts +10 -0
- package/dist/types/rsc/full-payload.d.ts +2 -2
- package/dist/types/rsc/handler-context.d.ts +6 -6
- package/dist/types/rsc/json-route-result.d.ts +5 -9
- package/dist/types/rsc/request-stages.d.ts +203 -0
- package/dist/types/rsc/response-cache-serve.d.ts +7 -12
- package/dist/types/rsc/response-route-handler.d.ts +2 -2
- package/dist/types/rsc/routine-plan.d.ts +1 -1
- package/dist/types/rsc/server-action.d.ts +2 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +1 -1
- package/dist/types/rsc/shell-capture.d.ts +132 -15
- package/dist/types/rsc/shell-serve.d.ts +37 -12
- package/dist/types/server/context.d.ts +9 -8
- package/dist/types/server/handle-store.d.ts +16 -0
- package/dist/types/server/render-pass.d.ts +72 -0
- package/dist/types/server/request-context.d.ts +24 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +9 -1
- package/dist/types/types/handler-context.d.ts +9 -1
- package/dist/types/types/segments.d.ts +1 -1
- package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
- package/dist/types/vite/discovery/build-env.d.ts +22 -0
- package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
- package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
- package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
- package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +12 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
- package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +8 -47
- package/dist/types/vite/temp-rsc-server.d.ts +39 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
- package/dist/types/wire-params.d.ts +34 -0
- package/dist/vite/index.js +3809 -3710
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +8 -5
- package/skills/caching/SKILL.md +37 -18
- package/skills/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +17 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/ppr/SKILL.md +18 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +5 -1
- package/skills/server-actions/SKILL.md +8 -1
- package/skills/testing/response-routes.md +6 -4
- package/skills/use-cache/SKILL.md +46 -8
- package/src/__internal.ts +7 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +5 -3
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +14 -13
- package/src/browser/navigation-client.ts +18 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- package/src/browser/prefetch/cache.ts +2 -1
- package/src/browser/prefetch/fetch.ts +8 -16
- package/src/browser/prefetch/queue.ts +4 -4
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/filter-segment-order.ts +2 -2
- package/src/browser/rsc-router.tsx +1 -0
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +50 -57
- package/src/browser/types.ts +2 -1
- package/src/browser/wire-protocol.ts +41 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +110 -50
- package/src/cache/cache-scope.ts +110 -64
- package/src/cache/cf/cf-cache-constants.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +486 -874
- package/src/cache/cf/cf-kv-utils.ts +1 -1
- package/src/cache/document-cache.ts +34 -13
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +21 -2
- package/src/cache/taint.ts +5 -3
- package/src/cache/types.ts +7 -3
- package/src/cache/vercel/vercel-cache-store.ts +243 -310
- package/src/handles/pushed-by.ts +1 -1
- package/src/prefetch-markers.ts +5 -16
- package/src/prerender/store.ts +76 -0
- package/src/prerender/warm-request.ts +8 -4
- package/src/prerender.ts +2 -2
- package/src/route-name.ts +8 -0
- package/src/router/error-handling.ts +4 -4
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +77 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +4 -5
- package/src/router/match-context.ts +20 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +41 -53
- package/src/router/match-middleware/cache-lookup.ts +675 -726
- package/src/router/match-middleware/cache-store.ts +4 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -4
- package/src/router/prerender-match.ts +100 -44
- package/src/router/preview-match.ts +6 -41
- package/src/router/request-classification.ts +13 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +10 -95
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/loader-cache.ts +14 -9
- package/src/router/segment-resolution/loader-mask.ts +5 -5
- package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
- package/src/router/segment-resolution/resolution-pass.ts +137 -0
- package/src/router/segment-resolution/revalidation.ts +897 -776
- package/src/router/segment-resolution.ts +4 -13
- package/src/router/segment-wrappers.ts +51 -269
- package/src/router/telemetry.ts +5 -0
- package/src/router/types.ts +10 -0
- package/src/router.ts +12 -13
- package/src/rsc/full-payload.ts +6 -5
- package/src/rsc/handler-context.ts +8 -14
- package/src/rsc/handler.ts +126 -507
- package/src/rsc/json-route-result.ts +5 -9
- package/src/rsc/loader-fetch.ts +6 -5
- package/src/rsc/progressive-enhancement.ts +5 -0
- package/src/rsc/request-stages.ts +757 -0
- package/src/rsc/response-cache-serve.ts +10 -14
- package/src/rsc/response-route-handler.ts +13 -5
- package/src/rsc/rewrite-passes.ts +2 -1
- package/src/rsc/routine-plan.ts +1 -1
- package/src/rsc/rsc-rendering.ts +164 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +1 -1
- package/src/rsc/shell-capture.ts +212 -169
- package/src/rsc/shell-serve.ts +121 -18
- package/src/rsc/ssr-setup.ts +5 -4
- package/src/segment-system.tsx +3 -3
- package/src/server/context.ts +22 -25
- package/src/server/handle-store.ts +55 -0
- package/src/server/render-pass.ts +291 -0
- package/src/server/request-context.ts +26 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +23 -36
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +12 -1
- package/src/types/segments.ts +1 -1
- package/src/vite/discovery/build-discovery-phase.ts +204 -0
- package/src/vite/discovery/build-env.ts +148 -0
- package/src/vite/discovery/bundle-scan-phase.ts +128 -0
- package/src/vite/discovery/dev-prerender-cache.ts +8 -3
- package/src/vite/discovery/dev-server-phase.ts +1776 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +1 -1
- package/src/vite/discovery/prerender-collection.ts +24 -39
- package/src/vite/discovery/state.ts +15 -0
- package/src/vite/plugins/version-plugin.ts +1 -1
- package/src/vite/plugins/virtual-entries.ts +1 -1
- package/src/vite/router-discovery.ts +29 -2428
- package/src/vite/temp-rsc-server.ts +223 -0
- package/src/vite/utils/prerender-utils.ts +47 -0
- package/src/wire-params.ts +35 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
|
@@ -114,6 +114,7 @@ import { createShellImplicitDocScope } from "../../cache/cache-scope.js";
|
|
|
114
114
|
import { recordSegmentTags } from "../../cache/cache-tag.js";
|
|
115
115
|
import { executionStart } from "../../cache/tag-invalidation.js";
|
|
116
116
|
import { getRouterContext } from "../router-context.js";
|
|
117
|
+
import { interceptRecordKey } from "../intercept-resolution.js";
|
|
117
118
|
import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
118
119
|
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
119
120
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
@@ -284,7 +285,7 @@ export function withCacheStore<TEnv>(
|
|
|
284
285
|
ctx.pathname,
|
|
285
286
|
ctx.matched.params,
|
|
286
287
|
allSegmentsToCache,
|
|
287
|
-
ctx.
|
|
288
|
+
interceptRecordKey(ctx.interceptResult),
|
|
288
289
|
// A capture's doc record lives only in the shell entry, which
|
|
289
290
|
// putShell gates by the capture start; skipping it here would
|
|
290
291
|
// read as a render that produced no record.
|
|
@@ -354,7 +355,7 @@ function recordShellCaptureDocRecord<TEnv>(
|
|
|
354
355
|
segments: ResolvedSegment[],
|
|
355
356
|
): void {
|
|
356
357
|
const requestCtx = getRequestContext();
|
|
357
|
-
if (!requestCtx?.
|
|
358
|
+
if (!requestCtx?._shellCaptureState?.run) return;
|
|
358
359
|
const marker = requestCtx._shellImplicitCache;
|
|
359
360
|
if (!marker?.store) return;
|
|
360
361
|
const scope = ctx.cacheScope;
|
|
@@ -381,12 +382,7 @@ function recordShellCaptureDocRecord<TEnv>(
|
|
|
381
382
|
// pushes settled, so the record's handle encode never races them.
|
|
382
383
|
requestCtx.onResponse((response) => {
|
|
383
384
|
requestCtx.waitUntil(() =>
|
|
384
|
-
docScope.cacheRoute(
|
|
385
|
-
ctx.pathname,
|
|
386
|
-
ctx.matched.params,
|
|
387
|
-
segments,
|
|
388
|
-
ctx.isIntercept,
|
|
389
|
-
),
|
|
385
|
+
docScope.cacheRoute(ctx.pathname, ctx.matched.params, segments),
|
|
390
386
|
);
|
|
391
387
|
return response;
|
|
392
388
|
});
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
*/
|
|
104
104
|
import type { ResolvedSegment } from "../../types.js";
|
|
105
105
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
106
|
+
import { resolutionPassInput } from "../match-context.js";
|
|
106
107
|
import { getRouterContext } from "../router-context.js";
|
|
107
108
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
108
109
|
import { debugLog } from "../logging.js";
|
|
@@ -175,21 +176,10 @@ export function withInterceptResolution<TEnv>(
|
|
|
175
176
|
const Store = ctx.Store;
|
|
176
177
|
const interceptSegments = await Store.run(() =>
|
|
177
178
|
resolveInterceptEntry(
|
|
179
|
+
resolutionPassInput(ctx),
|
|
178
180
|
ctx.interceptResult!.intercept,
|
|
179
181
|
ctx.interceptResult!.entry,
|
|
180
|
-
ctx.matched.params,
|
|
181
|
-
ctx.handlerContext,
|
|
182
182
|
true, // belongsToRoute
|
|
183
|
-
{
|
|
184
|
-
clientSegmentIds: ctx.clientSegmentSet,
|
|
185
|
-
prevParams: ctx.prevParams,
|
|
186
|
-
request: ctx.request,
|
|
187
|
-
prevUrl: ctx.prevUrl,
|
|
188
|
-
nextUrl: ctx.url,
|
|
189
|
-
routeKey: ctx.routeKey,
|
|
190
|
-
actionContext: ctx.actionContext,
|
|
191
|
-
stale: ctx.stale,
|
|
192
|
-
},
|
|
193
183
|
),
|
|
194
184
|
);
|
|
195
185
|
|
|
@@ -242,20 +232,10 @@ async function handleCacheHitIntercept<TEnv>(
|
|
|
242
232
|
const loaderSegments = resolveInterceptLoadersOnly
|
|
243
233
|
? await ctx.Store.run(() =>
|
|
244
234
|
resolveInterceptLoadersOnly(
|
|
235
|
+
resolutionPassInput(ctx),
|
|
245
236
|
ctx.interceptResult!.intercept,
|
|
246
237
|
ctx.interceptResult!.entry,
|
|
247
|
-
ctx.handlerContext,
|
|
248
238
|
true, // belongsToRoute
|
|
249
|
-
{
|
|
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,
|
|
258
|
-
},
|
|
259
239
|
),
|
|
260
240
|
)
|
|
261
241
|
: [];
|
|
@@ -29,15 +29,16 @@
|
|
|
29
29
|
* yes no
|
|
30
30
|
* | |
|
|
31
31
|
* v v
|
|
32
|
-
*
|
|
33
|
-
* Segments Segments
|
|
34
|
-
* | |
|
|
32
|
+
* FRESH pass partial pass
|
|
35
33
|
* | | (compares with prev state)
|
|
36
34
|
* | | (handles null components)
|
|
37
35
|
* | |
|
|
38
36
|
* +-----------+
|
|
39
37
|
* |
|
|
40
38
|
* v
|
|
39
|
+
* resolveAllSegments()
|
|
40
|
+
* |
|
|
41
|
+
* v
|
|
41
42
|
* +---------------------------+
|
|
42
43
|
* | Update state: |
|
|
43
44
|
* | - state.segments |
|
|
@@ -54,13 +55,14 @@
|
|
|
54
55
|
* RESOLUTION MODES
|
|
55
56
|
* ================
|
|
56
57
|
*
|
|
58
|
+
* Both use resolveAllSegments().
|
|
59
|
+
*
|
|
57
60
|
* Full Match (document request):
|
|
58
|
-
* -
|
|
59
|
-
*
|
|
61
|
+
* - A FRESH pass (freshPassInput): no revalidation logic (nothing to
|
|
62
|
+
* compare against)
|
|
60
63
|
* - Simple resolution of all route entries
|
|
61
64
|
*
|
|
62
65
|
* Partial Match (navigation):
|
|
63
|
-
* - Uses resolveAllSegmentsWithRevalidation()
|
|
64
66
|
* - Compares current vs previous params/URL
|
|
65
67
|
* - Sets component = null for segments client already has
|
|
66
68
|
* - Respects custom revalidation rules
|
|
@@ -90,6 +92,8 @@ import type { ResolvedSegment } from "../../types.js";
|
|
|
90
92
|
import type { EntryData } from "../../server/context.js";
|
|
91
93
|
import { _getRequestContext } from "../../server/request-context.js";
|
|
92
94
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
95
|
+
import { resolutionPassInput } from "../match-context.js";
|
|
96
|
+
import { freshPassInput } from "../segment-resolution/resolution-pass.js";
|
|
93
97
|
import { getRouterContext } from "../router-context.js";
|
|
94
98
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
95
99
|
|
|
@@ -155,64 +159,33 @@ export function withSegmentResolution<TEnv>(
|
|
|
155
159
|
reqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
156
160
|
}
|
|
157
161
|
|
|
158
|
-
const {
|
|
159
|
-
getRouterContext<TEnv>();
|
|
160
|
-
|
|
161
|
-
const Store = ctx.Store;
|
|
162
|
-
|
|
163
|
-
if (ctx.isFullMatch) {
|
|
164
|
-
const segments = await Store.run(() =>
|
|
165
|
-
resolveAllSegments(
|
|
166
|
-
ctx.entries,
|
|
167
|
-
ctx.routeKey,
|
|
168
|
-
ctx.matched.params,
|
|
169
|
-
ctx.handlerContext,
|
|
170
|
-
ctx.loaderPromises,
|
|
171
|
-
),
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
state.segments = segments;
|
|
175
|
-
state.matchedIds = segments.map((s: { id: string }) => s.id);
|
|
162
|
+
const { resolveAllSegments } = getRouterContext<TEnv>();
|
|
176
163
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
ctx.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
ctx.clientSegmentSet,
|
|
192
|
-
ctx.prevParams,
|
|
193
|
-
ctx.request,
|
|
194
|
-
ctx.prevUrl,
|
|
195
|
-
ctx.url,
|
|
196
|
-
ctx.actionContext,
|
|
197
|
-
ctx.interceptResult,
|
|
198
|
-
ctx.localRouteName,
|
|
199
|
-
ctx.pathname,
|
|
200
|
-
ctx.stale,
|
|
201
|
-
),
|
|
202
|
-
);
|
|
164
|
+
// A full match (interceptResult null) is a FRESH pass.
|
|
165
|
+
const result = await ctx.Store.run(() =>
|
|
166
|
+
resolveAllSegments(
|
|
167
|
+
ctx.isFullMatch
|
|
168
|
+
? freshPassInput(ctx.handlerContext, ctx.routeKey, ctx.matched.params)
|
|
169
|
+
: resolutionPassInput(ctx),
|
|
170
|
+
ctx.entries,
|
|
171
|
+
{
|
|
172
|
+
interceptResult: ctx.interceptResult,
|
|
173
|
+
localRouteName: ctx.localRouteName,
|
|
174
|
+
pathname: ctx.pathname,
|
|
175
|
+
},
|
|
176
|
+
),
|
|
177
|
+
);
|
|
203
178
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (reqCtx) {
|
|
209
|
-
reqCtx._resolveRenderBarrier(result.segments);
|
|
210
|
-
}
|
|
179
|
+
state.segments = result.segments;
|
|
180
|
+
state.matchedIds = ctx.isFullMatch
|
|
181
|
+
? result.segments.map((s: { id: string }) => s.id)
|
|
182
|
+
: result.matchedIds;
|
|
211
183
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
184
|
+
if (reqCtx) {
|
|
185
|
+
reqCtx._resolveRenderBarrier(result.segments);
|
|
186
|
+
}
|
|
187
|
+
for (const segment of result.segments) {
|
|
188
|
+
yield segment;
|
|
216
189
|
}
|
|
217
190
|
|
|
218
191
|
if (ms) {
|
package/src/router/middleware.ts
CHANGED
|
@@ -542,6 +542,51 @@ function mergeResponse(
|
|
|
542
542
|
return merged;
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
+
/**
|
|
546
|
+
* The response holder past one middleware. Attached, it reads and writes its
|
|
547
|
+
* middleware's holder, so a chain shares one response. A middleware that
|
|
548
|
+
* settles without that run's response while it runs (it returns or throws
|
|
549
|
+
* its own Response, throws, or rewrites: a timeout fallback, a stale
|
|
550
|
+
* document cache entry's background re-run, cache/document-cache.ts), or
|
|
551
|
+
* that calls next() after it returned, detaches that run: the rest of the
|
|
552
|
+
* chain builds its own response, and the middleware ahead keep what the
|
|
553
|
+
* middleware settled with. With one holder, a
|
|
554
|
+
* forgiving middleware ahead of the cache returned the re-run's response,
|
|
555
|
+
* whose body the cache had drained into the store.
|
|
556
|
+
*/
|
|
557
|
+
class DownstreamHolder implements ResponseHolder {
|
|
558
|
+
private own: Response | null = null;
|
|
559
|
+
private attached = true;
|
|
560
|
+
|
|
561
|
+
constructor(private readonly parent: ResponseHolder) {}
|
|
562
|
+
|
|
563
|
+
get response(): Response | null {
|
|
564
|
+
return this.attached ? this.parent.response : this.own;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
set response(response: Response | null) {
|
|
568
|
+
if (this.attached) this.parent.response = response;
|
|
569
|
+
else this.own = response;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** From `from`: what the run has seen so far, or a late run's stub. */
|
|
573
|
+
detach(from: Response | null = this.parent.response): void {
|
|
574
|
+
this.own = from;
|
|
575
|
+
this.attached = false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* A middleware settled without the response of the run its next() started
|
|
581
|
+
* (its own Response, a throw, a rewrite error) while that run still goes.
|
|
582
|
+
*/
|
|
583
|
+
function detachIfRunning(
|
|
584
|
+
holder: DownstreamHolder | undefined,
|
|
585
|
+
nextResolvedAt: number | undefined,
|
|
586
|
+
): void {
|
|
587
|
+
if (holder && nextResolvedAt === undefined) holder.detach();
|
|
588
|
+
}
|
|
589
|
+
|
|
545
590
|
/**
|
|
546
591
|
* Execute middleware chain
|
|
547
592
|
*
|
|
@@ -575,23 +620,23 @@ export async function executeMiddleware<TEnv>(
|
|
|
575
620
|
const stubResponse = new Response(null, { status: 200 });
|
|
576
621
|
const responseHolder: ResponseHolder = { response: stubResponse };
|
|
577
622
|
|
|
578
|
-
const next = async (): Promise<Response> => {
|
|
623
|
+
const next = async (holder: ResponseHolder): Promise<Response> => {
|
|
579
624
|
if (index >= middlewares.length) {
|
|
580
625
|
// End of chain - call actual RSC handler
|
|
581
626
|
const response = await finalHandler();
|
|
582
627
|
|
|
583
628
|
if (isWebSocketUpgradeResponse(response)) {
|
|
584
|
-
|
|
629
|
+
holder.response = response;
|
|
585
630
|
return response;
|
|
586
631
|
}
|
|
587
632
|
|
|
588
633
|
// Chain ran to completion: stub headers overwrite (stubOverridesNonCookie)
|
|
589
634
|
// and reqCtx stub headers are merged in.
|
|
590
|
-
|
|
635
|
+
holder.response = mergeResponse(response, stubResponse.headers, {
|
|
591
636
|
stubOverridesNonCookie: true,
|
|
592
637
|
});
|
|
593
638
|
|
|
594
|
-
return
|
|
639
|
+
return holder.response;
|
|
595
640
|
}
|
|
596
641
|
|
|
597
642
|
const middlewareOrdinal = index;
|
|
@@ -601,7 +646,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
601
646
|
env,
|
|
602
647
|
params,
|
|
603
648
|
variables,
|
|
604
|
-
|
|
649
|
+
holder,
|
|
605
650
|
reverse,
|
|
606
651
|
);
|
|
607
652
|
const metricStart = performance.now();
|
|
@@ -631,6 +676,8 @@ export async function executeMiddleware<TEnv>(
|
|
|
631
676
|
// throws after its next(), the render's own error), so a rewrite thrown
|
|
632
677
|
// after it would re-dispatch onto the used handle store.
|
|
633
678
|
let nextRejectedWithRewrite = false;
|
|
679
|
+
let downstreamHolder: DownstreamHolder | undefined;
|
|
680
|
+
let settled = false;
|
|
634
681
|
const wrappedNext = (): Promise<Response> => {
|
|
635
682
|
if (nextPromise) {
|
|
636
683
|
throw new Error(
|
|
@@ -638,7 +685,9 @@ export async function executeMiddleware<TEnv>(
|
|
|
638
685
|
);
|
|
639
686
|
}
|
|
640
687
|
finishMiddleware();
|
|
641
|
-
|
|
688
|
+
downstreamHolder = new DownstreamHolder(holder);
|
|
689
|
+
if (settled) downstreamHolder.detach(stubResponse);
|
|
690
|
+
const downstream = next(downstreamHolder);
|
|
642
691
|
nextPromise = downstream.then(
|
|
643
692
|
(res) => {
|
|
644
693
|
nextResolvedAt = performance.now();
|
|
@@ -687,10 +736,14 @@ export async function executeMiddleware<TEnv>(
|
|
|
687
736
|
throw error;
|
|
688
737
|
}
|
|
689
738
|
});
|
|
739
|
+
} catch (error) {
|
|
740
|
+
detachIfRunning(downstreamHolder, nextResolvedAt);
|
|
741
|
+
throw error;
|
|
690
742
|
} finally {
|
|
691
743
|
// Settle the middleware own-time metric once on both the success and
|
|
692
744
|
// error paths (idempotent guard in finishMiddleware).
|
|
693
745
|
finishMiddleware();
|
|
746
|
+
settled = true;
|
|
694
747
|
}
|
|
695
748
|
// A rewrite after this middleware's next() started the downstream chain
|
|
696
749
|
// (resolved, still running, or failed) would leave that render running
|
|
@@ -698,10 +751,14 @@ export async function executeMiddleware<TEnv>(
|
|
|
698
751
|
// already pushed, and drop the response it built. Only a rewrite after
|
|
699
752
|
// next() rejected with a Rewrite (nothing rendered) unwinds.
|
|
700
753
|
if (rewrite && nextPromise && !nextRejectedWithRewrite) {
|
|
754
|
+
detachIfRunning(downstreamHolder, nextResolvedAt);
|
|
701
755
|
throw rewriteAfterNextError(entry.handler, rewrite);
|
|
702
756
|
}
|
|
703
757
|
if (rewrite) throw rewrite;
|
|
704
|
-
if (isRewrite(result))
|
|
758
|
+
if (isRewrite(result)) {
|
|
759
|
+
detachIfRunning(downstreamHolder, nextResolvedAt);
|
|
760
|
+
throw returnedRewriteError(entry.handler);
|
|
761
|
+
}
|
|
705
762
|
|
|
706
763
|
// Record post-next() processing time when middleware did work after
|
|
707
764
|
// the downstream chain resolved (e.g. adding headers, logging).
|
|
@@ -723,8 +780,9 @@ export async function executeMiddleware<TEnv>(
|
|
|
723
780
|
// RequestContext stub headers (from cookies().set()) into the
|
|
724
781
|
// returned Response so they are not lost.
|
|
725
782
|
if (result instanceof Response) {
|
|
783
|
+
detachIfRunning(downstreamHolder, nextResolvedAt);
|
|
726
784
|
if (isWebSocketUpgradeResponse(result)) {
|
|
727
|
-
|
|
785
|
+
holder.response = result;
|
|
728
786
|
return result;
|
|
729
787
|
}
|
|
730
788
|
// Explicit short-circuit: the returned Response's own headers win
|
|
@@ -732,7 +790,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
732
790
|
const merged = mergeResponse(result, stubResponse.headers, {
|
|
733
791
|
stubOverridesNonCookie: false,
|
|
734
792
|
});
|
|
735
|
-
|
|
793
|
+
holder.response = merged;
|
|
736
794
|
return merged;
|
|
737
795
|
}
|
|
738
796
|
|
|
@@ -749,7 +807,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
749
807
|
// If middleware called next(), await it and return the response
|
|
750
808
|
if (nextPromise) {
|
|
751
809
|
await nextPromise;
|
|
752
|
-
return
|
|
810
|
+
return holder.response!;
|
|
753
811
|
}
|
|
754
812
|
|
|
755
813
|
// Middleware didn't call next() and didn't return a Response - that's an error
|
|
@@ -763,7 +821,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
763
821
|
);
|
|
764
822
|
};
|
|
765
823
|
|
|
766
|
-
await next();
|
|
824
|
+
await next(responseHolder);
|
|
767
825
|
|
|
768
826
|
// Use the final response from responseHolder (may have been modified by middleware)
|
|
769
827
|
const finalResponse = responseHolder.response;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isLoaderSegmentId } from "../browser/react/filter-segment-order.js";
|
|
2
2
|
import { requestHeaders } from "../server/request-headers.js";
|
|
3
|
-
import { STALE_PARAM } from "../prefetch-markers.js";
|
|
4
3
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
4
|
+
import { WIRE } from "../wire-params.js";
|
|
5
5
|
|
|
6
6
|
export interface NavigationSnapshot {
|
|
7
7
|
prevUrl: URL;
|
|
@@ -106,7 +106,7 @@ export function getInterceptSourceHeader(request: Request): string | null {
|
|
|
106
106
|
* left, never what renders, so a forged value runs or skips no middleware.
|
|
107
107
|
*/
|
|
108
108
|
function getShownRouteUrl(url: URL): URL | null {
|
|
109
|
-
const shown = url.searchParams.get(
|
|
109
|
+
const shown = url.searchParams.get(WIRE.rewriteTarget);
|
|
110
110
|
if (!shown) return null;
|
|
111
111
|
try {
|
|
112
112
|
const parsed = new URL(shown, url.origin);
|
|
@@ -134,8 +134,8 @@ export async function resolveNavigation(
|
|
|
134
134
|
deps: ResolveNavigationDeps,
|
|
135
135
|
): Promise<NavigationSnapshot | null> {
|
|
136
136
|
const clientSegmentIds =
|
|
137
|
-
url.searchParams.get(
|
|
138
|
-
const stale = url.searchParams.get(
|
|
137
|
+
url.searchParams.get(WIRE.segments)?.split(",").filter(Boolean) || [];
|
|
138
|
+
const stale = url.searchParams.get(WIRE.stale) === "true";
|
|
139
139
|
const previousUrl = getNavigationContextHeader(request, url.origin);
|
|
140
140
|
const interceptSourceUrl = getInterceptSourceHeader(request);
|
|
141
141
|
const isHmr = !!requestHeaders(request).get("X-RSC-HMR");
|