@scayle/storefront-core
Version:
Collection of essential utilities to work with the Storefront API
153 lines (152 loc) • 4.24 kB
JavaScript
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);
}
}