UNPKG

tiny-lru

Version:

Tiny LRU cache for Client or Server

450 lines (329 loc) 12.5 kB
# Tiny LRU A lightweight, high-performance Least Recently Used (LRU) cache implementation for JavaScript with optional TTL (time-to-live) support. Works in both Node.js and browser environments. ## Installation ```bash npm install tiny-lru ``` ## Usage ### Factory Function ```javascript import {lru} from "tiny-lru"; const cache = lru(max, ttl = 0, resetTtl = false); ``` ### Class Constructor ```javascript import {LRU} from "tiny-lru"; // Create a cache with 1000 items, 1 minute TTL, reset on access const cache = new LRU(1000, 60000, true); // Create a cache with TTL const cache2 = new LRU(100, 5000); // 100 items, 5 second TTL cache2.set('key1', 'value1'); // After 5 seconds, key1 will be expired ``` ### Class Inheritance ```javascript import {LRU} from "tiny-lru"; class MyCache extends LRU { constructor(max, ttl, resetTtl) { super(max, ttl, resetTtl); } } ``` ## Parameters - **max** `{Number}` - Maximum number of items to store. 0 means unlimited (default: 1000) - **ttl** `{Number}` - Time-to-live in milliseconds, 0 disables expiration (default: 0) - **resetTtl** `{Boolean}` - Reset TTL on each `set()` operation (default: false) ### Parameter Validation The factory function validates parameters and throws `TypeError` for invalid values: ```javascript // Invalid parameters will throw TypeError try { const cache = lru(-1); // Invalid max value } catch (error) { console.error(error.message); // "Invalid max value" } try { const cache = lru(100, -1); // Invalid ttl value } catch (error) { console.error(error.message); // "Invalid ttl value" } try { const cache = lru(100, 0, "true"); // Invalid resetTtl value } catch (error) { console.error(error.message); // "Invalid resetTtl value" } ``` ## Interoperability Compatible with Lodash's `memoize` function cache interface: ```javascript import _ from "lodash"; import {lru} from "tiny-lru"; _.memoize.Cache = lru().constructor; const memoized = _.memoize(myFunc); memoized.cache.max = 10; ``` ## Testing Tiny-LRU maintains 100% code coverage: ```console --------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s --------------|---------|----------|---------|---------|------------------- All files | 100 | 96.34 | 100 | 100 | tiny-lru.cjs | 100 | 96.34 | 100 | 100 | 190,225,245 --------------|---------|----------|---------|---------|------------------- ``` ## Benchmarks Tiny-LRU includes a comprehensive benchmark suite for performance analysis and comparison. The benchmark suite uses modern Node.js best practices and popular benchmarking tools. ### Benchmark Files #### Modern Benchmarks (`modern-benchmark.js`) ⭐ **Comprehensive benchmark suite using [Tinybench](https://github.com/tinylibs/tinybench)** Features: - Statistically analyzed latency and throughput values - Standard deviation, margin of error, variance calculations - Proper warmup phases and statistical significance - Realistic workload scenarios Test categories: - **SET operations**: Empty cache, full cache, eviction scenarios - **GET operations**: Hit/miss patterns, access patterns - **Mixed operations**: Real-world 80/20 read-write scenarios - **Special operations**: Delete, clear, different data types - **Memory usage analysis** #### Performance Observer Benchmarks (`performance-observer-benchmark.js`) **Native Node.js performance measurement using Performance Observer** Features: - Function-level timing using `performance.timerify()` - PerformanceObserver for automatic measurement collection - Custom high-resolution timer implementations - Scalability testing across different cache sizes ### Running Benchmarks ```bash # Run all modern benchmarks npm run benchmark:all # Run individual benchmark suites npm run benchmark:modern # Tinybench suite npm run benchmark:perf # Performance Observer suite # Or run directly node benchmarks/modern-benchmark.js node benchmarks/performance-observer-benchmark.js # Run with garbage collection exposed (for memory analysis) node --expose-gc benchmarks/modern-benchmark.js ``` ### Understanding Results #### Tinybench Output ``` ┌─────────┬─────────────────────────────┬─────────────────┬────────────────────┬──────────┬─────────┐ │ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │ ├─────────┼─────────────────────────────┼─────────────────┼────────────────────┼──────────┼─────────┤ │ 0 │ 'set-random-empty-cache-100'│ '2,486,234' │ 402.21854775934 │ '±0.45%'1243117``` - **ops/sec**: Operations per second (higher is better) - **Average Time**: Average execution time in nanoseconds - **Margin**: Statistical margin of error - **Samples**: Number of samples collected for statistical significance #### Performance Observer Output ``` ┌─────────────┬─────────┬────────────┬────────────┬────────────┬───────────────┬─────────┬────────┐ │ Function │ Calls │ Avg (ms) │ Min (ms) │ Max (ms) │ Median (ms) │ Std Dev │Ops/sec │ ├─────────────┼─────────┼────────────┼────────────┼────────────┼───────────────┼─────────┼────────┤ │ lru.set │ 10000.00240.00100.01560.00200.0012417292``` ### Performance Tips For accurate benchmark results: 1. **Close other applications** to reduce system noise 2. **Run multiple times** and compare results 3. **Use consistent hardware** for comparisons 4. **Enable garbage collection** with `--expose-gc` for memory tests 5. **Consider CPU frequency scaling** on laptops ### Good Performance Indicators - ✅ **Consistent ops/sec** across runs - ✅ **Low margin of error** (< 5%) - ✅ **GET operations faster than SET** - ✅ **Cache hits faster than misses** See `benchmarks/README.md` for complete documentation and advanced usage. ## API Reference ### Properties #### first `{Object|null}` - Item in first (least recently used) position ```javascript const cache = lru(); cache.first; // null - empty cache ``` #### last `{Object|null}` - Item in last (most recently used) position ```javascript const cache = lru(); cache.last; // null - empty cache ``` #### max `{Number}` - Maximum number of items to hold in cache ```javascript const cache = lru(500); cache.max; // 500 ``` #### resetTtl `{Boolean}` - Whether to reset TTL on each `set()` operation ```javascript const cache = lru(500, 5*6e4, true); cache.resetTtl; // true ``` #### size `{Number}` - Current number of items in cache ```javascript const cache = lru(); cache.size; // 0 - empty cache ``` #### ttl `{Number}` - TTL in milliseconds (0 = no expiration) ```javascript const cache = lru(100, 3e4); cache.ttl; // 30000 ``` ### Methods #### clear() Removes all items from cache. **Returns:** `{Object}` LRU instance ```javascript cache.clear(); ``` #### delete(key) Removes specified item from cache. **Parameters:** - `key` `{String}` - Item key **Returns:** `{Object}` LRU instance ```javascript cache.set('key1', 'value1'); cache.delete('key1'); console.log(cache.has('key1')); // false ``` **See also:** [has()](#has), [clear()](#clear) #### entries([keys]) Returns array of cache items as `[key, value]` pairs. **Parameters:** - `keys` `{Array}` - Optional array of specific keys to retrieve (defaults to all keys) **Returns:** `{Array}` Array of `[key, value]` pairs ```javascript cache.set('a', 1).set('b', 2); console.log(cache.entries()); // [['a', 1], ['b', 2]] console.log(cache.entries(['a'])); // [['a', 1]] ``` **See also:** [keys()](#keys), [values()](#values) #### evict() Removes the least recently used item from cache. **Returns:** `{Object}` LRU instance ```javascript cache.set('old', 'value').set('new', 'value'); cache.evict(); // Removes 'old' item ``` **See also:** [setWithEvicted()](#setwithevicted) #### expiresAt(key) Gets expiration timestamp for cached item. **Parameters:** - `key` `{String}` - Item key **Returns:** `{Number|undefined}` Expiration time (epoch milliseconds) or undefined if key doesn't exist ```javascript const cache = new LRU(100, 5000); // 5 second TTL cache.set('key1', 'value1'); console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now ``` **See also:** [get()](#get), [has()](#has) #### get(key) Retrieves cached item and promotes it to most recently used position. **Parameters:** - `key` `{String}` - Item key **Returns:** `{*}` Item value or undefined if not found/expired ```javascript cache.set('key1', 'value1'); console.log(cache.get('key1')); // 'value1' console.log(cache.get('nonexistent')); // undefined ``` **See also:** [set()](#set), [has()](#has) #### has(key) Checks if key exists in cache (without promoting it). **Parameters:** - `key` `{String}` - Item key **Returns:** `{Boolean}` True if key exists and is not expired ```javascript cache.set('key1', 'value1'); console.log(cache.has('key1')); // true console.log(cache.has('nonexistent')); // false ``` **See also:** [get()](#get), [delete()](#delete) #### keys() Returns array of all cache keys in LRU order (first = least recent). **Returns:** `{Array}` Array of keys ```javascript cache.set('a', 1).set('b', 2); cache.get('a'); // Move 'a' to most recent console.log(cache.keys()); // ['b', 'a'] ``` **See also:** [values()](#values), [entries()](#entries) #### set(key, value) Stores item in cache as most recently used. **Parameters:** - `key` `{String}` - Item key - `value` `{*}` - Item value **Returns:** `{Object}` LRU instance ```javascript cache.set('key1', 'value1') .set('key2', 'value2') .set('key3', 'value3'); ``` **See also:** [get()](#get), [setWithEvicted()](#setwithevicted) #### setWithEvicted(key, value) Stores item and returns evicted item if cache was full. **Parameters:** - `key` `{String}` - Item key - `value` `{*}` - Item value **Returns:** `{Object|null}` Evicted item `{key, value, expiry, prev, next}` or null ```javascript const cache = new LRU(2); cache.set('a', 1).set('b', 2); const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...} if (evicted) { console.log(`Evicted: ${evicted.key}`, evicted.value); } ``` **See also:** [set()](#set), [evict()](#evict) #### values([keys]) Returns array of cache values. **Parameters:** - `keys` `{Array}` - Optional array of specific keys to retrieve (defaults to all keys) **Returns:** `{Array}` Array of values ```javascript cache.set('a', 1).set('b', 2); console.log(cache.values()); // [1, 2] console.log(cache.values(['a'])); // [1] ``` **See also:** [keys()](#keys), [entries()](#entries) ## Examples ### Basic Usage ```javascript import {lru} from "tiny-lru"; // Create a cache with max 100 items const cache = lru(100); cache.set('key1', 'value1'); console.log(cache.get('key1')); // 'value1' // Method chaining cache.set("user:123", {name: "John", age: 30}) .set("session:abc", {token: "xyz", expires: Date.now()}); const user = cache.get("user:123"); // Promotes to most recent console.log(cache.size); // 2 ``` ### TTL with Auto-Expiration ```javascript import {LRU} from "tiny-lru"; const cache = new LRU(50, 5000); // 50 items, 5s TTL cache.set("temp-data", {result: "computed"}); setTimeout(() => { console.log(cache.get("temp-data")); // undefined - expired }, 6000); ``` ### Reset TTL on Access ```javascript const cache = lru(100, 10000, true); // Reset TTL on each set() cache.set("session", {user: "admin"}); // Each subsequent set() resets the 10s TTL ``` ## License Copyright (c) 2025 Jason Mulligan Licensed under the BSD-3 license.