@chrono-cache/core
Version:
Backbone of chrono-cache, designed to provide a flexible and efficient approach to cache management
276 lines (271 loc) • 7.19 kB
JavaScript
// 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
import path2 from "node:path";
// source/cache-modules/FileCache/ManifestManager.ts
import path from "node:path";
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 path.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(path.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(path2.dirname(filePath)),
this.fs.mkdir(path2.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 path2.join(this.distDir, ...pathname);
}
};
export {
FileCache,
LRUCache
};