cache
parent
a5e1d89b9d
commit
6da02e2e31
@ -0,0 +1,193 @@
|
||||
import { cache } from "beth-jsx";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
class BethPersistCache {
|
||||
private callBackMap: Map<
|
||||
string,
|
||||
{
|
||||
callBack: () => Promise<any>;
|
||||
tags: string[];
|
||||
cache: "memory" | "json";
|
||||
}
|
||||
>;
|
||||
private inMemoryDataCache: Map<string, any>;
|
||||
private jsonDataCache: Database;
|
||||
private intervals: Set<NodeJS.Timeout>;
|
||||
|
||||
constructor() {
|
||||
this.callBackMap = new Map();
|
||||
this.inMemoryDataCache = new Map();
|
||||
this.jsonDataCache = new Database("beth-cache.sqlite");
|
||||
this.intervals = new Set();
|
||||
|
||||
this.jsonDataCache.exec(`
|
||||
DROP TABLE IF EXISTS cache;
|
||||
`);
|
||||
|
||||
this.jsonDataCache.exec(`
|
||||
CREATE TABLE cache (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
private setJsonCache(key: string, value: any) {
|
||||
console.log("Seeding JSON Cache:", key, value);
|
||||
this.jsonDataCache.run(
|
||||
`
|
||||
INSERT INTO cache (key, value)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT (key) DO UPDATE SET value = excluded.value;
|
||||
`,
|
||||
[key, JSON.stringify(value)]
|
||||
);
|
||||
}
|
||||
|
||||
private async getJsonCache(key: string) {
|
||||
console.log("JSON Cache HIT:", key);
|
||||
const result = (await this.jsonDataCache
|
||||
.query("SELECT value FROM cache WHERE key = ?")
|
||||
.get(key)) as { value: string } | undefined;
|
||||
if (!result) throw new Error("JSON Cache Miss");
|
||||
return JSON.parse(result.value);
|
||||
}
|
||||
|
||||
public seed({
|
||||
key,
|
||||
value,
|
||||
tags,
|
||||
revalidate,
|
||||
callBack,
|
||||
cache,
|
||||
}: {
|
||||
cache: "memory" | "json";
|
||||
callBack: () => Promise<any>;
|
||||
key: string;
|
||||
value: any;
|
||||
tags: string[];
|
||||
revalidate: number;
|
||||
}) {
|
||||
console.log(`Seeding ${cache} Cache:`, key, value);
|
||||
if (cache === "memory") {
|
||||
this.inMemoryDataCache.set(key, value);
|
||||
} else if (cache === "json") {
|
||||
this.setJsonCache(key, value);
|
||||
}
|
||||
this.callBackMap.set(key, {
|
||||
callBack,
|
||||
tags,
|
||||
cache,
|
||||
});
|
||||
if (revalidate > 0) {
|
||||
console.log("Setting Revalidate Interval:", key, revalidate);
|
||||
this.setInterval(key, revalidate);
|
||||
}
|
||||
}
|
||||
|
||||
private rerunCallBack(key: string) {
|
||||
console.log("rerunning callback:", key);
|
||||
const result = this.callBackMap.get(key);
|
||||
if (!result) return;
|
||||
const { callBack, tags, cache } = result;
|
||||
const callBackPromise = callBack();
|
||||
callBackPromise.then((value) => {
|
||||
if (cache === "memory") {
|
||||
this.inMemoryDataCache.set(key, value);
|
||||
} else if (cache === "json") {
|
||||
this.setJsonCache(key, value);
|
||||
}
|
||||
this.callBackMap.set(key, {
|
||||
callBack,
|
||||
tags,
|
||||
cache,
|
||||
});
|
||||
});
|
||||
return callBackPromise;
|
||||
}
|
||||
|
||||
private setInterval(key: string, revalidate: number) {
|
||||
if (revalidate === Infinity) return;
|
||||
const interval = setInterval(async () => {
|
||||
console.log(`Cache Revalidating (on ${revalidate}s interval):`, key);
|
||||
this.rerunCallBack(key);
|
||||
}, revalidate);
|
||||
this.intervals.add(interval);
|
||||
}
|
||||
|
||||
private async getMemoryCache(key: string) {
|
||||
const cacheResult = this.inMemoryDataCache.get(key);
|
||||
if (cacheResult) {
|
||||
console.log("Memory Cache HIT:", key);
|
||||
return cacheResult;
|
||||
} else {
|
||||
throw new Error("Memory Cache Miss");
|
||||
}
|
||||
}
|
||||
|
||||
public revalidateTag(tag: string) {
|
||||
console.log("Revalidating tag:", tag);
|
||||
this.callBackMap.forEach((value, key) => {
|
||||
if (value.tags.includes(tag)) {
|
||||
this.rerunCallBack(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getCachedValue(key: string, cache: "memory" | "json") {
|
||||
try {
|
||||
if (cache === "memory") {
|
||||
return this.getMemoryCache(key);
|
||||
} else if (cache === "json") {
|
||||
return this.getJsonCache(key);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e.message === "Memory Cache Miss") {
|
||||
return this.rerunCallBack(key);
|
||||
} else if (e.message === "JSON Cache Miss") {
|
||||
return this.rerunCallBack(key);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GLOBAL_CACHE = new BethPersistCache();
|
||||
|
||||
type CacheOptions = {
|
||||
persist?: "memory" | "json";
|
||||
revalidate?: number;
|
||||
tags?: string[];
|
||||
};
|
||||
|
||||
export function persistedCache<T extends () => Promise<any>>(
|
||||
callBack: T,
|
||||
key: string,
|
||||
options?: CacheOptions
|
||||
): T {
|
||||
const persist = options?.persist ?? "memory";
|
||||
const revalidate = options?.revalidate ?? Infinity;
|
||||
const tags = options?.tags ?? [];
|
||||
|
||||
console.log("Cache MISS: ", key);
|
||||
callBack().then((result) => {
|
||||
GLOBAL_CACHE.seed({
|
||||
callBack,
|
||||
key,
|
||||
value: result,
|
||||
tags,
|
||||
revalidate,
|
||||
cache: persist,
|
||||
});
|
||||
});
|
||||
return cache(() => GLOBAL_CACHE.getCachedValue(key, persist)) as T;
|
||||
}
|
||||
|
||||
export function revalidateTag(tag: string) {
|
||||
GLOBAL_CACHE.revalidateTag(tag);
|
||||
}
|
Loading…
Reference in New Issue