UNPKG

@scayle/storefront-core

Version:

Collection of essential utilities to work with the Storefront API

153 lines (152 loc) 4.24 kB
import { timeout } from "../utils/index.mjs"; import { sha256 } from "../utils/hash.mjs"; const CACHE_TIMEOUT = 1e3; export const MINUTE = 60; const CACHE_NOT_INITIALIZED_MSG = "Cache is not initialized"; export class Cached { cache; log; prefix; enabled; /** * Creates a new Cached instance. * * @param cache The cache implementation to use. * @param log The logger instance. * @param prefix An optional prefix for cache keys. * @param enabled Whether caching is enabled. * * @throws {Error} If the cache is not initialized. */ constructor(cache, log, prefix = "", enabled) { if (!cache) { log.error(CACHE_NOT_INITIALIZED_MSG); throw new Error(CACHE_NOT_INITIALIZED_MSG); } this.cache = cache; this.log = log.space("cached"); this.prefix = prefix; this.enabled = enabled; } /** * Whether caching is enabled. */ get isCacheEnabled() { return this.enabled ?? false; } /** * Executes a function and caches its result. * * @param fn The asynchronous function to execute. * @param options Optional cache options. * * @returns A wrapped function that handles caching. * * @template TArgs The type of the function's arguments. * @template TResult The return type of the function. */ execute(fn, options) { return async (...args) => { const prefix = options?.cacheKeyPrefix ?? fn.name; const params = [...args, this?.prefix]; const cacheKey = options?.cacheKey ?? await this.createCacheKey(params, prefix); try { const cachedResponse = await this.getCacheValue(cacheKey, options); if (cachedResponse !== void 0 && cachedResponse !== null) { return cachedResponse; } } catch (e) { if (e instanceof Error) { this.handleError(e, "getting"); } } const response = await fn(...args); if (response === void 0 || response === null) { return response; } if (response instanceof Response && !response.ok) { return response; } try { await this.setCacheValue(cacheKey, response, options); } catch (e) { if (e instanceof Error) { this.handleError(e, "setting"); } } return response; }; } /** * Retrieves a cached value from the cache. * * @param cacheKey The key of the cached value. * @param options Optional cache options. * * @returns A promise that resolves with the cached value or undefined if not found or caching is disabled. * * @private */ async getCacheValue(cacheKey, options) { if (!this.isCacheEnabled) { return; } const data = await timeout( options?.timeout ?? CACHE_TIMEOUT, this.cache.get(cacheKey) ); if (data !== null) { return data; } } /** * Sets a value in the cache. * * @param cacheKey The key to store the value under. * @param value The value to store in the cache. * @param options Optional cache options. * * @returns A promise that resolves when the value is set or undefined if caching is disabled. * * @private */ async setCacheValue(cacheKey, value, options) { if (!this.isCacheEnabled) { return; } const ttl = options?.ttl ?? 60 * MINUTE; if (ttl) { await timeout(CACHE_TIMEOUT, this.cache.set(cacheKey, value, ttl)); } } /** * Creates a cache key from the given parameters and prefix. * * @param params The parameters to use for generating the key. * @param prefix An optional prefix for the key. * * @returns The generated cache key. * * @private */ async createCacheKey(params, prefix = "") { return [prefix, await sha256(JSON.stringify(params))].join(":"); } /** * Handles errors during cache operations. * * @param error The error that occurred. * @param operation The operation which caused the error * * @private */ handleError(error, operation) { if (error.message === "timeout") { return this.log.error( `Cache timeout while ${operation} cache value`, error ); } this.log.error(`Error while ${operation} cache value`, error); } }