UNPKG

@chrono-cache/core

Version:

Backbone of chrono-cache, designed to provide a flexible and efficient approach to cache management

314 lines (307 loc) 8.99 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 __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); // source/index.ts var index_exports = {}; __export(index_exports, { FileCache: () => FileCache, LRUCache: () => LRUCache }); module.exports = __toCommonJS(index_exports); // source/utils/console.ts var consoleColors = { text: { red: (val) => `\x1B[31m${val}\x1B[0m`, green: (val) => `\x1B[32m${val}\x1B[0m`, yellow: (val) => `\x1B[33m${val}\x1B[0m`, blue: (val) => `\x1B[34m${val}\x1B[0m` }, background: { red: (val) => `\x1B[41m${val}\x1B[0m`, green: (val) => `\x1B[42m${val}\x1B[0m`, yellow: (val) => `\x1B[43m${val}\x1B[0m`, blue: (val) => `\x1B[44m${val}\x1B[0m` } }; // source/cache-modules/LRUCache.ts var LRUCache = class { memoryCache = /* @__PURE__ */ new Map(); cacheSizes = /* @__PURE__ */ new Map(); serializeValue; maxSize = 10 * 1024 * 1024; totalSize = 0; ttl = 6e4; debug = false; constructor(options) { this.debug = options.debug ?? false; this.serializeValue = options.serializeValue; if (options.maxSize) this.maxSize = options.maxSize; if (options.ttl) { this.ttl = options.ttl; } } /** * Get memory cache value */ get(key) { const memoryValue = this.memoryCache.get(key); if (!memoryValue || memoryValue.expiresAt <= Date.now()) { if (this.debug) console.log( consoleColors.background.green("[CACHE:MEMORY]"), consoleColors.text.yellow("[SKIP]") ); return null; } if (this.debug) { console.log( consoleColors.background.green("[CACHE:MEMORY]"), consoleColors.text.green("[HIT]") ); } this.touch(key); return memoryValue; } /** * Set memory value */ set(key, value, tags = []) { const size = this.calculateSize(value); if (size > this.maxSize) { console.log( consoleColors.background.green("[CACHE:MEMORY]"), consoleColors.text.red("Single item size exceeds max size") ); return; } if (this.has(key)) { this.totalSize -= this.cacheSizes.get(key) || 0; } this.cacheSizes.set(key, size); this.memoryCache.set(key, { value, tags, lastModified: Date.now(), expiresAt: Date.now() + this.ttl }); this.totalSize += size; this.touch(key); } has(key) { if (!key) return false; this.touch(key); return Boolean(this.memoryCache.get(key)); } touch(key) { const value = this.memoryCache.get(key); if (value) { this.memoryCache.delete(key); this.memoryCache.set(key, value); this.evictIfNecessary(); } } evictIfNecessary() { while (this.totalSize > this.maxSize && this.memoryCache.size > 0) { this.evictLeastRecentlyUsed(); } } evictLeastRecentlyUsed() { const lruKey = this.getOldestCacheKey(); if (lruKey !== void 0) { const lruSize = this.cacheSizes.get(lruKey) || 0; this.totalSize -= lruSize; this.memoryCache.delete(lruKey); this.cacheSizes.delete(lruKey); } } getOldestCacheKey() { return this.memoryCache.keys().next().value; } calculateSize(value) { return Buffer.byteLength(this.serializeValue(value), "utf8"); } /** * Revalidate using tags array */ revalidateTags(tags) { this.memoryCache.forEach((value, key) => { if (tags.some((tag) => value.tags?.includes(tag))) { this.memoryCache.delete(key); } }); } }; // source/cache-modules/FileCache/index.ts var import_node_path2 = __toESM(require("path"), 1); // source/cache-modules/FileCache/ManifestManager.ts var import_node_path = __toESM(require("path"), 1); var ManifestManager = class _ManifestManager { static fileName = "tags-manifest.json"; value; baseTags = []; fs; dir; constructor(options) { this.dir = options.dir; this.fs = options.fs; if (options.baseTags?.length) this.baseTags.push(...options.baseTags); } get filePath() { return import_node_path.default.join(this.dir, _ManifestManager.fileName); } isLoaded() { return !!this.value; } async updateRevalidatedAt(tags) { for (const tag of tags) { const data = this.value?.items[tag] || { revalidatedAt: Date.now() }; data.revalidatedAt = Date.now(); if (this.value) { this.value.items[tag] = data; } } await this.write(); } async write() { try { await this.fs.mkdir(import_node_path.default.dirname(this.dir)); await this.fs.writeFile(this.filePath, JSON.stringify(this?.value || {})); } catch (err) { } } /** * Load the tags manifest from the file system */ async load() { try { this.value = JSON.parse(await this.fs.readFile(this.filePath, "utf8")); } catch (err) { this.value = { version: 1, items: {} }; } } /** * Check if tag is expired */ expired(tag, lastModified) { const now = Date.now(); return this.baseTags.includes(tag) || this.value?.items[tag]?.revalidatedAt && this.value?.items[tag].revalidatedAt >= lastModified; } }; // source/cache-modules/FileCache/index.ts var FileCache = class { distDir; debug = false; fs; manifestManager; constructor(options) { this.fs = options.fs; this.debug = !!options.debug; this.distDir = options.dir; this.manifestManager = new ManifestManager({ dir: options.dir, fs: options.fs, baseTags: [] }); } /** * Get cached value */ async get(key) { try { const filePath = this.getFilePath([key]); const fileTagsPath = this.getFilePath(["tags", key]); const [fileData, fileTags] = await Promise.all([ this.fs.readFile(filePath, "utf8"), this.fs.readFile(fileTagsPath, "utf8") ]); const { mtime } = await this.fs.stat(filePath); const tags = JSON.parse(fileTags); if (!fileData || await this.isExpired(tags, mtime.getTime())) { throw new Error("Not found or expired"); } if (this.debug) console.log( consoleColors.background.blue("[CACHE:SYSTEM]"), consoleColors.text.green("[HIT]") ); return { lastModified: mtime.getTime(), value: fileData, tags }; } catch (error) { } if (this.debug) console.log( consoleColors.background.blue("[CACHE:SYSTEM]"), consoleColors.text.red("[SKIP]") ); return await Promise.resolve(null); } /** * set cache */ async set(key, data, tags = []) { const filePath = this.getFilePath([key]); const fileTagsPath = this.getFilePath(["tags", key]); await Promise.all([ this.fs.mkdir(import_node_path2.default.dirname(filePath)), this.fs.mkdir(import_node_path2.default.dirname(fileTagsPath)) ]); await Promise.all([ await this.fs.writeFile(filePath, data), await this.fs.writeFile(fileTagsPath, JSON.stringify(tags)) ]); } /** * Revalidate cache using tags */ async revalidateTags(tags) { await this.manifestManager.load(); if (this.manifestManager.isLoaded()) { await this.manifestManager.updateRevalidatedAt(tags); } } async isExpired(tags, lastModified) { await this.manifestManager.load(); const wasRevalidated = tags.some((tag) => { return this.manifestManager.expired(tag, lastModified); }); return wasRevalidated; } getFilePath(pathname) { return import_node_path2.default.join(this.distDir, ...pathname); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { FileCache, LRUCache });