UNPKG

mastercache

Version:

Multi-tier cache module for Node.js. Redis, Upstash, CloudfareKV, File, in-memory and others drivers

226 lines (223 loc) 6.62 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/drivers/file/file.ts var file_exports = {}; __export(file_exports, { FileDriver: () => FileDriver, fileDriver: () => fileDriver }); module.exports = __toCommonJS(file_exports); var import_node_path = require("path"); var import_promises = require("fs/promises"); // src/drivers/base-driver.ts var BaseDriver = class { constructor(config) { this.config = config; this.prefix = this.#sanitizePrefix(config.prefix); } /** * Current cache prefix */ prefix; /** * Sanitizes the cache prefix by removing any trailing colons */ #sanitizePrefix(prefix) { if (!prefix) return ""; return prefix.replace(/:+$/, ""); } /** * Creates a namespace prefix by concatenating the cache prefix with the given namespace * If the cache prefix is not defined, the namespace is returned as is */ createNamespacePrefix(namespace) { const sanitizedPrefix = this.#sanitizePrefix(this.prefix); return sanitizedPrefix ? `${sanitizedPrefix}:${namespace}` : namespace; } /** * Returns the cache key with the prefix added to it, if a prefix is defined */ getItemKey(key) { return this.prefix ? `${this.prefix}:${key}` : key; } }; // src/drivers/file/file.ts var FileDriver = class _FileDriver extends BaseDriver { type = "l2"; /** * Root directory for storing the cache files */ #directory; /** * Worker thread that will clean up the expired files */ #cleanerWorker; constructor(config, isNamespace = false) { super(config); this.#directory = this.#sanitizePath((0, import_node_path.join)(config.directory, config.prefix || "")); if (isNamespace) return; if (config.pruneInterval === false) return; } /** * Since keys and namespace uses `:` as a separator, we need to * purge them from the given path. We replace them with `/` to * create a nested directory structure. */ #sanitizePath(path) { if (!path) return ""; return path.replaceAll(":", "/"); } /** * Converts the given key to a file path */ #keyToPath(key) { const keyWithoutPrefix = key.replace(this.prefix, ""); const re = /(\.\/|\.\.\/)/g; if (re.test(key)) { throw new Error(`Invalid key: ${keyWithoutPrefix}. Should not contain relative paths.`); } return (0, import_node_path.join)(this.#directory, this.#sanitizePath(keyWithoutPrefix)); } /** * Check if a file exists at a given path or not */ async pathExists(path) { try { await (0, import_promises.access)(path); return true; } catch { return false; } } /** * Output a file to the disk and create the directory recursively if * it's missing */ async #outputFile(filename, content) { const directory = (0, import_node_path.dirname)(filename); const pathExists = await this.pathExists(directory); if (!pathExists) { await (0, import_promises.mkdir)(directory, { recursive: true }); } await (0, import_promises.writeFile)(filename, content); } /** * Returns a new instance of the driver namespaced */ namespace(namespace) { return new _FileDriver({ ...this.config, prefix: this.createNamespacePrefix(namespace) }, true); } /** * Get a value from the cache */ async get(key) { key = this.getItemKey(key); const path = this.#keyToPath(key); const pathExists = await this.pathExists(path); if (!pathExists) { return void 0; } const content = await (0, import_promises.readFile)(path, { encoding: "utf-8" }); const [value, expire] = JSON.parse(content); if (expire !== -1 && expire < Date.now()) { await this.delete(key); return void 0; } return value; } /** * Get the value of a key and delete it * * Returns the value if the key exists, undefined otherwise */ async pull(key) { const value = await this.get(key); if (!value) return void 0; await this.delete(key); return value; } /** * Put a value in the cache * Returns true if the value was set, false otherwise */ async set(key, value, ttl) { key = this.getItemKey(key); await this.#outputFile( this.#keyToPath(key), JSON.stringify([value, ttl ? Date.now() + ttl : -1]) ); return true; } /** * Check if a key exists in the cache */ async has(key) { key = this.getItemKey(key); const path = this.#keyToPath(key); const pathExists = await this.pathExists(path); if (!pathExists) return false; const content = await (0, import_promises.readFile)(path, { encoding: "utf-8" }); const [, expire] = JSON.parse(content); if (expire !== -1 && expire < Date.now()) { await this.delete(key); return false; } return true; } /** * Remove all items from the cache */ async clear() { const cacheExists = await this.pathExists(this.#directory); if (!cacheExists) return; await (0, import_promises.rm)(this.#directory, { recursive: true }); } /** * Delete a key from the cache * Returns true if the key was deleted, false otherwise */ async delete(key) { key = this.getItemKey(key); const path = this.#keyToPath(key); const pathExists = await this.pathExists(path); if (!pathExists) { return false; } await (0, import_promises.rm)(path); return true; } /** * Delete multiple keys from the cache */ async deleteMany(keys) { await Promise.all(keys.map((key) => this.delete(key))); return true; } async disconnect() { await this.#cleanerWorker?.terminate(); } }; function fileDriver(options) { return { options, factory: (config) => new FileDriver(config) }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { FileDriver, fileDriver }); //# sourceMappingURL=file.cjs.map