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
Markdown
# 🚀 Tiny LRU
[](https://badge.fury.io/js/tiny-lru)
[](https://nodejs.org/)
[](https://opensource.org/licenses/BSD-3-Clause)
[](https://github.com/avoidwork/tiny-lru/actions)
[](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.