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
JavaScript
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