@rangojs/router 0.27.0 → 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/event-controller.d.ts +2 -1
- package/dist/types/browser/history-state.d.ts +3 -9
- 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/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 +5 -14
- package/dist/types/prerender/store.d.ts +31 -0
- package/dist/types/prerender/warm-request.d.ts +3 -3
- package/dist/types/prerender.d.ts +2 -2
- package/dist/types/route-name.d.ts +5 -0
- package/dist/types/router/error-handling.d.ts +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 +4 -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 +34 -86
- 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/router-context.d.ts +7 -46
- 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 +1 -1
- package/dist/types/rsc/shell-capture.d.ts +132 -15
- package/dist/types/rsc/shell-serve.d.ts +37 -12
- package/dist/types/server/context.d.ts +9 -8
- package/dist/types/server/handle-store.d.ts +16 -0
- package/dist/types/server/render-pass.d.ts +72 -0
- package/dist/types/server/request-context.d.ts +24 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- 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 +9 -1
- package/dist/types/types/segments.d.ts +1 -1
- package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
- package/dist/types/vite/discovery/build-env.d.ts +22 -0
- package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
- package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
- package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
- package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +12 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
- package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +8 -47
- package/dist/types/vite/temp-rsc-server.d.ts +39 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
- package/dist/types/wire-params.d.ts +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/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +17 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/ppr/SKILL.md +18 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +5 -1
- package/skills/server-actions/SKILL.md +8 -1
- package/skills/testing/response-routes.md +6 -4
- package/skills/use-cache/SKILL.md +46 -8
- package/src/__internal.ts +7 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +5 -3
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +14 -13
- package/src/browser/navigation-client.ts +18 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- 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 +1 -0
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +50 -57
- 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 +34 -13
- 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 +5 -16
- package/src/prerender/store.ts +76 -0
- package/src/prerender/warm-request.ts +8 -4
- package/src/prerender.ts +2 -2
- package/src/route-name.ts +8 -0
- package/src/router/error-handling.ts +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 +20 -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 +675 -726
- package/src/router/match-middleware/cache-store.ts +4 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -4
- package/src/router/prerender-match.ts +100 -44
- package/src/router/preview-match.ts +6 -41
- package/src/router/request-classification.ts +13 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +10 -95
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/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 +164 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +1 -1
- package/src/rsc/shell-capture.ts +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/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +23 -36
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +12 -1
- package/src/types/segments.ts +1 -1
- package/src/vite/discovery/build-discovery-phase.ts +204 -0
- package/src/vite/discovery/build-env.ts +148 -0
- package/src/vite/discovery/bundle-scan-phase.ts +128 -0
- package/src/vite/discovery/dev-prerender-cache.ts +8 -3
- package/src/vite/discovery/dev-server-phase.ts +1776 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +1 -1
- package/src/vite/discovery/prerender-collection.ts +24 -39
- package/src/vite/discovery/state.ts +15 -0
- package/src/vite/plugins/version-plugin.ts +1 -1
- package/src/vite/plugins/virtual-entries.ts +1 -1
- package/src/vite/router-discovery.ts +29 -2428
- package/src/vite/temp-rsc-server.ts +223 -0
- package/src/vite/utils/prerender-utils.ts +47 -0
- package/src/wire-params.ts +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, STALE_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,12 +231,12 @@ 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
|
|
|
@@ -245,9 +247,9 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
245
247
|
// the key cannot see. Partial only: _rsc params leave the key, so a
|
|
246
248
|
// document GET carrying them must not force an origin render.
|
|
247
249
|
if (
|
|
248
|
-
rawUrl.searchParams.has(
|
|
249
|
-
(rawUrl.searchParams.has(
|
|
250
|
-
rawUrl.searchParams.get(
|
|
250
|
+
rawUrl.searchParams.has(WIRE.partial) &&
|
|
251
|
+
(rawUrl.searchParams.has(WIRE.fill) ||
|
|
252
|
+
rawUrl.searchParams.get(WIRE.stale) === "true")
|
|
251
253
|
) {
|
|
252
254
|
return next();
|
|
253
255
|
}
|
|
@@ -283,7 +285,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
283
285
|
// Determine request type for cache key differentiation.
|
|
284
286
|
// Uses rawUrl for _rsc* param checks and mayNeedSSR for Accept-based
|
|
285
287
|
// detection. Full-document RSC fetches must not share the HTML cache slot.
|
|
286
|
-
const isPartial = rawUrl.searchParams.has(
|
|
288
|
+
const isPartial = rawUrl.searchParams.has(WIRE.partial);
|
|
287
289
|
const isRscRequest = !mayNeedSSR(ctx.request, rawUrl);
|
|
288
290
|
const typeLabel = isRscRequest ? "RSC" : "HTML";
|
|
289
291
|
|
|
@@ -296,7 +298,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
296
298
|
// gracefully to the origin handler instead of rejecting the request.
|
|
297
299
|
// This is a deliberate fail-open-to-origin policy: the fallback is
|
|
298
300
|
// "serve uncached from origin", not "use a different cache key".
|
|
299
|
-
const clientSegments = rawUrl.searchParams.get(
|
|
301
|
+
const clientSegments = rawUrl.searchParams.get(WIRE.segments) || "";
|
|
300
302
|
const segmentHash =
|
|
301
303
|
isPartial && clientSegments ? `:${hashSegmentIds(clientSegments)}` : "";
|
|
302
304
|
// Fragment envelopes require a capable Rango decoder. Keep that wire
|
|
@@ -359,6 +361,23 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
359
361
|
`[DocumentCache] STALE ${typeLabel}: ${url.pathname} (revalidating)`,
|
|
360
362
|
);
|
|
361
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
|
+
|
|
362
381
|
runBackground(requestCtx, async () => {
|
|
363
382
|
try {
|
|
364
383
|
// Re-establish the request-context ALS around the whole background
|
|
@@ -372,13 +391,15 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
372
391
|
// platform spans — nest under one explanatory parent instead of
|
|
373
392
|
// dangling under the ended foreground phases. Running the put
|
|
374
393
|
// inside the ALS matches the MISS path, where putResponse already
|
|
375
|
-
// executes within the foreground request context.
|
|
376
|
-
|
|
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, () =>
|
|
377
398
|
observePhase(
|
|
378
399
|
PHASES.background("document-revalidation"),
|
|
379
400
|
async () => {
|
|
380
401
|
const fresh = await next();
|
|
381
|
-
const directives = shouldCacheResponse(fresh,
|
|
402
|
+
const directives = shouldCacheResponse(fresh, rerunCtx);
|
|
382
403
|
|
|
383
404
|
if (!directives || !fresh.body) return;
|
|
384
405
|
|
|
@@ -386,10 +407,10 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
386
407
|
// drain the fresh render fully before snapshotting tags
|
|
387
408
|
// (same render-complete barrier as the miss path).
|
|
388
409
|
const body = await new Response(fresh.body).arrayBuffer();
|
|
389
|
-
throwIfRenderErrored(
|
|
410
|
+
throwIfRenderErrored(rerunCtx);
|
|
390
411
|
// Not written when one of its tags was invalidated since
|
|
391
412
|
// the render started (#977).
|
|
392
|
-
const tags = collectRequestTags(
|
|
413
|
+
const tags = collectRequestTags(rerunCtx);
|
|
393
414
|
if (await predatesInvalidation(store, tags, start)) return;
|
|
394
415
|
await store.putResponse!(
|
|
395
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<
|