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+
}