From 731499be813efd027a4bd88b58341f16cae76dbe Mon Sep 17 00:00:00 2001 From: Huskydog9988 <39809509+Huskydog9988@users.noreply.github.com> Date: Wed, 7 May 2025 22:13:22 -0400 Subject: [PATCH] feat: unified cache handler --- nuxt.config.ts | 14 ++++++++++ package.json | 4 +-- server/internal/cache/cacheHandler.ts | 33 ++++++++++++++++++++++ server/internal/cache/index.ts | 4 +++ server/internal/objects/fsBackend.ts | 40 ++++++++++++++------------- server/internal/session/db.ts | 15 ++++------ server/internal/userlibrary/index.ts | 10 ++++--- 7 files changed, 86 insertions(+), 34 deletions(-) create mode 100644 server/internal/cache/cacheHandler.ts create mode 100644 server/internal/cache/index.ts diff --git a/nuxt.config.ts b/nuxt.config.ts index 5fb4b93..e4e014d 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -48,6 +48,20 @@ export default defineNuxtConfig({ }, compressPublicAssets: true, + + storage: { + appCache: { + driver: "lru-cache", + }, + }, + + devStorage: { + appCache: { + // store cache on fs to handle dev server restarts + driver: "fs", + base: "./.data/appCache", + }, + }, }, typescript: { diff --git a/package.json b/package.json index 5489fb3..ea7fe5b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "fast-fuzzy": "^1.12.0", "file-type-mime": "^0.4.3", "jdenticon": "^3.3.0", - "lru-cache": "^11.1.0", "luxon": "^3.6.1", "micromark": "^4.0.1", "nuxt": "^3.16.2", @@ -40,6 +39,7 @@ "sharp": "^0.33.5", "stream-mime-type": "^2.0.0", "turndown": "^7.2.0", + "unstorage": "^1.15.0", "vue": "latest", "vue-router": "latest", "vue3-carousel": "^0.15.0", @@ -75,4 +75,4 @@ "prisma": { "schema": "./prisma" } -} +} \ No newline at end of file diff --git a/server/internal/cache/cacheHandler.ts b/server/internal/cache/cacheHandler.ts new file mode 100644 index 0000000..c42c072 --- /dev/null +++ b/server/internal/cache/cacheHandler.ts @@ -0,0 +1,33 @@ +import { prefixStorage, type StorageValue, type Storage } from "unstorage"; + +export interface CacheProviderOptions { + /** + * Max number of items in the cache + */ + max?: number; + + /** + * Time to live (in ms) + */ + ttl?: number; +} + +/** + * Creates and manages the lifecycles of various caches + */ +export class CacheHandler { + private caches = new Map>(); + + /** + * Create a new cache + * @param name + * @returns + */ + createCache(name: string) { + // will allow us to dynamicing use redis in the future just by changing the storage used + const provider = prefixStorage(useStorage("appCache"), name); + // hack to let ts have us store cache + this.caches.set(name, provider as unknown as Storage); + return provider; + } +} diff --git a/server/internal/cache/index.ts b/server/internal/cache/index.ts new file mode 100644 index 0000000..6a6035d --- /dev/null +++ b/server/internal/cache/index.ts @@ -0,0 +1,4 @@ +import { CacheHandler } from "./cacheHandler"; + +export const cacheHandler = new CacheHandler(); +export default cacheHandler; diff --git a/server/internal/objects/fsBackend.ts b/server/internal/objects/fsBackend.ts index 4f3aaae..0556622 100644 --- a/server/internal/objects/fsBackend.ts +++ b/server/internal/objects/fsBackend.ts @@ -1,12 +1,12 @@ import type { ObjectMetadata, ObjectReference, Source } from "./objectHandler"; import { ObjectBackend } from "./objectHandler"; -import { LRUCache } from "lru-cache"; import fs from "fs"; import path from "path"; import { Readable } from "stream"; import { createHash } from "crypto"; import prisma from "../db/database"; +import cacheHandler from "../cache"; export class FsObjectBackend extends ObjectBackend { private baseObjectPath: string; @@ -34,7 +34,7 @@ export class FsObjectBackend extends ObjectBackend { if (!fs.existsSync(objectPath)) return false; // remove item from cache - this.hashStore.delete(id); + await this.hashStore.delete(id); if (source instanceof Readable) { const outputStream = fs.createWriteStream(objectPath); @@ -54,7 +54,7 @@ export class FsObjectBackend extends ObjectBackend { const objectPath = path.join(this.baseObjectPath, id); if (!fs.existsSync(objectPath)) return undefined; // remove item from cache - this.hashStore.delete(id); + await this.hashStore.delete(id); return fs.createWriteStream(objectPath); } async create( @@ -99,7 +99,7 @@ export class FsObjectBackend extends ObjectBackend { if (!fs.existsSync(objectPath)) return true; fs.rmSync(objectPath); // remove item from cache - this.hashStore.delete(id); + await this.hashStore.delete(id); return true; } async fetchMetadata( @@ -121,36 +121,35 @@ export class FsObjectBackend extends ObjectBackend { } async fetchHash(id: ObjectReference): Promise { const cacheResult = await this.hashStore.get(id); - if (cacheResult !== undefined) return cacheResult; + if (cacheResult !== null) return cacheResult; const obj = await this.fetch(id); if (obj === undefined) return; - // local variable to point to object - const cache = this.hashStore; - // hash object const hash = createHash("md5"); hash.setEncoding("hex"); + // local variable to point to object + const store = this.hashStore; + // read obj into hash obj.pipe(hash); await new Promise((r) => { - obj.on("end", function () { + obj.on("end", async function () { hash.end(); - cache.save(id, hash.read()); + await store.save(id, hash.read()); r(); }); }); - return await this.hashStore.get(id); + const result = await this.hashStore.get(id); + return result === null ? undefined : result; } } class FsHashStore { - private cache = new LRUCache({ - max: 1000, // number of items - }); + private cache = cacheHandler.createCache("ObjectHashStore"); /** * Gets hash of object @@ -158,8 +157,11 @@ class FsHashStore { * @returns */ async get(id: ObjectReference) { - const cacheRes = this.cache.get(id); - if (cacheRes !== undefined) return cacheRes; + const cacheRes = await this.cache.get(id); + if (cacheRes !== null) { + console.log("object cache hit"); + return cacheRes; + } const objectHash = await prisma.objectHash.findUnique({ where: { @@ -170,7 +172,7 @@ class FsHashStore { }, }); if (objectHash === null) return undefined; - this.cache.set(id, objectHash.hash); + await this.cache.set(id, objectHash.hash); return objectHash.hash; } @@ -191,7 +193,7 @@ class FsHashStore { hash, }, }); - this.cache.set(id, hash); + await this.cache.set(id, hash); } /** @@ -199,7 +201,7 @@ class FsHashStore { * @param id */ async delete(id: ObjectReference) { - this.cache.delete(id); + await this.cache.remove(id); try { // need to catch in case the object doesn't exist diff --git a/server/internal/session/db.ts b/server/internal/session/db.ts index 58f52a8..5b8fd2e 100644 --- a/server/internal/session/db.ts +++ b/server/internal/session/db.ts @@ -1,16 +1,13 @@ -import { LRUCache } from "lru-cache"; import prisma from "../db/database"; import type { Session, SessionProvider } from "./types"; +import cacheHandler from "../cache"; export default function createDBSessionHandler(): SessionProvider { - const cache = new LRUCache({ - max: 50, // number of items - ttl: 30 * 100, // 30s (in ms) - }); + const cache = cacheHandler.createCache("DBSession"); return { async setSession(token, session) { - cache.set(token, session); + await cache.set(token, session); // const strData = JSON.stringify(data); await prisma.session.upsert({ @@ -29,8 +26,8 @@ export default function createDBSessionHandler(): SessionProvider { return await this.setSession(token, data); }, async getSession(token: string) { - const cached = cache.get(token); - if (cached !== undefined) return cached as T; + const cached = await cache.get(token); + if (cached !== null) return cached as T; const result = await prisma.session.findUnique({ where: { @@ -45,7 +42,7 @@ export default function createDBSessionHandler(): SessionProvider { return result as unknown as T; }, async removeSession(token) { - cache.delete(token); + await cache.remove(token); await prisma.session.delete({ where: { token, diff --git a/server/internal/userlibrary/index.ts b/server/internal/userlibrary/index.ts index 537ac8f..16db484 100644 --- a/server/internal/userlibrary/index.ts +++ b/server/internal/userlibrary/index.ts @@ -2,15 +2,17 @@ Handles managing collections */ +import cacheHandler from "../cache"; import prisma from "../db/database"; class UserLibraryManager { // Caches the user's core library - private userCoreLibraryCache: { [key: string]: string } = {}; + private coreLibraryCache = + cacheHandler.createCache("UserCoreLibrary"); private async fetchUserLibrary(userId: string) { - if (this.userCoreLibraryCache[userId]) - return this.userCoreLibraryCache[userId]; + const cached = await this.coreLibraryCache.get(userId); + if (cached !== null) return cached; let collection = await prisma.collection.findFirst({ where: { @@ -28,7 +30,7 @@ class UserLibraryManager { }, }); - this.userCoreLibraryCache[userId] = collection.id; + await this.coreLibraryCache.set(userId, collection.id); return collection.id; }