@rangojs/router 0.0.0-experimental.0f44aca1

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.
Files changed (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,382 @@
1
+ /**
2
+ * CacheScope - Runtime cache scope for iterator-based caching
3
+ *
4
+ * Each cache() boundary in the route tree creates a new CacheScope.
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.
9
+ */
10
+
11
+ import type { PartialCacheOptions } from "../types.js";
12
+ import type { ResolvedSegment } from "../types.js";
13
+ import type { SegmentCacheStore, CachedEntryData } from "./types.js";
14
+ import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
15
+ import {
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
+ }
33
+
34
+ // ============================================================================
35
+ // Key Generation (internal)
36
+ // ============================================================================
37
+
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.
43
+ * @internal
44
+ */
45
+ function getCacheKeyBase(
46
+ host: string,
47
+ pathname: string,
48
+ params?: Record<string, string>,
49
+ searchParams?: URLSearchParams,
50
+ ): string {
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;
58
+ }
59
+
60
+ /**
61
+ * Generate default cache key for a route request.
62
+ * Includes pathname, route params, and user-facing search params for
63
+ * correct scoping. Internal _rsc* params are excluded.
64
+ * Includes request type prefix since they produce different segment sets:
65
+ * - doc: document requests (full page load)
66
+ * - partial: navigation requests (client-side navigation)
67
+ * - intercept: intercept navigation (modal/overlay routes)
68
+ * @internal
69
+ */
70
+ function getDefaultRouteCacheKey(
71
+ pathname: string,
72
+ params?: Record<string, string>,
73
+ isIntercept?: boolean,
74
+ ): string {
75
+ const ctx = getRequestContext();
76
+ const isPartial = ctx?.url.searchParams.has("_rsc_partial") ?? false;
77
+ const searchParams = ctx?.url.searchParams;
78
+ const host = ctx?.url.host ?? "localhost";
79
+
80
+ // Intercept navigations get their own cache namespace
81
+ const prefix = isIntercept ? "intercept" : isPartial ? "partial" : "doc";
82
+
83
+ return `${prefix}:${getCacheKeyBase(host, pathname, params, searchParams)}`;
84
+ }
85
+
86
+ // ============================================================================
87
+ // CacheScope
88
+ // ============================================================================
89
+
90
+ /**
91
+ * CacheScope represents a cache boundary in the route tree.
92
+ *
93
+ * When withCache encounters an entry with cache config, it creates
94
+ * a new CacheScope. The scope owns key management, TTL resolution,
95
+ * and storage operations. Serialization is handled by segment-codec.ts.
96
+ *
97
+ * Store resolution priority:
98
+ * 1. Explicit store in cache() options
99
+ * 2. App-level store from handler config
100
+ *
101
+ * TTL resolution priority:
102
+ * 1. Explicit value in cache() options
103
+ * 2. Explicit store's defaults (if store specified)
104
+ * 3. App-level store's defaults
105
+ * 4. Hardcoded fallback (60 seconds)
106
+ */
107
+ export class CacheScope {
108
+ readonly config: PartialCacheOptions | false;
109
+ readonly parent: CacheScope | null;
110
+ /** Explicit store from cache() options, if specified */
111
+ private readonly explicitStore: SegmentCacheStore | undefined;
112
+
113
+ constructor(
114
+ config: PartialCacheOptions | false,
115
+ parent: CacheScope | null = null,
116
+ ) {
117
+ this.config = config;
118
+ this.parent = parent;
119
+ // Extract and store explicit store reference
120
+ this.explicitStore = config !== false ? config.store : undefined;
121
+ }
122
+
123
+ /**
124
+ * Whether caching is enabled for this scope
125
+ */
126
+ get enabled(): boolean {
127
+ return this.config !== false;
128
+ }
129
+
130
+ /**
131
+ * Get effective TTL from config or store defaults
132
+ */
133
+ get ttl(): number {
134
+ if (this.config === false) return 0;
135
+
136
+ // Explicit TTL in cache() options
137
+ if (this.config.ttl !== undefined) {
138
+ return this.config.ttl;
139
+ }
140
+
141
+ // Fall back to store defaults (explicit store first, then app-level)
142
+ const store = this.getStore();
143
+ if (store?.defaults?.ttl !== undefined) {
144
+ return store.defaults.ttl;
145
+ }
146
+
147
+ // Hardcoded fallback
148
+ return DEFAULT_ROUTE_TTL;
149
+ }
150
+
151
+ /**
152
+ * Get SWR window from config or store defaults
153
+ */
154
+ get swr(): number | undefined {
155
+ if (this.config === false) return undefined;
156
+
157
+ // Explicit SWR in cache() options
158
+ if (this.config.swr !== undefined) {
159
+ return this.config.swr;
160
+ }
161
+
162
+ // Fall back to store defaults
163
+ const store = this.getStore();
164
+ return store?.defaults?.swr;
165
+ }
166
+
167
+ /**
168
+ * Get the cache store - resolution priority:
169
+ * 1. Explicit store from cache() options
170
+ * 2. App-level store from request context
171
+ */
172
+ getStore(): SegmentCacheStore | null {
173
+ return resolveCacheStore(this.explicitStore);
174
+ }
175
+
176
+ /**
177
+ * Resolve the cache key using the shared 3-tier priority.
178
+ * @internal
179
+ */
180
+ private async resolveKey(
181
+ pathname: string,
182
+ params: Record<string, string>,
183
+ isIntercept?: boolean,
184
+ ): Promise<string> {
185
+ const defaultKey = getDefaultRouteCacheKey(pathname, params, isIntercept);
186
+ const keyFn = this.config !== false ? this.config.key : undefined;
187
+ return resolveCacheKey(keyFn, this.getStore(), defaultKey, "CacheScope");
188
+ }
189
+
190
+ /**
191
+ * Lookup cached segments for a route (single cache entry per request).
192
+ * Returns { segments, shouldRevalidate } or null if cache miss.
193
+ *
194
+ * @param pathname - URL pathname for cache key generation
195
+ * @param params - Route params for cache key generation
196
+ * @param isIntercept - Whether this is an intercept navigation (uses different cache key)
197
+ */
198
+ async lookupRoute(
199
+ pathname: string,
200
+ params: Record<string, string>,
201
+ isIntercept?: boolean,
202
+ ): Promise<{
203
+ segments: ResolvedSegment[];
204
+ shouldRevalidate: boolean;
205
+ } | null> {
206
+ if (!this.enabled) return null;
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
+
229
+ const store = this.getStore();
230
+ if (!store) return null;
231
+
232
+ // Resolve cache key (may use custom key functions)
233
+ const key = await this.resolveKey(pathname, params, isIntercept);
234
+
235
+ try {
236
+ const result = await store.get(key);
237
+
238
+ if (!result) {
239
+ debugCacheLog(`[CacheScope] MISS: ${key}`);
240
+ return null;
241
+ }
242
+
243
+ const { data: cached, shouldRevalidate } = result;
244
+
245
+ // Deserialize segments
246
+ const segments = await deserializeSegments(cached.segments);
247
+
248
+ // Replay handle data
249
+ const handleStore = _getRequestContext()?._handleStore;
250
+ if (handleStore) {
251
+ restoreHandles(cached.handles, handleStore);
252
+ }
253
+
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
+ }
262
+
263
+ return { segments, shouldRevalidate };
264
+ } catch (error) {
265
+ console.error(`[CacheScope] Failed to lookup ${key}:`, error);
266
+ return null;
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Cache all segments for a route (non-blocking via waitUntil)
272
+ * Single cache entry per route request.
273
+ * Loaders are excluded - they're always fresh unless they have their own cache() config.
274
+ *
275
+ * @param pathname - URL pathname for cache key generation
276
+ * @param params - Route params for cache key generation
277
+ * @param segments - All resolved segments to cache
278
+ * @param isIntercept - Whether this is an intercept navigation (uses different cache key)
279
+ */
280
+ async cacheRoute(
281
+ pathname: string,
282
+ params: Record<string, string>,
283
+ segments: ResolvedSegment[],
284
+ isIntercept?: boolean,
285
+ ): Promise<void> {
286
+ if (!this.enabled || segments.length === 0) return;
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
+
309
+ const store = this.getStore();
310
+ if (!store) return;
311
+
312
+ const requestCtx = getRequestContext();
313
+ const handleStore = requestCtx?._handleStore;
314
+
315
+ if (!handleStore || !requestCtx) return;
316
+
317
+ // Exclude loader segments - loaders are always fresh by default
318
+ // Loaders can opt-in to caching with their own cache() config
319
+ const nonLoaderSegments = segments.filter((s) => s.type !== "loader");
320
+ if (nonLoaderSegments.length === 0) return;
321
+
322
+ const ttl = this.ttl;
323
+ const swr = this.swr;
324
+
325
+ // Resolve cache key early (while request context is available)
326
+ const key = await this.resolveKey(pathname, params, isIntercept);
327
+
328
+ // Check if this is a partial request (navigation) vs document request
329
+ const isPartial = requestCtx.url.searchParams.has("_rsc_partial");
330
+
331
+ requestCtx.waitUntil(async () => {
332
+ await handleStore.settled;
333
+
334
+ // For document requests: only cache if ALL segments have components (complete render)
335
+ // For partial requests: null components are expected (client already has them)
336
+ if (!isPartial) {
337
+ const hasAllComponents = nonLoaderSegments.every(
338
+ (s) => s.component !== null,
339
+ );
340
+ if (!hasAllComponents) return;
341
+ }
342
+
343
+ // Collect handle data for non-loader segments only
344
+ const handles = captureHandles(nonLoaderSegments, handleStore);
345
+
346
+ try {
347
+ // Serialize non-loader segments only
348
+ const serializedSegments = await serializeSegments(nonLoaderSegments);
349
+
350
+ const data: CachedEntryData = {
351
+ segments: serializedSegments,
352
+ handles,
353
+ expiresAt: Date.now() + ttl * 1000,
354
+ };
355
+
356
+ await store.set(key, data, ttl, swr);
357
+
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
+ }
366
+ } catch (error) {
367
+ console.error(`[CacheScope] Failed to cache ${key}:`, error);
368
+ }
369
+ });
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Create a cache scope from entry's cache config
375
+ */
376
+ export function createCacheScope(
377
+ config: { options: PartialCacheOptions | false } | undefined,
378
+ parent: CacheScope | null = null,
379
+ ): CacheScope | null {
380
+ if (!config) return parent; // No config, inherit parent
381
+ return new CacheScope(config.options, parent);
382
+ }