fn-merge-cache
Version:
`FnMergeCache` is a caching utility that allows functions to cache their results based on input arguments, with options for cache lifetime, size limits, error handling, and parameter comparison, while supporting cache invalidation via tags and global reva
183 lines (181 loc) • 5.83 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
FnMergeCache: () => FnMergeCache,
createMergedCachedFn: () => createMergedCachedFn,
default: () => src_default,
revalidateTag: () => revalidateTag
});
module.exports = __toCommonJS(src_exports);
var import_lodash_es = require("lodash-es");
var import_events = require("events");
var _queueMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : (fn) => Promise.resolve().then(fn);
var revalidateEE = new import_events.EventEmitter();
var revalidateAllStr = "__FN_MERGE_CACHE_INSIDE__all";
var FnMergeCache = class {
/**
* Creates a new FnMergeCache instance
*
* @param fn - The original function to be cached
* @param options - Configuration options
* @param options.cache - Whether to enable caching
* @param options.cacheOnError - Whether to cache results when errors occur
* @param options.argComparer - Parameter comparison function, returns true if parameters are equal
* @param options.ttl - Cache lifetime in milliseconds, 0 means never expires
* @param options.maxCacheSize - Cache pool size limit, 0 means no limit
* @param options.tags - Tags for cache revalidation
*
* @throws Error when using reserved tag names
*/
constructor(fn, {
cache = true,
cacheOnError = false,
argComparer = import_lodash_es.isEqual,
ttl = 0,
maxCacheSize = 0,
tags = []
} = {}) {
this._disposed = false;
this._result = /* @__PURE__ */ new Map();
// arg: [time, result, error]
this._callGC = (0, import_lodash_es.throttle)(() => {
const now = Date.now();
for (const [k, v] of this._result) {
if (this._ttl && now - v[0] > this._ttl || this._maxCacheSize && this._result.size > this._maxCacheSize) {
this._result.delete(k);
} else {
break;
}
}
}, 1e3);
/**
* Clears all cached results
*/
this.revalidate = () => {
this._result.clear();
};
if (tags.includes(revalidateAllStr)) {
throw new Error(
`Tag name "${revalidateAllStr}" is reserved, please use another tag name`
);
}
this._fn = fn;
this._cache = cache;
this._cacheOnError = cacheOnError;
this._argComparer = argComparer;
this._ttl = ttl;
this._maxCacheSize = maxCacheSize;
this._tags = tags;
revalidateEE.on(revalidateAllStr, this.revalidate);
tags.forEach((tag) => revalidateEE.on(tag, this.revalidate));
}
/**
* Calls the cached function
*
* @param args - Arguments passed to the original function
* @returns Function return value, may be cached result
* @throws Error if instance is disposed or original function throws
*/
call(...args) {
if (this._disposed) {
throw new Error("FnMergeCache instance has been disposed");
}
if (this._cache && (this._ttl || this._maxCacheSize)) {
_queueMicrotask(this._callGC);
}
let resultKey;
for (const k of this._result.keys()) {
if (this._argComparer(k, args)) {
resultKey = k;
break;
}
}
if (resultKey) {
const result = this._result.get(resultKey);
if (!this._ttl || Date.now() - result[0] <= this._ttl) {
if (this._cache && !this._ttl && this._maxCacheSize) {
this._result.delete(resultKey);
this._result.set(resultKey, result);
}
if (result[2])
throw result[2];
return result[1];
}
this._result.delete(resultKey);
}
const now = Date.now();
try {
const fnResult = this._fn.call(void 0, ...args);
if (fnResult instanceof Promise) {
this._result.set(args, [now, fnResult]);
fnResult.then(
() => {
if (!this._cache) {
this._result.delete(args);
}
},
() => {
if (!this._cache || !this._cacheOnError) {
this._result.delete(args);
}
}
);
return fnResult;
} else {
if (this._cache) {
this._result.set(args, [now, fnResult]);
}
return fnResult;
}
} catch (e) {
if (this._cache && this._cacheOnError) {
this._result.set(args, [now, void 0, e]);
}
throw e;
}
}
/**
* Destroys the instance, clears all caches and event listeners
*/
dispose() {
this._disposed = true;
this._result.clear();
revalidateEE.off(revalidateAllStr, this.revalidate);
this._tags.forEach((tag) => revalidateEE.off(tag, this.revalidate));
}
};
var src_default = FnMergeCache;
function createMergedCachedFn(fn, opts) {
const cache = new FnMergeCache(fn, opts);
return cache.call.bind(cache);
}
function revalidateTag(tag = revalidateAllStr) {
if (Array.isArray(tag)) {
tag.forEach((t) => revalidateEE.emit(t));
} else {
revalidateEE.emit(tag);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FnMergeCache,
createMergedCachedFn,
revalidateTag
});