UNPKG

cacheable

Version:

High Performance Layer 1 / Layer 2 Caching with Keyv Storage

1,088 lines (1,083 loc) 39.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { Cacheable: () => Cacheable, CacheableEvents: () => CacheableEvents, CacheableHooks: () => CacheableHooks, CacheableMemory: () => import_memory2.CacheableMemory, CacheableStats: () => import_utils2.Stats, CacheableSync: () => CacheableSync, CacheableSyncEvents: () => CacheableSyncEvents, HashAlgorithm: () => import_utils2.HashAlgorithm, Keyv: () => import_keyv2.Keyv, KeyvCacheableMemory: () => import_memory2.KeyvCacheableMemory, KeyvHooks: () => import_keyv2.KeyvHooks, calculateTtlFromExpiration: () => import_utils2.calculateTtlFromExpiration, createKeyv: () => import_memory2.createKeyv, getCascadingTtl: () => import_utils2.getCascadingTtl, getOrSet: () => import_memoize2.getOrSet, hash: () => import_utils2.hash, shorthandToMilliseconds: () => import_utils2.shorthandToMilliseconds, shorthandToTime: () => import_utils2.shorthandToTime, wrap: () => import_memoize2.wrap, wrapSync: () => import_memoize2.wrapSync }); module.exports = __toCommonJS(index_exports); var import_memoize = require("@cacheable/memoize"); var import_memory = require("@cacheable/memory"); var import_utils = require("@cacheable/utils"); var import_hookified2 = require("hookified"); var import_keyv = require("keyv"); // src/enums.ts 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"; CacheableHooks2["BEFORE_SECONDARY_SETS_PRIMARY"] = "BEFORE_SECONDARY_SETS_PRIMARY"; return CacheableHooks2; })(CacheableHooks || {}); var CacheableEvents = /* @__PURE__ */ ((CacheableEvents2) => { CacheableEvents2["ERROR"] = "error"; CacheableEvents2["CACHE_HIT"] = "cache:hit"; CacheableEvents2["CACHE_MISS"] = "cache:miss"; return CacheableEvents2; })(CacheableEvents || {}); // src/sync.ts var import_hookified = require("hookified"); var import_qified = require("qified"); var CacheableSyncEvents = /* @__PURE__ */ ((CacheableSyncEvents2) => { CacheableSyncEvents2["ERROR"] = "error"; CacheableSyncEvents2["SET"] = "cache:set"; CacheableSyncEvents2["DELETE"] = "cache:delete"; return CacheableSyncEvents2; })(CacheableSyncEvents || {}); var CacheableSync = class extends import_hookified.Hookified { _qified = new import_qified.Qified(); /** * Creates an instance of CacheableSync * @param options - Configuration options for CacheableSync */ constructor(options) { super(options); this._qified = this.createQified(options.qified); } /** * Gets the Qified instance used for synchronization * @returns The Qified instance */ get qified() { return this._qified; } /** * Sets the Qified instance used for synchronization * @param value - Either an existing Qified instance or MessageProvider(s) */ set qified(value) { this._qified = this.createQified(value); } /** * Publishes a cache event to all the cache instances * @param data - The cache item data containing cacheId, key, value, and optional ttl */ async publish(event, data) { await this._qified.publish(event, { id: crypto.randomUUID(), data }); } /** * Subscribes to sync events and updates the provided storage * @param storage - The Keyv storage instance to update * @param cacheId - The cache ID to identify this instance */ subscribe(storage, cacheId) { this._qified.subscribe("cache:set" /* SET */, { handler: async (message) => { const data = message.data; if (data.cacheId !== cacheId) { await storage.set(data.key, data.value, data.ttl); } } }); this._qified.subscribe("cache:delete" /* DELETE */, { handler: async (message) => { const data = message.data; if (data.cacheId !== cacheId) { await storage.delete(data.key); } } }); } /** * Creates or returns a Qified instance from the provided value * @param value - Either an existing Qified instance or MessageProvider(s) * @returns A Qified instance configured with the provided message provider(s) */ createQified(value) { if (value instanceof import_qified.Qified) { return value; } const providers = Array.isArray(value) ? value : [value]; return new import_qified.Qified({ messageProviders: providers }); } }; // src/index.ts var import_memoize2 = require("@cacheable/memoize"); var import_memory2 = require("@cacheable/memory"); var import_utils2 = require("@cacheable/utils"); var import_keyv2 = require("keyv"); var Cacheable = class extends import_hookified2.Hookified { _primary = (0, import_memory.createKeyv)(); _secondary; _nonBlocking = false; _ttl; _stats = new import_utils.Stats({ enabled: false }); _namespace; _cacheId = Math.random().toString(36).slice(2); _sync; /** * 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?.cacheId) { this._cacheId = options.cacheId; } if (options?.namespace) { this._namespace = options.namespace; this._primary.namespace = this.getNameSpace(); if (this._secondary) { this._secondary.namespace = this.getNameSpace(); } } if (options?.sync) { this._sync = options.sync instanceof CacheableSync ? options.sync : new CacheableSync(options.sync); this._sync.subscribe(this._primary, this._cacheId); } } /** * 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); } /** * The cacheId for the cacheable instance. This is primarily used for the wrap function to not have conflicts. * If it is not set then it will be a random string that is generated * @returns {string} The cacheId for the cacheable instance */ get cacheId() { return this._cacheId; } /** * Sets the cacheId for the cacheable instance. This is primarily used for the wrap function to not have conflicts. * If it is not set then it will be a random string that is generated * @param {string} cacheId The cacheId for the cacheable instance */ set cacheId(cacheId) { this._cacheId = cacheId; } /** * Gets the sync instance for the cacheable instance * @returns {CacheableSync | undefined} The sync instance for the cacheable instance */ get sync() { return this._sync; } /** * Sets the sync instance for the cacheable instance * @param {CacheableSync | undefined} sync The sync instance for the cacheable instance */ set sync(sync) { this._sync = sync; if (this._sync) { this._sync.subscribe(this._primary, this._cacheId); } } /** * Sets the primary store for the cacheable instance * @param {Keyv | KeyvStoreAdapter} primary The primary store for the cacheable instance * @returns {void} */ setPrimary(primary) { if ((0, import_utils.isKeyvInstance)(primary)) { this._primary = primary; } else { this._primary = new import_keyv.Keyv(primary); } this._primary.on("error", (error) => { this.emit("error" /* ERROR */, error); }); } /** * 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) { if ((0, import_utils.isKeyvInstance)(secondary)) { this._secondary = secondary; } else { this._secondary = new import_keyv.Keyv(secondary); } this._secondary.on("error", (error) => { this.emit("error" /* ERROR */, error); }); } getNameSpace() { if (typeof this._namespace === "function") { return this._namespace(); } return this._namespace; } /** * Retrieves an entry from the cache. * * Checks the primary store first; if not found and a secondary store is configured, * it will fetch from the secondary, repopulate the primary, and return the result. * * @typeParam T - The expected type of the stored value. * @param {string} key - The cache key to retrieve. * @param {GetOptions} - options such as to bypass `nonBlocking` for this call * @returns {Promise<T | undefined>} * A promise that resolves to the cached value if found, or `undefined`. */ async get(key, options) { const result = await this.getRaw(key, options); return result?.value; } /** * Retrieves the raw entry from the cache including metadata like expiration. * * Checks the primary store first; if not found and a secondary store is configured, * it will fetch from the secondary, repopulate the primary, and return the result. * * @typeParam T - The expected type of the stored value. * @param {string} key - The cache key to retrieve. * @param {GetOptions} - options such as to bypass `nonBlocking` for this call * @returns {Promise<StoredDataRaw<T>>} * A promise that resolves to the full raw data object if found, or undefined. */ async getRaw(key, options) { let result; try { await this.hook("BEFORE_GET" /* BEFORE_GET */, key); result = await this._primary.getRaw(key); let ttl; if (result) { this.emit("cache:hit" /* CACHE_HIT */, { key, value: result.value, store: "primary" }); } else { this.emit("cache:miss" /* CACHE_MISS */, { key, store: "primary" }); } const nonBlocking = options?.nonBlocking ?? this._nonBlocking; if (!result && this._secondary) { let secondaryProcessResult; if (nonBlocking) { secondaryProcessResult = await this.processSecondaryForGetRawNonBlocking( this._primary, this._secondary, key ); } else { secondaryProcessResult = await this.processSecondaryForGetRaw( this._primary, this._secondary, key ); } if (secondaryProcessResult) { result = secondaryProcessResult.result; ttl = secondaryProcessResult.ttl; } } await this.hook("AFTER_GET" /* AFTER_GET */, { key, result, ttl }); } 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; } /** * Retrieves multiple raw entries from the cache including metadata like expiration. * * Checks the primary store for each key; if a key is missing and a secondary store is configured, * it will fetch from the secondary store, repopulate the primary store, and return the results. * * @typeParam T - The expected type of the stored values. * @param {string[]} keys - The cache keys to retrieve. * @param {GetOptions} - options such as to bypass `nonBlocking` on this call * @returns {Promise<Array<StoredDataRaw<T>>>} * A promise that resolves to an array of raw data objects. */ async getManyRaw(keys, options) { let result = []; try { await this.hook("BEFORE_GET_MANY" /* BEFORE_GET_MANY */, keys); result = await this._primary.getManyRaw(keys); for (const [i, key] of keys.entries()) { if (result[i]) { this.emit("cache:hit" /* CACHE_HIT */, { key, value: result[i].value, store: "primary" }); } else { this.emit("cache:miss" /* CACHE_MISS */, { key, store: "primary" }); } } const nonBlocking = options?.nonBlocking ?? this._nonBlocking; if (this._secondary) { if (nonBlocking) { await this.processSecondaryForGetManyRawNonBlocking( this._primary, this._secondary, keys, result ); } else { await this.processSecondaryForGetManyRaw( this._primary, this._secondary, keys, result ); } } 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; } /** * Retrieves multiple entries from the cache. * Checks the primary store for each key; if a key is missing and a secondary store is configured, * it will fetch from the secondary store, repopulate the primary store, and return the results. * * @typeParam T - The expected type of the stored values. * @param {string[]} keys - The cache keys to retrieve. * @param {GetOptions} - options such as to bypass `nonBlocking` on this call * @returns {Promise<Array<T | undefined>>} * A promise that resolves to an array of cached values or `undefined` for misses. */ async getMany(keys, options) { const result = await this.getManyRaw(keys, options); return result.map((item) => item?.value); } /** * 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 = (0, import_utils.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); for (const promise of promises) { promise.catch((error) => { this.emit("error" /* ERROR */, error); }); } } else { const results = await Promise.all(promises); result = results[0]; } await this.hook("AFTER_SET" /* AFTER_SET */, item); if (this._sync && result) { await this._sync.publish("cache:set" /* SET */, { cacheId: this._cacheId, key: item.key, value: item.value, ttl: item.ttl }); } } 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).catch((error) => { this.emit("error" /* ERROR */, error); }); } else { await this.setManyKeyv(this._secondary, items); } } await this.hook("AFTER_SET_MANY" /* AFTER_SET_MANY */, items); if (this._sync && result) { for (const item of items) { await this._sync.publish("cache:set" /* SET */, { cacheId: this._cacheId, key: item.key, value: item.value, ttl: (0, import_utils.shorthandToMilliseconds)(item.ttl) }); } } } 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<T | undefined>} 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<Array<T | undefined>>} 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<boolean>} 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<boolean[]>} 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<boolean>} 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); for (const promise of promises) { promise.catch((error) => { this.emit("error" /* ERROR */, error); }); } } else { const resultAll = await Promise.all(promises); result = resultAll[0]; } if (this._sync && result) { await this._sync.publish("cache:delete" /* DELETE */, { cacheId: this._cacheId, key }); } 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<boolean>} 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._primary.deleteMany(keys); if (this._secondary) { if (this._nonBlocking) { this._secondary.deleteMany(keys).catch((error) => { this.emit("error" /* ERROR */, error); }); } else { await this._secondary.deleteMany(keys); } } if (this._sync && result) { for (const key of keys) { await this._sync.publish("cache:delete" /* DELETE */, { cacheId: this._cacheId, key }); } } return result; } /** * Clears the primary store. If the secondary store is set then it will also clear the secondary store. * @returns {Promise<void>} */ 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<void>} */ 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 */ // biome-ignore lint/suspicious/noExplicitAny: type format wrap(function_, options) { const cacheAdapter = { get: async (key) => this.get(key), has: async (key) => this.has(key), // biome-ignore lint/suspicious/noExplicitAny: CacheInstance requires any type set: async (key, value, ttl) => { await this.set(key, value, ttl); }, /* c8 ignore start */ // biome-ignore lint/suspicious/noExplicitAny: CacheInstance interface on: (event, listener) => { this.on(event, listener); }, /* c8 ignore stop */ // biome-ignore lint/suspicious/noExplicitAny: CacheInstance requires any type emit: (event, ...args) => this.emit(event, ...args) }; const wrapOptions = { ttl: options?.ttl ?? this._ttl, keyPrefix: options?.keyPrefix, createKey: options?.createKey, cacheErrors: options?.cacheErrors, cache: cacheAdapter, cacheId: this._cacheId, serialize: options?.serialize }; return (0, import_memoize.wrap)(function_, wrapOptions); } /** * 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 {GetOrSetKey} key - The key to retrieve or set in the cache. This can also be a function that returns a string key. * If a function is provided, it will be called with the cache options to generate the key. * @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist. * @param {GetOrSetFunctionOptions} [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 cacheAdapter = { get: async (key2) => this.get(key2), has: async (key2) => this.has(key2), // biome-ignore lint/suspicious/noExplicitAny: CacheInstance requires any type set: async (key2, value, ttl) => { await this.set(key2, value, ttl); }, /* c8 ignore start */ // biome-ignore lint/suspicious/noExplicitAny: CacheInstance interface on: (event, listener) => { this.on(event, listener); }, /* c8 ignore stop */ // biome-ignore lint/suspicious/noExplicitAny: CacheInstance requires any type emit: (event, ...args) => this.emit(event, ...args) }; const getOrSetOptions = { cache: cacheAdapter, cacheId: this._cacheId, ttl: options?.ttl ?? this._ttl, cacheErrors: options?.cacheErrors, throwErrors: options?.throwErrors }; return (0, import_memoize.getOrSet)(key, function_, getOrSetOptions); } /** * 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 = import_utils.HashAlgorithm.SHA256) { const validAlgorithm = Object.values(import_utils.HashAlgorithm).includes(algorithm) ? algorithm : import_utils.HashAlgorithm.SHA256; return (0, import_utils.hash)(object, { algorithm: validAlgorithm }); } async setManyKeyv(keyv, items) { const entries = []; for (const item of items) { const finalTtl = (0, import_utils.shorthandToMilliseconds)(item.ttl ?? this._ttl); entries.push({ key: item.key, value: item.value, ttl: finalTtl }); } await keyv.setMany(entries); return true; } async hasManyKeyv(keyv, keys) { const promises = []; for (const key of keys) { promises.push(keyv.has(key)); } return Promise.all(promises); } /** * Processes a single key from secondary store for getRaw operation * @param primary - the primary store to use * @param secondary - the secondary store to use * @param key - The key to retrieve from secondary store * @returns Promise containing the result and TTL information */ async processSecondaryForGetRaw(primary, secondary, key) { const secondaryResult = await secondary.getRaw(key); if (secondaryResult?.value) { this.emit("cache:hit" /* CACHE_HIT */, { key, value: secondaryResult.value, store: "secondary" }); const cascadeTtl = (0, import_utils.getCascadingTtl)(this._ttl, this._primary.ttl); const expires = secondaryResult.expires ?? void 0; const ttl = (0, import_utils.calculateTtlFromExpiration)(cascadeTtl, expires); const setItem = { key, value: secondaryResult.value, ttl }; await this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem); await primary.set(setItem.key, setItem.value, setItem.ttl); return { result: secondaryResult, ttl }; } else { this.emit("cache:miss" /* CACHE_MISS */, { key, store: "secondary" }); return void 0; } } /** * Processes a single key from secondary store for getRaw operation in non-blocking mode * Non-blocking mode means we don't wait for secondary operations that update primary store * @param primary - the primary store to use * @param secondary - the secondary store to use * @param key - The key to retrieve from secondary store * @returns Promise containing the result and TTL information */ async processSecondaryForGetRawNonBlocking(primary, secondary, key) { const secondaryResult = await secondary.getRaw(key); if (secondaryResult?.value) { this.emit("cache:hit" /* CACHE_HIT */, { key, value: secondaryResult.value, store: "secondary" }); const cascadeTtl = (0, import_utils.getCascadingTtl)(this._ttl, this._primary.ttl); const expires = secondaryResult.expires ?? void 0; const ttl = (0, import_utils.calculateTtlFromExpiration)(cascadeTtl, expires); const setItem = { key, value: secondaryResult.value, ttl }; this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem).then(async () => { await primary.set(setItem.key, setItem.value, setItem.ttl); }).catch((error) => { this.emit("error" /* ERROR */, error); }); return { result: secondaryResult, ttl }; } else { this.emit("cache:miss" /* CACHE_MISS */, { key, store: "secondary" }); return void 0; } } /** * Processes missing keys from secondary store for getManyRaw operation * @param primary - the primary store to use * @param secondary - the secondary store to use * @param keys - The original array of keys requested * @param result - The result array from primary store (will be modified) * @returns Promise<void> */ async processSecondaryForGetManyRaw(primary, secondary, keys, result) { const missingKeys = []; for (const [i, key] of keys.entries()) { if (!result[i]) { missingKeys.push(key); } } const secondaryResults = await secondary.getManyRaw(missingKeys); let secondaryIndex = 0; for await (const [i, key] of keys.entries()) { if (!result[i]) { const secondaryResult = secondaryResults[secondaryIndex]; if (secondaryResult && secondaryResult.value !== void 0) { result[i] = secondaryResult; this.emit("cache:hit" /* CACHE_HIT */, { key, value: secondaryResult.value, store: "secondary" }); const cascadeTtl = (0, import_utils.getCascadingTtl)(this._ttl, this._primary.ttl); let { expires } = secondaryResult; if (expires === null) { expires = void 0; } const ttl = (0, import_utils.calculateTtlFromExpiration)(cascadeTtl, expires); const setItem = { key, value: secondaryResult.value, ttl }; await this.hook( "BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem ); await primary.set(setItem.key, setItem.value, setItem.ttl); } else { this.emit("cache:miss" /* CACHE_MISS */, { key, store: "secondary" }); } secondaryIndex++; } } } /** * Processes missing keys from secondary store for getManyRaw operation in non-blocking mode * Non-blocking mode means we don't wait for secondary operations that update primary store * @param secondary - the secondary store to use * @param keys - The original array of keys requested * @param result - The result array from primary store (will be modified) * @returns Promise<void> */ async processSecondaryForGetManyRawNonBlocking(primary, secondary, keys, result) { const missingKeys = []; for (const [i, key] of keys.entries()) { if (!result[i]) { missingKeys.push(key); } } const secondaryResults = await secondary.getManyRaw(missingKeys); let secondaryIndex = 0; for await (const [i, key] of keys.entries()) { if (!result[i]) { const secondaryResult = secondaryResults[secondaryIndex]; if (secondaryResult && secondaryResult.value !== void 0) { result[i] = secondaryResult; this.emit("cache:hit" /* CACHE_HIT */, { key, value: secondaryResult.value, store: "secondary" }); const cascadeTtl = (0, import_utils.getCascadingTtl)(this._ttl, this._primary.ttl); let { expires } = secondaryResult; if (expires === null) { expires = void 0; } const ttl = (0, import_utils.calculateTtlFromExpiration)(cascadeTtl, expires); const setItem = { key, value: secondaryResult.value, ttl }; this.hook("BEFORE_SECONDARY_SETS_PRIMARY" /* BEFORE_SECONDARY_SETS_PRIMARY */, setItem).then(async () => { await primary.set(setItem.key, setItem.value, setItem.ttl); }).catch((error) => { this.emit("error" /* ERROR */, error); }); } else { this.emit("cache:miss" /* CACHE_MISS */, { key, store: "secondary" }); } secondaryIndex++; } } } 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, CacheableSync, CacheableSyncEvents, HashAlgorithm, Keyv, KeyvCacheableMemory, KeyvHooks, calculateTtlFromExpiration, createKeyv, getCascadingTtl, getOrSet, hash, shorthandToMilliseconds, shorthandToTime, wrap, wrapSync });