@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126
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 +6 -4
- package/dist/bin/rango.js +3 -4
- package/dist/vite/index.js +315 -68
- package/package.json +19 -18
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/hooks/SKILL.md +2 -2
- package/skills/route/SKILL.md +6 -0
- package/skills/server-actions/SKILL.md +25 -1
- package/skills/testing/SKILL.md +17 -17
- package/skills/testing/cache-prerender.md +29 -3
- package/skills/testing/flight.md +13 -10
- package/skills/testing/render-handler.md +3 -0
- package/skills/testing/server-tree.md +1 -1
- package/skills/testing/setup.md +1 -1
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +10 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/navigation-store-handle.ts +3 -4
- package/src/browser/navigation-store.ts +0 -39
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +23 -84
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +2 -23
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -45
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +2 -1
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +10 -3
- package/src/browser/server-action-bridge.ts +51 -3
- package/src/browser/types.ts +23 -5
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/index.ts +8 -9
- package/src/build/route-trie.ts +46 -11
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +48 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +72 -45
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +10 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +0 -52
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/types.ts +0 -98
- package/src/client.rsc.tsx +4 -22
- package/src/client.tsx +19 -32
- package/src/context-var.ts +12 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +2 -12
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +0 -16
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +27 -2
- package/src/index.ts +7 -0
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +4 -15
- package/src/loader.ts +3 -9
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +23 -30
- package/src/prerender.ts +34 -0
- package/src/redirect-origin.ts +100 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-map-builder.ts +0 -16
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -31
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +25 -23
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +0 -43
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +96 -179
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +77 -60
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +5 -56
- package/src/router/prerender-match.ts +56 -51
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +14 -62
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +10 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +35 -23
- package/src/router/segment-resolution/revalidation.ts +188 -283
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +0 -22
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +66 -45
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +8 -11
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +20 -4
- package/src/rsc/helpers.ts +71 -3
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/origin-guard.ts +9 -15
- package/src/rsc/progressive-enhancement.ts +10 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-route-handler.ts +23 -18
- package/src/rsc/rsc-rendering.ts +2 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +34 -29
- package/src/rsc/types.ts +6 -3
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +29 -92
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +2 -27
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +1 -24
- package/src/testing/dispatch.ts +43 -6
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/flight-matchers.ts +0 -13
- package/src/testing/flight-normalize.ts +3 -30
- package/src/testing/flight.ts +46 -48
- package/src/testing/generated-routes.ts +1 -41
- package/src/testing/index.ts +1 -21
- package/src/testing/internal/context.ts +3 -45
- package/src/testing/internal/seed-vars.ts +0 -26
- package/src/testing/render-handler.ts +31 -61
- package/src/testing/render-route.tsx +75 -103
- package/src/testing/run-loader.ts +0 -96
- package/src/testing/run-middleware.ts +0 -26
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/error-types.ts +25 -89
- package/src/types/global-namespace.ts +4 -14
- package/src/types/handler-context.ts +28 -9
- package/src/types/index.ts +0 -10
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +28 -18
- package/src/vite/discovery/prerender-collection.ts +2 -4
- package/src/vite/discovery/state.ts +5 -0
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +35 -9
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +2 -108
- package/src/vite/router-discovery.ts +9 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getParallelEntries,
|
|
15
15
|
getParallelSlotEntries,
|
|
16
16
|
type EntryData,
|
|
17
|
+
type ParallelEntryData,
|
|
17
18
|
} from "../../server/context";
|
|
18
19
|
import type {
|
|
19
20
|
HandlerContext,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
import { resolveLoaderData } from "./loader-cache.js";
|
|
35
36
|
import {
|
|
36
37
|
handleHandlerResult,
|
|
38
|
+
warnOnStreamedResponse,
|
|
37
39
|
tryStaticHandler,
|
|
38
40
|
tryStaticSlot,
|
|
39
41
|
resolveLayoutComponent,
|
|
@@ -42,54 +44,13 @@ import {
|
|
|
42
44
|
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
43
45
|
import { getRouterContext } from "../router-context.js";
|
|
44
46
|
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
47
|
+
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
45
48
|
import {
|
|
46
49
|
track,
|
|
47
50
|
RangoContext,
|
|
48
51
|
runInsideLoaderScope,
|
|
49
52
|
} from "../../server/context.js";
|
|
50
53
|
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Telemetry helpers
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
57
|
-
* Silently no-ops when called outside RouterContext (e.g. in unit tests).
|
|
58
|
-
*/
|
|
59
|
-
function observeStreamedHandler(
|
|
60
|
-
promise: Promise<ReactNode>,
|
|
61
|
-
segmentId: string,
|
|
62
|
-
segmentType: string,
|
|
63
|
-
pathname?: string,
|
|
64
|
-
routeKey?: string,
|
|
65
|
-
params?: Record<string, string>,
|
|
66
|
-
): void {
|
|
67
|
-
let routerCtx;
|
|
68
|
-
try {
|
|
69
|
-
routerCtx = getRouterContext();
|
|
70
|
-
} catch {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (!routerCtx?.telemetry) return;
|
|
74
|
-
const sink = resolveSink(routerCtx.telemetry);
|
|
75
|
-
const reqId = routerCtx.requestId;
|
|
76
|
-
promise.catch((err: unknown) => {
|
|
77
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
78
|
-
safeEmit(sink, {
|
|
79
|
-
type: "handler.error",
|
|
80
|
-
timestamp: performance.now(),
|
|
81
|
-
requestId: reqId,
|
|
82
|
-
segmentId,
|
|
83
|
-
segmentType,
|
|
84
|
-
error: errorObj,
|
|
85
|
-
handledByBoundary: true,
|
|
86
|
-
pathname,
|
|
87
|
-
routeKey,
|
|
88
|
-
params,
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
54
|
/**
|
|
94
55
|
* Trace a parallel slot that's being force-rendered on a full refetch (client
|
|
95
56
|
* has no cached state). User revalidate fns are bypassed in this case — see
|
|
@@ -426,6 +387,97 @@ export function buildEntryRevalidateMap(
|
|
|
426
387
|
return map;
|
|
427
388
|
}
|
|
428
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Resolve the component for a single parallel slot on the revalidation path.
|
|
392
|
+
* Pure component resolution shared verbatim by
|
|
393
|
+
* resolveParallelSegmentsWithRevalidation and the orphan-inlined loop in
|
|
394
|
+
* resolveOrphanLayoutWithRevalidation: try the static slot cache, else run the
|
|
395
|
+
* slot handler (pinning _currentSegmentId to the slot id so handle pushes land
|
|
396
|
+
* in the slot's own bucket, and wrapping a streamed handler). Returns the
|
|
397
|
+
* resolved component and whether the handler actually ran. Does NOT touch the
|
|
398
|
+
* revalidate-default policy (the caller decides shouldResolve, including the
|
|
399
|
+
* orphan-vs-main defaultOverride divergence) or loader-resolution ordering.
|
|
400
|
+
*/
|
|
401
|
+
async function resolveParallelSlotComponent<TEnv>(args: {
|
|
402
|
+
shouldResolve: boolean;
|
|
403
|
+
parallelEntry: ParallelEntryData;
|
|
404
|
+
slot: string;
|
|
405
|
+
parallelId: string;
|
|
406
|
+
handler:
|
|
407
|
+
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
408
|
+
| ReactNode
|
|
409
|
+
| undefined;
|
|
410
|
+
context: HandlerContext<any, TEnv>;
|
|
411
|
+
deps: SegmentResolutionDeps<TEnv>;
|
|
412
|
+
routeKey: string;
|
|
413
|
+
params: Record<string, string>;
|
|
414
|
+
}): Promise<{ component: ReactNode | undefined; handlerRan: boolean }> {
|
|
415
|
+
const {
|
|
416
|
+
shouldResolve,
|
|
417
|
+
parallelEntry,
|
|
418
|
+
slot,
|
|
419
|
+
parallelId,
|
|
420
|
+
handler,
|
|
421
|
+
context,
|
|
422
|
+
deps,
|
|
423
|
+
routeKey,
|
|
424
|
+
params,
|
|
425
|
+
} = args;
|
|
426
|
+
|
|
427
|
+
let component: ReactNode | undefined;
|
|
428
|
+
let handlerRan = false;
|
|
429
|
+
if (shouldResolve) {
|
|
430
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
431
|
+
// tryStaticSlot returning a value means the static cache supplied the
|
|
432
|
+
// component — handler did NOT run. handlerRan stays false.
|
|
433
|
+
}
|
|
434
|
+
if (component === undefined) {
|
|
435
|
+
const hasLoadingFallback =
|
|
436
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
437
|
+
if (!shouldResolve) {
|
|
438
|
+
component = null;
|
|
439
|
+
} else if (handler === undefined) {
|
|
440
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
441
|
+
// Nothing to render — use null so the client keeps its cached version.
|
|
442
|
+
component = null;
|
|
443
|
+
} else {
|
|
444
|
+
// Slot-keyed pushes — slot owns its own bucket, parent layout owns its
|
|
445
|
+
// own. On slot-only revalidations the partial merge updates only the
|
|
446
|
+
// slot's bucket; the parent's bucket stays intact.
|
|
447
|
+
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
448
|
+
parallelId;
|
|
449
|
+
handlerRan = true;
|
|
450
|
+
if (hasLoadingFallback) {
|
|
451
|
+
const result =
|
|
452
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
453
|
+
if (result instanceof Promise) {
|
|
454
|
+
warnOnStreamedResponse(result, parallelId);
|
|
455
|
+
const tracked = deps.trackHandler(result, {
|
|
456
|
+
segmentId: parallelId,
|
|
457
|
+
segmentType: "parallel",
|
|
458
|
+
});
|
|
459
|
+
observeStreamedHandler(
|
|
460
|
+
tracked,
|
|
461
|
+
parallelId,
|
|
462
|
+
"parallel",
|
|
463
|
+
context.pathname,
|
|
464
|
+
routeKey,
|
|
465
|
+
params,
|
|
466
|
+
);
|
|
467
|
+
component = tracked as ReactNode;
|
|
468
|
+
} else {
|
|
469
|
+
component = result as ReactNode;
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
component =
|
|
473
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return { component, handlerRan };
|
|
479
|
+
}
|
|
480
|
+
|
|
429
481
|
/**
|
|
430
482
|
* Resolve parallel segments with revalidation.
|
|
431
483
|
*/
|
|
@@ -443,9 +495,30 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
443
495
|
deps: SegmentResolutionDeps<TEnv>,
|
|
444
496
|
actionContext?: ActionContext,
|
|
445
497
|
stale?: boolean,
|
|
498
|
+
options?: {
|
|
499
|
+
/**
|
|
500
|
+
* Seed for an unknown parent-chain slot (slot not in clientSegmentIds) when
|
|
501
|
+
* there are no deciding revalidate fns. "type-derived" (default, main path):
|
|
502
|
+
* `belongsToRoute || isNewParent`. "force-render" (orphan path): always
|
|
503
|
+
* `true` — orphan parallels always belong to the route and must render
|
|
504
|
+
* unless the user opts out via revalidate(); the #482 blank-parent-chain-
|
|
505
|
+
* slot guard.
|
|
506
|
+
*/
|
|
507
|
+
parentChainDefault?: "type-derived" | "force-render";
|
|
508
|
+
/**
|
|
509
|
+
* When a slot's loaders are resolved relative to the slot segment push.
|
|
510
|
+
* "after" (default, main path) pushes the slot segment first; "before"
|
|
511
|
+
* (orphan path) resolves loaders first. This only changes the
|
|
512
|
+
* segments/matchedIds emission ORDER (the client reconciler is insensitive
|
|
513
|
+
* to it: loader sub-ids are filtered out and slots are re-grouped by parent).
|
|
514
|
+
*/
|
|
515
|
+
loaderOrder?: "after" | "before";
|
|
516
|
+
},
|
|
446
517
|
): Promise<SegmentRevalidationResult> {
|
|
447
518
|
const segments: ResolvedSegment[] = [];
|
|
448
519
|
const matchedIds: string[] = [];
|
|
520
|
+
const parentChainDefault = options?.parentChainDefault ?? "type-derived";
|
|
521
|
+
const loaderOrder = options?.loaderOrder ?? "after";
|
|
449
522
|
|
|
450
523
|
const resolvedParallelEntries = new Set<string>();
|
|
451
524
|
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
@@ -470,6 +543,34 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
470
543
|
|
|
471
544
|
const isFullRefetch = clientSegmentIds.size === 0;
|
|
472
545
|
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
546
|
+
|
|
547
|
+
// A slot's loaders (never cached) are deduped per parallel entry and
|
|
548
|
+
// emitted either before or after the slot segment per loaderOrder.
|
|
549
|
+
const resolveSlotLoaders = async () => {
|
|
550
|
+
if (resolvedParallelEntries.has(parallelEntry.id)) return;
|
|
551
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
552
|
+
parallelEntry,
|
|
553
|
+
context,
|
|
554
|
+
belongsToRoute,
|
|
555
|
+
clientSegmentIds,
|
|
556
|
+
prevParams,
|
|
557
|
+
request,
|
|
558
|
+
prevUrl,
|
|
559
|
+
nextUrl,
|
|
560
|
+
routeKey,
|
|
561
|
+
deps,
|
|
562
|
+
actionContext,
|
|
563
|
+
entry.shortCode,
|
|
564
|
+
stale,
|
|
565
|
+
);
|
|
566
|
+
segments.push(...loaderResult.segments);
|
|
567
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
568
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
if (loaderOrder === "before") {
|
|
572
|
+
await resolveSlotLoaders();
|
|
573
|
+
}
|
|
473
574
|
// Always announce the slot in matchedIds — it's unconditionally appended
|
|
474
575
|
// to `segments` below, and a segment present in segments but missing from
|
|
475
576
|
// matched lets the client prune it (then it's missing from clientSegmentIds
|
|
@@ -489,7 +590,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
489
590
|
// soft chain seeds with the right "new segment" / "parent-chain" value.
|
|
490
591
|
let defaultOverride: { value: boolean; reason: string } | undefined;
|
|
491
592
|
if (!clientSegmentIds.has(parallelId)) {
|
|
492
|
-
const value =
|
|
593
|
+
const value =
|
|
594
|
+
parentChainDefault === "force-render"
|
|
595
|
+
? true
|
|
596
|
+
: belongsToRoute || isNewParent;
|
|
493
597
|
defaultOverride = {
|
|
494
598
|
value,
|
|
495
599
|
reason: value ? "new-segment" : "skip-parent-chain",
|
|
@@ -537,55 +641,17 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
537
641
|
shouldResolve,
|
|
538
642
|
);
|
|
539
643
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
component = null;
|
|
552
|
-
} else if (handler === undefined) {
|
|
553
|
-
// Handler evicted (production static slot) but static lookup missed.
|
|
554
|
-
// Nothing to render — use null so the client keeps its cached version.
|
|
555
|
-
component = null;
|
|
556
|
-
} else {
|
|
557
|
-
// Slot-keyed pushes — slot owns its own bucket, parent layout owns
|
|
558
|
-
// its own. On slot-only revalidations the partial merge updates only
|
|
559
|
-
// the slot's bucket; the parent's bucket stays intact.
|
|
560
|
-
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
561
|
-
parallelId;
|
|
562
|
-
handlerRan = true;
|
|
563
|
-
if (hasLoadingFallback) {
|
|
564
|
-
const result =
|
|
565
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
566
|
-
if (result instanceof Promise) {
|
|
567
|
-
const tracked = deps.trackHandler(result, {
|
|
568
|
-
segmentId: parallelId,
|
|
569
|
-
segmentType: "parallel",
|
|
570
|
-
});
|
|
571
|
-
observeStreamedHandler(
|
|
572
|
-
tracked,
|
|
573
|
-
parallelId,
|
|
574
|
-
"parallel",
|
|
575
|
-
context.pathname,
|
|
576
|
-
routeKey,
|
|
577
|
-
params,
|
|
578
|
-
);
|
|
579
|
-
component = tracked as ReactNode;
|
|
580
|
-
} else {
|
|
581
|
-
component = result as ReactNode;
|
|
582
|
-
}
|
|
583
|
-
} else {
|
|
584
|
-
component =
|
|
585
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
644
|
+
const { component, handlerRan } = await resolveParallelSlotComponent({
|
|
645
|
+
shouldResolve,
|
|
646
|
+
parallelEntry,
|
|
647
|
+
slot,
|
|
648
|
+
parallelId,
|
|
649
|
+
handler,
|
|
650
|
+
context,
|
|
651
|
+
deps,
|
|
652
|
+
routeKey,
|
|
653
|
+
params,
|
|
654
|
+
});
|
|
589
655
|
|
|
590
656
|
segments.push({
|
|
591
657
|
id: parallelId,
|
|
@@ -608,28 +674,9 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
608
674
|
: {}),
|
|
609
675
|
});
|
|
610
676
|
|
|
611
|
-
if (
|
|
612
|
-
|
|
677
|
+
if (loaderOrder === "after") {
|
|
678
|
+
await resolveSlotLoaders();
|
|
613
679
|
}
|
|
614
|
-
|
|
615
|
-
const loaderResult = await resolveLoadersWithRevalidation(
|
|
616
|
-
parallelEntry,
|
|
617
|
-
context,
|
|
618
|
-
belongsToRoute,
|
|
619
|
-
clientSegmentIds,
|
|
620
|
-
prevParams,
|
|
621
|
-
request,
|
|
622
|
-
prevUrl,
|
|
623
|
-
nextUrl,
|
|
624
|
-
routeKey,
|
|
625
|
-
deps,
|
|
626
|
-
actionContext,
|
|
627
|
-
entry.shortCode,
|
|
628
|
-
stale,
|
|
629
|
-
);
|
|
630
|
-
segments.push(...loaderResult.segments);
|
|
631
|
-
matchedIds.push(...loaderResult.matchedIds);
|
|
632
|
-
resolvedParallelEntries.add(parallelEntry.id);
|
|
633
680
|
}
|
|
634
681
|
|
|
635
682
|
return { segments, matchedIds };
|
|
@@ -762,6 +809,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
762
809
|
if (!actionContext) {
|
|
763
810
|
const result = handleHandlerResult(handler(context));
|
|
764
811
|
if (result instanceof Promise) {
|
|
812
|
+
warnOnStreamedResponse(result, routeEntry.id);
|
|
765
813
|
result.finally(doneHandler).catch(() => {});
|
|
766
814
|
const tracked = deps.trackHandler(result, {
|
|
767
815
|
segmentId: entry.shortCode,
|
|
@@ -836,7 +884,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
836
884
|
request: Request,
|
|
837
885
|
prevUrl: URL,
|
|
838
886
|
nextUrl: URL,
|
|
839
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
840
887
|
deps: SegmentResolutionDeps<TEnv>,
|
|
841
888
|
actionContext?: ActionContext,
|
|
842
889
|
stale?: boolean,
|
|
@@ -1151,173 +1198,33 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1151
1198
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
1152
1199
|
});
|
|
1153
1200
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
)
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
matchedIds.push(...loaderResult.matchedIds);
|
|
1182
|
-
resolvedParallelEntries.add(parallelEntry.id);
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
const slots = parallelEntry.handler as Record<
|
|
1186
|
-
`@${string}`,
|
|
1187
|
-
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1188
|
-
| ReactNode
|
|
1189
|
-
>;
|
|
1190
|
-
// Handler may be undefined in production after static handler eviction.
|
|
1191
|
-
const handler = slots[slot];
|
|
1192
|
-
|
|
1193
|
-
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1194
|
-
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1195
|
-
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1196
|
-
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1197
|
-
matchedIds.push(parallelId);
|
|
1198
|
-
|
|
1199
|
-
const isFullRefetch = clientSegmentIds.size === 0;
|
|
1200
|
-
let shouldResolve: boolean;
|
|
1201
|
-
if (isFullRefetch) {
|
|
1202
|
-
// Same load-bearing rationale as the main parallel path: full refetch
|
|
1203
|
-
// means the client has nothing to fall back to, so the slot must render.
|
|
1204
|
-
traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
|
|
1205
|
-
shouldResolve = true;
|
|
1206
|
-
} else {
|
|
1207
|
-
// When slot is unknown to the client, seed the soft chain with `true`
|
|
1208
|
-
// (orphan parallels always belong to the route — we want them rendered
|
|
1209
|
-
// unless the user explicitly opts out via revalidate()).
|
|
1210
|
-
const defaultOverride = clientSegmentIds.has(parallelId)
|
|
1211
|
-
? undefined
|
|
1212
|
-
: { value: true, reason: "new-segment" };
|
|
1213
|
-
|
|
1214
|
-
const dummySegment: ResolvedSegment = {
|
|
1215
|
-
id: parallelId,
|
|
1216
|
-
namespace: parallelEntry.id,
|
|
1217
|
-
type: "parallel",
|
|
1218
|
-
index: 0,
|
|
1219
|
-
component: null as any,
|
|
1220
|
-
params,
|
|
1221
|
-
slot,
|
|
1222
|
-
belongsToRoute,
|
|
1223
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1224
|
-
...(parallelEntry.mountPath
|
|
1225
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1226
|
-
: {}),
|
|
1227
|
-
};
|
|
1228
|
-
|
|
1229
|
-
shouldResolve = await evaluateRevalidation({
|
|
1230
|
-
segment: dummySegment,
|
|
1231
|
-
prevParams,
|
|
1232
|
-
getPrevSegment: null,
|
|
1233
|
-
request,
|
|
1234
|
-
prevUrl,
|
|
1235
|
-
nextUrl,
|
|
1236
|
-
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1237
|
-
name: `revalidate${i}`,
|
|
1238
|
-
fn,
|
|
1239
|
-
})),
|
|
1240
|
-
routeKey,
|
|
1241
|
-
context,
|
|
1242
|
-
actionContext,
|
|
1243
|
-
stale,
|
|
1244
|
-
traceSource: "parallel",
|
|
1245
|
-
defaultOverride,
|
|
1246
|
-
});
|
|
1247
|
-
}
|
|
1248
|
-
emitRevalidationDecision(
|
|
1249
|
-
parallelId,
|
|
1250
|
-
context.pathname,
|
|
1251
|
-
routeKey,
|
|
1252
|
-
shouldResolve,
|
|
1253
|
-
);
|
|
1254
|
-
|
|
1255
|
-
let component: ReactNode | undefined;
|
|
1256
|
-
let handlerRan = false;
|
|
1257
|
-
if (shouldResolve) {
|
|
1258
|
-
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1259
|
-
}
|
|
1260
|
-
if (component === undefined) {
|
|
1261
|
-
const hasLoadingFallback =
|
|
1262
|
-
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1263
|
-
if (!shouldResolve) {
|
|
1264
|
-
component = null;
|
|
1265
|
-
} else if (handler === undefined) {
|
|
1266
|
-
// Handler evicted (production static slot) but static lookup missed.
|
|
1267
|
-
component = null;
|
|
1268
|
-
} else {
|
|
1269
|
-
// Slot-keyed pushes — see resolveParallelSegmentsWithRevalidation.
|
|
1270
|
-
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
1271
|
-
parallelId;
|
|
1272
|
-
handlerRan = true;
|
|
1273
|
-
if (hasLoadingFallback) {
|
|
1274
|
-
const result =
|
|
1275
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
1276
|
-
if (result instanceof Promise) {
|
|
1277
|
-
const tracked = deps.trackHandler(result, {
|
|
1278
|
-
segmentId: parallelId,
|
|
1279
|
-
segmentType: "parallel",
|
|
1280
|
-
});
|
|
1281
|
-
observeStreamedHandler(
|
|
1282
|
-
tracked,
|
|
1283
|
-
parallelId,
|
|
1284
|
-
"parallel",
|
|
1285
|
-
context.pathname,
|
|
1286
|
-
routeKey,
|
|
1287
|
-
params,
|
|
1288
|
-
);
|
|
1289
|
-
component = tracked as ReactNode;
|
|
1290
|
-
} else {
|
|
1291
|
-
component = result as ReactNode;
|
|
1292
|
-
}
|
|
1293
|
-
} else {
|
|
1294
|
-
component =
|
|
1295
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
segments.push({
|
|
1301
|
-
id: parallelId,
|
|
1302
|
-
namespace: parallelEntry.id,
|
|
1303
|
-
type: "parallel",
|
|
1304
|
-
index: 0,
|
|
1305
|
-
component,
|
|
1306
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1307
|
-
transition: applyViewTransitionDefault(
|
|
1308
|
-
parallelEntry.transition,
|
|
1309
|
-
deps.viewTransitionDefault,
|
|
1310
|
-
),
|
|
1311
|
-
params,
|
|
1312
|
-
slot,
|
|
1313
|
-
_handlerRan: handlerRan,
|
|
1314
|
-
belongsToRoute,
|
|
1315
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1316
|
-
...(parallelEntry.mountPath
|
|
1317
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1318
|
-
: {}),
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1201
|
+
// Resolve the orphan layout's parallel slots through the shared main-path
|
|
1202
|
+
// helper. The orphan policy is carried by explicit args, byte-for-byte:
|
|
1203
|
+
// - parentChainDefault "force-render": an unknown parent-chain slot seeds
|
|
1204
|
+
// `true` (orphan parallels always belong to the route — the #482 guard),
|
|
1205
|
+
// where the main path would seed `belongsToRoute || isNewParent`.
|
|
1206
|
+
// - loaderOrder "before": a slot's loaders are emitted before the slot
|
|
1207
|
+
// segment, matching the prior inlined order.
|
|
1208
|
+
// `entry.shortCode` inside the helper is `orphan.shortCode` (orphan is passed
|
|
1209
|
+
// as `entry`), so the parallel ids + loader shortCodeOverride are unchanged.
|
|
1210
|
+
const parallelResult = await resolveParallelSegmentsWithRevalidation(
|
|
1211
|
+
orphan,
|
|
1212
|
+
params,
|
|
1213
|
+
context,
|
|
1214
|
+
belongsToRoute,
|
|
1215
|
+
clientSegmentIds,
|
|
1216
|
+
prevParams,
|
|
1217
|
+
request,
|
|
1218
|
+
prevUrl,
|
|
1219
|
+
nextUrl,
|
|
1220
|
+
routeKey,
|
|
1221
|
+
deps,
|
|
1222
|
+
actionContext,
|
|
1223
|
+
stale,
|
|
1224
|
+
{ parentChainDefault: "force-render", loaderOrder: "before" },
|
|
1225
|
+
);
|
|
1226
|
+
segments.push(...parallelResult.segments);
|
|
1227
|
+
matchedIds.push(...parallelResult.matchedIds);
|
|
1321
1228
|
|
|
1322
1229
|
return { segments, matchedIds };
|
|
1323
1230
|
}
|
|
@@ -1335,7 +1242,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1335
1242
|
request: Request,
|
|
1336
1243
|
prevUrl: URL,
|
|
1337
1244
|
nextUrl: URL,
|
|
1338
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
1339
1245
|
actionContext: ActionContext | undefined,
|
|
1340
1246
|
interceptResult: { intercept: any; entry: EntryData } | null,
|
|
1341
1247
|
localRouteName: string,
|
|
@@ -1387,7 +1293,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1387
1293
|
request,
|
|
1388
1294
|
prevUrl,
|
|
1389
1295
|
nextUrl,
|
|
1390
|
-
loaderPromises,
|
|
1391
1296
|
deps,
|
|
1392
1297
|
actionContext,
|
|
1393
1298
|
stale,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamed Handler Telemetry
|
|
3
|
+
*
|
|
4
|
+
* Shared fire-and-forget rejection observer for streamed handler promises,
|
|
5
|
+
* used by both the fresh and revalidation segment-resolution paths. Lives in
|
|
6
|
+
* its own module (never mocked) so the resolution unit tests that mock
|
|
7
|
+
* helpers.js / telemetry.js with explicit export lists do not resolve it to
|
|
8
|
+
* undefined.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ReactNode } from "react";
|
|
12
|
+
import { getRouterContext } from "../router-context.js";
|
|
13
|
+
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
17
|
+
* React catches the actual error via its error boundary; this only emits
|
|
18
|
+
* the handler.error telemetry event.
|
|
19
|
+
*/
|
|
20
|
+
export function observeStreamedHandler(
|
|
21
|
+
promise: Promise<ReactNode>,
|
|
22
|
+
segmentId: string,
|
|
23
|
+
segmentType: string,
|
|
24
|
+
pathname?: string,
|
|
25
|
+
routeKey?: string,
|
|
26
|
+
params?: Record<string, string>,
|
|
27
|
+
): void {
|
|
28
|
+
let routerCtx;
|
|
29
|
+
try {
|
|
30
|
+
routerCtx = getRouterContext();
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!routerCtx?.telemetry) return;
|
|
35
|
+
const sink = resolveSink(routerCtx.telemetry);
|
|
36
|
+
const reqId = routerCtx.requestId;
|
|
37
|
+
promise.catch((err: unknown) => {
|
|
38
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
39
|
+
safeEmit(sink, {
|
|
40
|
+
type: "handler.error",
|
|
41
|
+
timestamp: performance.now(),
|
|
42
|
+
requestId: reqId,
|
|
43
|
+
segmentId,
|
|
44
|
+
segmentType,
|
|
45
|
+
error: errorObj,
|
|
46
|
+
handledByBoundary: true,
|
|
47
|
+
pathname,
|
|
48
|
+
routeKey,
|
|
49
|
+
params,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Barrel re-export -- see segment-resolution/ for implementations.
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
handleHandlerResult,
|
|
4
|
+
warnOnStreamedResponse,
|
|
5
|
+
} from "./segment-resolution/helpers.js";
|
|
3
6
|
export {
|
|
4
7
|
resolveLoaders,
|
|
5
8
|
type ResolveSegmentOptions,
|
|
@@ -68,7 +68,6 @@ export interface SegmentWrappers<TEnv = any> {
|
|
|
68
68
|
request: Request,
|
|
69
69
|
prevUrl: URL,
|
|
70
70
|
nextUrl: URL,
|
|
71
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
72
71
|
actionContext:
|
|
73
72
|
| {
|
|
74
73
|
actionId?: string;
|
|
@@ -192,7 +191,6 @@ export function createSegmentWrappers<TEnv = any>(
|
|
|
192
191
|
request: Request,
|
|
193
192
|
prevUrl: URL,
|
|
194
193
|
nextUrl: URL,
|
|
195
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
196
194
|
actionContext:
|
|
197
195
|
| {
|
|
198
196
|
actionId?: string;
|
|
@@ -216,7 +214,6 @@ export function createSegmentWrappers<TEnv = any>(
|
|
|
216
214
|
request,
|
|
217
215
|
prevUrl,
|
|
218
216
|
nextUrl,
|
|
219
|
-
loaderPromises,
|
|
220
217
|
actionContext,
|
|
221
218
|
interceptResult,
|
|
222
219
|
localRouteName,
|