UNPKG

cache-kit

Version:

A simple caching layer for fetch requests — supports memory, browser (localStorage), and Node.js (filesystem) adapters with smart strategies.

471 lines (459 loc) 15.9 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // node_modules/tsup/assets/cjs_shims.js var init_cjs_shims = __esm({ "node_modules/tsup/assets/cjs_shims.js"() { "use strict"; } }); // src/utils/memory.util.ts var Memory, cacheMemory; var init_memory_util = __esm({ "src/utils/memory.util.ts"() { "use strict"; init_cjs_shims(); Memory = /* @__PURE__ */ new Map(); cacheMemory = { set: function(key, data) { Memory.set(key, data); }, get: function(key) { return Memory.get(key) || null; }, has: function(key) { return Memory.has(key); }, delete: function(key) { Memory.delete(key); }, clear: function() { Memory.clear(); } }; } }); // src/utils/common.util.ts var getExpiryTimeCacheKey, makeKey; var init_common_util = __esm({ "src/utils/common.util.ts"() { "use strict"; init_cjs_shims(); getExpiryTimeCacheKey = (expirySeconds) => { if (!expirySeconds) return Infinity; const expiryTime = Date.now() + expirySeconds * 1e3; return expiryTime; }; makeKey = (key) => `__CACHE_KIT__::${key}`; } }); // src/adapters/memory.ts var memory_exports = {}; __export(memory_exports, { default: () => memory_default }); var memoryCachedFetch, memory_default; var init_memory = __esm({ "src/adapters/memory.ts"() { "use strict"; init_cjs_shims(); init_memory_util(); init_common_util(); memoryCachedFetch = (normalizedUrl, options, cacheOptions) => __async(null, null, function* () { const cacheKey = normalizedUrl; let response; if (cacheOptions.strategy === "cache-first") { if (cacheMemory.has(cacheKey) && cacheMemory.get(cacheKey).expiredAt > Date.now()) { response = cacheMemory.get(cacheKey).response.clone(); } else { const res = yield fetch(normalizedUrl, options); const cacheData = { response: res.clone(), expiredAt: getExpiryTimeCacheKey(cacheOptions == null ? void 0 : cacheOptions.revalidate) }; cacheMemory.set(cacheKey, cacheData); response = res; } } else if (cacheOptions.strategy === "network-first") { let res; try { res = yield fetch(normalizedUrl, options); const cacheData = { response: res.clone(), expiredAt: getExpiryTimeCacheKey(cacheOptions == null ? void 0 : cacheOptions.revalidate) }; cacheMemory.set(cacheKey, cacheData); response = res; } catch (err) { if (cacheMemory.has(cacheKey) && cacheMemory.get(cacheKey).expiredAt > Date.now()) { response = cacheMemory.get(cacheKey).response.clone(); } else { throw new Error(`Network fetch failed and no cache available: ${err}`); } } } else if (cacheOptions.strategy === "stale-while-revalidate") { if (cacheMemory.has(cacheKey)) { response = cacheMemory.get(cacheKey).response.clone(); } fetch(normalizedUrl, options).then((res) => cacheMemory.set(cacheKey, { response: res, expiredAt: getExpiryTimeCacheKey(cacheOptions == null ? void 0 : cacheOptions.revalidate) })).catch((err) => { console.warn("Background revalidation failed:", err); }); } if (!response) { throw new Error("No valid response available."); } return response; }); memory_default = memoryCachedFetch; } }); // src/utils/browser.util.ts var BrowserMemory; var init_browser_util = __esm({ "src/utils/browser.util.ts"() { "use strict"; init_cjs_shims(); init_common_util(); BrowserMemory = { set: function(_key, data) { return __async(this, null, function* () { const key = makeKey(_key); const text = yield data.response.clone().text(); const resData = { responseBody: text, expiredAt: data.expiredAt, headers: Object.fromEntries(data.response.headers.entries()), status: data.response.status, statusText: data.response.statusText }; window.localStorage.setItem(key, JSON.stringify(resData)); }); }, get: function(_key) { const key = makeKey(_key); const raw = localStorage.getItem(key); if (!raw) return null; try { const parsed = JSON.parse(raw); const { responseBody, expiredAt, headers, status, statusText } = parsed; return { expiredAt, response: new Response(responseBody, { headers, status, statusText }) }; } catch (e) { return null; } }, has: function(_key) { const key = makeKey(_key); return localStorage.getItem(key) !== null; }, delete: function(_key) { const key = makeKey(_key); localStorage.removeItem(key); }, clear: function() { const keys = Object.keys(localStorage); for (const key of keys) { if (key.startsWith("__CACHE_KIT__::")) { localStorage.removeItem(key); } } } }; } }); // src/adapters/browser.ts var browser_exports = {}; __export(browser_exports, { default: () => browser_default }); var browserCachedFetch, browser_default; var init_browser = __esm({ "src/adapters/browser.ts"() { "use strict"; init_cjs_shims(); init_browser_util(); init_common_util(); browserCachedFetch = (normalizedUrl, options, cacheOptions) => __async(null, null, function* () { const cacheKey = normalizedUrl; let response; if (cacheOptions.strategy === "cache-first") { if (BrowserMemory.has(cacheKey) && BrowserMemory.get(cacheKey).expiredAt > Date.now()) { response = BrowserMemory.get(cacheKey).response.clone(); } else { const res = yield fetch(normalizedUrl, options); const cacheData = { response: res.clone(), expiredAt: getExpiryTimeCacheKey(cacheOptions == null ? void 0 : cacheOptions.revalidate) }; yield BrowserMemory.set(cacheKey, cacheData); response = res; } } else if (cacheOptions.strategy === "network-first") { let res; try { res = yield fetch(normalizedUrl, options); const cacheData = { response: res.clone(), expiredAt: getExpiryTimeCacheKey(cacheOptions == null ? void 0 : cacheOptions.revalidate) }; yield BrowserMemory.set(cacheKey, cacheData); response = res; } catch (err) { if (BrowserMemory.has(cacheKey) && BrowserMemory.get(cacheKey).expiredAt > Date.now()) { response = BrowserMemory.get(cacheKey).response.clone(); } else { throw new Error(`Network fetch failed and no cache available: ${err}`); } } } else if (cacheOptions.strategy === "stale-while-revalidate") { if (BrowserMemory.has(cacheKey)) { response = BrowserMemory.get(cacheKey).response.clone(); } fetch(normalizedUrl, options).then((res) => BrowserMemory.set(cacheKey, { response: res, expiredAt: getExpiryTimeCacheKey(cacheOptions == null ? void 0 : cacheOptions.revalidate) })).catch((err) => { console.warn("Background revalidation failed:", err); }); } if (!response) { throw new Error("No valid response available."); } return response; }); browser_default = browserCachedFetch; } }); // src/utils/node.util.ts var import_fs, import_path, FileMemory; var init_node_util = __esm({ "src/utils/node.util.ts"() { "use strict"; init_cjs_shims(); import_fs = __toESM(require("fs")); import_path = __toESM(require("path")); FileMemory = class { constructor(folderName = "cache") { this.baseDir = import_path.default.resolve(folderName); if (!import_fs.default.existsSync(this.baseDir)) { import_fs.default.mkdirSync(this.baseDir, { recursive: true }); } } getFilePath(key) { return import_path.default.resolve(this.baseDir, `${key}.json`); } set(key, data) { import_fs.default.writeFileSync(this.getFilePath(key), JSON.stringify(data)); } get(key) { try { const data = import_fs.default.readFileSync(this.getFilePath(key), "utf8"); return JSON.parse(data); } catch (e) { return null; } } has(key) { return import_fs.default.existsSync(this.getFilePath(key)); } delete(key) { const filePath = this.getFilePath(key); if (import_fs.default.existsSync(filePath)) { import_fs.default.unlinkSync(filePath); } } clear() { const files = import_fs.default.readdirSync(this.baseDir); for (const file of files) { if (file.endsWith(".json")) { import_fs.default.unlinkSync(import_path.default.join(this.baseDir, file)); } } } }; } }); // src/adapters/node.ts var node_exports = {}; __export(node_exports, { default: () => node_default }); var fileCache, nodeCachedFetch, node_default; var init_node = __esm({ "src/adapters/node.ts"() { "use strict"; init_cjs_shims(); init_common_util(); init_node_util(); fileCache = null; nodeCachedFetch = (normalizedUrl, options, cacheOptions) => __async(null, null, function* () { const cacheKey = btoa(normalizedUrl); if (!fileCache) { const folderName = (cacheOptions == null ? void 0 : cacheOptions.folderName) || "cache-kit-data"; fileCache = new FileMemory(folderName); } let response; if (cacheOptions.strategy === "cache-first") { if (fileCache.has(cacheKey)) { const cached = fileCache.get(cacheKey); if (cached.expiredAt > Date.now()) { return cached.response.clone(); } } const res = yield fetch(normalizedUrl, options); fileCache.set(cacheKey, { response: res.clone(), expiredAt: getExpiryTimeCacheKey(cacheOptions.revalidate) }); return res; } else if (cacheOptions.strategy === "network-first") { try { const res = yield fetch(normalizedUrl, options); fileCache.set(cacheKey, { response: res.clone(), expiredAt: getExpiryTimeCacheKey(cacheOptions.revalidate) }); return res; } catch (e) { if (fileCache.has(cacheKey)) { const cached = fileCache.get(cacheKey); if (cached.expiredAt > Date.now()) { return cached.response.clone(); } } throw new Error("Network failed and no valid cache found"); } } else if (cacheOptions.strategy === "stale-while-revalidate") { if (fileCache.has(cacheKey)) { const cached = fileCache.get(cacheKey); response = cached.response.clone(); } fetch(normalizedUrl, options).then((res) => { fileCache.set(cacheKey, { response: res, expiredAt: getExpiryTimeCacheKey(cacheOptions.revalidate) }); }).catch((err) => { console.warn("Revalidation failed:", err); }); return response; } throw new Error("No valid response available."); }); node_default = nodeCachedFetch; } }); // src/index.ts var index_exports = {}; __export(index_exports, { cachedFetch: () => cachedFetch }); module.exports = __toCommonJS(index_exports); init_cjs_shims(); // src/cacheManager.ts init_cjs_shims(); var cachedFetch = (url, options, cacheOptions) => __async(null, null, function* () { var _a; const methodName = (_a = options == null ? void 0 : options.method) == null ? void 0 : _a.toLowerCase(); let response; const normalizedUrl = new URL(url).href; try { if ((!methodName || methodName === "get") && !(options == null ? void 0 : options.body)) { switch (cacheOptions.adapter) { case "memory": { const { default: memoryCachedFetch2 } = yield Promise.resolve().then(() => (init_memory(), memory_exports)); response = yield memoryCachedFetch2(normalizedUrl, options, cacheOptions); break; } case "browser": { if (typeof window === "undefined") { throw new Error("Browser adapter cannot be used in a Node.js environment."); } const { default: browserCachedFetch2 } = yield Promise.resolve().then(() => (init_browser(), browser_exports)); response = yield browserCachedFetch2(normalizedUrl, options, cacheOptions); break; } case "node": { if (typeof window !== "undefined") { throw new Error("Node adapter cannot be used in the browser."); } const { default: nodeCachedFetch2 } = yield Promise.resolve().then(() => (init_node(), node_exports)); response = yield nodeCachedFetch2(normalizedUrl, options, cacheOptions); break; } default: throw new Error(`Unknown adapter: ${cacheOptions.adapter}`); } } else { response = yield fetch(url, options); } } catch (error) { console.warn("\u26A0\uFE0F cachedFetch fallback due to error:", error); response = yield fetch(url, options); } return response; }); // src/types.ts init_cjs_shims(); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { cachedFetch });