427 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			427 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|  | <h1 align="center">lru.min</h1> | |||
|  | <div align="center"> | |||
|  | 
 | |||
|  | [](https://www.npmjs.com/package/lru.min) | |||
|  | [](https://www.npmjs.com/package/lru.min) | |||
|  | [](https://app.codecov.io/gh/wellwelwel/lru.min)<br /> | |||
|  | [](https://github.com/wellwelwel/lru.min/actions/workflows/ci_node.yml?query=branch%3Amain) | |||
|  | [](https://github.com/wellwelwel/lru.min/actions/workflows/ci_bun.yml?query=branch%3Amain) | |||
|  | [](https://github.com/wellwelwel/lru.min/actions/workflows/ci_deno.yml?query=branch%3Amain) | |||
|  | 
 | |||
|  | 🔥 An extremely fast, efficient, and lightweight <strong><a href="https://en.m.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29">LRU</a> Cache</strong> for <strong>JavaScript</strong> (<strong>Browser</strong> compatible). | |||
|  | 
 | |||
|  | </div> | |||
|  | 
 | |||
|  | ## Why another LRU?
 | |||
|  | 
 | |||
|  | - 🎖️ **lru.min** is fully compatible with both **Node.js** _(8+)_, **Bun**, **Deno** and, browser environments. All of this, while maintaining the same high performance [_(and a little more)_](https://github.com/wellwelwel/lru.min?tab=readme-ov-file#performance) as the most popular **LRU** packages. | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | ## Install
 | |||
|  | 
 | |||
|  | ```bash | |||
|  | # Node.js
 | |||
|  | npm i lru.min | |||
|  | ``` | |||
|  | 
 | |||
|  | ```bash | |||
|  | # Bun
 | |||
|  | bun add lru.min | |||
|  | ``` | |||
|  | 
 | |||
|  | ```bash | |||
|  | # Deno
 | |||
|  | deno add npm:lru.min | |||
|  | ``` | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | ## Usage
 | |||
|  | 
 | |||
|  | ### Quickstart
 | |||
|  | 
 | |||
|  | ```js | |||
|  | import { createLRU } from 'lru.min'; | |||
|  | 
 | |||
|  | const max = 2; | |||
|  | const onEviction = (key, value) => { | |||
|  |   console.log(`Key "${key}" with value "${value}" has been evicted.`); | |||
|  | }; | |||
|  | 
 | |||
|  | const LRU = createLRU({ | |||
|  |   max, | |||
|  |   onEviction, | |||
|  | }); | |||
|  | 
 | |||
|  | LRU.set('A', 'My Value'); | |||
|  | LRU.set('B', 'Other Value'); | |||
|  | LRU.set('C', 'Another Value'); | |||
|  | 
 | |||
|  | // => Key "A" with value "My Value" has been evicted. | |||
|  | 
 | |||
|  | LRU.has('B'); | |||
|  | LRU.get('B'); | |||
|  | LRU.delete('B'); | |||
|  | 
 | |||
|  | // => Key "B" with value "Other Value" has been evicted. | |||
|  | 
 | |||
|  | LRU.peek('C'); | |||
|  | 
 | |||
|  | LRU.clear(); // ← recommended | LRU.evict(max) → (slower alternative) | |||
|  | 
 | |||
|  | // => Key "C" with value "Another Value" has been evicted. | |||
|  | 
 | |||
|  | LRU.set('D', "You're amazing 💛"); | |||
|  | 
 | |||
|  | LRU.size; // 1 | |||
|  | LRU.max; // 2 | |||
|  | LRU.available; // 1 | |||
|  | 
 | |||
|  | LRU.resize(10); | |||
|  | 
 | |||
|  | LRU.size; // 1 | |||
|  | LRU.max; // 10 | |||
|  | LRU.available; // 9 | |||
|  | ``` | |||
|  | 
 | |||
|  | > For _up-to-date_ documentation, always follow the [**README.md**](https://github.com/wellwelwel/lru.min?tab=readme-ov-file#readme) in the **GitHub** repository.
 | |||
|  | 
 | |||
|  | ### Import
 | |||
|  | 
 | |||
|  | #### ES Modules
 | |||
|  | 
 | |||
|  | ```js | |||
|  | import { createLRU } from 'lru.min'; | |||
|  | ``` | |||
|  | 
 | |||
|  | #### CommonJS
 | |||
|  | 
 | |||
|  | ```js | |||
|  | const { createLRU } = require('lru.min'); | |||
|  | ``` | |||
|  | 
 | |||
|  | #### Browser
 | |||
|  | 
 | |||
|  | > Requires **ES6**.
 | |||
|  | 
 | |||
|  | ```html | |||
|  | <script src="https://cdn.jsdelivr.net/npm/lru.min@1.x.x/browser/lru.min.js"></script> | |||
|  | ``` | |||
|  | 
 | |||
|  | - You can use tools such as [**Babel**](https://github.com/babel/babel) to increase the compatibility rate. | |||
|  | 
 | |||
|  | ### Create a new LRU Cache
 | |||
|  | 
 | |||
|  | > Set maximum size when creating **LRU**.
 | |||
|  | 
 | |||
|  | ```ts | |||
|  | const LRU = createLRU({ max: 150_000 }); | |||
|  | ``` | |||
|  | 
 | |||
|  | Also, you can set a callback for every deletion/eviction: | |||
|  | 
 | |||
|  | ```ts | |||
|  | const LRU = createLRU({ | |||
|  |   max: 150_000, | |||
|  |   onEviction: (key, value) => { | |||
|  |     // do something | |||
|  |   }, | |||
|  | }); | |||
|  | ``` | |||
|  | 
 | |||
|  | ### Set a cache
 | |||
|  | 
 | |||
|  | Adds a key-value pair to the cache. Updates the value if the key already exists | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.set('key', 'value'); | |||
|  | ``` | |||
|  | 
 | |||
|  | > `undefined` keys will simply be ignored.
 | |||
|  | 
 | |||
|  | - Complexity: **O(1)**. | |||
|  | 
 | |||
|  | ### Get a cache
 | |||
|  | 
 | |||
|  | Retrieves the value for a given key and moves the key to the most recent position. | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.get('key'); | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(1)**. | |||
|  | 
 | |||
|  | ### Peek a cache
 | |||
|  | 
 | |||
|  | Retrieves the value for a given key without changing its position. | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.peek('key'); | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(1)**. | |||
|  | 
 | |||
|  | ### Check if a key exists
 | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.has('key'); | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(1)**. | |||
|  | 
 | |||
|  | ### Delete a cache
 | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.delete('key'); | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(1)**. | |||
|  | 
 | |||
|  | ### Evict from the oldest cache
 | |||
|  | 
 | |||
|  | Evicts the specified number of the oldest items from the cache. | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.evict(1000); | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(key)** — even if passed a number greater than the number of items, only existing items will be evicted. | |||
|  | 
 | |||
|  | > [!TIP]
 | |||
|  | > | |||
|  | > - Methods that perform eviction(s) when maximum size is reached: `set` and `resize`.
 | |||
|  | > - Methods that always perform eviction(s): `delete`, `clear`, and `evict` itself.
 | |||
|  | 
 | |||
|  | ### Resize the cache
 | |||
|  | 
 | |||
|  | Resizes the cache to a new maximum size, evicting items if necessary. | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.resize(50_000); | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: | |||
|  |   - Increasing: **O(newMax - max)**. | |||
|  |   - Downsizing: **O(n)**. | |||
|  | 
 | |||
|  | ### Clear the cache
 | |||
|  | 
 | |||
|  | Clears and disposes (if used) all key-value pairs from the cache. | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.clear(); | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: | |||
|  |   - Without `onEviction`: **O(1)**. | |||
|  |   - Using `onEviction`: **O(entries)**. | |||
|  | 
 | |||
|  | ### Debugging
 | |||
|  | 
 | |||
|  | #### Get the max size of the cache
 | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.max; | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(1)**. | |||
|  | 
 | |||
|  | #### Get the current size of the cache
 | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.size; | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(1)**. | |||
|  | 
 | |||
|  | #### Get the available slots in the cache
 | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.available; | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(1)**. | |||
|  | 
 | |||
|  | ### Iterating the cache
 | |||
|  | 
 | |||
|  | #### Get all keys
 | |||
|  | 
 | |||
|  | Iterates over all keys in the cache, from most recent to least recent. | |||
|  | 
 | |||
|  | ```ts | |||
|  | const keys = [...LRU.keys()]; | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(keys)**. | |||
|  | 
 | |||
|  | #### Get all values
 | |||
|  | 
 | |||
|  | Iterates over all values in the cache, from most recent to least recent. | |||
|  | 
 | |||
|  | ```ts | |||
|  | const values = [...LRU.values()]; | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(values)**. | |||
|  | 
 | |||
|  | #### Get all entries
 | |||
|  | 
 | |||
|  | Iterates over `[key, value]` pairs in the cache, from most recent to least recent. | |||
|  | 
 | |||
|  | ```ts | |||
|  | const entries = [...LRU.entries()]; | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(entries)**. | |||
|  | 
 | |||
|  | #### Run a callback for each entry
 | |||
|  | 
 | |||
|  | Iterates over each value-key pair in the cache, from most recent to least recent. | |||
|  | 
 | |||
|  | ```ts | |||
|  | LRU.forEach((value, key) => { | |||
|  |   // do something | |||
|  | }); | |||
|  | ``` | |||
|  | 
 | |||
|  | - Complexity: **O(entries)**. | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | > [!NOTE]
 | |||
|  | > | |||
|  | > - We use `O(keys)`, `O(values)`, `O(entries)`, and `O(newMax - max)` to explicitly indicate what is being iterated over. In traditional complexity notation, this would be represented as `O(n)`.
 | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | ### TypeScript
 | |||
|  | 
 | |||
|  | You can set types for both keys and values. For example: | |||
|  | 
 | |||
|  | ```ts | |||
|  | import { createLRU } from 'lru.min'; | |||
|  | 
 | |||
|  | type Key = number; | |||
|  | 
 | |||
|  | type Value = { | |||
|  |   name: string; | |||
|  | }; | |||
|  | 
 | |||
|  | const LRU = createLRU<Key, Value>({ max: 1000 }); | |||
|  | 
 | |||
|  | LRU.set(1, { name: 'Peter' }); | |||
|  | LRU.set(2, { name: 'Mary' }); | |||
|  | ``` | |||
|  | 
 | |||
|  | Also: | |||
|  | 
 | |||
|  | ```ts | |||
|  | import { createLRU, type CacheOptions } from 'lru.min'; | |||
|  | 
 | |||
|  | type Key = number; | |||
|  | 
 | |||
|  | type Value = { | |||
|  |   name: string; | |||
|  | }; | |||
|  | 
 | |||
|  | const options: CacheOptions<Key, Value> = { | |||
|  |   max: 10, | |||
|  |   onEviction(key, value) { | |||
|  |     console.log(key, value); | |||
|  |   }, | |||
|  | }; | |||
|  | 
 | |||
|  | // No need to repeat the type params | |||
|  | const LRU = createLRU(options); | |||
|  | 
 | |||
|  | LRU.set(1, { name: 'Peter' }); | |||
|  | LRU.set(2, { name: 'Mary' }); | |||
|  | ``` | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | ### Performance
 | |||
|  | 
 | |||
|  | The benchmark is performed by comparing `1,000,000` runs through a maximum cache limit of `100,000`, getting `333,333` caches and deleting `200,000` keys 10 consecutive times, clearing the cache every run. | |||
|  | 
 | |||
|  | > - [**lru-cache**](https://github.com/isaacs/node-lru-cache) `v11.0.0`
 | |||
|  | > - [**quick-lru**](https://github.com/sindresorhus/quick-lru) `v7.0.0`
 | |||
|  | 
 | |||
|  | ```sh | |||
|  | # Time:
 | |||
|  |   lru.min:    240.45ms | |||
|  |   lru-cache:  258.32ms | |||
|  |   quick-lru:  279.89ms | |||
|  | 
 | |||
|  | # CPU:
 | |||
|  |   lru.min:    275558.30µs | |||
|  |   lru-cache:  306858.30µs | |||
|  |   quick-lru:  401318.80µs | |||
|  | ``` | |||
|  | 
 | |||
|  | - See detailed results and how the tests are run and compared in the [**benchmark**](https://github.com/wellwelwel/lru.min/tree/main/benchmark) directory. | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | ## Security Policy
 | |||
|  | 
 | |||
|  | [](https://github.com/wellwelwel/lru.min/actions/workflows/ci_codeql.yml?query=branch%3Amain) | |||
|  | 
 | |||
|  | Please check the [**SECURITY.md**](https://github.com/wellwelwel/lru.min/blob/main/SECURITY.md). | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | ## Contributing
 | |||
|  | 
 | |||
|  | See the [**Contributing Guide**](https://github.com/wellwelwel/lru.min/blob/main/CONTRIBUTING.md) and please follow our [**Code of Conduct**](https://github.com/wellwelwel/lru.min/blob/main/CODE_OF_CONDUCT.md) 🚀 | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | ## Acknowledgements
 | |||
|  | 
 | |||
|  | **lru.min** is based and inspired on the architecture and code of both [**lru-cache**](https://github.com/isaacs/node-lru-cache) and [**quick-lru**](https://github.com/sindresorhus/quick-lru), simplifying their core concepts for enhanced performance and compatibility. | |||
|  | 
 | |||
|  | For more comprehensive features such as **TTL** support, consider using and supporting them 🤝 | |||
|  | 
 | |||
|  | - The architecture is mostly based on [@isaacs](https://github.com/isaacs) — [**lru-cache**](https://github.com/isaacs/node-lru-cache/blob/8f51d75351cbb4ac819952eb8e9f95eda00ef800/src/index.ts). | |||
|  | - Most of the methods names and its functionalities were inspired by [@sindresorhus](https://github.com/sindresorhus) — [**quick-lru**](https://github.com/sindresorhus/quick-lru/blob/a2262c65e1952539cb4d985a67c46363a780d234/index.js). | |||
|  | - [](https://github.com/wellwelwel/lru.min/graphs/contributors) | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | #### What comes from [**lru-cache**](https://github.com/isaacs/node-lru-cache)?
 | |||
|  | 
 | |||
|  | Architecture's essence: | |||
|  | 
 | |||
|  | > _It's not the same code, but majority based on [this](https://github.com/isaacs/node-lru-cache/blob/8f51d75351cbb4ac819952eb8e9f95eda00ef800/src/index.ts#L1385-L1394)._
 | |||
|  | 
 | |||
|  | ```ts | |||
|  | let free: number[] = []; | |||
|  | 
 | |||
|  | const keyMap: Map<Key, number> = new Map(); | |||
|  | const keyList: (Key | undefined)[] = new Array(max).fill(undefined); | |||
|  | const valList: (Value | undefined)[] = new Array(max).fill(undefined); | |||
|  | const next: number[] = new Array(max).fill(0); | |||
|  | const prev: number[] = new Array(max).fill(0); | |||
|  | ``` | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | #### What comes from [**quick-lru**](https://github.com/sindresorhus/quick-lru)?
 | |||
|  | 
 | |||
|  | Name of methods and options _(including their final functionality ideas)_: | |||
|  | 
 | |||
|  | - `resize` | |||
|  | - `peek` | |||
|  | - `onEviction` | |||
|  | - `forEach` | |||
|  | - `entriesDescending` as `entries` | |||
|  | 
 | |||
|  | --- | |||
|  | 
 | |||
|  | ## License
 | |||
|  | 
 | |||
|  | **lru.min** is under the [**MIT License**](https://github.com/wellwelwel/lru.min/blob/main/LICENSE).<br /> | |||
|  | Copyright © 2024-present [Weslley Araújo](https://github.com/wellwelwel) and **lru.min** [contributors](https://github.com/wellwelwel/lru.min/graphs/contributors). |