@rangojs/router 0.0.0-experimental.18 → 0.0.0-experimental.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -8
- package/dist/bin/rango.js +105 -18
- package/dist/vite/index.js +227 -93
- package/package.json +15 -14
- package/skills/hooks/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +79 -0
- package/skills/layout/SKILL.md +62 -2
- package/skills/loader/SKILL.md +94 -1
- package/skills/middleware/SKILL.md +81 -0
- package/skills/parallel/SKILL.md +57 -2
- package/skills/prerender/SKILL.md +187 -17
- package/skills/route/SKILL.md +42 -1
- package/skills/router-setup/SKILL.md +77 -0
- package/src/__internal.ts +1 -1
- package/src/bin/rango.ts +38 -19
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/event-controller.ts +25 -27
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +1 -1
- package/src/browser/link-interceptor.ts +0 -3
- package/src/browser/merge-segment-loaders.ts +9 -2
- package/src/browser/navigation-bridge.ts +46 -13
- package/src/browser/navigation-client.ts +32 -61
- package/src/browser/navigation-store.ts +1 -31
- package/src/browser/navigation-transaction.ts +46 -207
- package/src/browser/partial-update.ts +102 -150
- package/src/browser/{prefetch-cache.ts → prefetch/cache.ts} +23 -4
- package/src/browser/{prefetch-fetch.ts → prefetch/fetch.ts} +36 -8
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/{prefetch-queue.ts → prefetch/queue.ts} +10 -3
- package/src/browser/react/Link.tsx +28 -23
- package/src/browser/react/NavigationProvider.tsx +9 -1
- package/src/browser/react/index.ts +2 -6
- package/src/browser/react/location-state-shared.ts +1 -1
- package/src/browser/react/location-state.ts +2 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/use-action.ts +9 -1
- package/src/browser/react/use-handle.ts +3 -25
- package/src/browser/react/use-params.ts +2 -4
- package/src/browser/react/use-pathname.ts +2 -3
- package/src/browser/react/use-router.ts +1 -1
- package/src/browser/react/use-search-params.ts +2 -1
- package/src/browser/react/use-segments.ts +7 -60
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +29 -23
- package/src/browser/scroll-restoration.ts +10 -7
- package/src/browser/server-action-bridge.ts +115 -96
- package/src/browser/types.ts +1 -31
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +5 -0
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/route-types/codegen.ts +13 -4
- package/src/build/route-types/include-resolution.ts +13 -0
- package/src/build/route-types/per-module-writer.ts +15 -3
- package/src/build/route-types/router-processing.ts +45 -3
- package/src/build/runtime-discovery.ts +13 -1
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +132 -96
- package/src/cache/cache-scope.ts +71 -73
- package/src/cache/cf/cf-cache-store.ts +9 -4
- package/src/cache/document-cache.ts +72 -47
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/memory-segment-store.ts +18 -7
- package/src/cache/profile-registry.ts +43 -8
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +101 -112
- package/src/cache/taint.ts +26 -0
- package/src/client.tsx +53 -30
- package/src/errors.ts +6 -1
- package/src/handle.ts +1 -1
- package/src/handles/MetaTags.tsx +5 -2
- package/src/host/cookie-handler.ts +8 -3
- package/src/host/router.ts +14 -1
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +33 -1
- package/src/index.ts +27 -0
- package/src/loader.rsc.ts +12 -4
- package/src/loader.ts +8 -0
- package/src/prerender/store.ts +4 -3
- package/src/prerender.ts +76 -18
- package/src/reverse.ts +11 -7
- package/src/root-error-boundary.tsx +30 -26
- package/src/route-definition/dsl-helpers.ts +9 -6
- package/src/route-definition/redirect.ts +15 -3
- package/src/route-map-builder.ts +38 -2
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +7 -0
- package/src/router/content-negotiation.ts +1 -1
- package/src/router/debug-manifest.ts +16 -3
- package/src/router/handler-context.ts +94 -15
- package/src/router/intercept-resolution.ts +6 -4
- package/src/router/lazy-includes.ts +4 -0
- package/src/router/loader-resolution.ts +1 -0
- package/src/router/logging.ts +100 -3
- package/src/router/manifest.ts +32 -3
- package/src/router/match-api.ts +61 -7
- package/src/router/match-context.ts +3 -0
- package/src/router/match-handlers.ts +185 -11
- package/src/router/match-middleware/background-revalidation.ts +65 -85
- package/src/router/match-middleware/cache-lookup.ts +69 -4
- package/src/router/match-middleware/cache-store.ts +2 -0
- package/src/router/match-pipelines.ts +8 -43
- package/src/router/middleware-types.ts +7 -0
- package/src/router/middleware.ts +93 -8
- package/src/router/pattern-matching.ts +41 -5
- package/src/router/prerender-match.ts +34 -6
- package/src/router/preview-match.ts +7 -1
- package/src/router/revalidation.ts +61 -2
- package/src/router/router-context.ts +15 -0
- package/src/router/router-interfaces.ts +34 -0
- package/src/router/router-options.ts +200 -0
- package/src/router/segment-resolution/fresh.ts +123 -30
- package/src/router/segment-resolution/helpers.ts +19 -0
- package/src/router/segment-resolution/loader-cache.ts +37 -146
- package/src/router/segment-resolution/revalidation.ts +358 -94
- package/src/router/segment-wrappers.ts +3 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/types.ts +7 -1
- package/src/router.ts +155 -11
- package/src/rsc/handler-context.ts +11 -0
- package/src/rsc/handler.ts +380 -88
- package/src/rsc/helpers.ts +25 -16
- package/src/rsc/loader-fetch.ts +84 -42
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +232 -19
- package/src/rsc/response-route-handler.ts +37 -26
- package/src/rsc/rsc-rendering.ts +12 -5
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +134 -58
- package/src/rsc/types.ts +8 -0
- package/src/search-params.ts +22 -10
- package/src/server/context.ts +53 -5
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +66 -9
- package/src/server/loader-registry.ts +11 -46
- package/src/server/request-context.ts +90 -9
- package/src/ssr/index.tsx +63 -27
- package/src/static-handler.ts +7 -0
- package/src/theme/ThemeProvider.tsx +6 -1
- package/src/theme/index.ts +1 -6
- package/src/theme/theme-context.ts +1 -28
- package/src/theme/theme-script.ts +2 -1
- package/src/types/cache-types.ts +5 -0
- package/src/types/error-types.ts +3 -0
- package/src/types/global-namespace.ts +9 -0
- package/src/types/handler-context.ts +35 -13
- package/src/types/loader-types.ts +7 -0
- package/src/types/route-entry.ts +28 -0
- package/src/urls/include-helper.ts +49 -8
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +30 -12
- package/src/urls/path-helper.ts +17 -2
- package/src/urls/pattern-types.ts +21 -1
- package/src/urls/response-types.ts +27 -2
- package/src/urls/type-extraction.ts +23 -15
- package/src/use-loader.tsx +12 -4
- package/src/vite/discovery/bundle-postprocess.ts +12 -7
- package/src/vite/discovery/discover-routers.ts +30 -18
- package/src/vite/discovery/prerender-collection.ts +24 -27
- package/src/vite/discovery/route-types-writer.ts +7 -7
- package/src/vite/discovery/virtual-module-codegen.ts +5 -2
- package/src/vite/plugins/client-ref-hashing.ts +3 -3
- package/src/vite/plugins/use-cache-transform.ts +91 -3
- package/src/vite/rango.ts +3 -3
- package/src/vite/router-discovery.ts +99 -36
- package/src/vite/utils/prerender-utils.ts +21 -0
- package/src/vite/utils/shared-utils.ts +3 -1
- package/src/browser/request-controller.ts +0 -164
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- /package/src/browser/{prefetch-observer.ts → prefetch/observer.ts} +0 -0
|
@@ -22,7 +22,11 @@ import type {
|
|
|
22
22
|
SegmentRevalidationResult,
|
|
23
23
|
ActionContext,
|
|
24
24
|
} from "../types.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
debugLog,
|
|
27
|
+
pushRevalidationTraceEntry,
|
|
28
|
+
isTraceActive,
|
|
29
|
+
} from "../logging.js";
|
|
26
30
|
import { resolveLoaderData } from "./loader-cache.js";
|
|
27
31
|
import {
|
|
28
32
|
handleHandlerResult,
|
|
@@ -31,6 +35,84 @@ import {
|
|
|
31
35
|
resolveLayoutComponent,
|
|
32
36
|
resolveWithErrorBoundary,
|
|
33
37
|
} from "./helpers.js";
|
|
38
|
+
import { getRouterContext } from "../router-context.js";
|
|
39
|
+
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Telemetry helpers
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
47
|
+
* Silently no-ops when called outside RouterContext (e.g. in unit tests).
|
|
48
|
+
*/
|
|
49
|
+
function observeStreamedHandler(
|
|
50
|
+
promise: Promise<ReactNode>,
|
|
51
|
+
segmentId: string,
|
|
52
|
+
segmentType: string,
|
|
53
|
+
pathname?: string,
|
|
54
|
+
routeKey?: string,
|
|
55
|
+
params?: Record<string, string>,
|
|
56
|
+
): void {
|
|
57
|
+
let routerCtx;
|
|
58
|
+
try {
|
|
59
|
+
routerCtx = getRouterContext();
|
|
60
|
+
} catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!routerCtx?.telemetry) return;
|
|
64
|
+
const sink = resolveSink(routerCtx.telemetry);
|
|
65
|
+
const reqId = routerCtx.requestId;
|
|
66
|
+
promise.catch((err: unknown) => {
|
|
67
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
68
|
+
safeEmit(sink, {
|
|
69
|
+
type: "handler.error",
|
|
70
|
+
timestamp: performance.now(),
|
|
71
|
+
requestId: reqId,
|
|
72
|
+
segmentId,
|
|
73
|
+
segmentType,
|
|
74
|
+
error: errorObj,
|
|
75
|
+
handledByBoundary: true,
|
|
76
|
+
pathname,
|
|
77
|
+
routeKey,
|
|
78
|
+
params,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Revalidation telemetry helper
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Emit revalidation.decision telemetry for a segment if a sink is configured.
|
|
89
|
+
* Called after evaluateRevalidation returns to capture the decision.
|
|
90
|
+
* Silently no-ops when called outside RouterContext (e.g. in unit tests).
|
|
91
|
+
*/
|
|
92
|
+
function emitRevalidationDecision(
|
|
93
|
+
segmentId: string,
|
|
94
|
+
pathname: string,
|
|
95
|
+
routeKey: string,
|
|
96
|
+
shouldRevalidate: boolean,
|
|
97
|
+
): void {
|
|
98
|
+
let routerCtx;
|
|
99
|
+
try {
|
|
100
|
+
routerCtx = getRouterContext();
|
|
101
|
+
} catch {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (routerCtx?.telemetry) {
|
|
105
|
+
safeEmit(resolveSink(routerCtx.telemetry), {
|
|
106
|
+
type: "revalidation.decision",
|
|
107
|
+
timestamp: performance.now(),
|
|
108
|
+
requestId: routerCtx.requestId,
|
|
109
|
+
segmentId,
|
|
110
|
+
pathname,
|
|
111
|
+
routeKey,
|
|
112
|
+
shouldRevalidate,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
34
116
|
|
|
35
117
|
// ---------------------------------------------------------------------------
|
|
36
118
|
// Revalidation path (partial match)
|
|
@@ -81,7 +163,20 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
81
163
|
}) => {
|
|
82
164
|
const shouldRun = await revalidate(
|
|
83
165
|
async () => {
|
|
84
|
-
if (!clientSegmentIds.has(segmentId))
|
|
166
|
+
if (!clientSegmentIds.has(segmentId)) {
|
|
167
|
+
if (isTraceActive()) {
|
|
168
|
+
pushRevalidationTraceEntry({
|
|
169
|
+
segmentId,
|
|
170
|
+
segmentType: "loader",
|
|
171
|
+
belongsToRoute,
|
|
172
|
+
source: "loader",
|
|
173
|
+
defaultShouldRevalidate: true,
|
|
174
|
+
finalShouldRevalidate: true,
|
|
175
|
+
reason: "new-segment",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
85
180
|
|
|
86
181
|
const dummySegment: ResolvedSegment = {
|
|
87
182
|
id: segmentId,
|
|
@@ -109,11 +204,13 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
109
204
|
context: ctx,
|
|
110
205
|
actionContext,
|
|
111
206
|
stale,
|
|
207
|
+
traceSource: "loader",
|
|
112
208
|
});
|
|
113
209
|
},
|
|
114
210
|
async () => true,
|
|
115
211
|
() => false,
|
|
116
212
|
);
|
|
213
|
+
emitRevalidationDecision(segmentId, ctx.pathname, routeKey, shouldRun);
|
|
117
214
|
return { shouldRun, loaderEntry, loader, segmentId, index };
|
|
118
215
|
},
|
|
119
216
|
),
|
|
@@ -156,6 +253,7 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
156
253
|
routeKey: string,
|
|
157
254
|
deps: SegmentResolutionDeps<TEnv>,
|
|
158
255
|
actionContext?: ActionContext,
|
|
256
|
+
stale?: boolean,
|
|
159
257
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
160
258
|
const allLoaderSegments: ResolvedSegment[] = [];
|
|
161
259
|
const allMatchedIds: string[] = [];
|
|
@@ -174,6 +272,8 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
174
272
|
routeKey,
|
|
175
273
|
deps,
|
|
176
274
|
actionContext,
|
|
275
|
+
undefined, // shortCodeOverride
|
|
276
|
+
stale,
|
|
177
277
|
);
|
|
178
278
|
allLoaderSegments.push(...segments);
|
|
179
279
|
allMatchedIds.push(...matchedIds);
|
|
@@ -279,9 +379,35 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
279
379
|
}
|
|
280
380
|
|
|
281
381
|
const shouldResolve = await (async () => {
|
|
282
|
-
if (isFullRefetch)
|
|
283
|
-
|
|
284
|
-
|
|
382
|
+
if (isFullRefetch) {
|
|
383
|
+
if (isTraceActive()) {
|
|
384
|
+
pushRevalidationTraceEntry({
|
|
385
|
+
segmentId: parallelId,
|
|
386
|
+
segmentType: "parallel",
|
|
387
|
+
belongsToRoute,
|
|
388
|
+
source: "parallel",
|
|
389
|
+
defaultShouldRevalidate: true,
|
|
390
|
+
finalShouldRevalidate: true,
|
|
391
|
+
reason: "full-refetch",
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
397
|
+
const result = belongsToRoute || isNewParent;
|
|
398
|
+
if (isTraceActive()) {
|
|
399
|
+
pushRevalidationTraceEntry({
|
|
400
|
+
segmentId: parallelId,
|
|
401
|
+
segmentType: "parallel",
|
|
402
|
+
belongsToRoute,
|
|
403
|
+
source: "parallel",
|
|
404
|
+
defaultShouldRevalidate: result,
|
|
405
|
+
finalShouldRevalidate: result,
|
|
406
|
+
reason: result ? "new-segment" : "skip-parent-chain",
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
285
411
|
|
|
286
412
|
const dummySegment: ResolvedSegment = {
|
|
287
413
|
id: parallelId,
|
|
@@ -313,8 +439,15 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
313
439
|
context,
|
|
314
440
|
actionContext,
|
|
315
441
|
stale,
|
|
442
|
+
traceSource: "parallel",
|
|
316
443
|
});
|
|
317
444
|
})();
|
|
445
|
+
emitRevalidationDecision(
|
|
446
|
+
parallelId,
|
|
447
|
+
context.pathname,
|
|
448
|
+
routeKey,
|
|
449
|
+
shouldResolve,
|
|
450
|
+
);
|
|
318
451
|
|
|
319
452
|
let component: ReactNode | undefined;
|
|
320
453
|
if (shouldResolve) {
|
|
@@ -327,9 +460,25 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
327
460
|
if (!shouldResolve) {
|
|
328
461
|
component = null;
|
|
329
462
|
} else if (hasLoadingFallback) {
|
|
330
|
-
|
|
331
|
-
typeof handler === "function" ? handler(context) : handler
|
|
332
|
-
)
|
|
463
|
+
const result =
|
|
464
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
465
|
+
if (result instanceof Promise) {
|
|
466
|
+
const tracked = deps.trackHandler(result, {
|
|
467
|
+
segmentId: parallelId,
|
|
468
|
+
segmentType: "parallel",
|
|
469
|
+
});
|
|
470
|
+
observeStreamedHandler(
|
|
471
|
+
tracked,
|
|
472
|
+
parallelId,
|
|
473
|
+
"parallel",
|
|
474
|
+
context.pathname,
|
|
475
|
+
routeKey,
|
|
476
|
+
params,
|
|
477
|
+
);
|
|
478
|
+
component = tracked as ReactNode;
|
|
479
|
+
} else {
|
|
480
|
+
component = result as ReactNode;
|
|
481
|
+
}
|
|
333
482
|
} else {
|
|
334
483
|
component =
|
|
335
484
|
typeof handler === "function" ? await handler(context) : handler;
|
|
@@ -407,7 +556,24 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
407
556
|
clientHasSegment: hasSegment,
|
|
408
557
|
belongsToRoute,
|
|
409
558
|
});
|
|
410
|
-
if (!hasSegment)
|
|
559
|
+
if (!hasSegment) {
|
|
560
|
+
if (isTraceActive()) {
|
|
561
|
+
const segType =
|
|
562
|
+
entry.type === "cache"
|
|
563
|
+
? "layout"
|
|
564
|
+
: (entry.type as "layout" | "route");
|
|
565
|
+
pushRevalidationTraceEntry({
|
|
566
|
+
segmentId: entry.shortCode,
|
|
567
|
+
segmentType: segType,
|
|
568
|
+
belongsToRoute,
|
|
569
|
+
source: "segment-resolution",
|
|
570
|
+
defaultShouldRevalidate: true,
|
|
571
|
+
finalShouldRevalidate: true,
|
|
572
|
+
reason: "new-segment",
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
411
577
|
|
|
412
578
|
const dummySegment: ResolvedSegment = {
|
|
413
579
|
id: entry.shortCode,
|
|
@@ -442,6 +608,12 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
442
608
|
actionContext,
|
|
443
609
|
stale,
|
|
444
610
|
});
|
|
611
|
+
emitRevalidationDecision(
|
|
612
|
+
entry.shortCode,
|
|
613
|
+
context.pathname,
|
|
614
|
+
routeKey,
|
|
615
|
+
shouldRevalidate,
|
|
616
|
+
);
|
|
445
617
|
debugLog("segment.revalidate", "entry revalidation decision", {
|
|
446
618
|
segmentId: entry.shortCode,
|
|
447
619
|
shouldRevalidate,
|
|
@@ -462,10 +634,22 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
462
634
|
}
|
|
463
635
|
if (!actionContext) {
|
|
464
636
|
const result = handleHandlerResult(routeEntry.handler(context));
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
637
|
+
if (result instanceof Promise) {
|
|
638
|
+
const tracked = deps.trackHandler(result, {
|
|
639
|
+
segmentId: entry.shortCode,
|
|
640
|
+
segmentType: entry.type,
|
|
641
|
+
});
|
|
642
|
+
observeStreamedHandler(
|
|
643
|
+
tracked,
|
|
644
|
+
entry.shortCode,
|
|
645
|
+
entry.type,
|
|
646
|
+
context.pathname,
|
|
647
|
+
routeKey,
|
|
648
|
+
params,
|
|
649
|
+
);
|
|
650
|
+
return { content: tracked };
|
|
651
|
+
}
|
|
652
|
+
return { content: result };
|
|
469
653
|
}
|
|
470
654
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
471
655
|
entryId: entry.id,
|
|
@@ -590,31 +774,32 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
590
774
|
}
|
|
591
775
|
}
|
|
592
776
|
|
|
593
|
-
const parallelResult = await resolveParallelSegmentsWithRevalidation(
|
|
594
|
-
entry,
|
|
595
|
-
params,
|
|
596
|
-
context,
|
|
597
|
-
belongsToRoute,
|
|
598
|
-
clientSegmentIds,
|
|
599
|
-
prevParams,
|
|
600
|
-
request,
|
|
601
|
-
prevUrl,
|
|
602
|
-
nextUrl,
|
|
603
|
-
routeKey,
|
|
604
|
-
deps,
|
|
605
|
-
actionContext,
|
|
606
|
-
stale,
|
|
607
|
-
);
|
|
608
|
-
segments.push(...parallelResult.segments);
|
|
609
|
-
matchedIds.push(...parallelResult.matchedIds);
|
|
610
|
-
|
|
611
|
-
// Push handler BEFORE orphan layouts for layout/cache entries (matching SSR
|
|
612
|
-
// order in resolveSegment). Route handler was already executed and is pushed
|
|
613
|
-
// after children for tree composition.
|
|
614
777
|
if (routeHandlerResult) {
|
|
778
|
+
// Route entry: handler already executed above; resolve parallels
|
|
779
|
+
// (handler data visible) then push handler segment last for tree order.
|
|
780
|
+
const parallelResult = await resolveParallelSegmentsWithRevalidation(
|
|
781
|
+
entry,
|
|
782
|
+
params,
|
|
783
|
+
context,
|
|
784
|
+
belongsToRoute,
|
|
785
|
+
clientSegmentIds,
|
|
786
|
+
prevParams,
|
|
787
|
+
request,
|
|
788
|
+
prevUrl,
|
|
789
|
+
nextUrl,
|
|
790
|
+
routeKey,
|
|
791
|
+
deps,
|
|
792
|
+
actionContext,
|
|
793
|
+
stale,
|
|
794
|
+
);
|
|
795
|
+
segments.push(...parallelResult.segments);
|
|
796
|
+
matchedIds.push(...parallelResult.matchedIds);
|
|
797
|
+
|
|
615
798
|
segments.push(routeHandlerResult.segment);
|
|
616
799
|
matchedIds.push(routeHandlerResult.matchedId);
|
|
617
800
|
} else {
|
|
801
|
+
// Layout/cache entry: handler-first — resolve handler before parallels
|
|
802
|
+
// so ctx.set() values are visible to parallel children.
|
|
618
803
|
const handlerResult = await resolveEntryHandlerWithRevalidation(
|
|
619
804
|
entry,
|
|
620
805
|
params,
|
|
@@ -632,9 +817,25 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
632
817
|
);
|
|
633
818
|
segments.push(handlerResult.segment);
|
|
634
819
|
matchedIds.push(handlerResult.matchedId);
|
|
635
|
-
}
|
|
636
820
|
|
|
637
|
-
|
|
821
|
+
const parallelResult = await resolveParallelSegmentsWithRevalidation(
|
|
822
|
+
entry,
|
|
823
|
+
params,
|
|
824
|
+
context,
|
|
825
|
+
belongsToRoute,
|
|
826
|
+
clientSegmentIds,
|
|
827
|
+
prevParams,
|
|
828
|
+
request,
|
|
829
|
+
prevUrl,
|
|
830
|
+
nextUrl,
|
|
831
|
+
routeKey,
|
|
832
|
+
deps,
|
|
833
|
+
actionContext,
|
|
834
|
+
stale,
|
|
835
|
+
);
|
|
836
|
+
segments.push(...parallelResult.segments);
|
|
837
|
+
matchedIds.push(...parallelResult.matchedIds);
|
|
838
|
+
|
|
638
839
|
for (const orphan of entry.layout) {
|
|
639
840
|
const orphanResult = await resolveOrphanLayoutWithRevalidation(
|
|
640
841
|
orphan,
|
|
@@ -705,6 +906,82 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
705
906
|
segments.push(...loaderResult.segments);
|
|
706
907
|
matchedIds.push(...loaderResult.matchedIds);
|
|
707
908
|
|
|
909
|
+
// Handler-first: resolve orphan layout handler before its parallels
|
|
910
|
+
// so ctx.set() values are visible to parallel children.
|
|
911
|
+
matchedIds.push(orphan.shortCode);
|
|
912
|
+
|
|
913
|
+
const component = await revalidate(
|
|
914
|
+
async () => {
|
|
915
|
+
if (!clientSegmentIds.has(orphan.shortCode)) {
|
|
916
|
+
if (isTraceActive()) {
|
|
917
|
+
pushRevalidationTraceEntry({
|
|
918
|
+
segmentId: orphan.shortCode,
|
|
919
|
+
segmentType: "layout",
|
|
920
|
+
belongsToRoute,
|
|
921
|
+
source: "orphan-layout",
|
|
922
|
+
defaultShouldRevalidate: true,
|
|
923
|
+
finalShouldRevalidate: true,
|
|
924
|
+
reason: "new-segment",
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const dummySegment: ResolvedSegment = {
|
|
931
|
+
id: orphan.shortCode,
|
|
932
|
+
namespace: orphan.id,
|
|
933
|
+
type: "layout",
|
|
934
|
+
index: 0,
|
|
935
|
+
component: null as any,
|
|
936
|
+
params,
|
|
937
|
+
belongsToRoute,
|
|
938
|
+
layoutName: orphan.id,
|
|
939
|
+
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
const shouldRevalidate = await evaluateRevalidation({
|
|
943
|
+
segment: dummySegment,
|
|
944
|
+
prevParams,
|
|
945
|
+
getPrevSegment: null,
|
|
946
|
+
request,
|
|
947
|
+
prevUrl,
|
|
948
|
+
nextUrl,
|
|
949
|
+
revalidations: orphan.revalidate.map((fn, i) => ({
|
|
950
|
+
name: `revalidate${i}`,
|
|
951
|
+
fn,
|
|
952
|
+
})),
|
|
953
|
+
routeKey,
|
|
954
|
+
context,
|
|
955
|
+
actionContext,
|
|
956
|
+
stale,
|
|
957
|
+
traceSource: "orphan-layout",
|
|
958
|
+
});
|
|
959
|
+
emitRevalidationDecision(
|
|
960
|
+
orphan.shortCode,
|
|
961
|
+
context.pathname,
|
|
962
|
+
routeKey,
|
|
963
|
+
shouldRevalidate,
|
|
964
|
+
);
|
|
965
|
+
return shouldRevalidate;
|
|
966
|
+
},
|
|
967
|
+
async () => resolveLayoutComponent(orphan, context),
|
|
968
|
+
() => null,
|
|
969
|
+
);
|
|
970
|
+
|
|
971
|
+
segments.push({
|
|
972
|
+
id: orphan.shortCode,
|
|
973
|
+
namespace: orphan.id,
|
|
974
|
+
type: "layout",
|
|
975
|
+
index: 0,
|
|
976
|
+
component,
|
|
977
|
+
params,
|
|
978
|
+
belongsToRoute,
|
|
979
|
+
layoutName: orphan.id,
|
|
980
|
+
loading: orphan.loading === false ? null : orphan.loading,
|
|
981
|
+
transition: orphan.transition,
|
|
982
|
+
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
983
|
+
});
|
|
984
|
+
|
|
708
985
|
for (const parallelEntry of orphan.parallel) {
|
|
709
986
|
invariant(
|
|
710
987
|
parallelEntry.type === "parallel",
|
|
@@ -743,7 +1020,20 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
743
1020
|
matchedIds.push(parallelId);
|
|
744
1021
|
|
|
745
1022
|
const shouldResolve = await (async () => {
|
|
746
|
-
if (!clientSegmentIds.has(parallelId))
|
|
1023
|
+
if (!clientSegmentIds.has(parallelId)) {
|
|
1024
|
+
if (isTraceActive()) {
|
|
1025
|
+
pushRevalidationTraceEntry({
|
|
1026
|
+
segmentId: parallelId,
|
|
1027
|
+
segmentType: "parallel",
|
|
1028
|
+
belongsToRoute,
|
|
1029
|
+
source: "parallel",
|
|
1030
|
+
defaultShouldRevalidate: true,
|
|
1031
|
+
finalShouldRevalidate: true,
|
|
1032
|
+
reason: "new-segment",
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
return true;
|
|
1036
|
+
}
|
|
747
1037
|
|
|
748
1038
|
const dummySegment: ResolvedSegment = {
|
|
749
1039
|
id: parallelId,
|
|
@@ -775,8 +1065,15 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
775
1065
|
context,
|
|
776
1066
|
actionContext,
|
|
777
1067
|
stale,
|
|
1068
|
+
traceSource: "parallel",
|
|
778
1069
|
});
|
|
779
1070
|
})();
|
|
1071
|
+
emitRevalidationDecision(
|
|
1072
|
+
parallelId,
|
|
1073
|
+
context.pathname,
|
|
1074
|
+
routeKey,
|
|
1075
|
+
shouldResolve,
|
|
1076
|
+
);
|
|
780
1077
|
|
|
781
1078
|
let component: ReactNode | undefined;
|
|
782
1079
|
if (shouldResolve) {
|
|
@@ -789,9 +1086,25 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
789
1086
|
if (!shouldResolve) {
|
|
790
1087
|
component = null;
|
|
791
1088
|
} else if (hasLoadingFallback) {
|
|
792
|
-
|
|
793
|
-
typeof handler === "function" ? handler(context) : handler
|
|
794
|
-
)
|
|
1089
|
+
const result =
|
|
1090
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
1091
|
+
if (result instanceof Promise) {
|
|
1092
|
+
const tracked = deps.trackHandler(result, {
|
|
1093
|
+
segmentId: parallelId,
|
|
1094
|
+
segmentType: "parallel",
|
|
1095
|
+
});
|
|
1096
|
+
observeStreamedHandler(
|
|
1097
|
+
tracked,
|
|
1098
|
+
parallelId,
|
|
1099
|
+
"parallel",
|
|
1100
|
+
context.pathname,
|
|
1101
|
+
routeKey,
|
|
1102
|
+
params,
|
|
1103
|
+
);
|
|
1104
|
+
component = tracked as ReactNode;
|
|
1105
|
+
} else {
|
|
1106
|
+
component = result as ReactNode;
|
|
1107
|
+
}
|
|
795
1108
|
} else {
|
|
796
1109
|
component =
|
|
797
1110
|
typeof handler === "function" ? await handler(context) : handler;
|
|
@@ -817,59 +1130,6 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
817
1130
|
}
|
|
818
1131
|
}
|
|
819
1132
|
|
|
820
|
-
matchedIds.push(orphan.shortCode);
|
|
821
|
-
|
|
822
|
-
const component = await revalidate(
|
|
823
|
-
async () => {
|
|
824
|
-
if (!clientSegmentIds.has(orphan.shortCode)) return true;
|
|
825
|
-
|
|
826
|
-
const dummySegment: ResolvedSegment = {
|
|
827
|
-
id: orphan.shortCode,
|
|
828
|
-
namespace: orphan.id,
|
|
829
|
-
type: "layout",
|
|
830
|
-
index: 0,
|
|
831
|
-
component: null as any,
|
|
832
|
-
params,
|
|
833
|
-
belongsToRoute,
|
|
834
|
-
layoutName: orphan.id,
|
|
835
|
-
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
return await evaluateRevalidation({
|
|
839
|
-
segment: dummySegment,
|
|
840
|
-
prevParams,
|
|
841
|
-
getPrevSegment: null,
|
|
842
|
-
request,
|
|
843
|
-
prevUrl,
|
|
844
|
-
nextUrl,
|
|
845
|
-
revalidations: orphan.revalidate.map((fn, i) => ({
|
|
846
|
-
name: `revalidate${i}`,
|
|
847
|
-
fn,
|
|
848
|
-
})),
|
|
849
|
-
routeKey,
|
|
850
|
-
context,
|
|
851
|
-
actionContext,
|
|
852
|
-
stale,
|
|
853
|
-
});
|
|
854
|
-
},
|
|
855
|
-
async () => resolveLayoutComponent(orphan, context),
|
|
856
|
-
() => null,
|
|
857
|
-
);
|
|
858
|
-
|
|
859
|
-
segments.push({
|
|
860
|
-
id: orphan.shortCode,
|
|
861
|
-
namespace: orphan.id,
|
|
862
|
-
type: "layout",
|
|
863
|
-
index: 0,
|
|
864
|
-
component,
|
|
865
|
-
params,
|
|
866
|
-
belongsToRoute,
|
|
867
|
-
layoutName: orphan.id,
|
|
868
|
-
loading: orphan.loading === false ? null : orphan.loading,
|
|
869
|
-
transition: orphan.transition,
|
|
870
|
-
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
871
|
-
});
|
|
872
|
-
|
|
873
1133
|
return { segments, matchedIds };
|
|
874
1134
|
}
|
|
875
1135
|
|
|
@@ -898,6 +1158,8 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
898
1158
|
const seenSegIds = new Set<string>();
|
|
899
1159
|
const seenMatchIds = new Set<string>();
|
|
900
1160
|
|
|
1161
|
+
const telemetry = getRouterContext()?.telemetry;
|
|
1162
|
+
|
|
901
1163
|
for (const entry of entries) {
|
|
902
1164
|
if (entry.type === "route" && interceptResult) {
|
|
903
1165
|
debugLog(
|
|
@@ -937,7 +1199,9 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
937
1199
|
),
|
|
938
1200
|
(seg) => ({ segments: [seg], matchedIds: [seg.id] }),
|
|
939
1201
|
deps,
|
|
940
|
-
|
|
1202
|
+
telemetry
|
|
1203
|
+
? { request, url: context.url, routeKey, isPartial: true, telemetry }
|
|
1204
|
+
: undefined,
|
|
941
1205
|
pathname,
|
|
942
1206
|
);
|
|
943
1207
|
|
|
@@ -50,6 +50,7 @@ export interface SegmentWrappers<TEnv = any> {
|
|
|
50
50
|
actionResult?: any;
|
|
51
51
|
formData?: FormData;
|
|
52
52
|
},
|
|
53
|
+
stale?: boolean,
|
|
53
54
|
) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
|
|
54
55
|
buildEntryRevalidateMap: (
|
|
55
56
|
entries: EntryData[],
|
|
@@ -158,6 +159,7 @@ export function createSegmentWrappers<TEnv = any>(
|
|
|
158
159
|
actionResult?: any;
|
|
159
160
|
formData?: FormData;
|
|
160
161
|
},
|
|
162
|
+
stale?: boolean,
|
|
161
163
|
): ReturnType<typeof _resolveLoadersOnlyWithRevalidation> {
|
|
162
164
|
return _resolveLoadersOnlyWithRevalidation(
|
|
163
165
|
entries,
|
|
@@ -170,6 +172,7 @@ export function createSegmentWrappers<TEnv = any>(
|
|
|
170
172
|
routeKey,
|
|
171
173
|
segmentDeps,
|
|
172
174
|
actionContext,
|
|
175
|
+
stale,
|
|
173
176
|
);
|
|
174
177
|
}
|
|
175
178
|
|