+184
-124
@@ -135,2 +135,22 @@ "use strict"; | ||
| } | ||
| function hashToNumber(object, min = 0, max = 10, 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); | ||
| const hashHex = hasher.digest("hex"); | ||
| const hashNumber = Number.parseInt(hashHex, 16); | ||
| const range = max - min + 1; | ||
| return min + hashNumber % range; | ||
| } | ||
| function djb2Hash(string_, min = 0, max = 10) { | ||
| let hash2 = 5381; | ||
| for (let i = 0; i < string_.length; i++) { | ||
| hash2 = hash2 * 33 ^ string_.charCodeAt(i); | ||
| } | ||
| const range = max - min + 1; | ||
| return min + Math.abs(hash2) % range; | ||
| } | ||
@@ -212,25 +232,27 @@ // src/coalesce-async.ts | ||
| } | ||
| async function getOrSet(key, function_, options) { | ||
| let value = await options.cache.get(key); | ||
| if (value === void 0) { | ||
| const cacheId = options.cacheId ?? "default"; | ||
| const coalesceKey = `${cacheId}::${key}`; | ||
| value = await coalesceAsync(coalesceKey, async () => { | ||
| try { | ||
| const result = await function_(); | ||
| await options.cache.set(key, result, options.ttl); | ||
| return result; | ||
| } catch (error) { | ||
| options.cache.emit("error", error); | ||
| if (options.cacheErrors) { | ||
| await options.cache.set(key, error, options.ttl); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| return value; | ||
| } | ||
| function wrap(function_, options) { | ||
| const { ttl, keyPrefix, cache } = options; | ||
| const { keyPrefix, cache } = options; | ||
| return async function(...arguments_) { | ||
| let value; | ||
| const cacheKey = createWrapKey(function_, arguments_, keyPrefix); | ||
| value = await cache.get(cacheKey); | ||
| if (value === void 0) { | ||
| const cacheId = options.cacheId ?? "default"; | ||
| const coalesceKey = `${cacheId}::${cacheKey}`; | ||
| value = await coalesceAsync(coalesceKey, async () => { | ||
| try { | ||
| const result = await function_(...arguments_); | ||
| await cache.set(cacheKey, result, ttl); | ||
| return result; | ||
| } catch (error) { | ||
| cache.emit("error", error); | ||
| if (options.cacheErrors) { | ||
| await cache.set(cacheKey, error, ttl); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| return value; | ||
| return cache.getOrSet(cacheKey, async () => function_(...arguments_), options); | ||
| }; | ||
@@ -319,15 +341,10 @@ } | ||
| // src/memory.ts | ||
| var defaultStoreHashSize = 16; | ||
| var maximumMapSize = 16777216; | ||
| var CacheableMemory = class extends import_hookified.Hookified { | ||
| _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(); | ||
| _storeHashSize = defaultStoreHashSize; | ||
| _storeHashAlgorithm = "djb2Hash" /* djb2Hash */; | ||
| // Default is djb2Hash | ||
| _store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map()); | ||
| _ttl; | ||
@@ -355,4 +372,11 @@ // Turned off by default | ||
| } | ||
| if (options?.storeHashSize && options.storeHashSize > 0) { | ||
| this._storeHashSize = options.storeHashSize; | ||
| } | ||
| if (options?.lruSize) { | ||
| this._lruSize = options.lruSize; | ||
| if (options.lruSize > maximumMapSize) { | ||
| this.emit("error", new Error(`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`)); | ||
| } else { | ||
| this._lruSize = options.lruSize; | ||
| } | ||
| } | ||
@@ -362,2 +386,6 @@ if (options?.checkInterval) { | ||
| } | ||
| if (options?.storeHashAlgorithm) { | ||
| this._storeHashAlgorithm = options.storeHashAlgorithm; | ||
| } | ||
| this._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map()); | ||
| this.startIntervalCheck(); | ||
@@ -395,3 +423,3 @@ } | ||
| * 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. | ||
| * @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| */ | ||
@@ -403,6 +431,14 @@ get 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. | ||
| * @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| */ | ||
| set lruSize(value) { | ||
| if (value > maximumMapSize) { | ||
| this.emit("error", new Error(`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`)); | ||
| return; | ||
| } | ||
| this._lruSize = value; | ||
| if (this._lruSize === 0) { | ||
| this._lru = new DoublyLinkedList(); | ||
| return; | ||
| } | ||
| this.lruResize(); | ||
@@ -429,5 +465,41 @@ } | ||
| 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; | ||
| let size = 0; | ||
| for (const store of this._store) { | ||
| size += store.size; | ||
| } | ||
| return size; | ||
| } | ||
| /** | ||
| * Gets the number of hash stores | ||
| * @returns {number} - The number of hash stores | ||
| */ | ||
| get storeHashSize() { | ||
| return this._storeHashSize; | ||
| } | ||
| /** | ||
| * Sets the number of hash stores. This will recreate the store and all data will be cleared | ||
| * @param {number} value - The number of hash stores | ||
| */ | ||
| set storeHashSize(value) { | ||
| if (value === this._storeHashSize) { | ||
| return; | ||
| } | ||
| this._storeHashSize = value; | ||
| this._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map()); | ||
| } | ||
| /** | ||
| * Gets the store hash algorithm | ||
| * @returns {StoreHashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm | ||
| */ | ||
| get storeHashAlgorithm() { | ||
| return this._storeHashAlgorithm; | ||
| } | ||
| /** | ||
| * Sets the store hash algorithm. This will recreate the store and all data will be cleared | ||
| * @param {StoreHashAlgorithm | StoreHashAlgorithmFunction} value - The store hash algorithm | ||
| */ | ||
| set storeHashAlgorithm(value) { | ||
| this._storeHashAlgorithm = value; | ||
| } | ||
| /** | ||
| * Gets the keys | ||
@@ -437,3 +509,14 @@ * @returns {IterableIterator<string>} - The keys | ||
| get keys() { | ||
| return this.concatStores().keys(); | ||
| const keys = new Array(); | ||
| for (const store of this._store) { | ||
| for (const key of store.keys()) { | ||
| const item = store.get(key); | ||
| if (item && this.hasExpired(item)) { | ||
| store.delete(key); | ||
| continue; | ||
| } | ||
| keys.push(key); | ||
| } | ||
| } | ||
| return keys.values(); | ||
| } | ||
@@ -445,5 +528,22 @@ /** | ||
| get items() { | ||
| return this.concatStores().values(); | ||
| const items = new Array(); | ||
| for (const store of this._store) { | ||
| for (const item of store.values()) { | ||
| if (this.hasExpired(item)) { | ||
| store.delete(item.key); | ||
| continue; | ||
| } | ||
| items.push(item); | ||
| } | ||
| } | ||
| return items.values(); | ||
| } | ||
| /** | ||
| * Gets the store | ||
| * @returns {Array<Map<string, CacheableStoreItem>>} - The store | ||
| */ | ||
| get store() { | ||
| return this._store; | ||
| } | ||
| /** | ||
| * Gets the value of the key | ||
@@ -459,3 +559,3 @@ * @param {string} key - The key to get the value | ||
| } | ||
| if (item.expires && item.expires && Date.now() > item.expires) { | ||
| if (item.expires && Date.now() > item.expires) { | ||
| store.delete(key); | ||
@@ -627,3 +727,2 @@ return void 0; | ||
| store.delete(key); | ||
| this._hashCache.delete(key); | ||
| } | ||
@@ -645,13 +744,3 @@ /** | ||
| 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._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map()); | ||
| this._lru = new DoublyLinkedList(); | ||
@@ -665,62 +754,23 @@ } | ||
| getStore(key) { | ||
| const hash2 = this.hashKey(key); | ||
| return this.getStoreFromHash(hash2); | ||
| const hash2 = this.getKeyStoreHash(key); | ||
| this._store[hash2] ||= /* @__PURE__ */ new Map(); | ||
| return this._store[hash2]; | ||
| } | ||
| /** | ||
| * Get the store based on the hash (internal use) | ||
| * @param {number} hash | ||
| * @returns {Map<string, CacheableStoreItem>} | ||
| * Hash the key for which store to go to (internal use) | ||
| * @param {string} key - The key to hash | ||
| * Available algorithms are: SHA256, SHA1, MD5, and djb2Hash. | ||
| * @returns {number} - The hashed key as a number | ||
| */ | ||
| 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; | ||
| } | ||
| getKeyStoreHash(key) { | ||
| if (this._store.length === 1) { | ||
| return 0; | ||
| } | ||
| } | ||
| /** | ||
| * Hash the key (internal use) | ||
| * @param key | ||
| * @returns {number} from 0 to 9 | ||
| */ | ||
| hashKey(key) { | ||
| const cacheHashNumber = this._hashCache.get(key); | ||
| if (typeof cacheHashNumber === "number") { | ||
| return cacheHashNumber; | ||
| if (this._storeHashAlgorithm === "djb2Hash" /* djb2Hash */) { | ||
| return djb2Hash(key, 0, this._storeHashSize); | ||
| } | ||
| let hash2 = 0; | ||
| const primeMultiplier = 31; | ||
| for (let i = 0; i < key.length; i++) { | ||
| hash2 = hash2 * primeMultiplier + key.charCodeAt(i); | ||
| if (typeof this._storeHashAlgorithm === "function") { | ||
| return this._storeHashAlgorithm(key, this._storeHashSize); | ||
| } | ||
| const result = Math.abs(hash2) % 10; | ||
| this._hashCache.set(key, result); | ||
| return result; | ||
| return hashToNumber(key, 0, this._storeHashSize, this._storeHashAlgorithm); | ||
| } | ||
@@ -761,9 +811,6 @@ /** | ||
| /** | ||
| * Resize the LRU cache. This is for internal use | ||
| * Resize the LRU cache. This is for internal use. | ||
| * @returns {void} | ||
| */ | ||
| lruResize() { | ||
| if (this._lruSize === 0) { | ||
| return; | ||
| } | ||
| while (this._lru.size > this._lruSize) { | ||
@@ -782,6 +829,7 @@ const oldestKey = this._lru.getOldest(); | ||
| checkExpiration() { | ||
| const stores = this.concatStores(); | ||
| for (const item of stores.values()) { | ||
| if (item.expires && Date.now() > item.expires) { | ||
| this.delete(item.key); | ||
| for (const store of this._store) { | ||
| for (const item of store.values()) { | ||
| if (item.expires && Date.now() > item.expires) { | ||
| store.delete(item.key); | ||
| } | ||
| } | ||
@@ -816,11 +864,2 @@ } | ||
| /** | ||
| * 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 | ||
@@ -849,5 +888,2 @@ * @param {Function} function_ - The function to wrap | ||
| } | ||
| 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) { | ||
@@ -862,2 +898,8 @@ if (typeof ttl === "string" || ttl === void 0) { | ||
| } | ||
| hasExpired(item) { | ||
| if (item.expires && Date.now() > item.expires) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| }; | ||
@@ -1723,2 +1765,20 @@ | ||
| /** | ||
| * Retrieves the value associated with the given key from the cache. If the key is not found, | ||
| * invokes the provided function to calculate the value, stores it in the cache, and then returns it. | ||
| * | ||
| * @param {string} key - The key to retrieve or set in the cache. | ||
| * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist. | ||
| * @param {WrapFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors. | ||
| * @return {Promise<T | undefined>} - A promise that resolves to the cached or newly computed value, or undefined if an error occurs and caching is not configured for errors. | ||
| */ | ||
| async getOrSet(key, function_, options) { | ||
| const getOrSetOptions = { | ||
| cache: this, | ||
| cacheId: this._cacheId, | ||
| ttl: options?.ttl ?? this._ttl, | ||
| cacheErrors: options?.cacheErrors | ||
| }; | ||
| return getOrSet(key, function_, getOrSetOptions); | ||
| } | ||
| /** | ||
| * Will hash an object using the specified algorithm. The default algorithm is 'sha256'. | ||
@@ -1725,0 +1785,0 @@ * @param {any} object the object to hash |
+62
-33
@@ -112,2 +112,6 @@ import { KeyvStoreAdapter, StoredData, Keyv, StoredDataRaw } from 'keyv'; | ||
| type GetOrSetFunctionOptions = { | ||
| ttl?: number | string; | ||
| cacheErrors?: boolean; | ||
| }; | ||
| type WrapFunctionOptions = { | ||
@@ -129,2 +133,9 @@ ttl?: number | string; | ||
| declare enum StoreHashAlgorithm { | ||
| SHA256 = "sha256", | ||
| SHA1 = "sha1", | ||
| MD5 = "md5", | ||
| djb2Hash = "djb2Hash" | ||
| } | ||
| type StoreHashAlgorithmFunction = ((key: string, storeHashSize: number) => number); | ||
| /** | ||
@@ -136,4 +147,5 @@ * @typedef {Object} CacheableMemoryOptions | ||
| * @property {boolean} [useClone] - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true. | ||
| * @property {number} [lruSize] - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. | ||
| * @property {number} [lruSize] - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| * @property {number} [checkInterval] - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0. | ||
| * @property {number} [storeHashSize] - The number of how many Map stores we have for the hash. Default is 10. | ||
| */ | ||
@@ -145,2 +157,4 @@ type CacheableMemoryOptions = { | ||
| checkInterval?: number; | ||
| storeHashSize?: number; | ||
| storeHashAlgorithm?: StoreHashAlgorithm | ((key: string, storeHashSize: number) => number); | ||
| }; | ||
@@ -153,13 +167,5 @@ type SetOptions = { | ||
| private _lru; | ||
| private readonly _hashCache; | ||
| private readonly _hash0; | ||
| private readonly _hash1; | ||
| private readonly _hash2; | ||
| private readonly _hash3; | ||
| private readonly _hash4; | ||
| private readonly _hash5; | ||
| private readonly _hash6; | ||
| private readonly _hash7; | ||
| private readonly _hash8; | ||
| private readonly _hash9; | ||
| private _storeHashSize; | ||
| private _storeHashAlgorithm; | ||
| private _store; | ||
| private _ttl; | ||
@@ -197,3 +203,3 @@ private _useClone; | ||
| * 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. | ||
| * @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| */ | ||
@@ -203,3 +209,3 @@ get lruSize(): number; | ||
| * 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. | ||
| * @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| */ | ||
@@ -223,2 +229,22 @@ set lruSize(value: number); | ||
| /** | ||
| * Gets the number of hash stores | ||
| * @returns {number} - The number of hash stores | ||
| */ | ||
| get storeHashSize(): number; | ||
| /** | ||
| * Sets the number of hash stores. This will recreate the store and all data will be cleared | ||
| * @param {number} value - The number of hash stores | ||
| */ | ||
| set storeHashSize(value: number); | ||
| /** | ||
| * Gets the store hash algorithm | ||
| * @returns {StoreHashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm | ||
| */ | ||
| get storeHashAlgorithm(): StoreHashAlgorithm | StoreHashAlgorithmFunction; | ||
| /** | ||
| * Sets the store hash algorithm. This will recreate the store and all data will be cleared | ||
| * @param {StoreHashAlgorithm | StoreHashAlgorithmFunction} value - The store hash algorithm | ||
| */ | ||
| set storeHashAlgorithm(value: StoreHashAlgorithm | StoreHashAlgorithmFunction); | ||
| /** | ||
| * Gets the keys | ||
@@ -234,2 +260,7 @@ * @returns {IterableIterator<string>} - The keys | ||
| /** | ||
| * Gets the store | ||
| * @returns {Array<Map<string, CacheableStoreItem>>} - The store | ||
| */ | ||
| get store(): Array<Map<string, CacheableStoreItem>>; | ||
| /** | ||
| * Gets the value of the key | ||
@@ -322,14 +353,9 @@ * @param {string} key - The key to get the value | ||
| /** | ||
| * Get the store based on the hash (internal use) | ||
| * @param {number} hash | ||
| * @returns {Map<string, CacheableStoreItem>} | ||
| * Hash the key for which store to go to (internal use) | ||
| * @param {string} key - The key to hash | ||
| * Available algorithms are: SHA256, SHA1, MD5, and djb2Hash. | ||
| * @returns {number} - The hashed key as a number | ||
| */ | ||
| getStoreFromHash(hash: number): Map<string, CacheableStoreItem>; | ||
| getKeyStoreHash(key: string): number; | ||
| /** | ||
| * Hash the key (internal use) | ||
| * @param key | ||
| * @returns {number} from 0 to 9 | ||
| */ | ||
| hashKey(key: string): number; | ||
| /** | ||
| * Clone the value. This is for internal use | ||
@@ -353,3 +379,3 @@ * @param {any} value - The value to clone | ||
| /** | ||
| * Resize the LRU cache. This is for internal use | ||
| * Resize the LRU cache. This is for internal use. | ||
| * @returns {void} | ||
@@ -374,9 +400,2 @@ */ | ||
| /** | ||
| * 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: any, algorithm?: string): string; | ||
| /** | ||
| * Wrap the function for caching | ||
@@ -389,4 +408,4 @@ * @param {Function} function_ - The function to wrap | ||
| private isPrimitive; | ||
| private concatStores; | ||
| private setTtl; | ||
| private hasExpired; | ||
| } | ||
@@ -721,2 +740,12 @@ | ||
| /** | ||
| * Retrieves the value associated with the given key from the cache. If the key is not found, | ||
| * invokes the provided function to calculate the value, stores it in the cache, and then returns it. | ||
| * | ||
| * @param {string} key - The key to retrieve or set in the cache. | ||
| * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist. | ||
| * @param {WrapFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors. | ||
| * @return {Promise<T | undefined>} - A promise that resolves to the cached or newly computed value, or undefined if an error occurs and caching is not configured for errors. | ||
| */ | ||
| getOrSet<T>(key: string, function_: () => Promise<T>, options?: GetOrSetFunctionOptions): Promise<T | undefined>; | ||
| /** | ||
| * Will hash an object using the specified algorithm. The default algorithm is 'sha256'. | ||
@@ -723,0 +752,0 @@ * @param {any} object the object to hash |
+62
-33
@@ -112,2 +112,6 @@ import { KeyvStoreAdapter, StoredData, Keyv, StoredDataRaw } from 'keyv'; | ||
| type GetOrSetFunctionOptions = { | ||
| ttl?: number | string; | ||
| cacheErrors?: boolean; | ||
| }; | ||
| type WrapFunctionOptions = { | ||
@@ -129,2 +133,9 @@ ttl?: number | string; | ||
| declare enum StoreHashAlgorithm { | ||
| SHA256 = "sha256", | ||
| SHA1 = "sha1", | ||
| MD5 = "md5", | ||
| djb2Hash = "djb2Hash" | ||
| } | ||
| type StoreHashAlgorithmFunction = ((key: string, storeHashSize: number) => number); | ||
| /** | ||
@@ -136,4 +147,5 @@ * @typedef {Object} CacheableMemoryOptions | ||
| * @property {boolean} [useClone] - If true, it will clone the value before returning it. If false, it will return the value directly. Default is true. | ||
| * @property {number} [lruSize] - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. | ||
| * @property {number} [lruSize] - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| * @property {number} [checkInterval] - The interval to check for expired items. If set to 0, it will not check for expired items. Default is 0. | ||
| * @property {number} [storeHashSize] - The number of how many Map stores we have for the hash. Default is 10. | ||
| */ | ||
@@ -145,2 +157,4 @@ type CacheableMemoryOptions = { | ||
| checkInterval?: number; | ||
| storeHashSize?: number; | ||
| storeHashAlgorithm?: StoreHashAlgorithm | ((key: string, storeHashSize: number) => number); | ||
| }; | ||
@@ -153,13 +167,5 @@ type SetOptions = { | ||
| private _lru; | ||
| private readonly _hashCache; | ||
| private readonly _hash0; | ||
| private readonly _hash1; | ||
| private readonly _hash2; | ||
| private readonly _hash3; | ||
| private readonly _hash4; | ||
| private readonly _hash5; | ||
| private readonly _hash6; | ||
| private readonly _hash7; | ||
| private readonly _hash8; | ||
| private readonly _hash9; | ||
| private _storeHashSize; | ||
| private _storeHashAlgorithm; | ||
| private _store; | ||
| private _ttl; | ||
@@ -197,3 +203,3 @@ private _useClone; | ||
| * 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. | ||
| * @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| */ | ||
@@ -203,3 +209,3 @@ get lruSize(): number; | ||
| * 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. | ||
| * @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| */ | ||
@@ -223,2 +229,22 @@ set lruSize(value: number); | ||
| /** | ||
| * Gets the number of hash stores | ||
| * @returns {number} - The number of hash stores | ||
| */ | ||
| get storeHashSize(): number; | ||
| /** | ||
| * Sets the number of hash stores. This will recreate the store and all data will be cleared | ||
| * @param {number} value - The number of hash stores | ||
| */ | ||
| set storeHashSize(value: number); | ||
| /** | ||
| * Gets the store hash algorithm | ||
| * @returns {StoreHashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm | ||
| */ | ||
| get storeHashAlgorithm(): StoreHashAlgorithm | StoreHashAlgorithmFunction; | ||
| /** | ||
| * Sets the store hash algorithm. This will recreate the store and all data will be cleared | ||
| * @param {StoreHashAlgorithm | StoreHashAlgorithmFunction} value - The store hash algorithm | ||
| */ | ||
| set storeHashAlgorithm(value: StoreHashAlgorithm | StoreHashAlgorithmFunction); | ||
| /** | ||
| * Gets the keys | ||
@@ -234,2 +260,7 @@ * @returns {IterableIterator<string>} - The keys | ||
| /** | ||
| * Gets the store | ||
| * @returns {Array<Map<string, CacheableStoreItem>>} - The store | ||
| */ | ||
| get store(): Array<Map<string, CacheableStoreItem>>; | ||
| /** | ||
| * Gets the value of the key | ||
@@ -322,14 +353,9 @@ * @param {string} key - The key to get the value | ||
| /** | ||
| * Get the store based on the hash (internal use) | ||
| * @param {number} hash | ||
| * @returns {Map<string, CacheableStoreItem>} | ||
| * Hash the key for which store to go to (internal use) | ||
| * @param {string} key - The key to hash | ||
| * Available algorithms are: SHA256, SHA1, MD5, and djb2Hash. | ||
| * @returns {number} - The hashed key as a number | ||
| */ | ||
| getStoreFromHash(hash: number): Map<string, CacheableStoreItem>; | ||
| getKeyStoreHash(key: string): number; | ||
| /** | ||
| * Hash the key (internal use) | ||
| * @param key | ||
| * @returns {number} from 0 to 9 | ||
| */ | ||
| hashKey(key: string): number; | ||
| /** | ||
| * Clone the value. This is for internal use | ||
@@ -353,3 +379,3 @@ * @param {any} value - The value to clone | ||
| /** | ||
| * Resize the LRU cache. This is for internal use | ||
| * Resize the LRU cache. This is for internal use. | ||
| * @returns {void} | ||
@@ -374,9 +400,2 @@ */ | ||
| /** | ||
| * 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: any, algorithm?: string): string; | ||
| /** | ||
| * Wrap the function for caching | ||
@@ -389,4 +408,4 @@ * @param {Function} function_ - The function to wrap | ||
| private isPrimitive; | ||
| private concatStores; | ||
| private setTtl; | ||
| private hasExpired; | ||
| } | ||
@@ -721,2 +740,12 @@ | ||
| /** | ||
| * Retrieves the value associated with the given key from the cache. If the key is not found, | ||
| * invokes the provided function to calculate the value, stores it in the cache, and then returns it. | ||
| * | ||
| * @param {string} key - The key to retrieve or set in the cache. | ||
| * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist. | ||
| * @param {WrapFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors. | ||
| * @return {Promise<T | undefined>} - A promise that resolves to the cached or newly computed value, or undefined if an error occurs and caching is not configured for errors. | ||
| */ | ||
| getOrSet<T>(key: string, function_: () => Promise<T>, options?: GetOrSetFunctionOptions): Promise<T | undefined>; | ||
| /** | ||
| * Will hash an object using the specified algorithm. The default algorithm is 'sha256'. | ||
@@ -723,0 +752,0 @@ * @param {any} object the object to hash |
+185
-125
@@ -81,3 +81,3 @@ // src/index.ts | ||
| // src/hash.ts | ||
| import * as crypto from "node:crypto"; | ||
| import * as crypto from "crypto"; | ||
| function hash(object, algorithm = "sha256") { | ||
@@ -92,2 +92,22 @@ const objectString = JSON.stringify(object); | ||
| } | ||
| function hashToNumber(object, min = 0, max = 10, 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); | ||
| const hashHex = hasher.digest("hex"); | ||
| const hashNumber = Number.parseInt(hashHex, 16); | ||
| const range = max - min + 1; | ||
| return min + hashNumber % range; | ||
| } | ||
| function djb2Hash(string_, min = 0, max = 10) { | ||
| let hash2 = 5381; | ||
| for (let i = 0; i < string_.length; i++) { | ||
| hash2 = hash2 * 33 ^ string_.charCodeAt(i); | ||
| } | ||
| const range = max - min + 1; | ||
| return min + Math.abs(hash2) % range; | ||
| } | ||
@@ -169,25 +189,27 @@ // src/coalesce-async.ts | ||
| } | ||
| async function getOrSet(key, function_, options) { | ||
| let value = await options.cache.get(key); | ||
| if (value === void 0) { | ||
| const cacheId = options.cacheId ?? "default"; | ||
| const coalesceKey = `${cacheId}::${key}`; | ||
| value = await coalesceAsync(coalesceKey, async () => { | ||
| try { | ||
| const result = await function_(); | ||
| await options.cache.set(key, result, options.ttl); | ||
| return result; | ||
| } catch (error) { | ||
| options.cache.emit("error", error); | ||
| if (options.cacheErrors) { | ||
| await options.cache.set(key, error, options.ttl); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| return value; | ||
| } | ||
| function wrap(function_, options) { | ||
| const { ttl, keyPrefix, cache } = options; | ||
| const { keyPrefix, cache } = options; | ||
| return async function(...arguments_) { | ||
| let value; | ||
| const cacheKey = createWrapKey(function_, arguments_, keyPrefix); | ||
| value = await cache.get(cacheKey); | ||
| if (value === void 0) { | ||
| const cacheId = options.cacheId ?? "default"; | ||
| const coalesceKey = `${cacheId}::${cacheKey}`; | ||
| value = await coalesceAsync(coalesceKey, async () => { | ||
| try { | ||
| const result = await function_(...arguments_); | ||
| await cache.set(cacheKey, result, ttl); | ||
| return result; | ||
| } catch (error) { | ||
| cache.emit("error", error); | ||
| if (options.cacheErrors) { | ||
| await cache.set(cacheKey, error, ttl); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| return value; | ||
| return cache.getOrSet(cacheKey, async () => function_(...arguments_), options); | ||
| }; | ||
@@ -276,15 +298,10 @@ } | ||
| // src/memory.ts | ||
| var defaultStoreHashSize = 16; | ||
| var maximumMapSize = 16777216; | ||
| var CacheableMemory = class extends Hookified { | ||
| _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(); | ||
| _storeHashSize = defaultStoreHashSize; | ||
| _storeHashAlgorithm = "djb2Hash" /* djb2Hash */; | ||
| // Default is djb2Hash | ||
| _store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map()); | ||
| _ttl; | ||
@@ -312,4 +329,11 @@ // Turned off by default | ||
| } | ||
| if (options?.storeHashSize && options.storeHashSize > 0) { | ||
| this._storeHashSize = options.storeHashSize; | ||
| } | ||
| if (options?.lruSize) { | ||
| this._lruSize = options.lruSize; | ||
| if (options.lruSize > maximumMapSize) { | ||
| this.emit("error", new Error(`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`)); | ||
| } else { | ||
| this._lruSize = options.lruSize; | ||
| } | ||
| } | ||
@@ -319,2 +343,6 @@ if (options?.checkInterval) { | ||
| } | ||
| if (options?.storeHashAlgorithm) { | ||
| this._storeHashAlgorithm = options.storeHashAlgorithm; | ||
| } | ||
| this._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map()); | ||
| this.startIntervalCheck(); | ||
@@ -352,3 +380,3 @@ } | ||
| * 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. | ||
| * @returns {number} - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| */ | ||
@@ -360,6 +388,14 @@ get 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. | ||
| * @param {number} value - The size of the LRU cache. If set to 0, it will not use LRU cache. Default is 0. If you are using LRU then the limit is based on Map() size 17mm. | ||
| */ | ||
| set lruSize(value) { | ||
| if (value > maximumMapSize) { | ||
| this.emit("error", new Error(`LRU size cannot be larger than ${maximumMapSize} due to Map limitations.`)); | ||
| return; | ||
| } | ||
| this._lruSize = value; | ||
| if (this._lruSize === 0) { | ||
| this._lru = new DoublyLinkedList(); | ||
| return; | ||
| } | ||
| this.lruResize(); | ||
@@ -386,5 +422,41 @@ } | ||
| 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; | ||
| let size = 0; | ||
| for (const store of this._store) { | ||
| size += store.size; | ||
| } | ||
| return size; | ||
| } | ||
| /** | ||
| * Gets the number of hash stores | ||
| * @returns {number} - The number of hash stores | ||
| */ | ||
| get storeHashSize() { | ||
| return this._storeHashSize; | ||
| } | ||
| /** | ||
| * Sets the number of hash stores. This will recreate the store and all data will be cleared | ||
| * @param {number} value - The number of hash stores | ||
| */ | ||
| set storeHashSize(value) { | ||
| if (value === this._storeHashSize) { | ||
| return; | ||
| } | ||
| this._storeHashSize = value; | ||
| this._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map()); | ||
| } | ||
| /** | ||
| * Gets the store hash algorithm | ||
| * @returns {StoreHashAlgorithm | StoreHashAlgorithmFunction} - The store hash algorithm | ||
| */ | ||
| get storeHashAlgorithm() { | ||
| return this._storeHashAlgorithm; | ||
| } | ||
| /** | ||
| * Sets the store hash algorithm. This will recreate the store and all data will be cleared | ||
| * @param {StoreHashAlgorithm | StoreHashAlgorithmFunction} value - The store hash algorithm | ||
| */ | ||
| set storeHashAlgorithm(value) { | ||
| this._storeHashAlgorithm = value; | ||
| } | ||
| /** | ||
| * Gets the keys | ||
@@ -394,3 +466,14 @@ * @returns {IterableIterator<string>} - The keys | ||
| get keys() { | ||
| return this.concatStores().keys(); | ||
| const keys = new Array(); | ||
| for (const store of this._store) { | ||
| for (const key of store.keys()) { | ||
| const item = store.get(key); | ||
| if (item && this.hasExpired(item)) { | ||
| store.delete(key); | ||
| continue; | ||
| } | ||
| keys.push(key); | ||
| } | ||
| } | ||
| return keys.values(); | ||
| } | ||
@@ -402,5 +485,22 @@ /** | ||
| get items() { | ||
| return this.concatStores().values(); | ||
| const items = new Array(); | ||
| for (const store of this._store) { | ||
| for (const item of store.values()) { | ||
| if (this.hasExpired(item)) { | ||
| store.delete(item.key); | ||
| continue; | ||
| } | ||
| items.push(item); | ||
| } | ||
| } | ||
| return items.values(); | ||
| } | ||
| /** | ||
| * Gets the store | ||
| * @returns {Array<Map<string, CacheableStoreItem>>} - The store | ||
| */ | ||
| get store() { | ||
| return this._store; | ||
| } | ||
| /** | ||
| * Gets the value of the key | ||
@@ -416,3 +516,3 @@ * @param {string} key - The key to get the value | ||
| } | ||
| if (item.expires && item.expires && Date.now() > item.expires) { | ||
| if (item.expires && Date.now() > item.expires) { | ||
| store.delete(key); | ||
@@ -584,3 +684,2 @@ return void 0; | ||
| store.delete(key); | ||
| this._hashCache.delete(key); | ||
| } | ||
@@ -602,13 +701,3 @@ /** | ||
| 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._store = Array.from({ length: this._storeHashSize }, () => /* @__PURE__ */ new Map()); | ||
| this._lru = new DoublyLinkedList(); | ||
@@ -622,62 +711,23 @@ } | ||
| getStore(key) { | ||
| const hash2 = this.hashKey(key); | ||
| return this.getStoreFromHash(hash2); | ||
| const hash2 = this.getKeyStoreHash(key); | ||
| this._store[hash2] ||= /* @__PURE__ */ new Map(); | ||
| return this._store[hash2]; | ||
| } | ||
| /** | ||
| * Get the store based on the hash (internal use) | ||
| * @param {number} hash | ||
| * @returns {Map<string, CacheableStoreItem>} | ||
| * Hash the key for which store to go to (internal use) | ||
| * @param {string} key - The key to hash | ||
| * Available algorithms are: SHA256, SHA1, MD5, and djb2Hash. | ||
| * @returns {number} - The hashed key as a number | ||
| */ | ||
| 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; | ||
| } | ||
| getKeyStoreHash(key) { | ||
| if (this._store.length === 1) { | ||
| return 0; | ||
| } | ||
| } | ||
| /** | ||
| * Hash the key (internal use) | ||
| * @param key | ||
| * @returns {number} from 0 to 9 | ||
| */ | ||
| hashKey(key) { | ||
| const cacheHashNumber = this._hashCache.get(key); | ||
| if (typeof cacheHashNumber === "number") { | ||
| return cacheHashNumber; | ||
| if (this._storeHashAlgorithm === "djb2Hash" /* djb2Hash */) { | ||
| return djb2Hash(key, 0, this._storeHashSize); | ||
| } | ||
| let hash2 = 0; | ||
| const primeMultiplier = 31; | ||
| for (let i = 0; i < key.length; i++) { | ||
| hash2 = hash2 * primeMultiplier + key.charCodeAt(i); | ||
| if (typeof this._storeHashAlgorithm === "function") { | ||
| return this._storeHashAlgorithm(key, this._storeHashSize); | ||
| } | ||
| const result = Math.abs(hash2) % 10; | ||
| this._hashCache.set(key, result); | ||
| return result; | ||
| return hashToNumber(key, 0, this._storeHashSize, this._storeHashAlgorithm); | ||
| } | ||
@@ -718,9 +768,6 @@ /** | ||
| /** | ||
| * Resize the LRU cache. This is for internal use | ||
| * Resize the LRU cache. This is for internal use. | ||
| * @returns {void} | ||
| */ | ||
| lruResize() { | ||
| if (this._lruSize === 0) { | ||
| return; | ||
| } | ||
| while (this._lru.size > this._lruSize) { | ||
@@ -739,6 +786,7 @@ const oldestKey = this._lru.getOldest(); | ||
| checkExpiration() { | ||
| const stores = this.concatStores(); | ||
| for (const item of stores.values()) { | ||
| if (item.expires && Date.now() > item.expires) { | ||
| this.delete(item.key); | ||
| for (const store of this._store) { | ||
| for (const item of store.values()) { | ||
| if (item.expires && Date.now() > item.expires) { | ||
| store.delete(item.key); | ||
| } | ||
| } | ||
@@ -773,11 +821,2 @@ } | ||
| /** | ||
| * 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 | ||
@@ -806,5 +845,2 @@ * @param {Function} function_ - The function to wrap | ||
| } | ||
| 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) { | ||
@@ -819,2 +855,8 @@ if (typeof ttl === "string" || ttl === void 0) { | ||
| } | ||
| hasExpired(item) { | ||
| if (item.expires && Date.now() > item.expires) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| }; | ||
@@ -1683,2 +1725,20 @@ | ||
| /** | ||
| * Retrieves the value associated with the given key from the cache. If the key is not found, | ||
| * invokes the provided function to calculate the value, stores it in the cache, and then returns it. | ||
| * | ||
| * @param {string} key - The key to retrieve or set in the cache. | ||
| * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist. | ||
| * @param {WrapFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors. | ||
| * @return {Promise<T | undefined>} - A promise that resolves to the cached or newly computed value, or undefined if an error occurs and caching is not configured for errors. | ||
| */ | ||
| async getOrSet(key, function_, options) { | ||
| const getOrSetOptions = { | ||
| cache: this, | ||
| cacheId: this._cacheId, | ||
| ttl: options?.ttl ?? this._ttl, | ||
| cacheErrors: options?.cacheErrors | ||
| }; | ||
| return getOrSet(key, function_, getOrSetOptions); | ||
| } | ||
| /** | ||
| * Will hash an object using the specified algorithm. The default algorithm is 'sha256'. | ||
@@ -1685,0 +1745,0 @@ * @param {any} object the object to hash |
+1
-1
| { | ||
| "name": "cacheable", | ||
| "version": "1.9.0", | ||
| "version": "1.10.0", | ||
| "description": "High Performance Layer 1 / Layer 2 Caching with Keyv Storage", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+149
-5
@@ -40,2 +40,5 @@ [<img align="center" src="https://cacheable.org/logo.svg" alt="Cacheable" />](https://github.com/jaredwray/cacheable) | ||
| * [CacheableMemory - In-Memory Cache](#cacheablememory---in-memory-cache) | ||
| * [CacheableMemory Store Hashing](#cacheablememory-store-hashing) | ||
| * [CacheableMemory LRU Feature](#cacheablememory-lru-feature) | ||
| * [CacheableMemory Performance](#cacheablememory-performance) | ||
| * [CacheableMemory Options](#cacheablememory-options) | ||
@@ -256,2 +259,37 @@ * [CacheableMemory - API](#cacheablememory---api) | ||
| # GetOrSet | ||
| The `getOrSet` method provides a convenient way to implement the cache-aside pattern. It attempts to retrieve a value | ||
| from cache, and if not found, calls the provided function to compute the value and store it in cache before returning | ||
| it. | ||
| ```typescript | ||
| import { Cacheable } from 'cacheable'; | ||
| // Create a new Cacheable instance | ||
| const cache = new Cacheable(); | ||
| // Use getOrSet to fetch user data | ||
| async function getUserData(userId: string) { | ||
| return await cache.getOrSet( | ||
| `user:${userId}`, | ||
| async () => { | ||
| // This function only runs if the data isn't in the cache | ||
| console.log('Fetching user from database...'); | ||
| // Simulate database fetch | ||
| return { id: userId, name: 'John Doe', email: 'john@example.com' }; | ||
| }, | ||
| { ttl: '30m' } // Cache for 30 minutes | ||
| ); | ||
| } | ||
| // First call - will fetch from "database" | ||
| const user1 = await getUserData('123'); | ||
| console.log(user1); // { id: '123', name: 'John Doe', email: 'john@example.com' } | ||
| // Second call - will retrieve from cache | ||
| const user2 = await getUserData('123'); | ||
| console.log(user2); // Same data, but retrieved from cache | ||
| ``` | ||
| ```javascript | ||
@@ -322,2 +360,3 @@ import { Cacheable } from 'cacheable'; | ||
| * `wrap(function, WrapOptions)`: Wraps an `async` function in a cache. | ||
| * `getOrSet(key, valueFunction, ttl?)`: Gets a value from cache or sets it if not found using the provided function. | ||
| * `disconnect()`: Disconnects from the cache stores. | ||
@@ -357,2 +396,99 @@ * `onHook(hook, callback)`: Sets a hook. | ||
| Here are some of the main features of `CacheableMemory`: | ||
| * High performance in-memory cache with a robust API and feature set. 🚀 | ||
| * Can scale past the `16,777,216 (2^24) keys` limit of a single `Map` via `hashStoreSize`. Default is `16` Map objects. | ||
| * LRU (Least Recently Used) cache feature to limit the number of keys in the cache via `lruSize`. Limit to `16,777,216 (2^24) keys` total. | ||
| * Expiration policy to delete expired keys with lazy deletion or aggressive deletion via `checkInterval`. | ||
| * `Wrap` feature to memoize `sync` and `async` functions with stampede protection. | ||
| * Ability to do many operations at once such as `setMany`, `getMany`, `deleteMany`, and `takeMany`. | ||
| * Supports `raw` data retrieval with `getRaw` and `getManyRaw` methods to get the full metadata of the cache entry. | ||
| ## CacheableMemory Store Hashing | ||
| `CacheableMemory` uses `Map` objects to store the keys and values. To make this scale past the `16,777,216 (2^24) keys` limit of a single `Map` we use a hash to balance the data across multiple `Map` objects. This is done by hashing the key and using the hash to determine which `Map` object to use. The default hashing algorithm is `djb2Hash` but you can change it by setting the `storeHashAlgorithm` property in the options. By default we set the amount of `Map` objects to `16`. | ||
| NOTE: if you are using the LRU cache feature the `lruSize` no matter how many `Map` objects you have it will be limited to the `16,777,216 (2^24) keys` limit of a single `Map` object. This is because we use a double linked list to manage the LRU cache and it is not possible to have more than `16,777,216 (2^24) keys` in a single `Map` object. | ||
| Here is an example of how to set the number of `Map` objects and the hashing algorithm: | ||
| ```javascript | ||
| import { CacheableMemory } from 'cacheable'; | ||
| const cache = new CacheableMemory({ | ||
| storeSize: 32, // set the number of Map objects to 32 | ||
| }); | ||
| cache.set('key', 'value'); | ||
| const value = cache.get('key'); // value | ||
| ``` | ||
| Here is an example of how to use the `storeHashAlgorithm` property: | ||
| ```javascript | ||
| import { CacheableMemory } from 'cacheable'; | ||
| const cache = new CacheableMemory({ storeHashAlgorithm: 'sha256' }); | ||
| cache.set('key', 'value'); | ||
| const value = cache.get('key'); // value | ||
| ``` | ||
| If you want to provide your own hashing function you can set the `storeHashAlgorithm` property to a function that takes an object and returns a `number` that is in the range of the amount of `Map` stores you have. | ||
| ```javascript | ||
| import { CacheableMemory } from 'cacheable'; | ||
| /** | ||
| * Custom hash function that takes a key and the size of the store | ||
| * and returns a number between 0 and storeHashSize - 1. | ||
| * @param {string} key - The key to hash. | ||
| * @param {number} storeHashSize - The size of the store (number of Map objects). | ||
| * @returns {number} - A number between 0 and storeHashSize - 1. | ||
| */ | ||
| const customHash = (key, storeHashSize) => { | ||
| // custom hashing logic | ||
| return key.length % storeHashSize; // returns a number between 0 and 31 for 32 Map objects | ||
| }; | ||
| const cache = new CacheableMemory({ storeHashAlgorithm: customHash, storeSize: 32 }); | ||
| cache.set('key', 'value'); | ||
| const value = cache.get('key'); // value | ||
| ``` | ||
| ## CacheableMemory LRU Feature | ||
| You can enable the LRU (Least Recently Used) feature in `CacheableMemory` by setting the `lruSize` property in the options. This will limit the number of keys in the cache to the size you set. When the cache reaches the limit it will remove the least recently used keys from the cache. This is useful if you want to limit the memory usage of the cache. | ||
| When you set the `lruSize` we use a double linked list to manage the LRU cache and also set the `hashStoreSize` to `1` which means we will only use a single `Map` object for the LRU cache. This is because the LRU cache is managed by the double linked list and it is not possible to have more than `16,777,216 (2^24) keys` in a single `Map` object. | ||
| ```javascript | ||
| import { CacheableMemory } from 'cacheable'; | ||
| const cache = new CacheableMemory({ lruSize: 1 }); // sets the LRU cache size to 1000 keys and hashStoreSize to 1 | ||
| cache.set('key1', 'value1'); | ||
| cache.set('key2', 'value2'); | ||
| const value1 = cache.get('key1'); | ||
| console.log(value1); // undefined if the cache is full and key1 is the least recently used | ||
| const value2 = cache.get('key2'); | ||
| console.log(value2); // value2 if key2 is still in the cache | ||
| console.log(cache.size()); // 1 | ||
| ``` | ||
| NOTE: if you set the `lruSize` property to `0` after it was enabled it will disable the LRU cache feature and will not limit the number of keys in the cache. This will remove the `16,777,216 (2^24) keys` limit of a single `Map` object and will allow you to store more keys in the cache. | ||
| ## CacheableMemory Performance | ||
| Our goal with `cacheable` and `CacheableMemory` is to provide a high performance caching engine that is simple to use and has a robust API. We test it against other cacheing engines such that are less feature rich to make sure there is little difference. Here are some of the benchmarks we have run: | ||
| *Memory Benchmark Results:* | ||
| | name | summary | ops/sec | time/op | margin | samples | | ||
| |------------------------------------------|:---------:|----------:|----------:|:--------:|----------:| | ||
| | Map (v22) - set / get | 🥇 | 117K | 9µs | ±1.29% | 110K | | ||
| | Cacheable Memory (v1.10.0) - set / get | -1.3% | 116K | 9µs | ±0.77% | 110K | | ||
| | Node Cache - set / get | -4.1% | 112K | 9µs | ±1.34% | 107K | | ||
| | bentocache (v1.4.0) - set / get | -45% | 65K | 17µs | ±1.10% | 100K | | ||
| *Memory LRU Benchmark Results:* | ||
| | name | summary | ops/sec | time/op | margin | samples | | ||
| |------------------------------------------|:---------:|----------:|----------:|:--------:|----------:| | ||
| | quick-lru (v7.0.1) - set / get | 🥇 | 118K | 9µs | ±0.85% | 112K | | ||
| | Map (v22) - set / get | -0.56% | 117K | 9µs | ±1.35% | 110K | | ||
| | lru.min (v1.1.2) - set / get | -1.7% | 116K | 9µs | ±0.90% | 110K | | ||
| | Cacheable Memory (v1.10.0) - set / get | -3.3% | 114K | 9µs | ±1.16% | 108K | | ||
| As you can see from the benchmarks `CacheableMemory` is on par with other caching engines such as `Map`, `Node Cache`, and `bentocache`. We have also tested it against other LRU caching engines such as `quick-lru` and `lru.min` and it performs well against them too. | ||
| ## CacheableMemory Options | ||
@@ -364,2 +500,4 @@ | ||
| * `checkInterval`: The interval to check for expired keys in milliseconds. Default is `0` which is disabled. | ||
| * `storeHashSize`: The number of `Map` objects to use for the cache. Default is `16`. | ||
| * `storeHashAlgorithm`: The hashing algorithm to use for the cache. Default is `djb2Hash`. | ||
@@ -382,9 +520,15 @@ ## CacheableMemory - API | ||
| * `clear()`: Clears the cache. | ||
| * `size()`: The number of keys in the cache. | ||
| * `keys()`: The keys in the cache. | ||
| * `items()`: The items in the cache as `CacheableStoreItem` example `{ key, value, expires? }`. | ||
| * `ttl`: The default time to live for the cache in milliseconds. Default is `undefined` which is disabled. | ||
| * `useClones`: If the cache should use clones for the values. Default is `true`. | ||
| * `lruSize`: The size of the LRU cache. Default is `0` which is unlimited. | ||
| * `size`: The number of keys in the cache. | ||
| * `checkInterval`: The interval to check for expired keys in milliseconds. Default is `0` which is disabled. | ||
| * `storeHashSize`: The number of `Map` objects to use for the cache. Default is `16`. | ||
| * `storeHashAlgorithm`: The hashing algorithm to use for the cache. Default is `djb2Hash`. | ||
| * `keys`: Get the keys in the cache. Not able to be set. | ||
| * `items`: Get the items in the cache as `CacheableStoreItem` example `{ key, value, expires? }`. | ||
| * `store`: The hash store for the cache which is an array of `Map` objects. | ||
| * `checkExpired()`: Checks for expired keys in the cache. This is used by the `checkInterval` property. | ||
| * `startIntervalCheck()`: Starts the interval check for expired keys if `checkInterval` is above 0 ms. | ||
| * `stopIntervalCheck()`: Stops the interval check for expired keys. | ||
| * `hash(object: any, algorithm = 'sha256'): string`: Hashes an object with the algorithm. Default is `sha256`. | ||
@@ -484,2 +628,2 @@ # Keyv Storage Adapter - KeyvCacheableMemory | ||
| # License and Copyright | ||
| [MIT © Jared Wray](./LICENSE) | ||
| [MIT © Jared Wray](./LICENSE) |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
200876
10.3%4347
3.55%623
30.33%