flat-cache
Version:
A simple key/value storage using files to persist the data
390 lines (389 loc) • 10.5 kB
JavaScript
// src/index.ts
import path from "node:path";
import fs from "node:fs";
import { CacheableMemory } from "cacheable";
import { parse, stringify } from "flatted";
import { Hookified } from "hookified";
var FlatCacheEvents = /* @__PURE__ */ ((FlatCacheEvents2) => {
FlatCacheEvents2["SAVE"] = "save";
FlatCacheEvents2["LOAD"] = "load";
FlatCacheEvents2["DELETE"] = "delete";
FlatCacheEvents2["CLEAR"] = "clear";
FlatCacheEvents2["DESTROY"] = "destroy";
FlatCacheEvents2["ERROR"] = "error";
FlatCacheEvents2["EXPIRED"] = "expired";
return FlatCacheEvents2;
})(FlatCacheEvents || {});
var FlatCache = class extends Hookified {
_cache = new CacheableMemory();
_cacheDir = ".cache";
_cacheId = "cache1";
_persistInterval = 0;
_persistTimer;
_changesSinceLastSave = false;
_parse = parse;
_stringify = stringify;
constructor(options) {
super();
if (options) {
this._cache = new CacheableMemory({
ttl: options.ttl,
useClone: options.useClone,
lruSize: options.lruSize,
checkInterval: options.expirationInterval
});
}
if (options?.cacheDir) {
this._cacheDir = options.cacheDir;
}
if (options?.cacheId) {
this._cacheId = options.cacheId;
}
if (options?.persistInterval) {
this._persistInterval = options.persistInterval;
this.startAutoPersist();
}
if (options?.deserialize) {
this._parse = options.deserialize;
}
if (options?.serialize) {
this._stringify = options.serialize;
}
}
/**
* The cache object
* @property cache
* @type {CacheableMemory}
*/
get cache() {
return this._cache;
}
/**
* The cache directory
* @property cacheDir
* @type {String}
* @default '.cache'
*/
get cacheDir() {
return this._cacheDir;
}
/**
* Set the cache directory
* @property cacheDir
* @type {String}
* @default '.cache'
*/
set cacheDir(value) {
this._cacheDir = value;
}
/**
* The cache id
* @property cacheId
* @type {String}
* @default 'cache1'
*/
get cacheId() {
return this._cacheId;
}
/**
* Set the cache id
* @property cacheId
* @type {String}
* @default 'cache1'
*/
set cacheId(value) {
this._cacheId = value;
}
/**
* The flag to indicate if there are changes since the last save
* @property changesSinceLastSave
* @type {Boolean}
* @default false
*/
get changesSinceLastSave() {
return this._changesSinceLastSave;
}
/**
* The interval to persist the cache to disk. 0 means no timed persistence
* @property persistInterval
* @type {Number}
* @default 0
*/
get persistInterval() {
return this._persistInterval;
}
/**
* Set the interval to persist the cache to disk. 0 means no timed persistence
* @property persistInterval
* @type {Number}
* @default 0
*/
set persistInterval(value) {
this._persistInterval = value;
}
/**
* Load a cache identified by the given Id. If the element does not exists, then initialize an empty
* cache storage. If specified `cacheDir` will be used as the directory to persist the data to. If omitted
* then the cache module directory `.cacheDir` will be used instead
*
* @method load
* @param cacheId {String} the id of the cache, would also be used as the name of the file cache
* @param cacheDir {String} directory for the cache entry
*/
// eslint-disable-next-line unicorn/prevent-abbreviations
load(cacheId, cacheDir) {
try {
const filePath = path.resolve(`${cacheDir ?? this._cacheDir}/${cacheId ?? this._cacheId}`);
this.loadFile(filePath);
this.emit("load" /* LOAD */);
} catch (error) {
this.emit("error" /* ERROR */, error);
}
}
/**
* Load the cache from the provided file
* @method loadFile
* @param {String} pathToFile the path to the file containing the info for the cache
*/
loadFile(pathToFile) {
if (fs.existsSync(pathToFile)) {
const data = fs.readFileSync(pathToFile, "utf8");
const items = this._parse(data);
for (const key of Object.keys(items)) {
this._cache.set(items[key].key, items[key].value, { expire: items[key].expires });
}
this._changesSinceLastSave = true;
}
}
/**
* Returns the entire persisted object
* @method all
* @returns {*}
*/
all() {
const result = {};
const items = Array.from(this._cache.items);
for (const item of items) {
result[item.key] = item.value;
}
return result;
}
/**
* Returns an array with all the items in the cache { key, value, ttl }
* @method items
* @returns {Array}
*/
get items() {
return Array.from(this._cache.items);
}
/**
* Returns the path to the file where the cache is persisted
* @method cacheFilePath
* @returns {String}
*/
get cacheFilePath() {
return path.resolve(`${this._cacheDir}/${this._cacheId}`);
}
/**
* Returns the path to the cache directory
* @method cacheDirPath
* @returns {String}
*/
get cacheDirPath() {
return path.resolve(this._cacheDir);
}
/**
* Returns an array with all the keys in the cache
* @method keys
* @returns {Array}
*/
keys() {
return Array.from(this._cache.keys);
}
/**
* (Legacy) set key method. This method will be deprecated in the future
* @method setKey
* @param key {string} the key to set
* @param value {object} the value of the key. Could be any object that can be serialized with JSON.stringify
*/
setKey(key, value, ttl) {
this.set(key, value, ttl);
}
/**
* Sets a key to a given value
* @method set
* @param key {string} the key to set
* @param value {object} the value of the key. Could be any object that can be serialized with JSON.stringify
* @param [ttl] {number} the time to live in milliseconds
*/
set(key, value, ttl) {
this._cache.set(key, value, ttl);
this._changesSinceLastSave = true;
}
/**
* (Legacy) Remove a given key from the cache. This method will be deprecated in the future
* @method removeKey
* @param key {String} the key to remove from the object
*/
removeKey(key) {
this.delete(key);
}
/**
* Remove a given key from the cache
* @method delete
* @param key {String} the key to remove from the object
*/
delete(key) {
this._cache.delete(key);
this._changesSinceLastSave = true;
this.emit("delete" /* DELETE */, key);
}
/**
* (Legacy) Return the value of the provided key. This method will be deprecated in the future
* @method getKey<T>
* @param key {String} the name of the key to retrieve
* @returns {*} at T the value from the key
*/
getKey(key) {
return this.get(key);
}
/**
* Return the value of the provided key
* @method get<T>
* @param key {String} the name of the key to retrieve
* @returns {*} at T the value from the key
*/
get(key) {
return this._cache.get(key);
}
/**
* Clear the cache and save the state to disk
* @method clear
*/
clear() {
try {
this._cache.clear();
this._changesSinceLastSave = true;
this.save();
this.emit("clear" /* CLEAR */);
} catch (error) {
this.emit("error" /* ERROR */, error);
}
}
/**
* Save the state of the cache identified by the docId to disk
* as a JSON structure
* @method save
*/
save(force = false) {
try {
if (this._changesSinceLastSave || force) {
const filePath = this.cacheFilePath;
const items = Array.from(this._cache.items);
const data = this._stringify(items);
if (!fs.existsSync(this._cacheDir)) {
fs.mkdirSync(this._cacheDir, { recursive: true });
}
fs.writeFileSync(filePath, data);
this._changesSinceLastSave = false;
this.emit("save" /* SAVE */);
}
} catch (error) {
this.emit("error" /* ERROR */, error);
}
}
/**
* Remove the file where the cache is persisted
* @method removeCacheFile
* @return {Boolean} true or false if the file was successfully deleted
*/
removeCacheFile() {
try {
if (fs.existsSync(this.cacheFilePath)) {
fs.rmSync(this.cacheFilePath);
return true;
}
} catch (error) {
this.emit("error" /* ERROR */, error);
}
return false;
}
/**
* Destroy the cache. This will remove the directory, file, and memory cache
* @method destroy
* @param [includeCacheDir=false] {Boolean} if true, the cache directory will be removed
* @return {undefined}
*/
destroy(includeCacheDirectory = false) {
try {
this._cache.clear();
this.stopAutoPersist();
if (includeCacheDirectory) {
fs.rmSync(this.cacheDirPath, { recursive: true, force: true });
} else {
fs.rmSync(this.cacheFilePath, { recursive: true, force: true });
}
this._changesSinceLastSave = false;
this.emit("destroy" /* DESTROY */);
} catch (error) {
this.emit("error" /* ERROR */, error);
}
}
/**
* Start the auto persist interval
* @method startAutoPersist
*/
startAutoPersist() {
if (this._persistInterval > 0) {
if (this._persistTimer) {
clearInterval(this._persistTimer);
this._persistTimer = void 0;
}
this._persistTimer = setInterval(() => {
this.save();
}, this._persistInterval);
}
}
/**
* Stop the auto persist interval
* @method stopAutoPersist
*/
stopAutoPersist() {
if (this._persistTimer) {
clearInterval(this._persistTimer);
this._persistTimer = void 0;
}
}
};
var FlatCacheDefault = class {
static create = create;
static createFromFile = createFromFile;
static clearCacheById = clearCacheById;
static clearAll = clearAll;
};
function create(options) {
const cache = new FlatCache(options);
cache.load();
return cache;
}
function createFromFile(filePath, options) {
const cache = new FlatCache(options);
cache.loadFile(filePath);
return cache;
}
function clearCacheById(cacheId, cacheDirectory) {
const cache = new FlatCache({ cacheId, cacheDir: cacheDirectory });
cache.destroy();
}
function clearAll(cacheDirectory) {
fs.rmSync(cacheDirectory ?? ".cache", { recursive: true, force: true });
}
export {
FlatCache,
FlatCacheEvents,
clearAll,
clearCacheById,
create,
createFromFile,
FlatCacheDefault as default
};