2025-01-06 16:14:29 +01:00
|
|
|
interface CreateCacheOptions {
|
|
|
|
expires?: number; // Default expiration time for all cache entries
|
|
|
|
}
|
|
|
|
|
|
|
|
interface SetCacheOptions {
|
|
|
|
expires?: number; // Override expiration for individual cache entries
|
|
|
|
}
|
|
|
|
|
2025-01-25 18:51:10 +01:00
|
|
|
export const caches = new Map<
|
2025-01-19 16:43:00 +01:00
|
|
|
string,
|
2025-01-25 18:51:10 +01:00
|
|
|
{
|
|
|
|
info: () => { name: string; count: number; sizeInKB: number };
|
|
|
|
clear: () => void;
|
|
|
|
}
|
2025-01-19 16:43:00 +01:00
|
|
|
>();
|
|
|
|
|
2025-01-06 16:14:29 +01:00
|
|
|
export function createCache<T>(
|
2025-01-19 16:43:00 +01:00
|
|
|
cacheName: string,
|
2025-01-06 16:14:29 +01:00
|
|
|
createOpts: CreateCacheOptions = {},
|
|
|
|
) {
|
|
|
|
const cache = new Map<string, { value: T; expiresAt?: number }>();
|
|
|
|
|
2025-01-19 16:43:00 +01:00
|
|
|
const api = {
|
2025-01-06 16:14:29 +01:00
|
|
|
get(key: string): T | undefined {
|
|
|
|
const entry = cache.get(key);
|
|
|
|
if (!entry) return undefined;
|
|
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
if (entry.expiresAt && entry.expiresAt <= now) {
|
|
|
|
cache.delete(key); // Remove expired entry
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return entry.value; // Return value if not expired
|
|
|
|
},
|
|
|
|
|
2025-01-25 18:51:10 +01:00
|
|
|
clear() {
|
|
|
|
cache.clear();
|
|
|
|
},
|
|
|
|
|
2025-01-06 16:14:29 +01:00
|
|
|
set(key: string, value: T | unknown, opts: SetCacheOptions = {}) {
|
|
|
|
const now = Date.now();
|
|
|
|
const expiresIn = opts.expires ?? createOpts.expires;
|
|
|
|
const expiresAt = expiresIn ? now + expiresIn : undefined;
|
|
|
|
|
|
|
|
cache.set(key, { value: value as T, expiresAt });
|
|
|
|
},
|
|
|
|
|
|
|
|
cleanup() {
|
|
|
|
const now = Date.now();
|
|
|
|
for (const [key, entry] of cache.entries()) {
|
|
|
|
if (entry.expiresAt && entry.expiresAt <= now) {
|
|
|
|
cache.delete(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2025-01-25 00:00:04 +01:00
|
|
|
info() {
|
2025-01-06 16:14:29 +01:00
|
|
|
// Cleanup expired entries before calculating info
|
|
|
|
this.cleanup();
|
|
|
|
|
|
|
|
// Count the number of objects in the cache
|
|
|
|
const count = cache.size;
|
|
|
|
|
|
|
|
// Approximate the size in KB by serializing each key and value
|
|
|
|
let totalBytes = 0;
|
|
|
|
for (const [key, entry] of cache.entries()) {
|
|
|
|
const keySize = new TextEncoder().encode(key).length;
|
|
|
|
const valueSize = new TextEncoder().encode(
|
|
|
|
JSON.stringify(entry.value),
|
|
|
|
).length;
|
|
|
|
totalBytes += keySize + valueSize;
|
|
|
|
}
|
|
|
|
|
2025-01-25 00:00:04 +01:00
|
|
|
const sizeInKB = Math.floor(totalBytes / 1024); // Convert bytes to kilobytes
|
|
|
|
return { name: cacheName, count, sizeInKB };
|
2025-01-06 16:14:29 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
has(key: string): boolean {
|
|
|
|
const entry = cache.get(key);
|
|
|
|
if (!entry) return false;
|
|
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
if (entry.expiresAt && entry.expiresAt <= now) {
|
|
|
|
cache.delete(key); // Remove expired entry
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
keys(): string[] {
|
|
|
|
this.cleanup(); // Cleanup before returning keys
|
|
|
|
return Array.from(cache.keys());
|
|
|
|
},
|
|
|
|
|
|
|
|
size(): number {
|
|
|
|
this.cleanup(); // Cleanup before returning size
|
|
|
|
return cache.size;
|
|
|
|
},
|
|
|
|
};
|
2025-01-19 16:43:00 +01:00
|
|
|
|
|
|
|
caches.set(cacheName, {
|
2025-01-25 18:51:10 +01:00
|
|
|
clear: api.clear.bind(api),
|
2025-01-19 16:43:00 +01:00
|
|
|
info: api.info.bind(api),
|
|
|
|
});
|
2025-01-25 00:45:22 +01:00
|
|
|
|
2025-01-19 16:43:00 +01:00
|
|
|
return api;
|
2025-01-06 16:14:29 +01:00
|
|
|
}
|
2025-01-25 00:00:04 +01:00
|
|
|
|
|
|
|
export function getCacheInfo() {
|
2025-01-25 00:45:22 +01:00
|
|
|
return [...caches.values().map((c) => c.info())];
|
2025-01-25 00:00:04 +01:00
|
|
|
}
|