"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); // src/index.ts var src_exports = {}; __export(src_exports, { Cacheable: () => Cacheable, CacheableEvents: () => CacheableEvents, CacheableHooks: () => CacheableHooks, CacheableMemory: () => CacheableMemory, CacheableStats: () => CacheableStats, Keyv: () => import_keyv2.Keyv, KeyvCacheableMemory: () => KeyvCacheableMemory, KeyvHooks: () => import_keyv2.KeyvHooks, shorthandToMilliseconds: () => shorthandToMilliseconds, shorthandToTime: () => shorthandToTime, wrap: () => wrap, wrapSync: () => wrapSync }); module.exports = __toCommonJS(src_exports); var import_keyv = require("keyv"); var import_hookified = require("hookified"); // src/shorthand-time.ts var shorthandToMilliseconds = (shorthand) => { let milliseconds; if (shorthand === void 0) { return void 0; } if (typeof shorthand === "number") { milliseconds = shorthand; } else if (typeof shorthand === "string") { shorthand = shorthand.trim(); if (Number.isNaN(Number(shorthand))) { const match = /^([\d.]+)\s*(ms|s|m|h|hr|d)$/i.exec(shorthand); if (!match) { throw new Error( `Unsupported time format: "${shorthand}". Use 'ms', 's', 'm', 'h', 'hr', or 'd'.` ); } const [, value, unit] = match; const numericValue = Number.parseFloat(value); const unitLower = unit.toLowerCase(); switch (unitLower) { case "ms": { milliseconds = numericValue; break; } case "s": { milliseconds = numericValue * 1e3; break; } case "m": { milliseconds = numericValue * 1e3 * 60; break; } case "h": { milliseconds = numericValue * 1e3 * 60 * 60; break; } case "hr": { milliseconds = numericValue * 1e3 * 60 * 60; break; } case "d": { milliseconds = numericValue * 1e3 * 60 * 60 * 24; break; } /* c8 ignore next 3 */ default: { milliseconds = Number(shorthand); } } } else { milliseconds = Number(shorthand); } } else { throw new TypeError("Time must be a string or a number."); } return milliseconds; }; var shorthandToTime = (shorthand, fromDate) => { fromDate ||= /* @__PURE__ */ new Date(); const milliseconds = shorthandToMilliseconds(shorthand); if (milliseconds === void 0) { return fromDate.getTime(); } return fromDate.getTime() + milliseconds; }; // src/hash.ts var crypto = __toESM(require("crypto"), 1); function hash(object, algorithm = "sha256") { const objectString = JSON.stringify(object); if (!crypto.getHashes().includes(algorithm)) { throw new Error(`Unsupported hash algorithm: '${algorithm}'`); } const hasher = crypto.createHash(algorithm); hasher.update(objectString); return hasher.digest("hex"); } // src/coalesce-async.ts var callbacks = /* @__PURE__ */ new Map(); function hasKey(key) { return callbacks.has(key); } function addKey(key) { callbacks.set(key, []); } function removeKey(key) { callbacks.delete(key); } function addCallbackToKey(key, callback) { const stash = getCallbacksByKey(key); stash.push(callback); callbacks.set(key, stash); } function getCallbacksByKey(key) { return callbacks.get(key) ?? []; } async function enqueue(key) { return new Promise((resolve, reject) => { const callback = { resolve, reject }; addCallbackToKey(key, callback); }); } function dequeue(key) { const stash = getCallbacksByKey(key); removeKey(key); return stash; } function coalesce(options) { const { key, error, result } = options; for (const callback of dequeue(key)) { if (error) { callback.reject(error); } else { callback.resolve(result); } } } async function coalesceAsync(key, fnc) { if (!hasKey(key)) { addKey(key); try { const result = await Promise.resolve(fnc()); coalesce({ key, result }); return result; } catch (error) { coalesce({ key, error }); throw error; } } return enqueue(key); } // src/wrap.ts function wrapSync(function_, options) { const { ttl, keyPrefix, cache } = options; return function(...arguments_) { const cacheKey = createWrapKey(function_, arguments_, keyPrefix); let value = cache.get(cacheKey); if (value === void 0) { value = function_(...arguments_); cache.set(cacheKey, value, ttl); } return value; }; } function wrap(function_, options) { const { ttl, keyPrefix, cache } = options; return async function(...arguments_) { let value; try { const cacheKey = createWrapKey(function_, arguments_, keyPrefix); value = await cache.get(cacheKey); if (value === void 0) { value = await coalesceAsync(cacheKey, async () => { const result = await function_(...arguments_); await cache.set(cacheKey, result, ttl); return result; }); } } catch { } return value; }; } function createWrapKey(function_, arguments_, keyPrefix) { if (!keyPrefix) { return `${function_.name}::${hash(arguments_)}`; } return `${keyPrefix}::${function_.name}::${hash(arguments_)}`; } // src/memory-lru.ts var ListNode = class { // eslint-disable-next-line @typescript-eslint/parameter-properties value; prev = void 0; next = void 0; constructor(value) { this.value = value; } }; var DoublyLinkedList = class { head = void 0; tail = void 0; nodesMap = /* @__PURE__ */ new Map(); // Add a new node to the front (most recently used) addToFront(value) { const newNode = new ListNode(value); if (this.head) { newNode.next = this.head; this.head.prev = newNode; this.head = newNode; } else { this.head = this.tail = newNode; } this.nodesMap.set(value, newNode); } // Move an existing node to the front (most recently used) moveToFront(value) { const node = this.nodesMap.get(value); if (!node || this.head === node) { return; } if (node.prev) { node.prev.next = node.next; } if (node.next) { node.next.prev = node.prev; } if (node === this.tail) { this.tail = node.prev; } node.prev = void 0; node.next = this.head; if (this.head) { this.head.prev = node; } this.head = node; this.tail ||= node; } // Get the oldest node (tail) getOldest() { return this.tail ? this.tail.value : void 0; } // Remove the oldest node (tail) removeOldest() { if (!this.tail) { return void 0; } const oldValue = this.tail.value; if (this.tail.prev) { this.tail = this.tail.prev; this.tail.next = void 0; } else { this.head = this.tail = void 0; } this.nodesMap.delete(oldValue); return oldValue; } get size() { return this.nodesMap.size; } }; // src/memory.ts var CacheableMemory = class { _lru = new DoublyLinkedList(); _hashCache = /* @__PURE__ */ new Map(); _hash0 = /* @__PURE__ */ new Map(); _hash1 = /* @__PURE__ */ new Map(); _hash2 = /* @__PURE__ */ new Map(); _hash3 = /* @__PURE__ */ new Map(); _hash4 = /* @__PURE__ */ new Map(); _hash5 = /* @__PURE__ */ new Map(); _hash6 = /* @__PURE__ */ new Map(); _hash7 = /* @__PURE__ */ new Map(); _hash8 = /* @__PURE__ */ new Map(); _hash9 = /* @__PURE__ */ new Map(); _ttl; // Turned off by default _useClone = true; // Turned on by default _lruSize = 0; // Turned off by default _checkInterval = 0; // Turned off by default _interval = 0; // Turned off by default /** * @constructor * @param {CacheableMemoryOptions} [options] - The options for the CacheableMemory */ constructor(options) { if (options?.ttl) { this.setTtl(options.ttl); } if (options?.useClone !== void 0) { this._useClone = options.useClone; } if (options?.lruSize) { this._lruSize = options.lruSize; } if (options?.checkInterval) { this._checkInterval = options.checkInterval; } this.startIntervalCheck(); } /** * Gets the time-to-live * @returns {number|string|undefined} - The time-to-live in miliseconds or a human-readable format. If undefined, it will not have a time-to-live. */ get ttl() { return this._ttl; } /** * Sets the time-to-live * @param {number|string|undefined} value - The time-to-live in miliseconds or a human-readable format (example '1s' = 1 second, '1h' = 1 hour). If undefined, it will not have a time-to-live. */ set ttl(value) { this.setTtl(value); } /** * Gets whether to use clone * @returns {boolean} - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true. */ get useClone() { return this._useClone; } /** * Sets whether to use clone * @param {boolean} value - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true. */ set useClone(value) { this._useClone = value; } /** * Gets the size of the LRU cache * @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. */ get lruSize() { return this._lruSize; } /** * Sets the size of the LRU cache * @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. */ set lruSize(value) { this._lruSize = value; this.lruResize(); } /** * Gets the check interval * @returns {number} - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0. */ get checkInterval() { return this._checkInterval; } /** * Sets the check interval * @param {number} value - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0. */ set checkInterval(value) { this._checkInterval = value; } /** * Gets the size of the cache * @returns {number} - The size of the cache */ get size() { return this._hash0.size + this._hash1.size + this._hash2.size + this._hash3.size + this._hash4.size + this._hash5.size + this._hash6.size + this._hash7.size + this._hash8.size + this._hash9.size; } /** * Gets the keys * @returns {IterableIterator} - The keys */ get keys() { return this.concatStores().keys(); } /** * Gets the items * @returns {IterableIterator} - The items */ get items() { return this.concatStores().values(); } /** * Gets the value of the key * @param {string} key - The key to get the value * @returns {T | undefined} - The value of the key */ get(key) { const store = this.getStore(key); const item = store.get(key); if (!item) { return void 0; } if (item.expires && item.expires && Date.now() > item.expires) { store.delete(key); return void 0; } this.lruMoveToFront(key); if (!this._useClone) { return item.value; } return this.clone(item.value); } /** * Gets the values of the keys * @param {string[]} keys - The keys to get the values * @returns {T[]} - The values of the keys */ getMany(keys) { const result = new Array(); for (const key of keys) { result.push(this.get(key)); } return result; } /** * Gets the raw value of the key * @param {string} key - The key to get the value * @returns {CacheableStoreItem | undefined} - The raw value of the key */ getRaw(key) { const store = this.getStore(key); const item = store.get(key); if (!item) { return void 0; } if (item.expires && item.expires && Date.now() > item.expires) { store.delete(key); return void 0; } this.lruMoveToFront(key); return item; } /** * Gets the raw values of the keys * @param {string[]} keys - The keys to get the values * @returns {CacheableStoreItem[]} - The raw values of the keys */ getManyRaw(keys) { const result = new Array(); for (const key of keys) { result.push(this.getRaw(key)); } return result; } /** * Sets the value of the key * @param {string} key - The key to set the value * @param {any} value - The value to set * @param {number|string} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable. * If you set undefined, it will use the default time-to-live. If both are undefined then it will not have a time-to-live. * @returns {void} */ set(key, value, ttl) { const store = this.getStore(key); let expires; if (ttl !== void 0 || this._ttl !== void 0) { const finalTtl = shorthandToTime(ttl ?? this._ttl); if (finalTtl !== void 0) { expires = finalTtl; } } if (this._lruSize > 0) { if (store.has(key)) { this.lruMoveToFront(key); } else { this.lruAddToFront(key); if (this._lru.size > this._lruSize) { const oldestKey = this._lru.getOldest(); if (oldestKey) { this._lru.removeOldest(); this.delete(oldestKey); } } } } const item = { key, value, expires }; store.set( key, item ); } /** * Sets the values of the keys * @param {CacheableItem[]} items - The items to set * @returns {void} */ setMany(items) { for (const item of items) { this.set(item.key, item.value, item.ttl); } } /** * Checks if the key exists * @param {string} key - The key to check * @returns {boolean} - If true, the key exists. If false, the key does not exist. */ has(key) { const item = this.get(key); return Boolean(item); } /** * @function hasMany * @param {string[]} keys - The keys to check * @returns {boolean[]} - If true, the key exists. If false, the key does not exist. */ hasMany(keys) { const result = new Array(); for (const key of keys) { const item = this.get(key); result.push(Boolean(item)); } return result; } /** * Take will get the key and delete the entry from cache * @param {string} key - The key to take * @returns {T | undefined} - The value of the key */ take(key) { const item = this.get(key); if (!item) { return void 0; } this.delete(key); return item; } /** * TakeMany will get the keys and delete the entries from cache * @param {string[]} keys - The keys to take * @returns {T[]} - The values of the keys */ takeMany(keys) { const result = new Array(); for (const key of keys) { result.push(this.take(key)); } return result; } /** * Delete the key * @param {string} key - The key to delete * @returns {void} */ delete(key) { const store = this.getStore(key); store.delete(key); } /** * Delete the keys * @param {string[]} keys - The keys to delete * @returns {void} */ deleteMany(keys) { for (const key of keys) { this.delete(key); } } /** * Clear the cache * @returns {void} */ clear() { this._hash0.clear(); this._hash1.clear(); this._hash2.clear(); this._hash3.clear(); this._hash4.clear(); this._hash5.clear(); this._hash6.clear(); this._hash7.clear(); this._hash8.clear(); this._hash9.clear(); this._hashCache.clear(); this._lru = new DoublyLinkedList(); } /** * Get the store based on the key (internal use) * @param {string} key - The key to get the store * @returns {CacheableHashStore} - The store */ getStore(key) { const hash2 = this.hashKey(key); return this.getStoreFromHash(hash2); } /** * Get the store based on the hash (internal use) * @param {number} hash * @returns {Map} */ getStoreFromHash(hash2) { switch (hash2) { case 1: { return this._hash1; } case 2: { return this._hash2; } case 3: { return this._hash3; } case 4: { return this._hash4; } case 5: { return this._hash5; } case 6: { return this._hash6; } case 7: { return this._hash7; } case 8: { return this._hash8; } case 9: { return this._hash9; } default: { return this._hash0; } } } /** * Hash the key (internal use) * @param key * @returns {number} from 0 to 9 */ hashKey(key) { const cacheHashNumber = this._hashCache.get(key); if (cacheHashNumber) { return cacheHashNumber; } let hash2 = 0; const primeMultiplier = 31; for (let i = 0; i < key.length; i++) { hash2 = hash2 * primeMultiplier + key.charCodeAt(i); } const result = Math.abs(hash2) % 10; this._hashCache.set(key, result); return result; } /** * Clone the value. This is for internal use * @param {any} value - The value to clone * @returns {any} - The cloned value */ clone(value) { if (this.isPrimitive(value)) { return value; } return structuredClone(value); } /** * Add to the front of the LRU cache. This is for internal use * @param {string} key - The key to add to the front * @returns {void} */ lruAddToFront(key) { if (this._lruSize === 0) { return; } this._lru.addToFront(key); } /** * Move to the front of the LRU cache. This is for internal use * @param {string} key - The key to move to the front * @returns {void} */ lruMoveToFront(key) { if (this._lruSize === 0) { return; } this._lru.moveToFront(key); } /** * Resize the LRU cache. This is for internal use * @returns {void} */ lruResize() { if (this._lruSize === 0) { return; } while (this._lru.size > this._lruSize) { const oldestKey = this._lru.getOldest(); if (oldestKey) { this._lru.removeOldest(); this.delete(oldestKey); } } } /** * Check for expiration. This is for internal use * @returns {void} */ checkExpiration() { const stores = this.concatStores(); for (const item of stores.values()) { if (item.expires && Date.now() > item.expires) { this.delete(item.key); } } } /** * Start the interval check. This is for internal use * @returns {void} */ startIntervalCheck() { if (this._checkInterval > 0) { this._interval = setInterval(() => { this.checkExpiration(); }, this._checkInterval); } } /** * Stop the interval check. This is for internal use * @returns {void} */ stopIntervalCheck() { if (this._interval) { clearInterval(this._interval); } this._interval = 0; this._checkInterval = 0; } /** * Hash the object. This is for internal use * @param {any} object - The object to hash * @param {string} [algorithm='sha256'] - The algorithm to hash * @returns {string} - The hashed string */ hash(object, algorithm = "sha256") { return hash(object, algorithm); } /** * Wrap the function for caching * @param {Function} function_ - The function to wrap * @param {Object} [options] - The options to wrap * @returns {Function} - The wrapped function */ wrap(function_, options) { const wrapOptions = { ttl: options?.ttl ?? this._ttl, keyPrefix: options?.keyPrefix, cache: this }; return wrapSync(function_, wrapOptions); } isPrimitive(value) { const result = false; if (value === null || value === void 0) { return true; } if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { return true; } return result; } concatStores() { return new Map([...this._hash0, ...this._hash1, ...this._hash2, ...this._hash3, ...this._hash4, ...this._hash5, ...this._hash6, ...this._hash7, ...this._hash8, ...this._hash9]); } setTtl(ttl) { if (typeof ttl === "string" || ttl === void 0) { this._ttl = ttl; } else if (ttl > 0) { this._ttl = ttl; } else { this._ttl = void 0; } } }; // src/keyv-memory.ts var KeyvCacheableMemory = class { opts = { ttl: 0, useClone: true, lruSize: 0, checkInterval: 0 }; _defaultCache = new CacheableMemory(); _nCache = /* @__PURE__ */ new Map(); _namespace; constructor(options) { if (options) { this.opts = options; this._defaultCache = new CacheableMemory(options); if (options.namespace) { this._namespace = options.namespace; this._nCache.set(this._namespace, new CacheableMemory(options)); } } } get namespace() { return this._namespace; } set namespace(value) { this._namespace = value; } get store() { return this.getStore(this._namespace); } async get(key) { const result = this.getStore(this._namespace).get(key); if (result) { return result; } return void 0; } async getMany(keys) { const result = this.getStore(this._namespace).getMany(keys); return result; } async set(key, value, ttl) { this.getStore(this._namespace).set(key, value, ttl); } async setMany(values) { this.getStore(this._namespace).setMany(values); } async delete(key) { this.getStore(this._namespace).delete(key); return true; } async deleteMany(key) { this.getStore(this._namespace).deleteMany(key); return true; } async clear() { this.getStore(this._namespace).clear(); } async has(key) { return this.getStore(this._namespace).has(key); } on(event, listener) { return this; } getStore(namespace) { if (!namespace) { return this._defaultCache; } if (!this._nCache.has(namespace)) { this._nCache.set(namespace, new CacheableMemory(this.opts)); } return this._nCache.get(namespace); } }; // src/stats.ts var CacheableStats = class { _hits = 0; _misses = 0; _gets = 0; _sets = 0; _deletes = 0; _clears = 0; _vsize = 0; _ksize = 0; _count = 0; _enabled = false; constructor(options) { if (options?.enabled) { this._enabled = options.enabled; } } /** * @returns {boolean} - Whether the stats are enabled */ get enabled() { return this._enabled; } /** * @param {boolean} enabled - Whether to enable the stats */ set enabled(enabled) { this._enabled = enabled; } /** * @returns {number} - The number of hits * @readonly */ get hits() { return this._hits; } /** * @returns {number} - The number of misses * @readonly */ get misses() { return this._misses; } /** * @returns {number} - The number of gets * @readonly */ get gets() { return this._gets; } /** * @returns {number} - The number of sets * @readonly */ get sets() { return this._sets; } /** * @returns {number} - The number of deletes * @readonly */ get deletes() { return this._deletes; } /** * @returns {number} - The number of clears * @readonly */ get clears() { return this._clears; } /** * @returns {number} - The vsize (value size) of the cache instance * @readonly */ get vsize() { return this._vsize; } /** * @returns {number} - The ksize (key size) of the cache instance * @readonly */ get ksize() { return this._ksize; } /** * @returns {number} - The count of the cache instance * @readonly */ get count() { return this._count; } incrementHits() { if (!this._enabled) { return; } this._hits++; } incrementMisses() { if (!this._enabled) { return; } this._misses++; } incrementGets() { if (!this._enabled) { return; } this._gets++; } incrementSets() { if (!this._enabled) { return; } this._sets++; } incrementDeletes() { if (!this._enabled) { return; } this._deletes++; } incrementClears() { if (!this._enabled) { return; } this._clears++; } // eslint-disable-next-line @typescript-eslint/naming-convention incrementVSize(value) { if (!this._enabled) { return; } this._vsize += this.roughSizeOfObject(value); } // eslint-disable-next-line @typescript-eslint/naming-convention decreaseVSize(value) { if (!this._enabled) { return; } this._vsize -= this.roughSizeOfObject(value); } // eslint-disable-next-line @typescript-eslint/naming-convention incrementKSize(key) { if (!this._enabled) { return; } this._ksize += this.roughSizeOfString(key); } // eslint-disable-next-line @typescript-eslint/naming-convention decreaseKSize(key) { if (!this._enabled) { return; } this._ksize -= this.roughSizeOfString(key); } incrementCount() { if (!this._enabled) { return; } this._count++; } decreaseCount() { if (!this._enabled) { return; } this._count--; } setCount(count) { if (!this._enabled) { return; } this._count = count; } roughSizeOfString(value) { return value.length * 2; } roughSizeOfObject(object) { const objectList = []; const stack = [object]; let bytes = 0; while (stack.length > 0) { const value = stack.pop(); if (typeof value === "boolean") { bytes += 4; } else if (typeof value === "string") { bytes += value.length * 2; } else if (typeof value === "number") { bytes += 8; } else if (typeof value === "object" && value !== null && !objectList.includes(value)) { objectList.push(value); for (const key in value) { bytes += key.length * 2; stack.push(value[key]); } } } return bytes; } reset() { this._hits = 0; this._misses = 0; this._gets = 0; this._sets = 0; this._deletes = 0; this._clears = 0; this._vsize = 0; this._ksize = 0; this._count = 0; } resetStoreValues() { this._vsize = 0; this._ksize = 0; this._count = 0; } }; // src/index.ts var import_keyv2 = require("keyv"); var CacheableHooks = /* @__PURE__ */ ((CacheableHooks2) => { CacheableHooks2["BEFORE_SET"] = "BEFORE_SET"; CacheableHooks2["AFTER_SET"] = "AFTER_SET"; CacheableHooks2["BEFORE_SET_MANY"] = "BEFORE_SET_MANY"; CacheableHooks2["AFTER_SET_MANY"] = "AFTER_SET_MANY"; CacheableHooks2["BEFORE_GET"] = "BEFORE_GET"; CacheableHooks2["AFTER_GET"] = "AFTER_GET"; CacheableHooks2["BEFORE_GET_MANY"] = "BEFORE_GET_MANY"; CacheableHooks2["AFTER_GET_MANY"] = "AFTER_GET_MANY"; return CacheableHooks2; })(CacheableHooks || {}); var CacheableEvents = /* @__PURE__ */ ((CacheableEvents2) => { CacheableEvents2["ERROR"] = "error"; return CacheableEvents2; })(CacheableEvents || {}); var Cacheable = class extends import_hookified.Hookified { _primary = new import_keyv.Keyv({ store: new KeyvCacheableMemory() }); _secondary; _nonBlocking = false; _ttl; _stats = new CacheableStats({ enabled: false }); _namespace; /** * Creates a new cacheable instance * @param {CacheableOptions} [options] The options for the cacheable instance */ constructor(options) { super(); if (options?.primary) { this.setPrimary(options.primary); } if (options?.secondary) { this.setSecondary(options.secondary); } if (options?.nonBlocking) { this._nonBlocking = options.nonBlocking; } if (options?.stats) { this._stats.enabled = options.stats; } if (options?.ttl) { this.setTtl(options.ttl); } if (options?.namespace) { this._namespace = options.namespace; this._primary.namespace = this.getNameSpace(); if (this._secondary) { this._secondary.namespace = this.getNameSpace(); } } } /** * The namespace for the cacheable instance * @returns {string | (() => string) | undefined} The namespace for the cacheable instance */ get namespace() { return this._namespace; } /** * Sets the namespace for the cacheable instance * @param {string | (() => string) | undefined} namespace The namespace for the cacheable instance * @returns {void} */ set namespace(namespace) { this._namespace = namespace; this._primary.namespace = this.getNameSpace(); if (this._secondary) { this._secondary.namespace = this.getNameSpace(); } } /** * The statistics for the cacheable instance * @returns {CacheableStats} The statistics for the cacheable instance */ get stats() { return this._stats; } /** * The primary store for the cacheable instance * @returns {Keyv} The primary store for the cacheable instance */ get primary() { return this._primary; } /** * Sets the primary store for the cacheable instance * @param {Keyv} primary The primary store for the cacheable instance */ set primary(primary) { this._primary = primary; } /** * The secondary store for the cacheable instance * @returns {Keyv | undefined} The secondary store for the cacheable instance */ get secondary() { return this._secondary; } /** * Sets the secondary store for the cacheable instance. If it is set to undefined then the secondary store is disabled. * @param {Keyv | undefined} secondary The secondary store for the cacheable instance * @returns {void} */ set secondary(secondary) { this._secondary = secondary; } /** * Gets whether the secondary store is non-blocking mode. It is set to false by default. * If it is set to true then the secondary store will not block the primary store. * * [Learn more about non-blocking mode](https://cacheable.org/docs/cacheable/#non-blocking-operations). * * @returns {boolean} Whether the cacheable instance is non-blocking */ get nonBlocking() { return this._nonBlocking; } /** * Sets whether the secondary store is non-blocking mode. It is set to false by default. * If it is set to true then the secondary store will not block the primary store. * * [Learn more about non-blocking mode](https://cacheable.org/docs/cacheable/#non-blocking-operations). * * @param {boolean} nonBlocking Whether the cacheable instance is non-blocking * @returns {void} */ set nonBlocking(nonBlocking) { this._nonBlocking = nonBlocking; } /** * The time-to-live for the cacheable instance and will be used as the default value. * can be a number in milliseconds or a human-readable format such as `1s` for 1 second or `1h` for 1 hour * or undefined if there is no time-to-live. * * [Learn more about time-to-live](https://cacheable.org/docs/cacheable/#shorthand-for-time-to-live-ttl). * * @returns {number | string | undefined} The time-to-live for the cacheable instance in milliseconds, human-readable format or undefined * @example * ```typescript * const cacheable = new Cacheable({ ttl: '1h' }); * console.log(cacheable.ttl); // 1h * ``` */ get ttl() { return this._ttl; } /** * Sets the time-to-live for the cacheable instance and will be used as the default value. * If you set a number it is miliseconds, if you set a string it is a human-readable * format such as `1s` for 1 second or `1h` for 1 hour. Setting undefined means that * there is no time-to-live. * * [Learn more about time-to-live](https://cacheable.org/docs/cacheable/#shorthand-for-time-to-live-ttl). * * @param {number | string | undefined} ttl The time-to-live for the cacheable instance * @example * ```typescript * const cacheable = new Cacheable(); * cacheable.ttl = '1h'; // Set the time-to-live to 1 hour * ``` * or setting the time-to-live in milliseconds * ```typescript * const cacheable = new Cacheable(); * cacheable.ttl = 3600000; // Set the time-to-live to 1 hour * ``` */ set ttl(ttl) { this.setTtl(ttl); } /** * Sets the primary store for the cacheable instance * @param {Keyv | KeyvStoreAdapter} primary The primary store for the cacheable instance * @returns {void} */ setPrimary(primary) { this._primary = primary instanceof import_keyv.Keyv ? primary : new import_keyv.Keyv(primary); } /** * Sets the secondary store for the cacheable instance. If it is set to undefined then the secondary store is disabled. * @param {Keyv | KeyvStoreAdapter} secondary The secondary store for the cacheable instance * @returns {void} */ setSecondary(secondary) { this._secondary = secondary instanceof import_keyv.Keyv ? secondary : new import_keyv.Keyv(secondary); } getNameSpace() { if (typeof this._namespace === "function") { return this._namespace(); } return this._namespace; } /** * Gets the value of the key. If the key does not exist in the primary store then it will check the secondary store. * @param {string} key The key to get the value of * @returns {Promise} The value of the key or undefined if the key does not exist */ async get(key) { let result; try { await this.hook("BEFORE_GET" /* BEFORE_GET */, key); result = await this._primary.get(key); if (!result && this._secondary) { result = await this._secondary.get(key); if (result) { const finalTtl = shorthandToMilliseconds(this._ttl); await this._primary.set(key, result, finalTtl); } } await this.hook("AFTER_GET" /* AFTER_GET */, { key, result }); } catch (error) { this.emit("error" /* ERROR */, error); } if (this.stats.enabled) { if (result) { this._stats.incrementHits(); } else { this._stats.incrementMisses(); } this.stats.incrementGets(); } return result; } /** * Gets the values of the keys. If the key does not exist in the primary store then it will check the secondary store. * @param {string[]} keys The keys to get the values of * @returns {Promise>} The values of the keys or undefined if the key does not exist */ async getMany(keys) { let result = []; try { await this.hook("BEFORE_GET_MANY" /* BEFORE_GET_MANY */, keys); result = await this._primary.get(keys); if (this._secondary) { const missingKeys = []; for (const [i, key] of keys.entries()) { if (!result[i]) { missingKeys.push(key); } } const secondaryResult = await this._secondary.get(missingKeys); for (const [i, key] of keys.entries()) { if (!result[i] && secondaryResult[i]) { result[i] = secondaryResult[i]; const finalTtl = shorthandToMilliseconds(this._ttl); await this._primary.set(key, secondaryResult[i], finalTtl); } } } await this.hook("AFTER_GET_MANY" /* AFTER_GET_MANY */, { keys, result }); } catch (error) { this.emit("error" /* ERROR */, error); } if (this.stats.enabled) { for (const item of result) { if (item) { this._stats.incrementHits(); } else { this._stats.incrementMisses(); } } this.stats.incrementGets(); } return result; } /** * Sets the value of the key. If the secondary store is set then it will also set the value in the secondary store. * @param {string} key the key to set the value of * @param {T} value The value to set * @param {number | string} [ttl] set a number it is miliseconds, set a string it is a human-readable * format such as `1s` for 1 second or `1h` for 1 hour. Setting undefined means that it will use the default time-to-live. * @returns {boolean} Whether the value was set */ async set(key, value, ttl) { let result = false; const finalTtl = shorthandToMilliseconds(ttl ?? this._ttl); try { const item = { key, value, ttl: finalTtl }; await this.hook("BEFORE_SET" /* BEFORE_SET */, item); const promises = []; promises.push(this._primary.set(item.key, item.value, item.ttl)); if (this._secondary) { promises.push(this._secondary.set(item.key, item.value, item.ttl)); } if (this._nonBlocking) { result = await Promise.race(promises); } else { const results = await Promise.all(promises); result = results[0]; } await this.hook("AFTER_SET" /* AFTER_SET */, item); } catch (error) { this.emit("error" /* ERROR */, error); } if (this.stats.enabled) { this.stats.incrementKSize(key); this.stats.incrementCount(); this.stats.incrementVSize(value); this.stats.incrementSets(); } return result; } /** * Sets the values of the keys. If the secondary store is set then it will also set the values in the secondary store. * @param {CacheableItem[]} items The items to set * @returns {boolean} Whether the values were set */ async setMany(items) { let result = false; try { await this.hook("BEFORE_SET_MANY" /* BEFORE_SET_MANY */, items); result = await this.setManyKeyv(this._primary, items); if (this._secondary) { if (this._nonBlocking) { this.setManyKeyv(this._secondary, items); } else { await this.setManyKeyv(this._secondary, items); } } await this.hook("AFTER_SET_MANY" /* AFTER_SET_MANY */, items); } catch (error) { this.emit("error" /* ERROR */, error); } if (this.stats.enabled) { for (const item of items) { this.stats.incrementKSize(item.key); this.stats.incrementCount(); this.stats.incrementVSize(item.value); } } return result; } /** * Takes the value of the key and deletes the key. If the key does not exist then it will return undefined. * @param {string} key The key to take the value of * @returns {Promise} The value of the key or undefined if the key does not exist */ async take(key) { const result = await this.get(key); await this.delete(key); return result; } /** * Takes the values of the keys and deletes the keys. If the key does not exist then it will return undefined. * @param {string[]} keys The keys to take the values of * @returns {Promise>} The values of the keys or undefined if the key does not exist */ async takeMany(keys) { const result = await this.getMany(keys); await this.deleteMany(keys); return result; } /** * Checks if the key exists in the primary store. If it does not exist then it will check the secondary store. * @param {string} key The key to check * @returns {Promise} Whether the key exists */ async has(key) { const promises = []; promises.push(this._primary.has(key)); if (this._secondary) { promises.push(this._secondary.has(key)); } const resultAll = await Promise.all(promises); for (const result of resultAll) { if (result) { return true; } } return false; } /** * Checks if the keys exist in the primary store. If it does not exist then it will check the secondary store. * @param {string[]} keys The keys to check * @returns {Promise} Whether the keys exist */ async hasMany(keys) { const result = await this.hasManyKeyv(this._primary, keys); const missingKeys = []; for (const [i, key] of keys.entries()) { if (!result[i] && this._secondary) { missingKeys.push(key); } } if (missingKeys.length > 0 && this._secondary) { const secondary = await this.hasManyKeyv(this._secondary, keys); for (const [i, key] of keys.entries()) { if (!result[i] && secondary[i]) { result[i] = secondary[i]; } } } return result; } /** * Deletes the key from the primary store. If the secondary store is set then it will also delete the key from the secondary store. * @param {string} key The key to delete * @returns {Promise} Whether the key was deleted */ async delete(key) { let result = false; const promises = []; if (this.stats.enabled) { const statResult = await this._primary.get(key); if (statResult) { this.stats.decreaseKSize(key); this.stats.decreaseVSize(statResult); this.stats.decreaseCount(); this.stats.incrementDeletes(); } } promises.push(this._primary.delete(key)); if (this._secondary) { promises.push(this._secondary.delete(key)); } if (this.nonBlocking) { result = await Promise.race(promises); } else { const resultAll = await Promise.all(promises); result = resultAll[0]; } return result; } /** * Deletes the keys from the primary store. If the secondary store is set then it will also delete the keys from the secondary store. * @param {string[]} keys The keys to delete * @returns {Promise} Whether the keys were deleted */ async deleteMany(keys) { if (this.stats.enabled) { const statResult = await this._primary.get(keys); for (const key of keys) { this.stats.decreaseKSize(key); this.stats.decreaseVSize(statResult); this.stats.decreaseCount(); this.stats.incrementDeletes(); } } const result = await this.deleteManyKeyv(this._primary, keys); if (this._secondary) { if (this._nonBlocking) { this.deleteManyKeyv(this._secondary, keys); } else { await this.deleteManyKeyv(this._secondary, keys); } } return result; } /** * Clears the primary store. If the secondary store is set then it will also clear the secondary store. * @returns {Promise} */ async clear() { const promises = []; promises.push(this._primary.clear()); if (this._secondary) { promises.push(this._secondary.clear()); } await (this._nonBlocking ? Promise.race(promises) : Promise.all(promises)); if (this.stats.enabled) { this._stats.resetStoreValues(); this._stats.incrementClears(); } } /** * Disconnects the primary store. If the secondary store is set then it will also disconnect the secondary store. * @returns {Promise} */ async disconnect() { const promises = []; promises.push(this._primary.disconnect()); if (this._secondary) { promises.push(this._secondary.disconnect()); } await (this._nonBlocking ? Promise.race(promises) : Promise.all(promises)); } /** * Wraps a function with caching * * [Learn more about wrapping functions](https://cacheable.org/docs/cacheable/#wrap--memoization-for-sync-and-async-functions). * @param {Function} function_ The function to wrap * @param {WrapOptions} [options] The options for the wrap function * @returns {Function} The wrapped function */ wrap(function_, options) { const wrapOptions = { ttl: options?.ttl ?? this._ttl, keyPrefix: options?.keyPrefix, cache: this }; return wrap(function_, wrapOptions); } /** * Will hash an object using the specified algorithm. The default algorithm is 'sha256'. * @param {any} object the object to hash * @param {string} algorithm the hash algorithm to use. The default is 'sha256' * @returns {string} the hash of the object */ hash(object, algorithm = "sha256") { return hash(object, algorithm); } async deleteManyKeyv(keyv, keys) { const promises = []; for (const key of keys) { promises.push(keyv.delete(key)); } await Promise.all(promises); return true; } async setManyKeyv(keyv, items) { const promises = []; for (const item of items) { const finalTtl = shorthandToMilliseconds(item.ttl ?? this._ttl); promises.push(keyv.set(item.key, item.value, finalTtl)); } await Promise.all(promises); return true; } async hasManyKeyv(keyv, keys) { const promises = []; for (const key of keys) { promises.push(keyv.has(key)); } return Promise.all(promises); } setTtl(ttl) { if (typeof ttl === "string" || ttl === void 0) { this._ttl = ttl; } else if (ttl > 0) { this._ttl = ttl; } else { this._ttl = void 0; } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Cacheable, CacheableEvents, CacheableHooks, CacheableMemory, CacheableStats, Keyv, KeyvCacheableMemory, KeyvHooks, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync });