Review an HTTP conditional-request cache

src/conditional-json-cache.tsTypeScript
@@ -0,0 +1,43 @@
1+interface CacheEntry<T> {
2+ body: T;
3+ etag: string;
4+}
5+
6+export interface RepresentationRequest {
7+ url: string;
8+ userId: string;
9+ locale: string;
10+}
11+
12+export class ConditionalJsonCache {
13+ readonly #entries = new Map<string, CacheEntry<unknown>>();
14+
15+ async get<T>(request: RepresentationRequest): Promise<T> {
16+ const key = request.url;
17+ const cached = this.#entries.get(key) as CacheEntry<T> | undefined;
18+ const headers = new Headers({
19+ "Accept-Language": request.locale,
20+ "X-User-Id": request.userId,
21+ });
22+
23+ if (cached !== undefined) {
24+ headers.set("If-None-Match", cached.etag.replaceAll('"', ""));
25+ }
26+
27+ const response = await fetch(request.url, { headers });
28+ if (!response.ok) {
29+ throw new Error(`Request failed: ${response.status}`);
30+ }
31+
32+ if (response.status === 304) {
33+ return cached!.body;
34+ }
35+
36+ const body = (await response.json()) as T;
37+ this.#entries.set(key, {
38+ body,
39+ etag: cached?.etag ?? response.headers.get("ETag") ?? "",
40+ });
41+ return body;
42+ }
43+}