feat: lru cache
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"@inlang/paraglide-js": "^1.11.0",
|
"@inlang/paraglide-js": "^1.11.0",
|
||||||
"@inlang/paraglide-sveltekit": "^0.16.1",
|
"@inlang/paraglide-sveltekit": "^0.16.1",
|
||||||
"@ts-stack/markdown": "^1.5.0",
|
"@ts-stack/markdown": "^1.5.0",
|
||||||
|
"lru-cache": "^11.2.4",
|
||||||
"mime-types": "^3.0.2",
|
"mime-types": "^3.0.2",
|
||||||
"svelte-preprocess": "^6.0.2",
|
"svelte-preprocess": "^6.0.2",
|
||||||
"webdav": "^5.8.0"
|
"webdav": "^5.8.0"
|
||||||
|
|||||||
Generated
+9
@@ -20,6 +20,9 @@ importers:
|
|||||||
'@ts-stack/markdown':
|
'@ts-stack/markdown':
|
||||||
specifier: ^1.5.0
|
specifier: ^1.5.0
|
||||||
version: 1.5.0
|
version: 1.5.0
|
||||||
|
lru-cache:
|
||||||
|
specifier: ^11.2.4
|
||||||
|
version: 11.2.4
|
||||||
mime-types:
|
mime-types:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
@@ -1269,6 +1272,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
|
resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
|
||||||
engines: {node: 14 || >=16.14}
|
engines: {node: 14 || >=16.14}
|
||||||
|
|
||||||
|
lru-cache@11.2.4:
|
||||||
|
resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}
|
||||||
|
engines: {node: 20 || >=22}
|
||||||
|
|
||||||
magic-string@0.30.10:
|
magic-string@0.30.10:
|
||||||
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
|
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
|
||||||
|
|
||||||
@@ -2988,6 +2995,8 @@ snapshots:
|
|||||||
|
|
||||||
lru-cache@10.2.2: {}
|
lru-cache@10.2.2: {}
|
||||||
|
|
||||||
|
lru-cache@11.2.4: {}
|
||||||
|
|
||||||
magic-string@0.30.10:
|
magic-string@0.30.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
|
|||||||
+45
-5
@@ -1,5 +1,44 @@
|
|||||||
const cache = new Map<string, { timestamp: number; data: any }>();
|
import { LRUCache } from "lru-cache";
|
||||||
|
|
||||||
|
// const cache = new Map<string, { timestamp: number; data: any }>();
|
||||||
const DEFAULT_TTL = 1000 * 60 * 15; // 15 minutes default TTL
|
const DEFAULT_TTL = 1000 * 60 * 15; // 15 minutes default TTL
|
||||||
|
const MAX_CACHE_SIZE = 1000 * 1000 * 750; // 750 MB
|
||||||
|
|
||||||
|
const cache = new LRUCache({
|
||||||
|
max: 100,
|
||||||
|
// Maximum allowed size (in bytes)
|
||||||
|
maxSize: MAX_CACHE_SIZE,
|
||||||
|
|
||||||
|
// IMPORTANT: We must teach the cache how to calculate the size of an item.
|
||||||
|
// We check for your specific image structure, generic buffers, or fallback to JSON length.
|
||||||
|
sizeCalculation: (value) => {
|
||||||
|
// 1. Check if it's our specific image object { buffer: ArrayBuffer, ... }
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
const obj = value as Record<string, any>;
|
||||||
|
if (obj.buffer instanceof ArrayBuffer) {
|
||||||
|
return obj.buffer.byteLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Handle raw ArrayBuffers or Node.js Buffers
|
||||||
|
if (value instanceof ArrayBuffer) {
|
||||||
|
return value.byteLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Buffer.isBuffer(value)) {
|
||||||
|
return value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fallback for strings or JSON metadata
|
||||||
|
try {
|
||||||
|
const str = JSON.stringify(value);
|
||||||
|
return str ? str.length : 1;
|
||||||
|
} catch {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ttl: DEFAULT_TTL,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caches the result of a promise-returning function.
|
* Caches the result of a promise-returning function.
|
||||||
@@ -12,16 +51,17 @@ export async function withCache<T>(
|
|||||||
fetcher: () => Promise<T>,
|
fetcher: () => Promise<T>,
|
||||||
ttl = DEFAULT_TTL,
|
ttl = DEFAULT_TTL,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const now = Date.now();
|
|
||||||
const entry = cache.get(key);
|
const entry = cache.get(key);
|
||||||
|
|
||||||
if (entry && now - entry.timestamp < ttl) {
|
if (entry) {
|
||||||
return entry.data as T;
|
return entry as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await fetcher();
|
const data = await fetcher();
|
||||||
cache.set(key, { timestamp: now, data });
|
if (data) {
|
||||||
|
cache.set(key, data, { ttl: ttl });
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching data for key ${key}:`, error);
|
console.error(`Error fetching data for key ${key}:`, error);
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import mime from "mime-types";
|
|||||||
import { env } from "$env/dynamic/private";
|
import { env } from "$env/dynamic/private";
|
||||||
import { withCache } from "$lib/server/cache";
|
import { withCache } from "$lib/server/cache";
|
||||||
|
|
||||||
|
const DEFAULT_TTL = 1000 * 60 * 15; // 15min
|
||||||
|
const EXTENDED_TTL = 1000 * 60 * 60 * 24 * 7; // 1 week
|
||||||
|
|
||||||
// Define what we are storing in the cache
|
// Define what we are storing in the cache
|
||||||
type CachedAsset = {
|
type CachedAsset = {
|
||||||
buffer: ArrayBuffer;
|
buffer: ArrayBuffer;
|
||||||
@@ -24,6 +27,8 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
|||||||
// 1. Create a unique cache key for this specific asset variation
|
// 1. Create a unique cache key for this specific asset variation
|
||||||
const cacheKey = `asset:${path}?w=${width ?? ""}&h=${height ?? ""}`;
|
const cacheKey = `asset:${path}?w=${width ?? ""}&h=${height ?? ""}`;
|
||||||
|
|
||||||
|
const ttl = path.toString().includes("cover") ? DEFAULT_TTL : EXTENDED_TTL;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 2. Use withCache to wrap the fetching logic
|
// 2. Use withCache to wrap the fetching logic
|
||||||
const cachedData = await withCache<CachedAsset>(
|
const cachedData = await withCache<CachedAsset>(
|
||||||
@@ -113,8 +118,7 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
|||||||
throw lastError || new Error("Asset not found");
|
throw lastError || new Error("Asset not found");
|
||||||
// --- END OF FETCH LOGIC ---
|
// --- END OF FETCH LOGIC ---
|
||||||
},
|
},
|
||||||
// Optional: Set a specific TTL for images (e.g., 1 hour instead of default 15m)
|
ttl,
|
||||||
1000 * 60 * 60,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Construct a fresh Response using the cached data
|
// 3. Construct a fresh Response using the cached data
|
||||||
|
|||||||
Reference in New Issue
Block a user