@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
JavaScript
"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
});