@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847
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 +1531 -212
- package/dist/vite/index.js +3995 -2489
- package/package.json +57 -52
- 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 +328 -70
- 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 +62 -15
- package/skills/loader/SKILL.md +368 -42
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +14 -10
- package/skills/parallel/SKILL.md +137 -1
- package/skills/prerender/SKILL.md +366 -28
- package/skills/rango/SKILL.md +85 -21
- package/skills/response-routes/SKILL.md +136 -83
- package/skills/route/SKILL.md +195 -21
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +240 -102
- 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 +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 +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +266 -558
- package/src/browser/navigation-client.ts +132 -75
- 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 +303 -309
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +144 -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 +188 -57
- package/src/browser/scroll-restoration.ts +117 -44
- package/src/browser/segment-reconciler.ts +221 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +488 -606
- 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 +63 -21
- package/src/build/generate-route-types.ts +36 -1038
- package/src/build/index.ts +2 -5
- package/src/build/route-trie.ts +38 -12
- 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 +342 -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 +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 +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 +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +12 -7
- 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 +104 -40
- package/src/index.ts +122 -67
- package/src/internal-debug.ts +9 -3
- package/src/loader.rsc.ts +18 -93
- package/src/loader.ts +26 -9
- 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 +121 -17
- package/src/prerender.ts +325 -20
- package/src/reverse.ts +144 -124
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +959 -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 -1450
- package/src/route-map-builder.ts +87 -133
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +41 -6
- 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 +324 -116
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +179 -133
- package/src/router/logging.ts +112 -6
- package/src/router/manifest.ts +58 -19
- package/src/router/match-api.ts +89 -88
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +86 -89
- package/src/router/match-middleware/cache-lookup.ts +295 -49
- package/src/router/match-middleware/cache-store.ts +56 -13
- package/src/router/match-middleware/intercept-resolution.ts +45 -22
- package/src/router/match-middleware/segment-resolution.ts +20 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +44 -21
- package/src/router/metrics.ts +240 -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 +169 -31
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +105 -14
- 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 +677 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1296 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1354
- 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 +15 -9
- package/src/router.ts +642 -2366
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +639 -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 +66 -54
- package/src/segment-system.tsx +165 -17
- package/src/server/context.ts +237 -54
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +438 -71
- package/src/server.ts +26 -164
- package/src/ssr/index.tsx +101 -31
- package/src/static-handler.ts +22 -4
- 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 +773 -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 +150 -0
- package/src/types.ts +1 -1795
- 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 -1323
- 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 -2259
- 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 -47
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +8 -43
- 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 +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 +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +181 -9
- 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/dist/vite/index.named-routes.gen.ts +0 -103
- 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/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/cache/cache-scope.ts
CHANGED
|
@@ -2,59 +2,65 @@
|
|
|
2
2
|
* CacheScope - Runtime cache scope for iterator-based caching
|
|
3
3
|
*
|
|
4
4
|
* Each cache() boundary in the route tree creates a new CacheScope.
|
|
5
|
-
* The scope owns: config,
|
|
5
|
+
* The scope owns: config, key management, and storage operations.
|
|
6
|
+
*
|
|
7
|
+
* Serialization is delegated to segment-codec.ts.
|
|
8
|
+
* Handle data capture/restore is delegated to handle-snapshot.ts.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
|
-
/// <reference types="@vitejs/plugin-rsc/types" />
|
|
9
|
-
|
|
10
11
|
import type { PartialCacheOptions } from "../types.js";
|
|
11
12
|
import type { ResolvedSegment } from "../types.js";
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
SegmentHandleData,
|
|
15
|
-
CachedEntryData,
|
|
16
|
-
SerializedSegmentData,
|
|
17
|
-
} from "./types.js";
|
|
18
|
-
import { getRequestContext } from "../server/request-context.js";
|
|
13
|
+
import type { SegmentCacheStore, CachedEntryData } from "./types.js";
|
|
14
|
+
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
19
15
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from "
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
getRequestContext,
|
|
17
|
+
_getRequestContext,
|
|
18
|
+
} from "../server/request-context.js";
|
|
19
|
+
import { serializeSegments, deserializeSegments } from "./segment-codec.js";
|
|
20
|
+
import { captureHandles, restoreHandles } from "./handle-snapshot.js";
|
|
21
|
+
import { sortedSearchString, sortedRouteParams } from "./cache-key-utils.js";
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_ROUTE_TTL,
|
|
24
|
+
resolveCacheKey,
|
|
25
|
+
resolveCacheStore,
|
|
26
|
+
} from "./cache-policy.js";
|
|
27
|
+
|
|
28
|
+
function debugCacheLog(message: string): void {
|
|
29
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
30
|
+
console.log(message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
31
33
|
|
|
32
34
|
// ============================================================================
|
|
33
|
-
//
|
|
35
|
+
// Key Generation (internal)
|
|
34
36
|
// ============================================================================
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
|
-
* Generate cache key base from pathname and params.
|
|
38
|
-
*
|
|
39
|
+
* Generate cache key base from host, pathname, route params, and search params.
|
|
40
|
+
* Host is included to prevent cross-host cache collisions on shared stores.
|
|
41
|
+
* Route params and search params are sorted alphabetically for deterministic keys.
|
|
42
|
+
* Internal _rsc* and __* query params are excluded.
|
|
39
43
|
* @internal
|
|
40
44
|
*/
|
|
41
45
|
function getCacheKeyBase(
|
|
46
|
+
host: string,
|
|
42
47
|
pathname: string,
|
|
43
|
-
params?: Record<string, string
|
|
48
|
+
params?: Record<string, string>,
|
|
49
|
+
searchParams?: URLSearchParams,
|
|
44
50
|
): string {
|
|
45
|
-
const paramStr = params
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return paramStr ? `${pathname}:${paramStr}` : pathname;
|
|
51
|
+
const paramStr = sortedRouteParams(params);
|
|
52
|
+
const searchStr = searchParams ? sortedSearchString(searchParams) : "";
|
|
53
|
+
|
|
54
|
+
let key = `${host}${pathname}`;
|
|
55
|
+
if (paramStr) key += `:${paramStr}`;
|
|
56
|
+
if (searchStr) key += `?${searchStr}`;
|
|
57
|
+
return key;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/**
|
|
56
61
|
* Generate default cache key for a route request.
|
|
57
|
-
*
|
|
62
|
+
* Includes pathname, route params, and user-facing search params for
|
|
63
|
+
* correct scoping. Internal _rsc* params are excluded.
|
|
58
64
|
* Includes request type prefix since they produce different segment sets:
|
|
59
65
|
* - doc: document requests (full page load)
|
|
60
66
|
* - partial: navigation requests (client-side navigation)
|
|
@@ -64,201 +70,17 @@ function getCacheKeyBase(
|
|
|
64
70
|
function getDefaultRouteCacheKey(
|
|
65
71
|
pathname: string,
|
|
66
72
|
params?: Record<string, string>,
|
|
67
|
-
isIntercept?: boolean
|
|
73
|
+
isIntercept?: boolean,
|
|
68
74
|
): string {
|
|
69
75
|
const ctx = getRequestContext();
|
|
70
|
-
const isPartial = ctx?.
|
|
76
|
+
const isPartial = ctx?.originalUrl?.searchParams.has("_rsc_partial") ?? false;
|
|
77
|
+
const searchParams = ctx?.url.searchParams;
|
|
78
|
+
const host = ctx?.url.host ?? "localhost";
|
|
71
79
|
|
|
72
80
|
// Intercept navigations get their own cache namespace
|
|
73
81
|
const prefix = isIntercept ? "intercept" : isPartial ? "partial" : "doc";
|
|
74
82
|
|
|
75
|
-
return `${prefix}:${getCacheKeyBase(pathname, params)}`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Convert a ReadableStream to a string.
|
|
80
|
-
* @internal
|
|
81
|
-
*/
|
|
82
|
-
async function streamToString(
|
|
83
|
-
stream: ReadableStream<Uint8Array>
|
|
84
|
-
): Promise<string> {
|
|
85
|
-
const reader = stream.getReader();
|
|
86
|
-
const decoder = new TextDecoder();
|
|
87
|
-
let result = "";
|
|
88
|
-
|
|
89
|
-
while (true) {
|
|
90
|
-
const { done, value } = await reader.read();
|
|
91
|
-
if (done) break;
|
|
92
|
-
result += decoder.decode(value, { stream: true });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
result += decoder.decode(); // flush
|
|
96
|
-
return result;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Convert a string to a ReadableStream.
|
|
101
|
-
* @internal
|
|
102
|
-
*/
|
|
103
|
-
function stringToStream(str: string): ReadableStream<Uint8Array> {
|
|
104
|
-
const encoder = new TextEncoder();
|
|
105
|
-
const uint8 = encoder.encode(str);
|
|
106
|
-
|
|
107
|
-
return new ReadableStream({
|
|
108
|
-
start(controller) {
|
|
109
|
-
controller.enqueue(uint8);
|
|
110
|
-
controller.close();
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* RSC-serialize a value using React Server Components stream.
|
|
117
|
-
* Used for serializing loaderData, layout, loading components etc.
|
|
118
|
-
* @internal
|
|
119
|
-
*/
|
|
120
|
-
async function rscSerialize(value: unknown): Promise<string | undefined> {
|
|
121
|
-
if (value === undefined || value === null) return undefined;
|
|
122
|
-
|
|
123
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
124
|
-
const stream = renderToReadableStream(value, { temporaryReferences });
|
|
125
|
-
return streamToString(stream);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* RSC-deserialize a value from a stored string.
|
|
130
|
-
* @internal
|
|
131
|
-
*/
|
|
132
|
-
async function rscDeserialize<T>(
|
|
133
|
-
encoded: string | undefined
|
|
134
|
-
): Promise<T | undefined> {
|
|
135
|
-
if (!encoded) return undefined;
|
|
136
|
-
|
|
137
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
138
|
-
const stream = stringToStream(encoded);
|
|
139
|
-
return createFromReadableStream<T>(stream, { temporaryReferences });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Serialize segments for storage.
|
|
144
|
-
* Each segment's component, layout, loading, and loaderData are RSC-serialized.
|
|
145
|
-
* Metadata is preserved as-is.
|
|
146
|
-
*/
|
|
147
|
-
export async function serializeSegments(
|
|
148
|
-
segments: ResolvedSegment[]
|
|
149
|
-
): Promise<SerializedSegmentData[]> {
|
|
150
|
-
const serialized: SerializedSegmentData[] = [];
|
|
151
|
-
|
|
152
|
-
for (const segment of segments) {
|
|
153
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
154
|
-
|
|
155
|
-
// Await component if it's a Promise (intercepts with loading keep component as Promise)
|
|
156
|
-
const componentResolved =
|
|
157
|
-
segment.component instanceof Promise
|
|
158
|
-
? await segment.component
|
|
159
|
-
: segment.component;
|
|
160
|
-
|
|
161
|
-
// Serialize the component to RSC stream
|
|
162
|
-
const stream = renderToReadableStream(componentResolved, {
|
|
163
|
-
temporaryReferences,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// Convert stream to string
|
|
167
|
-
const encoded = await streamToString(stream);
|
|
168
|
-
|
|
169
|
-
// RSC-serialize layout if present (ReactNode)
|
|
170
|
-
const encodedLayout = segment.layout
|
|
171
|
-
? await rscSerialize(segment.layout)
|
|
172
|
-
: undefined;
|
|
173
|
-
|
|
174
|
-
// RSC-serialize loading if present (ReactNode) - preserves tree structure
|
|
175
|
-
// Use "null" string to distinguish explicit null from undefined
|
|
176
|
-
const encodedLoading =
|
|
177
|
-
segment.loading !== undefined
|
|
178
|
-
? segment.loading === null
|
|
179
|
-
? "null"
|
|
180
|
-
: await rscSerialize(segment.loading)
|
|
181
|
-
: undefined;
|
|
182
|
-
|
|
183
|
-
// Await and RSC-serialize loaderData if present
|
|
184
|
-
const loaderDataResolved =
|
|
185
|
-
segment.loaderData instanceof Promise
|
|
186
|
-
? await segment.loaderData
|
|
187
|
-
: segment.loaderData;
|
|
188
|
-
const encodedLoaderData = await rscSerialize(loaderDataResolved);
|
|
189
|
-
|
|
190
|
-
// Await and RSC-serialize loaderDataPromise if present
|
|
191
|
-
const loaderDataPromiseResolved =
|
|
192
|
-
segment.loaderDataPromise instanceof Promise
|
|
193
|
-
? await segment.loaderDataPromise
|
|
194
|
-
: segment.loaderDataPromise;
|
|
195
|
-
const encodedLoaderDataPromise = await rscSerialize(
|
|
196
|
-
loaderDataPromiseResolved
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
serialized.push({
|
|
200
|
-
encoded,
|
|
201
|
-
encodedLayout,
|
|
202
|
-
encodedLoading,
|
|
203
|
-
encodedLoaderData,
|
|
204
|
-
encodedLoaderDataPromise,
|
|
205
|
-
metadata: {
|
|
206
|
-
id: segment.id,
|
|
207
|
-
type: segment.type,
|
|
208
|
-
namespace: segment.namespace,
|
|
209
|
-
index: segment.index,
|
|
210
|
-
params: segment.params,
|
|
211
|
-
slot: segment.slot,
|
|
212
|
-
belongsToRoute: segment.belongsToRoute,
|
|
213
|
-
layoutName: segment.layoutName,
|
|
214
|
-
parallelName: segment.parallelName,
|
|
215
|
-
loaderId: segment.loaderId,
|
|
216
|
-
loaderIds: segment.loaderIds,
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return serialized;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Deserialize segments from storage.
|
|
226
|
-
* Reconstructs ResolvedSegment objects from RSC-serialized data.
|
|
227
|
-
*/
|
|
228
|
-
export async function deserializeSegments(
|
|
229
|
-
data: SerializedSegmentData[]
|
|
230
|
-
): Promise<ResolvedSegment[]> {
|
|
231
|
-
const segments: ResolvedSegment[] = [];
|
|
232
|
-
|
|
233
|
-
for (const item of data) {
|
|
234
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
235
|
-
|
|
236
|
-
// Revive the component from cached string
|
|
237
|
-
const stream = stringToStream(item.encoded);
|
|
238
|
-
const component = await createFromReadableStream(stream, {
|
|
239
|
-
temporaryReferences,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// RSC-deserialize layout, loaderData, loaderDataPromise in parallel
|
|
243
|
-
const [layout, loaderData, loaderDataPromise, loadingData] =
|
|
244
|
-
await Promise.all([
|
|
245
|
-
rscDeserialize(item.encodedLayout),
|
|
246
|
-
rscDeserialize(item.encodedLoaderData),
|
|
247
|
-
rscDeserialize(item.encodedLoaderDataPromise),
|
|
248
|
-
rscDeserialize(item.encodedLoading),
|
|
249
|
-
]);
|
|
250
|
-
|
|
251
|
-
segments.push({
|
|
252
|
-
...item.metadata,
|
|
253
|
-
component: await component,
|
|
254
|
-
layout,
|
|
255
|
-
loading: loadingData,
|
|
256
|
-
loaderData,
|
|
257
|
-
loaderDataPromise,
|
|
258
|
-
} as ResolvedSegment);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return segments;
|
|
83
|
+
return `${prefix}:${getCacheKeyBase(host, pathname, params, searchParams)}`;
|
|
262
84
|
}
|
|
263
85
|
|
|
264
86
|
// ============================================================================
|
|
@@ -269,7 +91,8 @@ export async function deserializeSegments(
|
|
|
269
91
|
* CacheScope represents a cache boundary in the route tree.
|
|
270
92
|
*
|
|
271
93
|
* When withCache encounters an entry with cache config, it creates
|
|
272
|
-
* a new CacheScope. The scope owns
|
|
94
|
+
* a new CacheScope. The scope owns key management, TTL resolution,
|
|
95
|
+
* and storage operations. Serialization is handled by segment-codec.ts.
|
|
273
96
|
*
|
|
274
97
|
* Store resolution priority:
|
|
275
98
|
* 1. Explicit store in cache() options
|
|
@@ -289,7 +112,7 @@ export class CacheScope {
|
|
|
289
112
|
|
|
290
113
|
constructor(
|
|
291
114
|
config: PartialCacheOptions | false,
|
|
292
|
-
parent: CacheScope | null = null
|
|
115
|
+
parent: CacheScope | null = null,
|
|
293
116
|
) {
|
|
294
117
|
this.config = config;
|
|
295
118
|
this.parent = parent;
|
|
@@ -322,7 +145,7 @@ export class CacheScope {
|
|
|
322
145
|
}
|
|
323
146
|
|
|
324
147
|
// Hardcoded fallback
|
|
325
|
-
return
|
|
148
|
+
return DEFAULT_ROUTE_TTL;
|
|
326
149
|
}
|
|
327
150
|
|
|
328
151
|
/**
|
|
@@ -346,65 +169,22 @@ export class CacheScope {
|
|
|
346
169
|
* 1. Explicit store from cache() options
|
|
347
170
|
* 2. App-level store from request context
|
|
348
171
|
*/
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (this.explicitStore) {
|
|
352
|
-
return this.explicitStore;
|
|
353
|
-
}
|
|
354
|
-
// Fall back to app-level store from request context
|
|
355
|
-
const ctx = getRequestContext();
|
|
356
|
-
return ctx?._cacheStore ?? null;
|
|
172
|
+
getStore(): SegmentCacheStore | null {
|
|
173
|
+
return resolveCacheStore(this.explicitStore);
|
|
357
174
|
}
|
|
358
175
|
|
|
359
176
|
/**
|
|
360
|
-
* Resolve the cache key using
|
|
361
|
-
*
|
|
362
|
-
* Resolution priority:
|
|
363
|
-
* 1. Route-level `key` function (full override)
|
|
364
|
-
* 2. Store-level `keyGenerator` (modifies default key)
|
|
365
|
-
* 3. Default key generation (prefix:pathname:params)
|
|
366
|
-
*
|
|
177
|
+
* Resolve the cache key using the shared 3-tier priority.
|
|
367
178
|
* @internal
|
|
368
179
|
*/
|
|
369
180
|
private async resolveKey(
|
|
370
181
|
pathname: string,
|
|
371
182
|
params: Record<string, string>,
|
|
372
|
-
isIntercept?: boolean
|
|
183
|
+
isIntercept?: boolean,
|
|
373
184
|
): Promise<string> {
|
|
374
|
-
const requestCtx = getRequestContext();
|
|
375
|
-
if (!requestCtx) {
|
|
376
|
-
// Fallback to default key if no request context
|
|
377
|
-
return getDefaultRouteCacheKey(pathname, params, isIntercept);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Priority 1: Route-level key function (full override)
|
|
381
|
-
if (this.config !== false && this.config.key) {
|
|
382
|
-
try {
|
|
383
|
-
const customKey = await this.config.key(requestCtx);
|
|
384
|
-
return customKey;
|
|
385
|
-
} catch (error) {
|
|
386
|
-
console.error(`[CacheScope] Custom key function failed, using default:`, error);
|
|
387
|
-
return getDefaultRouteCacheKey(pathname, params, isIntercept);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Generate default key
|
|
392
185
|
const defaultKey = getDefaultRouteCacheKey(pathname, params, isIntercept);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const store = this.getStore();
|
|
396
|
-
if (store?.keyGenerator) {
|
|
397
|
-
try {
|
|
398
|
-
const modifiedKey = await store.keyGenerator(requestCtx, defaultKey);
|
|
399
|
-
return modifiedKey;
|
|
400
|
-
} catch (error) {
|
|
401
|
-
console.error(`[CacheScope] Store keyGenerator failed, using default:`, error);
|
|
402
|
-
return defaultKey;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Priority 3: Default key
|
|
407
|
-
return defaultKey;
|
|
186
|
+
const keyFn = this.config !== false ? this.config.key : undefined;
|
|
187
|
+
return resolveCacheKey(keyFn, this.getStore(), defaultKey, "CacheScope");
|
|
408
188
|
}
|
|
409
189
|
|
|
410
190
|
/**
|
|
@@ -418,13 +198,34 @@ export class CacheScope {
|
|
|
418
198
|
async lookupRoute(
|
|
419
199
|
pathname: string,
|
|
420
200
|
params: Record<string, string>,
|
|
421
|
-
isIntercept?: boolean
|
|
201
|
+
isIntercept?: boolean,
|
|
422
202
|
): Promise<{
|
|
423
203
|
segments: ResolvedSegment[];
|
|
424
204
|
shouldRevalidate: boolean;
|
|
425
205
|
} | null> {
|
|
426
206
|
if (!this.enabled) return null;
|
|
427
207
|
|
|
208
|
+
// Evaluate condition — skip cache read when condition returns false
|
|
209
|
+
if (this.config !== false && this.config.condition) {
|
|
210
|
+
const requestCtx = getRequestContext();
|
|
211
|
+
if (requestCtx) {
|
|
212
|
+
try {
|
|
213
|
+
if (!this.config.condition(requestCtx)) {
|
|
214
|
+
debugCacheLog(
|
|
215
|
+
`[CacheScope] condition returned false, skipping cache read`,
|
|
216
|
+
);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(
|
|
221
|
+
`[CacheScope] condition function threw, skipping cache read:`,
|
|
222
|
+
error,
|
|
223
|
+
);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
428
229
|
const store = this.getStore();
|
|
429
230
|
if (!store) return null;
|
|
430
231
|
|
|
@@ -435,7 +236,7 @@ export class CacheScope {
|
|
|
435
236
|
const result = await store.get(key);
|
|
436
237
|
|
|
437
238
|
if (!result) {
|
|
438
|
-
|
|
239
|
+
debugCacheLog(`[CacheScope] MISS: ${key}`);
|
|
439
240
|
return null;
|
|
440
241
|
}
|
|
441
242
|
|
|
@@ -445,21 +246,19 @@ export class CacheScope {
|
|
|
445
246
|
const segments = await deserializeSegments(cached.segments);
|
|
446
247
|
|
|
447
248
|
// Replay handle data
|
|
448
|
-
const handleStore =
|
|
249
|
+
const handleStore = _getRequestContext()?._handleStore;
|
|
449
250
|
if (handleStore) {
|
|
450
|
-
|
|
451
|
-
if (Object.keys(segHandles).length > 0) {
|
|
452
|
-
handleStore.replaySegmentData(segId, segHandles);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
251
|
+
restoreHandles(cached.handles, handleStore);
|
|
455
252
|
}
|
|
456
253
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
254
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
255
|
+
const segmentTypes = segments.map((s) =>
|
|
256
|
+
s.type === "parallel" ? s.slot : s.type,
|
|
257
|
+
);
|
|
258
|
+
debugCacheLog(
|
|
259
|
+
`[CacheScope] ${shouldRevalidate ? "STALE" : "HIT"}: ${key} (${segmentTypes.join(", ")})`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
463
262
|
|
|
464
263
|
return { segments, shouldRevalidate };
|
|
465
264
|
} catch (error) {
|
|
@@ -482,10 +281,31 @@ export class CacheScope {
|
|
|
482
281
|
pathname: string,
|
|
483
282
|
params: Record<string, string>,
|
|
484
283
|
segments: ResolvedSegment[],
|
|
485
|
-
isIntercept?: boolean
|
|
284
|
+
isIntercept?: boolean,
|
|
486
285
|
): Promise<void> {
|
|
487
286
|
if (!this.enabled || segments.length === 0) return;
|
|
488
287
|
|
|
288
|
+
// Evaluate condition — skip cache write when condition returns false
|
|
289
|
+
if (this.config !== false && this.config.condition) {
|
|
290
|
+
const conditionCtx = getRequestContext();
|
|
291
|
+
if (conditionCtx) {
|
|
292
|
+
try {
|
|
293
|
+
if (!this.config.condition(conditionCtx)) {
|
|
294
|
+
debugCacheLog(
|
|
295
|
+
`[CacheScope] condition returned false, skipping cache write`,
|
|
296
|
+
);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error(
|
|
301
|
+
`[CacheScope] condition function threw, skipping cache write:`,
|
|
302
|
+
error,
|
|
303
|
+
);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
489
309
|
const store = this.getStore();
|
|
490
310
|
if (!store) return;
|
|
491
311
|
|
|
@@ -506,7 +326,7 @@ export class CacheScope {
|
|
|
506
326
|
const key = await this.resolveKey(pathname, params, isIntercept);
|
|
507
327
|
|
|
508
328
|
// Check if this is a partial request (navigation) vs document request
|
|
509
|
-
const isPartial = requestCtx.
|
|
329
|
+
const isPartial = requestCtx.originalUrl.searchParams.has("_rsc_partial");
|
|
510
330
|
|
|
511
331
|
requestCtx.waitUntil(async () => {
|
|
512
332
|
await handleStore.settled;
|
|
@@ -515,16 +335,13 @@ export class CacheScope {
|
|
|
515
335
|
// For partial requests: null components are expected (client already has them)
|
|
516
336
|
if (!isPartial) {
|
|
517
337
|
const hasAllComponents = nonLoaderSegments.every(
|
|
518
|
-
(s) => s.component !== null
|
|
338
|
+
(s) => s.component !== null,
|
|
519
339
|
);
|
|
520
340
|
if (!hasAllComponents) return;
|
|
521
341
|
}
|
|
522
342
|
|
|
523
343
|
// Collect handle data for non-loader segments only
|
|
524
|
-
const handles
|
|
525
|
-
for (const seg of nonLoaderSegments) {
|
|
526
|
-
handles[seg.id] = handleStore.getDataForSegment(seg.id);
|
|
527
|
-
}
|
|
344
|
+
const handles = captureHandles(nonLoaderSegments, handleStore);
|
|
528
345
|
|
|
529
346
|
try {
|
|
530
347
|
// Serialize non-loader segments only
|
|
@@ -538,12 +355,14 @@ export class CacheScope {
|
|
|
538
355
|
|
|
539
356
|
await store.set(key, data, ttl, swr);
|
|
540
357
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
358
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
359
|
+
const segmentTypes = nonLoaderSegments.map((s) =>
|
|
360
|
+
s.type === "parallel" ? s.slot : s.type,
|
|
361
|
+
);
|
|
362
|
+
debugCacheLog(
|
|
363
|
+
`[CacheScope] Cached: ${key} (${segmentTypes.join(", ")}) ttl=${ttl}s [loaders excluded]`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
547
366
|
} catch (error) {
|
|
548
367
|
console.error(`[CacheScope] Failed to cache ${key}:`, error);
|
|
549
368
|
}
|
|
@@ -556,7 +375,7 @@ export class CacheScope {
|
|
|
556
375
|
*/
|
|
557
376
|
export function createCacheScope(
|
|
558
377
|
config: { options: PartialCacheOptions | false } | undefined,
|
|
559
|
-
parent: CacheScope | null = null
|
|
378
|
+
parent: CacheScope | null = null,
|
|
560
379
|
): CacheScope | null {
|
|
561
380
|
if (!config) return parent; // No config, inherit parent
|
|
562
381
|
return new CacheScope(config.options, parent);
|