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/.husky/pre-commit b/.husky/pre-commit
index a03c57b..598cbfb 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,2 +1,2 @@
-npm test
+npm run fix && npm run coverage && git add -A
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
new file mode 100644
index 0000000..93b0830
--- /dev/null
+++ b/.oxfmtrc.json
@@ -0,0 +1,5 @@
+{
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
+ "ignorePatterns": [],
+ "useTabs": true
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..2ac9f75
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,84 @@
+# 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 coverage # Run tests with coverage reporting
+npm run fix # Fix linting and formatting issues
+npm run lint # Lint code with oxlint
+```
+
+## Development Workflow
+
+Source code is in `src/`.
+
+- **lint**: Check and fix linting issues with oxlint
+- **fix**: Fix linting and formatting issues
+- **build**: Lint + rollup build
+- **coverage**: 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)
+
+## Development Principles
+
+- **DRY (Don't Repeat Yourself)**: Extract common logic into reusable functions; avoid duplication
+- **YAGNI (You Ain't Gonna Need It)**: Implement only what's needed; avoid over-engineering
+- **SOLID**: Follow single responsibility, open/closed, and interface segregation principles
+- **OWASP**: Prioritize security; avoid unsafe operations, validate inputs, prevent injection risks
+
+## 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%
+- Test pattern: `tests/**/*.js`
+- All tests must pass with 100% coverage before merging
+- Run: `npm test` (lint + tests) or `npm run coverage` for coverage report
+
+## 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/CHANGELOG.md b/CHANGELOG.md
index 93da2f7..d04ae55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1059,7 +1059,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
> 10 February 2017
- Changing the signature of `remove()` to avoid edge case creation [`bb88a78`](https://github.com/avoidwork/tiny-lru/commit/bb88a78366f1ae4af209025810e0409f77ff67dc)
-- Adding some tests double checking deleting things that don't exist won''t be an issue [`e208b76`](https://github.com/avoidwork/tiny-lru/commit/e208b7601c1b31e59ebca94cd320ad8d1b40a067)
+- Adding some tests double checking deleting things that don't exist won''t be an issue [`e208b76`](https://github.com/avoidwork/tiny-lru/commit/e208b7601c1b31e59ebca94cd320ad8d1b40a067)
#### [1.4.2](https://github.com/avoidwork/tiny-lru/compare/1.4.1...1.4.2)
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/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..f356ce7
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,112 @@
+# Contributing to tiny-lru
+
+Thank you for your interest in contributing to tiny-lru! This document outlines the process for contributing to the project.
+
+## Code of Conduct
+
+This project follows the [Contributor Covenant](https://www.contributor-covenant.org/) code of conduct. By participating, you are expected to uphold this code.
+
+## Getting Started
+
+1. Fork the repository
+2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/tiny-lru.git`
+3. Install dependencies: `npm install`
+4. Create a branch: `git checkout -b feature/your-feature-name`
+
+## Development
+
+### Setup
+
+```bash
+npm install
+```
+
+### Running Tests
+
+```bash
+npm test # Run lint and tests
+npm run mocha # Run tests with coverage
+```
+
+### Code Style
+
+- **Indentation**: Tabs
+- **Quotes**: Double quotes
+- **Semicolons**: Required
+- **Formatting**: Run `npm run fix` before committing
+
+### Linting and Formatting
+
+```bash
+npm run lint # Check linting and formatting
+npm run fix # Fix linting and formatting issues
+```
+
+## Making Changes
+
+### Guiding Principles
+
+- **DRY (Don't Repeat Yourself)**: Extract common logic into reusable functions
+- **YAGNI (You Ain't Gonna Need It)**: Implement only what's needed
+- **SOLID**: Follow single responsibility, open/closed, and interface segregation principles
+- **OWASP**: Prioritize security; validate inputs, avoid unsafe operations
+
+### Writing Tests
+
+- All new features must include tests
+- Maintain 100% line coverage
+- Tests live in `tests/unit/`
+- Run `npm run mocha` to verify coverage
+
+### Commit Messages
+
+Follow conventional commits:
+
+```
+type(scope): description
+
+[optional body]
+
+[optional footer]
+```
+
+**Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
+
+**Examples**:
+```
+feat: add setWithEvicted method
+fix: correct TTL expiration check
+docs: update README API reference
+```
+
+## Pull Request Process
+
+1. Ensure all tests pass: `npm test`
+2. Ensure 100% test coverage: `npm run mocha`
+3. Update documentation if needed
+4. Squash commits if necessary
+5. Submit pull request with clear description
+
+### PR Checklist
+
+- [ ] Tests pass with `npm test`
+- [ ] 100% code coverage maintained
+- [ ] Linting passes with `npm run lint`
+- [ ] Documentation updated
+- [ ] No breaking changes (or clearly documented)
+
+## Releasing
+
+Releases are managed by the maintainers. If you're preparing a release:
+
+1. Update version in `package.json`
+2. Update `CHANGELOG.md`
+3. Create release tag
+
+## Questions?
+
+Open an issue for any questions or discussions about contributions.
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the BSD-3-Clause license.
diff --git a/README.md b/README.md
index 75d00f8..57114b2 100644
--- a/README.md
+++ b/README.md
@@ -1,654 +1,280 @@
-# 🚀 Tiny LRU
+# Tiny LRU
-[](https://badge.fury.io/js/tiny-lru)
-[](https://nodejs.org/)
-[](https://opensource.org/licenses/BSD-3-Clause)
+[](https://www.npmjs.com/package/tiny-lru)
+[](https://www.npmjs.com/package/tiny-lru)
[](https://github.com/avoidwork/tiny-lru/actions)
-[](https://github.com/avoidwork/tiny-lru)
+[](https://github.com/avoidwork/tiny-lru)
-A **high-performance, lightweight** LRU cache for JavaScript with **strong UPDATE performance and competitive SET/GET/DELETE**, and a **compact bundle size**. Built for developers who need fast caching without compromising on features.
+A fast, lightweight LRU (Least Recently Used) cache for JavaScript with O(1) operations and optional TTL support.
-## 📦 Installation
+## What is an LRU Cache?
-```bash
-npm install tiny-lru
-# or
-yarn add tiny-lru
-# or
-pnpm add tiny-lru
-```
-
-**Requirements:** Node.js ≥12
-
-## ⚡ Quick Start
-
-```javascript
-import {lru} from "tiny-lru";
-
-// Create cache and start using immediately
-const cache = lru(100); // Max 100 items
-cache.set('user:123', {name: 'John', age: 30});
-const user = cache.get('user:123'); // {name: 'John', age: 30}
-
-// With TTL (5 second expiration)
-const tempCache = lru(50, 5000);
-tempCache.set('session', 'abc123'); // Automatically expires after 5 seconds
-```
-
-## 📑 Table of Contents
-
-- [✨ Features & Benefits](#-features--benefits)
-- [📊 Performance Deep Dive](#-performance-deep-dive)
-- [📖 API Reference](#-api-reference)
-- [🚀 Getting Started](#-getting-started)
-- [💡 Real-World Examples](#-real-world-examples)
-- [🔗 Interoperability](#-interoperability)
-- [🛠️ Development](#️-development)
-- [📄 License](#-license)
-
-## ✨ Features & Benefits
-
-### Why Choose Tiny LRU?
-
-- **🔄 Strong Cache Updates** - Excellent performance in update-heavy workloads
-- **📦 Compact Bundle** - Just ~2.2 KiB minified for a full-featured LRU library
-- **⚖️ Balanced Performance** - Competitive across all operations with O(1) complexity
-- **⏱️ TTL Support** - Optional time-to-live with automatic expiration
-- **🔄 Method Chaining** - Fluent API for better developer experience
-- **🎯 TypeScript Ready** - Full TypeScript support with complete type definitions
-- **🌐 Universal Compatibility** - Works seamlessly in Node.js and browsers
-- **🛡️ Production Ready** - Battle-tested and reliable
-
-### Benchmark Comparison (Mean of 5 runs)
-
-| Library | SET ops/sec | GET ops/sec | UPDATE ops/sec | DELETE ops/sec |
-|---------|-------------|-------------|----------------|----------------|
-| **tiny-lru** | 404,753 | 1,768,449 | 1,703,716 | 298,770 |
-| lru-cache | 326,221 | 1,069,061 | 878,858 | 277,734 |
-| quick-lru | 591,683 | 1,298,487 | 935,481 | 359,600 |
-| mnemonist | 412,467 | 2,478,778 | 2,156,690 | 0 |
-
-Notes:
-- Mean values computed from the Performance Summary across 5 consecutive runs of `npm run benchmark:comparison`.
-- mnemonist lacks a compatible delete method in this harness, so DELETE ops/sec is 0.
-- Performance varies by hardware, Node.js version, and workload patterns; run the provided benchmarks locally to assess your specific use case.
-- Environment: Node.js v24.5.0, macOS arm64.
+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.
-## 📊 Performance Deep Dive
-
-### When to Choose Tiny LRU
-
-**✅ Perfect for:**
-- **Frequent cache updates** - Leading UPDATE performance
-- **Mixed read/write workloads** - Balanced across all operations
-- **Bundle size constraints** - Compact library with full features
-- **Production applications** - Battle-tested with comprehensive testing
-
-### Running Your Own Benchmarks
-
-```bash
-# Run all performance benchmarks
-npm run benchmark:all
-
-# Individual benchmark suites
-npm run benchmark:modern # Comprehensive Tinybench suite
-npm run benchmark:perf # Performance measurements
-npm run benchmark:comparison # Compare against other LRU libraries
-```
-
-## 🚀 Getting Started
-
-### Installation
+## Installation
```bash
npm install tiny-lru
-# or
-yarn add tiny-lru
-# or
-pnpm add tiny-lru
```
-### Quick Examples
+Requires Node.js ≥14 or modern browsers with ES Module support.
+
+## Quick Start
```javascript
-import {lru} from "tiny-lru";
+import { lru } from "tiny-lru";
-// Basic cache
+// Create a cache that holds up to 100 items
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
+// Store and retrieve data
+cache.set("user:42", { name: "Alice", score: 1500 });
+const user = cache.get("user:42"); // { name: "Alice", score: 1500 }
-// With TTL (time-to-live)
-const cacheWithTtl = lru(50, 30000); // 30 second TTL
-cacheWithTtl.set('temp-data', {important: true});
-// Automatically expires after 30 seconds
+// Chain operations
+cache.set("a", 1).set("b", 2).set("c", 3);
-const resetCache = lru(25, 10000, true);
-resetCache.set('session', 'user123');
-// Because resetTtl is true, TTL resets when you set() the same key again
+// Check what's in the cache
+cache.has("a"); // true
+cache.size; // 3
+cache.keys(); // ['a', 'b', 'c'] (LRU order)
```
-### CDN Usage (Browser)
-
-```html
-
-
-
-
-
-
-```
+## With TTL (Time-to-Live)
-### TypeScript Usage
+Items can automatically expire after a set time:
-```typescript
-import {lru, LRU} from "tiny-lru";
-
-// 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');
-
-// Class inheritance
-class MyCache extends LRU {
- constructor() {
- super(1000, 60000, true); // 1000 items, 1 min TTL, reset TTL on set
- }
-}
-```
-
-### Configuration Options
-
-#### Factory Function
```javascript
-import {lru} from "tiny-lru";
+// Cache that expires after 5 seconds
+const sessionCache = lru(100, 5000);
-const cache = lru(max, ttl = 0, resetTtl = false);
+sessionCache.set("session:id", { userId: 123 });
+// After 5 seconds, this returns undefined
+sessionCache.get("session:id");
```
-**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)
+Want TTL to reset when you update an item? Enable `resetTtl`:
-#### Class Constructor
```javascript
-import {LRU} from "tiny-lru";
-
-const cache = new LRU(1000, 60000, true); // 1000 items, 1 min TTL, reset TTL on set
+const cache = lru(100, 60000, true); // 1 minute TTL, resets on update
+cache.set("key", "value");
+cache.set("key", "new value"); // TTL resets
```
-#### Best Practices
-```javascript
-// 1. Size your cache appropriately
-const cache = lru(1000); // Not too small, not too large
-
-// 2. Use meaningful keys
-cache.set(`user:${userId}:profile`, userProfile);
-cache.set(`product:${productId}:details`, productDetails);
-
-// 3. Handle cache misses gracefully
-function getData(key) {
- const cached = cache.get(key);
- if (cached !== undefined) {
- return cached;
- }
-
- // Fallback to slower data source
- const data = expensiveOperation(key);
- cache.set(key, data);
- return data;
-}
+## When to Use Tiny LRU
-// 4. Clean up when needed
-process.on('exit', () => {
- cache.clear(); // Help garbage collection
-});
-```
+**Great for:**
-#### 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
+- API response caching
+- Function memoization
+- Session storage with expiration
+- Rate limiting
+- Any scenario where you want to limit memory usage
-## 💡 Real-World Examples
+**Not ideal for:**
-### API Response Caching
+- Non-string keys (works best with strings)
+- Very large caches (consider a database)
-```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;
- }
-}
-```
+## API Reference
-### Function Memoization
+### Factory Function: `lru(max?, ttl?, resetTtl?)`
```javascript
-import {lru} from "tiny-lru";
-
-function memoize(fn, maxSize = 100) {
- const cache = lru(maxSize);
-
- return function(...args) {
- const key = JSON.stringify(args);
-
- if (cache.has(key)) {
- return cache.get(key);
- }
-
- const result = fn.apply(this, args);
- cache.set(key, result);
- return result;
- };
-}
-
-// Usage
-const expensiveCalculation = memoize((n) => {
- console.log(`Computing for ${n}`);
- return n * n * n;
-}, 50);
+import { lru } from "tiny-lru";
-console.log(expensiveCalculation(5)); // Computing for 5 -> 125
-console.log(expensiveCalculation(5)); // 125 (cached)
+const cache1 = lru(); // 1000 items, no TTL
+const cache2 = lru(500); // 500 items, no TTL
+const cache3 = lru(100, 30000); // 100 items, 30s TTL
+const cache4 = lru(100, 60000, true); // with resetTtl enabled
```
-### Session Management
+### Class: `new LRU(max?, ttl?, resetTtl?)`
```javascript
-import {lru} from "tiny-lru";
-
-class SessionManager {
- constructor() {
- // 30 minute TTL, with resetTtl enabled for set()
- this.sessions = lru(1000, 1800000, true);
- }
-
- createSession(userId, data) {
- const sessionId = this.generateId();
- const session = {
- userId,
- data,
- createdAt: Date.now()
- };
-
- this.sessions.set(sessionId, session);
- return sessionId;
- }
-
- getSession(sessionId) {
- // get() does not extend TTL; to extend, set the session again when resetTtl is true
- return this.sessions.get(sessionId);
- }
-
- endSession(sessionId) {
- this.sessions.delete(sessionId);
- }
-}
-```
-
-
-
-## 🔗 Interoperability
-
-Compatible with Lodash's `memoize` function cache interface:
-
-```javascript
-import _ from "lodash";
-import {lru} from "tiny-lru";
+import { LRU } from "tiny-lru";
-_.memoize.Cache = lru().constructor;
-const memoized = _.memoize(myFunc);
-memoized.cache.max = 10;
+const cache = new LRU(100, 5000);
```
-## 🛠️ Development
-
-### Testing
-
-Tiny LRU maintains 100% test coverage with comprehensive unit and integration tests.
-
-```bash
-# Run all tests with coverage
-npm test
-
-# Run tests with verbose output
-npm run mocha
-
-# Lint code
-npm run lint
-
-# Full build (lint + build)
-npm run build
-```
-
-**Test Coverage:** 100% coverage across all modules
-
-```console
-----------|---------|----------|---------|---------|-------------------
-File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
-All files | 100 | 100 | 100 | 100 |
- lru.js | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
-```
-
-### Contributing
-
-#### Quick Start for Contributors
-
-```bash
-# Clone and setup
-git clone https://github.com/avoidwork/tiny-lru.git
-cd tiny-lru
-npm install
-
-# Run tests
-npm test
+### TypeScript
-# Run linting
-npm run lint
+```typescript
+import { LRU } from "tiny-lru";
-# Run benchmarks
-npm run benchmark:all
+interface User {
+ id: number;
+ name: string;
+}
-# Build distribution files
-npm run build
+const cache = new LRU(100);
+cache.set("user:1", { id: 1, name: "Alice" });
+const user: User | undefined = cache.get("user:1");
```
-#### Development Workflow
-
-1. **Fork** the repository on GitHub
-2. **Clone** your fork locally
-3. **Create** a feature branch: `git checkout -b feature/amazing-feature`
-4. **Develop** your changes with tests
-5. **Test** thoroughly: `npm test && npm run lint`
-6. **Commit** using conventional commits: `git commit -m "feat: add amazing feature"`
-7. **Push** to your fork: `git push origin feature/amazing-feature`
-8. **Submit** a Pull Request
-
-#### Contribution Guidelines
-
-- **Code Quality**: Follow ESLint rules and existing code style
-- **Testing**: Maintain 100% test coverage for all changes
-- **Documentation**: Update README.md and JSDoc for API changes
-- **Performance**: Benchmark changes that could impact performance
-- **Compatibility**: Ensure Node.js ≥12 compatibility
-- **Commit Messages**: Use [Conventional Commits](https://conventionalcommits.org/) format
-
----
-
-## 📖 API Reference
-
-### Factory Function
-
-#### lru(max, ttl, resetTtl)
-
-Creates a new LRU cache instance using the factory function.
-
-**Parameters:**
-- `max` `{Number}` - Maximum number of items to store (default: 1000; 0 = unlimited)
-- `ttl` `{Number}` - Time-to-live in milliseconds (default: 0; 0 = no expiration)
-- `resetTtl` `{Boolean}` - Reset TTL when updating existing items via `set()` (default: false)
-
-**Returns:** `{LRU}` New LRU cache instance
-
-**Throws:** `{TypeError}` When parameters are invalid
-
-```javascript
-import {lru} from "tiny-lru";
-
-// Basic cache
-const cache = lru(100);
-
-// With TTL
-const cacheWithTtl = lru(50, 30000); // 30 second TTL
-
-// With resetTtl enabled for set()
-const resetCache = lru(25, 10000, true);
+### Methods
-// Validation errors
-lru(-1); // TypeError: Invalid max value
-lru(100, -1); // TypeError: Invalid ttl value
-lru(100, 0, "no"); // TypeError: Invalid resetTtl value
-```
+| Method | Description |
+| ----------------------- | ---------------------------------------------- |
+| `clear()` | Remove all items. Returns `this` for chaining. |
+| `delete(key)` | Remove an item. Returns `this` for chaining. |
+| `entries(keys?)` | Get `[key, value]` pairs in LRU order. |
+| `evict()` | Remove the least recently used item. |
+| `expiresAt(key)` | Get expiration timestamp for a key. |
+| `get(key)` | Retrieve a value. Moves item to most recent. |
+| `has(key)` | Check if key exists and is not expired. |
+| `keys()` | Get all keys in LRU order (oldest first). |
+| `set(key, value)` | Store a value. Returns `this` for chaining. |
+| `setWithEvicted(key, value)` | Store value, return evicted item if full. |
+| `values(keys?)` | Get all values, or values for specific keys. |
### Properties
-#### first
-`{Object|null}` - Item in first (least recently used) position
+| Property | Type | Description |
+| -------- | ------ | ---------------------------- |
+| `first` | object | Least recently used item |
+| `last` | object | Most recently used item |
+| `max` | number | Maximum items allowed |
+| `size` | number | Current number of items |
+| `ttl` | number | Time-to-live in milliseconds |
-```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
-```
+## Common Patterns
-#### max
-`{Number}` - Maximum number of items to hold in cache
+### Memoization
```javascript
-const cache = lru(500);
-cache.max; // 500
-```
+function memoize(fn, maxSize = 100) {
+ const cache = lru(maxSize);
-#### resetTtl
-`{Boolean}` - Whether to reset TTL when updating existing items via `set()`
+ return function (...args) {
+ const key = JSON.stringify(args);
-```javascript
-const cache = lru(500, 5*6e4, true);
-cache.resetTtl; // true
-```
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
-#### size
-`{Number}` - Current number of items in cache
+ const result = fn(...args);
+ cache.set(key, result);
+ return result;
+ };
+}
-```javascript
-const cache = lru();
-cache.size; // 0 - empty cache
+// 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
```
-#### ttl
-`{Number}` - TTL in milliseconds (0 = no expiration)
+### Cache-Aside Pattern
```javascript
-const cache = lru(100, 3e4);
-cache.ttl; // 30000
-```
+async function getUser(userId) {
+ const cache = lru(1000, 60000); // 1 minute cache
-### Methods
+ // Check cache first
+ const cached = cache.get(`user:${userId}`);
+ if (cached) {
+ return cached;
+ }
-#### clear()
-Removes all items from cache.
+ // Fetch from database
+ const user = await db.users.findById(userId);
-**Returns:** `{Object}` LRU instance
+ // Store in cache
+ cache.set(`user:${userId}`, user);
-```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
+ return user;
+}
```
-#### 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
+### Finding What Was Evicted
```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.
+const cache = lru(3);
+cache.set("a", 1).set("b", 2).set("c", 3);
-**Returns:** `{Object}` LRU instance
+const evicted = cache.setWithEvicted("d", 4);
+console.log(evicted); // { key: 'a', value: 1, expiry: 0 }
-```javascript
-cache.set('old', 'value').set('new', 'value');
-cache.evict(); // Removes 'old' item
+cache.keys(); // ['b', 'c', 'd']
```
-#### expiresAt(key)
-Gets expiration timestamp for cached item.
+## Advanced Usage
-**Parameters:**
-- `key` `{String}` - Item key
-
-**Returns:** `{Number|undefined}` Expiration time (epoch milliseconds) or undefined if key doesn't exist
+### Batch Operations with Keys
```javascript
-const cache = new LRU(100, 5000); // 5 second TTL
-cache.set('key1', 'value1');
-console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
-```
-
-#### get(key)
-Retrieves cached item and promotes it to most recently used position.
-
-**Parameters:**
-- `key` `{String}` - Item key
-
-**Returns:** `{*}` Item value or undefined if not found/expired
-
-Note: `get()` does not reset or extend TTL. TTL is only reset on `set()` when `resetTtl` is `true`.
+const cache = lru(100);
+cache.set("users:1", { name: "Alice" });
+cache.set("users:2", { name: "Bob" });
+cache.set("users:3", { name: "Carol" });
-```javascript
-cache.set('key1', 'value1');
-console.log(cache.get('key1')); // 'value1'
-console.log(cache.get('nonexistent')); // undefined
+// Get values for specific keys (order matches input array)
+const values = cache.values(["users:3", "users:1"]);
+// ['Carol', 'Alice'] - matches input key order
```
-#### 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
+### Interop with Lodash
```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).
+import _ from "lodash";
+import { lru } from "tiny-lru";
-**Returns:** `{Array}` Array of keys
+_.memoize.Cache = lru().constructor;
-```javascript
-cache.set('a', 1).set('b', 2);
-cache.get('a'); // Move 'a' to most recent
-console.log(cache.keys()); // ['b', 'a']
+const slowFunc = _.memoize(expensiveOperation);
+slowFunc.cache.max = 100; // Configure cache size
```
-#### set(key, value)
-Stores item in cache as most recently used.
+## Why Tiny LRU?
-**Parameters:**
-- `key` `{String}` - Item key
-- `value` `{*}` - Item value
+| Feature | tiny-lru | lru-cache |
+| ---------------- | ------------ | --------- |
+| Bundle size | ~2.2 KB | ~15 KB |
+| O(1) operations | ✅ | ✅ |
+| TTL support | ✅ | ✅ |
+| TypeScript | ✅ | ✅ |
+| Zero dependencies| ✅ | ❌ |
-**Returns:** `{Object}` LRU instance
+## Performance
-```javascript
-cache.set('key1', 'value1')
- .set('key2', 'value2')
- .set('key3', 'value3');
-```
+All core operations are O(1):
-#### setWithEvicted(key, value)
-Stores item and returns evicted item if cache was full.
+- **Set**: Add or update items
+- **Get**: Retrieve and promote to most recent
+- **Delete**: Remove items
+- **Has**: Quick existence check
-**Parameters:**
-- `key` `{String}` - Item key
-- `value` `{*}` - Item value
+## Development
-**Returns:** `{Object|null}` Evicted item `{key, value, expiry, prev, next}` or null
-
-```javascript
-const cache = new LRU(2);
-cache.set('a', 1).set('b', 2);
-const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
-if (evicted) {
- console.log(`Evicted: ${evicted.key}`, evicted.value);
-}
+```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.
+## Test Coverage
-**Parameters:**
-- `keys` `{Array}` - Optional array of specific keys to retrieve (defaults to all keys)
+| Metric | Coverage |
+| --------- | -------- |
+| Lines | 100% |
+| Branches | 95% |
+| Functions | 100% |
-**Returns:** `{Array}` Array of values
+## Contributing
-```javascript
-cache.set('a', 1).set('b', 2);
-console.log(cache.values()); // [1, 2]
-console.log(cache.values(['a'])); // [1]
-```
+1. Fork the repository
+2. Create a feature branch (`git checkout -b feature/amazing-feature`)
+3. Run `npm test` to ensure all tests pass
+4. Commit your changes (`git commit -m 'Add amazing feature'`)
+5. Push to the branch (`git push origin feature/amazing-feature`)
+6. Open a Pull Request
----
+## License
-## 📄 License
+Copyright (c) 2026, Jason Mulligan
-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..6b5a6bf 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', () => {
- // Your test code here
- const cache = lru(1000);
- cache.set('key', 'value');
+bench.add("your-test-name", () => {
+ // Your test code here
+ const cache = lru(1000);
+ 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..91e1746 100644
--- a/benchmarks/comparison-benchmark.js
+++ b/benchmarks/comparison-benchmark.js
@@ -49,16 +49,16 @@ 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);
+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 }
+ nested: { foo: "bar", baz: i },
};
}
@@ -72,15 +72,17 @@ function generateTestData (count) {
* @param {number} modulo - Upper bound for indices
* @returns {Uint32Array}
*/
-function generateAccessPattern (length, modulo) {
+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;
+ x ^= x << 13;
+ x ^= x >>> 17;
+ x ^= x << 5;
+ y = (y + 1) >>> 0;
+ const n = (x + y) >>> 0;
pattern[i] = n % modulo;
}
@@ -88,14 +90,14 @@ function generateAccessPattern (length, modulo) {
}
// Initialize caches
-function createCaches () {
+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)
+ mnemonist: new MnemonistLRU(CACHE_SIZE),
};
}
@@ -106,7 +108,7 @@ function createCaches () {
* @param {boolean} force - Whether to call global.gc() if available
* @returns {NodeJS.MemoryUsage}
*/
-function getMemoryUsage (force = false) {
+function getMemoryUsage(force = false) {
if (force && global.gc) {
global.gc();
}
@@ -115,7 +117,7 @@ function getMemoryUsage (force = false) {
}
// Calculate memory per item
-function calculateMemoryPerItem (beforeMem, afterMem, itemCount) {
+function calculateMemoryPerItem(beforeMem, afterMem, itemCount) {
const heapDiff = afterMem.heapUsed - beforeMem.heapUsed;
return Math.round(heapDiff / itemCount);
@@ -126,10 +128,10 @@ const bundleSizes = {
"tiny-lru": "2.1KB",
"lru-cache": "~15KB",
"quick-lru": "~1.8KB",
- "mnemonist": "~45KB"
+ mnemonist: "~45KB",
};
-async function runBenchmarks () {
+async function runBenchmarks() {
console.log("🚀 LRU Cache Library Comparison Benchmark\n");
console.log(`Cache Size: ${CACHE_SIZE} items`);
console.log(`Iterations: ${ITERATIONS.toLocaleString()}`);
@@ -144,7 +146,7 @@ async function runBenchmarks () {
// SET operations benchmark
console.log("📊 SET Operations Benchmark");
- console.log("=" .repeat(50));
+ console.log("=".repeat(50));
const setBench = new Bench({ time: 2000 });
@@ -156,7 +158,7 @@ async function runBenchmarks () {
"lru-cache": 0,
"lru-cache-ttl": 0,
"quick-lru": 0,
- "mnemonist": 0
+ mnemonist: 0,
};
setBench
@@ -220,13 +222,13 @@ async function runBenchmarks () {
// GET operations benchmark (with pre-populated caches)
console.log("\n📊 GET Operations Benchmark");
- console.log("=" .repeat(50));
+ console.log("=".repeat(50));
const caches = createCaches();
// Pre-populate all caches deterministically
const prepopulated = Math.min(CACHE_SIZE, 500);
- Object.values(caches).forEach(cache => {
+ Object.values(caches).forEach((cache) => {
for (let i = 0; i < prepopulated; i++) {
cache.set(testData.keys[i], testData.values[i]);
}
@@ -290,7 +292,7 @@ async function runBenchmarks () {
// DELETE operations benchmark
console.log("\n📊 DELETE Operations Benchmark");
- console.log("=" .repeat(50));
+ console.log("=".repeat(50));
const deleteBench = new Bench({ time: 2000 });
@@ -299,12 +301,12 @@ async function runBenchmarks () {
"tiny-lru": tinyLru(CACHE_SIZE),
"lru-cache": new LRUCache({ max: CACHE_SIZE }),
"quick-lru": new QuickLRU({ maxSize: CACHE_SIZE }),
- "mnemonist": new MnemonistLRU(CACHE_SIZE)
+ mnemonist: new MnemonistLRU(CACHE_SIZE),
};
const deleteState = { idx: 0 };
// Pre-populate
- Object.values(deleteCaches).forEach(cache => {
+ Object.values(deleteCaches).forEach((cache) => {
for (let i = 0; i < 100; i++) {
cache.set(testData.keys[i], testData.values[i]);
}
@@ -354,14 +356,14 @@ async function runBenchmarks () {
// UPDATE operations benchmark
console.log("\n📊 UPDATE Operations Benchmark");
- console.log("=" .repeat(50));
+ 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 => {
+ Object.values(updateCaches).forEach((cache) => {
for (let i = 0; i < 100; i++) {
cache.set(testData.keys[i], testData.values[i]);
}
@@ -374,7 +376,10 @@ async function runBenchmarks () {
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]);
+ updateCaches["tiny-lru"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
}
updateState.idx = i;
})
@@ -382,7 +387,10 @@ async function runBenchmarks () {
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]);
+ updateCaches["tiny-lru-ttl"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
}
updateState.idx = i;
})
@@ -390,7 +398,10 @@ async function runBenchmarks () {
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]);
+ updateCaches["lru-cache"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
}
updateState.idx = i;
})
@@ -398,7 +409,10 @@ async function runBenchmarks () {
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]);
+ updateCaches["lru-cache-ttl"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
}
updateState.idx = i;
})
@@ -406,7 +420,10 @@ async function runBenchmarks () {
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]);
+ updateCaches["quick-lru"].set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
}
updateState.idx = i;
})
@@ -414,7 +431,10 @@ async function runBenchmarks () {
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]);
+ updateCaches.mnemonist.set(
+ testData.keys[idx],
+ testData.values[(idx + 50) % testData.values.length],
+ );
}
updateState.idx = i;
});
@@ -424,7 +444,7 @@ async function runBenchmarks () {
// Memory usage analysis
console.log("\n📊 Memory Usage Analysis");
- console.log("=" .repeat(50));
+ console.log("=".repeat(50));
const memoryResults = {};
const testSize = 1000;
@@ -443,7 +463,7 @@ async function runBenchmarks () {
memoryResults[name] = {
totalMemory: afterMem.heapUsed - beforeMem.heapUsed,
memoryPerItem: memoryPerItem,
- bundleSize: bundleSizes[name.split("-")[0]] || "N/A"
+ bundleSize: bundleSizes[name.split("-")[0]] || "N/A",
};
}
@@ -464,41 +484,47 @@ async function runBenchmarks () {
// Performance summary
console.log("\n📊 Performance Summary");
- console.log("=" .repeat(50));
+ console.log("=".repeat(50));
- const setResults = setBench.tasks.map(task => ({
+ const setResults = setBench.tasks.map((task) => ({
name: task.name,
- opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0
+ opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0,
}));
- const getResults = getBench.tasks.map(task => ({
+ const getResults = getBench.tasks.map((task) => ({
name: task.name,
- opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0
+ opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0,
}));
- const updateResults = updateBench.tasks.map(task => ({
+ const updateResults = updateBench.tasks.map((task) => ({
name: task.name,
- opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0
+ opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0,
}));
- const deleteResults = deleteBench.tasks.map(task => ({
+ const deleteResults = deleteBench.tasks.map((task) => ({
name: task.name,
- opsPerSec: task.result?.hz ? Math.round(task.result.hz) : 0
+ 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("├─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤");
+ 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));
+ 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);
@@ -510,14 +536,16 @@ async function runBenchmarks () {
}
});
- console.log("└─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┘");
+ 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 => {
+process.on("unhandledRejection", (error) => {
console.error("Unhandled promise rejection:", error);
process.exit(1);
});
diff --git a/benchmarks/modern-benchmark.js b/benchmarks/modern-benchmark.js
index ed06560..da209c7 100644
--- a/benchmarks/modern-benchmark.js
+++ b/benchmarks/modern-benchmark.js
@@ -6,7 +6,7 @@ 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
+ iterations: 100, // Minimum iterations for statistical significance
};
// Utility functions for generating test data
@@ -16,28 +16,30 @@ const ITERATIONS = {
* @param {number} size - Number of items
* @returns {Array<{key:string,value:string}>}
*/
-function generateRandomData (size) {
- const data = new Array(size);
+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;
+ 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}`
+ value: `value_${i}_${n}${n}`,
};
}
return data;
}
-function generateSequentialData (size) {
- const data = new Array(size);
+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}`
+ value: `seq_value_${i}`,
};
}
@@ -51,21 +53,23 @@ function generateSequentialData (size) {
* @param {number} modulo - Upper bound (exclusive)
* @returns {Uint32Array}
*/
-function generateAccessPattern (length, modulo) {
+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;
+ 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) {
+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);
@@ -73,9 +77,9 @@ function prepopulateCache (cache, data, fillRatio = 0.8) {
}
// Benchmark suites
-async function runSetOperationsBenchmarks () {
+async function runSetOperationsBenchmarks() {
console.log("\n📝 SET Operations Benchmarks");
- console.log("=" .repeat(50));
+ console.log("=".repeat(50));
for (const cacheSize of CACHE_SIZES) {
const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
@@ -123,9 +127,9 @@ async function runSetOperationsBenchmarks () {
}
}
-async function runGetOperationsBenchmarks () {
+async function runGetOperationsBenchmarks() {
console.log("\n🔍 GET Operations Benchmarks");
- console.log("=" .repeat(50));
+ console.log("=".repeat(50));
for (const cacheSize of CACHE_SIZES) {
const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
@@ -143,8 +147,10 @@ async function runGetOperationsBenchmarks () {
prepopulateCache(randomCache, randomData);
prepopulateCache(sequentialCache, sequentialData);
- prepopulateCache(mixedCache, [...randomData.slice(0, Math.floor(workloadSize / 2)),
- ...sequentialData.slice(0, Math.floor(workloadSize / 2))]);
+ 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);
@@ -182,9 +188,9 @@ async function runGetOperationsBenchmarks () {
}
}
-async function runMixedOperationsBenchmarks () {
+async function runMixedOperationsBenchmarks() {
console.log("\n🔄 Mixed Operations Benchmarks (Real-world scenarios)");
- console.log("=" .repeat(60));
+ console.log("=".repeat(60));
for (const cacheSize of CACHE_SIZES) {
const workloadSize = WORKLOAD_SIZES[CACHE_SIZES.indexOf(cacheSize)];
@@ -250,9 +256,9 @@ async function runMixedOperationsBenchmarks () {
}
}
-async function runSpecialOperationsBenchmarks () {
+async function runSpecialOperationsBenchmarks() {
console.log("\n⚙️ Special Operations Benchmarks");
- console.log("=" .repeat(50));
+ console.log("=".repeat(50));
const cacheSize = 1000;
const workloadSize = 500;
@@ -263,10 +269,10 @@ async function runSpecialOperationsBenchmarks () {
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) => ({
+ 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}}
+ value: { id: i, data: `object_data_${i}`, nested: { prop: i } },
}));
bench
@@ -312,9 +318,9 @@ async function runSpecialOperationsBenchmarks () {
}
// Memory usage benchmarks
-async function runMemoryBenchmarks () {
+async function runMemoryBenchmarks() {
console.log("\n🧠 Memory Usage Analysis");
- console.log("=" .repeat(40));
+ console.log("=".repeat(40));
const testSizes = [100, 1000, 10000];
@@ -331,7 +337,7 @@ async function runMemoryBenchmarks () {
const memBefore = process.memoryUsage();
// Fill cache
- testData.forEach(item => cache.set(item.key, item.value));
+ testData.forEach((item) => cache.set(item.key, item.value));
// Memory after
if (global.gc) {
@@ -349,7 +355,7 @@ async function runMemoryBenchmarks () {
}
// Main execution
-async function runAllBenchmarks () {
+async function runAllBenchmarks() {
console.log("🚀 Tiny-LRU Modern Benchmark Suite");
console.log("==================================");
console.log(`Node.js version: ${process.version}`);
@@ -370,7 +376,6 @@ async function runAllBenchmarks () {
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);
@@ -388,6 +393,5 @@ export {
runGetOperationsBenchmarks,
runMixedOperationsBenchmarks,
runSpecialOperationsBenchmarks,
- runMemoryBenchmarks
+ runMemoryBenchmarks,
};
-
diff --git a/benchmarks/performance-observer-benchmark.js b/benchmarks/performance-observer-benchmark.js
index 9929990..96bc7d4 100644
--- a/benchmarks/performance-observer-benchmark.js
+++ b/benchmarks/performance-observer-benchmark.js
@@ -3,11 +3,11 @@ import { lru } from "../dist/tiny-lru.js";
// Custom high-resolution timer benchmark (alternative approach)
class CustomTimer {
- constructor () {
+ constructor() {
this.results = new Map();
}
- async timeFunction (name, fn, iterations = 1000) {
+ async timeFunction(name, fn, iterations = 1000) {
const times = [];
// Warmup
@@ -43,42 +43,44 @@ class CustomTimer {
maxTime,
median,
stdDev,
- opsPerSec: 1000 / avgTime // Convert ms to ops/sec
+ opsPerSec: 1000 / avgTime, // Convert ms to ops/sec
});
}
- printResults () {
+ 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)
- })));
+ 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);
+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)}`
+ value: `value_${i}_${"x".repeat(50)}`,
};
}
return out;
}
-async function runPerformanceBenchmarks () {
+async function runPerformanceBenchmarks() {
console.log("🔬 LRU Performance Benchmarks");
console.log("==============================");
console.log("(Using CustomTimer for high-resolution function timing)");
@@ -94,11 +96,15 @@ async function runPerformanceBenchmarks () {
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);
+ 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");
@@ -123,64 +129,100 @@ async function runPerformanceBenchmarks () {
}
let mixedGetIndex = 0;
- await timer.timeFunction("lru.get", () => {
- const idx = getIndices[mixedGetIndex % iterations];
- phase2Cache.get(testData[idx].key);
- mixedGetIndex++;
- }, iterations);
+ 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);
+ 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);
+ 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);
+ await timer.timeFunction(
+ "lru.keys",
+ () => {
+ phase2Cache.keys();
+ },
+ iterations,
+ );
// values()
- await timer.timeFunction("lru.values", () => {
- phase2Cache.values();
- }, iterations);
+ await timer.timeFunction(
+ "lru.values",
+ () => {
+ phase2Cache.values();
+ },
+ iterations,
+ );
// entries()
- await timer.timeFunction("lru.entries", () => {
- phase2Cache.entries();
- }, iterations);
+ 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);
+ 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);
+ 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);
+ 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");
@@ -190,26 +232,34 @@ async function runPerformanceBenchmarks () {
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);
+ 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);
+ await timer.timeFunction(
+ "lru.expiresAt",
+ () => {
+ expiresCache.expiresAt(expiresKey);
+ },
+ iterations,
+ );
timer.printResults();
}
// Comparison with different cache sizes
-async function runScalabilityTest () {
+async function runScalabilityTest() {
console.log("\n📈 Scalability Test");
console.log("===================");
@@ -223,7 +273,7 @@ async function runScalabilityTest () {
// Test set performance
const cache = lru(size);
const setStart = performance.now();
- testData.forEach(item => cache.set(item.key, item.value));
+ testData.forEach((item) => cache.set(item.key, item.value));
const setEnd = performance.now();
const setTime = setEnd - setStart;
@@ -237,11 +287,11 @@ async function runScalabilityTest () {
const getTime = getEnd - getStart;
results.push({
- "Size": size,
+ 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)
+ "Get Per Item (ms)": (getTime / 1000).toFixed(4),
});
}
@@ -249,7 +299,7 @@ async function runScalabilityTest () {
}
// Main execution
-async function runAllPerformanceTests () {
+async function runAllPerformanceTests() {
console.log("🔬 Node.js Performance API Benchmarks");
console.log("======================================");
console.log(`Node.js version: ${process.version}`);
@@ -262,9 +312,10 @@ async function runAllPerformanceTests () {
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(
+ "- 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);
@@ -276,9 +327,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
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..b235dc8
--- /dev/null
+++ b/coverage.txt
@@ -0,0 +1,10 @@
+ℹ start of coverage report
+ℹ ----------------------------------------------------------
+ℹ file | line % | branch % | funcs % | uncovered lines
+ℹ ----------------------------------------------------------
+ℹ src | | | |
+ℹ lru.js | 100.00 | 97.30 | 100.00 |
+ℹ ----------------------------------------------------------
+ℹ all files | 100.00 | 97.30 | 100.00 |
+ℹ ----------------------------------------------------------
+ℹ end of coverage report
diff --git a/dist/tiny-lru.cjs b/dist/tiny-lru.cjs
index 7baf605..c9b5fae 100644
--- a/dist/tiny-lru.cjs
+++ b/dist/tiny-lru.cjs
@@ -13,17 +13,6 @@
* removing the least recently used items first. All core operations (get, set, delete) are O(1).
*
* @class LRU
- * @example
- * // Create a cache with max 100 items
- * const cache = new LRU(100);
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- *
- * @example
- * // Create a cache with TTL
- * const cache = new LRU(100, 5000); // 5 second TTL
- * cache.set('key1', 'value1');
- * // After 5 seconds, key1 will be expired
*/
class LRU {
/**
@@ -33,13 +22,9 @@ class LRU {
* @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
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when updating existing items via set().
*/
- constructor (max = 0, ttl = 0, resetTtl = false) {
+ constructor(max = 0, ttl = 0, resetTtl = false) {
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -52,15 +37,9 @@ class LRU {
/**
* 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 () {
+ clear() {
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -72,40 +51,20 @@ class LRU {
/**
* 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(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;
- }
+ this.#unlink(item);
- if (this.last === item) {
- this.last = item.prev;
- }
+ item.prev = null;
+ item.next = null;
}
return this;
@@ -113,25 +72,22 @@ class LRU {
/**
* Returns an array of [key, value] pairs for the specified keys.
- * Order follows LRU order (least to most recently used).
+ * When no keys provided, returns all entries in LRU order.
+ * When keys provided, order matches the input array.
*
- * @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
+ * @returns {Array>} Array of [key, value] pairs.
*/
- entries (keys = this.keys()) {
- const result = new Array(keys.length);
+ 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];
- result[i] = [key, this.get(key)];
+ const item = this.items[key];
+ result[i] = [key, item !== undefined ? item.value : undefined];
}
return result;
@@ -140,75 +96,47 @@ class LRU {
/**
* 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;
+ evict() {
+ if (this.size === 0) {
+ return this;
+ }
- delete this.items[item.key];
+ const item = this.first;
- if (--this.size === 0) {
- this.first = null;
- this.last = null;
- } else {
- this.first = item.next;
- this.first.prev = null;
- }
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.#unlink(item);
}
+ 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) {
- let result;
-
- if (this.has(key)) {
- result = this.items[key].expiry;
- }
-
- return result;
+ 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) {
+ get(key) {
const item = this.items[key];
if (item !== undefined) {
@@ -233,40 +161,22 @@ class LRU {
/**
* 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;
+ 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.
+ * Unlinks an item from the doubly-linked list.
+ * Updates first/last pointers if needed.
+ * Does NOT clear the item's prev/next pointers or delete from items map.
*
- * @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
+ #unlink(item) {
if (item.prev !== null) {
item.prev.next = item.next;
}
@@ -275,43 +185,43 @@ class LRU {
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 === item) {
+ this.last = item.prev;
+ }
+ }
- if (this.last !== null) {
- this.last.next = item;
+ /**
+ * 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.
+ *
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ */
+ moveToEnd(item) {
+ if (this.last === item) {
+ return;
}
- this.last = item;
+ this.#unlink(item);
- // Handle edge case: if this was the only item, it's also first
- if (this.first === null) {
- this.first = item;
- }
+ 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 = new Array(this.size);
+ keys() {
+ const result = Array.from({ length: this.size });
let x = this.first;
let i = 0;
@@ -326,37 +236,36 @@ class LRU {
/**
* 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
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry}, or null.
*/
- setWithEvicted (key, value, resetTtl = this.resetTtl) {
+ setWithEvicted(key, value) {
let evicted = null;
+ let item = this.items[key];
- if (this.has(key)) {
- this.set(key, value, true, resetTtl);
+ if (item !== undefined) {
+ item.value = value;
+ if (this.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 = {...this.first};
- this.evict(true);
+ evicted = {
+ key: this.first.key,
+ value: this.first.value,
+ expiry: this.first.expiry,
+ };
+ this.evict();
}
- let item = this.items[key] = {
+ item = this.items[key] = {
expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
key: key,
prev: this.last,
next: null,
- value
+ value,
};
if (++this.size === 1) {
@@ -374,38 +283,24 @@ class LRU {
/**
* 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) {
+ set(key, value) {
let item = this.items[key];
- if (bypass || item !== undefined) {
- // Existing item: update value and position
+ if (item !== undefined) {
item.value = value;
- if (bypass === false && resetTtl) {
+ if (this.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);
+ this.evict();
}
item = this.items[key] = {
@@ -413,7 +308,7 @@ class LRU {
key: key,
prev: this.last,
next: null,
- value
+ value,
};
if (++this.size === 1) {
@@ -430,24 +325,21 @@ class LRU {
/**
* Returns an array of all values in the cache for the specified keys.
- * Order follows LRU order (least to most recently used).
+ * When no keys provided, returns all values in LRU order.
+ * When keys provided, order matches the input array.
*
- * @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
+ * @returns {Array<*>} Array of values corresponding to the keys.
*/
- values (keys = this.keys()) {
- const result = new Array(keys.length);
+ values(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
for (let i = 0; i < keys.length; i++) {
- result[i] = this.get(keys[i]);
+ const item = this.items[keys[i]];
+ result[i] = item !== undefined ? item.value : undefined;
}
return result;
@@ -463,22 +355,8 @@ class LRU {
* @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
* @returns {LRU} A new LRU cache instance.
* @throws {TypeError} When parameters are invalid (negative numbers or wrong types).
- * @example
- * // Create cache with factory function
- * const cache = lru(100, 5000, true);
- * cache.set('key', 'value');
- *
- * @example
- * // Error handling
- * try {
- * const cache = lru(-1); // Invalid max
- * } catch (error) {
- * console.error(error.message); // "Invalid max value"
- * }
- * @see {@link LRU}
- * @since 1.0.0
*/
-function lru (max = 1000, ttl = 0, resetTtl = false) {
+function lru(max = 1000, ttl = 0, resetTtl = false) {
if (isNaN(max) || max < 0) {
throw new TypeError("Invalid max value");
}
diff --git a/dist/tiny-lru.js b/dist/tiny-lru.js
index cdec409..9d8e36e 100644
--- a/dist/tiny-lru.js
+++ b/dist/tiny-lru.js
@@ -11,17 +11,6 @@
* removing the least recently used items first. All core operations (get, set, delete) are O(1).
*
* @class LRU
- * @example
- * // Create a cache with max 100 items
- * const cache = new LRU(100);
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- *
- * @example
- * // Create a cache with TTL
- * const cache = new LRU(100, 5000); // 5 second TTL
- * cache.set('key1', 'value1');
- * // After 5 seconds, key1 will be expired
*/
class LRU {
/**
@@ -31,13 +20,9 @@ class LRU {
* @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
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when updating existing items via set().
*/
- constructor (max = 0, ttl = 0, resetTtl = false) {
+ constructor(max = 0, ttl = 0, resetTtl = false) {
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -50,15 +35,9 @@ class LRU {
/**
* 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 () {
+ clear() {
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -70,40 +49,20 @@ class LRU {
/**
* 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(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;
- }
+ this.#unlink(item);
- if (this.last === item) {
- this.last = item.prev;
- }
+ item.prev = null;
+ item.next = null;
}
return this;
@@ -111,25 +70,22 @@ class LRU {
/**
* Returns an array of [key, value] pairs for the specified keys.
- * Order follows LRU order (least to most recently used).
+ * When no keys provided, returns all entries in LRU order.
+ * When keys provided, order matches the input array.
*
- * @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
+ * @returns {Array>} Array of [key, value] pairs.
*/
- entries (keys = this.keys()) {
- const result = new Array(keys.length);
+ 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];
- result[i] = [key, this.get(key)];
+ const item = this.items[key];
+ result[i] = [key, item !== undefined ? item.value : undefined];
}
return result;
@@ -138,75 +94,47 @@ class LRU {
/**
* 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;
+ evict() {
+ if (this.size === 0) {
+ return this;
+ }
- delete this.items[item.key];
+ const item = this.first;
- if (--this.size === 0) {
- this.first = null;
- this.last = null;
- } else {
- this.first = item.next;
- this.first.prev = null;
- }
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.#unlink(item);
}
+ 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) {
- let result;
-
- if (this.has(key)) {
- result = this.items[key].expiry;
- }
-
- return result;
+ 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) {
+ get(key) {
const item = this.items[key];
if (item !== undefined) {
@@ -231,40 +159,22 @@ class LRU {
/**
* 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;
+ 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.
+ * Unlinks an item from the doubly-linked list.
+ * Updates first/last pointers if needed.
+ * Does NOT clear the item's prev/next pointers or delete from items map.
*
- * @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
+ #unlink(item) {
if (item.prev !== null) {
item.prev.next = item.next;
}
@@ -273,43 +183,43 @@ class LRU {
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 === item) {
+ this.last = item.prev;
+ }
+ }
- if (this.last !== null) {
- this.last.next = item;
+ /**
+ * 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.
+ *
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ */
+ moveToEnd(item) {
+ if (this.last === item) {
+ return;
}
- this.last = item;
+ this.#unlink(item);
- // Handle edge case: if this was the only item, it's also first
- if (this.first === null) {
- this.first = item;
- }
+ 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 = new Array(this.size);
+ keys() {
+ const result = Array.from({ length: this.size });
let x = this.first;
let i = 0;
@@ -324,37 +234,36 @@ class LRU {
/**
* 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
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry}, or null.
*/
- setWithEvicted (key, value, resetTtl = this.resetTtl) {
+ setWithEvicted(key, value) {
let evicted = null;
+ let item = this.items[key];
- if (this.has(key)) {
- this.set(key, value, true, resetTtl);
+ if (item !== undefined) {
+ item.value = value;
+ if (this.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 = {...this.first};
- this.evict(true);
+ evicted = {
+ key: this.first.key,
+ value: this.first.value,
+ expiry: this.first.expiry,
+ };
+ this.evict();
}
- let item = this.items[key] = {
+ item = this.items[key] = {
expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
key: key,
prev: this.last,
next: null,
- value
+ value,
};
if (++this.size === 1) {
@@ -372,38 +281,24 @@ class LRU {
/**
* 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) {
+ set(key, value) {
let item = this.items[key];
- if (bypass || item !== undefined) {
- // Existing item: update value and position
+ if (item !== undefined) {
item.value = value;
- if (bypass === false && resetTtl) {
+ if (this.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);
+ this.evict();
}
item = this.items[key] = {
@@ -411,7 +306,7 @@ class LRU {
key: key,
prev: this.last,
next: null,
- value
+ value,
};
if (++this.size === 1) {
@@ -428,24 +323,21 @@ class LRU {
/**
* Returns an array of all values in the cache for the specified keys.
- * Order follows LRU order (least to most recently used).
+ * When no keys provided, returns all values in LRU order.
+ * When keys provided, order matches the input array.
*
- * @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
+ * @returns {Array<*>} Array of values corresponding to the keys.
*/
- values (keys = this.keys()) {
- const result = new Array(keys.length);
+ values(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
for (let i = 0; i < keys.length; i++) {
- result[i] = this.get(keys[i]);
+ const item = this.items[keys[i]];
+ result[i] = item !== undefined ? item.value : undefined;
}
return result;
@@ -461,22 +353,8 @@ class LRU {
* @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
* @returns {LRU} A new LRU cache instance.
* @throws {TypeError} When parameters are invalid (negative numbers or wrong types).
- * @example
- * // Create cache with factory function
- * const cache = lru(100, 5000, true);
- * cache.set('key', 'value');
- *
- * @example
- * // Error handling
- * try {
- * const cache = lru(-1); // Invalid max
- * } catch (error) {
- * console.error(error.message); // "Invalid max value"
- * }
- * @see {@link LRU}
- * @since 1.0.0
*/
-function lru (max = 1000, ttl = 0, resetTtl = false) {
+function lru(max = 1000, ttl = 0, resetTtl = false) {
if (isNaN(max) || max < 0) {
throw new TypeError("Invalid max value");
}
diff --git a/dist/tiny-lru.min.js b/dist/tiny-lru.min.js
index 63f8d9a..bbcf3aa 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&&i.expiry<=Date.now()?void this.delete(t):(this.moveToEnd(i),i.value)}has(t){const i=this.items[t];return void 0!==i&&(0===this.ttl||i.expiry>Date.now())}#t(t){null!==t.prev&&(t.prev.next=t.next),null!==t.next&&(t.next.prev=t.prev),this.first===t&&(this.first=t.next),this.last===t&&(this.last=t.prev)}moveToEnd(t){this.last!==t&&(this.#t(t),t.prev=this.last,t.next=null,this.last.next=t,this.last=t)}keys(){const t=Array.from({length:this.size});let i=this.first,s=0;for(;null!==i;)t[s++]=i.key,i=i.next;return t}setWithEvicted(t,i){let s=null,e=this.items[t];return void 0!==e?(e.value=i,this.resetTtl&&(e.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(e)):(this.max>0&&this.size===this.max&&(s={key:this.first.key,value:this.first.value,expiry:this.first.expiry},this.evict()),e=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:i},1==++this.size?this.first=e:this.last.next=e,this.last=e),s}set(t,i){let s=this.items[t];return void 0!==s?(s.value=i,this.resetTtl&&(s.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(s)):(this.max>0&&this.size===this.max&&this.evict(),s=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:i},1==++this.size?this.first=s:this.last.next=s,this.last=s),this}values(t){void 0===t&&(t=this.keys());const i=Array.from({length:t.length});for(let s=0;s>} 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 */\nexport class LRU {\n\t/**\n\t * Creates a new LRU cache instance.\n\t * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.\n\t *\n\t * @constructor\n\t * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.\n\t * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.\n\t * @param {boolean} [resetTtl=false] - Whether to reset TTL when updating existing items via set().\n\t */\n\tconstructor(max = 0, ttl = 0, resetTtl = false) {\n\t\tthis.first = null;\n\t\tthis.items = Object.create(null);\n\t\tthis.last = null;\n\t\tthis.max = max;\n\t\tthis.resetTtl = resetTtl;\n\t\tthis.size = 0;\n\t\tthis.ttl = ttl;\n\t}\n\n\t/**\n\t * Removes all items from the cache.\n\t *\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tclear() {\n\t\tthis.first = null;\n\t\tthis.items = Object.create(null);\n\t\tthis.last = null;\n\t\tthis.size = 0;\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes an item from the cache by key.\n\t *\n\t * @param {string} key - The key of the item to delete.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tdelete(key) {\n\t\tconst item = this.items[key];\n\n\t\tif (item !== undefined) {\n\t\t\tdelete this.items[key];\n\t\t\tthis.size--;\n\n\t\t\tthis.#unlink(item);\n\n\t\t\titem.prev = null;\n\t\t\titem.next = null;\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns an array of [key, value] pairs for the specified keys.\n\t * When no keys provided, returns all entries in LRU order.\n\t * When keys provided, order matches the input array.\n\t *\n\t * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.\n\t * @returns {Array>} Array of [key, value] pairs.\n\t */\n\tentries(keys) {\n\t\tif (keys === undefined) {\n\t\t\tkeys = this.keys();\n\t\t}\n\n\t\tconst result = Array.from({ length: keys.length });\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst key = keys[i];\n\t\t\tconst item = this.items[key];\n\t\t\tresult[i] = [key, item !== undefined ? item.value : undefined];\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 * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tevict() {\n\t\tif (this.size === 0) {\n\t\t\treturn this;\n\t\t}\n\n\t\tconst item = this.first;\n\n\t\tdelete this.items[item.key];\n\n\t\tif (--this.size === 0) {\n\t\t\tthis.first = null;\n\t\t\tthis.last = null;\n\t\t} else {\n\t\t\tthis.#unlink(item);\n\t\t}\n\n\t\titem.next = null;\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns the expiration timestamp for a given key.\n\t *\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 */\n\texpiresAt(key) {\n\t\tconst item = this.items[key];\n\t\treturn item !== undefined ? item.expiry : undefined;\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 * @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 */\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 * @param {string} key - The key to check for.\n\t * @returns {boolean} True if the key exists, false otherwise.\n\t */\n\thas(key) {\n\t\tconst item = this.items[key];\n\t\treturn item !== undefined && (this.ttl === 0 || item.expiry > Date.now());\n\t}\n\n\t/**\n\t * Unlinks an item from the doubly-linked list.\n\t * Updates first/last pointers if needed.\n\t * Does NOT clear the item's prev/next pointers or delete from items map.\n\t *\n\t * @private\n\t */\n\t#unlink(item) {\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\tif (this.first === item) {\n\t\t\tthis.first = item.next;\n\t\t}\n\n\t\tif (this.last === item) {\n\t\t\tthis.last = item.prev;\n\t\t}\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 * @param {Object} item - The cache item with prev/next pointers to reposition.\n\t * @private\n\t */\n\tmoveToEnd(item) {\n\t\tif (this.last === item) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#unlink(item);\n\n\t\titem.prev = this.last;\n\t\titem.next = null;\n\t\tthis.last.next = item;\n\t\tthis.last = item;\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 * @returns {string[]} Array of keys in LRU order.\n\t */\n\tkeys() {\n\t\tconst result = Array.from({ length: 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 * @param {string} key - The key to set.\n\t * @param {*} value - The value to store.\n\t * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry}, or null.\n\t */\n\tsetWithEvicted(key, value) {\n\t\tlet evicted = null;\n\t\tlet item = this.items[key];\n\n\t\tif (item !== undefined) {\n\t\t\titem.value = value;\n\t\t\tif (this.resetTtl) {\n\t\t\t\titem.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n\t\t\t}\n\t\t\tthis.moveToEnd(item);\n\t\t} else {\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tevicted = {\n\t\t\t\t\tkey: this.first.key,\n\t\t\t\t\tvalue: this.first.value,\n\t\t\t\t\texpiry: this.first.expiry,\n\t\t\t\t};\n\t\t\t\tthis.evict();\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 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 * @param {string} key - The key to set.\n\t * @param {*} value - The value to store.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tset(key, value) {\n\t\tlet item = this.items[key];\n\n\t\tif (item !== undefined) {\n\t\t\titem.value = value;\n\n\t\t\tif (this.resetTtl) {\n\t\t\t\titem.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n\t\t\t}\n\n\t\t\tthis.moveToEnd(item);\n\t\t} else {\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tthis.evict();\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 * When no keys provided, returns all values in LRU order.\n\t * When keys provided, order matches the input array.\n\t *\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.\n\t */\n\tvalues(keys) {\n\t\tif (keys === undefined) {\n\t\t\tkeys = this.keys();\n\t\t}\n\n\t\tconst result = Array.from({ length: keys.length });\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst item = this.items[keys[i]];\n\t\t\tresult[i] = item !== undefined ? item.value : undefined;\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 */\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","item","undefined","unlink","prev","next","entries","keys","result","Array","from","length","i","value","evict","expiresAt","expiry","get","Date","now","delete","moveToEnd","has","x","setWithEvicted","evicted","set","values","lru","isNaN","TypeError"],"mappings":";;;;AAOO,MAAMA,EAUZ,WAAAC,CAAYC,EAAM,EAAGC,EAAM,EAAGC,GAAW,GACxCC,KAAKC,MAAQ,KACbD,KAAKE,MAAQC,OAAOC,OAAO,MAC3BJ,KAAKK,KAAO,KACZL,KAAKH,IAAMA,EACXG,KAAKD,SAAWA,EAChBC,KAAKM,KAAO,EACZN,KAAKF,IAAMA,CACZ,CAOA,KAAAS,GAMC,OALAP,KAAKC,MAAQ,KACbD,KAAKE,MAAQC,OAAOC,OAAO,MAC3BJ,KAAKK,KAAO,KACZL,KAAKM,KAAO,EAELN,IACR,CAQA,OAAOQ,GACN,MAAMC,EAAOT,KAAKE,MAAMM,GAYxB,YAVaE,IAATD,WACIT,KAAKE,MAAMM,GAClBR,KAAKM,OAELN,MAAKW,EAAQF,GAEbA,EAAKG,KAAO,KACZH,EAAKI,KAAO,MAGNb,IACR,CAUA,OAAAc,CAAQC,QACML,IAATK,IACHA,EAAOf,KAAKe,QAGb,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQJ,EAAKI,SACzC,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAKI,OAAQC,IAAK,CACrC,MAAMZ,EAAMO,EAAKK,GACXX,EAAOT,KAAKE,MAAMM,GACxBQ,EAAOI,GAAK,CAACZ,OAAcE,IAATD,EAAqBA,EAAKY,WAAQX,EACrD,CAEA,OAAOM,CACR,CAOA,KAAAM,GACC,GAAkB,IAAdtB,KAAKM,KACR,OAAON,KAGR,MAAMS,EAAOT,KAAKC,MAalB,cAXOD,KAAKE,MAAMO,EAAKD,KAEH,KAAdR,KAAKM,MACVN,KAAKC,MAAQ,KACbD,KAAKK,KAAO,MAEZL,MAAKW,EAAQF,GAGdA,EAAKI,KAAO,KAELb,IACR,CAQA,SAAAuB,CAAUf,GACT,MAAMC,EAAOT,KAAKE,MAAMM,GACxB,YAAgBE,IAATD,EAAqBA,EAAKe,YAASd,CAC3C,CAQA,GAAAe,CAAIjB,GACH,MAAMC,EAAOT,KAAKE,MAAMM,GAExB,QAAaE,IAATD,EAEH,OAAIT,KAAKF,IAAM,GACVW,EAAKe,QAAUE,KAAKC,WACvB3B,KAAK4B,OAAOpB,IAOdR,KAAK6B,UAAUpB,GAERA,EAAKY,MAId,CAQA,GAAAS,CAAItB,GACH,MAAMC,EAAOT,KAAKE,MAAMM,GACxB,YAAgBE,IAATD,IAAoC,IAAbT,KAAKF,KAAaW,EAAKe,OAASE,KAAKC,MACpE,CASA,EAAAhB,CAAQF,GACW,OAAdA,EAAKG,OACRH,EAAKG,KAAKC,KAAOJ,EAAKI,MAGL,OAAdJ,EAAKI,OACRJ,EAAKI,KAAKD,KAAOH,EAAKG,MAGnBZ,KAAKC,QAAUQ,IAClBT,KAAKC,MAAQQ,EAAKI,MAGfb,KAAKK,OAASI,IACjBT,KAAKK,KAAOI,EAAKG,KAEnB,CAUA,SAAAiB,CAAUpB,GACLT,KAAKK,OAASI,IAIlBT,MAAKW,EAAQF,GAEbA,EAAKG,KAAOZ,KAAKK,KACjBI,EAAKI,KAAO,KACZb,KAAKK,KAAKQ,KAAOJ,EACjBT,KAAKK,KAAOI,EACb,CAOA,IAAAM,GACC,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQnB,KAAKM,OACzC,IAAIyB,EAAI/B,KAAKC,MACTmB,EAAI,EAER,KAAa,OAANW,GACNf,EAAOI,KAAOW,EAAEvB,IAChBuB,EAAIA,EAAElB,KAGP,OAAOG,CACR,CASA,cAAAgB,CAAexB,EAAKa,GACnB,IAAIY,EAAU,KACVxB,EAAOT,KAAKE,MAAMM,GAmCtB,YAjCaE,IAATD,GACHA,EAAKY,MAAQA,EACTrB,KAAKD,WACRU,EAAKe,OAASxB,KAAKF,IAAM,EAAI4B,KAAKC,MAAQ3B,KAAKF,IAAME,KAAKF,KAE3DE,KAAK6B,UAAUpB,KAEXT,KAAKH,IAAM,GAAKG,KAAKM,OAASN,KAAKH,MACtCoC,EAAU,CACTzB,IAAKR,KAAKC,MAAMO,IAChBa,MAAOrB,KAAKC,MAAMoB,MAClBG,OAAQxB,KAAKC,MAAMuB,QAEpBxB,KAAKsB,SAGNb,EAAOT,KAAKE,MAAMM,GAAO,CACxBgB,OAAQxB,KAAKF,IAAM,EAAI4B,KAAKC,MAAQ3B,KAAKF,IAAME,KAAKF,IACpDU,IAAKA,EACLI,KAAMZ,KAAKK,KACXQ,KAAM,KACNQ,SAGmB,KAAdrB,KAAKM,KACVN,KAAKC,MAAQQ,EAEbT,KAAKK,KAAKQ,KAAOJ,EAGlBT,KAAKK,KAAOI,GAGNwB,CACR,CASA,GAAAC,CAAI1B,EAAKa,GACR,IAAIZ,EAAOT,KAAKE,MAAMM,GAgCtB,YA9BaE,IAATD,GACHA,EAAKY,MAAQA,EAETrB,KAAKD,WACRU,EAAKe,OAASxB,KAAKF,IAAM,EAAI4B,KAAKC,MAAQ3B,KAAKF,IAAME,KAAKF,KAG3DE,KAAK6B,UAAUpB,KAEXT,KAAKH,IAAM,GAAKG,KAAKM,OAASN,KAAKH,KACtCG,KAAKsB,QAGNb,EAAOT,KAAKE,MAAMM,GAAO,CACxBgB,OAAQxB,KAAKF,IAAM,EAAI4B,KAAKC,MAAQ3B,KAAKF,IAAME,KAAKF,IACpDU,IAAKA,EACLI,KAAMZ,KAAKK,KACXQ,KAAM,KACNQ,SAGmB,KAAdrB,KAAKM,KACVN,KAAKC,MAAQQ,EAEbT,KAAKK,KAAKQ,KAAOJ,EAGlBT,KAAKK,KAAOI,GAGNT,IACR,CAUA,MAAAmC,CAAOpB,QACOL,IAATK,IACHA,EAAOf,KAAKe,QAGb,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQJ,EAAKI,SACzC,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAKI,OAAQC,IAAK,CACrC,MAAMX,EAAOT,KAAKE,MAAMa,EAAKK,IAC7BJ,EAAOI,QAAcV,IAATD,EAAqBA,EAAKY,WAAQX,CAC/C,CAEA,OAAOM,CACR,EAaM,SAASoB,EAAIvC,EAAM,IAAMC,EAAM,EAAGC,GAAW,GACnD,GAAIsC,MAAMxC,IAAQA,EAAM,EACvB,MAAM,IAAIyC,UAAU,qBAGrB,GAAID,MAAMvC,IAAQA,EAAM,EACvB,MAAM,IAAIwC,UAAU,qBAGrB,GAAwB,kBAAbvC,EACV,MAAM,IAAIuC,UAAU,0BAGrB,OAAO,IAAI3C,EAAIE,EAAKC,EAAKC,EAC1B,QAAAJ,SAAAyC"}
\ No newline at end of file
diff --git a/dist/tiny-lru.umd.js b/dist/tiny-lru.umd.js
index 9222cf9..a326af6 100644
--- a/dist/tiny-lru.umd.js
+++ b/dist/tiny-lru.umd.js
@@ -11,17 +11,6 @@
* removing the least recently used items first. All core operations (get, set, delete) are O(1).
*
* @class LRU
- * @example
- * // Create a cache with max 100 items
- * const cache = new LRU(100);
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- *
- * @example
- * // Create a cache with TTL
- * const cache = new LRU(100, 5000); // 5 second TTL
- * cache.set('key1', 'value1');
- * // After 5 seconds, key1 will be expired
*/
class LRU {
/**
@@ -31,13 +20,9 @@ class LRU {
* @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
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when updating existing items via set().
*/
- constructor (max = 0, ttl = 0, resetTtl = false) {
+ constructor(max = 0, ttl = 0, resetTtl = false) {
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -50,15 +35,9 @@ class LRU {
/**
* 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 () {
+ clear() {
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -70,40 +49,20 @@ class LRU {
/**
* 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(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;
- }
+ this.#unlink(item);
- if (this.last === item) {
- this.last = item.prev;
- }
+ item.prev = null;
+ item.next = null;
}
return this;
@@ -111,25 +70,22 @@ class LRU {
/**
* Returns an array of [key, value] pairs for the specified keys.
- * Order follows LRU order (least to most recently used).
+ * When no keys provided, returns all entries in LRU order.
+ * When keys provided, order matches the input array.
*
- * @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
+ * @returns {Array>} Array of [key, value] pairs.
*/
- entries (keys = this.keys()) {
- const result = new Array(keys.length);
+ 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];
- result[i] = [key, this.get(key)];
+ const item = this.items[key];
+ result[i] = [key, item !== undefined ? item.value : undefined];
}
return result;
@@ -138,75 +94,47 @@ class LRU {
/**
* 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;
+ evict() {
+ if (this.size === 0) {
+ return this;
+ }
- delete this.items[item.key];
+ const item = this.first;
- if (--this.size === 0) {
- this.first = null;
- this.last = null;
- } else {
- this.first = item.next;
- this.first.prev = null;
- }
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.#unlink(item);
}
+ 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) {
- let result;
-
- if (this.has(key)) {
- result = this.items[key].expiry;
- }
-
- return result;
+ 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) {
+ get(key) {
const item = this.items[key];
if (item !== undefined) {
@@ -231,40 +159,22 @@ class LRU {
/**
* 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;
+ 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.
+ * Unlinks an item from the doubly-linked list.
+ * Updates first/last pointers if needed.
+ * Does NOT clear the item's prev/next pointers or delete from items map.
*
- * @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
+ #unlink(item) {
if (item.prev !== null) {
item.prev.next = item.next;
}
@@ -273,43 +183,43 @@ class LRU {
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 === item) {
+ this.last = item.prev;
+ }
+ }
- if (this.last !== null) {
- this.last.next = item;
+ /**
+ * 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.
+ *
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ */
+ moveToEnd(item) {
+ if (this.last === item) {
+ return;
}
- this.last = item;
+ this.#unlink(item);
- // Handle edge case: if this was the only item, it's also first
- if (this.first === null) {
- this.first = item;
- }
+ 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 = new Array(this.size);
+ keys() {
+ const result = Array.from({ length: this.size });
let x = this.first;
let i = 0;
@@ -324,37 +234,36 @@ class LRU {
/**
* 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
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry}, or null.
*/
- setWithEvicted (key, value, resetTtl = this.resetTtl) {
+ setWithEvicted(key, value) {
let evicted = null;
+ let item = this.items[key];
- if (this.has(key)) {
- this.set(key, value, true, resetTtl);
+ if (item !== undefined) {
+ item.value = value;
+ if (this.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 = {...this.first};
- this.evict(true);
+ evicted = {
+ key: this.first.key,
+ value: this.first.value,
+ expiry: this.first.expiry,
+ };
+ this.evict();
}
- let item = this.items[key] = {
+ item = this.items[key] = {
expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
key: key,
prev: this.last,
next: null,
- value
+ value,
};
if (++this.size === 1) {
@@ -372,38 +281,24 @@ class LRU {
/**
* 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) {
+ set(key, value) {
let item = this.items[key];
- if (bypass || item !== undefined) {
- // Existing item: update value and position
+ if (item !== undefined) {
item.value = value;
- if (bypass === false && resetTtl) {
+ if (this.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);
+ this.evict();
}
item = this.items[key] = {
@@ -411,7 +306,7 @@ class LRU {
key: key,
prev: this.last,
next: null,
- value
+ value,
};
if (++this.size === 1) {
@@ -428,24 +323,21 @@ class LRU {
/**
* Returns an array of all values in the cache for the specified keys.
- * Order follows LRU order (least to most recently used).
+ * When no keys provided, returns all values in LRU order.
+ * When keys provided, order matches the input array.
*
- * @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
+ * @returns {Array<*>} Array of values corresponding to the keys.
*/
- values (keys = this.keys()) {
- const result = new Array(keys.length);
+ values(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
for (let i = 0; i < keys.length; i++) {
- result[i] = this.get(keys[i]);
+ const item = this.items[keys[i]];
+ result[i] = item !== undefined ? item.value : undefined;
}
return result;
@@ -461,22 +353,8 @@ class LRU {
* @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
* @returns {LRU} A new LRU cache instance.
* @throws {TypeError} When parameters are invalid (negative numbers or wrong types).
- * @example
- * // Create cache with factory function
- * const cache = lru(100, 5000, true);
- * cache.set('key', 'value');
- *
- * @example
- * // Error handling
- * try {
- * const cache = lru(-1); // Invalid max
- * } catch (error) {
- * console.error(error.message); // "Invalid max value"
- * }
- * @see {@link LRU}
- * @since 1.0.0
*/
-function lru (max = 1000, ttl = 0, resetTtl = false) {
+function lru(max = 1000, ttl = 0, resetTtl = false) {
if (isNaN(max) || max < 0) {
throw new TypeError("Invalid max value");
}
diff --git a/dist/tiny-lru.umd.min.js b/dist/tiny-lru.umd.min.js
index 8406124..a96241d 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&&i.expiry<=Date.now()?void this.delete(t):(this.moveToEnd(i),i.value)}has(t){const i=this.items[t];return void 0!==i&&(0===this.ttl||i.expiry>Date.now())}#t(t){null!==t.prev&&(t.prev.next=t.next),null!==t.next&&(t.next.prev=t.prev),this.first===t&&(this.first=t.next),this.last===t&&(this.last=t.prev)}moveToEnd(t){this.last!==t&&(this.#t(t),t.prev=this.last,t.next=null,this.last.next=t,this.last=t)}keys(){const t=Array.from({length:this.size});let i=this.first,e=0;for(;null!==i;)t[e++]=i.key,i=i.next;return t}setWithEvicted(t,i){let e=null,s=this.items[t];return void 0!==s?(s.value=i,this.resetTtl&&(s.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(s)):(this.max>0&&this.size===this.max&&(e={key:this.first.key,value:this.first.value,expiry:this.first.expiry},this.evict()),s=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:i},1==++this.size?this.first=s:this.last.next=s,this.last=s),e}set(t,i){let e=this.items[t];return void 0!==e?(e.value=i,this.resetTtl&&(e.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(e)):(this.max>0&&this.size===this.max&&this.evict(),e=this.items[t]={expiry:this.ttl>0?Date.now()+this.ttl:this.ttl,key:t,prev:this.last,next:null,value:i},1==++this.size?this.first=e:this.last.next=e,this.last=e),this}values(t){void 0===t&&(t=this.keys());const i=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":["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 */\nexport class LRU {\n\t/**\n\t * Creates a new LRU cache instance.\n\t * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.\n\t *\n\t * @constructor\n\t * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.\n\t * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.\n\t * @param {boolean} [resetTtl=false] - Whether to reset TTL when updating existing items via set().\n\t */\n\tconstructor(max = 0, ttl = 0, resetTtl = false) {\n\t\tthis.first = null;\n\t\tthis.items = Object.create(null);\n\t\tthis.last = null;\n\t\tthis.max = max;\n\t\tthis.resetTtl = resetTtl;\n\t\tthis.size = 0;\n\t\tthis.ttl = ttl;\n\t}\n\n\t/**\n\t * Removes all items from the cache.\n\t *\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tclear() {\n\t\tthis.first = null;\n\t\tthis.items = Object.create(null);\n\t\tthis.last = null;\n\t\tthis.size = 0;\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes an item from the cache by key.\n\t *\n\t * @param {string} key - The key of the item to delete.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tdelete(key) {\n\t\tconst item = this.items[key];\n\n\t\tif (item !== undefined) {\n\t\t\tdelete this.items[key];\n\t\t\tthis.size--;\n\n\t\t\tthis.#unlink(item);\n\n\t\t\titem.prev = null;\n\t\t\titem.next = null;\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns an array of [key, value] pairs for the specified keys.\n\t * When no keys provided, returns all entries in LRU order.\n\t * When keys provided, order matches the input array.\n\t *\n\t * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.\n\t * @returns {Array>} Array of [key, value] pairs.\n\t */\n\tentries(keys) {\n\t\tif (keys === undefined) {\n\t\t\tkeys = this.keys();\n\t\t}\n\n\t\tconst result = Array.from({ length: keys.length });\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst key = keys[i];\n\t\t\tconst item = this.items[key];\n\t\t\tresult[i] = [key, item !== undefined ? item.value : undefined];\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 * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tevict() {\n\t\tif (this.size === 0) {\n\t\t\treturn this;\n\t\t}\n\n\t\tconst item = this.first;\n\n\t\tdelete this.items[item.key];\n\n\t\tif (--this.size === 0) {\n\t\t\tthis.first = null;\n\t\t\tthis.last = null;\n\t\t} else {\n\t\t\tthis.#unlink(item);\n\t\t}\n\n\t\titem.next = null;\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns the expiration timestamp for a given key.\n\t *\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 */\n\texpiresAt(key) {\n\t\tconst item = this.items[key];\n\t\treturn item !== undefined ? item.expiry : undefined;\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 * @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 */\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 * @param {string} key - The key to check for.\n\t * @returns {boolean} True if the key exists, false otherwise.\n\t */\n\thas(key) {\n\t\tconst item = this.items[key];\n\t\treturn item !== undefined && (this.ttl === 0 || item.expiry > Date.now());\n\t}\n\n\t/**\n\t * Unlinks an item from the doubly-linked list.\n\t * Updates first/last pointers if needed.\n\t * Does NOT clear the item's prev/next pointers or delete from items map.\n\t *\n\t * @private\n\t */\n\t#unlink(item) {\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\tif (this.first === item) {\n\t\t\tthis.first = item.next;\n\t\t}\n\n\t\tif (this.last === item) {\n\t\t\tthis.last = item.prev;\n\t\t}\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 * @param {Object} item - The cache item with prev/next pointers to reposition.\n\t * @private\n\t */\n\tmoveToEnd(item) {\n\t\tif (this.last === item) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#unlink(item);\n\n\t\titem.prev = this.last;\n\t\titem.next = null;\n\t\tthis.last.next = item;\n\t\tthis.last = item;\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 * @returns {string[]} Array of keys in LRU order.\n\t */\n\tkeys() {\n\t\tconst result = Array.from({ length: 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 * @param {string} key - The key to set.\n\t * @param {*} value - The value to store.\n\t * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry}, or null.\n\t */\n\tsetWithEvicted(key, value) {\n\t\tlet evicted = null;\n\t\tlet item = this.items[key];\n\n\t\tif (item !== undefined) {\n\t\t\titem.value = value;\n\t\t\tif (this.resetTtl) {\n\t\t\t\titem.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n\t\t\t}\n\t\t\tthis.moveToEnd(item);\n\t\t} else {\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tevicted = {\n\t\t\t\t\tkey: this.first.key,\n\t\t\t\t\tvalue: this.first.value,\n\t\t\t\t\texpiry: this.first.expiry,\n\t\t\t\t};\n\t\t\t\tthis.evict();\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 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 * @param {string} key - The key to set.\n\t * @param {*} value - The value to store.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tset(key, value) {\n\t\tlet item = this.items[key];\n\n\t\tif (item !== undefined) {\n\t\t\titem.value = value;\n\n\t\t\tif (this.resetTtl) {\n\t\t\t\titem.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;\n\t\t\t}\n\n\t\t\tthis.moveToEnd(item);\n\t\t} else {\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tthis.evict();\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 * When no keys provided, returns all values in LRU order.\n\t * When keys provided, order matches the input array.\n\t *\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.\n\t */\n\tvalues(keys) {\n\t\tif (keys === undefined) {\n\t\t\tkeys = this.keys();\n\t\t}\n\n\t\tconst result = Array.from({ length: keys.length });\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst item = this.items[keys[i]];\n\t\t\tresult[i] = item !== undefined ? item.value : undefined;\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 */\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","item","undefined","unlink","prev","next","entries","keys","result","Array","from","length","i","value","evict","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,aAOO,MAAMQ,EAUZ,WAAAC,CAAYC,EAAM,EAAGC,EAAM,EAAGC,GAAW,GACxCL,KAAKM,MAAQ,KACbN,KAAKO,MAAQC,OAAOC,OAAO,MAC3BT,KAAKU,KAAO,KACZV,KAAKG,IAAMA,EACXH,KAAKK,SAAWA,EAChBL,KAAKW,KAAO,EACZX,KAAKI,IAAMA,CACZ,CAOA,KAAAQ,GAMC,OALAZ,KAAKM,MAAQ,KACbN,KAAKO,MAAQC,OAAOC,OAAO,MAC3BT,KAAKU,KAAO,KACZV,KAAKW,KAAO,EAELX,IACR,CAQA,OAAOa,GACN,MAAMC,EAAOd,KAAKO,MAAMM,GAYxB,YAVaE,IAATD,WACId,KAAKO,MAAMM,GAClBb,KAAKW,OAELX,MAAKgB,EAAQF,GAEbA,EAAKG,KAAO,KACZH,EAAKI,KAAO,MAGNlB,IACR,CAUA,OAAAmB,CAAQC,QACML,IAATK,IACHA,EAAOpB,KAAKoB,QAGb,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQJ,EAAKI,SACzC,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAKI,OAAQC,IAAK,CACrC,MAAMZ,EAAMO,EAAKK,GACXX,EAAOd,KAAKO,MAAMM,GACxBQ,EAAOI,GAAK,CAACZ,OAAcE,IAATD,EAAqBA,EAAKY,WAAQX,EACrD,CAEA,OAAOM,CACR,CAOA,KAAAM,GACC,GAAkB,IAAd3B,KAAKW,KACR,OAAOX,KAGR,MAAMc,EAAOd,KAAKM,MAalB,cAXON,KAAKO,MAAMO,EAAKD,KAEH,KAAdb,KAAKW,MACVX,KAAKM,MAAQ,KACbN,KAAKU,KAAO,MAEZV,MAAKgB,EAAQF,GAGdA,EAAKI,KAAO,KAELlB,IACR,CAQA,SAAA4B,CAAUf,GACT,MAAMC,EAAOd,KAAKO,MAAMM,GACxB,YAAgBE,IAATD,EAAqBA,EAAKe,YAASd,CAC3C,CAQA,GAAAe,CAAIjB,GACH,MAAMC,EAAOd,KAAKO,MAAMM,GAExB,QAAaE,IAATD,EAEH,OAAId,KAAKI,IAAM,GACVU,EAAKe,QAAUE,KAAKC,WACvBhC,KAAKiC,OAAOpB,IAOdb,KAAKkC,UAAUpB,GAERA,EAAKY,MAId,CAQA,GAAAS,CAAItB,GACH,MAAMC,EAAOd,KAAKO,MAAMM,GACxB,YAAgBE,IAATD,IAAoC,IAAbd,KAAKI,KAAaU,EAAKe,OAASE,KAAKC,MACpE,CASA,EAAAhB,CAAQF,GACW,OAAdA,EAAKG,OACRH,EAAKG,KAAKC,KAAOJ,EAAKI,MAGL,OAAdJ,EAAKI,OACRJ,EAAKI,KAAKD,KAAOH,EAAKG,MAGnBjB,KAAKM,QAAUQ,IAClBd,KAAKM,MAAQQ,EAAKI,MAGflB,KAAKU,OAASI,IACjBd,KAAKU,KAAOI,EAAKG,KAEnB,CAUA,SAAAiB,CAAUpB,GACLd,KAAKU,OAASI,IAIlBd,MAAKgB,EAAQF,GAEbA,EAAKG,KAAOjB,KAAKU,KACjBI,EAAKI,KAAO,KACZlB,KAAKU,KAAKQ,KAAOJ,EACjBd,KAAKU,KAAOI,EACb,CAOA,IAAAM,GACC,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQxB,KAAKW,OACzC,IAAIyB,EAAIpC,KAAKM,MACTmB,EAAI,EAER,KAAa,OAANW,GACNf,EAAOI,KAAOW,EAAEvB,IAChBuB,EAAIA,EAAElB,KAGP,OAAOG,CACR,CASA,cAAAgB,CAAexB,EAAKa,GACnB,IAAIY,EAAU,KACVxB,EAAOd,KAAKO,MAAMM,GAmCtB,YAjCaE,IAATD,GACHA,EAAKY,MAAQA,EACT1B,KAAKK,WACRS,EAAKe,OAAS7B,KAAKI,IAAM,EAAI2B,KAAKC,MAAQhC,KAAKI,IAAMJ,KAAKI,KAE3DJ,KAAKkC,UAAUpB,KAEXd,KAAKG,IAAM,GAAKH,KAAKW,OAASX,KAAKG,MACtCmC,EAAU,CACTzB,IAAKb,KAAKM,MAAMO,IAChBa,MAAO1B,KAAKM,MAAMoB,MAClBG,OAAQ7B,KAAKM,MAAMuB,QAEpB7B,KAAK2B,SAGNb,EAAOd,KAAKO,MAAMM,GAAO,CACxBgB,OAAQ7B,KAAKI,IAAM,EAAI2B,KAAKC,MAAQhC,KAAKI,IAAMJ,KAAKI,IACpDS,IAAKA,EACLI,KAAMjB,KAAKU,KACXQ,KAAM,KACNQ,SAGmB,KAAd1B,KAAKW,KACVX,KAAKM,MAAQQ,EAEbd,KAAKU,KAAKQ,KAAOJ,EAGlBd,KAAKU,KAAOI,GAGNwB,CACR,CASA,GAAAC,CAAI1B,EAAKa,GACR,IAAIZ,EAAOd,KAAKO,MAAMM,GAgCtB,YA9BaE,IAATD,GACHA,EAAKY,MAAQA,EAET1B,KAAKK,WACRS,EAAKe,OAAS7B,KAAKI,IAAM,EAAI2B,KAAKC,MAAQhC,KAAKI,IAAMJ,KAAKI,KAG3DJ,KAAKkC,UAAUpB,KAEXd,KAAKG,IAAM,GAAKH,KAAKW,OAASX,KAAKG,KACtCH,KAAK2B,QAGNb,EAAOd,KAAKO,MAAMM,GAAO,CACxBgB,OAAQ7B,KAAKI,IAAM,EAAI2B,KAAKC,MAAQhC,KAAKI,IAAMJ,KAAKI,IACpDS,IAAKA,EACLI,KAAMjB,KAAKU,KACXQ,KAAM,KACNQ,SAGmB,KAAd1B,KAAKW,KACVX,KAAKM,MAAQQ,EAEbd,KAAKU,KAAKQ,KAAOJ,EAGlBd,KAAKU,KAAOI,GAGNd,IACR,CAUA,MAAAwC,CAAOpB,QACOL,IAATK,IACHA,EAAOpB,KAAKoB,QAGb,MAAMC,EAASC,MAAMC,KAAK,CAAEC,OAAQJ,EAAKI,SACzC,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAKI,OAAQC,IAAK,CACrC,MAAMX,EAAOd,KAAKO,MAAMa,EAAKK,IAC7BJ,EAAOI,QAAcV,IAATD,EAAqBA,EAAKY,WAAQX,CAC/C,CAEA,OAAOM,CACR,EA2BD5B,EAAAQ,IAAAA,EAAAR,EAAAM,IAdO,SAAaI,EAAM,IAAMC,EAAM,EAAGC,GAAW,GACnD,GAAIoC,MAAMtC,IAAQA,EAAM,EACvB,MAAM,IAAIuC,UAAU,qBAGrB,GAAID,MAAMrC,IAAQA,EAAM,EACvB,MAAM,IAAIsC,UAAU,qBAGrB,GAAwB,kBAAbrC,EACV,MAAM,IAAIqC,UAAU,0BAGrB,OAAO,IAAIzC,EAAIE,EAAKC,EAAKC,EAC1B,CAAA"}
\ No newline at end of file
diff --git a/docs/API.md b/docs/API.md
new file mode 100644
index 0000000..0d2e110
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,374 @@
+# 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()`
+
+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']
+```
+
+**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)`
+
+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 | Description |
+| ----- | ----- | ----------- |
+| `key` | `string` | Item key |
+| `value` | `*` | Item value |
+
+**Returns:** `LRU` - this instance (for chaining)
+
+---
+
+### `setWithEvicted(key, value)`
+
+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 | Description |
+| ----- | ----- | ----------- |
+| `key` | `string` | Item key |
+| `value` | `*` | Item value |
+
+**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..c129134 100644
--- a/docs/CODE_STYLE_GUIDE.md
+++ b/docs/CODE_STYLE_GUIDE.md
@@ -1,279 +1,161 @@
# 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
+
+- `@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
-### Code Comments
-- Use inline comments sparingly and only when code logic is complex
-- Write self-documenting code when possible
-- Explain **why**, not **what**
+## 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 +165,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.
+## Lint 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..4ff417b 100644
--- a/docs/TECHNICAL_DOCUMENTATION.md
+++ b/docs/TECHNICAL_DOCUMENTATION.md
@@ -28,17 +28,17 @@ The LRU cache implements a doubly-linked list combined with a hash map for O(1)
graph TD
A["LRU Cache Instance"] --> B["Hash Map
items: Object"]
A --> C["Doubly Linked List"]
-
+
B --> D["Key-Value Mapping
O(1) Access"]
C --> E["Node: first"]
C --> F["Node: last"]
-
+
E --> G["prev: null
next: Node
key: string
value: any
expiry: number"]
F --> H["prev: Node
next: null
key: string
value: any
expiry: number"]
-
+
G --> I["Middle Nodes"]
I --> F
-
+
style A fill:#2563eb,stroke:#1e40af,stroke-width:2px,color:#ffffff
style B fill:#7c3aed,stroke:#5b21b6,stroke-width:2px,color:#ffffff
style C fill:#059669,stroke:#047857,stroke-width:2px,color:#ffffff
@@ -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()` and `setWithEvicted()` operations (not on `get()`)
## Data Flow
@@ -68,11 +68,11 @@ sequenceDiagram
participant LRU as LRU Cache
participant HashMap as Hash Map
participant LinkedList as Linked List
-
+
Client->>LRU: get(key)
LRU->>HashMap: items[key]
HashMap-->>LRU: node reference
-
+
alt TTL enabled
LRU->>LRU: check item.expiry <= Date.now()
alt expired
@@ -97,18 +97,18 @@ sequenceDiagram
participant LRU as LRU Cache
participant HashMap as Hash Map
participant LinkedList as Linked List
-
+
Client->>LRU: set(key, value)
LRU->>HashMap: check items[key]
HashMap-->>LRU: undefined (miss)
-
+
alt cache full (size === max)
LRU->>LRU: evict() - remove this.first
LRU->>HashMap: delete items[first.key]
LRU->>LinkedList: update first pointer
Note over LRU: LRU eviction complete
end
-
+
LRU->>HashMap: items[key] = new item
LRU->>LinkedList: set item.prev = this.last
LRU->>LinkedList: update this.last = item
@@ -120,20 +120,20 @@ sequenceDiagram
### Time Complexity
-| Operation | Average Case | Worst Case | Space | Description |
-|-----------|--------------|------------|-------|-------------|
-| `get(key)` | O(1) | O(1) | O(1) | Retrieve value and move to end |
-| `set(key, value)` | O(1) | O(1) | O(1) | Store value, evict if needed |
-| `setWithEvicted(key, value)` | O(1) | O(1) | O(1) | Store value, return evicted item |
-| `delete(key)` | O(1) | O(1) | O(1) | Remove item from cache |
-| `has(key)` | O(1) | O(1) | O(1) | Check key existence |
-| `clear()` | O(1) | O(1) | O(1) | Reset all pointers |
-| `evict()` | O(1) | O(1) | O(1) | Remove least recently used item |
-| `expiresAt(key)` | O(1) | O(1) | O(1) | Get expiration timestamp |
-| `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 |
+| Operation | Average Case | Worst Case | Space | Description |
+| ---------------------------- | ------------ | ---------- | ----- | ---------------------------------- |
+| `get(key)` | O(1) | O(1) | O(1) | Retrieve value and move to end |
+| `set(key, value)` | O(1) | O(1) | O(1) | Store value, evict if needed |
+| `setWithEvicted(key, value)` | O(1) | O(1) | O(1) | Store value, return evicted item |
+| `delete(key)` | O(1) | O(1) | O(1) | Remove item from cache |
+| `has(key)` | O(1) | O(1) | O(1) | Check key existence |
+| `clear()` | O(1) | O(1) | O(1) | Reset all pointers |
+| `evict()` | O(1) | O(1) | O(1) | Remove least recently used item |
+| `expiresAt(key)` | O(1) | O(1) | O(1) | Get expiration timestamp |
+| `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 |
### Memory Usage
@@ -148,6 +148,7 @@ sequenceDiagram
The LRU cache maintains a doubly-linked list $L$ and a hash table $H$ for O(1) operations:
**Data Structure:**
+
- $L = (first, last, size)$ - Doubly-linked list with head/tail pointers
- $H: K \rightarrow \{key: K, value: V, prev: Object, next: Object, expiry: \mathbb{N}_0\}$ - Hash table mapping keys to item objects
- $max \in \mathbb{N}_0$ - Maximum cache size (0 = unlimited)
@@ -158,77 +159,103 @@ The LRU cache maintains a doubly-linked list $L$ and a hash table $H$ for O(1) o
**Note:** The mathematical notation uses `create(k, v)` to represent the item creation logic that is inline in the actual implementation.
-#### Set Operation: $set(k, v, bypass = false, resetTtl = resetTtl) \rightarrow \text{LRU}$
-$$\begin{align}
-set(k, v, bypass, resetTtl) &= \begin{cases}
-update(k, v, bypass, resetTtl) & \text{if } k \in H \\
+#### Set Operation: $set(k, v) \rightarrow \text{LRU}$
+
+$$
+\begin{align}
+set(k, v) &= \begin{cases}
+update(k, v) & \text{if } k \in H \\
insert(k, v) & \text{if } k \notin H
\end{cases} \\
-update(k, v, bypass, resetTtl) &= H[k].value \leftarrow v \land moveToEnd(H[k]) \\
+update(k, v) &= H[k].value \leftarrow v \land moveToEnd(H[k]) \\
& \quad \land \begin{cases}
-H[k].expiry \leftarrow t_{now} + ttl & \text{if } bypass = false \land resetTtl = true \land ttl > 0 \\
+H[k].expiry \leftarrow t_{now} + ttl & \text{if } resetTtl = true \land ttl > 0 \\
\text{no-op} & \text{otherwise}
\end{cases} \\
insert(k, v) &= \begin{cases}
evict() \land create(k, v) & \text{if } max > 0 \land size = max \\
create(k, v) & \text{otherwise}
\end{cases} \\
-create(k, v) &= H[k] \leftarrow \{key: k, value: v, prev: last, next: null, expiry: t_{now} + ttl\} \\
+create(k, v) &= H[k] \leftarrow \{key: k, value: v, prev: last, next: null, expiry: ttl > 0 ? t_{now} + ttl : 0\} \\
& \quad \land last \leftarrow H[k] \land size \leftarrow size + 1 \\
& \quad \land \begin{cases}
first \leftarrow H[k] & \text{if } size = 1 \\
last.next \leftarrow H[k] & \text{otherwise}
\end{cases}
-\end{align}$$
+\end{align}
+$$
**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\}$
-$$\begin{align}
-setWithEvicted(k, v, resetTtl) &= \begin{cases}
-set(k, v, true, resetTtl) \land \bot & \text{if } k \in H \\
+#### Set With Evicted Operation: $setWithEvicted(k, v) \rightarrow \{key: K, value: V, expiry: \mathbb{N}_0\} \cup \{\bot\}$
+
+$$
+\begin{align}
+setWithEvicted(k, v) &= \begin{cases}
+update(k, v) \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) &= 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.
+\end{align}
+$$
**Time Complexity:** $O(1)$ amortized
#### Get Operation: $get(k) \rightarrow V \cup \{\bot\}$
-$$\begin{align}
+
+$$
+\begin{align}
get(k) &= \begin{cases}
cleanup(k) \land moveToEnd(H[k]) \land H[k].value & \text{if } k \in H \land (ttl = 0 \lor H[k].expiry > t_{now}) \\
\bot & \text{otherwise}
\end{cases}
-\end{align}$$
+\end{align}
+$$
**Note:** `get()` operations never reset TTL, regardless of the `resetTtl` setting.
**Time Complexity:** $O(1)$
#### Delete Operation: $delete(k) \rightarrow \text{LRU}$
-$$\begin{align}
+
+$$
+\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}$$
+\end{align}
+$$
**Time Complexity:** $O(1)$
#### Move to End: $moveToEnd(item)$
-$$\begin{align}
+
+$$
+\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}$$
+\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)$
@@ -236,10 +263,12 @@ removeFromList(item) \land appendToList(item) & \text{otherwise}
**LRU Eviction:** When $max > 0 \land size = max$ and inserting a new item:
-$$evict(bypass = false) = \begin{cases}
-first \leftarrow first.next \land first.prev \leftarrow null \land H \setminus \{first.key\} \land size \leftarrow size - 1 & \text{if } (bypass \lor size > 0) \\
-\text{no-op} & \text{otherwise}
-\end{cases}$$
+$$
+evict() = \begin{cases}
+\text{no-op} & \text{if } size = 0 \\
+first \leftarrow first.next \land first.prev \leftarrow null \land H \setminus \{first.key\} \land size \leftarrow size - 1 \land item.next \leftarrow null & \text{if } size > 0
+\end{cases}
+$$
### TTL Expiration
@@ -249,15 +278,17 @@ $$isExpired(k) = ttl > 0 \land H[k].expiry \leq t_{now}$$
**Automatic Cleanup:** Expired items are removed on access:
-$$cleanup(k) = \begin{cases}
+$$
+cleanup(k) = \begin{cases}
delete(k) & \text{if } isExpired(k) \\
\text{no-op} & \text{otherwise}
-\end{cases}$$
+\end{cases}
+$$
**TTL Reset Behavior:**
-- TTL is only reset during `set()` operations when `resetTtl = true` and `bypass = false`
+
+- TTL is reset during `set()` and `setWithEvicted()` operations when `resetTtl = true`
- `get()` operations never reset TTL, regardless of the `resetTtl` setting
-- `setWithEvicted()` operations never reset TTL because they always call `set()` with `bypass = true`
### Space Complexity
@@ -272,8 +303,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()` and `setWithEvicted()` operations when `resetTtl = true`
## TypeScript Support
@@ -287,75 +318,75 @@ export function lru(max?: number, ttl?: number, resetTtl?: boolean): LR
// Main LRU class with generic value type
export class LRU {
- constructor(max?: number, ttl?: number, resetTtl?: boolean);
-
- // Instance properties
- first: LRUItem | null;
- items: Record>;
- last: LRUItem | null;
- max: number;
- resetTtl: boolean;
- size: number;
- ttl: number;
-
- // Core methods
- clear(): this;
- delete(key: any): this;
- entries(keys?: any[]): [any, T][];
- evict(bypass?: boolean): this;
- expiresAt(key: any): number | undefined;
- get(key: any): T | undefined;
- 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;
- values(keys?: any[]): T[];
+ constructor(max?: number, ttl?: number, resetTtl?: boolean);
+
+ // Instance properties
+ first: LRUItem | null;
+ items: Record>;
+ last: LRUItem | null;
+ max: number;
+ resetTtl: boolean;
+ size: number;
+ ttl: number;
+
+ // Core methods
+ clear(): this;
+ delete(key: any): this;
+ entries(keys?: any[]): [any, T | undefined][];
+ evict(): this;
+ expiresAt(key: any): number | undefined;
+ get(key: any): T | undefined;
+ has(key: any): boolean;
+ keys(): any[];
+ set(key: any, value: T): this;
+ setWithEvicted(key: any, value: T): EvictedItem | null;
+ values(keys?: any[]): (T | undefined)[];
}
// Internal item structure
interface LRUItem {
- expiry: number;
- key: any;
- prev: LRUItem | null;
- next: LRUItem | null;
- value: T;
+ expiry: number;
+ key: any;
+ prev: LRUItem | null;
+ next: LRUItem | null;
+ value: T;
}
```
### TypeScript Usage Examples
```typescript
-import { LRU, lru } from 'tiny-lru';
+import { LRU, lru } from "tiny-lru";
// Type-safe cache for user objects
interface User {
- id: number;
- name: string;
- email: string;
+ id: number;
+ name: string;
+ email: string;
}
const userCache = new LRU(1000, 300000); // 1000 users, 5 min TTL
-userCache.set('user_123', { id: 123, name: 'John', email: 'john@example.com' });
-const user: User | undefined = userCache.get('user_123'); // Fully typed
+userCache.set("user_123", { id: 123, name: "John", email: "john@example.com" });
+const user: User | undefined = userCache.get("user_123"); // Fully typed
// Type-safe cache for API responses
interface APIResponse {
- data: T;
- status: number;
- timestamp: number;
+ data: T;
+ status: number;
+ timestamp: number;
}
const apiCache = lru>(500, 600000); // 10 min TTL
-apiCache.set('endpoint_abc', {
- data: { results: [] },
- status: 200,
- timestamp: Date.now()
+apiCache.set("endpoint_abc", {
+ data: { results: [] },
+ status: 200,
+ timestamp: Date.now(),
});
// String cache with factory function
const stringCache = lru(100);
-stringCache.set('key1', 'value1');
-const value: string | undefined = stringCache.get('key1');
+stringCache.set("key1", "value1");
+const value: string | undefined = stringCache.get("key1");
```
## Modern Usage Patterns
@@ -365,62 +396,62 @@ const value: string | undefined = stringCache.get('key1');
Cache expensive AI model responses with content-based keys and reasonable TTLs.
```javascript
-import { LRU } from 'tiny-lru';
+import { LRU } from "tiny-lru";
class LLMCache {
- constructor() {
- // Cache up to 1000 responses for 1 hour
- this.cache = new LRU(1000, 3600000); // 1 hour TTL
- }
-
- /**
- * Generate cache key for LLM request
- * @param {string} model - Model identifier
- * @param {string} prompt - User prompt
- * @param {object} params - Model parameters
- */
- generateKey(model, prompt, params = {}) {
- const paramsHash = this.hashObject(params);
- const promptHash = this.hashString(prompt);
- return `llm:${model}:${promptHash}:${paramsHash}`;
- }
-
- async getResponse(model, prompt, params = {}) {
- const key = this.generateKey(model, prompt, params);
-
- // Check cache first
- const cached = this.cache.get(key);
- if (cached) {
- return { ...cached, fromCache: true };
- }
-
- // Make expensive API call
- const response = await this.callLLMAPI(model, prompt, params);
-
- // Cache the response
- this.cache.set(key, {
- response: response.text,
- tokens: response.tokens,
- timestamp: Date.now()
- });
-
- return { ...response, fromCache: false };
- }
-
- hashString(str) {
- // Simple hash function for demonstration
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- const char = str.charCodeAt(i);
- hash = ((hash << 5) - hash) + char;
- hash = hash & hash; // Convert to 32-bit integer
- }
- return Math.abs(hash).toString(36);
- }
-
- hashObject(obj) {
- return this.hashString(JSON.stringify(obj, Object.keys(obj).sort()));
- }
+ constructor() {
+ // Cache up to 1000 responses for 1 hour
+ this.cache = new LRU(1000, 3600000); // 1 hour TTL
+ }
+
+ /**
+ * Generate cache key for LLM request
+ * @param {string} model - Model identifier
+ * @param {string} prompt - User prompt
+ * @param {object} params - Model parameters
+ */
+ generateKey(model, prompt, params = {}) {
+ const paramsHash = this.hashObject(params);
+ const promptHash = this.hashString(prompt);
+ return `llm:${model}:${promptHash}:${paramsHash}`;
+ }
+
+ async getResponse(model, prompt, params = {}) {
+ const key = this.generateKey(model, prompt, params);
+
+ // Check cache first
+ const cached = this.cache.get(key);
+ if (cached) {
+ return { ...cached, fromCache: true };
+ }
+
+ // Make expensive API call
+ const response = await this.callLLMAPI(model, prompt, params);
+
+ // Cache the response
+ this.cache.set(key, {
+ response: response.text,
+ tokens: response.tokens,
+ timestamp: Date.now(),
+ });
+
+ return { ...response, fromCache: false };
+ }
+
+ hashString(str) {
+ // Simple hash function for demonstration
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ const char = str.charCodeAt(i);
+ hash = (hash << 5) - hash + char;
+ hash = hash & hash; // Convert to 32-bit integer
+ }
+ return Math.abs(hash).toString(36);
+ }
+
+ hashObject(obj) {
+ return this.hashString(JSON.stringify(obj, Object.keys(obj).sort()));
+ }
}
```
@@ -429,54 +460,54 @@ class LLMCache {
Cache external API responses with different TTLs based on data sensitivity.
```javascript
-import { LRU } from 'tiny-lru';
+import { LRU } from "tiny-lru";
class APICache {
- constructor() {
- this.caches = {
- // Fast-changing data: 5 minutes
- realtime: new LRU(500, 300000),
- // Moderate data: 30 minutes
- standard: new LRU(1000, 1800000),
- // Stable data: 24 hours
- stable: new LRU(2000, 86400000)
- };
- }
-
- async fetchUserProfile(userId, domain = 'users') {
- const key = `${domain}:profile:${userId}`;
- const cache = this.caches.standard;
-
- const cached = cache.get(key);
- if (cached) {
- return cached;
- }
-
- const profile = await fetch(`/api/users/${userId}`).then(r => r.json());
-
- // Use setWithEvicted to track what gets evicted for analytics
- const evicted = cache.setWithEvicted(key, profile);
- if (evicted) {
- console.log(`Evicted user profile: ${evicted.key}`);
- }
-
- return profile;
- }
-
- async fetchRealtimeData(symbol, domain = 'market') {
- const key = `${domain}:price:${symbol}`;
- const cache = this.caches.realtime;
-
- const cached = cache.get(key);
- if (cached) {
- return cached;
- }
-
- const data = await fetch(`/api/market/${symbol}`).then(r => r.json());
- cache.set(key, data);
-
- return data;
- }
+ constructor() {
+ this.caches = {
+ // Fast-changing data: 5 minutes
+ realtime: new LRU(500, 300000),
+ // Moderate data: 30 minutes
+ standard: new LRU(1000, 1800000),
+ // Stable data: 24 hours
+ stable: new LRU(2000, 86400000),
+ };
+ }
+
+ async fetchUserProfile(userId, domain = "users") {
+ const key = `${domain}:profile:${userId}`;
+ const cache = this.caches.standard;
+
+ const cached = cache.get(key);
+ if (cached) {
+ return cached;
+ }
+
+ const profile = await fetch(`/api/users/${userId}`).then((r) => r.json());
+
+ // Use setWithEvicted to track what gets evicted for analytics
+ const evicted = cache.setWithEvicted(key, profile);
+ if (evicted) {
+ console.log(`Evicted user profile: ${evicted.key}`);
+ }
+
+ return profile;
+ }
+
+ async fetchRealtimeData(symbol, domain = "market") {
+ const key = `${domain}:price:${symbol}`;
+ const cache = this.caches.realtime;
+
+ const cached = cache.get(key);
+ if (cached) {
+ return cached;
+ }
+
+ const data = await fetch(`/api/market/${symbol}`).then((r) => r.json());
+ cache.set(key, data);
+
+ return data;
+ }
}
```
@@ -485,75 +516,75 @@ class APICache {
Cache expensive database queries with intelligent cache invalidation.
```javascript
-import { LRU } from 'tiny-lru';
+import { LRU } from "tiny-lru";
class QueryCache {
- constructor() {
- // No TTL - manual invalidation
- this.cache = new LRU(10000);
- this.dependencyMap = new Map(); // Track query dependencies
- }
-
- async query(sql, params = [], dependencies = []) {
- const key = this.generateQueryKey(sql, params);
-
- const cached = this.cache.get(key);
- if (cached) {
- return cached;
- }
-
- const result = await this.executeQuery(sql, params);
- this.cache.set(key, result);
-
- // Track dependencies for invalidation
- this.trackDependencies(key, dependencies);
-
- return result;
- }
-
- generateQueryKey(sql, params) {
- const sqlHash = this.hashString(sql.replace(/\s+/g, ' ').trim());
- const paramsHash = this.hashString(JSON.stringify(params));
- return `query:${sqlHash}:${paramsHash}`;
- }
-
- // Invalidate cache when specific tables change
- invalidateByTable(tableName) {
- const keysToDelete = [];
-
- for (const [key, deps] of this.dependencyMap.entries()) {
- if (deps.includes(tableName)) {
- keysToDelete.push(key);
- }
- }
-
- keysToDelete.forEach(key => {
- this.cache.delete(key);
- this.dependencyMap.delete(key);
- });
- }
-
- // Get cache statistics using modern API methods
- getCacheStats() {
- const allKeys = this.cache.keys();
- const allEntries = this.cache.entries(); // Gets [key, value] pairs
- const dependentQueries = this.cache.values(
- allKeys.filter(key => this.dependencyMap.has(key))
- );
-
- return {
- totalQueries: this.cache.size,
- queryKeys: allKeys,
- dependentQueryCount: dependentQueries.length,
- lruOrder: allEntries.map(([key, _]) => key) // Least to most recent
- };
- }
-
- trackDependencies(key, dependencies) {
- if (dependencies.length > 0) {
- this.dependencyMap.set(key, dependencies);
- }
- }
+ constructor() {
+ // No TTL - manual invalidation
+ this.cache = new LRU(10000);
+ this.dependencyMap = new Map(); // Track query dependencies
+ }
+
+ async query(sql, params = [], dependencies = []) {
+ const key = this.generateQueryKey(sql, params);
+
+ const cached = this.cache.get(key);
+ if (cached) {
+ return cached;
+ }
+
+ const result = await this.executeQuery(sql, params);
+ this.cache.set(key, result);
+
+ // Track dependencies for invalidation
+ this.trackDependencies(key, dependencies);
+
+ return result;
+ }
+
+ generateQueryKey(sql, params) {
+ const sqlHash = this.hashString(sql.replace(/\s+/g, " ").trim());
+ const paramsHash = this.hashString(JSON.stringify(params));
+ return `query:${sqlHash}:${paramsHash}`;
+ }
+
+ // Invalidate cache when specific tables change
+ invalidateByTable(tableName) {
+ const keysToDelete = [];
+
+ for (const [key, deps] of this.dependencyMap.entries()) {
+ if (deps.includes(tableName)) {
+ keysToDelete.push(key);
+ }
+ }
+
+ keysToDelete.forEach((key) => {
+ this.cache.delete(key);
+ this.dependencyMap.delete(key);
+ });
+ }
+
+ // Get cache statistics using modern API methods
+ getCacheStats() {
+ const allKeys = this.cache.keys();
+ const allEntries = this.cache.entries(); // Gets [key, value] pairs
+ const dependentQueries = this.cache.values(
+ allKeys.filter((key) => this.dependencyMap.has(key)),
+ );
+
+ return {
+ totalQueries: this.cache.size,
+ queryKeys: allKeys,
+ dependentQueryCount: dependentQueries.length,
+ lruOrder: allEntries.map(([key, _]) => key), // Least to most recent
+ };
+ }
+
+ trackDependencies(key, dependencies) {
+ if (dependencies.length > 0) {
+ this.dependencyMap.set(key, dependencies);
+ }
+ }
}
```
@@ -562,49 +593,49 @@ class QueryCache {
Cache user sessions and authentication tokens with proper security.
```javascript
-import { LRU } from 'tiny-lru';
+import { LRU } from "tiny-lru";
class AuthCache {
- constructor() {
- // Session cache: 30 minutes with TTL reset on access
- this.sessions = new LRU(10000, 1800000, true);
- // Token validation cache: 5 minutes, no reset
- this.tokens = new LRU(5000, 300000, false);
- // Permission cache: 15 minutes
- this.permissions = new LRU(5000, 900000);
- }
-
- cacheSession(sessionId, userData, domain = 'app') {
- const key = `${domain}:session:${sessionId}`;
- this.sessions.set(key, {
- userId: userData.userId,
- permissions: userData.permissions,
- loginTime: Date.now(),
- lastActivity: Date.now()
- });
-
- // Log when this session will expire
- const expiryTime = this.sessions.expiresAt(key);
- if (expiryTime) {
- console.log(`Session ${sessionId} expires at: ${new Date(expiryTime)}`);
- }
- }
-
- getSession(sessionId, domain = 'app') {
- const key = `${domain}:session:${sessionId}`;
- return this.sessions.get(key);
- }
-
- cacheTokenValidation(tokenHash, isValid, userId = null) {
- const key = `auth:token:${tokenHash}`;
- this.tokens.set(key, { isValid, userId, validatedAt: Date.now() });
- }
-
- isTokenValid(tokenHash) {
- const key = `auth:token:${tokenHash}`;
- const cached = this.tokens.get(key);
- return cached?.isValid || false;
- }
+ constructor() {
+ // Session cache: 30 minutes with TTL reset on access
+ this.sessions = new LRU(10000, 1800000, true);
+ // Token validation cache: 5 minutes, no reset
+ this.tokens = new LRU(5000, 300000, false);
+ // Permission cache: 15 minutes
+ this.permissions = new LRU(5000, 900000);
+ }
+
+ cacheSession(sessionId, userData, domain = "app") {
+ const key = `${domain}:session:${sessionId}`;
+ this.sessions.set(key, {
+ userId: userData.userId,
+ permissions: userData.permissions,
+ loginTime: Date.now(),
+ lastActivity: Date.now(),
+ });
+
+ // Log when this session will expire
+ const expiryTime = this.sessions.expiresAt(key);
+ if (expiryTime) {
+ console.log(`Session ${sessionId} expires at: ${new Date(expiryTime)}`);
+ }
+ }
+
+ getSession(sessionId, domain = "app") {
+ const key = `${domain}:session:${sessionId}`;
+ return this.sessions.get(key);
+ }
+
+ cacheTokenValidation(tokenHash, isValid, userId = null) {
+ const key = `auth:token:${tokenHash}`;
+ this.tokens.set(key, { isValid, userId, validatedAt: Date.now() });
+ }
+
+ isTokenValid(tokenHash) {
+ const key = `auth:token:${tokenHash}`;
+ const cached = this.tokens.get(key);
+ return cached?.isValid || false;
+ }
}
```
@@ -624,36 +655,36 @@ Implement a hierarchical key naming convention to prevent cross-domain data leak
```javascript
const DOMAIN_PREFIXES = {
- // User-related data
- USER: 'usr',
- // Authentication & authorization
- AUTH: 'auth',
- // External API responses
- API: 'api',
- // Database query results
- DB: 'db',
- // Application logic
- APP: 'app',
- // System/infrastructure
- SYS: 'sys',
- // Analytics & metrics
- ANALYTICS: 'analytics',
- // Machine learning / AI
- ML: 'ml'
+ // User-related data
+ USER: "usr",
+ // Authentication & authorization
+ AUTH: "auth",
+ // External API responses
+ API: "api",
+ // Database query results
+ DB: "db",
+ // Application logic
+ APP: "app",
+ // System/infrastructure
+ SYS: "sys",
+ // Analytics & metrics
+ ANALYTICS: "analytics",
+ // Machine learning / AI
+ ML: "ml",
};
const SERVICE_PREFIXES = {
- // Authentication services
- LOGIN: 'login',
- LOGOUT: 'logout',
- REFRESH: 'refresh',
- // Data services
- PROFILE: 'profile',
- SETTINGS: 'settings',
- // External integrations
- PAYMENT: 'payment',
- EMAIL: 'email',
- SMS: 'sms'
+ // Authentication services
+ LOGIN: "login",
+ LOGOUT: "logout",
+ REFRESH: "refresh",
+ // Data services
+ PROFILE: "profile",
+ SETTINGS: "settings",
+ // External integrations
+ PAYMENT: "payment",
+ EMAIL: "email",
+ SMS: "sms",
};
```
@@ -661,59 +692,59 @@ const SERVICE_PREFIXES = {
```javascript
class SecureKeyManager {
- constructor(domain, service) {
- this.domain = domain;
- this.service = service;
- this.separator = ':';
- }
-
- generateKey(resource, identifier, version = null) {
- const parts = [this.domain, this.service, resource, identifier];
- if (version) parts.push(version);
-
- return parts.join(this.separator);
- }
-
- parseKey(key) {
- const parts = key.split(this.separator);
- return {
- domain: parts[0],
- service: parts[1],
- resource: parts[2],
- identifier: parts[3],
- version: parts[4] || null
- };
- }
-
- // Validate key follows security convention
- validateKey(key) {
- const parts = key.split(this.separator);
-
- if (parts.length < 4 || parts.length > 5) {
- throw new Error('Invalid key format: must have 4-5 parts');
- }
-
- if (!Object.values(DOMAIN_PREFIXES).includes(parts[0])) {
- throw new Error(`Invalid domain prefix: ${parts[0]}`);
- }
-
- return true;
- }
+ constructor(domain, service) {
+ this.domain = domain;
+ this.service = service;
+ this.separator = ":";
+ }
+
+ generateKey(resource, identifier, version = null) {
+ const parts = [this.domain, this.service, resource, identifier];
+ if (version) parts.push(version);
+
+ return parts.join(this.separator);
+ }
+
+ parseKey(key) {
+ const parts = key.split(this.separator);
+ return {
+ domain: parts[0],
+ service: parts[1],
+ resource: parts[2],
+ identifier: parts[3],
+ version: parts[4] || null,
+ };
+ }
+
+ // Validate key follows security convention
+ validateKey(key) {
+ const parts = key.split(this.separator);
+
+ if (parts.length < 4 || parts.length > 5) {
+ throw new Error("Invalid key format: must have 4-5 parts");
+ }
+
+ if (!Object.values(DOMAIN_PREFIXES).includes(parts[0])) {
+ throw new Error(`Invalid domain prefix: ${parts[0]}`);
+ }
+
+ return true;
+ }
}
// Usage examples
const authKeys = new SecureKeyManager(DOMAIN_PREFIXES.AUTH, SERVICE_PREFIXES.LOGIN);
const userKeys = new SecureKeyManager(DOMAIN_PREFIXES.USER, SERVICE_PREFIXES.PROFILE);
-const mlKeys = new SecureKeyManager(DOMAIN_PREFIXES.ML, 'llm');
+const mlKeys = new SecureKeyManager(DOMAIN_PREFIXES.ML, "llm");
// Generate secure keys
-const sessionKey = authKeys.generateKey('session', 'abc123def456');
+const sessionKey = authKeys.generateKey("session", "abc123def456");
// Result: "auth:login:session:abc123def456"
-const profileKey = userKeys.generateKey('data', '12345', 'v2');
+const profileKey = userKeys.generateKey("data", "12345", "v2");
// Result: "usr:profile:data:12345:v2"
-const llmKey = mlKeys.generateKey('response', 'gpt4-prompt-hash');
+const llmKey = mlKeys.generateKey("response", "gpt4-prompt-hash");
// Result: "ml:llm:response:gpt4-prompt-hash"
```
@@ -721,43 +752,43 @@ const llmKey = mlKeys.generateKey('response', 'gpt4-prompt-hash');
```javascript
class IsolatedCacheManager {
- constructor() {
- this.caches = new Map();
- }
-
- getCache(domain, service, maxSize = 1000, ttl = 0) {
- const cacheKey = `${domain}:${service}`;
-
- if (!this.caches.has(cacheKey)) {
- this.caches.set(cacheKey, new LRU(maxSize, ttl));
- }
-
- return this.caches.get(cacheKey);
- }
-
- // Secure method to access only your domain's cache
- accessCache(domain, service, operation, ...args) {
- if (!this.isAuthorized(domain, service)) {
- throw new Error('Unauthorized cache access');
- }
-
- const cache = this.getCache(domain, service);
- return cache[operation](...args);
- }
-
- isAuthorized(domain, service) {
- // Implement your authorization logic
- return true; // Simplified for example
- }
-
- // Clear caches by domain for security incidents
- clearDomain(domain) {
- for (const [key, cache] of this.caches.entries()) {
- if (key.startsWith(`${domain}:`)) {
- cache.clear();
- }
- }
- }
+ constructor() {
+ this.caches = new Map();
+ }
+
+ getCache(domain, service, maxSize = 1000, ttl = 0) {
+ const cacheKey = `${domain}:${service}`;
+
+ if (!this.caches.has(cacheKey)) {
+ this.caches.set(cacheKey, new LRU(maxSize, ttl));
+ }
+
+ return this.caches.get(cacheKey);
+ }
+
+ // Secure method to access only your domain's cache
+ accessCache(domain, service, operation, ...args) {
+ if (!this.isAuthorized(domain, service)) {
+ throw new Error("Unauthorized cache access");
+ }
+
+ const cache = this.getCache(domain, service);
+ return cache[operation](...args);
+ }
+
+ isAuthorized(domain, service) {
+ // Implement your authorization logic
+ return true; // Simplified for example
+ }
+
+ // Clear caches by domain for security incidents
+ clearDomain(domain) {
+ for (const [key, cache] of this.caches.entries()) {
+ if (key.startsWith(`${domain}:`)) {
+ cache.clear();
+ }
+ }
+ }
}
```
@@ -767,35 +798,35 @@ class IsolatedCacheManager {
```javascript
// Performance testing setup
-import { LRU } from 'tiny-lru';
-import { performance } from 'perf_hooks';
+import { LRU } from "tiny-lru";
+import { performance } from "perf_hooks";
class PerformanceTester {
- constructor() {
- this.cache = new LRU(10000);
- }
-
- async benchmarkOperations(iterations = 100000) {
- const results = {};
-
- // Benchmark set operations
- const setStart = performance.now();
- for (let i = 0; i < iterations; i++) {
- this.cache.set(`key_${i}`, `value_${i}`);
- }
- const setEnd = performance.now();
- results.setOpsPerSecond = iterations / ((setEnd - setStart) / 1000);
-
- // Benchmark get operations
- const getStart = performance.now();
- for (let i = 0; i < iterations; i++) {
- this.cache.get(`key_${i % 5000}`); // Mix hits and misses
- }
- const getEnd = performance.now();
- results.getOpsPerSecond = iterations / ((getEnd - getStart) / 1000);
-
- return results;
- }
+ constructor() {
+ this.cache = new LRU(10000);
+ }
+
+ async benchmarkOperations(iterations = 100000) {
+ const results = {};
+
+ // Benchmark set operations
+ const setStart = performance.now();
+ for (let i = 0; i < iterations; i++) {
+ this.cache.set(`key_${i}`, `value_${i}`);
+ }
+ const setEnd = performance.now();
+ results.setOpsPerSecond = iterations / ((setEnd - setStart) / 1000);
+
+ // Benchmark get operations
+ const getStart = performance.now();
+ for (let i = 0; i < iterations; i++) {
+ this.cache.get(`key_${i % 5000}`); // Mix hits and misses
+ }
+ const getEnd = performance.now();
+ results.getOpsPerSecond = iterations / ((getEnd - getStart) / 1000);
+
+ return results;
+ }
}
```
@@ -807,11 +838,11 @@ graph LR
B --> C["Until Max Size"]
C --> D["Constant Memory"]
D --> E["With Eviction"]
-
+
F["GC Pressure"] --> G["Low"]
F --> H["Object Reuse"]
F --> I["Minimal Allocation"]
-
+
style A fill:#2563eb,stroke:#1e40af,stroke-width:2px,color:#ffffff
style F fill:#ea580c,stroke:#c2410c,stroke-width:2px,color:#ffffff
```
@@ -821,92 +852,95 @@ graph LR
### Express.js Middleware
```javascript
-import { LRU } from 'tiny-lru';
+import { LRU } from "tiny-lru";
function createCacheMiddleware(options = {}) {
- const cache = new LRU(
- options.maxSize || 1000,
- options.ttl || 300000 // 5 minutes default
- );
-
- return (req, res, next) => {
- // Generate cache key from request
- const key = `http:${req.method}:${req.path}:${JSON.stringify(req.query)}`;
-
- // Check cache
- const cached = cache.get(key);
- if (cached) {
- res.set(cached.headers);
- return res.status(cached.status).json(cached.data);
- }
-
- // Intercept response
- const originalSend = res.json;
- res.json = function(data) {
- // Cache successful responses
- if (res.statusCode < 400) {
- cache.set(key, {
- status: res.statusCode,
- headers: res.getHeaders(),
- data: data
- });
- }
-
- return originalSend.call(this, data);
- };
-
- next();
- };
+ const cache = new LRU(
+ options.maxSize || 1000,
+ options.ttl || 300000, // 5 minutes default
+ );
+
+ return (req, res, next) => {
+ // Generate cache key from request
+ const key = `http:${req.method}:${req.path}:${JSON.stringify(req.query)}`;
+
+ // Check cache
+ const cached = cache.get(key);
+ if (cached) {
+ res.set(cached.headers);
+ return res.status(cached.status).json(cached.data);
+ }
+
+ // Intercept response
+ const originalSend = res.json;
+ res.json = function (data) {
+ // Cache successful responses
+ if (res.statusCode < 400) {
+ cache.set(key, {
+ status: res.statusCode,
+ headers: res.getHeaders(),
+ data: data,
+ });
+ }
+
+ return originalSend.call(this, data);
+ };
+
+ next();
+ };
}
// Usage
-app.use('/api/users', createCacheMiddleware({ ttl: 600000 })); // 10 minutes
+app.use("/api/users", createCacheMiddleware({ ttl: 600000 })); // 10 minutes
```
### React Cache Hook
```javascript
-import { useMemo, useRef } from 'react';
-import { LRU } from 'tiny-lru';
+import { useMemo, useRef } from "react";
+import { LRU } from "tiny-lru";
function useCache(maxSize = 100, ttl = 300000) {
- const cache = useRef(null);
-
- if (!cache.current) {
- cache.current = new LRU(maxSize, ttl);
- }
-
- const memoizedCache = useMemo(() => ({
- get: (key) => cache.current.get(key),
- set: (key, value) => cache.current.set(key, value),
- delete: (key) => cache.current.delete(key),
- clear: () => cache.current.clear(),
- has: (key) => cache.current.has(key)
- }), []);
-
- return memoizedCache;
+ const cache = useRef(null);
+
+ if (!cache.current) {
+ cache.current = new LRU(maxSize, ttl);
+ }
+
+ const memoizedCache = useMemo(
+ () => ({
+ get: (key) => cache.current.get(key),
+ set: (key, value) => cache.current.set(key, value),
+ delete: (key) => cache.current.delete(key),
+ clear: () => cache.current.clear(),
+ has: (key) => cache.current.has(key),
+ }),
+ [],
+ );
+
+ return memoizedCache;
}
// Usage in component
function UserProfile({ userId }) {
- const cache = useCache(50, 600000); // 10 minutes
- const [profile, setProfile] = useState(null);
-
- useEffect(() => {
- const cacheKey = `usr:profile:data:${userId}`;
- const cached = cache.get(cacheKey);
-
- if (cached) {
- setProfile(cached);
- } else {
- fetchUserProfile(userId).then(data => {
- cache.set(cacheKey, data);
- setProfile(data);
- });
- }
- }, [userId, cache]);
-
- return {/* render profile */}
;
+ const cache = useCache(50, 600000); // 10 minutes
+ const [profile, setProfile] = useState(null);
+
+ useEffect(() => {
+ const cacheKey = `usr:profile:data:${userId}`;
+ const cached = cache.get(cacheKey);
+
+ if (cached) {
+ setProfile(cached);
+ } else {
+ fetchUserProfile(userId).then((data) => {
+ cache.set(cacheKey, data);
+ setProfile(data);
+ });
+ }
+ }, [userId, cache]);
+
+ return {/* render profile */}
;
}
```
@@ -919,17 +953,17 @@ flowchart TD
A["Determine Cache Size"] --> B{"Memory Available?"}
B -->|Limited| C["Conservative Sizing
~1000 items"]
B -->|Abundant| D["Aggressive Sizing
~10000+ items"]
-
+
C --> E["Monitor Hit Rate"]
D --> E
-
+
E --> F{"Hit Rate < 80%?"}
F -->|Yes| G["Increase Size
or Adjust TTL"]
F -->|No| H["Optimize Keys
or Logic"]
-
+
G --> I["Re-evaluate"]
H --> I
-
+
style A fill:#2563eb,stroke:#1e40af,stroke-width:2px,color:#ffffff
style I fill:#059669,stroke:#047857,stroke-width:2px,color:#ffffff
style B fill:#7c3aed,stroke:#5b21b6,stroke-width:2px,color:#ffffff
@@ -938,44 +972,44 @@ flowchart TD
### 2. TTL Selection Guidelines
-| Data Type | Recommended TTL | Rationale |
-|-----------|----------------|-----------|
-| User Sessions | 30-60 minutes | Balance security and UX |
-| API Responses | 5-30 minutes | Depends on data freshness needs |
-| Database Queries | 15-60 minutes | Based on update frequency |
-| LLM Responses | 1-24 hours | Expensive to regenerate |
-| Static Content | 24+ hours | Rarely changes |
+| Data Type | Recommended TTL | Rationale |
+| ---------------- | --------------- | ------------------------------- |
+| User Sessions | 30-60 minutes | Balance security and UX |
+| API Responses | 5-30 minutes | Depends on data freshness needs |
+| Database Queries | 15-60 minutes | Based on update frequency |
+| LLM Responses | 1-24 hours | Expensive to regenerate |
+| Static Content | 24+ hours | Rarely changes |
### 3. Error Handling
```javascript
class RobustCache {
- constructor(maxSize, ttl) {
- try {
- this.cache = new LRU(maxSize, ttl);
- } catch (error) {
- console.error('Cache initialization failed:', error);
- this.cache = null;
- }
- }
-
- safeGet(key) {
- try {
- return this.cache?.get(key);
- } catch (error) {
- console.error('Cache get failed:', error);
- return undefined;
- }
- }
-
- safeSet(key, value) {
- try {
- return this.cache?.set(key, value);
- } catch (error) {
- console.error('Cache set failed:', error);
- return false;
- }
- }
+ constructor(maxSize, ttl) {
+ try {
+ this.cache = new LRU(maxSize, ttl);
+ } catch (error) {
+ console.error("Cache initialization failed:", error);
+ this.cache = null;
+ }
+ }
+
+ safeGet(key) {
+ try {
+ return this.cache?.get(key);
+ } catch (error) {
+ console.error("Cache get failed:", error);
+ return undefined;
+ }
+ }
+
+ safeSet(key, value) {
+ try {
+ return this.cache?.set(key, value);
+ } catch (error) {
+ console.error("Cache set failed:", error);
+ return false;
+ }
+ }
}
```
@@ -983,50 +1017,50 @@ class RobustCache {
```javascript
class MonitoredCache {
- constructor(maxSize, ttl) {
- this.cache = new LRU(maxSize, ttl);
- this.metrics = {
- hits: 0,
- misses: 0,
- sets: 0,
- deletes: 0,
- evictions: 0
- };
- }
-
- get(key) {
- const value = this.cache.get(key);
- this.metrics[value !== undefined ? 'hits' : 'misses']++;
- return value;
- }
-
- set(key, value) {
- const hadKey = this.cache.has(key);
- const wasAtCapacity = this.cache.size >= this.cache.max;
-
- const result = this.cache.set(key, value);
-
- this.metrics.sets++;
- if (!hadKey && wasAtCapacity && this.cache.max > 0) {
- this.metrics.evictions++;
- }
-
- return result;
- }
-
- getHitRate() {
- const total = this.metrics.hits + this.metrics.misses;
- return total > 0 ? this.metrics.hits / total : 0;
- }
-
- getMetrics() {
- return {
- ...this.metrics,
- hitRate: this.getHitRate(),
- size: this.cache.size,
- maxSize: this.cache.max
- };
- }
+ constructor(maxSize, ttl) {
+ this.cache = new LRU(maxSize, ttl);
+ this.metrics = {
+ hits: 0,
+ misses: 0,
+ sets: 0,
+ deletes: 0,
+ evictions: 0,
+ };
+ }
+
+ get(key) {
+ const value = this.cache.get(key);
+ this.metrics[value !== undefined ? "hits" : "misses"]++;
+ return value;
+ }
+
+ set(key, value) {
+ const hadKey = this.cache.has(key);
+ const wasAtCapacity = this.cache.size >= this.cache.max;
+
+ const result = this.cache.set(key, value);
+
+ this.metrics.sets++;
+ if (!hadKey && wasAtCapacity && this.cache.max > 0) {
+ this.metrics.evictions++;
+ }
+
+ return result;
+ }
+
+ getHitRate() {
+ const total = this.metrics.hits + this.metrics.misses;
+ return total > 0 ? this.metrics.hits / total : 0;
+ }
+
+ getMetrics() {
+ return {
+ ...this.metrics,
+ hitRate: this.getHitRate(),
+ size: this.cache.size,
+ maxSize: this.cache.max,
+ };
+ }
}
```
@@ -1038,36 +1072,38 @@ The library uses Rollup for building and distributing multiple module formats to
```javascript
// rollup.config.js
-export default [{
- input: "./src/lru.js",
- output: [
- // CommonJS for Node.js
- { format: "cjs", file: "dist/tiny-lru.cjs" },
- // ES Modules for modern bundlers
- { format: "esm", file: "dist/tiny-lru.js" },
- // Minified ES Modules
- { format: "esm", file: "dist/tiny-lru.min.js", plugins: [terser()] },
- // UMD for browsers
- { format: "umd", file: "dist/tiny-lru.umd.js", name: "lru" },
- // Minified UMD
- { format: "umd", file: "dist/tiny-lru.umd.min.js", name: "lru", plugins: [terser()] }
- ]
-}];
+export default [
+ {
+ input: "./src/lru.js",
+ output: [
+ // CommonJS for Node.js
+ { format: "cjs", file: "dist/tiny-lru.cjs" },
+ // ES Modules for modern bundlers
+ { format: "esm", file: "dist/tiny-lru.js" },
+ // Minified ES Modules
+ { format: "esm", file: "dist/tiny-lru.min.js", plugins: [terser()] },
+ // UMD for browsers
+ { format: "umd", file: "dist/tiny-lru.umd.js", name: "lru" },
+ // Minified UMD
+ { format: "umd", file: "dist/tiny-lru.umd.min.js", name: "lru", plugins: [terser()] },
+ ],
+ },
+];
```
### Package Exports
```json
{
- "source": "src/lru.js",
- "main": "dist/tiny-lru.cjs",
- "exports": {
- "types": "./types/lru.d.ts",
- "import": "./dist/tiny-lru.js",
- "require": "./dist/tiny-lru.cjs"
- },
- "type": "module",
- "types": "types/lru.d.ts"
+ "source": "src/lru.js",
+ "main": "dist/tiny-lru.cjs",
+ "exports": {
+ "types": "./types/lru.d.ts",
+ "import": "./dist/tiny-lru.js",
+ "require": "./dist/tiny-lru.cjs"
+ },
+ "type": "module",
+ "types": "types/lru.d.ts"
}
```
@@ -1110,7 +1146,7 @@ tiny-lru/
│ ├── tiny-lru.umd.js # UMD
│ └── tiny-lru.umd.min.js # Minified UMD
├── tests/
-│ ├── unit/ # Unit tests with mocha
+│ ├── unit/ # Unit tests with Node.js built-in test runner
│ └── integration/ # Integration tests
├── benchmarks/ # Performance benchmarks
└── docs/ # Documentation
@@ -1121,10 +1157,11 @@ tiny-lru/
The tiny-lru library provides a robust foundation for caching in modern applications. By following these patterns, security guidelines, and best practices, you can build efficient, secure, and maintainable caching solutions for contemporary use cases including AI/ML applications, API gateways, and high-performance web services.
Remember to:
+
- Use proper key namespacing for security
- Monitor cache performance and hit rates
- Implement appropriate TTL strategies
- Handle errors gracefully
- Consider memory constraints in your sizing decisions
- Choose the appropriate module format for your environment
-- Leverage TypeScript support for type safety
+- Leverage TypeScript support for type safety
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/oxlint.json b/oxlint.json
new file mode 100644
index 0000000..672ec03
--- /dev/null
+++ b/oxlint.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
+ "ignorePatterns": ["!**/src/**", "!**/tests/**"],
+ "rules": {
+ "no-console": "error",
+ "no-unused-vars": "error"
+ }
+}
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..ad77125 100644
--- a/package.json
+++ b/package.json
@@ -1,73 +1,75 @@
{
- "name": "tiny-lru",
- "description": "A high-performance, lightweight LRU cache. Built for developers who need fast caching without compromising on features.",
- "version": "11.4.7",
- "homepage": "https://github.com/avoidwork/tiny-lru",
- "author": "Jason Mulligan ",
- "repository": {
- "type": "git",
- "url": "git://github.com/avoidwork/tiny-lru.git"
- },
- "bugs": {
- "url": "https://github.com/avoidwork/tiny-lru/issues"
- },
- "files": [
- "dist/tiny-lru.cjs",
- "dist/tiny-lru.js",
- "types/*.d.ts"
- ],
- "license": "BSD-3-Clause",
- "source": "src/lru.js",
- "main": "dist/tiny-lru.cjs",
- "exports": {
- "types": "./types/lru.d.ts",
- "import": "./dist/tiny-lru.js",
- "require": "./dist/tiny-lru.cjs"
- },
- "type": "module",
- "types": "types/lru.d.ts",
- "engines": {
- "node": ">=12"
- },
- "engineStrict": true,
- "scripts": {
- "build": "npm run lint && npm run rollup",
- "benchmark:modern": "node benchmarks/modern-benchmark.js",
- "benchmark:perf": "node benchmarks/performance-observer-benchmark.js",
- "benchmark:comparison": "npm run benchmark:install-deps && node benchmarks/comparison-benchmark.js",
- "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\"",
- "rollup": "rollup --config",
- "test": "npm run lint && npm run mocha",
- "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",
- "rollup": "^4.43.0",
- "tinybench": "^6.0.0"
- },
- "keywords": [
- "LRU",
- "cache",
- "caching",
- "performance",
- "memory",
- "TTL",
- "expiration",
- "memoization",
- "typescript",
- "browser",
- "nodejs",
- "lightweight",
- "fast",
- "least-recently-used"
- ]
+ "name": "tiny-lru",
+ "version": "11.4.7",
+ "description": "A fast, lightweight LRU (Least Recently Used) cache for JavaScript with O(1) operations and optional TTL support.",
+ "keywords": [
+ "LRU",
+ "cache",
+ "caching",
+ "TTL",
+ "ttl-cache",
+ "least-recently-used",
+ "lightweight",
+ "fast",
+ "performance",
+ "memoization",
+ "expiration",
+ "typescript",
+ "nodejs",
+ "browser",
+ "esm",
+ "zero-dependencies"
+ ],
+ "homepage": "https://github.com/avoidwork/tiny-lru",
+ "bugs": {
+ "url": "https://github.com/avoidwork/tiny-lru/issues"
+ },
+ "license": "BSD-3-Clause",
+ "author": "Jason Mulligan ",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/avoidwork/tiny-lru.git"
+ },
+ "source": "src/lru.js",
+ "files": [
+ "dist/tiny-lru.cjs",
+ "dist/tiny-lru.js",
+ "types/lru.d.ts"
+ ],
+ "type": "module",
+ "main": "dist/tiny-lru.cjs",
+ "types": "types/lru.d.ts",
+ "exports": {
+ "types": "./types/lru.d.ts",
+ "import": "./dist/tiny-lru.js",
+ "require": "./dist/tiny-lru.cjs"
+ },
+ "scripts": {
+ "build": "npm run lint && npm run rollup",
+ "benchmark:modern": "node benchmarks/modern-benchmark.js",
+ "benchmark:perf": "node benchmarks/performance-observer-benchmark.js",
+ "benchmark:comparison": "npm run benchmark:install-deps && node benchmarks/comparison-benchmark.js",
+ "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",
+ "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 && node --test tests/**/*.js",
+ "prepare": "husky"
+ },
+ "devDependencies": {
+ "@rollup/plugin-terser": "^1.0.0",
+ "auto-changelog": "^2.5.0",
+ "husky": "^9.1.7",
+ "oxfmt": "^0.41.0",
+ "oxlint": "^1.56.0",
+ "rollup": "^4.43.0",
+ "tinybench": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "engineStrict": true
}
diff --git a/rollup.config.js b/rollup.config.js
index 31caa38..05849ef 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -14,12 +14,11 @@ 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 [
{
@@ -27,28 +26,28 @@ export default [
output: [
{
...cjOutBase,
- file: `dist/${pkg.name}.cjs`
+ file: `dist/${pkg.name}.cjs`,
},
{
...esmOutBase,
- file: `dist/${pkg.name}.js`
+ file: `dist/${pkg.name}.js`,
},
{
...esmOutBase,
...minOutBase,
- file: `dist/${pkg.name}.min.js`
+ file: `dist/${pkg.name}.min.js`,
},
{
...umdOutBase,
file: `dist/${pkg.name}.umd.js`,
- name: "lru"
+ name: "lru",
},
{
...umdOutBase,
...minOutBase,
file: `dist/${pkg.name}.umd.min.js`,
- name: "lru"
- }
- ]
- }
+ name: "lru",
+ },
+ ],
+ },
];
diff --git a/src/lru.js b/src/lru.js
index 8ed976d..8f73490 100644
--- a/src/lru.js
+++ b/src/lru.js
@@ -4,17 +4,6 @@
* removing the least recently used items first. All core operations (get, set, delete) are O(1).
*
* @class LRU
- * @example
- * // Create a cache with max 100 items
- * const cache = new LRU(100);
- * cache.set('key1', 'value1');
- * console.log(cache.get('key1')); // 'value1'
- *
- * @example
- * // Create a cache with TTL
- * const cache = new LRU(100, 5000); // 5 second TTL
- * cache.set('key1', 'value1');
- * // After 5 seconds, key1 will be expired
*/
export class LRU {
/**
@@ -24,13 +13,9 @@ export class LRU {
* @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
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when updating existing items via set().
*/
- constructor (max = 0, ttl = 0, resetTtl = false) {
+ constructor(max = 0, ttl = 0, resetTtl = false) {
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -43,15 +28,9 @@ export class LRU {
/**
* 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 () {
+ clear() {
this.first = null;
this.items = Object.create(null);
this.last = null;
@@ -63,40 +42,20 @@ export class LRU {
/**
* 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(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;
- }
+ this.#unlink(item);
- if (this.last === item) {
- this.last = item.prev;
- }
+ item.prev = null;
+ item.next = null;
}
return this;
@@ -104,25 +63,22 @@ export class LRU {
/**
* Returns an array of [key, value] pairs for the specified keys.
- * Order follows LRU order (least to most recently used).
+ * When no keys provided, returns all entries in LRU order.
+ * When keys provided, order matches the input array.
*
- * @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
+ * @returns {Array>} Array of [key, value] pairs.
*/
- entries (keys = this.keys()) {
- const result = new Array(keys.length);
+ 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];
- result[i] = [key, this.get(key)];
+ const item = this.items[key];
+ result[i] = [key, item !== undefined ? item.value : undefined];
}
return result;
@@ -131,75 +87,47 @@ export class LRU {
/**
* 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;
+ evict() {
+ if (this.size === 0) {
+ return this;
+ }
- delete this.items[item.key];
+ const item = this.first;
- if (--this.size === 0) {
- this.first = null;
- this.last = null;
- } else {
- this.first = item.next;
- this.first.prev = null;
- }
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.#unlink(item);
}
+ 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) {
- let result;
-
- if (this.has(key)) {
- result = this.items[key].expiry;
- }
-
- return result;
+ 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) {
+ get(key) {
const item = this.items[key];
if (item !== undefined) {
@@ -224,40 +152,22 @@ export class LRU {
/**
* 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;
+ 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.
+ * Unlinks an item from the doubly-linked list.
+ * Updates first/last pointers if needed.
+ * Does NOT clear the item's prev/next pointers or delete from items map.
*
- * @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
+ #unlink(item) {
if (item.prev !== null) {
item.prev.next = item.next;
}
@@ -266,43 +176,43 @@ export class LRU {
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 === item) {
+ this.last = item.prev;
+ }
+ }
- if (this.last !== null) {
- this.last.next = item;
+ /**
+ * 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.
+ *
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ */
+ moveToEnd(item) {
+ if (this.last === item) {
+ return;
}
- this.last = item;
+ this.#unlink(item);
- // Handle edge case: if this was the only item, it's also first
- if (this.first === null) {
- this.first = item;
- }
+ 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 = new Array(this.size);
+ keys() {
+ const result = Array.from({ length: this.size });
let x = this.first;
let i = 0;
@@ -317,37 +227,36 @@ export class LRU {
/**
* 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
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry}, or null.
*/
- setWithEvicted (key, value, resetTtl = this.resetTtl) {
+ setWithEvicted(key, value) {
let evicted = null;
+ let item = this.items[key];
- if (this.has(key)) {
- this.set(key, value, true, resetTtl);
+ if (item !== undefined) {
+ item.value = value;
+ if (this.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 = {...this.first};
- this.evict(true);
+ evicted = {
+ key: this.first.key,
+ value: this.first.value,
+ expiry: this.first.expiry,
+ };
+ this.evict();
}
- let item = this.items[key] = {
+ item = this.items[key] = {
expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
key: key,
prev: this.last,
next: null,
- value
+ value,
};
if (++this.size === 1) {
@@ -365,38 +274,24 @@ export class LRU {
/**
* 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) {
+ set(key, value) {
let item = this.items[key];
- if (bypass || item !== undefined) {
- // Existing item: update value and position
+ if (item !== undefined) {
item.value = value;
- if (bypass === false && resetTtl) {
+ if (this.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);
+ this.evict();
}
item = this.items[key] = {
@@ -404,7 +299,7 @@ export class LRU {
key: key,
prev: this.last,
next: null,
- value
+ value,
};
if (++this.size === 1) {
@@ -421,24 +316,21 @@ export class LRU {
/**
* Returns an array of all values in the cache for the specified keys.
- * Order follows LRU order (least to most recently used).
+ * When no keys provided, returns all values in LRU order.
+ * When keys provided, order matches the input array.
*
- * @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
+ * @returns {Array<*>} Array of values corresponding to the keys.
*/
- values (keys = this.keys()) {
- const result = new Array(keys.length);
+ values(keys) {
+ if (keys === undefined) {
+ keys = this.keys();
+ }
+
+ const result = Array.from({ length: keys.length });
for (let i = 0; i < keys.length; i++) {
- result[i] = this.get(keys[i]);
+ const item = this.items[keys[i]];
+ result[i] = item !== undefined ? item.value : undefined;
}
return result;
@@ -454,22 +346,8 @@ export class LRU {
* @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
* @returns {LRU} A new LRU cache instance.
* @throws {TypeError} When parameters are invalid (negative numbers or wrong types).
- * @example
- * // Create cache with factory function
- * const cache = lru(100, 5000, true);
- * cache.set('key', 'value');
- *
- * @example
- * // Error handling
- * try {
- * const cache = lru(-1); // Invalid max
- * } catch (error) {
- * console.error(error.message); // "Invalid max value"
- * }
- * @see {@link LRU}
- * @since 1.0.0
*/
-export function lru (max = 1000, ttl = 0, resetTtl = false) {
+export function lru(max = 1000, ttl = 0, resetTtl = false) {
if (isNaN(max) || max < 0) {
throw new TypeError("Invalid max value");
}
diff --git a/tests/unit/lru.js b/tests/unit/lru.test.js
similarity index 62%
rename from tests/unit/lru.js
rename to tests/unit/lru.test.js
index 4137fad..2c22683 100644
--- a/tests/unit/lru.js
+++ b/tests/unit/lru.test.js
@@ -1,5 +1,6 @@
-import {LRU, lru} from "../../src/lru.js";
-import {strict as assert} from "assert";
+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 () {
@@ -129,7 +130,7 @@ describe("LRU Cache", function () {
cache.set("key1", "value1");
cache.set("key2", "value2");
cache.set("key3", "value3");
- cache.set("key4", "value4"); // Should evict key1
+ cache.set("key4", "value4");
assert.equal(cache.size, 3);
assert.equal(cache.has("key1"), false);
@@ -143,10 +144,9 @@ describe("LRU Cache", function () {
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
+ cache.set("key4", "value4");
assert.equal(cache.has("key1"), true);
assert.equal(cache.has("key2"), false);
@@ -162,7 +162,6 @@ describe("LRU Cache", function () {
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"]);
@@ -195,11 +194,6 @@ describe("LRU Cache", function () {
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();
@@ -276,7 +270,7 @@ describe("LRU Cache", function () {
assert.deepEqual(entries, [
["key1", "value1"],
["key2", "value2"],
- ["key3", "value3"]
+ ["key3", "value3"],
]);
});
@@ -284,7 +278,7 @@ describe("LRU Cache", function () {
const entries = cache.entries(["key3", "key1"]);
assert.deepEqual(entries, [
["key3", "value3"],
- ["key1", "value1"]
+ ["key1", "value1"],
]);
});
@@ -300,7 +294,7 @@ describe("LRU Cache", function () {
let cache;
beforeEach(function () {
- cache = new LRU(5, 100); // 100ms TTL
+ cache = new LRU(5, 100);
});
it("should set expiration time", function () {
@@ -309,23 +303,21 @@ describe("LRU Cache", function () {
const expiresAt = cache.expiresAt("key1");
assert.ok(expiresAt >= beforeTime + 100);
- assert.ok(expiresAt <= beforeTime + 200); // Allow some margin
+ 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", function (done) {
+ it("should expire items after TTL", async function () {
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);
+ 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 () {
@@ -334,37 +326,42 @@ describe("LRU Cache", function () {
assert.equal(neverExpireCache.expiresAt("key1"), 0);
});
- it("should reset TTL when accessing with resetTtl=true", function (done) {
+ it("should reset TTL when updating with resetTtl=true", async function () {
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");
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ resetCache.set("key1", "value1");
+ const secondExpiry = resetCache.expiresAt("key1");
- assert.ok(secondExpiry > firstExpiry, "TTL should be reset");
- done();
- }, 10);
+ assert.ok(secondExpiry > firstExpiry, "TTL should be reset");
});
- it("should not reset TTL when resetTtl=false", function (done) {
+ it("should not reset TTL when resetTtl=false", async function () {
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");
+ 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);
+ });
+
+ it("should not reset TTL on get() even with resetTtl=true", async function () {
+ const resetCache = new LRU(5, 100, true);
+ resetCache.set("key1", "value1");
+
+ const firstExpiry = resetCache.expiresAt("key1");
- // Check that it expires at original time
- setTimeout(() => {
- assert.equal(noResetCache.get("key1"), undefined);
- done();
- }, 75);
- }, 50);
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ resetCache.get("key1");
+ const secondExpiry = resetCache.expiresAt("key1");
+
+ // TTL should NOT be reset by get()
+ assert.ok(secondExpiry <= firstExpiry + 15, "TTL should not be reset by get()");
});
});
@@ -373,7 +370,7 @@ describe("LRU Cache", function () {
const cache = new LRU(3);
cache.set("key1", "value1");
cache.set("key2", "value2");
- cache.set("key1", "newvalue1"); // Update existing key
+ cache.set("key1", "newvalue1");
assert.equal(cache.get("key1"), "newvalue1");
assert.equal(cache.size, 2);
@@ -385,15 +382,12 @@ describe("LRU Cache", function () {
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);
@@ -407,44 +401,20 @@ describe("LRU Cache", function () {
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
+ 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");
-
- // 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);
@@ -455,13 +425,11 @@ describe("LRU Cache", function () {
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
+ cache.delete("key1");
assert.equal(cache.expiresAt("key1"), undefined);
- // Evict on empty cache
cache.evict();
assert.equal(cache.size, 0);
});
@@ -472,29 +440,168 @@ describe("LRU Cache", function () {
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"]);
});
+
+ it("should clear prev/next pointers on eviction for garbage collection", function () {
+ const cache = new LRU(2);
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+
+ const firstItem = cache.first;
+ cache.set("key3", "value3");
+
+ // Evicted item should have nullified pointers
+ assert.equal(firstItem.prev, null);
+ assert.equal(firstItem.next, null);
+ });
+
+ it("should clear prev/next pointers on delete for garbage collection", function () {
+ const cache = new LRU(3);
+ cache.set("key1", "value1");
+ cache.set("key2", "value2");
+ cache.set("key3", "value3");
+
+ const middleItem = cache.items["key2"];
+ cache.delete("key2");
+
+ assert.equal(middleItem.prev, null);
+ assert.equal(middleItem.next, null);
+ });
+
+ it("should handle first/last consistency after middle deletion", function () {
+ const cache = new LRU(3);
+ cache.set("a", 1);
+ cache.set("b", 2);
+ cache.set("c", 3);
+
+ cache.delete("b");
+
+ assert.equal(cache.first.key, "a");
+ assert.equal(cache.last.key, "c");
+ assert.equal(cache.first.next.key, "c");
+ assert.equal(cache.last.prev.key, "a");
+ });
+
+ it("should handle first/last consistency after first deletion", function () {
+ const cache = new LRU(3);
+ cache.set("a", 1);
+ cache.set("b", 2);
+ cache.set("c", 3);
+
+ cache.delete("a");
+
+ assert.equal(cache.first.key, "b");
+ assert.equal(cache.last.key, "c");
+ assert.equal(cache.first.prev, null);
+ });
+
+ it("should handle first/last consistency after last deletion", function () {
+ const cache = new LRU(3);
+ cache.set("a", 1);
+ cache.set("b", 2);
+ cache.set("c", 3);
+
+ cache.delete("c");
+
+ assert.equal(cache.first.key, "a");
+ assert.equal(cache.last.key, "b");
+ assert.equal(cache.last.next, null);
+ });
+ });
+
+ describe("Different key types", function () {
+ it("should handle number keys", function () {
+ const cache = new LRU(3);
+ cache.set(1, "one");
+ cache.set(2, "two");
+ cache.set(3, "three");
+
+ assert.equal(cache.get(1), "one");
+ assert.equal(cache.get(2), "two");
+ assert.equal(cache.has(3), true);
+ });
+
+ it("should handle null as key value", function () {
+ const cache = new LRU(3);
+ cache.set("key1", null);
+ assert.equal(cache.get("key1"), null);
+ assert.equal(cache.has("key1"), true);
+ });
+
+ it("should handle undefined as key value", function () {
+ const cache = new LRU(3);
+ cache.set("key1", undefined);
+ assert.equal(cache.get("key1"), undefined);
+ assert.equal(cache.has("key1"), true);
+ });
+
+ it("should handle function values", function () {
+ const cache = new LRU(3);
+ const fn = () => "hello";
+ cache.set("key1", fn);
+
+ const retrieved = cache.get("key1");
+ assert.equal(retrieved(), "hello");
+ });
+
+ it("should handle object values", function () {
+ const cache = new LRU(3);
+ const obj = { nested: { value: 42 } };
+ cache.set("key1", obj);
+
+ const retrieved = cache.get("key1");
+ assert.equal(retrieved.nested.value, 42);
+ });
+
+ it("should handle array values", function () {
+ const cache = new LRU(3);
+ const arr = [1, 2, 3, 4, 5];
+ cache.set("key1", arr);
+
+ const retrieved = cache.get("key1");
+ assert.deepEqual(retrieved, [1, 2, 3, 4, 5]);
+ });
+ });
+
+ describe("Unlimited cache (max=0) edge cases", function () {
+ it("should never evict with unlimited size", function () {
+ const cache = new LRU(0);
+ for (let i = 0; i < 10000; i++) {
+ cache.set(`key${i}`, `value${i}`);
+ }
+ assert.equal(cache.size, 10000);
+ assert.equal(cache.has("key0"), true);
+ assert.equal(cache.has("key9999"), true);
+ });
+
+ it("should maintain LRU order with unlimited size", function () {
+ const cache = new LRU(0);
+ cache.set("a", 1);
+ cache.set("b", 2);
+ cache.set("c", 3);
+
+ cache.get("a");
+
+ assert.deepEqual(cache.keys(), ["b", "c", "a"]);
+ });
});
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}`);
}
@@ -515,7 +622,7 @@ describe("LRU Cache", function () {
describe("Additional coverage tests", function () {
it("should handle setWithEvicted with unlimited cache size", function () {
- const cache = new LRU(0); // Unlimited size
+ const cache = new LRU(0);
const evicted = cache.setWithEvicted("key1", "value1");
assert.equal(evicted, null);
assert.equal(cache.size, 1);
@@ -528,95 +635,97 @@ describe("LRU Cache", function () {
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 cache = new LRU(2, 100);
const before = Date.now();
cache.set("a", 1);
cache.set("b", 2);
- const evicted = cache.setWithEvicted("c", 3); // triggers eviction and new item creation
+ 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); // allow some margin
+ 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); // ttl = 0
+ const cache = new LRU(2, 0, true);
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);
+ cache.set("x", 2);
assert.equal(cache.expiresAt("x"), 0);
});
- it("should handle moveToEnd edge case by direct method invocation", function () {
- const cache = new LRU(1);
+ it("should set expiry to 0 when resetTtl=true and ttl=0 on setWithEvicted", function () {
+ const cache = new LRU(2, 0, true);
+ cache.set("x", 1);
+ assert.equal(cache.expiresAt("x"), 0);
+ cache.setWithEvicted("x", 2);
+ assert.equal(cache.expiresAt("x"), 0);
+ });
- // Add a single item
- cache.set("only", "value");
+ it("should handle evict on empty cache without errors", function () {
+ const cache = new LRU(3);
+ cache.evict();
+ assert.equal(cache.size, 0);
+ assert.equal(cache.first, null);
+ assert.equal(cache.last, null);
+ });
+
+ it("should handle updating first item with setWithEvicted", function () {
+ const cache = new LRU(2);
+ cache.set("a", 1);
+ cache.set("b", 2);
- // 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 evicted = cache.setWithEvicted("a", 10);
+ assert.equal(evicted, null);
+ assert.equal(cache.get("a"), 10);
+ assert.deepEqual(cache.keys(), ["b", "a"]);
+ });
- 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;
+ it("should handle updating last item with setWithEvicted", function () {
+ const cache = new LRU(2);
+ cache.set("a", 1);
+ cache.set("b", 2);
+
+ const evicted = cache.setWithEvicted("b", 20);
+ assert.equal(evicted, null);
+ assert.equal(cache.get("b"), 20);
+ assert.deepEqual(cache.keys(), ["a", "b"]);
+ });
+
+ it("should return correct evicted item shape", function () {
+ const cache = new LRU(1, 1000);
+ cache.set("old", "value");
+ const firstExpiry = cache.expiresAt("old");
+
+ const evicted = cache.setWithEvicted("new", "newvalue");
+
+ assert.equal(evicted.key, "old");
+ assert.equal(evicted.value, "value");
+ assert.equal(evicted.expiry, firstExpiry);
+ assert.equal(typeof evicted.expiry, "number");
+ });
+
+ it("should handle entries() with non-existent keys", function () {
+ const cache = new LRU(3);
+ cache.set("a", 1);
+ cache.set("b", 2);
+
+ const entries = cache.entries(["a", "nonexistent", "b"]);
+ assert.deepEqual(entries, [
+ ["a", 1],
+ ["nonexistent", undefined],
+ ["b", 2],
+ ]);
+ });
+
+ it("should handle values() with non-existent keys", function () {
+ const cache = new LRU(3);
+ cache.set("a", 1);
+ cache.set("b", 2);
+
+ const values = cache.values(["a", "nonexistent", "b"]);
+ assert.deepEqual(values, [1, undefined, 2]);
});
});
});
-
diff --git a/types/lru.d.ts b/types/lru.d.ts
index 3f83b9d..3429ff5 100644
--- a/types/lru.d.ts
+++ b/types/lru.d.ts
@@ -12,16 +12,28 @@ export function lru(max?: number, ttl?: number, resetTtl?: boolean): LR
* Internal structure representing a cache item in the doubly-linked list.
*/
export interface LRUItem {
- /** Expiration timestamp in milliseconds (0 if no TTL) */
- expiry: number;
- /** The key associated with this item */
- key: any;
- /** Pointer to the previous item in the LRU list */
- prev: LRUItem | null;
- /** Pointer to the next item in the LRU list */
- next: LRUItem | null;
- /** The cached value */
- value: T;
+ /** Expiration timestamp in milliseconds (0 if no TTL) */
+ expiry: number;
+ /** The key associated with this item */
+ key: any;
+ /** Pointer to the previous item in the LRU list */
+ prev: LRUItem | null;
+ /** Pointer to the next item in the LRU list */
+ next: LRUItem | null;
+ /** The cached value */
+ 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;
}
/**
@@ -29,109 +41,105 @@ export interface LRUItem {
* All core operations (get, set, delete) are O(1).
*/
export class LRU {
- /**
- * Creates a new LRU cache instance.
- * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
- * @param max Maximum number of items to store (default: 0, 0 = unlimited)
- * @param ttl Time to live in milliseconds (default: 0, 0 = no expiration)
- * @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 */
- readonly items: Record>;
- /** Pointer to the most recently used item */
- readonly last: LRUItem | null;
- /** Maximum number of items to store (0 = unlimited) */
- readonly max: number;
- /** Whether to reset TTL on each get() operation */
- readonly resetTtl: boolean;
- /** Current number of items in the cache */
- 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][];
-
- /**
- * Removes the least recently used item from the cache.
- * @param bypass Whether to force eviction even when cache is empty
- * @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.
- * @param key The key to check for
- * @returns True if the key exists, 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
- * @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
- */
- setWithEvicted(key: any, value: T, resetTtl?: boolean): LRUItem | 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
- */
- values(keys?: any[]): T[];
+ /**
+ * Creates a new LRU cache instance.
+ * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
+ * @param max Maximum number of items to store (default: 0, 0 = unlimited)
+ * @param ttl Time to live in milliseconds (default: 0, 0 = no expiration)
+ * @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 */
+ readonly items: Record>;
+ /** Pointer to the most recently used item */
+ readonly last: LRUItem | null;
+ /** Maximum number of items to store (0 = unlimited) */
+ readonly max: number;
+ /** Whether to reset TTL on each get() operation */
+ readonly resetTtl: boolean;
+ /** Current number of items in the cache */
+ 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 | undefined][];
+
+ /**
+ * Removes the least recently used item from the cache.
+ * @returns The LRU instance for method chaining
+ */
+ evict(): 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 (not expired).
+ * @param key The key to check for
+ * @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
+ * @returns The LRU instance for method chaining
+ */
+ set(key: any, value: T): this;
+
+ /**
+ * Sets a value in the cache and returns any evicted item.
+ * @param key The key to set
+ * @param value The value to store
+ * @returns The evicted item (if any) with {key, value, expiry} or null
+ */
+ setWithEvicted(key: any, value: T): 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 (undefined for missing/expired keys)
+ */
+ values(keys?: any[]): (T | undefined)[];
}