@rangojs/router 0.0.0-experimental.2
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/CLAUDE.md +7 -0
- package/README.md +19 -0
- package/dist/vite/index.js +1298 -0
- package/package.json +140 -0
- package/skills/caching/SKILL.md +319 -0
- package/skills/document-cache/SKILL.md +152 -0
- package/skills/hooks/SKILL.md +359 -0
- package/skills/intercept/SKILL.md +292 -0
- package/skills/layout/SKILL.md +216 -0
- package/skills/loader/SKILL.md +365 -0
- package/skills/middleware/SKILL.md +442 -0
- package/skills/parallel/SKILL.md +255 -0
- package/skills/route/SKILL.md +141 -0
- package/skills/router-setup/SKILL.md +403 -0
- package/skills/theme/SKILL.md +54 -0
- package/skills/typesafety/SKILL.md +352 -0
- package/src/__mocks__/version.ts +6 -0
- package/src/__tests__/component-utils.test.ts +76 -0
- package/src/__tests__/route-definition.test.ts +63 -0
- package/src/__tests__/urls.test.tsx +436 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +893 -0
- package/src/browser/navigation-client.ts +162 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +559 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +275 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +178 -0
- package/src/browser/react/use-href.tsx +208 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-navigation.ts +150 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +353 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/server-action-bridge.ts +747 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +464 -0
- package/src/cache/__tests__/document-cache.test.ts +522 -0
- package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
- package/src/cache/__tests__/memory-store.test.ts +484 -0
- package/src/cache/cache-scope.ts +565 -0
- package/src/cache/cf/__tests__/cf-cache-store.test.ts +428 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +387 -0
- package/src/client.rsc.tsx +88 -0
- package/src/client.tsx +621 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +259 -0
- package/src/handle.ts +120 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/href-client.ts +128 -0
- package/src/href-context.ts +33 -0
- package/src/href.ts +177 -0
- package/src/index.rsc.ts +79 -0
- package/src/index.ts +87 -0
- package/src/loader.rsc.ts +204 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +198 -0
- package/src/route-definition.ts +1371 -0
- package/src/route-map-builder.ts +146 -0
- package/src/route-types.ts +198 -0
- package/src/route-utils.ts +89 -0
- package/src/router/__tests__/match-context.test.ts +104 -0
- package/src/router/__tests__/match-pipelines.test.ts +537 -0
- package/src/router/__tests__/match-result.test.ts +566 -0
- package/src/router/__tests__/on-error.test.ts +935 -0
- package/src/router/__tests__/pattern-matching.test.ts +577 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/loader-resolution.ts +326 -0
- package/src/router/manifest.ts +138 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +261 -0
- package/src/router/match-middleware/cache-store.ts +266 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +268 -0
- package/src/router/match-middleware/segment-resolution.ts +174 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +214 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.test.ts +1355 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +272 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +299 -0
- package/src/router/types.ts +96 -0
- package/src/router.ts +3876 -0
- package/src/rsc/__tests__/helpers.test.ts +175 -0
- package/src/rsc/handler.ts +1060 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +237 -0
- package/src/segment-system.tsx +456 -0
- package/src/server/__tests__/request-context.test.ts +171 -0
- package/src/server/context.ts +417 -0
- package/src/server/handle-store.ts +230 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +146 -0
- package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
- package/src/ssr/index.tsx +234 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/__tests__/theme.test.ts +120 -0
- package/src/theme/constants.ts +55 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1561 -0
- package/src/urls.ts +726 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +357 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/index.ts +787 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +109 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Cache Store
|
|
3
|
+
*
|
|
4
|
+
* Simple implementation for development and testing.
|
|
5
|
+
* Not suitable for production (no persistence, single-instance only).
|
|
6
|
+
*
|
|
7
|
+
* @internal This is reserved for future extensibility.
|
|
8
|
+
* For segment caching, use MemorySegmentCacheStore instead.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
CacheStore,
|
|
13
|
+
CacheEntry,
|
|
14
|
+
CacheValue,
|
|
15
|
+
CachePutOptions,
|
|
16
|
+
CacheMetadata,
|
|
17
|
+
CacheValueType,
|
|
18
|
+
} from "./types.js";
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Constants
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
/** Default TTL when no explicit value is provided */
|
|
25
|
+
const DEFAULT_TTL_SECONDS = 60;
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Types
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
interface StoredEntry {
|
|
32
|
+
/** Stored value (streams/responses converted to ArrayBuffer) */
|
|
33
|
+
value: ArrayBuffer | string | object;
|
|
34
|
+
metadata: CacheMetadata;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* In-memory cache store implementation
|
|
39
|
+
*/
|
|
40
|
+
export class MemoryCacheStore implements CacheStore {
|
|
41
|
+
private cache = new Map<string, StoredEntry>();
|
|
42
|
+
|
|
43
|
+
async match<T = CacheValue>(key: string): Promise<CacheEntry<T> | undefined> {
|
|
44
|
+
const entry = this.cache.get(key);
|
|
45
|
+
|
|
46
|
+
if (!entry) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check expiration
|
|
51
|
+
if (entry.metadata.expiresAt && Date.now() > entry.metadata.expiresAt) {
|
|
52
|
+
this.cache.delete(key);
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Reconstruct value based on original type
|
|
57
|
+
const value = this.reconstructValue(entry);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
value: value as T,
|
|
61
|
+
metadata: entry.metadata,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async put<T extends CacheValue>(
|
|
66
|
+
key: string,
|
|
67
|
+
value: T,
|
|
68
|
+
options?: CachePutOptions
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const ttl = options?.ttl ?? DEFAULT_TTL_SECONDS;
|
|
71
|
+
const expiresAt = Date.now() + ttl * 1000;
|
|
72
|
+
|
|
73
|
+
// Detect value type and convert for storage
|
|
74
|
+
const { storedValue, valueType, responseHeaders, responseStatus } =
|
|
75
|
+
await this.prepareForStorage(value);
|
|
76
|
+
|
|
77
|
+
const metadata: CacheMetadata = {
|
|
78
|
+
...options?.metadata,
|
|
79
|
+
expiresAt,
|
|
80
|
+
valueType,
|
|
81
|
+
responseHeaders,
|
|
82
|
+
responseStatus,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
this.cache.set(key, {
|
|
86
|
+
value: storedValue,
|
|
87
|
+
metadata,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async delete(key: string): Promise<boolean> {
|
|
92
|
+
return this.cache.delete(key);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Clear all entries (useful for testing)
|
|
97
|
+
*/
|
|
98
|
+
clear(): void {
|
|
99
|
+
this.cache.clear();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get current cache size (useful for testing/debugging)
|
|
104
|
+
*/
|
|
105
|
+
get size(): number {
|
|
106
|
+
return this.cache.size;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Manually purge expired entries
|
|
111
|
+
*/
|
|
112
|
+
purgeExpired(): number {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
let purged = 0;
|
|
115
|
+
|
|
116
|
+
for (const [key, entry] of this.cache) {
|
|
117
|
+
if (entry.metadata.expiresAt && now > entry.metadata.expiresAt) {
|
|
118
|
+
this.cache.delete(key);
|
|
119
|
+
purged++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return purged;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Prepare a value for storage
|
|
128
|
+
* Converts streams and responses to ArrayBuffer, detects type
|
|
129
|
+
*/
|
|
130
|
+
private async prepareForStorage(value: CacheValue): Promise<{
|
|
131
|
+
storedValue: ArrayBuffer | string | object;
|
|
132
|
+
valueType: CacheValueType;
|
|
133
|
+
responseHeaders?: Record<string, string>;
|
|
134
|
+
responseStatus?: number;
|
|
135
|
+
}> {
|
|
136
|
+
// ReadableStream -> ArrayBuffer
|
|
137
|
+
if (value instanceof ReadableStream) {
|
|
138
|
+
return {
|
|
139
|
+
storedValue: await streamToArrayBuffer(value),
|
|
140
|
+
valueType: "stream",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Response -> ArrayBuffer + headers/status
|
|
145
|
+
if (value instanceof Response) {
|
|
146
|
+
const headers: Record<string, string> = {};
|
|
147
|
+
value.headers.forEach((v, k) => {
|
|
148
|
+
headers[k] = v;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
storedValue: await value.clone().arrayBuffer(),
|
|
153
|
+
valueType: "response",
|
|
154
|
+
responseHeaders: headers,
|
|
155
|
+
responseStatus: value.status,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ArrayBuffer -> store as-is
|
|
160
|
+
if (value instanceof ArrayBuffer) {
|
|
161
|
+
return {
|
|
162
|
+
storedValue: value,
|
|
163
|
+
valueType: "arraybuffer",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// String -> store as-is
|
|
168
|
+
if (typeof value === "string") {
|
|
169
|
+
return {
|
|
170
|
+
storedValue: value,
|
|
171
|
+
valueType: "string",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Object -> store as-is (JSON-serializable)
|
|
176
|
+
return {
|
|
177
|
+
storedValue: value,
|
|
178
|
+
valueType: "object",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Reconstruct original value type from stored entry
|
|
184
|
+
*/
|
|
185
|
+
private reconstructValue(entry: StoredEntry): CacheValue {
|
|
186
|
+
const { value, metadata } = entry;
|
|
187
|
+
|
|
188
|
+
switch (metadata.valueType) {
|
|
189
|
+
case "stream":
|
|
190
|
+
return arrayBufferToStream(value as ArrayBuffer);
|
|
191
|
+
|
|
192
|
+
case "response": {
|
|
193
|
+
const status = metadata.responseStatus ?? 200;
|
|
194
|
+
// Status codes 204 (No Content) and 304 (Not Modified) cannot have a body
|
|
195
|
+
const isNullBodyStatus = status === 204 || status === 304;
|
|
196
|
+
return new Response(isNullBodyStatus ? null : (value as ArrayBuffer), {
|
|
197
|
+
status,
|
|
198
|
+
headers: metadata.responseHeaders,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
case "arraybuffer":
|
|
203
|
+
case "string":
|
|
204
|
+
case "object":
|
|
205
|
+
default:
|
|
206
|
+
return value as CacheValue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Convert a ReadableStream to ArrayBuffer.
|
|
213
|
+
* @internal
|
|
214
|
+
*/
|
|
215
|
+
async function streamToArrayBuffer(
|
|
216
|
+
stream: ReadableStream<Uint8Array>
|
|
217
|
+
): Promise<ArrayBuffer> {
|
|
218
|
+
const chunks: Uint8Array[] = [];
|
|
219
|
+
const reader = stream.getReader();
|
|
220
|
+
|
|
221
|
+
while (true) {
|
|
222
|
+
const { done, value } = await reader.read();
|
|
223
|
+
if (done) break;
|
|
224
|
+
chunks.push(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Concatenate chunks
|
|
228
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
229
|
+
const result = new Uint8Array(totalLength);
|
|
230
|
+
let offset = 0;
|
|
231
|
+
|
|
232
|
+
for (const chunk of chunks) {
|
|
233
|
+
result.set(chunk, offset);
|
|
234
|
+
offset += chunk.length;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result.buffer;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Convert an ArrayBuffer to a ReadableStream.
|
|
242
|
+
* @internal
|
|
243
|
+
*/
|
|
244
|
+
function arrayBufferToStream(buffer: ArrayBuffer): ReadableStream<Uint8Array> {
|
|
245
|
+
const uint8 = new Uint8Array(buffer);
|
|
246
|
+
|
|
247
|
+
return new ReadableStream({
|
|
248
|
+
start(controller) {
|
|
249
|
+
controller.enqueue(uint8);
|
|
250
|
+
controller.close();
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Store Types
|
|
3
|
+
*
|
|
4
|
+
* Generic caching interface supporting multiple value types.
|
|
5
|
+
* Designed to be implemented by different backends:
|
|
6
|
+
* - MemoryCacheStore (dev/testing)
|
|
7
|
+
* - Cloudflare Cache API adapter
|
|
8
|
+
* - Cloudflare KV adapter
|
|
9
|
+
* - Redis adapter
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ResolvedSegment } from "../types.js";
|
|
13
|
+
import type { RequestContext } from "../server/request-context.js";
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Segment Cache Store (low-level storage interface)
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Result from cache get() including data and revalidation status
|
|
21
|
+
*/
|
|
22
|
+
export interface CacheGetResult {
|
|
23
|
+
/** The cached entry data */
|
|
24
|
+
data: CachedEntryData;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the caller should trigger background revalidation.
|
|
27
|
+
* True when entry is stale AND not already being revalidated.
|
|
28
|
+
* The store atomically marks the entry as REVALIDATING when returning true.
|
|
29
|
+
*/
|
|
30
|
+
shouldRevalidate: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Low-level segment cache store interface.
|
|
35
|
+
*
|
|
36
|
+
* Implementations handle the actual storage (memory, KV, Redis, etc.).
|
|
37
|
+
* The store deals with serialized data - RSC serialization is handled
|
|
38
|
+
* by the cache provider layer.
|
|
39
|
+
*
|
|
40
|
+
* @typeParam TEnv - Platform bindings type (e.g., Cloudflare env)
|
|
41
|
+
*/
|
|
42
|
+
export interface SegmentCacheStore<TEnv = unknown> {
|
|
43
|
+
/**
|
|
44
|
+
* Default cache options for this store.
|
|
45
|
+
* Used by cache() boundaries when ttl/swr are not explicitly specified.
|
|
46
|
+
*/
|
|
47
|
+
readonly defaults?: CacheDefaults;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Custom key generator applied to all cache operations using this store.
|
|
51
|
+
* Receives the full RequestContext and the default-generated key.
|
|
52
|
+
* Return value becomes the final cache key (unless route overrides with `key` option).
|
|
53
|
+
*
|
|
54
|
+
* Resolution priority:
|
|
55
|
+
* 1. Route-level `key` function (full override)
|
|
56
|
+
* 2. Store-level `keyGenerator` (modifies default key)
|
|
57
|
+
* 3. Default key generation (prefix:pathname:params)
|
|
58
|
+
*
|
|
59
|
+
* @example Using headers for cache segmentation
|
|
60
|
+
* ```typescript
|
|
61
|
+
* keyGenerator: (ctx, defaultKey) => {
|
|
62
|
+
* const segment = ctx.request.headers.get('x-user-segment') || 'default';
|
|
63
|
+
* return `${segment}:${defaultKey}`;
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @example Using env bindings (Cloudflare)
|
|
68
|
+
* ```typescript
|
|
69
|
+
* keyGenerator: (ctx, defaultKey) => {
|
|
70
|
+
* const region = ctx.env.REGION || 'us';
|
|
71
|
+
* return `${region}:${defaultKey}`;
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @example Using cookies for locale
|
|
76
|
+
* ```typescript
|
|
77
|
+
* keyGenerator: (ctx, defaultKey) => {
|
|
78
|
+
* const locale = ctx.cookie('locale') || 'en';
|
|
79
|
+
* return `${locale}:${defaultKey}`;
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
readonly keyGenerator?: (
|
|
84
|
+
ctx: RequestContext<TEnv>,
|
|
85
|
+
defaultKey: string
|
|
86
|
+
) => string | Promise<string>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get cached entry data by key
|
|
90
|
+
* @returns Cache result with data and staleness, or null if not found/expired
|
|
91
|
+
*/
|
|
92
|
+
get(key: string): Promise<CacheGetResult | null>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Store entry data with TTL
|
|
96
|
+
* @param key - Cache key
|
|
97
|
+
* @param data - Serialized entry data
|
|
98
|
+
* @param ttl - Time-to-live in seconds
|
|
99
|
+
* @param swr - Optional stale-while-revalidate window in seconds
|
|
100
|
+
*/
|
|
101
|
+
set(key: string, data: CachedEntryData, ttl: number, swr?: number): Promise<void>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Delete a cached entry
|
|
105
|
+
* @returns true if deleted, false if not found
|
|
106
|
+
*/
|
|
107
|
+
delete(key: string): Promise<boolean>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Clear all cached entries (optional, for testing)
|
|
111
|
+
*/
|
|
112
|
+
clear?(): Promise<void>;
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Document Cache Methods (optional)
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// These methods are for caching full HTTP responses (document-level caching).
|
|
118
|
+
// Stores that support response caching should implement these methods.
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get a cached Response by key.
|
|
122
|
+
* Returns the response and whether it should be revalidated (SWR).
|
|
123
|
+
*/
|
|
124
|
+
getResponse?(key: string): Promise<{ response: Response; shouldRevalidate: boolean } | null>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Store a Response with TTL and optional SWR window.
|
|
128
|
+
* @param key - Cache key
|
|
129
|
+
* @param response - Response to cache (will be cloned)
|
|
130
|
+
* @param ttl - Time-to-live in seconds
|
|
131
|
+
* @param swr - Optional stale-while-revalidate window in seconds
|
|
132
|
+
*/
|
|
133
|
+
putResponse?(key: string, response: Response, ttl: number, swr?: number): Promise<void>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Serialized segment data stored in cache
|
|
138
|
+
* Note: loading is preserved to ensure consistent tree structure between cached and fresh renders
|
|
139
|
+
*/
|
|
140
|
+
export interface SerializedSegmentData {
|
|
141
|
+
/** RSC-encoded component string */
|
|
142
|
+
encoded: string;
|
|
143
|
+
/** RSC-encoded layout string (if present) */
|
|
144
|
+
encodedLayout?: string;
|
|
145
|
+
/** RSC-encoded loading skeleton string (if present), or "null" for explicit null */
|
|
146
|
+
encodedLoading?: string;
|
|
147
|
+
/** RSC-encoded loaderData (if present) */
|
|
148
|
+
encodedLoaderData?: string;
|
|
149
|
+
/** RSC-encoded loaderDataPromise (if present) */
|
|
150
|
+
encodedLoaderDataPromise?: string;
|
|
151
|
+
/** Segment metadata (everything except component, layout, loading, and loader data) */
|
|
152
|
+
metadata: Omit<ResolvedSegment, "component" | "layout" | "loading" | "loaderData" | "loaderDataPromise">;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Raw data stored in cache for an entry
|
|
157
|
+
*/
|
|
158
|
+
export interface CachedEntryData {
|
|
159
|
+
/** Serialized segments for this entry */
|
|
160
|
+
segments: SerializedSegmentData[];
|
|
161
|
+
/** Handle data keyed by segment ID */
|
|
162
|
+
handles: Record<string, SegmentHandleData>;
|
|
163
|
+
/** Expiration timestamp (ms since epoch) */
|
|
164
|
+
expiresAt: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// Cache Configuration
|
|
169
|
+
// ============================================================================
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Default cache options applied to all cache() boundaries.
|
|
173
|
+
* Individual cache() calls can override any of these values.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* const store = new CFCacheStore({
|
|
178
|
+
* defaults: { ttl: 60, swr: 300 }
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export interface CacheDefaults {
|
|
183
|
+
/**
|
|
184
|
+
* Default time-to-live in seconds.
|
|
185
|
+
* After TTL expires, cached entry is considered stale.
|
|
186
|
+
*/
|
|
187
|
+
ttl?: number;
|
|
188
|
+
/**
|
|
189
|
+
* Default stale-while-revalidate window in seconds.
|
|
190
|
+
* During SWR window, stale content is served while revalidating in background.
|
|
191
|
+
*/
|
|
192
|
+
swr?: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Cache configuration for RSC handler
|
|
197
|
+
*/
|
|
198
|
+
export interface CacheConfig {
|
|
199
|
+
/** Cache store implementation (includes defaults) */
|
|
200
|
+
store: SegmentCacheStore;
|
|
201
|
+
/** Enable/disable caching (default: true) */
|
|
202
|
+
enabled?: boolean;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Cache configuration - can be static or a function receiving env
|
|
207
|
+
*/
|
|
208
|
+
export type CacheConfigOrFactory<TEnv> =
|
|
209
|
+
| CacheConfig
|
|
210
|
+
| ((env: TEnv) => CacheConfig);
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// Segment Cache Provider (request-level interface)
|
|
214
|
+
// ============================================================================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Handle data for a single segment
|
|
218
|
+
* Structure: { handleName: [values...] }
|
|
219
|
+
*/
|
|
220
|
+
export type SegmentHandleData = Record<string, unknown[]>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Result from cache get() including segments and their handle data
|
|
224
|
+
* Each entry can produce multiple segments (main + parallels)
|
|
225
|
+
*/
|
|
226
|
+
export interface CachedEntryResult {
|
|
227
|
+
/** All segments for this entry (main segment + parallels) */
|
|
228
|
+
segments: ResolvedSegment[];
|
|
229
|
+
/** Handle data keyed by segment ID */
|
|
230
|
+
handles: Record<string, SegmentHandleData>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Segment cache provider interface
|
|
236
|
+
*
|
|
237
|
+
* Used by router to check/store segment cache during matching.
|
|
238
|
+
* Accessed via request context - if not present, caching is disabled.
|
|
239
|
+
*
|
|
240
|
+
* @internal Not currently implemented - CacheScope is used directly.
|
|
241
|
+
* Reserved for future extensibility.
|
|
242
|
+
*/
|
|
243
|
+
export interface SegmentCacheProvider {
|
|
244
|
+
/** Whether caching is enabled for this request */
|
|
245
|
+
readonly enabled: boolean;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get cached segments and restore handles/loaders.
|
|
249
|
+
*
|
|
250
|
+
* Combines cache get with handle replay and loader data restoration.
|
|
251
|
+
* Returns tuple of [segments, segmentIds] if cache hit, null if miss or disabled.
|
|
252
|
+
*
|
|
253
|
+
* @param cacheKey - Cache key to look up
|
|
254
|
+
* @param params - Route params for cache key generation
|
|
255
|
+
* @param loaderPromises - Map to restore loader data into
|
|
256
|
+
* @returns Tuple of [segments, segmentIds] or null if miss
|
|
257
|
+
*/
|
|
258
|
+
restore(
|
|
259
|
+
cacheKey: string,
|
|
260
|
+
params: Record<string, string>,
|
|
261
|
+
loaderPromises: Map<string, Promise<any>>
|
|
262
|
+
): Promise<[ResolvedSegment[], string[]] | null>;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Cache entry with automatic handle collection (non-blocking).
|
|
266
|
+
*
|
|
267
|
+
* Schedules caching via waitUntil - handles are collected after they settle.
|
|
268
|
+
* Validates segments have actual components before caching.
|
|
269
|
+
*
|
|
270
|
+
* @param cacheKey - The cache key to store under
|
|
271
|
+
* @param segments - All resolved segments for this entry
|
|
272
|
+
*/
|
|
273
|
+
cacheEntry(cacheKey: string, segments: ResolvedSegment[]): void;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================================================
|
|
277
|
+
// Generic Cache Store (for future extensibility)
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// These types support a general-purpose cache interface that can be used
|
|
280
|
+
// for caching arbitrary values (responses, streams, objects). Currently,
|
|
281
|
+
// the segment caching system uses SegmentCacheStore directly, but these
|
|
282
|
+
// types enable future use cases like response caching or data caching.
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Supported cache value types for the generic CacheStore interface.
|
|
286
|
+
* @internal Reserved for future extensibility
|
|
287
|
+
*/
|
|
288
|
+
export type CacheValue =
|
|
289
|
+
| ReadableStream<Uint8Array>
|
|
290
|
+
| Response
|
|
291
|
+
| ArrayBuffer
|
|
292
|
+
| string
|
|
293
|
+
| Record<string, unknown>; // JSON-serializable object
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Cache entry returned by match().
|
|
297
|
+
* @internal Reserved for future extensibility
|
|
298
|
+
*/
|
|
299
|
+
export interface CacheEntry<T = CacheValue> {
|
|
300
|
+
/** The cached value */
|
|
301
|
+
value: T;
|
|
302
|
+
/** Optional metadata stored with the entry */
|
|
303
|
+
metadata?: CacheMetadata;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Original value type for reconstruction.
|
|
308
|
+
* @internal Reserved for future extensibility
|
|
309
|
+
*/
|
|
310
|
+
export type CacheValueType =
|
|
311
|
+
| "stream"
|
|
312
|
+
| "response"
|
|
313
|
+
| "arraybuffer"
|
|
314
|
+
| "string"
|
|
315
|
+
| "object";
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Metadata associated with a cache entry.
|
|
319
|
+
* @internal Reserved for future extensibility
|
|
320
|
+
*/
|
|
321
|
+
export interface CacheMetadata {
|
|
322
|
+
/** Timestamp when entry expires (ms since epoch) */
|
|
323
|
+
expiresAt?: number;
|
|
324
|
+
/** Tags for bulk invalidation */
|
|
325
|
+
tags?: string[];
|
|
326
|
+
/** Original value type for reconstruction on read */
|
|
327
|
+
valueType?: CacheValueType;
|
|
328
|
+
/** Response headers (preserved when caching Response) */
|
|
329
|
+
responseHeaders?: Record<string, string>;
|
|
330
|
+
/** Response status (preserved when caching Response) */
|
|
331
|
+
responseStatus?: number;
|
|
332
|
+
/** Custom metadata */
|
|
333
|
+
[key: string]: unknown;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Options for put().
|
|
338
|
+
* @internal Reserved for future extensibility
|
|
339
|
+
*/
|
|
340
|
+
export interface CachePutOptions {
|
|
341
|
+
/** Time-to-live in seconds */
|
|
342
|
+
ttl?: number;
|
|
343
|
+
/** Metadata to store with entry */
|
|
344
|
+
metadata?: Omit<CacheMetadata, "expiresAt">;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Generic cache store interface for arbitrary value types.
|
|
349
|
+
*
|
|
350
|
+
* This interface is designed for future extensibility to support caching
|
|
351
|
+
* responses, streams, and other values. Currently, segment caching uses
|
|
352
|
+
* the SegmentCacheStore interface directly.
|
|
353
|
+
*
|
|
354
|
+
* Implementations must handle:
|
|
355
|
+
* - Stream values (clone before storing, streams can only be read once)
|
|
356
|
+
* - Promise values (await before storing)
|
|
357
|
+
* - Expiration/TTL
|
|
358
|
+
*
|
|
359
|
+
* @internal Reserved for future extensibility
|
|
360
|
+
*/
|
|
361
|
+
export interface CacheStore {
|
|
362
|
+
/**
|
|
363
|
+
* Retrieve a cached entry by key.
|
|
364
|
+
* @param key - Cache key
|
|
365
|
+
* @returns The cached entry or undefined if not found/expired
|
|
366
|
+
*/
|
|
367
|
+
match<T = CacheValue>(key: string): Promise<CacheEntry<T> | undefined>;
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Store a value in the cache.
|
|
371
|
+
* @param key - Cache key
|
|
372
|
+
* @param value - Value to cache (stream, response, string, object, etc.)
|
|
373
|
+
* @param options - TTL, metadata, etc.
|
|
374
|
+
*/
|
|
375
|
+
put<T extends CacheValue>(
|
|
376
|
+
key: string,
|
|
377
|
+
value: T,
|
|
378
|
+
options?: CachePutOptions
|
|
379
|
+
): Promise<void>;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Delete a cached entry.
|
|
383
|
+
* @param key - Cache key
|
|
384
|
+
* @returns true if entry was deleted, false if not found
|
|
385
|
+
*/
|
|
386
|
+
delete(key: string): Promise<boolean>;
|
|
387
|
+
}
|