diff --git a/.cursor/rules/javascript.mdc b/.cursor/rules/javascript.mdc
deleted file mode 100644
index 78c6287..0000000
--- a/.cursor/rules/javascript.mdc
+++ /dev/null
@@ -1,14 +0,0 @@
----
-description: JavaScript module
-globs:
-alwaysApply: true
----
-
-- Use JSDoc standard for creating docblocks of functions and classes.
-- Always use camelCase for function names.
-- Always use upper-case snake_case for constants.
-- Create integration tests in 'tests/integration' that use node-assert, which run with mocha.
-- Create unit tests in 'tests/unit' that use node-assert, which run with mocha.
-- Use node.js community "Best Practices".
-- Adhere to DRY, KISS, YAGNI, & SOLID principles
-- Adhere to OWASP security guidance
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..5a6cd4b
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,77 @@
+# AGENTS.md
+
+## Project Overview
+
+`tiny-lru` is a high-performance, lightweight LRU (Least Recently Used) cache library for JavaScript with O(1) operations and optional TTL support.
+
+## Setup Commands
+
+```bash
+npm install # Install dependencies
+npm run build # Lint and build (runs lint then rollup)
+npm run rollup # Build with rollup
+npm run test # Run lint and tests
+npm run mocha # Run tests with coverage (c8)
+npm run fmt # Format code with oxfmt
+npm run lint # Lint code with oxlint
+```
+
+## Development Workflow
+
+Source code is in `src/`.
+
+- **lint**: Check and fix linting issues with oxlint
+- **fmt**: Format code with oxfmt
+- **build**: Lint + rollup build
+- **mocha**: Run test suite with coverage reporting
+
+## Project Structure
+
+```
+├── src/lru.js # Main LRU cache implementation
+├── tests/ # Test files
+├── benchmarks/ # Performance benchmarks
+├── dist/ # Built distribution files
+├── types/ # TypeScript definitions
+├── docs/ # Documentation
+├── rollup.config.js # Build configuration
+└── package.json # Project configuration
+```
+
+## Code Style
+
+- Indentation: Tabs
+- Quotes: Double quotes
+- Semicolons: Required
+- Array constructor: Avoid `new Array()` (oxlint will warn)
+
+## API Reference
+
+- `lru(max, ttl, resetTtl)` - Factory function to create cache
+- `LRU` class - Direct instantiation with `new LRU(max, ttl, resetTtl)`
+- Key methods: `get()`, `set()`, `delete()`, `has()`, `clear()`, `evict()`
+- Array methods: `keys()`, `values()`, `entries()`
+- Properties: `first`, `last`, `max`, `size`, `ttl`, `resetTtl`
+
+## Testing
+
+- Framework: Node.js built-in test runner (`node --test`)
+- Coverage: 100% (c8)
+- Test pattern: `tests/**/*.js`
+- All tests must pass with 100% coverage before merging
+- Run: `npm test` (lint + tests)
+
+## Common Issues to Avoid
+
+- **Memory leaks**: When removing items from the linked list, always clear `prev`/`next` pointers to allow garbage collection
+- **LRU order pollution**: Methods like `entries()` and `values()` should access items directly rather than calling `get()`, which moves items and can delete expired items mid-iteration
+- **TTL edge cases**: Direct property access (`this.items[key]`) should be used instead of `has()` when you need to inspect expired-but-not-yet-deleted items
+- **Dead code**: Always verify edge case code is actually reachable before adding special handling
+- **Constructor assignment**: Use `let` not `const` for variables that may be reassigned (e.g., in `setWithEvicted`)
+
+## Implementation Notes
+
+- The LRU uses a doubly-linked list with `first` and `last` pointers for O(1) operations
+- TTL is stored per-item as an `expiry` timestamp; `0` means no expiration
+- `moveToEnd()` is the core method for maintaining LRU order - item is always moved to the `last` position
+- `setWithEvicted()` optimizes updates by avoiding the full `set()` path for existing keys
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..43c994c
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/README.md b/README.md
index 75d00f8..e3a1539 100644
--- a/README.md
+++ b/README.md
@@ -1,264 +1,125 @@
-# 🚀 Tiny LRU
+# 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 fast, lightweight LRU (Least Recently Used) cache for JavaScript with O(1) operations and optional TTL support.
-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.
+## What is an LRU Cache?
-## 📦 Installation
+Think of an LRU cache like a limited-size bookshelf. When you add a new book and the shelf is full, you remove the **least recently used** book to make room. Every time you read a book, it moves to the front. This pattern is perfect for caching where you want to keep the most frequently accessed items.
+
+## Installation
```bash
npm install tiny-lru
-# or
-yarn add tiny-lru
-# or
-pnpm add tiny-lru
```
-**Requirements:** Node.js ≥12
+Requires Node.js ≥12.
-## ⚡ Quick Start
+## 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.
+import { lru } from "tiny-lru";
-## 📊 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
+// Create a cache that holds up to 100 items
+const cache = lru(100);
-### Running Your Own Benchmarks
+// Store and retrieve data
+cache.set("user:42", { name: "Alice", score: 1500 });
+const user = cache.get("user:42"); // { name: "Alice", score: 1500 }
-```bash
-# Run all performance benchmarks
-npm run benchmark:all
+// Chain operations
+cache.set("a", 1).set("b", 2).set("c", 3);
-# Individual benchmark suites
-npm run benchmark:modern # Comprehensive Tinybench suite
-npm run benchmark:perf # Performance measurements
-npm run benchmark:comparison # Compare against other LRU libraries
+// Check what's in the cache
+cache.has("a"); // true
+cache.size; // 3
+cache.keys(); // ['a', 'b', 'c'] (LRU order)
```
-## 🚀 Getting Started
-
-### Installation
-
-```bash
-npm install tiny-lru
-# or
-yarn add tiny-lru
-# or
-pnpm add tiny-lru
-```
+## With TTL (Time-to-Live)
-### Quick Examples
+Items can automatically expire after a set time:
```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
+// Cache that expires after 5 seconds
+const sessionCache = lru(100, 5000);
-// 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
+sessionCache.set("session:id", { userId: 123 });
+// After 5 seconds, this returns undefined
+sessionCache.get("session:id");
```
-### CDN Usage (Browser)
-
-```html
-
-
-
-
-
-
+Want TTL to reset when you update an item? Enable `resetTtl`:
+
+```javascript
+const cache = lru(100, 60000, true); // 1 minute TTL, resets on update
+cache.set("key", "value");
+cache.set("key", "new value"); // TTL resets
```
-### TypeScript Usage
+## When to Use Tiny LRU
-```typescript
-import {lru, LRU} from "tiny-lru";
+**Great for:**
+- API response caching
+- Function memoization
+- Session storage with expiration
+- Rate limiting
+- Any scenario where you want to limit memory usage
-// Type-safe cache
-const cache = lru(100);
-// or: const cache: LRU = lru(100);
-cache.set('user:123', 'John Doe');
-const user: string | undefined = cache.get('user:123');
+**Not ideal for:**
+- Non-string keys (works best with strings)
+- Very large caches (consider a database)
-// Class inheritance
-class MyCache extends LRU {
- constructor() {
- super(1000, 60000, true); // 1000 items, 1 min TTL, reset TTL on set
- }
-}
-```
+## API Reference
-### Configuration Options
+### Factory Function: `lru(max?, ttl?, resetTtl?)`
-#### Factory Function
```javascript
-import {lru} from "tiny-lru";
+import { lru } from "tiny-lru";
-const cache = lru(max, ttl = 0, resetTtl = false);
+const cache = lru(); // 1000 items, no TTL
+const cache = lru(500); // 500 items, no TTL
+const cache = lru(100, 30000); // 100 items, 30s TTL
+const cache = lru(100, 60000, true); // with resetTtl enabled
```
-**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: `new LRU(max?, ttl?, resetTtl?)`
-#### Class Constructor
```javascript
-import {LRU} from "tiny-lru";
+import { LRU } from "tiny-lru";
-const cache = new LRU(1000, 60000, true); // 1000 items, 1 min TTL, reset TTL on set
+const cache = new LRU(100, 5000);
```
-#### 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);
+### Methods
-// 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;
-}
+| Method | Description |
+|--------|-------------|
+| `set(key, value)` | Store a value. Returns `this` for chaining. |
+| `get(key)` | Retrieve a value. Moves item to most recent. |
+| `has(key)` | Check if key exists and is not expired. |
+| `delete(key)` | Remove an item. Returns `this` for chaining. |
+| `clear()` | Remove all items. Returns `this` for chaining. |
+| `evict()` | Remove the least recently used item. |
+| `keys()` | Get all keys in LRU order (oldest first). |
+| `values(keys?)` | Get all values, or values for specific keys. |
+| `entries(keys?)` | Get `[key, value]` pairs in LRU order. |
+| `expiresAt(key)` | Get expiration timestamp for a key. |
-// 4. Clean up when needed
-process.on('exit', () => {
- cache.clear(); // Help garbage collection
-});
-```
+### Properties
-#### 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
+| Property | Type | Description |
+|----------|------|-------------|
+| `size` | number | Current number of items |
+| `max` | number | Maximum items allowed |
+| `ttl` | number | Time-to-live in milliseconds |
+| `first` | object | Least recently used item |
+| `last` | object | Most recently used item |
-## 💡 Real-World Examples
+## Common Patterns
-### API Response Caching
+### Memoization
```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);
@@ -269,386 +130,106 @@ function memoize(fn, maxSize = 100) {
return cache.get(key);
}
- const result = fn.apply(this, args);
+ const result = fn(...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)
+// Cache expensive computations
+const fib = memoize(n => n <= 1 ? n : fib(n - 1) + fib(n - 2), 50);
+fib(100); // fast - cached
+fib(100); // even faster - from cache
```
-### Session Management
+### Cache-Aside Pattern
```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);
+async function getUser(userId) {
+ const cache = lru(1000, 60000); // 1 minute cache
+
+ // Check cache first
+ const cached = cache.get(`user:${userId}`);
+ if (cached) {
+ return cached;
}
+
+ // Fetch from database
+ const user = await db.users.findById(userId);
+
+ // Store in cache
+ cache.set(`user:${userId}`, user);
+
+ return user;
}
```
-
-
-## 🔗 Interoperability
-
-Compatible with Lodash's `memoize` function cache interface:
+### Finding What Was Evicted
```javascript
-import _ from "lodash";
-import {lru} from "tiny-lru";
+const cache = lru(3);
+cache.set("a", 1).set("b", 2).set("c", 3);
-_.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
+const evicted = cache.setWithEvicted("d", 4);
+console.log(evicted); // { key: 'a', value: 1, expiry: 0 }
-# 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 |
-----------|---------|----------|---------|---------|-------------------
+cache.keys(); // ['b', 'c', 'd']
```
-### 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
+## Advanced Usage
----
-
-## 📖 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
+### Batch Operations with Keys
```javascript
-import {lru} from "tiny-lru";
-
-// Basic cache
const cache = lru(100);
+cache.set("users:1", { name: "Alice" });
+cache.set("users:2", { name: "Bob" });
+cache.set("users:3", { name: "Carol" });
-// 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 values for specific keys
+const values = cache.values(["users:3", "users:1"]);
+// ['Carol', 'Alice'] - maintains LRU order
```
-#### 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`.
+### Interop with Lodash
```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
+import _ from "lodash";
+import { lru } from "tiny-lru";
-**Returns:** `{Object}` LRU instance
+_.memoize.Cache = lru().constructor;
-```javascript
-cache.set('key1', 'value1')
- .set('key2', 'value2')
- .set('key3', 'value3');
+const slowFunc = _.memoize(expensiveOperation);
+slowFunc.cache.max = 100; // Configure cache size
```
-#### setWithEvicted(key, value)
-Stores item and returns evicted item if cache was full.
+## Performance
-**Parameters:**
-- `key` `{String}` - Item key
-- `value` `{*}` - Item value
+All core operations are O(1):
+- **Set**: Add or update items
+- **Get**: Retrieve and promote to most recent
+- **Delete**: Remove items
+- **Has**: Quick existence check
-**Returns:** `{Object|null}` Evicted item `{key, value, expiry, prev, next}` or null
+## Development
-```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);
-}
+```bash
+npm install # Install dependencies
+npm test # Run lint and tests
+npm run lint # Lint and check formatting
+npm run fix # Fix lint and formatting issues
+npm run build # Build distribution files
+npm run coverage # Generate test coverage report
```
-#### 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]
-```
+## Test Coverage
----
+| Metric | Coverage |
+|--------|----------|
+| Lines | 100% |
+| Branches | 95% |
+| Functions | 100% |
-## 📄 License
+## License
-Copyright (c) 2026 Jason Mulligan
-Licensed under the BSD-3 license.
+BSD-3-Clause
diff --git a/benchmarks/README.md b/benchmarks/README.md
index c6d16b6..dc9247e 100644
--- a/benchmarks/README.md
+++ b/benchmarks/README.md
@@ -16,6 +16,7 @@ This directory contains modern benchmark implementations for the tiny-lru librar
- Realistic workload scenarios without measuring setup/teardown
**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)
@@ -26,7 +27,7 @@ This directory contains modern benchmark implementations for the tiny-lru librar
**Comprehensive comparison against other popular LRU cache libraries**
-- **Libraries Tested**:
+- **Libraries Tested**:
- `tiny-lru` (this library)
- `lru-cache` (most popular npm LRU implementation)
- `quick-lru` (fast, lightweight alternative)
@@ -39,6 +40,7 @@ This directory contains modern benchmark implementations for the tiny-lru librar
- Multiple operations per measured callback to reduce harness overhead
**Test Categories**:
+
- SET operations across all libraries
- GET operations with pre-populated caches
- DELETE operations comparison
@@ -59,6 +61,7 @@ This directory contains modern benchmark implementations for the tiny-lru librar
- Deterministic mixed workloads (no `Math.random()` in measured loops)
**Test Categories**:
+
- Performance Observer based function timing
- Custom timer with statistical analysis
- Scalability tests (100 to 10,000 cache sizes)
@@ -111,6 +114,7 @@ node benchmarks/comparison-benchmark.js
## Understanding the Results
### Tinybench Output
+
```
┌─────────┬─────────────────────────────┬─────────────────┬────────────────────┬──────────┬─────────┐
│ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
@@ -124,6 +128,7 @@ node benchmarks/comparison-benchmark.js
- **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 │
@@ -132,6 +137,7 @@ node benchmarks/comparison-benchmark.js
```
### Comparison Benchmark Output
+
```
📊 SET Operations Benchmark
┌─────────┬─────────────────────────────┬─────────────────┬────────────────────┬──────────┬─────────┐
@@ -155,71 +161,88 @@ Memory Usage Results:
## Benchmark Categories Explained
### SET Operations
+
Tests cache write performance under various conditions:
+
- **Empty cache**: Setting items in a fresh cache
- **Full cache**: Setting items when cache is at capacity (triggers eviction)
- **Random vs Sequential**: Different access patterns
-
+
Implementation details:
+
- Deterministic keys/values are pre-generated once per run
- Access indices are precomputed via a fast PRNG (xorshift) to avoid runtime randomness
- Multiple operations are executed per benchmark callback to minimize harness overhead
-### GET Operations
+### GET Operations
+
Tests cache read performance:
+
- **Cache hits**: Reading existing items
- **Cache misses**: Reading non-existent items
- **Mixed patterns**: Realistic 80% hit / 20% miss scenarios
-
+
Implementation details:
+
- Caches are pre-populated outside the measured section
- Access indices are precomputed; no `Math.random()` inside measured loops
### Mixed Operations
+
Real-world usage simulation:
+
- **80/20 read-write**: Typical web application pattern
- **Cache warming**: Sequential population scenarios
- **High churn**: Frequent eviction scenarios
- **LRU access patterns**: Testing LRU algorithm efficiency
-
+
Implementation details:
+
- Choice and index streams are precomputed
- No wall-clock calls (`Date.now`) inside hot paths
### Special Operations
+
Edge cases and additional functionality:
+
- **Delete operations**: Individual item removal
- **Clear operations**: Complete cache clearing
- **Different data types**: Numbers, objects, strings
- **Memory usage**: Heap consumption analysis
-
+
Implementation details:
+
- Delete benchmarks maintain a steady state by re-adding deleted keys to keep cardinality stable
## Best Practices Implemented
### 1. Statistical Significance
+
- Minimum execution time (1 second) for reliable results
- Multiple iterations for statistical validity
- Standard deviation and margin of error reporting
### 2. Realistic Test Data
+
- Variable key/value sizes mimicking real applications
- Deterministic pseudo-random and sequential access patterns (precomputed)
- Pre-population scenarios for realistic cache states
### 3. Multiple Measurement Approaches
+
- **Tinybench**: Modern, accurate micro-benchmarking
- **Performance Observer**: Native Node.js function timing
- **Custom timers**: High-resolution manual timing
### 4. Comprehensive Coverage
+
- Different cache sizes (100, 1K, 5K, 10K)
- Various workload patterns
- Memory consumption analysis
- Edge case testing
### 5. Methodology Improvements (Current)
+
- Setup/teardown moved outside measured sections to avoid skewing results
- Deterministic data and access patterns (no randomness in hot paths)
- Batched operations per invocation reduce harness overhead reliably across tasks
@@ -228,6 +251,7 @@ Implementation details:
## Performance Tips
### For accurate results:
+
1. **Close other applications** to reduce system noise
2. **Run multiple times** and compare results
3. **Use consistent hardware** for comparisons
@@ -235,13 +259,15 @@ Implementation details:
5. **Consider CPU frequency scaling** on laptops
### Environment information included:
+
- Node.js version
-- Platform and architecture
+- Platform and architecture
- Timestamp for result tracking
## Interpreting Results
### Good Performance Indicators:
+
- ✅ **Consistent ops/sec** across runs
- ✅ **Low margin of error** (< 5%)
- ✅ **Reasonable standard deviation**
@@ -249,6 +275,7 @@ Implementation details:
- ✅ **Cache hits faster than misses**
### Warning Signs:
+
- ⚠️ **High margin of error** (> 10%)
- ⚠️ **Widely varying results** between runs
- ⚠️ **Memory usage growing unexpectedly**
@@ -260,16 +287,17 @@ To add new benchmark scenarios:
```javascript
// In modern-benchmark.js
-bench.add('your-test-name', () => {
+bench.add("your-test-name", () => {
// Your test code here
const cache = lru(1000);
- cache.set('key', 'value');
+ cache.set("key", "value");
});
```
## Contributing
When adding new benchmarks:
+
1. Follow the existing naming conventions
2. Include proper setup/teardown
3. Add statistical significance checks
@@ -279,6 +307,7 @@ When adding new benchmarks:
## Benchmark Results Archive
Consider saving benchmark results with:
+
```bash
# Save all benchmark results
npm run benchmark:all > results/benchmark-$(date +%Y%m%d-%H%M%S).txt
@@ -296,19 +325,22 @@ This helps track performance improvements/regressions over time.
Choose the right benchmark for your needs:
### Use `modern-benchmark.js` when:
+
- ✅ You want comprehensive analysis of tiny-lru performance
- ✅ You need statistical significance and margin of error data
- ✅ You're testing different cache sizes and workload patterns
- ✅ You want realistic scenario testing
### Use `comparison-benchmark.js` when:
+
- ✅ You're evaluating tiny-lru against other LRU libraries
- ✅ You need bundle size and memory usage comparisons
- ✅ You want to see competitive performance analysis
- ✅ You're making library selection decisions
### Use `performance-observer-benchmark.js` when:
+
- ✅ You need native Node.js performance measurement
- ✅ You want function-level timing analysis
- ✅ You're testing scalability across different cache sizes
-- ✅ You prefer Performance API over external libraries
\ No newline at end of file
+- ✅ You prefer Performance API over external libraries
diff --git a/benchmarks/comparison-benchmark.js b/benchmarks/comparison-benchmark.js
index 69cb2cd..2cde8be 100644
--- a/benchmarks/comparison-benchmark.js
+++ b/benchmarks/comparison-benchmark.js
@@ -12,29 +12,29 @@ import { lru as tinyLru } from "../src/lru.js";
let LRUCache, QuickLRU, MnemonistLRU;
try {
- const lruCacheModule = await import("lru-cache");
- LRUCache = lruCacheModule.LRUCache || lruCacheModule.default;
+ const lruCacheModule = await import("lru-cache");
+ LRUCache = lruCacheModule.LRUCache || lruCacheModule.default;
} catch {
- console.error("lru-cache not found. Run: npm install --no-save lru-cache");
- process.exit(1);
+ console.error("lru-cache not found. Run: npm install --no-save lru-cache");
+ process.exit(1);
}
try {
- const quickLruModule = await import("quick-lru");
- QuickLRU = quickLruModule.default;
+ const quickLruModule = await import("quick-lru");
+ QuickLRU = quickLruModule.default;
} catch {
- console.error("quick-lru not found. Run: npm install --no-save quick-lru");
- process.exit(1);
+ console.error("quick-lru not found. Run: npm install --no-save quick-lru");
+ process.exit(1);
}
try {
- // Import from mnemonist using the correct export pattern
- const mnemonistModule = await import("mnemonist");
- MnemonistLRU = mnemonistModule.LRUCacheWithDelete;
+ // Import from mnemonist using the correct export pattern
+ const mnemonistModule = await import("mnemonist");
+ MnemonistLRU = mnemonistModule.LRUCacheWithDelete;
} catch (error) {
- console.error("mnemonist not found. Run: npm install --no-save mnemonist");
- console.error("Error:", error.message);
- process.exit(1);
+ console.error("mnemonist not found. Run: npm install --no-save mnemonist");
+ console.error("Error:", error.message);
+ process.exit(1);
}
// Configuration
@@ -49,20 +49,20 @@ const OPS_PER_INVOCATION = 50; // Do multiple ops per call to reduce harness ove
* @param {number} count - Number of items to generate
* @returns {{keys: string[], values: Array<{id:number,data:string,nested:{foo:string,baz:number}}>} }
*/
-function generateTestData (count) {
- const keys = new Array(count);
- const values = new Array(count);
-
- for (let i = 0; i < count; i++) {
- keys[i] = `key_${i}`;
- values[i] = {
- id: i,
- data: `value_${i}`,
- nested: { foo: "bar", baz: i }
- };
- }
-
- return { keys, values };
+function generateTestData(count) {
+ const keys = Array.from({ length: count });
+ const values = Array.from({ length: count });
+
+ for (let i = 0; i < count; i++) {
+ keys[i] = `key_${i}`;
+ values[i] = {
+ id: i,
+ data: `value_${i}`,
+ nested: { foo: "bar", baz: i },
+ };
+ }
+
+ return { keys, values };
}
/**
@@ -72,31 +72,33 @@ function generateTestData (count) {
* @param {number} modulo - Upper bound for indices
* @returns {Uint32Array}
*/
-function generateAccessPattern (length, modulo) {
- const pattern = new Uint32Array(length);
- let x = 123456789;
- let y = 362436069;
- // Xorshift-based fast PRNG to avoid using Math.random()
- for (let i = 0; i < length; i++) {
- x ^= x << 13; x ^= x >>> 17; x ^= x << 5;
- y = y + 1 >>> 0;
- const n = x + y >>> 0;
- pattern[i] = n % modulo;
- }
-
- return pattern;
+function generateAccessPattern(length, modulo) {
+ const pattern = new Uint32Array(length);
+ let x = 123456789;
+ let y = 362436069;
+ // Xorshift-based fast PRNG to avoid using Math.random()
+ for (let i = 0; i < length; i++) {
+ x ^= x << 13;
+ x ^= x >>> 17;
+ x ^= x << 5;
+ y = (y + 1) >>> 0;
+ const n = (x + y) >>> 0;
+ pattern[i] = n % modulo;
+ }
+
+ return pattern;
}
// Initialize caches
-function createCaches () {
- return {
- "tiny-lru": tinyLru(CACHE_SIZE),
- "tiny-lru-ttl": tinyLru(CACHE_SIZE, TTL_MS),
- "lru-cache": new LRUCache({ max: CACHE_SIZE }),
- "lru-cache-ttl": new LRUCache({ max: CACHE_SIZE, ttl: TTL_MS }),
- "quick-lru": new QuickLRU({ maxSize: CACHE_SIZE }),
- "mnemonist": new MnemonistLRU(CACHE_SIZE)
- };
+function createCaches() {
+ return {
+ "tiny-lru": tinyLru(CACHE_SIZE),
+ "tiny-lru-ttl": tinyLru(CACHE_SIZE, TTL_MS),
+ "lru-cache": new LRUCache({ max: CACHE_SIZE }),
+ "lru-cache-ttl": new LRUCache({ max: CACHE_SIZE, ttl: TTL_MS }),
+ "quick-lru": new QuickLRU({ maxSize: CACHE_SIZE }),
+ mnemonist: new MnemonistLRU(CACHE_SIZE),
+ };
}
// Memory usage helper
@@ -106,420 +108,446 @@ function createCaches () {
* @param {boolean} force - Whether to call global.gc() if available
* @returns {NodeJS.MemoryUsage}
*/
-function getMemoryUsage (force = false) {
- if (force && global.gc) {
- global.gc();
- }
+function getMemoryUsage(force = false) {
+ if (force && global.gc) {
+ global.gc();
+ }
- return process.memoryUsage();
+ return process.memoryUsage();
}
// Calculate memory per item
-function calculateMemoryPerItem (beforeMem, afterMem, itemCount) {
- const heapDiff = afterMem.heapUsed - beforeMem.heapUsed;
+function calculateMemoryPerItem(beforeMem, afterMem, itemCount) {
+ const heapDiff = afterMem.heapUsed - beforeMem.heapUsed;
- return Math.round(heapDiff / itemCount);
+ return Math.round(heapDiff / itemCount);
}
// Bundle size estimation (approximate)
const bundleSizes = {
- "tiny-lru": "2.1KB",
- "lru-cache": "~15KB",
- "quick-lru": "~1.8KB",
- "mnemonist": "~45KB"
+ "tiny-lru": "2.1KB",
+ "lru-cache": "~15KB",
+ "quick-lru": "~1.8KB",
+ mnemonist: "~45KB",
};
-async function runBenchmarks () {
- console.log("🚀 LRU Cache Library Comparison Benchmark\n");
- console.log(`Cache Size: ${CACHE_SIZE} items`);
- console.log(`Iterations: ${ITERATIONS.toLocaleString()}`);
- console.log(`Node.js: ${process.version}`);
- console.log(`Platform: ${process.platform} ${process.arch}\n`);
-
- const testData = generateTestData(ITERATIONS);
- const setPattern = generateAccessPattern(ITERATIONS, testData.keys.length);
- const getPattern = generateAccessPattern(ITERATIONS, Math.min(CACHE_SIZE, 500));
- const updatePattern = generateAccessPattern(ITERATIONS, 100);
- const deletePattern = generateAccessPattern(ITERATIONS, 50);
-
- // SET operations benchmark
- console.log("📊 SET Operations Benchmark");
- console.log("=" .repeat(50));
-
- const setBench = new Bench({ time: 2000 });
-
- // Dedicated caches and state for SET to avoid measuring setup per-iteration
- const setCaches = createCaches();
- const setState = {
- "tiny-lru": 0,
- "tiny-lru-ttl": 0,
- "lru-cache": 0,
- "lru-cache-ttl": 0,
- "quick-lru": 0,
- "mnemonist": 0
- };
-
- setBench
- .add("tiny-lru set", () => {
- const cache = setCaches["tiny-lru"];
- let i = setState["tiny-lru"]; // cursor
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = setPattern[i++ % setPattern.length];
- cache.set(testData.keys[idx], testData.values[idx]);
- }
- setState["tiny-lru"] = i;
- })
- .add("tiny-lru-ttl set", () => {
- const cache = setCaches["tiny-lru-ttl"];
- let i = setState["tiny-lru-ttl"];
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = setPattern[i++ % setPattern.length];
- cache.set(testData.keys[idx], testData.values[idx]);
- }
- setState["tiny-lru-ttl"] = i;
- })
- .add("lru-cache set", () => {
- const cache = setCaches["lru-cache"];
- let i = setState["lru-cache"];
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = setPattern[i++ % setPattern.length];
- cache.set(testData.keys[idx], testData.values[idx]);
- }
- setState["lru-cache"] = i;
- })
- .add("lru-cache-ttl set", () => {
- const cache = setCaches["lru-cache-ttl"];
- let i = setState["lru-cache-ttl"];
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = setPattern[i++ % setPattern.length];
- cache.set(testData.keys[idx], testData.values[idx]);
- }
- setState["lru-cache-ttl"] = i;
- })
- .add("quick-lru set", () => {
- const cache = setCaches["quick-lru"];
- let i = setState["quick-lru"];
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = setPattern[i++ % setPattern.length];
- cache.set(testData.keys[idx], testData.values[idx]);
- }
- setState["quick-lru"] = i;
- })
- .add("mnemonist set", () => {
- const cache = setCaches.mnemonist;
- let i = setState.mnemonist;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = setPattern[i++ % setPattern.length];
- cache.set(testData.keys[idx], testData.values[idx]);
- }
- setState.mnemonist = i;
- });
-
- await setBench.run();
- console.table(setBench.table());
-
- // GET operations benchmark (with pre-populated caches)
- console.log("\n📊 GET Operations Benchmark");
- console.log("=" .repeat(50));
-
- const caches = createCaches();
-
- // Pre-populate all caches deterministically
- const prepopulated = Math.min(CACHE_SIZE, 500);
- Object.values(caches).forEach(cache => {
- for (let i = 0; i < prepopulated; i++) {
- cache.set(testData.keys[i], testData.values[i]);
- }
- });
-
- const getBench = new Bench({ time: 2000 });
- const getState = { idx: 0 };
-
- getBench
- .add("tiny-lru get", () => {
- let i = getState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = getPattern[i++ % getPattern.length];
- caches["tiny-lru"].get(testData.keys[idx]);
- }
- getState.idx = i;
- })
- .add("tiny-lru-ttl get", () => {
- let i = getState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = getPattern[i++ % getPattern.length];
- caches["tiny-lru-ttl"].get(testData.keys[idx]);
- }
- getState.idx = i;
- })
- .add("lru-cache get", () => {
- let i = getState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = getPattern[i++ % getPattern.length];
- caches["lru-cache"].get(testData.keys[idx]);
- }
- getState.idx = i;
- })
- .add("lru-cache-ttl get", () => {
- let i = getState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = getPattern[i++ % getPattern.length];
- caches["lru-cache-ttl"].get(testData.keys[idx]);
- }
- getState.idx = i;
- })
- .add("quick-lru get", () => {
- let i = getState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = getPattern[i++ % getPattern.length];
- caches["quick-lru"].get(testData.keys[idx]);
- }
- getState.idx = i;
- })
- .add("mnemonist get", () => {
- let i = getState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = getPattern[i++ % getPattern.length];
- caches.mnemonist.get(testData.keys[idx]);
- }
- getState.idx = i;
- });
-
- await getBench.run();
- console.table(getBench.table());
-
- // DELETE operations benchmark
- console.log("\n📊 DELETE Operations Benchmark");
- console.log("=" .repeat(50));
-
- const deleteBench = new Bench({ time: 2000 });
-
- // Dedicated caches and state for DELETE
- const deleteCaches = {
- "tiny-lru": tinyLru(CACHE_SIZE),
- "lru-cache": new LRUCache({ max: CACHE_SIZE }),
- "quick-lru": new QuickLRU({ maxSize: CACHE_SIZE }),
- "mnemonist": new MnemonistLRU(CACHE_SIZE)
- };
- const deleteState = { idx: 0 };
-
- // Pre-populate
- Object.values(deleteCaches).forEach(cache => {
- for (let i = 0; i < 100; i++) {
- cache.set(testData.keys[i], testData.values[i]);
- }
- });
-
- deleteBench
- .add("tiny-lru delete", () => {
- let i = deleteState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = deletePattern[i++ % deletePattern.length];
- deleteCaches["tiny-lru"].delete(testData.keys[idx]);
- // Re-add to keep steady state for future deletes
- deleteCaches["tiny-lru"].set(testData.keys[idx], testData.values[idx]);
- }
- deleteState.idx = i;
- })
- .add("lru-cache delete", () => {
- let i = deleteState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = deletePattern[i++ % deletePattern.length];
- deleteCaches["lru-cache"].delete(testData.keys[idx]);
- deleteCaches["lru-cache"].set(testData.keys[idx], testData.values[idx]);
- }
- deleteState.idx = i;
- })
- .add("quick-lru delete", () => {
- let i = deleteState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = deletePattern[i++ % deletePattern.length];
- deleteCaches["quick-lru"].delete(testData.keys[idx]);
- deleteCaches["quick-lru"].set(testData.keys[idx], testData.values[idx]);
- }
- deleteState.idx = i;
- })
- .add("mnemonist delete", () => {
- let i = deleteState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = deletePattern[i++ % deletePattern.length];
- deleteCaches.mnemonist.remove(testData.keys[idx]);
- deleteCaches.mnemonist.set(testData.keys[idx], testData.values[idx]);
- }
- deleteState.idx = i;
- });
-
- await deleteBench.run();
- console.table(deleteBench.table());
-
- // UPDATE operations benchmark
- console.log("\n📊 UPDATE Operations Benchmark");
- console.log("=" .repeat(50));
-
- const updateBench = new Bench({ time: 2000 });
-
- // Dedicated caches for UPDATE
- const updateCaches = createCaches();
- // Pre-populate with initial values
- Object.values(updateCaches).forEach(cache => {
- for (let i = 0; i < 100; i++) {
- cache.set(testData.keys[i], testData.values[i]);
- }
- });
-
- const updateState = { idx: 0 };
-
- updateBench
- .add("tiny-lru update", () => {
- let i = updateState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = updatePattern[i++ % updatePattern.length];
- updateCaches["tiny-lru"].set(testData.keys[idx], testData.values[(idx + 50) % testData.values.length]);
- }
- updateState.idx = i;
- })
- .add("tiny-lru-ttl update", () => {
- let i = updateState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = updatePattern[i++ % updatePattern.length];
- updateCaches["tiny-lru-ttl"].set(testData.keys[idx], testData.values[(idx + 50) % testData.values.length]);
- }
- updateState.idx = i;
- })
- .add("lru-cache update", () => {
- let i = updateState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = updatePattern[i++ % updatePattern.length];
- updateCaches["lru-cache"].set(testData.keys[idx], testData.values[(idx + 50) % testData.values.length]);
- }
- updateState.idx = i;
- })
- .add("lru-cache-ttl update", () => {
- let i = updateState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = updatePattern[i++ % updatePattern.length];
- updateCaches["lru-cache-ttl"].set(testData.keys[idx], testData.values[(idx + 50) % testData.values.length]);
- }
- updateState.idx = i;
- })
- .add("quick-lru update", () => {
- let i = updateState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = updatePattern[i++ % updatePattern.length];
- updateCaches["quick-lru"].set(testData.keys[idx], testData.values[(idx + 50) % testData.values.length]);
- }
- updateState.idx = i;
- })
- .add("mnemonist update", () => {
- let i = updateState.idx;
- for (let j = 0; j < OPS_PER_INVOCATION; j++) {
- const idx = updatePattern[i++ % updatePattern.length];
- updateCaches.mnemonist.set(testData.keys[idx], testData.values[(idx + 50) % testData.values.length]);
- }
- updateState.idx = i;
- });
-
- await updateBench.run();
- console.table(updateBench.table());
-
- // Memory usage analysis
- console.log("\n📊 Memory Usage Analysis");
- console.log("=" .repeat(50));
-
- const memoryResults = {};
- const testSize = 1000;
-
- for (const [name, cache] of Object.entries(createCaches())) {
- const beforeMem = getMemoryUsage(true);
-
- // Fill cache
- for (let i = 0; i < testSize; i++) {
- cache.set(testData.keys[i], testData.values[i]);
- }
-
- const afterMem = getMemoryUsage(true);
- const memoryPerItem = calculateMemoryPerItem(beforeMem, afterMem, testSize);
-
- memoryResults[name] = {
- totalMemory: afterMem.heapUsed - beforeMem.heapUsed,
- memoryPerItem: memoryPerItem,
- bundleSize: bundleSizes[name.split("-")[0]] || "N/A"
- };
- }
-
- console.log("\nMemory Usage Results:");
- console.log("┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐");
- console.log("│ Library │ Bundle Size │ Memory/Item │ Total Memory │");
- console.log("├─────────────────┼─────────────────┼─────────────────┼─────────────────┤");
-
- Object.entries(memoryResults).forEach(([name, data]) => {
- const nameCol = name.padEnd(15);
- const bundleCol = data.bundleSize.padEnd(15);
- const memoryCol = `${data.memoryPerItem} bytes`.padEnd(15);
- const totalCol = `${Math.round(data.totalMemory / 1024)} KB`.padEnd(15);
- console.log(`│ ${nameCol} │ ${bundleCol} │ ${memoryCol} │ ${totalCol} │`);
- });
-
- console.log("└─────────────────┴─────────────────┴─────────────────┴─────────────────┘");
-
- // Performance summary
- console.log("\n📊 Performance Summary");
- console.log("=" .repeat(50));
-
- const setResults = setBench.tasks.map(task => ({
- name: task.name,
- opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0
- }));
-
- const getResults = getBench.tasks.map(task => ({
- name: task.name,
- opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0
- }));
-
- const updateResults = updateBench.tasks.map(task => ({
- name: task.name,
- opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0
- }));
-
- const deleteResults = deleteBench.tasks.map(task => ({
- name: task.name,
- opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0
- }));
-
- console.log("\nOperations per second (higher is better):");
- console.log("┌─────────────────┬─────────────────┬─────────────────┬─────────────────┬─────────────────┐");
- console.log("│ Library │ SET ops/sec │ GET ops/sec │ UPDATE ops/sec │ DELETE ops/sec │");
- console.log("├─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤");
-
- // Group results by library
- const libraries = ["tiny-lru", "lru-cache", "quick-lru", "mnemonist"];
-
- libraries.forEach(lib => {
- const setResult = setResults.find(r => r.name.includes(lib));
- const getResult = getResults.find(r => r.name.includes(lib));
- const updateResult = updateResults.find(r => r.name.includes(lib));
- const deleteResult = deleteResults.find(r => r.name.includes(lib));
-
- if (setResult && getResult && updateResult && deleteResult) {
- const nameCol = lib.padEnd(15);
- const setCol = setResult.opsPerSec.toLocaleString().padEnd(15);
- const getCol = getResult.opsPerSec.toLocaleString().padEnd(15);
- const updateCol = updateResult.opsPerSec.toLocaleString().padEnd(15);
- const deleteCol = deleteResult.opsPerSec.toLocaleString().padEnd(15);
- console.log(`│ ${nameCol} │ ${setCol} │ ${getCol} │ ${updateCol} │ ${deleteCol} │`);
- }
- });
-
- console.log("└─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┘");
-
- console.log("\n✅ Benchmark completed!");
- console.log("\nTo regenerate this data, run: npm run benchmark:comparison");
+async function runBenchmarks() {
+ console.log("🚀 LRU Cache Library Comparison Benchmark\n");
+ console.log(`Cache Size: ${CACHE_SIZE} items`);
+ console.log(`Iterations: ${ITERATIONS.toLocaleString()}`);
+ console.log(`Node.js: ${process.version}`);
+ console.log(`Platform: ${process.platform} ${process.arch}\n`);
+
+ const testData = generateTestData(ITERATIONS);
+ const setPattern = generateAccessPattern(ITERATIONS, testData.keys.length);
+ const getPattern = generateAccessPattern(ITERATIONS, Math.min(CACHE_SIZE, 500));
+ const updatePattern = generateAccessPattern(ITERATIONS, 100);
+ const deletePattern = generateAccessPattern(ITERATIONS, 50);
+
+ // SET operations benchmark
+ console.log("📊 SET Operations Benchmark");
+ console.log("=".repeat(50));
+
+ const setBench = new Bench({ time: 2000 });
+
+ // Dedicated caches and state for SET to avoid measuring setup per-iteration
+ const setCaches = createCaches();
+ const setState = {
+ "tiny-lru": 0,
+ "tiny-lru-ttl": 0,
+ "lru-cache": 0,
+ "lru-cache-ttl": 0,
+ "quick-lru": 0,
+ mnemonist: 0,
+ };
+
+ setBench
+ .add("tiny-lru set", () => {
+ const cache = setCaches["tiny-lru"];
+ let i = setState["tiny-lru"]; // cursor
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = setPattern[i++ % setPattern.length];
+ cache.set(testData.keys[idx], testData.values[idx]);
+ }
+ setState["tiny-lru"] = i;
+ })
+ .add("tiny-lru-ttl set", () => {
+ const cache = setCaches["tiny-lru-ttl"];
+ let i = setState["tiny-lru-ttl"];
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = setPattern[i++ % setPattern.length];
+ cache.set(testData.keys[idx], testData.values[idx]);
+ }
+ setState["tiny-lru-ttl"] = i;
+ })
+ .add("lru-cache set", () => {
+ const cache = setCaches["lru-cache"];
+ let i = setState["lru-cache"];
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = setPattern[i++ % setPattern.length];
+ cache.set(testData.keys[idx], testData.values[idx]);
+ }
+ setState["lru-cache"] = i;
+ })
+ .add("lru-cache-ttl set", () => {
+ const cache = setCaches["lru-cache-ttl"];
+ let i = setState["lru-cache-ttl"];
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = setPattern[i++ % setPattern.length];
+ cache.set(testData.keys[idx], testData.values[idx]);
+ }
+ setState["lru-cache-ttl"] = i;
+ })
+ .add("quick-lru set", () => {
+ const cache = setCaches["quick-lru"];
+ let i = setState["quick-lru"];
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = setPattern[i++ % setPattern.length];
+ cache.set(testData.keys[idx], testData.values[idx]);
+ }
+ setState["quick-lru"] = i;
+ })
+ .add("mnemonist set", () => {
+ const cache = setCaches.mnemonist;
+ let i = setState.mnemonist;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = setPattern[i++ % setPattern.length];
+ cache.set(testData.keys[idx], testData.values[idx]);
+ }
+ setState.mnemonist = i;
+ });
+
+ await setBench.run();
+ console.table(setBench.table());
+
+ // GET operations benchmark (with pre-populated caches)
+ console.log("\n📊 GET Operations Benchmark");
+ console.log("=".repeat(50));
+
+ const caches = createCaches();
+
+ // Pre-populate all caches deterministically
+ const prepopulated = Math.min(CACHE_SIZE, 500);
+ Object.values(caches).forEach((cache) => {
+ for (let i = 0; i < prepopulated; i++) {
+ cache.set(testData.keys[i], testData.values[i]);
+ }
+ });
+
+ const getBench = new Bench({ time: 2000 });
+ const getState = { idx: 0 };
+
+ getBench
+ .add("tiny-lru get", () => {
+ let i = getState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = getPattern[i++ % getPattern.length];
+ caches["tiny-lru"].get(testData.keys[idx]);
+ }
+ getState.idx = i;
+ })
+ .add("tiny-lru-ttl get", () => {
+ let i = getState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = getPattern[i++ % getPattern.length];
+ caches["tiny-lru-ttl"].get(testData.keys[idx]);
+ }
+ getState.idx = i;
+ })
+ .add("lru-cache get", () => {
+ let i = getState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = getPattern[i++ % getPattern.length];
+ caches["lru-cache"].get(testData.keys[idx]);
+ }
+ getState.idx = i;
+ })
+ .add("lru-cache-ttl get", () => {
+ let i = getState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = getPattern[i++ % getPattern.length];
+ caches["lru-cache-ttl"].get(testData.keys[idx]);
+ }
+ getState.idx = i;
+ })
+ .add("quick-lru get", () => {
+ let i = getState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = getPattern[i++ % getPattern.length];
+ caches["quick-lru"].get(testData.keys[idx]);
+ }
+ getState.idx = i;
+ })
+ .add("mnemonist get", () => {
+ let i = getState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = getPattern[i++ % getPattern.length];
+ caches.mnemonist.get(testData.keys[idx]);
+ }
+ getState.idx = i;
+ });
+
+ await getBench.run();
+ console.table(getBench.table());
+
+ // DELETE operations benchmark
+ console.log("\n📊 DELETE Operations Benchmark");
+ console.log("=".repeat(50));
+
+ const deleteBench = new Bench({ time: 2000 });
+
+ // Dedicated caches and state for DELETE
+ const deleteCaches = {
+ "tiny-lru": tinyLru(CACHE_SIZE),
+ "lru-cache": new LRUCache({ max: CACHE_SIZE }),
+ "quick-lru": new QuickLRU({ maxSize: CACHE_SIZE }),
+ mnemonist: new MnemonistLRU(CACHE_SIZE),
+ };
+ const deleteState = { idx: 0 };
+
+ // Pre-populate
+ Object.values(deleteCaches).forEach((cache) => {
+ for (let i = 0; i < 100; i++) {
+ cache.set(testData.keys[i], testData.values[i]);
+ }
+ });
+
+ deleteBench
+ .add("tiny-lru delete", () => {
+ let i = deleteState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = deletePattern[i++ % deletePattern.length];
+ deleteCaches["tiny-lru"].delete(testData.keys[idx]);
+ // Re-add to keep steady state for future deletes
+ deleteCaches["tiny-lru"].set(testData.keys[idx], testData.values[idx]);
+ }
+ deleteState.idx = i;
+ })
+ .add("lru-cache delete", () => {
+ let i = deleteState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = deletePattern[i++ % deletePattern.length];
+ deleteCaches["lru-cache"].delete(testData.keys[idx]);
+ deleteCaches["lru-cache"].set(testData.keys[idx], testData.values[idx]);
+ }
+ deleteState.idx = i;
+ })
+ .add("quick-lru delete", () => {
+ let i = deleteState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = deletePattern[i++ % deletePattern.length];
+ deleteCaches["quick-lru"].delete(testData.keys[idx]);
+ deleteCaches["quick-lru"].set(testData.keys[idx], testData.values[idx]);
+ }
+ deleteState.idx = i;
+ })
+ .add("mnemonist delete", () => {
+ let i = deleteState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = deletePattern[i++ % deletePattern.length];
+ deleteCaches.mnemonist.remove(testData.keys[idx]);
+ deleteCaches.mnemonist.set(testData.keys[idx], testData.values[idx]);
+ }
+ deleteState.idx = i;
+ });
+
+ await deleteBench.run();
+ console.table(deleteBench.table());
+
+ // UPDATE operations benchmark
+ console.log("\n📊 UPDATE Operations Benchmark");
+ console.log("=".repeat(50));
+
+ const updateBench = new Bench({ time: 2000 });
+
+ // Dedicated caches for UPDATE
+ const updateCaches = createCaches();
+ // Pre-populate with initial values
+ Object.values(updateCaches).forEach((cache) => {
+ for (let i = 0; i < 100; i++) {
+ cache.set(testData.keys[i], testData.values[i]);
+ }
+ });
+
+ const updateState = { idx: 0 };
+
+ updateBench
+ .add("tiny-lru update", () => {
+ let i = updateState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = updatePattern[i++ % updatePattern.length];
+ updateCaches["tiny-lru"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
+ }
+ updateState.idx = i;
+ })
+ .add("tiny-lru-ttl update", () => {
+ let i = updateState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = updatePattern[i++ % updatePattern.length];
+ updateCaches["tiny-lru-ttl"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
+ }
+ updateState.idx = i;
+ })
+ .add("lru-cache update", () => {
+ let i = updateState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = updatePattern[i++ % updatePattern.length];
+ updateCaches["lru-cache"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
+ }
+ updateState.idx = i;
+ })
+ .add("lru-cache-ttl update", () => {
+ let i = updateState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = updatePattern[i++ % updatePattern.length];
+ updateCaches["lru-cache-ttl"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
+ }
+ updateState.idx = i;
+ })
+ .add("quick-lru update", () => {
+ let i = updateState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = updatePattern[i++ % updatePattern.length];
+ updateCaches["quick-lru"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
+ }
+ updateState.idx = i;
+ })
+ .add("mnemonist update", () => {
+ let i = updateState.idx;
+ for (let j = 0; j < OPS_PER_INVOCATION; j++) {
+ const idx = updatePattern[i++ % updatePattern.length];
+ updateCaches.mnemonist.set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
+ }
+ updateState.idx = i;
+ });
+
+ await updateBench.run();
+ console.table(updateBench.table());
+
+ // Memory usage analysis
+ console.log("\n📊 Memory Usage Analysis");
+ console.log("=".repeat(50));
+
+ const memoryResults = {};
+ const testSize = 1000;
+
+ for (const [name, cache] of Object.entries(createCaches())) {
+ const beforeMem = getMemoryUsage(true);
+
+ // Fill cache
+ for (let i = 0; i < testSize; i++) {
+ cache.set(testData.keys[i], testData.values[i]);
+ }
+
+ const afterMem = getMemoryUsage(true);
+ const memoryPerItem = calculateMemoryPerItem(beforeMem, afterMem, testSize);
+
+ memoryResults[name] = {
+ totalMemory: afterMem.heapUsed - beforeMem.heapUsed,
+ memoryPerItem: memoryPerItem,
+ bundleSize: bundleSizes[name.split("-")[0]] || "N/A",
+ };
+ }
+
+ console.log("\nMemory Usage Results:");
+ console.log("┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐");
+ console.log("│ Library │ Bundle Size │ Memory/Item │ Total Memory │");
+ console.log("├─────────────────┼─────────────────┼─────────────────┼─────────────────┤");
+
+ Object.entries(memoryResults).forEach(([name, data]) => {
+ const nameCol = name.padEnd(15);
+ const bundleCol = data.bundleSize.padEnd(15);
+ const memoryCol = `${data.memoryPerItem} bytes`.padEnd(15);
+ const totalCol = `${Math.round(data.totalMemory / 1024)} KB`.padEnd(15);
+ console.log(`│ ${nameCol} │ ${bundleCol} │ ${memoryCol} │ ${totalCol} │`);
+ });
+
+ console.log("└─────────────────┴─────────────────┴─────────────────┴─────────────────┘");
+
+ // Performance summary
+ console.log("\n📊 Performance Summary");
+ console.log("=".repeat(50));
+
+ const setResults = setBench.tasks.map((task) => ({
+ name: task.name,
+ opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0,
+ }));
+
+ const getResults = getBench.tasks.map((task) => ({
+ name: task.name,
+ opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0,
+ }));
+
+ const updateResults = updateBench.tasks.map((task) => ({
+ name: task.name,
+ opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0,
+ }));
+
+ const deleteResults = deleteBench.tasks.map((task) => ({
+ name: task.name,
+ opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0,
+ }));
+
+ console.log("\nOperations per second (higher is better):");
+ console.log(
+ "┌─────────────────┬─────────────────┬─────────────────┬─────────────────┬─────────────────┐",
+ );
+ console.log(
+ "│ Library │ SET ops/sec │ GET ops/sec │ UPDATE ops/sec │ DELETE ops/sec │",
+ );
+ console.log(
+ "├─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤",
+ );
+
+ // Group results by library
+ const libraries = ["tiny-lru", "lru-cache", "quick-lru", "mnemonist"];
+
+ libraries.forEach((lib) => {
+ const setResult = setResults.find((r) => r.name.includes(lib));
+ const getResult = getResults.find((r) => r.name.includes(lib));
+ const updateResult = updateResults.find((r) => r.name.includes(lib));
+ const deleteResult = deleteResults.find((r) => r.name.includes(lib));
+
+ if (setResult && getResult && updateResult && deleteResult) {
+ const nameCol = lib.padEnd(15);
+ const setCol = setResult.opsPerSec.toLocaleString().padEnd(15);
+ const getCol = getResult.opsPerSec.toLocaleString().padEnd(15);
+ const updateCol = updateResult.opsPerSec.toLocaleString().padEnd(15);
+ const deleteCol = deleteResult.opsPerSec.toLocaleString().padEnd(15);
+ console.log(`│ ${nameCol} │ ${setCol} │ ${getCol} │ ${updateCol} │ ${deleteCol} │`);
+ }
+ });
+
+ console.log(
+ "└─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┘",
+ );
+
+ console.log("\n✅ Benchmark completed!");
+ console.log("\nTo regenerate this data, run: npm run benchmark:comparison");
}
// Handle unhandled promise rejections
-process.on("unhandledRejection", error => {
- console.error("Unhandled promise rejection:", error);
- process.exit(1);
+process.on("unhandledRejection", (error) => {
+ console.error("Unhandled promise rejection:", error);
+ process.exit(1);
});
// Run benchmarks
diff --git a/benchmarks/modern-benchmark.js b/benchmarks/modern-benchmark.js
index ed06560..b3282cd 100644
--- a/benchmarks/modern-benchmark.js
+++ b/benchmarks/modern-benchmark.js
@@ -5,8 +5,8 @@ import { lru } from "../dist/tiny-lru.js";
const CACHE_SIZES = [100, 1000, 5000];
const WORKLOAD_SIZES = [50, 500, 2500]; // Half of cache size for realistic workloads
const ITERATIONS = {
- time: 1000, // Run for 1 second minimum
- iterations: 100 // Minimum iterations for statistical significance
+ time: 1000, // Run for 1 second minimum
+ iterations: 100, // Minimum iterations for statistical significance
};
// Utility functions for generating test data
@@ -16,32 +16,34 @@ const ITERATIONS = {
* @param {number} size - Number of items
* @returns {Array<{key:string,value:string}>}
*/
-function generateRandomData (size) {
- const data = new Array(size);
- let x = 2463534242;
- for (let i = 0; i < size; i++) {
- // xorshift32
- x ^= x << 13; x ^= x >>> 17; x ^= x << 5;
- const n = (x >>> 0).toString(36);
- data[i] = {
- key: `key_${i}_${n}`,
- value: `value_${i}_${n}${n}`
- };
- }
-
- return data;
+function generateRandomData(size) {
+ const data = Array.from({ length: size });
+ let x = 2463534242;
+ for (let i = 0; i < size; i++) {
+ // xorshift32
+ x ^= x << 13;
+ x ^= x >>> 17;
+ x ^= x << 5;
+ const n = (x >>> 0).toString(36);
+ data[i] = {
+ key: `key_${i}_${n}`,
+ value: `value_${i}_${n}${n}`,
+ };
+ }
+
+ return data;
}
-function generateSequentialData (size) {
- const data = new Array(size);
- for (let i = 0; i < size; i++) {
- data[i] = {
- key: `seq_key_${i}`,
- value: `seq_value_${i}`
- };
- }
+function generateSequentialData(size) {
+ const data = Array.from({ length: size });
+ for (let i = 0; i < size; i++) {
+ data[i] = {
+ key: `seq_key_${i}`,
+ value: `seq_value_${i}`,
+ };
+ }
- return data;
+ return data;
}
/**
@@ -51,343 +53,345 @@ function generateSequentialData (size) {
* @param {number} modulo - Upper bound (exclusive)
* @returns {Uint32Array}
*/
-function generateAccessPattern (length, modulo) {
- const pattern = new Uint32Array(length);
- let x = 123456789;
- let y = 362436069;
- for (let i = 0; i < length; i++) {
- x ^= x << 13; x ^= x >>> 17; x ^= x << 5;
- y = y + 1 >>> 0;
- pattern[i] = (x + y >>> 0) % modulo;
- }
-
- return pattern;
+function generateAccessPattern(length, modulo) {
+ const pattern = new Uint32Array(length);
+ let x = 123456789;
+ let y = 362436069;
+ for (let i = 0; i < length; i++) {
+ x ^= x << 13;
+ x ^= x >>> 17;
+ x ^= x << 5;
+ y = (y + 1) >>> 0;
+ pattern[i] = ((x + y) >>> 0) % modulo;
+ }
+
+ return pattern;
}
// Pre-populate cache with data
-function prepopulateCache (cache, data, fillRatio = 0.8) {
- const fillCount = Math.floor(data.length * fillRatio);
- for (let i = 0; i < fillCount; i++) {
- cache.set(data[i].key, data[i].value);
- }
+function prepopulateCache(cache, data, fillRatio = 0.8) {
+ const fillCount = Math.floor(data.length * fillRatio);
+ for (let i = 0; i < fillCount; i++) {
+ cache.set(data[i].key, data[i].value);
+ }
}
// Benchmark suites
-async function runSetOperationsBenchmarks () {
- console.log("\n📝 SET Operations Benchmarks");
- console.log("=" .repeat(50));
-
- for (const cacheSize of CACHE_SIZES) {
- const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
- const bench = new Bench(ITERATIONS);
-
- console.log(`\nCache Size: ${cacheSize}, Workload: ${workloadSize}`);
-
- // Prepare test data & patterns
- const randomData = generateRandomData(workloadSize);
- const sequentialData = generateSequentialData(workloadSize);
- const randomPattern = generateAccessPattern(10000, workloadSize);
- let randomCursor = 0;
-
- // Test scenarios
- bench
- .add(`set-random-empty-cache-${cacheSize}`, () => {
- const cache = lru(cacheSize);
- const idx = randomPattern[randomCursor++ % randomPattern.length];
- const item = randomData[idx];
- cache.set(item.key, item.value);
- })
- .add(`set-sequential-empty-cache-${cacheSize}`, () => {
- const cache = lru(cacheSize);
- const idx = randomPattern[randomCursor++ % randomPattern.length];
- const item = sequentialData[idx];
- cache.set(item.key, item.value);
- })
- .add(`set-random-full-cache-${cacheSize}`, () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, randomData);
- const idx = randomPattern[randomCursor++ % randomPattern.length];
- const item = randomData[idx];
- cache.set(item.key, item.value);
- })
- .add(`set-new-items-full-cache-${cacheSize}`, () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, randomData);
- // Force eviction by adding new items
- const idx = randomPattern[randomCursor++ % randomPattern.length];
- cache.set(`new_key_${cacheSize}_${idx}`, `new_value_${idx}`);
- });
-
- await bench.run();
- console.table(bench.table());
- }
+async function runSetOperationsBenchmarks() {
+ console.log("\n📝 SET Operations Benchmarks");
+ console.log("=".repeat(50));
+
+ for (const cacheSize of CACHE_SIZES) {
+ const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
+ const bench = new Bench(ITERATIONS);
+
+ console.log(`\nCache Size: ${cacheSize}, Workload: ${workloadSize}`);
+
+ // Prepare test data & patterns
+ const randomData = generateRandomData(workloadSize);
+ const sequentialData = generateSequentialData(workloadSize);
+ const randomPattern = generateAccessPattern(10000, workloadSize);
+ let randomCursor = 0;
+
+ // Test scenarios
+ bench
+ .add(`set-random-empty-cache-${cacheSize}`, () => {
+ const cache = lru(cacheSize);
+ const idx = randomPattern[randomCursor++ % randomPattern.length];
+ const item = randomData[idx];
+ cache.set(item.key, item.value);
+ })
+ .add(`set-sequential-empty-cache-${cacheSize}`, () => {
+ const cache = lru(cacheSize);
+ const idx = randomPattern[randomCursor++ % randomPattern.length];
+ const item = sequentialData[idx];
+ cache.set(item.key, item.value);
+ })
+ .add(`set-random-full-cache-${cacheSize}`, () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, randomData);
+ const idx = randomPattern[randomCursor++ % randomPattern.length];
+ const item = randomData[idx];
+ cache.set(item.key, item.value);
+ })
+ .add(`set-new-items-full-cache-${cacheSize}`, () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, randomData);
+ // Force eviction by adding new items
+ const idx = randomPattern[randomCursor++ % randomPattern.length];
+ cache.set(`new_key_${cacheSize}_${idx}`, `new_value_${idx}`);
+ });
+
+ await bench.run();
+ console.table(bench.table());
+ }
}
-async function runGetOperationsBenchmarks () {
- console.log("\n🔍 GET Operations Benchmarks");
- console.log("=" .repeat(50));
-
- for (const cacheSize of CACHE_SIZES) {
- const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
- const bench = new Bench(ITERATIONS);
-
- console.log(`\nCache Size: ${cacheSize}, Workload: ${workloadSize}`);
-
- // Prepare test data and caches
- const randomData = generateRandomData(workloadSize);
- const sequentialData = generateSequentialData(workloadSize);
-
- const randomCache = lru(cacheSize);
- const sequentialCache = lru(cacheSize);
- const mixedCache = lru(cacheSize);
-
- prepopulateCache(randomCache, randomData);
- prepopulateCache(sequentialCache, sequentialData);
- prepopulateCache(mixedCache, [...randomData.slice(0, Math.floor(workloadSize / 2)),
- ...sequentialData.slice(0, Math.floor(workloadSize / 2))]);
-
- const hitPattern = generateAccessPattern(20000, Math.floor(workloadSize * 0.8));
- const missPattern = generateAccessPattern(20000, 1 << 30);
- let getCursor = 0;
-
- bench
- .add(`get-hit-random-${cacheSize}`, () => {
- const idx = hitPattern[getCursor++ % hitPattern.length];
- const item = randomData[idx];
- randomCache.get(item.key);
- })
- .add(`get-hit-sequential-${cacheSize}`, () => {
- const idx = hitPattern[getCursor++ % hitPattern.length];
- const item = sequentialData[idx];
- sequentialCache.get(item.key);
- })
- .add(`get-miss-${cacheSize}`, () => {
- const idx = missPattern[getCursor++ % missPattern.length];
- randomCache.get(`nonexistent_key_${idx}`);
- })
- .add(`get-mixed-pattern-${cacheSize}`, () => {
- const choose = hitPattern[getCursor++ % hitPattern.length] % 10; // 0..9
- if (choose < 8) {
- const idx = hitPattern[getCursor++ % hitPattern.length];
- const item = randomData[idx];
- mixedCache.get(item.key);
- } else {
- const idx = missPattern[getCursor++ % missPattern.length];
- mixedCache.get(`miss_key_${idx}`);
- }
- });
-
- await bench.run();
- console.table(bench.table());
- }
+async function runGetOperationsBenchmarks() {
+ console.log("\n🔍 GET Operations Benchmarks");
+ console.log("=".repeat(50));
+
+ for (const cacheSize of CACHE_SIZES) {
+ const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
+ const bench = new Bench(ITERATIONS);
+
+ console.log(`\nCache Size: ${cacheSize}, Workload: ${workloadSize}`);
+
+ // Prepare test data and caches
+ const randomData = generateRandomData(workloadSize);
+ const sequentialData = generateSequentialData(workloadSize);
+
+ const randomCache = lru(cacheSize);
+ const sequentialCache = lru(cacheSize);
+ const mixedCache = lru(cacheSize);
+
+ prepopulateCache(randomCache, randomData);
+ prepopulateCache(sequentialCache, sequentialData);
+ prepopulateCache(mixedCache, [
+ ...randomData.slice(0, Math.floor(workloadSize / 2)),
+ ...sequentialData.slice(0, Math.floor(workloadSize / 2)),
+ ]);
+
+ const hitPattern = generateAccessPattern(20000, Math.floor(workloadSize * 0.8));
+ const missPattern = generateAccessPattern(20000, 1 << 30);
+ let getCursor = 0;
+
+ bench
+ .add(`get-hit-random-${cacheSize}`, () => {
+ const idx = hitPattern[getCursor++ % hitPattern.length];
+ const item = randomData[idx];
+ randomCache.get(item.key);
+ })
+ .add(`get-hit-sequential-${cacheSize}`, () => {
+ const idx = hitPattern[getCursor++ % hitPattern.length];
+ const item = sequentialData[idx];
+ sequentialCache.get(item.key);
+ })
+ .add(`get-miss-${cacheSize}`, () => {
+ const idx = missPattern[getCursor++ % missPattern.length];
+ randomCache.get(`nonexistent_key_${idx}`);
+ })
+ .add(`get-mixed-pattern-${cacheSize}`, () => {
+ const choose = hitPattern[getCursor++ % hitPattern.length] % 10; // 0..9
+ if (choose < 8) {
+ const idx = hitPattern[getCursor++ % hitPattern.length];
+ const item = randomData[idx];
+ mixedCache.get(item.key);
+ } else {
+ const idx = missPattern[getCursor++ % missPattern.length];
+ mixedCache.get(`miss_key_${idx}`);
+ }
+ });
+
+ await bench.run();
+ console.table(bench.table());
+ }
}
-async function runMixedOperationsBenchmarks () {
- console.log("\n🔄 Mixed Operations Benchmarks (Real-world scenarios)");
- console.log("=" .repeat(60));
-
- for (const cacheSize of CACHE_SIZES) {
- const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
- const bench = new Bench(ITERATIONS);
-
- console.log(`\nCache Size: ${cacheSize}, Workload: ${workloadSize}`);
-
- const testData = generateRandomData(workloadSize * 2); // More data than cache
- const choosePattern = generateAccessPattern(50000, 10);
- const idxPattern = generateAccessPattern(50000, testData.length);
- let mixedCursor = 0;
-
- bench
- .add(`real-world-80-20-read-write-${cacheSize}`, () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, testData, 0.5);
- // Simulate 80% reads, 20% writes
- for (let i = 0; i < 10; i++) {
- const choose = choosePattern[mixedCursor++ % choosePattern.length];
- if (choose < 8) {
- const item = testData[idxPattern[mixedCursor++ % idxPattern.length] % workloadSize];
- cache.get(item.key);
- } else {
- const item = testData[idxPattern[mixedCursor++ % idxPattern.length]];
- cache.set(item.key, item.value);
- }
- }
- })
- .add(`cache-warming-${cacheSize}`, () => {
- const cache = lru(cacheSize);
-
- // Simulate cache warming - sequential fills
- for (let i = 0; i < Math.min(cacheSize, workloadSize); i++) {
- cache.set(testData[i].key, testData[i].value);
- }
- })
- .add(`high-churn-${cacheSize}`, () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, testData, 1.0); // Fill cache completely
-
- // High churn - constantly adding new items
- for (let i = 0; i < 5; i++) {
- const idx = idxPattern[mixedCursor++ % idxPattern.length];
- cache.set(`churn_${cacheSize}_${i}_${idx}`, `value_${i}`);
- }
- })
- .add(`lru-access-pattern-${cacheSize}`, () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, testData, 1.0);
-
- // Access patterns that test LRU behavior
- const hotKeys = testData.slice(0, 3);
- cache.get(hotKeys[0].key);
- cache.get(hotKeys[1].key);
- cache.get(hotKeys[2].key);
- cache.get(hotKeys[0].key);
- cache.get(hotKeys[1].key);
- cache.get(hotKeys[2].key);
- });
-
- await bench.run();
- console.table(bench.table());
- }
+async function runMixedOperationsBenchmarks() {
+ console.log("\n🔄 Mixed Operations Benchmarks (Real-world scenarios)");
+ console.log("=".repeat(60));
+
+ for (const cacheSize of CACHE_SIZES) {
+ const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
+ const bench = new Bench(ITERATIONS);
+
+ console.log(`\nCache Size: ${cacheSize}, Workload: ${workloadSize}`);
+
+ const testData = generateRandomData(workloadSize * 2); // More data than cache
+ const choosePattern = generateAccessPattern(50000, 10);
+ const idxPattern = generateAccessPattern(50000, testData.length);
+ let mixedCursor = 0;
+
+ bench
+ .add(`real-world-80-20-read-write-${cacheSize}`, () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, testData, 0.5);
+ // Simulate 80% reads, 20% writes
+ for (let i = 0; i < 10; i++) {
+ const choose = choosePattern[mixedCursor++ % choosePattern.length];
+ if (choose < 8) {
+ const item = testData[idxPattern[mixedCursor++ % idxPattern.length] % workloadSize];
+ cache.get(item.key);
+ } else {
+ const item = testData[idxPattern[mixedCursor++ % idxPattern.length]];
+ cache.set(item.key, item.value);
+ }
+ }
+ })
+ .add(`cache-warming-${cacheSize}`, () => {
+ const cache = lru(cacheSize);
+
+ // Simulate cache warming - sequential fills
+ for (let i = 0; i < Math.min(cacheSize, workloadSize); i++) {
+ cache.set(testData[i].key, testData[i].value);
+ }
+ })
+ .add(`high-churn-${cacheSize}`, () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, testData, 1.0); // Fill cache completely
+
+ // High churn - constantly adding new items
+ for (let i = 0; i < 5; i++) {
+ const idx = idxPattern[mixedCursor++ % idxPattern.length];
+ cache.set(`churn_${cacheSize}_${i}_${idx}`, `value_${i}`);
+ }
+ })
+ .add(`lru-access-pattern-${cacheSize}`, () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, testData, 1.0);
+
+ // Access patterns that test LRU behavior
+ const hotKeys = testData.slice(0, 3);
+ cache.get(hotKeys[0].key);
+ cache.get(hotKeys[1].key);
+ cache.get(hotKeys[2].key);
+ cache.get(hotKeys[0].key);
+ cache.get(hotKeys[1].key);
+ cache.get(hotKeys[2].key);
+ });
+
+ await bench.run();
+ console.table(bench.table());
+ }
}
-async function runSpecialOperationsBenchmarks () {
- console.log("\n⚙️ Special Operations Benchmarks");
- console.log("=" .repeat(50));
-
- const cacheSize = 1000;
- const workloadSize = 500;
- const bench = new Bench(ITERATIONS);
-
- const testData = generateRandomData(workloadSize);
- const hitPattern = generateAccessPattern(20000, Math.floor(workloadSize * 0.8));
- let cursor = 0;
-
- // Test cache with different data types
- const numberData = Array.from({length: workloadSize}, (_, i) => ({key: i, value: i * 2}));
- const objectData = Array.from({length: workloadSize}, (_, i) => ({
- key: `obj_${i}`,
- value: {id: i, data: `object_data_${i}`, nested: {prop: i}}
- }));
-
- bench
- .add("cache-clear", () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, testData);
- cache.clear();
- })
- .add("cache-delete", () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, testData);
- const item = testData[hitPattern[cursor++ % hitPattern.length]];
- cache.delete(item.key);
- })
- .add("number-keys-values", () => {
- const cache = lru(cacheSize);
- const item = numberData[Math.floor(Math.random() * numberData.length)];
- cache.set(item.key, item.value);
- cache.get(item.key);
- })
- .add("object-values", () => {
- const cache = lru(cacheSize);
- const item = objectData[Math.floor(Math.random() * objectData.length)];
- cache.set(item.key, item.value);
- cache.get(item.key);
- })
- .add("has-operation", () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, testData);
- const item = testData[hitPattern[cursor++ % hitPattern.length]];
- cache.has(item.key);
- })
- .add("size-property", () => {
- const cache = lru(cacheSize);
- prepopulateCache(cache, testData);
- // Access size property
-
- return cache.size;
- });
-
- await bench.run();
- console.table(bench.table());
+async function runSpecialOperationsBenchmarks() {
+ console.log("\n⚙️ Special Operations Benchmarks");
+ console.log("=".repeat(50));
+
+ const cacheSize = 1000;
+ const workloadSize = 500;
+ const bench = new Bench(ITERATIONS);
+
+ const testData = generateRandomData(workloadSize);
+ const hitPattern = generateAccessPattern(20000, Math.floor(workloadSize * 0.8));
+ let cursor = 0;
+
+ // Test cache with different data types
+ const numberData = Array.from({ length: workloadSize }, (_, i) => ({ key: i, value: i * 2 }));
+ const objectData = Array.from({ length: workloadSize }, (_, i) => ({
+ key: `obj_${i}`,
+ value: { id: i, data: `object_data_${i}`, nested: { prop: i } },
+ }));
+
+ bench
+ .add("cache-clear", () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, testData);
+ cache.clear();
+ })
+ .add("cache-delete", () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, testData);
+ const item = testData[hitPattern[cursor++ % hitPattern.length]];
+ cache.delete(item.key);
+ })
+ .add("number-keys-values", () => {
+ const cache = lru(cacheSize);
+ const item = numberData[Math.floor(Math.random() * numberData.length)];
+ cache.set(item.key, item.value);
+ cache.get(item.key);
+ })
+ .add("object-values", () => {
+ const cache = lru(cacheSize);
+ const item = objectData[Math.floor(Math.random() * objectData.length)];
+ cache.set(item.key, item.value);
+ cache.get(item.key);
+ })
+ .add("has-operation", () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, testData);
+ const item = testData[hitPattern[cursor++ % hitPattern.length]];
+ cache.has(item.key);
+ })
+ .add("size-property", () => {
+ const cache = lru(cacheSize);
+ prepopulateCache(cache, testData);
+ // Access size property
+
+ return cache.size;
+ });
+
+ await bench.run();
+ console.table(bench.table());
}
// Memory usage benchmarks
-async function runMemoryBenchmarks () {
- console.log("\n🧠 Memory Usage Analysis");
- console.log("=" .repeat(40));
+async function runMemoryBenchmarks() {
+ console.log("\n🧠 Memory Usage Analysis");
+ console.log("=".repeat(40));
- const testSizes = [100, 1000, 10000];
+ const testSizes = [100, 1000, 10000];
- for (const size of testSizes) {
- console.log(`\nAnalyzing memory usage for cache size: ${size}`);
+ for (const size of testSizes) {
+ console.log(`\nAnalyzing memory usage for cache size: ${size}`);
- const cache = lru(size);
- const testData = generateRandomData(size);
+ const cache = lru(size);
+ const testData = generateRandomData(size);
- // Memory before
- if (global.gc) {
- global.gc();
- }
- const memBefore = process.memoryUsage();
+ // Memory before
+ if (global.gc) {
+ global.gc();
+ }
+ const memBefore = process.memoryUsage();
- // Fill cache
- testData.forEach(item => cache.set(item.key, item.value));
+ // Fill cache
+ testData.forEach((item) => cache.set(item.key, item.value));
- // Memory after
- if (global.gc) {
- global.gc();
- }
- const memAfter = process.memoryUsage();
+ // Memory after
+ if (global.gc) {
+ global.gc();
+ }
+ const memAfter = process.memoryUsage();
- const heapUsed = memAfter.heapUsed - memBefore.heapUsed;
- const perItem = heapUsed / size;
+ const heapUsed = memAfter.heapUsed - memBefore.heapUsed;
+ const perItem = heapUsed / size;
- console.log(`Heap used: ${(heapUsed / 1024 / 1024).toFixed(2)} MB`);
- console.log(`Per item: ${perItem.toFixed(2)} bytes`);
- console.log(`Cache size: ${cache.size}`);
- }
+ console.log(`Heap used: ${(heapUsed / 1024 / 1024).toFixed(2)} MB`);
+ console.log(`Per item: ${perItem.toFixed(2)} bytes`);
+ console.log(`Cache size: ${cache.size}`);
+ }
}
// Main execution
-async function runAllBenchmarks () {
- console.log("🚀 Tiny-LRU Modern Benchmark Suite");
- console.log("==================================");
- console.log(`Node.js version: ${process.version}`);
- console.log(`Platform: ${process.platform} ${process.arch}`);
- console.log(`Date: ${new Date().toISOString()}`);
-
- try {
- await runSetOperationsBenchmarks();
- await runGetOperationsBenchmarks();
- await runMixedOperationsBenchmarks();
- await runSpecialOperationsBenchmarks();
- await runMemoryBenchmarks();
-
- console.log("\n✅ All benchmarks completed successfully!");
- console.log("\n📊 Summary:");
- console.log("- SET operations: Tests cache population under various conditions");
- console.log("- GET operations: Tests cache retrieval with different hit/miss patterns");
- console.log("- Mixed operations: Simulates real-world usage scenarios");
- console.log("- Special operations: Tests additional cache methods and edge cases");
- console.log("- Memory analysis: Shows memory consumption patterns");
-
- } catch (error) {
- console.error("❌ Benchmark failed:", error);
- process.exit(1);
- }
+async function runAllBenchmarks() {
+ console.log("🚀 Tiny-LRU Modern Benchmark Suite");
+ console.log("==================================");
+ console.log(`Node.js version: ${process.version}`);
+ console.log(`Platform: ${process.platform} ${process.arch}`);
+ console.log(`Date: ${new Date().toISOString()}`);
+
+ try {
+ await runSetOperationsBenchmarks();
+ await runGetOperationsBenchmarks();
+ await runMixedOperationsBenchmarks();
+ await runSpecialOperationsBenchmarks();
+ await runMemoryBenchmarks();
+
+ console.log("\n✅ All benchmarks completed successfully!");
+ console.log("\n📊 Summary:");
+ console.log("- SET operations: Tests cache population under various conditions");
+ console.log("- GET operations: Tests cache retrieval with different hit/miss patterns");
+ console.log("- Mixed operations: Simulates real-world usage scenarios");
+ console.log("- Special operations: Tests additional cache methods and edge cases");
+ console.log("- Memory analysis: Shows memory consumption patterns");
+ } catch (error) {
+ console.error("❌ Benchmark failed:", error);
+ process.exit(1);
+ }
}
// Allow running this file directly
if (import.meta.url === `file://${process.argv[1]}`) {
- runAllBenchmarks();
+ runAllBenchmarks();
}
export {
- runAllBenchmarks,
- runSetOperationsBenchmarks,
- runGetOperationsBenchmarks,
- runMixedOperationsBenchmarks,
- runSpecialOperationsBenchmarks,
- runMemoryBenchmarks
+ runAllBenchmarks,
+ runSetOperationsBenchmarks,
+ runGetOperationsBenchmarks,
+ runMixedOperationsBenchmarks,
+ runSpecialOperationsBenchmarks,
+ runMemoryBenchmarks,
};
-
diff --git a/benchmarks/performance-observer-benchmark.js b/benchmarks/performance-observer-benchmark.js
index 9929990..a0a1402 100644
--- a/benchmarks/performance-observer-benchmark.js
+++ b/benchmarks/performance-observer-benchmark.js
@@ -3,282 +3,328 @@ import { lru } from "../dist/tiny-lru.js";
// Custom high-resolution timer benchmark (alternative approach)
class CustomTimer {
- constructor () {
- this.results = new Map();
- }
-
- async timeFunction (name, fn, iterations = 1000) {
- const times = [];
-
- // Warmup
- for (let i = 0; i < Math.min(100, iterations / 10); i++) {
- await fn();
- }
-
- // Actual measurement
- for (let i = 0; i < iterations; i++) {
- const start = performance.now();
- await fn();
- const end = performance.now();
- times.push(end - start);
- }
-
- // Calculate statistics
- const totalTime = times.reduce((a, b) => a + b, 0);
- const avgTime = totalTime / iterations;
- const minTime = Math.min(...times);
- const maxTime = Math.max(...times);
-
- const sorted = [...times].sort((a, b) => a - b);
- const median = sorted[Math.floor(sorted.length / 2)];
-
- const variance = times.reduce((acc, time) => acc + Math.pow(time - avgTime, 2), 0) / iterations;
- const stdDev = Math.sqrt(variance);
-
- this.results.set(name, {
- name,
- iterations,
- avgTime,
- minTime,
- maxTime,
- median,
- stdDev,
- opsPerSec: 1000 / avgTime // Convert ms to ops/sec
- });
- }
-
- printResults () {
- console.log("\n⏱️ Performance Results");
- console.log("========================");
-
- const results = Array.from(this.results.values());
- console.table(results.map(r => ({
- "Operation": r.name,
- "Iterations": r.iterations,
- "Avg (ms)": r.avgTime.toFixed(6),
- "Min (ms)": r.minTime.toFixed(6),
- "Max (ms)": r.maxTime.toFixed(6),
- "Median (ms)": r.median.toFixed(6),
- "Std Dev": r.stdDev.toFixed(6),
- "Ops/sec": Math.round(r.opsPerSec)
- })));
- }
+ constructor() {
+ this.results = new Map();
+ }
+
+ async timeFunction(name, fn, iterations = 1000) {
+ const times = [];
+
+ // Warmup
+ for (let i = 0; i < Math.min(100, iterations / 10); i++) {
+ await fn();
+ }
+
+ // Actual measurement
+ for (let i = 0; i < iterations; i++) {
+ const start = performance.now();
+ await fn();
+ const end = performance.now();
+ times.push(end - start);
+ }
+
+ // Calculate statistics
+ const totalTime = times.reduce((a, b) => a + b, 0);
+ const avgTime = totalTime / iterations;
+ const minTime = Math.min(...times);
+ const maxTime = Math.max(...times);
+
+ const sorted = [...times].sort((a, b) => a - b);
+ const median = sorted[Math.floor(sorted.length / 2)];
+
+ const variance = times.reduce((acc, time) => acc + Math.pow(time - avgTime, 2), 0) / iterations;
+ const stdDev = Math.sqrt(variance);
+
+ this.results.set(name, {
+ name,
+ iterations,
+ avgTime,
+ minTime,
+ maxTime,
+ median,
+ stdDev,
+ opsPerSec: 1000 / avgTime, // Convert ms to ops/sec
+ });
+ }
+
+ printResults() {
+ console.log("\n⏱️ Performance Results");
+ console.log("========================");
+
+ const results = Array.from(this.results.values());
+ console.table(
+ results.map((r) => ({
+ Operation: r.name,
+ Iterations: r.iterations,
+ "Avg (ms)": r.avgTime.toFixed(6),
+ "Min (ms)": r.minTime.toFixed(6),
+ "Max (ms)": r.maxTime.toFixed(6),
+ "Median (ms)": r.median.toFixed(6),
+ "Std Dev": r.stdDev.toFixed(6),
+ "Ops/sec": Math.round(r.opsPerSec),
+ })),
+ );
+ }
}
// Test data generation
-function generateTestData (size) {
- const out = new Array(size);
- for (let i = 0; i < size; i++) {
- out[i] = {
- key: `key_${i}`,
- value: `value_${i}_${"x".repeat(50)}`
- };
- }
-
- return out;
+function generateTestData(size) {
+ const out = Array.from({ length: size });
+ for (let i = 0; i < size; i++) {
+ out[i] = {
+ key: `key_${i}`,
+ value: `value_${i}_${"x".repeat(50)}`,
+ };
+ }
+
+ return out;
}
-async function runPerformanceBenchmarks () {
- console.log("🔬 LRU Performance Benchmarks");
- console.log("==============================");
- console.log("(Using CustomTimer for high-resolution function timing)");
-
- const timer = new CustomTimer();
- const cacheSize = 1000;
- const iterations = 10000;
- const testData = generateTestData(cacheSize * 2);
-
- console.log("Running operations...");
-
- // Phase 1: Fill cache with initial data
- console.log("Phase 1: Initial cache population");
- const phase1Cache = lru(cacheSize);
- let phase1Index = 0;
- await timer.timeFunction("lru.set (initial population)", () => {
- const i = phase1Index % cacheSize;
- phase1Cache.set(testData[i].key, testData[i].value);
- phase1Index++;
- }, iterations);
-
- // Phase 2: Mixed read/write operations
- console.log("Phase 2: Mixed operations");
- const phase2Cache = lru(cacheSize);
- // Pre-populate for realistic workload
- for (let i = 0; i < cacheSize; i++) {
- phase2Cache.set(testData[i].key, testData[i].value);
- }
-
- // Deterministic mixed workload that exercises the entire cache without conditionals
- const getIndices = new Uint32Array(iterations);
- const setIndices = new Uint32Array(iterations);
- const hasIndices = new Uint32Array(iterations);
- const deleteIndices = new Uint32Array(iterations);
-
- for (let i = 0; i < iterations; i++) {
- const idx = i % cacheSize;
- getIndices[i] = idx;
- setIndices[i] = idx;
- hasIndices[i] = idx;
- deleteIndices[i] = idx;
- }
-
- let mixedGetIndex = 0;
- await timer.timeFunction("lru.get", () => {
- const idx = getIndices[mixedGetIndex % iterations];
- phase2Cache.get(testData[idx].key);
- mixedGetIndex++;
- }, iterations);
-
- let mixedSetIndex = 0;
- await timer.timeFunction("lru.set", () => {
- const idx = setIndices[mixedSetIndex % iterations];
- phase2Cache.set(testData[idx].key, testData[idx].value);
- mixedSetIndex++;
- }, iterations);
-
- let mixedHasIndex = 0;
- await timer.timeFunction("lru.has", () => {
- const idx = hasIndices[mixedHasIndex % iterations];
- phase2Cache.has(testData[idx].key);
- mixedHasIndex++;
- }, iterations);
-
- // keys()
- await timer.timeFunction("lru.keys", () => {
- phase2Cache.keys();
- }, iterations);
-
- // values()
- await timer.timeFunction("lru.values", () => {
- phase2Cache.values();
- }, iterations);
-
- // entries()
- await timer.timeFunction("lru.entries", () => {
- phase2Cache.entries();
- }, iterations);
-
- let mixedDeleteIndex = 0;
- await timer.timeFunction("lru.delete", () => {
- const idx = deleteIndices[mixedDeleteIndex % iterations];
- phase2Cache.delete(testData[idx].key);
- mixedDeleteIndex++;
- }, iterations);
-
- // Phase 3: Cache eviction stress test
- console.log("Phase 3: Cache eviction stress test");
- const phase3Cache = lru(2);
- let phase3Index = 1;
- phase3Cache.set(`evict_key_${phase3Index}`, `evict__value_${phase3Index++}`);
- await timer.timeFunction("lru.set (eviction stress)", () => {
- phase3Cache.set(`evict_key_${phase3Index}`, `evict_value_${phase3Index++}`);
- }, iterations);
-
- // Phase 4: Some clear operations
- console.log("Phase 4: Clear operations");
- const phase4Cache = lru(1);
- await timer.timeFunction("lru.clear", () => {
- phase4Cache.set("temp_1", "temp_value_1");
- phase4Cache.clear();
- }, iterations);
-
- // Phase 5: Additional API method benchmarks
- console.log("Phase 5: Additional API method benchmarks");
-
- // setWithEvicted()
- const setWithEvictedCache = lru(2);
- setWithEvictedCache.set("a", "value_a");
- setWithEvictedCache.set("b", "value_b");
- let setWithEvictedIndex = 0;
- await timer.timeFunction("lru.setWithEvicted", () => {
- const key = `extra_key_${setWithEvictedIndex}`;
- const value = `extra_value_${setWithEvictedIndex}`;
- setWithEvictedCache.setWithEvicted(key, value);
- setWithEvictedIndex++;
- }, iterations);
-
- // expiresAt()
- const expiresCache = lru(cacheSize, 6e4);
- const expiresKey = "expires_key";
- expiresCache.set(expiresKey, "expires_value");
- await timer.timeFunction("lru.expiresAt", () => {
- expiresCache.expiresAt(expiresKey);
- }, iterations);
-
- timer.printResults();
+async function runPerformanceBenchmarks() {
+ console.log("🔬 LRU Performance Benchmarks");
+ console.log("==============================");
+ console.log("(Using CustomTimer for high-resolution function timing)");
+
+ const timer = new CustomTimer();
+ const cacheSize = 1000;
+ const iterations = 10000;
+ const testData = generateTestData(cacheSize * 2);
+
+ console.log("Running operations...");
+
+ // Phase 1: Fill cache with initial data
+ console.log("Phase 1: Initial cache population");
+ const phase1Cache = lru(cacheSize);
+ let phase1Index = 0;
+ await timer.timeFunction(
+ "lru.set (initial population)",
+ () => {
+ const i = phase1Index % cacheSize;
+ phase1Cache.set(testData[i].key, testData[i].value);
+ phase1Index++;
+ },
+ iterations,
+ );
+
+ // Phase 2: Mixed read/write operations
+ console.log("Phase 2: Mixed operations");
+ const phase2Cache = lru(cacheSize);
+ // Pre-populate for realistic workload
+ for (let i = 0; i < cacheSize; i++) {
+ phase2Cache.set(testData[i].key, testData[i].value);
+ }
+
+ // Deterministic mixed workload that exercises the entire cache without conditionals
+ const getIndices = new Uint32Array(iterations);
+ const setIndices = new Uint32Array(iterations);
+ const hasIndices = new Uint32Array(iterations);
+ const deleteIndices = new Uint32Array(iterations);
+
+ for (let i = 0; i < iterations; i++) {
+ const idx = i % cacheSize;
+ getIndices[i] = idx;
+ setIndices[i] = idx;
+ hasIndices[i] = idx;
+ deleteIndices[i] = idx;
+ }
+
+ let mixedGetIndex = 0;
+ await timer.timeFunction(
+ "lru.get",
+ () => {
+ const idx = getIndices[mixedGetIndex % iterations];
+ phase2Cache.get(testData[idx].key);
+ mixedGetIndex++;
+ },
+ iterations,
+ );
+
+ let mixedSetIndex = 0;
+ await timer.timeFunction(
+ "lru.set",
+ () => {
+ const idx = setIndices[mixedSetIndex % iterations];
+ phase2Cache.set(testData[idx].key, testData[idx].value);
+ mixedSetIndex++;
+ },
+ iterations,
+ );
+
+ let mixedHasIndex = 0;
+ await timer.timeFunction(
+ "lru.has",
+ () => {
+ const idx = hasIndices[mixedHasIndex % iterations];
+ phase2Cache.has(testData[idx].key);
+ mixedHasIndex++;
+ },
+ iterations,
+ );
+
+ // keys()
+ await timer.timeFunction(
+ "lru.keys",
+ () => {
+ phase2Cache.keys();
+ },
+ iterations,
+ );
+
+ // values()
+ await timer.timeFunction(
+ "lru.values",
+ () => {
+ phase2Cache.values();
+ },
+ iterations,
+ );
+
+ // entries()
+ await timer.timeFunction(
+ "lru.entries",
+ () => {
+ phase2Cache.entries();
+ },
+ iterations,
+ );
+
+ let mixedDeleteIndex = 0;
+ await timer.timeFunction(
+ "lru.delete",
+ () => {
+ const idx = deleteIndices[mixedDeleteIndex % iterations];
+ phase2Cache.delete(testData[idx].key);
+ mixedDeleteIndex++;
+ },
+ iterations,
+ );
+
+ // Phase 3: Cache eviction stress test
+ console.log("Phase 3: Cache eviction stress test");
+ const phase3Cache = lru(2);
+ let phase3Index = 1;
+ phase3Cache.set(`evict_key_${phase3Index}`, `evict__value_${phase3Index++}`);
+ await timer.timeFunction(
+ "lru.set (eviction stress)",
+ () => {
+ phase3Cache.set(`evict_key_${phase3Index}`, `evict_value_${phase3Index++}`);
+ },
+ iterations,
+ );
+
+ // Phase 4: Some clear operations
+ console.log("Phase 4: Clear operations");
+ const phase4Cache = lru(1);
+ await timer.timeFunction(
+ "lru.clear",
+ () => {
+ phase4Cache.set("temp_1", "temp_value_1");
+ phase4Cache.clear();
+ },
+ iterations,
+ );
+
+ // Phase 5: Additional API method benchmarks
+ console.log("Phase 5: Additional API method benchmarks");
+
+ // setWithEvicted()
+ const setWithEvictedCache = lru(2);
+ setWithEvictedCache.set("a", "value_a");
+ setWithEvictedCache.set("b", "value_b");
+ let setWithEvictedIndex = 0;
+ await timer.timeFunction(
+ "lru.setWithEvicted",
+ () => {
+ const key = `extra_key_${setWithEvictedIndex}`;
+ const value = `extra_value_${setWithEvictedIndex}`;
+ setWithEvictedCache.setWithEvicted(key, value);
+ setWithEvictedIndex++;
+ },
+ iterations,
+ );
+
+ // expiresAt()
+ const expiresCache = lru(cacheSize, 6e4);
+ const expiresKey = "expires_key";
+ expiresCache.set(expiresKey, "expires_value");
+ await timer.timeFunction(
+ "lru.expiresAt",
+ () => {
+ expiresCache.expiresAt(expiresKey);
+ },
+ iterations,
+ );
+
+ timer.printResults();
}
// Comparison with different cache sizes
-async function runScalabilityTest () {
- console.log("\n📈 Scalability Test");
- console.log("===================");
-
- const sizes = [100, 500, 1000, 5000, 10000];
- const results = [];
-
- for (const size of sizes) {
- console.log(`Testing cache size: ${size}`);
- const testData = generateTestData(size);
-
- // Test set performance
- const cache = lru(size);
- const setStart = performance.now();
- testData.forEach(item => cache.set(item.key, item.value));
- const setEnd = performance.now();
- const setTime = setEnd - setStart;
-
- // Test get performance
- const getStart = performance.now();
- for (let i = 0; i < 1000; i++) {
- const item = testData[Math.floor(Math.random() * testData.length)];
- cache.get(item.key);
- }
- const getEnd = performance.now();
- const getTime = getEnd - getStart;
-
- results.push({
- "Size": size,
- "Set Total (ms)": setTime.toFixed(2),
- "Set Per Item (ms)": (setTime / size).toFixed(4),
- "Get 1K Items (ms)": getTime.toFixed(2),
- "Get Per Item (ms)": (getTime / 1000).toFixed(4)
- });
- }
-
- console.table(results);
+async function runScalabilityTest() {
+ console.log("\n📈 Scalability Test");
+ console.log("===================");
+
+ const sizes = [100, 500, 1000, 5000, 10000];
+ const results = [];
+
+ for (const size of sizes) {
+ console.log(`Testing cache size: ${size}`);
+ const testData = generateTestData(size);
+
+ // Test set performance
+ const cache = lru(size);
+ const setStart = performance.now();
+ testData.forEach((item) => cache.set(item.key, item.value));
+ const setEnd = performance.now();
+ const setTime = setEnd - setStart;
+
+ // Test get performance
+ const getStart = performance.now();
+ for (let i = 0; i < 1000; i++) {
+ const item = testData[Math.floor(Math.random() * testData.length)];
+ cache.get(item.key);
+ }
+ const getEnd = performance.now();
+ const getTime = getEnd - getStart;
+
+ results.push({
+ Size: size,
+ "Set Total (ms)": setTime.toFixed(2),
+ "Set Per Item (ms)": (setTime / size).toFixed(4),
+ "Get 1K Items (ms)": getTime.toFixed(2),
+ "Get Per Item (ms)": (getTime / 1000).toFixed(4),
+ });
+ }
+
+ console.table(results);
}
// Main execution
-async function runAllPerformanceTests () {
- console.log("🔬 Node.js Performance API Benchmarks");
- console.log("======================================");
- console.log(`Node.js version: ${process.version}`);
- console.log(`Platform: ${process.platform} ${process.arch}`);
- console.log(`Date: ${new Date().toISOString()}`);
-
- try {
- await runPerformanceBenchmarks();
- await runScalabilityTest();
-
- console.log("\n✅ Performance tests completed!");
- console.log("\n📋 Notes:");
- console.log("- Benchmarks: High-resolution timing with statistical analysis using CustomTimer (based on performance.now())");
- console.log("- Scalability Test: Shows how performance scales with cache size");
-
- } catch (error) {
- console.error("❌ Performance test failed:", error);
- process.exit(1);
- }
+async function runAllPerformanceTests() {
+ console.log("🔬 Node.js Performance API Benchmarks");
+ console.log("======================================");
+ console.log(`Node.js version: ${process.version}`);
+ console.log(`Platform: ${process.platform} ${process.arch}`);
+ console.log(`Date: ${new Date().toISOString()}`);
+
+ try {
+ await runPerformanceBenchmarks();
+ await runScalabilityTest();
+
+ console.log("\n✅ Performance tests completed!");
+ console.log("\n📋 Notes:");
+ console.log(
+ "- Benchmarks: High-resolution timing with statistical analysis using CustomTimer (based on performance.now())",
+ );
+ console.log("- Scalability Test: Shows how performance scales with cache size");
+ } catch (error) {
+ console.error("❌ Performance test failed:", error);
+ process.exit(1);
+ }
}
// Allow running this file directly
if (import.meta.url === `file://${process.argv[1]}`) {
- runAllPerformanceTests();
+ runAllPerformanceTests();
}
-export {
- runAllPerformanceTests,
- runPerformanceBenchmarks,
- runScalabilityTest,
- CustomTimer
-};
+export { runAllPerformanceTests, runPerformanceBenchmarks, runScalabilityTest, CustomTimer };
diff --git a/coverage.txt b/coverage.txt
new file mode 100644
index 0000000..a965e38
--- /dev/null
+++ b/coverage.txt
@@ -0,0 +1,10 @@
+ℹ start of coverage report
+ℹ ----------------------------------------------------------
+ℹ file | line % | branch % | funcs % | uncovered lines
+ℹ ----------------------------------------------------------
+ℹ src | | | |
+ℹ lru.js | 100.00 | 95.00 | 100.00 |
+ℹ ----------------------------------------------------------
+ℹ all files | 100.00 | 95.00 | 100.00 |
+ℹ ----------------------------------------------------------
+ℹ end of coverage report
diff --git a/dist/tiny-lru.cjs b/dist/tiny-lru.cjs
index 7baf605..67d0824 100644
--- a/dist/tiny-lru.cjs
+++ b/dist/tiny-lru.cjs
@@ -26,432 +26,443 @@
* // After 5 seconds, key1 will be expired
*/
class LRU {
- /**
- * Creates a new LRU cache instance.
- * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
- *
- * @constructor
- * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
- * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
- * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
- * @example
- * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
- * @see {@link lru} For parameter validation
- * @since 1.0.0
- */
- constructor (max = 0, ttl = 0, resetTtl = false) {
- this.first = null;
- this.items = Object.create(null);
- this.last = null;
- this.max = max;
- this.resetTtl = resetTtl;
- this.size = 0;
- this.ttl = ttl;
- }
-
- /**
- * Removes all items from the cache.
- *
- * @method clear
- * @memberof LRU
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.clear();
- * console.log(cache.size); // 0
- * @since 1.0.0
- */
- clear () {
- this.first = null;
- this.items = Object.create(null);
- this.last = null;
- this.size = 0;
-
- return this;
- }
-
- /**
- * Removes an item from the cache by key.
- *
- * @method delete
- * @memberof LRU
- * @param {string} key - The key of the item to delete.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('key1', 'value1');
- * cache.delete('key1');
- * console.log(cache.has('key1')); // false
- * @see {@link LRU#has}
- * @see {@link LRU#clear}
- * @since 1.0.0
- */
- delete (key) {
- if (this.has(key)) {
- const item = this.items[key];
-
- delete this.items[key];
- this.size--;
-
- if (item.prev !== null) {
- item.prev.next = item.next;
- }
-
- if (item.next !== null) {
- item.next.prev = item.prev;
- }
-
- if (this.first === item) {
- this.first = item.next;
- }
-
- if (this.last === item) {
- this.last = item.prev;
- }
- }
-
- return this;
- }
-
- /**
- * Returns an array of [key, value] pairs for the specified keys.
- * Order follows LRU order (least to most recently used).
- *
- * @method entries
- * @memberof LRU
- * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
- * @returns {Array>} Array of [key, value] pairs in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * console.log(cache.entries()); // [['a', 1], ['b', 2]]
- * console.log(cache.entries(['a'])); // [['a', 1]]
- * @see {@link LRU#keys}
- * @see {@link LRU#values}
- * @since 11.1.0
- */
- entries (keys = this.keys()) {
- const result = new Array(keys.length);
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i];
- result[i] = [key, this.get(key)];
- }
-
- return result;
- }
-
- /**
- * Removes the least recently used item from the cache.
- *
- * @method evict
- * @memberof LRU
- * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('old', 'value').set('new', 'value');
- * cache.evict(); // Removes 'old' item
- * @see {@link LRU#setWithEvicted}
- * @since 1.0.0
- */
- evict (bypass = false) {
- if (bypass || this.size > 0) {
- const item = this.first;
-
- delete this.items[item.key];
-
- if (--this.size === 0) {
- this.first = null;
- this.last = null;
- } else {
- this.first = item.next;
- this.first.prev = null;
- }
- }
-
- return this;
- }
-
- /**
- * Returns the expiration timestamp for a given key.
- *
- * @method expiresAt
- * @memberof LRU
- * @param {string} key - The key to check expiration for.
- * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
- * @example
- * const cache = new LRU(100, 5000); // 5 second TTL
- * cache.set('key1', 'value1');
- * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
- * @see {@link LRU#get}
- * @see {@link LRU#has}
- * @since 1.0.0
- */
- expiresAt (key) {
- let result;
-
- if (this.has(key)) {
- result = this.items[key].expiry;
- }
-
- return result;
- }
-
- /**
- * Retrieves a value from the cache by key. Updates the item's position to most recently used.
- *
- * @method get
- * @memberof LRU
- * @param {string} key - The key to retrieve.
- * @returns {*} The value associated with the key, or undefined if not found or expired.
- * @example
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- * console.log(cache.get('nonexistent')); // undefined
- * @see {@link LRU#set}
- * @see {@link LRU#has}
- * @since 1.0.0
- */
- get (key) {
- const item = this.items[key];
-
- if (item !== undefined) {
- // Check TTL only if enabled to avoid unnecessary Date.now() calls
- if (this.ttl > 0) {
- if (item.expiry <= Date.now()) {
- this.delete(key);
-
- return undefined;
- }
- }
-
- // Fast LRU update without full set() overhead
- this.moveToEnd(item);
-
- return item.value;
- }
-
- return undefined;
- }
-
- /**
- * Checks if a key exists in the cache.
- *
- * @method has
- * @memberof LRU
- * @param {string} key - The key to check for.
- * @returns {boolean} True if the key exists, false otherwise.
- * @example
- * cache.set('key1', 'value1');
- * console.log(cache.has('key1')); // true
- * console.log(cache.has('nonexistent')); // false
- * @see {@link LRU#get}
- * @see {@link LRU#delete}
- * @since 9.0.0
- */
- has (key) {
- return key in this.items;
- }
-
- /**
- * Efficiently moves an item to the end of the LRU list (most recently used position).
- * This is an internal optimization method that avoids the overhead of the full set() operation
- * when only LRU position needs to be updated.
- *
- * @method moveToEnd
- * @memberof LRU
- * @param {Object} item - The cache item with prev/next pointers to reposition.
- * @private
- * @since 11.3.5
- */
- moveToEnd (item) {
- // If already at the end, nothing to do
- if (this.last === item) {
- return;
- }
-
- // Remove item from current position in the list
- if (item.prev !== null) {
- item.prev.next = item.next;
- }
-
- if (item.next !== null) {
- item.next.prev = item.prev;
- }
-
- // Update first pointer if this was the first item
- if (this.first === item) {
- this.first = item.next;
- }
-
- // Add item to the end
- item.prev = this.last;
- item.next = null;
-
- if (this.last !== null) {
- this.last.next = item;
- }
-
- this.last = item;
-
- // Handle edge case: if this was the only item, it's also first
- if (this.first === null) {
- this.first = item;
- }
- }
-
- /**
- * Returns an array of all keys in the cache, ordered from least to most recently used.
- *
- * @method keys
- * @memberof LRU
- * @returns {string[]} Array of keys in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * cache.get('a'); // Move 'a' to most recent
- * console.log(cache.keys()); // ['b', 'a']
- * @see {@link LRU#values}
- * @see {@link LRU#entries}
- * @since 9.0.0
- */
- keys () {
- const result = new Array(this.size);
- let x = this.first;
- let i = 0;
-
- while (x !== null) {
- result[i++] = x.key;
- x = x.next;
- }
-
- return result;
- }
-
- /**
- * Sets a value in the cache and returns any evicted item.
- *
- * @method setWithEvicted
- * @memberof LRU
- * @param {string} key - The key to set.
- * @param {*} value - The value to store.
- * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
- * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
- * @example
- * const cache = new LRU(2);
- * cache.set('a', 1).set('b', 2);
- * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
- * @see {@link LRU#set}
- * @see {@link LRU#evict}
- * @since 11.3.0
- */
- setWithEvicted (key, value, resetTtl = this.resetTtl) {
- let evicted = null;
-
- if (this.has(key)) {
- this.set(key, value, true, resetTtl);
- } else {
- if (this.max > 0 && this.size === this.max) {
- evicted = {...this.first};
- this.evict(true);
- }
-
- let item = this.items[key] = {
- expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
- key: key,
- prev: this.last,
- next: null,
- value
- };
-
- if (++this.size === 1) {
- this.first = item;
- } else {
- this.last.next = item;
- }
-
- this.last = item;
- }
-
- return evicted;
- }
-
- /**
- * Sets a value in the cache. Updates the item's position to most recently used.
- *
- * @method set
- * @memberof LRU
- * @param {string} key - The key to set.
- * @param {*} value - The value to store.
- * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
- * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('key1', 'value1')
- * .set('key2', 'value2')
- * .set('key3', 'value3');
- * @see {@link LRU#get}
- * @see {@link LRU#setWithEvicted}
- * @since 1.0.0
- */
- set (key, value, bypass = false, resetTtl = this.resetTtl) {
- let item = this.items[key];
-
- if (bypass || item !== undefined) {
- // Existing item: update value and position
- item.value = value;
-
- if (bypass === false && resetTtl) {
- item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
- }
-
- // Always move to end, but the bypass parameter affects TTL reset behavior
- this.moveToEnd(item);
- } else {
- // New item: check for eviction and create
- if (this.max > 0 && this.size === this.max) {
- this.evict(true);
- }
-
- item = this.items[key] = {
- expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
- key: key,
- prev: this.last,
- next: null,
- value
- };
-
- if (++this.size === 1) {
- this.first = item;
- } else {
- this.last.next = item;
- }
-
- this.last = item;
- }
-
- return this;
- }
-
- /**
- * Returns an array of all values in the cache for the specified keys.
- * Order follows LRU order (least to most recently used).
- *
- * @method values
- * @memberof LRU
- * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
- * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * console.log(cache.values()); // [1, 2]
- * console.log(cache.values(['a'])); // [1]
- * @see {@link LRU#keys}
- * @see {@link LRU#entries}
- * @since 11.1.0
- */
- values (keys = this.keys()) {
- const result = new Array(keys.length);
- for (let i = 0; i < keys.length; i++) {
- result[i] = this.get(keys[i]);
- }
-
- return result;
- }
+ /**
+ * Creates a new LRU cache instance.
+ * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
+ *
+ * @constructor
+ * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
+ * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
+ * @example
+ * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
+ * @see {@link lru} For parameter validation
+ * @since 1.0.0
+ */
+ constructor(max = 0, ttl = 0, resetTtl = false) {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.max = max;
+ this.resetTtl = resetTtl;
+ this.size = 0;
+ this.ttl = ttl;
+ }
+
+ /**
+ * Removes all items from the cache.
+ *
+ * @method clear
+ * @memberof LRU
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.clear();
+ * console.log(cache.size); // 0
+ * @since 1.0.0
+ */
+ clear() {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.size = 0;
+
+ return this;
+ }
+
+ /**
+ * Removes an item from the cache by key.
+ *
+ * @method delete
+ * @memberof LRU
+ * @param {string} key - The key of the item to delete.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1');
+ * cache.delete('key1');
+ * console.log(cache.has('key1')); // false
+ * @see {@link LRU#has}
+ * @see {@link LRU#clear}
+ * @since 1.0.0
+ */
+ delete(key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ delete this.items[key];
+ this.size--;
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ if (this.last === item) {
+ this.last = item.prev;
+ }
+
+ item.prev = null;
+ item.next = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of [key, value] pairs for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method entries
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
+ * @returns {Array>} Array of [key, value] pairs in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.entries()); // [['a', 1], ['b', 2]]
+ * console.log(cache.entries(['a'])); // [['a', 1]]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#values}
+ * @since 11.1.0
+ */
+ entries(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ const item = this.items[key];
+ result[i] = [key, item !== undefined ? item.value : undefined];
+ }
+
+ return result;
+ }
+
+ /**
+ * Removes the least recently used item from the cache.
+ *
+ * @method evict
+ * @memberof LRU
+ * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('old', 'value').set('new', 'value');
+ * cache.evict(); // Removes 'old' item
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ evict(bypass = false) {
+ if (bypass || this.size > 0) {
+ const item = this.first;
+
+ if (!item) {
+ return this;
+ }
+
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.first = item.next;
+ this.first.prev = null;
+ }
+
+ item.next = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns the expiration timestamp for a given key.
+ *
+ * @method expiresAt
+ * @memberof LRU
+ * @param {string} key - The key to check expiration for.
+ * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
+ * @example
+ * const cache = new LRU(100, 5000); // 5 second TTL
+ * cache.set('key1', 'value1');
+ * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
+ * @see {@link LRU#get}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ expiresAt(key) {
+ const item = this.items[key];
+ return item !== undefined ? item.expiry : undefined;
+ }
+
+ /**
+ * Retrieves a value from the cache by key. Updates the item's position to most recently used.
+ *
+ * @method get
+ * @memberof LRU
+ * @param {string} key - The key to retrieve.
+ * @returns {*} The value associated with the key, or undefined if not found or expired.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.get('key1')); // 'value1'
+ * console.log(cache.get('nonexistent')); // undefined
+ * @see {@link LRU#set}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ get(key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ // Check TTL only if enabled to avoid unnecessary Date.now() calls
+ if (this.ttl > 0) {
+ if (item.expiry <= Date.now()) {
+ this.delete(key);
+
+ return undefined;
+ }
+ }
+
+ // Fast LRU update without full set() overhead
+ this.moveToEnd(item);
+
+ return item.value;
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Checks if a key exists in the cache.
+ *
+ * @method has
+ * @memberof LRU
+ * @param {string} key - The key to check for.
+ * @returns {boolean} True if the key exists, false otherwise.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.has('key1')); // true
+ * console.log(cache.has('nonexistent')); // false
+ * @see {@link LRU#get}
+ * @see {@link LRU#delete}
+ * @since 9.0.0
+ */
+ has(key) {
+ const item = this.items[key];
+ return item !== undefined && (this.ttl === 0 || item.expiry > Date.now());
+ }
+
+ /**
+ * Efficiently moves an item to the end of the LRU list (most recently used position).
+ * This is an internal optimization method that avoids the overhead of the full set() operation
+ * when only LRU position needs to be updated.
+ *
+ * @method moveToEnd
+ * @memberof LRU
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ * @since 11.3.5
+ */
+ moveToEnd(item) {
+ if (this.last === item) {
+ return;
+ }
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ item.prev = this.last;
+ item.next = null;
+ this.last.next = item;
+ this.last = item;
+ }
+
+ /**
+ * Returns an array of all keys in the cache, ordered from least to most recently used.
+ *
+ * @method keys
+ * @memberof LRU
+ * @returns {string[]} Array of keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * cache.get('a'); // Move 'a' to most recent
+ * console.log(cache.keys()); // ['b', 'a']
+ * @see {@link LRU#values}
+ * @see {@link LRU#entries}
+ * @since 9.0.0
+ */
+ keys() {
+ const result = Array.from({ length: this.size });
+ let x = this.first;
+ let i = 0;
+
+ while (x !== null) {
+ result[i++] = x.key;
+ x = x.next;
+ }
+
+ return result;
+ }
+
+ /**
+ * Sets a value in the cache and returns any evicted item.
+ *
+ * @method setWithEvicted
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
+ * @example
+ * const cache = new LRU(2);
+ * cache.set('a', 1).set('b', 2);
+ * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
+ * @see {@link LRU#set}
+ * @see {@link LRU#evict}
+ * @since 11.3.0
+ */
+ setWithEvicted(key, value, resetTtl = this.resetTtl) {
+ let evicted = null;
+ let item = this.items[key];
+
+ if (item !== undefined) {
+ item.value = value;
+ if (resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+ this.moveToEnd(item);
+ } else {
+ if (this.max > 0 && this.size === this.max) {
+ evicted = {
+ key: this.first.key,
+ value: this.first.value,
+ expiry: this.first.expiry,
+ };
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value,
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return evicted;
+ }
+
+ /**
+ * Sets a value in the cache. Updates the item's position to most recently used.
+ *
+ * @method set
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1')
+ * .set('key2', 'value2')
+ * .set('key3', 'value3');
+ * @see {@link LRU#get}
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ set(key, value, bypass = false, resetTtl = this.resetTtl) {
+ let item = this.items[key];
+
+ if (bypass || item !== undefined) {
+ // Existing item: update value and position
+ item.value = value;
+
+ if (bypass === false && resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+
+ // Always move to end, but the bypass parameter affects TTL reset behavior
+ this.moveToEnd(item);
+ } else {
+ // New item: check for eviction and create
+ if (this.max > 0 && this.size === this.max) {
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value,
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of all values in the cache for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method values
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
+ * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.values()); // [1, 2]
+ * console.log(cache.values(['a'])); // [1]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#entries}
+ * @since 11.1.0
+ */
+ values(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
+ for (let i = 0; i < keys.length; i++) {
+ const item = this.items[keys[i]];
+ result[i] = item !== undefined ? item.value : undefined;
+ }
+
+ return result;
+ }
}
/**
@@ -478,20 +489,20 @@ class LRU {
* @see {@link LRU}
* @since 1.0.0
*/
-function lru (max = 1000, ttl = 0, resetTtl = false) {
- if (isNaN(max) || max < 0) {
- throw new TypeError("Invalid max value");
- }
+function lru(max = 1000, ttl = 0, resetTtl = false) {
+ if (isNaN(max) || max < 0) {
+ throw new TypeError("Invalid max value");
+ }
- if (isNaN(ttl) || ttl < 0) {
- throw new TypeError("Invalid ttl value");
- }
+ if (isNaN(ttl) || ttl < 0) {
+ throw new TypeError("Invalid ttl value");
+ }
- if (typeof resetTtl !== "boolean") {
- throw new TypeError("Invalid resetTtl value");
- }
+ if (typeof resetTtl !== "boolean") {
+ throw new TypeError("Invalid resetTtl value");
+ }
- return new LRU(max, ttl, resetTtl);
+ return new LRU(max, ttl, resetTtl);
}
exports.LRU = LRU;
diff --git a/dist/tiny-lru.js b/dist/tiny-lru.js
index cdec409..be888e9 100644
--- a/dist/tiny-lru.js
+++ b/dist/tiny-lru.js
@@ -24,432 +24,443 @@
* // After 5 seconds, key1 will be expired
*/
class LRU {
- /**
- * Creates a new LRU cache instance.
- * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
- *
- * @constructor
- * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
- * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
- * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
- * @example
- * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
- * @see {@link lru} For parameter validation
- * @since 1.0.0
- */
- constructor (max = 0, ttl = 0, resetTtl = false) {
- this.first = null;
- this.items = Object.create(null);
- this.last = null;
- this.max = max;
- this.resetTtl = resetTtl;
- this.size = 0;
- this.ttl = ttl;
- }
-
- /**
- * Removes all items from the cache.
- *
- * @method clear
- * @memberof LRU
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.clear();
- * console.log(cache.size); // 0
- * @since 1.0.0
- */
- clear () {
- this.first = null;
- this.items = Object.create(null);
- this.last = null;
- this.size = 0;
-
- return this;
- }
-
- /**
- * Removes an item from the cache by key.
- *
- * @method delete
- * @memberof LRU
- * @param {string} key - The key of the item to delete.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('key1', 'value1');
- * cache.delete('key1');
- * console.log(cache.has('key1')); // false
- * @see {@link LRU#has}
- * @see {@link LRU#clear}
- * @since 1.0.0
- */
- delete (key) {
- if (this.has(key)) {
- const item = this.items[key];
-
- delete this.items[key];
- this.size--;
-
- if (item.prev !== null) {
- item.prev.next = item.next;
- }
-
- if (item.next !== null) {
- item.next.prev = item.prev;
- }
-
- if (this.first === item) {
- this.first = item.next;
- }
-
- if (this.last === item) {
- this.last = item.prev;
- }
- }
-
- return this;
- }
-
- /**
- * Returns an array of [key, value] pairs for the specified keys.
- * Order follows LRU order (least to most recently used).
- *
- * @method entries
- * @memberof LRU
- * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
- * @returns {Array>} Array of [key, value] pairs in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * console.log(cache.entries()); // [['a', 1], ['b', 2]]
- * console.log(cache.entries(['a'])); // [['a', 1]]
- * @see {@link LRU#keys}
- * @see {@link LRU#values}
- * @since 11.1.0
- */
- entries (keys = this.keys()) {
- const result = new Array(keys.length);
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i];
- result[i] = [key, this.get(key)];
- }
-
- return result;
- }
-
- /**
- * Removes the least recently used item from the cache.
- *
- * @method evict
- * @memberof LRU
- * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('old', 'value').set('new', 'value');
- * cache.evict(); // Removes 'old' item
- * @see {@link LRU#setWithEvicted}
- * @since 1.0.0
- */
- evict (bypass = false) {
- if (bypass || this.size > 0) {
- const item = this.first;
-
- delete this.items[item.key];
-
- if (--this.size === 0) {
- this.first = null;
- this.last = null;
- } else {
- this.first = item.next;
- this.first.prev = null;
- }
- }
-
- return this;
- }
-
- /**
- * Returns the expiration timestamp for a given key.
- *
- * @method expiresAt
- * @memberof LRU
- * @param {string} key - The key to check expiration for.
- * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
- * @example
- * const cache = new LRU(100, 5000); // 5 second TTL
- * cache.set('key1', 'value1');
- * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
- * @see {@link LRU#get}
- * @see {@link LRU#has}
- * @since 1.0.0
- */
- expiresAt (key) {
- let result;
-
- if (this.has(key)) {
- result = this.items[key].expiry;
- }
-
- return result;
- }
-
- /**
- * Retrieves a value from the cache by key. Updates the item's position to most recently used.
- *
- * @method get
- * @memberof LRU
- * @param {string} key - The key to retrieve.
- * @returns {*} The value associated with the key, or undefined if not found or expired.
- * @example
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- * console.log(cache.get('nonexistent')); // undefined
- * @see {@link LRU#set}
- * @see {@link LRU#has}
- * @since 1.0.0
- */
- get (key) {
- const item = this.items[key];
-
- if (item !== undefined) {
- // Check TTL only if enabled to avoid unnecessary Date.now() calls
- if (this.ttl > 0) {
- if (item.expiry <= Date.now()) {
- this.delete(key);
-
- return undefined;
- }
- }
-
- // Fast LRU update without full set() overhead
- this.moveToEnd(item);
-
- return item.value;
- }
-
- return undefined;
- }
-
- /**
- * Checks if a key exists in the cache.
- *
- * @method has
- * @memberof LRU
- * @param {string} key - The key to check for.
- * @returns {boolean} True if the key exists, false otherwise.
- * @example
- * cache.set('key1', 'value1');
- * console.log(cache.has('key1')); // true
- * console.log(cache.has('nonexistent')); // false
- * @see {@link LRU#get}
- * @see {@link LRU#delete}
- * @since 9.0.0
- */
- has (key) {
- return key in this.items;
- }
-
- /**
- * Efficiently moves an item to the end of the LRU list (most recently used position).
- * This is an internal optimization method that avoids the overhead of the full set() operation
- * when only LRU position needs to be updated.
- *
- * @method moveToEnd
- * @memberof LRU
- * @param {Object} item - The cache item with prev/next pointers to reposition.
- * @private
- * @since 11.3.5
- */
- moveToEnd (item) {
- // If already at the end, nothing to do
- if (this.last === item) {
- return;
- }
-
- // Remove item from current position in the list
- if (item.prev !== null) {
- item.prev.next = item.next;
- }
-
- if (item.next !== null) {
- item.next.prev = item.prev;
- }
-
- // Update first pointer if this was the first item
- if (this.first === item) {
- this.first = item.next;
- }
-
- // Add item to the end
- item.prev = this.last;
- item.next = null;
-
- if (this.last !== null) {
- this.last.next = item;
- }
-
- this.last = item;
-
- // Handle edge case: if this was the only item, it's also first
- if (this.first === null) {
- this.first = item;
- }
- }
-
- /**
- * Returns an array of all keys in the cache, ordered from least to most recently used.
- *
- * @method keys
- * @memberof LRU
- * @returns {string[]} Array of keys in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * cache.get('a'); // Move 'a' to most recent
- * console.log(cache.keys()); // ['b', 'a']
- * @see {@link LRU#values}
- * @see {@link LRU#entries}
- * @since 9.0.0
- */
- keys () {
- const result = new Array(this.size);
- let x = this.first;
- let i = 0;
-
- while (x !== null) {
- result[i++] = x.key;
- x = x.next;
- }
-
- return result;
- }
-
- /**
- * Sets a value in the cache and returns any evicted item.
- *
- * @method setWithEvicted
- * @memberof LRU
- * @param {string} key - The key to set.
- * @param {*} value - The value to store.
- * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
- * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
- * @example
- * const cache = new LRU(2);
- * cache.set('a', 1).set('b', 2);
- * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
- * @see {@link LRU#set}
- * @see {@link LRU#evict}
- * @since 11.3.0
- */
- setWithEvicted (key, value, resetTtl = this.resetTtl) {
- let evicted = null;
-
- if (this.has(key)) {
- this.set(key, value, true, resetTtl);
- } else {
- if (this.max > 0 && this.size === this.max) {
- evicted = {...this.first};
- this.evict(true);
- }
-
- let item = this.items[key] = {
- expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
- key: key,
- prev: this.last,
- next: null,
- value
- };
-
- if (++this.size === 1) {
- this.first = item;
- } else {
- this.last.next = item;
- }
-
- this.last = item;
- }
-
- return evicted;
- }
-
- /**
- * Sets a value in the cache. Updates the item's position to most recently used.
- *
- * @method set
- * @memberof LRU
- * @param {string} key - The key to set.
- * @param {*} value - The value to store.
- * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
- * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('key1', 'value1')
- * .set('key2', 'value2')
- * .set('key3', 'value3');
- * @see {@link LRU#get}
- * @see {@link LRU#setWithEvicted}
- * @since 1.0.0
- */
- set (key, value, bypass = false, resetTtl = this.resetTtl) {
- let item = this.items[key];
-
- if (bypass || item !== undefined) {
- // Existing item: update value and position
- item.value = value;
-
- if (bypass === false && resetTtl) {
- item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
- }
-
- // Always move to end, but the bypass parameter affects TTL reset behavior
- this.moveToEnd(item);
- } else {
- // New item: check for eviction and create
- if (this.max > 0 && this.size === this.max) {
- this.evict(true);
- }
-
- item = this.items[key] = {
- expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
- key: key,
- prev: this.last,
- next: null,
- value
- };
-
- if (++this.size === 1) {
- this.first = item;
- } else {
- this.last.next = item;
- }
-
- this.last = item;
- }
-
- return this;
- }
-
- /**
- * Returns an array of all values in the cache for the specified keys.
- * Order follows LRU order (least to most recently used).
- *
- * @method values
- * @memberof LRU
- * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
- * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * console.log(cache.values()); // [1, 2]
- * console.log(cache.values(['a'])); // [1]
- * @see {@link LRU#keys}
- * @see {@link LRU#entries}
- * @since 11.1.0
- */
- values (keys = this.keys()) {
- const result = new Array(keys.length);
- for (let i = 0; i < keys.length; i++) {
- result[i] = this.get(keys[i]);
- }
-
- return result;
- }
+ /**
+ * Creates a new LRU cache instance.
+ * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
+ *
+ * @constructor
+ * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
+ * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
+ * @example
+ * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
+ * @see {@link lru} For parameter validation
+ * @since 1.0.0
+ */
+ constructor(max = 0, ttl = 0, resetTtl = false) {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.max = max;
+ this.resetTtl = resetTtl;
+ this.size = 0;
+ this.ttl = ttl;
+ }
+
+ /**
+ * Removes all items from the cache.
+ *
+ * @method clear
+ * @memberof LRU
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.clear();
+ * console.log(cache.size); // 0
+ * @since 1.0.0
+ */
+ clear() {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.size = 0;
+
+ return this;
+ }
+
+ /**
+ * Removes an item from the cache by key.
+ *
+ * @method delete
+ * @memberof LRU
+ * @param {string} key - The key of the item to delete.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1');
+ * cache.delete('key1');
+ * console.log(cache.has('key1')); // false
+ * @see {@link LRU#has}
+ * @see {@link LRU#clear}
+ * @since 1.0.0
+ */
+ delete(key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ delete this.items[key];
+ this.size--;
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ if (this.last === item) {
+ this.last = item.prev;
+ }
+
+ item.prev = null;
+ item.next = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of [key, value] pairs for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method entries
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
+ * @returns {Array>} Array of [key, value] pairs in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.entries()); // [['a', 1], ['b', 2]]
+ * console.log(cache.entries(['a'])); // [['a', 1]]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#values}
+ * @since 11.1.0
+ */
+ entries(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ const item = this.items[key];
+ result[i] = [key, item !== undefined ? item.value : undefined];
+ }
+
+ return result;
+ }
+
+ /**
+ * Removes the least recently used item from the cache.
+ *
+ * @method evict
+ * @memberof LRU
+ * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('old', 'value').set('new', 'value');
+ * cache.evict(); // Removes 'old' item
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ evict(bypass = false) {
+ if (bypass || this.size > 0) {
+ const item = this.first;
+
+ if (!item) {
+ return this;
+ }
+
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.first = item.next;
+ this.first.prev = null;
+ }
+
+ item.next = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns the expiration timestamp for a given key.
+ *
+ * @method expiresAt
+ * @memberof LRU
+ * @param {string} key - The key to check expiration for.
+ * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
+ * @example
+ * const cache = new LRU(100, 5000); // 5 second TTL
+ * cache.set('key1', 'value1');
+ * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
+ * @see {@link LRU#get}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ expiresAt(key) {
+ const item = this.items[key];
+ return item !== undefined ? item.expiry : undefined;
+ }
+
+ /**
+ * Retrieves a value from the cache by key. Updates the item's position to most recently used.
+ *
+ * @method get
+ * @memberof LRU
+ * @param {string} key - The key to retrieve.
+ * @returns {*} The value associated with the key, or undefined if not found or expired.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.get('key1')); // 'value1'
+ * console.log(cache.get('nonexistent')); // undefined
+ * @see {@link LRU#set}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ get(key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ // Check TTL only if enabled to avoid unnecessary Date.now() calls
+ if (this.ttl > 0) {
+ if (item.expiry <= Date.now()) {
+ this.delete(key);
+
+ return undefined;
+ }
+ }
+
+ // Fast LRU update without full set() overhead
+ this.moveToEnd(item);
+
+ return item.value;
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Checks if a key exists in the cache.
+ *
+ * @method has
+ * @memberof LRU
+ * @param {string} key - The key to check for.
+ * @returns {boolean} True if the key exists, false otherwise.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.has('key1')); // true
+ * console.log(cache.has('nonexistent')); // false
+ * @see {@link LRU#get}
+ * @see {@link LRU#delete}
+ * @since 9.0.0
+ */
+ has(key) {
+ const item = this.items[key];
+ return item !== undefined && (this.ttl === 0 || item.expiry > Date.now());
+ }
+
+ /**
+ * Efficiently moves an item to the end of the LRU list (most recently used position).
+ * This is an internal optimization method that avoids the overhead of the full set() operation
+ * when only LRU position needs to be updated.
+ *
+ * @method moveToEnd
+ * @memberof LRU
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ * @since 11.3.5
+ */
+ moveToEnd(item) {
+ if (this.last === item) {
+ return;
+ }
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ item.prev = this.last;
+ item.next = null;
+ this.last.next = item;
+ this.last = item;
+ }
+
+ /**
+ * Returns an array of all keys in the cache, ordered from least to most recently used.
+ *
+ * @method keys
+ * @memberof LRU
+ * @returns {string[]} Array of keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * cache.get('a'); // Move 'a' to most recent
+ * console.log(cache.keys()); // ['b', 'a']
+ * @see {@link LRU#values}
+ * @see {@link LRU#entries}
+ * @since 9.0.0
+ */
+ keys() {
+ const result = Array.from({ length: this.size });
+ let x = this.first;
+ let i = 0;
+
+ while (x !== null) {
+ result[i++] = x.key;
+ x = x.next;
+ }
+
+ return result;
+ }
+
+ /**
+ * Sets a value in the cache and returns any evicted item.
+ *
+ * @method setWithEvicted
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
+ * @example
+ * const cache = new LRU(2);
+ * cache.set('a', 1).set('b', 2);
+ * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
+ * @see {@link LRU#set}
+ * @see {@link LRU#evict}
+ * @since 11.3.0
+ */
+ setWithEvicted(key, value, resetTtl = this.resetTtl) {
+ let evicted = null;
+ let item = this.items[key];
+
+ if (item !== undefined) {
+ item.value = value;
+ if (resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+ this.moveToEnd(item);
+ } else {
+ if (this.max > 0 && this.size === this.max) {
+ evicted = {
+ key: this.first.key,
+ value: this.first.value,
+ expiry: this.first.expiry,
+ };
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value,
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return evicted;
+ }
+
+ /**
+ * Sets a value in the cache. Updates the item's position to most recently used.
+ *
+ * @method set
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1')
+ * .set('key2', 'value2')
+ * .set('key3', 'value3');
+ * @see {@link LRU#get}
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ set(key, value, bypass = false, resetTtl = this.resetTtl) {
+ let item = this.items[key];
+
+ if (bypass || item !== undefined) {
+ // Existing item: update value and position
+ item.value = value;
+
+ if (bypass === false && resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+
+ // Always move to end, but the bypass parameter affects TTL reset behavior
+ this.moveToEnd(item);
+ } else {
+ // New item: check for eviction and create
+ if (this.max > 0 && this.size === this.max) {
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value,
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of all values in the cache for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method values
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
+ * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.values()); // [1, 2]
+ * console.log(cache.values(['a'])); // [1]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#entries}
+ * @since 11.1.0
+ */
+ values(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
+ for (let i = 0; i < keys.length; i++) {
+ const item = this.items[keys[i]];
+ result[i] = item !== undefined ? item.value : undefined;
+ }
+
+ return result;
+ }
}
/**
@@ -476,18 +487,18 @@ class LRU {
* @see {@link LRU}
* @since 1.0.0
*/
-function lru (max = 1000, ttl = 0, resetTtl = false) {
- if (isNaN(max) || max < 0) {
- throw new TypeError("Invalid max value");
- }
+function lru(max = 1000, ttl = 0, resetTtl = false) {
+ if (isNaN(max) || max < 0) {
+ throw new TypeError("Invalid max value");
+ }
- if (isNaN(ttl) || ttl < 0) {
- throw new TypeError("Invalid ttl value");
- }
+ if (isNaN(ttl) || ttl < 0) {
+ throw new TypeError("Invalid ttl value");
+ }
- if (typeof resetTtl !== "boolean") {
- throw new TypeError("Invalid resetTtl value");
- }
+ if (typeof resetTtl !== "boolean") {
+ throw new TypeError("Invalid resetTtl value");
+ }
- return new LRU(max, ttl, resetTtl);
+ return new LRU(max, ttl, resetTtl);
}export{LRU,lru};
\ No newline at end of file
diff --git a/dist/tiny-lru.min.js b/dist/tiny-lru.min.js
index 63f8d9a..382c0c0 100644
--- a/dist/tiny-lru.min.js
+++ b/dist/tiny-lru.min.js
@@ -2,4 +2,4 @@
2026 Jason Mulligan
@version 11.4.7
*/
-class t{constructor(t=0,s=0,e=!1){this.first=null,this.items=Object.create(null),this.last=null,this.max=t,this.resetTtl=e,this.size=0,this.ttl=s}clear(){return this.first=null,this.items=Object.create(null),this.last=null,this.size=0,this}delete(t){if(this.has(t)){const s=this.items[t];delete this.items[t],this.size--,null!==s.prev&&(s.prev.next=s.next),null!==s.next&&(s.next.prev=s.prev),this.first===s&&(this.first=s.next),this.last===s&&(this.last=s.prev)}return this}entries(t=this.keys()){const s=new Array(t.length);for(let e=0;e0){const t=this.first;delete this.items[t.key],0==--this.size?(this.first=null,this.last=null):(this.first=t.next,this.first.prev=null)}return this}expiresAt(t){let s;return this.has(t)&&(s=this.items[t].expiry),s}get(t){const s=this.items[t];if(void 0!==s)return this.ttl>0&&s.expiry<=Date.now()?void this.delete(t):(this.moveToEnd(s),s.value)}has(t){return t in this.items}moveToEnd(t){this.last!==t&&(null!==t.prev&&(t.prev.next=t.next),null!==t.next&&(t.next.prev=t.prev),this.first===t&&(this.first=t.next),t.prev=this.last,t.next=null,null!==this.last&&(this.last.next=t),this.last=t,null===this.first&&(this.first=t))}keys(){const t=new Array(this.size);let s=this.first,e=0;for(;null!==s;)t[e++]=s.key,s=s.next;return t}setWithEvicted(t,s,e=this.resetTtl){let i=null;if(this.has(t))this.set(t,s,!0,e);else{this.max>0&&this.size===this.max&&(i={...this.first},this.evict(!0));let e=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:s};1==++this.size?this.first=e:this.last.next=e,this.last=e}return i}set(t,s,e=!1,i=this.resetTtl){let l=this.items[t];return e||void 0!==l?(l.value=s,!1===e&&i&&(l.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(l)):(this.max>0&&this.size===this.max&&this.evict(!0),l=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:s},1==++this.size?this.first=l:this.last.next=l,this.last=l),this}values(t=this.keys()){const s=new Array(t.length);for(let e=0;e0){const t=this.first;if(!t)return this;delete this.items[t.key],0==--this.size?(this.first=null,this.last=null):(this.first=t.next,this.first.prev=null),t.next=null}return this}expiresAt(t){const s=this.items[t];return void 0!==s?s.expiry:void 0}get(t){const s=this.items[t];if(void 0!==s)return this.ttl>0&&s.expiry<=Date.now()?void this.delete(t):(this.moveToEnd(s),s.value)}has(t){const s=this.items[t];return void 0!==s&&(0===this.ttl||s.expiry>Date.now())}moveToEnd(t){this.last!==t&&(null!==t.prev&&(t.prev.next=t.next),null!==t.next&&(t.next.prev=t.prev),this.first===t&&(this.first=t.next),t.prev=this.last,t.next=null,this.last.next=t,this.last=t)}keys(){const t=Array.from({length:this.size});let s=this.first,e=0;for(;null!==s;)t[e++]=s.key,s=s.next;return t}setWithEvicted(t,s,e=this.resetTtl){let i=null,l=this.items[t];return void 0!==l?(l.value=s,e&&(l.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(l)):(this.max>0&&this.size===this.max&&(i={key:this.first.key,value:this.first.value,expiry:this.first.expiry},this.evict(!0)),l=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:s},1==++this.size?this.first=l:this.last.next=l,this.last=l),i}set(t,s,e=!1,i=this.resetTtl){let l=this.items[t];return e||void 0!==l?(l.value=s,!1===e&&i&&(l.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(l)):(this.max>0&&this.size===this.max&&this.evict(!0),l=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:s},1==++this.size?this.first=l:this.last.next=l,this.last=l),this}values(t){void 0===t&&(t=this.keys());const s=Array.from({length:t.length});for(let e=0;e>} Array of [key, value] pairs in LRU order.\n\t * @example\n\t * cache.set('a', 1).set('b', 2);\n\t * console.log(cache.entries()); // [['a', 1], ['b', 2]]\n\t * console.log(cache.entries(['a'])); // [['a', 1]]\n\t * @see {@link LRU#keys}\n\t * @see {@link LRU#values}\n\t * @since 11.1.0\n\t */\n\tentries (keys = this.keys()) {\n\t\tconst result = new Array(keys.length);\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst key = keys[i];\n\t\t\tresult[i] = [key, this.get(key)];\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Removes the least recently used item from the cache.\n\t *\n\t * @method evict\n\t * @memberof LRU\n\t * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t * @example\n\t * cache.set('old', 'value').set('new', 'value');\n\t * cache.evict(); // Removes 'old' item\n\t * @see {@link LRU#setWithEvicted}\n\t * @since 1.0.0\n\t */\n\tevict (bypass = false) {\n\t\tif (bypass || this.size > 0) {\n\t\t\tconst item = this.first;\n\n\t\t\tdelete this.items[item.key];\n\n\t\t\tif (--this.size === 0) {\n\t\t\t\tthis.first = null;\n\t\t\t\tthis.last = null;\n\t\t\t} else {\n\t\t\t\tthis.first = item.next;\n\t\t\t\tthis.first.prev = null;\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns the expiration timestamp for a given key.\n\t *\n\t * @method expiresAt\n\t * @memberof LRU\n\t * @param {string} key - The key to check expiration for.\n\t * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.\n\t * @example\n\t * const cache = new LRU(100, 5000); // 5 second TTL\n\t * cache.set('key1', 'value1');\n\t * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now\n\t * @see {@link LRU#get}\n\t * @see {@link LRU#has}\n\t * @since 1.0.0\n\t */\n\texpiresAt (key) {\n\t\tlet result;\n\n\t\tif (this.has(key)) {\n\t\t\tresult = this.items[key].expiry;\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Retrieves a value from the cache by key. Updates the item's position to most recently used.\n\t *\n\t * @method get\n\t * @memberof LRU\n\t * @param {string} key - The key to retrieve.\n\t * @returns {*} The value associated with the key, or undefined if not found or expired.\n\t * @example\n\t * cache.set('key1', 'value1');\n\t * console.log(cache.get('key1')); // 'value1'\n\t * console.log(cache.get('nonexistent')); // undefined\n\t * @see {@link LRU#set}\n\t * @see {@link LRU#has}\n\t * @since 1.0.0\n\t */\n\tget (key) {\n\t\tconst item = this.items[key];\n\n\t\tif (item !== undefined) {\n\t\t\t// Check TTL only if enabled to avoid unnecessary Date.now() calls\n\t\t\tif (this.ttl > 0) {\n\t\t\t\tif (item.expiry <= Date.now()) {\n\t\t\t\t\tthis.delete(key);\n\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fast LRU update without full set() overhead\n\t\t\tthis.moveToEnd(item);\n\n\t\t\treturn item.value;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Checks if a key exists in the cache.\n\t *\n\t * @method has\n\t * @memberof LRU\n\t * @param {string} key - The key to check for.\n\t * @returns {boolean} True if the key exists, false otherwise.\n\t * @example\n\t * cache.set('key1', 'value1');\n\t * console.log(cache.has('key1')); // true\n\t * console.log(cache.has('nonexistent')); // false\n\t * @see {@link LRU#get}\n\t * @see {@link LRU#delete}\n\t * @since 9.0.0\n\t */\n\thas (key) {\n\t\treturn key in this.items;\n\t}\n\n\t/**\n\t * Efficiently moves an item to the end of the LRU list (most recently used position).\n\t * This is an internal optimization method that avoids the overhead of the full set() operation\n\t * when only LRU position needs to be updated.\n\t *\n\t * @method moveToEnd\n\t * @memberof LRU\n\t * @param {Object} item - The cache item with prev/next pointers to reposition.\n\t * @private\n\t * @since 11.3.5\n\t */\n\tmoveToEnd (item) {\n\t\t// If already at the end, nothing to do\n\t\tif (this.last === item) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove item from current position in the list\n\t\tif (item.prev !== null) {\n\t\t\titem.prev.next = item.next;\n\t\t}\n\n\t\tif (item.next !== null) {\n\t\t\titem.next.prev = item.prev;\n\t\t}\n\n\t\t// Update first pointer if this was the first item\n\t\tif (this.first === item) {\n\t\t\tthis.first = item.next;\n\t\t}\n\n\t\t// Add item to the end\n\t\titem.prev = this.last;\n\t\titem.next = null;\n\n\t\tif (this.last !== null) {\n\t\t\tthis.last.next = item;\n\t\t}\n\n\t\tthis.last = item;\n\n\t\t// Handle edge case: if this was the only item, it's also first\n\t\tif (this.first === null) {\n\t\t\tthis.first = item;\n\t\t}\n\t}\n\n\t/**\n\t * Returns an array of all keys in the cache, ordered from least to most recently used.\n\t *\n\t * @method keys\n\t * @memberof LRU\n\t * @returns {string[]} Array of keys in LRU order.\n\t * @example\n\t * cache.set('a', 1).set('b', 2);\n\t * cache.get('a'); // Move 'a' to most recent\n\t * console.log(cache.keys()); // ['b', 'a']\n\t * @see {@link LRU#values}\n\t * @see {@link LRU#entries}\n\t * @since 9.0.0\n\t */\n\tkeys () {\n\t\tconst result = new Array(this.size);\n\t\tlet x = this.first;\n\t\tlet i = 0;\n\n\t\twhile (x !== null) {\n\t\t\tresult[i++] = x.key;\n\t\t\tx = x.next;\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Sets a value in the cache and returns any evicted item.\n\t *\n\t * @method setWithEvicted\n\t * @memberof LRU\n\t * @param {string} key - The key to set.\n\t * @param {*} value - The value to store.\n\t * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.\n\t * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.\n\t * @example\n\t * const cache = new LRU(2);\n\t * cache.set('a', 1).set('b', 2);\n\t * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}\n\t * @see {@link LRU#set}\n\t * @see {@link LRU#evict}\n\t * @since 11.3.0\n\t */\n\tsetWithEvicted (key, value, resetTtl = this.resetTtl) {\n\t\tlet evicted = null;\n\n\t\tif (this.has(key)) {\n\t\t\tthis.set(key, value, true, resetTtl);\n\t\t} else {\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tevicted = {...this.first};\n\t\t\t\tthis.evict(true);\n\t\t\t}\n\n\t\t\tlet item = this.items[key] = {\n\t\t\t\texpiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,\n\t\t\t\tkey: key,\n\t\t\t\tprev: this.last,\n\t\t\t\tnext: null,\n\t\t\t\tvalue\n\t\t\t};\n\n\t\t\tif (++this.size === 1) {\n\t\t\t\tthis.first = item;\n\t\t\t} else {\n\t\t\t\tthis.last.next = item;\n\t\t\t}\n\n\t\t\tthis.last = item;\n\t\t}\n\n\t\treturn evicted;\n\t}\n\n\t/**\n\t * Sets a value in the cache. Updates the item's position to most recently used.\n\t *\n\t * @method set\n\t * @memberof LRU\n\t * @param {string} key - The key to set.\n\t * @param {*} value - The value to store.\n\t * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.\n\t * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t * @example\n\t * cache.set('key1', 'value1')\n\t * .set('key2', 'value2')\n\t * .set('key3', 'value3');\n\t * @see {@link LRU#get}\n\t * @see {@link LRU#setWithEvicted}\n\t * @since 1.0.0\n\t */\n\tset (key, value, bypass = false, resetTtl = this.resetTtl) {\n\t\tlet item = this.items[key];\n\n\t\tif (bypass || item !== undefined) {\n\t\t\t// Existing item: update value and position\n\t\t\titem.value = value;\n\n\t\t\tif (bypass === false && resetTtl) {\n\t\t\t\titem.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n\t\t\t}\n\n\t\t\t// Always move to end, but the bypass parameter affects TTL reset behavior\n\t\t\tthis.moveToEnd(item);\n\t\t} else {\n\t\t\t// New item: check for eviction and create\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tthis.evict(true);\n\t\t\t}\n\n\t\t\titem = this.items[key] = {\n\t\t\t\texpiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,\n\t\t\t\tkey: key,\n\t\t\t\tprev: this.last,\n\t\t\t\tnext: null,\n\t\t\t\tvalue\n\t\t\t};\n\n\t\t\tif (++this.size === 1) {\n\t\t\t\tthis.first = item;\n\t\t\t} else {\n\t\t\t\tthis.last.next = item;\n\t\t\t}\n\n\t\t\tthis.last = item;\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns an array of all values in the cache for the specified keys.\n\t * Order follows LRU order (least to most recently used).\n\t *\n\t * @method values\n\t * @memberof LRU\n\t * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.\n\t * @returns {Array<*>} Array of values corresponding to the keys in LRU order.\n\t * @example\n\t * cache.set('a', 1).set('b', 2);\n\t * console.log(cache.values()); // [1, 2]\n\t * console.log(cache.values(['a'])); // [1]\n\t * @see {@link LRU#keys}\n\t * @see {@link LRU#entries}\n\t * @since 11.1.0\n\t */\n\tvalues (keys = this.keys()) {\n\t\tconst result = new Array(keys.length);\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tresult[i] = this.get(keys[i]);\n\t\t}\n\n\t\treturn result;\n\t}\n}\n\n/**\n * Factory function to create a new LRU cache instance with parameter validation.\n *\n * @function lru\n * @param {number} [max=1000] - Maximum number of items to store. Must be >= 0. Use 0 for unlimited size.\n * @param {number} [ttl=0] - Time to live in milliseconds. Must be >= 0. Use 0 for no expiration.\n * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().\n * @returns {LRU} A new LRU cache instance.\n * @throws {TypeError} When parameters are invalid (negative numbers or wrong types).\n * @example\n * // Create cache with factory function\n * const cache = lru(100, 5000, true);\n * cache.set('key', 'value');\n *\n * @example\n * // Error handling\n * try {\n * const cache = lru(-1); // Invalid max\n * } catch (error) {\n * console.error(error.message); // \"Invalid max value\"\n * }\n * @see {@link LRU}\n * @since 1.0.0\n */\nexport function lru (max = 1000, ttl = 0, resetTtl = false) {\n\tif (isNaN(max) || max < 0) {\n\t\tthrow new TypeError(\"Invalid max value\");\n\t}\n\n\tif (isNaN(ttl) || ttl < 0) {\n\t\tthrow new TypeError(\"Invalid ttl value\");\n\t}\n\n\tif (typeof resetTtl !== \"boolean\") {\n\t\tthrow new TypeError(\"Invalid resetTtl value\");\n\t}\n\n\treturn new LRU(max, ttl, resetTtl);\n}\n"],"names":["LRU","constructor","max","ttl","resetTtl","this","first","items","Object","create","last","size","clear","key","has","item","prev","next","entries","keys","result","Array","length","i","get","evict","bypass","expiresAt","expiry","undefined","Date","now","delete","moveToEnd","value","x","setWithEvicted","evicted","set","values","lru","isNaN","TypeError"],"mappings":";;;;AAkBO,MAAMA,EAcZ,WAAAC,CAAaC,EAAM,EAAGC,EAAM,EAAGC,GAAW,GACzCC,KAAKC,MAAQ,KACbD,KAAKE,MAAQC,OAAOC,OAAO,MAC3BJ,KAAKK,KAAO,KACZL,KAAKH,IAAMA,EACXG,KAAKD,SAAWA,EAChBC,KAAKM,KAAO,EACZN,KAAKF,IAAMA,CACZ,CAaA,KAAAS,GAMC,OALAP,KAAKC,MAAQ,KACbD,KAAKE,MAAQC,OAAOC,OAAO,MAC3BJ,KAAKK,KAAO,KACZL,KAAKM,KAAO,EAELN,IACR,CAiBA,OAAQQ,GACP,GAAIR,KAAKS,IAAID,GAAM,CAClB,MAAME,EAAOV,KAAKE,MAAMM,UAEjBR,KAAKE,MAAMM,GAClBR,KAAKM,OAEa,OAAdI,EAAKC,OACRD,EAAKC,KAAKC,KAAOF,EAAKE,MAGL,OAAdF,EAAKE,OACRF,EAAKE,KAAKD,KAAOD,EAAKC,MAGnBX,KAAKC,QAAUS,IAClBV,KAAKC,MAAQS,EAAKE,MAGfZ,KAAKK,OAASK,IACjBV,KAAKK,KAAOK,EAAKC,KAEnB,CAEA,OAAOX,IACR,CAkBA,OAAAa,CAASC,EAAOd,KAAKc,QACpB,MAAMC,EAAS,IAAIC,MAAMF,EAAKG,QAC9B,IAAK,IAAIC,EAAI,EAAGA,EAAIJ,EAAKG,OAAQC,IAAK,CACrC,MAAMV,EAAMM,EAAKI,GACjBH,EAAOG,GAAK,CAACV,EAAKR,KAAKmB,IAAIX,GAC5B,CAEA,OAAOO,CACR,CAeA,KAAAK,CAAOC,GAAS,GACf,GAAIA,GAAUrB,KAAKM,KAAO,EAAG,CAC5B,MAAMI,EAAOV,KAAKC,aAEXD,KAAKE,MAAMQ,EAAKF,KAEH,KAAdR,KAAKM,MACVN,KAAKC,MAAQ,KACbD,KAAKK,KAAO,OAEZL,KAAKC,MAAQS,EAAKE,KAClBZ,KAAKC,MAAMU,KAAO,KAEpB,CAEA,OAAOX,IACR,CAiBA,SAAAsB,CAAWd,GACV,IAAIO,EAMJ,OAJIf,KAAKS,IAAID,KACZO,EAASf,KAAKE,MAAMM,GAAKe,QAGnBR,CACR,CAiBA,GAAAI,CAAKX,GACJ,MAAME,EAAOV,KAAKE,MAAMM,GAExB,QAAagB,IAATd,EAEH,OAAIV,KAAKF,IAAM,GACVY,EAAKa,QAAUE,KAAKC,WACvB1B,KAAK2B,OAAOnB,IAOdR,KAAK4B,UAAUlB,GAERA,EAAKmB,MAId,CAiBA,GAAApB,CAAKD,GACJ,OAAOA,KAAOR,KAAKE,KACpB,CAaA,SAAA0B,CAAWlB,GAENV,KAAKK,OAASK,IAKA,OAAdA,EAAKC,OACRD,EAAKC,KAAKC,KAAOF,EAAKE,MAGL,OAAdF,EAAKE,OACRF,EAAKE,KAAKD,KAAOD,EAAKC,MAInBX,KAAKC,QAAUS,IAClBV,KAAKC,MAAQS,EAAKE,MAInBF,EAAKC,KAAOX,KAAKK,KACjBK,EAAKE,KAAO,KAEM,OAAdZ,KAAKK,OACRL,KAAKK,KAAKO,KAAOF,GAGlBV,KAAKK,KAAOK,EAGO,OAAfV,KAAKC,QACRD,KAAKC,MAAQS,GAEf,CAgBA,IAAAI,GACC,MAAMC,EAAS,IAAIC,MAAMhB,KAAKM,MAC9B,IAAIwB,EAAI9B,KAAKC,MACTiB,EAAI,EAER,KAAa,OAANY,GACNf,EAAOG,KAAOY,EAAEtB,IAChBsB,EAAIA,EAAElB,KAGP,OAAOG,CACR,CAmBA,cAAAgB,CAAgBvB,EAAKqB,EAAO9B,EAAWC,KAAKD,UAC3C,IAAIiC,EAAU,KAEd,GAAIhC,KAAKS,IAAID,GACZR,KAAKiC,IAAIzB,EAAKqB,GAAO,EAAM9B,OACrB,CACFC,KAAKH,IAAM,GAAKG,KAAKM,OAASN,KAAKH,MACtCmC,EAAU,IAAIhC,KAAKC,OACnBD,KAAKoB,OAAM,IAGZ,IAAIV,EAAOV,KAAKE,MAAMM,GAAO,CAC5Be,OAAQvB,KAAKF,IAAM,EAAI2B,KAAKC,MAAQ1B,KAAKF,IAAME,KAAKF,IACpDU,IAAKA,EACLG,KAAMX,KAAKK,KACXO,KAAM,KACNiB,SAGmB,KAAd7B,KAAKM,KACVN,KAAKC,MAAQS,EAEbV,KAAKK,KAAKO,KAAOF,EAGlBV,KAAKK,KAAOK,CACb,CAEA,OAAOsB,CACR,CAoBA,GAAAC,CAAKzB,EAAKqB,EAAOR,GAAS,EAAOtB,EAAWC,KAAKD,UAChD,IAAIW,EAAOV,KAAKE,MAAMM,GAmCtB,OAjCIa,QAAmBG,IAATd,GAEbA,EAAKmB,MAAQA,GAEE,IAAXR,GAAoBtB,IACvBW,EAAKa,OAASvB,KAAKF,IAAM,EAAI2B,KAAKC,MAAQ1B,KAAKF,IAAME,KAAKF,KAI3DE,KAAK4B,UAAUlB,KAGXV,KAAKH,IAAM,GAAKG,KAAKM,OAASN,KAAKH,KACtCG,KAAKoB,OAAM,GAGZV,EAAOV,KAAKE,MAAMM,GAAO,CACxBe,OAAQvB,KAAKF,IAAM,EAAI2B,KAAKC,MAAQ1B,KAAKF,IAAME,KAAKF,IACpDU,IAAKA,EACLG,KAAMX,KAAKK,KACXO,KAAM,KACNiB,SAGmB,KAAd7B,KAAKM,KACVN,KAAKC,MAAQS,EAEbV,KAAKK,KAAKO,KAAOF,EAGlBV,KAAKK,KAAOK,GAGNV,IACR,CAkBA,MAAAkC,CAAQpB,EAAOd,KAAKc,QACnB,MAAMC,EAAS,IAAIC,MAAMF,EAAKG,QAC9B,IAAK,IAAIC,EAAI,EAAGA,EAAIJ,EAAKG,OAAQC,IAChCH,EAAOG,GAAKlB,KAAKmB,IAAIL,EAAKI,IAG3B,OAAOH,CACR,EA2BM,SAASoB,EAAKtC,EAAM,IAAMC,EAAM,EAAGC,GAAW,GACpD,GAAIqC,MAAMvC,IAAQA,EAAM,EACvB,MAAM,IAAIwC,UAAU,qBAGrB,GAAID,MAAMtC,IAAQA,EAAM,EACvB,MAAM,IAAIuC,UAAU,qBAGrB,GAAwB,kBAAbtC,EACV,MAAM,IAAIsC,UAAU,0BAGrB,OAAO,IAAI1C,EAAIE,EAAKC,EAAKC,EAC1B,QAAAJ,SAAAwC"}
\ No newline at end of file
+{"version":3,"file":"tiny-lru.min.js","sources":["../src/lru.js"],"sourcesContent":["/**\n * A high-performance Least Recently Used (LRU) cache implementation with optional TTL support.\n * Items are automatically evicted when the cache reaches its maximum size,\n * removing the least recently used items first. All core operations (get, set, delete) are O(1).\n *\n * @class LRU\n * @example\n * // Create a cache with max 100 items\n * const cache = new LRU(100);\n * cache.set('key1', 'value1');\n * console.log(cache.get('key1')); // 'value1'\n *\n * @example\n * // Create a cache with TTL\n * const cache = new LRU(100, 5000); // 5 second TTL\n * cache.set('key1', 'value1');\n * // After 5 seconds, key1 will be expired\n */\nexport class LRU {\n /**\n * Creates a new LRU cache instance.\n * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.\n *\n * @constructor\n * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.\n * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.\n * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().\n * @example\n * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access\n * @see {@link lru} For parameter validation\n * @since 1.0.0\n */\n constructor(max = 0, ttl = 0, resetTtl = false) {\n this.first = null;\n this.items = Object.create(null);\n this.last = null;\n this.max = max;\n this.resetTtl = resetTtl;\n this.size = 0;\n this.ttl = ttl;\n }\n\n /**\n * Removes all items from the cache.\n *\n * @method clear\n * @memberof LRU\n * @returns {LRU} The LRU instance for method chaining.\n * @example\n * cache.clear();\n * console.log(cache.size); // 0\n * @since 1.0.0\n */\n clear() {\n this.first = null;\n this.items = Object.create(null);\n this.last = null;\n this.size = 0;\n\n return this;\n }\n\n /**\n * Removes an item from the cache by key.\n *\n * @method delete\n * @memberof LRU\n * @param {string} key - The key of the item to delete.\n * @returns {LRU} The LRU instance for method chaining.\n * @example\n * cache.set('key1', 'value1');\n * cache.delete('key1');\n * console.log(cache.has('key1')); // false\n * @see {@link LRU#has}\n * @see {@link LRU#clear}\n * @since 1.0.0\n */\n delete(key) {\n const item = this.items[key];\n\n if (item !== undefined) {\n delete this.items[key];\n this.size--;\n\n if (item.prev !== null) {\n item.prev.next = item.next;\n }\n\n if (item.next !== null) {\n item.next.prev = item.prev;\n }\n\n if (this.first === item) {\n this.first = item.next;\n }\n\n if (this.last === item) {\n this.last = item.prev;\n }\n\n item.prev = null;\n item.next = null;\n }\n\n return this;\n }\n\n /**\n * Returns an array of [key, value] pairs for the specified keys.\n * Order follows LRU order (least to most recently used).\n *\n * @method entries\n * @memberof LRU\n * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.\n * @returns {Array>} Array of [key, value] pairs in LRU order.\n * @example\n * cache.set('a', 1).set('b', 2);\n * console.log(cache.entries()); // [['a', 1], ['b', 2]]\n * console.log(cache.entries(['a'])); // [['a', 1]]\n * @see {@link LRU#keys}\n * @see {@link LRU#values}\n * @since 11.1.0\n */\n entries(keys) {\n if (keys === undefined) {\n keys = this.keys();\n }\n\n const result = Array.from({ length: keys.length });\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n const item = this.items[key];\n result[i] = [key, item !== undefined ? item.value : undefined];\n }\n\n return result;\n }\n\n /**\n * Removes the least recently used item from the cache.\n *\n * @method evict\n * @memberof LRU\n * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.\n * @returns {LRU} The LRU instance for method chaining.\n * @example\n * cache.set('old', 'value').set('new', 'value');\n * cache.evict(); // Removes 'old' item\n * @see {@link LRU#setWithEvicted}\n * @since 1.0.0\n */\n evict(bypass = false) {\n if (bypass || this.size > 0) {\n const item = this.first;\n\n if (!item) {\n return this;\n }\n\n delete this.items[item.key];\n\n if (--this.size === 0) {\n this.first = null;\n this.last = null;\n } else {\n this.first = item.next;\n this.first.prev = null;\n }\n\n item.next = null;\n }\n\n return this;\n }\n\n /**\n * Returns the expiration timestamp for a given key.\n *\n * @method expiresAt\n * @memberof LRU\n * @param {string} key - The key to check expiration for.\n * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.\n * @example\n * const cache = new LRU(100, 5000); // 5 second TTL\n * cache.set('key1', 'value1');\n * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now\n * @see {@link LRU#get}\n * @see {@link LRU#has}\n * @since 1.0.0\n */\n expiresAt(key) {\n const item = this.items[key];\n return item !== undefined ? item.expiry : undefined;\n }\n\n /**\n * Retrieves a value from the cache by key. Updates the item's position to most recently used.\n *\n * @method get\n * @memberof LRU\n * @param {string} key - The key to retrieve.\n * @returns {*} The value associated with the key, or undefined if not found or expired.\n * @example\n * cache.set('key1', 'value1');\n * console.log(cache.get('key1')); // 'value1'\n * console.log(cache.get('nonexistent')); // undefined\n * @see {@link LRU#set}\n * @see {@link LRU#has}\n * @since 1.0.0\n */\n get(key) {\n const item = this.items[key];\n\n if (item !== undefined) {\n // Check TTL only if enabled to avoid unnecessary Date.now() calls\n if (this.ttl > 0) {\n if (item.expiry <= Date.now()) {\n this.delete(key);\n\n return undefined;\n }\n }\n\n // Fast LRU update without full set() overhead\n this.moveToEnd(item);\n\n return item.value;\n }\n\n return undefined;\n }\n\n /**\n * Checks if a key exists in the cache.\n *\n * @method has\n * @memberof LRU\n * @param {string} key - The key to check for.\n * @returns {boolean} True if the key exists, false otherwise.\n * @example\n * cache.set('key1', 'value1');\n * console.log(cache.has('key1')); // true\n * console.log(cache.has('nonexistent')); // false\n * @see {@link LRU#get}\n * @see {@link LRU#delete}\n * @since 9.0.0\n */\n has(key) {\n const item = this.items[key];\n return item !== undefined && (this.ttl === 0 || item.expiry > Date.now());\n }\n\n /**\n * Efficiently moves an item to the end of the LRU list (most recently used position).\n * This is an internal optimization method that avoids the overhead of the full set() operation\n * when only LRU position needs to be updated.\n *\n * @method moveToEnd\n * @memberof LRU\n * @param {Object} item - The cache item with prev/next pointers to reposition.\n * @private\n * @since 11.3.5\n */\n moveToEnd(item) {\n if (this.last === item) {\n return;\n }\n\n if (item.prev !== null) {\n item.prev.next = item.next;\n }\n\n if (item.next !== null) {\n item.next.prev = item.prev;\n }\n\n if (this.first === item) {\n this.first = item.next;\n }\n\n item.prev = this.last;\n item.next = null;\n this.last.next = item;\n this.last = item;\n }\n\n /**\n * Returns an array of all keys in the cache, ordered from least to most recently used.\n *\n * @method keys\n * @memberof LRU\n * @returns {string[]} Array of keys in LRU order.\n * @example\n * cache.set('a', 1).set('b', 2);\n * cache.get('a'); // Move 'a' to most recent\n * console.log(cache.keys()); // ['b', 'a']\n * @see {@link LRU#values}\n * @see {@link LRU#entries}\n * @since 9.0.0\n */\n keys() {\n const result = Array.from({ length: this.size });\n let x = this.first;\n let i = 0;\n\n while (x !== null) {\n result[i++] = x.key;\n x = x.next;\n }\n\n return result;\n }\n\n /**\n * Sets a value in the cache and returns any evicted item.\n *\n * @method setWithEvicted\n * @memberof LRU\n * @param {string} key - The key to set.\n * @param {*} value - The value to store.\n * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.\n * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.\n * @example\n * const cache = new LRU(2);\n * cache.set('a', 1).set('b', 2);\n * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}\n * @see {@link LRU#set}\n * @see {@link LRU#evict}\n * @since 11.3.0\n */\n setWithEvicted(key, value, resetTtl = this.resetTtl) {\n let evicted = null;\n let item = this.items[key];\n\n if (item !== undefined) {\n item.value = value;\n if (resetTtl) {\n item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n }\n this.moveToEnd(item);\n } else {\n if (this.max > 0 && this.size === this.max) {\n evicted = {\n key: this.first.key,\n value: this.first.value,\n expiry: this.first.expiry,\n };\n this.evict(true);\n }\n\n item = this.items[key] = {\n expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,\n key: key,\n prev: this.last,\n next: null,\n value,\n };\n\n if (++this.size === 1) {\n this.first = item;\n } else {\n this.last.next = item;\n }\n\n this.last = item;\n }\n\n return evicted;\n }\n\n /**\n * Sets a value in the cache. Updates the item's position to most recently used.\n *\n * @method set\n * @memberof LRU\n * @param {string} key - The key to set.\n * @param {*} value - The value to store.\n * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.\n * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.\n * @returns {LRU} The LRU instance for method chaining.\n * @example\n * cache.set('key1', 'value1')\n * .set('key2', 'value2')\n * .set('key3', 'value3');\n * @see {@link LRU#get}\n * @see {@link LRU#setWithEvicted}\n * @since 1.0.0\n */\n set(key, value, bypass = false, resetTtl = this.resetTtl) {\n let item = this.items[key];\n\n if (bypass || item !== undefined) {\n // Existing item: update value and position\n item.value = value;\n\n if (bypass === false && resetTtl) {\n item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n }\n\n // Always move to end, but the bypass parameter affects TTL reset behavior\n this.moveToEnd(item);\n } else {\n // New item: check for eviction and create\n if (this.max > 0 && this.size === this.max) {\n this.evict(true);\n }\n\n item = this.items[key] = {\n expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,\n key: key,\n prev: this.last,\n next: null,\n value,\n };\n\n if (++this.size === 1) {\n this.first = item;\n } else {\n this.last.next = item;\n }\n\n this.last = item;\n }\n\n return this;\n }\n\n /**\n * Returns an array of all values in the cache for the specified keys.\n * Order follows LRU order (least to most recently used).\n *\n * @method values\n * @memberof LRU\n * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.\n * @returns {Array<*>} Array of values corresponding to the keys in LRU order.\n * @example\n * cache.set('a', 1).set('b', 2);\n * console.log(cache.values()); // [1, 2]\n * console.log(cache.values(['a'])); // [1]\n * @see {@link LRU#keys}\n * @see {@link LRU#entries}\n * @since 11.1.0\n */\n values(keys) {\n if (keys === undefined) {\n keys = this.keys();\n }\n\n const result = Array.from({ length: keys.length });\n for (let i = 0; i < keys.length; i++) {\n const item = this.items[keys[i]];\n result[i] = item !== undefined ? item.value : undefined;\n }\n\n return result;\n }\n}\n\n/**\n * Factory function to create a new LRU cache instance with parameter validation.\n *\n * @function lru\n * @param {number} [max=1000] - Maximum number of items to store. Must be >= 0. Use 0 for unlimited size.\n * @param {number} [ttl=0] - Time to live in milliseconds. Must be >= 0. Use 0 for no expiration.\n * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().\n * @returns {LRU} A new LRU cache instance.\n * @throws {TypeError} When parameters are invalid (negative numbers or wrong types).\n * @example\n * // Create cache with factory function\n * const cache = lru(100, 5000, true);\n * cache.set('key', 'value');\n *\n * @example\n * // Error handling\n * try {\n * const cache = lru(-1); // Invalid max\n * } catch (error) {\n * console.error(error.message); // \"Invalid max value\"\n * }\n * @see {@link LRU}\n * @since 1.0.0\n */\nexport function lru(max = 1000, ttl = 0, resetTtl = false) {\n if (isNaN(max) || max < 0) {\n throw new TypeError(\"Invalid max value\");\n }\n\n if (isNaN(ttl) || ttl < 0) {\n throw new TypeError(\"Invalid ttl value\");\n }\n\n if (typeof resetTtl !== \"boolean\") {\n throw new TypeError(\"Invalid resetTtl value\");\n }\n\n return new LRU(max, ttl, resetTtl);\n}\n"],"names":["LRU","constructor","max","ttl","resetTtl","this","first","items","Object","create","last","size","clear","key","item","undefined","prev","next","entries","keys","result","Array","from","length","i","value","evict","bypass","expiresAt","expiry","get","Date","now","delete","moveToEnd","has","x","setWithEvicted","evicted","set","values","lru","isNaN","TypeError"],"mappings":";;;;AAkBO,MAAMA,EAcX,WAAAC,CAAYC,EAAM,EAAGC,EAAM,EAAGC,GAAW,GACvCC,KAAKC,MAAQ,KACbD,KAAKE,MAAQC,OAAOC,OAAO,MAC3BJ,KAAKK,KAAO,KACZL,KAAKH,IAAMA,EACXG,KAAKD,SAAWA,EAChBC,KAAKM,KAAO,EACZN,KAAKF,IAAMA,CACb,CAaA,KAAAS,GAME,OALAP,KAAKC,MAAQ,KACbD,KAAKE,MAAQC,OAAOC,OAAO,MAC3BJ,KAAKK,KAAO,KACZL,KAAKM,KAAO,EAELN,IACT,CAiBA,OAAOQ,GACL,MAAMC,EAAOT,KAAKE,MAAMM,GA0BxB,YAxBaE,IAATD,WACKT,KAAKE,MAAMM,GAClBR,KAAKM,OAEa,OAAdG,EAAKE,OACPF,EAAKE,KAAKC,KAAOH,EAAKG,MAGN,OAAdH,EAAKG,OACPH,EAAKG,KAAKD,KAAOF,EAAKE,MAGpBX,KAAKC,QAAUQ,IACjBT,KAAKC,MAAQQ,EAAKG,MAGhBZ,KAAKK,OAASI,IAChBT,KAAKK,KAAOI,EAAKE,MAGnBF,EAAKE,KAAO,KACZF,EAAKG,KAAO,MAGPZ,IACT,CAkBA,OAAAa,CAAQC,QACOJ,IAATI,IACFA,EAAOd,KAAKc,QAGd,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQJ,EAAKI,SACzC,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAKI,OAAQC,IAAK,CACpC,MAAMX,EAAMM,EAAKK,GACXV,EAAOT,KAAKE,MAAMM,GACxBO,EAAOI,GAAK,CAACX,OAAcE,IAATD,EAAqBA,EAAKW,WAAQV,EACtD,CAEA,OAAOK,CACT,CAeA,KAAAM,CAAMC,GAAS,GACb,GAAIA,GAAUtB,KAAKM,KAAO,EAAG,CAC3B,MAAMG,EAAOT,KAAKC,MAElB,IAAKQ,EACH,OAAOT,YAGFA,KAAKE,MAAMO,EAAKD,KAEH,KAAdR,KAAKM,MACTN,KAAKC,MAAQ,KACbD,KAAKK,KAAO,OAEZL,KAAKC,MAAQQ,EAAKG,KAClBZ,KAAKC,MAAMU,KAAO,MAGpBF,EAAKG,KAAO,IACd,CAEA,OAAOZ,IACT,CAiBA,SAAAuB,CAAUf,GACR,MAAMC,EAAOT,KAAKE,MAAMM,GACxB,YAAgBE,IAATD,EAAqBA,EAAKe,YAASd,CAC5C,CAiBA,GAAAe,CAAIjB,GACF,MAAMC,EAAOT,KAAKE,MAAMM,GAExB,QAAaE,IAATD,EAEF,OAAIT,KAAKF,IAAM,GACTW,EAAKe,QAAUE,KAAKC,WACtB3B,KAAK4B,OAAOpB,IAOhBR,KAAK6B,UAAUpB,GAERA,EAAKW,MAIhB,CAiBA,GAAAU,CAAItB,GACF,MAAMC,EAAOT,KAAKE,MAAMM,GACxB,YAAgBE,IAATD,IAAoC,IAAbT,KAAKF,KAAaW,EAAKe,OAASE,KAAKC,MACrE,CAaA,SAAAE,CAAUpB,GACJT,KAAKK,OAASI,IAIA,OAAdA,EAAKE,OACPF,EAAKE,KAAKC,KAAOH,EAAKG,MAGN,OAAdH,EAAKG,OACPH,EAAKG,KAAKD,KAAOF,EAAKE,MAGpBX,KAAKC,QAAUQ,IACjBT,KAAKC,MAAQQ,EAAKG,MAGpBH,EAAKE,KAAOX,KAAKK,KACjBI,EAAKG,KAAO,KACZZ,KAAKK,KAAKO,KAAOH,EACjBT,KAAKK,KAAOI,EACd,CAgBA,IAAAK,GACE,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQlB,KAAKM,OACzC,IAAIyB,EAAI/B,KAAKC,MACTkB,EAAI,EAER,KAAa,OAANY,GACLhB,EAAOI,KAAOY,EAAEvB,IAChBuB,EAAIA,EAAEnB,KAGR,OAAOG,CACT,CAmBA,cAAAiB,CAAexB,EAAKY,EAAOrB,EAAWC,KAAKD,UACzC,IAAIkC,EAAU,KACVxB,EAAOT,KAAKE,MAAMM,GAmCtB,YAjCaE,IAATD,GACFA,EAAKW,MAAQA,EACTrB,IACFU,EAAKe,OAASxB,KAAKF,IAAM,EAAI4B,KAAKC,MAAQ3B,KAAKF,IAAME,KAAKF,KAE5DE,KAAK6B,UAAUpB,KAEXT,KAAKH,IAAM,GAAKG,KAAKM,OAASN,KAAKH,MACrCoC,EAAU,CACRzB,IAAKR,KAAKC,MAAMO,IAChBY,MAAOpB,KAAKC,MAAMmB,MAClBI,OAAQxB,KAAKC,MAAMuB,QAErBxB,KAAKqB,OAAM,IAGbZ,EAAOT,KAAKE,MAAMM,GAAO,CACvBgB,OAAQxB,KAAKF,IAAM,EAAI4B,KAAKC,MAAQ3B,KAAKF,IAAME,KAAKF,IACpDU,IAAKA,EACLG,KAAMX,KAAKK,KACXO,KAAM,KACNQ,SAGkB,KAAdpB,KAAKM,KACTN,KAAKC,MAAQQ,EAEbT,KAAKK,KAAKO,KAAOH,EAGnBT,KAAKK,KAAOI,GAGPwB,CACT,CAoBA,GAAAC,CAAI1B,EAAKY,EAAOE,GAAS,EAAOvB,EAAWC,KAAKD,UAC9C,IAAIU,EAAOT,KAAKE,MAAMM,GAmCtB,OAjCIc,QAAmBZ,IAATD,GAEZA,EAAKW,MAAQA,GAEE,IAAXE,GAAoBvB,IACtBU,EAAKe,OAASxB,KAAKF,IAAM,EAAI4B,KAAKC,MAAQ3B,KAAKF,IAAME,KAAKF,KAI5DE,KAAK6B,UAAUpB,KAGXT,KAAKH,IAAM,GAAKG,KAAKM,OAASN,KAAKH,KACrCG,KAAKqB,OAAM,GAGbZ,EAAOT,KAAKE,MAAMM,GAAO,CACvBgB,OAAQxB,KAAKF,IAAM,EAAI4B,KAAKC,MAAQ3B,KAAKF,IAAME,KAAKF,IACpDU,IAAKA,EACLG,KAAMX,KAAKK,KACXO,KAAM,KACNQ,SAGkB,KAAdpB,KAAKM,KACTN,KAAKC,MAAQQ,EAEbT,KAAKK,KAAKO,KAAOH,EAGnBT,KAAKK,KAAOI,GAGPT,IACT,CAkBA,MAAAmC,CAAOrB,QACQJ,IAATI,IACFA,EAAOd,KAAKc,QAGd,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQJ,EAAKI,SACzC,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAKI,OAAQC,IAAK,CACpC,MAAMV,EAAOT,KAAKE,MAAMY,EAAKK,IAC7BJ,EAAOI,QAAcT,IAATD,EAAqBA,EAAKW,WAAQV,CAChD,CAEA,OAAOK,CACT,EA2BK,SAASqB,EAAIvC,EAAM,IAAMC,EAAM,EAAGC,GAAW,GAClD,GAAIsC,MAAMxC,IAAQA,EAAM,EACtB,MAAM,IAAIyC,UAAU,qBAGtB,GAAID,MAAMvC,IAAQA,EAAM,EACtB,MAAM,IAAIwC,UAAU,qBAGtB,GAAwB,kBAAbvC,EACT,MAAM,IAAIuC,UAAU,0BAGtB,OAAO,IAAI3C,EAAIE,EAAKC,EAAKC,EAC3B,QAAAJ,SAAAyC"}
\ No newline at end of file
diff --git a/dist/tiny-lru.umd.js b/dist/tiny-lru.umd.js
index 9222cf9..6d40874 100644
--- a/dist/tiny-lru.umd.js
+++ b/dist/tiny-lru.umd.js
@@ -24,432 +24,443 @@
* // After 5 seconds, key1 will be expired
*/
class LRU {
- /**
- * Creates a new LRU cache instance.
- * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
- *
- * @constructor
- * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
- * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
- * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
- * @example
- * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
- * @see {@link lru} For parameter validation
- * @since 1.0.0
- */
- constructor (max = 0, ttl = 0, resetTtl = false) {
- this.first = null;
- this.items = Object.create(null);
- this.last = null;
- this.max = max;
- this.resetTtl = resetTtl;
- this.size = 0;
- this.ttl = ttl;
- }
-
- /**
- * Removes all items from the cache.
- *
- * @method clear
- * @memberof LRU
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.clear();
- * console.log(cache.size); // 0
- * @since 1.0.0
- */
- clear () {
- this.first = null;
- this.items = Object.create(null);
- this.last = null;
- this.size = 0;
-
- return this;
- }
-
- /**
- * Removes an item from the cache by key.
- *
- * @method delete
- * @memberof LRU
- * @param {string} key - The key of the item to delete.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('key1', 'value1');
- * cache.delete('key1');
- * console.log(cache.has('key1')); // false
- * @see {@link LRU#has}
- * @see {@link LRU#clear}
- * @since 1.0.0
- */
- delete (key) {
- if (this.has(key)) {
- const item = this.items[key];
-
- delete this.items[key];
- this.size--;
-
- if (item.prev !== null) {
- item.prev.next = item.next;
- }
-
- if (item.next !== null) {
- item.next.prev = item.prev;
- }
-
- if (this.first === item) {
- this.first = item.next;
- }
-
- if (this.last === item) {
- this.last = item.prev;
- }
- }
-
- return this;
- }
-
- /**
- * Returns an array of [key, value] pairs for the specified keys.
- * Order follows LRU order (least to most recently used).
- *
- * @method entries
- * @memberof LRU
- * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
- * @returns {Array>} Array of [key, value] pairs in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * console.log(cache.entries()); // [['a', 1], ['b', 2]]
- * console.log(cache.entries(['a'])); // [['a', 1]]
- * @see {@link LRU#keys}
- * @see {@link LRU#values}
- * @since 11.1.0
- */
- entries (keys = this.keys()) {
- const result = new Array(keys.length);
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i];
- result[i] = [key, this.get(key)];
- }
-
- return result;
- }
-
- /**
- * Removes the least recently used item from the cache.
- *
- * @method evict
- * @memberof LRU
- * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('old', 'value').set('new', 'value');
- * cache.evict(); // Removes 'old' item
- * @see {@link LRU#setWithEvicted}
- * @since 1.0.0
- */
- evict (bypass = false) {
- if (bypass || this.size > 0) {
- const item = this.first;
-
- delete this.items[item.key];
-
- if (--this.size === 0) {
- this.first = null;
- this.last = null;
- } else {
- this.first = item.next;
- this.first.prev = null;
- }
- }
-
- return this;
- }
-
- /**
- * Returns the expiration timestamp for a given key.
- *
- * @method expiresAt
- * @memberof LRU
- * @param {string} key - The key to check expiration for.
- * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
- * @example
- * const cache = new LRU(100, 5000); // 5 second TTL
- * cache.set('key1', 'value1');
- * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
- * @see {@link LRU#get}
- * @see {@link LRU#has}
- * @since 1.0.0
- */
- expiresAt (key) {
- let result;
-
- if (this.has(key)) {
- result = this.items[key].expiry;
- }
-
- return result;
- }
-
- /**
- * Retrieves a value from the cache by key. Updates the item's position to most recently used.
- *
- * @method get
- * @memberof LRU
- * @param {string} key - The key to retrieve.
- * @returns {*} The value associated with the key, or undefined if not found or expired.
- * @example
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- * console.log(cache.get('nonexistent')); // undefined
- * @see {@link LRU#set}
- * @see {@link LRU#has}
- * @since 1.0.0
- */
- get (key) {
- const item = this.items[key];
-
- if (item !== undefined) {
- // Check TTL only if enabled to avoid unnecessary Date.now() calls
- if (this.ttl > 0) {
- if (item.expiry <= Date.now()) {
- this.delete(key);
-
- return undefined;
- }
- }
-
- // Fast LRU update without full set() overhead
- this.moveToEnd(item);
-
- return item.value;
- }
-
- return undefined;
- }
-
- /**
- * Checks if a key exists in the cache.
- *
- * @method has
- * @memberof LRU
- * @param {string} key - The key to check for.
- * @returns {boolean} True if the key exists, false otherwise.
- * @example
- * cache.set('key1', 'value1');
- * console.log(cache.has('key1')); // true
- * console.log(cache.has('nonexistent')); // false
- * @see {@link LRU#get}
- * @see {@link LRU#delete}
- * @since 9.0.0
- */
- has (key) {
- return key in this.items;
- }
-
- /**
- * Efficiently moves an item to the end of the LRU list (most recently used position).
- * This is an internal optimization method that avoids the overhead of the full set() operation
- * when only LRU position needs to be updated.
- *
- * @method moveToEnd
- * @memberof LRU
- * @param {Object} item - The cache item with prev/next pointers to reposition.
- * @private
- * @since 11.3.5
- */
- moveToEnd (item) {
- // If already at the end, nothing to do
- if (this.last === item) {
- return;
- }
-
- // Remove item from current position in the list
- if (item.prev !== null) {
- item.prev.next = item.next;
- }
-
- if (item.next !== null) {
- item.next.prev = item.prev;
- }
-
- // Update first pointer if this was the first item
- if (this.first === item) {
- this.first = item.next;
- }
-
- // Add item to the end
- item.prev = this.last;
- item.next = null;
-
- if (this.last !== null) {
- this.last.next = item;
- }
-
- this.last = item;
-
- // Handle edge case: if this was the only item, it's also first
- if (this.first === null) {
- this.first = item;
- }
- }
-
- /**
- * Returns an array of all keys in the cache, ordered from least to most recently used.
- *
- * @method keys
- * @memberof LRU
- * @returns {string[]} Array of keys in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * cache.get('a'); // Move 'a' to most recent
- * console.log(cache.keys()); // ['b', 'a']
- * @see {@link LRU#values}
- * @see {@link LRU#entries}
- * @since 9.0.0
- */
- keys () {
- const result = new Array(this.size);
- let x = this.first;
- let i = 0;
-
- while (x !== null) {
- result[i++] = x.key;
- x = x.next;
- }
-
- return result;
- }
-
- /**
- * Sets a value in the cache and returns any evicted item.
- *
- * @method setWithEvicted
- * @memberof LRU
- * @param {string} key - The key to set.
- * @param {*} value - The value to store.
- * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
- * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
- * @example
- * const cache = new LRU(2);
- * cache.set('a', 1).set('b', 2);
- * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
- * @see {@link LRU#set}
- * @see {@link LRU#evict}
- * @since 11.3.0
- */
- setWithEvicted (key, value, resetTtl = this.resetTtl) {
- let evicted = null;
-
- if (this.has(key)) {
- this.set(key, value, true, resetTtl);
- } else {
- if (this.max > 0 && this.size === this.max) {
- evicted = {...this.first};
- this.evict(true);
- }
-
- let item = this.items[key] = {
- expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
- key: key,
- prev: this.last,
- next: null,
- value
- };
-
- if (++this.size === 1) {
- this.first = item;
- } else {
- this.last.next = item;
- }
-
- this.last = item;
- }
-
- return evicted;
- }
-
- /**
- * Sets a value in the cache. Updates the item's position to most recently used.
- *
- * @method set
- * @memberof LRU
- * @param {string} key - The key to set.
- * @param {*} value - The value to store.
- * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
- * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('key1', 'value1')
- * .set('key2', 'value2')
- * .set('key3', 'value3');
- * @see {@link LRU#get}
- * @see {@link LRU#setWithEvicted}
- * @since 1.0.0
- */
- set (key, value, bypass = false, resetTtl = this.resetTtl) {
- let item = this.items[key];
-
- if (bypass || item !== undefined) {
- // Existing item: update value and position
- item.value = value;
-
- if (bypass === false && resetTtl) {
- item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
- }
-
- // Always move to end, but the bypass parameter affects TTL reset behavior
- this.moveToEnd(item);
- } else {
- // New item: check for eviction and create
- if (this.max > 0 && this.size === this.max) {
- this.evict(true);
- }
-
- item = this.items[key] = {
- expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
- key: key,
- prev: this.last,
- next: null,
- value
- };
-
- if (++this.size === 1) {
- this.first = item;
- } else {
- this.last.next = item;
- }
-
- this.last = item;
- }
-
- return this;
- }
-
- /**
- * Returns an array of all values in the cache for the specified keys.
- * Order follows LRU order (least to most recently used).
- *
- * @method values
- * @memberof LRU
- * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
- * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * console.log(cache.values()); // [1, 2]
- * console.log(cache.values(['a'])); // [1]
- * @see {@link LRU#keys}
- * @see {@link LRU#entries}
- * @since 11.1.0
- */
- values (keys = this.keys()) {
- const result = new Array(keys.length);
- for (let i = 0; i < keys.length; i++) {
- result[i] = this.get(keys[i]);
- }
-
- return result;
- }
+ /**
+ * Creates a new LRU cache instance.
+ * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
+ *
+ * @constructor
+ * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
+ * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
+ * @example
+ * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
+ * @see {@link lru} For parameter validation
+ * @since 1.0.0
+ */
+ constructor(max = 0, ttl = 0, resetTtl = false) {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.max = max;
+ this.resetTtl = resetTtl;
+ this.size = 0;
+ this.ttl = ttl;
+ }
+
+ /**
+ * Removes all items from the cache.
+ *
+ * @method clear
+ * @memberof LRU
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.clear();
+ * console.log(cache.size); // 0
+ * @since 1.0.0
+ */
+ clear() {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.size = 0;
+
+ return this;
+ }
+
+ /**
+ * Removes an item from the cache by key.
+ *
+ * @method delete
+ * @memberof LRU
+ * @param {string} key - The key of the item to delete.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1');
+ * cache.delete('key1');
+ * console.log(cache.has('key1')); // false
+ * @see {@link LRU#has}
+ * @see {@link LRU#clear}
+ * @since 1.0.0
+ */
+ delete(key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ delete this.items[key];
+ this.size--;
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ if (this.last === item) {
+ this.last = item.prev;
+ }
+
+ item.prev = null;
+ item.next = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of [key, value] pairs for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method entries
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
+ * @returns {Array>} Array of [key, value] pairs in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.entries()); // [['a', 1], ['b', 2]]
+ * console.log(cache.entries(['a'])); // [['a', 1]]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#values}
+ * @since 11.1.0
+ */
+ entries(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ const item = this.items[key];
+ result[i] = [key, item !== undefined ? item.value : undefined];
+ }
+
+ return result;
+ }
+
+ /**
+ * Removes the least recently used item from the cache.
+ *
+ * @method evict
+ * @memberof LRU
+ * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('old', 'value').set('new', 'value');
+ * cache.evict(); // Removes 'old' item
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ evict(bypass = false) {
+ if (bypass || this.size > 0) {
+ const item = this.first;
+
+ if (!item) {
+ return this;
+ }
+
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.first = item.next;
+ this.first.prev = null;
+ }
+
+ item.next = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns the expiration timestamp for a given key.
+ *
+ * @method expiresAt
+ * @memberof LRU
+ * @param {string} key - The key to check expiration for.
+ * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
+ * @example
+ * const cache = new LRU(100, 5000); // 5 second TTL
+ * cache.set('key1', 'value1');
+ * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
+ * @see {@link LRU#get}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ expiresAt(key) {
+ const item = this.items[key];
+ return item !== undefined ? item.expiry : undefined;
+ }
+
+ /**
+ * Retrieves a value from the cache by key. Updates the item's position to most recently used.
+ *
+ * @method get
+ * @memberof LRU
+ * @param {string} key - The key to retrieve.
+ * @returns {*} The value associated with the key, or undefined if not found or expired.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.get('key1')); // 'value1'
+ * console.log(cache.get('nonexistent')); // undefined
+ * @see {@link LRU#set}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ get(key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ // Check TTL only if enabled to avoid unnecessary Date.now() calls
+ if (this.ttl > 0) {
+ if (item.expiry <= Date.now()) {
+ this.delete(key);
+
+ return undefined;
+ }
+ }
+
+ // Fast LRU update without full set() overhead
+ this.moveToEnd(item);
+
+ return item.value;
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Checks if a key exists in the cache.
+ *
+ * @method has
+ * @memberof LRU
+ * @param {string} key - The key to check for.
+ * @returns {boolean} True if the key exists, false otherwise.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.has('key1')); // true
+ * console.log(cache.has('nonexistent')); // false
+ * @see {@link LRU#get}
+ * @see {@link LRU#delete}
+ * @since 9.0.0
+ */
+ has(key) {
+ const item = this.items[key];
+ return item !== undefined && (this.ttl === 0 || item.expiry > Date.now());
+ }
+
+ /**
+ * Efficiently moves an item to the end of the LRU list (most recently used position).
+ * This is an internal optimization method that avoids the overhead of the full set() operation
+ * when only LRU position needs to be updated.
+ *
+ * @method moveToEnd
+ * @memberof LRU
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ * @since 11.3.5
+ */
+ moveToEnd(item) {
+ if (this.last === item) {
+ return;
+ }
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ item.prev = this.last;
+ item.next = null;
+ this.last.next = item;
+ this.last = item;
+ }
+
+ /**
+ * Returns an array of all keys in the cache, ordered from least to most recently used.
+ *
+ * @method keys
+ * @memberof LRU
+ * @returns {string[]} Array of keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * cache.get('a'); // Move 'a' to most recent
+ * console.log(cache.keys()); // ['b', 'a']
+ * @see {@link LRU#values}
+ * @see {@link LRU#entries}
+ * @since 9.0.0
+ */
+ keys() {
+ const result = Array.from({ length: this.size });
+ let x = this.first;
+ let i = 0;
+
+ while (x !== null) {
+ result[i++] = x.key;
+ x = x.next;
+ }
+
+ return result;
+ }
+
+ /**
+ * Sets a value in the cache and returns any evicted item.
+ *
+ * @method setWithEvicted
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
+ * @example
+ * const cache = new LRU(2);
+ * cache.set('a', 1).set('b', 2);
+ * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
+ * @see {@link LRU#set}
+ * @see {@link LRU#evict}
+ * @since 11.3.0
+ */
+ setWithEvicted(key, value, resetTtl = this.resetTtl) {
+ let evicted = null;
+ let item = this.items[key];
+
+ if (item !== undefined) {
+ item.value = value;
+ if (resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+ this.moveToEnd(item);
+ } else {
+ if (this.max > 0 && this.size === this.max) {
+ evicted = {
+ key: this.first.key,
+ value: this.first.value,
+ expiry: this.first.expiry,
+ };
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value,
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return evicted;
+ }
+
+ /**
+ * Sets a value in the cache. Updates the item's position to most recently used.
+ *
+ * @method set
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1')
+ * .set('key2', 'value2')
+ * .set('key3', 'value3');
+ * @see {@link LRU#get}
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ set(key, value, bypass = false, resetTtl = this.resetTtl) {
+ let item = this.items[key];
+
+ if (bypass || item !== undefined) {
+ // Existing item: update value and position
+ item.value = value;
+
+ if (bypass === false && resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+
+ // Always move to end, but the bypass parameter affects TTL reset behavior
+ this.moveToEnd(item);
+ } else {
+ // New item: check for eviction and create
+ if (this.max > 0 && this.size === this.max) {
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value,
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of all values in the cache for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method values
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
+ * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.values()); // [1, 2]
+ * console.log(cache.values(['a'])); // [1]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#entries}
+ * @since 11.1.0
+ */
+ values(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
+ for (let i = 0; i < keys.length; i++) {
+ const item = this.items[keys[i]];
+ result[i] = item !== undefined ? item.value : undefined;
+ }
+
+ return result;
+ }
}
/**
@@ -476,18 +487,18 @@ class LRU {
* @see {@link LRU}
* @since 1.0.0
*/
-function lru (max = 1000, ttl = 0, resetTtl = false) {
- if (isNaN(max) || max < 0) {
- throw new TypeError("Invalid max value");
- }
+function lru(max = 1000, ttl = 0, resetTtl = false) {
+ if (isNaN(max) || max < 0) {
+ throw new TypeError("Invalid max value");
+ }
- if (isNaN(ttl) || ttl < 0) {
- throw new TypeError("Invalid ttl value");
- }
+ if (isNaN(ttl) || ttl < 0) {
+ throw new TypeError("Invalid ttl value");
+ }
- if (typeof resetTtl !== "boolean") {
- throw new TypeError("Invalid resetTtl value");
- }
+ if (typeof resetTtl !== "boolean") {
+ throw new TypeError("Invalid resetTtl value");
+ }
- return new LRU(max, ttl, resetTtl);
+ return new LRU(max, ttl, resetTtl);
}exports.LRU=LRU;exports.lru=lru;}));
\ No newline at end of file
diff --git a/dist/tiny-lru.umd.min.js b/dist/tiny-lru.umd.min.js
index 8406124..d2b0b70 100644
--- a/dist/tiny-lru.umd.min.js
+++ b/dist/tiny-lru.umd.min.js
@@ -2,4 +2,4 @@
2026 Jason Mulligan
@version 11.4.7
*/
-!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports):"function"==typeof define&&define.amd?define(["exports"],s):s((t="undefined"!=typeof globalThis?globalThis:t||self).lru={})}(this,(function(t){"use strict";class s{constructor(t=0,s=0,e=!1){this.first=null,this.items=Object.create(null),this.last=null,this.max=t,this.resetTtl=e,this.size=0,this.ttl=s}clear(){return this.first=null,this.items=Object.create(null),this.last=null,this.size=0,this}delete(t){if(this.has(t)){const s=this.items[t];delete this.items[t],this.size--,null!==s.prev&&(s.prev.next=s.next),null!==s.next&&(s.next.prev=s.prev),this.first===s&&(this.first=s.next),this.last===s&&(this.last=s.prev)}return this}entries(t=this.keys()){const s=new Array(t.length);for(let e=0;e0){const t=this.first;delete this.items[t.key],0==--this.size?(this.first=null,this.last=null):(this.first=t.next,this.first.prev=null)}return this}expiresAt(t){let s;return this.has(t)&&(s=this.items[t].expiry),s}get(t){const s=this.items[t];if(void 0!==s)return this.ttl>0&&s.expiry<=Date.now()?void this.delete(t):(this.moveToEnd(s),s.value)}has(t){return t in this.items}moveToEnd(t){this.last!==t&&(null!==t.prev&&(t.prev.next=t.next),null!==t.next&&(t.next.prev=t.prev),this.first===t&&(this.first=t.next),t.prev=this.last,t.next=null,null!==this.last&&(this.last.next=t),this.last=t,null===this.first&&(this.first=t))}keys(){const t=new Array(this.size);let s=this.first,e=0;for(;null!==s;)t[e++]=s.key,s=s.next;return t}setWithEvicted(t,s,e=this.resetTtl){let i=null;if(this.has(t))this.set(t,s,!0,e);else{this.max>0&&this.size===this.max&&(i={...this.first},this.evict(!0));let e=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:s};1==++this.size?this.first=e:this.last.next=e,this.last=e}return i}set(t,s,e=!1,i=this.resetTtl){let l=this.items[t];return e||void 0!==l?(l.value=s,!1===e&&i&&(l.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(l)):(this.max>0&&this.size===this.max&&this.evict(!0),l=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:s},1==++this.size?this.first=l:this.last.next=l,this.last=l),this}values(t=this.keys()){const s=new Array(t.length);for(let e=0;e0){const t=this.first;if(!t)return this;delete this.items[t.key],0==--this.size?(this.first=null,this.last=null):(this.first=t.next,this.first.prev=null),t.next=null}return this}expiresAt(t){const e=this.items[t];return void 0!==e?e.expiry:void 0}get(t){const e=this.items[t];if(void 0!==e)return this.ttl>0&&e.expiry<=Date.now()?void this.delete(t):(this.moveToEnd(e),e.value)}has(t){const e=this.items[t];return void 0!==e&&(0===this.ttl||e.expiry>Date.now())}moveToEnd(t){this.last!==t&&(null!==t.prev&&(t.prev.next=t.next),null!==t.next&&(t.next.prev=t.prev),this.first===t&&(this.first=t.next),t.prev=this.last,t.next=null,this.last.next=t,this.last=t)}keys(){const t=Array.from({length:this.size});let e=this.first,i=0;for(;null!==e;)t[i++]=e.key,e=e.next;return t}setWithEvicted(t,e,i=this.resetTtl){let s=null,l=this.items[t];return void 0!==l?(l.value=e,i&&(l.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(l)):(this.max>0&&this.size===this.max&&(s={key:this.first.key,value:this.first.value,expiry:this.first.expiry},this.evict(!0)),l=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:e},1==++this.size?this.first=l:this.last.next=l,this.last=l),s}set(t,e,i=!1,s=this.resetTtl){let l=this.items[t];return i||void 0!==l?(l.value=e,!1===i&&s&&(l.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(l)):(this.max>0&&this.size===this.max&&this.evict(!0),l=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:e},1==++this.size?this.first=l:this.last.next=l,this.last=l),this}values(t){void 0===t&&(t=this.keys());const e=Array.from({length:t.length});for(let i=0;i>} Array of [key, value] pairs in LRU order.\n\t * @example\n\t * cache.set('a', 1).set('b', 2);\n\t * console.log(cache.entries()); // [['a', 1], ['b', 2]]\n\t * console.log(cache.entries(['a'])); // [['a', 1]]\n\t * @see {@link LRU#keys}\n\t * @see {@link LRU#values}\n\t * @since 11.1.0\n\t */\n\tentries (keys = this.keys()) {\n\t\tconst result = new Array(keys.length);\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst key = keys[i];\n\t\t\tresult[i] = [key, this.get(key)];\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Removes the least recently used item from the cache.\n\t *\n\t * @method evict\n\t * @memberof LRU\n\t * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t * @example\n\t * cache.set('old', 'value').set('new', 'value');\n\t * cache.evict(); // Removes 'old' item\n\t * @see {@link LRU#setWithEvicted}\n\t * @since 1.0.0\n\t */\n\tevict (bypass = false) {\n\t\tif (bypass || this.size > 0) {\n\t\t\tconst item = this.first;\n\n\t\t\tdelete this.items[item.key];\n\n\t\t\tif (--this.size === 0) {\n\t\t\t\tthis.first = null;\n\t\t\t\tthis.last = null;\n\t\t\t} else {\n\t\t\t\tthis.first = item.next;\n\t\t\t\tthis.first.prev = null;\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns the expiration timestamp for a given key.\n\t *\n\t * @method expiresAt\n\t * @memberof LRU\n\t * @param {string} key - The key to check expiration for.\n\t * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.\n\t * @example\n\t * const cache = new LRU(100, 5000); // 5 second TTL\n\t * cache.set('key1', 'value1');\n\t * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now\n\t * @see {@link LRU#get}\n\t * @see {@link LRU#has}\n\t * @since 1.0.0\n\t */\n\texpiresAt (key) {\n\t\tlet result;\n\n\t\tif (this.has(key)) {\n\t\t\tresult = this.items[key].expiry;\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Retrieves a value from the cache by key. Updates the item's position to most recently used.\n\t *\n\t * @method get\n\t * @memberof LRU\n\t * @param {string} key - The key to retrieve.\n\t * @returns {*} The value associated with the key, or undefined if not found or expired.\n\t * @example\n\t * cache.set('key1', 'value1');\n\t * console.log(cache.get('key1')); // 'value1'\n\t * console.log(cache.get('nonexistent')); // undefined\n\t * @see {@link LRU#set}\n\t * @see {@link LRU#has}\n\t * @since 1.0.0\n\t */\n\tget (key) {\n\t\tconst item = this.items[key];\n\n\t\tif (item !== undefined) {\n\t\t\t// Check TTL only if enabled to avoid unnecessary Date.now() calls\n\t\t\tif (this.ttl > 0) {\n\t\t\t\tif (item.expiry <= Date.now()) {\n\t\t\t\t\tthis.delete(key);\n\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fast LRU update without full set() overhead\n\t\t\tthis.moveToEnd(item);\n\n\t\t\treturn item.value;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Checks if a key exists in the cache.\n\t *\n\t * @method has\n\t * @memberof LRU\n\t * @param {string} key - The key to check for.\n\t * @returns {boolean} True if the key exists, false otherwise.\n\t * @example\n\t * cache.set('key1', 'value1');\n\t * console.log(cache.has('key1')); // true\n\t * console.log(cache.has('nonexistent')); // false\n\t * @see {@link LRU#get}\n\t * @see {@link LRU#delete}\n\t * @since 9.0.0\n\t */\n\thas (key) {\n\t\treturn key in this.items;\n\t}\n\n\t/**\n\t * Efficiently moves an item to the end of the LRU list (most recently used position).\n\t * This is an internal optimization method that avoids the overhead of the full set() operation\n\t * when only LRU position needs to be updated.\n\t *\n\t * @method moveToEnd\n\t * @memberof LRU\n\t * @param {Object} item - The cache item with prev/next pointers to reposition.\n\t * @private\n\t * @since 11.3.5\n\t */\n\tmoveToEnd (item) {\n\t\t// If already at the end, nothing to do\n\t\tif (this.last === item) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove item from current position in the list\n\t\tif (item.prev !== null) {\n\t\t\titem.prev.next = item.next;\n\t\t}\n\n\t\tif (item.next !== null) {\n\t\t\titem.next.prev = item.prev;\n\t\t}\n\n\t\t// Update first pointer if this was the first item\n\t\tif (this.first === item) {\n\t\t\tthis.first = item.next;\n\t\t}\n\n\t\t// Add item to the end\n\t\titem.prev = this.last;\n\t\titem.next = null;\n\n\t\tif (this.last !== null) {\n\t\t\tthis.last.next = item;\n\t\t}\n\n\t\tthis.last = item;\n\n\t\t// Handle edge case: if this was the only item, it's also first\n\t\tif (this.first === null) {\n\t\t\tthis.first = item;\n\t\t}\n\t}\n\n\t/**\n\t * Returns an array of all keys in the cache, ordered from least to most recently used.\n\t *\n\t * @method keys\n\t * @memberof LRU\n\t * @returns {string[]} Array of keys in LRU order.\n\t * @example\n\t * cache.set('a', 1).set('b', 2);\n\t * cache.get('a'); // Move 'a' to most recent\n\t * console.log(cache.keys()); // ['b', 'a']\n\t * @see {@link LRU#values}\n\t * @see {@link LRU#entries}\n\t * @since 9.0.0\n\t */\n\tkeys () {\n\t\tconst result = new Array(this.size);\n\t\tlet x = this.first;\n\t\tlet i = 0;\n\n\t\twhile (x !== null) {\n\t\t\tresult[i++] = x.key;\n\t\t\tx = x.next;\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Sets a value in the cache and returns any evicted item.\n\t *\n\t * @method setWithEvicted\n\t * @memberof LRU\n\t * @param {string} key - The key to set.\n\t * @param {*} value - The value to store.\n\t * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.\n\t * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.\n\t * @example\n\t * const cache = new LRU(2);\n\t * cache.set('a', 1).set('b', 2);\n\t * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}\n\t * @see {@link LRU#set}\n\t * @see {@link LRU#evict}\n\t * @since 11.3.0\n\t */\n\tsetWithEvicted (key, value, resetTtl = this.resetTtl) {\n\t\tlet evicted = null;\n\n\t\tif (this.has(key)) {\n\t\t\tthis.set(key, value, true, resetTtl);\n\t\t} else {\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tevicted = {...this.first};\n\t\t\t\tthis.evict(true);\n\t\t\t}\n\n\t\t\tlet item = this.items[key] = {\n\t\t\t\texpiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,\n\t\t\t\tkey: key,\n\t\t\t\tprev: this.last,\n\t\t\t\tnext: null,\n\t\t\t\tvalue\n\t\t\t};\n\n\t\t\tif (++this.size === 1) {\n\t\t\t\tthis.first = item;\n\t\t\t} else {\n\t\t\t\tthis.last.next = item;\n\t\t\t}\n\n\t\t\tthis.last = item;\n\t\t}\n\n\t\treturn evicted;\n\t}\n\n\t/**\n\t * Sets a value in the cache. Updates the item's position to most recently used.\n\t *\n\t * @method set\n\t * @memberof LRU\n\t * @param {string} key - The key to set.\n\t * @param {*} value - The value to store.\n\t * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.\n\t * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t * @example\n\t * cache.set('key1', 'value1')\n\t * .set('key2', 'value2')\n\t * .set('key3', 'value3');\n\t * @see {@link LRU#get}\n\t * @see {@link LRU#setWithEvicted}\n\t * @since 1.0.0\n\t */\n\tset (key, value, bypass = false, resetTtl = this.resetTtl) {\n\t\tlet item = this.items[key];\n\n\t\tif (bypass || item !== undefined) {\n\t\t\t// Existing item: update value and position\n\t\t\titem.value = value;\n\n\t\t\tif (bypass === false && resetTtl) {\n\t\t\t\titem.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n\t\t\t}\n\n\t\t\t// Always move to end, but the bypass parameter affects TTL reset behavior\n\t\t\tthis.moveToEnd(item);\n\t\t} else {\n\t\t\t// New item: check for eviction and create\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tthis.evict(true);\n\t\t\t}\n\n\t\t\titem = this.items[key] = {\n\t\t\t\texpiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,\n\t\t\t\tkey: key,\n\t\t\t\tprev: this.last,\n\t\t\t\tnext: null,\n\t\t\t\tvalue\n\t\t\t};\n\n\t\t\tif (++this.size === 1) {\n\t\t\t\tthis.first = item;\n\t\t\t} else {\n\t\t\t\tthis.last.next = item;\n\t\t\t}\n\n\t\t\tthis.last = item;\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns an array of all values in the cache for the specified keys.\n\t * Order follows LRU order (least to most recently used).\n\t *\n\t * @method values\n\t * @memberof LRU\n\t * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.\n\t * @returns {Array<*>} Array of values corresponding to the keys in LRU order.\n\t * @example\n\t * cache.set('a', 1).set('b', 2);\n\t * console.log(cache.values()); // [1, 2]\n\t * console.log(cache.values(['a'])); // [1]\n\t * @see {@link LRU#keys}\n\t * @see {@link LRU#entries}\n\t * @since 11.1.0\n\t */\n\tvalues (keys = this.keys()) {\n\t\tconst result = new Array(keys.length);\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tresult[i] = this.get(keys[i]);\n\t\t}\n\n\t\treturn result;\n\t}\n}\n\n/**\n * Factory function to create a new LRU cache instance with parameter validation.\n *\n * @function lru\n * @param {number} [max=1000] - Maximum number of items to store. Must be >= 0. Use 0 for unlimited size.\n * @param {number} [ttl=0] - Time to live in milliseconds. Must be >= 0. Use 0 for no expiration.\n * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().\n * @returns {LRU} A new LRU cache instance.\n * @throws {TypeError} When parameters are invalid (negative numbers or wrong types).\n * @example\n * // Create cache with factory function\n * const cache = lru(100, 5000, true);\n * cache.set('key', 'value');\n *\n * @example\n * // Error handling\n * try {\n * const cache = lru(-1); // Invalid max\n * } catch (error) {\n * console.error(error.message); // \"Invalid max value\"\n * }\n * @see {@link LRU}\n * @since 1.0.0\n */\nexport function lru (max = 1000, ttl = 0, resetTtl = false) {\n\tif (isNaN(max) || max < 0) {\n\t\tthrow new TypeError(\"Invalid max value\");\n\t}\n\n\tif (isNaN(ttl) || ttl < 0) {\n\t\tthrow new TypeError(\"Invalid ttl value\");\n\t}\n\n\tif (typeof resetTtl !== \"boolean\") {\n\t\tthrow new TypeError(\"Invalid resetTtl value\");\n\t}\n\n\treturn new LRU(max, ttl, resetTtl);\n}\n"],"names":["g","f","exports","module","define","amd","globalThis","self","lru","this","LRU","constructor","max","ttl","resetTtl","first","items","Object","create","last","size","clear","key","has","item","prev","next","entries","keys","result","Array","length","i","get","evict","bypass","expiresAt","expiry","undefined","Date","now","delete","moveToEnd","value","x","setWithEvicted","evicted","set","values","isNaN","TypeError"],"mappings":";;;;CAAA,SAAAA,EAAAC,GAAA,iBAAAC,SAAA,oBAAAC,OAAAF,EAAAC,SAAA,mBAAAE,QAAAA,OAAAC,IAAAD,OAAA,CAAA,WAAAH,GAAAA,GAAAD,EAAA,oBAAAM,WAAAA,WAAAN,GAAAO,MAAAC,IAAA,CAAA,EAAA,CAAA,CAAAC,MAAA,SAAAP,GAAA,aAkBO,MAAMQ,EAcZ,WAAAC,CAAaC,EAAM,EAAGC,EAAM,EAAGC,GAAW,GACzCL,KAAKM,MAAQ,KACbN,KAAKO,MAAQC,OAAOC,OAAO,MAC3BT,KAAKU,KAAO,KACZV,KAAKG,IAAMA,EACXH,KAAKK,SAAWA,EAChBL,KAAKW,KAAO,EACZX,KAAKI,IAAMA,CACZ,CAaA,KAAAQ,GAMC,OALAZ,KAAKM,MAAQ,KACbN,KAAKO,MAAQC,OAAOC,OAAO,MAC3BT,KAAKU,KAAO,KACZV,KAAKW,KAAO,EAELX,IACR,CAiBA,OAAQa,GACP,GAAIb,KAAKc,IAAID,GAAM,CAClB,MAAME,EAAOf,KAAKO,MAAMM,UAEjBb,KAAKO,MAAMM,GAClBb,KAAKW,OAEa,OAAdI,EAAKC,OACRD,EAAKC,KAAKC,KAAOF,EAAKE,MAGL,OAAdF,EAAKE,OACRF,EAAKE,KAAKD,KAAOD,EAAKC,MAGnBhB,KAAKM,QAAUS,IAClBf,KAAKM,MAAQS,EAAKE,MAGfjB,KAAKU,OAASK,IACjBf,KAAKU,KAAOK,EAAKC,KAEnB,CAEA,OAAOhB,IACR,CAkBA,OAAAkB,CAASC,EAAOnB,KAAKmB,QACpB,MAAMC,EAAS,IAAIC,MAAMF,EAAKG,QAC9B,IAAK,IAAIC,EAAI,EAAGA,EAAIJ,EAAKG,OAAQC,IAAK,CACrC,MAAMV,EAAMM,EAAKI,GACjBH,EAAOG,GAAK,CAACV,EAAKb,KAAKwB,IAAIX,GAC5B,CAEA,OAAOO,CACR,CAeA,KAAAK,CAAOC,GAAS,GACf,GAAIA,GAAU1B,KAAKW,KAAO,EAAG,CAC5B,MAAMI,EAAOf,KAAKM,aAEXN,KAAKO,MAAMQ,EAAKF,KAEH,KAAdb,KAAKW,MACVX,KAAKM,MAAQ,KACbN,KAAKU,KAAO,OAEZV,KAAKM,MAAQS,EAAKE,KAClBjB,KAAKM,MAAMU,KAAO,KAEpB,CAEA,OAAOhB,IACR,CAiBA,SAAA2B,CAAWd,GACV,IAAIO,EAMJ,OAJIpB,KAAKc,IAAID,KACZO,EAASpB,KAAKO,MAAMM,GAAKe,QAGnBR,CACR,CAiBA,GAAAI,CAAKX,GACJ,MAAME,EAAOf,KAAKO,MAAMM,GAExB,QAAagB,IAATd,EAEH,OAAIf,KAAKI,IAAM,GACVW,EAAKa,QAAUE,KAAKC,WACvB/B,KAAKgC,OAAOnB,IAOdb,KAAKiC,UAAUlB,GAERA,EAAKmB,MAId,CAiBA,GAAApB,CAAKD,GACJ,OAAOA,KAAOb,KAAKO,KACpB,CAaA,SAAA0B,CAAWlB,GAENf,KAAKU,OAASK,IAKA,OAAdA,EAAKC,OACRD,EAAKC,KAAKC,KAAOF,EAAKE,MAGL,OAAdF,EAAKE,OACRF,EAAKE,KAAKD,KAAOD,EAAKC,MAInBhB,KAAKM,QAAUS,IAClBf,KAAKM,MAAQS,EAAKE,MAInBF,EAAKC,KAAOhB,KAAKU,KACjBK,EAAKE,KAAO,KAEM,OAAdjB,KAAKU,OACRV,KAAKU,KAAKO,KAAOF,GAGlBf,KAAKU,KAAOK,EAGO,OAAff,KAAKM,QACRN,KAAKM,MAAQS,GAEf,CAgBA,IAAAI,GACC,MAAMC,EAAS,IAAIC,MAAMrB,KAAKW,MAC9B,IAAIwB,EAAInC,KAAKM,MACTiB,EAAI,EAER,KAAa,OAANY,GACNf,EAAOG,KAAOY,EAAEtB,IAChBsB,EAAIA,EAAElB,KAGP,OAAOG,CACR,CAmBA,cAAAgB,CAAgBvB,EAAKqB,EAAO7B,EAAWL,KAAKK,UAC3C,IAAIgC,EAAU,KAEd,GAAIrC,KAAKc,IAAID,GACZb,KAAKsC,IAAIzB,EAAKqB,GAAO,EAAM7B,OACrB,CACFL,KAAKG,IAAM,GAAKH,KAAKW,OAASX,KAAKG,MACtCkC,EAAU,IAAIrC,KAAKM,OACnBN,KAAKyB,OAAM,IAGZ,IAAIV,EAAOf,KAAKO,MAAMM,GAAO,CAC5Be,OAAQ5B,KAAKI,IAAM,EAAI0B,KAAKC,MAAQ/B,KAAKI,IAAMJ,KAAKI,IACpDS,IAAKA,EACLG,KAAMhB,KAAKU,KACXO,KAAM,KACNiB,SAGmB,KAAdlC,KAAKW,KACVX,KAAKM,MAAQS,EAEbf,KAAKU,KAAKO,KAAOF,EAGlBf,KAAKU,KAAOK,CACb,CAEA,OAAOsB,CACR,CAoBA,GAAAC,CAAKzB,EAAKqB,EAAOR,GAAS,EAAOrB,EAAWL,KAAKK,UAChD,IAAIU,EAAOf,KAAKO,MAAMM,GAmCtB,OAjCIa,QAAmBG,IAATd,GAEbA,EAAKmB,MAAQA,GAEE,IAAXR,GAAoBrB,IACvBU,EAAKa,OAAS5B,KAAKI,IAAM,EAAI0B,KAAKC,MAAQ/B,KAAKI,IAAMJ,KAAKI,KAI3DJ,KAAKiC,UAAUlB,KAGXf,KAAKG,IAAM,GAAKH,KAAKW,OAASX,KAAKG,KACtCH,KAAKyB,OAAM,GAGZV,EAAOf,KAAKO,MAAMM,GAAO,CACxBe,OAAQ5B,KAAKI,IAAM,EAAI0B,KAAKC,MAAQ/B,KAAKI,IAAMJ,KAAKI,IACpDS,IAAKA,EACLG,KAAMhB,KAAKU,KACXO,KAAM,KACNiB,SAGmB,KAAdlC,KAAKW,KACVX,KAAKM,MAAQS,EAEbf,KAAKU,KAAKO,KAAOF,EAGlBf,KAAKU,KAAOK,GAGNf,IACR,CAkBA,MAAAuC,CAAQpB,EAAOnB,KAAKmB,QACnB,MAAMC,EAAS,IAAIC,MAAMF,EAAKG,QAC9B,IAAK,IAAIC,EAAI,EAAGA,EAAIJ,EAAKG,OAAQC,IAChCH,EAAOG,GAAKvB,KAAKwB,IAAIL,EAAKI,IAG3B,OAAOH,CACR,EAyCD3B,EAAAQ,IAAAA,EAAAR,EAAAM,IAdO,SAAcI,EAAM,IAAMC,EAAM,EAAGC,GAAW,GACpD,GAAImC,MAAMrC,IAAQA,EAAM,EACvB,MAAM,IAAIsC,UAAU,qBAGrB,GAAID,MAAMpC,IAAQA,EAAM,EACvB,MAAM,IAAIqC,UAAU,qBAGrB,GAAwB,kBAAbpC,EACV,MAAM,IAAIoC,UAAU,0BAGrB,OAAO,IAAIxC,EAAIE,EAAKC,EAAKC,EAC1B,CAAA"}
\ No newline at end of file
+{"version":3,"file":"tiny-lru.umd.min.js","sources":["../src/lru.js"],"sourcesContent":["/**\n * A high-performance Least Recently Used (LRU) cache implementation with optional TTL support.\n * Items are automatically evicted when the cache reaches its maximum size,\n * removing the least recently used items first. All core operations (get, set, delete) are O(1).\n *\n * @class LRU\n * @example\n * // Create a cache with max 100 items\n * const cache = new LRU(100);\n * cache.set('key1', 'value1');\n * console.log(cache.get('key1')); // 'value1'\n *\n * @example\n * // Create a cache with TTL\n * const cache = new LRU(100, 5000); // 5 second TTL\n * cache.set('key1', 'value1');\n * // After 5 seconds, key1 will be expired\n */\nexport class LRU {\n /**\n * Creates a new LRU cache instance.\n * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.\n *\n * @constructor\n * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.\n * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.\n * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().\n * @example\n * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access\n * @see {@link lru} For parameter validation\n * @since 1.0.0\n */\n constructor(max = 0, ttl = 0, resetTtl = false) {\n this.first = null;\n this.items = Object.create(null);\n this.last = null;\n this.max = max;\n this.resetTtl = resetTtl;\n this.size = 0;\n this.ttl = ttl;\n }\n\n /**\n * Removes all items from the cache.\n *\n * @method clear\n * @memberof LRU\n * @returns {LRU} The LRU instance for method chaining.\n * @example\n * cache.clear();\n * console.log(cache.size); // 0\n * @since 1.0.0\n */\n clear() {\n this.first = null;\n this.items = Object.create(null);\n this.last = null;\n this.size = 0;\n\n return this;\n }\n\n /**\n * Removes an item from the cache by key.\n *\n * @method delete\n * @memberof LRU\n * @param {string} key - The key of the item to delete.\n * @returns {LRU} The LRU instance for method chaining.\n * @example\n * cache.set('key1', 'value1');\n * cache.delete('key1');\n * console.log(cache.has('key1')); // false\n * @see {@link LRU#has}\n * @see {@link LRU#clear}\n * @since 1.0.0\n */\n delete(key) {\n const item = this.items[key];\n\n if (item !== undefined) {\n delete this.items[key];\n this.size--;\n\n if (item.prev !== null) {\n item.prev.next = item.next;\n }\n\n if (item.next !== null) {\n item.next.prev = item.prev;\n }\n\n if (this.first === item) {\n this.first = item.next;\n }\n\n if (this.last === item) {\n this.last = item.prev;\n }\n\n item.prev = null;\n item.next = null;\n }\n\n return this;\n }\n\n /**\n * Returns an array of [key, value] pairs for the specified keys.\n * Order follows LRU order (least to most recently used).\n *\n * @method entries\n * @memberof LRU\n * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.\n * @returns {Array>} Array of [key, value] pairs in LRU order.\n * @example\n * cache.set('a', 1).set('b', 2);\n * console.log(cache.entries()); // [['a', 1], ['b', 2]]\n * console.log(cache.entries(['a'])); // [['a', 1]]\n * @see {@link LRU#keys}\n * @see {@link LRU#values}\n * @since 11.1.0\n */\n entries(keys) {\n if (keys === undefined) {\n keys = this.keys();\n }\n\n const result = Array.from({ length: keys.length });\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n const item = this.items[key];\n result[i] = [key, item !== undefined ? item.value : undefined];\n }\n\n return result;\n }\n\n /**\n * Removes the least recently used item from the cache.\n *\n * @method evict\n * @memberof LRU\n * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.\n * @returns {LRU} The LRU instance for method chaining.\n * @example\n * cache.set('old', 'value').set('new', 'value');\n * cache.evict(); // Removes 'old' item\n * @see {@link LRU#setWithEvicted}\n * @since 1.0.0\n */\n evict(bypass = false) {\n if (bypass || this.size > 0) {\n const item = this.first;\n\n if (!item) {\n return this;\n }\n\n delete this.items[item.key];\n\n if (--this.size === 0) {\n this.first = null;\n this.last = null;\n } else {\n this.first = item.next;\n this.first.prev = null;\n }\n\n item.next = null;\n }\n\n return this;\n }\n\n /**\n * Returns the expiration timestamp for a given key.\n *\n * @method expiresAt\n * @memberof LRU\n * @param {string} key - The key to check expiration for.\n * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.\n * @example\n * const cache = new LRU(100, 5000); // 5 second TTL\n * cache.set('key1', 'value1');\n * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now\n * @see {@link LRU#get}\n * @see {@link LRU#has}\n * @since 1.0.0\n */\n expiresAt(key) {\n const item = this.items[key];\n return item !== undefined ? item.expiry : undefined;\n }\n\n /**\n * Retrieves a value from the cache by key. Updates the item's position to most recently used.\n *\n * @method get\n * @memberof LRU\n * @param {string} key - The key to retrieve.\n * @returns {*} The value associated with the key, or undefined if not found or expired.\n * @example\n * cache.set('key1', 'value1');\n * console.log(cache.get('key1')); // 'value1'\n * console.log(cache.get('nonexistent')); // undefined\n * @see {@link LRU#set}\n * @see {@link LRU#has}\n * @since 1.0.0\n */\n get(key) {\n const item = this.items[key];\n\n if (item !== undefined) {\n // Check TTL only if enabled to avoid unnecessary Date.now() calls\n if (this.ttl > 0) {\n if (item.expiry <= Date.now()) {\n this.delete(key);\n\n return undefined;\n }\n }\n\n // Fast LRU update without full set() overhead\n this.moveToEnd(item);\n\n return item.value;\n }\n\n return undefined;\n }\n\n /**\n * Checks if a key exists in the cache.\n *\n * @method has\n * @memberof LRU\n * @param {string} key - The key to check for.\n * @returns {boolean} True if the key exists, false otherwise.\n * @example\n * cache.set('key1', 'value1');\n * console.log(cache.has('key1')); // true\n * console.log(cache.has('nonexistent')); // false\n * @see {@link LRU#get}\n * @see {@link LRU#delete}\n * @since 9.0.0\n */\n has(key) {\n const item = this.items[key];\n return item !== undefined && (this.ttl === 0 || item.expiry > Date.now());\n }\n\n /**\n * Efficiently moves an item to the end of the LRU list (most recently used position).\n * This is an internal optimization method that avoids the overhead of the full set() operation\n * when only LRU position needs to be updated.\n *\n * @method moveToEnd\n * @memberof LRU\n * @param {Object} item - The cache item with prev/next pointers to reposition.\n * @private\n * @since 11.3.5\n */\n moveToEnd(item) {\n if (this.last === item) {\n return;\n }\n\n if (item.prev !== null) {\n item.prev.next = item.next;\n }\n\n if (item.next !== null) {\n item.next.prev = item.prev;\n }\n\n if (this.first === item) {\n this.first = item.next;\n }\n\n item.prev = this.last;\n item.next = null;\n this.last.next = item;\n this.last = item;\n }\n\n /**\n * Returns an array of all keys in the cache, ordered from least to most recently used.\n *\n * @method keys\n * @memberof LRU\n * @returns {string[]} Array of keys in LRU order.\n * @example\n * cache.set('a', 1).set('b', 2);\n * cache.get('a'); // Move 'a' to most recent\n * console.log(cache.keys()); // ['b', 'a']\n * @see {@link LRU#values}\n * @see {@link LRU#entries}\n * @since 9.0.0\n */\n keys() {\n const result = Array.from({ length: this.size });\n let x = this.first;\n let i = 0;\n\n while (x !== null) {\n result[i++] = x.key;\n x = x.next;\n }\n\n return result;\n }\n\n /**\n * Sets a value in the cache and returns any evicted item.\n *\n * @method setWithEvicted\n * @memberof LRU\n * @param {string} key - The key to set.\n * @param {*} value - The value to store.\n * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.\n * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.\n * @example\n * const cache = new LRU(2);\n * cache.set('a', 1).set('b', 2);\n * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}\n * @see {@link LRU#set}\n * @see {@link LRU#evict}\n * @since 11.3.0\n */\n setWithEvicted(key, value, resetTtl = this.resetTtl) {\n let evicted = null;\n let item = this.items[key];\n\n if (item !== undefined) {\n item.value = value;\n if (resetTtl) {\n item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n }\n this.moveToEnd(item);\n } else {\n if (this.max > 0 && this.size === this.max) {\n evicted = {\n key: this.first.key,\n value: this.first.value,\n expiry: this.first.expiry,\n };\n this.evict(true);\n }\n\n item = this.items[key] = {\n expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,\n key: key,\n prev: this.last,\n next: null,\n value,\n };\n\n if (++this.size === 1) {\n this.first = item;\n } else {\n this.last.next = item;\n }\n\n this.last = item;\n }\n\n return evicted;\n }\n\n /**\n * Sets a value in the cache. Updates the item's position to most recently used.\n *\n * @method set\n * @memberof LRU\n * @param {string} key - The key to set.\n * @param {*} value - The value to store.\n * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.\n * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.\n * @returns {LRU} The LRU instance for method chaining.\n * @example\n * cache.set('key1', 'value1')\n * .set('key2', 'value2')\n * .set('key3', 'value3');\n * @see {@link LRU#get}\n * @see {@link LRU#setWithEvicted}\n * @since 1.0.0\n */\n set(key, value, bypass = false, resetTtl = this.resetTtl) {\n let item = this.items[key];\n\n if (bypass || item !== undefined) {\n // Existing item: update value and position\n item.value = value;\n\n if (bypass === false && resetTtl) {\n item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n }\n\n // Always move to end, but the bypass parameter affects TTL reset behavior\n this.moveToEnd(item);\n } else {\n // New item: check for eviction and create\n if (this.max > 0 && this.size === this.max) {\n this.evict(true);\n }\n\n item = this.items[key] = {\n expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,\n key: key,\n prev: this.last,\n next: null,\n value,\n };\n\n if (++this.size === 1) {\n this.first = item;\n } else {\n this.last.next = item;\n }\n\n this.last = item;\n }\n\n return this;\n }\n\n /**\n * Returns an array of all values in the cache for the specified keys.\n * Order follows LRU order (least to most recently used).\n *\n * @method values\n * @memberof LRU\n * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.\n * @returns {Array<*>} Array of values corresponding to the keys in LRU order.\n * @example\n * cache.set('a', 1).set('b', 2);\n * console.log(cache.values()); // [1, 2]\n * console.log(cache.values(['a'])); // [1]\n * @see {@link LRU#keys}\n * @see {@link LRU#entries}\n * @since 11.1.0\n */\n values(keys) {\n if (keys === undefined) {\n keys = this.keys();\n }\n\n const result = Array.from({ length: keys.length });\n for (let i = 0; i < keys.length; i++) {\n const item = this.items[keys[i]];\n result[i] = item !== undefined ? item.value : undefined;\n }\n\n return result;\n }\n}\n\n/**\n * Factory function to create a new LRU cache instance with parameter validation.\n *\n * @function lru\n * @param {number} [max=1000] - Maximum number of items to store. Must be >= 0. Use 0 for unlimited size.\n * @param {number} [ttl=0] - Time to live in milliseconds. Must be >= 0. Use 0 for no expiration.\n * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().\n * @returns {LRU} A new LRU cache instance.\n * @throws {TypeError} When parameters are invalid (negative numbers or wrong types).\n * @example\n * // Create cache with factory function\n * const cache = lru(100, 5000, true);\n * cache.set('key', 'value');\n *\n * @example\n * // Error handling\n * try {\n * const cache = lru(-1); // Invalid max\n * } catch (error) {\n * console.error(error.message); // \"Invalid max value\"\n * }\n * @see {@link LRU}\n * @since 1.0.0\n */\nexport function lru(max = 1000, ttl = 0, resetTtl = false) {\n if (isNaN(max) || max < 0) {\n throw new TypeError(\"Invalid max value\");\n }\n\n if (isNaN(ttl) || ttl < 0) {\n throw new TypeError(\"Invalid ttl value\");\n }\n\n if (typeof resetTtl !== \"boolean\") {\n throw new TypeError(\"Invalid resetTtl value\");\n }\n\n return new LRU(max, ttl, resetTtl);\n}\n"],"names":["g","f","exports","module","define","amd","globalThis","self","lru","this","LRU","constructor","max","ttl","resetTtl","first","items","Object","create","last","size","clear","key","item","undefined","prev","next","entries","keys","result","Array","from","length","i","value","evict","bypass","expiresAt","expiry","get","Date","now","delete","moveToEnd","has","x","setWithEvicted","evicted","set","values","isNaN","TypeError"],"mappings":";;;;CAAA,SAAAA,EAAAC,GAAA,iBAAAC,SAAA,oBAAAC,OAAAF,EAAAC,SAAA,mBAAAE,QAAAA,OAAAC,IAAAD,OAAA,CAAA,WAAAH,GAAAA,GAAAD,EAAA,oBAAAM,WAAAA,WAAAN,GAAAO,MAAAC,IAAA,CAAA,EAAA,CAAA,CAAAC,MAAA,SAAAP,GAAA,aAkBO,MAAMQ,EAcX,WAAAC,CAAYC,EAAM,EAAGC,EAAM,EAAGC,GAAW,GACvCL,KAAKM,MAAQ,KACbN,KAAKO,MAAQC,OAAOC,OAAO,MAC3BT,KAAKU,KAAO,KACZV,KAAKG,IAAMA,EACXH,KAAKK,SAAWA,EAChBL,KAAKW,KAAO,EACZX,KAAKI,IAAMA,CACb,CAaA,KAAAQ,GAME,OALAZ,KAAKM,MAAQ,KACbN,KAAKO,MAAQC,OAAOC,OAAO,MAC3BT,KAAKU,KAAO,KACZV,KAAKW,KAAO,EAELX,IACT,CAiBA,OAAOa,GACL,MAAMC,EAAOd,KAAKO,MAAMM,GA0BxB,YAxBaE,IAATD,WACKd,KAAKO,MAAMM,GAClBb,KAAKW,OAEa,OAAdG,EAAKE,OACPF,EAAKE,KAAKC,KAAOH,EAAKG,MAGN,OAAdH,EAAKG,OACPH,EAAKG,KAAKD,KAAOF,EAAKE,MAGpBhB,KAAKM,QAAUQ,IACjBd,KAAKM,MAAQQ,EAAKG,MAGhBjB,KAAKU,OAASI,IAChBd,KAAKU,KAAOI,EAAKE,MAGnBF,EAAKE,KAAO,KACZF,EAAKG,KAAO,MAGPjB,IACT,CAkBA,OAAAkB,CAAQC,QACOJ,IAATI,IACFA,EAAOnB,KAAKmB,QAGd,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQJ,EAAKI,SACzC,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAKI,OAAQC,IAAK,CACpC,MAAMX,EAAMM,EAAKK,GACXV,EAAOd,KAAKO,MAAMM,GACxBO,EAAOI,GAAK,CAACX,OAAcE,IAATD,EAAqBA,EAAKW,WAAQV,EACtD,CAEA,OAAOK,CACT,CAeA,KAAAM,CAAMC,GAAS,GACb,GAAIA,GAAU3B,KAAKW,KAAO,EAAG,CAC3B,MAAMG,EAAOd,KAAKM,MAElB,IAAKQ,EACH,OAAOd,YAGFA,KAAKO,MAAMO,EAAKD,KAEH,KAAdb,KAAKW,MACTX,KAAKM,MAAQ,KACbN,KAAKU,KAAO,OAEZV,KAAKM,MAAQQ,EAAKG,KAClBjB,KAAKM,MAAMU,KAAO,MAGpBF,EAAKG,KAAO,IACd,CAEA,OAAOjB,IACT,CAiBA,SAAA4B,CAAUf,GACR,MAAMC,EAAOd,KAAKO,MAAMM,GACxB,YAAgBE,IAATD,EAAqBA,EAAKe,YAASd,CAC5C,CAiBA,GAAAe,CAAIjB,GACF,MAAMC,EAAOd,KAAKO,MAAMM,GAExB,QAAaE,IAATD,EAEF,OAAId,KAAKI,IAAM,GACTU,EAAKe,QAAUE,KAAKC,WACtBhC,KAAKiC,OAAOpB,IAOhBb,KAAKkC,UAAUpB,GAERA,EAAKW,MAIhB,CAiBA,GAAAU,CAAItB,GACF,MAAMC,EAAOd,KAAKO,MAAMM,GACxB,YAAgBE,IAATD,IAAoC,IAAbd,KAAKI,KAAaU,EAAKe,OAASE,KAAKC,MACrE,CAaA,SAAAE,CAAUpB,GACJd,KAAKU,OAASI,IAIA,OAAdA,EAAKE,OACPF,EAAKE,KAAKC,KAAOH,EAAKG,MAGN,OAAdH,EAAKG,OACPH,EAAKG,KAAKD,KAAOF,EAAKE,MAGpBhB,KAAKM,QAAUQ,IACjBd,KAAKM,MAAQQ,EAAKG,MAGpBH,EAAKE,KAAOhB,KAAKU,KACjBI,EAAKG,KAAO,KACZjB,KAAKU,KAAKO,KAAOH,EACjBd,KAAKU,KAAOI,EACd,CAgBA,IAAAK,GACE,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQvB,KAAKW,OACzC,IAAIyB,EAAIpC,KAAKM,MACTkB,EAAI,EAER,KAAa,OAANY,GACLhB,EAAOI,KAAOY,EAAEvB,IAChBuB,EAAIA,EAAEnB,KAGR,OAAOG,CACT,CAmBA,cAAAiB,CAAexB,EAAKY,EAAOpB,EAAWL,KAAKK,UACzC,IAAIiC,EAAU,KACVxB,EAAOd,KAAKO,MAAMM,GAmCtB,YAjCaE,IAATD,GACFA,EAAKW,MAAQA,EACTpB,IACFS,EAAKe,OAAS7B,KAAKI,IAAM,EAAI2B,KAAKC,MAAQhC,KAAKI,IAAMJ,KAAKI,KAE5DJ,KAAKkC,UAAUpB,KAEXd,KAAKG,IAAM,GAAKH,KAAKW,OAASX,KAAKG,MACrCmC,EAAU,CACRzB,IAAKb,KAAKM,MAAMO,IAChBY,MAAOzB,KAAKM,MAAMmB,MAClBI,OAAQ7B,KAAKM,MAAMuB,QAErB7B,KAAK0B,OAAM,IAGbZ,EAAOd,KAAKO,MAAMM,GAAO,CACvBgB,OAAQ7B,KAAKI,IAAM,EAAI2B,KAAKC,MAAQhC,KAAKI,IAAMJ,KAAKI,IACpDS,IAAKA,EACLG,KAAMhB,KAAKU,KACXO,KAAM,KACNQ,SAGkB,KAAdzB,KAAKW,KACTX,KAAKM,MAAQQ,EAEbd,KAAKU,KAAKO,KAAOH,EAGnBd,KAAKU,KAAOI,GAGPwB,CACT,CAoBA,GAAAC,CAAI1B,EAAKY,EAAOE,GAAS,EAAOtB,EAAWL,KAAKK,UAC9C,IAAIS,EAAOd,KAAKO,MAAMM,GAmCtB,OAjCIc,QAAmBZ,IAATD,GAEZA,EAAKW,MAAQA,GAEE,IAAXE,GAAoBtB,IACtBS,EAAKe,OAAS7B,KAAKI,IAAM,EAAI2B,KAAKC,MAAQhC,KAAKI,IAAMJ,KAAKI,KAI5DJ,KAAKkC,UAAUpB,KAGXd,KAAKG,IAAM,GAAKH,KAAKW,OAASX,KAAKG,KACrCH,KAAK0B,OAAM,GAGbZ,EAAOd,KAAKO,MAAMM,GAAO,CACvBgB,OAAQ7B,KAAKI,IAAM,EAAI2B,KAAKC,MAAQhC,KAAKI,IAAMJ,KAAKI,IACpDS,IAAKA,EACLG,KAAMhB,KAAKU,KACXO,KAAM,KACNQ,SAGkB,KAAdzB,KAAKW,KACTX,KAAKM,MAAQQ,EAEbd,KAAKU,KAAKO,KAAOH,EAGnBd,KAAKU,KAAOI,GAGPd,IACT,CAkBA,MAAAwC,CAAOrB,QACQJ,IAATI,IACFA,EAAOnB,KAAKmB,QAGd,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQJ,EAAKI,SACzC,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAKI,OAAQC,IAAK,CACpC,MAAMV,EAAOd,KAAKO,MAAMY,EAAKK,IAC7BJ,EAAOI,QAAcT,IAATD,EAAqBA,EAAKW,WAAQV,CAChD,CAEA,OAAOK,CACT,EAyCF3B,EAAAQ,IAAAA,EAAAR,EAAAM,IAdO,SAAaI,EAAM,IAAMC,EAAM,EAAGC,GAAW,GAClD,GAAIoC,MAAMtC,IAAQA,EAAM,EACtB,MAAM,IAAIuC,UAAU,qBAGtB,GAAID,MAAMrC,IAAQA,EAAM,EACtB,MAAM,IAAIsC,UAAU,qBAGtB,GAAwB,kBAAbrC,EACT,MAAM,IAAIqC,UAAU,0BAGtB,OAAO,IAAIzC,EAAIE,EAAKC,EAAKC,EAC3B,CAAA"}
\ No newline at end of file
diff --git a/docs/API.md b/docs/API.md
new file mode 100644
index 0000000..3a4fd83
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,388 @@
+# API Reference
+
+Complete API documentation for tiny-lru.
+
+## Table of Contents
+
+- [Factory Function](#factory-function)
+- [LRU Class](#lru-class)
+- [Properties](#properties)
+- [Methods](#methods)
+
+---
+
+## Factory Function
+
+### `lru(max?, ttl?, resetTtl?)`
+
+Creates a new LRU cache instance with parameter validation.
+
+```javascript
+import { lru } from "tiny-lru";
+
+const cache = lru();
+const cache = lru(100);
+const cache = lru(100, 5000);
+const cache = lru(100, 5000, true);
+```
+
+**Parameters:**
+
+| Name | Type | Default | Description |
+|------|------|---------|-------------|
+| `max` | `number` | `1000` | Maximum items. `0` = unlimited. Must be >= 0. |
+| `ttl` | `number` | `0` | Time-to-live in milliseconds. `0` = no expiration. Must be >= 0. |
+| `resetTtl` | `boolean` | `false` | Reset TTL when updating existing items via `set()` |
+
+**Returns:** `LRU` - New cache instance
+
+**Throws:** `TypeError` if parameters are invalid
+
+```javascript
+lru(-1); // TypeError: Invalid max value
+lru(100, -1); // TypeError: Invalid ttl value
+lru(100, 0, "yes"); // TypeError: Invalid resetTtl value
+```
+
+---
+
+## LRU Class
+
+### `new LRU(max?, ttl?, resetTtl?)`
+
+Creates an LRU cache instance. Does not validate parameters.
+
+```javascript
+import { LRU } from "tiny-lru";
+
+const cache = new LRU(100, 5000, true);
+```
+
+**Parameters:**
+
+| Name | Type | Default | Description |
+|------|------|---------|-------------|
+| `max` | `number` | `0` | Maximum items. `0` = unlimited. |
+| `ttl` | `number` | `0` | Time-to-live in milliseconds. `0` = no expiration. |
+| `resetTtl` | `boolean` | `false` | Reset TTL when updating via `set()` |
+
+---
+
+## Properties
+
+### `size`
+
+`number` - Current number of items in cache.
+
+```javascript
+const cache = lru(10);
+cache.set("a", 1).set("b", 2);
+console.log(cache.size); // 2
+```
+
+### `max`
+
+`number` - Maximum number of items allowed.
+
+```javascript
+const cache = lru(100);
+console.log(cache.max); // 100
+```
+
+### `ttl`
+
+`number` - Time-to-live in milliseconds. `0` = no expiration.
+
+```javascript
+const cache = lru(100, 60000);
+console.log(cache.ttl); // 60000
+```
+
+### `resetTtl`
+
+`boolean` - Whether TTL resets on `set()` updates.
+
+```javascript
+const cache = lru(100, 5000, true);
+console.log(cache.resetTtl); // true
+```
+
+### `first`
+
+`Object | null` - Least recently used item (node with `key`, `value`, `prev`, `next`, `expiry`).
+
+```javascript
+const cache = lru(2);
+cache.set("a", 1).set("b", 2);
+console.log(cache.first.key); // "a"
+console.log(cache.first.value); // 1
+```
+
+### `last`
+
+`Object | null` - Most recently used item.
+
+```javascript
+const cache = lru(2);
+cache.set("a", 1).set("b", 2);
+console.log(cache.last.key); // "b"
+```
+
+---
+
+## Methods
+
+### `clear()`
+
+Removes all items from cache.
+
+```javascript
+cache.set("a", 1).set("b", 2);
+cache.clear();
+console.log(cache.size); // 0
+```
+
+**Returns:** `LRU` - this instance (for chaining)
+
+---
+
+### `delete(key)`
+
+Removes item by key.
+
+```javascript
+cache.set("a", 1).set("b", 2);
+cache.delete("a");
+console.log(cache.has("a")); // false
+console.log(cache.size); // 1
+```
+
+**Parameters:**
+
+| Name | Type | Description |
+|------|------|-------------|
+| `key` | `string` | Key to delete |
+
+**Returns:** `LRU` - this instance (for chaining)
+
+---
+
+### `entries(keys?)`
+
+Returns `[key, value]` pairs in LRU order.
+
+```javascript
+cache.set("a", 1).set("b", 2).set("c", 3);
+console.log(cache.entries());
+// [['a', 1], ['b', 2], ['c', 3]]
+
+console.log(cache.entries(["c", "a"]));
+// [['c', 3], ['a', 1]] - respects LRU order
+```
+
+**Parameters:**
+
+| Name | Type | Description |
+|------|------|-------------|
+| `keys` | `string[]` | Optional specific keys to retrieve |
+
+**Returns:** `Array<[string, *]>` - Array of key-value pairs
+
+---
+
+### `evict(bypass?)`
+
+Removes the least recently used item.
+
+```javascript
+cache.set("a", 1).set("b", 2).set("c", 3);
+cache.evict();
+console.log(cache.has("a")); // false
+console.log(cache.keys()); // ['b', 'c']
+```
+
+**Parameters:**
+
+| Name | Type | Default | Description |
+|------|------|---------|-------------|
+| `bypass` | `boolean` | `false` | Force eviction even when empty |
+
+**Returns:** `LRU` - this instance (for chaining)
+
+---
+
+### `expiresAt(key)`
+
+Gets expiration timestamp for a key.
+
+```javascript
+const cache = lru(100, 5000);
+cache.set("key", "value");
+console.log(cache.expiresAt("key")); // timestamp ~5 seconds from now
+console.log(cache.expiresAt("nonexistent")); // undefined
+```
+
+**Parameters:**
+
+| Name | Type | Description |
+|------|------|-------------|
+| `key` | `string` | Key to check |
+
+**Returns:** `number | undefined` - Expiration timestamp or undefined
+
+---
+
+### `get(key)`
+
+Retrieves value and promotes to most recently used.
+
+```javascript
+cache.set("a", 1).set("b", 2);
+cache.get("a"); // 1
+console.log(cache.keys()); // ['b', 'a'] - 'a' moved to end
+```
+
+Expired items are deleted and return `undefined`.
+
+**Parameters:**
+
+| Name | Type | Description |
+|------|------|-------------|
+| `key` | `string` | Key to retrieve |
+
+**Returns:** `* | undefined` - Value or undefined if not found/expired
+
+---
+
+### `has(key)`
+
+Checks if key exists and is not expired.
+
+```javascript
+cache.set("a", 1);
+cache.has("a"); // true
+cache.has("nonexistent"); // false
+```
+
+**Parameters:**
+
+| Name | Type | Description |
+|------|------|-------------|
+| `key` | `string` | Key to check |
+
+**Returns:** `boolean`
+
+---
+
+### `keys()`
+
+Returns all keys in LRU order (oldest first).
+
+```javascript
+cache.set("a", 1).set("b", 2).set("c", 3);
+cache.get("a"); // Promote 'a'
+console.log(cache.keys()); // ['b', 'c', 'a']
+```
+
+**Returns:** `string[]`
+
+---
+
+### `set(key, value, bypass?, resetTtl?)`
+
+Stores value and moves to most recently used.
+
+```javascript
+cache.set("a", 1).set("b", 2).set("c", 3);
+console.log(cache.keys()); // ['a', 'b', 'c']
+```
+
+**Parameters:**
+
+| Name | Type | Default | Description |
+|------|------|---------|-------------|
+| `key` | `string` | - | Item key |
+| `value` | `*` | - | Item value |
+| `bypass` | `boolean` | `false` | Internal flag for `setWithEvicted` |
+| `resetTtl` | `boolean` | `this.resetTtl` | Reset TTL for this operation |
+
+**Returns:** `LRU` - this instance (for chaining)
+
+---
+
+### `setWithEvicted(key, value, resetTtl?)`
+
+Stores value and returns evicted item if cache was full.
+
+```javascript
+const cache = lru(2);
+cache.set("a", 1).set("b", 2);
+
+const evicted = cache.setWithEvicted("c", 3);
+console.log(evicted); // { key: 'a', value: 1, expiry: 0 }
+console.log(cache.keys()); // ['b', 'c']
+```
+
+**Parameters:**
+
+| Name | Type | Default | Description |
+|------|------|---------|-------------|
+| `key` | `string` | - | Item key |
+| `value` | `*` | - | Item value |
+| `resetTtl` | `boolean` | `this.resetTtl` | Reset TTL for this operation |
+
+**Returns:** `{ key, value, expiry } | null` - Evicted item or null
+
+---
+
+### `values(keys?)`
+
+Returns values in LRU order.
+
+```javascript
+cache.set("a", 1).set("b", 2).set("c", 3);
+console.log(cache.values());
+// [1, 2, 3]
+
+console.log(cache.values(["c", "a"]));
+// [3, 1] - respects LRU order
+```
+
+**Parameters:**
+
+| Name | Type | Description |
+|------|------|-------------|
+| `keys` | `string[]` | Optional specific keys to retrieve |
+
+**Returns:** `*[]` - Array of values
+
+---
+
+## Evicted Item Shape
+
+When `setWithEvicted` returns an evicted item:
+
+```javascript
+{
+ key: string, // The evicted key
+ value: *, // The evicted value
+ expiry: number // Expiration timestamp (0 if no TTL)
+}
+```
+
+---
+
+## Method Chaining
+
+All mutation methods return `this` for chaining:
+
+```javascript
+cache
+ .set("a", 1)
+ .set("b", 2)
+ .set("c", 3)
+ .delete("a")
+ .evict();
+
+console.log(cache.keys()); // ['c']
+```
diff --git a/docs/CODE_STYLE_GUIDE.md b/docs/CODE_STYLE_GUIDE.md
index 15de74b..684941b 100644
--- a/docs/CODE_STYLE_GUIDE.md
+++ b/docs/CODE_STYLE_GUIDE.md
@@ -1,279 +1,159 @@
# Code Style Guide
-This document outlines the coding standards and best practices for the tiny-lru project. Following these guidelines ensures consistency, maintainability, and readability across the codebase.
+Coding conventions for tiny-lru source code.
-## Table of Contents
+## Editor Configuration
-- [General Principles](#general-principles)
-- [Naming Conventions](#naming-conventions)
-- [Code Formatting](#code-formatting)
-- [Documentation](#documentation)
-- [Functions and Methods](#functions-and-methods)
-- [Classes](#classes)
-- [Error Handling](#error-handling)
-- [Testing](#testing)
-- [Security](#security)
-- [Performance](#performance)
-- [File Organization](#file-organization)
+Set your editor to use **tabs** for indentation.
-## General Principles
+## JavaScript Style
-We adhere to the following software engineering principles:
-
-- **DRY (Don't Repeat Yourself)**: Avoid code duplication
-- **KISS (Keep It Simple, Stupid)**: Favor simple solutions over complex ones
-- **YAGNI (You Aren't Gonna Need It)**: Don't implement features until they're needed
-- **SOLID Principles**: Follow object-oriented design principles
-- **OWASP Security Guidelines**: Implement secure coding practices
-
-## Naming Conventions
-
-### Variables and Functions
-- Use **camelCase** for all variable and function names
-- Use descriptive names that clearly indicate purpose
+### Formatting
```javascript
-// ✅ Good
-const userCache = new LRU(100);
-const calculateExpiry = (ttl) => Date.now() + ttl;
-
-// ❌ Bad
-const uc = new LRU(100);
-const calc = (t) => Date.now() + t;
-```
-
-### Constants
-- Use **UPPER_CASE_SNAKE_CASE** for constants
-- Group related constants together
-
-```javascript
-// ✅ Good
-const DEFAULT_MAX_SIZE = 1000;
-const DEFAULT_TTL = 0;
-const CACHE_MISS_PENALTY = 100;
-
-// ❌ Bad
-const defaultMaxSize = 1000;
-const default_ttl = 0;
-```
-
-### Classes
-- Use **PascalCase** for class names
-- Use descriptive names that indicate the class purpose
-
-```javascript
-// ✅ Good
-class LRU {
- // implementation
+// Tabs for indentation
+function example() {
+ const cache = new LRU();
}
-class CacheNode {
- // implementation
-}
+// Double quotes
+const name = "tiny-lru";
-// ❌ Bad
-class lru {
- // implementation
-}
-```
+// Semicolons required
+const result = cache.get("key");
-### Files and Directories
-- Use **kebab-case** for file and directory names
-- Use descriptive names that indicate content purpose
+// K&R braces
+if (condition) {
+ doSomething();
+} else {
+ doSomethingElse();
+}
+// Space before function parens
+function myFunction() { }
+const arrowFn = () => { };
+const x = function() { };
```
-src/
- lru.js
- cache-utils.js
-test/
- integration/
- lru-integration-test.js
- unit/
- lru-unit-test.js
-```
-
-## Code Formatting
-
-### Indentation
-- Use **tabs** for indentation (as per existing codebase)
-- Be consistent throughout the project
-### Line Length
-- Keep lines under **120 characters** when possible
-- Break long lines at logical points
-
-### Spacing
-- Use spaces around operators
-- Use spaces after commas
-- No trailing whitespace
+### Comparisons
```javascript
-// ✅ Good
-const result = a + b * c;
-const array = [1, 2, 3, 4];
-const object = { key: value, another: data };
-
-// ❌ Bad
-const result=a+b*c;
-const array=[1,2,3,4];
-const object={key:value,another:data};
+// Use === and !== for comparisons
+if (item !== undefined) { }
+if (this.first === null) { }
```
-### Semicolons
-- Use semicolons consistently throughout the codebase
-- Follow the existing project pattern
-
-### Braces
-- Use consistent brace style (K&R style as per existing code)
+### Object Creation
```javascript
-// ✅ Good
-if (condition) {
- doSomething();
-} else {
- doSomethingElse();
-}
+// Use Object.create(null) for hash maps - avoids prototype pollution
+this.items = Object.create(null);
-// ❌ Bad
-if (condition)
-{
- doSomething();
-}
-else
-{
- doSomethingElse();
-}
+// Use Array.from() for pre-allocated arrays
+const result = Array.from({ length: this.size });
```
-## Documentation
+## JSDoc Comments
-### JSDoc Standards
-- Use **JSDoc standard** for all function and class documentation
-- Include comprehensive descriptions, parameters, return values, and examples
+Every exported function and class method must have JSDoc:
```javascript
/**
- * Retrieves a value from the cache by key.
+ * Short description of the method.
*
- * @method get
+ * @method methodName
* @memberof LRU
- * @param {string} key - The key to retrieve.
- * @returns {*} The value associated with the key, or undefined if not found.
+ * @param {string} key - Description of parameter.
+ * @returns {LRU} Description of return value.
* @example
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- * @see {@link LRU#set}
+ * cache.set('key', 'value');
+ * @see {@link LRU#get}
* @since 1.0.0
*/
-get(key) {
+methodName(key) {
// implementation
}
```
-### Required JSDoc Tags
-- `@param` for all parameters with type and description
-- `@returns` for return values with type and description
-- `@throws` for exceptions that may be thrown
-- `@example` for usage examples
-- `@since` for version information
-- `@see` for related methods/classes
-- `@memberof` for class methods
+### JSDoc Tags
-### Code Comments
-- Use inline comments sparingly and only when code logic is complex
-- Write self-documenting code when possible
-- Explain **why**, not **what**
+- `@method` - Method name
+- `@memberof` - Parent class
+- `@param` - Parameters with type and description
+- `@returns` - Return value with type
+- `@example` - Usage example
+- `@see` - Related methods
+- `@since` - Version introduced
+- `@private` - For internal methods
+
+## Naming
```javascript
-// ✅ Good - explains why
-if (this.ttl > 0 && item.expiry <= Date.now()) {
- // Item has expired, remove it from cache
- this.delete(key);
-}
+// Classes: PascalCase
+export class LRU { }
-// ❌ Bad - explains what (obvious from code)
-// Check if ttl is greater than 0 and item expiry is less than or equal to current time
-if (this.ttl > 0 && item.expiry <= Date.now()) {
- this.delete(key);
-}
+// Methods: camelCase
+clear() { }
+setWithEvicted() { }
+
+// Variables: camelCase
+const maxSize = 1000;
+let currentItem = null;
+
+// Constants: camelCase (not UPPER_SNAKE)
+const defaultMax = 1000;
```
-## Functions and Methods
+## Method Patterns
-### Function Design
-- Keep functions small and focused on a single responsibility
-- Use pure functions when possible (no side effects)
-- Limit function parameters (prefer 3 or fewer)
+### Method Chaining
-```javascript
-// ✅ Good - single responsibility
-function isExpired(item, currentTime) {
- return item.expiry > 0 && item.expiry <= currentTime;
-}
+Methods that modify state return `this`:
-// ❌ Bad - multiple responsibilities
-function processItemAndUpdateCache(item, cache, currentTime) {
- if (item.expiry > 0 && item.expiry <= currentTime) {
- cache.delete(item.key);
- cache.stats.evictions++;
- return null;
- }
- cache.stats.hits++;
- return item.value;
+```javascript
+clear() {
+ this.size = 0;
+ return this;
}
```
-### Method Chaining
-- Return `this` from methods that modify state to enable chaining
-- Document chaining capability in JSDoc
+### Null Safety
+
+Always check for null/undefined:
```javascript
-/**
- * @returns {LRU} The LRU instance for method chaining.
- */
-set(key, value) {
- // implementation
+if (item.prev !== null) {
+ item.prev.next = item.next;
+}
+
+if (!item) {
return this;
}
```
-### Parameter Validation
-- Validate parameters at function entry
-- Throw appropriate errors for invalid inputs
-- Use meaningful error messages
+### Early Returns
+
+Use early returns to reduce nesting:
```javascript
-function lru(max = 1000, ttl = 0, resetTtl = false) {
- if (isNaN(max) || max < 0) {
- throw new TypeError("Invalid max value");
- }
-
- if (isNaN(ttl) || ttl < 0) {
- throw new TypeError("Invalid ttl value");
- }
-
- if (typeof resetTtl !== "boolean") {
- throw new TypeError("Invalid resetTtl value");
+get(key) {
+ const item = this.items[key];
+
+ if (item === undefined) {
+ return undefined;
}
-
- return new LRU(max, ttl, resetTtl);
+
+ // Main logic here
+ return item.value;
}
```
-## Classes
-
-### Class Structure
-- Order class members logically: constructor, public methods, private methods
-- Use consistent method ordering across similar classes
-- Initialize all properties in constructor
+## Class Structure
```javascript
export class LRU {
- /**
- * Constructor with full documentation
- */
+ // Constructor first
constructor(max = 0, ttl = 0, resetTtl = false) {
- // Initialize all properties
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -283,169 +163,26 @@ export class LRU {
this.ttl = ttl;
}
- // Public methods first
- get(key) { /* implementation */ }
-
- set(key, value) { /* implementation */ }
-
- // Private methods last (if any)
- _internalMethod() { /* implementation */ }
+ // Public methods
+ clear() { }
+ get(key) { }
+ set(key, value) { }
+
+ // Private methods at end (if any)
+ moveToEnd(item) { }
}
```
-### Property Access
-- Use public properties for API surface
-- Use private properties (starting with underscore) for internal state
-- Avoid getter/setter overhead unless necessary
-
## Error Handling
-### Error Types
-- Use built-in error types when appropriate (`TypeError`, `RangeError`, etc.)
-- Create custom error classes for domain-specific errors
-- Include helpful error messages
+Use TypeError with clear messages:
```javascript
-// ✅ Good
-if (typeof key !== 'string') {
- throw new TypeError(`Expected string key, got ${typeof key}`);
-}
-
-// ❌ Bad
-if (typeof key !== 'string') {
- throw new Error('Bad key');
+if (isNaN(max) || max < 0) {
+ throw new TypeError("Invalid max value");
}
```
-### Error Documentation
-- Document all errors that functions may throw
-- Include error conditions in JSDoc
-
-```javascript
-/**
- * @throws {TypeError} When key is not a string.
- * @throws {RangeError} When value exceeds maximum size.
- */
-```
-
-## Testing
-
-### Test Organization
-- **Unit tests** go in `tests/unit/` using node-assert and mocha
-- **Integration tests** go in `tests/integration/` using node-assert and mocha
-- Follow the same naming conventions as source files
-
-### Test Structure
-- Use descriptive test names that explain the scenario
-- Follow Arrange-Act-Assert pattern
-- Test both success and failure cases
-
-```javascript
-import assert from 'node:assert';
-import { describe, it } from 'mocha';
-import { LRU } from '../src/lru.js';
-
-describe('LRU Cache', () => {
- it('should return undefined for non-existent keys', () => {
- // Arrange
- const cache = new LRU(10);
-
- // Act
- const result = cache.get('nonexistent');
-
- // Assert
- assert.strictEqual(result, undefined);
- });
-
- it('should throw TypeError for invalid max value', () => {
- // Assert
- assert.throws(() => {
- new LRU(-1);
- }, TypeError, 'Invalid max value');
- });
-});
-```
-
-## Security
-
-### Input Validation
-- Validate all external inputs
-- Sanitize data before processing
-- Use parameterized queries for database operations
-
-### Memory Safety
-- Avoid memory leaks by properly cleaning up references
-- Be careful with circular references
-- Monitor memory usage in long-running operations
-
-### OWASP Guidelines
-- Follow OWASP security guidelines for all code
-- Avoid common vulnerabilities (injection, XSS, etc.)
-- Use secure coding practices
-
-## Performance
-
-### Algorithmic Efficiency
-- Choose appropriate data structures for the use case
-- Consider time and space complexity
-- Profile code to identify bottlenecks
-
-### Memory Management
-- Reuse objects when possible
-- Avoid unnecessary object creation in hot paths
-- Use `Object.create(null)` for hash maps without prototype pollution
-
-```javascript
-// ✅ Good - no prototype pollution
-this.items = Object.create(null);
-
-// ❌ Potentially problematic
-this.items = {};
-```
-
-### Micro-optimizations
-- Avoid premature optimization
-- Measure before optimizing
-- Focus on algorithmic improvements over micro-optimizations
-
-## File Organization
-
-### Project Structure
-```
-tiny-lru/
-├── src/ # Source code
-│ └── lru.js
-├── test/ # Test files
-│ ├── unit/ # Unit tests
-│ └── integration/ # Integration tests
-├── types/ # TypeScript definitions
-├── docs/ # Documentation
-├── benchmarks/ # Performance benchmarks
-└── dist/ # Built files
-```
-
-### Import/Export
-- Use ES6 modules (`import`/`export`)
-- Use named exports for utilities, default exports for main classes
-- Group imports logically
-
-```javascript
-// ✅ Good - grouped imports
-import assert from 'node:assert';
-import { describe, it } from 'mocha';
-
-import { LRU, lru } from '../src/lru.js';
-import { helper } from './test-utils.js';
-
-// ❌ Bad - mixed import order
-import { LRU } from '../src/lru.js';
-import assert from 'node:assert';
-import { helper } from './test-utils.js';
-import { describe, it } from 'mocha';
-```
-
-## Conclusion
-
-This style guide ensures consistency and quality across the tiny-lru codebase. When in doubt, refer to existing code patterns and prioritize readability and maintainability over cleverness.
+## ESLint Configuration
-For questions or suggestions about this style guide, please open an issue in the project repository.
\ No newline at end of file
+The project uses oxlint. Run `npm run lint` to check code style.
diff --git a/docs/TECHNICAL_DOCUMENTATION.md b/docs/TECHNICAL_DOCUMENTATION.md
index f60d026..2840241 100644
--- a/docs/TECHNICAL_DOCUMENTATION.md
+++ b/docs/TECHNICAL_DOCUMENTATION.md
@@ -56,7 +56,7 @@ graph TD
- `max`: Maximum cache size (0 = unlimited)
- `size`: Current number of items
- `ttl`: Time-to-live in milliseconds (0 = no expiration)
- - `resetTtl`: Whether to reset TTL on access
+ - `resetTtl`: Whether to reset TTL on `set()` operations (not on `get()`)
## Data Flow
@@ -133,7 +133,7 @@ sequenceDiagram
| `moveToEnd(item)` | O(1) | O(1) | O(1) | Internal: optimize LRU positioning |
| `keys()` | O(n) | O(n) | O(n) | Array of all keys in LRU order |
| `values(keys?)` | O(n) | O(n) | O(n) | Array of values for specified keys |
-| `entries(keys?)` | O(n) | O(n) | O(n) | Array of [key, value] pairs |
+| `entries([keys])` | O(n) | O(n) | O(n) | Array of [key, value] pairs |
### Memory Usage
@@ -183,20 +183,25 @@ last.next \leftarrow H[k] & \text{otherwise}
**Time Complexity:** $O(1)$ amortized
-#### Set With Evicted Operation: $setWithEvicted(k, v, resetTtl = resetTtl) \rightarrow \{key: K, value: V, expiry: \mathbb{N}_0, prev: Object, next: Object\} \cup \{\bot\}$
+#### Set With Evicted Operation: $setWithEvicted(k, v, resetTtl = resetTtl) \rightarrow \{key: K, value: V, expiry: \mathbb{N}_0\} \cup \{\bot\}$
$$\begin{align}
setWithEvicted(k, v, resetTtl) &= \begin{cases}
-set(k, v, true, resetTtl) \land \bot & \text{if } k \in H \\
+update(k, v, resetTtl) \land \bot & \text{if } k \in H \\
evicted \land create(k, v) & \text{if } k \notin H \land max > 0 \land size = max \\
\bot \land create(k, v) & \text{if } k \notin H \land (max = 0 \lor size < max)
\end{cases} \\
+update(k, v, resetTtl) &= H[k].value \leftarrow v \land moveToEnd(H[k]) \\
+& \quad \land \begin{cases}
+H[k].expiry \leftarrow t_{now} + ttl & \text{if } resetTtl = true \land ttl > 0 \\
+\text{no-op} & \text{otherwise}
+\end{cases} \\
\text{where } evicted &= \begin{cases}
-\{...this.first\} & \text{if } size > 0 \\
+\{key: this.first.key, value: this.first.value, expiry: this.first.expiry\} & \text{if } size > 0 \\
\bot & \text{otherwise}
\end{cases}
\end{align}$$
-**Note:** `setWithEvicted()` always calls `set()` with `bypass = true`, which means TTL is never reset during `setWithEvicted()` operations, regardless of the `resetTtl` parameter.
+**Note:** Unlike `set()`, `setWithEvicted()` does not use a `bypass` parameter, so TTL is reset when `resetTtl = true`.
**Time Complexity:** $O(1)$ amortized
@@ -217,6 +222,12 @@ $$\begin{align}
delete(k) &= \begin{cases}
removeFromList(H[k]) \land H \setminus \{k\} \land size \leftarrow size - 1 & \text{if } k \in H \\
\text{no-op} & \text{otherwise}
+\end{cases} \\
+removeFromList(item) &= \begin{cases}
+item.prev.next \leftarrow item.next \land item.next.prev \leftarrow item.prev \land first \leftarrow item.next \land last \leftarrow item.prev & \text{if } item.prev \neq null \land item.next \neq null \\
+item.prev.next \leftarrow item.next \land first \leftarrow item.next \land last \leftarrow null & \text{if } item.prev \neq null \land item.next = null \\
+item.next.prev \leftarrow item.prev \land first \leftarrow item.next \land last \leftarrow null & \text{if } item.prev = null \land item.next \neq null \\
+first \leftarrow null \land last \leftarrow null & \text{if } item.prev = null \land item.next = null
\end{cases}
\end{align}$$
@@ -226,10 +237,12 @@ removeFromList(H[k]) \land H \setminus \{k\} \land size \leftarrow size - 1 & \t
$$\begin{align}
moveToEnd(item) &= \begin{cases}
\text{no-op} & \text{if } item = last \\
-removeFromList(item) \land appendToList(item) & \text{otherwise}
+item.prev.next \leftarrow item.next \land item.next.prev \leftarrow item.prev \land first \leftarrow item.next \land item.prev \leftarrow last \land last.next \leftarrow item \land last \leftarrow item & \text{if } item \neq last
\end{cases}
\end{align}$$
+**Edge Case:** When item is the only node in the list ($item.prev = null \land item.next = null$), the condition $item = last$ is true since $first = last = item$, so the operation is a no-op.
+
**Time Complexity:** $O(1)$
### Eviction Policy
@@ -257,7 +270,7 @@ delete(k) & \text{if } isExpired(k) \\
**TTL Reset Behavior:**
- TTL is only reset during `set()` operations when `resetTtl = true` and `bypass = false`
- `get()` operations never reset TTL, regardless of the `resetTtl` setting
-- `setWithEvicted()` operations never reset TTL because they always call `set()` with `bypass = true`
+- `setWithEvicted()` operations reset TTL when `resetTtl = true` (does not use bypass parameter)
### Space Complexity
@@ -272,8 +285,8 @@ delete(k) & \text{if } isExpired(k) \\
2. **List Consistency:** $first \neq null \iff last \neq null \iff size > 0$
3. **Hash Consistency:** $|H| = size$
4. **LRU Order:** Items in list are ordered from least to most recently used
-5. **TTL Validity:** $ttl = 0 \lor \forall k \in H: H[k].expiry > t_{now}$
-6. **TTL Reset Invariant:** TTL is only reset during `set()` operations when `bypass = false`, never during `get()` or `setWithEvicted()` operations
+5. **TTL Validity:** $(ttl = 0 \Rightarrow \forall k \in H: H[k].expiry = 0) \land (ttl > 0 \Rightarrow \forall k \in H: H[k].expiry \geq t_{now})$
+6. **TTL Reset Invariant:** TTL is only reset during `set()` operations when `bypass = false`, and during `setWithEvicted()` operations when `resetTtl = true`
## TypeScript Support
@@ -308,7 +321,7 @@ export class LRU {
has(key: any): boolean;
keys(): any[];
set(key: any, value: T, bypass?: boolean, resetTtl?: boolean): this;
- setWithEvicted(key: any, value: T, resetTtl?: boolean): LRUItem | null;
+ setWithEvicted(key: any, value: T, resetTtl?: boolean): { key: any; value: T; expiry: number } | null;
values(keys?: any[]): T[];
}
diff --git a/eslint.config.js b/eslint.config.js
deleted file mode 100644
index 6de2781..0000000
--- a/eslint.config.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import globals from "globals";
-import pluginJs from "@eslint/js";
-
-export default [
- {
- languageOptions: {
- globals: {
- ...globals.node,
- it: true,
- describe: true,
- beforeEach: true
- },
- parserOptions: {
- ecmaVersion: 2022
- }
- },
- rules: {
- "arrow-parens": [2, "as-needed"],
- "arrow-spacing": [2, {"before": true, "after": true}],
- "block-scoped-var": [0],
- "brace-style": [2, "1tbs", {"allowSingleLine": true}],
- "camelcase": [0],
- "comma-dangle": [2, "never"],
- "comma-spacing": [2],
- "comma-style": [2, "last"],
- "complexity": [0, 11],
- "consistent-return": [2],
- "consistent-this": [0, "that"],
- "curly": [2, "multi-line"],
- "default-case": [2],
- "dot-notation": [2, {"allowKeywords": true}],
- "eol-last": [2],
- "eqeqeq": [2],
- "func-names": [0],
- "func-style": [0, "declaration"],
- "generator-star-spacing": [2, "after"],
- "guard-for-in": [0],
- "handle-callback-err": [0],
- "indent": ["error", "tab", {"VariableDeclarator": {"var": 1, "let": 1, "const": 1}, "SwitchCase": 1}],
- "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
- "quotes": [2, "double", "avoid-escape"],
- "max-depth": [0, 4],
- "max-len": [0, 80, 4],
- "max-nested-callbacks": [0, 2],
- "max-params": [0, 3],
- "max-statements": [0, 10],
- "new-parens": [2],
- "new-cap": [2, {"capIsNewExceptions": ["ToInteger", "ToObject", "ToPrimitive", "ToUint32"]}],
- "newline-after-var": [0],
- "newline-before-return": [2],
- "no-alert": [2],
- "no-array-constructor": [2],
- "no-bitwise": [0],
- "no-caller": [2],
- "no-catch-shadow": [2],
- "no-cond-assign": [2],
- "no-console": [0],
- "no-constant-condition": [1],
- "no-continue": [2],
- "no-control-regex": [2],
- "no-debugger": [2],
- "no-delete-var": [2],
- "no-div-regex": [0],
- "no-dupe-args": [2],
- "no-dupe-keys": [2],
- "no-duplicate-case": [2],
- "no-else-return": [0],
- "no-empty": [2],
- "no-eq-null": [0],
- "no-eval": [2],
- "no-ex-assign": [2],
- "no-extend-native": [1],
- "no-extra-bind": [2],
- "no-extra-boolean-cast": [2],
- "no-extra-semi": [1],
- "no-empty-character-class": [2],
- "no-fallthrough": [2],
- "no-floating-decimal": [2],
- "no-func-assign": [2],
- "no-implied-eval": [2],
- "no-inline-comments": [0],
- "no-inner-declarations": [2, "functions"],
- "no-invalid-regexp": [2],
- "no-irregular-whitespace": [2],
- "no-iterator": [2],
- "no-label-var": [2],
- "no-labels": [2],
- "no-lone-blocks": [2],
- "no-lonely-if": [2],
- "no-loop-func": [2],
- "no-mixed-requires": [0, false],
- "no-mixed-spaces-and-tabs": [2, false],
- "no-multi-spaces": [2],
- "no-multi-str": [2],
- "no-multiple-empty-lines": [2, {"max": 2}],
- "no-native-reassign": [0],
- "no-negated-in-lhs": [2],
- "no-nested-ternary": [0],
- "no-new": [2],
- "no-new-func": [0],
- "no-new-object": [2],
- "no-new-require": [0],
- "no-new-wrappers": [2],
- "no-obj-calls": [2],
- "no-octal": [2],
- "no-octal-escape": [2],
- "no-param-reassign": [0],
- "no-path-concat": [0],
- "no-plusplus": [0],
- "no-process-env": [0],
- "no-process-exit": [0],
- "no-proto": [2],
- "no-redeclare": [2],
- "no-regex-spaces": [2],
- "no-reserved-keys": [0],
- "no-reno-new-funced-modules": [0],
- "no-return-assign": [2],
- "no-script-url": [2],
- "no-self-compare": [0],
- "no-sequences": [2],
- "no-shadow": [2],
- "no-shadow-restricted-names": [2],
- "no-spaced-func": [2],
- "no-sparse-arrays": [2],
- "no-sync": [0],
- "no-ternary": [0],
- "no-throw-literal": [2],
- "no-trailing-spaces": [2],
- "no-undef": [2],
- "no-undef-init": [2],
- "no-undefined": [0],
- "no-underscore-dangle": [0],
- "no-unreachable": [2],
- "no-unused-expressions": [2],
- "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
- "no-use-before-define": [2],
- "no-void": [0],
- "no-warning-comments": [0, {"terms": ["todo", "fixme", "xxx"], "location": "start"}],
- "no-with": [2],
- "no-extra-parens": [2],
- "one-var": [0],
- "operator-assignment": [0, "always"],
- "operator-linebreak": [2, "after"],
- "padded-blocks": [0],
- "quote-props": [0],
- "radix": [0],
- "semi": [2],
- "semi-spacing": [2, {before: false, after: true}],
- "sort-vars": [0],
- "keyword-spacing": [2],
- "space-before-function-paren": [2, {anonymous: "always", named: "always"}],
- "space-before-blocks": [2, "always"],
- "space-in-brackets": [0, "never", {
- singleValue: true,
- arraysInArrays: false,
- arraysInObjects: false,
- objectsInArrays: true,
- objectsInObjects: true,
- propertyName: false
- }],
- "space-in-parens": [2, "never"],
- "space-infix-ops": [2],
- "space-unary-ops": [2, {words: true, nonwords: false}],
- "spaced-line-comment": [0, "always"],
- strict: [0],
- "use-isnan": [2],
- "valid-jsdoc": [0],
- "valid-typeof": [2],
- "vars-on-top": [0],
- "wrap-iife": [2],
- "wrap-regex": [2],
- yoda: [2, "never", {exceptRange: true}]
- }
- },
- pluginJs.configs.recommended
-];
diff --git a/package-lock.json b/package-lock.json
index abc1572..0af45fd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,10 +11,9 @@
"devDependencies": {
"@rollup/plugin-terser": "^1.0.0",
"auto-changelog": "^2.5.0",
- "c8": "^11.0.0",
- "eslint": "^9.29.0",
"husky": "^9.1.7",
- "mocha": "^11.7.0",
+ "oxfmt": "^0.41.0",
+ "oxlint": "^1.56.0",
"rollup": "^4.43.0",
"tinybench": "^6.0.0"
},
@@ -22,328 +21,6 @@
"node": ">=12"
}
},
- "node_modules/@aashutoshrathi/word-wrap": {
- "version": "1.2.6",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/@bcoe/v8-coverage": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
- "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
- "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
- "dev": true,
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.7",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/config-helpers": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
- "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
- "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
- "dev": true,
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
- "dev": true,
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
- "dev": true,
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
- "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
@@ -399,54 +76,10 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@rollup/plugin-terser": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz",
- "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "serialize-javascript": "^7.0.3",
- "smob": "^1.0.0",
- "terser": "^5.17.4"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "rollup": "^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
- "node_modules/@rollup/plugin-terser/node_modules/serialize-javascript": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.4.tgz",
- "integrity": "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
- "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "node_modules/@oxfmt/binding-android-arm-eabi": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.41.0.tgz",
+ "integrity": "sha512-REfrqeMKGkfMP+m/ScX4f5jJBSmVNYcpoDF8vP8f8eYPDuPGZmzp56NIUsYmx3h7f6NzC6cE3gqh8GDWrJHCKw==",
"cpu": [
"arm"
],
@@ -455,12 +88,15 @@
"optional": true,
"os": [
"android"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
- "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "node_modules/@oxfmt/binding-android-arm64": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.41.0.tgz",
+ "integrity": "sha512-s0b1dxNgb2KomspFV2LfogC2XtSJB42POXF4bMCLJyvQmAGos4ZtjGPfQreToQEaY0FQFjz3030ggI36rF1q5g==",
"cpu": [
"arm64"
],
@@ -469,12 +105,15 @@
"optional": true,
"os": [
"android"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
- "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "node_modules/@oxfmt/binding-darwin-arm64": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.41.0.tgz",
+ "integrity": "sha512-EGXGualADbv/ZmamE7/2DbsrYmjoPlAmHEpTL4vapLF4EfVD6fr8/uQDFnPJkUBjiSWFJZtFNsGeN1B6V3owmA==",
"cpu": [
"arm64"
],
@@ -483,12 +122,15 @@
"optional": true,
"os": [
"darwin"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
- "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "node_modules/@oxfmt/binding-darwin-x64": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.41.0.tgz",
+ "integrity": "sha512-WxySJEvdQQYMmyvISH3qDpTvoS0ebnIP63IMxLLWowJyPp/AAH0hdWtlo+iGNK5y3eVfa5jZguwNaQkDKWpGSw==",
"cpu": [
"x64"
],
@@ -497,40 +139,49 @@
"optional": true,
"os": [
"darwin"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
- "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "node_modules/@oxfmt/binding-freebsd-x64": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.41.0.tgz",
+ "integrity": "sha512-Y2kzMkv3U3oyuYaR4wTfGjOTYTXiFC/hXmG0yVASKkbh02BJkvD98Ij8bIevr45hNZ0DmZEgqiXF+9buD4yMYQ==",
"cpu": [
- "arm64"
+ "x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
- "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "node_modules/@oxfmt/binding-linux-arm-gnueabihf": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.41.0.tgz",
+ "integrity": "sha512-ptazDjdUyhket01IjPTT6ULS1KFuBfTUU97osTP96X5y/0oso+AgAaJzuH81oP0+XXyrWIHbRzozSAuQm4p48g==",
"cpu": [
- "x64"
+ "arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "freebsd"
- ]
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
- "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "node_modules/@oxfmt/binding-linux-arm-musleabihf": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.41.0.tgz",
+ "integrity": "sha512-UkoL2OKxFD+56bPEBcdGn+4juTW4HRv/T6w1dIDLnvKKWr6DbarB/mtHXlADKlFiJubJz8pRkttOR7qjYR6lTA==",
"cpu": [
"arm"
],
@@ -539,166 +190,226 @@
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
- "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "node_modules/@oxfmt/binding-linux-arm64-gnu": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.41.0.tgz",
+ "integrity": "sha512-gofu0PuumSOHYczD8p62CPY4UF6ee+rSLZJdUXkpwxg6pILiwSDBIouPskjF/5nF3A7QZTz2O9KFNkNxxFN9tA==",
"cpu": [
- "arm"
+ "arm64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
- "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "node_modules/@oxfmt/binding-linux-arm64-musl": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.41.0.tgz",
+ "integrity": "sha512-VfVZxL0+6RU86T8F8vKiDBa+iHsr8PAjQmKGBzSCAX70b6x+UOMFl+2dNihmKmUwqkCazCPfYjt6SuAPOeQJ3g==",
"cpu": [
"arm64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
- "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "node_modules/@oxfmt/binding-linux-ppc64-gnu": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.41.0.tgz",
+ "integrity": "sha512-bwzokz2eGvdfJbc0i+zXMJ4BBjQPqg13jyWpEEZDOrBCQ91r8KeY2Mi2kUeuMTZNFXju+jcAbAbpyJxRGla0eg==",
"cpu": [
- "arm64"
+ "ppc64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
- "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "node_modules/@oxfmt/binding-linux-riscv64-gnu": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.41.0.tgz",
+ "integrity": "sha512-POLM//PCH9uqDeNDwWL3b3DkMmI3oI2cU6hwc2lnztD1o7dzrQs3R9nq555BZ6wI7t2lyhT9CS+CRaz5X0XqLA==",
"cpu": [
- "loong64"
+ "riscv64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
- "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "node_modules/@oxfmt/binding-linux-riscv64-musl": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.41.0.tgz",
+ "integrity": "sha512-NNK7PzhFqLUwx/G12Xtm6scGv7UITvyGdAR5Y+TlqsG+essnuRWR4jRNODWRjzLZod0T3SayRbnkSIWMBov33w==",
"cpu": [
- "loong64"
+ "riscv64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
- "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "node_modules/@oxfmt/binding-linux-s390x-gnu": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.41.0.tgz",
+ "integrity": "sha512-qVf/zDC5cN9eKe4qI/O/m445er1IRl6swsSl7jHkqmOSVfknwCe5JXitYjZca+V/cNJSU/xPlC5EFMabMMFDpw==",
"cpu": [
- "ppc64"
+ "s390x"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
- "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "node_modules/@oxfmt/binding-linux-x64-gnu": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.41.0.tgz",
+ "integrity": "sha512-ojxYWu7vUb6ysYqVCPHuAPVZHAI40gfZ0PDtZAMwVmh2f0V8ExpPIKoAKr7/8sNbAXJBBpZhs2coypIo2jJX4w==",
"cpu": [
- "ppc64"
+ "x64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
- "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "node_modules/@oxfmt/binding-linux-x64-musl": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.41.0.tgz",
+ "integrity": "sha512-O2exZLBxoCMIv2vlvcbkdedazJPTdG0VSup+0QUCfYQtx751zCZNboX2ZUOiQ/gDTdhtXvSiot0h6GEGkOyalA==",
"cpu": [
- "riscv64"
+ "x64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
- "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "node_modules/@oxfmt/binding-openharmony-arm64": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.41.0.tgz",
+ "integrity": "sha512-N+31/VoL+z+NNBt8viy3I4NaIdPbiYeOnB884LKqvXldaE2dRztdPv3q5ipfZYv0RwFp7JfqS4I27K/DSHCakg==",
"cpu": [
- "riscv64"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
- "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "node_modules/@oxfmt/binding-win32-arm64-msvc": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.41.0.tgz",
+ "integrity": "sha512-Z7NAtu/RN8kjCQ1y5oDD0nTAeRswh3GJ93qwcW51srmidP7XPBmZbLlwERu1W5veCevQJtPS9xmkpcDTYsGIwQ==",
"cpu": [
- "s390x"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "node_modules/@oxfmt/binding-win32-ia32-msvc": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.41.0.tgz",
+ "integrity": "sha512-uNxxP3l4bJ6VyzIeRqCmBU2Q0SkCFgIhvx9/9dJ9V8t/v+jP1IBsuaLwCXGR8JPHtkj4tFp+RHtUmU2ZYAUpMA==",
"cpu": [
- "x64"
+ "ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
- "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "node_modules/@oxfmt/binding-win32-x64-msvc": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.41.0.tgz",
+ "integrity": "sha512-49ZSpbZ1noozyPapE8SUOSm3IN0Ze4b5nkO+4+7fq6oEYQQJFhE0saj5k/Gg4oewVPdjn0L3ZFeWk2Vehjcw7A==",
"cpu": [
"x64"
],
@@ -706,27 +417,33 @@
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
- "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "node_modules/@oxlint/binding-android-arm-eabi": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.56.0.tgz",
+ "integrity": "sha512-IyfYPthZyiSKwAv/dLjeO18SaK8MxLI9Yss2JrRDyweQAkuL3LhEy7pwIwI7uA3KQc1Vdn20kdmj3q0oUIQL6A==",
"cpu": [
- "x64"
+ "arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "openbsd"
- ]
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
- "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "node_modules/@oxlint/binding-android-arm64": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.56.0.tgz",
+ "integrity": "sha512-Ga5zYrzH6vc/VFxhn6MmyUnYEfy9vRpwTIks99mY3j6Nz30yYpIkWryI0QKPCgvGUtDSXVLEaMum5nA+WrNOSg==",
"cpu": [
"arm64"
],
@@ -734,13 +451,16 @@
"license": "MIT",
"optional": true,
"os": [
- "openharmony"
- ]
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
- "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "node_modules/@oxlint/binding-darwin-arm64": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.56.0.tgz",
+ "integrity": "sha512-ogmbdJysnw/D4bDcpf1sPLpFThZ48lYp4aKYm10Z/6Nh1SON6NtnNhTNOlhEY296tDFItsZUz+2tgcSYqh8Eyw==",
"cpu": [
"arm64"
],
@@ -748,27 +468,33 @@
"license": "MIT",
"optional": true,
"os": [
- "win32"
- ]
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
- "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "node_modules/@oxlint/binding-darwin-x64": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.56.0.tgz",
+ "integrity": "sha512-x8QE1h+RAtQ2g+3KPsP6Fk/tdz6zJQUv5c7fTrJxXV3GHOo+Ry5p/PsogU4U+iUZg0rj6hS+E4xi+mnwwlDCWQ==",
"cpu": [
- "ia32"
+ "x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
- ]
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "node_modules/@oxlint/binding-freebsd-x64": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.56.0.tgz",
+ "integrity": "sha512-6G+WMZvwJpMvY7my+/SHEjb7BTk/PFbePqLpmVmUJRIsJMy/UlyYqjpuh0RCgYYkPLcnXm1rUM04kbTk8yS1Yg==",
"cpu": [
"x64"
],
@@ -776,1192 +502,834 @@
"license": "MIT",
"optional": true,
"os": [
- "win32"
- ]
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
- "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "node_modules/@oxlint/binding-linux-arm-gnueabihf": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.56.0.tgz",
+ "integrity": "sha512-YYHBsk/sl7fYwQOok+6W5lBPeUEvisznV/HZD2IfZmF3Bns6cPC3Z0vCtSEOaAWTjYWN3jVsdu55jMxKlsdlhg==",
"cpu": [
- "x64"
+ "arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
- ]
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
- "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
+ "linux"
+ ],
"engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "node_modules/@oxlint/binding-linux-arm-musleabihf": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.56.0.tgz",
+ "integrity": "sha512-+AZK8rOUr78y8WT6XkDb04IbMRqauNV+vgT6f8ZLOH8wnpQ9i7Nol0XLxAu+Cq7Sb+J9wC0j6Km5hG8rj47/yQ==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/ansi-regex": {
- "version": "5.0.1",
+ "node_modules/@oxlint/binding-linux-arm64-gnu": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.56.0.tgz",
+ "integrity": "sha512-urse2SnugwJRojUkGSSeH2LPMaje5Q50yQtvtL9HFckiyeqXzoFwOAZqD5TR29R2lq7UHidfFDM9EGcchcbb8A==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=8"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/ansi-styles": {
- "version": "4.3.0",
+ "node_modules/@oxlint/binding-linux-arm64-musl": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.56.0.tgz",
+ "integrity": "sha512-rkTZkBfJ4TYLjansjSzL6mgZOdN5IvUnSq3oNJSLwBcNvy3dlgQtpHPrRxrCEbbcp7oQ6If0tkNaqfOsphYZ9g==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/argparse": {
- "version": "2.0.1",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/auto-changelog": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/auto-changelog/-/auto-changelog-2.5.0.tgz",
- "integrity": "sha512-UTnLjT7I9U2U/xkCUH5buDlp8C7g0SGChfib+iDrJkamcj5kaMqNKHNfbKJw1kthJUq8sUo3i3q2S6FzO/l/wA==",
+ "node_modules/@oxlint/binding-linux-ppc64-gnu": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.56.0.tgz",
+ "integrity": "sha512-uqL1kMH3u69/e1CH2EJhP3CP28jw2ExLsku4o8RVAZ7fySo9zOyI2fy9pVlTAp4voBLVgzndXi3SgtdyCTa2aA==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
- "dependencies": {
- "commander": "^7.2.0",
- "handlebars": "^4.7.7",
- "import-cwd": "^3.0.0",
- "node-fetch": "^2.6.1",
- "parse-github-url": "^1.0.3",
- "semver": "^7.3.5"
- },
- "bin": {
- "auto-changelog": "src/index.js"
- },
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=8.3"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "node_modules/@oxlint/binding-linux-riscv64-gnu": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.56.0.tgz",
+ "integrity": "sha512-j0CcMBOgV6KsRaBdsebIeiy7hCjEvq2KdEsiULf2LZqAq0v1M1lWjelhCV57LxsqaIGChXFuFJ0RiFrSRHPhSg==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/browser-stdout": {
- "version": "1.3.1",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/c8": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz",
- "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==",
+ "node_modules/@oxlint/binding-linux-riscv64-musl": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.56.0.tgz",
+ "integrity": "sha512-7VDOiL8cDG3DQ/CY3yKjbV1c4YPvc4vH8qW09Vv+5ukq3l/Kcyr6XGCd5NvxUmxqDb2vjMpM+eW/4JrEEsUetA==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "@bcoe/v8-coverage": "^1.0.1",
- "@istanbuljs/schema": "^0.1.3",
- "find-up": "^5.0.0",
- "foreground-child": "^3.1.1",
- "istanbul-lib-coverage": "^3.2.0",
- "istanbul-lib-report": "^3.0.1",
- "istanbul-reports": "^3.1.6",
- "test-exclude": "^8.0.0",
- "v8-to-istanbul": "^9.0.0",
- "yargs": "^17.7.2",
- "yargs-parser": "^21.1.1"
- },
- "bin": {
- "c8": "bin/c8.js"
- },
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "20 || >=22"
- },
- "peerDependencies": {
- "monocart-coverage-reports": "^2"
- },
- "peerDependenciesMeta": {
- "monocart-coverage-reports": {
- "optional": true
- }
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "node_modules/@oxlint/binding-linux-s390x-gnu": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.56.0.tgz",
+ "integrity": "sha512-JGRpX0M+ikD3WpwJ7vKcHKV6Kg0dT52BW2Eu2BupXotYeqGXBrbY+QPkAyKO6MNgKozyTNaRh3r7g+VWgyAQYQ==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/chalk": {
- "version": "4.1.2",
+ "node_modules/@oxlint/binding-linux-x64-gnu": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.56.0.tgz",
+ "integrity": "sha512-dNaICPvtmuxFP/VbqdofrLqdS3bM/AKJN3LMJD52si44ea7Be1cBk6NpfIahaysG9Uo+L98QKddU9CD5L8UHnQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "node_modules/@oxlint/binding-linux-x64-musl": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.56.0.tgz",
+ "integrity": "sha512-pF1vOtM+GuXmbklM1hV8WMsn6tCNPvkUzklj/Ej98JhlanbmA2RB1BILgOpwSuCTRTIYx2MXssmEyQQ90QF5aA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "dependencies": {
- "readdirp": "^4.0.1"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "node_modules/@oxlint/binding-openharmony-arm64": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.56.0.tgz",
+ "integrity": "sha512-bp8NQ4RE6fDIFLa4bdBiOA+TAvkNkg+rslR+AvvjlLTYXLy9/uKAYLQudaQouWihLD/hgkrXIKKzXi5IXOewwg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
"engines": {
- "node": ">=12"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/color-convert": {
- "version": "2.0.1",
+ "node_modules/@oxlint/binding-win32-arm64-msvc": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.56.0.tgz",
+ "integrity": "sha512-PxT4OJDfMOQBzo3OlzFb9gkoSD+n8qSBxyVq2wQSZIHFQYGEqIRTo9M0ZStvZm5fdhMqaVYpOnJvH2hUMEDk/g==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=7.0.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/color-name": {
- "version": "1.1.4",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/commander": {
- "version": "7.2.0",
+ "node_modules/@oxlint/binding-win32-ia32-msvc": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.56.0.tgz",
+ "integrity": "sha512-PTRy6sIEPqy2x8PTP1baBNReN/BNEFmde0L+mYeHmjXE1Vlcc9+I5nsqENsB2yAm5wLkzPoTNCMY/7AnabT4/A==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">= 10"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "node_modules/@oxlint/binding-win32-x64-msvc": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.56.0.tgz",
+ "integrity": "sha512-ZHa0clocjLmIDr+1LwoWtxRcoYniAvERotvwKUYKhH41NVfl0Y4LNbyQkwMZzwDvKklKGvGZ5+DAG58/Ik47tQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">= 8"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "node_modules/@rollup/plugin-terser": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz",
+ "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ms": "^2.1.3"
+ "serialize-javascript": "^7.0.3",
+ "smob": "^1.0.0",
+ "terser": "^5.17.4"
},
"engines": {
- "node": ">=6.0"
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
- "supports-color": {
+ "rollup": {
"optional": true
}
}
},
- "node_modules/deep-is": {
- "version": "0.1.4",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/diff": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
- "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
+ "node_modules/@rollup/plugin-terser/node_modules/serialize-javascript": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.4.tgz",
+ "integrity": "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
- "node": ">=0.3.1"
+ "node": ">=20.0.0"
}
},
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
},
- "node_modules/emoji-regex": {
- "version": "8.0.0",
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
},
- "node_modules/escalade": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
- "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "engines": {
- "node": ">=6"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
},
- "node_modules/eslint": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
- "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
- "@eslint/config-helpers": "^0.4.2",
- "@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.2",
- "@eslint/plugin-kit": "^0.4.1",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esquery": {
- "version": "1.5.0",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
},
- "node_modules/find-up": {
- "version": "5.0.0",
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat": {
- "version": "5.0.2",
- "dev": true,
- "license": "BSD-3-Clause",
- "bin": {
- "flat": "cli.js"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
- "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
- "dev": true
- },
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
"optional": true,
"os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
+ "freebsd"
+ ]
},
- "node_modules/glob/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/glob/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/handlebars": {
- "version": "4.7.8",
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "minimist": "^1.2.5",
- "neo-async": "^2.6.2",
- "source-map": "^0.6.1",
- "wordwrap": "^1.0.0"
- },
- "bin": {
- "handlebars": "bin/handlebars"
- },
- "engines": {
- "node": ">=0.4.7"
- },
- "optionalDependencies": {
- "uglify-js": "^3.1.4"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/has-flag": {
- "version": "4.0.0",
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "engines": {
- "node": ">=8"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/he": {
- "version": "1.2.0",
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "cpu": [
+ "loong64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "bin": {
- "he": "bin/he"
- }
- },
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/husky": {
- "version": "9.1.7",
- "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
- "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
- "dev": true,
- "bin": {
- "husky": "bin.js"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/typicode"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "cpu": [
+ "loong64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-cwd": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
- "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==",
- "dev": true,
- "dependencies": {
- "import-from": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/import-from": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz",
- "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==",
- "dev": true,
- "dependencies": {
- "resolve-from": "^5.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/import-from/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/imurmurhash": {
- "version": "0.1.4",
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/is-extglob": {
- "version": "2.1.1",
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "engines": {
- "node": ">=8"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/is-glob": {
- "version": "4.0.3",
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=8"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/is-plain-obj": {
- "version": "2.1.0",
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=8"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/is-unicode-supported": {
- "version": "0.1.0",
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=8"
- }
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
},
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
},
- "node_modules/istanbul-reports": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
- "node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
+ "bin": {
+ "acorn": "bin/acorn"
},
"engines": {
- "node": ">= 0.8.0"
+ "node": ">=0.4.0"
}
},
- "node_modules/locate-path": {
- "version": "6.0.0",
+ "node_modules/auto-changelog": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/auto-changelog/-/auto-changelog-2.5.0.tgz",
+ "integrity": "sha512-UTnLjT7I9U2U/xkCUH5buDlp8C7g0SGChfib+iDrJkamcj5kaMqNKHNfbKJw1kthJUq8sUo3i3q2S6FzO/l/wA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "p-locate": "^5.0.0"
+ "commander": "^7.2.0",
+ "handlebars": "^4.7.7",
+ "import-cwd": "^3.0.0",
+ "node-fetch": "^2.6.1",
+ "parse-github-url": "^1.0.3",
+ "semver": "^7.3.5"
},
- "engines": {
- "node": ">=10"
+ "bin": {
+ "auto-changelog": "src/index.js"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">=8.3"
}
},
- "node_modules/lodash.merge": {
- "version": "4.6.2",
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
"dev": true,
"license": "MIT"
},
- "node_modules/log-symbols": {
- "version": "4.1.0",
+ "node_modules/commander": {
+ "version": "7.2.0",
"dev": true,
"license": "MIT",
- "dependencies": {
- "chalk": "^4.1.0",
- "is-unicode-supported": "^0.1.0"
- },
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">= 10"
}
},
- "node_modules/lru-cache": {
- "version": "11.2.6",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
- "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
- "license": "BlueOak-1.0.0",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "20 || >=22"
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/make-dir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "node_modules/handlebars": {
+ "version": "4.7.8",
"dev": true,
"license": "MIT",
"dependencies": {
- "semver": "^7.5.3"
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
},
"engines": {
- "node": ">=10"
+ "node": ">=0.4.7"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
}
},
- "node_modules/minimatch": {
- "version": "3.1.2",
+ "node_modules/husky": {
+ "version": "9.1.7",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
+ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
+ "bin": {
+ "husky": "bin.js"
},
"engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "dev": true,
- "license": "MIT",
+ "node": ">=18"
+ },
"funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
- "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=16 || 14 >=14.17"
+ "url": "https://github.com/sponsors/typicode"
}
},
- "node_modules/mocha": {
- "version": "11.7.5",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz",
- "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==",
+ "node_modules/import-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
+ "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "browser-stdout": "^1.3.1",
- "chokidar": "^4.0.1",
- "debug": "^4.3.5",
- "diff": "^7.0.0",
- "escape-string-regexp": "^4.0.0",
- "find-up": "^5.0.0",
- "glob": "^10.4.5",
- "he": "^1.2.0",
- "is-path-inside": "^3.0.3",
- "js-yaml": "^4.1.0",
- "log-symbols": "^4.1.0",
- "minimatch": "^9.0.5",
- "ms": "^2.1.3",
- "picocolors": "^1.1.1",
- "serialize-javascript": "^6.0.2",
- "strip-json-comments": "^3.1.1",
- "supports-color": "^8.1.1",
- "workerpool": "^9.2.0",
- "yargs": "^17.7.2",
- "yargs-parser": "^21.1.1",
- "yargs-unparser": "^2.0.0"
- },
- "bin": {
- "_mocha": "bin/_mocha",
- "mocha": "bin/mocha.js"
+ "import-from": "^3.0.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=8"
}
},
- "node_modules/mocha/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "node_modules/import-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz",
+ "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/mocha/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "node_modules/import-from/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
"engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=8"
}
},
- "node_modules/mocha/node_modules/supports-color": {
- "version": "8.1.1",
+ "node_modules/minimist": {
+ "version": "1.2.8",
"dev": true,
"license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
"funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/neo-async": {
"version": "2.6.2",
"dev": true,
@@ -1986,185 +1354,101 @@
}
}
},
- "node_modules/optionator": {
- "version": "0.9.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@aashutoshrathi/word-wrap": "^1.2.3",
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "node_modules/oxfmt": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.41.0.tgz",
+ "integrity": "sha512-sKLdJZdQ3bw6x9qKiT7+eID4MNEXlDHf5ZacfIircrq6Qwjk0L6t2/JQlZZrVHTXJawK3KaMuBoJnEJPcqCEdg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "callsites": "^3.0.0"
+ "tinypool": "2.1.0"
},
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/parse-github-url": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.3.tgz",
- "integrity": "sha512-tfalY5/4SqGaV/GIGzWyHnFjlpTPTNpENR9Ea2lLldSJ8EWXMsvacWucqY3m3I4YPtas15IxTLQVQ5NSYXPrww==",
- "dev": true,
"bin": {
- "parse-github-url": "cli.js"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ "oxfmt": "bin/oxfmt"
},
"engines": {
- "node": ">=16 || 14 >=14.18"
+ "node": "^20.19.0 || >=22.12.0"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/path-scurry/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/randombytes": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "^5.1.0"
- }
- },
- "node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.18.0"
+ "url": "https://github.com/sponsors/Boshen"
},
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
+ "optionalDependencies": {
+ "@oxfmt/binding-android-arm-eabi": "0.41.0",
+ "@oxfmt/binding-android-arm64": "0.41.0",
+ "@oxfmt/binding-darwin-arm64": "0.41.0",
+ "@oxfmt/binding-darwin-x64": "0.41.0",
+ "@oxfmt/binding-freebsd-x64": "0.41.0",
+ "@oxfmt/binding-linux-arm-gnueabihf": "0.41.0",
+ "@oxfmt/binding-linux-arm-musleabihf": "0.41.0",
+ "@oxfmt/binding-linux-arm64-gnu": "0.41.0",
+ "@oxfmt/binding-linux-arm64-musl": "0.41.0",
+ "@oxfmt/binding-linux-ppc64-gnu": "0.41.0",
+ "@oxfmt/binding-linux-riscv64-gnu": "0.41.0",
+ "@oxfmt/binding-linux-riscv64-musl": "0.41.0",
+ "@oxfmt/binding-linux-s390x-gnu": "0.41.0",
+ "@oxfmt/binding-linux-x64-gnu": "0.41.0",
+ "@oxfmt/binding-linux-x64-musl": "0.41.0",
+ "@oxfmt/binding-openharmony-arm64": "0.41.0",
+ "@oxfmt/binding-win32-arm64-msvc": "0.41.0",
+ "@oxfmt/binding-win32-ia32-msvc": "0.41.0",
+ "@oxfmt/binding-win32-x64-msvc": "0.41.0"
+ }
+ },
+ "node_modules/oxlint": {
+ "version": "1.56.0",
+ "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.56.0.tgz",
+ "integrity": "sha512-Q+5Mj5PVaH/R6/fhMMFzw4dT+KPB+kQW4kaL8FOIq7tfhlnEVp6+3lcWqFruuTNlUo9srZUW3qH7Id4pskeR6g==",
"dev": true,
"license": "MIT",
+ "bin": {
+ "oxlint": "bin/oxlint"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxlint/binding-android-arm-eabi": "1.56.0",
+ "@oxlint/binding-android-arm64": "1.56.0",
+ "@oxlint/binding-darwin-arm64": "1.56.0",
+ "@oxlint/binding-darwin-x64": "1.56.0",
+ "@oxlint/binding-freebsd-x64": "1.56.0",
+ "@oxlint/binding-linux-arm-gnueabihf": "1.56.0",
+ "@oxlint/binding-linux-arm-musleabihf": "1.56.0",
+ "@oxlint/binding-linux-arm64-gnu": "1.56.0",
+ "@oxlint/binding-linux-arm64-musl": "1.56.0",
+ "@oxlint/binding-linux-ppc64-gnu": "1.56.0",
+ "@oxlint/binding-linux-riscv64-gnu": "1.56.0",
+ "@oxlint/binding-linux-riscv64-musl": "1.56.0",
+ "@oxlint/binding-linux-s390x-gnu": "1.56.0",
+ "@oxlint/binding-linux-x64-gnu": "1.56.0",
+ "@oxlint/binding-linux-x64-musl": "1.56.0",
+ "@oxlint/binding-openharmony-arm64": "1.56.0",
+ "@oxlint/binding-win32-arm64-msvc": "1.56.0",
+ "@oxlint/binding-win32-ia32-msvc": "1.56.0",
+ "@oxlint/binding-win32-x64-msvc": "1.56.0"
+ },
+ "peerDependencies": {
+ "oxlint-tsgolint": ">=0.15.0"
+ },
+ "peerDependenciesMeta": {
+ "oxlint-tsgolint": {
+ "optional": true
+ }
}
},
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "node_modules/parse-github-url": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.3.tgz",
+ "integrity": "sha512-tfalY5/4SqGaV/GIGzWyHnFjlpTPTNpENR9Ea2lLldSJ8EWXMsvacWucqY3m3I4YPtas15IxTLQVQ5NSYXPrww==",
"dev": true,
- "license": "MIT",
+ "bin": {
+ "parse-github-url": "cli.js"
+ },
"engines": {
- "node": ">=4"
+ "node": ">= 0.10"
}
},
"node_modules/rollup": {
@@ -2212,25 +1496,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/semver": {
"version": "7.5.4",
"dev": true,
@@ -2261,47 +1526,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/serialize-javascript": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
- "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
- "dev": true,
- "dependencies": {
- "randombytes": "^2.1.0"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/smob": {
"version": "1.4.1",
"dev": true,
@@ -2324,82 +1548,6 @@
"source-map": "^0.6.0"
}
},
- "node_modules/string-width": {
- "version": "4.2.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/terser": {
"version": "5.21.0",
"dev": true,
@@ -2422,95 +1570,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/test-exclude": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz",
- "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^13.0.6",
- "minimatch": "^10.2.2"
- },
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/test-exclude/node_modules/balanced-match": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
- "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
- "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/test-exclude/node_modules/glob": {
- "version": "13.0.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
- "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "minimatch": "^10.2.2",
- "minipass": "^7.1.3",
- "path-scurry": "^2.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/test-exclude/node_modules/minimatch": {
- "version": "10.2.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz",
- "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/test-exclude/node_modules/path-scurry": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
- "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/tinybench": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.0.0.tgz",
@@ -2521,22 +1580,21 @@
"node": ">=20.0.0"
}
},
- "node_modules/tr46": {
- "version": "0.0.3",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/type-check": {
- "version": "0.4.0",
+ "node_modules/tinypool": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz",
+ "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
"engines": {
- "node": ">= 0.8.0"
+ "node": "^20.0.0 || >=22.0.0"
}
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/uglify-js": {
"version": "3.17.4",
"dev": true,
@@ -2549,31 +1607,6 @@
"node": ">=0.8.0"
}
},
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/v8-to-istanbul": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
- "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.12",
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^2.0.0"
- },
- "engines": {
- "node": ">=10.12.0"
- }
- },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"dev": true,
@@ -2588,154 +1621,10 @@
"webidl-conversions": "^3.0.0"
}
},
- "node_modules/which": {
- "version": "2.0.2",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/wordwrap": {
"version": "1.0.0",
"dev": true,
"license": "MIT"
- },
- "node_modules/workerpool": {
- "version": "9.3.2",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz",
- "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yargs-unparser": {
- "version": "2.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "camelcase": "^6.0.0",
- "decamelize": "^4.0.0",
- "flat": "^5.0.2",
- "is-plain-obj": "^2.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yargs-unparser/node_modules/camelcase": {
- "version": "6.3.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/yargs-unparser/node_modules/decamelize": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
}
}
}
diff --git a/package.json b/package.json
index c3bf37e..383207f 100644
--- a/package.json
+++ b/package.json
@@ -38,19 +38,19 @@
"benchmark:install-deps": "npm install --no-save lru-cache quick-lru mnemonist",
"benchmark:all": "npm run benchmark:modern && npm run benchmark:perf && npm run benchmark:comparison",
"changelog": "auto-changelog -p",
- "lint": "eslint --fix *.js src/*.js tests/**/*.js benchmarks/*.js",
- "mocha": "c8 mocha \"tests/**/*.js\"",
+ "fix": "oxlint --fix *.js benchmarks src tests/unit && oxfmt *.js benchmarks src tests/unit --write",
+ "lint": "oxlint *.js benchmarks src tests/unit && oxfmt *.js benchmarks/*.js src/*.js tests/unit/*.js --check",
+ "coverage": "node --test --experimental-test-coverage --test-coverage-exclude=dist/** --test-coverage-exclude=tests/** --test-reporter=spec tests/**/*.test.js 2>&1 | grep -A 1000 \"start of coverage report\" > coverage.txt",
"rollup": "rollup --config",
- "test": "npm run lint && npm run mocha",
+ "test": "npm run lint && node --test tests/**/*.js",
"prepare": "husky"
},
"devDependencies": {
"@rollup/plugin-terser": "^1.0.0",
"auto-changelog": "^2.5.0",
- "c8": "^11.0.0",
- "eslint": "^9.29.0",
"husky": "^9.1.7",
- "mocha": "^11.7.0",
+ "oxfmt": "^0.41.0",
+ "oxlint": "^1.56.0",
"rollup": "^4.43.0",
"tinybench": "^6.0.0"
},
diff --git a/rollup.config.js b/rollup.config.js
index 31caa38..ff5d0e9 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -14,41 +14,40 @@ const bannerShort = `/*!
${year} ${pkg.author}
@version ${pkg.version}
*/`;
-const defaultOutBase = {compact: true, banner: bannerLong, name: pkg.name};
-const cjOutBase = {...defaultOutBase, compact: false, format: "cjs", exports: "named"};
-const esmOutBase = {...defaultOutBase, format: "esm"};
-const umdOutBase = {...defaultOutBase, format: "umd"};
-const minOutBase = {banner: bannerShort, name: pkg.name, plugins: [terser()], sourcemap: true};
-
+const defaultOutBase = { compact: true, banner: bannerLong, name: pkg.name };
+const cjOutBase = { ...defaultOutBase, compact: false, format: "cjs", exports: "named" };
+const esmOutBase = { ...defaultOutBase, format: "esm" };
+const umdOutBase = { ...defaultOutBase, format: "umd" };
+const minOutBase = { banner: bannerShort, name: pkg.name, plugins: [terser()], sourcemap: true };
export default [
- {
- input: "./src/lru.js",
- output: [
- {
- ...cjOutBase,
- file: `dist/${pkg.name}.cjs`
- },
- {
- ...esmOutBase,
- file: `dist/${pkg.name}.js`
- },
- {
- ...esmOutBase,
- ...minOutBase,
- file: `dist/${pkg.name}.min.js`
- },
- {
- ...umdOutBase,
- file: `dist/${pkg.name}.umd.js`,
- name: "lru"
- },
- {
- ...umdOutBase,
- ...minOutBase,
- file: `dist/${pkg.name}.umd.min.js`,
- name: "lru"
- }
- ]
- }
+ {
+ input: "./src/lru.js",
+ output: [
+ {
+ ...cjOutBase,
+ file: `dist/${pkg.name}.cjs`,
+ },
+ {
+ ...esmOutBase,
+ file: `dist/${pkg.name}.js`,
+ },
+ {
+ ...esmOutBase,
+ ...minOutBase,
+ file: `dist/${pkg.name}.min.js`,
+ },
+ {
+ ...umdOutBase,
+ file: `dist/${pkg.name}.umd.js`,
+ name: "lru",
+ },
+ {
+ ...umdOutBase,
+ ...minOutBase,
+ file: `dist/${pkg.name}.umd.min.js`,
+ name: "lru",
+ },
+ ],
+ },
];
diff --git a/src/lru.js b/src/lru.js
index 8ed976d..0ee96de 100644
--- a/src/lru.js
+++ b/src/lru.js
@@ -17,432 +17,443 @@
* // After 5 seconds, key1 will be expired
*/
export class LRU {
- /**
- * Creates a new LRU cache instance.
- * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
- *
- * @constructor
- * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
- * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
- * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
- * @example
- * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
- * @see {@link lru} For parameter validation
- * @since 1.0.0
- */
- constructor (max = 0, ttl = 0, resetTtl = false) {
- this.first = null;
- this.items = Object.create(null);
- this.last = null;
- this.max = max;
- this.resetTtl = resetTtl;
- this.size = 0;
- this.ttl = ttl;
- }
-
- /**
- * Removes all items from the cache.
- *
- * @method clear
- * @memberof LRU
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.clear();
- * console.log(cache.size); // 0
- * @since 1.0.0
- */
- clear () {
- this.first = null;
- this.items = Object.create(null);
- this.last = null;
- this.size = 0;
-
- return this;
- }
-
- /**
- * Removes an item from the cache by key.
- *
- * @method delete
- * @memberof LRU
- * @param {string} key - The key of the item to delete.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('key1', 'value1');
- * cache.delete('key1');
- * console.log(cache.has('key1')); // false
- * @see {@link LRU#has}
- * @see {@link LRU#clear}
- * @since 1.0.0
- */
- delete (key) {
- if (this.has(key)) {
- const item = this.items[key];
-
- delete this.items[key];
- this.size--;
-
- if (item.prev !== null) {
- item.prev.next = item.next;
- }
-
- if (item.next !== null) {
- item.next.prev = item.prev;
- }
-
- if (this.first === item) {
- this.first = item.next;
- }
-
- if (this.last === item) {
- this.last = item.prev;
- }
- }
-
- return this;
- }
-
- /**
- * Returns an array of [key, value] pairs for the specified keys.
- * Order follows LRU order (least to most recently used).
- *
- * @method entries
- * @memberof LRU
- * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
- * @returns {Array>} Array of [key, value] pairs in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * console.log(cache.entries()); // [['a', 1], ['b', 2]]
- * console.log(cache.entries(['a'])); // [['a', 1]]
- * @see {@link LRU#keys}
- * @see {@link LRU#values}
- * @since 11.1.0
- */
- entries (keys = this.keys()) {
- const result = new Array(keys.length);
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i];
- result[i] = [key, this.get(key)];
- }
-
- return result;
- }
-
- /**
- * Removes the least recently used item from the cache.
- *
- * @method evict
- * @memberof LRU
- * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('old', 'value').set('new', 'value');
- * cache.evict(); // Removes 'old' item
- * @see {@link LRU#setWithEvicted}
- * @since 1.0.0
- */
- evict (bypass = false) {
- if (bypass || this.size > 0) {
- const item = this.first;
-
- delete this.items[item.key];
-
- if (--this.size === 0) {
- this.first = null;
- this.last = null;
- } else {
- this.first = item.next;
- this.first.prev = null;
- }
- }
-
- return this;
- }
-
- /**
- * Returns the expiration timestamp for a given key.
- *
- * @method expiresAt
- * @memberof LRU
- * @param {string} key - The key to check expiration for.
- * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
- * @example
- * const cache = new LRU(100, 5000); // 5 second TTL
- * cache.set('key1', 'value1');
- * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
- * @see {@link LRU#get}
- * @see {@link LRU#has}
- * @since 1.0.0
- */
- expiresAt (key) {
- let result;
-
- if (this.has(key)) {
- result = this.items[key].expiry;
- }
-
- return result;
- }
-
- /**
- * Retrieves a value from the cache by key. Updates the item's position to most recently used.
- *
- * @method get
- * @memberof LRU
- * @param {string} key - The key to retrieve.
- * @returns {*} The value associated with the key, or undefined if not found or expired.
- * @example
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- * console.log(cache.get('nonexistent')); // undefined
- * @see {@link LRU#set}
- * @see {@link LRU#has}
- * @since 1.0.0
- */
- get (key) {
- const item = this.items[key];
-
- if (item !== undefined) {
- // Check TTL only if enabled to avoid unnecessary Date.now() calls
- if (this.ttl > 0) {
- if (item.expiry <= Date.now()) {
- this.delete(key);
-
- return undefined;
- }
- }
-
- // Fast LRU update without full set() overhead
- this.moveToEnd(item);
-
- return item.value;
- }
-
- return undefined;
- }
-
- /**
- * Checks if a key exists in the cache.
- *
- * @method has
- * @memberof LRU
- * @param {string} key - The key to check for.
- * @returns {boolean} True if the key exists, false otherwise.
- * @example
- * cache.set('key1', 'value1');
- * console.log(cache.has('key1')); // true
- * console.log(cache.has('nonexistent')); // false
- * @see {@link LRU#get}
- * @see {@link LRU#delete}
- * @since 9.0.0
- */
- has (key) {
- return key in this.items;
- }
-
- /**
- * Efficiently moves an item to the end of the LRU list (most recently used position).
- * This is an internal optimization method that avoids the overhead of the full set() operation
- * when only LRU position needs to be updated.
- *
- * @method moveToEnd
- * @memberof LRU
- * @param {Object} item - The cache item with prev/next pointers to reposition.
- * @private
- * @since 11.3.5
- */
- moveToEnd (item) {
- // If already at the end, nothing to do
- if (this.last === item) {
- return;
- }
-
- // Remove item from current position in the list
- if (item.prev !== null) {
- item.prev.next = item.next;
- }
-
- if (item.next !== null) {
- item.next.prev = item.prev;
- }
-
- // Update first pointer if this was the first item
- if (this.first === item) {
- this.first = item.next;
- }
-
- // Add item to the end
- item.prev = this.last;
- item.next = null;
-
- if (this.last !== null) {
- this.last.next = item;
- }
-
- this.last = item;
-
- // Handle edge case: if this was the only item, it's also first
- if (this.first === null) {
- this.first = item;
- }
- }
-
- /**
- * Returns an array of all keys in the cache, ordered from least to most recently used.
- *
- * @method keys
- * @memberof LRU
- * @returns {string[]} Array of keys in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * cache.get('a'); // Move 'a' to most recent
- * console.log(cache.keys()); // ['b', 'a']
- * @see {@link LRU#values}
- * @see {@link LRU#entries}
- * @since 9.0.0
- */
- keys () {
- const result = new Array(this.size);
- let x = this.first;
- let i = 0;
-
- while (x !== null) {
- result[i++] = x.key;
- x = x.next;
- }
-
- return result;
- }
-
- /**
- * Sets a value in the cache and returns any evicted item.
- *
- * @method setWithEvicted
- * @memberof LRU
- * @param {string} key - The key to set.
- * @param {*} value - The value to store.
- * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
- * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
- * @example
- * const cache = new LRU(2);
- * cache.set('a', 1).set('b', 2);
- * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
- * @see {@link LRU#set}
- * @see {@link LRU#evict}
- * @since 11.3.0
- */
- setWithEvicted (key, value, resetTtl = this.resetTtl) {
- let evicted = null;
-
- if (this.has(key)) {
- this.set(key, value, true, resetTtl);
- } else {
- if (this.max > 0 && this.size === this.max) {
- evicted = {...this.first};
- this.evict(true);
- }
-
- let item = this.items[key] = {
- expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
- key: key,
- prev: this.last,
- next: null,
- value
- };
-
- if (++this.size === 1) {
- this.first = item;
- } else {
- this.last.next = item;
- }
-
- this.last = item;
- }
-
- return evicted;
- }
-
- /**
- * Sets a value in the cache. Updates the item's position to most recently used.
- *
- * @method set
- * @memberof LRU
- * @param {string} key - The key to set.
- * @param {*} value - The value to store.
- * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
- * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
- * @returns {LRU} The LRU instance for method chaining.
- * @example
- * cache.set('key1', 'value1')
- * .set('key2', 'value2')
- * .set('key3', 'value3');
- * @see {@link LRU#get}
- * @see {@link LRU#setWithEvicted}
- * @since 1.0.0
- */
- set (key, value, bypass = false, resetTtl = this.resetTtl) {
- let item = this.items[key];
-
- if (bypass || item !== undefined) {
- // Existing item: update value and position
- item.value = value;
-
- if (bypass === false && resetTtl) {
- item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
- }
-
- // Always move to end, but the bypass parameter affects TTL reset behavior
- this.moveToEnd(item);
- } else {
- // New item: check for eviction and create
- if (this.max > 0 && this.size === this.max) {
- this.evict(true);
- }
-
- item = this.items[key] = {
- expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
- key: key,
- prev: this.last,
- next: null,
- value
- };
-
- if (++this.size === 1) {
- this.first = item;
- } else {
- this.last.next = item;
- }
-
- this.last = item;
- }
-
- return this;
- }
-
- /**
- * Returns an array of all values in the cache for the specified keys.
- * Order follows LRU order (least to most recently used).
- *
- * @method values
- * @memberof LRU
- * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
- * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
- * @example
- * cache.set('a', 1).set('b', 2);
- * console.log(cache.values()); // [1, 2]
- * console.log(cache.values(['a'])); // [1]
- * @see {@link LRU#keys}
- * @see {@link LRU#entries}
- * @since 11.1.0
- */
- values (keys = this.keys()) {
- const result = new Array(keys.length);
- for (let i = 0; i < keys.length; i++) {
- result[i] = this.get(keys[i]);
- }
-
- return result;
- }
+ /**
+ * Creates a new LRU cache instance.
+ * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
+ *
+ * @constructor
+ * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
+ * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
+ * @example
+ * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
+ * @see {@link lru} For parameter validation
+ * @since 1.0.0
+ */
+ constructor(max = 0, ttl = 0, resetTtl = false) {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.max = max;
+ this.resetTtl = resetTtl;
+ this.size = 0;
+ this.ttl = ttl;
+ }
+
+ /**
+ * Removes all items from the cache.
+ *
+ * @method clear
+ * @memberof LRU
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.clear();
+ * console.log(cache.size); // 0
+ * @since 1.0.0
+ */
+ clear() {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.size = 0;
+
+ return this;
+ }
+
+ /**
+ * Removes an item from the cache by key.
+ *
+ * @method delete
+ * @memberof LRU
+ * @param {string} key - The key of the item to delete.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1');
+ * cache.delete('key1');
+ * console.log(cache.has('key1')); // false
+ * @see {@link LRU#has}
+ * @see {@link LRU#clear}
+ * @since 1.0.0
+ */
+ delete(key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ delete this.items[key];
+ this.size--;
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ if (this.last === item) {
+ this.last = item.prev;
+ }
+
+ item.prev = null;
+ item.next = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of [key, value] pairs for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method entries
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
+ * @returns {Array>} Array of [key, value] pairs in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.entries()); // [['a', 1], ['b', 2]]
+ * console.log(cache.entries(['a'])); // [['a', 1]]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#values}
+ * @since 11.1.0
+ */
+ entries(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ const item = this.items[key];
+ result[i] = [key, item !== undefined ? item.value : undefined];
+ }
+
+ return result;
+ }
+
+ /**
+ * Removes the least recently used item from the cache.
+ *
+ * @method evict
+ * @memberof LRU
+ * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('old', 'value').set('new', 'value');
+ * cache.evict(); // Removes 'old' item
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ evict(bypass = false) {
+ if (bypass || this.size > 0) {
+ const item = this.first;
+
+ if (!item) {
+ return this;
+ }
+
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.first = item.next;
+ this.first.prev = null;
+ }
+
+ item.next = null;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns the expiration timestamp for a given key.
+ *
+ * @method expiresAt
+ * @memberof LRU
+ * @param {string} key - The key to check expiration for.
+ * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
+ * @example
+ * const cache = new LRU(100, 5000); // 5 second TTL
+ * cache.set('key1', 'value1');
+ * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
+ * @see {@link LRU#get}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ expiresAt(key) {
+ const item = this.items[key];
+ return item !== undefined ? item.expiry : undefined;
+ }
+
+ /**
+ * Retrieves a value from the cache by key. Updates the item's position to most recently used.
+ *
+ * @method get
+ * @memberof LRU
+ * @param {string} key - The key to retrieve.
+ * @returns {*} The value associated with the key, or undefined if not found or expired.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.get('key1')); // 'value1'
+ * console.log(cache.get('nonexistent')); // undefined
+ * @see {@link LRU#set}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ get(key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ // Check TTL only if enabled to avoid unnecessary Date.now() calls
+ if (this.ttl > 0) {
+ if (item.expiry <= Date.now()) {
+ this.delete(key);
+
+ return undefined;
+ }
+ }
+
+ // Fast LRU update without full set() overhead
+ this.moveToEnd(item);
+
+ return item.value;
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Checks if a key exists in the cache.
+ *
+ * @method has
+ * @memberof LRU
+ * @param {string} key - The key to check for.
+ * @returns {boolean} True if the key exists, false otherwise.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.has('key1')); // true
+ * console.log(cache.has('nonexistent')); // false
+ * @see {@link LRU#get}
+ * @see {@link LRU#delete}
+ * @since 9.0.0
+ */
+ has(key) {
+ const item = this.items[key];
+ return item !== undefined && (this.ttl === 0 || item.expiry > Date.now());
+ }
+
+ /**
+ * Efficiently moves an item to the end of the LRU list (most recently used position).
+ * This is an internal optimization method that avoids the overhead of the full set() operation
+ * when only LRU position needs to be updated.
+ *
+ * @method moveToEnd
+ * @memberof LRU
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ * @since 11.3.5
+ */
+ moveToEnd(item) {
+ if (this.last === item) {
+ return;
+ }
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ item.prev = this.last;
+ item.next = null;
+ this.last.next = item;
+ this.last = item;
+ }
+
+ /**
+ * Returns an array of all keys in the cache, ordered from least to most recently used.
+ *
+ * @method keys
+ * @memberof LRU
+ * @returns {string[]} Array of keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * cache.get('a'); // Move 'a' to most recent
+ * console.log(cache.keys()); // ['b', 'a']
+ * @see {@link LRU#values}
+ * @see {@link LRU#entries}
+ * @since 9.0.0
+ */
+ keys() {
+ const result = Array.from({ length: this.size });
+ let x = this.first;
+ let i = 0;
+
+ while (x !== null) {
+ result[i++] = x.key;
+ x = x.next;
+ }
+
+ return result;
+ }
+
+ /**
+ * Sets a value in the cache and returns any evicted item.
+ *
+ * @method setWithEvicted
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
+ * @example
+ * const cache = new LRU(2);
+ * cache.set('a', 1).set('b', 2);
+ * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
+ * @see {@link LRU#set}
+ * @see {@link LRU#evict}
+ * @since 11.3.0
+ */
+ setWithEvicted(key, value, resetTtl = this.resetTtl) {
+ let evicted = null;
+ let item = this.items[key];
+
+ if (item !== undefined) {
+ item.value = value;
+ if (resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+ this.moveToEnd(item);
+ } else {
+ if (this.max > 0 && this.size === this.max) {
+ evicted = {
+ key: this.first.key,
+ value: this.first.value,
+ expiry: this.first.expiry,
+ };
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value,
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return evicted;
+ }
+
+ /**
+ * Sets a value in the cache. Updates the item's position to most recently used.
+ *
+ * @method set
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1')
+ * .set('key2', 'value2')
+ * .set('key3', 'value3');
+ * @see {@link LRU#get}
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ set(key, value, bypass = false, resetTtl = this.resetTtl) {
+ let item = this.items[key];
+
+ if (bypass || item !== undefined) {
+ // Existing item: update value and position
+ item.value = value;
+
+ if (bypass === false && resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+
+ // Always move to end, but the bypass parameter affects TTL reset behavior
+ this.moveToEnd(item);
+ } else {
+ // New item: check for eviction and create
+ if (this.max > 0 && this.size === this.max) {
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value,
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of all values in the cache for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method values
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
+ * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.values()); // [1, 2]
+ * console.log(cache.values(['a'])); // [1]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#entries}
+ * @since 11.1.0
+ */
+ values(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
+ for (let i = 0; i < keys.length; i++) {
+ const item = this.items[keys[i]];
+ result[i] = item !== undefined ? item.value : undefined;
+ }
+
+ return result;
+ }
}
/**
@@ -469,18 +480,18 @@ export class LRU {
* @see {@link LRU}
* @since 1.0.0
*/
-export function lru (max = 1000, ttl = 0, resetTtl = false) {
- if (isNaN(max) || max < 0) {
- throw new TypeError("Invalid max value");
- }
+export function lru(max = 1000, ttl = 0, resetTtl = false) {
+ if (isNaN(max) || max < 0) {
+ throw new TypeError("Invalid max value");
+ }
- if (isNaN(ttl) || ttl < 0) {
- throw new TypeError("Invalid ttl value");
- }
+ if (isNaN(ttl) || ttl < 0) {
+ throw new TypeError("Invalid ttl value");
+ }
- if (typeof resetTtl !== "boolean") {
- throw new TypeError("Invalid resetTtl value");
- }
+ if (typeof resetTtl !== "boolean") {
+ throw new TypeError("Invalid resetTtl value");
+ }
- return new LRU(max, ttl, resetTtl);
+ return new LRU(max, ttl, resetTtl);
}
diff --git a/tests/unit/lru.js b/tests/unit/lru.js
deleted file mode 100644
index 4137fad..0000000
--- a/tests/unit/lru.js
+++ /dev/null
@@ -1,622 +0,0 @@
-import {LRU, lru} from "../../src/lru.js";
-import {strict as assert} from "assert";
-
-describe("LRU Cache", function () {
- describe("Constructor", function () {
- it("should create an LRU instance with default parameters", function () {
- const cache = new LRU();
- assert.equal(cache.max, 0);
- assert.equal(cache.ttl, 0);
- assert.equal(cache.resetTtl, false);
- assert.equal(cache.size, 0);
- assert.equal(cache.first, null);
- assert.equal(cache.last, null);
- assert.notEqual(cache.items, null);
- assert.equal(typeof cache.items, "object");
- });
-
- it("should create an LRU instance with custom parameters", function () {
- const cache = new LRU(10, 5000, true);
- assert.equal(cache.max, 10);
- assert.equal(cache.ttl, 5000);
- assert.equal(cache.resetTtl, true);
- assert.equal(cache.size, 0);
- });
- });
-
- describe("lru factory function", function () {
- it("should create an LRU instance with default parameters", function () {
- const cache = lru();
- assert.equal(cache.max, 1000);
- assert.equal(cache.ttl, 0);
- assert.equal(cache.resetTtl, false);
- });
-
- it("should create an LRU instance with custom parameters", function () {
- const cache = lru(50, 1000, true);
- assert.equal(cache.max, 50);
- assert.equal(cache.ttl, 1000);
- assert.equal(cache.resetTtl, true);
- });
-
- it("should throw TypeError for invalid max value", function () {
- assert.throws(() => lru("invalid"), TypeError, "Invalid max value");
- assert.throws(() => lru(-1), TypeError, "Invalid max value");
- assert.throws(() => lru(NaN), TypeError, "Invalid max value");
- });
-
- it("should throw TypeError for invalid ttl value", function () {
- assert.throws(() => lru(10, "invalid"), TypeError, "Invalid ttl value");
- assert.throws(() => lru(10, -1), TypeError, "Invalid ttl value");
- assert.throws(() => lru(10, NaN), TypeError, "Invalid ttl value");
- });
-
- it("should throw TypeError for invalid resetTtl value", function () {
- assert.throws(() => lru(10, 0, "invalid"), TypeError, "Invalid resetTtl value");
- assert.throws(() => lru(10, 0, 1), TypeError, "Invalid resetTtl value");
- });
- });
-
- describe("Basic operations", function () {
- let cache;
-
- beforeEach(function () {
- cache = new LRU(3);
- });
-
- it("should set and get values", function () {
- cache.set("key1", "value1");
- assert.equal(cache.get("key1"), "value1");
- assert.equal(cache.size, 1);
- });
-
- it("should return undefined for non-existent keys", function () {
- assert.equal(cache.get("nonexistent"), undefined);
- });
-
- it("should check if key exists with has()", function () {
- cache.set("key1", "value1");
- assert.equal(cache.has("key1"), true);
- assert.equal(cache.has("nonexistent"), false);
- });
-
- it("should delete items", function () {
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- assert.equal(cache.size, 2);
-
- cache.delete("key1");
- assert.equal(cache.size, 1);
- assert.equal(cache.has("key1"), false);
- assert.equal(cache.get("key1"), undefined);
- assert.equal(cache.get("key2"), "value2");
- });
-
- it("should delete non-existent key gracefully", function () {
- cache.set("key1", "value1");
- cache.delete("nonexistent");
- assert.equal(cache.size, 1);
- assert.equal(cache.get("key1"), "value1");
- });
-
- it("should clear all items", function () {
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- assert.equal(cache.size, 2);
-
- cache.clear();
- assert.equal(cache.size, 0);
- assert.equal(cache.first, null);
- assert.equal(cache.last, null);
- assert.notEqual(cache.items, null);
- assert.equal(typeof cache.items, "object");
- });
-
- it("should support method chaining", function () {
- const result = cache.set("key1", "value1").set("key2", "value2").clear();
- assert.equal(result, cache);
- });
- });
-
- describe("LRU eviction", function () {
- let cache;
-
- beforeEach(function () {
- cache = new LRU(3);
- });
-
- it("should evict least recently used item when max is reached", function () {
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- cache.set("key3", "value3");
- cache.set("key4", "value4"); // Should evict key1
-
- assert.equal(cache.size, 3);
- assert.equal(cache.has("key1"), false);
- assert.equal(cache.has("key2"), true);
- assert.equal(cache.has("key3"), true);
- assert.equal(cache.has("key4"), true);
- });
-
- it("should update position when accessing existing item", function () {
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- cache.set("key3", "value3");
-
- // Access key1 to make it most recently used
- cache.get("key1");
-
- cache.set("key4", "value4"); // Should evict key2, not key1
-
- assert.equal(cache.has("key1"), true);
- assert.equal(cache.has("key2"), false);
- assert.equal(cache.has("key3"), true);
- assert.equal(cache.has("key4"), true);
- });
-
- it("should maintain correct order in keys()", function () {
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- cache.set("key3", "value3");
-
- let keys = cache.keys();
- assert.deepEqual(keys, ["key1", "key2", "key3"]);
-
- // Access key1 to move it to end
- cache.get("key1");
- keys = cache.keys();
- assert.deepEqual(keys, ["key2", "key3", "key1"]);
- });
-
- it("should handle unlimited cache size (max = 0)", function () {
- const unlimitedCache = new LRU(0);
- for (let i = 0; i < 1000; i++) {
- unlimitedCache.set(`key${i}`, `value${i}`);
- }
- assert.equal(unlimitedCache.size, 1000);
- });
- });
-
- describe("Eviction methods", function () {
- let cache;
-
- beforeEach(function () {
- cache = new LRU(3);
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- cache.set("key3", "value3");
- });
-
- it("should evict first item with evict()", function () {
- cache.evict();
- assert.equal(cache.size, 2);
- assert.equal(cache.has("key1"), false);
- assert.equal(cache.has("key2"), true);
- assert.equal(cache.has("key3"), true);
- });
-
- it("should evict with bypass flag", function () {
- cache.evict(true);
- assert.equal(cache.size, 2);
- });
-
- it("should handle evict on empty cache", function () {
- cache.clear();
- cache.evict();
- assert.equal(cache.size, 0);
- });
-
- it("should handle evict on single item cache", function () {
- cache.clear();
- cache.set("only", "value");
- cache.evict();
- assert.equal(cache.size, 0);
- assert.equal(cache.first, null);
- assert.equal(cache.last, null);
- });
- });
-
- describe("setWithEvicted method", function () {
- let cache;
-
- beforeEach(function () {
- cache = new LRU(2);
- });
-
- it("should return null when no eviction occurs", function () {
- const evicted = cache.setWithEvicted("key1", "value1");
- assert.equal(evicted, null);
- });
-
- it("should return evicted item when max is reached", function () {
- cache.set("key1", "value1");
- cache.set("key2", "value2");
-
- const evicted = cache.setWithEvicted("key3", "value3");
- assert.notEqual(evicted, null);
- assert.equal(evicted.key, "key1");
- assert.equal(evicted.value, "value1");
- });
-
- it("should update existing key without eviction", function () {
- cache.set("key1", "value1");
- const evicted = cache.setWithEvicted("key1", "newvalue1");
- assert.equal(evicted, null);
- assert.equal(cache.get("key1"), "newvalue1");
- });
- });
-
- describe("Array methods", function () {
- let cache;
-
- beforeEach(function () {
- cache = new LRU(5);
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- cache.set("key3", "value3");
- });
-
- it("should return all keys in LRU order", function () {
- const keys = cache.keys();
- assert.deepEqual(keys, ["key1", "key2", "key3"]);
- });
-
- it("should return all values in LRU order", function () {
- const values = cache.values();
- assert.deepEqual(values, ["value1", "value2", "value3"]);
- });
-
- it("should return values for specific keys", function () {
- const values = cache.values(["key3", "key1"]);
- assert.deepEqual(values, ["value3", "value1"]);
- });
-
- it("should return entries as [key, value] pairs", function () {
- const entries = cache.entries();
- assert.deepEqual(entries, [
- ["key1", "value1"],
- ["key2", "value2"],
- ["key3", "value3"]
- ]);
- });
-
- it("should return entries for specific keys", function () {
- const entries = cache.entries(["key3", "key1"]);
- assert.deepEqual(entries, [
- ["key3", "value3"],
- ["key1", "value1"]
- ]);
- });
-
- it("should handle empty cache", function () {
- cache.clear();
- assert.deepEqual(cache.keys(), []);
- assert.deepEqual(cache.values(), []);
- assert.deepEqual(cache.entries(), []);
- });
- });
-
- describe("TTL (Time To Live)", function () {
- let cache;
-
- beforeEach(function () {
- cache = new LRU(5, 100); // 100ms TTL
- });
-
- it("should set expiration time", function () {
- const beforeTime = Date.now();
- cache.set("key1", "value1");
- const expiresAt = cache.expiresAt("key1");
-
- assert.ok(expiresAt >= beforeTime + 100);
- assert.ok(expiresAt <= beforeTime + 200); // Allow some margin
- });
-
- it("should return undefined for non-existent key expiration", function () {
- assert.equal(cache.expiresAt("nonexistent"), undefined);
- });
-
- it("should expire items after TTL", function (done) {
- cache.set("key1", "value1");
- assert.equal(cache.get("key1"), "value1");
-
- setTimeout(() => {
- assert.equal(cache.get("key1"), undefined);
- assert.equal(cache.has("key1"), false);
- assert.equal(cache.size, 0);
- done();
- }, 150);
- });
-
- it("should handle TTL = 0 (no expiration)", function () {
- const neverExpireCache = new LRU(5, 0);
- neverExpireCache.set("key1", "value1");
- assert.equal(neverExpireCache.expiresAt("key1"), 0);
- });
-
- it("should reset TTL when accessing with resetTtl=true", function (done) {
- const resetCache = new LRU(5, 1000, true);
- resetCache.set("key1", "value1");
-
- // Check that expiration timestamp changes when updating with resetTtl=true
- const firstExpiry = resetCache.expiresAt("key1");
-
- // Small delay to ensure timestamp difference
- setTimeout(() => {
- resetCache.set("key1", "value1", false, true); // This should reset TTL
- const secondExpiry = resetCache.expiresAt("key1");
-
- assert.ok(secondExpiry > firstExpiry, "TTL should be reset");
- done();
- }, 10);
- });
-
- it("should not reset TTL when resetTtl=false", function (done) {
- const noResetCache = new LRU(5, 100, false);
- noResetCache.set("key1", "value1");
-
- setTimeout(() => {
- // Access the key but don't reset TTL
- assert.equal(noResetCache.get("key1"), "value1");
-
- // Check that it expires at original time
- setTimeout(() => {
- assert.equal(noResetCache.get("key1"), undefined);
- done();
- }, 75);
- }, 50);
- });
- });
-
- describe("Edge cases and complex scenarios", function () {
- it("should handle updating existing key with set()", function () {
- const cache = new LRU(3);
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- cache.set("key1", "newvalue1"); // Update existing key
-
- assert.equal(cache.get("key1"), "newvalue1");
- assert.equal(cache.size, 2);
- });
-
- it("should maintain correct first/last pointers during deletion", function () {
- const cache = new LRU(3);
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- cache.set("key3", "value3");
-
- // Delete middle item
- cache.delete("key2");
- assert.deepEqual(cache.keys(), ["key1", "key3"]);
-
- // Delete first item
- cache.delete("key1");
- assert.deepEqual(cache.keys(), ["key3"]);
-
- // Delete last item
- cache.delete("key3");
- assert.deepEqual(cache.keys(), []);
- assert.equal(cache.first, null);
- assert.equal(cache.last, null);
- });
-
- it("should handle complex LRU repositioning", function () {
- const cache = new LRU(4);
- cache.set("a", 1);
- cache.set("b", 2);
- cache.set("c", 3);
- cache.set("d", 4);
-
- // Access items in different order
- cache.set("b", 22); // Move b to end
- cache.get("a"); // Move a to end
- cache.set("c", 33); // Move c to end
-
- assert.deepEqual(cache.keys(), ["d", "b", "a", "c"]);
- });
-
- it("should handle set with bypass parameter", function () {
- const cache = new LRU(3);
- cache.set("key1", "value1");
- cache.set("key2", "value2");
-
- // Set with bypass=true should not reposition but still updates to last
- cache.set("key1", "newvalue1", true);
- assert.deepEqual(cache.keys(), ["key2", "key1"]);
- });
-
- it("should handle resetTtl parameter in set method", function () {
- const cache = new LRU(3, 1000, false);
- const beforeTime = Date.now();
- cache.set("key1", "value1");
-
- // Set with resetTtl=true should update expiry
- cache.set("key1", "newvalue1", false, true);
- const expiresAt = cache.expiresAt("key1");
- assert.ok(expiresAt > beforeTime + 900); // Should be close to current time + TTL
- });
-
- it("should handle single item cache operations", function () {
- const cache = new LRU(1);
-
- // Set first item
- cache.set("key1", "value1");
- assert.equal(cache.first, cache.last);
- assert.equal(cache.size, 1);
-
- // Replace with second item
- cache.set("key2", "value2");
- assert.equal(cache.first, cache.last);
- assert.equal(cache.size, 1);
- assert.equal(cache.has("key1"), false);
- assert.equal(cache.has("key2"), true);
- });
-
- it("should handle empty cache operations", function () {
- const cache = new LRU(3);
-
- // Operations on empty cache
- assert.equal(cache.get("key1"), undefined);
- assert.equal(cache.has("key1"), false);
- cache.delete("key1"); // Should not throw
- assert.equal(cache.expiresAt("key1"), undefined);
-
- // Evict on empty cache
- cache.evict();
- assert.equal(cache.size, 0);
- });
-
- it("should handle accessing items that become last", function () {
- const cache = new LRU(3);
- cache.set("key1", "value1");
- cache.set("key2", "value2");
- cache.set("key3", "value3");
-
- // Access the last item (should not change position)
- cache.get("key3");
- assert.deepEqual(cache.keys(), ["key1", "key2", "key3"]);
- });
- });
-
- describe("Memory and performance", function () {
- it("should handle large number of operations", function () {
- const cache = new LRU(1000);
-
- // Add 1000 items
- for (let i = 0; i < 1000; i++) {
- cache.set(`key${i}`, `value${i}`);
- }
- assert.equal(cache.size, 1000);
-
- // Access random items
- for (let i = 0; i < 100; i++) {
- const key = `key${Math.floor(Math.random() * 1000)}`;
- cache.get(key);
- }
-
- // Add more items to trigger eviction
- for (let i = 1000; i < 1100; i++) {
- cache.set(`key${i}`, `value${i}`);
- }
- assert.equal(cache.size, 1000);
- });
-
- it("should handle alternating set/get operations", function () {
- const cache = new LRU(10);
-
- for (let i = 0; i < 100; i++) {
- cache.set(`key${i % 10}`, `value${i}`);
- cache.get(`key${(i + 5) % 10}`);
- }
-
- assert.equal(cache.size, 10);
- });
- });
-
- describe("Additional coverage tests", function () {
- it("should handle setWithEvicted with unlimited cache size", function () {
- const cache = new LRU(0); // Unlimited size
- const evicted = cache.setWithEvicted("key1", "value1");
- assert.equal(evicted, null);
- assert.equal(cache.size, 1);
- });
-
- it("should handle setWithEvicted with first item insertion", function () {
- const cache = new LRU(2);
- cache.setWithEvicted("key1", "value1");
- assert.equal(cache.size, 1);
- assert.equal(cache.first, cache.last);
- });
-
- it("should handle bypass parameter with resetTtl false", function () {
- const cache = new LRU(3, 1000, false);
- cache.set("key1", "value1");
- const originalExpiry = cache.expiresAt("key1");
-
- // Call set with bypass=true, resetTtl=false
- cache.set("key1", "newvalue1", true, false);
- const newExpiry = cache.expiresAt("key1");
-
- // TTL should not be reset
- assert.equal(originalExpiry, newExpiry);
- });
-
- it("should set expiry when using setWithEvicted with ttl > 0", function () {
- const cache = new LRU(2, 100); // ttl > 0
- const before = Date.now();
- cache.set("a", 1);
- cache.set("b", 2);
- const evicted = cache.setWithEvicted("c", 3); // triggers eviction and new item creation
- assert.notEqual(evicted, null);
- const expiry = cache.expiresAt("c");
- assert.ok(expiry >= before + 100);
- assert.ok(expiry <= before + 250); // allow some margin
- });
-
- it("should set expiry to 0 when resetTtl=true and ttl=0 on update", function () {
- const cache = new LRU(2, 0); // ttl = 0
- cache.set("x", 1);
- assert.equal(cache.expiresAt("x"), 0);
- // update existing key with resetTtl=true to exercise branch in set()
- cache.set("x", 2, false, true);
- assert.equal(cache.expiresAt("x"), 0);
- });
-
- it("should handle moveToEnd edge case by direct method invocation", function () {
- const cache = new LRU(1);
-
- // Add a single item
- cache.set("only", "value");
-
- // Create a minimal test case that directly exercises the uncovered lines
- // The edge case in moveToEnd (lines 275-276) occurs when:
- // 1. An item is moved that was the first item (making first = item.next = null)
- // 2. But the cache wasn't empty (last !== null)
- // 3. The condition if (this.first === null) triggers to restore consistency
-
- const item = cache.first;
- assert.equal(cache.first, cache.last);
- assert.equal(item, cache.last);
-
- // Since moveToEnd has early return for item === last, we need to
- // create a scenario where the item is first but not last
- // Let's create a second dummy item and manipulate pointers
- const dummyItem = {
- key: "dummy",
- value: "dummy",
- prev: item,
- next: null,
- expiry: 0
- };
-
- // Set up the linked list: item <-> dummyItem
- item.next = dummyItem;
- cache.last = dummyItem;
-
- // Now item is first but not last, so moveToEnd won't early return
- // When moveToEnd processes item:
- // 1. Sets first = item.next (which is dummyItem)
- // 2. Removes item from its position
- // 3. But then we manipulate to make first = null to trigger the edge case
-
- // Temporarily null out the next pointer to simulate the edge case
- const originalNext = item.next;
- item.next = null;
-
- // This manipulation will cause first to become null in moveToEnd
- // triggering the if (this.first === null) condition on lines 274-276
- cache.first = null;
- cache.last = dummyItem; // last is not null
-
- // Now call moveToEnd - this should trigger the uncovered lines
- cache.moveToEnd(item);
-
- // Verify the edge case was handled correctly
- assert.equal(cache.first, item);
-
- // Restore the item for cleanup
- item.next = originalNext;
- });
- });
-});
-
diff --git a/tests/unit/lru.test.js b/tests/unit/lru.test.js
new file mode 100644
index 0000000..60c9de5
--- /dev/null
+++ b/tests/unit/lru.test.js
@@ -0,0 +1,552 @@
+import { LRU, lru } from "../../src/lru.js";
+import { describe, it, beforeEach } from "node:test";
+import assert from "node:assert";
+
+describe("LRU Cache", function () {
+ describe("Constructor", function () {
+ it("should create an LRU instance with default parameters", function () {
+ const cache = new LRU();
+ assert.equal(cache.max, 0);
+ assert.equal(cache.ttl, 0);
+ assert.equal(cache.resetTtl, false);
+ assert.equal(cache.size, 0);
+ assert.equal(cache.first, null);
+ assert.equal(cache.last, null);
+ assert.notEqual(cache.items, null);
+ assert.equal(typeof cache.items, "object");
+ });
+
+ it("should create an LRU instance with custom parameters", function () {
+ const cache = new LRU(10, 5000, true);
+ assert.equal(cache.max, 10);
+ assert.equal(cache.ttl, 5000);
+ assert.equal(cache.resetTtl, true);
+ assert.equal(cache.size, 0);
+ });
+ });
+
+ describe("lru factory function", function () {
+ it("should create an LRU instance with default parameters", function () {
+ const cache = lru();
+ assert.equal(cache.max, 1000);
+ assert.equal(cache.ttl, 0);
+ assert.equal(cache.resetTtl, false);
+ });
+
+ it("should create an LRU instance with custom parameters", function () {
+ const cache = lru(50, 1000, true);
+ assert.equal(cache.max, 50);
+ assert.equal(cache.ttl, 1000);
+ assert.equal(cache.resetTtl, true);
+ });
+
+ it("should throw TypeError for invalid max value", function () {
+ assert.throws(() => lru("invalid"), TypeError, "Invalid max value");
+ assert.throws(() => lru(-1), TypeError, "Invalid max value");
+ assert.throws(() => lru(NaN), TypeError, "Invalid max value");
+ });
+
+ it("should throw TypeError for invalid ttl value", function () {
+ assert.throws(() => lru(10, "invalid"), TypeError, "Invalid ttl value");
+ assert.throws(() => lru(10, -1), TypeError, "Invalid ttl value");
+ assert.throws(() => lru(10, NaN), TypeError, "Invalid ttl value");
+ });
+
+ it("should throw TypeError for invalid resetTtl value", function () {
+ assert.throws(() => lru(10, 0, "invalid"), TypeError, "Invalid resetTtl value");
+ assert.throws(() => lru(10, 0, 1), TypeError, "Invalid resetTtl value");
+ });
+ });
+
+ describe("Basic operations", function () {
+ let cache;
+
+ beforeEach(function () {
+ cache = new LRU(3);
+ });
+
+ it("should set and get values", function () {
+ cache.set("key1", "value1");
+ assert.equal(cache.get("key1"), "value1");
+ assert.equal(cache.size, 1);
+ });
+
+ it("should return undefined for non-existent keys", function () {
+ assert.equal(cache.get("nonexistent"), undefined);
+ });
+
+ it("should check if key exists with has()", function () {
+ cache.set("key1", "value1");
+ assert.equal(cache.has("key1"), true);
+ assert.equal(cache.has("nonexistent"), false);
+ });
+
+ it("should delete items", function () {
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ assert.equal(cache.size, 2);
+
+ cache.delete("key1");
+ assert.equal(cache.size, 1);
+ assert.equal(cache.has("key1"), false);
+ assert.equal(cache.get("key1"), undefined);
+ assert.equal(cache.get("key2"), "value2");
+ });
+
+ it("should delete non-existent key gracefully", function () {
+ cache.set("key1", "value1");
+ cache.delete("nonexistent");
+ assert.equal(cache.size, 1);
+ assert.equal(cache.get("key1"), "value1");
+ });
+
+ it("should clear all items", function () {
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ assert.equal(cache.size, 2);
+
+ cache.clear();
+ assert.equal(cache.size, 0);
+ assert.equal(cache.first, null);
+ assert.equal(cache.last, null);
+ assert.notEqual(cache.items, null);
+ assert.equal(typeof cache.items, "object");
+ });
+
+ it("should support method chaining", function () {
+ const result = cache.set("key1", "value1").set("key2", "value2").clear();
+ assert.equal(result, cache);
+ });
+ });
+
+ describe("LRU eviction", function () {
+ let cache;
+
+ beforeEach(function () {
+ cache = new LRU(3);
+ });
+
+ it("should evict least recently used item when max is reached", function () {
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key3", "value3");
+ cache.set("key4", "value4");
+
+ assert.equal(cache.size, 3);
+ assert.equal(cache.has("key1"), false);
+ assert.equal(cache.has("key2"), true);
+ assert.equal(cache.has("key3"), true);
+ assert.equal(cache.has("key4"), true);
+ });
+
+ it("should update position when accessing existing item", function () {
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key3", "value3");
+
+ cache.get("key1");
+
+ cache.set("key4", "value4");
+
+ assert.equal(cache.has("key1"), true);
+ assert.equal(cache.has("key2"), false);
+ assert.equal(cache.has("key3"), true);
+ assert.equal(cache.has("key4"), true);
+ });
+
+ it("should maintain correct order in keys()", function () {
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key3", "value3");
+
+ let keys = cache.keys();
+ assert.deepEqual(keys, ["key1", "key2", "key3"]);
+
+ cache.get("key1");
+ keys = cache.keys();
+ assert.deepEqual(keys, ["key2", "key3", "key1"]);
+ });
+
+ it("should handle unlimited cache size (max = 0)", function () {
+ const unlimitedCache = new LRU(0);
+ for (let i = 0; i < 1000; i++) {
+ unlimitedCache.set(`key${i}`, `value${i}`);
+ }
+ assert.equal(unlimitedCache.size, 1000);
+ });
+ });
+
+ describe("Eviction methods", function () {
+ let cache;
+
+ beforeEach(function () {
+ cache = new LRU(3);
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key3", "value3");
+ });
+
+ it("should evict first item with evict()", function () {
+ cache.evict();
+ assert.equal(cache.size, 2);
+ assert.equal(cache.has("key1"), false);
+ assert.equal(cache.has("key2"), true);
+ assert.equal(cache.has("key3"), true);
+ });
+
+ it("should evict with bypass flag", function () {
+ cache.evict(true);
+ assert.equal(cache.size, 2);
+ });
+
+ it("should handle evict on empty cache", function () {
+ cache.clear();
+ cache.evict();
+ assert.equal(cache.size, 0);
+ });
+
+ it("should handle evict on single item cache", function () {
+ cache.clear();
+ cache.set("only", "value");
+ cache.evict();
+ assert.equal(cache.size, 0);
+ assert.equal(cache.first, null);
+ assert.equal(cache.last, null);
+ });
+ });
+
+ describe("setWithEvicted method", function () {
+ let cache;
+
+ beforeEach(function () {
+ cache = new LRU(2);
+ });
+
+ it("should return null when no eviction occurs", function () {
+ const evicted = cache.setWithEvicted("key1", "value1");
+ assert.equal(evicted, null);
+ });
+
+ it("should return evicted item when max is reached", function () {
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+
+ const evicted = cache.setWithEvicted("key3", "value3");
+ assert.notEqual(evicted, null);
+ assert.equal(evicted.key, "key1");
+ assert.equal(evicted.value, "value1");
+ });
+
+ it("should update existing key without eviction", function () {
+ cache.set("key1", "value1");
+ const evicted = cache.setWithEvicted("key1", "newvalue1");
+ assert.equal(evicted, null);
+ assert.equal(cache.get("key1"), "newvalue1");
+ });
+ });
+
+ describe("Array methods", function () {
+ let cache;
+
+ beforeEach(function () {
+ cache = new LRU(5);
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key3", "value3");
+ });
+
+ it("should return all keys in LRU order", function () {
+ const keys = cache.keys();
+ assert.deepEqual(keys, ["key1", "key2", "key3"]);
+ });
+
+ it("should return all values in LRU order", function () {
+ const values = cache.values();
+ assert.deepEqual(values, ["value1", "value2", "value3"]);
+ });
+
+ it("should return values for specific keys", function () {
+ const values = cache.values(["key3", "key1"]);
+ assert.deepEqual(values, ["value3", "value1"]);
+ });
+
+ it("should return entries as [key, value] pairs", function () {
+ const entries = cache.entries();
+ assert.deepEqual(entries, [
+ ["key1", "value1"],
+ ["key2", "value2"],
+ ["key3", "value3"],
+ ]);
+ });
+
+ it("should return entries for specific keys", function () {
+ const entries = cache.entries(["key3", "key1"]);
+ assert.deepEqual(entries, [
+ ["key3", "value3"],
+ ["key1", "value1"],
+ ]);
+ });
+
+ it("should handle empty cache", function () {
+ cache.clear();
+ assert.deepEqual(cache.keys(), []);
+ assert.deepEqual(cache.values(), []);
+ assert.deepEqual(cache.entries(), []);
+ });
+ });
+
+ describe("TTL (Time To Live)", function () {
+ let cache;
+
+ beforeEach(function () {
+ cache = new LRU(5, 100);
+ });
+
+ it("should set expiration time", function () {
+ const beforeTime = Date.now();
+ cache.set("key1", "value1");
+ const expiresAt = cache.expiresAt("key1");
+
+ assert.ok(expiresAt >= beforeTime + 100);
+ assert.ok(expiresAt <= beforeTime + 200);
+ });
+
+ it("should return undefined for non-existent key expiration", function () {
+ assert.equal(cache.expiresAt("nonexistent"), undefined);
+ });
+
+ it("should expire items after TTL", async function () {
+ cache.set("key1", "value1");
+ assert.equal(cache.get("key1"), "value1");
+
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ assert.equal(cache.get("key1"), undefined);
+ assert.equal(cache.has("key1"), false);
+ assert.equal(cache.size, 0);
+ });
+
+ it("should handle TTL = 0 (no expiration)", function () {
+ const neverExpireCache = new LRU(5, 0);
+ neverExpireCache.set("key1", "value1");
+ assert.equal(neverExpireCache.expiresAt("key1"), 0);
+ });
+
+ it("should reset TTL when accessing with resetTtl=true", async function () {
+ const resetCache = new LRU(5, 1000, true);
+ resetCache.set("key1", "value1");
+
+ const firstExpiry = resetCache.expiresAt("key1");
+
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ resetCache.set("key1", "value1", false, true);
+ const secondExpiry = resetCache.expiresAt("key1");
+
+ assert.ok(secondExpiry > firstExpiry, "TTL should be reset");
+ });
+
+ it("should not reset TTL when resetTtl=false", async function () {
+ const noResetCache = new LRU(5, 100, false);
+ noResetCache.set("key1", "value1");
+
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ assert.equal(noResetCache.get("key1"), "value1");
+
+ await new Promise((resolve) => setTimeout(resolve, 75));
+ assert.equal(noResetCache.get("key1"), undefined);
+ });
+ });
+
+ describe("Edge cases and complex scenarios", function () {
+ it("should handle updating existing key with set()", function () {
+ const cache = new LRU(3);
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key1", "newvalue1");
+
+ assert.equal(cache.get("key1"), "newvalue1");
+ assert.equal(cache.size, 2);
+ });
+
+ it("should maintain correct first/last pointers during deletion", function () {
+ const cache = new LRU(3);
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key3", "value3");
+
+ cache.delete("key2");
+ assert.deepEqual(cache.keys(), ["key1", "key3"]);
+
+ cache.delete("key1");
+ assert.deepEqual(cache.keys(), ["key3"]);
+
+ cache.delete("key3");
+ assert.deepEqual(cache.keys(), []);
+ assert.equal(cache.first, null);
+ assert.equal(cache.last, null);
+ });
+
+ it("should handle complex LRU repositioning", function () {
+ const cache = new LRU(4);
+ cache.set("a", 1);
+ cache.set("b", 2);
+ cache.set("c", 3);
+ cache.set("d", 4);
+
+ cache.set("b", 22);
+ cache.get("a");
+ cache.set("c", 33);
+
+ assert.deepEqual(cache.keys(), ["d", "b", "a", "c"]);
+ });
+
+ it("should handle set with bypass parameter", function () {
+ const cache = new LRU(3);
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+
+ cache.set("key1", "newvalue1", true);
+ assert.deepEqual(cache.keys(), ["key2", "key1"]);
+ });
+
+ it("should handle resetTtl parameter in set method", function () {
+ const cache = new LRU(3, 1000, false);
+ const beforeTime = Date.now();
+ cache.set("key1", "value1");
+
+ cache.set("key1", "newvalue1", false, true);
+ const expiresAt = cache.expiresAt("key1");
+ assert.ok(expiresAt > beforeTime + 900);
+ });
+
+ it("should handle single item cache operations", function () {
+ const cache = new LRU(1);
+
+ cache.set("key1", "value1");
+ assert.equal(cache.first, cache.last);
+ assert.equal(cache.size, 1);
+
+ cache.set("key2", "value2");
+ assert.equal(cache.first, cache.last);
+ assert.equal(cache.size, 1);
+ assert.equal(cache.has("key1"), false);
+ assert.equal(cache.has("key2"), true);
+ });
+
+ it("should handle empty cache operations", function () {
+ const cache = new LRU(3);
+
+ assert.equal(cache.get("key1"), undefined);
+ assert.equal(cache.has("key1"), false);
+ cache.delete("key1");
+ assert.equal(cache.expiresAt("key1"), undefined);
+
+ cache.evict();
+ assert.equal(cache.size, 0);
+ });
+
+ it("should handle accessing items that become last", function () {
+ const cache = new LRU(3);
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key3", "value3");
+
+ cache.get("key3");
+ assert.deepEqual(cache.keys(), ["key1", "key2", "key3"]);
+ });
+ });
+
+ describe("Memory and performance", function () {
+ it("should handle large number of operations", function () {
+ const cache = new LRU(1000);
+
+ for (let i = 0; i < 1000; i++) {
+ cache.set(`key${i}`, `value${i}`);
+ }
+ assert.equal(cache.size, 1000);
+
+ for (let i = 0; i < 100; i++) {
+ const key = `key${Math.floor(Math.random() * 1000)}`;
+ cache.get(key);
+ }
+
+ for (let i = 1000; i < 1100; i++) {
+ cache.set(`key${i}`, `value${i}`);
+ }
+ assert.equal(cache.size, 1000);
+ });
+
+ it("should handle alternating set/get operations", function () {
+ const cache = new LRU(10);
+
+ for (let i = 0; i < 100; i++) {
+ cache.set(`key${i % 10}`, `value${i}`);
+ cache.get(`key${(i + 5) % 10}`);
+ }
+
+ assert.equal(cache.size, 10);
+ });
+ });
+
+ describe("Additional coverage tests", function () {
+ it("should handle setWithEvicted with unlimited cache size", function () {
+ const cache = new LRU(0);
+ const evicted = cache.setWithEvicted("key1", "value1");
+ assert.equal(evicted, null);
+ assert.equal(cache.size, 1);
+ });
+
+ it("should handle setWithEvicted with first item insertion", function () {
+ const cache = new LRU(2);
+ cache.setWithEvicted("key1", "value1");
+ assert.equal(cache.size, 1);
+ assert.equal(cache.first, cache.last);
+ });
+
+ it("should handle bypass parameter with resetTtl false", function () {
+ const cache = new LRU(3, 1000, false);
+ cache.set("key1", "value1");
+ const originalExpiry = cache.expiresAt("key1");
+
+ cache.set("key1", "newvalue1", true, false);
+ const newExpiry = cache.expiresAt("key1");
+
+ assert.equal(originalExpiry, newExpiry);
+ });
+
+ it("should set expiry when using setWithEvicted with ttl > 0", function () {
+ const cache = new LRU(2, 100);
+ const before = Date.now();
+ cache.set("a", 1);
+ cache.set("b", 2);
+ const evicted = cache.setWithEvicted("c", 3);
+ assert.notEqual(evicted, null);
+ const expiry = cache.expiresAt("c");
+ assert.ok(expiry >= before + 100);
+ assert.ok(expiry <= before + 250);
+ });
+
+ it("should set expiry to 0 when resetTtl=true and ttl=0 on update", function () {
+ const cache = new LRU(2, 0);
+ cache.set("x", 1);
+ assert.equal(cache.expiresAt("x"), 0);
+ cache.set("x", 2, false, true);
+ assert.equal(cache.expiresAt("x"), 0);
+ });
+
+ it("should handle evict with bypass on empty cache", function () {
+ const cache = new LRU(3);
+ cache.evict(true);
+ assert.equal(cache.size, 0);
+ assert.equal(cache.first, null);
+ assert.equal(cache.last, null);
+ });
+
+ it("should set expiry to 0 when resetTtl=true and ttl=0 on setWithEvicted", function () {
+ const cache = new LRU(2, 0);
+ cache.set("x", 1);
+ assert.equal(cache.expiresAt("x"), 0);
+ cache.setWithEvicted("x", 2, true);
+ assert.equal(cache.expiresAt("x"), 0);
+ });
+ });
+});
diff --git a/types/lru.d.ts b/types/lru.d.ts
index 3f83b9d..f401d7d 100644
--- a/types/lru.d.ts
+++ b/types/lru.d.ts
@@ -24,6 +24,18 @@ export interface LRUItem {
value: T;
}
+/**
+ * Represents the evicted item returned by setWithEvicted().
+ */
+export interface EvictedItem {
+ /** The key of the evicted item */
+ key: any;
+ /** The value of the evicted item */
+ value: T;
+ /** The expiration timestamp of the evicted item */
+ expiry: number;
+}
+
/**
* High-performance Least Recently Used (LRU) cache with optional TTL support.
* All core operations (get, set, delete) are O(1).
@@ -37,7 +49,7 @@ export class LRU {
* @param resetTtl Whether to reset TTL when accessing existing items via get() (default: false)
*/
constructor(max?: number, ttl?: number, resetTtl?: boolean);
-
+
/** Pointer to the least recently used item (first to be evicted) */
readonly first: LRUItem | null;
/** Hash map for O(1) key-based access to cache nodes */
@@ -52,86 +64,86 @@ export class LRU {
readonly size: number;
/** Time-to-live in milliseconds (0 = no expiration) */
readonly ttl: number;
-
+
/**
* Removes all items from the cache.
* @returns The LRU instance for method chaining
*/
clear(): this;
-
+
/**
* Removes an item from the cache by key.
* @param key The key of the item to delete
* @returns The LRU instance for method chaining
*/
delete(key: any): this;
-
+
/**
* Returns an array of [key, value] pairs for the specified keys.
* Order follows LRU order (least to most recently used).
* @param keys Array of keys to get entries for (defaults to all keys)
* @returns Array of [key, value] pairs in LRU order
*/
- entries(keys?: any[]): [any, T][];
-
+ entries(keys?: any[]): [any, T | undefined][];
+
/**
* Removes the least recently used item from the cache.
- * @param bypass Whether to force eviction even when cache is empty
+ * @param bypass Whether to force eviction even when cache is empty (default: false)
* @returns The LRU instance for method chaining
*/
evict(bypass?: boolean): this;
-
+
/**
* Returns the expiration timestamp for a given key.
* @param key The key to check expiration for
* @returns The expiration timestamp in milliseconds, or undefined if key doesn't exist
*/
expiresAt(key: any): number | undefined;
-
+
/**
* Retrieves a value from the cache by key. Updates the item's position to most recently used.
* @param key The key to retrieve
* @returns The value associated with the key, or undefined if not found or expired
*/
get(key: any): T | undefined;
-
+
/**
- * Checks if a key exists in the cache.
+ * Checks if a key exists in the cache (not expired).
* @param key The key to check for
- * @returns True if the key exists, false otherwise
+ * @returns True if the key exists and is not expired, false otherwise
*/
has(key: any): boolean;
-
+
/**
* Returns an array of all keys in the cache, ordered from least to most recently used.
* @returns Array of keys in LRU order
*/
keys(): any[];
-
+
/**
* Sets a value in the cache. Updates the item's position to most recently used.
* @param key The key to set
* @param value The value to store
- * @param bypass Internal parameter for setWithEvicted method
- * @param resetTtl Whether to reset the TTL for this operation
+ * @param bypass Internal parameter for setWithEvicted method (default: false)
+ * @param resetTtl Whether to reset the TTL for this operation (default: this.resetTtl)
* @returns The LRU instance for method chaining
*/
set(key: any, value: T, bypass?: boolean, resetTtl?: boolean): this;
-
+
/**
* Sets a value in the cache and returns any evicted item.
* @param key The key to set
* @param value The value to store
- * @param resetTtl Whether to reset the TTL for this operation
- * @returns The evicted item (if any) or null
+ * @param resetTtl Whether to reset the TTL for this operation (default: this.resetTtl)
+ * @returns The evicted item (if any) with {key, value, expiry} or null
*/
- setWithEvicted(key: any, value: T, resetTtl?: boolean): LRUItem | null;
-
+ setWithEvicted(key: any, value: T, resetTtl?: boolean): EvictedItem | null;
+
/**
* Returns an array of all values in the cache for the specified keys.
* Order follows LRU order (least to most recently used).
* @param keys Array of keys to get values for (defaults to all keys)
- * @returns Array of values corresponding to the keys in LRU order
+ * @returns Array of values corresponding to the keys (undefined for missing/expired keys)
*/
- values(keys?: any[]): T[];
+ values(keys?: any[]): (T | undefined)[];
}