# Cacheable Caching for Node.js Source Index: https://cacheable.org/llms.txt ## Documentation ### Getting Started Guide URL: https://cacheable.org/docs/ [Cacheable](https://github.com/jaredwray/cacheable) > Caching for Nodejs based on Keyv [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) `Cacheable` provides a robust, scalable, and maintained set of caching packages that can be used in various projects. The packages in this repository are: | Package | Downloads | Description | |---------|-----------|-------------| | [cacheable](https://github.com/jaredwray/cacheable/tree/main/packages/cacheable) | [![npm](https://img.shields.io/npm/dm/cacheable.svg)](https://www.npmjs.com/package/cacheable) | Next generation caching framework built from the ground up with layer 1 / layer 2 caching. | | [cache-manager](https://github.com/jaredwray/cacheable/tree/main/packages/cache-manager) | [![npm](https://img.shields.io/npm/dm/cache-manager.svg)](https://www.npmjs.com/package/cache-manager) | Cache Manager that is used in services such as NestJS and others with robust features such as `wrap` and more. | | [cacheable-request](https://github.com/jaredwray/cacheable/tree/main/packages/cacheable-request) | [![npm](https://img.shields.io/npm/dm/cacheable-request.svg)](https://www.npmjs.com/package/cacheable-request) | Wrap native HTTP requests with RFC compliant cache support | | [flat-cache](https://github.com/jaredwray/cacheable/tree/main/packages/flat-cache) | [![npm](https://img.shields.io/npm/dm/flat-cache.svg)](https://www.npmjs.com/package/flat-cache) | Fast In-Memory Caching with file store persistence | | [file-entry-cache](https://github.com/jaredwray/cacheable/tree/main/packages/file-entry-cache) | [![npm](https://img.shields.io/npm/dm/file-entry-cache.svg)](https://www.npmjs.com/package/file-entry-cache) | A lightweight cache for file metadata, ideal for processes that work on a specific set of files and only need to reprocess files that have changed since the last run | | [@cacheable/node-cache](https://github.com/jaredwray/cacheable/tree/main/packages/node-cache) | [![npm](https://img.shields.io/npm/dm/@cacheable/node-cache.svg)](https://www.npmjs.com/package/@cacheable/node-cache) | Maintained built in replacement of `node-cache` | | [@cacheable/memory](https://github.com/jaredwray/cacheable/tree/main/packages/memory) | [![npm](https://img.shields.io/npm/dm/@cacheable/memory.svg)](https://www.npmjs.com/package/@cacheable/memory) | In-Memory Caching with LRU support | | [@cacheable/utils](https://github.com/jaredwray/cacheable/tree/main/packages/utils) | [![npm](https://img.shields.io/npm/dm/@cacheable/utils.svg)](https://www.npmjs.com/package/@cacheable/utils) | Utility functions for cacheable with `hashing`, `shorthand time`, `memoize` and more | The website documentation for https://cacheable.org is included in this repository [here](https://github.com/jaredwray/cacheable/tree/main/packages/website). # How to Use the Cacheable Mono Repo * [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) - Our code of conduct * [CONTRIBUTING](CONTRIBUTING.md) - How to contribute to this project * [SECURITY](SECURITY.md) - Security guidelines and supported versions ## Open a Pull Request Please follow the [CONTRIBUTING](CONTRIBUTING.md) guidelines provided and remember you will need to do setup on this project such as having redis running (via docker), building the project `pnpm build`, and testing `pnpm test` which will also perform linting. ## Post an Issue To post an issue, navigate to the "Issues" tab in the main repository, and then select "New Issue." Enter a clear title describing the issue, as well as a description containing additional relevant information. Also select the label that best describes your issue type. For a bug report, for example, create an issue with the label "bug." In the description field, Be sure to include replication steps, as well as any relevant error messages. If you're reporting a security violation, be sure to check out the project's [security policy](https://github.com/jaredwray/cacheable/blob/main/SECURITY.md). Please also refer to our [Code of Conduct](https://github.com/jaredwray/cacheable/blob/main/CODE_OF_CONDUCT.md) for more information on how to report issues. ## Ask a Question To ask a question, create an issue with the label "question." In the issue description, include the related code and any context that can help us answer your question. ## License [MIT © Jared Wray](LICENSE) ### @cacheable/benchmark URL: https://cacheable.org/docs/benchmark/ [Cacheable](https://github.com/jaredwray/cacheable) # Cacheable Benchmark # How to Contribute You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`. # License and Copyright [MIT © Jared Wray](./LICENSE) ### cache-manager URL: https://cacheable.org/docs/cache-manager/ [Cacheable](https://github.com/jaredwray/cacheable) # cache-manager [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/cache-manager)](https://npmjs.com/package/cache-manager) [![npm](https://img.shields.io/npm/v/cache-manager)](https://npmjs.com/package/cache-manager) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) # Simple and fast NodeJS caching module. A cache module for NodeJS that allows easy wrapping of functions in cache, tiered caches, and a consistent interface. - Made with Typescript and compatible with [ESModules](https://nodejs.org/docs/latest-v14.x/api/esm.html). - Easy way to wrap any function in cache, supports a mechanism to refresh expiring cache keys in background. - Tiered caches -- data gets stored in each cache and fetched from the highest priority cache(s) first. - `nonBlocking` option that optimizes how the system handles multiple stores. - Use with any [Keyv](https://keyv.org/) compatible storage adapter. - 100% test coverage via [vitest](https://github.com/vitest-dev/vitest). We moved to using [Keyv](https://keyv.org/) which are more actively maintained and have a larger community. A special thanks to [Tim Phan](https://github.com/timphandev) who took `cache-manager` v5 and ported it to [Keyv](https://keyv.org/) which is the foundation of v6. 🎉 Another special thanks to [Doug Ayers](https://github.com/douglascayers) who wrote `promise-coalesce` which was used in v5 and now embedded in v6. # Migration from v6 to v7 `v7` has only one breaking change which is changing the return type from `null` to `undefined` when there is no data to return. This is to align with the [Keyv](https://keyv.org) API and to make it more consistent with the rest of the methods. Below is an example of how to migrate from `v6` to `v7`: ```ts import { createCache } from 'cache-manager'; const cache = createCache(); const result = await cache.get('key'); // result will be undefined if the key is not found or expired console.log(result); // undefined ``` # Migration from v5 to v6 `v6` is a major update and has breaking changes primarily around the storage adapters. We have moved to using [Keyv](https://keyv.org/) which are more actively maintained and have a larger community. Below are the changes you need to make to migrate from `v5` to `v6`. In `v5` the `memoryStore` was used to create a memory store, in `v6` you can use any storage adapter that Keyv supports. Below is an example of how to migrate from `v5` to `v6`: ```ts import { createCache, memoryStore } from 'cache-manager'; // Create memory cache synchronously const memoryCache = createCache(memoryStore({ max: 100, ttl: 10 * 1000 /*milliseconds*/, })); ``` In `v6` you can use any storage adapter that Keyv supports. Below is an example of using the in memory store with `Keyv`: ```ts import { createCache } from 'cache-manager'; const cache = createCache(); ``` If you would like to do multiple stores you can do the following: ```ts import { createCache } from 'cache-manager'; import { createKeyv } from 'cacheable'; import { createKeyv as createKeyvRedis } from '@keyv/redis'; const memoryStore = createKeyv(); const redisStore = createKeyvRedis('redis://user:pass@localhost:6379'); const cache = createCache({ stores: [memoryStore, redisStore], }); ``` When doing in memory caching and getting errors on `symbol` or if the object is coming back wrong like on `Uint8Array` you will want to set the `serialization` and `deserialization` options in Keyv to `undefined` as it will try to do json serialization. ```ts import { createCache } from "cache-manager"; import { Keyv } from "keyv"; const keyv = new Keyv(); keyv.serialize = undefined; keyv.deserialize = undefined; const memoryCache = createCache({ stores: [keyv], }); ``` The other option is to set the serialization to something that is not `JSON.stringify`. You can read more about it here: https://keyv.org/docs/keyv/#custom-serializers If you would like a more robust in memory storage adapter you can use `CacheableMemory` from Cacheable. Below is an example of how to migrate from `v5` to `v6` using `CacheableMemory`: ```ts import { createCache } from 'cache-manager'; import { createKeyv } from 'cacheable'; const cache = createCache({ stores: [createKeyv({ ttl: 60000, lruSize: 5000 })], }); ``` To learn more about `CacheableMemory` please visit: http://cacheable.org/docs/cacheable/#cacheablememory---in-memory-cache If you are still wanting to use the legacy storage adapters you can use the `KeyvAdapter` to wrap the storage adapter. Below is an example of how to migrate from `v5` to `v6` using `cache-manager-redis-yet` by going to [Using Legacy Storage Adapters](#using-legacy-storage-adapters). If you are looking for older documentation you can find it here: * [v5 Documentation](https://github.com/jaredwray/cacheable/blob/main/packages/cache-manager/READMEv5.md) * [v4 Documentation](https://github.com/jaredwray/cacheable/blob/main/packages/cache-manager/READMEv4.md) ## Table of Contents * [Installation](#installation) * [Quick start](#quick-start) * [Using `CacheableMemory` or `lru-cache` as storage adapter](#using-cacheablememory-or-lru-cache-as-storage-adapter) * [Options](#options) * [Methods](#methods) * [.set](#set) * [.mset](#mset) * [.get](#get) * [.mget](#mget) * [.ttl](#ttl) * [.del](#del) * [.mdel](#mdel) * [.clear](#clear) * [.wrap](#wrap) * [.disconnect](#disconnect) * [Events](#events) * [.set](#set) * [.del](#del) * [.clear](#clear) * [.refresh](#refresh) * [Properties](#properties) * [.cacheId](#cacheId) * [.stores](#stores) * [Doing Iteration on Stores](#doing-iteration-on-stores) * [Update on `redis` and `ioredis` Support](#update-on-redis-and-ioredis-support) * [Using Legacy Storage Adapters](#using-legacy-storage-adapters) * [Contribute](#contribute) * [License](#license) # Installation ```sh npm install cache-manager ``` By default, everything is stored in memory; you can optionally also install a storage adapter; choose one from any of the storage adapters supported by Keyv: ```sh npm install @keyv/redis npm install @keyv/memcache npm install @keyv/mongo npm install @keyv/sqlite npm install @keyv/postgres npm install @keyv/mysql npm install @keyv/etcd ``` In addition Keyv supports other storage adapters such as `lru-cache` and `CacheableMemory` from Cacheable (more examples below). Please read [Keyv document](https://keyv.org/docs/) for more information. # Quick start ```typescript import { Keyv } from 'keyv'; import { createCache } from 'cache-manager'; // Memory store by default const cache = createCache() // Single store which is in memory const cache = createCache({ stores: [new Keyv()], }) ``` Here is an example of doing layer 1 and layer 2 caching with the in-memory being `CacheableMemory` from Cacheable and the second layer being `@keyv/redis`: ```ts import { Keyv } from 'keyv'; import KeyvRedis from '@keyv/redis'; import { CacheableMemory } from 'cacheable'; import { createCache } from 'cache-manager'; // Multiple stores const cache = createCache({ stores: [ // High performance in-memory cache with LRU and TTL new Keyv({ store: new CacheableMemory({ ttl: 60000, lruSize: 5000 }), }), // Redis Store new Keyv({ store: new KeyvRedis('redis://user:pass@localhost:6379'), }), ], }) ``` Once it is created, you can use the cache object to set, get, delete, and wrap functions in cache. ```ts // With default ttl and refreshThreshold const cache = createCache({ ttl: 10000, refreshThreshold: 3000, }) await cache.set('foo', 'bar') // => bar await cache.get('foo') // => bar await cache.del('foo') // => true await cache.get('foo') // => null await cache.wrap('key', () => 'value') // => value ``` # Using CacheableMemory or lru-cache as storage adapter Because we are using [Keyv](https://keyv.org/), you can use any storage adapter that Keyv supports such as `lru-cache` or `CacheableMemory` from Cacheable. Below is an example of using `CacheableMemory`: In this example we are using `CacheableMemory` from Cacheable which is a fast in-memory cache that supports LRU and and TTL expiration. ```ts import { createCache } from 'cache-manager'; import { Keyv } from 'keyv'; import { KeyvCacheableMemory } from 'cacheable'; const store = new KeyvCacheableMemory({ ttl: 60000, lruSize: 5000 }); const keyv = new Keyv({ store }); const cache = createCache({ stores: [keyv] }); ``` Here is an example using `lru-cache`: ```ts import { createCache } from 'cache-manager'; import { Keyv } from 'keyv'; import { LRU } from 'lru-cache'; const keyv = new Keyv({ store: new LRU({ max: 5000, maxAge: 60000 }) }); const cache = createCache({ stores: [keyv] }); ``` ## Options - **stores**?: Keyv[] List of Keyv instance. Please refer to the [Keyv document](https://keyv.org/docs/#3.-create-a-new-keyv-instance) for more information. - **ttl**?: number - Default time to live in milliseconds. The time to live in milliseconds. This is the maximum amount of time that an item can be in the cache before it is removed. - **refreshThreshold**?: number | (value:T) => number - Default refreshThreshold in milliseconds. You can also provide a function that will return the refreshThreshold based on the value. If the remaining TTL is less than **refreshThreshold**, the system will update the value asynchronously in background. - **refreshAllStores**?: boolean - Default false If set to true, the system will update the value of all stores when the refreshThreshold is met. Otherwise, it will only update from the top to the store that triggered the refresh. - **nonBlocking**?: boolean - Default false If set to true, the system will not block when multiple stores are used. Here is how it affects the type of functions: * `set and mset` - will not wait for all stores to finish. * `get and mget` - will return the first (fastest) value found. * `del and mdel` - will not wait for all stores to finish. * `clear` - will not wait for all stores to finish. * `wrap` - will do the same as `get` and `set` (return the first value found and not wait for all stores to finish). - **cacheId**?: string - Defaults to random string Unique identifier for the cache instance. This is primarily used to not have conflicts when using `wrap` with multiple cache instances. # Methods ## set `set(key, value, [ttl]): Promise` Sets a key value pair. It is possible to define a ttl (in milliseconds). An error will be throw on any failed ```ts await cache.set('key-1', 'value 1') // expires after 5 seconds await cache.set('key 2', 'value 2', 5000) ``` See unit tests in [`test/set.test.ts`](./test/set.test.ts) for more information. ## mset `mset(keys: [ { key, value, ttl } ]): Promise` Sets multiple key value pairs. It is possible to define a ttl (in milliseconds). An error will be throw on any failed ```ts await cache.mset([ { key: 'key-1', value: 'value 1' }, { key: 'key-2', value: 'value 2', ttl: 5000 }, ]); ``` ## get `get(key): Promise` Gets a saved value from the cache. Returns a null if not found or expired. If the value was found it returns the value. ```ts await cache.set('key', 'value') await cache.get('key') // => value await cache.get('foo') // => null ``` See unit tests in [`test/get.test.ts`](./test/get.test.ts) for more information. ## mget `mget(keys: [key]): Promise` Gets multiple saved values from the cache. Returns a null if not found or expired. If the value was found it returns the value. ```ts await cache.mset([ { key: 'key-1', value: 'value 1' }, { key: 'key-2', value: 'value 2' }, ]); await cache.mget(['key-1', 'key-2', 'key-3']) // => ['value 1', 'value 2', null] ``` ## ttl `ttl(key): Promise` Gets the expiration time of a key in milliseconds. Returns a null if not found or expired. ```ts await cache.set('key', 'value', 1000); // expires after 1 second await cache.ttl('key'); // => the expiration time in milliseconds await cache.get('foo'); // => null ``` See unit tests in [`test/ttl.test.ts`](./test/ttl.test.ts) for more information. ## del `del(key): Promise` Delete a key, an error will be throw on any failed. ```ts await cache.set('key', 'value') await cache.get('key') // => value await cache.del('key') await cache.get('key') // => null ``` See unit tests in [`test/del.test.ts`](./test/del.test.ts) for more information. ## mdel `mdel(keys: [key]): Promise` Delete multiple keys, an error will be throw on any failed. ```ts await cache.mset([ { key: 'key-1', value: 'value 1' }, { key: 'key-2', value: 'value 2' }, ]); await cache.mdel(['key-1', 'key-2']) ``` ## clear `clear(): Promise` Flush all data, an error will be throw on any failed. ```ts await cache.set('key-1', 'value 1') await cache.set('key-2', 'value 2') await cache.get('key-1') // => value 1 await cache.get('key-2') // => value 2 await cache.clear() await cache.get('key-1') // => null await cache.get('key-2') // => null ``` See unit tests in [`test/clear.test.ts`](./test/clear.test.ts) for more information. ## wrap `wrap(key, fn: async () => value, [ttl], [refreshThreshold]): Promise` Alternatively, with optional parameters as options object supporting a `raw` parameter: `wrap(key, fn: async () => value, { ttl?: number, refreshThreshold?: number, raw?: true }): Promise` Wraps a function in cache. The first time the function is run, its results are stored in cache so subsequent calls retrieve from cache instead of calling the function. If `refreshThreshold` is set and the remaining TTL is less than `refreshThreshold`, the system will update the value asynchronously. In the meantime, the system will return the old value until expiration. You can also provide a function that will return the refreshThreshold based on the value `(value:T) => number`. If the object format for the optional parameters is used, an additional `raw` parameter can be applied, changing the function return type to raw data including expiration timestamp as `{ value: [data], expires: [timestamp] }`. ```typescript await cache.wrap('key', () => 1, 5000, 3000) // call function then save the result to cache // => 1 await cache.wrap('key', () => 2, 5000, 3000) // return data from cache, function will not be called again // => 1 await cache.wrap('key', () => 2, { ttl: 5000, refreshThreshold: 3000, raw: true }) // returns raw data including expiration timestamp // => { value: 1, expires: [timestamp] } // wait 3 seconds await sleep(3000) await cache.wrap('key', () => 2, 5000, 3000) // return data from cache, call function in background and save the result to cache // => 1 await cache.wrap('key', () => 3, 5000, 3000) // return data from cache, function will not be called // => 2 await cache.wrap('key', () => 4, 5000, () => 3000); // return data from cache, function will not be called // => 4 await cache.wrap('error', () => { throw new Error('failed') }) // => error ``` **NOTES:** * The store that will be checked for refresh is the one where the key will be found first (highest priority). * If the threshold is low and the worker function is slow, the key may expire and you may encounter a racing condition with updating values. * If no `ttl` is set for the key, the refresh mechanism will not be triggered. See unit tests in [`test/wrap.test.ts`](./test/wrap.test.ts) for more information. ## disconnect `disconnect(): Promise` Will disconnect from the relevant store(s). It is highly recommended to use this when using a [Keyv](https://keyv.org/) storage adapter that requires a disconnect. For each storage adapter, the use case for when to use disconnect is different. An example is that `@keyv/redis` should be used only when you are done with the cache. ```ts await cache.disconnect(); ``` See unit tests in [`test/disconnect.test.ts`](./test/disconnect.test.ts) for more information. # Properties ## cacheId `cacheId(): string` Returns cache instance id. This is primarily used to not have conflicts when using `wrap` with multiple cache instances. ## stores `stores(): Keyv[]` Returns the list of Keyv instances. This can be used to get the list of stores and then use the Keyv API to interact with the store directly. ```ts const cache = createCache({cacheId: 'my-cache-id'}); cache.cacheId(); // => 'my-cache-id' ``` See unit tests in [`test/cache-id.test.ts`](./test/cache-id.test.ts) for more information. # Events ## set Fired when a key has been added or changed. ```ts cache.on('set', ({ key, value, error }) => { // ... do something ... }) ``` ## del Fired when a key has been removed manually. ```ts cache.on('del', ({ key, error }) => { // ... do something ... }) ``` ## clear Fired when the cache has been flushed. ```ts cache.on('clear', (error) => { if (error) { // ... do something ... } }) ``` ## refresh Fired when the cache has been refreshed in the background. ```ts cache.on('refresh', ({ key, value, error }) => { if (error) { // ... do something ... } }) ``` See unit tests in [`test/events.test.ts`](./test/events.test.ts) for more information. # Doing Iteration on Stores You can use the `stores` method to get the list of stores and then use the Keyv API to interact with the store directly. Below is an example of iterating over all stores and getting all keys: ```ts import Keyv from 'keyv'; import { createKeyv } from '@keyv/redis'; import { createCache } from 'cache-manager'; const keyv = new Keyv(); const keyvRedis = createKeyv('redis://user:pass@localhost:6379'); const cache = createCache({ stores: [keyv, keyvRedis], }); // add some data await cache.set('key-1', 'value 1'); await cache.set('key-2', 'value 2'); // get the store you want to iterate over. In this example we are using the second store (redis) const store = cache.stores[1]; if(store?.iterator) { for await (const [key, value] of store.iterator({})) { console.log(key, value); } } ``` WARNING: Be careful when using `iterator` as it can cause major performance issues with the amount of data being retrieved. Also, Not all storage adapters support `iterator` so you may need to check the documentation for the storage adapter you are using. # Update on redis and ioredis Support We will not be supporting `cache-manager-ioredis-yet` or `cache-manager-redis-yet` in the future as we have moved to using `Keyv` as the storage adapter `@keyv/redis`. # Using Legacy Storage Adapters There are many storage adapters built for `cache-manager` and because of that we wanted to provide a way to use them with `KeyvAdapter`. Below is an example of using `cache-manager-redis-yet`: ```ts import { createCache, KeyvAdapter } from 'cache-manager'; import { Keyv } from 'keyv'; import { redisStore } from 'cache-manager-redis-yet'; const adapter = new KeyvAdapter( await redisStore() ); const keyv = new Keyv({ store: adapter }); const cache = createCache({ stores: [keyv]}); ``` This adapter will allow you to add in any storage adapter. If there are issues it needs to follow `CacheManagerStore` interface. # Contribute If you would like to contribute to the project, please read how to contribute here [CONTRIBUTING.md](https://github.com/jaredwray/cacheable/blob/main/CONTRIBUTING.md). # License [MIT © Jared Wray ](./LICENSE) ### cacheable-request URL: https://cacheable.org/docs/cacheable-request/ [Cacheable](https://github.com/jaredwray/cacheable) # cacheable-request > Wrap native HTTP requests with RFC compliant cache support [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/cacheable-request.svg)](https://www.npmjs.com/package/cacheable-request) [![npm](https://img.shields.io/npm/v/cacheable-request.svg)](https://www.npmjs.com/package/cacheable-request) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) [RFC 7234](http://httpwg.org/specs/rfc7234.html) compliant HTTP caching for native Node.js HTTP/HTTPS requests. Caching works out of the box in memory or is easily pluggable with a wide range of storage adapters. **Note:** This is a low level wrapper around the core HTTP modules, it's not a high level request library. # Table of Contents * [Latest Changes](#latest-changes) * [Features](#features) * [Install and Usage](#install-and-usage) * [Storage Adapters](#storage-adapters) * [API](#api) * [Using Hooks](#using-hooks) * [Contributing](#contributing) * [Ask a Question](#ask-a-question) * [License](#license) (MIT) # Latest Changes ## Breaking Changes with v13.0.0 [Keyv](https://keyv.org) has been updated to version 5. With this update, you can no longer pass in a connection string directly to the `CacheableRequest` constructor. Instead, you should pass in a Keyv or Keyv storage adapter instance. ## Breaking Changes with v10.0.0 This release contains breaking changes. This is the new way to use this package. ### Usage Before v10 ```js import http from 'http'; import CacheableRequest from 'cacheable-request'; // Then instead of const req = http.request('http://example.com', cb); req.end(); // You can do const cacheableRequest = new CacheableRequest(http.request); const cacheReq = cacheableRequest('http://example.com', cb); cacheReq.on('request', req => req.end()); // Future requests to 'example.com' will be returned from cache if still valid // You pass in any other http.request API compatible method to be wrapped with cache support: const cacheableRequest = new CacheableRequest(https.request); const cacheableRequest = new CacheableRequest(electron.net); ``` ### Usage After v10.1.0 ```js import CacheableRequest from 'cacheable-request'; // Now You can do const cacheableRequest = new CacheableRequest(http.request).request(); const cacheReq = cacheableRequest('http://example.com', cb); cacheReq.on('request', req => req.end()); // Future requests to 'example.com' will be returned from cache if still valid // You pass in any other http.request API compatible method to be wrapped with cache support: const cacheableRequest = new CacheableRequest(https.request).request(); const cacheableRequest = new CacheableRequest(electron.net).request(); ``` The biggest change is that when you do a `new` CacheableRequest you now want to call `request` method will give you the instance to use. ```diff - const cacheableRequest = new CacheableRequest(http.request); + const cacheableRequest = new CacheableRequest(http.request).request(); ``` ### ESM Support in version 9 and higher. We are now using pure esm support in our package. If you need to use commonjs you can use v8 or lower. To learn more about using ESM please read this from `sindresorhus`: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c ## Features - Only stores cacheable responses as defined by RFC 7234 - Fresh cache entries are served directly from cache - Stale cache entries are revalidated with `If-None-Match`/`If-Modified-Since` headers - 304 responses from revalidation requests use cached body - Updates `Age` header on cached responses - Can completely bypass cache on a per request basis - In memory cache by default - Official support for Redis, Memcache, Etcd, MongoDB, SQLite, PostgreSQL and MySQL storage adapters - Easily plug in your own or third-party storage adapters - If DB connection fails, cache is automatically bypassed ([disabled by default](#optsautomaticfailover)) - Adds cache support to any existing HTTP code with minimal changes - Uses [http-cache-semantics](https://github.com/pornel/http-cache-semantics) internally for HTTP RFC 7234 compliance ## Install and Usage ```shell npm install cacheable-request ``` ```js import http from 'http'; import CacheableRequest from 'cacheable-request'; // Then instead of const req = http.request('http://example.com', cb); req.end(); // You can do const cacheableRequest = new CacheableRequest(http.request).createCacheableRequest(); const cacheReq = cacheableRequest('http://example.com', cb); cacheReq.on('request', req => req.end()); // Future requests to 'example.com' will be returned from cache if still valid // You pass in any other http.request API compatible method to be wrapped with cache support: const cacheableRequest = new CacheableRequest(https.request).createCacheableRequest(); const cacheableRequest = new CacheableRequest(electron.net).createCacheableRequest(); ``` ## Storage Adapters `cacheable-request` uses [Keyv](https://github.com/jaredwray/keyv) to support a wide range of storage adapters. For example, to use Redis as a cache backend, you just need to install the official Redis Keyv storage adapter: ``` npm install @keyv/redis ``` And then you can pass `CacheableRequest` your connection string: ```js import KeyvRedis from '@keyv/redis'; import CacheableRequest from 'cacheable-request'; const keyvRedis = new KeyvRedis('redis://localhost:6379'); const cacheableRequest = new CacheableRequest(http.request, KeyvRedis).createCacheableRequest(); ``` [View all official Keyv storage adapters.](https://github.com/jaredwray/keyv#official-storage-adapters) Keyv also supports anything that follows the Map API so it's easy to write your own storage adapter or use a third-party solution. e.g The following are all valid storage adapters ```js const storageAdapter = new Map(); // or const storageAdapter = require('./my-storage-adapter'); // or const QuickLRU = require('quick-lru'); const storageAdapter = new QuickLRU({ maxSize: 1000 }); const cacheableRequest = new CacheableRequest(http.request, storageAdapter).createCacheableRequest(); ``` View the [Keyv docs](https://github.com/jaredwray/keyv) for more information on how to use storage adapters. ## API ### new cacheableRequest(request, [storageAdapter]) Returns the provided request function wrapped with cache support. #### request Type: `function` Request function to wrap with cache support. Should be [`http.request`](https://nodejs.org/api/http.html#http_http_request_options_callback) or a similar API compatible request function. #### storageAdapter Type: `Keyv storage adapter`
Default: `new Map()` A [Keyv](https://github.com/jaredwray/keyv) storage adapter instance, or connection string if using with an official Keyv storage adapter. ### Instance #### cacheableRequest(opts, [cb]) Returns an event emitter. ##### opts Type: `object`, `string` - Any of the default request functions options. - Any [`http-cache-semantics`](https://github.com/kornelski/http-cache-semantics#constructor-options) options. - Any of the following: ###### opts.cache Type: `boolean`
Default: `true` If the cache should be used. Setting this to false will completely bypass the cache for the current request. ###### opts.strictTtl Type: `boolean`
Default: `false` If set to `true` once a cached resource has expired it is deleted and will have to be re-requested. If set to `false` (default), after a cached resource's TTL expires it is kept in the cache and will be revalidated on the next request with `If-None-Match`/`If-Modified-Since` headers. ###### opts.maxTtl Type: `number`
Default: `undefined` Limits TTL. The `number` represents milliseconds. ###### opts.automaticFailover Type: `boolean`
Default: `false` When set to `true`, if the DB connection fails we will automatically fallback to a network request. DB errors will still be emitted to notify you of the problem even though the request callback may succeed. ###### opts.forceRefresh Type: `boolean`
Default: `false` Forces refreshing the cache. If the response could be retrieved from the cache, it will perform a new request and override the cache instead. ##### cb Type: `function` The callback function which will receive the response as an argument. The response can be either a [Node.js HTTP response stream](https://nodejs.org/api/http.html#http_class_http_incomingmessage) or a [responselike object](https://github.com/lukechilds/responselike). The response will also have a `fromCache` property set with a boolean value. ##### .on('request', request) `request` event to get the request object of the request. **Note:** This event will only fire if an HTTP request is actually made, not when a response is retrieved from cache. However, you should always handle the `request` event to end the request and handle any potential request errors. ##### .on('response', response) `response` event to get the response object from the HTTP request or cache. ##### .on('error', error) `error` event emitted in case of an error with the cache. Errors emitted here will be an instance of `CacheableRequest.RequestError` or `CacheableRequest.CacheError`. You will only ever receive a `RequestError` if the request function throws (normally caused by invalid user input). Normal request errors should be handled inside the `request` event. To properly handle all error scenarios you should use the following pattern: ```js cacheableRequest('example.com', cb) .on('error', err => { if (err instanceof CacheableRequest.CacheError) { handleCacheError(err); // Cache error } else if (err instanceof CacheableRequest.RequestError) { handleRequestError(err); // Request function thrown } }) .on('request', req => { req.on('error', handleRequestError); // Request error emitted req.end(); }); ``` **Note:** Database connection errors are emitted here, however `cacheable-request` will attempt to re-request the resource and bypass the cache on a connection error. Therefore a database connection error doesn't necessarily mean the request won't be fulfilled. ## Using Hooks Hooks have been implemented since version `v9` and are very useful to modify response before saving it in cache. You can use hooks to modify response before saving it in cache. ### Add Hooks The hook will pre compute response right before saving it in cache. You can include many hooks and it will run in order you add hook on response object. ```js import http from 'http'; import CacheableRequest from 'cacheable-request'; const cacheableRequest = new CacheableRequest(request, cache).request(); // adding a hook to decompress response cacheableRequest.addHook('onResponse', async (value: CacheValue, response: any) => { const buffer = await pm(gunzip)(value.body); value.body = buffer.toString(); return value; }); ``` here is also an example of how to add in the remote address ```js import CacheableRequest, {CacheValue} from 'cacheable-request'; const cacheableRequest = new CacheableRequest(request, cache).request(); cacheableRequest.addHook('onResponse', (value: CacheValue, response: any) => { if (response.connection) { value.remoteAddress = response.connection.remoteAddress; } return value; }); ``` ### Remove Hooks You can also remove hook by using below ```js CacheableRequest.removeHook('onResponse'); ``` ## How to Contribute Cacheable-Request is an open source package and community driven that is maintained regularly. In addition we have a couple of other guidelines for review: * [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Our code of conduct * [CONTRIBUTING.md](CONTRIBUTING.md) - How to contribute to this project * [SECURITY.md](SECURITY.md) - Security guidelines and supported versions ## Post an Issue To post an issue, navigate to the "Issues" tab in the main repository, and then select "New Issue." Enter a clear title describing the issue, as well as a description containing additional relevant information. Also select the label that best describes your issue type. For a bug report, for example, create an issue with the label "bug." In the description field, Be sure to include replication steps, as well as any relevant error messages. If you're reporting a security violation, be sure to check out the project's [security policy](SECURITY.md). Please also refer to our [Code of Conduct](CODE_OF_CONDUCT.md) for more information on how to report issues. ## Ask a Question To ask a question, create an issue with the label "question." In the issue description, include the related code and any context that can help us answer your question. ## License and Copyright [MIT © Luke Childs 2017-2021 and Jared Wray 2022+](./LICENSE) ### cacheable URL: https://cacheable.org/docs/cacheable/ [Cacheable](https://github.com/jaredwray/cacheable) > High Performance Layer 1 / Layer 2 Caching with Keyv Storage [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/cacheable.svg)](https://www.npmjs.com/package/cacheable) [![npm](https://img.shields.io/npm/v/cacheable)](https://www.npmjs.com/package/cacheable) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) `cacheable` is a high performance layer 1 / layer 2 caching engine that is focused on distributed caching with enterprise features such as `CacheSync`. It is built on top of the robust storage engine [Keyv](https://keyv.org) and provides a simple API to cache and retrieve data. * Simple to use with robust API * Not bloated with additional modules * Scalable and trusted storage engine by Keyv * Memory Caching with LRU and Expiration `CacheableMemory` * Resilient to failures with try/catch and offline * Wrap / Memoization for Sync and Async Functions with Stampede Protection * Hooks and Events to extend functionality * Shorthand for ttl in milliseconds `(1m = 60000) (1h = 3600000) (1d = 86400000)` * Non-blocking operations for layer 2 caching * **Distributed Caching Sync via Pub/Sub with CacheSync** * Comprehensive testing and code coverage * ESM and CommonJS support with Typescript * Maintained and supported regularly # Table of Contents * [Getting Started](#getting-started) * [v1 to v2 Changes](#v1-to-v2-changes) * [Basic Usage](#basic-usage) * [Hooks and Events](#hooks-and-events) * [Storage Tiering and Caching](#storage-tiering-and-caching) * [TTL Propagation and Storage Tiering](#ttl-propagation-and-storage-tiering) * [Shorthand for Time to Live (ttl)](#shorthand-for-time-to-live-ttl) * [Maximum Time to Live (maxTtl)](#maximum-time-to-live-maxttl) * [Iteration on Primary and Secondary Stores](#iteration-on-primary-and-secondary-stores) * [Non-Blocking Operations](#non-blocking-operations) * [Non-Blocking with @keyv/redis](#non-blocking-with-keyvredis) * [CacheableSync - Distributed Updates](#cacheablesync---distributed-updates) * [Cacheable Options](#cacheable-options) * [Cacheable Statistics (Instance Only)](#cacheable-statistics-instance-only) * [Cacheable - API](#cacheable---api) * [CacheableMemory - In-Memory Cache](#cacheablememory---in-memory-cache) * [Keyv Storage Adapter - KeyvCacheableMemory](#keyv-storage-adapter---keyvcacheablememory) * [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions) * [Get Or Set Memoization Function](#get-or-set-memoization-function) * [How to Contribute](#how-to-contribute) * [License and Copyright](#license-and-copyright) # Getting Started `cacheable` is primarily used as an extension to your caching engine with a robust storage backend [Keyv](https://keyv.org), Memoization (Wrap), Hooks, Events, and Statistics. ```bash npm install cacheable ``` # Basic Usage ```javascript import { Cacheable } from 'cacheable'; const cacheable = new Cacheable(); await cacheable.set('key', 'value', 1000); const value = await cacheable.get('key'); ``` This is a basic example where you are only using the in-memory storage engine. To enable layer 1 and layer 2 caching you can use the `secondary` property in the options: ```javascript import { Cacheable } from 'cacheable'; import KeyvRedis from '@keyv/redis'; const secondary = new KeyvRedis('redis://user:pass@localhost:6379'); const cache = new Cacheable({secondary}); ``` In this example, the primary store we will use `lru-cache` and the secondary store is Redis. You can also set multiple stores in the options: ```javascript import { Cacheable } from 'cacheable'; import { Keyv } from 'keyv'; import KeyvRedis from '@keyv/redis'; import { LRUCache } from 'lru-cache' const primary = new Keyv({store: new LRUCache()}); const secondary = new KeyvRedis('redis://user:pass@localhost:6379'); const cache = new Cacheable({primary, secondary}); ``` This is a more advanced example and not needed for most use cases. # Hooks and Events The following hooks are available for you to extend the functionality of `cacheable` via `CacheableHooks` enum: * `BEFORE_SET`: This is called before the `set()` method is called. * `AFTER_SET`: This is called after the `set()` method is called. * `BEFORE_SET_MANY`: This is called before the `setMany()` method is called. * `AFTER_SET_MANY`: This is called after the `setMany()` method is called. * `BEFORE_GET`: This is called before the `get()` method is called. * `AFTER_GET`: This is called after the `get()` method is called. * `BEFORE_GET_MANY`: This is called before the `getMany()` method is called. * `AFTER_GET_MANY`: This is called after the `getMany()` method is called. * `BEFORE_SECONDARY_SETS_PRIMARY`: This is called when the secondary store sets the value in the primary store. An example of how to use these hooks: ```javascript import { Cacheable, CacheableHooks } from 'cacheable'; const cacheable = new Cacheable(); cacheable.onHook(CacheableHooks.BEFORE_SET, (data) => { console.log(`before set: ${data.key} ${data.value}`); }); ``` Here is an example of how to use `BEFORE_SECONDARY_SETS_PRIMARY` hook: ```javascript import { Cacheable, CacheableHooks } from 'cacheable'; import KeyvRedis from '@keyv/redis'; const secondary = new KeyvRedis('redis://user:pass@localhost:6379'); const cache = new Cacheable({secondary}); cache.onHook(CacheableHooks.BEFORE_SECONDARY_SETS_PRIMARY, (data) => { console.log(`before secondary sets primary: ${data.key} ${data.value} ${data.ttl}`); }); ``` This is called when the secondary store sets the value in the primary store. This is useful if you want to do something before the value is set in the primary store such as manipulating the ttl or the value. The following events are provided: - `error`: Emitted when an error occurs. - `cache:hit`: Emitted when a cache hit occurs. - `cache:miss`: Emitted when a cache miss occurs. Here is an example of using the `error` event: ```javascript import { Cacheable, CacheableEvents } from 'cacheable'; const cacheable = new Cacheable(); cacheable.on(CacheableEvents.ERROR, (error) => { console.error(`Cacheable error: ${error.message}`); }); ``` We also offer `cache:hit` and `cache:miss` events. These events are emitted when a cache hit or miss occurs, respectively. Here is how to use them: ```javascript import { Cacheable, CacheableEvents } from 'cacheable'; const cacheable = new Cacheable(); cacheable.on(CacheableEvents.CACHE_HIT, (data) => { console.log(`Cache hit: ${data.key} ${data.value} ${data.store}`); // the store will say primary or secondary }); cacheable.on(CacheableEvents.CACHE_MISS, (data) => { console.log(`Cache miss: ${data.key} ${data.store}`); // the store will say primary or secondary }); ``` # Storage Tiering and Caching `cacheable` is built as a layer 1 and layer 2 caching engine by default. The purpose is to have your layer 1 be fast and your layer 2 be more persistent. The primary store is the layer 1 cache and the secondary store is the layer 2 cache. By adding the secondary store you are enabling layer 2 caching. By default the operations are blocking but fault tolerant: * `Setting Data`: Sets the value in the primary store and then the secondary store. * `Getting Data`: Gets the value from the primary if the value does not exist it will get it from the secondary store and set it in the primary store. * `Deleting Data`: Deletes the value from the primary store and secondary store at the same time waiting for both to respond. * `Clearing Data`: Clears the primary store and secondary store at the same time waiting for both to respond. When `Getting Data` if the value does not exist in the primary store it will try to get it from the secondary store. If the secondary store returns the value it will set it in the primary store. Because we use [TTL Propagation](#ttl-propagation-and-storage-tiering) the value will be set in the primary store with the TTL of the secondary store unless the time to live (TTL) is greater than the primary store which will then use the TTL of the primary store. An example of this is: ```javascript import { Cacheable } from 'cacheable'; import {Keyv} from 'keyv'; import KeyvRedis from '@keyv/redis'; const secondary = new Keyv({ store: new KeyvRedis('redis://user:pass@localhost:6379'), ttl: 1000 }); const cache = new Cacheable({secondary, ttl: 100}); await cache.set('key', 'value'); // sets the value in the primary store with a ttl of 100 ms and secondary store with a ttl of 1000 ms await sleep(500); // wait for .5 seconds const value = await cache.get('key'); // gets the value from the secondary store and now sets the value in the primary store with a ttl of 500 ms which is what is left from the secondary store ``` In this example the primary store has a ttl of `100 ms` and the secondary store has a ttl of `1000 ms`. Because the ttl is greater in the secondary store it will default to setting ttl value in the primary store. ```javascript import { Cacheable } from 'cacheable'; import {Keyv} from 'keyv'; import KeyvRedis from '@keyv/redis'; const primary = new Keyv({ ttl: 200 }); const secondary = new Keyv({ store: new KeyvRedis('redis://user:pass@localhost:6379'), ttl: 1000 }); const cache = new Cacheable({primary, secondary}); await cache.set('key', 'value'); // sets the value in the primary store with a ttl of 200 ms and secondary store with a ttl of 1000 ms await sleep(200); // wait for .2 seconds const value = await cache.get('key'); // gets the value from the secondary store and now sets the value in the primary store with a ttl of 200 ms which is what the primary store is set with ``` # TTL Propagation and Storage Tiering Cacheable TTL propagation is a feature that allows you to set a time to live (TTL) for the cache. By default the TTL is set in the following order: ``` ttl = set at the function ?? storage adapter ttl ?? cacheable ttl ``` This means that if you set a TTL at the function level it will override the storage adapter TTL and the cacheable TTL. If you do not set a TTL at the function level it will use the storage adapter TTL and then the cacheable TTL. If you do not set a TTL at all it will use the default TTL of `undefined` which is disabled. # Shorthand for Time to Live (ttl) By default `Cacheable` and `CacheableMemory` the `ttl` is in milliseconds but you can use shorthand for the time to live. Here are the following shorthand values: * `ms`: Milliseconds such as (1ms = 1) * `s`: Seconds such as (1s = 1000) * `m`: Minutes such as (1m = 60000) * `h` or `hr`: Hours such as (1h = 3600000) * `d`: Days such as (1d = 86400000) Here is an example of how to use the shorthand for the `ttl`: ```javascript import { Cacheable } from 'cacheable'; const cache = new Cacheable({ ttl: '15m' }); //sets the default ttl to 15 minutes (900000 ms) cache.set('key', 'value', '1h'); //sets the ttl to 1 hour (3600000 ms) and overrides the default ``` if you want to disable the `ttl` you can set it to `0` or `undefined`: ```javascript import { Cacheable } from 'cacheable'; const cache = new Cacheable({ ttl: 0 }); //sets the default ttl to 0 which is disabled cache.set('key', 'value', 0); //sets the ttl to 0 which is disabled ``` If you set the ttl to anything below `0` or `undefined` it will disable the ttl for the cache and the value that returns will be `undefined`. With no ttl set the value will be stored `indefinitely`. ```javascript import { Cacheable } from 'cacheable'; const cache = new Cacheable({ ttl: 0 }); //sets the default ttl to 0 which is disabled console.log(cache.ttl); // undefined cache.ttl = '1h'; // sets the default ttl to 1 hour (3600000 ms) console.log(cache.ttl); // '1h' cache.ttl = -1; // sets the default ttl to 0 which is disabled console.log(cache.ttl); // undefined ``` ## Retrieving raw cache entries The `get` and `getMany` methods support a `raw` option, which returns the full stored metadata (`StoredDataRaw`) instead of just the value: ```typescript import { Cacheable } from 'cacheable'; const cache = new Cacheable(); // store a value await cache.set('user:1', { name: 'Alice' }); // default: only the value const user = await cache.get<{ name: string }>('user:1'); console.log(user); // { name: 'Alice' } // with raw: full record including expiration const raw = await cache.get<{ name: string }>('user:1', { raw: true }); console.log(raw.value); // { name: 'Alice' } console.log(raw.expires); // e.g. 1677628495000 or null ``` ```typescript // getMany with raw option await cache.set('a', 1); await cache.set('b', 2); const raws = await cache.getMany(['a', 'b'], { raw: true }); raws.forEach((entry, idx) => { console.log(`key=${['a','b'][idx]}, value=${entry?.value}, expires=${entry?.expires}`); }); ``` ## Checking multiple keys with hasMany The `hasMany` method allows you to efficiently check if multiple keys exist in the cache. It leverages Keyv's native `hasMany` support for optimal performance: ```typescript import { Cacheable } from 'cacheable'; const cache = new Cacheable(); // set some values await cache.set('user:1', { name: 'Alice' }); await cache.set('user:2', { name: 'Bob' }); // check if multiple keys exist const exists = await cache.hasMany(['user:1', 'user:2', 'user:3']); console.log(exists); // [true, true, false] ``` The `hasMany` method returns an array of booleans in the same order as the input keys. This is particularly useful when you need to verify the existence of multiple cache entries before performing batch operations. # Iteration on Primary and Secondary Stores The `Cacheable` class exposes both `primary` and `secondary` Keyv instances, which support iteration over their stored entries using the `iterator()` method. This allows you to access and process all keys and values in either storage layer. **Important Notes:** - Not all storage adapters support iteration. Always check if `iterator` exists before using it. - The iterator automatically filters by namespace, skips expired entries (and deletes them), and deserializes values. - **Performance Warning:** Be careful when using `iterator()` as it can cause performance issues with large datasets. ## Basic Iteration Example ```typescript import { Cacheable } from 'cacheable'; import KeyvRedis from '@keyv/redis'; // Create cache with primary (in-memory) and secondary (Redis) stores const secondary = new KeyvRedis('redis://user:pass@localhost:6379'); const cache = new Cacheable({ secondary }); // Add some data await cache.set('user:1', { name: 'Alice', role: 'admin' }); await cache.set('user:2', { name: 'Bob', role: 'user' }); await cache.set('session:abc', { userId: '1', active: true }); // Iterate over primary store (in-memory) console.log('Primary store contents:'); if (cache.primary.iterator) { for await (const [key, value] of cache.primary.iterator()) { console.log(` ${key}:`, JSON.stringify(value)); } } // Iterate over secondary store (Redis) console.log('\nSecondary store contents:'); if (cache.secondary?.iterator) { for await (const [key, value] of cache.secondary.iterator()) { console.log(` ${key}:`, JSON.stringify(value)); } } ``` ## Safe Iteration Helper Here's a recommended helper function for safe iteration that checks for store availability and iterator support: ```typescript import { Cacheable } from 'cacheable'; import type { Keyv } from 'keyv'; async function iterateStore(store: Keyv | undefined, storeName: string) { if (!store) { console.log(`${storeName} store not configured`); return; } if (!store.iterator) { console.log(`${storeName} store does not support iteration`); return; } console.log(`${storeName} store entries:`); for await (const [key, value] of store.iterator()) { console.log(` ${key}:`, value); } } // Usage const cache = new Cacheable({ /* options */ }); await iterateStore(cache.primary, 'Primary'); await iterateStore(cache.secondary, 'Secondary'); ``` ## Storage Adapter Support The `iterator()` method is available when: - The store is a Map instance (has Symbol.iterator) - The store implements an `iterator()` method (e.g., Redis, Valkey, etc.) - The store is a supported iterable adapter Common stores that support iteration: - In-memory (Map-based stores) - @keyv/redis - @keyv/valkey - Other Keyv adapters that implement the iterator interface # Non-Blocking Operations If you want your layer 2 (secondary) store to be non-blocking you can set the `nonBlocking` property to `true` in the options. This will make the secondary store non-blocking and will not wait for the secondary store to respond on `setting data`, `deleting data`, or `clearing data`. This is useful if you want to have a faster response time and not wait for the secondary store to respond. Here is a full list of what each method does in nonBlocking mode: * `set` - in non-blocking mode it will set at the `primary` storage and then in the background update `secondary` * `get` - in non-blocking mode it will only check the primary storage but then in the background look to see if there is a value in the `secondary` and update the primary * `getMany` - in non-blocking mode it will only check the primary storage but then in the background look to see if there is a value in the `secondary` and update the primary * `getRaw` - in non-blocking mode it will only check the primary storage but then in the background look to see if there is a value in the `secondary` and update the primary * `getManyRaw` - in non-blocking mode it will only check the primary storage but then in the background look to see if there is a value in the `secondary` and update the primary # Non-Blocking with @keyv/redis `@keyv/redis` is one of the most popular storage adapters used with `cacheable`. It provides a Redis-backed cache store that can be used as a secondary store. It is a bit complicated to setup as by default it causes hangs and blocking with its default configuration. To get past this you will need to configure the following: Construct your own Redis client via the `createClient()` method from `@keyv/redis` with the following options: * Set `disableOfflineQueue` to `true` * Set `socket.reconnectStrategy` to `false` In the KeyvRedis options: * Set `throwOnConnectError` to `false` In the Cacheable options: * Set `nonBlocking` to `true` We have also build a function to help with this called `createKeyvNonBlocking` inside the `@keyv/redis` package after version `4.6.0`. Here is an example of how to use it: ```javascript import { Cacheable } from 'cacheable'; import { createKeyvNonBlocking } from '@keyv/redis'; const secondary = createKeyvNonBlocking('redis://user:pass@localhost:6379'); const cache = new Cacheable({ secondary, nonBlocking: true }); ``` # 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 import { Cacheable } from 'cacheable'; import {KeyvRedis} from '@keyv/redis'; const secondary = new KeyvRedis('redis://user:pass@localhost:6379'); const cache = new Cacheable({secondary, nonBlocking: true}); ``` # CacheableSync - Distributed Updates `cacheable` includes `CacheableSync`, a feature that enables distributed cache synchronization across multiple instances using Pub/Sub messaging via [Qified](https://github.com/jaredwray/qified). When a value is set or deleted in one cache instance, all other connected instances automatically receive and apply the update. ## How It Works `CacheableSync` uses message providers from Qified to broadcast cache operations (SET and DELETE) to all connected cache instances. Each instance subscribes to these events and automatically updates its `primary` (example: in-memory) storage when receiving updates from other instances. ## Supported Message Providers `Qified` supports multiple providers and you can learn more by going to https://qified.org. ## Basic Usage ```javascript import { Cacheable } from 'cacheable'; import { RedisMessageProvider } from '@qified/redis'; // Create a Redis message provider const provider = new RedisMessageProvider({ connection: { host: 'localhost', port: 6379 } }); // Create cache instances with sync enabled const cache1 = new Cacheable({ sync: { qified: provider } }); const cache2 = new Cacheable({ sync: { qified: provider } }); // Set a value in cache1 await cache1.set('key', 'value'); // Note: you might want to sleep for a bit based on the backend. // The value is automatically synced to cache2 const value = await cache2.get('key'); // Returns 'value' ``` ## Using Multiple Message Providers You can use multiple message providers for redundancy: ```javascript import { Cacheable } from 'cacheable'; import { RedisMessageProvider } from '@qified/redis'; import { NatsMessageProvider } from '@qified/nats'; const redisProvider = new RedisMessageProvider({ connection: { host: 'localhost', port: 6379 } }); const natsProvider = new NatsMessageProvider({ servers: ['nats://localhost:4222'] }); const cache = new Cacheable({ sync: { qified: [redisProvider, natsProvider] } }); ``` ## Using an Existing Qified Instance You can also pass a pre-configured Qified instance: ```javascript import { Cacheable } from 'cacheable'; import { Qified } from 'qified'; import { RedisMessageProvider } from '@qified/redis'; const provider = new RedisMessageProvider({ connection: { host: 'localhost', port: 6379 } }); const qified = new Qified({ messageProviders: [provider] }); const cache = new Cacheable({ sync: { qified } }); ``` ## Namespace Isolation with Sync When multiple services share the same Redis instance (or other message provider), you can use namespaces to isolate cache synchronization events between services. This prevents one service's cache updates from affecting another service's cache. ```javascript import { Cacheable } from 'cacheable'; import { RedisMessageProvider } from '@qified/redis'; const provider = new RedisMessageProvider({ connection: { host: 'localhost', port: 6379 } }); // Service 1 with namespace const serviceA = new Cacheable({ namespace: 'service-a', sync: { qified: provider } }); // Service 2 with different namespace const serviceB = new Cacheable({ namespace: 'service-b', sync: { qified: provider } }); // Set value in service A await serviceA.set('config', { timeout: 5000 }); // Service B won't receive this update because it has a different namespace const value = await serviceB.get('config'); // undefined ``` **How Namespace Isolation Works:** - Without namespaces, sync events use channel names like `cache:set` and `cache:delete` - With namespaces, events are prefixed: `service-a::cache:set`, `service-b::cache:set` - Services only subscribe to events matching their namespace, ensuring complete isolation - Namespaces can be static strings or functions that return strings **Note:** The namespace is automatically passed from Cacheable to CacheableSync, so you only need to set it once in the Cacheable options. ## How Sync Works 1. **SET Operations**: When you call `cache.set()` or `cache.setMany()`, the cache: - Updates the local primary storage and secondary storage - Publishes a `cache:set` event with the key, value, ttl, and cacheId - Other cache instances receive the event and update their `primary` storage (excluding the originating instance) 2. **DELETE Operations**: When you call `cache.delete()` or `cache.deleteMany()`, the cache: - Removes the key from primary and secondary storage - Publishes a `cache:delete` event with the key and cacheId - Other cache instances receive the event and remove the key from their storage ## Important Notes * Cache sync only works with the **primary storage layer**. Secondary storage is usually handled by the instance doing the initial work. * Each cache instance should have a unique `cacheId` to properly filter sync events. This is setup by default but you can set it if you want. * Sync events are **eventually consistent** - there may be a small delay between when a value is set and when it appears in other instances. * The sync feature requires a message provider to be running and accessible by all cache instances. * Each cache instance has a unique `cacheId`. Events are only applied if they come from a different instance, preventing infinite loops. # Maximum Time to Live (maxTtl) You can set a `maxTtl` option to enforce an upper bound on any TTL in the cache. When `maxTtl` is set: - Any per-entry TTL that exceeds `maxTtl` will be capped to `maxTtl`. - Entries with no TTL (that would otherwise live indefinitely) will be capped to `maxTtl`. - The default TTL is still respected if it is within the `maxTtl` limit. - The `maxTtl` is enforced on both primary and secondary stores. This is useful when you want to guarantee that no cache entry lives longer than a certain duration, regardless of what TTL is passed to individual `set()` calls. ```javascript import { Cacheable } from 'cacheable'; // No entry can live longer than 1 hour const cache = new Cacheable({ maxTtl: '1h' }); await cache.set('key1', 'value1', '2h'); // capped to 1 hour await cache.set('key2', 'value2'); // also capped to 1 hour (would otherwise be indefinite) await cache.set('key3', 'value3', '30m'); // 30 minutes is within maxTtl, so it stays as-is ``` You can also set `maxTtl` after construction: ```javascript const cache = new Cacheable(); cache.maxTtl = 5000; // 5 seconds max cache.maxTtl = '10m'; // 10 minutes max cache.maxTtl = undefined; // disable maxTtl (no upper bound) ``` # Cacheable Options The following options are available for you to configure `cacheable`: * `primary`: The primary store for the cache (layer 1) defaults to in-memory by Keyv. * `secondary`: The secondary store for the cache (layer 2) usually a persistent cache by Keyv. * `nonBlocking`: If the secondary store is non-blocking. Default is `false`. * `stats`: To enable statistics for this instance. Default is `false`. * `ttl`: The default time to live for the cache in milliseconds. Default is `undefined` which is disabled. * `maxTtl`: The maximum time to live for any cache entry. When set, TTLs exceeding this value are capped. Enforced on both primary and secondary stores. Default is `undefined` (no maximum). * `namespace`: The namespace for the cache. Default is `undefined`. * `cacheId`: A unique identifier for this cache instance. Used for sync filtering. Default is a random string. * `sync`: Enable distributed cache synchronization. Can be: - `CacheableSync` instance - `CacheableSyncOptions` object with `{ qified: MessageProvider | MessageProvider[] | Qified }` # Cacheable Statistics (Instance Only) If you want to enable statistics for your instance you can set the `.stats.enabled` property to `true` in the options. This will enable statistics for your instance and you can get the statistics by calling the `stats` property. Here are the following property statistics: * `hits`: The number of hits in the cache. * `misses`: The number of misses in the cache. * `sets`: The number of sets in the cache. * `deletes`: The number of deletes in the cache. * `clears`: The number of clears in the cache. * `errors`: The number of errors in the cache. * `count`: The number of keys in the cache. * `vsize`: The estimated byte size of the values in the cache. * `ksize`: The estimated byte size of the keys in the cache. You can clear / reset the stats by calling the `.stats.reset()` method. _This does not enable statistics for your layer 2 cache as that is a distributed cache_. # Cacheable - API * `set(key, value, ttl?)`: Sets a value in the cache. * `setMany([{key, value, ttl?}])`: Sets multiple values in the cache. * `get(key)`: Gets a value from the cache. * `get(key, { raw: true })`: Gets a raw value from the cache. * `getMany([keys])`: Gets multiple values from the cache. * `getMany([keys], { raw: true })`: Gets multiple raw values from the cache. * `has(key)`: Checks if a value exists in the cache. * `hasMany([keys])`: Checks if multiple values exist in the cache. * `take(key)`: Takes a value from the cache and deletes it. * `takeMany([keys])`: Takes multiple values from the cache and deletes them. * `delete(key)`: Deletes a value from the cache. * `deleteMany([keys])`: Deletes multiple values from the cache. * `clear()`: Clears the cache stores. Be careful with this as it will clear both layer 1 and layer 2. * `wrap(function, WrapOptions)`: Wraps an `async` function in a cache. * `getOrSet(GetOrSetKey, valueFunction, GetOrSetFunctionOptions)`: Gets a value from cache or sets it if not found using the provided function. * `disconnect()`: Disconnects from the cache stores. * `onHook(hook, callback)`: Sets a hook. * `removeHook(hook)`: Removes a hook. * `on(event, callback)`: Listens for an event. * `removeListener(event, callback)`: Removes a listener. * `hash(object: any, algorithm = 'SHA-256'): Promise`: Asynchronously hashes an object with a cryptographic algorithm (SHA-256, SHA-384, SHA-512). Default is `SHA-256`. * `hashSync(object: any, algorithm = 'djb2'): string`: Synchronously hashes an object with a non-cryptographic algorithm (djb2, fnv1, murmer, crc32). Default is `djb2`. * `primary`: The primary store for the cache (layer 1) defaults to in-memory by Keyv. * `secondary`: The secondary store for the cache (layer 2) usually a persistent cache by Keyv. * `namespace`: The namespace for the cache. Default is `undefined`. This will set the namespace for the primary and secondary stores. * `maxTtl`: The maximum time to live for any cache entry. When set, TTLs exceeding this value are capped. Default is `undefined` (no maximum). * `nonBlocking`: If the secondary store is non-blocking. Default is `false`. * `stats`: The statistics for this instance which includes `hits`, `misses`, `sets`, `deletes`, `clears`, `errors`, `count`, `vsize`, `ksize`. # CacheableMemory - In-Memory Cache `cacheable` comes with a built-in in-memory cache called `CacheableMemory` from `@cacheable/memory`. This is a simple in-memory cache that is used as the primary store for `cacheable`. You can use this as a standalone cache or as a primary store for `cacheable`. Here is an example of how to use `CacheableMemory`: ```javascript import { CacheableMemory } from 'cacheable'; const options = { ttl: '1h', // 1 hour useClones: true, // use clones for the values (default is true) lruSize: 1000, // the size of the LRU cache (default is 0 which is unlimited) } const cache = new CacheableMemory(options); cache.set('key', 'value'); const value = cache.get('key'); // value ``` To learn more go to [@cacheable/memory](https://cacheable.org/docs/memory/) # Wrap / Memoization for Sync and Async Functions `Cacheable` and `CacheableMemory` has a feature called `wrap` that comes from [@cacheable/utils](https://cacheable.org/docs/utils/) and allows you to wrap a function in a cache. This is useful for memoization and caching the results of a function. You can wrap a `sync` or `async` function in a cache. Here is an example of how to use the `wrap` function: ```javascript import { Cacheable } from 'cacheable'; const asyncFunction = async (value: number) => { return Math.random() * value; }; const cache = new Cacheable(); const options = { ttl: '1h', // 1 hour keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix. } const wrappedFunction = cache.wrap(asyncFunction, options); console.log(await wrappedFunction(2)); // 4 console.log(await wrappedFunction(2)); // 4 from cache ``` With `Cacheable` we have also included stampede protection so that a `Promise` based call will only be called once if multiple requests of the same are executed at the same time. Here is an example of how to test for stampede protection: ```javascript import { Cacheable } from 'cacheable'; const asyncFunction = async (value: number) => { return value; }; const cache = new Cacheable(); const options = { ttl: '1h', // 1 hour keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix. } const wrappedFunction = cache.wrap(asyncFunction, options); const promises = []; for (let i = 0; i < 10; i++) { promises.push(wrappedFunction(i)); } const results = await Promise.all(promises); // all results should be the same console.log(results); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ``` In this example we are wrapping an `async` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also wrap a `sync` function in a cache: ```javascript import { CacheableMemory } from 'cacheable'; const syncFunction = (value: number) => { return value * 2; }; const cache = new CacheableMemory(); const wrappedFunction = cache.wrap(syncFunction, { ttl: '1h', key: 'syncFunction' }); console.log(wrappedFunction(2)); // 4 console.log(wrappedFunction(2)); // 4 from cache ``` In this example we are wrapping a `sync` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also set the `key` property in the `wrap()` options to set a custom key for the cache. When an error occurs in the function it will not cache the value and will return the error. This is useful if you want to cache the results of a function but not cache the error. If you want it to cache the error you can set the `cacheError` property to `true` in the `wrap()` options. This is disabled by default. ```javascript import { CacheableMemory } from 'cacheable'; const syncFunction = (value: number) => { throw new Error('error'); }; const cache = new CacheableMemory(); const wrappedFunction = cache.wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cacheError: true }); console.log(wrappedFunction()); // error console.log(wrappedFunction()); // error from cache ``` If you would like to generate your own key for the wrapped function you can set the `createKey` property in the `wrap()` options. This is useful if you want to generate a key based on the arguments of the function or any other criteria. ```javascript const cache = new Cacheable(); const options: WrapOptions = { cache, keyPrefix: 'test', createKey: (function_, arguments_, options: WrapOptions) => `customKey:${options?.keyPrefix}:${arguments_[0]}`, }; const wrapped = wrap((argument: string) => `Result for ${argument}`, options); const result1 = await wrapped('arg1'); const result2 = await wrapped('arg1'); // Should hit the cache console.log(result1); // Result for arg1 console.log(result2); // Result for arg1 (from cache) ``` We will pass in the `function` that is being wrapped, the `arguments` passed to the function, and the `options` used to wrap the function. You can then use these to generate a custom key for the cache. To learn more visit [@cacheable/utils](https://cacheable.org/docs/utils/) # Get Or Set Memoization Function The `getOrSet` method that comes from [@cacheable/utils](https://cacheable.org/docs/utils/) 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. Here are the options: ```typescript export type GetOrSetFunctionOptions = { ttl?: number | string; cacheErrors?: boolean; throwErrors?: boolean; nonBlocking?: boolean; }; ``` The `nonBlocking` option allows you to override the instance-level `nonBlocking` setting for the `get` call within `getOrSet`. When set to `false`, the `get` will block and wait for a response from the secondary store before deciding whether to call the provided function. When set to `true`, the primary store returns immediately and syncs from secondary in the background. Here is an example of how to use the `getOrSet` method: ```javascript import { Cacheable } from 'cacheable'; const cache = new Cacheable(); // Use getOrSet to fetch user data const function_ = async () => Math.random() * 100; const value = await cache.getOrSet('randomValue', function_, { ttl: '1h' }); console.log(value); // e.g. 42.123456789 ``` You can also use a function to compute the key for the function: ```javascript import { Cacheable, GetOrSetOptions } from 'cacheable'; const cache = new Cacheable(); // Function to generate a key based on options const generateKey = (options?: GetOrSetOptions) => { return `custom_key_:${options?.cacheId || 'default'}`; }; const function_ = async () => Math.random() * 100; const value = await cache.getOrSet(generateKey(), function_, { ttl: '1h' }); ``` To learn more go to [@cacheable/utils](https://cacheable.org/docs/utils/) # v1 to v2 Changes `cacheable` is now using `@cacheable/utils` and `@cacheable/memory` for its core functionality as we are moving to this modular architecture and plan to eventually have these modules across `cache-manager` and `flat-cache`. In addition there are some breaking changes: * `get()` and `getMany()` no longer have the `raw` option but instead we have built out `getRaw()` and `getManyRaw()` to use. * All `get` related functions now support `nonBlocking` which means if `nonBlocking: true` the primary store will return what it has and then in the background will work to sync from secondary storage for any misses. You can disable this by setting at the `get` function level the option `nonBlocking: false` which will look for any missing keys in the secondary. * `Keyv` v5.5+ is now the recommended supported version as we are using its native `getMany*`, `getRaw*`, and `hasMany` methods for improved performance * `Wrap` and `getOrSet` have been updated with more robust options including the ability to use your own `serialize` function for creating the key in `wrap`. * `hash` has been split into async (`hash()` and `hashToNumber()`) and sync (`hashSync()` and `hashToNumberSync()`) methods. MD5 support has been removed. Now uses Hashery library with support for additional algorithms (SHA-384, FNV1, MURMER, CRC32). # How to Contribute You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`. # License and Copyright [MIT © Jared Wray](./LICENSE) ### file-entry-cache URL: https://cacheable.org/docs/file-entry-cache/ [Cacheable](https://github.com/jaredwray/cacheable) # file-entry-cache > A lightweight cache for file metadata, ideal for processes that work on a specific set of files and only need to reprocess files that have changed since the last run [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/file-entry-cache.svg)](https://www.npmjs.com/package/file-entry-cache) [![npm](https://img.shields.io/npm/v/file-entry-cache)](https://www.npmjs.com/package/file-entry-cache) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) # Features - Lightweight cache for file metadata - Ideal for processes that work on a specific set of files - Persists cache to Disk via `reconcile()` or `persistInterval` on `cache` options. - Uses `checksum` to determine if a file has changed - Supports `relative` and `absolute` paths with configurable current working directory - Portable cache files when using relative paths - ESM and CommonJS support with Typescript # Table of Contents - [Installation](#installation) - [Getting Started](#getting-started) - [Changes from v10 to v11](#changes-from-v10-to-v11) - [Changes from v9 to v10](#changes-from-v9-to-v10) - [Global Default Functions](#global-default-functions) - [FileEntryCache Options (FileEntryCacheOptions)](#fileentrycache-options-fileentrycacheoptions) - [API](#api) - [Get File Descriptor](#get-file-descriptor) - [Path Handling and Current Working Directory](#path-handling-and-current-working-directory) - [Cache Portability](#cache-portability) - [Maximum Portability with Checksums](#maximum-portability-with-checksums) - [Handling Project Relocations](#handling-project-relocations) - [Path Security and Traversal Prevention](#path-security-and-traversal-prevention) - [Using Checksums to Determine if a File has Changed (useCheckSum)](#using-checksums-to-determine-if-a-file-has-changed-usechecksum) - [Setting Additional Meta Data](#setting-additional-meta-data) - [Logger Support](#logger-support) - [Logger Interface](#logger-interface) - [Using Pino Logger](#using-pino-logger) - [Log Levels](#log-levels) - [Example with Custom Log Levels](#example-with-custom-log-levels) - [Debugging Cache Operations](#debugging-cache-operations) - [How to Contribute](#how-to-contribute) - [License and Copyright](#license-and-copyright) # Installation ```bash npm install file-entry-cache ``` # Getting Started ```javascript import fileEntryCache from 'file-entry-cache'; const cache = fileEntryCache.create('cache1'); // Using relative paths let fileDescriptor = cache.getFileDescriptor('./src/file.txt'); console.log(fileDescriptor.changed); // true as it is the first time console.log(fileDescriptor.key); // './src/file.txt' (stored as provided) fileDescriptor = cache.getFileDescriptor('./src/file.txt'); console.log(fileDescriptor.changed); // false as it has not changed // do something to change the file fs.writeFileSync('./src/file.txt', 'new data foo bar'); // check if the file has changed fileDescriptor = cache.getFileDescriptor('./src/file.txt'); console.log(fileDescriptor.changed); // true ``` Using `create()` with options for more control: ```javascript import fileEntryCache from 'file-entry-cache'; // Create cache with options const cache = fileEntryCache.create('myCache', './.cache', { useCheckSum: true, // Use checksums for more reliable change detection cwd: '/path/to/project', // Custom working directory restrictAccessToCwd: false, // Allow access outside cwd (use with caution) useAbsolutePathAsKey: false // Store relative paths in cache }); let fileDescriptor = cache.getFileDescriptor('./src/file.txt'); console.log(fileDescriptor.changed); // true console.log(fileDescriptor.meta.hash); // checksum hash ``` Save it to Disk and Reconcile files that are no longer found ```javascript import fileEntryCache from 'file-entry-cache'; const cache = fileEntryCache.create('cache1'); let fileDescriptor = cache.getFileDescriptor('./src/file.txt'); console.log(fileDescriptor.changed); // true as it is the first time cache.reconcile(); // save the cache to disk and remove files that are no longer found ``` Load the cache from a file: ```javascript import fileEntryCache from 'file-entry-cache'; // Basic usage const cache = fileEntryCache.createFromFile('/path/to/cache/file'); let fileDescriptor = cache.getFileDescriptor('./src/file.txt'); console.log(fileDescriptor.changed); // false as it has not changed from the saved cache. // With options const cache2 = fileEntryCache.createFromFile('/path/to/cache/file', { useCheckSum: true, cwd: '/path/to/project' }); ``` # Changes from v10 to v11 **BREAKING CHANGES:** - **`create()` and `createFromFile()` now use `CreateOptions` object** - The function signatures have changed to accept an options object instead of individual parameters for better extensibility and clarity. **Old API (v10):** ```javascript // v10 - positional parameters const cache = fileEntryCache.create(cacheId, cacheDirectory, useCheckSum, cwd); const cache2 = fileEntryCache.createFromFile(filePath, useCheckSum, cwd); ``` **New API (v11):** ```javascript // v11 - options object const cache = fileEntryCache.create(cacheId, cacheDirectory, { useCheckSum: true, cwd: '/path/to/project', restrictAccessToCwd: false }); const cache2 = fileEntryCache.createFromFile(filePath, { useCheckSum: true, cwd: '/path/to/project', restrictAccessToCwd: false }); ``` - **Renamed `strictPaths` to `restrictAccessToCwd`** - For better clarity and self-documentation, the option that restricts file access to the current working directory has been renamed. **Migration:** ```javascript // Old const cache = new FileEntryCache({ strictPaths: true }); cache.strictPaths = false; // New const cache = new FileEntryCache({ restrictAccessToCwd: true }); cache.restrictAccessToCwd = false; ``` - **Renamed `currentWorkingDirectory` to `cwd`** - For consistency with common conventions and brevity, the property has been renamed to `cwd`. **Migration:** ```javascript // Old const cache = new FileEntryCache({ currentWorkingDirectory: '/path/to/project' }); cache.currentWorkingDirectory = '/new/path'; // New const cache = new FileEntryCache({ cwd: '/path/to/project' }); cache.cwd = '/new/path'; ``` **NEW FEATURES:** - **Added `cwd` option** - You can now specify a custom current working directory for resolving relative paths - **Added `restrictAccessToCwd` option** - Provides protection against path traversal attacks (disabled by default for backwards compatibility) - **Added `useAbsolutePathAsKey` option** - When `true`, cache keys use absolute paths instead of the provided paths. Default is `false` for better cache portability with relative paths - **Added `logger` option** - Support for Pino-compatible logger instances to enable debugging and monitoring of cache operations. See [Logger Support](#logger-support) section for details - **Improved cache portability** - When using relative paths with the same `cwd`, cache files are portable across different environments # Changes from v9 to v10 There have been many features added and changes made to the `file-entry-cache` class. Here are the main changes: - Added `cache` object to the options to allow for more control over the cache - Added `hashAlgorithm` to the options to allow for different checksum algorithms. Note that if you load from file it most likely will break if the value was something before. - Migrated to Typescript with ESM and CommonJS support. This allows for better type checking and support for both ESM and CommonJS. - Once options are passed in they get assigned as properties such as `hashAlgorithm`. For the Cache options they are assigned to `cache` such as `cache.ttl` and `cache.lruSize`. - Added `cache.persistInterval` to allow for saving the cache to disk at a specific interval. This will save the cache to disk at the interval specified instead of calling `reconsile()` to save. (`off` by default) - Added `getFileDescriptorsByPath(filePath: string): FileEntryDescriptor[]` to get all the file descriptors that start with the path specified. This is useful when you want to get all the files in a directory or a specific path. - Using `flat-cache` v6 which is a major update. This allows for better performance and more control over the cache. - On `FileEntryDescriptor.meta` if using typescript you need to use the `meta.data` to set additional information. This is to allow for better type checking and to avoid conflicts with the `meta` object which was `any`. # Global Default Functions - `create(cacheId: string, cacheDirectory?: string, options?: CreateOptions)` - Creates a new instance of the `FileEntryCache` class - `createFromFile(filePath: string, options?: CreateOptions)` - Creates a new instance of the `FileEntryCache` class and loads the cache from a file. ## CreateOptions Type All options from `FileEntryCacheOptions` except `cache`: - `useCheckSum?` - If `true` it will use a checksum to determine if the file has changed. Default is `false` - `hashAlgorithm?` - The algorithm to use for the checksum. Default is `md5` - `cwd?` - The current working directory for resolving relative paths. Default is `process.cwd()` - `restrictAccessToCwd?` - If `true` restricts file access to within `cwd` boundaries. Default is `false` - `useAbsolutePathAsKey?` - If `true` uses absolute paths as cache keys. Default is `false` - `logger?` - A logger instance for debugging. Default is `undefined` # FileEntryCache Options (FileEntryCacheOptions) - `useModifiedTime?` - If `true` it will use the modified time to determine if the file has changed. Default is `true` - `useCheckSum?` - If `true` it will use a checksum to determine if the file has changed. Default is `false` - `hashAlgorithm?` - The algorithm to use for the checksum. Default is `md5` but can be any algorithm supported by `crypto.createHash` - `cwd?` - The current working directory for resolving relative paths. Default is `process.cwd()` - `restrictAccessToCwd?` - If `true` restricts file access to within `cwd` boundaries, preventing path traversal attacks. Default is `true` - `logger?` - A logger instance compatible with Pino logger interface for debugging and monitoring. Default is `undefined` - `cache.ttl?` - The time to live for the cache in milliseconds. Default is `0` which means no expiration - `cache.lruSize?` - The number of items to keep in the cache. Default is `0` which means no limit - `cache.useClone?` - If `true` it will clone the data before returning it. Default is `false` - `cache.expirationInterval?` - The interval to check for expired items in the cache. Default is `0` which means no expiration - `cache.persistInterval?` - The interval to save the data to disk. Default is `0` which means no persistence - `cache.cacheDir?` - The directory to save the cache files. Default is `./cache` - `cache.cacheId?` - The id of the cache. Default is `cache1` - `cache.parse?` - The function to parse the data. Default is `flatted.parse` - `cache.stringify?` - The function to stringify the data. Default is `flatted.stringify` # API - `constructor(options?: FileEntryCacheOptions)` - Creates a new instance of the `FileEntryCache` class - `useCheckSum: boolean` - If `true` it will use a checksum to determine if the file has changed. Default is `false` - `hashAlgorithm: string` - The algorithm to use for the checksum. Default is `md5` but can be any algorithm supported by `crypto.createHash` - `getHash(buffer: Buffer): string` - Gets the hash of a buffer used for checksums - `cwd: string` - The current working directory for resolving relative paths. Default is `process.cwd()` - `restrictAccessToCwd: boolean` - If `true` restricts file access to within `cwd` boundaries. Default is `true` - `useAbsolutePathAsKey: boolean` - If `true` uses absolute paths as cache keys. Default is `false` to maintain better cache portability - `logger: ILogger | undefined` - A logger instance for debugging and monitoring cache operations - `createFileKey(filePath: string): string` - Returns the cache key for the file path (returns the path exactly as provided when `useAbsolutePathAsKey` is `false`, otherwise returns the absolute path). - `deleteCacheFile(): boolean` - Deletes the cache file from disk - `destroy(): void` - Destroys the cache. This will clear the cache in memory. If using cache persistence it will stop the interval. - `removeEntry(filePath: string): void` - Removes an entry from the cache. - `reconcile(): void` - Saves the cache to disk and removes any files that are no longer found. - `hasFileChanged(filePath: string): boolean` - Checks if the file has changed. This will return `true` if the file has changed. - `getFileDescriptor(filePath: string, options?: { useModifiedTime?: boolean, useCheckSum?: boolean }): FileEntryDescriptor` - Gets the file descriptor for the file. Please refer to the entire section on `Get File Descriptor` for more information. - `normalizeEntries(files?: string[]): FileDescriptor[]` - Normalizes the entries. If no files are provided, it will return all cached entries. - `analyzeFiles(files: string[])` will return `AnalyzedFiles` object with `changedFiles`, `notFoundFiles`, and `notChangedFiles` as FileDescriptor arrays. - `getUpdatedFiles(files: string[])` will return an array of `FileEntryDescriptor` objects that have changed. - `getFileDescriptorsByPath(filePath: string): FileEntryDescriptor[]` will return an array of `FileEntryDescriptor` objects that starts with the path prefix specified. - `getAbsolutePath(filePath: string): string` - Resolves a relative path to absolute using the configured `cwd`. Returns absolute paths unchanged. When `restrictAccessToCwd` is enabled, throws an error if the path resolves outside `cwd`. - `getAbsolutePathWithCwd(filePath: string, cwd: string): string` - Resolves a relative path to absolute using a custom working directory. When `restrictAccessToCwd` is enabled, throws an error if the path resolves outside the provided `cwd`. # Get File Descriptor The `getFileDescriptor(filePath: string, options?: { useCheckSum?: boolean, useModifiedTime?: boolean }): FileEntryDescriptor` function is used to get the file descriptor for the file. This function will return a `FileEntryDescriptor` object that has the following properties: - `key: string` - The cache key for the file. This is exactly the path that was provided (relative or absolute). - `changed: boolean` - If the file has changed since the last time it was analyzed. - `notFound: boolean` - If the file was not found. - `meta: FileEntryMeta` - The meta data for the file. This has the following properties: `size`, `mtime`, `hash`, `data`. Note that `data` is an object that can be used to store additional information. - `err` - If there was an error analyzing the file. ## Path Handling and Current Working Directory The cache stores paths exactly as they are provided (relative or absolute). When checking if files have changed, relative paths are resolved using the configured `cwd` (current working directory): ```javascript // Default: uses process.cwd() const cache1 = fileEntryCache.create('cache1'); // Custom working directory using options object const cache2 = fileEntryCache.create('cache2', './cache', { cwd: '/project/root' }); // Or using the class constructor directly const cache3 = new FileEntryCache({ cwd: '/project/root' }); // The cache key is always the provided path const descriptor = cache2.getFileDescriptor('./src/file.txt'); console.log(descriptor.key); // './src/file.txt' // But file operations resolve from: '/project/root/src/file.txt' ``` ### Cache Portability Using relative paths with a consistent `cwd` (defaults to `process.cwd()`) makes cache files portable across different machines and environments. This is especially useful for CI/CD pipelines and team development. ```javascript // On machine A (project at /home/user/project) const cacheA = fileEntryCache.create('build-cache', './cache', { cwd: '/home/user/project' }); cacheA.getFileDescriptor('./src/index.js'); // Resolves to /home/user/project/src/index.js cacheA.reconcile(); // On machine B (project at /workspace/project) const cacheB = fileEntryCache.create('build-cache', './cache', { cwd: '/workspace/project' }); cacheB.getFileDescriptor('./src/index.js'); // Resolves to /workspace/project/src/index.js // Cache hit! File hasn't changed since machine A ``` ### Maximum Portability with Checksums For maximum cache portability across different environments, use checksums (`useCheckSum: true`) along with relative paths and `cwd` which defaults to `process.cwd()`. This ensures that cache validity is determined by file content rather than modification times, which can vary across systems: ```javascript // Development machine const devCache = fileEntryCache.create('.buildcache', './cache', { useCheckSum: true // Use checksums for content-based comparison }); // Process files using relative paths const descriptor = devCache.getFileDescriptor('./src/index.js'); if (descriptor.changed) { console.log('Building ./src/index.js...'); // Build process here } devCache.reconcile(); // Save cache // CI/CD Pipeline or another developer's machine const ciCache = fileEntryCache.create('.buildcache', './node_modules/.cache', { useCheckSum: true, // Same checksum setting cwd: process.cwd() // Different absolute path, same relative structure }); // Same relative path works across environments const descriptor2 = ciCache.getFileDescriptor('./src/index.js'); if (!descriptor2.changed) { console.log('Using cached result for ./src/index.js'); // Skip rebuild - file content unchanged } ``` ### Handling Project Relocations Cache remains valid even when projects are moved or renamed: ```javascript // Original location: /projects/my-app const cache1 = fileEntryCache.create('.cache', './cache', { useCheckSum: true, cwd: '/projects/my-app' }); cache1.getFileDescriptor('./src/app.js'); cache1.reconcile(); // After moving project to: /archived/2024/my-app const cache2 = fileEntryCache.create('.cache', './cache', { useCheckSum: true, cwd: '/archived/2024/my-app' }); cache2.getFileDescriptor('./src/app.js'); // Still finds cached entry! // Cache valid as long as relative structure unchanged ``` If there is an error when trying to get the file descriptor it will return a `notFound` and `err` property with the error. ```javascript const fileEntryCache = new FileEntryCache(); const fileDescriptor = fileEntryCache.getFileDescriptor('no-file'); if (fileDescriptor.err) { console.error(fileDescriptor.err); } if (fileDescriptor.notFound) { console.error('File not found'); } ``` # Path Security and Traversal Prevention The `restrictAccessToCwd` option provides security against path traversal attacks by restricting file access to within the configured `cwd` boundaries. **This is enabled by default (since v11)** to ensure secure defaults when processing untrusted input or when running in security-sensitive environments. ## Basic Usage ```javascript // restrictAccessToCwd is enabled by default for security const cache = new FileEntryCache({ cwd: '/project/root' }); // This will work - file is within cwd const descriptor = cache.getFileDescriptor('./src/index.js'); // This will throw an error - attempts to access parent directory try { cache.getFileDescriptor('../../../etc/passwd'); } catch (error) { console.error(error); // Path traversal attempt blocked } // To allow parent directory access (not recommended for untrusted input) const unsafeCache = new FileEntryCache({ cwd: '/project/root', restrictAccessToCwd: false // Explicitly disable protection }); ``` ## Security Features When `restrictAccessToCwd` is enabled: - **Path Traversal Prevention**: Blocks attempts to access files outside the working directory using `../` sequences - **Null Byte Protection**: Automatically removes null bytes from paths to prevent injection attacks - **Path Normalization**: Cleans and normalizes paths to prevent bypass attempts ## Use Cases ### Build Tools with Untrusted Input ```javascript // Secure build tool configuration const cache = fileEntryCache.create('.buildcache', './cache', { useCheckSum: true, cwd: process.cwd(), restrictAccessToCwd: true // Enable strict path checking for security }); // Process user-provided file paths safely function processUserFile(userProvidedPath) { try { const descriptor = cache.getFileDescriptor(userProvidedPath); // Safe to process - file is within boundaries return descriptor; } catch (error) { if (error.message.includes('Path traversal attempt blocked')) { console.warn('Security: Blocked access to:', userProvidedPath); return null; } throw error; } } ``` ### CI/CD Environments ```javascript // Strict security for CI/CD pipelines const cache = new FileEntryCache({ cwd: process.env.GITHUB_WORKSPACE || process.cwd(), restrictAccessToCwd: true, // Prevent access outside workspace useCheckSum: true // Content-based validation }); // All file operations are now restricted to the workspace cache.getFileDescriptor('./src/app.js'); // ✓ Allowed cache.getFileDescriptor('/etc/passwd'); // ✗ Blocked (absolute path outside cwd) cache.getFileDescriptor('../../../root'); // ✗ Blocked (path traversal) ``` ### Dynamic Security Control ```javascript const cache = new FileEntryCache({ cwd: '/safe/directory' }); // Start with relaxed mode for trusted operations cache.restrictAccessToCwd = false; processInternalFiles(); // Enable strict mode for untrusted input cache.restrictAccessToCwd = true; processUserUploadedPaths(); // Return to relaxed mode if needed cache.restrictAccessToCwd = false; ``` ## Default Behavior **As of v11, `restrictAccessToCwd` is enabled by default** to provide secure defaults. This means: - Path traversal attempts using `../` are blocked - File access is restricted to within the configured `cwd` - Null bytes in paths are automatically sanitized ### Migrating from v10 or Earlier If you're upgrading from v10 or earlier and need to maintain the previous behavior (for example, if your code legitimately accesses parent directories), you can explicitly disable strict paths: ```javascript const cache = new FileEntryCache({ cwd: process.cwd(), restrictAccessToCwd: false // Restore v10 behavior }); ``` However, we strongly recommend keeping `restrictAccessToCwd: true` and adjusting your code to work within the security boundaries, especially when processing any untrusted input. # Using Checksums to Determine if a File has Changed (useCheckSum) By default the `useCheckSum` is `false`. This means that the `FileEntryCache` will use the `mtime` and `ctime` to determine if the file has changed. If you set `useCheckSum` to `true` it will use a checksum to determine if the file has changed. This is useful when you want to make sure that the file has not changed at all. ```javascript const fileEntryCache = new FileEntryCache(); const fileDescriptor = fileEntryCache.getFileDescriptor('file.txt', { useCheckSum: true }); ``` You can pass `useCheckSum` in the FileEntryCache options, as a property `.useCheckSum` to make it default for all files, or in the `getFileDescriptor` function. Here is an example where you set it globally but then override it for a specific file: ```javascript const fileEntryCache = new FileEntryCache({ useCheckSum: true }); const fileDescriptor = fileEntryCache.getFileDescriptor('file.txt', { useCheckSum: false }); ``` # Setting Additional Meta Data In the past we have seen people do random values on the `meta` object. This can cause issues with the `meta` object. To avoid this we have `data` which can be anything. ```javascript const fileEntryCache = new FileEntryCache(); const fileDescriptor = fileEntryCache.getFileDescriptor('file.txt'); fileDescriptor.meta.data = { myData: 'myData' }; //anything you want ``` # Logger Support The `FileEntryCache` supports logging through a Pino-compatible logger interface. This is useful for debugging and monitoring cache operations in production environments. ## Logger Interface The logger must implement the following interface: ```typescript interface ILogger { level?: string; trace: (message: string | object, ...args: unknown[]) => void; debug: (message: string | object, ...args: unknown[]) => void; info: (message: string | object, ...args: unknown[]) => void; warn: (message: string | object, ...args: unknown[]) => void; error: (message: string | object, ...args: unknown[]) => void; fatal: (message: string | object, ...args: unknown[]) => void; } ``` ## Using Pino Logger You can pass a Pino logger instance to the `FileEntryCache` constructor or set it via the `logger` property: ```javascript import pino from 'pino'; import fileEntryCache from 'file-entry-cache'; // Create a Pino logger const logger = pino({ level: 'debug', transport: { target: 'pino-pretty', options: { colorize: true } } }); // Pass logger in constructor const cache = new fileEntryCache.FileEntryCache({ logger, cacheId: 'my-cache' }); // Or set it after creation cache.logger = logger; // Now all cache operations will be logged const descriptor = cache.getFileDescriptor('./src/file.txt'); ``` ## Log Levels The logger will output different levels of information: - **trace**: Detailed internal operations (key creation, cached meta lookup, file stats) - **debug**: Method entry, checksum settings, change detection, file status - **info**: Important state changes (file has changed) - **error**: File read errors and exceptions ## Example with Custom Log Levels ```javascript import pino from 'pino'; import { FileEntryCache } from 'file-entry-cache'; // Create logger with specific level const logger = pino({ level: 'info' }); const cache = new FileEntryCache({ logger, useCheckSum: true }); // This will log at info level when files change const files = ['./src/index.js', './src/utils.js']; files.forEach(file => { const descriptor = cache.getFileDescriptor(file); if (descriptor.changed) { console.log(`Processing changed file: ${file}`); } }); cache.reconcile(); ``` ## Debugging Cache Operations For detailed debugging, set the logger level to `debug` or `trace`: ```javascript import pino from 'pino'; import { FileEntryCache } from 'file-entry-cache'; const logger = pino({ level: 'trace', transport: { target: 'pino-pretty' } }); const cache = new FileEntryCache({ logger, useCheckSum: true, cwd: '/project/root' }); // Will log detailed information about: // - File path resolution // - Cache key creation // - Cached metadata lookup // - File stats reading // - Hash calculation (if using checksums) // - Change detection logic const descriptor = cache.getFileDescriptor('./src/app.js'); ``` # How to Contribute You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`. # License and Copyright [MIT © Jared Wray](./LICENSE) ### flat-cache URL: https://cacheable.org/docs/flat-cache/ [Cacheable](https://github.com/jaredwray/cacheable) # flat-cache > A simple key/value storage using files to persist the data [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/flat-cache.svg)](https://www.npmjs.com/package/flat-cache) [![npm](https://img.shields.io/npm/v/flat-cache)](https://www.npmjs.com/package/flat-cache) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) # Features - A simple key/value storage using files to persist the data - Uses a in-memory cache (via `CacheableMemory`) as the primary storage and then persists the data to disk - Automatically saves the data to disk via `persistInterval` setting. Off By Default - Uses `expirationInterval` to check for expired items in the cache. If it is not set it will do a lazy check on `get` or `getKey` - Easily Loads the data from disk and into memory with `load` or `loadFile` - Uses `ttl` and `lruSize` to manage the cache and persist the data - Only saves the data to disk if the data has changed even when using `persistInterval` or calling `save()` - Uses `flatted` to parse and stringify the data by default but can be overridden using `serialize` and `deserialize` in options - ESM and CommonJS support with TypeScript typings and maintained regularly! # Table of Contents - [Installation](#installation) - [Getting Started](#getting-started) - [Breaking Changes from v5 to v6](#breaking-changes-from-v5-to-v6) - [Global Functions](#global-functions) - [FlatCache Options (FlatCacheOptions)](#flatcache-options-flatcacheoptions) - [API](#api) - [Events (FlatCacheEvents)](#events-flatcacheevents) - [Parse and Stringify for File Caching](#parse-and-stringify-for-file-caching) - [Loading Data via Streams](#loading-data-via-streams) - [How to Contribute](#how-to-contribute) - [License and Copyright](#license-and-copyright) # Installation ```bash npm install flat-cache ``` # Getting Started ```javascript import { FlatCache } from 'flat-cache'; const cache = new FlatCache(); cache.setKey('key', 'value'); cache.save(); // Saves the data to disk ``` lets add it with `ttl`, `lruSize`, and `persistInterval` ```javascript import { FlatCache } from 'flat-cache'; const cache = new FlatCache({ ttl: 60 * 60 * 1000 , // 1 hour lruSize: 10000, // 10,000 items expirationInterval: 5 * 1000 * 60, // 5 minutes persistInterval: 5 * 1000 * 60, // 5 minutes }); cache.setKey('key', 'value'); ``` This will save the data to disk every 5 minutes and will remove any data that has not been accessed in 1 hour or if the cache has more than 10,000 items. The `expirationInterval` will check every 5 minutes for expired items and evict them. This is replacement to the `save()` method with a `prune` option as it is no longer needed due to the fact that the in-memory cache handles pruning by `ttl` expiration or `lruSize` which will keep the most recent there. here is an example doing load from already existing persisted cache using the `createFromFile` function: ```javascript import { createFromFile } from 'flat-cache'; const cache = createFromFile('./cacheAltDirectory/cache1'); ``` You can also use the legacy load function to do this: ```javascript import { FlatCache } from 'flat-cache'; const cache = new FlatCache(); cache.load('cache1', './cacheAltDirectory'); ``` or ```javascript import { FlatCache } from 'flat-cache'; const cache = new FlatCache({ cacheDir: './cacheAltDirectory' }); cache.load('cache1'); ``` This will load the cache from the `./cacheAltDirectory` directory with the `cache1` id. If it doesnt exist it will not throw an error but will just return an empty cache. # Breaking Changes from v5 to v6 There have been many features added and changes made to the `FlatCache` class. Here are the main changes: - `FlatCache` is now a class and not a function which you can create instances of or using legacy method `load`, `loadFile`, or `create` - `FlatCache` now uses `CacheableMemory` as the primary storage and then persists the data to disk - `FlatCache` now uses `ttl` and `lruSize` to manage the cache and persist the data - `FlatCache` now uses `expirationInterval` to check for expired items in the cache. If it is not set it will do a lazy check on `get` or `getKey` - `getKey` still exists but is now is an alias to `get` and will be removed in the future - `setKey` still exists but is now is an alias to `set` and will be removed in the future - `removeKey` still exists but is now is an alias to `delete` and will be removed in the future Here is an example of the legacy method `load`: ```javascript const flatCache = require('flat-cache'); // loads the cache, if one does not exists for the given // Id a new one will be prepared to be created const cache = flatCache.load('cacheId'); ``` Now you can use the `load` method and ES6 imports: ```javascript import { FlatCache } from 'flat-cache'; const cache = new FlatCache(); cache.load('cacheId'); ``` If you do not specify a `cacheId` it will default to what was set in `FlatCacheOptions` or the default property `cacheId` of `cache1` and default `cacheDir` of `./cache`. If you want to create a new cache and load from disk if it exists you can use the `create` method: ```javascript import { create } from 'flat-cache'; const cache = create({ cacheId: 'myCacheId', cacheDir: './mycache', ttl: 60 * 60 * 1000 }); ``` # Global Functions In version 6 we attempted to keep as much as the functionality as possible which includes these functions: - `create(options?: FlatCacheOptions)` - Creates a new cache and will load the data from disk if it exists - `createFromFile(filePath, options?: FlatCacheOptions)` - Creates a new cache from a file - `clearCacheById(cacheId: string, cacheDir?: string)` - Clears the cache by the cacheId on the file system not in memory. - `clearAll(cacheDirectory?: string)` - Clears all the caches # FlatCache Options (FlatCacheOptions) - `ttl?` - The time to live for the cache in milliseconds. Default is `0` which means no expiration - `lruSize?` - The number of items to keep in the cache. Default is `0` which means no limit - `useClone?` - If `true` it will clone the data before returning it. Default is `false` - `expirationInterval?` - The interval to check for expired items in the cache. Default is `0` which means no expiration - `persistInterval?` - The interval to save the data to disk. Default is `0` which means no persistence - `cacheDir?` - The directory to save the cache files. Default is `./cache` - `cacheId?` - The id of the cache. Default is `cache1` - `serialize?` - The function to parse the data. Default is `flatted.parse` - `deserialize?` - The function to stringify the data. Default is `flatted.stringify` # API - `cache` - The in-memory cache as a `CacheableMemory` instance - `cacheDir` - The directory to save the cache files - `cacheId` - The id of the cache - `cacheFilePath` - The full path to the cache file - `cacheDirPath` - The full path to the cache directory - `persistInterval` - The interval to save the data to disk - `changesSinceLastSave` - If there have been changes since the last save - `load(cacheId: string, cacheDir?: string)` - Loads the data from disk - `loadFile(pathToFile: string)` - Loads the data from disk - `loadFileStream(pathToFile: string, onProgress: function, onEnd: function, onError?: function)` - Loads the data from disk as a stream - `all()` - Gets all the data in the cache - `items()` - Gets all the items in the cache - `keys()` - Gets all the keys in the cache - `setKey(key: string, value: any, ttl?: string | number)` - (legacy) Sets the key/value pair in the cache - `set(key: string, value: any, ttl?: string | number)` - Sets the key/value pair in the cache - `getKey(key: string)` - Gets the value for the key or the default value - `get(key: string)` - Gets the value for the key or the default value - `removeKey(key: string)` - Removes the key from the cache - `delete(key: string)` - Removes the key from the cache - `clear()` - Clears the cache - `save(force? boolean)` - Saves the data to disk. If `force` is `true` it will save even if `changesSinceLastSave` is `false` - `destroy()` - Destroys the cache and remove files # Events (FlatCacheEvents) Events have been added since v6 to allow for more control and visibility into the cache. Here are the events that are available: - `on(event: 'save', listener: () => void)` - Emitted when the cache is saved - `on(event: 'load', listener: () => void)` - Emitted when the cache is loaded - `on(event: 'delete', listener: (key: string) => void)` - Emitted when the cache is changed - `on(event: 'clear', listener: () => void)` - Emitted when the cache is cleared - `on(event: 'destroy', listener: () => void)` - Emitted when the cache is destroyed - `on(event: 'error', listener: (error: Error) => void)` - Emitted when there is an error Here is an example of how to use the `error` events: ```javascript import { FlatCache, FlatCacheEvents } from 'flat-cache'; const cache = new FlatCache(); cache.on(FlatCacheEvents.error, (error) => { console.error(error); }); ``` `FlatCacheEvents` is an enum that contains the event names for the `on` method. You do not have to use it but makes it easier to know what events are available. # Parse and Stringify for File Caching By default `flat-cache` uses `flatted` to parse and stringify the data. This is to allow for more complex data structures to be saved to disk. If you want to override this you can pass in your own `parse` and `stringify` functions. Here is an example: ```javascript import { FlatCache } from 'flat-cache'; const cache = new FlatCache({ deserialize: JSON.parse, serialize: JSON.stringify, }); ``` This will use `JSON.parse` and `JSON.stringify` to parse and stringify the data. This is useful if you want to use a different library or have a custom way of parsing and stringifying the data. **NOTE: This could cause issues if you are trying to load data that was saved with a different parser or stringifier.** # Loading Data via Streams Because of some large files we have added a helper function to load data from a file using streams. This is useful if you have a large file and want to load it in chunks. Here is an example: ```javascript import { FlatCache } from 'flat-cache'; const cache = new FlatCache(); let progressCount = 0; const onProgress = (progress: number, total: number) => { progressCount++; }; let errorCount = 0; const onError = (error: Error) => { errorCount++; }; let endCount = 0; const onEnd = () => { console.log(`Loaded ${progressCount} chunks with ${errorCount} errors.`); }; cache.loadFileStream('/path/to/cache/file', onProgress, onEnd, onError); ``` This will load the data from the file in chunks and emit the `onProgress`, `onEnd`, and `onError` events. You can use these events to track the progress of the loading and handle any errors that may occur. # How to Contribute You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`. # License and Copyright [MIT © Jared Wray](./LICENSE) ### @cacheable/memory URL: https://cacheable.org/docs/memory/ [Cacheable](https://github.com/jaredwray/cacheable) > High Performance Layer 1 / Layer 2 Caching with Keyv Storage [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/@cacheable/memory.svg)](https://www.npmjs.com/package/@cacheable/memory) [![npm](https://img.shields.io/npm/v/@cacheable/memory.svg)](https://www.npmjs.com/package/@cacheable/memory) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) You can use `CacheableMemory` as a standalone cache or as a primary store for `cacheable`. You can also set the `useClones` property to `false` if you want to use the same reference for the values. This is useful if you are using large objects and want to save memory. The `lruSize` property is the size of the LRU cache and is set to `0` by default, which disables the LRU cache (no LRU eviction is performed and the cache size is bounded only by the underlying `Map` stores). When the `lruSize` property is set to a value greater than `0`, it limits the number of keys in the cache and evicts the least recently used entries when full. This simple in-memory cache uses multiple Map objects and a with `expiration` and `lru` policies if set to manage the in memory cache at scale. By default we use lazy expiration deletion which means on `get` and `getMany` type functions we look if it is expired and then delete it. If you want to have a more aggressive expiration policy you can set the `checkInterval` property to a value greater than `0` which will check for expired keys at the interval you set. 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. # Table of Contents * [Getting Started](#getting-started) * [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) * [CacheableMemory - API](#cacheablememory---api) * [Keyv Storage Adapter - KeyvCacheableMemory](#keyv-storage-adapter---keyvcacheablememory) * [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions) * [Get Or Set Memoization Function](#get-or-set-memoization-function) * [How to Contribute](#how-to-contribute) * [License and Copyright](#license-and-copyright) # Getting Started ```bash npm install @cacheable/memory ``` # Basic Usage ```javascript import { CacheableMemory } from '@cacheable/memory'; const cacheable = new CacheableMemory(); await cacheable.set('key', 'value', 1000); const value = await cacheable.get('key'); ``` In this example, the primary store we will use `lru-cache` and the secondary store is Redis. You can also set multiple stores in the options: ```javascript import { CacheableMemory } from '@cacheable/memory'; // we set the storeHashSize to 1 so that we only use a single Map object as the lru is limited to a single Map size const cache = new CacheableMemory({storeHashSize: 1, lruSize: 80000}); cache.set('key1', 'value1'); const result = cache.get('key1'); console.log(result); // 'value1' ``` This is a more advanced example and not needed for most use cases. # Shorthand for Time to Live (ttl) By default `Cacheable` and `CacheableMemory` the `ttl` is in milliseconds but you can use shorthand for the time to live. Here are the following shorthand values: * `ms`: Milliseconds such as (1ms = 1) * `s`: Seconds such as (1s = 1000) * `m`: Minutes such as (1m = 60000) * `h` or `hr`: Hours such as (1h = 3600000) * `d`: Days such as (1d = 86400000) Here is an example of how to use the shorthand for the `ttl`: ```javascript import { CacheableMemory } from 'cacheable'; const cache = new CacheableMemory({ ttl: '15m' }); //sets the default ttl to 15 minutes (900000 ms) cache.set('key', 'value', '1h'); //sets the ttl to 1 hour (3600000 ms) and overrides the default ``` if you want to disable the `ttl` you can set it to `0` or `undefined`: ```javascript import { CacheableMemory } from 'cacheable'; const cache = new CacheableMemory({ ttl: 0 }); //sets the default ttl to 0 which is disabled cache.set('key', 'value', 0); //sets the ttl to 0 which is disabled ``` If you set the ttl to anything below `0` or `undefined` it will disable the ttl for the cache and the value that returns will be `undefined`. With no ttl set the value will be stored `indefinitely`. ```javascript import { CacheableMemory } from 'cacheable'; const cache = new CacheableMemory({ ttl: 0 }); //sets the default ttl to 0 which is disabled console.log(cache.ttl); // undefined cache.ttl = '1h'; // sets the default ttl to 1 hour (3600000 ms) console.log(cache.ttl); // '1h' cache.ttl = -1; // sets the default ttl to 0 which is disabled console.log(cache.ttl); // undefined ``` ## Retrieving raw cache entries The `getRaw` and `getManyRaw` methods return the full stored metadata (`StoredDataRaw`) instead of just the value: ```typescript import { CacheableMemory } from 'cacheable'; const cache = new CacheableMemory(); // store a value await cache.set('user:1', { name: 'Alice' }, '1h'); // 1 hour // default: only the value const user = await cache.get<{ name: string }>('user:1'); console.log(user); // { name: 'Alice' } // with raw: full record including expiration const raw = await cache.getRaw('user:1'); console.log(raw.value); // { name: 'Alice' } console.log(raw.expires); // e.g. 1677628495000 or null ``` ## 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 `djb2` but you can change it by setting the `storeHashAlgorithm` property in the options. Supported algorithms include DJB2, FNV1, MURMER, and CRC32. 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/memory'; 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 with supported algorithms: ```javascript import { CacheableMemory, HashAlgorithm } from '@cacheable/memory'; // Using DJB2 (default) const cache = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.DJB2 }); // Or other non-cryptographic algorithms for better performance const cache2 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.FNV1 }); const cache3 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.MURMER }); const cache4 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.CRC32 }); cache.set('key', 'value'); const value = cache.get('key'); // value ``` **Available algorithms:** DJB2 (default), FNV1, MURMER, CRC32. Note: Cryptographic algorithms (SHA-256, SHA-384, SHA-512) are not recommended for store hashing due to performance overhead. 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 doubly linked list to track the LRU order across the underlying `Map` stores. The `lruSize` itself is capped at `16,777,216 (2^24) keys` — values above this limit are rejected and an `error` event is emitted. Setting `lruSize` does not change `storeHashSize`; the underlying stores keep whatever `storeHashSize` you configured (default `16`). ```javascript import { CacheableMemory } from 'cacheable'; const cache = new CacheableMemory({ lruSize: 1 }); // sets the LRU cache size to 1 key 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 | |------------------------------------------|:---------:|----------:|----------:|:--------:|----------:| | Cacheable Memory (v1.10.0) - set / get | 🥇 | 152K | 7µs | ±0.94% | 147K | | Map (v22) - set / get | -1.1% | 151K | 7µs | ±0.69% | 145K | | Node Cache - set / get | -4.3% | 146K | 7µs | ±1.13% | 142K | | bentocache (v1.4.0) - set / get | -20% | 121K | 8µs | ±0.40% | 119K | *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. ## Maximum Time to Live (maxTtl) You can set a `maxTtl` option to enforce an upper bound on any TTL in the cache. When `maxTtl` is set: - Any per-entry TTL that exceeds `maxTtl` will be capped to `maxTtl`. - Entries with no TTL (that would otherwise live indefinitely) will be capped to `maxTtl`. - The default TTL is still respected if it is within the `maxTtl` limit. This is useful when you want to guarantee that no cache entry lives longer than a certain duration, regardless of what TTL is passed to individual `set()` calls. ```javascript import { CacheableMemory } from '@cacheable/memory'; // No entry can live longer than 1 hour const cache = new CacheableMemory({ maxTtl: '1h' }); cache.set('key1', 'value1', '2h'); // capped to 1 hour cache.set('key2', 'value2'); // also capped to 1 hour (would otherwise be indefinite) cache.set('key3', 'value3', '30m'); // 30 minutes is within maxTtl, so it stays as-is ``` You can also set `maxTtl` after construction: ```javascript const cache = new CacheableMemory(); cache.maxTtl = 5000; // 5 seconds max cache.maxTtl = '10m'; // 10 minutes max cache.maxTtl = undefined; // disable maxTtl (no upper bound) ``` ## CacheableMemory Options * `ttl`: The time to live for the cache in milliseconds. Default is `undefined` which is means indefinitely. * `maxTtl`: The maximum time to live for any cache entry. When set, TTLs exceeding this value are capped. Default is `undefined` (no maximum). * `useClones`: If the cache should use clones for the values. Default is `true`. * `lruSize`: The size of the LRU cache. Default is `0`, which disables the LRU cache (no LRU eviction is performed). Maximum is `16,777,216 (2^24)`. * `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 `djb2`. Supported: DJB2, FNV1, MURMER, CRC32. ## CacheableMemory - API * `set(key, value, ttl?)`: Sets a value in the cache. * `setMany([{key, value, ttl?}])`: Sets multiple values in the cache from `CacheableItem`. * `get(key)`: Gets a value from the cache. * `getMany([keys])`: Gets multiple values from the cache. * `getRaw(key)`: Gets a value from the cache as `CacheableStoreItem`. * `getManyRaw([keys])`: Gets multiple values from the cache as `CacheableStoreItem`. * `has(key)`: Checks if a value exists in the cache. * `hasMany([keys])`: Checks if multiple values exist in the cache. * `delete(key)`: Deletes a value from the cache. * `deleteMany([keys])`: Deletes multiple values from the cache. * `take(key)`: Takes a value from the cache and deletes it. * `takeMany([keys])`: Takes multiple values from the cache and deletes them. * `wrap(function, WrapSyncOptions)`: Wraps a `sync` function in a cache. * `clear()`: Clears the cache. * `ttl`: The default time to live for the cache in milliseconds. Default is `undefined` which is disabled. * `maxTtl`: The maximum time to live for any cache entry. When set, TTLs exceeding this value are capped. Default is `undefined` (no maximum). * `useClones`: If the cache should use clones for the values. Default is `true`. * `lruSize`: The size of the LRU cache. Default is `0`, which disables the LRU cache (no LRU eviction is performed). Maximum is `16,777,216 (2^24)`. * `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 `djb2`. Supported: DJB2, FNV1, MURMER, CRC32. * `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. # Keyv Storage Adapter - KeyvCacheableMemory `cacheable` comes with a built-in storage adapter for Keyv called `KeyvCacheableMemory`. This takes `CacheableMemory` and creates a storage adapter for Keyv. This is useful if you want to use `CacheableMemory` as a storage adapter for Keyv. Here is an example of how to use `KeyvCacheableMemory`: ```javascript import { Keyv } from 'keyv'; import { KeyvCacheableMemory } from 'cacheable'; const keyv = new Keyv({ store: new KeyvCacheableMemory() }); await keyv.set('foo', 'bar'); const value = await keyv.get('foo'); console.log(value); // bar ``` # Wrap / Memoization for Sync and Async Functions `CacheableMemory` has a feature called `wrap` that allows you to wrap a function in a cache. This is useful for memoization and caching the results of a function. You can wrap a `sync` function in a cache. Here is an example of how to use the `wrap` function: ```javascript import { CacheableMemory } from 'cacheable'; const syncFunction = (value: number) => { return value * 2; }; const cache = new CacheableMemory(); const wrappedFunction = cache.wrap(syncFunction, { ttl: '1h', key: 'syncFunction' }); console.log(wrappedFunction(2)); // 4 console.log(wrappedFunction(2)); // 4 from cache ``` In this example we are wrapping a `sync` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also set the `key` property in the `wrap()` options to set a custom key for the cache. When an error occurs in the function it will not cache the value and will return the error. This is useful if you want to cache the results of a function but not cache the error. If you want it to cache the error you can set the `cacheError` property to `true` in the `wrap()` options. This is disabled by default. ```javascript import { CacheableMemory } from 'cacheable'; const syncFunction = (value: number) => { throw new Error('error'); }; const cache = new CacheableMemory(); const wrappedFunction = cache.wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cacheError: true }); console.log(wrappedFunction()); // error console.log(wrappedFunction()); // error from cache ``` If you would like to generate your own key for the wrapped function you can set the `createKey` property in the `wrap()` options. This is useful if you want to generate a key based on the arguments of the function or any other criteria. ```javascript const cache = new CacheableMemory(); const options: WrapOptions = { cache, keyPrefix: 'test', createKey: (function_, arguments_, options: WrapOptions) => `customKey:${options?.keyPrefix}:${arguments_[0]}`, }; const wrapped = wrap((argument: string) => `Result for ${argument}`, options); const result1 = wrapped('arg1'); const result2 = wrapped('arg1'); // Should hit the cache console.log(result1); // Result for arg1 console.log(result2); // Result for arg1 (from cache) ``` We will pass in the `function` that is being wrapped, the `arguments` passed to the function, and the `options` used to wrap the function. You can then use these to generate a custom key for the cache. # How to Contribute You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`. # License and Copyright [MIT © Jared Wray](./LICENSE) ### @cacheable/net URL: https://cacheable.org/docs/net/ [Cacheable](https://github.com/jaredwray/cacheable) > High Performance Network Caching for Node.js with fetch support and HTTP cache semantics [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/@cacheable/net.svg)](https://www.npmjs.com/package/@cacheable/net) [![npm](https://img.shields.io/npm/v/@cacheable/net.svg)](https://www.npmjs.com/package/@cacheable/net) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) Features: * `fetch` from [undici](https://github.com/nodejs/undici) with caching enabled via `cacheable` * HTTP method helpers: `get`, `post`, `put`, `patch`, `delete`, and `head` for easier development * [RFC 7234](http://httpwg.org/specs/rfc7234.html) compliant HTTP caching with `http-cache-semantics` * Smart caching with automatic cache key generation * Support for custom serialization/deserialization with `stringify` and `parse` functions * Configurable cache policies - use HTTP cache semantics or simple TTL-based caching * Full TypeScript support with comprehensive type definitions * Request-level cache control with the `caching` option * All the features of [cacheable](https://npmjs.com/package/cacheable) - layered caching, LRU, TTL expiration, and more! * Extensively tested with 100% code coverage # Table of Contents * [Getting Started](#getting-started) * [How to Contribute](#how-to-contribute) * [License and Copyright](#license-and-copyright) # Getting Started ```bash npm install @cacheable/net ``` ## Basic Usage ```javascript import { CacheableNet } from '@cacheable/net'; const net = new CacheableNet(); // Simple GET request with caching const response = await net.get('https://api.example.com/data'); console.log(response.data); // POST request with data const result = await net.post('https://api.example.com/users', { name: 'John Doe', email: 'john@example.com' }); // Using fetch directly with caching const fetchResponse = await net.fetch('https://api.example.com/data', { method: 'GET', headers: { 'Authorization': 'Bearer token' } }); ``` ## Custom Serialization You can provide custom `stringify` and `parse` functions for handling data serialization. This is particularly useful when working with complex data types that JSON doesn't natively support: ```javascript import { CacheableNet } from '@cacheable/net'; import superjson from 'superjson'; // Using superjson for enhanced serialization // Supports Dates, BigInt, RegExp, Set, Map, Error and more const net = new CacheableNet({ stringify: (value) => superjson.stringify(value), parse: (text) => superjson.parse(text) }); // Now you can work with complex data types const response = await net.post('https://api.example.com/data', { timestamp: new Date(), userId: BigInt(12345), pattern: /[a-z]+/gi, metadata: new Map([['key', 'value']]), tags: new Set(['important', 'urgent']) }); // Or provide per-request custom serialization const result = await net.get('https://api.example.com/data', { parse: (text) => { // Custom parsing with superjson for this request only return superjson.parse(text); } }); ``` ## Caching Control You can control caching behavior at multiple levels: ```javascript import { CacheableNet } from '@cacheable/net'; const net = new CacheableNet({ httpCachePolicy: true // Enable HTTP cache semantics globally (default) }); // GET requests are cached by default const data1 = await net.get('https://api.example.com/data'); // Disable caching for a specific GET request const data2 = await net.get('https://api.example.com/data', { caching: false }); // POST requests are NOT cached by default const result1 = await net.post('https://api.example.com/data', { value: 1 }); // Enable caching for a specific POST request const result2 = await net.post('https://api.example.com/data', { value: 1 }, { caching: true }); ``` ## API Reference ### CacheableNet Class The main class that provides cached network operations. #### Constructor Options ```typescript interface CacheableNetOptions { cache?: Cacheable | CacheableOptions; // Cacheable instance or options httpCachePolicy?: boolean; // Enable HTTP cache semantics (default: true) stringify?: (value: unknown) => string; // Custom JSON stringifier (default: JSON.stringify) parse?: (value: string) => unknown; // Custom JSON parser (default: JSON.parse) } ``` #### Methods All methods accept request options of type `FetchOptions` (excluding the `cache` property which is managed internally): - **fetch(url: string, options?: FetchOptions)**: Fetch with caching support - **get(url: string, options?: NetFetchOptions)**: GET request helper with caching control - **post(url: string, data?: unknown, options?: NetFetchOptions)**: POST request helper with caching control - **put(url: string, data?: unknown, options?: NetFetchOptions)**: PUT request helper with caching control - **patch(url: string, data?: unknown, options?: NetFetchOptions)**: PATCH request helper with caching control - **delete(url: string, data?: unknown, options?: NetFetchOptions)**: DELETE request helper with caching control - **head(url: string, options?: NetFetchOptions)**: HEAD request helper with caching control The `FetchOptions` type extends the standard fetch `RequestInit` options with additional caching controls: ```typescript type FetchOptions = Omit & { cache?: Cacheable; // Optional cache instance (if not provided, no caching) httpCachePolicy?: boolean; // Override instance-level HTTP cache setting }; ``` The `NetFetchOptions` type (used by all HTTP method helpers) provides additional control: ```typescript type NetFetchOptions = { caching?: boolean; // Enable/disable caching for this request stringify?: (value: unknown) => string; // Custom JSON stringifier parse?: (value: string) => unknown; // Custom JSON parser } & Omit; ``` **Note**: When using the CacheableNet methods, you don't need to provide the `cache` property as it's automatically injected from the instance. #### Caching Behavior By default: - **GET** and **HEAD** requests are cached automatically - **POST**, **PUT**, **PATCH**, and **DELETE** requests are NOT cached by default - To enable caching for POST/PUT/PATCH/DELETE, set `caching: true` in the options - To disable caching for GET/HEAD, set `caching: false` in the options # How to Contribute You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`. # License and Copyright [MIT © Jared Wray](./LICENSE) ### @cacheable/node-cache URL: https://cacheable.org/docs/node-cache/ [Cacheable](https://github.com/jaredwray/cacheable) # Node-Cache > Simple and Maintained fast Node.js caching [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/@cacheable/node-cache.svg)](https://www.npmjs.com/package/@cacheable/node-cache) [![npm](https://img.shields.io/npm/v/@cacheable/node-cache)](https://www.npmjs.com/package/@cacheable/node-cache) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) `@cacheable/node-cache` is compatible with the [node-cache](https://www.npmjs.com/package/node-cache) package with regular maintenance and additional functionality (async/await and storage adapters) via `{NodeCacheStore}`. The only thing not implemented is the `enableLegacyCallbacks` option and functions. If you need them we are happy to take a PR to add them. * Fully Compatible with `node-cache` using `{NodeCache}` * Faster than the original `node-cache` package 🚀 * Storage Adapters via [Keyv](https://keyv.org) * Async/Await functionality with `{NodeCacheStore}` * Lightweight - uses `@cacheable/utils` for utilities * Maintained and Updated Regularly! 🎉 # Table of Contents * [Getting Started](#getting-started) * [Basic Usage](#basic-usage) * [NodeCache Performance](#nodecache-performance) * [NodeCache API](#nodecache-api) * [NodeCacheStore](#nodecachestore) * [NodeCacheStore API](#nodecachestore-api) * [Migrating to v2](#migrating-to-v2) * [Migrating to v3](#migrating-to-v3) * [How to Contribute](#how-to-contribute) * [License and Copyright](#license-and-copyright) # Getting Started ```bash npm install @cacheable/node-cache --save ``` # Basic Usage ```javascript import NodeCache from '@cacheable/node-cache'; const cache = new NodeCache(); cache.set('foo', 'bar'); cache.get('foo'); // 'bar' cache.set('foo', 'bar', 10); // 10 seconds cache.del('foo'); // 1 cache.set('bar', 'baz', '35m'); // 35 minutes using shorthand ``` The `NodeCache` is not the default export, so you need to import it like this: ```javascript import {NodeCache} from '@cacheable/node-cache'; const cache = new NodeCache(); cache.set('foo', 'bar'); cache.get('foo'); // 'bar' ``` `NodeCache` also offers the ability to set the type of values that can be cached in Typescript environments. ```typescript import {NodeCache} from '@cacheable/node-cache'; const cache = new NodeCache(); cache.set('foo', 'bar'); cache.get('foo'); // 'bar' ``` # NodeCache Performance The performance is comparable if not faster to the original `node-cache` package, but with additional features and improvements. | name | summary | ops/sec | time/op | margin | samples | |-----------------------------------|:---------:|----------:|----------:|:--------:|----------:| | Cacheable NodeCache - set / get | 🥇 | 117K | 9µs | ±1.01% | 111K | | Node Cache - set / get | -4.6% | 112K | 9µs | ±1.31% | 106K | # NodeCache API ## `constructor(options?: NodeCacheOptions)` Create a new cache instance. You can pass in options to set the configuration: ```javascript export type NodeCacheOptions = { stdTTL?: number | string; checkperiod?: number; useClones?: boolean; deleteOnExpire?: boolean; maxKeys?: number; }; ``` Here is a description of the options: | Option | Default Setting | Description | |--------|----------------|-------------| | `stdTTL` | `0` | The standard time to live (TTL) in seconds for every generated cache element. If set to `0`, it means unlimited. If a string is provided, it will be parsed as shorthand and default to milliseconds if it is a number as a string. | | `checkperiod` | `600` | The interval in seconds to check for expired keys. If set to `0`, it means no periodic check will be performed. | | `useClones` | `true` | If set to `true`, the cache will clone the returned items via `get()` functions. This means that every time you set a value into the cache, `node-cache` makes a deep clone of it. When you get that value back, you receive another deep clone. This mimics the behavior of an external cache like Redis or Memcached, meaning mutations to the returned object do not affect the cached copy (and vice versa). If set to `false`, the original object will be returned, and mutations will affect the cached copy. | | `deleteOnExpire` | `true` | If set to `true`, the key will be deleted when it expires. If set to `false`, the key will remain in the cache, but the value returned by `get()` will be `undefined`. You can manage the key with the `on('expired')` event. | | `maxKeys` | `-1` | If set to a positive number, it will limit the number of keys in the cache. If the number of keys exceeds this limit, it will throw an error when trying to set more keys than the maximum. If set to `-1`, it means unlimited keys are allowed. | When initializing the cache you can pass in the options to set the configuration like the example below where we set the `stdTTL` to 10 seconds and `checkperiod` to 5 seconds.: ```javascript const cache = new NodeCache({stdTTL: 10, checkperiod: 5}); ``` When setting `deleteOnExpire` to `true` it will delete the key when it expires. If you set it to `false` it will keep the key but the value on `get()` will be `undefined`. You can manage the key with `on('expired')` event. ```javascript const cache = new NodeCache({deleteOnExpire: false}); cache.on('expired', (key, value) => { console.log(`Key ${key} has expired with value ${value}`); }); ``` ## `set(key: string | number, value: any, ttl?: number | string): boolean` Set a key value pair with an optional ttl (in seconds). Will return true on success. If the ttl is not set it will default to 0 (no ttl). ```javascript cache.set('foo', 'bar', 10); // true ``` ## `mset(data: Array): boolean` Set multiple key value pairs at once. This will take an array of objects with the key, value, and optional ttl. ```javascript cache.mset([{key: 'foo', value: 'bar', ttl: 10}, {key: 'bar', value: 'baz'}]); // true ``` the `PartialNodeCacheItem` is defined as: ```javascript export type PartialNodeCacheItem = { key: string | number; value: any; ttl?: number; }; ``` ## `get(key: string | number): T | undefined` Get a value from the cache by key. If the key does not exist it will return `undefined`. ```javascript cache.get('foo'); // 'bar' ``` ## `mget(keys: Array): Record` Get multiple values from the cache by keys. This will return an object with the keys and values. ```javascript const obj = { my: 'value', my2: 'value2' }; const obj2 = { special: 'value3', life: 'value4' }; cache.set('my', obj); cache.set('my2', obj2); cache.mget(['my', 'my2']); // { my: { my: 'value', my2: 'value2' }, my2: { special: 'value3', life: 'value4' } } ``` ## `take(key: string | number): T | undefined` Get a value from the cache by key and delete it. If the key does not exist it will return `undefined`. ```javascript cache.set('foo', 'bar'); cache.take('foo'); // 'bar' cache.get('foo'); // undefined ``` ## `del(key: string | number | Array): number` Delete a key from the cache. Will return the number of deleted entries and never fail. You can also pass in an array of keys to delete multiple keys. All examples assume that you have initialized the cache like `const cache = new NodeCache();`. ```javascript cache.del('foo'); // 1 ``` passing in an array of keys: ```javascript cache.del(['foo', 'bar']); // 2 ``` ## `mdel(keys: Array): number` Delete multiple keys from the cache. Will return the number of deleted entries and never fail. ```javascript cache.mdel(['foo', 'bar']); // 2 ``` ## `ttl(key: string | number, ttl?: number | string): boolean` Redefine the ttl of a key. Returns true if the key has been found and changed. Otherwise returns false. If the ttl-argument isn't passed the default-TTL will be used. ```javascript cache.ttl('foo', 10); // true ``` ## `getTtl(key: string | number): number | undefined` Get the ttl expiration from `Date.now()` of a key. If the key does not exist it will return `undefined`. ```javascript cache.getTtl('foo'); // 1725993344859 ``` ## `has(key: string | number): boolean` Check if a key exists in the cache. ```javascript cache.set('foo', 'bar'); cache.has('foo'); // true ``` ## `keys(): string[]` Get all keys from the cache. ```javascript cache.keys(); // ['foo', 'bar'] ``` ## `getStats(): NodeCacheStats` Get the stats of the cache. ```javascript cache.getStats(); // {hits: 1, misses: 1, keys: 1, ksize: 2, vsize: 3} ``` ## `flushAll(): void` Flush the cache. Will remove all keys and reset the stats. ```javascript cache.flushAll(); cache.keys(); // [] cache.getStats(); // {hits: 0, misses: 0, keys: 0, ksize: 0, vsize: 0} ``` ## `flushStats(): void` Flush the stats. Will reset the stats but keep the keys. ```javascript cache.set('foo', 'bar'); cache.flushStats(); cache.getStats(); // {hits: 0, misses: 0, keys: 0, ksize: 0, vsize: 0} cache.keys(); // ['foo'] ``` ## `on(event: string, callback: Function): void` Listen to events. Here are the events that you can listen to: * `set` - when a key is set and it will pass in the `key` and `value`. * `expired` - when a key is expired and it will pass in the `key` and `value`. * `flush` - when the cache is flushed * `flush_stats` - when the stats are flushed * `del` - when a key is deleted and it will pass in the `key` and `value`. ```javascript cache.on('set', (key, value) => { console.log(`Key ${key} has been set with value ${value}`); }); ``` ## `close(): void` Close the cache. This will stop the interval timeout which is set on the `checkperiod` option. ```javascript cache.close(); ``` ## `store: Map>` The internal store is a public readonly `Map` that holds all cached items. Each item includes the key, value, and TTL expiration timestamp. ## `getIntervalId(): number | NodeJS.Timeout` Get the interval ID for the expiration checker. ## `startInterval(): void` Start the interval for checking expired keys based on the `checkperiod` option. ## `stopInterval(): void` Stop the interval for checking expired keys. # NodeCacheStore `NodeCacheStore` has a similar API to `NodeCache` but it is using `async / await` as it uses [Keyv](https://keyv.org) under the hood. This means that you can use any storage adapter that is available in `Keyv` and it will work seamlessly with the `NodeCacheStore`. To learn more about the `Keyv` storage adapters you can check out the [Keyv documentation](https://keyv.org). ```javascript import {NodeCacheStore} from '@cacheable/node-cache'; const cache = new NodeCacheStore(); await cache.set('foo', 'bar'); await cache.get('foo'); // 'bar' ``` Here is an example of how to use the `NodeCacheStore` with a Redis storage adapter: ```javascript import {NodeCacheStore} from '@cacheable/node-cache'; import Keyv from 'keyv'; import KeyvRedis from '@keyv/redis'; const keyv = new Keyv({store: new KeyvRedis('redis://user:pass@localhost:6379')}); const cache = new NodeCacheStore({store: keyv}); // with storage you have the same functionality as the NodeCache but will be using async/await await cache.set('foo', 'bar'); await cache.get('foo'); // 'bar' ``` When initializing the cache you can pass in the options below: ```javascript export type NodeCacheStoreOptions = { ttl?: number | string; // The standard ttl as number in milliseconds. 0 = unlimited. Supports shorthand like '1h'. store?: Keyv; // The storage adapter (defaults to in-memory Keyv) useClones?: boolean; // Clone values on get/set via structuredClone. Default: false checkperiod?: number; // Interval in seconds to check for expired items. 0 = disabled. Default: 0 deleteOnExpire?: boolean; // Delete expired items when detected. Default: true }; ``` | Option | Default | Description | |--------|---------|-------------| | `ttl` | `undefined` | The standard TTL in milliseconds for every cache element. `undefined` or `0` = unlimited. Supports shorthand strings like `'1h'`, `'30m'`, `'5s'`. | | `store` | `new Keyv()` | The Keyv storage adapter. Pass any Keyv-compatible store (Redis, MongoDB, etc.). | | `useClones` | `false` | If `true`, values are deep-cloned via `structuredClone` on both `set()` and `get()`. This prevents mutations to the returned object from affecting the cached copy and vice versa. | | `checkperiod` | `0` | The interval in seconds to check for expired items. `0` = disabled (expiration is checked lazily on access). | | `deleteOnExpire` | `true` | If `true`, expired keys are automatically deleted. If `false`, expired keys remain in the store but `get()` returns `undefined` and `has()` returns `false`. You can handle them via the `expired` event. | Note: the `ttl` is now in milliseconds and not seconds like `stdTTL` in `NodeCache`. You can also use shorthand notation for TTL values. Here is an example: ```javascript const cache = new NodeCacheStore({ttl: 60000 }); // 1 minute as it defaults to milliseconds await cache.set('foo', 'bar', '1h'); // 1 hour await cache.set('longfoo', 'bar', '1d'); // 1 day ``` ## NodeCacheStore API ### `set(key, value, ttl?): Promise` Set a key value pair with an optional ttl (in milliseconds or shorthand string). Returns `true` on success. ```javascript await cache.set('foo', 'bar'); await cache.set('foo', 'bar', 5000); // 5 seconds in milliseconds await cache.set('foo', 'bar', '1h'); // 1 hour using shorthand ``` ### `mset(data): Promise` Set multiple key value pairs at once. ```javascript await cache.mset([{key: 'foo', value: 'bar'}, {key: 'baz', value: 'qux', ttl: 5000}]); ``` ### `get(key): Promise` Get a value from the cache by key. Returns `undefined` if the key does not exist or has expired. ```javascript await cache.get('foo'); // 'bar' ``` ### `mget(keys): Promise>` Get multiple values from the cache by keys. ```javascript await cache.mget(['foo', 'bar']); // { foo: 'value1', bar: 'value2' } ``` ### `take(key): Promise` Get a value from the cache by key and delete it. Useful for single-use values like OTPs. ```javascript await cache.take('foo'); // 'bar' await cache.get('foo'); // undefined ``` ### `del(key): Promise` Delete a key from the cache. Returns `true` if the key was deleted. ```javascript await cache.del('foo'); // true ``` ### `mdel(keys): Promise` Delete multiple keys from the cache. ```javascript await cache.mdel(['foo', 'bar']); // true ``` ### `has(key): Promise` Check if a key exists in the cache and is not expired. ```javascript await cache.set('foo', 'bar'); await cache.has('foo'); // true await cache.has('missing'); // false ``` ### `keys(): Promise` Get all keys from the cache. ```javascript await cache.keys(); // ['foo', 'bar'] ``` ### `getTtl(key): Promise` Get the TTL expiration timestamp of a key. Returns `0` if the key has no TTL (unlimited), `undefined` if the key does not exist, or a timestamp in milliseconds of when the key will expire. ```javascript await cache.set('foo', 'bar', 5000); await cache.getTtl('foo'); // 1725993344859 (timestamp) await cache.set('bar', 'baz'); await cache.getTtl('bar'); // 0 (unlimited) await cache.getTtl('missing'); // undefined ``` ### `setTtl(key, ttl?): Promise` Set the TTL of an existing key. Returns `true` if the key was found and updated. ```javascript await cache.setTtl('foo', 10000); // true ``` ### `clear(): Promise` Clear the cache. Removes all keys and resets store-related stats (keys, ksize, vsize) but preserves hit/miss counts. ```javascript await cache.clear(); ``` ### `flushAll(): Promise` Flush the entire cache. Removes all keys and resets all stats. Emits a `flush` event. ```javascript await cache.flushAll(); await cache.keys(); // [] cache.getStats(); // {hits: 0, misses: 0, keys: 0, ksize: 0, vsize: 0} ``` ### `getStats(): NodeCacheStats` Get the stats of the cache. ```javascript cache.getStats(); // {hits: 1, misses: 0, keys: 2, ksize: 12, vsize: 24} ``` ### `flushStats(): void` Flush the stats. Resets all stats but keeps the cached data. ### `close(): void` Stop the check interval timer. Use this to clean up when you're done with the cache. ```javascript cache.close(); ``` ### `disconnect(): Promise` Disconnect the storage adapter and stop the check interval. ```javascript await cache.disconnect(); ``` ### Events Listen to events using `on()`: ```javascript cache.on('set', (key, value) => { console.log(`Key ${key} has been set with value ${value}`); }); cache.on('del', (key, value) => { console.log(`Key ${key} has been deleted`); }); cache.on('expired', (key, value) => { console.log(`Key ${key} has expired`); }); cache.on('flush', () => { console.log('Cache has been flushed'); }); cache.on('flush_stats', () => { console.log('Stats have been flushed'); }); ``` | Event | Arguments | Description | |-------|-----------|-------------| | `set` | `key, value` | Emitted when a key is set | | `del` | `key, value` | Emitted when a key is deleted (including via `take()` and `mdel()`) | | `expired` | `key, value` | Emitted when an expired key is detected | | `flush` | — | Emitted when `flushAll()` is called | | `flush_stats` | — | Emitted when `flushStats()` is called | ### Properties * `ttl`: `number | string | undefined` - The standard ttl for every generated cache element. `undefined` = unlimited * `store`: `Keyv` - The storage adapter (read-only) * `useClones`: `boolean` - Whether values are cloned on get/set * `deleteOnExpire`: `boolean` - Whether expired items are automatically deleted # Migrating to v2 The main `NodeCache` class API has not changed and remains fully compatible. The primary internal change is that it now uses Keyv as the underlying store. ## NodeCacheStore Changes ### Removed `cache` Property - **V1**: `nodeCache.cache` returned a `Cacheable` instance - **V2**: Use `nodeCache.store` which returns a `Keyv` instance ### Removed Storage Tiering (primary/secondary) - **V1**: Supported `primary` and `secondary` store options for multi-tier caching - **V2**: Uses single `store` option only **Migration:** ```javascript // V1 const cache = new NodeCacheStore({ primary: keyv1, secondary: keyv2 }); // V2 - use single store const cache = new NodeCacheStore({ store: keyv }); ``` If you need storage tiering functionality, use the `cacheable` package instead which supports primary and secondary stores. ### Internal Dependency Change - V2 uses `@cacheable/utils` instead of the `cacheable` package for a lighter footprint # Migrating to v3 ## Removed `maxKeys` from NodeCacheStore The `maxKeys` option has been removed from `NodeCacheStore`. It does not make sense for a store backed by external services (Redis, MongoDB, etc.) where the backend manages its own capacity. The `maxKeys` option remains available on the in-memory `NodeCache` class. **Migration:** ```javascript // V2 - maxKeys was accepted but not meaningful for external stores const cache = new NodeCacheStore({ maxKeys: 100 }); // V3 - remove maxKeys from NodeCacheStore options const cache = new NodeCacheStore(); ``` If you need key limits with an external store, configure the limit at the storage layer instead. ## Removed `stats` from NodeCacheStore The `stats` option and internal stats tracking have been removed from `NodeCacheStore`. The stats were collected internally but never exposed via a public API, making them effectively unused. ## Upgraded `hookified` to v2 The underlying `hookified` dependency has been upgraded from v1 to v2. Both `NodeCache` and `NodeCacheStore` extend `Hookified`. Key changes in hookified v2: - `logger` property renamed to `eventLogger` - `Hook` type renamed to `HookFn` - `onHook` signature changed to handle `IHook` interface - Removed `throwHookErrors` configuration option - `throwOnEmptyListeners` default changed to `true` If you use hooks or advanced event features from the `Hookified` base class directly, review the [hookified v2 changelog](https://github.com/jaredwray/hookified) for details. # How to Contribute You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`. # License and Copyright [MIT © Jared Wray](./LICENSE) ### @cacheable/utils URL: https://cacheable.org/docs/utils/ [Cacheable](https://github.com/jaredwray/cacheable) > Cacheble Utils [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable) [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml) [![npm](https://img.shields.io/npm/dm/@cacheable/utils.svg)](https://www.npmjs.com/package/@cacheable/utils) [![npm](https://img.shields.io/npm/v/@cacheable/utils)](https://www.npmjs.com/package/@cacheable/utils) [![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE) `@cacheable/utils` is a collecton of utility functions, helpers, and types for `cacheable` and other caching libraries. It provides a robust set of features to enhance caching capabilities, including: * Data Types for Caching Items * Hash Functions for Key Generation * Coalesce Async for Handling Multiple Promises * Stats Helpers for Caching Statistics * Sleep / Delay for Testing and Timing * Memoization for wraping or get / set options * Time to Live (TTL) Helpers # Table of Contents * [Getting Started](#getting-started) * [Cacheable Types](#cacheable-types) * [Coalesce Async](#coalesce-async) * [Hash Functions](#hash-functions) * [Shorthand Time Helpers](#shorthand-time-helpers) * [Sleep Helper](#sleep-helper) * [Stats Helpers](#stats-helpers) * [Time to Live (TTL) Helpers](#time-to-live-ttl-helpers) * [Run if Function Helper](#run-if-function-helper) * [Less Than Helper](#less-than-helper) * [Is Object Helper](#is-object-helper) * [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions) * [Get Or Set Memoization Function](#get-or-set-memoization-function) * [How to Contribute](#how-to-contribute) * [License and Copyright](#license-and-copyright) # Getting Started ```bash npm install @cacheable/utils --save ``` # Cacheable Types The `@cacheable/utils` package provides various types that are used throughout the caching library. These types help in defining the structure of cached items, ensuring type safety and consistency across your caching operations. ```typescript /** * CacheableItem * @typedef {Object} CacheableItem * @property {string} key - The key of the cacheable item * @property {any} value - The value of the cacheable item * @property {number|string} [ttl] - Time to Live - 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 it will use the default time-to-live. If both are * undefined then it will not have a time-to-live. */ export type CacheableItem = { key: string; value: any; ttl?: number | string; }; /** * CacheableStoreItem * @typedef {Object} CacheableStoreItem * @property {string} key - The key of the cacheable store item * @property {any} value - The value of the cacheable store item * @property {number} [expires] - The expiration time in milliseconds since epoch. If not set, the item does not expire. */ export type CacheableStoreItem = { key: string; value: any; expires?: number; }; ``` # Coalesce Async The `coalesceAsync` function is a utility that allows you to handle multiple asynchronous operations efficiently. It was designed by `Douglas Cayers` https://github.com/douglascayers/promise-coalesce. It helps in coalescing multiple promises into a single promise, ensuring that only one operation is executed at a time for the same key. ```typescript import { coalesceAsync } from '@cacheable/utils'; const fetchData = async (key: string) => { // Simulate an asynchronous operation return new Promise((resolve) => setTimeout(() => resolve(`Data for ${key}`), 1000)); }; const result = await Promise.all([ coalesceAsync('my-key', fetchData), coalesceAsync('my-key', fetchData), coalesceAsync('my-key', fetchData), ]); console.log(result); // Data for my-key only executed once ``` # Hash Functions The `@cacheable/utils` package provides hash functions that can be used to generate unique keys for caching operations. These functions are useful for creating consistent and unique identifiers for cached items. The hashing API provides both **async** (for cryptographic algorithms) and **sync** (for non-cryptographic algorithms) methods. ## Async Hashing (Cryptographic Algorithms) Use `hash()` and `hashToNumber()` for cryptographic algorithms like SHA-256, SHA-384, and SHA-512: ```typescript import { hash, hashToNumber, HashAlgorithm } from '@cacheable/utils'; // Hash using SHA-256 (default) const key = await hash('my-cache-key'); console.log(key); // Unique hash for 'my-cache-key' // Hash with specific algorithm const sha512Hash = await hash('my-data', { algorithm: HashAlgorithm.SHA512 }); // Convert hash to number within range const min = 0; const max = 10; const result = await hashToNumber({foo: 'bar'}, { min, max, algorithm: HashAlgorithm.SHA256 }); console.log(result); // A number between 0 and 10 based on the hash value ``` ## Sync Hashing (Non-Cryptographic Algorithms) Use `hashSync()` and `hashToNumberSync()` for faster, non-cryptographic algorithms like DJB2, FNV1, MURMER, and CRC32: ```typescript import { hashSync, hashToNumberSync, HashAlgorithm } from '@cacheable/utils'; // Hash using DJB2 (default for sync) const key = hashSync('my-cache-key'); console.log(key); // Unique hash for 'my-cache-key' // Hash with specific algorithm const fnv1Hash = hashSync('my-data', { algorithm: HashAlgorithm.FNV1 }); // Convert hash to number within range const min = 0; const max = 10; const result = hashToNumberSync({foo: 'bar'}, { min, max, algorithm: HashAlgorithm.DJB2 }); console.log(result); // A number between 0 and 10 based on the hash value ``` ## Available Hash Algorithms **Cryptographic (Async):** - `HashAlgorithm.SHA256` - SHA-256 (default for async methods) - `HashAlgorithm.SHA384` - SHA-384 - `HashAlgorithm.SHA512` - SHA-512 **Non-Cryptographic (Sync):** - `HashAlgorithm.DJB2` - DJB2 (default for sync methods) - `HashAlgorithm.FNV1` - FNV-1 - `HashAlgorithm.MURMER` - Murmur hash - `HashAlgorithm.CRC32` - CRC32 # Shorthand Time Helpers The `@cacheable/utils` package provides a shorthand function to convert human-readable time strings into milliseconds. This is useful for setting time-to-live (TTL) values in caching operations. You can also use the `shorthandToMilliseconds` function: ```typescript import { shorthandToMilliseconds } from '@cacheable/utils'; const milliseconds = shorthandToMilliseconds('1h'); console.log(milliseconds); // 3600000 ``` You can also use the `shorthandToTime` function to get the current date plus the shorthand time: ```typescript import { shorthandToTime } from '@cacheable/utils'; const currentDate = new Date(); const timeInMs = shorthandToTime('1h', currentDate); console.log(timeInMs); // Current date + 1 hour in milliseconds since epoch ``` # Sleep Helper The `sleep` function is a utility that allows you to pause execution for a specified duration. This can be useful in testing scenarios or when you need to introduce delays in your code. ```typescript import { sleep } from '@cacheable/utils'; await sleep(1000); // Pause for 1 second console.log('Execution resumed after 1 second'); ``` # Stats Helpers The `@cacheable/utils` package provides statistics helpers that can be used to track and analyze caching operations. These helpers can be used to gather metrics such as hit rates, miss rates, and other performance-related statistics. ```typescript import { stats } from '@cacheable/utils'; const cacheStats = stats(); cacheStats.incrementHits(); console.log(cacheStats.hits); // Get the hit rate of the cache ``` # Time to Live (TTL) Helpers The `@cacheable/utils` package provides helpers for managing time-to-live (TTL) values for cached items. You can use the `calculateTtlFromExpiration` function to calculate the TTL based on an expiration date: ```typescript import { calculateTtlFromExpiration } from '@cacheable/utils'; const expirationDate = new Date(Date.now() + 1000 * 60 * 5); // 5 minutes from now const ttl = calculateTtlFromExpiration(Date.now(), expirationDate); console.log(ttl); // 300000 ``` You can also use `getTtlFromExpires` to get the TTL from an expiration date: ```typescript import { getTtlFromExpires } from '@cacheable/utils'; const expirationDate = new Date(Date.now() + 1000 * 60 * 5); // 5 minutes from now const ttl = getTtlFromExpires(expirationDate); console.log(ttl); // 300000 ``` You can use `getCascadingTtl` to get the TTL for cascading cache operations: ```typescript import { getCascadingTtl } from '@cacheable/utils'; const cacheableTtl = 1000 * 60 * 5; // 5 minutes const primaryTtl = 1000 * 60 * 2; // 2 minutes const secondaryTtl = 1000 * 60; // 1 minute const ttl = getCascadingTtl(cacheableTtl, primaryTtl, secondaryTtl); ``` # Run if Function Helper The `runIfFn` utility function provides a convenient way to conditionally execute functions or return values based on whether the input is a function or not. This pattern is commonly used in UI libraries and configuration systems where values can be either static or computed. ```typescript import { runIfFn } from '@cacheable/utils'; // Static value - returns the value as-is const staticValue = runIfFn('hello world'); console.log(staticValue); // 'hello world' // Function with no arguments - executes the function const dynamicValue = runIfFn(() => new Date().toISOString()); console.log(dynamicValue); // Current timestamp // Function with arguments - executes with provided arguments const sum = runIfFn((a: number, b: number) => a + b, 5, 10); console.log(sum); // 15 // Complex example with conditional logic const getConfig = (isDevelopment: boolean) => ({ apiUrl: isDevelopment ? 'http://localhost:3000' : 'https://api.example.com', timeout: isDevelopment ? 5000 : 30000 }); const config = runIfFn(getConfig, true); console.log(config); // { apiUrl: 'http://localhost:3000', timeout: 5000 } ``` # Less Than Helper The `lessThan` utility function provides a safe way to compare two values and determine if the first value is less than the second. It only performs the comparison if both values are valid numbers, returning `false` for any non-number inputs. ```typescript import { lessThan } from '@cacheable/utils'; // Basic number comparisons console.log(lessThan(1, 2)); // true console.log(lessThan(2, 1)); // false console.log(lessThan(1, 1)); // false // Works with negative numbers console.log(lessThan(-1, 0)); // true console.log(lessThan(-2, -1)); // true // Works with decimal numbers console.log(lessThan(1.5, 2.5)); // true console.log(lessThan(2.7, 2.7)); // false // Safe handling of non-number values console.log(lessThan("1", 2)); // false console.log(lessThan(1, "2")); // false console.log(lessThan(null, 1)); // false console.log(lessThan(undefined, 1)); // false console.log(lessThan(NaN, 1)); // false // Useful in filtering and sorting operations const numbers = [5, 2, 8, 1, 9]; const lessThanFive = numbers.filter(n => lessThan(n, 5)); console.log(lessThanFive); // [2, 1] // Safe comparison in conditional logic function processValue(a?: number, b?: number) { if (lessThan(a, b)) { return `${a} is less than ${b}`; } return 'Invalid comparison or a >= b'; } ``` This utility is particularly useful when dealing with potentially undefined or invalid numeric values, ensuring type safety in comparison operations. # Is Object Helper The `isObject` utility function provides a type-safe way to determine if a value is a plain object. It returns `true` for objects but `false` for arrays, `null`, functions, and primitive types. This function also serves as a TypeScript type guard. ```typescript import { isObject } from '@cacheable/utils'; // Basic object detection console.log(isObject({})); // true console.log(isObject({ name: 'John', age: 30 })); // true console.log(isObject(Object.create(null))); // true // Arrays are not considered objects console.log(isObject([])); // false console.log(isObject([1, 2, 3])); // false // null is not considered an object (despite typeof null === 'object') console.log(isObject(null)); // false // Primitive types return false console.log(isObject('string')); // false console.log(isObject(123)); // false console.log(isObject(true)); // false console.log(isObject(undefined)); // false // Functions return false console.log(isObject(() => {})); // false console.log(isObject(Date)); // false // Built-in object types return true console.log(isObject(new Date())); // true console.log(isObject(/regex/)); // true console.log(isObject(new Error('test'))); // true console.log(isObject(new Map())); // true // TypeScript type guard usage function processValue(value: unknown) { if (isObject<{ name: string; age: number }>(value)) { // TypeScript now knows value is an object with name and age properties console.log(`Name: ${value.name}, Age: ${value.age}`); } } // Useful for configuration validation function validateConfig(config: unknown) { if (!isObject(config)) { throw new Error('Configuration must be an object'); } // Safe to access object properties return config; } // Filtering arrays for objects only const mixedArray = [1, 'string', {}, [], null, { valid: true }]; const objectsOnly = mixedArray.filter(isObject); console.log(objectsOnly); // [{}', { valid: true }] ``` This utility is particularly useful for: - **Type validation** - Ensuring values are objects before accessing properties - **TypeScript type guarding** - Narrowing types in conditional blocks - **Configuration parsing** - Validating that configuration values are objects - **Data filtering** - Separating objects from other data types # Wrap / Memoization for Sync and Async Functions The `@cacheable/utils` package provides two main functions: `wrap` and `wrapSync`. These functions are used to memoize asynchronous and synchronous functions, respectively. ```javascript import { Cacheable } from 'cacheable'; const asyncFunction = async (value: number) => { return Math.random() * value; }; const cache = new Cacheable(); const options = { ttl: '1h', // 1 hour keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix. cache, } const wrappedFunction = wrap(asyncFunction, options); console.log(await wrappedFunction(2)); // 4 console.log(await wrappedFunction(2)); // 4 from cache ``` With `wrap` we have also included stampede protection so that a `Promise` based call will only be called once if multiple requests of the same are executed at the same time. Here is an example of how to test for stampede protection: ```javascript import { Cacheable } from 'cacheable'; const asyncFunction = async (value: number) => { return value; }; const cache = new Cacheable(); const options = { ttl: '1h', // 1 hour keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix. cache, } const wrappedFunction = wrap(asyncFunction, options); const promises = []; for (let i = 0; i < 10; i++) { promises.push(wrappedFunction(i)); } const results = await Promise.all(promises); // all results should be the same console.log(results); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ``` In this example we are wrapping an `async` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also wrap a `sync` function in a cache: ```javascript import { CacheableMemory } from 'cacheable'; const syncFunction = (value: number) => { return value * 2; }; const cache = new CacheableMemory(); const wrappedFunction = wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cache }); console.log(wrappedFunction(2)); // 4 console.log(wrappedFunction(2)); // 4 from cache ``` In this example we are wrapping a `sync` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also set the `key` property in the `wrap()` options to set a custom key for the cache. When an error occurs in the function it will not cache the value and will return the error. This is useful if you want to cache the results of a function but not cache the error. If you want it to cache the error you can set the `cacheError` property to `true` in the `wrap()` options. This is disabled by default. ```javascript import { CacheableMemory } from 'cacheable'; const syncFunction = (value: number) => { throw new Error('error'); }; const cache = new CacheableMemory(); const wrappedFunction = wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cacheError: true, cache }); console.log(wrappedFunction()); // error console.log(wrappedFunction()); // error from cache ``` If you would like to generate your own key for the wrapped function you can set the `createKey` property in the `wrap()` options. This is useful if you want to generate a key based on the arguments of the function or any other criteria. ```javascript const cache = new Cacheable(); const options: WrapOptions = { cache, keyPrefix: 'test', createKey: (function_, arguments_, options: WrapOptions) => `customKey:${options?.keyPrefix}:${arguments_[0]}`, }; const wrapped = wrap((argument: string) => `Result for ${argument}`, options); const result1 = await wrapped('arg1'); const result2 = await wrapped('arg1'); // Should hit the cache console.log(result1); // Result for arg1 console.log(result2); // Result for arg1 (from cache) ``` We will pass in the `function` that is being wrapped, the `arguments` passed to the function, and the `options` used to wrap the function. You can then use these to generate a custom key for the cache. # Get Or Set Memoization Function 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. Here are the options: ```typescript export type GetOrSetFunctionOptions = { ttl?: number | string; cacheErrors?: boolean; throwErrors?: boolean; nonBlocking?: boolean; }; ``` The `nonBlocking` option allows you to override the instance-level `nonBlocking` setting for the `get` call within `getOrSet`. When set to `false`, the `get` will block and wait for a response from the secondary store before deciding whether to call the provided function. When set to `true`, the primary store returns immediately and syncs from secondary in the background. Here is an example of how to use the `getOrSet` method: ```javascript import { Cacheable } from 'cacheable'; const cache = new Cacheable(); // Use getOrSet to fetch user data const function_ = async () => Math.random() * 100; const value = await getOrSet('randomValue', function_, { ttl: '1h', cache }); console.log(value); // e.g. 42.123456789 ``` You can also use a function to compute the key for the function: ```javascript import { Cacheable, GetOrSetOptions } from 'cacheable'; const cache = new Cacheable(); // Function to generate a key based on options const generateKey = (options?: GetOrSetOptions) => { return `custom_key_:${options?.cacheId || 'default'}`; }; const function_ = async () => Math.random() * 100; const value = await getOrSet(generateKey(), function_, { ttl: '1h', cache }); ``` # How to Contribute You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`. # License and Copyright [MIT © Jared Wray](./LICENSE) ## API Reference URL: https://cacheable.org/api - Not available. ## Changelog URL: https://cacheable.org/changelog ### 2026-05-27 URL: https://cacheable.org/changelog/2026-05-27 Date: May 27, 2026 Tag: Release Releasing 8 packages: @cacheable/memory@2.1.0 (minor), @cacheable/node-cache@3.1.0 (minor), cacheable@2.4.0 (minor), cache-manager@7.2.9 (patch), file-entry-cache@11.1.4 (patch), flat-cache@6.1.23 (patch), @cacheable/net@2.0.9 (patch), @cacheable/utils@2.4.2 (patch). ## @cacheable/memory@2.1.0 — 2026-05-27 Add lifecycle hooks and maxTtl cap to CacheableMemory. ### Features - add hooks for all cache operations via `CacheableMemoryHooks` enum (1ff149d, #1644) ```ts import { CacheableMemory, CacheableMemoryHooks } from '@cacheable/memory'; const cache = new CacheableMemory(); cache.onHookSync(CacheableMemoryHooks.BEFORE_SET, (data) => { data.value = transform(data.value); // mutate before write }); cache.set('key', 'value'); ``` - add `maxTtl` option to cap maximum time-to-live (948234a, #1645) ```ts const cache = new CacheableMemory({ ttl: '10m', maxTtl: '1h' }); cache.set('key', 'value', '2h'); // capped to 1h cache.set('key2', 'value2'); // no TTL → capped to 1h ``` ### Internal - migrate build from tsup to tsdown, pnpm 11 (1508695, #1642) ### Contributors - @jaredwray (3) ### Full List of Changes - feat(cacheable, memory): add maxTtl option to cap maximum time-to-live by @jaredwray in #1645 - feat(@cacheable/memory): add hooks like cacheable by @jaredwray in #1644 - feat: Migrate to pnpm 11 with corepack and tsdown from tsup by @jaredwray in #1642 **Full diff:** [https://github.com/jaredwray/cacheable/compare/9346f94...release/2026](https://github.com/jaredwray/cacheable/compare/9346f94...release/2026)-05-27-8-packages --- ## @cacheable/node-cache@3.1.0 — 2026-05-27 Add keys/has/getTtl/flushAll, events, useClones, checkperiod, and fix multiple stat-tracking bugs. ### Features - add `keys()` and `has()` methods for cache inspection (bf3ea48, #1643) ```ts const store = new NodeCacheStore(); await store.set('a', 1); await store.keys(); // ['a'] await store.has('a'); // true ``` - add `getTtl()` to inspect key expiration timestamps (bf3ea48, #1643) ```ts await store.set('key', 'val', 5000); const ttl = await store.getTtl('key'); // ms timestamp when key expires ``` - add `flushAll()` to clear data and reset all stats (bf3ea48, #1643) ```ts await store.flushAll(); // clears data + resets stats, emits "flush" ``` - add event emitters for `set`, `del`, `expired`, and `flush` operations (bf3ea48, #1643) ```ts store.on('set', (key, value, ttl) => { /* ... */ }); store.on('del', (key, value) => { /* ... */ }); store.on('expired', (key, value) => { /* ... */ }); store.on('flush', () => { /* ... */ }); ``` - add `useClones` option for deep-cloning via structuredClone (bf3ea48, #1643) ```ts const store = new NodeCacheStore({ useClones: true }); ``` - add `checkperiod` option for interval-based expired item detection (bf3ea48, #1643) ```ts const store = new NodeCacheStore({ checkperiod: 60 }); // check every 60s store.close(); // stop interval ``` - add `deleteOnExpire` option and `close()`/`getIntervalId()` lifecycle methods (bf3ea48, #1643) ### Bug Fixes - fix `setTtl()` treating falsy cached values (0, "", false, null) as non-existent (bf3ea48, #1643) - fix `mdel()` firing stats and events for non-existent keys (bf3ea48, #1643) - fix `startInterval()` leaking old timer when called twice (bf3ea48, #1643) - fix `set()` double-counting stats on key overwrites (bf3ea48, #1643) - fix `checkData()` swallowing unhandled promise rejections (bf3ea48, #1643) - fix `handleExpired()` stats underflow when Keyv auto-expires items (bf3ea48, #1643) - fix `checkData()` mutating Map during iteration (bf3ea48, #1643) ### Internal - migrate build from tsup to tsdown, pnpm 11 (1508695, #1642) - move store tests to use @faker-js/faker (d51b5f2, #1641) ### Contributors - @jaredwray (3) ### Full List of Changes - feat(@cacheable/node-cache): enhance NodeCacheStore with missing features by @jaredwray in #1643 - feat: Migrate to pnpm 11 with corepack and tsdown from tsup by @jaredwray in #1642 - @cacheable/node-cache: move store tests to use @faker-js/faker by @jaredwray in #1641 **Full diff:** [https://github.com/jaredwray/cacheable/compare/9346f94...release/2026](https://github.com/jaredwray/cacheable/compare/9346f94...release/2026)-05-27-8-packages --- ## cacheable@2.4.0 — 2026-05-27 Add maxTtl option to enforce an upper bound on cache entry lifetimes. ### Features - add `maxTtl` option to cap maximum time-to-live on `set()` and `setMany()` (948234a, #1645) ```ts import { Cacheable } from 'cacheable'; const cache = new Cacheable({ ttl: '10m', maxTtl: '1h' }); await cache.set('key', 'value', '2h'); // capped to 1h await cache.set('key2', 'value2'); // no TTL → capped to 1h ``` ### Internal - migrate build from tsup to tsdown, pnpm 11 (1508695, #1642) ### Contributors - @jaredwray (2) ### Full List of Changes - feat(cacheable, memory): add maxTtl option to cap maximum time-to-live by @jaredwray in #1645 - feat: Migrate to pnpm 11 with corepack and tsdown from tsup by @jaredwray in #1642 **Full diff:** [https://github.com/jaredwray/cacheable/compare/9346f94...release/2026](https://github.com/jaredwray/cacheable/compare/9346f94...release/2026)-05-27-8-packages --- ## cache-manager@7.2.9 — 2026-05-27 Build tooling migration to tsdown and pnpm 11. ### Internal - migrate build from tsup to tsdown, pnpm 11 (1508695, #1642) ### Contributors - @jaredwray (1) ### Full List of Changes - feat: Migrate to pnpm 11 with corepack and tsdown from tsup by @jaredwray in #1642 **Full diff:** [https://github.com/jaredwray/cacheable/compare/9346f94...release/2026](https://github.com/jaredwray/cacheable/compare/9346f94...release/2026)-05-27-8-packages --- ## file-entry-cache@11.1.4 — 2026-05-27 Build tooling migration to tsdown and pnpm 11. ### Internal - migrate build from tsup to tsdown, pnpm 11 (1508695, #1642) ### Contributors - @jaredwray (1) ### Full List of Changes - feat: Migrate to pnpm 11 with corepack and tsdown from tsup by @jaredwray in #1642 **Full diff:** [https://github.com/jaredwray/cacheable/compare/9346f94...release/2026](https://github.com/jaredwray/cacheable/compare/9346f94...release/2026)-05-27-8-packages --- ## flat-cache@6.1.23 — 2026-05-27 Build tooling migration to tsdown and pnpm 11. ### Internal - migrate build from tsup to tsdown, pnpm 11 (1508695, #1642) ### Contributors - @jaredwray (1) ### Full List of Changes - feat: Migrate to pnpm 11 with corepack and tsdown from tsup by @jaredwray in #1642 **Full diff:** [https://github.com/jaredwray/cacheable/compare/9346f94...release/2026](https://github.com/jaredwray/cacheable/compare/9346f94...release/2026)-05-27-8-packages --- ## @cacheable/net@2.0.9 — 2026-05-27 Build tooling migration to tsdown and pnpm 11. ### Internal - migrate build from tsup to tsdown, pnpm 11 (1508695, #1642) ### Contributors - @jaredwray (1) ### Full List of Changes - feat: Migrate to pnpm 11 with corepack and tsdown from tsup by @jaredwray in #1642 **Full diff:** [https://github.com/jaredwray/cacheable/compare/9346f94...release/2026](https://github.com/jaredwray/cacheable/compare/9346f94...release/2026)-05-27-8-packages --- ## @cacheable/utils@2.4.2 — 2026-05-27 Build tooling migration to tsdown and pnpm 11. ### Internal - migrate build from tsup to tsdown, pnpm 11 (1508695, #1642) ### Contributors - @jaredwray (1) ### Full List of Changes - feat: Migrate to pnpm 11 with corepack and tsdown from tsup by @jaredwray in #1642 **Full diff:** [https://github.com/jaredwray/cacheable/compare/9346f94...release/2026](https://github.com/jaredwray/cacheable/compare/9346f94...release/2026)-05-27-8-packages ### 2026-05-16 URL: https://cacheable.org/changelog/2026-05-16 Date: May 16, 2026 Tag: Release ## @cacheable/net@2.0.8 Fix FormData/Blob/URLSearchParams bodies by routing through the runtime's own `fetch` so the body classes share a realm with the fetch implementation. ### Bug Fixes - send FormData/Blob correctly using the runtime's own fetch (`7cbe243`, #1636) ### Contributors - @jaredwray (1) ## @cacheable/memory@2.0.9 Clarify in the README that `lruSize=0` disables LRU. ### Documentation - clarify that lruSize=0 disables LRU (`2abfb68`, #1638) ### Contributors - @jaredwray (1) **Full diff:** [https://github.com/jaredwray/cacheable/compare/2026](https://github.com/jaredwray/cacheable/compare/2026)-05-07...2026-05-16 ### 2026-05-07 URL: https://cacheable.org/changelog/2026-05-07 Date: May 7, 2026 Tag: Release ## What's Changed * node-cache - fix: prototype pollution vulnerability in mget methods by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1613](https://github.com/jaredwray/cacheable/pull/1613) * node-cache - fix: has was not removing expired keys by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1618](https://github.com/jaredwray/cacheable/pull/1618) * cacheable - fix: ttl in documentation and cascading ttl in set and setMany by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1620](https://github.com/jaredwray/cacheable/pull/1620) * cacheable - fix: upgrade lru-cache to 11.3.6 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1625](https://github.com/jaredwray/cacheable/pull/1625) * cacheable - fix: upgrade qified pair to 0.10.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1631](https://github.com/jaredwray/cacheable/pull/1631) * cacheable-request - fix: upgrade @keyv/sqlite to 4.0.8 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1626](https://github.com/jaredwray/cacheable/pull/1626) * cacheable-request - chore: upgrading sqlite3 to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1634](https://github.com/jaredwray/cacheable/pull/1634) * file-entry-cache - fix: upgrade pino to 10.3.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1627](https://github.com/jaredwray/cacheable/pull/1627) * @cacheable/benchmark - fix: upgrade tsx to 4.21.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1628](https://github.com/jaredwray/cacheable/pull/1628) * @cacheable/benchmark - fix: upgrade @faker-js/faker to 10.4.0 (breaking) by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1632](https://github.com/jaredwray/cacheable/pull/1632) * @cacheable/benchmark - fix: upgrade misc dependencies by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1630](https://github.com/jaredwray/cacheable/pull/1630) * benchmark - chore: upgrading tinybench and cleanup by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1635](https://github.com/jaredwray/cacheable/pull/1635) * mono - fix: upgrade vitest cluster to 4.1.5 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1621](https://github.com/jaredwray/cacheable/pull/1621) * mono - fix: upgrade @biomejs/biome to 2.4.14 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1622](https://github.com/jaredwray/cacheable/pull/1622) * mono - fix: upgrade @types/node to 24.12.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1623](https://github.com/jaredwray/cacheable/pull/1623) * mono - fix: upgrade wrangler to 4.87.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1624](https://github.com/jaredwray/cacheable/pull/1624) * mono - upgrading vitest, faker, biome, and types by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1614](https://github.com/jaredwray/cacheable/pull/1614) * mono - chore: upgrading wrangler to 4.81.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1615](https://github.com/jaredwray/cacheable/pull/1615) * website - chore: upgrading docula to 1.12.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1616](https://github.com/jaredwray/cacheable/pull/1616) * @cacheable/website - fix: upgrade docula to 1.14.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1629](https://github.com/jaredwray/cacheable/pull/1629) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2026](https://github.com/jaredwray/cacheable/compare/2026)-03-26...2026-05-07 ### 2026-03-26 URL: https://cacheable.org/changelog/2026-03-26 Date: March 27, 2026 Tag: Release ## What's Changed * mono - chore: moving to prepublishOnly by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1599](https://github.com/jaredwray/cacheable/pull/1599) * cacheable-request - chore: fixing tests with parse deprecation by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1600](https://github.com/jaredwray/cacheable/pull/1600) * node-cache - fix: generic type propagation in get, mget, and take methods by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1602](https://github.com/jaredwray/cacheable/pull/1602) * node-cache - fix: (breaking) Remove maxKeys limit feature from NodeCacheStore by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1605](https://github.com/jaredwray/cacheable/pull/1605) * node-cache - chore: (breaking) upgrading hookified adding stats by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1609](https://github.com/jaredwray/cacheable/pull/1609) * flat-cache - fix: Upgrade flatted to ^3.4.2 to fix GHSA-rf6f-7fwh-wjgh by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1606](https://github.com/jaredwray/cacheable/pull/1606) * utils - chore: upgrading hashery by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1607](https://github.com/jaredwray/cacheable/pull/1607) * net - chore: upgrading undici by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1608](https://github.com/jaredwray/cacheable/pull/1608) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2026](https://github.com/jaredwray/cacheable/compare/2026)-03-17...2026-03-26 ### 2026-03-17 URL: https://cacheable.org/changelog/2026-03-17 Date: March 17, 2026 Tag: Release ## What's Changed * flat-cache - chore: bump `flatted` version from `v3.3.3` -> `v3.4.1` by @brionmario in [https://github.com/jaredwray/cacheable/pull/1594](https://github.com/jaredwray/cacheable/pull/1594) * net - fix: port issue with local mockhttp by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1595](https://github.com/jaredwray/cacheable/pull/1595) * chore: moving core dev dependencies to mono hoist by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1596](https://github.com/jaredwray/cacheable/pull/1596) * cacheable - chore: upgrading qified to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1597](https://github.com/jaredwray/cacheable/pull/1597) * cacheable - chore: upgrading lru-cache and @keyv/redis by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1598](https://github.com/jaredwray/cacheable/pull/1598) ## New Contributors * @brionmario made their first contribution in [https://github.com/jaredwray/cacheable/pull/1594](https://github.com/jaredwray/cacheable/pull/1594) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2026](https://github.com/jaredwray/cacheable/compare/2026)-02-27...2026-03-17 ### 2026-02-27 URL: https://cacheable.org/changelog/2026-02-27 Date: February 27, 2026 Tag: Release ## What's Changed * utils -Add nonBlocking option support to getOrSet method by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1580](https://github.com/jaredwray/cacheable/pull/1580) * utils - chore: upgrading faker-js and types to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1581](https://github.com/jaredwray/cacheable/pull/1581) * utils - chore: upgrading hashery to 1.5.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1583](https://github.com/jaredwray/cacheable/pull/1583) * memory - chore: upgrading faker, rimraf, and types to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1584](https://github.com/jaredwray/cacheable/pull/1584) * memory - chore: upgrading hookified to 1.15.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1585](https://github.com/jaredwray/cacheable/pull/1585) * mono - chore: upgrading biome and wrangler to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1586](https://github.com/jaredwray/cacheable/pull/1586) * net - chore: upgrading types, rimraf, and faker to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1587](https://github.com/jaredwray/cacheable/pull/1587) * net - chore: upgrading hookified to 1.15.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1588](https://github.com/jaredwray/cacheable/pull/1588) * net - chore: upgrading undici to 7.22.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1589](https://github.com/jaredwray/cacheable/pull/1589) * mono - chore: moving to nodejs 24 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1590](https://github.com/jaredwray/cacheable/pull/1590) * node-cache - fix: ttl was not defaulting to 0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1591](https://github.com/jaredwray/cacheable/pull/1591) * mono - fix: updating codecov badge by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1592](https://github.com/jaredwray/cacheable/pull/1592) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2026](https://github.com/jaredwray/cacheable/compare/2026)-02-06...2026-02-27 ### 2026-02-06 URL: https://cacheable.org/changelog/2026-02-06 Date: February 6, 2026 Tag: Release ## What's Changed * utils - chore: upgrading vitest to 4.0.18 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1555](https://github.com/jaredwray/cacheable/pull/1555) * utils - chore: upgrading keyv to 5.6.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1556](https://github.com/jaredwray/cacheable/pull/1556) * cache-manager - chore: upgrading keyv to 5.6.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1557](https://github.com/jaredwray/cacheable/pull/1557) * cacheable - chore: upgrading keyv to 5.6.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1558](https://github.com/jaredwray/cacheable/pull/1558) * cacheable-request - chore: upgrading keyv to 5.6.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1559](https://github.com/jaredwray/cacheable/pull/1559) * memory - chore: upgrading keyv to 5.6.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1560](https://github.com/jaredwray/cacheable/pull/1560) * node-cache - chore: upgrading keyv to 5.6.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1561](https://github.com/jaredwray/cacheable/pull/1561) * utils - fix: adding index.ts filter for coverage by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1562](https://github.com/jaredwray/cacheable/pull/1562) * memory - chore: upgrading vitest to 4.0.18 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1563](https://github.com/jaredwray/cacheable/pull/1563) * memory - chore: upgraind @keyv/bigmap to 1.3.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1564](https://github.com/jaredwray/cacheable/pull/1564) * memory - chore: upgrading hookified to 1.15.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1565](https://github.com/jaredwray/cacheable/pull/1565) * net - chore: upgrading vitest to 4.0.18 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1566](https://github.com/jaredwray/cacheable/pull/1566) * net - chore: upgrading undici to 7.19.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1567](https://github.com/jaredwray/cacheable/pull/1567) * net - chore: upgrading hookified to 1.15.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1568](https://github.com/jaredwray/cacheable/pull/1568) * utils - fix: Improve error handling for getOrSet by @nrutman in [https://github.com/jaredwray/cacheable/pull/1553](https://github.com/jaredwray/cacheable/pull/1553) * memory - fix: memory leak when using lru not removing key by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1571](https://github.com/jaredwray/cacheable/pull/1571) * mono - feat: adding in Agents by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1572](https://github.com/jaredwray/cacheable/pull/1572) * cache-manager - chore: upgrading @keyv/redis to 5.1.6 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1573](https://github.com/jaredwray/cacheable/pull/1573) * mono - chore: centralizing dev dependencies to mono by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1574](https://github.com/jaredwray/cacheable/pull/1574) * cacheable-request - chore: upgrading tsup to 8.5.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1575](https://github.com/jaredwray/cacheable/pull/1575) * website - chore: upgrading docula to 0.40.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1576](https://github.com/jaredwray/cacheable/pull/1576) * chore: upgrading @biomejs/biome to 2.3.14 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1577](https://github.com/jaredwray/cacheable/pull/1577) * mono - chore: upgrading wrangler to 4.62.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1578](https://github.com/jaredwray/cacheable/pull/1578) ## New Contributors * @nrutman made their first contribution in [https://github.com/jaredwray/cacheable/pull/1553](https://github.com/jaredwray/cacheable/pull/1553) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-01-17...2026-02-06 ### 2025-01-17 URL: https://cacheable.org/changelog/2025-01-17 Date: January 17, 2026 Tag: Release ## What's Changed * cacheable - chore: upgrading vitest to 4.0.17 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1542](https://github.com/jaredwray/cacheable/pull/1542) * cacheable - chore: upgrading qified to 0.6.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1543](https://github.com/jaredwray/cacheable/pull/1543) * cacheable - chore: upgrading hookified to 1.15.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1544](https://github.com/jaredwray/cacheable/pull/1544) * node-cache - chore: upgrading vitest to 4.0.17 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1545](https://github.com/jaredwray/cacheable/pull/1545) * node-cache - chore: upgrading hookified to 1.15.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1546](https://github.com/jaredwray/cacheable/pull/1546) * node-cache - doc: adding in breaking changes from v1 to v2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1547](https://github.com/jaredwray/cacheable/pull/1547) * flat-cache - chore: upgrading vitest to 4.0.17 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1548](https://github.com/jaredwray/cacheable/pull/1548) * flat-cache - chore: upgrading hookified to 1.15.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1549](https://github.com/jaredwray/cacheable/pull/1549) * file-entry-cache - chore: upgrading vitest to 4.0.17 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1550](https://github.com/jaredwray/cacheable/pull/1550) * file-entry-cache - chore: upgrading pino to 10.1.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1551](https://github.com/jaredwray/cacheable/pull/1551) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2026](https://github.com/jaredwray/cacheable/compare/2026)-01-09...2025-01-17 ### 2026-01-09 URL: https://cacheable.org/changelog/2026-01-09 Date: January 9, 2026 Tag: Release ## What's Changed * node-cache - feat: (breaking) moving to Keyv as the storage by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1524](https://github.com/jaredwray/cacheable/pull/1524) * cache-manager - chore: upgrading vitest to 4.0.16 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1532](https://github.com/jaredwray/cacheable/pull/1532) * cache-manager - chore: upgrading @biomejs/biome to 2.3.11 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1533](https://github.com/jaredwray/cacheable/pull/1533) * cacheable-request - chore: upgrading vitest to 4.0.16 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1534](https://github.com/jaredwray/cacheable/pull/1534) * cacheable-request - chore: upgrading normalize-url to 8.1.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1535](https://github.com/jaredwray/cacheable/pull/1535) * cacheable-request - chore: upgrading body-parser to 2.2.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1536](https://github.com/jaredwray/cacheable/pull/1536) * website - chore: upgrading docula to 0.31.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1537](https://github.com/jaredwray/cacheable/pull/1537) * mono - chore: upgrading vitest to 4.0.16 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1538](https://github.com/jaredwray/cacheable/pull/1538) * mono - chore: upgrading @biomejs/biome to 2.3.11 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1539](https://github.com/jaredwray/cacheable/pull/1539) * mono - chore: upgrading wrangler to 4.57.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1540](https://github.com/jaredwray/cacheable/pull/1540) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-12-26...2026-01-09 ### 2025-12-26 URL: https://cacheable.org/changelog/2025-12-26 Date: December 26, 2025 Tag: Release ## What's Changed * cache-manager - chore: upgrading keyv to 5.5.5 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1510](https://github.com/jaredwray/cacheable/pull/1510) * utils - chore: upgrading vitest to 4.0.16 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1513](https://github.com/jaredwray/cacheable/pull/1513) * utils - chore: upgrading @biomejs/biome to 2.3.10 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1514](https://github.com/jaredwray/cacheable/pull/1514) * utils - chore: upgrading lru-cache to 11.2.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1515](https://github.com/jaredwray/cacheable/pull/1515) * utils - chore: upgrading hashery to 1.3.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1516](https://github.com/jaredwray/cacheable/pull/1516) * memory - chore: upgrading vitest to 4.0.16 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1517](https://github.com/jaredwray/cacheable/pull/1517) * memory - chore: upgrading @biomejs/biome to 2.3.10 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1518](https://github.com/jaredwray/cacheable/pull/1518) * memory - chore: upgrading hookified to 1.14.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1519](https://github.com/jaredwray/cacheable/pull/1519) * net - chore: upgrading vitest to 4.0.16 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1520](https://github.com/jaredwray/cacheable/pull/1520) * net - chore: upgrading @biomejs/biome to 2.3.10 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1521](https://github.com/jaredwray/cacheable/pull/1521) * net - chore: upgrading hookified to 1.14.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1522](https://github.com/jaredwray/cacheable/pull/1522) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-12-16...2025-12-26 ### 2025-12-16 URL: https://cacheable.org/changelog/2025-12-16 Date: December 16, 2025 Tag: Release ## What's Changed * cacheable - chore: upgrading vitest to 4.0.15 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1498](https://github.com/jaredwray/cacheable/pull/1498) * cacheable - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1499](https://github.com/jaredwray/cacheable/pull/1499) * cacheable - chore: upgrading qifiied to 0.5.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1500](https://github.com/jaredwray/cacheable/pull/1500) * cacheable - chore: upgrading hookified to 1.14.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1501](https://github.com/jaredwray/cacheable/pull/1501) * cacheable - chore: upgrading keyv to 5.5.5 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1502](https://github.com/jaredwray/cacheable/pull/1502) * node-cache - chore: upgrading vitest to 4.0.15 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1503](https://github.com/jaredwray/cacheable/pull/1503) * node-cache - chore: upgrading keyv to 5.5.5 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1504](https://github.com/jaredwray/cacheable/pull/1504) * node-cache - chore: upgrading hookified to 1.14.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1505](https://github.com/jaredwray/cacheable/pull/1505) * flat-cache - chore: upgrading vitest to 4.0.15 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1506](https://github.com/jaredwray/cacheable/pull/1506) * flat-cache - chore: upgrading hookified to 1.14.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1507](https://github.com/jaredwray/cacheable/pull/1507) * file-entry-cache - chore: upgrading vitest to 4.0.15 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1508](https://github.com/jaredwray/cacheable/pull/1508) * file-entry-cache - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1509](https://github.com/jaredwray/cacheable/pull/1509) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-12-09...2025-12-16 ### 2025-12-09 URL: https://cacheable.org/changelog/2025-12-09 Date: December 9, 2025 Tag: Release ## What's Changed * cache-manager - fix: moving to carat instead of pinning the module by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1497](https://github.com/jaredwray/cacheable/pull/1497) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-12-06...2025-12-09 ### 2025-12-06 URL: https://cacheable.org/changelog/2025-12-06 Date: December 6, 2025 Tag: Release ## What's Changed * mono - chore: upgrading wrangler to 4.52.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1483](https://github.com/jaredwray/cacheable/pull/1483) * chore: upgrading wrangler to 4.53.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1484](https://github.com/jaredwray/cacheable/pull/1484) * mono - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1485](https://github.com/jaredwray/cacheable/pull/1485) * cache-manager - chore: upgrading vitest to 4.0.15 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1486](https://github.com/jaredwray/cacheable/pull/1486) * cache-manager - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1487](https://github.com/jaredwray/cacheable/pull/1487) * cacheable-request - chore: upgrading express to 5.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1488](https://github.com/jaredwray/cacheable/pull/1488) * cacheable-request - chore: upgrading vitest to 4.0.15 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1489](https://github.com/jaredwray/cacheable/pull/1489) * cacheable-request - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1490](https://github.com/jaredwray/cacheable/pull/1490) * website - chore: upgrading tsx to 4.21.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1491](https://github.com/jaredwray/cacheable/pull/1491) * cacheable-request - fix: moving to dependency pinning for security by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1492](https://github.com/jaredwray/cacheable/pull/1492) * cache-manager - fix: moving to pinning on dependencies for security by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1493](https://github.com/jaredwray/cacheable/pull/1493) * website - chore: upgrading docula to 0.31.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1494](https://github.com/jaredwray/cacheable/pull/1494) * cache-manager - chore: upgrading tsup to 8.5.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1495](https://github.com/jaredwray/cacheable/pull/1495) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-11-26...2025-12-06 ### 2025-11-26 URL: https://cacheable.org/changelog/2025-11-26 Date: November 26, 2025 Tag: Release ## What's Changed * cacheable - fix: exports were mapped wrong by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1463](https://github.com/jaredwray/cacheable/pull/1463) * cacheable - fix: updating exports for unique type definitions by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1464](https://github.com/jaredwray/cacheable/pull/1464) * cacheable - feat: adding namespace support to sync by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1473](https://github.com/jaredwray/cacheable/pull/1473) * cache-manager - fix: updating exports to work correctly by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1465](https://github.com/jaredwray/cacheable/pull/1465) * file-entry-cache - fix: updating exports to work correctly by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1466](https://github.com/jaredwray/cacheable/pull/1466) * flat-cache - fix: updating to exports to work correctly by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1467](https://github.com/jaredwray/cacheable/pull/1467) * node-cache - fix: upgrading exports to be correct by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1470](https://github.com/jaredwray/cacheable/pull/1470) * utils - fix: upgrading exports to work correctly by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1471](https://github.com/jaredwray/cacheable/pull/1471) * utils - chore: upgrading @biomejs/biome to 2.3.7 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1474](https://github.com/jaredwray/cacheable/pull/1474) * utils - chore: upgrading vitest to 4.0.14 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1475](https://github.com/jaredwray/cacheable/pull/1475) * memory - fix: upgrading exports to work correctly by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1468](https://github.com/jaredwray/cacheable/pull/1468) * memory - chore: upgrading tsup to 8.5.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1476](https://github.com/jaredwray/cacheable/pull/1476) * memory - chore: upgrading @keyv/bigmap to 1.3.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1477](https://github.com/jaredwray/cacheable/pull/1477) * memory - chore: upgrading hookified to 1.13.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1478](https://github.com/jaredwray/cacheable/pull/1478) * memory - chore: upgrading vitest to 4.0.14 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1479](https://github.com/jaredwray/cacheable/pull/1479) * net - fix: upgrading exports to work correctly by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1469](https://github.com/jaredwray/cacheable/pull/1469) * net - chore: upgrading @biomejs/biome to 2.3.7 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1480](https://github.com/jaredwray/cacheable/pull/1480) * net - chore: upgrading hookified to 1.13.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1481](https://github.com/jaredwray/cacheable/pull/1481) * net - chore: upgrading vitest to 4.0.14 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1482](https://github.com/jaredwray/cacheable/pull/1482) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-11-17...2025-11-26 ### 2025-11-17 URL: https://cacheable.org/changelog/2025-11-17 Date: November 17, 2025 Tag: Release ## What's Changed * utils - fix: making Keyv a dependency by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1462](https://github.com/jaredwray/cacheable/pull/1462) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-11-16...2025-11-17 ### 2025-11-16 URL: https://cacheable.org/changelog/2025-11-16 Date: November 16, 2025 Tag: Release ## What's Changed * cacheable - chore: upgrading tsup to 8.5.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1443](https://github.com/jaredwray/cacheable/pull/1443) * cacheable - chore: upgrading vitest to 4.0.9 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1444](https://github.com/jaredwray/cacheable/pull/1444) * cacheable - chore: upgrading qified to 0.5.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1445](https://github.com/jaredwray/cacheable/pull/1445) * cacheable - chore: upgrading hookified to 1.13.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1446](https://github.com/jaredwray/cacheable/pull/1446) * cacheable - feat: using hash and hashSync now in [https://github.com/jaredwray/cacheable/pull/1459](https://github.com/jaredwray/cacheable/pull/1459) * memory - feat: using hash and hashSync now in [https://github.com/jaredwray/cacheable/pull/1459](https://github.com/jaredwray/cacheable/pull/1459) * cache-manager - chore: upgrading keyv across all projects to 5.5.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1447](https://github.com/jaredwray/cacheable/pull/1447) * net - chore: upgrading keyv across all projects to 5.5.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1447](https://github.com/jaredwray/cacheable/pull/1447) * cacheable-request - chore: upgrading keyv across all projects to 5.5.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1447](https://github.com/jaredwray/cacheable/pull/1447) * node-cache - chore: upgrading tsup to 8.5.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1448](https://github.com/jaredwray/cacheable/pull/1448) * node-cache - chore: upgrading vitest to 4.0.9 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1449](https://github.com/jaredwray/cacheable/pull/1449) * node-cache - chore: upgrading hookified to 1.13.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1450](https://github.com/jaredwray/cacheable/pull/1450) * flat-cache - chore: upgrading tsup to 8.5.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1451](https://github.com/jaredwray/cacheable/pull/1451) * flat-cache - chore: upgrading vitest to 4.0.9 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1452](https://github.com/jaredwray/cacheable/pull/1452) * flat-cache - chore: upgrading hookified to 1.13.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1453](https://github.com/jaredwray/cacheable/pull/1453) * file-entry-cache - chore: upgrading @biomejs/biome to 2.3.5 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1454](https://github.com/jaredwray/cacheable/pull/1454) * file-entry-cache - chore: upgrading pino to 10.1.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1455](https://github.com/jaredwray/cacheable/pull/1455) * file-entry-cache - chore: upgrading vitest to 4.0.9 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1456](https://github.com/jaredwray/cacheable/pull/1456) * cacheable - feat: moving to native Keyv hasMany by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1457](https://github.com/jaredwray/cacheable/pull/1457) * cacheable - feat: adding in documentation for store iteration by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1458](https://github.com/jaredwray/cacheable/pull/1458) * utils - feat: moving to hashery for web / browser compatiblity by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1459](https://github.com/jaredwray/cacheable/pull/1459) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-11-07...2025-11-16 ### 2025-11-07 URL: https://cacheable.org/changelog/2025-11-07 Date: November 7, 2025 Tag: Release ## What's Changed * mono - chore: upgrading wrangler to 4.46.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1428](https://github.com/jaredwray/cacheable/pull/1428) * mono - chore: upgrading @biomejs/biome to 2.3.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1429](https://github.com/jaredwray/cacheable/pull/1429) * mono - chore: upgrading vitest to 4.0.7 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1430](https://github.com/jaredwray/cacheable/pull/1430) * website - chore: upgrading docula to 0.31.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1431](https://github.com/jaredwray/cacheable/pull/1431) * cache-manager - chore: upgrading vitest to 4.0.7 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1432](https://github.com/jaredwray/cacheable/pull/1432) * cache-manager - chore: upgrading @biomejs/biome to 2.3.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1433](https://github.com/jaredwray/cacheable/pull/1433) * cache-manager - chore: upgrading @keyv/redis to 5.1.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1434](https://github.com/jaredwray/cacheable/pull/1434) * cache-manager - chore: upgrading vitest to 4.0.8 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1436](https://github.com/jaredwray/cacheable/pull/1436) * cache-manager - chore: upgrading @faker-js/faker to 10.1.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1437](https://github.com/jaredwray/cacheable/pull/1437) * cacheable-request - chore: upgrading @biomejs/biome to 2.3.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1438](https://github.com/jaredwray/cacheable/pull/1438) * cacheable-request - chore: upgrading delay to 7.0.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1439](https://github.com/jaredwray/cacheable/pull/1439) * cacheable-request - chore: upgrading responselike to 4.0.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1440](https://github.com/jaredwray/cacheable/pull/1440) * cacheable-request - chore: upgrading vitest to 4.0.8 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1441](https://github.com/jaredwray/cacheable/pull/1441) * cacheable - fix: adding in disconnect for sync by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1442](https://github.com/jaredwray/cacheable/pull/1442) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-10-27...2025-11-07 ### 2025-10-27 URL: https://cacheable.org/changelog/2025-10-27 Date: October 31, 2025 Tag: Release ## What's Changed * cacheable-request - fix: 304 Not Modified has completely empty headers by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1409](https://github.com/jaredwray/cacheable/pull/1409) * utils - chore: upgrading vitest to 4.0.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1410](https://github.com/jaredwray/cacheable/pull/1410) * utils - chore: upgrading typescript to 5.9.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1411](https://github.com/jaredwray/cacheable/pull/1411) * utils - chore: upgrading @biomejs/biome to 2.3.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1412](https://github.com/jaredwray/cacheable/pull/1412) * utils - chore: upgrading @faker-js/faker to 10.1.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1413](https://github.com/jaredwray/cacheable/pull/1413) * utils - chore: removing @keyv/redis and @keyv/valkey as not needed by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1414](https://github.com/jaredwray/cacheable/pull/1414) * utils - feat migrating memoize to utils by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1415](https://github.com/jaredwray/cacheable/pull/1415) * memory - chore: upgrading typescript to 5.9.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1416](https://github.com/jaredwray/cacheable/pull/1416) * memory - chore: upgrading hookified to 1.12.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1417](https://github.com/jaredwray/cacheable/pull/1417) * memory - chore: upgrading @keyv/bigmap to 1.1.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1418](https://github.com/jaredwray/cacheable/pull/1418) * memory - chore: upgrading @biomejs/biome to 2.3.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1419](https://github.com/jaredwray/cacheable/pull/1419) * memory - chore: upgrading @faker-js/faker to 10.1.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1420](https://github.com/jaredwray/cacheable/pull/1420) * memory - chore: upgrading vitest to 4.0.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1421](https://github.com/jaredwray/cacheable/pull/1421) * net - chore: upgrading vitest to 4.0.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1422](https://github.com/jaredwray/cacheable/pull/1422) * net - chore: upgrading typescript to 5.9.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1423](https://github.com/jaredwray/cacheable/pull/1423) * net - chore: upgrading hookified to 1.12.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1424](https://github.com/jaredwray/cacheable/pull/1424) * net - chore: upgrading @biomejs/biome to 2.3.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1425](https://github.com/jaredwray/cacheable/pull/1425) * net - chore: upgrading vitest to 4.0.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1426](https://github.com/jaredwray/cacheable/pull/1426) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-10-17...2025-10-27 ### 2025-10-17 URL: https://cacheable.org/changelog/2025-10-17 Date: October 17, 2025 Tag: Release ## What's Changed * mono - chore: moving away from minify by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1401](https://github.com/jaredwray/cacheable/pull/1401) * cacheable - chore: upgrading typescript to 5.9.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1403](https://github.com/jaredwray/cacheable/pull/1403) * cacheable - chore: upgrading hookified to 1.12.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1404](https://github.com/jaredwray/cacheable/pull/1404) * cacheable - chore: upgrading @faker-js/faker to 10.1.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1405](https://github.com/jaredwray/cacheable/pull/1405) * cacheable - chore: upgrading @faker-js/faker to 2.2.6 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1406](https://github.com/jaredwray/cacheable/pull/1406) * cacheable - chore: upgrading lru-cache, and keyv storage adapters to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1407](https://github.com/jaredwray/cacheable/pull/1407) * node-cache - chore: upgrading typescript to 5.9.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1408](https://github.com/jaredwray/cacheable/pull/1408) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-10-12...2025-10-17 ### 2025-10-12 URL: https://cacheable.org/changelog/2025-10-12 Date: October 13, 2025 Tag: Release ## What's Changed * net - fix: adding in FetchRequestInit by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1387](https://github.com/jaredwray/cacheable/pull/1387) * file-entry-cache - feat: adding in dynamic meta properties by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1388](https://github.com/jaredwray/cacheable/pull/1388) * file-entry-cache - feat: adding in logging capabilities by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1389](https://github.com/jaredwray/cacheable/pull/1389) * cacheable - fix: updating types in tests by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1390](https://github.com/jaredwray/cacheable/pull/1390) * flat-cache - fix: handling legacy data store by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1391](https://github.com/jaredwray/cacheable/pull/1391) * flat-cache - fix: legacy fixes by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1392](https://github.com/jaredwray/cacheable/pull/1392) * file-entry-cache - fix: allowing path traversal by default for eslint by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1393](https://github.com/jaredwray/cacheable/pull/1393) * file-entry-cache - feat: adding in key as absolute path by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1394](https://github.com/jaredwray/cacheable/pull/1394) * file-entry-cache - feat: moving to options in create and createFromFile by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1395](https://github.com/jaredwray/cacheable/pull/1395) * file-entry-cache - feat: adding in test example for eslint by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1396](https://github.com/jaredwray/cacheable/pull/1396) * file-entry-cache - feat: renaming strictPaths to restrictAccessToCwd by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1397](https://github.com/jaredwray/cacheable/pull/1397) * file-entry-cache - chore: upgrading @types/node to 24.7.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1398](https://github.com/jaredwray/cacheable/pull/1398) * file-entry-cache - chore: updating types on tests to work by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1399](https://github.com/jaredwray/cacheable/pull/1399) * file-entry-cache - feat: adding back in useModifiedTime by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1400](https://github.com/jaredwray/cacheable/pull/1400) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-10-06...2025-10-12 ### 2025-10-06 URL: https://cacheable.org/changelog/2025-10-06 Date: October 6, 2025 Tag: Release ## What's Changed * file-entry-cache - fix: removing useModifiedTime as not needed by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1373](https://github.com/jaredwray/cacheable/pull/1373) * file-entry-cache - chore: upgrading biome to 2.2.5 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1374](https://github.com/jaredwray/cacheable/pull/1374) * cacheable - feat: adding in cache sync based on qified by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1375](https://github.com/jaredwray/cacheable/pull/1375) * cacheable - fix: moving isKeyvInstance to utils by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1376](https://github.com/jaredwray/cacheable/pull/1376) * cacheable - chore: updating readme around cacheable sync by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1378](https://github.com/jaredwray/cacheable/pull/1378) * utils - fix: adding in keyv instance by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1377](https://github.com/jaredwray/cacheable/pull/1377) * cache-manager - chore: upgrading typescript to 5.9.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1379](https://github.com/jaredwray/cacheable/pull/1379) * cache-manager - chore: upgrading @keyv/redis to 5.1.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1380](https://github.com/jaredwray/cacheable/pull/1380) * cacheable-request - chore: upgrading biome to 2.2.5 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1381](https://github.com/jaredwray/cacheable/pull/1381) * cacheable-request - chore: upgrading normalize-url to 8.1.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1382](https://github.com/jaredwray/cacheable/pull/1382) * website - chore - upgrading tsx to 4.20.6 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1383](https://github.com/jaredwray/cacheable/pull/1383) * website - chore: migrating from fs-extra to node:fs by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1384](https://github.com/jaredwray/cacheable/pull/1384) * mono - chore: upgrading wrangler to 4.42.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1385](https://github.com/jaredwray/cacheable/pull/1385) * mono - feat: adding in minify to all packages other than cacheable-re… by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1386](https://github.com/jaredwray/cacheable/pull/1386) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-10-01...2025-10-06 ### 2025-10-01 URL: https://cacheable.org/changelog/2025-10-01 Date: October 1, 2025 Tag: Release ## What's Changed * utils - chore: upgrading biome to 2.2.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1350](https://github.com/jaredwray/cacheable/pull/1350) * utils - chore: upgrading lru-cache to 11.2.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1351](https://github.com/jaredwray/cacheable/pull/1351) * utils - chore: upgrading @keyv/redis to 5.1.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1352](https://github.com/jaredwray/cacheable/pull/1352) * memoize - fix: removing lru-cache as no longer needed by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1353](https://github.com/jaredwray/cacheable/pull/1353) * memoize - chore: upgrading biome to 2.2.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1354](https://github.com/jaredwray/cacheable/pull/1354) * mono - chore upgrading keyv to 5.5.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1355](https://github.com/jaredwray/cacheable/pull/1355) * net - chore: upgrading undici to 7.16.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1356](https://github.com/jaredwray/cacheable/pull/1356) * net - chore: upgrading hookified to 1.12.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1357](https://github.com/jaredwray/cacheable/pull/1357) * net - chore: upgrading biome to 2.2.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1358](https://github.com/jaredwray/cacheable/pull/1358) * net - feat: adding in jsDoc for cache property by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1359](https://github.com/jaredwray/cacheable/pull/1359) * net - feat: moving to FetchOptions in Net by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1360](https://github.com/jaredwray/cacheable/pull/1360) * net - feat: adding readme information for fetch helpers by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1361](https://github.com/jaredwray/cacheable/pull/1361) * net - feat: adding in put method helper by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1362](https://github.com/jaredwray/cacheable/pull/1362) * net - feat: if there is no cache passed then it does not cache by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1363](https://github.com/jaredwray/cacheable/pull/1363) * net - feat: ability to set the caching to false on get() by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1364](https://github.com/jaredwray/cacheable/pull/1364) * net - feat: ability to set caching to false in head method by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1365](https://github.com/jaredwray/cacheable/pull/1365) * net - feat: ability for post to add caching if needed by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1366](https://github.com/jaredwray/cacheable/pull/1366) * net - feat: ability for put to have caching if true by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1367](https://github.com/jaredwray/cacheable/pull/1367) * net - feat: patch with caching when set to true by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1368](https://github.com/jaredwray/cacheable/pull/1368) * net - feat: added caching to delete as an option by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1369](https://github.com/jaredwray/cacheable/pull/1369) * net - feat: adding in options for stringify and parse by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1370](https://github.com/jaredwray/cacheable/pull/1370) * net - feat: renaming useHttpCache to httpCachePolicy by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1371](https://github.com/jaredwray/cacheable/pull/1371) * file-entry-cache - feat: adding in current working directory by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1372](https://github.com/jaredwray/cacheable/pull/1372) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-09-22...2025-10-01 ### 2025-09-22 URL: https://cacheable.org/changelog/2025-09-22 Date: September 22, 2025 Tag: Release ## What's Changed * mono - chore: upgrading wrangler to 4.37.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1328](https://github.com/jaredwray/cacheable/pull/1328) * cacheable - chore: upgrading lru-cache to 11.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1329](https://github.com/jaredwray/cacheable/pull/1329) * cacheable - chore: upgradking keyv to 5.5.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1330](https://github.com/jaredwray/cacheable/pull/1330) * cacheable - chore: upgrading hookified to 1.12.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1331](https://github.com/jaredwray/cacheable/pull/1331) * cacheable - chore: upgrading biome and faker-js to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1332](https://github.com/jaredwray/cacheable/pull/1332) * node-cache - chore: upgrading keyv to 5.5.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1333](https://github.com/jaredwray/cacheable/pull/1333) * node-cache - chore: upgrading hookified to 1.12.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1334](https://github.com/jaredwray/cacheable/pull/1334) * node-cache - chore: upgrading biome to 2.2.4 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1335](https://github.com/jaredwray/cacheable/pull/1335) * node-cache - fix: class name in documentation by @jakebman in [https://github.com/jaredwray/cacheable/pull/1342](https://github.com/jaredwray/cacheable/pull/1342) * memory - chore: upgrading lru-cache to 11.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1347](https://github.com/jaredwray/cacheable/pull/1347) * memory - chore: upgrading hookified to 1.12.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1348](https://github.com/jaredwray/cacheable/pull/1348) * mono - chore: upgrading keyv on all packages to 5.5.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1349](https://github.com/jaredwray/cacheable/pull/1349) ## New Contributors * @jakebman made their first contribution in [https://github.com/jaredwray/cacheable/pull/1342](https://github.com/jaredwray/cacheable/pull/1342) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-09-09...2025-09-22 ### 2025-09-09 URL: https://cacheable.org/changelog/2025-09-09 Date: September 9, 2025 Tag: Release ## Cacheable v2 `cacheable` is now using `@cacheable/utils`, `@cacheable/memoize`, and `@cacheable/memory` for its core functionality as we are moving to this modular architecture and plan to eventually have these modules across `cache-manager` and `flat-cache`. In addition there are some breaking changes: * `get()` and `getMany()` no longer have the `raw` option but instead we have built out `getRaw()` and `getManyRaw()` to use. * All `get` related functions now support `nonBlocking` which means if `nonBlocking: true` the primary store will return what it has and then in the background will work to sync from secondary storage for any misses. You can disable this by setting at the `get` function level the option `nonBlocking: false` which will look for any missing keys in the secondary. * `Keyv` v5.5+ is now the recommended supported version as we are using its native `getMany*` and `getRaw*` * `Wrap` and `getOrSet` have been updated with more robust options including the ability to use your own `serialize` function for creating the key in `wrap`. * `hash` has now been updated with robust options and also an enum for setting the algorithm. ## What's Changed * net - fix: making it so fetch is cached by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1292](https://github.com/jaredwray/cacheable/pull/1292) * net - feat: adding in get helper for fetch by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1293](https://github.com/jaredwray/cacheable/pull/1293) * net - feat: adding in get response by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1294](https://github.com/jaredwray/cacheable/pull/1294) * net - feat: adding in post from fetch by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1295](https://github.com/jaredwray/cacheable/pull/1295) * net - feat: updating patch and post to have data param by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1296](https://github.com/jaredwray/cacheable/pull/1296) * net - feat: adding in head helper method for fetch by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1297](https://github.com/jaredwray/cacheable/pull/1297) * net - feat: adding in delete fetch helper by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1298](https://github.com/jaredwray/cacheable/pull/1298) * net - feat: adding in http cache policy by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1299](https://github.com/jaredwray/cacheable/pull/1299) * cacheable - fix: handling nonBlocking errors better by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1300](https://github.com/jaredwray/cacheable/pull/1300) * cacheable - feat: (BREAKING) using memory, utils, and memoize by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1301](https://github.com/jaredwray/cacheable/pull/1301) * mono - chore: upgrading wrangler to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1304](https://github.com/jaredwray/cacheable/pull/1304) * mono - fix: centralizing biome to root by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1305](https://github.com/jaredwray/cacheable/pull/1305) * cache-manager - chore: upgrading @keyv/redis to 5.1.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1306](https://github.com/jaredwray/cacheable/pull/1306) * cache-manager - chore: upgrading @faker-js/faker to 10.0.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1307](https://github.com/jaredwray/cacheable/pull/1307) * cacheable-request - chore: upgrading @biomejs/biome to 2.2.3 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1308](https://github.com/jaredwray/cacheable/pull/1308) * website - chore: upgrading docula to 0.20.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1309](https://github.com/jaredwray/cacheable/pull/1309) * utils - feat: adding in helper functions from cache manager by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1311](https://github.com/jaredwray/cacheable/pull/1311) * cache-manager - feat: using @cacheable/utils for helper functions by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1312](https://github.com/jaredwray/cacheable/pull/1312) * cacheable - fix: if there are no keys needed from secondary do not ca… by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1313](https://github.com/jaredwray/cacheable/pull/1313) * cacheable - feat: moving to @cacheable/utils hash function by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1314](https://github.com/jaredwray/cacheable/pull/1314) * cacheable - feat: moving to native Keyv getManyRaw function by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1315](https://github.com/jaredwray/cacheable/pull/1315) * cacheable - feat: moving to Keyv native setMany by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1316](https://github.com/jaredwray/cacheable/pull/1316) * cacheable - feat: moving to native Keyv deleteMany by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1318](https://github.com/jaredwray/cacheable/pull/1318) * cacheable - feat: moving to native Keyv getRaw by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1319](https://github.com/jaredwray/cacheable/pull/1319) * cacheable - feat: adding getRaw and getManyRaw by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1320](https://github.com/jaredwray/cacheable/pull/1320) * cacheable - feat: get() and getMany() to not return raw value by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1321](https://github.com/jaredwray/cacheable/pull/1321) * cacheable - feat: moving get and getMany to use raw methods by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1322](https://github.com/jaredwray/cacheable/pull/1322) * cacheable - chore: clean up types and enums by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1323](https://github.com/jaredwray/cacheable/pull/1323) * utils - feat: moving hash function to take options with stringify by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1324](https://github.com/jaredwray/cacheable/pull/1324) * memoize - feat: adding options with serialize and deserialize for create key hash by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1325](https://github.com/jaredwray/cacheable/pull/1325) * cacheable - feat: adding in the ability to not set serializers by default by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1326](https://github.com/jaredwray/cacheable/pull/1326) * cacheable - feat: adding in non blocking to get by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1327](https://github.com/jaredwray/cacheable/pull/1327) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-08-26...2025-09-09 ### 2025-08-26 URL: https://cacheable.org/changelog/2025-08-26 Date: August 26, 2025 Tag: Release ## What's Changed * cache-manager - feat: add store layer identification to event payloads by @faizanu94 in [https://github.com/jaredwray/cacheable/pull/1270](https://github.com/jaredwray/cacheable/pull/1270) * cacheable - fix: pass createKey option through wrap methods by @amit13k in [https://github.com/jaredwray/cacheable/pull/1269](https://github.com/jaredwray/cacheable/pull/1269) * cacheable - feat: adding in cache:hit and cache:miss events by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1273](https://github.com/jaredwray/cacheable/pull/1273) * utils - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1274](https://github.com/jaredwray/cacheable/pull/1274) * utils - chore: upgrading keyv adapters to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1275](https://github.com/jaredwray/cacheable/pull/1275) * utlis - chore: upgrading @biomejs/biome to 2.2.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1276](https://github.com/jaredwray/cacheable/pull/1276) * utils - chore: upgrading @fakerjs/faker to 10.0.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1277](https://github.com/jaredwray/cacheable/pull/1277) * memoize - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1278](https://github.com/jaredwray/cacheable/pull/1278) * memoize - chore: upgrading @biomejs/biome to 2.2.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1279](https://github.com/jaredwray/cacheable/pull/1279) * memoize - chore: upgrading @faker-js/faker to 10.0.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1280](https://github.com/jaredwray/cacheable/pull/1280) * memoize - chore: upgrading keyv adapters to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1281](https://github.com/jaredwray/cacheable/pull/1281) * net - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1282](https://github.com/jaredwray/cacheable/pull/1282) * net - chore: upgrading hookified to 1.12.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1283](https://github.com/jaredwray/cacheable/pull/1283) * net - chore: upgrading undici to 7.15.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1284](https://github.com/jaredwray/cacheable/pull/1284) * net - chore: upgrading @biomejs/biome to 2.2.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1285](https://github.com/jaredwray/cacheable/pull/1285) * net - chore: upgrading @faker-js/faker to 10.0.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1286](https://github.com/jaredwray/cacheable/pull/1286) * memory - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1287](https://github.com/jaredwray/cacheable/pull/1287) * memory - chore: upgrading keyv to 5.5.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1288](https://github.com/jaredwray/cacheable/pull/1288) * memory - chore: upgrading hookified to 1.12.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1289](https://github.com/jaredwray/cacheable/pull/1289) * memory - chore: upgrading @biomejs/biome to 2.2.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1290](https://github.com/jaredwray/cacheable/pull/1290) * memory - chore: upgrading @faker-js/faker to 10.0.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1291](https://github.com/jaredwray/cacheable/pull/1291) * mono - fix: moving codecov to better file search by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1272](https://github.com/jaredwray/cacheable/pull/1272) * mono - fix: updating biome to fail on warnings by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1271](https://github.com/jaredwray/cacheable/pull/1271) ## New Contributors * @amit13k made their first contribution in [https://github.com/jaredwray/cacheable/pull/1269](https://github.com/jaredwray/cacheable/pull/1269) * @faizanu94 made their first contribution in [https://github.com/jaredwray/cacheable/pull/1270](https://github.com/jaredwray/cacheable/pull/1270) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-08-17...2025-08-26 ### 2025-08-17 URL: https://cacheable.org/changelog/2025-08-17 Date: August 17, 2025 Tag: Release ## What's Changed * node-cache - fix: adding in supported node versions by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1256](https://github.com/jaredwray/cacheable/pull/1256) * node-cache - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1262](https://github.com/jaredwray/cacheable/pull/1262) * node-cache - chore: upgrading hookified to 1.11.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1263](https://github.com/jaredwray/cacheable/pull/1263) * node-cache - chore: upgrading keyv to 5.5.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1264](https://github.com/jaredwray/cacheable/pull/1264) * mono - feat: migrating to biome from xo by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1258](https://github.com/jaredwray/cacheable/pull/1258) * cacheable - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1259](https://github.com/jaredwray/cacheable/pull/1259) * cacheable - chore: upgrading hookified to 1.11.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1260](https://github.com/jaredwray/cacheable/pull/1260) * cacheable - chore: upgrading keyv to 5.5.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1261](https://github.com/jaredwray/cacheable/pull/1261) * flat-cache - chore: upgrading hookified to 1.11.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1265](https://github.com/jaredwray/cacheable/pull/1265) * flat-cache - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1266](https://github.com/jaredwray/cacheable/pull/1266) * file-entry-cache - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1267](https://github.com/jaredwray/cacheable/pull/1267) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-08-06...2025-08-17 ### 2025-08-06 URL: https://cacheable.org/changelog/2025-08-06 Date: August 6, 2025 Tag: Release ## What's Changed * net - feat: creating @cacheable/net for http caching and more by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1246](https://github.com/jaredwray/cacheable/pull/1246) * mono - chore: upgrading wrangler to 4.28.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1247](https://github.com/jaredwray/cacheable/pull/1247) * website - chore: upgrading fs-extra to 11.3.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1248](https://github.com/jaredwray/cacheable/pull/1248) * website - chore: upgrading docula to 0.13.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1249](https://github.com/jaredwray/cacheable/pull/1249) * cacheable-request - chore: upgrading xo to 1.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1250](https://github.com/jaredwray/cacheable/pull/1250) * cacheable-request - chore: upgrading keyv to 5.5.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1251](https://github.com/jaredwray/cacheable/pull/1251) * cacheable-request - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1252](https://github.com/jaredwray/cacheable/pull/1252) * cacheable-request - feat: adding testTimeout to some unit tests by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1244](https://github.com/jaredwray/cacheable/pull/1244) * cache-manager - chore: upgrading xo to 1.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1253](https://github.com/jaredwray/cacheable/pull/1253) * cache-manager - chore: upgrading typescript to 5.9.2 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1254](https://github.com/jaredwray/cacheable/pull/1254) * cache-manager - chore: upgrading keyv to 5.5.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1255](https://github.com/jaredwray/cacheable/pull/1255) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-07-30...2025-08-06 ### 2025-07-30 URL: https://cacheable.org/changelog/2025-07-30 Date: July 31, 2025 Tag: Release ## What's Changed * cache-manager - fix: rename vite.config.ts to vitest.config.ts by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1233](https://github.com/jaredwray/cacheable/pull/1233) * utils - feat: creating @cacheable/utils by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1234](https://github.com/jaredwray/cacheable/pull/1234) * utils - fix: updating package to the correct keywords by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1235](https://github.com/jaredwray/cacheable/pull/1235) * utils - feat: adding in readme by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1236](https://github.com/jaredwray/cacheable/pull/1236) * utils - feat: making hashToNumber universal by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1237](https://github.com/jaredwray/cacheable/pull/1237) * memoize - feat: adding in @cacheable/memoize by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1238](https://github.com/jaredwray/cacheable/pull/1238) * memory - feat: creating @cacheable/memory by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1240](https://github.com/jaredwray/cacheable/pull/1240) * memory - fix: updating readme with correct npmjs by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1241](https://github.com/jaredwray/cacheable/pull/1241) * memoize - fix: fixing the readme npmjs by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1242](https://github.com/jaredwray/cacheable/pull/1242) * mono - fix: updating main readme by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1243](https://github.com/jaredwray/cacheable/pull/1243) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-07-23...2025-07-30 ### 2025-07-23 URL: https://cacheable.org/changelog/2025-07-23 Date: July 23, 2025 Tag: Release ## What's Changed * cacheable - fix: updating you to your in README by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1220](https://github.com/jaredwray/cacheable/pull/1220) * cacheable - chore: upgrading xo to 1.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1222](https://github.com/jaredwray/cacheable/pull/1222) * cacheable - chore: upgrading keyv to 5.4.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1223](https://github.com/jaredwray/cacheable/pull/1223) * cacheable - chore: upgrading @faker-js/faker to 9.9.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1224](https://github.com/jaredwray/cacheable/pull/1224) * node-cache - chore: upgrading xo to 1.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1225](https://github.com/jaredwray/cacheable/pull/1225) * node-cache - chore: upgrading keyv to 5.4.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1227](https://github.com/jaredwray/cacheable/pull/1227) * flat-cache - chore: upgrading xo to 1.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1228](https://github.com/jaredwray/cacheable/pull/1228) * file-entry-cache - chore: upgrading xo to 1.2.1 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1229](https://github.com/jaredwray/cacheable/pull/1229) * cache-manager - feat: `mget` and `mset` call `getMany` and `setMany` by @nathan-knight in [https://github.com/jaredwray/cacheable/pull/1221](https://github.com/jaredwray/cacheable/pull/1221) * flat-cache - fix: updating documentation on clearCacheById by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1232](https://github.com/jaredwray/cacheable/pull/1232) ## New Contributors * @nathan-knight made their first contribution in [https://github.com/jaredwray/cacheable/pull/1221](https://github.com/jaredwray/cacheable/pull/1221) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-07-17...2025-07-23 ### 2025-07-17 URL: https://cacheable.org/changelog/2025-07-17 Date: July 17, 2025 Tag: Release ## What's Changed * mono - chore: removing approve builds by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1203](https://github.com/jaredwray/cacheable/pull/1203) * mono - chore: upgrading wrangler to 4.25.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1212](https://github.com/jaredwray/cacheable/pull/1212) * node-cache - feat: Add generic to class by @dangowans in [https://github.com/jaredwray/cacheable/pull/1210](https://github.com/jaredwray/cacheable/pull/1210) * cacheable - feat: adding in readme around @keyv/redis and non blocking by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1211](https://github.com/jaredwray/cacheable/pull/1211) * website - chore: upgrading @types/node to latest by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1213](https://github.com/jaredwray/cacheable/pull/1213) * cacheable-request - chore: upgrading xo to 1.2.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1214](https://github.com/jaredwray/cacheable/pull/1214) * cache-manager - chore: upgrading xo to 1.2.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1215](https://github.com/jaredwray/cacheable/pull/1215) * cache-manager - chore: upgrading @faker-js/faker to 9.9.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1216](https://github.com/jaredwray/cacheable/pull/1216) * cache-manager - chore: upgrading @keyv/redis to 4.6.0 by @jaredwray in [https://github.com/jaredwray/cacheable/pull/1217](https://github.com/jaredwray/cacheable/pull/1217) ## New Contributors * @dangowans made their first contribution in [https://github.com/jaredwray/cacheable/pull/1210](https://github.com/jaredwray/cacheable/pull/1210) **Full Changelog**: [https://github.com/jaredwray/cacheable/compare/2025](https://github.com/jaredwray/cacheable/compare/2025)-06-30...2025-07-17