Dynamic SWR cache

Created by Anonymous
Public

Created March 16, 2024 Expires in 36428 days

Loading editor...

interface CacheEntry<T> {
    value: T;
    timestamp: number; // Timestamp when the data was fetched
}

interface Options {
    swr: number; // Time in seconds to allow stale data to be used while revalidating
    maxAge: number; // Maximum age in seconds before data is considered stale
    debug: boolean; // Enable debug mode
}

const defaultOptions: Options = {
    swr: 0,
    maxAge: 0,
    debug: false,
};

export const createAsyncCache = <T, Args extends any[]>(
    func: (...args: Args) => Promise<T>,
    paramOptions: Partial<Options> = {}
) => {
    const options: Options = { ...defaultOptions, ...paramOptions };
    const cache: Record<string, CacheEntry<T>> = {};

    const debugLog = (message: string, ...data: any[]) => {
        if (options.debug) {
            console.log(`[AsyncCache]: ${message}`, ...data);
        }
    };

    const fetchAndCache = async (...args: Args): Promise<T> => {
        const key = JSON.stringify(args);
        try {
            const result = await func(...args);
            cache[key] = { value: result, timestamp: Date.now() };
            return result;
        } catch (error) {
            debugLog(`Error fetching data for key ${key}:`, error);
            throw error; // Rethrow to handle upstream
        }
    };

    const isEntryStale = (entry: CacheEntry<T> | undefined): boolean => {
        if (!entry) return true;
        const ageInSeconds = (Date.now() - entry.timestamp) / 1000;
        return ageInSeconds > options.maxAge;
    };

    const get = async (...args: Args): Promise<T> => {
        const key = JSON.stringify(args);
        const entry = cache[key];
        const entryExists = !!entry;
        const stale = isEntryStale(entry);

        if (!entryExists || stale) {
            if (entryExists && stale) {
                const ageInSeconds = (Date.now() - entry.timestamp) / 1000;
                if (ageInSeconds <= options.maxAge + options.swr) {
                    debugLog("STALE (revalidating)", { key, ageInSeconds });
                    fetchAndCache(...args).catch(error =>
                        debugLog(`Error revalidating data for key ${key}:`, error)
                    );
                } else {
                    debugLog("STALE (no revalidation)", { key, ageInSeconds });
                    return fetchAndCache(...args);
                }
            } else {
                debugLog("MISS", { key });
                return fetchAndCache(...args);
            }
        } else {
            debugLog("HIT", { key });
        }

        return entry.value;
    };

    return { get };
};