@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,328 @@
1
+ /**
2
+ * In-Memory Segment Cache Store
3
+ *
4
+ * Simple in-memory implementation of SegmentCacheStore.
5
+ * Uses globalThis to survive HMR in development.
6
+ */
7
+
8
+ import type {
9
+ SegmentCacheStore,
10
+ CachedEntryData,
11
+ CacheDefaults,
12
+ CacheGetResult,
13
+ CacheItemResult,
14
+ CacheItemOptions,
15
+ SegmentHandleData,
16
+ } from "./types.js";
17
+ import type { RequestContext } from "../server/request-context.js";
18
+ import {
19
+ resolveTtl,
20
+ resolveSwrWindow,
21
+ computeExpiration,
22
+ DEFAULT_FUNCTION_TTL,
23
+ } from "./cache-policy.js";
24
+
25
+ const CACHE_REGISTRY_KEY = "__rsc_router_segment_cache_registry__";
26
+ const RESPONSE_CACHE_REGISTRY_KEY = "__rsc_router_response_cache_registry__";
27
+ const ITEM_CACHE_REGISTRY_KEY = "__rsc_router_item_cache_registry__";
28
+
29
+ /**
30
+ * Get or create a named Map from a globalThis-backed registry.
31
+ * The registry survives HMR; individual stores are keyed by name.
32
+ */
33
+ function getNamedMap<V>(registryKey: string, name: string): Map<string, V> {
34
+ let registry = (globalThis as any)[registryKey] as
35
+ | Map<string, Map<string, V>>
36
+ | undefined;
37
+ if (!registry) {
38
+ registry = new Map();
39
+ (globalThis as any)[registryKey] = registry;
40
+ }
41
+ let map = registry.get(name);
42
+ if (!map) {
43
+ map = new Map<string, V>();
44
+ registry.set(name, map);
45
+ }
46
+ return map;
47
+ }
48
+
49
+ interface CachedResponseEntry {
50
+ body: ArrayBuffer;
51
+ status: number;
52
+ headers: [string, string][];
53
+ expiresAt: number;
54
+ staleAt: number;
55
+ }
56
+
57
+ interface CachedItemEntry {
58
+ value: string;
59
+ handles?: Record<string, SegmentHandleData>;
60
+ expiresAt: number;
61
+ staleAt: number;
62
+ }
63
+
64
+ /**
65
+ * Options for MemorySegmentCacheStore
66
+ */
67
+ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
68
+ /**
69
+ * Optional name for this store instance. Named stores persist their
70
+ * backing Map on globalThis so data survives Vite HMR module reloads.
71
+ * Stores with different names get separate Maps.
72
+ *
73
+ * When omitted, the store uses a plain instance-level Map with no
74
+ * globalThis sharing, which is the safest default for isolation.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Two named stores are isolated from each other
79
+ * const fast = new MemorySegmentCacheStore({ name: "fast", defaults: { ttl: 10 } });
80
+ * const slow = new MemorySegmentCacheStore({ name: "slow", defaults: { ttl: 300 } });
81
+ * ```
82
+ */
83
+ name?: string;
84
+
85
+ /**
86
+ * Default cache options for cache() boundaries.
87
+ * When cache() is called without explicit ttl/swr,
88
+ * these defaults are used.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const store = new MemorySegmentCacheStore({
93
+ * defaults: { ttl: 60, swr: 300 }
94
+ * });
95
+ * ```
96
+ */
97
+ defaults?: CacheDefaults;
98
+
99
+ /**
100
+ * Custom key generator applied to all cache operations.
101
+ * Receives the full RequestContext and the default-generated key.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * keyGenerator: (ctx, defaultKey) => {
106
+ * const locale = cookies().get('locale')?.value || 'en';
107
+ * return `${locale}:${defaultKey}`;
108
+ * }
109
+ * ```
110
+ */
111
+ keyGenerator?: (
112
+ ctx: RequestContext<TEnv>,
113
+ defaultKey: string,
114
+ ) => string | Promise<string>;
115
+ }
116
+
117
+ /**
118
+ * In-memory segment cache store.
119
+ *
120
+ * Suitable for development and single-instance deployments.
121
+ * For production with multiple instances, use a distributed store
122
+ * like Cloudflare KV or Redis.
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * // Basic usage
127
+ * const store = new MemorySegmentCacheStore();
128
+ *
129
+ * // With defaults for cache() boundaries
130
+ * const store = new MemorySegmentCacheStore({
131
+ * defaults: { ttl: 60 }
132
+ * });
133
+ *
134
+ * createRSCHandler({
135
+ * router,
136
+ * cache: { store }
137
+ * })
138
+ * ```
139
+ */
140
+ export class MemorySegmentCacheStore<
141
+ TEnv = unknown,
142
+ > implements SegmentCacheStore<TEnv> {
143
+ private cache: Map<string, CachedEntryData>;
144
+ private responseCache: Map<string, CachedResponseEntry>;
145
+ private itemCache: Map<string, CachedItemEntry>;
146
+ readonly defaults?: CacheDefaults;
147
+ readonly keyGenerator?: (
148
+ ctx: RequestContext<TEnv>,
149
+ defaultKey: string,
150
+ ) => string | Promise<string>;
151
+
152
+ constructor(options?: MemorySegmentCacheStoreOptions<TEnv>) {
153
+ if (options?.name != null) {
154
+ // Named stores use the globalThis registry so data survives HMR.
155
+ // Each name gets its own isolated Map.
156
+ this.cache = getNamedMap<CachedEntryData>(
157
+ CACHE_REGISTRY_KEY,
158
+ options.name,
159
+ );
160
+ this.responseCache = getNamedMap<CachedResponseEntry>(
161
+ RESPONSE_CACHE_REGISTRY_KEY,
162
+ options.name,
163
+ );
164
+ this.itemCache = getNamedMap<CachedItemEntry>(
165
+ ITEM_CACHE_REGISTRY_KEY,
166
+ options.name,
167
+ );
168
+ } else {
169
+ // Unnamed stores get a plain instance-level Map (no globalThis sharing).
170
+ this.cache = new Map<string, CachedEntryData>();
171
+ this.responseCache = new Map<string, CachedResponseEntry>();
172
+ this.itemCache = new Map<string, CachedItemEntry>();
173
+ }
174
+ this.defaults = options?.defaults;
175
+ this.keyGenerator = options?.keyGenerator;
176
+ }
177
+
178
+ async get(key: string): Promise<CacheGetResult | null> {
179
+ const cached = this.cache.get(key);
180
+
181
+ if (!cached) {
182
+ return null;
183
+ }
184
+
185
+ // Check expiration
186
+ if (Date.now() > cached.expiresAt) {
187
+ this.cache.delete(key);
188
+ return null;
189
+ }
190
+
191
+ // Memory store doesn't support SWR - never triggers revalidation
192
+ return { data: cached, shouldRevalidate: false };
193
+ }
194
+
195
+ async set(
196
+ key: string,
197
+ data: CachedEntryData,
198
+ ttl: number,
199
+ _swr?: number,
200
+ ): Promise<void> {
201
+ // Note: Memory store doesn't implement SWR - entries just expire at TTL
202
+ // For SWR support, use CFCacheStore or similar distributed cache
203
+ const entry: CachedEntryData = {
204
+ ...data,
205
+ expiresAt: Date.now() + ttl * 1000,
206
+ };
207
+ this.cache.set(key, entry);
208
+ }
209
+
210
+ async delete(key: string): Promise<boolean> {
211
+ return this.cache.delete(key);
212
+ }
213
+
214
+ async clear(): Promise<void> {
215
+ this.cache.clear();
216
+ this.responseCache.clear();
217
+ this.itemCache.clear();
218
+ }
219
+
220
+ async getResponse(
221
+ key: string,
222
+ ): Promise<{ response: Response; shouldRevalidate: boolean } | null> {
223
+ const cached = this.responseCache.get(key);
224
+ if (!cached) return null;
225
+
226
+ if (Date.now() > cached.expiresAt) {
227
+ this.responseCache.delete(key);
228
+ return null;
229
+ }
230
+
231
+ const isStale = cached.staleAt > 0 && Date.now() > cached.staleAt;
232
+ const headers = new Headers(cached.headers);
233
+ return {
234
+ response: new Response(cached.body, {
235
+ status: cached.status,
236
+ headers,
237
+ }),
238
+ shouldRevalidate: isStale,
239
+ };
240
+ }
241
+
242
+ async putResponse(
243
+ key: string,
244
+ response: Response,
245
+ ttl: number,
246
+ swr?: number,
247
+ ): Promise<void> {
248
+ const body = await response.clone().arrayBuffer();
249
+ const headers: [string, string][] = [];
250
+ response.headers.forEach((value, name) => {
251
+ headers.push([name, value]);
252
+ });
253
+
254
+ const swrWindow = resolveSwrWindow(swr, this.defaults);
255
+ const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
256
+
257
+ this.responseCache.set(key, {
258
+ body,
259
+ status: response.status,
260
+ headers,
261
+ expiresAt,
262
+ staleAt,
263
+ });
264
+ }
265
+
266
+ async getItem(key: string): Promise<CacheItemResult | null> {
267
+ const cached = this.itemCache.get(key);
268
+ if (!cached) return null;
269
+
270
+ const now = Date.now();
271
+ if (now > cached.expiresAt) {
272
+ this.itemCache.delete(key);
273
+ return null;
274
+ }
275
+
276
+ const isStale = now > cached.staleAt;
277
+ return {
278
+ value: cached.value,
279
+ handles: cached.handles,
280
+ shouldRevalidate: isStale,
281
+ };
282
+ }
283
+
284
+ async setItem(
285
+ key: string,
286
+ value: string,
287
+ options?: CacheItemOptions,
288
+ ): Promise<void> {
289
+ const ttl = resolveTtl(options?.ttl, this.defaults, DEFAULT_FUNCTION_TTL);
290
+ const swrWindow = resolveSwrWindow(options?.swr, this.defaults);
291
+ const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
292
+ this.itemCache.set(key, {
293
+ value,
294
+ handles: options?.handles,
295
+ expiresAt,
296
+ staleAt,
297
+ });
298
+ }
299
+
300
+ /**
301
+ * Get cache statistics for debugging purposes.
302
+ * @internal
303
+ */
304
+ getStats(): { size: number; keys: string[] } {
305
+ return {
306
+ size: this.cache.size,
307
+ keys: Array.from(this.cache.keys()),
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Reset the global cache registry.
313
+ * Useful for test isolation - call this in beforeEach to ensure
314
+ * tests don't share cache state via globalThis.
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * beforeEach(() => {
319
+ * MemorySegmentCacheStore.resetGlobalCache();
320
+ * });
321
+ * ```
322
+ */
323
+ static resetGlobalCache(): void {
324
+ delete (globalThis as any)[CACHE_REGISTRY_KEY];
325
+ delete (globalThis as any)[RESPONSE_CACHE_REGISTRY_KEY];
326
+ delete (globalThis as any)[ITEM_CACHE_REGISTRY_KEY];
327
+ }
328
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Cache Profile Registry
3
+ *
4
+ * Named cache profiles for "use cache" directive.
5
+ * Profiles define TTL, SWR, and optional default tags.
6
+ * Set by createRouter() at startup, read by registerCachedFunction() at runtime.
7
+ */
8
+
9
+ export interface CacheProfile {
10
+ /** Time-to-live in seconds */
11
+ ttl: number;
12
+ /** Stale-while-revalidate window in seconds */
13
+ swr?: number;
14
+ /** Default cache tags for invalidation */
15
+ tags?: string[];
16
+ }
17
+
18
+ const DEFAULT_PROFILE: CacheProfile = { ttl: 900, swr: 1800 };
19
+
20
+ let _profiles: Record<string, CacheProfile> = {
21
+ default: DEFAULT_PROFILE,
22
+ };
23
+
24
+ const PROFILE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
25
+
26
+ /**
27
+ * Validate and merge user profiles with the default profile.
28
+ * Returns a new object suitable for both DSL-time and request-scoped use.
29
+ *
30
+ * Used by createRouter() to compute the resolved profile map once,
31
+ * stored on the router instance and passed to every request context.
32
+ */
33
+ export function resolveCacheProfiles(
34
+ profiles?: Record<string, CacheProfile>,
35
+ ): Record<string, CacheProfile> {
36
+ const merged: Record<string, CacheProfile> = {
37
+ default: DEFAULT_PROFILE,
38
+ };
39
+ if (profiles) {
40
+ for (const name of Object.keys(profiles)) {
41
+ if (!PROFILE_NAME_RE.test(name)) {
42
+ throw new Error(
43
+ `Invalid cache profile name "${name}". ` +
44
+ `Profile names must match [a-zA-Z0-9_-]+.`,
45
+ );
46
+ }
47
+ merged[name] = profiles[name];
48
+ }
49
+ }
50
+ return merged;
51
+ }
52
+
53
+ /**
54
+ * Set all cache profiles in the global registry.
55
+ * Called by createRouter() at startup for DSL-time resolution
56
+ * (cache("profileName") reads from this during route definition).
57
+ *
58
+ * WARNING: This is global mutable state. It exists only for DSL-time
59
+ * reads. Runtime resolution (registerCachedFunction) uses request-scoped
60
+ * profiles and does NOT read from this registry.
61
+ */
62
+ export function setCacheProfiles(profiles: Record<string, CacheProfile>): void {
63
+ _profiles = resolveCacheProfiles(profiles);
64
+ }
65
+
66
+ /**
67
+ * Get a cache profile by name from the global registry.
68
+ * Used only at DSL-time (cache("profileName") inside urls() evaluation).
69
+ * Runtime code uses request-scoped profiles instead.
70
+ */
71
+ export function getCacheProfile(name: string): CacheProfile | undefined {
72
+ return _profiles[name];
73
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * SWR Read-Through Engine
3
+ *
4
+ * Generic read-through cache with stale-while-revalidate support
5
+ * for item-level caching (getItem/setItem).
6
+ *
7
+ * Flow:
8
+ * 1. Lookup cached item by key
9
+ * 2. Fresh hit → deserialize, return
10
+ * 3. Stale hit → deserialize, return, revalidate in background
11
+ * 4. Miss → execute, cache write (blocking when no waitUntil), return
12
+ */
13
+
14
+ import type { CacheItemResult, CacheItemOptions } from "./types.js";
15
+ import { runBackground } from "./background-task.js";
16
+
17
+ interface WaitUntilHost {
18
+ waitUntil?: (fn: () => Promise<void>) => void;
19
+ }
20
+
21
+ export interface ReadThroughItemConfig<T> {
22
+ /** Retrieve a cached item by key */
23
+ getItem: (key: string) => Promise<CacheItemResult | null>;
24
+ /** Store a serialized item by key */
25
+ setItem: (
26
+ key: string,
27
+ value: string,
28
+ options?: CacheItemOptions,
29
+ ) => Promise<void>;
30
+ /** Cache key */
31
+ key: string;
32
+ /** Execute the underlying function/loader on miss or revalidation */
33
+ execute: () => Promise<T>;
34
+ /** Serialize result for storage. Return null to skip caching. */
35
+ serialize: (data: T) => Promise<string | null>;
36
+ /** Deserialize cached value back to the original type */
37
+ deserialize: (value: string) => Promise<T>;
38
+ /** Options passed to setItem on cache write */
39
+ storeOptions: CacheItemOptions;
40
+ /** Called on fresh cache hit (before returning data) */
41
+ onHit?: (cached: CacheItemResult) => void;
42
+ /** Called on stale cache hit (before scheduling background revalidation) */
43
+ onStale?: (cached: CacheItemResult) => void;
44
+ /** Called on cache miss (before executing) */
45
+ onMiss?: () => void;
46
+ /** Called after successful cache write */
47
+ onCached?: () => void;
48
+ /** Host with optional waitUntil for background tasks */
49
+ host?: WaitUntilHost | null;
50
+ }
51
+
52
+ /**
53
+ * Read-through cache with SWR support for item-level caching.
54
+ *
55
+ * On fresh hit: returns deserialized cached data.
56
+ * On stale hit: returns stale data, schedules background revalidation.
57
+ * On miss: executes, writes to cache (blocking when no waitUntil), returns.
58
+ */
59
+ export async function readThroughItem<T>(
60
+ config: ReadThroughItemConfig<T>,
61
+ ): Promise<T> {
62
+ const {
63
+ getItem,
64
+ setItem,
65
+ key,
66
+ execute,
67
+ serialize,
68
+ deserialize,
69
+ storeOptions,
70
+ onHit,
71
+ onStale,
72
+ onMiss,
73
+ onCached,
74
+ host,
75
+ } = config;
76
+
77
+ // Cache lookup
78
+ try {
79
+ const cached = await getItem(key);
80
+
81
+ if (cached) {
82
+ const data = await deserialize(cached.value);
83
+
84
+ if (!cached.shouldRevalidate) {
85
+ onHit?.(cached);
86
+ return data;
87
+ }
88
+
89
+ // Stale hit — return stale data, revalidate in background
90
+ onStale?.(cached);
91
+ runBackground(
92
+ host,
93
+ async () => {
94
+ try {
95
+ const fresh = await execute();
96
+ const serialized = await serialize(fresh);
97
+ if (serialized !== null) {
98
+ await setItem(key, serialized, storeOptions);
99
+ }
100
+ } catch {
101
+ // Background revalidation failed silently
102
+ }
103
+ },
104
+ true,
105
+ );
106
+ return data;
107
+ }
108
+ } catch {
109
+ // Cache lookup failed, fall through to fresh execution
110
+ }
111
+
112
+ // Cache miss
113
+ onMiss?.();
114
+ const data = await execute();
115
+
116
+ // Non-blocking cache write (blocks when no waitUntil)
117
+ await runBackground(
118
+ host,
119
+ async () => {
120
+ try {
121
+ const serialized = await serialize(data);
122
+ if (serialized !== null) {
123
+ await setItem(key, serialized, storeOptions);
124
+ onCached?.();
125
+ }
126
+ } catch {
127
+ // Cache write failed silently
128
+ }
129
+ },
130
+ true,
131
+ );
132
+
133
+ return data;
134
+ }