@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80
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 +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +4960 -935
- package/package.json +70 -60
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -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 +167 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +764 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -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 +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +87 -11
- package/src/browser/react/context.ts +11 -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 +30 -126
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- 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 +76 -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 +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -0
- 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 +418 -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 +618 -0
- package/src/build/route-types/scan-filter.ts +85 -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 +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- 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 +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +135 -301
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -29
- 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 +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +251 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- 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 +37 -0
- package/src/prerender/store.ts +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +77 -8
- package/src/router/content-negotiation.ts +215 -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 +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +460 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +748 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1379 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- 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 +239 -0
- package/src/router/types.ts +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +391 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +356 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -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 +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -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 +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -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 +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -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 +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -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 +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +918 -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 +221 -0
- package/src/vite/utils/shared-utils.ts +170 -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/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- 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/{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,9 @@
|
|
|
13
13
|
|
|
14
14
|
import type { MiddlewareFn, MiddlewareContext } from "../router/middleware.js";
|
|
15
15
|
import { getRequestContext } from "../server/request-context.js";
|
|
16
|
+
import { mayNeedSSR } from "../rsc/ssr-setup.js";
|
|
17
|
+
import { sortedSearchString } from "./cache-key-utils.js";
|
|
18
|
+
import { runBackground } from "./background-task.js";
|
|
16
19
|
|
|
17
20
|
// ============================================================================
|
|
18
21
|
// Constants
|
|
@@ -110,15 +113,24 @@ function addCacheStatusHeader(
|
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
/**
|
|
113
|
-
*
|
|
116
|
+
* Drain and run onResponse callbacks registered on the request context.
|
|
117
|
+
* Mirrors the drain semantics of finalizeResponse() in rsc/helpers.ts:
|
|
118
|
+
* callbacks are spliced out so they fire at most once per request.
|
|
114
119
|
*/
|
|
115
|
-
function
|
|
120
|
+
function drainOnResponseCallbacks(
|
|
116
121
|
response: Response,
|
|
117
|
-
|
|
122
|
+
requestCtx:
|
|
123
|
+
| { _onResponseCallbacks: Array<(r: Response) => Response> }
|
|
124
|
+
| undefined,
|
|
118
125
|
): Response {
|
|
126
|
+
if (!requestCtx || requestCtx._onResponseCallbacks.length === 0) {
|
|
127
|
+
return response;
|
|
128
|
+
}
|
|
129
|
+
const callbacks = requestCtx._onResponseCallbacks;
|
|
130
|
+
requestCtx._onResponseCallbacks = [];
|
|
119
131
|
let result = response;
|
|
120
132
|
for (const callback of callbacks) {
|
|
121
|
-
result = callback(result);
|
|
133
|
+
result = callback(result) ?? result;
|
|
122
134
|
}
|
|
123
135
|
return result;
|
|
124
136
|
}
|
|
@@ -185,9 +197,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
185
197
|
): MiddlewareFn<TEnv> {
|
|
186
198
|
const { skipPaths = [], keyGenerator, isEnabled, debug = false } = options;
|
|
187
199
|
|
|
188
|
-
const log = debug
|
|
189
|
-
? (message: string) => console.log(message)
|
|
190
|
-
: () => {};
|
|
200
|
+
const log = debug ? (message: string) => console.log(message) : () => {};
|
|
191
201
|
|
|
192
202
|
return async function documentCacheMiddleware(
|
|
193
203
|
ctx: MiddlewareContext<TEnv>,
|
|
@@ -195,13 +205,24 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
195
205
|
): Promise<Response> {
|
|
196
206
|
const url = ctx.url;
|
|
197
207
|
|
|
208
|
+
// Use the original request URL for _rsc* param detection and cache key
|
|
209
|
+
// differentiation. ctx.url is stripped of _rsc* params by the middleware
|
|
210
|
+
// pipeline (stripInternalParams), so _rsc_partial, _rsc_segments, etc.
|
|
211
|
+
// are not visible on ctx.url in production.
|
|
212
|
+
const rawUrl = new URL(ctx.request.url);
|
|
213
|
+
|
|
214
|
+
// Only cache GET requests — mutations and other methods must not be cached
|
|
215
|
+
if (ctx.request.method !== "GET") {
|
|
216
|
+
return next();
|
|
217
|
+
}
|
|
218
|
+
|
|
198
219
|
// Skip RSC action requests (mutations shouldn't be cached)
|
|
199
|
-
if (
|
|
220
|
+
if (rawUrl.searchParams.has("_rsc_action")) {
|
|
200
221
|
return next();
|
|
201
222
|
}
|
|
202
223
|
|
|
203
224
|
// Skip loader requests (have their own caching)
|
|
204
|
-
if (
|
|
225
|
+
if (rawUrl.searchParams.has("_rsc_loader")) {
|
|
205
226
|
return next();
|
|
206
227
|
}
|
|
207
228
|
|
|
@@ -227,21 +248,38 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
227
248
|
return next();
|
|
228
249
|
}
|
|
229
250
|
|
|
230
|
-
// Determine request type for cache key differentiation
|
|
231
|
-
|
|
232
|
-
|
|
251
|
+
// Determine request type for cache key differentiation.
|
|
252
|
+
// Uses rawUrl for _rsc* param checks and mayNeedSSR for Accept-based
|
|
253
|
+
// detection. Full-document RSC fetches must not share the HTML cache slot.
|
|
254
|
+
const isPartial = rawUrl.searchParams.has("_rsc_partial");
|
|
255
|
+
const isRscRequest = !mayNeedSSR(ctx.request, rawUrl);
|
|
256
|
+
const typeLabel = isRscRequest ? "RSC" : "HTML";
|
|
233
257
|
|
|
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}`;
|
|
258
|
+
// Track whether next() has been called so the catch block knows
|
|
259
|
+
// whether it is safe to fall through to the handler.
|
|
260
|
+
let handlerCalled = false;
|
|
243
261
|
|
|
244
262
|
try {
|
|
263
|
+
// Generate cache key inside try so a throwing keyGenerator degrades
|
|
264
|
+
// gracefully to the origin handler instead of rejecting the request.
|
|
265
|
+
// This is a deliberate fail-open-to-origin policy: the fallback is
|
|
266
|
+
// "serve uncached from origin", not "use a different cache key".
|
|
267
|
+
const clientSegments = rawUrl.searchParams.get("_rsc_segments") || "";
|
|
268
|
+
const segmentHash =
|
|
269
|
+
isPartial && clientSegments ? `:${hashSegmentIds(clientSegments)}` : "";
|
|
270
|
+
const typeSuffix = isRscRequest ? ":rsc" : ":html";
|
|
271
|
+
|
|
272
|
+
let searchSuffix = "";
|
|
273
|
+
if (!keyGenerator) {
|
|
274
|
+
const sorted = sortedSearchString(url.searchParams);
|
|
275
|
+
if (sorted) {
|
|
276
|
+
searchSuffix = `?${sorted}`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const cacheKey = keyGenerator
|
|
281
|
+
? keyGenerator(url) + segmentHash + typeSuffix
|
|
282
|
+
: `${url.pathname}${searchSuffix}${segmentHash}${typeSuffix}`;
|
|
245
283
|
// 1. Check cache
|
|
246
284
|
const cached = await store.getResponse(cacheKey);
|
|
247
285
|
|
|
@@ -249,79 +287,75 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
249
287
|
if (!cached.shouldRevalidate) {
|
|
250
288
|
// Fresh hit - return immediately
|
|
251
289
|
log(`[DocumentCache] HIT ${typeLabel}: ${url.pathname}`);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
response,
|
|
257
|
-
requestCtx._onResponseCallbacks,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
return response;
|
|
290
|
+
return drainOnResponseCallbacks(
|
|
291
|
+
addCacheStatusHeader(cached.response, "HIT"),
|
|
292
|
+
requestCtx,
|
|
293
|
+
);
|
|
261
294
|
}
|
|
262
295
|
|
|
263
296
|
// 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);
|
|
297
|
+
log(
|
|
298
|
+
`[DocumentCache] STALE ${typeLabel}: ${url.pathname} (revalidating)`,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
runBackground(requestCtx, async () => {
|
|
302
|
+
try {
|
|
303
|
+
const fresh = await next();
|
|
304
|
+
const directives = shouldCacheResponse(fresh);
|
|
305
|
+
|
|
306
|
+
if (directives) {
|
|
307
|
+
await store.putResponse!(
|
|
308
|
+
cacheKey,
|
|
309
|
+
fresh,
|
|
310
|
+
directives.sMaxAge!,
|
|
311
|
+
directives.staleWhileRevalidate,
|
|
312
|
+
);
|
|
313
|
+
log(`[DocumentCache] REVALIDATED ${typeLabel}: ${url.pathname}`);
|
|
283
314
|
}
|
|
284
|
-
})
|
|
285
|
-
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.error(`[DocumentCache] Revalidation failed:`, error);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
286
319
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
response,
|
|
292
|
-
requestCtx._onResponseCallbacks,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
return response;
|
|
320
|
+
return drainOnResponseCallbacks(
|
|
321
|
+
addCacheStatusHeader(cached.response, "STALE"),
|
|
322
|
+
requestCtx,
|
|
323
|
+
);
|
|
296
324
|
}
|
|
297
325
|
|
|
298
326
|
// 2. Cache miss - run handler
|
|
327
|
+
handlerCalled = true;
|
|
299
328
|
const originalResponse = await next();
|
|
300
329
|
|
|
301
330
|
// 3. Cache if response has appropriate headers
|
|
302
331
|
const directives = shouldCacheResponse(originalResponse);
|
|
303
332
|
|
|
304
333
|
if (directives) {
|
|
305
|
-
log(
|
|
334
|
+
log(
|
|
335
|
+
`[DocumentCache] MISS ${typeLabel}: ${url.pathname} (caching with s-maxage=${directives.sMaxAge})`,
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// If the response has no body (e.g., 200 with empty body), skip caching
|
|
339
|
+
if (!originalResponse.body) {
|
|
340
|
+
return originalResponse;
|
|
341
|
+
}
|
|
306
342
|
|
|
307
343
|
// Tee the body so we can return one stream and cache the other
|
|
308
|
-
const [returnStream, cacheStream] = originalResponse.body
|
|
344
|
+
const [returnStream, cacheStream] = originalResponse.body.tee();
|
|
309
345
|
|
|
310
346
|
// Clone response for caching (non-blocking)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
});
|
|
324
|
-
}
|
|
347
|
+
runBackground(requestCtx, async () => {
|
|
348
|
+
try {
|
|
349
|
+
await store.putResponse!(
|
|
350
|
+
cacheKey,
|
|
351
|
+
new Response(cacheStream, originalResponse),
|
|
352
|
+
directives.sMaxAge!,
|
|
353
|
+
directives.staleWhileRevalidate,
|
|
354
|
+
);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.error(`[DocumentCache] Cache write failed:`, error);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
325
359
|
|
|
326
360
|
return addCacheStatusHeader(
|
|
327
361
|
new Response(returnStream, originalResponse),
|
|
@@ -333,7 +367,12 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
333
367
|
return originalResponse;
|
|
334
368
|
} catch (error) {
|
|
335
369
|
console.error(`[DocumentCache] Error:`, error);
|
|
336
|
-
|
|
370
|
+
if (handlerCalled) {
|
|
371
|
+
// Post-handler failure (e.g. body.tee()): do not call next() again
|
|
372
|
+
// as that would re-run handler side effects.
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
// Pre-handler failure (cache lookup): degrade gracefully to origin
|
|
337
376
|
return next();
|
|
338
377
|
}
|
|
339
378
|
};
|
|
@@ -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";
|