@rangojs/router 0.0.0-experimental.132 → 0.0.0-experimental.133
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/AGENTS.md +8 -0
- package/README.md +43 -2
- package/dist/bin/rango.js +92 -16
- package/dist/vite/index.js +166 -70
- package/package.json +19 -18
- package/skills/breadcrumbs/SKILL.md +1 -1
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +2 -2
- package/skills/caching/SKILL.md +16 -9
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/hooks/SKILL.md +2 -2
- package/skills/host-router/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +1 -1
- package/skills/loader/SKILL.md +2 -0
- package/skills/migrate-react-router/SKILL.md +4 -2
- package/skills/mime-routes/SKILL.md +1 -1
- package/skills/prerender/SKILL.md +2 -0
- package/skills/rango/SKILL.md +12 -11
- package/skills/response-routes/SKILL.md +2 -2
- package/skills/route/SKILL.md +4 -0
- package/skills/router-setup/SKILL.md +3 -0
- package/skills/scripts/SKILL.md +179 -0
- package/skills/testing/SKILL.md +1 -1
- package/skills/testing/bindings.md +20 -6
- package/skills/testing/cache-prerender.md +5 -2
- package/skills/testing/client-components.md +2 -0
- package/skills/testing/e2e-parity.md +1 -1
- package/skills/testing/flight.md +8 -9
- package/skills/testing/render-handler.md +1 -1
- package/skills/testing/response-routes.md +1 -1
- package/skills/testing/server-actions.md +11 -11
- package/skills/testing/setup.md +3 -0
- package/skills/typesafety/SKILL.md +3 -2
- package/skills/use-cache/SKILL.md +10 -9
- package/src/browser/event-controller.ts +109 -2
- package/src/browser/partial-update.ts +12 -0
- package/src/browser/prefetch/cache.ts +17 -0
- package/src/browser/prefetch/fetch.ts +69 -2
- package/src/browser/react/Link.tsx +30 -5
- package/src/browser/react/NavigationProvider.tsx +12 -2
- package/src/browser/react/location-state-shared.ts +14 -2
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +23 -2
- package/src/browser/response-adapter.ts +14 -3
- package/src/browser/rsc-router.tsx +3 -0
- package/src/browser/scroll-restoration.ts +8 -3
- package/src/browser/server-action-bridge.ts +46 -11
- package/src/browser/types.ts +6 -0
- package/src/build/generate-route-types.ts +0 -1
- package/src/build/route-trie.ts +33 -9
- package/src/build/route-types/include-resolution.ts +7 -1
- package/src/build/route-types/router-processing.ts +0 -6
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/cache/cache-policy.ts +42 -8
- package/src/cache/cache-runtime.ts +65 -5
- package/src/cache/cache-scope.ts +71 -11
- package/src/cache/cache-tag.ts +7 -2
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +85 -613
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/document-cache.ts +11 -0
- package/src/cache/handle-snapshot.ts +8 -1
- package/src/cache/profile-registry.ts +25 -1
- package/src/cache/segment-codec.ts +9 -1
- package/src/cache/types.ts +4 -0
- package/src/client.rsc.tsx +38 -0
- package/src/client.tsx +11 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +1 -1
- package/src/decode-loader-results.ts +7 -1
- package/src/escape-script.ts +52 -0
- package/src/handles/MetaTags.tsx +56 -5
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +29 -11
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +46 -0
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +7 -3
- package/src/host/pattern-matcher.ts +16 -2
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +5 -0
- package/src/response-utils.ts +25 -0
- package/src/route-definition/dsl-helpers.ts +7 -0
- package/src/route-definition/redirect.ts +1 -2
- package/src/router/content-negotiation.ts +58 -10
- package/src/router/intercept-resolution.ts +9 -0
- package/src/router/match-middleware/cache-store.ts +10 -1
- package/src/router/middleware.ts +10 -3
- package/src/router/pattern-matching.ts +25 -23
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/router-interfaces.ts +7 -0
- package/src/router/router-options.ts +23 -0
- package/src/router/segment-resolution/fresh.ts +10 -0
- package/src/router/segment-resolution/helpers.ts +35 -1
- package/src/router/segment-resolution/loader-cache.ts +10 -6
- package/src/router/segment-resolution/revalidation.ts +6 -0
- package/src/router/segment-resolution.ts +1 -0
- package/src/router/trie-matching.ts +14 -9
- package/src/router.ts +18 -10
- package/src/rsc/handler.ts +52 -13
- package/src/rsc/helpers.ts +7 -1
- package/src/rsc/index.ts +1 -4
- package/src/rsc/loader-fetch.ts +107 -37
- package/src/rsc/progressive-enhancement.ts +18 -6
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-route-handler.ts +16 -133
- package/src/rsc/rsc-rendering.ts +13 -4
- package/src/rsc/server-action.ts +52 -6
- package/src/rsc/types.ts +7 -0
- package/src/search-params.ts +24 -5
- package/src/segment-loader-promise.ts +17 -2
- package/src/server/loader-registry.ts +16 -18
- package/src/server/request-context.ts +47 -20
- package/src/testing/dispatch.ts +108 -25
- package/src/testing/flight.ts +25 -0
- package/src/testing/internal/context.ts +25 -2
- package/src/testing/render-handler.ts +3 -1
- package/src/testing/render-route.tsx +15 -0
- package/src/testing/run-loader.ts +10 -3
- package/src/theme/ThemeProvider.tsx +20 -6
- package/src/theme/ThemeScript.tsx +7 -3
- package/src/theme/constants.ts +54 -3
- package/src/theme/theme-script.ts +22 -7
- package/src/types/request-scope.ts +8 -3
- package/src/vite/plugins/cjs-to-esm.ts +8 -1
- package/src/vite/plugins/expose-id-utils.ts +10 -1
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -16
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +6 -1
- package/src/vite/plugins/expose-internal-ids.ts +0 -1
- package/src/vite/plugins/version-plugin.ts +5 -17
- package/src/vite/plugins/virtual-entries.ts +12 -2
- package/src/vite/rango.ts +15 -6
- package/src/vite/utils/ast-handler-extract.ts +11 -4
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/prerender-utils.ts +17 -2
package/src/cache/cache-scope.ts
CHANGED
|
@@ -18,7 +18,13 @@ import {
|
|
|
18
18
|
} from "../server/request-context.js";
|
|
19
19
|
import { recordRequestTags } from "./cache-tag.js";
|
|
20
20
|
import { reportCacheError } from "./cache-error.js";
|
|
21
|
-
|
|
21
|
+
// segment-codec is the only module on cache-scope's import graph that eagerly
|
|
22
|
+
// pulls @vitejs/plugin-rsc (a virtual: module the plain node/vitest runner cannot
|
|
23
|
+
// resolve). It is imported LAZILY at the two call sites below (deserializeSegments
|
|
24
|
+
// in lookupRoute, serializeSegments in cacheRoute) so that requiring cache-scope —
|
|
25
|
+
// e.g. dispatch's lazy `import("../cache/cache-scope.js")` for the response-route
|
|
26
|
+
// cache path — does not crash a consumer test that never mocks plugin-rsc. Behavior
|
|
27
|
+
// is unchanged: both methods are async and already awaited the codec.
|
|
22
28
|
import {
|
|
23
29
|
captureHandles,
|
|
24
30
|
restoreHandles,
|
|
@@ -28,6 +34,7 @@ import {
|
|
|
28
34
|
import { sortedSearchString, sortedRouteParams } from "./cache-key-utils.js";
|
|
29
35
|
import {
|
|
30
36
|
DEFAULT_ROUTE_TTL,
|
|
37
|
+
isFiniteNonNegativeSeconds,
|
|
31
38
|
resolveCacheKey,
|
|
32
39
|
resolveCacheStore,
|
|
33
40
|
resolveTagsOption,
|
|
@@ -48,6 +55,36 @@ function debugCacheLog(message: string): void {
|
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
|
|
58
|
+
/**
|
|
59
|
+
* A finite, non-negative seconds value? A NaN/Infinity ttl/swr (from a bad
|
|
60
|
+
* cache() option or store defaults) flows into computeExpiration ->
|
|
61
|
+
* staleAt/expiresAt = NaN, where every `now > NaN` is false so the entry never
|
|
62
|
+
* evicts and is served fresh forever; a negative value makes every read a miss.
|
|
63
|
+
* Mirror profile-registry.ts's Number.isFinite + >= 0 check, but the callers
|
|
64
|
+
* degrade to a default (warning in dev) rather than throw — this runs on the
|
|
65
|
+
* foreground render.
|
|
66
|
+
*/
|
|
67
|
+
function isValidCacheSeconds(value: number, label: string): boolean {
|
|
68
|
+
if (isFiniteNonNegativeSeconds(value)) return true;
|
|
69
|
+
if (process.env.NODE_ENV !== "production") {
|
|
70
|
+
console.warn(
|
|
71
|
+
`[CacheScope] Invalid ${label} ${value}; falling back to default`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Coerce a resolved ttl to a finite, non-negative number (default on invalid). */
|
|
78
|
+
function validatedTtl(value: number): number {
|
|
79
|
+
return isValidCacheSeconds(value, "ttl") ? value : DEFAULT_ROUTE_TTL;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Coerce a resolved swr to a finite, non-negative number, or undefined (no SWR window). */
|
|
83
|
+
function validatedSwr(value: number | undefined): number | undefined {
|
|
84
|
+
if (value === undefined) return undefined;
|
|
85
|
+
return isValidCacheSeconds(value, "swr") ? value : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
51
88
|
function getCacheKeyBase(
|
|
52
89
|
host: string,
|
|
53
90
|
pathname: string,
|
|
@@ -124,20 +161,26 @@ export class CacheScope {
|
|
|
124
161
|
}
|
|
125
162
|
|
|
126
163
|
/**
|
|
127
|
-
* Get effective TTL from config or store defaults
|
|
164
|
+
* Get effective TTL from config or store defaults.
|
|
165
|
+
*
|
|
166
|
+
* Unlike profile-registry.ts (which fails fast at config time), the render
|
|
167
|
+
* path must DEGRADE: a non-finite/negative ttl (NaN/Infinity from a bad
|
|
168
|
+
* defaults config) would make computeExpiration produce NaN deadlines so the
|
|
169
|
+
* entry never evicts, or a guaranteed miss for a negative value. Fall back to
|
|
170
|
+
* DEFAULT_ROUTE_TTL instead of throwing in the foreground render.
|
|
128
171
|
*/
|
|
129
172
|
get ttl(): number {
|
|
130
173
|
if (this.config === false) return 0;
|
|
131
174
|
|
|
132
175
|
// Explicit TTL in cache() options
|
|
133
176
|
if (this.config.ttl !== undefined) {
|
|
134
|
-
return this.config.ttl;
|
|
177
|
+
return validatedTtl(this.config.ttl);
|
|
135
178
|
}
|
|
136
179
|
|
|
137
180
|
// Fall back to store defaults (explicit store first, then app-level)
|
|
138
181
|
const store = this.getStore();
|
|
139
182
|
if (store?.defaults?.ttl !== undefined) {
|
|
140
|
-
return store.defaults.ttl;
|
|
183
|
+
return validatedTtl(store.defaults.ttl);
|
|
141
184
|
}
|
|
142
185
|
|
|
143
186
|
// Hardcoded fallback
|
|
@@ -145,19 +188,22 @@ export class CacheScope {
|
|
|
145
188
|
}
|
|
146
189
|
|
|
147
190
|
/**
|
|
148
|
-
* Get SWR window from config or store defaults
|
|
191
|
+
* Get SWR window from config or store defaults.
|
|
192
|
+
*
|
|
193
|
+
* A non-finite/negative swr is degraded to undefined (no SWR window) rather
|
|
194
|
+
* than fed into expiry math; see the ttl getter for the rationale.
|
|
149
195
|
*/
|
|
150
196
|
get swr(): number | undefined {
|
|
151
197
|
if (this.config === false) return undefined;
|
|
152
198
|
|
|
153
199
|
// Explicit SWR in cache() options
|
|
154
200
|
if (this.config.swr !== undefined) {
|
|
155
|
-
return this.config.swr;
|
|
201
|
+
return validatedSwr(this.config.swr);
|
|
156
202
|
}
|
|
157
203
|
|
|
158
204
|
// Fall back to store defaults
|
|
159
205
|
const store = this.getStore();
|
|
160
|
-
return store?.defaults?.swr;
|
|
206
|
+
return validatedSwr(store?.defaults?.swr);
|
|
161
207
|
}
|
|
162
208
|
|
|
163
209
|
/**
|
|
@@ -231,10 +277,16 @@ export class CacheScope {
|
|
|
231
277
|
const store = this.getStore();
|
|
232
278
|
if (!store) return null;
|
|
233
279
|
|
|
234
|
-
// Resolve cache key
|
|
235
|
-
|
|
236
|
-
|
|
280
|
+
// Resolve cache key INSIDE the try so a throwing consumer key() (or a
|
|
281
|
+
// store.keyGenerator) degrades to a cache miss (return null -> render
|
|
282
|
+
// uncached) instead of crashing the foreground render. resolveCacheKey
|
|
283
|
+
// itself keeps its hard-fail/no-fallback-to-default contract (a throw must
|
|
284
|
+
// not silently collide onto the default slot); the graceful degradation
|
|
285
|
+
// happens here, where a miss is a safe outcome.
|
|
286
|
+
let key: string | undefined;
|
|
237
287
|
try {
|
|
288
|
+
key = await this.resolveKey(pathname, params, isIntercept);
|
|
289
|
+
|
|
238
290
|
const result = await store.get(key);
|
|
239
291
|
|
|
240
292
|
if (!result) {
|
|
@@ -250,6 +302,7 @@ export class CacheScope {
|
|
|
250
302
|
// error (handled by the outer catch).
|
|
251
303
|
let segments: ResolvedSegment[];
|
|
252
304
|
try {
|
|
305
|
+
const { deserializeSegments } = await import("./segment-codec.js");
|
|
253
306
|
segments = await deserializeSegments(cached.segments);
|
|
254
307
|
} catch (error) {
|
|
255
308
|
reportCacheError(
|
|
@@ -294,7 +347,13 @@ export class CacheScope {
|
|
|
294
347
|
|
|
295
348
|
return { segments, shouldRevalidate };
|
|
296
349
|
} catch (error) {
|
|
297
|
-
|
|
350
|
+
// Covers a store.get() failure AND a throwing consumer key()/keyGenerator
|
|
351
|
+
// (resolveKey). Either way degrade to a cache miss so the render proceeds.
|
|
352
|
+
reportCacheError(
|
|
353
|
+
error,
|
|
354
|
+
"cache-read",
|
|
355
|
+
`[CacheScope] lookup ${key ?? "(key resolution failed)"}`,
|
|
356
|
+
);
|
|
298
357
|
return null;
|
|
299
358
|
}
|
|
300
359
|
}
|
|
@@ -420,6 +479,7 @@ export class CacheScope {
|
|
|
420
479
|
// Serialize segments and Flight-encode handles in parallel. Handles go
|
|
421
480
|
// through the codec (not raw into the entry) so Promise/ReactNode handle
|
|
422
481
|
// values survive a JSON-serializing store — see encodeHandles.
|
|
482
|
+
const { serializeSegments } = await import("./segment-codec.js");
|
|
423
483
|
const [serializedSegments, encodedHandles] = await Promise.all([
|
|
424
484
|
serializeSegments(nonLoaderSegments),
|
|
425
485
|
encodeHandles(handles),
|
package/src/cache/cache-tag.ts
CHANGED
|
@@ -19,8 +19,13 @@ import {
|
|
|
19
19
|
const cacheTagStorage = new AsyncLocalStorage<Set<string>>();
|
|
20
20
|
|
|
21
21
|
export function normalizeTag(tag: string): string | null {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Trim and return the canonical (trimmed) form, not the raw tag. Both the
|
|
23
|
+
// write path (cacheTag) and the invalidate path (updateTag/revalidateTag)
|
|
24
|
+
// route through here, and matching is exact-string: returning the untrimmed
|
|
25
|
+
// tag made cacheTag(" products ") and updateTag("products") two different
|
|
26
|
+
// logical tags, a silent failure-to-invalidate (stale data served forever).
|
|
27
|
+
const trimmed = tag?.trim();
|
|
28
|
+
return trimmed ? trimmed : null;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
export function normalizeTags(tags: Iterable<string>): string[] {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Base64 Helpers (binary-safe response body encoding for KV)
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
// Chunk size for String.fromCharCode.apply: large enough to amortize the call
|
|
6
|
+
// overhead, small enough to stay well under the JS engine argument-count limit
|
|
7
|
+
// (~65k). 8192 turns a per-byte concat loop into O(n/8192) apply calls.
|
|
8
|
+
const FROM_CHARCODE_CHUNK = 8192;
|
|
9
|
+
|
|
10
|
+
/** Encode ArrayBuffer to base64 string. */
|
|
11
|
+
export function bufferToBase64(buffer: ArrayBuffer): string {
|
|
12
|
+
const bytes = new Uint8Array(buffer);
|
|
13
|
+
// Build the binary (latin1) string in fixed-size chunks instead of one
|
|
14
|
+
// String.fromCharCode per byte. Identical output to the per-byte loop (each
|
|
15
|
+
// byte maps to the same code unit); just far fewer string concatenations for
|
|
16
|
+
// large document payloads.
|
|
17
|
+
let binary = "";
|
|
18
|
+
for (let i = 0; i < bytes.length; i += FROM_CHARCODE_CHUNK) {
|
|
19
|
+
const chunk = bytes.subarray(i, i + FROM_CHARCODE_CHUNK);
|
|
20
|
+
binary += String.fromCharCode.apply(null, chunk as unknown as number[]);
|
|
21
|
+
}
|
|
22
|
+
return btoa(binary);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Decode base64 string to ArrayBuffer. */
|
|
26
|
+
export function base64ToBuffer(base64: string): ArrayBuffer {
|
|
27
|
+
const binary = atob(base64);
|
|
28
|
+
const bytes = new Uint8Array(binary.length);
|
|
29
|
+
for (let i = 0; i < binary.length; i++) {
|
|
30
|
+
bytes[i] = binary.charCodeAt(i);
|
|
31
|
+
}
|
|
32
|
+
return bytes.buffer;
|
|
33
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Constants
|
|
3
|
+
// ============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Header names, KV prefixes, and timeout/interval defaults for the CF cache
|
|
6
|
+
// store. Extracted from cf-cache-store.ts so the constants can be shared by the
|
|
7
|
+
// store and its sibling collaborator modules without a circular import back to
|
|
8
|
+
// the class. The public ones (CACHE_*_HEADER, TAG_MARKER_PREFIX,
|
|
9
|
+
// MAX_REVALIDATION_INTERVAL, EDGE_*_TIMEOUT_MS, KV_READ_TIMEOUT_MS) are
|
|
10
|
+
// re-exported from cf-cache-store.ts so existing import paths still resolve.
|
|
11
|
+
|
|
12
|
+
/** Header storing timestamp when entry becomes stale */
|
|
13
|
+
export const CACHE_STALE_AT_HEADER = "x-edge-cache-stale-at";
|
|
14
|
+
|
|
15
|
+
/** Header storing cache status: HIT | REVALIDATING */
|
|
16
|
+
export const CACHE_STATUS_HEADER = "x-edge-cache-status";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Header storing this entry's cache tags as a JSON array. JSON-encoded (not the
|
|
20
|
+
* comma-delimited CF `Cache-Tag` format) so tags containing commas round-trip
|
|
21
|
+
* safely; the read paths parse this to run the tag-invalidation check.
|
|
22
|
+
*/
|
|
23
|
+
export const CACHE_TAGS_HEADER = "x-edge-cache-tags";
|
|
24
|
+
|
|
25
|
+
/** Header storing the ms-epoch timestamp when this entry's tags were attached. */
|
|
26
|
+
export const CACHE_TAGGED_AT_HEADER = "x-edge-cache-tagged-at";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* KV key prefix for tag-invalidation markers. A marker stores the ms-epoch
|
|
30
|
+
* timestamp of the most recent invalidation of a tag; reads treat any entry
|
|
31
|
+
* whose taggedAt is older than its tags' latest marker as invalidated. Markers
|
|
32
|
+
* live in the SAME KV namespace as the cached entries - there is no separate
|
|
33
|
+
* tag-invalidation store.
|
|
34
|
+
*/
|
|
35
|
+
export const TAG_MARKER_PREFIX = "__tag__/";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Header storing the epoch-ms timestamp when an entry was marked REVALIDATING.
|
|
39
|
+
* The SWR thundering-herd guard reads this to decide whether the in-flight
|
|
40
|
+
* revalidation is still recent. It replaces a prior reliance on the HTTP `Age`
|
|
41
|
+
* header: CF's Cache API does not populate `Age` reliably per-colo (and our own
|
|
42
|
+
* unit MockCache never set it), so an absent `Age` defaulted to 0 and made every
|
|
43
|
+
* REVALIDATING entry look "just revalidated" forever -- a dropped/never-finished
|
|
44
|
+
* background revalidation could then pin an entry stale until hard expiry. An
|
|
45
|
+
* explicit timestamp we write ourselves (same pattern as CACHE_STALE_AT_HEADER)
|
|
46
|
+
* is reliable and lets the MAX_REVALIDATION_INTERVAL re-arm actually fire.
|
|
47
|
+
*/
|
|
48
|
+
export const CACHE_REVALIDATING_AT_HEADER = "x-edge-cache-revalidating-at";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Header storing the absolute epoch-ms hard-expiry deadline (staleAt +
|
|
52
|
+
* swrWindow*1000) of an L1 entry. The stale-path REVALIDATING re-put reads this
|
|
53
|
+
* to recompute a SHRINKING Cache-Control max-age instead of copying set()'s
|
|
54
|
+
* original full-window max-age. Without it, every MAX_REVALIDATION_INTERVAL
|
|
55
|
+
* re-arm re-puts the full window and restarts CF's retention clock, pinning a
|
|
56
|
+
* perpetually-stale entry (one whose background revalidation keeps failing) past
|
|
57
|
+
* its intended hard-expiry indefinitely. Mirrors the KVSegmentEnvelope `e`
|
|
58
|
+
* field and the remaining-ttl math in promoteSegmentToL1/promoteItemToL1.
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export const CACHE_EXPIRES_AT_HEADER = "x-edge-cache-expires-at";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Header stashing the route author's original Cache-Control on L1 document
|
|
65
|
+
* entries. putResponse/promoteResponseToL1 overwrite Cache-Control with a long
|
|
66
|
+
* `max-age` so the CF Cache API retains the entry across the whole SWR window;
|
|
67
|
+
* getResponse restores this original value before serving so the client and any
|
|
68
|
+
* upstream CDN see the author's intended directive, not the internal edge TTL.
|
|
69
|
+
*/
|
|
70
|
+
export const CACHE_ORIG_CC_HEADER = "x-edge-cache-orig-cc";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Maximum age in seconds for REVALIDATING status before allowing new revalidation.
|
|
74
|
+
* After this period, a stale entry in REVALIDATING status will trigger revalidation again.
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
export const MAX_REVALIDATION_INTERVAL = 30;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Maximum time (ms) to wait for an L1 edge cache (CF Cache API) read before
|
|
81
|
+
* giving up and treating it as a miss. The Cache API is normally sub-millisecond
|
|
82
|
+
* per-colo, so a slow `match` signals a degraded colo; we don't want it adding
|
|
83
|
+
* latency to the request. On timeout the lookup is abandoned, a warning is
|
|
84
|
+
* logged, and the read falls through to its normal miss path (L2/KV or render).
|
|
85
|
+
*
|
|
86
|
+
* This is the default; override per store via
|
|
87
|
+
* `CFCacheStoreOptions.edgeLookupTimeoutMs` (<= 0 disables the budget).
|
|
88
|
+
*/
|
|
89
|
+
export const EDGE_LOOKUP_TIMEOUT_MS = 10;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Maximum time (ms) to wait for the BODY of a matched L1 entry to be read
|
|
93
|
+
* (response.json()) before treating the read as a miss.
|
|
94
|
+
*
|
|
95
|
+
* This is separate from {@link EDGE_LOOKUP_TIMEOUT_MS} on purpose. CF's Cache
|
|
96
|
+
* API resolves `match()` with a lazily-streamed body, so a fast `match` can be
|
|
97
|
+
* followed by a multi-second stall while the body bytes are fetched -- the
|
|
98
|
+
* latency tail lives here, after the match budget has already passed. The
|
|
99
|
+
* default bounds that tail aggressively: a healthy per-colo body read (fetch +
|
|
100
|
+
* JSON parse) settles in low single-digit milliseconds, so 20ms clears a
|
|
101
|
+
* healthy read while still failing fast to L2/KV (or render) on a degraded colo
|
|
102
|
+
* instead of pinning the request behind a seconds-long read. Raise it per store
|
|
103
|
+
* if large Flight payloads legitimately need longer.
|
|
104
|
+
*
|
|
105
|
+
* Override per store via `CFCacheStoreOptions.edgeReadTimeoutMs` (<= 0 disables).
|
|
106
|
+
*/
|
|
107
|
+
export const EDGE_READ_TIMEOUT_MS = 20;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Maximum time (ms) to wait for an L2 (KV) read (`kv.get(key, {type:"json"})`)
|
|
111
|
+
* before treating it as a miss. Unlike the L1 budgets, KV is a GLOBAL store: the
|
|
112
|
+
* file header documents ~50ms healthy reads, and a degraded namespace can tail
|
|
113
|
+
* to seconds. KV is the LAST cache tier before a full render, so an unbounded
|
|
114
|
+
* read here pins the whole request behind a degraded global lookup.
|
|
115
|
+
*
|
|
116
|
+
* The default (170ms) sits a few multiples above the documented ~50ms healthy
|
|
117
|
+
* read, leaving headroom for legitimate latency tails (larger payloads,
|
|
118
|
+
* far-from-colo regions) so a healthy-but-slow read does not false-miss into a
|
|
119
|
+
* render, while still abandoning a genuinely degraded namespace well before its
|
|
120
|
+
* multi-second tail can pin the request. A deployment with a tighter SLA can
|
|
121
|
+
* lower it, and one whose healthy p99 runs higher should raise it: measure the
|
|
122
|
+
* KV read p99 (Workers Analytics) and add margin. It is a degradation
|
|
123
|
+
* guard-rail, not a tuning lever for "slow KV is normal here".
|
|
124
|
+
*
|
|
125
|
+
* Override per store via `CFCacheStoreOptions.kvReadTimeoutMs` (<= 0 disables).
|
|
126
|
+
*/
|
|
127
|
+
export const KV_READ_TIMEOUT_MS = 170;
|