@rangojs/router 0.17.0 → 0.19.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/README.md +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +9 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +47 -3
- package/src/rsc/handler.ts +24 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Capture data snapshot: recording + seeding stores for PPR shell parity.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* A PPR HIT serves frozen prelude bytes, then a Flight payload for hydration
|
|
5
|
+
* that must agree with them byte for byte. The handler layer's part of that
|
|
6
|
+
* payload is the capture's own doc segment record: the capture writes it
|
|
7
|
+
* first, renders its prelude from it, and every HIT replays it (no handler
|
|
8
|
+
* runs on a HIT). The loader layer's part is the bake-lane (`ssr: false`)
|
|
9
|
+
* loader pins: a promise-free bake-lane loader is served from its pin and
|
|
10
|
+
* does not run; one whose return carries promises (`holes`), or whose pushes
|
|
11
|
+
* the record could not keep (`runs`), runs on the HIT. See
|
|
12
|
+
* docs/design/ppr-shell-resume.md.
|
|
11
13
|
*
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* The invariant, verbatim: the snapshot is exactly the set of cache-store reads
|
|
22
|
-
* the capture render performed; replaying them on a HIT reproduces the shell
|
|
23
|
-
* content byte-identically; everything not recorded stays live.
|
|
14
|
+
* The invariant, verbatim: the snapshot is the doc record and the bake-lane
|
|
15
|
+
* loader pins; replaying them on a HIT reproduces the shell content
|
|
16
|
+
* byte-identically; everything else a HIT reads comes from the store. A hole
|
|
17
|
+
* (a live-lane loader, or a promise nested in a bake-lane return) never reads
|
|
18
|
+
* the capture's copy of a value: it is dynamic or has its own cache. A
|
|
19
|
+
* bake-lane loader body that runs on a HIT reads the store too; its pinned
|
|
20
|
+
* container paths stay the pin's (overlayLoaderContainer), and its settled
|
|
21
|
+
* pushes stay the record's (HandleStore.pushRestored).
|
|
24
22
|
*/
|
|
25
23
|
|
|
26
24
|
import type {
|
|
@@ -31,63 +29,45 @@ import type {
|
|
|
31
29
|
CachedEntryData,
|
|
32
30
|
ShellCacheEntry,
|
|
33
31
|
ShellSnapshotRecord,
|
|
34
|
-
ShellSnapshotItemValue,
|
|
35
|
-
ShellSnapshotResponseValue,
|
|
36
32
|
ShellSnapshotLoaderValue,
|
|
37
33
|
CacheReadError,
|
|
38
34
|
} from "./types.js";
|
|
39
|
-
import {
|
|
40
|
-
import { bufferToBase64, base64ToBuffer } from "./cf/cf-base64.js";
|
|
41
|
-
import { isPerClientSignalHeader } from "../browser/cookie-name.js";
|
|
42
|
-
|
|
43
|
-
/** Compose the last-write-wins map key. NUL (`\u0000`) cannot appear in a cache key. */
|
|
44
|
-
function recordKey(family: ShellSnapshotRecord["family"], key: string): string {
|
|
45
|
-
return `${family}\u0000${key}`;
|
|
46
|
-
}
|
|
35
|
+
import { settleGrowing } from "./background-task.js";
|
|
47
36
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/** Rebuild a live Response from a snapshot's stored response shape. */
|
|
63
|
-
function deserializeResponse(value: ShellSnapshotResponseValue): Response {
|
|
64
|
-
return new Response(base64ToBuffer(value.body), {
|
|
65
|
-
status: value.status,
|
|
66
|
-
headers: new Headers(value.headers),
|
|
67
|
-
});
|
|
37
|
+
/**
|
|
38
|
+
* The inner store's isTagsInvalidatedSince, absent when it has none. A
|
|
39
|
+
* capture runs on RecordingShellStore as the request's store, and the writes
|
|
40
|
+
* it passes through gate on it (#977, tag-invalidation.ts
|
|
41
|
+
* predatesInvalidation).
|
|
42
|
+
*/
|
|
43
|
+
function forwardTagHistory<TEnv>(
|
|
44
|
+
inner: SegmentCacheStore<TEnv>,
|
|
45
|
+
): SegmentCacheStore<TEnv>["isTagsInvalidatedSince"] {
|
|
46
|
+
return inner.isTagsInvalidatedSince
|
|
47
|
+
? (tags, sinceMs, options) =>
|
|
48
|
+
inner.isTagsInvalidatedSince!(tags, sinceMs, options)
|
|
49
|
+
: undefined;
|
|
68
50
|
}
|
|
69
51
|
|
|
70
52
|
/**
|
|
71
|
-
* A store wrapper the CAPTURE render reads through. Every call passes through
|
|
72
|
-
* the underlying store unchanged;
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* The shell family (getShell/putShell) is never recorded (the snapshot rides
|
|
78
|
-
* inside a shell entry). Reads that MISS are not recorded (a miss produced no
|
|
79
|
-
* shell content; if the render then computed and wrote, that write is recorded).
|
|
53
|
+
* A store wrapper the CAPTURE render reads through. Every call passes through
|
|
54
|
+
* to the underlying store unchanged; the one thing it records is the doc
|
|
55
|
+
* segment record ({@link recordSegmentWrite}). No cache read is recorded: a
|
|
56
|
+
* HIT replays the handler layer from the doc record and serves a promise-free
|
|
57
|
+
* bake-lane loader from its pin, and everything else a HIT reads (a hole, a
|
|
58
|
+
* bake-lane loader body that runs there) comes from the store.
|
|
80
59
|
*
|
|
81
60
|
* Deferred writes: cache writes run under waitUntil (fire-and-forget on Node,
|
|
82
|
-
* executionContext on workerd), so
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
61
|
+
* executionContext on workerd), so the doc record's write may land after the
|
|
62
|
+
* capture's match returns. The capture collects those write promises via
|
|
63
|
+
* {@link trackWrite} and awaits them ({@link settleWrites}) before reading the
|
|
64
|
+
* record and draining.
|
|
86
65
|
*/
|
|
87
66
|
export class RecordingShellStore<
|
|
88
67
|
TEnv = unknown,
|
|
89
68
|
> implements SegmentCacheStore<TEnv> {
|
|
90
|
-
|
|
69
|
+
/** The recorded segment records by key, last write wins. */
|
|
70
|
+
private readonly records = new Map<string, CachedEntryData>();
|
|
91
71
|
private readonly writes: Promise<unknown>[] = [];
|
|
92
72
|
|
|
93
73
|
constructor(private readonly inner: SegmentCacheStore<TEnv>) {}
|
|
@@ -101,13 +81,8 @@ export class RecordingShellStore<
|
|
|
101
81
|
get supportsPassiveShellReads(): true | undefined {
|
|
102
82
|
return this.inner.supportsPassiveShellReads;
|
|
103
83
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
family: ShellSnapshotRecord["family"],
|
|
107
|
-
key: string,
|
|
108
|
-
value: ShellSnapshotRecord["value"],
|
|
109
|
-
): void {
|
|
110
|
-
this.records.set(recordKey(family, key), { family, key, value });
|
|
84
|
+
get isTagsInvalidatedSince(): SegmentCacheStore<TEnv>["isTagsInvalidatedSince"] {
|
|
85
|
+
return forwardTagHistory(this.inner);
|
|
111
86
|
}
|
|
112
87
|
|
|
113
88
|
/** Track a deferred cache-write promise so the capture can await it pre-drain. */
|
|
@@ -116,56 +91,50 @@ export class RecordingShellStore<
|
|
|
116
91
|
}
|
|
117
92
|
|
|
118
93
|
/**
|
|
119
|
-
* Record
|
|
120
|
-
* store. The
|
|
121
|
-
* (via {@link SnapshotOnlySegmentStore}): the
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
94
|
+
* Record the doc segment record into the snapshot WITHOUT touching the
|
|
95
|
+
* inner store. The capture's implicit doc-cache scope writes through this
|
|
96
|
+
* (via {@link SnapshotOnlySegmentStore}): the record must ride ONLY inside
|
|
97
|
+
* the shell entry — a passthrough write would leave a doc-keyed entry in the
|
|
98
|
+
* real store that the NEXT capture's lookup would hit, replaying the
|
|
99
|
+
* previous generation's segments instead of re-running handlers (breaking
|
|
125
100
|
* SWR recapture freshness).
|
|
126
101
|
*/
|
|
127
102
|
recordSegmentWrite(key: string, data: CachedEntryData): void {
|
|
128
|
-
this.
|
|
103
|
+
this.records.set(key, data);
|
|
129
104
|
}
|
|
130
105
|
|
|
131
106
|
/**
|
|
132
|
-
* Await the tracked deferred writes so their records are present before
|
|
133
|
-
*
|
|
134
|
-
* cacheRoute path schedules its actual store.set in a second waitUntil
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
107
|
+
* Await the tracked deferred writes so their records are present before
|
|
108
|
+
* drain, including a NESTED write a settled one scheduled (the ring-3
|
|
109
|
+
* cacheRoute path schedules its actual store.set in a second waitUntil).
|
|
110
|
+
* Bounded: a pathologically slow write must never stall the capture task.
|
|
111
|
+
* Resolves true when every tracked write settled, false at the deadline
|
|
112
|
+
* (settleCaptureRecord then fails the capture when the doc record is among
|
|
113
|
+
* the writes still pending); a later call still waits for what is pending.
|
|
139
114
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
while (this.writes.length > 0) {
|
|
143
|
-
const remaining = deadline - Date.now();
|
|
144
|
-
if (remaining <= 0) return;
|
|
145
|
-
// Take the current batch; new writes scheduled while awaiting accumulate in
|
|
146
|
-
// this.writes and are drained on the next iteration.
|
|
147
|
-
const batch = this.writes.splice(0);
|
|
148
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
149
|
-
const guard = new Promise<void>((resolve) => {
|
|
150
|
-
timer = setTimeout(resolve, remaining);
|
|
151
|
-
(timer as { unref?: () => void }).unref?.();
|
|
152
|
-
});
|
|
153
|
-
await Promise.race([Promise.allSettled(batch).then(() => {}), guard]);
|
|
154
|
-
if (timer) clearTimeout(timer);
|
|
155
|
-
}
|
|
115
|
+
settleWrites(timeoutMs: number): Promise<boolean> {
|
|
116
|
+
return settleGrowing(this.writes, Date.now() + timeoutMs);
|
|
156
117
|
}
|
|
157
118
|
|
|
158
|
-
/** The recorded
|
|
119
|
+
/** The recorded segment records, or undefined if none. */
|
|
159
120
|
drainSnapshot(): ShellSnapshotRecord[] | undefined {
|
|
160
|
-
|
|
121
|
+
if (this.records.size === 0) return undefined;
|
|
122
|
+
return [...this.records].map(
|
|
123
|
+
([key, value]): ShellSnapshotRecord => ({
|
|
124
|
+
family: "segment",
|
|
125
|
+
key,
|
|
126
|
+
value,
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** One recorded segment record, by key. */
|
|
132
|
+
getRecord(key: string): CachedEntryData | undefined {
|
|
133
|
+
return this.records.get(key);
|
|
161
134
|
}
|
|
162
135
|
|
|
163
136
|
async get(key: string): Promise<CacheGetResult | null | CacheReadError> {
|
|
164
|
-
|
|
165
|
-
if (result && result !== CACHE_READ_ERROR) {
|
|
166
|
-
this.record("segment", key, result.data);
|
|
167
|
-
}
|
|
168
|
-
return result;
|
|
137
|
+
return this.inner.get(key);
|
|
169
138
|
}
|
|
170
139
|
|
|
171
140
|
async set(
|
|
@@ -174,7 +143,6 @@ export class RecordingShellStore<
|
|
|
174
143
|
ttl: number,
|
|
175
144
|
swr?: number,
|
|
176
145
|
): Promise<void> {
|
|
177
|
-
this.record("segment", key, data);
|
|
178
146
|
return this.inner.set(key, data, ttl, swr);
|
|
179
147
|
}
|
|
180
148
|
|
|
@@ -189,11 +157,7 @@ export class RecordingShellStore<
|
|
|
189
157
|
async getResponse(
|
|
190
158
|
key: string,
|
|
191
159
|
): Promise<{ response: Response; shouldRevalidate: boolean } | null> {
|
|
192
|
-
|
|
193
|
-
const result = await this.inner.getResponse(key);
|
|
194
|
-
if (result)
|
|
195
|
-
this.record("response", key, await serializeResponse(result.response));
|
|
196
|
-
return result;
|
|
160
|
+
return this.inner.getResponse ? this.inner.getResponse(key) : null;
|
|
197
161
|
}
|
|
198
162
|
|
|
199
163
|
async putResponse(
|
|
@@ -203,23 +167,11 @@ export class RecordingShellStore<
|
|
|
203
167
|
swr?: number,
|
|
204
168
|
tags?: string[],
|
|
205
169
|
): Promise<void> {
|
|
206
|
-
|
|
207
|
-
this.record("response", key, await serializeResponse(response));
|
|
208
|
-
return this.inner.putResponse(key, response, ttl, swr, tags);
|
|
170
|
+
return this.inner.putResponse?.(key, response, ttl, swr, tags);
|
|
209
171
|
}
|
|
210
172
|
|
|
211
173
|
async getItem(key: string): Promise<CacheItemResult | null> {
|
|
212
|
-
|
|
213
|
-
const result = await this.inner.getItem(key);
|
|
214
|
-
if (result) {
|
|
215
|
-
const value: ShellSnapshotItemValue = {
|
|
216
|
-
value: result.value,
|
|
217
|
-
handles: result.handles,
|
|
218
|
-
tags: result.tags,
|
|
219
|
-
};
|
|
220
|
-
this.record("item", key, value);
|
|
221
|
-
}
|
|
222
|
-
return result;
|
|
174
|
+
return this.inner.getItem ? this.inner.getItem(key) : null;
|
|
223
175
|
}
|
|
224
176
|
|
|
225
177
|
async setItem(
|
|
@@ -227,14 +179,7 @@ export class RecordingShellStore<
|
|
|
227
179
|
value: string,
|
|
228
180
|
options?: CacheItemOptions,
|
|
229
181
|
): Promise<void> {
|
|
230
|
-
|
|
231
|
-
const stored: ShellSnapshotItemValue = {
|
|
232
|
-
value,
|
|
233
|
-
handles: options?.handles,
|
|
234
|
-
tags: options?.tags,
|
|
235
|
-
};
|
|
236
|
-
this.record("item", key, stored);
|
|
237
|
-
return this.inner.setItem(key, value, options);
|
|
182
|
+
return this.inner.setItem?.(key, value, options);
|
|
238
183
|
}
|
|
239
184
|
|
|
240
185
|
async getShell(
|
|
@@ -259,7 +204,7 @@ export class RecordingShellStore<
|
|
|
259
204
|
}
|
|
260
205
|
}
|
|
261
206
|
|
|
262
|
-
/**
|
|
207
|
+
/** `store` when it is a RecordingShellStore (an instanceof check). */
|
|
263
208
|
export function getRecordingStore<TEnv>(
|
|
264
209
|
store: SegmentCacheStore<TEnv> | undefined,
|
|
265
210
|
): RecordingShellStore<TEnv> | undefined {
|
|
@@ -267,13 +212,119 @@ export function getRecordingStore<TEnv>(
|
|
|
267
212
|
}
|
|
268
213
|
|
|
269
214
|
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
215
|
+
* True when `snapshot` carries the canonical doc segment record `docKey`
|
|
216
|
+
* names, with at least one segment: the record partial replay and a document
|
|
217
|
+
* HIT tail consume.
|
|
218
|
+
*/
|
|
219
|
+
export function hasDocRecord(
|
|
220
|
+
snapshot: readonly ShellSnapshotRecord[] | undefined,
|
|
221
|
+
docKey: string | undefined,
|
|
222
|
+
): boolean {
|
|
223
|
+
if (docKey === undefined || !snapshot) return false;
|
|
224
|
+
return snapshot.some((record) => {
|
|
225
|
+
if (
|
|
226
|
+
!record ||
|
|
227
|
+
typeof record !== "object" ||
|
|
228
|
+
record.family !== "segment" ||
|
|
229
|
+
record.key !== docKey ||
|
|
230
|
+
typeof record.value !== "object" ||
|
|
231
|
+
record.value === null
|
|
232
|
+
) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const segments = (record.value as { segments?: unknown }).segments;
|
|
236
|
+
return Array.isArray(segments) && segments.length > 0;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Split a capture's snapshot into what a HIT or a partial replay reads and
|
|
242
|
+
* the rest (docs/design/shell-entry-layout.md §2). Kept: the doc record
|
|
243
|
+
* (`docKey`), as a copy inside the entry (a separately stored record could be
|
|
244
|
+
* evicted on its own, and the HIT would have nothing to replay), and the
|
|
245
|
+
* loader-family pins unless the entry is navigation-only (partial replay of
|
|
246
|
+
* such an entry seeds the doc record alone). Every other record goes.
|
|
247
|
+
*/
|
|
248
|
+
export function pruneShellSnapshot(
|
|
249
|
+
snapshot: readonly ShellSnapshotRecord[],
|
|
250
|
+
navigationOnly: boolean,
|
|
251
|
+
docKey: string | undefined,
|
|
252
|
+
): { kept: ShellSnapshotRecord[]; pruned: ShellSnapshotRecord[] } {
|
|
253
|
+
const kept: ShellSnapshotRecord[] = [];
|
|
254
|
+
const pruned: ShellSnapshotRecord[] = [];
|
|
255
|
+
for (const record of snapshot) {
|
|
256
|
+
const keep =
|
|
257
|
+
(record.family === "segment" && record.key === docKey) ||
|
|
258
|
+
(record.family === "loader" && !navigationOnly);
|
|
259
|
+
(keep ? kept : pruned).push(record);
|
|
260
|
+
}
|
|
261
|
+
return { kept, pruned };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The shell entry head's fields estimateShellEntryBytes does not measure
|
|
266
|
+
* (versions, timestamps, theme, lengths, flags; about 200 bytes in a
|
|
267
|
+
* CFCacheStore frame) and the frame's 12-byte prefix, with room to spare.
|
|
268
|
+
*/
|
|
269
|
+
export const SHELL_ENTRY_HEAD_ALLOWANCE_BYTES: number = 512;
|
|
270
|
+
|
|
271
|
+
const ENTRY_BYTE_ENCODER = new TextEncoder();
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Bytes (UTF-8) a stored shell entry takes, as the built-in stores write it:
|
|
275
|
+
* the prelude, the snapshot's JSON, and a JSON head that carries the
|
|
276
|
+
* postponed state as an escaped string next to the tags and doc key
|
|
277
|
+
* (CFCacheStore's frame head, VercelCacheStore's envelope), plus
|
|
278
|
+
* SHELL_ENTRY_HEAD_ALLOWANCE_BYTES for the head's fixed fields and the
|
|
279
|
+
* frame prefix. Measuring the raw postponed bytes undercounted: every quote
|
|
280
|
+
* in it is escaped in the head (a /ppr-large/holes entry measured 1,098
|
|
281
|
+
* bytes under its KV value). The capture's whole-entry guard compares this
|
|
282
|
+
* with the store's value limit.
|
|
283
|
+
*/
|
|
284
|
+
export function estimateShellEntryBytes(parts: {
|
|
285
|
+
preludeBytes: number;
|
|
286
|
+
postponed: string | null | undefined;
|
|
287
|
+
snapshot: readonly ShellSnapshotRecord[] | undefined;
|
|
288
|
+
tags: readonly string[] | undefined;
|
|
289
|
+
docKey: string | undefined;
|
|
290
|
+
prunedRecords: string | undefined;
|
|
291
|
+
}): number {
|
|
292
|
+
const head = ENTRY_BYTE_ENCODER.encode(
|
|
293
|
+
JSON.stringify({
|
|
294
|
+
po: parts.postponed,
|
|
295
|
+
t: parts.tags,
|
|
296
|
+
dk: parts.docKey,
|
|
297
|
+
pr: parts.prunedRecords,
|
|
298
|
+
}),
|
|
299
|
+
).length;
|
|
300
|
+
const snapshot =
|
|
301
|
+
parts.snapshot && parts.snapshot.length > 0
|
|
302
|
+
? ENTRY_BYTE_ENCODER.encode(JSON.stringify(parts.snapshot)).length
|
|
303
|
+
: 0;
|
|
304
|
+
return (
|
|
305
|
+
parts.preludeBytes + head + snapshot + SHELL_ENTRY_HEAD_ALLOWANCE_BYTES
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Snapshot records by family, e.g. `segment:1/loader:2` (no commas: it rides a Server-Timing desc). */
|
|
310
|
+
export function countSnapshotFamilies(
|
|
311
|
+
snapshot: readonly ShellSnapshotRecord[],
|
|
312
|
+
): string {
|
|
313
|
+
const counts = new Map<string, number>();
|
|
314
|
+
for (const record of snapshot) {
|
|
315
|
+
counts.set(record.family, (counts.get(record.family) ?? 0) + 1);
|
|
316
|
+
}
|
|
317
|
+
return [...counts].map(([family, n]) => `${family}:${n}`).join("/");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The store the capture's IMPLICIT doc-cache scope resolves: reads pass
|
|
322
|
+
* through the recording store to the real one, but segment WRITES are
|
|
323
|
+
* recorded into the snapshot only — see
|
|
324
|
+
* {@link RecordingShellStore.recordSegmentWrite} for why passthrough would
|
|
325
|
+
* break SWR recapture. A route with its OWN cache() scope keeps that scope's
|
|
326
|
+
* real-store reads and writes; recordShellCaptureDocRecord (cache-store.ts)
|
|
327
|
+
* writes its doc record through this store too.
|
|
277
328
|
*/
|
|
278
329
|
export class SnapshotOnlySegmentStore<
|
|
279
330
|
TEnv = unknown,
|
|
@@ -300,32 +351,38 @@ export class SnapshotOnlySegmentStore<
|
|
|
300
351
|
}
|
|
301
352
|
}
|
|
302
353
|
|
|
354
|
+
/** One decoded loader-family pin (buildShellLoaderSeed). */
|
|
355
|
+
export interface ShellLoaderSeedEntry {
|
|
356
|
+
/** The Flight-deserialized elided container (recorded paths + markers). */
|
|
357
|
+
container: unknown;
|
|
358
|
+
/**
|
|
359
|
+
* True when the record carries hole markers: the HIT overlay must gate on
|
|
360
|
+
* the fresh run. False = fully pinned; the payload promise resolves
|
|
361
|
+
* immediately from the pin (loader-cache.ts pin-first path).
|
|
362
|
+
*/
|
|
363
|
+
holes: boolean;
|
|
364
|
+
/**
|
|
365
|
+
* The record needs its loader body to run on a HIT for pushes the capture
|
|
366
|
+
* could not record (ShellSnapshotLoaderValue.runs). Only read for a
|
|
367
|
+
* hole-free record: a hole-carrying one always runs.
|
|
368
|
+
*/
|
|
369
|
+
runs: boolean;
|
|
370
|
+
}
|
|
371
|
+
|
|
303
372
|
/**
|
|
304
373
|
* Materialize the loader-family seed from a shell snapshot for a HIT's tail
|
|
305
374
|
* render: Flight-deserialize each recorded (promise-elided) bake-lane
|
|
306
375
|
* container into a segment-key -> container Map, which serveShellHit assigns
|
|
307
376
|
* to the tail context's `_shellLoaderSeed` for the resolveLoaderData overlay.
|
|
308
|
-
* Lives here so every snapshot family is decoded in this module (the
|
|
309
|
-
*
|
|
310
|
-
*
|
|
377
|
+
* Lives here so every snapshot family is decoded in this module (the segment
|
|
378
|
+
* family via {@link SeededShellStore}); the loader family is not a store
|
|
379
|
+
* read, so it seeds the context instead of a store.
|
|
311
380
|
*
|
|
312
381
|
* Deserializations run in parallel; a record that fails to decode is skipped
|
|
313
382
|
* (that loader drifts — the pre-snapshot behavior — instead of failing the
|
|
314
383
|
* HIT). Returns undefined when the snapshot carries no loader records, without
|
|
315
384
|
* touching the Flight codec (kept lazy for cold paths and non-RSC configs).
|
|
316
385
|
*/
|
|
317
|
-
export interface ShellLoaderSeedEntry {
|
|
318
|
-
/** The Flight-deserialized elided container (recorded paths + markers). */
|
|
319
|
-
container: unknown;
|
|
320
|
-
/**
|
|
321
|
-
* True when the record carries hole markers (or predates the capture-side
|
|
322
|
-
* hole bit, where hole-ness is unknown): the HIT overlay must gate on the
|
|
323
|
-
* fresh run. False = fully pinned; the payload promise resolves immediately
|
|
324
|
-
* from the pin (loader-cache.ts pin-first path).
|
|
325
|
-
*/
|
|
326
|
-
holes: boolean;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
386
|
export async function buildShellLoaderSeed(
|
|
330
387
|
snapshot: ShellSnapshotRecord[],
|
|
331
388
|
): Promise<Map<string, ShellLoaderSeedEntry> | undefined> {
|
|
@@ -345,9 +402,11 @@ export async function buildShellLoaderSeed(
|
|
|
345
402
|
rec.key,
|
|
346
403
|
{
|
|
347
404
|
container: await deserializeResult(stored.value),
|
|
348
|
-
// A record
|
|
349
|
-
//
|
|
405
|
+
// A record stored before the bits existed (v0.17) lacks the
|
|
406
|
+
// loader-owned pushes: it reads as hole-carrying and as runs,
|
|
407
|
+
// so the loader body supplies them.
|
|
350
408
|
holes: stored.holes !== 0,
|
|
409
|
+
runs: stored.runs !== 0,
|
|
351
410
|
},
|
|
352
411
|
];
|
|
353
412
|
} catch {
|
|
@@ -364,50 +423,29 @@ export async function buildShellLoaderSeed(
|
|
|
364
423
|
}
|
|
365
424
|
|
|
366
425
|
/**
|
|
367
|
-
*
|
|
368
|
-
* the snapshot
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
* partial navigation cannot freeze captured data or write a partial result into
|
|
377
|
-
* the canonical document namespace.
|
|
426
|
+
* The implicit doc scope's store on a HIT tail and on a partial replay: it
|
|
427
|
+
* serves the snapshot's segment records, the doc record among them, and
|
|
428
|
+
* fully isolates the segment family. Misses do not fall through, and writes
|
|
429
|
+
* or deletes stay local to the overlay, so a partial navigation cannot write
|
|
430
|
+
* a partial result into the canonical document namespace. It carries nothing
|
|
431
|
+
* else: every other read on a HIT (a hole, a bake-lane loader body that runs
|
|
432
|
+
* there) resolves the request's own store. Loader-family records are not
|
|
433
|
+
* store reads: serveShellHit seeds them onto the tail context
|
|
434
|
+
* (_shellLoaderSeed) for the resolveLoaderData overlay instead.
|
|
378
435
|
*/
|
|
379
436
|
export class SeededShellStore<
|
|
380
437
|
TEnv = unknown,
|
|
381
438
|
> implements SegmentCacheStore<TEnv> {
|
|
382
|
-
private readonly items: Map<string, ShellSnapshotItemValue> | undefined;
|
|
383
439
|
private readonly segments = new Map<string, CachedEntryData>();
|
|
384
|
-
private readonly responses:
|
|
385
|
-
| Map<string, ShellSnapshotResponseValue>
|
|
386
|
-
| undefined;
|
|
387
|
-
private readonly segmentsOnly: boolean;
|
|
388
440
|
|
|
389
441
|
constructor(
|
|
390
442
|
private readonly inner: SegmentCacheStore<TEnv>,
|
|
391
|
-
snapshot: ShellSnapshotRecord[],
|
|
392
|
-
options?: { segmentsOnly?: boolean },
|
|
443
|
+
snapshot: readonly ShellSnapshotRecord[],
|
|
393
444
|
) {
|
|
394
|
-
this.segmentsOnly = options?.segmentsOnly === true;
|
|
395
|
-
this.items = this.segmentsOnly ? undefined : new Map();
|
|
396
|
-
this.responses = this.segmentsOnly ? undefined : new Map();
|
|
397
445
|
for (const rec of snapshot) {
|
|
398
|
-
if (
|
|
399
|
-
if (rec.family === "segment") {
|
|
446
|
+
if (rec && typeof rec === "object" && rec.family === "segment") {
|
|
400
447
|
this.segments.set(rec.key, rec.value as CachedEntryData);
|
|
401
|
-
} else if (this.segmentsOnly) {
|
|
402
|
-
continue;
|
|
403
|
-
} else if (rec.family === "item") {
|
|
404
|
-
this.items?.set(rec.key, rec.value as ShellSnapshotItemValue);
|
|
405
|
-
} else if (rec.family === "response") {
|
|
406
|
-
this.responses?.set(rec.key, rec.value as ShellSnapshotResponseValue);
|
|
407
448
|
}
|
|
408
|
-
// "loader" family records are not store reads — serveShellHit seeds them
|
|
409
|
-
// onto the tail context (_shellLoaderSeed) for the resolveLoaderData
|
|
410
|
-
// overlay instead.
|
|
411
449
|
}
|
|
412
450
|
}
|
|
413
451
|
|
|
@@ -417,100 +455,34 @@ export class SeededShellStore<
|
|
|
417
455
|
get keyGenerator(): SegmentCacheStore<TEnv>["keyGenerator"] {
|
|
418
456
|
return this.inner.keyGenerator;
|
|
419
457
|
}
|
|
420
|
-
get supportsPassiveShellReads(): true | undefined {
|
|
421
|
-
return this.inner.supportsPassiveShellReads;
|
|
422
|
-
}
|
|
423
458
|
|
|
424
459
|
async get(key: string): Promise<CacheGetResult | null | CacheReadError> {
|
|
425
460
|
const seeded = this.segments.get(key);
|
|
426
|
-
|
|
427
|
-
if (this.segmentsOnly) return null;
|
|
428
|
-
return this.inner.get(key);
|
|
461
|
+
return seeded ? { data: seeded, shouldRevalidate: false } : null;
|
|
429
462
|
}
|
|
430
463
|
|
|
431
|
-
async set(
|
|
432
|
-
key
|
|
433
|
-
data: CachedEntryData,
|
|
434
|
-
ttl: number,
|
|
435
|
-
swr?: number,
|
|
436
|
-
): Promise<void> {
|
|
437
|
-
if (this.segmentsOnly) {
|
|
438
|
-
this.segments.set(key, data);
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
return this.inner.set(key, data, ttl, swr);
|
|
464
|
+
async set(key: string, data: CachedEntryData): Promise<void> {
|
|
465
|
+
this.segments.set(key, data);
|
|
442
466
|
}
|
|
443
467
|
|
|
444
468
|
async delete(key: string): Promise<boolean> {
|
|
445
|
-
|
|
446
|
-
return this.segments.delete(key);
|
|
447
|
-
}
|
|
448
|
-
return this.inner.delete(key);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
async clear(): Promise<void> {
|
|
452
|
-
return this.inner.clear?.();
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
async getResponse(
|
|
456
|
-
key: string,
|
|
457
|
-
): Promise<{ response: Response; shouldRevalidate: boolean } | null> {
|
|
458
|
-
const seeded = this.responses?.get(key);
|
|
459
|
-
if (seeded) {
|
|
460
|
-
return { response: deserializeResponse(seeded), shouldRevalidate: false };
|
|
461
|
-
}
|
|
462
|
-
return this.inner.getResponse ? this.inner.getResponse(key) : null;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
async putResponse(
|
|
466
|
-
key: string,
|
|
467
|
-
response: Response,
|
|
468
|
-
ttl: number,
|
|
469
|
-
swr?: number,
|
|
470
|
-
tags?: string[],
|
|
471
|
-
): Promise<void> {
|
|
472
|
-
return this.inner.putResponse?.(key, response, ttl, swr, tags);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
async getItem(key: string): Promise<CacheItemResult | null> {
|
|
476
|
-
const seeded = this.items?.get(key);
|
|
477
|
-
if (seeded) {
|
|
478
|
-
return {
|
|
479
|
-
value: seeded.value,
|
|
480
|
-
handles: seeded.handles,
|
|
481
|
-
tags: seeded.tags,
|
|
482
|
-
shouldRevalidate: false,
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
return this.inner.getItem ? this.inner.getItem(key) : null;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
async setItem(
|
|
489
|
-
key: string,
|
|
490
|
-
value: string,
|
|
491
|
-
options?: CacheItemOptions,
|
|
492
|
-
): Promise<void> {
|
|
493
|
-
return this.inner.setItem?.(key, value, options);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
async getShell(
|
|
497
|
-
key: string,
|
|
498
|
-
options?: { claimRevalidation?: boolean },
|
|
499
|
-
): Promise<{ entry: ShellCacheEntry; shouldRevalidate?: boolean } | null> {
|
|
500
|
-
return this.inner.getShell ? this.inner.getShell(key, options) : null;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
async putShell(
|
|
504
|
-
key: string,
|
|
505
|
-
entry: ShellCacheEntry,
|
|
506
|
-
ttlSeconds?: number,
|
|
507
|
-
swrSeconds?: number,
|
|
508
|
-
tags?: string[],
|
|
509
|
-
): Promise<"stored" | "invalidated" | "uncacheable" | void> {
|
|
510
|
-
return this.inner.putShell?.(key, entry, ttlSeconds, swrSeconds, tags);
|
|
469
|
+
return this.segments.delete(key);
|
|
511
470
|
}
|
|
471
|
+
}
|
|
512
472
|
|
|
513
|
-
|
|
514
|
-
|
|
473
|
+
/**
|
|
474
|
+
* Thrown by a document HIT tail's match when its doc record cannot supply the
|
|
475
|
+
* handler layer (it failed to decode, or the entry lost it). The tail must not
|
|
476
|
+
* run handlers after the prelude committed, so withCacheLookup throws this
|
|
477
|
+
* instead of resolving segments, and serveShellHit degrades the response:
|
|
478
|
+
* the entry is replaced with a tombstone, the memo is dropped, a recapture is
|
|
479
|
+
* scheduled, and the browser reloads into a MISS.
|
|
480
|
+
*/
|
|
481
|
+
export class ShellRecordUnavailableError extends Error {
|
|
482
|
+
constructor(docKey: string | undefined) {
|
|
483
|
+
super(
|
|
484
|
+
`PPR shell HIT: the doc record${docKey ? ` "${docKey}"` : ""} could not supply the handler layer`,
|
|
485
|
+
);
|
|
486
|
+
this.name = "ShellRecordUnavailableError";
|
|
515
487
|
}
|
|
516
488
|
}
|