UNPKG

tiny-lru

Version:

A high-performance, lightweight LRU cache. Built for developers who need fast caching without compromising on features.

654 lines (482 loc) 16.5 kB
# 🚀 Tiny LRU [![npm version](https://badge.fury.io/js/tiny-lru.svg)](https://badge.fury.io/js/tiny-lru) [![Node.js Version](https://img.shields.io/node/v/tiny-lru.svg)](https://nodejs.org/) [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Build Status](https://github.com/avoidwork/tiny-lru/actions/workflows/ci.yml/badge.svg)](https://github.com/avoidwork/tiny-lru/actions) [![Test Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/avoidwork/tiny-lru) A **high-performance, lightweight** LRU cache for JavaScript with **strong UPDATE performance and competitive SET/GET/DELETE**, and a **compact bundle size**. Built for developers who need fast caching without compromising on features. ## 📦 Installation ```bash npm install tiny-lru # or yarn add tiny-lru # or pnpm add tiny-lru ``` **Requirements:** Node.js ≥12 ## ⚡ Quick Start ```javascript import {lru} from "tiny-lru"; // Create cache and start using immediately const cache = lru(100); // Max 100 items cache.set('user:123', {name: 'John', age: 30}); const user = cache.get('user:123'); // {name: 'John', age: 30} // With TTL (5 second expiration) const tempCache = lru(50, 5000); tempCache.set('session', 'abc123'); // Automatically expires after 5 seconds ``` ## 📑 Table of Contents - [✨ Features & Benefits](#-features--benefits) - [📊 Performance Deep Dive](#-performance-deep-dive) - [📖 API Reference](#-api-reference) - [🚀 Getting Started](#-getting-started) - [💡 Real-World Examples](#-real-world-examples) - [🔗 Interoperability](#-interoperability) - [🛠️ Development](#️-development) - [📄 License](#-license) ## ✨ Features & Benefits ### Why Choose Tiny LRU? - **🔄 Strong Cache Updates** - Excellent performance in update-heavy workloads - **📦 Compact Bundle** - Just ~2.2 KiB minified for a full-featured LRU library - **⚖️ Balanced Performance** - Competitive across all operations with O(1) complexity - **⏱️ TTL Support** - Optional time-to-live with automatic expiration - **🔄 Method Chaining** - Fluent API for better developer experience - **🎯 TypeScript Ready** - Full TypeScript support with complete type definitions - **🌐 Universal Compatibility** - Works seamlessly in Node.js and browsers - **🛡️ Production Ready** - Battle-tested and reliable ### Benchmark Comparison (Mean of 5 runs) | Library | SET ops/sec | GET ops/sec | UPDATE ops/sec | DELETE ops/sec | |---------|-------------|-------------|----------------|----------------| | **tiny-lru** | 404,753 | 1,768,449 | 1,703,716 | 298,770 | | lru-cache | 326,221 | 1,069,061 | 878,858 | 277,734 | | quick-lru | 591,683 | 1,298,487 | 935,481 | 359,600 | | mnemonist | 412,467 | 2,478,778 | 2,156,690 | 0 | Notes: - Mean values computed from the Performance Summary across 5 consecutive runs of `npm run benchmark:comparison`. - mnemonist lacks a compatible delete method in this harness, so DELETE ops/sec is 0. - Performance varies by hardware, Node.js version, and workload patterns; run the provided benchmarks locally to assess your specific use case. - Environment: Node.js v24.5.0, macOS arm64. ## 📊 Performance Deep Dive ### When to Choose Tiny LRU **✅ Perfect for:** - **Frequent cache updates** - Leading UPDATE performance - **Mixed read/write workloads** - Balanced across all operations - **Bundle size constraints** - Compact library with full features - **Production applications** - Battle-tested with comprehensive testing ### Running Your Own Benchmarks ```bash # Run all performance benchmarks npm run benchmark:all # Individual benchmark suites npm run benchmark:modern # Comprehensive Tinybench suite npm run benchmark:perf # Performance Observer measurements npm run benchmark:comparison # Compare against other LRU libraries ``` ## 🚀 Getting Started ### Installation ```bash npm install tiny-lru # or yarn add tiny-lru # or pnpm add tiny-lru ``` ### Quick Examples ```javascript import {lru} from "tiny-lru"; // Basic cache const cache = lru(100); cache.set('key1', 'value1') .set('key2', 'value2') .set('key3', 'value3'); console.log(cache.get('key1')); // 'value1' console.log(cache.size); // 3 // With TTL (time-to-live) const cacheWithTtl = lru(50, 30000); // 30 second TTL cacheWithTtl.set('temp-data', {important: true}); // Automatically expires after 30 seconds const resetCache = lru(25, 10000, true); resetCache.set('session', 'user123'); // Because resetTtl is true, TTL resets when you set() the same key again ``` ### CDN Usage (Browser) ```html <!-- ES Modules --> <script type="module"> import {lru, LRU} from 'https://cdn.skypack.dev/tiny-lru'; const cache = lru(100); </script> <!-- UMD Bundle (global: window.lru) --> <script src="https://unpkg.com/tiny-lru/dist/tiny-lru.umd.js"></script> <script> const {lru, LRU} = window.lru; const cache = lru(100); // or: const cache = new LRU(100); </script> ``` ### TypeScript Usage ```typescript import {lru, LRU} from "tiny-lru"; // Type-safe cache const cache = lru<string>(100); // or: const cache: LRU<string> = lru<string>(100); cache.set('user:123', 'John Doe'); const user: string | undefined = cache.get('user:123'); // Class inheritance class MyCache extends LRU<User> { constructor() { super(1000, 60000, true); // 1000 items, 1 min TTL, reset TTL on set } } ``` ### Configuration Options #### Factory Function ```javascript import {lru} from "tiny-lru"; const cache = lru(max, ttl = 0, resetTtl = false); ``` **Parameters:** - `max` `{Number}` - Maximum number of items (0 = unlimited, default: 1000) - `ttl` `{Number}` - Time-to-live in milliseconds (0 = no expiration, default: 0) - `resetTtl` `{Boolean}` - Reset TTL when updating existing items via `set()` (default: false) #### Class Constructor ```javascript import {LRU} from "tiny-lru"; const cache = new LRU(1000, 60000, true); // 1000 items, 1 min TTL, reset TTL on set ``` #### Best Practices ```javascript // 1. Size your cache appropriately const cache = lru(1000); // Not too small, not too large // 2. Use meaningful keys cache.set(`user:${userId}:profile`, userProfile); cache.set(`product:${productId}:details`, productDetails); // 3. Handle cache misses gracefully function getData(key) { const cached = cache.get(key); if (cached !== undefined) { return cached; } // Fallback to slower data source const data = expensiveOperation(key); cache.set(key, data); return data; } // 4. Clean up when needed process.on('exit', () => { cache.clear(); // Help garbage collection }); ``` #### Optimization Tips - **Cache Size**: Keep cache size reasonable (1000-10000 items for most use cases) - **TTL Usage**: Only use TTL when necessary; it adds overhead - **Key Types**: String keys perform better than object keys - **Memory**: Call `clear()` when done to help garbage collection ## 💡 Real-World Examples ### API Response Caching ```javascript import {lru} from "tiny-lru"; class ApiClient { constructor() { this.cache = lru(100, 300000); // 5 minute cache } async fetchUser(userId) { const cacheKey = `user:${userId}`; // Return cached result if available if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } // Fetch from API and cache const response = await fetch(`/api/users/${userId}`); const user = await response.json(); this.cache.set(cacheKey, user); return user; } } ``` ### Function Memoization ```javascript import {lru} from "tiny-lru"; function memoize(fn, maxSize = 100) { const cache = lru(maxSize); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn.apply(this, args); cache.set(key, result); return result; }; } // Usage const expensiveCalculation = memoize((n) => { console.log(`Computing for ${n}`); return n * n * n; }, 50); console.log(expensiveCalculation(5)); // Computing for 5 -> 125 console.log(expensiveCalculation(5)); // 125 (cached) ``` ### Session Management ```javascript import {lru} from "tiny-lru"; class SessionManager { constructor() { // 30 minute TTL, with resetTtl enabled for set() this.sessions = lru(1000, 1800000, true); } createSession(userId, data) { const sessionId = this.generateId(); const session = { userId, data, createdAt: Date.now() }; this.sessions.set(sessionId, session); return sessionId; } getSession(sessionId) { // get() does not extend TTL; to extend, set the session again when resetTtl is true return this.sessions.get(sessionId); } endSession(sessionId) { this.sessions.delete(sessionId); } } ``` ## 🔗 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; ``` ## 🛠️ Development ### Testing Tiny LRU maintains 100% test coverage with comprehensive unit and integration tests. ```bash # Run all tests with coverage npm test # Run tests with verbose output npm run mocha # Lint code npm run lint # Full build (lint + build) npm run build ``` **Test Coverage:** 100% coverage across all modules ```console ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- All files | 100 | 100 | 100 | 100 | lru.js | 100 | 100 | 100 | 100 | ----------|---------|----------|---------|---------|------------------- ``` ### Contributing #### Quick Start for Contributors ```bash # Clone and setup git clone https://github.com/avoidwork/tiny-lru.git cd tiny-lru npm install # Run tests npm test # Run linting npm run lint # Run benchmarks npm run benchmark:all # Build distribution files npm run build ``` #### Development Workflow 1. **Fork** the repository on GitHub 2. **Clone** your fork locally 3. **Create** a feature branch: `git checkout -b feature/amazing-feature` 4. **Develop** your changes with tests 5. **Test** thoroughly: `npm test && npm run lint` 6. **Commit** using conventional commits: `git commit -m "feat: add amazing feature"` 7. **Push** to your fork: `git push origin feature/amazing-feature` 8. **Submit** a Pull Request #### Contribution Guidelines - **Code Quality**: Follow ESLint rules and existing code style - **Testing**: Maintain 100% test coverage for all changes - **Documentation**: Update README.md and JSDoc for API changes - **Performance**: Benchmark changes that could impact performance - **Compatibility**: Ensure Node.js ≥12 compatibility - **Commit Messages**: Use [Conventional Commits](https://conventionalcommits.org/) format --- ## 📖 API Reference ### Factory Function #### lru(max, ttl, resetTtl) Creates a new LRU cache instance using the factory function. **Parameters:** - `max` `{Number}` - Maximum number of items to store (default: 1000; 0 = unlimited) - `ttl` `{Number}` - Time-to-live in milliseconds (default: 0; 0 = no expiration) - `resetTtl` `{Boolean}` - Reset TTL when updating existing items via `set()` (default: false) **Returns:** `{LRU}` New LRU cache instance **Throws:** `{TypeError}` When parameters are invalid ```javascript import {lru} from "tiny-lru"; // Basic cache const cache = lru(100); // With TTL const cacheWithTtl = lru(50, 30000); // 30 second TTL // With resetTtl enabled for set() const resetCache = lru(25, 10000, true); // Validation errors lru(-1); // TypeError: Invalid max value lru(100, -1); // TypeError: Invalid ttl value lru(100, 0, "no"); // TypeError: Invalid resetTtl value ``` ### 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 when updating existing items via `set()` ```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 ``` #### 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]] ``` #### 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 ``` #### 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 ``` #### 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 Note: `get()` does not reset or extend TTL. TTL is only reset on `set()` when `resetTtl` is `true`. ```javascript cache.set('key1', 'value1'); console.log(cache.get('key1')); // 'value1' console.log(cache.get('nonexistent')); // undefined ``` #### 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 ``` #### 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'] ``` #### 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'); ``` #### 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); } ``` #### 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] ``` --- ## 📄 License Copyright (c) 2025 Jason Mulligan Licensed under the BSD-3 license.