@rangojs/router 0.27.0 → 0.29.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-fence.d.ts +2 -1
- package/dist/types/browser/event-controller.d.ts +8 -2
- package/dist/types/browser/history-state.d.ts +3 -9
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/react/use-action.d.ts +7 -2
- package/dist/types/browser/react/use-handle.d.ts +19 -16
- package/dist/types/browser/react/use-link-status.d.ts +5 -2
- package/dist/types/browser/segment-reconciler.d.ts +32 -4
- package/dist/types/browser/types.d.ts +24 -5
- package/dist/types/browser/wire-protocol.d.ts +26 -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/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 +11 -5
- 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 +6 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +13 -22
- 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/navigation-snapshot.d.ts +7 -0
- 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/helpers.d.ts +19 -3
- 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 +95 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
- 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 +11 -1
- 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 +53 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/render-route.d.ts +43 -8
- package/dist/types/testing/serve-shell-request.d.ts +8 -1
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +30 -3
- 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 +39 -0
- package/dist/vite/index.js +3822 -3727
- 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 +4 -2
- package/skills/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/hooks/SKILL.md +1 -1
- package/skills/hooks/data.md +8 -1
- package/skills/hooks/handle-and-actions.md +33 -2
- package/skills/hooks/navigation.md +121 -15
- package/skills/hooks/state.md +6 -4
- package/skills/hooks/urls.md +16 -5
- package/skills/intercept/SKILL.md +23 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/observability/SKILL.md +30 -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 +15 -1
- package/skills/testing/client-components.md +25 -18
- 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/view-transitions/SKILL.md +1 -1
- package/src/__internal.ts +7 -1
- package/src/browser/action-fence.ts +2 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +37 -13
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +59 -26
- package/src/browser/navigation-client.ts +20 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- package/src/browser/network-error-handler.ts +4 -0
- package/src/browser/partial-update.ts +33 -6
- 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/react/use-action.ts +24 -48
- package/src/browser/react/use-handle.ts +22 -18
- package/src/browser/react/use-link-status.ts +48 -60
- package/src/browser/react/use-navigation.ts +92 -41
- package/src/browser/rsc-router.tsx +1 -0
- package/src/browser/segment-reconciler.ts +109 -53
- package/src/browser/server-action-bridge.ts +81 -61
- package/src/browser/types.ts +25 -5
- package/src/browser/wire-protocol.ts +43 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +132 -75
- 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 +64 -46
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/read-through-swr.ts +20 -18
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +6 -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 +22 -10
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +82 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +5 -5
- package/src/router/match-context.ts +23 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +46 -57
- package/src/router/match-middleware/cache-lookup.ts +677 -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 +14 -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/helpers.ts +49 -13
- package/src/router/segment-resolution/loader-cache.ts +15 -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 +139 -0
- package/src/router/segment-resolution/revalidation.ts +939 -789
- package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
- 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 +11 -1
- package/src/router.ts +19 -17
- 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 +759 -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 +275 -227
- 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 +75 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/testing/dispatch.ts +226 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/render-route.tsx +199 -13
- package/src/testing/serve-shell-request.ts +36 -40
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +30 -3
- 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 +40 -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
|
+
runAsBackgroundWork,
|
|
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,26 +361,46 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
359
361
|
`[DocumentCache] STALE ${typeLabel}: ${url.pathname} (revalidating)`,
|
|
360
362
|
);
|
|
361
363
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
+
|
|
381
|
+
// Re-establish the request-context ALS around the whole background
|
|
382
|
+
// task: next() re-runs the full handler pipeline, and on workerd a
|
|
383
|
+
// waitUntil task runs detached from the request's I/O context, so a
|
|
384
|
+
// handler/component reading getRequestContext() would otherwise
|
|
385
|
+
// throw. Same fix as the route-level/use-cache background
|
|
386
|
+
// revalidation paths. The rango.background span (kind=
|
|
387
|
+
// document-revalidation) wraps the re-render AND the store write so
|
|
388
|
+
// the task's spans — the re-run's own rango.* set and the drain/put
|
|
389
|
+
// platform spans — nest under one explanatory parent instead of
|
|
390
|
+
// dangling under the ended foreground phases. Running the put
|
|
391
|
+
// inside the ALS matches the MISS path, where putResponse already
|
|
392
|
+
// executes within the foreground request context. No visitor made
|
|
393
|
+
// the invalidations the re-run makes: the pass's fresh-reads
|
|
394
|
+
// writer is the sink's, which sets nothing. onError marks the
|
|
395
|
+
// re-run's errors `background`, the failure caught below included.
|
|
396
|
+
runBackground(requestCtx, () =>
|
|
397
|
+
runAsBackgroundWork(rerunCtx, async () => {
|
|
398
|
+
try {
|
|
399
|
+
await observePhase(
|
|
378
400
|
PHASES.background("document-revalidation"),
|
|
379
401
|
async () => {
|
|
380
402
|
const fresh = await next();
|
|
381
|
-
const directives = shouldCacheResponse(fresh,
|
|
403
|
+
const directives = shouldCacheResponse(fresh, rerunCtx);
|
|
382
404
|
|
|
383
405
|
if (!directives || !fresh.body) return;
|
|
384
406
|
|
|
@@ -386,10 +408,10 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
386
408
|
// drain the fresh render fully before snapshotting tags
|
|
387
409
|
// (same render-complete barrier as the miss path).
|
|
388
410
|
const body = await new Response(fresh.body).arrayBuffer();
|
|
389
|
-
throwIfRenderErrored(
|
|
411
|
+
throwIfRenderErrored(rerunCtx);
|
|
390
412
|
// Not written when one of its tags was invalidated since
|
|
391
413
|
// the render started (#977).
|
|
392
|
-
const tags = collectRequestTags(
|
|
414
|
+
const tags = collectRequestTags(rerunCtx);
|
|
393
415
|
if (await predatesInvalidation(store, tags, start)) return;
|
|
394
416
|
await store.putResponse!(
|
|
395
417
|
cacheKey,
|
|
@@ -404,24 +426,20 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
404
426
|
`[DocumentCache] REVALIDATED ${typeLabel}: ${url.pathname}`,
|
|
405
427
|
);
|
|
406
428
|
},
|
|
407
|
-
)
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
requestCtx,
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
});
|
|
429
|
+
);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
// The route now rewrites (route middleware): nothing to refresh
|
|
432
|
+
// this entry with.
|
|
433
|
+
if (isRewrite(error)) return;
|
|
434
|
+
reportCacheError(
|
|
435
|
+
error,
|
|
436
|
+
"cache-write",
|
|
437
|
+
"[DocumentCache] revalidation",
|
|
438
|
+
requestCtx,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
425
443
|
|
|
426
444
|
return drainOnResponseCallbacks(
|
|
427
445
|
addCacheStatusHeader(cached.response, "STALE"),
|
|
@@ -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
|
/**
|
|
@@ -117,29 +117,31 @@ export async function readThroughItem<T>(
|
|
|
117
117
|
// span cover the write too (a slow or failing store write is part of
|
|
118
118
|
// the revalidation, not an untraced tail). The foreground miss below
|
|
119
119
|
// stays unwrapped: its context is already established.
|
|
120
|
+
// Reported inside the task, within wrapBackground's frame, so onError
|
|
121
|
+
// marks it `background`; rethrown so the span records the failure.
|
|
120
122
|
const revalidateTask = async () => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
try {
|
|
124
|
+
const fresh = await execute();
|
|
125
|
+
const serialized = await serialize(fresh);
|
|
126
|
+
if (serialized !== null) {
|
|
127
|
+
await setItem(key, serialized, storeOptions);
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
reportCacheError(
|
|
131
|
+
error,
|
|
132
|
+
"stale-revalidation",
|
|
133
|
+
"[read-through] background revalidation",
|
|
134
|
+
host ?? undefined,
|
|
135
|
+
);
|
|
136
|
+
throw error;
|
|
125
137
|
}
|
|
126
138
|
};
|
|
127
139
|
runBackground(
|
|
128
140
|
host,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
: revalidateTask());
|
|
134
|
-
} catch (error) {
|
|
135
|
-
reportCacheError(
|
|
136
|
-
error,
|
|
137
|
-
"stale-revalidation",
|
|
138
|
-
"[read-through] background revalidation",
|
|
139
|
-
host ?? undefined,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
},
|
|
141
|
+
() =>
|
|
142
|
+
(wrapBackground ? wrapBackground(revalidateTask) : revalidateTask())
|
|
143
|
+
// Reported above.
|
|
144
|
+
.catch(() => {}),
|
|
143
145
|
true,
|
|
144
146
|
);
|
|
145
147
|
return data;
|
|
@@ -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,10 @@
|
|
|
36
36
|
* predatesInvalidation, and docs/design/caching.md "Read-your-own-writes".
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
_getRequestContext,
|
|
41
|
+
isBackgroundWork,
|
|
42
|
+
} from "../server/request-context.js";
|
|
40
43
|
import { reportingAsync } from "./cache-error.js";
|
|
41
44
|
import { normalizeTags } from "./cache-tag.js";
|
|
42
45
|
import type { SegmentCacheStore } from "./types.js";
|
|
@@ -136,7 +139,8 @@ function warnPartialTagStore(fn: string, incapable: number): void {
|
|
|
136
139
|
*/
|
|
137
140
|
function markFreshReads(stores: SegmentCacheStore[]): void {
|
|
138
141
|
const ctx = _getRequestContext();
|
|
139
|
-
|
|
142
|
+
// No visitor made an invalidation in background work (runAsBackgroundWork).
|
|
143
|
+
if (!ctx?._setFreshReadsCookie || isBackgroundWork()) return;
|
|
140
144
|
let windowMs = 0;
|
|
141
145
|
for (const store of stores) {
|
|
142
146
|
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<
|