@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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Shared Cache Key Utilities
|
|
3
3
|
*
|
|
4
4
|
* Deterministic normalization of search params and route params
|
|
5
|
-
* for cache key generation
|
|
6
|
-
* document-cache, and loader-cache.
|
|
5
|
+
* for cache key generation, and the composition of nested cache() keys.
|
|
6
|
+
* Used by cache-runtime, cache-scope, document-cache, and loader-cache.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { encodeKV } from "../encode-kv.js";
|
|
@@ -95,3 +95,63 @@ export function cacheKeyBase(
|
|
|
95
95
|
if (searchStr) key += `?${searchStr}`;
|
|
96
96
|
return key;
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The prefix of a `key()` result's part in a record key (#975), and of a
|
|
101
|
+
* loader's own `key()` result after its `loader:<id>:` namespace (#1009,
|
|
102
|
+
* loader-cache.ts loaderKeyFromResult).
|
|
103
|
+
*/
|
|
104
|
+
export const KEY_PART_PREFIX = "key:";
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A cache() record key, or ppr shell partition, from its parts
|
|
108
|
+
* (CacheScope.resolveKeyFrom, CacheScope.resolvePartition): the `key()`
|
|
109
|
+
* results of the scope's cache() chain, outermost first, then its default
|
|
110
|
+
* parts: the keyGenerator results of enclosing scopes on another store
|
|
111
|
+
* (#974) and, for a record key under a scope without its own `key()`, its
|
|
112
|
+
* default key (or its store's keyGenerator result, #970).
|
|
113
|
+
*
|
|
114
|
+
* Key scheme invariant (#975). A `key()` result is often request input,
|
|
115
|
+
* and a record key has no route or scope discriminator (the store reads
|
|
116
|
+
* `store.get(key)`), so a result that equals another record's key writes
|
|
117
|
+
* one route's content under the other's entry. Before #975 a single
|
|
118
|
+
* `key()` result was stored raw: the header value `doc:localhost/pricing`
|
|
119
|
+
* named /pricing's default-keyed record, and `gold|doc%3Alocalhost%2Fpricing`
|
|
120
|
+
* named gold's nested /pricing record (#970). So every part is encoded:
|
|
121
|
+
*
|
|
122
|
+
* - a `key()` part is `key:` plus `encodeURIComponent(result)`: it starts
|
|
123
|
+
* with `key:` and holds no `|` and no other `:`;
|
|
124
|
+
* - a default part is `encodeURIComponent(value)`: it holds no `:` or `|`;
|
|
125
|
+
* an enclosing store's keyGenerator result equal to the default key is an
|
|
126
|
+
* empty part, so each store keeps its position (CacheScope
|
|
127
|
+
* positionalParts);
|
|
128
|
+
* - one `key()` part is the key; two or more parts are joined by `|`,
|
|
129
|
+
* `key()` parts first;
|
|
130
|
+
* - a lone default part stays raw: with no `key()` on the chain the record
|
|
131
|
+
* key is the default key or the store's keyGenerator result, unchanged.
|
|
132
|
+
*
|
|
133
|
+
* Why nothing collides: the router's default keys (`doc:`, `partial:`,
|
|
134
|
+
* `intercept:`, `response:<type>:`) never start with `key:` and hold a `:`.
|
|
135
|
+
* A namespaced `key()` result starts with `key:` and holds no `|`. A
|
|
136
|
+
* composed key holds a `|` and either starts with `key:` or holds no `:`.
|
|
137
|
+
* Among composed keys, splitting on `|` recovers each part, a part's kind
|
|
138
|
+
* (a `key:` prefix, or no `:` at all) and its value. Pinned by the collision
|
|
139
|
+
* probe in src/cache/__tests__/cache-scope-chain.test.ts. A keyGenerator
|
|
140
|
+
* result is the store's own namespace, as before.
|
|
141
|
+
*
|
|
142
|
+
* Shared with the testing helper `shellCacheKey`, so a test builds the
|
|
143
|
+
* production partition.
|
|
144
|
+
*/
|
|
145
|
+
export function composeCacheKeys(
|
|
146
|
+
keyResults: readonly string[],
|
|
147
|
+
defaultParts: readonly string[] = [],
|
|
148
|
+
): string {
|
|
149
|
+
if (keyResults.length === 0 && defaultParts.length === 1) {
|
|
150
|
+
return defaultParts[0];
|
|
151
|
+
}
|
|
152
|
+
const parts = keyResults.map(
|
|
153
|
+
(result) => KEY_PART_PREFIX + encodeURIComponent(result),
|
|
154
|
+
);
|
|
155
|
+
for (const part of defaultParts) parts.push(encodeURIComponent(part));
|
|
156
|
+
return parts.join("|");
|
|
157
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { CacheDefaults, SegmentCacheStore } from "./types.js";
|
|
10
10
|
import { _getRequestContext } from "../server/request-context.js";
|
|
11
|
+
import { runIdentityExempt } from "./cache-exec-scope.js";
|
|
11
12
|
import type { RequestContext } from "../server/request-context.js";
|
|
12
13
|
import { normalizeTags } from "./cache-tag.js";
|
|
13
14
|
import { reportCacheError } from "./cache-error.js";
|
|
@@ -83,6 +84,28 @@ export function resolveSwrWindow(
|
|
|
83
84
|
return 0;
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Minimum age (ms) of a stale PPR shell before a HIT schedules its recapture.
|
|
89
|
+
* A shell capped to a route cache() record inside its swr window is stale
|
|
90
|
+
* from the start (rsc/shell-capture.ts capShellWindow), so without a floor
|
|
91
|
+
* every HIT of it recaptured, and a capture's store write is one KV write per
|
|
92
|
+
* key: Cloudflare KV accepts one per second per key. Inside the floor the
|
|
93
|
+
* stale shell is still served. Per isolate: each isolate's stale HIT past the
|
|
94
|
+
* floor can schedule one (stampede-guarded per isolate).
|
|
95
|
+
*/
|
|
96
|
+
export const SHELL_MIN_RECAPTURE_INTERVAL_MS = 1_000;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Whether a stale shell HIT schedules its recapture (see the floor above).
|
|
100
|
+
* Fails open: an entry without a numeric `createdAt` recaptures.
|
|
101
|
+
*/
|
|
102
|
+
export function staleShellRecaptureDue(
|
|
103
|
+
entry: { readonly createdAt?: number },
|
|
104
|
+
now: number,
|
|
105
|
+
): boolean {
|
|
106
|
+
return !(now - (entry.createdAt as number) < SHELL_MIN_RECAPTURE_INTERVAL_MS);
|
|
107
|
+
}
|
|
108
|
+
|
|
86
109
|
/**
|
|
87
110
|
* Compute staleAt and expiresAt timestamps from TTL and SWR window.
|
|
88
111
|
*
|
|
@@ -105,16 +128,18 @@ export async function resolveCacheKey(
|
|
|
105
128
|
keyFn: ((ctx: RequestContext) => string | Promise<string>) | undefined,
|
|
106
129
|
store: SegmentCacheStore | null,
|
|
107
130
|
defaultKey: string,
|
|
108
|
-
_label: string,
|
|
109
131
|
): Promise<string> {
|
|
110
132
|
const requestCtx = _getRequestContext();
|
|
111
133
|
|
|
134
|
+
// A key reads the request to partition the entry (runIdentityExempt): the
|
|
135
|
+
// identity guards let it through, a ppr capture included.
|
|
112
136
|
if (keyFn && requestCtx) {
|
|
113
|
-
return await keyFn(requestCtx);
|
|
137
|
+
return await runIdentityExempt(() => keyFn(requestCtx));
|
|
114
138
|
}
|
|
115
139
|
|
|
116
140
|
if (store?.keyGenerator && requestCtx) {
|
|
117
|
-
|
|
141
|
+
const keyGenerator = store.keyGenerator;
|
|
142
|
+
return await runIdentityExempt(() => keyGenerator(requestCtx, defaultKey));
|
|
118
143
|
}
|
|
119
144
|
|
|
120
145
|
return defaultKey;
|
|
@@ -135,7 +160,9 @@ export function resolveTagsOption<TEnv>(
|
|
|
135
160
|
return undefined;
|
|
136
161
|
}
|
|
137
162
|
try {
|
|
138
|
-
|
|
163
|
+
// Tags label the entry, never render: identity reads pass
|
|
164
|
+
// (runIdentityExempt), a ppr capture's record write included.
|
|
165
|
+
return normalizeTagList(runIdentityExempt(() => tags(ctx)));
|
|
139
166
|
} catch (error) {
|
|
140
167
|
reportCacheError(
|
|
141
168
|
error,
|
|
@@ -48,6 +48,7 @@ import { runBackground } from "./background-task.js";
|
|
|
48
48
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
49
49
|
import {
|
|
50
50
|
normalizeTags,
|
|
51
|
+
outsideCacheTagScope,
|
|
51
52
|
recordRequestTags,
|
|
52
53
|
runWithCacheTagScope,
|
|
53
54
|
} from "./cache-tag.js";
|
|
@@ -58,6 +59,12 @@ import {
|
|
|
58
59
|
type CacheExecScope,
|
|
59
60
|
} from "./cache-exec-scope.js";
|
|
60
61
|
import { reportCacheError } from "./cache-error.js";
|
|
62
|
+
import {
|
|
63
|
+
executionStart,
|
|
64
|
+
invalidatedSince,
|
|
65
|
+
predatesInvalidation,
|
|
66
|
+
type ExecutionStart,
|
|
67
|
+
} from "./tag-invalidation.js";
|
|
61
68
|
import type { CacheItemResult } from "./types.js";
|
|
62
69
|
import type { InternalHandlerContext } from "../types.js";
|
|
63
70
|
|
|
@@ -266,6 +273,12 @@ interface CacheEnvelope {
|
|
|
266
273
|
tags: string[];
|
|
267
274
|
/** RSC-encoded handle blob captured during execution, if any. */
|
|
268
275
|
handles?: string;
|
|
276
|
+
/**
|
|
277
|
+
* One of `tags` was invalidated after the leader started, as the store
|
|
278
|
+
* tells it (another isolate, #977): the leader does not write it, and a
|
|
279
|
+
* follower does not serve it.
|
|
280
|
+
*/
|
|
281
|
+
predates?: boolean;
|
|
269
282
|
}
|
|
270
283
|
|
|
271
284
|
/**
|
|
@@ -292,7 +305,10 @@ interface CacheEnvelope {
|
|
|
292
305
|
*/
|
|
293
306
|
interface InFlightExecution {
|
|
294
307
|
promise: Promise<CacheEnvelope>;
|
|
308
|
+
/** Date.now() at registration, for IN_FLIGHT_LEADER_MAX_WAIT_MS. */
|
|
295
309
|
registeredAt: number;
|
|
310
|
+
/** Where the leader started, for invalidatedSince (#973, #977). */
|
|
311
|
+
start: ExecutionStart;
|
|
296
312
|
}
|
|
297
313
|
|
|
298
314
|
const inFlightExecutions = new Map<string, InFlightExecution>();
|
|
@@ -309,6 +325,18 @@ const IN_FLIGHT_LEADER_MAX_WAIT_MS = 15_000;
|
|
|
309
325
|
/** Distinguishes a leader timeout from a leader rejection in raceLeader. */
|
|
310
326
|
const LEADER_TIMED_OUT = Symbol("leader-timed-out");
|
|
311
327
|
|
|
328
|
+
/**
|
|
329
|
+
* @internal Reset this module's per-isolate state: the in-flight leaders a
|
|
330
|
+
* follower would join for up to IN_FLIGHT_LEADER_MAX_WAIT_MS, and the
|
|
331
|
+
* once-per-function warnings. Tests only (testing/serve-shell-request.ts
|
|
332
|
+
* resetShellTestState), never while a request is in flight.
|
|
333
|
+
*/
|
|
334
|
+
export function resetCacheRuntimeForTests(): void {
|
|
335
|
+
inFlightExecutions.clear();
|
|
336
|
+
warnedUncachedUnderTest.clear();
|
|
337
|
+
warnedUnkeyableArgs.clear();
|
|
338
|
+
}
|
|
339
|
+
|
|
312
340
|
/**
|
|
313
341
|
* Await a leader's envelope for at most `remainingMs`. Resolves with the
|
|
314
342
|
* envelope, `undefined` on leader rejection (the leader already cleared its
|
|
@@ -427,6 +455,20 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
427
455
|
);
|
|
428
456
|
}
|
|
429
457
|
|
|
458
|
+
// An entry's tags: the profile/DSL tags plus what its execution
|
|
459
|
+
// recorded, normalized (drops empty profile tags, matching the
|
|
460
|
+
// invalidate path) and deduped.
|
|
461
|
+
const entryTags = (recorded: Set<string>): string[] => [
|
|
462
|
+
...new Set(normalizeTags([...(profile.tags ?? []), ...recorded])),
|
|
463
|
+
];
|
|
464
|
+
// The value's Flight encode runs back inside its execution's tag scope:
|
|
465
|
+
// it renders the server components in the value, and a "use cache" call
|
|
466
|
+
// or cacheTag() among them belongs to this entry (#980).
|
|
467
|
+
const inTagScope = <T>(
|
|
468
|
+
recorded: Set<string>,
|
|
469
|
+
encode: () => Promise<T>,
|
|
470
|
+
): Promise<T> => runWithCacheTagScope(encode, recorded).result;
|
|
471
|
+
|
|
430
472
|
// Separate request-scoped args (ctx, a Request, the request's env) from
|
|
431
473
|
// key-generating args. For those that carry route context (ctx params,
|
|
432
474
|
// pathname, searchParams; a Request's URL), extract serializable values
|
|
@@ -627,116 +669,121 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
627
669
|
const liveStore = hasTaintedArgs ? requestCtx?._handleStore : undefined;
|
|
628
670
|
const recordKey = ownerKeyed ? useCacheRecordKey() : undefined;
|
|
629
671
|
// Background revalidation — must capture handles if tainted args present.
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
//
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
)
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
)
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
672
|
+
// Scheduled outside the caller's tag scope: an enclosing "use cache"
|
|
673
|
+
// entry bakes the stale value, not the refreshed one (#980).
|
|
674
|
+
outsideCacheTagScope(() =>
|
|
675
|
+
runBackground(requestCtx, async () => {
|
|
676
|
+
// The background body's handle pushes belong to the refreshed entry
|
|
677
|
+
// only: the foreground already replayed the stale entry's. They
|
|
678
|
+
// reach the REQUEST's store (a handler ctx.use(Handle) binds it at
|
|
679
|
+
// setupLoaderAccess, so a derived context with its own store never
|
|
680
|
+
// sees them), so the capture accepts only this body's chain and
|
|
681
|
+
// diverts what it records away from the live store. The foreground
|
|
682
|
+
// is still rendering here (runBackground starts on the next
|
|
683
|
+
// microtask), and its concurrent pushes are neither recorded nor
|
|
684
|
+
// diverted (issue #684, plan 010). A root scope: the task is
|
|
685
|
+
// detached, so an outer "use cache" body that scheduled it must not
|
|
686
|
+
// record the refresh's pushes.
|
|
687
|
+
const bgScope: CacheExecScope = {};
|
|
688
|
+
const bgCapture = liveStore
|
|
689
|
+
? startHandleCapture(liveStore, {
|
|
690
|
+
accept: () => isInCacheExecChain(bgScope),
|
|
691
|
+
divert: true,
|
|
692
|
+
key: recordKey,
|
|
693
|
+
})
|
|
694
|
+
: undefined;
|
|
695
|
+
|
|
696
|
+
// Tainted args are NOT stamped here, in contrast to the foreground
|
|
697
|
+
// miss path below. The args include the live HandlerContext the
|
|
698
|
+
// still-rendering foreground holds, and INSIDE_CACHE_EXEC is a
|
|
699
|
+
// property stamped onto that SHARED object — so for the whole
|
|
700
|
+
// revalidation window a concurrent foreground ctx.set() /
|
|
701
|
+
// ctx.headers.*() would throw (issue #684, plan 010). requestCtx is
|
|
702
|
+
// never stamped anywhere for the same reason. In-body misuse IS
|
|
703
|
+
// still guarded here: runWithCacheExecScope below follows only this
|
|
704
|
+
// body's async chain, so a conditional cookies()/headers() read
|
|
705
|
+
// that fires only during revalidation throws instead of baking one
|
|
706
|
+
// user's value into the shared entry — with zero visibility to the
|
|
707
|
+
// concurrent foreground.
|
|
708
|
+
|
|
709
|
+
try {
|
|
710
|
+
// Re-establish the request-context ALS so a "use cache" body that
|
|
711
|
+
// reads the ambient getRequestContext() (e.g.
|
|
712
|
+
// getRequestContext().env.ApiKey) resolves during the background
|
|
713
|
+
// revalidation instead of throwing "called outside of a request
|
|
714
|
+
// context". runWithRequestContext sets the store for fn's
|
|
715
|
+
// synchronous kickoff; its async continuations inherit it.
|
|
716
|
+
//
|
|
717
|
+
// The span opens inside the request ALS and wraps the WHOLE task —
|
|
718
|
+
// fn kickoff through the store write — so its platform spans nest
|
|
719
|
+
// under rango.background and a slow or failing setItem is part of
|
|
720
|
+
// the traced revalidation, not an untraced tail.
|
|
721
|
+
await runWithRequestContext(requestCtx, () =>
|
|
722
|
+
observePhase(
|
|
723
|
+
PHASES.background("use-cache-revalidation"),
|
|
724
|
+
async () => {
|
|
725
|
+
const refreshArgs = args.map(refreshView);
|
|
726
|
+
// The refresh reads its data from here on (#973, #977).
|
|
727
|
+
const start = executionStart();
|
|
728
|
+
const scoped = runWithCacheTagScope(() =>
|
|
729
|
+
runWithCacheExecScope(
|
|
730
|
+
() => fn.apply(this, refreshArgs),
|
|
731
|
+
bgScope,
|
|
732
|
+
),
|
|
733
|
+
);
|
|
734
|
+
const freshResult = await scoped.result;
|
|
735
|
+
bgCapture?.stop();
|
|
736
|
+
recordRequestTags(entryTags(scoped.tags), requestCtx);
|
|
737
|
+
const flightErrors: unknown[] = [];
|
|
738
|
+
const onFlightError = (error: unknown): void => {
|
|
739
|
+
flightErrors.push(error);
|
|
740
|
+
};
|
|
741
|
+
const serialized = await inTagScope(scoped.tags, () =>
|
|
742
|
+
serializeResult(freshResult, onFlightError),
|
|
743
|
+
);
|
|
744
|
+
if (serialized !== null) {
|
|
745
|
+
const encodedHandles = bgCapture
|
|
746
|
+
? await inTagScope(scoped.tags, () =>
|
|
747
|
+
encodeHandles(
|
|
748
|
+
bgCapture.capture.data,
|
|
749
|
+
onFlightError,
|
|
750
|
+
),
|
|
751
|
+
)
|
|
752
|
+
: undefined;
|
|
753
|
+
// An error row would replace the stale entry; the catch
|
|
754
|
+
// below reports it and the stale entry keeps serving.
|
|
755
|
+
if (flightErrors.length > 0) throw flightErrors[0];
|
|
756
|
+
const freshTags = entryTags(scoped.tags);
|
|
757
|
+
// One of the refreshed value's tags was invalidated after
|
|
758
|
+
// the refresh started: written now, a value that may
|
|
759
|
+
// predate it would outlive it.
|
|
760
|
+
if (await predatesInvalidation(store, freshTags, start)) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
await store.setItem!(cacheKey, serialized, {
|
|
764
|
+
handles: encodedHandles,
|
|
765
|
+
ttl: profile.ttl,
|
|
766
|
+
swr: profile.swr,
|
|
767
|
+
tags: freshTags.length > 0 ? freshTags : undefined,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
),
|
|
772
|
+
);
|
|
773
|
+
} catch (bgError) {
|
|
774
|
+
bgCapture?.stop();
|
|
775
|
+
// Pass requestCtx explicitly: this runs in a detached background
|
|
776
|
+
// task where the ALS context is gone, so onError can only fire if
|
|
777
|
+
// we hand it the context captured up front.
|
|
778
|
+
reportCacheError(
|
|
779
|
+
bgError,
|
|
780
|
+
"stale-revalidation",
|
|
781
|
+
"[use cache] background revalidation failed",
|
|
782
|
+
requestCtx,
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
}),
|
|
786
|
+
);
|
|
740
787
|
return result;
|
|
741
788
|
} catch (error) {
|
|
742
789
|
// Stale value is corrupt/partial; report and fall through to a fresh
|
|
@@ -757,8 +804,8 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
757
804
|
// against its OWN handle store (gated on ITS hasTaintedArgs), and records
|
|
758
805
|
// tags into its OWN request — no deserialized result is shared across
|
|
759
806
|
// requests. The store write stays exactly once (the leader's).
|
|
760
|
-
|
|
761
|
-
|
|
807
|
+
let existing = inFlightExecutions.get(cacheKey);
|
|
808
|
+
while (existing) {
|
|
762
809
|
const remainingMs =
|
|
763
810
|
IN_FLIGHT_LEADER_MAX_WAIT_MS - (Date.now() - existing.registeredAt);
|
|
764
811
|
const raced =
|
|
@@ -780,8 +827,15 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
780
827
|
"cache-read",
|
|
781
828
|
`[use cache] "${id}" inflight-timeout`,
|
|
782
829
|
);
|
|
783
|
-
//
|
|
784
|
-
}
|
|
830
|
+
break; // This call becomes the leader below.
|
|
831
|
+
}
|
|
832
|
+
// Leader rejected: its map entry is already cleared; run fresh.
|
|
833
|
+
if (!raced) break;
|
|
834
|
+
// Checked after the wait, so an invalidation made meanwhile counts too.
|
|
835
|
+
if (
|
|
836
|
+
!raced.predates &&
|
|
837
|
+
!invalidatedSince(raced.tags, existing.start.seq)
|
|
838
|
+
) {
|
|
785
839
|
try {
|
|
786
840
|
return await serveCached({
|
|
787
841
|
value: raced.serialized,
|
|
@@ -795,11 +849,17 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
795
849
|
"cache-corrupt",
|
|
796
850
|
`[use cache] "${id}" inflight-hit`,
|
|
797
851
|
);
|
|
798
|
-
//
|
|
852
|
+
break; // Run fresh below.
|
|
799
853
|
}
|
|
800
854
|
}
|
|
801
|
-
//
|
|
802
|
-
//
|
|
855
|
+
// One of the leader's tags was invalidated after it started, in any
|
|
856
|
+
// request or, as the store tells it, another isolate (#973, #977): its
|
|
857
|
+
// value may predate the invalidation. The leader cleared its entry
|
|
858
|
+
// before resolving, so an entry now is a newer execution (another
|
|
859
|
+
// caller that made this same decision): join it rather than start one
|
|
860
|
+
// more.
|
|
861
|
+
const next = inFlightExecutions.get(cacheKey);
|
|
862
|
+
existing = next !== existing ? next : undefined;
|
|
803
863
|
}
|
|
804
864
|
|
|
805
865
|
// This call becomes the leader. Register a deferred envelope so concurrent
|
|
@@ -816,9 +876,11 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
816
876
|
// Followers attach their own catch; guard the map's own reference so a
|
|
817
877
|
// rejected envelope with no waiter is not an unhandled rejection.
|
|
818
878
|
envelopePromise.catch(() => {});
|
|
879
|
+
const start = executionStart();
|
|
819
880
|
inFlightExecutions.set(cacheKey, {
|
|
820
881
|
promise: envelopePromise,
|
|
821
882
|
registeredAt: Date.now(),
|
|
883
|
+
start,
|
|
822
884
|
});
|
|
823
885
|
const clearSelf = (): void => {
|
|
824
886
|
if (inFlightExecutions.get(cacheKey)?.promise === envelopePromise) {
|
|
@@ -881,13 +943,10 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
881
943
|
capture?.stop();
|
|
882
944
|
}
|
|
883
945
|
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
...new Set(normalizeTags([...(profile.tags ?? []), ...scoped!.tags])),
|
|
889
|
-
];
|
|
890
|
-
recordRequestTags(allTags, requestCtx);
|
|
946
|
+
// Read scoped.tags after awaiting result so post-await cacheTag() calls
|
|
947
|
+
// are included.
|
|
948
|
+
const recorded = scoped!.tags;
|
|
949
|
+
recordRequestTags(entryTags(recorded), requestCtx);
|
|
891
950
|
|
|
892
951
|
// Serialize + encode handles ONCE, resolve the in-flight envelope so any
|
|
893
952
|
// concurrent followers can serve a synthetic hit, then persist to the store.
|
|
@@ -901,9 +960,13 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
901
960
|
flightErrors.push(error);
|
|
902
961
|
};
|
|
903
962
|
try {
|
|
904
|
-
serialized = await
|
|
963
|
+
serialized = await inTagScope(recorded, () =>
|
|
964
|
+
serializeResult(result, onFlightError),
|
|
965
|
+
);
|
|
905
966
|
encodedHandles = capture
|
|
906
|
-
? await
|
|
967
|
+
? await inTagScope(recorded, () =>
|
|
968
|
+
encodeHandles(capture.capture.data, onFlightError),
|
|
969
|
+
)
|
|
907
970
|
: undefined;
|
|
908
971
|
// Flight encodes an async component that throws, or a rejected
|
|
909
972
|
// promise in the result or a handle value, as an error row and
|
|
@@ -917,8 +980,8 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
917
980
|
requestCtx?._reportBackgroundError?.(buildError, "cache-write");
|
|
918
981
|
return;
|
|
919
982
|
}
|
|
920
|
-
clearSelf();
|
|
921
983
|
if (serialized === null) {
|
|
984
|
+
clearSelf();
|
|
922
985
|
// Non-serializable result: no store write (matches the prior silent
|
|
923
986
|
// skip); reject so any waiter falls through to a fresh execution.
|
|
924
987
|
rejectEnvelope(
|
|
@@ -928,15 +991,32 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
928
991
|
);
|
|
929
992
|
return;
|
|
930
993
|
}
|
|
994
|
+
const tags = entryTags(recorded);
|
|
995
|
+
// One of the value's tags was invalidated after the execution started
|
|
996
|
+
// (#973, #977): written now, a value that may predate it would outlive
|
|
997
|
+
// it. Asked before the followers get the value: another isolate's
|
|
998
|
+
// invalidation is only in the store's answer, and a follower serving
|
|
999
|
+
// the value would bake it into its own entries. Yes also when the store
|
|
1000
|
+
// cannot read a marker (predatesInvalidation asks it fail-closed) or
|
|
1001
|
+
// throws: the cost is a later miss.
|
|
1002
|
+
let predates = true;
|
|
1003
|
+
try {
|
|
1004
|
+
predates = await predatesInvalidation(store, tags, start);
|
|
1005
|
+
} catch (gateError) {
|
|
1006
|
+
requestCtx?._reportBackgroundError?.(gateError, "cache-write");
|
|
1007
|
+
}
|
|
1008
|
+
clearSelf();
|
|
931
1009
|
// Hand followers the envelope before the store write so a slow/failed
|
|
932
1010
|
// write never stalls them.
|
|
933
|
-
resolveEnvelope({ serialized, tags
|
|
1011
|
+
resolveEnvelope({ serialized, tags, handles: encodedHandles, predates });
|
|
1012
|
+
// Skipped, the next read runs fresh.
|
|
1013
|
+
if (predates) return;
|
|
934
1014
|
try {
|
|
935
1015
|
await store.setItem!(cacheKey, serialized, {
|
|
936
1016
|
handles: encodedHandles,
|
|
937
1017
|
ttl: profile.ttl,
|
|
938
1018
|
swr: profile.swr,
|
|
939
|
-
tags:
|
|
1019
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
940
1020
|
});
|
|
941
1021
|
} catch (writeError) {
|
|
942
1022
|
requestCtx?._reportBackgroundError?.(writeError, "cache-write");
|