@modern-js/runtime-utils
Version:
A Progressive React Framework for modern web development.
279 lines (278 loc) • 8.61 kB
JavaScript
import { LRUCache } from "lru-cache";
import { getAsyncLocalStorage } from "./async_storage";
const CacheSize = {
KB: 1024,
MB: 1024 * 1024,
GB: 1024 * 1024 * 1024
};
const CacheTime = {
SECOND: 1e3,
MINUTE: 60 * 1e3,
HOUR: 60 * 60 * 1e3,
DAY: 24 * 60 * 60 * 1e3,
WEEK: 7 * 24 * 60 * 60 * 1e3,
MONTH: 30 * 24 * 60 * 60 * 1e3
};
const isServer = typeof window === "undefined";
const requestCacheMap = /* @__PURE__ */ new WeakMap();
let lruCache;
let cacheConfig = {
maxSize: CacheSize.GB
};
const tagKeyMap = /* @__PURE__ */ new Map();
function addTagKeyRelation(tag, key) {
let keys = tagKeyMap.get(tag);
if (!keys) {
keys = /* @__PURE__ */ new Set();
tagKeyMap.set(tag, keys);
}
keys.add(key);
}
function configureCache(config) {
cacheConfig = {
...cacheConfig,
...config
};
}
function getLRUCache() {
if (!lruCache) {
var _cacheConfig_maxSize;
lruCache = new LRUCache({
maxSize: (_cacheConfig_maxSize = cacheConfig.maxSize) !== null && _cacheConfig_maxSize !== void 0 ? _cacheConfig_maxSize : CacheSize.GB,
sizeCalculation: (value) => {
if (!value.size) {
return 1;
}
let size = 0;
for (const [k, item] of value.entries()) {
size += k.length * 2;
size += estimateObjectSize(item.data);
size += 8;
}
return size;
},
updateAgeOnGet: true,
updateAgeOnHas: true
});
}
return lruCache;
}
function estimateObjectSize(data) {
const type = typeof data;
if (type === "number")
return 8;
if (type === "boolean")
return 4;
if (type === "string")
return Math.max(data.length * 2, 1);
if (data === null || data === void 0)
return 1;
if (ArrayBuffer.isView(data)) {
return Math.max(data.byteLength, 1);
}
if (Array.isArray(data)) {
return Math.max(data.reduce((acc, item) => acc + estimateObjectSize(item), 0), 1);
}
if (data instanceof Map || data instanceof Set) {
return 1024;
}
if (data instanceof Date) {
return 8;
}
if (type === "object") {
return Math.max(Object.entries(data).reduce((acc, [key, value]) => acc + key.length * 2 + estimateObjectSize(value), 0), 1);
}
return 1;
}
function generateKey(args) {
return JSON.stringify(args, (_, value) => {
if (value && typeof value === "object" && !Array.isArray(value)) {
return Object.keys(value).sort().reduce((result, key) => {
result[key] = value[key];
return result;
}, {});
}
return value;
});
}
function cache(fn, options) {
const { tag = "default", maxAge = CacheTime.MINUTE * 5, revalidate = 0, customKey, onCache, getKey } = options || {};
const store = getLRUCache();
const tags = Array.isArray(tag) ? tag : [
tag
];
const getCacheKey = (args, generatedKey) => {
return customKey ? customKey({
params: args,
fn,
generatedKey
}) : fn;
};
return async (...args) => {
if (isServer && typeof options === "undefined") {
var _storage_useContext;
const storage = getAsyncLocalStorage();
const request = storage === null || storage === void 0 ? void 0 : (_storage_useContext = storage.useContext()) === null || _storage_useContext === void 0 ? void 0 : _storage_useContext.request;
if (request) {
let shouldDisableCaching = false;
if (cacheConfig.unstable_shouldDisable) {
shouldDisableCaching = await cacheConfig.unstable_shouldDisable({
request
});
}
if (shouldDisableCaching) {
return fn(...args);
}
let requestCache = requestCacheMap.get(request);
if (!requestCache) {
requestCache = /* @__PURE__ */ new Map();
requestCacheMap.set(request, requestCache);
}
let fnCache = requestCache.get(fn);
if (!fnCache) {
fnCache = /* @__PURE__ */ new Map();
requestCache.set(fn, fnCache);
}
const key = generateKey(args);
if (fnCache.has(key)) {
return fnCache.get(key);
}
const promise = fn(...args);
fnCache.set(key, promise);
try {
const data = await promise;
return data;
} catch (error) {
fnCache.delete(key);
throw error;
}
}
} else if (typeof options !== "undefined") {
const genKey = getKey ? getKey(...args) : generateKey(args);
const now = Date.now();
const cacheKey = getCacheKey(args, genKey);
const finalKey = typeof cacheKey === "function" ? genKey : cacheKey;
tags.forEach((t) => addTagKeyRelation(t, cacheKey));
let cacheStore = store.get(cacheKey);
if (!cacheStore) {
cacheStore = /* @__PURE__ */ new Map();
}
const storeKey = customKey && typeof cacheKey === "symbol" ? "symbol-key" : genKey;
let shouldDisableCaching = false;
if (isServer && cacheConfig.unstable_shouldDisable) {
var _storage_useContext1;
const storage = getAsyncLocalStorage();
const request = storage === null || storage === void 0 ? void 0 : (_storage_useContext1 = storage.useContext()) === null || _storage_useContext1 === void 0 ? void 0 : _storage_useContext1.request;
if (request) {
shouldDisableCaching = await cacheConfig.unstable_shouldDisable({
request
});
}
}
const cached = cacheStore.get(storeKey);
if (cached && !shouldDisableCaching) {
const age = now - cached.timestamp;
if (age < maxAge) {
if (onCache) {
onCache({
status: "hit",
key: finalKey,
params: args,
result: cached.data
});
}
return cached.data;
}
if (revalidate > 0 && age < maxAge + revalidate) {
if (onCache) {
onCache({
status: "stale",
key: finalKey,
params: args,
result: cached.data
});
}
if (!cached.isRevalidating) {
cached.isRevalidating = true;
Promise.resolve().then(async () => {
try {
const newData = await fn(...args);
cacheStore.set(storeKey, {
data: newData,
timestamp: Date.now(),
isRevalidating: false
});
store.set(cacheKey, cacheStore);
} catch (error) {
cached.isRevalidating = false;
if (isServer) {
var _storage_useContext_monitors, _storage_useContext2;
const storage = getAsyncLocalStorage();
storage === null || storage === void 0 ? void 0 : (_storage_useContext2 = storage.useContext()) === null || _storage_useContext2 === void 0 ? void 0 : (_storage_useContext_monitors = _storage_useContext2.monitors) === null || _storage_useContext_monitors === void 0 ? void 0 : _storage_useContext_monitors.error(error.message);
} else {
console.error("Background revalidation failed:", error);
}
}
});
}
return cached.data;
}
}
const data = await fn(...args);
if (!shouldDisableCaching) {
cacheStore.set(storeKey, {
data,
timestamp: now,
isRevalidating: false
});
store.set(cacheKey, cacheStore);
}
if (onCache) {
onCache({
status: "miss",
key: finalKey,
params: args,
result: data
});
}
return data;
} else {
console.warn("The cache function will not work because it runs on the browser and there are no options are provided.");
return fn(...args);
}
};
}
function withRequestCache(handler) {
if (!isServer) {
return handler;
}
return async (req, ...args) => {
const storage = getAsyncLocalStorage();
return storage.run({
request: req
}, () => handler(req, ...args));
};
}
function revalidateTag(tag) {
const keys = tagKeyMap.get(tag);
if (keys) {
keys.forEach((key) => {
lruCache === null || lruCache === void 0 ? void 0 : lruCache.delete(key);
});
}
}
function clearStore() {
lruCache === null || lruCache === void 0 ? void 0 : lruCache.clear();
lruCache = void 0;
tagKeyMap.clear();
}
export {
CacheSize,
CacheTime,
cache,
clearStore,
configureCache,
generateKey,
revalidateTag,
withRequestCache
};