@rangojs/router 0.26.6 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rango.js +1 -1
- package/dist/types/__internal.d.ts +7 -1
- package/dist/types/browser/action-coordinator.d.ts +0 -4
- package/dist/types/browser/action-fence.d.ts +11 -3
- package/dist/types/browser/event-controller.d.ts +20 -1
- package/dist/types/browser/history-state.d.ts +7 -9
- package/dist/types/browser/intercept-utils.d.ts +23 -0
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/segment-reconciler.d.ts +6 -4
- package/dist/types/browser/types.d.ts +2 -1
- package/dist/types/browser/wire-protocol.d.ts +25 -0
- package/dist/types/cache/cache-key-utils.d.ts +11 -5
- package/dist/types/cache/cache-scope.d.ts +34 -22
- package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
- package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
- package/dist/types/cache/handle-snapshot.d.ts +9 -4
- package/dist/types/cache/segment-codec.d.ts +14 -0
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/tag-invalidation.d.ts +10 -0
- package/dist/types/cache/taint.d.ts +5 -3
- package/dist/types/cache/types.d.ts +7 -3
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
- package/dist/types/prefetch-markers.d.ts +4 -6
- package/dist/types/prerender/store.d.ts +31 -0
- package/dist/types/prerender/warm-request.d.ts +3 -3
- package/dist/types/prerender.d.ts +2 -2
- package/dist/types/route-name.d.ts +5 -0
- package/dist/types/router/error-handling.d.ts +4 -4
- package/dist/types/router/instrument.d.ts +4 -3
- package/dist/types/router/intercept-resolution.d.ts +25 -9
- package/dist/types/router/match-context.d.ts +12 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +10 -20
- package/dist/types/router/match-middleware/cache-lookup.d.ts +35 -87
- package/dist/types/router/match-middleware/cache-store.d.ts +2 -2
- package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
- package/dist/types/router/prerender-match.d.ts +21 -6
- package/dist/types/router/preview-match.d.ts +2 -7
- package/dist/types/router/request-classification.d.ts +5 -0
- package/dist/types/router/revalidation.d.ts +6 -1
- package/dist/types/router/router-context.d.ts +9 -63
- package/dist/types/router/router-interfaces.d.ts +8 -14
- package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
- package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +93 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution.d.ts +1 -2
- package/dist/types/router/segment-wrappers.d.ts +11 -48
- package/dist/types/router/telemetry.d.ts +2 -0
- package/dist/types/router/types.d.ts +10 -0
- package/dist/types/rsc/full-payload.d.ts +2 -2
- package/dist/types/rsc/handler-context.d.ts +6 -6
- package/dist/types/rsc/json-route-result.d.ts +5 -9
- package/dist/types/rsc/request-stages.d.ts +203 -0
- package/dist/types/rsc/response-cache-serve.d.ts +7 -12
- package/dist/types/rsc/response-route-handler.d.ts +2 -2
- package/dist/types/rsc/routine-plan.d.ts +1 -1
- package/dist/types/rsc/server-action.d.ts +2 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
- package/dist/types/rsc/shell-capture.d.ts +132 -15
- package/dist/types/rsc/shell-serve.d.ts +37 -12
- package/dist/types/server/context.d.ts +9 -8
- package/dist/types/server/handle-store.d.ts +16 -0
- package/dist/types/server/render-pass.d.ts +72 -0
- package/dist/types/server/request-context.d.ts +24 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/serve-shell-request.d.ts +7 -0
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +9 -1
- package/dist/types/types/handler-context.d.ts +18 -7
- package/dist/types/types/segments.d.ts +1 -1
- package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
- package/dist/types/vite/discovery/build-env.d.ts +22 -0
- package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
- package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
- package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
- package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +12 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
- package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +8 -47
- package/dist/types/vite/temp-rsc-server.d.ts +39 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
- package/dist/types/wire-params.d.ts +34 -0
- package/dist/vite/index.js +3809 -3710
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +8 -5
- package/skills/caching/SKILL.md +37 -18
- package/skills/client-urls/SKILL.md +2 -1
- package/skills/document-cache/SKILL.md +18 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +53 -13
- package/skills/layout/SKILL.md +11 -7
- package/skills/loader/SKILL.md +30 -13
- package/skills/middleware/SKILL.md +19 -1
- package/skills/migrate-nextjs/SKILL.md +5 -4
- package/skills/parallel/SKILL.md +5 -4
- package/skills/ppr/SKILL.md +19 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +23 -11
- package/skills/server-actions/SKILL.md +51 -6
- package/skills/testing/predicates.md +1 -1
- package/skills/testing/response-routes.md +6 -4
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/use-cache/SKILL.md +46 -8
- package/skills/ux-architecture/SKILL.md +3 -2
- package/src/__internal.ts +7 -1
- package/src/browser/action-coordinator.ts +2 -13
- package/src/browser/action-fence.ts +11 -3
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +42 -3
- package/src/browser/history-state.ts +15 -13
- package/src/browser/intercept-utils.ts +51 -0
- package/src/browser/navigation-bridge.ts +34 -30
- package/src/browser/navigation-client.ts +21 -33
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +7 -11
- package/src/browser/partial-update.ts +8 -0
- package/src/browser/prefetch/cache.ts +2 -1
- package/src/browser/prefetch/fetch.ts +8 -16
- package/src/browser/prefetch/queue.ts +4 -4
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/filter-segment-order.ts +2 -2
- package/src/browser/rsc-router.tsx +17 -2
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +265 -127
- package/src/browser/types.ts +2 -1
- package/src/browser/wire-protocol.ts +41 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +110 -50
- package/src/cache/cache-scope.ts +110 -64
- package/src/cache/cf/cf-cache-constants.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +486 -874
- package/src/cache/cf/cf-kv-utils.ts +1 -1
- package/src/cache/document-cache.ts +42 -15
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +21 -2
- package/src/cache/taint.ts +5 -3
- package/src/cache/types.ts +7 -3
- package/src/cache/vercel/vercel-cache-store.ts +243 -310
- package/src/handles/pushed-by.ts +1 -1
- package/src/prefetch-markers.ts +4 -7
- package/src/prerender/store.ts +76 -0
- package/src/prerender/warm-request.ts +8 -4
- package/src/prerender.ts +2 -2
- package/src/route-name.ts +8 -0
- package/src/router/error-handling.ts +4 -4
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +77 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +4 -5
- package/src/router/match-context.ts +33 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +41 -53
- package/src/router/match-middleware/cache-lookup.ts +680 -720
- package/src/router/match-middleware/cache-store.ts +14 -12
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/match-result.ts +1 -20
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -3
- package/src/router/prerender-match.ts +100 -44
- package/src/router/preview-match.ts +6 -41
- package/src/router/request-classification.ts +13 -7
- package/src/router/revalidation.ts +33 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +14 -117
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/loader-cache.ts +14 -9
- package/src/router/segment-resolution/loader-mask.ts +5 -5
- package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
- package/src/router/segment-resolution/resolution-pass.ts +137 -0
- package/src/router/segment-resolution/revalidation.ts +897 -776
- package/src/router/segment-resolution.ts +4 -13
- package/src/router/segment-wrappers.ts +51 -269
- package/src/router/telemetry.ts +5 -0
- package/src/router/types.ts +10 -0
- package/src/router.ts +12 -13
- package/src/rsc/full-payload.ts +6 -5
- package/src/rsc/handler-context.ts +8 -14
- package/src/rsc/handler.ts +126 -507
- package/src/rsc/json-route-result.ts +5 -9
- package/src/rsc/loader-fetch.ts +6 -5
- package/src/rsc/progressive-enhancement.ts +5 -0
- package/src/rsc/request-stages.ts +757 -0
- package/src/rsc/response-cache-serve.ts +10 -14
- package/src/rsc/response-route-handler.ts +13 -5
- package/src/rsc/rewrite-passes.ts +2 -1
- package/src/rsc/routine-plan.ts +1 -1
- package/src/rsc/rsc-rendering.ts +172 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +3 -1
- package/src/rsc/shell-capture.ts +212 -169
- package/src/rsc/shell-serve.ts +121 -18
- package/src/rsc/ssr-setup.ts +5 -4
- package/src/segment-system.tsx +3 -3
- package/src/server/context.ts +22 -25
- package/src/server/handle-store.ts +55 -0
- package/src/server/render-pass.ts +291 -0
- package/src/server/request-context.ts +26 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/ssr/ssr-root.tsx +1 -0
- package/src/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +37 -32
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +21 -7
- package/src/types/segments.ts +1 -1
- package/src/vite/discovery/build-discovery-phase.ts +204 -0
- package/src/vite/discovery/build-env.ts +148 -0
- package/src/vite/discovery/bundle-scan-phase.ts +128 -0
- package/src/vite/discovery/dev-prerender-cache.ts +8 -3
- package/src/vite/discovery/dev-server-phase.ts +1776 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +1 -1
- package/src/vite/discovery/prerender-collection.ts +24 -39
- package/src/vite/discovery/state.ts +15 -0
- package/src/vite/plugins/version-plugin.ts +1 -1
- package/src/vite/plugins/virtual-entries.ts +1 -1
- package/src/vite/router-discovery.ts +29 -2428
- package/src/vite/temp-rsc-server.ts +223 -0
- package/src/vite/utils/prerender-utils.ts +47 -0
- package/src/wire-params.ts +35 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
|
@@ -71,7 +71,7 @@ export async function kvKeyDigest(value: string): Promise<string> {
|
|
|
71
71
|
* of restarting it -- copying set()'s original full-window max-age would reset
|
|
72
72
|
* CF's retention clock on every re-arm and pin a perpetually-stale entry forever.
|
|
73
73
|
* An entry lacking a valid deadline (legacy/tampered) floors to max-age=1, so it
|
|
74
|
-
* hard-expires in ~1s and self-heals via KV. Mirrors
|
|
74
|
+
* hard-expires in ~1s and self-heals via KV. Mirrors promoteToL1's math.
|
|
75
75
|
* @internal
|
|
76
76
|
*/
|
|
77
77
|
export function remainingCacheControl(headers: Headers, now: number): string {
|
|
@@ -14,17 +14,18 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { requestHeaders } from "../server/request-headers.js";
|
|
17
|
-
import { FILL_PARAM } from "../prefetch-markers.js";
|
|
18
17
|
import type { MiddlewareFn, MiddlewareContext } from "../router/middleware.js";
|
|
19
18
|
import {
|
|
20
19
|
documentCacheStoresRender,
|
|
21
20
|
shouldCacheResponse,
|
|
22
21
|
} from "./document-cache-policy.js";
|
|
23
22
|
import {
|
|
23
|
+
createRequestContext,
|
|
24
24
|
getRequestContext,
|
|
25
25
|
runWithRequestContext,
|
|
26
26
|
type RequestContext,
|
|
27
27
|
} from "../server/request-context.js";
|
|
28
|
+
import { deriveRenderPass } from "../server/render-pass.js";
|
|
28
29
|
import { mayNeedSSR } from "../rsc/ssr-setup.js";
|
|
29
30
|
import { requestKeyBase } from "./cache-key-utils.js";
|
|
30
31
|
import { runBackground } from "./background-task.js";
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
SEGMENT_FRAGMENT_CAPABILITY_HEADER,
|
|
46
47
|
SEGMENT_FRAGMENT_RECOVERY_HEADER,
|
|
47
48
|
} from "../segment-fragments.js";
|
|
49
|
+
import { WIRE } from "../wire-params.js";
|
|
48
50
|
|
|
49
51
|
export { documentCacheStoresRender };
|
|
50
52
|
|
|
@@ -229,20 +231,26 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
// Skip RSC action requests (mutations shouldn't be cached)
|
|
232
|
-
if (rawUrl.searchParams.has(
|
|
234
|
+
if (rawUrl.searchParams.has(WIRE.action)) {
|
|
233
235
|
return next();
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
// Skip loader requests (have their own caching)
|
|
237
|
-
if (rawUrl.searchParams.has(
|
|
239
|
+
if (rawUrl.searchParams.has(WIRE.loader)) {
|
|
238
240
|
return next();
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
// Skip fill
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
|
|
243
|
+
// Skip fill and stale partial requests: never served from a stored body,
|
|
244
|
+
// never stored. A fill renders against what one client holds. A stale
|
|
245
|
+
// request (the follow-up revalidation after an action, a Back/Forward
|
|
246
|
+
// entry's revalidation, or one holding no segments) may follow a write
|
|
247
|
+
// the key cannot see. Partial only: _rsc params leave the key, so a
|
|
248
|
+
// document GET carrying them must not force an origin render.
|
|
249
|
+
if (
|
|
250
|
+
rawUrl.searchParams.has(WIRE.partial) &&
|
|
251
|
+
(rawUrl.searchParams.has(WIRE.fill) ||
|
|
252
|
+
rawUrl.searchParams.get(WIRE.stale) === "true")
|
|
253
|
+
) {
|
|
246
254
|
return next();
|
|
247
255
|
}
|
|
248
256
|
|
|
@@ -277,7 +285,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
277
285
|
// Determine request type for cache key differentiation.
|
|
278
286
|
// Uses rawUrl for _rsc* param checks and mayNeedSSR for Accept-based
|
|
279
287
|
// detection. Full-document RSC fetches must not share the HTML cache slot.
|
|
280
|
-
const isPartial = rawUrl.searchParams.has(
|
|
288
|
+
const isPartial = rawUrl.searchParams.has(WIRE.partial);
|
|
281
289
|
const isRscRequest = !mayNeedSSR(ctx.request, rawUrl);
|
|
282
290
|
const typeLabel = isRscRequest ? "RSC" : "HTML";
|
|
283
291
|
|
|
@@ -290,7 +298,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
290
298
|
// gracefully to the origin handler instead of rejecting the request.
|
|
291
299
|
// This is a deliberate fail-open-to-origin policy: the fallback is
|
|
292
300
|
// "serve uncached from origin", not "use a different cache key".
|
|
293
|
-
const clientSegments = rawUrl.searchParams.get(
|
|
301
|
+
const clientSegments = rawUrl.searchParams.get(WIRE.segments) || "";
|
|
294
302
|
const segmentHash =
|
|
295
303
|
isPartial && clientSegments ? `:${hashSegmentIds(clientSegments)}` : "";
|
|
296
304
|
// Fragment envelopes require a capable Rango decoder. Keep that wire
|
|
@@ -353,6 +361,23 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
353
361
|
`[DocumentCache] STALE ${typeLabel}: ${url.pathname} (revalidating)`,
|
|
354
362
|
);
|
|
355
363
|
|
|
364
|
+
// The re-run renders on its own pass (render-pass.ts). Its response
|
|
365
|
+
// writes go to a sink that starts from this request's response stub
|
|
366
|
+
// as of now, as a MISS's render would: they build the re-run's own
|
|
367
|
+
// response, never the visitor's, and the visitor's later writes stay
|
|
368
|
+
// off it.
|
|
369
|
+
const rerunCtx = deriveRenderPass(requestCtx, "document-revalidation", {
|
|
370
|
+
handleStore: requestCtx._handleStore,
|
|
371
|
+
sink: createRequestContext({
|
|
372
|
+
env: ctx.env,
|
|
373
|
+
request: ctx.request,
|
|
374
|
+
url,
|
|
375
|
+
variables: {},
|
|
376
|
+
initialResponse: requestCtx.res,
|
|
377
|
+
themeConfig: requestCtx._themeConfig,
|
|
378
|
+
}),
|
|
379
|
+
});
|
|
380
|
+
|
|
356
381
|
runBackground(requestCtx, async () => {
|
|
357
382
|
try {
|
|
358
383
|
// Re-establish the request-context ALS around the whole background
|
|
@@ -366,13 +391,15 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
366
391
|
// platform spans — nest under one explanatory parent instead of
|
|
367
392
|
// dangling under the ended foreground phases. Running the put
|
|
368
393
|
// inside the ALS matches the MISS path, where putResponse already
|
|
369
|
-
// executes within the foreground request context.
|
|
370
|
-
|
|
394
|
+
// executes within the foreground request context. No visitor made
|
|
395
|
+
// the invalidations the re-run makes: the pass's fresh-reads
|
|
396
|
+
// writer is the sink's, which sets nothing.
|
|
397
|
+
await runWithRequestContext(rerunCtx, () =>
|
|
371
398
|
observePhase(
|
|
372
399
|
PHASES.background("document-revalidation"),
|
|
373
400
|
async () => {
|
|
374
401
|
const fresh = await next();
|
|
375
|
-
const directives = shouldCacheResponse(fresh,
|
|
402
|
+
const directives = shouldCacheResponse(fresh, rerunCtx);
|
|
376
403
|
|
|
377
404
|
if (!directives || !fresh.body) return;
|
|
378
405
|
|
|
@@ -380,10 +407,10 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
380
407
|
// drain the fresh render fully before snapshotting tags
|
|
381
408
|
// (same render-complete barrier as the miss path).
|
|
382
409
|
const body = await new Response(fresh.body).arrayBuffer();
|
|
383
|
-
throwIfRenderErrored(
|
|
410
|
+
throwIfRenderErrored(rerunCtx);
|
|
384
411
|
// Not written when one of its tags was invalidated since
|
|
385
412
|
// the render started (#977).
|
|
386
|
-
const tags = collectRequestTags(
|
|
413
|
+
const tags = collectRequestTags(rerunCtx);
|
|
387
414
|
if (await predatesInvalidation(store, tags, start)) return;
|
|
388
415
|
await store.putResponse!(
|
|
389
416
|
cacheKey,
|
|
@@ -14,7 +14,7 @@ import type { HandleOwners, SegmentHandleData } from "./types.js";
|
|
|
14
14
|
// in plain node/vitest). It is imported LAZILY inside the two async encode/decode
|
|
15
15
|
// helpers below so that modules which import handle-snapshot only for the
|
|
16
16
|
// plugin-rsc-free captureHandles/restoreHandles (e.g. cache-scope, on dispatch's
|
|
17
|
-
//
|
|
17
|
+
// response-route path) do not pull plugin-rsc at module load. Behavior
|
|
18
18
|
// is unchanged: both helpers are async and already awaited the codec.
|
|
19
19
|
|
|
20
20
|
const HANDLE_ENCODE_TIMEOUT_MS = 5000;
|
|
@@ -100,7 +100,7 @@ export async function decodeHandleValue<T>(encoded: string): Promise<T | null> {
|
|
|
100
100
|
*
|
|
101
101
|
* DSL-loader pushes stay out of the CACHE WRITE: a HIT runs loaders exactly
|
|
102
102
|
* as an uncached render of the same request would (cache-lookup.ts
|
|
103
|
-
*
|
|
103
|
+
* serveStoredSegments), so replaying a recorded copy would duplicate
|
|
104
104
|
* the live push. Handler pushes, including
|
|
105
105
|
* handler-invoked ctx.use(Loader) bodies (skipped with their handler on a
|
|
106
106
|
* HIT), are kept. The store tags loader pushes by array position at push
|
|
@@ -179,29 +179,39 @@ export type OwnedPushDelivery = (loaderId: string) => RecordAuthority;
|
|
|
179
179
|
* owner. Neither claims its loader, so the loader's own cache() HIT still
|
|
180
180
|
* delivers (the pushes holding a promise a restored copy lets through, or its
|
|
181
181
|
* entry's pushes in a placeholder's place).
|
|
182
|
+
*
|
|
183
|
+
* `sent` (a partial request) names the segments the response sends. Any
|
|
184
|
+
* other segment is kept with its handler's values, as on a live walk: only
|
|
185
|
+
* its loaders' copies are restored, which follow their loader as above.
|
|
182
186
|
*/
|
|
183
187
|
export function restoreHandles(
|
|
184
188
|
handles: Record<string, SegmentHandleData>,
|
|
185
189
|
handleStore: HandleStore,
|
|
186
190
|
owners?: HandleOwners,
|
|
187
191
|
owned?: OwnedPushDelivery,
|
|
192
|
+
sent?: ReadonlySet<string>,
|
|
188
193
|
): void {
|
|
189
194
|
for (const [segId, segHandles] of Object.entries(handles)) {
|
|
190
195
|
if (Object.keys(segHandles).length === 0) continue;
|
|
191
196
|
const segOwners = owners?.[segId];
|
|
197
|
+
const kept = sent !== undefined && !sent.has(segId);
|
|
192
198
|
if (!segOwners) {
|
|
193
|
-
handleStore.replaySegmentData(segId, segHandles);
|
|
199
|
+
if (!kept) handleStore.replaySegmentData(segId, segHandles);
|
|
194
200
|
continue;
|
|
195
201
|
}
|
|
196
202
|
const cleared: SegmentHandleData = {};
|
|
197
|
-
for (const handleName in segHandles)
|
|
203
|
+
for (const handleName in segHandles) {
|
|
204
|
+
if (!kept || segOwners[handleName]?.some(Boolean)) {
|
|
205
|
+
cleared[handleName] = [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
198
208
|
handleStore.replaySegmentData(segId, cleared);
|
|
199
209
|
for (const [handleName, values] of Object.entries(segHandles)) {
|
|
200
210
|
const valueOwners = segOwners[handleName];
|
|
201
211
|
for (let i = 0; i < values.length; i++) {
|
|
202
212
|
const owner = valueOwners?.[i];
|
|
203
213
|
if (!owner) {
|
|
204
|
-
handleStore.push(handleName, segId, values[i]);
|
|
214
|
+
if (!kept) handleStore.push(handleName, segId, values[i]);
|
|
205
215
|
continue;
|
|
206
216
|
}
|
|
207
217
|
const authority = owned?.(owner);
|
|
@@ -220,13 +230,15 @@ export function restoreHandles(
|
|
|
220
230
|
* (with or without a handles blob: a pinned loader whose capture pushed
|
|
221
231
|
* nothing has no copy, and its run must still add no settled push), then
|
|
222
232
|
* decode and restoreHandles. `keepIds` limits the restore to the segments a
|
|
223
|
-
* boundary-scoped scope serves. A decode failure
|
|
233
|
+
* boundary-scoped scope serves; `sent` is restoreHandles'. A decode failure
|
|
234
|
+
* skips the restore.
|
|
224
235
|
*/
|
|
225
236
|
export async function restoreRecordHandles(
|
|
226
237
|
handleStore: HandleStore,
|
|
227
238
|
record: { handles?: string; handleOwners?: HandleOwners },
|
|
228
239
|
owned: OwnedPushDelivery | undefined,
|
|
229
240
|
keepIds?: ReadonlySet<string>,
|
|
241
|
+
sent?: ReadonlySet<string>,
|
|
230
242
|
): Promise<void> {
|
|
231
243
|
if (owned) handleStore.setRecordAuthority(owned);
|
|
232
244
|
if (!record.handles) return;
|
|
@@ -237,7 +249,7 @@ export async function restoreRecordHandles(
|
|
|
237
249
|
if (!keepIds.has(id)) delete decoded[id];
|
|
238
250
|
}
|
|
239
251
|
}
|
|
240
|
-
restoreHandles(decoded, handleStore, record.handleOwners, owned);
|
|
252
|
+
restoreHandles(decoded, handleStore, record.handleOwners, owned, sent);
|
|
241
253
|
}
|
|
242
254
|
|
|
243
255
|
/**
|
|
@@ -129,6 +129,61 @@ export async function serializeResult(
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/** The Flight encode of a value and a decoded copy of it (teeResult). */
|
|
133
|
+
export interface TeedResult {
|
|
134
|
+
/** The copy: settles when the root row arrives; later rows stream in. */
|
|
135
|
+
decoded: Promise<unknown>;
|
|
136
|
+
/** The whole encoded text, or null when the stream failed. */
|
|
137
|
+
serialized: Promise<string | null>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Encode `value` once and decode a copy of it as the encode streams, so a
|
|
142
|
+
* "use cache" miss renders the copy as a hit renders a stored entry, and
|
|
143
|
+
* stores the same bytes. Null when the encode could not start.
|
|
144
|
+
*/
|
|
145
|
+
export function teeResult(
|
|
146
|
+
value: unknown,
|
|
147
|
+
onError?: (error: unknown) => void,
|
|
148
|
+
): TeedResult | null {
|
|
149
|
+
try {
|
|
150
|
+
const stream = renderToReadableStream(value, {
|
|
151
|
+
temporaryReferences: createTemporaryReferenceSet(),
|
|
152
|
+
onError,
|
|
153
|
+
});
|
|
154
|
+
const [toCopy, toStore] = stream.tee();
|
|
155
|
+
const decoded = Promise.resolve(
|
|
156
|
+
createFromReadableStream(
|
|
157
|
+
toCopy,
|
|
158
|
+
{ temporaryReferences: createTemporaryReferenceSet() },
|
|
159
|
+
PRESERVE_SERVER_REFS,
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
// Awaited by the caller only after the write is scheduled.
|
|
163
|
+
decoded.catch(() => {});
|
|
164
|
+
return { decoded, serialized: streamToString(toStore).catch(() => null) };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
167
|
+
console.warn("[segment-codec] teeResult failed:", error);
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Flight's refusal of a value it cannot encode (a function, an `on*`
|
|
175
|
+
* function, a class instance, a local symbol), by the message its server
|
|
176
|
+
* throws; the development build words a function in a children position on
|
|
177
|
+
* its own. A component that throws, or a "use cache" guard refusal, is not
|
|
178
|
+
* one.
|
|
179
|
+
*/
|
|
180
|
+
const UNSUPPORTED_VALUE =
|
|
181
|
+
/^(Only plain objects|Functions cannot be passed directly to Client Components|Functions are not valid as a child of Client Components|Event handlers cannot be passed to Client Component props|Only global symbols)/;
|
|
182
|
+
|
|
183
|
+
export function isUnsupportedValueError(error: unknown): boolean {
|
|
184
|
+
return error instanceof Error && UNSUPPORTED_VALUE.test(error.message);
|
|
185
|
+
}
|
|
186
|
+
|
|
132
187
|
/**
|
|
133
188
|
* RSC-deserialize a cached result string.
|
|
134
189
|
* Counterpart to serializeResult() — always receives a non-empty string.
|
|
@@ -383,7 +383,7 @@ export interface ShellLoaderSeedEntry {
|
|
|
383
383
|
|
|
384
384
|
/**
|
|
385
385
|
* A pin's loader. A pin is stored under its loader segment id,
|
|
386
|
-
* `${shortCode}D${index}.${loaderId}` (
|
|
386
|
+
* `${shortCode}D${index}.${loaderId}` (resolveOwnerLoaders), and a
|
|
387
387
|
* shortCode holds no "D" and no "." (server/context.ts getShortCode: M, L, P,
|
|
388
388
|
* R, C, I and digits), so the first `D<index>.` ends it. A loader id can hold
|
|
389
389
|
* both.
|
|
@@ -462,7 +462,7 @@ export const SHELL_HANDLES_RECORD_KEY = "handles";
|
|
|
462
462
|
|
|
463
463
|
/**
|
|
464
464
|
* The `handles` record of a prerender-served entry, which a HIT tail restores
|
|
465
|
-
* after the prerender store's handles (cache-lookup.ts
|
|
465
|
+
* after the prerender store's handles (cache-lookup.ts decodeStoredEntry), or
|
|
466
466
|
* undefined: an entry with a doc record, or one written before the record
|
|
467
467
|
* existed (v0.21), which serves as it did.
|
|
468
468
|
*/
|
|
@@ -36,7 +36,12 @@
|
|
|
36
36
|
* predatesInvalidation, and docs/design/caching.md "Read-your-own-writes".
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
-
import {
|
|
39
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
40
|
+
import {
|
|
41
|
+
_getRequestContext,
|
|
42
|
+
runWithRequestContext,
|
|
43
|
+
type RequestContext,
|
|
44
|
+
} from "../server/request-context.js";
|
|
40
45
|
import { reportingAsync } from "./cache-error.js";
|
|
41
46
|
import { normalizeTags } from "./cache-tag.js";
|
|
42
47
|
import type { SegmentCacheStore } from "./types.js";
|
|
@@ -129,6 +134,20 @@ function warnPartialTagStore(fn: string, incapable: number): void {
|
|
|
129
134
|
);
|
|
130
135
|
}
|
|
131
136
|
|
|
137
|
+
const backgroundRefresh = new AsyncLocalStorage<true>();
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Run a stale entry's background refresh on the visitor's request context: a
|
|
141
|
+
* "use cache" function's (cache-runtime.ts) or a cached loader's
|
|
142
|
+
* (loader-cache.ts). No visitor made the invalidations in it, so they set no
|
|
143
|
+
* fresh-reads cookie, as in a stale cache() HIT's re-render or a document
|
|
144
|
+
* cache entry's re-run, whose passes take a sink's no-op writer
|
|
145
|
+
* (server/render-pass.ts).
|
|
146
|
+
*/
|
|
147
|
+
export function runAsBackgroundRefresh<T>(ctx: RequestContext, fn: () => T): T {
|
|
148
|
+
return runWithRequestContext(ctx, () => backgroundRefresh.run(true, fn));
|
|
149
|
+
}
|
|
150
|
+
|
|
132
151
|
/**
|
|
133
152
|
* Set the fresh-reads cookie on this request's response for as long as the
|
|
134
153
|
* invalidated stores' isolate memos can stay stale (isolate-tag-memo.ts), so
|
|
@@ -136,7 +155,7 @@ function warnPartialTagStore(fn: string, incapable: number): void {
|
|
|
136
155
|
*/
|
|
137
156
|
function markFreshReads(stores: SegmentCacheStore[]): void {
|
|
138
157
|
const ctx = _getRequestContext();
|
|
139
|
-
if (!ctx?._setFreshReadsCookie) return;
|
|
158
|
+
if (!ctx?._setFreshReadsCookie || backgroundRefresh.getStore()) return;
|
|
140
159
|
let windowMs = 0;
|
|
141
160
|
for (const store of stores) {
|
|
142
161
|
windowMs = Math.max(windowMs, store.freshReadsWindowMs ?? 0);
|
package/src/cache/taint.ts
CHANGED
|
@@ -21,9 +21,11 @@ export function isTainted(value: unknown): boolean {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Symbol stamped on tainted ctx objects PASSED AS
|
|
25
|
-
* function during its execution. ctx.set(),
|
|
26
|
-
* flag and throw if present — those side
|
|
24
|
+
* Symbol stamped on tainted handler and middleware ctx objects PASSED AS
|
|
25
|
+
* ARGUMENTS to a "use cache" function during its execution. ctx.set(),
|
|
26
|
+
* ctx.header(), etc. check this flag and throw if present — those side
|
|
27
|
+
* effects are lost on cache hit. A request context passed as an argument is
|
|
28
|
+
* not stamped (registerCachedFunction in cache-runtime.ts).
|
|
27
29
|
*
|
|
28
30
|
* Argument objects only. Ambient access (cookies()/headers() and ctx methods
|
|
29
31
|
* reached via getRequestContext()) is guarded by the AsyncLocalStorage scope
|
package/src/cache/types.ts
CHANGED
|
@@ -96,10 +96,14 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
96
96
|
* Return value becomes the final cache key (unless route overrides with `key` option).
|
|
97
97
|
*
|
|
98
98
|
* Resolution priority:
|
|
99
|
-
* 1. Route-level `key` function (
|
|
99
|
+
* 1. Route-level `key` function (replaces the URL part; the request kind stays)
|
|
100
100
|
* 2. Store-level `keyGenerator` (modifies default key)
|
|
101
101
|
* 3. Default key generation (prefix:pathname:params)
|
|
102
102
|
*
|
|
103
|
+
* Keep `defaultKey` in the result: its prefix names the request kind (a
|
|
104
|
+
* document, a navigation or an intercept navigation), whose records hold
|
|
105
|
+
* different segments. A result without it gives every kind one record.
|
|
106
|
+
*
|
|
103
107
|
* Return `defaultKey` unchanged to leave a request unpartitioned. An empty
|
|
104
108
|
* string is not a key: where the result partitions a nested cache() on
|
|
105
109
|
* another store, or a ppr shell, it fails key resolution, so the request
|
|
@@ -293,7 +297,7 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
293
297
|
/**
|
|
294
298
|
* Declares the shell family present-but-inert: getShell/putShell exist but
|
|
295
299
|
* no-op (a custom store whose backing tier is conditionally unavailable).
|
|
296
|
-
*
|
|
300
|
+
* planShellCapture skips captures whose only write target is inert —
|
|
297
301
|
* the background render would be dead work that still occupies the
|
|
298
302
|
* per-isolate serialized capture queue (a promise-heavy route bakes for
|
|
299
303
|
* seconds per MISS with nothing stored). Absent/false means the family,
|
|
@@ -659,7 +663,7 @@ export type ShellSnapshotFamily = "segment" | "loader" | "handles";
|
|
|
659
663
|
* format (CachedEntryData `handles` + `handleOwners`). A Prerender + ppr
|
|
660
664
|
* route's handler layer comes from the prerender store, whose build-time
|
|
661
665
|
* entry has no loader push, so the entry keeps these for the HIT to restore
|
|
662
|
-
* as a doc record's (issue #1057,
|
|
666
|
+
* as a doc record's (issue #1057, decodeStoredEntry in cache-lookup.ts). An
|
|
663
667
|
* entry written before it (v0.21) has none and serves as it did.
|
|
664
668
|
*/
|
|
665
669
|
export type ShellSnapshotHandlesValue = Pick<
|