@rangojs/router 0.0.0-experimental.9 → 0.0.0-experimental.98d9a51b
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 +9 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1532 -155
- package/dist/vite/index.js +4444 -2235
- package/package.json +69 -62
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +85 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/hooks/SKILL.md +333 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +74 -15
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +78 -1
- package/skills/prerender/SKILL.md +405 -45
- package/skills/rango/SKILL.md +85 -21
- package/skills/response-routes/SKILL.md +144 -91
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +316 -87
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +282 -557
- package/src/browser/navigation-client.ts +169 -73
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +292 -310
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +190 -70
- package/src/browser/react/NavigationProvider.tsx +78 -11
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +29 -70
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +106 -27
- package/src/browser/scroll-restoration.ts +117 -44
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +116 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +82 -21
- package/src/build/generate-route-types.ts +36 -752
- package/src/build/index.ts +6 -5
- package/src/build/route-trie.ts +39 -13
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +122 -303
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +84 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +77 -7
- package/src/handle.ts +15 -10
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +133 -21
- package/src/index.ts +164 -52
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +158 -13
- package/src/prerender.ts +333 -26
- package/src/reverse.ts +184 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1431
- package/src/route-map-builder.ts +162 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +48 -9
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +26 -16
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +85 -32
- package/src/router/match-api.ts +118 -119
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +336 -84
- package/src/router/match-middleware/cache-store.ts +43 -24
- package/src/router/match-middleware/intercept-resolution.ts +45 -20
- package/src/router/match-middleware/segment-resolution.ts +17 -8
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +327 -369
- package/src/router/pattern-matching.ts +197 -41
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +40 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1242 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +96 -29
- package/src/router/types.ts +16 -9
- package/src/router.ts +595 -1984
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +680 -1027
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +25 -13
- package/src/server/context.ts +173 -48
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +439 -73
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +100 -31
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1757
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1282
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -1963
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +27 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/vite/expose-prerender-handler-id.ts +0 -429
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/cache/cf/index.ts
CHANGED
|
@@ -10,10 +10,20 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
// Public API
|
|
13
|
-
export {
|
|
13
|
+
export {
|
|
14
|
+
CFCacheStore,
|
|
15
|
+
type CFCacheStoreOptions,
|
|
16
|
+
type KVNamespace,
|
|
17
|
+
} from "./cf-cache-store.js";
|
|
14
18
|
|
|
15
19
|
// Header constants for debugging and inspection
|
|
16
|
-
export {
|
|
20
|
+
export {
|
|
21
|
+
CACHE_STALE_AT_HEADER,
|
|
22
|
+
CACHE_STATUS_HEADER,
|
|
23
|
+
} from "./cf-cache-store.js";
|
|
17
24
|
|
|
18
25
|
// Internal exports (re-exported for backwards compatibility, marked @internal in source)
|
|
19
|
-
export {
|
|
26
|
+
export {
|
|
27
|
+
type CacheStatus,
|
|
28
|
+
MAX_REVALIDATION_INTERVAL,
|
|
29
|
+
} from "./cf-cache-store.js";
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import type { MiddlewareFn, MiddlewareContext } from "../router/middleware.js";
|
|
15
15
|
import { getRequestContext } from "../server/request-context.js";
|
|
16
|
+
import { sortedSearchString } from "./cache-key-utils.js";
|
|
17
|
+
import { runBackground } from "./background-task.js";
|
|
16
18
|
|
|
17
19
|
// ============================================================================
|
|
18
20
|
// Constants
|
|
@@ -110,15 +112,24 @@ function addCacheStatusHeader(
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
/**
|
|
113
|
-
*
|
|
115
|
+
* Drain and run onResponse callbacks registered on the request context.
|
|
116
|
+
* Mirrors the drain semantics of finalizeResponse() in rsc/helpers.ts:
|
|
117
|
+
* callbacks are spliced out so they fire at most once per request.
|
|
114
118
|
*/
|
|
115
|
-
function
|
|
119
|
+
function drainOnResponseCallbacks(
|
|
116
120
|
response: Response,
|
|
117
|
-
|
|
121
|
+
requestCtx:
|
|
122
|
+
| { _onResponseCallbacks: Array<(r: Response) => Response> }
|
|
123
|
+
| undefined,
|
|
118
124
|
): Response {
|
|
125
|
+
if (!requestCtx || requestCtx._onResponseCallbacks.length === 0) {
|
|
126
|
+
return response;
|
|
127
|
+
}
|
|
128
|
+
const callbacks = requestCtx._onResponseCallbacks;
|
|
129
|
+
requestCtx._onResponseCallbacks = [];
|
|
119
130
|
let result = response;
|
|
120
131
|
for (const callback of callbacks) {
|
|
121
|
-
result = callback(result);
|
|
132
|
+
result = callback(result) ?? result;
|
|
122
133
|
}
|
|
123
134
|
return result;
|
|
124
135
|
}
|
|
@@ -185,9 +196,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
185
196
|
): MiddlewareFn<TEnv> {
|
|
186
197
|
const { skipPaths = [], keyGenerator, isEnabled, debug = false } = options;
|
|
187
198
|
|
|
188
|
-
const log = debug
|
|
189
|
-
? (message: string) => console.log(message)
|
|
190
|
-
: () => {};
|
|
199
|
+
const log = debug ? (message: string) => console.log(message) : () => {};
|
|
191
200
|
|
|
192
201
|
return async function documentCacheMiddleware(
|
|
193
202
|
ctx: MiddlewareContext<TEnv>,
|
|
@@ -195,6 +204,11 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
195
204
|
): Promise<Response> {
|
|
196
205
|
const url = ctx.url;
|
|
197
206
|
|
|
207
|
+
// Only cache GET requests — mutations and other methods must not be cached
|
|
208
|
+
if (ctx.request.method !== "GET") {
|
|
209
|
+
return next();
|
|
210
|
+
}
|
|
211
|
+
|
|
198
212
|
// Skip RSC action requests (mutations shouldn't be cached)
|
|
199
213
|
if (url.searchParams.has("_rsc_action")) {
|
|
200
214
|
return next();
|
|
@@ -231,17 +245,31 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
231
245
|
const isPartial = url.searchParams.has("_rsc_partial");
|
|
232
246
|
const typeLabel = isPartial ? "RSC" : "HTML";
|
|
233
247
|
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
const clientSegments = url.searchParams.get("_rsc_segments") || "";
|
|
238
|
-
const segmentHash = isPartial && clientSegments ? `:${hashSegmentIds(clientSegments)}` : "";
|
|
239
|
-
const typeSuffix = isPartial ? ":rsc" : ":html";
|
|
240
|
-
const cacheKey = keyGenerator
|
|
241
|
-
? keyGenerator(url) + segmentHash + typeSuffix
|
|
242
|
-
: `${url.pathname}${segmentHash}${typeSuffix}`;
|
|
248
|
+
// Track whether next() has been called so the catch block knows
|
|
249
|
+
// whether it is safe to fall through to the handler.
|
|
250
|
+
let handlerCalled = false;
|
|
243
251
|
|
|
244
252
|
try {
|
|
253
|
+
// Generate cache key inside try so a throwing keyGenerator degrades
|
|
254
|
+
// gracefully to the origin handler instead of rejecting the request.
|
|
255
|
+
// This is a deliberate fail-open-to-origin policy: the fallback is
|
|
256
|
+
// "serve uncached from origin", not "use a different cache key".
|
|
257
|
+
const clientSegments = url.searchParams.get("_rsc_segments") || "";
|
|
258
|
+
const segmentHash =
|
|
259
|
+
isPartial && clientSegments ? `:${hashSegmentIds(clientSegments)}` : "";
|
|
260
|
+
const typeSuffix = isPartial ? ":rsc" : ":html";
|
|
261
|
+
|
|
262
|
+
let searchSuffix = "";
|
|
263
|
+
if (!keyGenerator) {
|
|
264
|
+
const sorted = sortedSearchString(url.searchParams);
|
|
265
|
+
if (sorted) {
|
|
266
|
+
searchSuffix = `?${sorted}`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const cacheKey = keyGenerator
|
|
271
|
+
? keyGenerator(url) + segmentHash + typeSuffix
|
|
272
|
+
: `${url.pathname}${searchSuffix}${segmentHash}${typeSuffix}`;
|
|
245
273
|
// 1. Check cache
|
|
246
274
|
const cached = await store.getResponse(cacheKey);
|
|
247
275
|
|
|
@@ -249,79 +277,75 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
249
277
|
if (!cached.shouldRevalidate) {
|
|
250
278
|
// Fresh hit - return immediately
|
|
251
279
|
log(`[DocumentCache] HIT ${typeLabel}: ${url.pathname}`);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
response,
|
|
257
|
-
requestCtx._onResponseCallbacks,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
return response;
|
|
280
|
+
return drainOnResponseCallbacks(
|
|
281
|
+
addCacheStatusHeader(cached.response, "HIT"),
|
|
282
|
+
requestCtx,
|
|
283
|
+
);
|
|
261
284
|
}
|
|
262
285
|
|
|
263
286
|
// Stale hit - return cached response, revalidate in background
|
|
264
|
-
log(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
} catch (error) {
|
|
282
|
-
console.error(`[DocumentCache] Revalidation failed:`, error);
|
|
287
|
+
log(
|
|
288
|
+
`[DocumentCache] STALE ${typeLabel}: ${url.pathname} (revalidating)`,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
runBackground(requestCtx, async () => {
|
|
292
|
+
try {
|
|
293
|
+
const fresh = await next();
|
|
294
|
+
const directives = shouldCacheResponse(fresh);
|
|
295
|
+
|
|
296
|
+
if (directives) {
|
|
297
|
+
await store.putResponse!(
|
|
298
|
+
cacheKey,
|
|
299
|
+
fresh,
|
|
300
|
+
directives.sMaxAge!,
|
|
301
|
+
directives.staleWhileRevalidate,
|
|
302
|
+
);
|
|
303
|
+
log(`[DocumentCache] REVALIDATED ${typeLabel}: ${url.pathname}`);
|
|
283
304
|
}
|
|
284
|
-
})
|
|
285
|
-
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error(`[DocumentCache] Revalidation failed:`, error);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
286
309
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
response,
|
|
292
|
-
requestCtx._onResponseCallbacks,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
return response;
|
|
310
|
+
return drainOnResponseCallbacks(
|
|
311
|
+
addCacheStatusHeader(cached.response, "STALE"),
|
|
312
|
+
requestCtx,
|
|
313
|
+
);
|
|
296
314
|
}
|
|
297
315
|
|
|
298
316
|
// 2. Cache miss - run handler
|
|
317
|
+
handlerCalled = true;
|
|
299
318
|
const originalResponse = await next();
|
|
300
319
|
|
|
301
320
|
// 3. Cache if response has appropriate headers
|
|
302
321
|
const directives = shouldCacheResponse(originalResponse);
|
|
303
322
|
|
|
304
323
|
if (directives) {
|
|
305
|
-
log(
|
|
324
|
+
log(
|
|
325
|
+
`[DocumentCache] MISS ${typeLabel}: ${url.pathname} (caching with s-maxage=${directives.sMaxAge})`,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// If the response has no body (e.g., 200 with empty body), skip caching
|
|
329
|
+
if (!originalResponse.body) {
|
|
330
|
+
return originalResponse;
|
|
331
|
+
}
|
|
306
332
|
|
|
307
333
|
// Tee the body so we can return one stream and cache the other
|
|
308
|
-
const [returnStream, cacheStream] = originalResponse.body
|
|
334
|
+
const [returnStream, cacheStream] = originalResponse.body.tee();
|
|
309
335
|
|
|
310
336
|
// Clone response for caching (non-blocking)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
});
|
|
324
|
-
}
|
|
337
|
+
runBackground(requestCtx, async () => {
|
|
338
|
+
try {
|
|
339
|
+
await store.putResponse!(
|
|
340
|
+
cacheKey,
|
|
341
|
+
new Response(cacheStream, originalResponse),
|
|
342
|
+
directives.sMaxAge!,
|
|
343
|
+
directives.staleWhileRevalidate,
|
|
344
|
+
);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error(`[DocumentCache] Cache write failed:`, error);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
325
349
|
|
|
326
350
|
return addCacheStatusHeader(
|
|
327
351
|
new Response(returnStream, originalResponse),
|
|
@@ -333,7 +357,12 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
333
357
|
return originalResponse;
|
|
334
358
|
} catch (error) {
|
|
335
359
|
console.error(`[DocumentCache] Error:`, error);
|
|
336
|
-
|
|
360
|
+
if (handlerCalled) {
|
|
361
|
+
// Post-handler failure (e.g. body.tee()): do not call next() again
|
|
362
|
+
// as that would re-run handler side effects.
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
// Pre-handler failure (cache lookup): degrade gracefully to origin
|
|
337
366
|
return next();
|
|
338
367
|
}
|
|
339
368
|
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle Capture
|
|
3
|
+
*
|
|
4
|
+
* Captures handle pushes during cached function execution.
|
|
5
|
+
* Extracted from cache-runtime.ts so tests can import without
|
|
6
|
+
* pulling in @vitejs/plugin-rsc/rsc dependencies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { HandleStore } from "../server/handle-store.js";
|
|
10
|
+
import type { SegmentHandleData } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export interface HandleCapture {
|
|
13
|
+
data: Record<string, SegmentHandleData>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Active capture tokens per HandleStore.
|
|
18
|
+
*
|
|
19
|
+
* Instead of mutating handleStore.push (which breaks when overlapping
|
|
20
|
+
* captures finish out of order), we install a single interceptor on
|
|
21
|
+
* first use and manage a set of active capture tokens. Each push fans
|
|
22
|
+
* out to every active token. Stopping a capture simply removes the
|
|
23
|
+
* token — order does not matter.
|
|
24
|
+
*/
|
|
25
|
+
const activeCapturesMap = new WeakMap<HandleStore, Set<HandleCapture>>();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* One-time interceptor installation. Wraps the original push so every
|
|
29
|
+
* call fans out to all active capture tokens. Installed once per
|
|
30
|
+
* HandleStore instance; subsequent startHandleCapture calls on the
|
|
31
|
+
* same store just add tokens to the Set.
|
|
32
|
+
*/
|
|
33
|
+
function ensureInterceptorInstalled(handleStore: HandleStore): void {
|
|
34
|
+
if (activeCapturesMap.has(handleStore)) return;
|
|
35
|
+
|
|
36
|
+
const captures = new Set<HandleCapture>();
|
|
37
|
+
activeCapturesMap.set(handleStore, captures);
|
|
38
|
+
|
|
39
|
+
const originalPush = handleStore.push.bind(handleStore);
|
|
40
|
+
handleStore.push = (
|
|
41
|
+
handleName: string,
|
|
42
|
+
segmentId: string,
|
|
43
|
+
value: unknown,
|
|
44
|
+
) => {
|
|
45
|
+
for (const capture of captures) {
|
|
46
|
+
if (!capture.data[segmentId]) {
|
|
47
|
+
capture.data[segmentId] = {};
|
|
48
|
+
}
|
|
49
|
+
if (!capture.data[segmentId][handleName]) {
|
|
50
|
+
capture.data[segmentId][handleName] = [];
|
|
51
|
+
}
|
|
52
|
+
capture.data[segmentId][handleName].push(value);
|
|
53
|
+
}
|
|
54
|
+
originalPush(handleName, segmentId, value);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Start capturing handle pushes for a cached function execution.
|
|
60
|
+
*
|
|
61
|
+
* Concurrency-safe: multiple overlapping captures on the same
|
|
62
|
+
* HandleStore are independent. Each capture registers a token in a
|
|
63
|
+
* Set; stopping removes it. No ordering requirement (LIFO not needed).
|
|
64
|
+
*/
|
|
65
|
+
export function startHandleCapture(handleStore: HandleStore): {
|
|
66
|
+
capture: HandleCapture;
|
|
67
|
+
stop: () => void;
|
|
68
|
+
} {
|
|
69
|
+
ensureInterceptorInstalled(handleStore);
|
|
70
|
+
|
|
71
|
+
const capture: HandleCapture = { data: {} };
|
|
72
|
+
const captures = activeCapturesMap.get(handleStore)!;
|
|
73
|
+
captures.add(capture);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
capture,
|
|
77
|
+
stop() {
|
|
78
|
+
captures.delete(capture);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle Snapshot
|
|
3
|
+
*
|
|
4
|
+
* Capture and restore handle data for cached segments.
|
|
5
|
+
* Handle data (breadcrumbs, metadata from ctx.use(Handle)) is collected
|
|
6
|
+
* during segment resolution and stored alongside cached segments.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ResolvedSegment } from "../types.js";
|
|
10
|
+
import type { HandleStore } from "../server/handle-store.js";
|
|
11
|
+
import type { SegmentHandleData } from "./types.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Capture handle data for a set of segments from the handle store.
|
|
15
|
+
* Used when caching segments to preserve their handle data.
|
|
16
|
+
*/
|
|
17
|
+
export function captureHandles(
|
|
18
|
+
segments: ResolvedSegment[],
|
|
19
|
+
handleStore: HandleStore,
|
|
20
|
+
): Record<string, SegmentHandleData> {
|
|
21
|
+
const handles: Record<string, SegmentHandleData> = {};
|
|
22
|
+
for (const seg of segments) {
|
|
23
|
+
handles[seg.id] = handleStore.getDataForSegment(seg.id);
|
|
24
|
+
}
|
|
25
|
+
return handles;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Restore handle data from a cached snapshot into the handle store.
|
|
30
|
+
* Used when serving cached segments to replay their handle data.
|
|
31
|
+
*/
|
|
32
|
+
export function restoreHandles(
|
|
33
|
+
handles: Record<string, SegmentHandleData>,
|
|
34
|
+
handleStore: HandleStore,
|
|
35
|
+
): void {
|
|
36
|
+
for (const [segId, segHandles] of Object.entries(handles)) {
|
|
37
|
+
if (Object.keys(segHandles).length > 0) {
|
|
38
|
+
handleStore.replaySegmentData(segId, segHandles);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/cache/index.ts
CHANGED
|
@@ -10,21 +10,6 @@
|
|
|
10
10
|
* - CacheScope / createCacheScope - Request-scoped cache provider
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
// Generic cache store types (reserved for future extensibility)
|
|
14
|
-
// These types support caching arbitrary values like Response, Stream, etc.
|
|
15
|
-
// Currently unused - segment caching uses SegmentCacheStore directly.
|
|
16
|
-
export type {
|
|
17
|
-
CacheStore,
|
|
18
|
-
CacheEntry,
|
|
19
|
-
CacheValue,
|
|
20
|
-
CacheValueType,
|
|
21
|
-
CachePutOptions,
|
|
22
|
-
CacheMetadata,
|
|
23
|
-
} from "./types.js";
|
|
24
|
-
|
|
25
|
-
// Generic memory cache (reserved for future extensibility)
|
|
26
|
-
export { MemoryCacheStore } from "./memory-store.js";
|
|
27
|
-
|
|
28
13
|
// Segment cache store types and implementations
|
|
29
14
|
export type {
|
|
30
15
|
SegmentCacheStore,
|
|
@@ -44,6 +29,7 @@ export { MemorySegmentCacheStore } from "./memory-segment-store.js";
|
|
|
44
29
|
export {
|
|
45
30
|
CFCacheStore,
|
|
46
31
|
type CFCacheStoreOptions,
|
|
32
|
+
type KVNamespace,
|
|
47
33
|
CACHE_STALE_AT_HEADER,
|
|
48
34
|
CACHE_STATUS_HEADER,
|
|
49
35
|
} from "./cf/index.js";
|
|
@@ -5,15 +5,83 @@
|
|
|
5
5
|
* Uses globalThis to survive HMR in development.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
SegmentCacheStore,
|
|
10
|
+
CachedEntryData,
|
|
11
|
+
CacheDefaults,
|
|
12
|
+
CacheGetResult,
|
|
13
|
+
CacheItemResult,
|
|
14
|
+
CacheItemOptions,
|
|
15
|
+
SegmentHandleData,
|
|
16
|
+
} from "./types.js";
|
|
9
17
|
import type { RequestContext } from "../server/request-context.js";
|
|
18
|
+
import {
|
|
19
|
+
resolveTtl,
|
|
20
|
+
resolveSwrWindow,
|
|
21
|
+
computeExpiration,
|
|
22
|
+
DEFAULT_FUNCTION_TTL,
|
|
23
|
+
} from "./cache-policy.js";
|
|
10
24
|
|
|
11
|
-
const
|
|
25
|
+
const CACHE_REGISTRY_KEY = "__rsc_router_segment_cache_registry__";
|
|
26
|
+
const RESPONSE_CACHE_REGISTRY_KEY = "__rsc_router_response_cache_registry__";
|
|
27
|
+
const ITEM_CACHE_REGISTRY_KEY = "__rsc_router_item_cache_registry__";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get or create a named Map from a globalThis-backed registry.
|
|
31
|
+
* The registry survives HMR; individual stores are keyed by name.
|
|
32
|
+
*/
|
|
33
|
+
function getNamedMap<V>(registryKey: string, name: string): Map<string, V> {
|
|
34
|
+
let registry = (globalThis as any)[registryKey] as
|
|
35
|
+
| Map<string, Map<string, V>>
|
|
36
|
+
| undefined;
|
|
37
|
+
if (!registry) {
|
|
38
|
+
registry = new Map();
|
|
39
|
+
(globalThis as any)[registryKey] = registry;
|
|
40
|
+
}
|
|
41
|
+
let map = registry.get(name);
|
|
42
|
+
if (!map) {
|
|
43
|
+
map = new Map<string, V>();
|
|
44
|
+
registry.set(name, map);
|
|
45
|
+
}
|
|
46
|
+
return map;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface CachedResponseEntry {
|
|
50
|
+
body: ArrayBuffer;
|
|
51
|
+
status: number;
|
|
52
|
+
headers: [string, string][];
|
|
53
|
+
expiresAt: number;
|
|
54
|
+
staleAt: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface CachedItemEntry {
|
|
58
|
+
value: string;
|
|
59
|
+
handles?: Record<string, SegmentHandleData>;
|
|
60
|
+
expiresAt: number;
|
|
61
|
+
staleAt: number;
|
|
62
|
+
}
|
|
12
63
|
|
|
13
64
|
/**
|
|
14
65
|
* Options for MemorySegmentCacheStore
|
|
15
66
|
*/
|
|
16
67
|
export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
68
|
+
/**
|
|
69
|
+
* Optional name for this store instance. Named stores persist their
|
|
70
|
+
* backing Map on globalThis so data survives Vite HMR module reloads.
|
|
71
|
+
* Stores with different names get separate Maps.
|
|
72
|
+
*
|
|
73
|
+
* When omitted, the store uses a plain instance-level Map with no
|
|
74
|
+
* globalThis sharing, which is the safest default for isolation.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // Two named stores are isolated from each other
|
|
79
|
+
* const fast = new MemorySegmentCacheStore({ name: "fast", defaults: { ttl: 10 } });
|
|
80
|
+
* const slow = new MemorySegmentCacheStore({ name: "slow", defaults: { ttl: 300 } });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
name?: string;
|
|
84
|
+
|
|
17
85
|
/**
|
|
18
86
|
* Default cache options for cache() boundaries.
|
|
19
87
|
* When cache() is called without explicit ttl/swr,
|
|
@@ -35,14 +103,14 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
|
35
103
|
* @example
|
|
36
104
|
* ```typescript
|
|
37
105
|
* keyGenerator: (ctx, defaultKey) => {
|
|
38
|
-
* const locale =
|
|
106
|
+
* const locale = cookies().get('locale')?.value || 'en';
|
|
39
107
|
* return `${locale}:${defaultKey}`;
|
|
40
108
|
* }
|
|
41
109
|
* ```
|
|
42
110
|
*/
|
|
43
111
|
keyGenerator?: (
|
|
44
112
|
ctx: RequestContext<TEnv>,
|
|
45
|
-
defaultKey: string
|
|
113
|
+
defaultKey: string,
|
|
46
114
|
) => string | Promise<string>;
|
|
47
115
|
}
|
|
48
116
|
|
|
@@ -69,19 +137,40 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
|
69
137
|
* })
|
|
70
138
|
* ```
|
|
71
139
|
*/
|
|
72
|
-
export class MemorySegmentCacheStore<
|
|
140
|
+
export class MemorySegmentCacheStore<
|
|
141
|
+
TEnv = unknown,
|
|
142
|
+
> implements SegmentCacheStore<TEnv> {
|
|
73
143
|
private cache: Map<string, CachedEntryData>;
|
|
144
|
+
private responseCache: Map<string, CachedResponseEntry>;
|
|
145
|
+
private itemCache: Map<string, CachedItemEntry>;
|
|
74
146
|
readonly defaults?: CacheDefaults;
|
|
75
147
|
readonly keyGenerator?: (
|
|
76
148
|
ctx: RequestContext<TEnv>,
|
|
77
|
-
defaultKey: string
|
|
149
|
+
defaultKey: string,
|
|
78
150
|
) => string | Promise<string>;
|
|
79
151
|
|
|
80
152
|
constructor(options?: MemorySegmentCacheStoreOptions<TEnv>) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
153
|
+
if (options?.name != null) {
|
|
154
|
+
// Named stores use the globalThis registry so data survives HMR.
|
|
155
|
+
// Each name gets its own isolated Map.
|
|
156
|
+
this.cache = getNamedMap<CachedEntryData>(
|
|
157
|
+
CACHE_REGISTRY_KEY,
|
|
158
|
+
options.name,
|
|
159
|
+
);
|
|
160
|
+
this.responseCache = getNamedMap<CachedResponseEntry>(
|
|
161
|
+
RESPONSE_CACHE_REGISTRY_KEY,
|
|
162
|
+
options.name,
|
|
163
|
+
);
|
|
164
|
+
this.itemCache = getNamedMap<CachedItemEntry>(
|
|
165
|
+
ITEM_CACHE_REGISTRY_KEY,
|
|
166
|
+
options.name,
|
|
167
|
+
);
|
|
168
|
+
} else {
|
|
169
|
+
// Unnamed stores get a plain instance-level Map (no globalThis sharing).
|
|
170
|
+
this.cache = new Map<string, CachedEntryData>();
|
|
171
|
+
this.responseCache = new Map<string, CachedResponseEntry>();
|
|
172
|
+
this.itemCache = new Map<string, CachedItemEntry>();
|
|
173
|
+
}
|
|
85
174
|
this.defaults = options?.defaults;
|
|
86
175
|
this.keyGenerator = options?.keyGenerator;
|
|
87
176
|
}
|
|
@@ -103,7 +192,12 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
|
|
|
103
192
|
return { data: cached, shouldRevalidate: false };
|
|
104
193
|
}
|
|
105
194
|
|
|
106
|
-
async set(
|
|
195
|
+
async set(
|
|
196
|
+
key: string,
|
|
197
|
+
data: CachedEntryData,
|
|
198
|
+
ttl: number,
|
|
199
|
+
_swr?: number,
|
|
200
|
+
): Promise<void> {
|
|
107
201
|
// Note: Memory store doesn't implement SWR - entries just expire at TTL
|
|
108
202
|
// For SWR support, use CFCacheStore or similar distributed cache
|
|
109
203
|
const entry: CachedEntryData = {
|
|
@@ -119,6 +213,88 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
|
|
|
119
213
|
|
|
120
214
|
async clear(): Promise<void> {
|
|
121
215
|
this.cache.clear();
|
|
216
|
+
this.responseCache.clear();
|
|
217
|
+
this.itemCache.clear();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async getResponse(
|
|
221
|
+
key: string,
|
|
222
|
+
): Promise<{ response: Response; shouldRevalidate: boolean } | null> {
|
|
223
|
+
const cached = this.responseCache.get(key);
|
|
224
|
+
if (!cached) return null;
|
|
225
|
+
|
|
226
|
+
if (Date.now() > cached.expiresAt) {
|
|
227
|
+
this.responseCache.delete(key);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const isStale = cached.staleAt > 0 && Date.now() > cached.staleAt;
|
|
232
|
+
const headers = new Headers(cached.headers);
|
|
233
|
+
return {
|
|
234
|
+
response: new Response(cached.body, {
|
|
235
|
+
status: cached.status,
|
|
236
|
+
headers,
|
|
237
|
+
}),
|
|
238
|
+
shouldRevalidate: isStale,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async putResponse(
|
|
243
|
+
key: string,
|
|
244
|
+
response: Response,
|
|
245
|
+
ttl: number,
|
|
246
|
+
swr?: number,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const body = await response.clone().arrayBuffer();
|
|
249
|
+
const headers: [string, string][] = [];
|
|
250
|
+
response.headers.forEach((value, name) => {
|
|
251
|
+
headers.push([name, value]);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const swrWindow = resolveSwrWindow(swr, this.defaults);
|
|
255
|
+
const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
|
|
256
|
+
|
|
257
|
+
this.responseCache.set(key, {
|
|
258
|
+
body,
|
|
259
|
+
status: response.status,
|
|
260
|
+
headers,
|
|
261
|
+
expiresAt,
|
|
262
|
+
staleAt,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async getItem(key: string): Promise<CacheItemResult | null> {
|
|
267
|
+
const cached = this.itemCache.get(key);
|
|
268
|
+
if (!cached) return null;
|
|
269
|
+
|
|
270
|
+
const now = Date.now();
|
|
271
|
+
if (now > cached.expiresAt) {
|
|
272
|
+
this.itemCache.delete(key);
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const isStale = now > cached.staleAt;
|
|
277
|
+
return {
|
|
278
|
+
value: cached.value,
|
|
279
|
+
handles: cached.handles,
|
|
280
|
+
shouldRevalidate: isStale,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async setItem(
|
|
285
|
+
key: string,
|
|
286
|
+
value: string,
|
|
287
|
+
options?: CacheItemOptions,
|
|
288
|
+
): Promise<void> {
|
|
289
|
+
const ttl = resolveTtl(options?.ttl, this.defaults, DEFAULT_FUNCTION_TTL);
|
|
290
|
+
const swrWindow = resolveSwrWindow(options?.swr, this.defaults);
|
|
291
|
+
const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
|
|
292
|
+
this.itemCache.set(key, {
|
|
293
|
+
value,
|
|
294
|
+
handles: options?.handles,
|
|
295
|
+
expiresAt,
|
|
296
|
+
staleAt,
|
|
297
|
+
});
|
|
122
298
|
}
|
|
123
299
|
|
|
124
300
|
/**
|
|
@@ -133,7 +309,7 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
|
|
|
133
309
|
}
|
|
134
310
|
|
|
135
311
|
/**
|
|
136
|
-
* Reset the global cache
|
|
312
|
+
* Reset the global cache registry.
|
|
137
313
|
* Useful for test isolation - call this in beforeEach to ensure
|
|
138
314
|
* tests don't share cache state via globalThis.
|
|
139
315
|
*
|
|
@@ -145,6 +321,8 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
|
|
|
145
321
|
* ```
|
|
146
322
|
*/
|
|
147
323
|
static resetGlobalCache(): void {
|
|
148
|
-
delete (globalThis as any)[
|
|
324
|
+
delete (globalThis as any)[CACHE_REGISTRY_KEY];
|
|
325
|
+
delete (globalThis as any)[RESPONSE_CACHE_REGISTRY_KEY];
|
|
326
|
+
delete (globalThis as any)[ITEM_CACHE_REGISTRY_KEY];
|
|
149
327
|
}
|
|
150
328
|
}
|