Repository navigation
Expand file tree
/
Copy pathcache.ts
More file actions
93 lines (76 loc) · 2.46 KB
/
Copy pathcache.ts
File metadata and controls
93 lines (76 loc) · 2.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
* Cloudflare Edge Cache 유틸리티
*
* caches.default를 사용할 수 있는 런타임에서만 동작하며,
* 로컬(Node) 테스트 환경에서는 자동으로 캐시를 건너뜁니다.
*/
interface EdgeCacheOptions {
bypassCache?: boolean;
ttlSeconds: number;
staleWhileRevalidateSeconds?: number;
keyPrefix?: string;
shouldCache?: (response: Response) => Promise<boolean>;
}
function getCacheStorage(): CacheStorage | undefined {
const globalObject = globalThis as { caches?: CacheStorage };
return globalObject.caches;
}
function createCacheKey(urlString: string, keyPrefix?: string): Request {
const url = new URL(urlString);
const normalized = new URL(`${url.origin}${url.pathname}`);
const sortedEntries = Array.from(url.searchParams.entries()).sort(([a], [b]) =>
a.localeCompare(b),
);
for (const [key, value] of sortedEntries) {
normalized.searchParams.append(key, value);
}
if (keyPrefix) {
normalized.searchParams.append('__cache_prefix', keyPrefix);
}
return new Request(normalized.toString(), { method: 'GET' });
}
function buildCacheControl(options: EdgeCacheOptions): string {
const { ttlSeconds, staleWhileRevalidateSeconds = 30 } = options;
return [
`public`,
`max-age=${ttlSeconds}`,
`s-maxage=${ttlSeconds}`,
`stale-while-revalidate=${staleWhileRevalidateSeconds}`,
].join(', ');
}
/**
* GET 응답을 Cloudflare Edge Cache에 저장/조회합니다.
*/
export async function withEdgeCache(
requestUrl: string,
options: EdgeCacheOptions,
fetcher: () => Promise<Response>,
): Promise<Response> {
if (options.bypassCache) {
const response = await fetcher();
response.headers.set('Cache-Control', 'no-store');
return response;
}
const cacheStorage = getCacheStorage();
const cache = cacheStorage?.default;
if (!cache && !options.shouldCache) {
return fetcher();
}
const cacheKey = createCacheKey(requestUrl, options.keyPrefix);
const cachedResponse = await cache?.match(cacheKey);
if (cachedResponse) {
return cachedResponse;
}
const response = await fetcher();
if (options.shouldCache && !(await options.shouldCache(response.clone()))) {
response.headers.set('Cache-Control', 'no-store');
return response;
}
if (!cache || !response.ok) {
return response;
}
const cacheControl = buildCacheControl(options);
response.headers.set('Cache-Control', cacheControl);
await cache.put(cacheKey, response.clone());
return response;
}