@trifrost/core
Version:
Blazingly fast, runtime-agnostic server framework for modern edge and node environments
110 lines (109 loc) • 4.32 kB
JavaScript
import { isNeString } from '@valkyriestudios/utils/string';
import { ctx as getCtx } from '../../utils/Als';
export const Sym_TriFrostCached = Symbol('trifrost.cache.cached');
export const Sym_TriFrostSkipCache = Symbol('trifrost.cache.skip');
function hasCache(val) {
return val?.cache && typeof val.cache.get === 'function' && typeof val.cache.set === 'function';
}
/**
* Resolve a TriFrostCache instance from any combination of:
* - ALS-bound context (if available)
* - First argument (if it's a TriFrostContext)
* - `this.cache` or `this.ctx.cache` (for instance methods)
*/
function resolveCache(self, args) {
// ALS-bound context
const ctxAls = getCtx();
if (hasCache(ctxAls))
return ctxAls?.cache;
// First argument
const ctxArg = Array.isArray(args) && args.length ? args[0] : undefined;
if (hasCache(ctxArg))
return ctxArg?.cache;
// Fallback to self.cache
if (hasCache(self))
return self.cache;
// Fallback to self.ctx.cache
if (hasCache(self?.ctx))
return self.ctx.cache;
return null;
}
export function cache(key, opts) {
return function (method) {
/* Prevent re-decoration */
if (Reflect.get(method, Sym_TriFrostCached))
return method;
const wrapped = async function (...args) {
/* Get trifrost cache either from passed ctx, this.cache or this.ctx.cache */
const trifrost_cache = resolveCache(this, args);
/* No viable cache found */
if (!trifrost_cache)
return method.call(this, ...args);
/* Determine cache key */
const ckey = typeof key === 'function' ? key(...args.slice(0, key.length)) : isNeString(key) ? key : null;
if (!ckey)
return method.call(this, ...args);
/* Retrieve from cache, if exists -> return */
const cached = await trifrost_cache.get(ckey);
if (cached !== null && cached !== undefined)
return cached;
/* Run method */
const result = await method.call(this, ...args);
if (Object.prototype.toString.call(result) === '[object Object]' &&
Reflect.get(result, Sym_TriFrostSkipCache))
return result.value;
/* Cache */
await trifrost_cache.set(ckey, result, opts);
return result;
};
/* Set to prevent re-decoration */
Reflect.set(wrapped, Sym_TriFrostCached, true);
return wrapped;
};
}
/**
* Marks a value as "do not cache", will still return the value directly from the method.
*/
export function cacheSkip(value) {
const v = { value };
Reflect.set(v, Sym_TriFrostSkipCache, true);
return v;
}
/**
* Returns whether or not a value is a cache skip value
*/
export function cacheSkipped(v) {
return (Object.prototype.toString.call(v) === '[object Object]' && Reflect.get(v, Sym_TriFrostSkipCache) === true);
}
export function cacheFn(key, opts) {
return function (fn) {
/* Prevent re-decoration */
if (Reflect.get(fn, Sym_TriFrostCached))
return fn;
const wrapped = async function (...args) {
/* Get trifrost cache either from passed ctx, this.cache or this.ctx.cache */
const trifrost_cache = resolveCache(this, args);
/* No viable cache found */
if (!trifrost_cache)
return fn.apply(this, args);
/* Determine cache key */
const ckey = typeof key === 'function' ? key(...args.slice(0, key.length)) : isNeString(key) ? key : null;
if (!ckey)
return fn.apply(this, args);
/* Retrieve from cache, if exists -> return */
const cached = await trifrost_cache.get(ckey);
if (cached !== null && cached !== undefined)
return cached;
/* Run method */
const result = await fn.apply(this, args);
if (cacheSkipped(result))
return result.value;
/* Cache */
await trifrost_cache.set(ckey, result, opts);
return result;
};
/* Set to prevent re-decoration */
Reflect.set(wrapped, Sym_TriFrostCached, true);
return wrapped;
};
}