@rangojs/router 0.23.0 → 0.24.1
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 +4 -3
- package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
- package/dist/types/browser/partial-update.d.ts +6 -4
- package/dist/types/browser/react/NavigationProvider.d.ts +11 -7
- package/dist/types/browser/react/context.d.ts +3 -2
- package/dist/types/browser/types.d.ts +5 -1
- package/dist/types/build/route-types/scan-filter.d.ts +7 -1
- package/dist/types/cache/document-cache-policy.d.ts +24 -0
- package/dist/types/cache/document-cache.d.ts +2 -10
- package/dist/types/client-urls/types.d.ts +8 -0
- package/dist/types/context-var.d.ts +12 -0
- package/dist/types/errors.d.ts +10 -0
- package/dist/types/handles/pushed-by.d.ts +8 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.rsc.d.ts +2 -0
- package/dist/types/response-utils.d.ts +7 -0
- package/dist/types/reverse.d.ts +8 -8
- package/dist/types/route-definition/rewrite.d.ts +56 -0
- package/dist/types/route-map-builder.d.ts +7 -2
- package/dist/types/router/basename.d.ts +7 -0
- package/dist/types/router/handler-context.d.ts +1 -5
- package/dist/types/router/internal-params.d.ts +8 -0
- package/dist/types/router/middleware-types.d.ts +15 -0
- package/dist/types/router/revalidation.d.ts +1 -2
- package/dist/types/router/route-trie-builder.d.ts +10 -0
- package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +36 -13
- package/dist/types/rsc/full-payload.d.ts +11 -0
- package/dist/types/rsc/handler.d.ts +33 -0
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/loader-fetch.d.ts +16 -0
- package/dist/types/rsc/rewrite-passes.d.ts +74 -0
- package/dist/types/rsc/ssr-setup.d.ts +3 -1
- package/dist/types/server/request-context.d.ts +39 -2
- package/dist/types/server/rewrite-trail.d.ts +49 -0
- package/dist/types/server/slot-accessors.d.ts +29 -0
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +8 -0
- package/dist/types/testing/run-middleware.d.ts +20 -0
- package/dist/types/testing/serve-shell-request.d.ts +2 -1
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/request-scope.d.ts +7 -0
- package/dist/types/vite/discovery/state.d.ts +12 -9
- package/dist/vite/index.js +164 -103
- package/package.json +1 -1
- package/skills/caching/SKILL.md +12 -0
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +86 -1
- package/skills/hooks/SKILL.md +11 -6
- package/skills/hooks/data.md +3 -1
- package/skills/loader/SKILL.md +7 -1
- package/skills/middleware/SKILL.md +183 -2
- package/skills/ppr/SKILL.md +9 -0
- package/skills/rango/SKILL.md +1 -1
- package/skills/server-actions/SKILL.md +27 -0
- package/skills/testing/client-components.md +1 -0
- package/skills/testing/middleware.md +31 -10
- package/skills/testing/response-routes.md +1 -0
- package/skills/testing/serve-shell-request.md +19 -1
- package/skills/use-cache/SKILL.md +4 -2
- package/src/__augment-tests__/augmented.check.ts +20 -0
- package/src/browser/event-controller.ts +3 -0
- package/src/browser/merge-segment-loaders.ts +31 -18
- package/src/browser/navigation-bridge.ts +19 -3
- package/src/browser/partial-update.ts +27 -12
- package/src/browser/react/Link.tsx +7 -9
- package/src/browser/react/NavigationProvider.tsx +46 -26
- package/src/browser/react/context.ts +3 -2
- package/src/browser/react/use-router.ts +2 -4
- package/src/browser/server-action-bridge.ts +6 -0
- package/src/browser/types.ts +5 -1
- package/src/build/route-types/scan-filter.ts +11 -4
- package/src/cache/document-cache-policy.ts +131 -0
- package/src/cache/document-cache.ts +12 -119
- package/src/client-urls/client-root.tsx +3 -3
- package/src/client-urls/client-urls.ts +8 -1
- package/src/client-urls/types.ts +8 -0
- package/src/context-var.ts +32 -2
- package/src/errors.ts +19 -0
- package/src/handles/pushed-by.ts +26 -0
- package/src/index.rsc.ts +8 -0
- package/src/index.ts +7 -0
- package/src/response-utils.ts +19 -0
- package/src/reverse.ts +17 -14
- package/src/route-definition/redirect.ts +2 -15
- package/src/route-definition/rewrite.ts +146 -0
- package/src/route-map-builder.ts +14 -11
- package/src/router/basename.ts +20 -0
- package/src/router/handler-context.ts +3 -13
- package/src/router/intercept-resolution.ts +29 -12
- package/src/router/internal-params.ts +16 -0
- package/src/router/loader-resolution.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +11 -9
- package/src/router/match-middleware/cache-lookup.ts +20 -15
- package/src/router/match-middleware/cache-store.ts +9 -11
- package/src/router/match-middleware/intercept-resolution.ts +4 -2
- package/src/router/middleware-types.ts +16 -0
- package/src/router/middleware.ts +127 -49
- package/src/router/revalidation.ts +1 -1
- package/src/router/route-trie-builder.ts +28 -1
- package/src/router/segment-resolution/loader-snapshot.ts +18 -4
- package/src/router/segment-resolution/mask-nested.ts +8 -4
- package/src/router/segment-resolution/prefetch-deferral.ts +57 -17
- package/src/router/segment-resolution/revalidation.ts +57 -33
- package/src/rsc/full-payload.ts +37 -5
- package/src/rsc/handler.ts +82 -55
- package/src/rsc/helpers.ts +24 -0
- package/src/rsc/loader-fetch.ts +79 -24
- package/src/rsc/response-cache-serve.ts +18 -2
- package/src/rsc/response-route-handler.ts +4 -7
- package/src/rsc/rewrite-passes.ts +203 -0
- package/src/rsc/rsc-rendering.ts +4 -2
- package/src/rsc/ssr-setup.ts +11 -5
- package/src/server/cookie-parse.ts +36 -12
- package/src/server/cookie-store.ts +7 -3
- package/src/server/handle-store.ts +48 -17
- package/src/server/request-context.ts +154 -63
- package/src/server/request-headers.ts +4 -14
- package/src/server/rewrite-trail.ts +190 -0
- package/src/server/slot-accessors.ts +57 -0
- package/src/ssr/index.tsx +118 -16
- package/src/testing/dispatch.ts +138 -67
- package/src/testing/index.ts +1 -0
- package/src/testing/render-route.tsx +16 -3
- package/src/testing/run-loader.ts +2 -0
- package/src/testing/run-middleware.ts +56 -11
- package/src/testing/serve-shell-request.ts +15 -2
- package/src/thenable-status.ts +11 -0
- package/src/types/request-scope.ts +8 -0
- package/src/vite/discovery/state.ts +21 -13
- package/src/vite/plugins/expose-internal-ids.ts +2 -3
- package/src/vite/router-discovery.ts +29 -27
|
@@ -153,6 +153,31 @@ function sortSuffixParams(node: TrieNode): void {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Give every static-children map in the trie a null prototype, in place, and
|
|
158
|
+
* return the trie (#1109). The walker indexes `node.s` with raw URL segments,
|
|
159
|
+
* so a map with Object.prototype would answer `/constructor` or `/__proto__`
|
|
160
|
+
* with an inherited member. The builder creates null-prototype maps; a trie
|
|
161
|
+
* that arrives as JSON (the build manifest) or across the dev runner boundary
|
|
162
|
+
* gets them here. Object.setPrototypeOf keeps a JSON-parsed map's fast
|
|
163
|
+
* properties, where Object.create(null) starts in dictionary mode.
|
|
164
|
+
*/
|
|
165
|
+
export function nullPrototypeStaticChildren(node: TrieNode): TrieNode {
|
|
166
|
+
if (node.s) {
|
|
167
|
+
Object.setPrototypeOf(node.s, null);
|
|
168
|
+
for (const child of Object.values(node.s)) {
|
|
169
|
+
nullPrototypeStaticChildren(child);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (node.xp) {
|
|
173
|
+
for (const child of Object.values(node.xp)) {
|
|
174
|
+
nullPrototypeStaticChildren(child.c);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (node.p) nullPrototypeStaticChildren(node.p.c);
|
|
178
|
+
return node;
|
|
179
|
+
}
|
|
180
|
+
|
|
156
181
|
/**
|
|
157
182
|
* Insert a route into the trie. Optional params expand into two branches at
|
|
158
183
|
* registration time (skip-first, then present), so each terminal lives at the
|
|
@@ -271,7 +296,9 @@ function insertSegments(
|
|
|
271
296
|
const segment = segments[index];
|
|
272
297
|
|
|
273
298
|
if (segment.type === "static") {
|
|
274
|
-
|
|
299
|
+
// Null prototype: a segment named `constructor` or `__proto__` is an own
|
|
300
|
+
// key, never an inherited member to descend into and write a leaf on.
|
|
301
|
+
if (!node.s) node.s = Object.create(null) as Record<string, TrieNode>;
|
|
275
302
|
if (!node.s[segment.value]) node.s[segment.value] = {};
|
|
276
303
|
insertSegments(
|
|
277
304
|
node.s[segment.value],
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import { isValidElement, type ReactElement } from "react";
|
|
28
28
|
import { isThenable } from "../../handles/is-thenable.js";
|
|
29
|
+
import { assignOwn } from "../../context-var.js";
|
|
29
30
|
import {
|
|
30
31
|
cloneElementWithProps,
|
|
31
32
|
isPlainDataObject,
|
|
@@ -178,7 +179,7 @@ async function elideNested(
|
|
|
178
179
|
for (const key of Object.keys(source)) {
|
|
179
180
|
const r = await elideNested(source[key], seen);
|
|
180
181
|
if (r.state === "rejected") return r;
|
|
181
|
-
out
|
|
182
|
+
assignOwn(out, key, r.value);
|
|
182
183
|
if (r.value !== source[key]) changed = true;
|
|
183
184
|
result.hasHole ||= r.hasHole;
|
|
184
185
|
}
|
|
@@ -301,13 +302,21 @@ export function overlayLoaderContainer(
|
|
|
301
302
|
// Shape drift (fresh not a plain object): markers fall back to undefined.
|
|
302
303
|
const freshObj = isPlainDataObject(fresh) ? fresh : undefined;
|
|
303
304
|
let out: Record<string, unknown> | undefined;
|
|
305
|
+
// Own keys only: a key named like an Object.prototype member is data.
|
|
306
|
+
// The spread copies an own `__proto__` as data, so assigning a recorded
|
|
307
|
+
// key writes it; a fresh-only key goes through assignOwn.
|
|
304
308
|
for (const key of Object.keys(recorded)) {
|
|
305
|
-
const next = overlayLoaderContainer(
|
|
309
|
+
const next = overlayLoaderContainer(
|
|
310
|
+
freshObj && Object.hasOwn(freshObj, key) ? freshObj[key] : undefined,
|
|
311
|
+
recorded[key],
|
|
312
|
+
);
|
|
306
313
|
if (next !== recorded[key]) (out ??= { ...recorded })[key] = next;
|
|
307
314
|
}
|
|
308
315
|
if (freshObj) {
|
|
309
316
|
for (const key of Object.keys(freshObj)) {
|
|
310
|
-
if (!(key
|
|
317
|
+
if (!Object.hasOwn(recorded, key)) {
|
|
318
|
+
assignOwn((out ??= { ...recorded }), key, freshObj[key]);
|
|
319
|
+
}
|
|
311
320
|
}
|
|
312
321
|
}
|
|
313
322
|
return out ?? recorded;
|
|
@@ -341,7 +350,12 @@ function overlayProps(
|
|
|
341
350
|
const props = recorded.props;
|
|
342
351
|
let changed: Record<string, unknown> | undefined;
|
|
343
352
|
for (const key of Object.keys(props)) {
|
|
344
|
-
const next = overlayLoaderContainer(
|
|
353
|
+
const next = overlayLoaderContainer(
|
|
354
|
+
freshProps && Object.hasOwn(freshProps, key)
|
|
355
|
+
? freshProps[key]
|
|
356
|
+
: undefined,
|
|
357
|
+
props[key],
|
|
358
|
+
);
|
|
345
359
|
if (next !== props[key]) (changed ??= {})[key] = next;
|
|
346
360
|
}
|
|
347
361
|
return changed ? cloneElementWithProps(recorded, changed) : recorded;
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Deliberately a LEAF module: loader-mask (the other natural home) imports
|
|
10
10
|
* request-context, so any funnel importing the mask from there inherits that
|
|
11
|
-
* graph. This module imports only is-thenable and
|
|
11
|
+
* graph. This module imports only react and two leaves, is-thenable and
|
|
12
|
+
* context-var.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { cloneElement, isValidElement, type ReactElement } from "react";
|
|
16
|
+
import { assignOwn } from "../../context-var.js";
|
|
15
17
|
import { isThenable } from "../../handles/is-thenable.js";
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -52,7 +54,8 @@ type KeyValidated = { _store?: { validated?: unknown } };
|
|
|
52
54
|
* keyless child of a static children list (validated at creation) would warn
|
|
53
55
|
* "Each child in a list should have a unique key" once cloned. Pass only the
|
|
54
56
|
* props that change: cloneElement re-parents the element (owner) when its
|
|
55
|
-
* config carries `ref`.
|
|
57
|
+
* config carries `ref`. cloneElement copies props by assignment, so a prop
|
|
58
|
+
* key named `__proto__` can't survive a clone; prop copies don't use assignOwn.
|
|
56
59
|
*/
|
|
57
60
|
export function cloneElementWithProps(
|
|
58
61
|
element: ReactElement,
|
|
@@ -206,8 +209,9 @@ export function maskNestedContainerThenables(
|
|
|
206
209
|
seen.set(value, out);
|
|
207
210
|
let changed = false;
|
|
208
211
|
for (const key of Object.keys(source)) {
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
const masked = maskNestedContainerThenables(source[key], seen, report);
|
|
213
|
+
assignOwn(out, key, masked);
|
|
214
|
+
if (masked !== source[key]) changed = true;
|
|
211
215
|
}
|
|
212
216
|
if (changed) return out;
|
|
213
217
|
seen.set(value, value);
|
|
@@ -99,11 +99,8 @@ export interface PrefetchDeferral {
|
|
|
99
99
|
* not stored and not held. Empty outside a prefetch.
|
|
100
100
|
*/
|
|
101
101
|
readonly skipped: ReadonlySet<string>;
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
* has no content for it, a route cache() record included.
|
|
105
|
-
*/
|
|
106
|
-
readonly skipsChain: boolean;
|
|
102
|
+
/** How this request may use the route's cache() record (recordUse). */
|
|
103
|
+
readonly record: RecordUse;
|
|
107
104
|
/** Segment id of the handler unit this prefetch skipped. */
|
|
108
105
|
deferredUnit?: string;
|
|
109
106
|
/**
|
|
@@ -125,6 +122,31 @@ export interface PrefetchDeferral {
|
|
|
125
122
|
deferredLoaders?: Map<string, DeferredLoader>;
|
|
126
123
|
}
|
|
127
124
|
|
|
125
|
+
/**
|
|
126
|
+
* How a request may use the route's cache() record, decided once per match
|
|
127
|
+
* (planPrefetchDeferral) for the cache middleware.
|
|
128
|
+
*/
|
|
129
|
+
export interface RecordUse {
|
|
130
|
+
/**
|
|
131
|
+
* withCacheLookup reads it. False when this prefetch skips a chain unit
|
|
132
|
+
* (PrefetchDeferral.skipped): the unit takes everything below it, so the
|
|
133
|
+
* read would replay the record's handle pushes for segments the response
|
|
134
|
+
* does not carry. Decided from what the client holds, never per tree: a
|
|
135
|
+
* prefetch that holds the flagged layout reads and writes the record like
|
|
136
|
+
* any other request.
|
|
137
|
+
*/
|
|
138
|
+
readonly read: boolean;
|
|
139
|
+
/** withCacheStore writes it. False where `read` is: the record would be incomplete. */
|
|
140
|
+
readonly write: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* A background re-render of the whole route may write it: the stale-hit
|
|
143
|
+
* refresh (withBackgroundRevalidation) and the proactive write a partial
|
|
144
|
+
* response needs (withCacheStore). False for a fill: the re-render runs
|
|
145
|
+
* the handlers of segments the client holds, which a fill does not run.
|
|
146
|
+
*/
|
|
147
|
+
readonly refresh: boolean;
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
/** A loader this prefetch deferred: PrefetchDeferral.deferredLoaders. */
|
|
129
151
|
export interface DeferredLoader {
|
|
130
152
|
/**
|
|
@@ -329,7 +351,12 @@ export function planPrefetchDeferral(
|
|
|
329
351
|
}
|
|
330
352
|
}
|
|
331
353
|
}
|
|
332
|
-
|
|
354
|
+
const record: RecordUse = {
|
|
355
|
+
read: !skipsChain,
|
|
356
|
+
write: !skipsChain,
|
|
357
|
+
refresh: mode !== "fill",
|
|
358
|
+
};
|
|
359
|
+
return { scope, mode, storedFrom, held, skipped, record };
|
|
333
360
|
}
|
|
334
361
|
|
|
335
362
|
export function getPrefetchDeferral(
|
|
@@ -344,6 +371,30 @@ export function isFillRequest(ctx: HandlerContext<any, any>): boolean {
|
|
|
344
371
|
return getPrefetchDeferral(ctx)?.mode === "fill";
|
|
345
372
|
}
|
|
346
373
|
|
|
374
|
+
/**
|
|
375
|
+
* A fill's whole revalidation rule (R5): send a segment the client does not
|
|
376
|
+
* hold, keep one it holds, and consult nothing else (no revalidate()
|
|
377
|
+
* predicate, no params or search comparison, no trace entry). Undefined on
|
|
378
|
+
* any other request, where revalidation decides. `held` is the set the
|
|
379
|
+
* deciding site resolves with.
|
|
380
|
+
*/
|
|
381
|
+
export function fillSends(
|
|
382
|
+
ctx: HandlerContext<any, any>,
|
|
383
|
+
segmentId: string,
|
|
384
|
+
held: ReadonlySet<string>,
|
|
385
|
+
): boolean | undefined {
|
|
386
|
+
return isFillRequest(ctx) ? !held.has(segmentId) : undefined;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const RECORD_OPEN: RecordUse = { read: true, write: true, refresh: true };
|
|
390
|
+
|
|
391
|
+
/** PrefetchDeferral.record, or all of it open when the match has no plan. */
|
|
392
|
+
export function recordUse(
|
|
393
|
+
ctx: HandlerContext<any, any> | undefined,
|
|
394
|
+
): RecordUse {
|
|
395
|
+
return getPrefetchDeferral(ctx)?.record ?? RECORD_OPEN;
|
|
396
|
+
}
|
|
397
|
+
|
|
347
398
|
/**
|
|
348
399
|
* Register a loader this prefetch deferred, with the segment that registers
|
|
349
400
|
* it and how to start it there (PrefetchDeferral.deferredLoaders). The first
|
|
@@ -481,17 +532,6 @@ export function firstUnitCandidate(
|
|
|
481
532
|
return undefined;
|
|
482
533
|
}
|
|
483
534
|
|
|
484
|
-
/**
|
|
485
|
-
* This prefetch skips a layout above the route cache's boundary, and with it
|
|
486
|
-
* everything the record below holds: withCacheLookup and withCacheStore leave
|
|
487
|
-
* the record alone for it. Decided per request, from what the client holds:
|
|
488
|
-
* a prefetch of the same tree that holds the layout reads and writes the
|
|
489
|
-
* record like any other request.
|
|
490
|
-
*/
|
|
491
|
-
export function defersAboveRecord(ctx: HandlerContext<any, any>): boolean {
|
|
492
|
-
return getPrefetchDeferral(ctx)?.skipsChain === true;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
535
|
/**
|
|
496
536
|
* Record the handler unit this prefetch skipped. A `chain` entry takes what
|
|
497
537
|
* the walk reaches after it (PrefetchDeferral.below); a slot is skipped
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
import type { ReactNode } from "react";
|
|
10
10
|
import { invariant } from "../../errors";
|
|
11
11
|
import { revalidate } from "../loader-resolution.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
evaluateRevalidation,
|
|
14
|
+
type EvaluateRevalidationOptions,
|
|
15
|
+
} from "../revalidation.js";
|
|
13
16
|
import {
|
|
14
17
|
getParallelEntries,
|
|
15
18
|
getParallelSlotEntries,
|
|
@@ -38,8 +41,8 @@ import { entryLoadingMasksLoaders } from "./loader-mask.js";
|
|
|
38
41
|
import {
|
|
39
42
|
defersLoader,
|
|
40
43
|
deferUnit,
|
|
44
|
+
fillSends,
|
|
41
45
|
getPrefetchDeferral,
|
|
42
|
-
isFillRequest,
|
|
43
46
|
registerDeferredLoader,
|
|
44
47
|
} from "./prefetch-deferral.js";
|
|
45
48
|
import {
|
|
@@ -110,6 +113,28 @@ function emitRevalidationDecision(
|
|
|
110
113
|
});
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Whether a segment the client holds (`held`, the set resolution works on)
|
|
118
|
+
* renders again. A fill keeps it (prefetch-deferral.ts fillSends): no
|
|
119
|
+
* revalidate() predicate runs and nothing is traced or observed. Any other
|
|
120
|
+
* request asks the segment's revalidate() chain, and `onEvaluated` sees that
|
|
121
|
+
* answer.
|
|
122
|
+
*/
|
|
123
|
+
async function revalidateHeld<TEnv>({
|
|
124
|
+
held,
|
|
125
|
+
onEvaluated,
|
|
126
|
+
...options
|
|
127
|
+
}: EvaluateRevalidationOptions<TEnv> & {
|
|
128
|
+
held: ReadonlySet<string>;
|
|
129
|
+
onEvaluated?: (shouldRevalidate: boolean) => void;
|
|
130
|
+
}): Promise<boolean> {
|
|
131
|
+
const fill = fillSends(options.context, options.segment.id, held);
|
|
132
|
+
if (fill !== undefined) return fill;
|
|
133
|
+
const shouldRevalidate = await evaluateRevalidation(options);
|
|
134
|
+
onEvaluated?.(shouldRevalidate);
|
|
135
|
+
return shouldRevalidate;
|
|
136
|
+
}
|
|
137
|
+
|
|
113
138
|
// ---------------------------------------------------------------------------
|
|
114
139
|
// prefetch: false (prefetch-deferral.ts)
|
|
115
140
|
// ---------------------------------------------------------------------------
|
|
@@ -194,7 +219,6 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
194
219
|
}));
|
|
195
220
|
|
|
196
221
|
const matchedIds = loaderMeta.map((m) => m.segmentId);
|
|
197
|
-
const fill = isFillRequest(ctx);
|
|
198
222
|
|
|
199
223
|
const revalidationChecks = await Promise.all(
|
|
200
224
|
loaderMeta.map(
|
|
@@ -221,8 +245,6 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
221
245
|
}
|
|
222
246
|
return true;
|
|
223
247
|
}
|
|
224
|
-
// Fill: a held segment is skipped outright, no predicate runs.
|
|
225
|
-
if (fill) return false;
|
|
226
248
|
|
|
227
249
|
const dummySegment: ResolvedSegment = {
|
|
228
250
|
id: segmentId,
|
|
@@ -235,7 +257,7 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
235
257
|
belongsToRoute,
|
|
236
258
|
};
|
|
237
259
|
|
|
238
|
-
return await
|
|
260
|
+
return await revalidateHeld({
|
|
239
261
|
segment: dummySegment,
|
|
240
262
|
prevParams,
|
|
241
263
|
getPrevSegment: null,
|
|
@@ -251,6 +273,7 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
251
273
|
actionContext,
|
|
252
274
|
stale,
|
|
253
275
|
traceSource: "loader",
|
|
276
|
+
held: clientSegmentIds,
|
|
254
277
|
});
|
|
255
278
|
},
|
|
256
279
|
async () => true,
|
|
@@ -704,10 +727,12 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
704
727
|
// runs: a prefetch sends what the slot declares instead of running its
|
|
705
728
|
// handler.
|
|
706
729
|
let deferred = deferUnit(context, parallelId, "below");
|
|
730
|
+
// A fill sends exactly the slots the client lacks, ahead of every rule
|
|
731
|
+
// below (fillSends).
|
|
732
|
+
const fill = fillSends(context, parallelId, clientSegmentIds);
|
|
707
733
|
let shouldResolve: boolean;
|
|
708
|
-
if (
|
|
709
|
-
|
|
710
|
-
shouldResolve = !clientSegmentIds.has(parallelId);
|
|
734
|
+
if (fill !== undefined) {
|
|
735
|
+
shouldResolve = fill;
|
|
711
736
|
} else if (deferred) {
|
|
712
737
|
shouldResolve = true;
|
|
713
738
|
} else if (isFullRefetch) {
|
|
@@ -880,8 +905,6 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
880
905
|
}
|
|
881
906
|
return true;
|
|
882
907
|
}
|
|
883
|
-
// Fill: a held segment is skipped outright, no predicate runs.
|
|
884
|
-
if (isFillRequest(context)) return false;
|
|
885
908
|
|
|
886
909
|
const dummySegment: ResolvedSegment = {
|
|
887
910
|
id: entry.shortCode,
|
|
@@ -900,7 +923,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
900
923
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
901
924
|
};
|
|
902
925
|
|
|
903
|
-
|
|
926
|
+
return revalidateHeld({
|
|
904
927
|
segment: dummySegment,
|
|
905
928
|
prevParams,
|
|
906
929
|
getPrevSegment: null,
|
|
@@ -917,18 +940,20 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
917
940
|
stale,
|
|
918
941
|
traceSource:
|
|
919
942
|
entry.type === "route" ? "route-handler" : "layout-handler",
|
|
943
|
+
held: clientSegmentIds,
|
|
944
|
+
onEvaluated: (shouldRevalidate) => {
|
|
945
|
+
emitRevalidationDecision(
|
|
946
|
+
entry.shortCode,
|
|
947
|
+
context.pathname,
|
|
948
|
+
routeKey,
|
|
949
|
+
shouldRevalidate,
|
|
950
|
+
);
|
|
951
|
+
debugLog("segment.revalidate", "entry revalidation decision", {
|
|
952
|
+
segmentId: entry.shortCode,
|
|
953
|
+
shouldRevalidate,
|
|
954
|
+
});
|
|
955
|
+
},
|
|
920
956
|
});
|
|
921
|
-
emitRevalidationDecision(
|
|
922
|
-
entry.shortCode,
|
|
923
|
-
context.pathname,
|
|
924
|
-
routeKey,
|
|
925
|
-
shouldRevalidate,
|
|
926
|
-
);
|
|
927
|
-
debugLog("segment.revalidate", "entry revalidation decision", {
|
|
928
|
-
segmentId: entry.shortCode,
|
|
929
|
-
shouldRevalidate,
|
|
930
|
-
});
|
|
931
|
-
return shouldRevalidate;
|
|
932
957
|
},
|
|
933
958
|
async () => {
|
|
934
959
|
// Only an entry the client lacks (defersUnit). Its handler would
|
|
@@ -1308,8 +1333,6 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1308
1333
|
}
|
|
1309
1334
|
return true;
|
|
1310
1335
|
}
|
|
1311
|
-
// Fill: a held segment is skipped outright, no predicate runs.
|
|
1312
|
-
if (isFillRequest(context)) return false;
|
|
1313
1336
|
|
|
1314
1337
|
const dummySegment: ResolvedSegment = {
|
|
1315
1338
|
id: orphan.shortCode,
|
|
@@ -1323,7 +1346,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1323
1346
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
1324
1347
|
};
|
|
1325
1348
|
|
|
1326
|
-
|
|
1349
|
+
return revalidateHeld({
|
|
1327
1350
|
segment: dummySegment,
|
|
1328
1351
|
prevParams,
|
|
1329
1352
|
getPrevSegment: null,
|
|
@@ -1339,14 +1362,15 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1339
1362
|
actionContext,
|
|
1340
1363
|
stale,
|
|
1341
1364
|
traceSource: "orphan-layout",
|
|
1365
|
+
held: clientSegmentIds,
|
|
1366
|
+
onEvaluated: (shouldRevalidate) =>
|
|
1367
|
+
emitRevalidationDecision(
|
|
1368
|
+
orphan.shortCode,
|
|
1369
|
+
context.pathname,
|
|
1370
|
+
routeKey,
|
|
1371
|
+
shouldRevalidate,
|
|
1372
|
+
),
|
|
1342
1373
|
});
|
|
1343
|
-
emitRevalidationDecision(
|
|
1344
|
-
orphan.shortCode,
|
|
1345
|
-
context.pathname,
|
|
1346
|
-
routeKey,
|
|
1347
|
-
shouldRevalidate,
|
|
1348
|
-
);
|
|
1349
|
-
return shouldRevalidate;
|
|
1350
1374
|
},
|
|
1351
1375
|
async () => {
|
|
1352
1376
|
// Below a unit a prefetch skipped: the orphan goes with it.
|
package/src/rsc/full-payload.ts
CHANGED
|
@@ -14,7 +14,11 @@ import type { HandlerContext } from "./handler-context.js";
|
|
|
14
14
|
import type { RequestContext } from "../server/request-context.js";
|
|
15
15
|
import type { HandleStore } from "../server/handle-store.js";
|
|
16
16
|
import type { Theme } from "../theme/types.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
rendersForSharedAudience,
|
|
19
|
+
rewriteTrail,
|
|
20
|
+
visitorUrl,
|
|
21
|
+
} from "../server/rewrite-trail.js";
|
|
18
22
|
import { attachTransitionWhen } from "./attach-transition-when.js";
|
|
19
23
|
import { publicRouteName } from "../route-name.js";
|
|
20
24
|
import { resolvedHandleStream } from "../handles/deferred-resolution.js";
|
|
@@ -38,7 +42,7 @@ export function buildFullPayload(
|
|
|
38
42
|
): RscPayload {
|
|
39
43
|
return {
|
|
40
44
|
metadata: {
|
|
41
|
-
pathname: url.pathname,
|
|
45
|
+
pathname: renderedLocation(reqCtx, url).pathname,
|
|
42
46
|
routerId: ctx.router.id,
|
|
43
47
|
basename: ctx.router.basename,
|
|
44
48
|
segments: attachTransitionWhen(m.segments, reqCtx),
|
|
@@ -84,6 +88,36 @@ export function buildFullPayload(
|
|
|
84
88
|
};
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
/**
|
|
92
|
+
* The URL a render shows as the page's location (payload `pathname`, the SSR
|
|
93
|
+
* search and origin seed). For a request middleware rewrote, the visitor's
|
|
94
|
+
* URL, so the server markup and the hydrating client (which reads the address
|
|
95
|
+
* bar) agree on `usePathname()` and `useSearchParams()`. A render other
|
|
96
|
+
* visitors may be served shows `url`, the target, as a direct visit does: a
|
|
97
|
+
* shell capture, and a render the document cache stores (its key is the
|
|
98
|
+
* target). A render that shows the visitor's URL marks
|
|
99
|
+
* `_payloadVisitorLocation`, and the document cache does not store it.
|
|
100
|
+
*/
|
|
101
|
+
export function renderedLocation(
|
|
102
|
+
reqCtx:
|
|
103
|
+
| Pick<
|
|
104
|
+
RequestContext<any>,
|
|
105
|
+
| "_variables"
|
|
106
|
+
| "_shellCaptureRun"
|
|
107
|
+
| "_documentCacheRender"
|
|
108
|
+
| "_payloadVisitorLocation"
|
|
109
|
+
| "res"
|
|
110
|
+
>
|
|
111
|
+
| undefined,
|
|
112
|
+
url: URL,
|
|
113
|
+
): URL {
|
|
114
|
+
if (!reqCtx || !rewriteTrail(reqCtx) || rendersForSharedAudience(reqCtx)) {
|
|
115
|
+
return url;
|
|
116
|
+
}
|
|
117
|
+
reqCtx._payloadVisitorLocation = true;
|
|
118
|
+
return visitorUrl(reqCtx, url);
|
|
119
|
+
}
|
|
120
|
+
|
|
87
121
|
/**
|
|
88
122
|
* The theme a document payload carries as `metadata.initialTheme` (the
|
|
89
123
|
* ThemeProvider's first state), read with no identity guard: it is the
|
|
@@ -116,9 +150,7 @@ export function payloadInitialTheme(
|
|
|
116
150
|
): Theme | undefined {
|
|
117
151
|
const config = reqCtx._themeConfig;
|
|
118
152
|
if (!config) return undefined;
|
|
119
|
-
if (
|
|
120
|
-
return config.defaultTheme;
|
|
121
|
-
}
|
|
153
|
+
if (rendersForSharedAudience(reqCtx)) return config.defaultTheme;
|
|
122
154
|
const theme = reqCtx._readTheme();
|
|
123
155
|
if (theme !== config.defaultTheme) reqCtx._payloadVisitorTheme = true;
|
|
124
156
|
return theme;
|