52 lines
1.4 KiB
TypeScript
52 lines
1.4 KiB
TypeScript
|
|
import fs from "fs";
|
||
|
|
import path from "path";
|
||
|
|
import crypto from "crypto";
|
||
|
|
|
||
|
|
const CACHE_DIR = path.join(process.cwd(), "data", "search-cache");
|
||
|
|
|
||
|
|
function ensureDir() {
|
||
|
|
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
function hashKey(input: string): string {
|
||
|
|
return crypto.createHash("sha256").update(input).digest("hex");
|
||
|
|
}
|
||
|
|
|
||
|
|
interface CacheEntry<T> {
|
||
|
|
expiresAt: number;
|
||
|
|
value: T;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function cacheGet<T>(namespace: string, key: string): T | null {
|
||
|
|
try {
|
||
|
|
const file = path.join(CACHE_DIR, namespace, `${hashKey(key)}.json`);
|
||
|
|
if (!fs.existsSync(file)) return null;
|
||
|
|
const entry = JSON.parse(fs.readFileSync(file, "utf8")) as CacheEntry<T>;
|
||
|
|
if (Date.now() > entry.expiresAt) {
|
||
|
|
fs.unlinkSync(file);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return entry.value;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function cacheSet<T>(namespace: string, key: string, value: T, ttlMs: number): void {
|
||
|
|
try {
|
||
|
|
const dir = path.join(CACHE_DIR, namespace);
|
||
|
|
fs.mkdirSync(dir, { recursive: true });
|
||
|
|
const file = path.join(dir, `${hashKey(key)}.json`);
|
||
|
|
const entry: CacheEntry<T> = { expiresAt: Date.now() + ttlMs, value };
|
||
|
|
fs.writeFileSync(file, JSON.stringify(entry));
|
||
|
|
} catch {
|
||
|
|
// cache 失敗不阻擋搜尋
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function clearSearchCacheForTests(): void {
|
||
|
|
if (fs.existsSync(CACHE_DIR)) {
|
||
|
|
fs.rmSync(CACHE_DIR, { recursive: true, force: true });
|
||
|
|
}
|
||
|
|
ensureDir();
|
||
|
|
}
|