@rangojs/router 0.27.0 → 0.29.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/action-fence.d.ts +2 -1
- package/dist/types/browser/event-controller.d.ts +8 -2
- package/dist/types/browser/history-state.d.ts +3 -9
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/react/use-action.d.ts +7 -2
- package/dist/types/browser/react/use-handle.d.ts +19 -16
- package/dist/types/browser/react/use-link-status.d.ts +5 -2
- package/dist/types/browser/segment-reconciler.d.ts +32 -4
- package/dist/types/browser/types.d.ts +24 -5
- package/dist/types/browser/wire-protocol.d.ts +26 -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/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 +11 -5
- 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 +6 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +13 -22
- 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/navigation-snapshot.d.ts +7 -0
- 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/helpers.d.ts +19 -3
- 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 +95 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
- 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 +11 -1
- 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 +53 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/render-route.d.ts +43 -8
- package/dist/types/testing/serve-shell-request.d.ts +8 -1
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +30 -3
- 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 +39 -0
- package/dist/vite/index.js +3822 -3727
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +8 -5
- package/skills/caching/SKILL.md +37 -18
- package/skills/client-urls/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/hooks/SKILL.md +1 -1
- package/skills/hooks/data.md +8 -1
- package/skills/hooks/handle-and-actions.md +33 -2
- package/skills/hooks/navigation.md +121 -15
- package/skills/hooks/state.md +6 -4
- package/skills/hooks/urls.md +16 -5
- package/skills/intercept/SKILL.md +23 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/observability/SKILL.md +30 -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 +15 -1
- package/skills/testing/client-components.md +25 -18
- package/skills/testing/response-routes.md +6 -4
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/use-cache/SKILL.md +46 -8
- package/skills/view-transitions/SKILL.md +1 -1
- package/src/__internal.ts +7 -1
- package/src/browser/action-fence.ts +2 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +37 -13
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +59 -26
- package/src/browser/navigation-client.ts +20 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- package/src/browser/network-error-handler.ts +4 -0
- package/src/browser/partial-update.ts +33 -6
- 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/react/use-action.ts +24 -48
- package/src/browser/react/use-handle.ts +22 -18
- package/src/browser/react/use-link-status.ts +48 -60
- package/src/browser/react/use-navigation.ts +92 -41
- package/src/browser/rsc-router.tsx +1 -0
- package/src/browser/segment-reconciler.ts +109 -53
- package/src/browser/server-action-bridge.ts +81 -61
- package/src/browser/types.ts +25 -5
- package/src/browser/wire-protocol.ts +43 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +132 -75
- 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 +64 -46
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/read-through-swr.ts +20 -18
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +6 -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 +22 -10
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +82 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +5 -5
- package/src/router/match-context.ts +23 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +46 -57
- package/src/router/match-middleware/cache-lookup.ts +677 -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 +14 -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/helpers.ts +49 -13
- package/src/router/segment-resolution/loader-cache.ts +15 -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 +139 -0
- package/src/router/segment-resolution/revalidation.ts +939 -789
- package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
- 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 +11 -1
- package/src/router.ts +19 -17
- 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 +759 -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 +275 -227
- 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 +75 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/testing/dispatch.ts +226 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/render-route.tsx +199 -13
- package/src/testing/serve-shell-request.ts +36 -40
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +30 -3
- 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 +40 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
|
@@ -204,15 +204,49 @@ export interface ErrorReportContext {
|
|
|
204
204
|
routeKey?: string;
|
|
205
205
|
env?: any;
|
|
206
206
|
isPartial?: boolean;
|
|
207
|
-
requestStartTime?: number;
|
|
208
207
|
telemetry?: TelemetrySink;
|
|
209
208
|
}
|
|
210
209
|
|
|
210
|
+
type Thrower = { id: string; type: ErrorInfo["segmentType"] };
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Note that `id` threw `error`, for an error its owning entry's boundary
|
|
214
|
+
* catches (a parallel slot's sync throw, an orphan layout's): onError names
|
|
215
|
+
* the segment that threw, as a streamed slot's rejection does. Kept on the
|
|
216
|
+
* request context, never the module: a reused error object (a constant, a
|
|
217
|
+
* memoized rejection) can be thrown by another request, or by a Prerender
|
|
218
|
+
* pass that rethrows before catchSegmentError reads it. Returns `error`, to
|
|
219
|
+
* rethrow.
|
|
220
|
+
*/
|
|
221
|
+
export function noteThrower(
|
|
222
|
+
error: unknown,
|
|
223
|
+
id: string,
|
|
224
|
+
type: ErrorInfo["segmentType"],
|
|
225
|
+
): unknown {
|
|
226
|
+
const reqCtx = _getRequestContext();
|
|
227
|
+
if (reqCtx && error !== null && typeof error === "object") {
|
|
228
|
+
(reqCtx._throwers ??= new WeakMap()).set(error, { id, type });
|
|
229
|
+
}
|
|
230
|
+
return error;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The segment noteThrower recorded for `error`, read once. */
|
|
234
|
+
function takeThrower(error: unknown): Thrower | undefined {
|
|
235
|
+
const throwers = _getRequestContext()?._throwers;
|
|
236
|
+
if (!throwers || error === null || typeof error !== "object") return;
|
|
237
|
+
const noted = throwers.get(error) as Thrower | undefined;
|
|
238
|
+
throwers.delete(error);
|
|
239
|
+
return noted;
|
|
240
|
+
}
|
|
241
|
+
|
|
211
242
|
/**
|
|
212
243
|
* Handle a caught error during segment resolution by creating an
|
|
213
244
|
* error or not-found segment with the nearest boundary.
|
|
214
245
|
*
|
|
215
246
|
* Called by resolveWithErrorBoundary to produce error/notFound segments.
|
|
247
|
+
* `entry` is where the boundary is looked up; `thrower` names the segment
|
|
248
|
+
* that threw when it is not `entry` (an intercept's slot, under its declaring
|
|
249
|
+
* entry), for the report, the server print and the client's ErrorInfo.
|
|
216
250
|
*/
|
|
217
251
|
export function catchSegmentError<TEnv>(
|
|
218
252
|
error: unknown,
|
|
@@ -221,7 +255,11 @@ export function catchSegmentError<TEnv>(
|
|
|
221
255
|
deps: SegmentResolutionDeps<TEnv>,
|
|
222
256
|
report?: ErrorReportContext,
|
|
223
257
|
pathname?: string,
|
|
258
|
+
thrower?: Thrower,
|
|
224
259
|
): ResolvedSegment {
|
|
260
|
+
const named = thrower ?? takeThrower(error);
|
|
261
|
+
const segmentId = named?.id ?? entry.shortCode;
|
|
262
|
+
const segmentType: ErrorInfo["segmentType"] = named?.type ?? entry.type;
|
|
225
263
|
const reportError = (
|
|
226
264
|
handledByBoundary: boolean,
|
|
227
265
|
metadata?: Record<string, unknown>,
|
|
@@ -232,13 +270,12 @@ export function catchSegmentError<TEnv>(
|
|
|
232
270
|
url: report.url,
|
|
233
271
|
routeKey: report.routeKey,
|
|
234
272
|
params,
|
|
235
|
-
segmentId
|
|
236
|
-
segmentType:
|
|
273
|
+
segmentId,
|
|
274
|
+
segmentType: segmentType as any,
|
|
237
275
|
env: report.env,
|
|
238
276
|
isPartial: report.isPartial,
|
|
239
277
|
handledByBoundary,
|
|
240
278
|
metadata,
|
|
241
|
-
requestStartTime: report.requestStartTime,
|
|
242
279
|
});
|
|
243
280
|
if (report.telemetry) {
|
|
244
281
|
const errorObj =
|
|
@@ -247,8 +284,8 @@ export function catchSegmentError<TEnv>(
|
|
|
247
284
|
type: "handler.error",
|
|
248
285
|
timestamp: performance.now(),
|
|
249
286
|
requestId: report.request ? getRequestId(report.request) : undefined,
|
|
250
|
-
segmentId
|
|
251
|
-
segmentType
|
|
287
|
+
segmentId,
|
|
288
|
+
segmentType,
|
|
252
289
|
error: errorObj,
|
|
253
290
|
handledByBoundary,
|
|
254
291
|
pathname,
|
|
@@ -274,8 +311,8 @@ export function catchSegmentError<TEnv>(
|
|
|
274
311
|
|
|
275
312
|
const notFoundInfo = createNotFoundInfo(
|
|
276
313
|
error,
|
|
277
|
-
|
|
278
|
-
|
|
314
|
+
segmentId,
|
|
315
|
+
segmentType,
|
|
279
316
|
pathname,
|
|
280
317
|
);
|
|
281
318
|
|
|
@@ -285,7 +322,7 @@ export function catchSegmentError<TEnv>(
|
|
|
285
322
|
});
|
|
286
323
|
|
|
287
324
|
debugLog("segment", "notFound boundary handled error", {
|
|
288
|
-
segmentId
|
|
325
|
+
segmentId,
|
|
289
326
|
message: notFoundInfo.message,
|
|
290
327
|
});
|
|
291
328
|
|
|
@@ -302,19 +339,18 @@ export function catchSegmentError<TEnv>(
|
|
|
302
339
|
// The client gets the masked ErrorInfo; the original prints here.
|
|
303
340
|
printServerError(
|
|
304
341
|
error,
|
|
305
|
-
{ label: "Handler error", name:
|
|
342
|
+
{ label: "Handler error", name: segmentId, pathname },
|
|
306
343
|
_getRequestContext(),
|
|
307
344
|
);
|
|
308
345
|
|
|
309
346
|
const fallback = deps.findNearestErrorBoundary(entry);
|
|
310
|
-
const
|
|
311
|
-
const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
|
|
347
|
+
const errorInfo = createErrorInfo(error, segmentId, segmentType);
|
|
312
348
|
const effectiveFallback = fallback ?? DefaultErrorFallback;
|
|
313
349
|
|
|
314
350
|
reportError(!!effectiveFallback);
|
|
315
351
|
|
|
316
352
|
debugLog("segment", "error boundary handled error", {
|
|
317
|
-
segmentId
|
|
353
|
+
segmentId,
|
|
318
354
|
boundary: fallback ? "custom" : "default",
|
|
319
355
|
message: errorInfo.message,
|
|
320
356
|
});
|
|
@@ -72,7 +72,7 @@ import { runIdentityExempt } from "../../cache/cache-exec-scope.js";
|
|
|
72
72
|
import {
|
|
73
73
|
getRequestContext,
|
|
74
74
|
_getRequestContext,
|
|
75
|
-
|
|
75
|
+
runAsBackgroundWork,
|
|
76
76
|
type RequestContext,
|
|
77
77
|
} from "../../server/request-context.js";
|
|
78
78
|
import { observePhase, PHASES } from "../instrument.js";
|
|
@@ -458,8 +458,8 @@ function recordOwnerKey(cachedLoaderId: string): () => string {
|
|
|
458
458
|
* PPR loader lane rule — the canonical source statement; other sites point
|
|
459
459
|
* here (consumer docs: skills/ppr/SKILL.md "The loader lane rule";
|
|
460
460
|
* docs/design/loader-container-bake.md). This is the single funnel every
|
|
461
|
-
* loader segment path routes through (
|
|
462
|
-
*
|
|
461
|
+
* loader segment path routes through (resolveOwnerLoaders: the full match's
|
|
462
|
+
* FRESH pass, cache-hit and revalidation resolveLoadersOnly, and
|
|
463
463
|
* intercept loaders in intercept-resolution.ts, which pass no bake key and
|
|
464
464
|
* never run under a shell capture), so the lane is decided here, per LOADER,
|
|
465
465
|
* never by the entry's loading():
|
|
@@ -473,7 +473,7 @@ function recordOwnerKey(cachedLoaderId: string): () => string {
|
|
|
473
473
|
* plain objects, arrays and JSX props are masked (mask-nested.ts) and stay
|
|
474
474
|
* live holes at the consumer's own Suspense — per-request material must be
|
|
475
475
|
* promise-shaped (the #692 cross-session scar). The masked container
|
|
476
|
-
* registers on `
|
|
476
|
+
* registers on `_shellCaptureState.loaderRecords`: it holds the capture gate
|
|
477
477
|
* (bounded by `ppr.captureTimeout`) and pins into the snapshot's loader
|
|
478
478
|
* family, and on a HIT the recorded container is overlaid onto the fresh
|
|
479
479
|
* run (below) so the payload matches the frozen prelude outside the holes.
|
|
@@ -511,7 +511,10 @@ export function resolveLoaderData<TEnv>(
|
|
|
511
511
|
maskNestedContainerThenables(container),
|
|
512
512
|
);
|
|
513
513
|
maskedPromise.catch(() => {});
|
|
514
|
-
reqCtx?.
|
|
514
|
+
reqCtx?._shellCaptureState?.loaderRecords.set(
|
|
515
|
+
bakeSegmentKey,
|
|
516
|
+
maskedPromise,
|
|
517
|
+
);
|
|
515
518
|
// Its data bakes into the shell, so its tags tag the shell.
|
|
516
519
|
linkLoaderTagsTo(SHELL_BAKE_TAG_OWNER, loaderEntry.loader.$$id, reqCtx);
|
|
517
520
|
return maskedPromise;
|
|
@@ -629,14 +632,14 @@ function executeLoaderData<TEnv>(
|
|
|
629
632
|
|
|
630
633
|
// Dedup the cache read-through across repeated resolutions of the SAME
|
|
631
634
|
// loaderId in one request. An orphan layout with parallel slots inherits its
|
|
632
|
-
// parent route's loaders, so resolveOrphanLayout
|
|
635
|
+
// parent route's loaders, so resolveOrphanLayout re-resolves the
|
|
633
636
|
// parent's loaders under a different shortCode — calling resolveLoaderData
|
|
634
637
|
// again for the same loaderId. The cache key (loader:{loaderId}:{host}
|
|
635
638
|
// {pathname}:{sortedParams}) does not include the shortCode and ctx/params
|
|
636
639
|
// are identical, so both resolutions produce the same data. Reuse the already
|
|
637
640
|
// in-flight dataPromise instead of issuing a second getItem/setItem (e.g. a
|
|
638
641
|
// second KV round-trip) for one logical cached loader. The shortCode only
|
|
639
|
-
// affects the emitted segmentId in
|
|
642
|
+
// affects the emitted segmentId in resolveOwnerLoaders, not the cached value.
|
|
640
643
|
const existing = overrides.get(loaderId);
|
|
641
644
|
if (existing) return existing;
|
|
642
645
|
|
|
@@ -809,11 +812,14 @@ function executeLoaderData<TEnv>(
|
|
|
809
812
|
// (read-through-swr routes the full task through wrapBackground) — so
|
|
810
813
|
// the loader's rango.loader span, its fetch/KV platform spans, and the
|
|
811
814
|
// store write all nest under one explanatory parent instead of dangling
|
|
812
|
-
// under the ended foreground phases. Inside
|
|
815
|
+
// under the ended foreground phases. Inside the request context so
|
|
813
816
|
// observePhase can read tracing on workerd (ALS detaches in waitUntil).
|
|
817
|
+
// No visitor made the invalidations it runs: they set no fresh-reads
|
|
818
|
+
// cookie, and onError marks its errors `background`
|
|
819
|
+
// (runAsBackgroundWork).
|
|
814
820
|
wrapBackground: (run) => {
|
|
815
821
|
revalidating = true;
|
|
816
|
-
return
|
|
822
|
+
return runAsBackgroundWork(requestCtxForExecute, () =>
|
|
817
823
|
observePhase(PHASES.background("loader-revalidation"), run),
|
|
818
824
|
);
|
|
819
825
|
},
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* consuming Suspense subtree postpones as a hole, and the serve pass runs them
|
|
8
8
|
* fresh into it.
|
|
9
9
|
*
|
|
10
|
-
* Capture mode is signalled by `requestCtx.
|
|
10
|
+
* Capture mode is signalled by `requestCtx._shellCaptureState.run`, set ONLY on
|
|
11
11
|
* the derived request context of the background capture task (shell-capture.ts);
|
|
12
|
-
* the foreground render never
|
|
12
|
+
* the foreground render never has it. This module is the single home for the
|
|
13
13
|
* mask so every loader execution site gates the same way (loader-cache.ts
|
|
14
|
-
* `resolveLoaderData`,
|
|
14
|
+
* `resolveLoaderData`, revalidation.ts `resolveOwnerLoaders`).
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import {
|
|
@@ -22,7 +22,7 @@ import { isRenderableLoading } from "../../types/boundaries.js";
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* True when the current render is the active PPR shell capture and route loaders
|
|
25
|
-
* must be masked rather than executed. Reads `
|
|
25
|
+
* must be masked rather than executed. Reads `_shellCaptureState` off the ALS
|
|
26
26
|
* request context (the capture task re-establishes its derived context via
|
|
27
27
|
* runWithRequestContext), so it is accurate at the loader resolution sites, which
|
|
28
28
|
* run synchronously inside the pipeline's context frame.
|
|
@@ -30,7 +30,7 @@ import { isRenderableLoading } from "../../types/boundaries.js";
|
|
|
30
30
|
export function isShellCaptureActive(
|
|
31
31
|
reqCtx: RequestContext<any> | undefined = _getRequestContext(),
|
|
32
32
|
): boolean {
|
|
33
|
-
return reqCtx?.
|
|
33
|
+
return reqCtx?._shellCaptureState?.run === true;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// createMaskedLoaderPromise lives in the leaf module mask-nested.ts, beside
|
|
@@ -36,7 +36,8 @@ import {
|
|
|
36
36
|
import { requestHeaders } from "../../server/request-headers.js";
|
|
37
37
|
import type { HandlerContext, InternalHandlerContext } from "../../types.js";
|
|
38
38
|
import type { CacheScope } from "../../cache/cache-scope.js";
|
|
39
|
-
import {
|
|
39
|
+
import { PREFETCH_HEADER } from "../../prefetch-markers.js";
|
|
40
|
+
import { WIRE } from "../../wire-params.js";
|
|
40
41
|
import { isInterceptSegment } from "../../browser/intercept-utils.js";
|
|
41
42
|
|
|
42
43
|
/**
|
|
@@ -50,7 +51,7 @@ export function requestKind(
|
|
|
50
51
|
request: Request,
|
|
51
52
|
rawUrl: URL,
|
|
52
53
|
): "prefetch" | "fill" | undefined {
|
|
53
|
-
if (rawUrl.searchParams.has(
|
|
54
|
+
if (rawUrl.searchParams.has(WIRE.fill)) return "fill";
|
|
54
55
|
return requestHeaders(request).has(PREFETCH_HEADER) ? "prefetch" : undefined;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -114,14 +115,6 @@ export interface PrefetchDeferral {
|
|
|
114
115
|
* a navigation to the page lists.
|
|
115
116
|
*/
|
|
116
117
|
below?: true;
|
|
117
|
-
/**
|
|
118
|
-
* The loaders this prefetch deferred, by `$$id` (registerDeferredLoader).
|
|
119
|
-
* A deferred loader runs only if something reads it: a handler's or another
|
|
120
|
-
* loader's ctx.use() starts it through its registration
|
|
121
|
-
* (startDeferredLoader), and revalidation.ts deliverStartedLoaders delivers
|
|
122
|
-
* what was started by the end of resolution.
|
|
123
|
-
*/
|
|
124
|
-
deferredLoaders?: Map<string, DeferredLoader>;
|
|
125
118
|
}
|
|
126
119
|
|
|
127
120
|
/**
|
|
@@ -149,7 +142,14 @@ export interface RecordUse {
|
|
|
149
142
|
readonly refresh: boolean;
|
|
150
143
|
}
|
|
151
144
|
|
|
152
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* A DSL loader registered and not started (InternalHandlerContext
|
|
147
|
+
* ._deferredLoaders): a prefetch deferred it, or a pass that skips loaders
|
|
148
|
+
* (`skipLoaders`) did not run it. It runs only if something reads it: a
|
|
149
|
+
* handler's or another loader's ctx.use() starts it through its registration
|
|
150
|
+
* (startDeferredLoader), and revalidation.ts deliverStartedLoaders delivers
|
|
151
|
+
* what a prefetch started by the end of resolution.
|
|
152
|
+
*/
|
|
153
153
|
export interface DeferredLoader {
|
|
154
154
|
/**
|
|
155
155
|
* The segment that registers it. Its run files its handle pushes there,
|
|
@@ -232,7 +232,7 @@ export function resolveDeferralScope(
|
|
|
232
232
|
loaderIds.add(loaderEntry.loader.$$id);
|
|
233
233
|
}
|
|
234
234
|
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
235
|
-
// A slot's segment id, as
|
|
235
|
+
// A slot's segment id, as resolveParallelSegments
|
|
236
236
|
// (revalidation.ts) forms it.
|
|
237
237
|
const slotIds = flagsLoading(parallelEntry)
|
|
238
238
|
? getParallelSlotEntries(entry.parallel)
|
|
@@ -245,7 +245,7 @@ export function resolveDeferralScope(
|
|
|
245
245
|
for (const orphan of entry.layout) {
|
|
246
246
|
if (chain.has(orphan)) continue;
|
|
247
247
|
// An orphan layout's handler is awaited after its owner's ran
|
|
248
|
-
// (revalidation.ts
|
|
248
|
+
// (revalidation.ts resolveOrphanLayout): never a unit,
|
|
249
249
|
// its loaders are still behind its fallback.
|
|
250
250
|
collect(
|
|
251
251
|
orphan,
|
|
@@ -446,8 +446,8 @@ export function recordUse(
|
|
|
446
446
|
}
|
|
447
447
|
|
|
448
448
|
/**
|
|
449
|
-
* Register a loader
|
|
450
|
-
* it and how to start it there (
|
|
449
|
+
* Register a loader resolution does not start, with the segment that
|
|
450
|
+
* registers it and how to start it there (DeferredLoader). The first
|
|
451
451
|
* registration wins, as the loader's one run per request does.
|
|
452
452
|
*/
|
|
453
453
|
export function registerDeferredLoader(
|
|
@@ -456,18 +456,21 @@ export function registerDeferredLoader(
|
|
|
456
456
|
owner: string,
|
|
457
457
|
start: () => Promise<unknown>,
|
|
458
458
|
): void {
|
|
459
|
-
const
|
|
460
|
-
if (
|
|
461
|
-
(
|
|
459
|
+
const internal = ctx as InternalHandlerContext<any, any>;
|
|
460
|
+
if (!internal._deferredLoaders?.has(loaderId)) {
|
|
461
|
+
(internal._deferredLoaders ??= new Map()).set(loaderId, { owner, start });
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
|
|
465
465
|
/**
|
|
466
|
-
* A reader asks for a loader
|
|
467
|
-
* start it through its registration and return the run, or
|
|
468
|
-
* the read takes the ordinary path. Without this the reader
|
|
469
|
-
* bare,
|
|
470
|
-
*
|
|
466
|
+
* A reader asks for a registered loader (registerDeferredLoader) and nothing
|
|
467
|
+
* has started it: start it through its registration and return the run, or
|
|
468
|
+
* undefined when the read takes the ordinary path. Without this the reader
|
|
469
|
+
* ran the loader bare, as a handler-invoked loader: in a prefetch without its
|
|
470
|
+
* cache() binding, so the segment delivered afterwards read the binding (two
|
|
471
|
+
* values on one page, an extra run on a HIT); in a route record's background
|
|
472
|
+
* re-render outside its DSL scope, so the record kept its handle pushes and
|
|
473
|
+
* every HIT showed them twice.
|
|
471
474
|
*
|
|
472
475
|
* A started cache() binding of the loader (`_loaderCacheOverrides`) is never
|
|
473
476
|
* redirected here: the read is that binding's own MISS execution, and
|
|
@@ -478,7 +481,7 @@ export function startDeferredLoader(
|
|
|
478
481
|
loaderId: string,
|
|
479
482
|
): Promise<unknown> | undefined {
|
|
480
483
|
const internal = ctx as InternalHandlerContext<any, any>;
|
|
481
|
-
const deferred = internal.
|
|
484
|
+
const deferred = internal._deferredLoaders?.get(loaderId);
|
|
482
485
|
if (
|
|
483
486
|
!deferred ||
|
|
484
487
|
deferred.started ||
|
|
@@ -492,8 +495,8 @@ export function startDeferredLoader(
|
|
|
492
495
|
|
|
493
496
|
/**
|
|
494
497
|
* The segment a run of `loaderId` files its handle pushes under: the one
|
|
495
|
-
* that registers it when
|
|
496
|
-
* resolved.
|
|
498
|
+
* that registers it when it is registered (registerDeferredLoader), else the
|
|
499
|
+
* segment being resolved.
|
|
497
500
|
*/
|
|
498
501
|
export function loaderOwner(
|
|
499
502
|
ctx: HandlerContext<any, any>,
|
|
@@ -501,7 +504,7 @@ export function loaderOwner(
|
|
|
501
504
|
): string | undefined {
|
|
502
505
|
const internal = ctx as InternalHandlerContext<any, any>;
|
|
503
506
|
return (
|
|
504
|
-
internal.
|
|
507
|
+
internal._deferredLoaders?.get(loaderId)?.owner ??
|
|
505
508
|
internal._currentSegmentId
|
|
506
509
|
);
|
|
507
510
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResolutionPass: what one walk over a matched chain reads.
|
|
3
|
+
*
|
|
4
|
+
* Every function of the segment walker (revalidation.ts, intercept-resolution.ts)
|
|
5
|
+
* resolves against the same request: the handler context, the segments the
|
|
6
|
+
* client holds, the previous and next location, the action, the staleness and
|
|
7
|
+
* the router's deps. They take this one object instead of threading the same
|
|
8
|
+
* ten values through each signature in a fixed order, where two adjacent
|
|
9
|
+
* `URL`s or `Record<string, string>`s could swap without a type error.
|
|
10
|
+
*
|
|
11
|
+
* A full match (a document, the background re-render of a route record, a
|
|
12
|
+
* build-time prerender) is a FRESH pass: see FRESH below.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { HandlerContext } from "../../types.js";
|
|
16
|
+
import type { ActionContext, SegmentResolutionDeps } from "../types.js";
|
|
17
|
+
import { NOTHING_HELD } from "./prefetch-deferral.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The segment ids the client holds, the set resolution works on (match-api.ts
|
|
21
|
+
* may delete a route's id from the listed set to force a render). Empty on a
|
|
22
|
+
* full refetch.
|
|
23
|
+
*/
|
|
24
|
+
export type ClientHeld = ReadonlySet<string>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The client of a full match: it holds nothing and asks for nothing to be
|
|
28
|
+
* kept. Compared by identity (`pass.client === FRESH`), never by size: a
|
|
29
|
+
* partial request whose client holds nothing (NOTHING_HELD, an empty
|
|
30
|
+
* `_rsc_segments`) still asks the revalidate() rules. On a FRESH pass every
|
|
31
|
+
* segment is new without a decision, trace entry or `revalidation.decision`
|
|
32
|
+
* event, and the walker takes the full match's branches (revalidation.ts,
|
|
33
|
+
* each marked FRESH).
|
|
34
|
+
*/
|
|
35
|
+
export const FRESH: ClientHeld = new Set<string>();
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A pass without the router's deps: what match middleware knows about the
|
|
39
|
+
* request. createSegmentWrappers (segment-wrappers.ts) binds the deps.
|
|
40
|
+
*/
|
|
41
|
+
export interface ResolutionPassInput<TEnv = any> {
|
|
42
|
+
readonly context: HandlerContext<any, TEnv>;
|
|
43
|
+
readonly client: ClientHeld;
|
|
44
|
+
/** The matched route's params, stamped on every segment the walk emits. */
|
|
45
|
+
readonly params: Record<string, string>;
|
|
46
|
+
readonly prevParams: Record<string, string>;
|
|
47
|
+
/** Undefined only on a build-time FRESH pass (freshPassInput). */
|
|
48
|
+
readonly request: Request | undefined;
|
|
49
|
+
readonly prevUrl: URL;
|
|
50
|
+
readonly nextUrl: URL;
|
|
51
|
+
readonly routeKey: string;
|
|
52
|
+
readonly actionContext?: ActionContext;
|
|
53
|
+
readonly stale?: boolean;
|
|
54
|
+
/** NavigationSnapshot.heldBoundary: the walk cuts there, sending no segment. */
|
|
55
|
+
readonly heldBoundary?: string;
|
|
56
|
+
/**
|
|
57
|
+
* No loader is started and no loader segment is emitted: build time
|
|
58
|
+
* (prerender-match.ts) and the background re-render of a route record
|
|
59
|
+
* (background-revalidation.ts), whose records never hold loaders.
|
|
60
|
+
* resolveOwnerLoaders registers each loader instead: in the re-render a
|
|
61
|
+
* handler's read starts it as its declaration; at build time a read
|
|
62
|
+
* throws (setupBuildUse).
|
|
63
|
+
*/
|
|
64
|
+
readonly skipLoaders?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* A render error rethrows instead of rendering the error boundary, and a
|
|
67
|
+
* streamed handler's rejection is not recovered. Build time only, so a
|
|
68
|
+
* failure (or `throw new Skip()`) reaches the build instead of being baked
|
|
69
|
+
* into a frozen 200 (issue #587).
|
|
70
|
+
*/
|
|
71
|
+
readonly throwOnError?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The request is a navigation, though the pass is FRESH: the background
|
|
74
|
+
* re-render of a record a navigation read stale or wrote
|
|
75
|
+
* (background-revalidation.ts). An entry's error reports isPartial, as the
|
|
76
|
+
* navigation's own pass does.
|
|
77
|
+
*/
|
|
78
|
+
readonly partialRequest?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ResolutionPass<TEnv = any> extends ResolutionPassInput<TEnv> {
|
|
82
|
+
readonly deps: SegmentResolutionDeps<TEnv>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The input of a full match's pass (FRESH). At build time the prerender
|
|
87
|
+
* context's `request` is a throwing getter (handler-context.ts
|
|
88
|
+
* createPrerenderContext): the pass carries undefined instead.
|
|
89
|
+
*/
|
|
90
|
+
export function freshPassInput<TEnv>(
|
|
91
|
+
context: HandlerContext<any, TEnv>,
|
|
92
|
+
routeKey: string,
|
|
93
|
+
params: Record<string, string>,
|
|
94
|
+
options?: {
|
|
95
|
+
skipLoaders?: boolean;
|
|
96
|
+
throwOnError?: boolean;
|
|
97
|
+
partialRequest?: boolean;
|
|
98
|
+
},
|
|
99
|
+
): ResolutionPassInput<TEnv> {
|
|
100
|
+
let request: Request | undefined;
|
|
101
|
+
try {
|
|
102
|
+
request = context.request;
|
|
103
|
+
} catch {}
|
|
104
|
+
return {
|
|
105
|
+
context,
|
|
106
|
+
client: FRESH,
|
|
107
|
+
params,
|
|
108
|
+
prevParams: {},
|
|
109
|
+
request,
|
|
110
|
+
prevUrl: context.url,
|
|
111
|
+
nextUrl: context.url,
|
|
112
|
+
routeKey,
|
|
113
|
+
...options,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The input of a pass whose client holds nothing: every segment is new and
|
|
119
|
+
* renders. The background re-render of an intercept (background-revalidation.ts)
|
|
120
|
+
* resolves with it.
|
|
121
|
+
*/
|
|
122
|
+
export function heldNothingPassInput<TEnv>(
|
|
123
|
+
context: HandlerContext<any, TEnv>,
|
|
124
|
+
routeKey: string,
|
|
125
|
+
params: Record<string, string>,
|
|
126
|
+
options?: { skipLoaders?: boolean },
|
|
127
|
+
): ResolutionPassInput<TEnv> {
|
|
128
|
+
return {
|
|
129
|
+
context,
|
|
130
|
+
client: NOTHING_HELD,
|
|
131
|
+
params,
|
|
132
|
+
prevParams: {},
|
|
133
|
+
request: context.request,
|
|
134
|
+
prevUrl: context.url,
|
|
135
|
+
nextUrl: context.url,
|
|
136
|
+
routeKey,
|
|
137
|
+
...options,
|
|
138
|
+
};
|
|
139
|
+
}
|