78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
/**
|
||
* Cache für CMS-API-Responses (nur serverseitig, Node fs/crypto).
|
||
* Wird ausschließlich per dynamic import aus cms.ts geladen – nie von Layout/Middleware,
|
||
* damit es nicht ins Client-Bundle gelangt. Flag/State: cms-cache-state.ts.
|
||
*/
|
||
|
||
import { createHash } from "node:crypto";
|
||
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
||
import { existsSync } from "node:fs";
|
||
import { setCmsFromCacheUsed } from "./cms-cache-state";
|
||
|
||
const CACHE_DIR = `${process.cwd()}/.cache/cms`;
|
||
|
||
function cacheKeyFromUrl(url: string): string {
|
||
return createHash("sha256").update(url).digest("hex");
|
||
}
|
||
|
||
async function ensureCacheDir(): Promise<void> {
|
||
if (!existsSync(CACHE_DIR)) {
|
||
await mkdir(CACHE_DIR, { recursive: true });
|
||
}
|
||
}
|
||
|
||
interface CachedResponse {
|
||
status: number;
|
||
body: string;
|
||
}
|
||
|
||
async function getCached(key: string): Promise<CachedResponse | null> {
|
||
try {
|
||
const path = `${CACHE_DIR}/${key}.json`;
|
||
if (!existsSync(path)) return null;
|
||
const raw = await readFile(path, "utf-8");
|
||
const data = JSON.parse(raw) as CachedResponse;
|
||
return data?.body != null ? data : null;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function setCached(key: string, data: CachedResponse): Promise<void> {
|
||
try {
|
||
await ensureCacheDir();
|
||
const path = `${CACHE_DIR}/${key}.json`;
|
||
await writeFile(path, JSON.stringify(data), "utf-8");
|
||
} catch {
|
||
// Cache schreiben optional
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Fetch mit Cache: bei Erfolg wird die Response in .cache/cms gespeichert (überschreibt vorhanden).
|
||
* Bei Fehler wird versucht, aus dem Cache zu lesen; wenn gefunden, wird usedCacheThisRequest gesetzt.
|
||
*/
|
||
export async function cachedFetch(url: string): Promise<Response> {
|
||
const key = cacheKeyFromUrl(url);
|
||
|
||
try {
|
||
const res = await fetch(url);
|
||
const body = await res.text();
|
||
await setCached(key, { status: res.status, body });
|
||
return new Response(body, {
|
||
status: res.status,
|
||
headers: res.headers,
|
||
});
|
||
} catch {
|
||
const cached = await getCached(key);
|
||
if (cached) {
|
||
setCmsFromCacheUsed();
|
||
return new Response(cached.body, {
|
||
status: cached.status,
|
||
headers: { "Content-Type": "application/json" },
|
||
});
|
||
}
|
||
throw new Error(`CMS nicht erreichbar (${url}) und kein Cache für diese Anfrage.`);
|
||
}
|
||
}
|