From ac8d354ec9cd96ce0a8bdf058d9aa4bf490f6552 Mon Sep 17 00:00:00 2001 From: Jason Mulligan Date: Sat, 26 Sep 2026 22:54:18 -0400 Subject: [PATCH] chore: release v13.1.0 --- CHANGELOG.md | 13 +++++++++++++ dist/tiny-lru.cjs | 25 ++++++++++++++++--------- dist/tiny-lru.js | 25 ++++++++++++++++--------- dist/tiny-lru.min.js | 4 ++-- dist/tiny-lru.min.js.map | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 7 files changed, 51 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8fa6b0..ced1a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,21 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [13.1.0](https://github.com/avoidwork/tiny-lru/compare/13.0.1...13.1.0) + +- feat: add fireOnEvict option to cleanup() [`#498`](https://github.com/avoidwork/tiny-lru/pull/498) +- docs: correct README, API docs, and type definitions [`#497`](https://github.com/avoidwork/tiny-lru/pull/497) +- build(deps-dev): bump oxlint from 1.83.0 to 1.85.0 [`#496`](https://github.com/avoidwork/tiny-lru/pull/496) +- build(deps-dev): bump rollup from 4.63.3 to 4.63.4 [`#494`](https://github.com/avoidwork/tiny-lru/pull/494) +- build(deps-dev): bump rollup from 4.63.2 to 4.63.3 [`#492`](https://github.com/avoidwork/tiny-lru/pull/492) +- build(deps-dev): bump oxlint from 1.82.0 to 1.83.0 [`#491`](https://github.com/avoidwork/tiny-lru/pull/491) +- build(deps-dev): bump rollup from 4.63.1 to 4.63.2 [`#490`](https://github.com/avoidwork/tiny-lru/pull/490) + #### [13.0.1](https://github.com/avoidwork/tiny-lru/compare/13.0.0...13.0.1) +> 15 September 2026 + +- chore: release v13.0.1 [`#489`](https://github.com/avoidwork/tiny-lru/pull/489) - fix: LRU edge cases in TTL handling and factory validation [`#488`](https://github.com/avoidwork/tiny-lru/pull/488) - build(deps-dev): bump tinybench from 6.1.6 to 6.2.0 [`#486`](https://github.com/avoidwork/tiny-lru/pull/486) - build(deps-dev): bump oxlint from 1.81.0 to 1.82.0 [`#484`](https://github.com/avoidwork/tiny-lru/pull/484) diff --git a/dist/tiny-lru.cjs b/dist/tiny-lru.cjs index e27f4d5..dddd421 100644 --- a/dist/tiny-lru.cjs +++ b/dist/tiny-lru.cjs @@ -3,7 +3,7 @@ * * @copyright 2026 Jason Mulligan * @license BSD-3-Clause - * @version 13.0.1 + * @version 13.1.0 */ 'use strict'; @@ -321,17 +321,16 @@ class LRU { /** * Returns an array of all keys in the cache, ordered from least to most recently used. + * Expired items are skipped, consistent with entries(), values(), and toJSON(). * * @returns {string[]} Array of keys in LRU order. */ keys() { - const result = Array.from({ length: this.size }); - let x = this.first; - let i = 0; - - while (x !== null) { - result[i++] = x.key; - x = x.next; + const result = []; + for (let x = this.first; x !== null; x = x.next) { + if (!this.#isExpired(x)) { + result.push(x.key); + } } return result; @@ -557,9 +556,10 @@ class LRU { * Remove expired items without affecting LRU order. * Unlike get(), this does not move items to the end. * + * @param {boolean} [fireOnEvict=false] - When true, invokes the onEvict callback for each removed item. * @returns {number} Number of expired items removed. */ - cleanup() { + cleanup(fireOnEvict = false) { if (this.ttl === 0 || this.size === 0) { return 0; } @@ -577,6 +577,13 @@ class LRU { this.#unlink(x); x.prev = null; x.next = null; + if (fireOnEvict && this.#onEvict !== null) { + this.#onEvict({ + key: x.key, + value: x.value, + expiry: x.expiry, + }); + } } } x = next; diff --git a/dist/tiny-lru.js b/dist/tiny-lru.js index 8edb288..0cd9aa6 100644 --- a/dist/tiny-lru.js +++ b/dist/tiny-lru.js @@ -3,7 +3,7 @@ * * @copyright 2026 Jason Mulligan * @license BSD-3-Clause - * @version 13.0.1 + * @version 13.1.0 */ /** * A high-performance Least Recently Used (LRU) cache implementation with optional TTL support. @@ -319,17 +319,16 @@ class LRU { /** * Returns an array of all keys in the cache, ordered from least to most recently used. + * Expired items are skipped, consistent with entries(), values(), and toJSON(). * * @returns {string[]} Array of keys in LRU order. */ keys() { - const result = Array.from({ length: this.size }); - let x = this.first; - let i = 0; - - while (x !== null) { - result[i++] = x.key; - x = x.next; + const result = []; + for (let x = this.first; x !== null; x = x.next) { + if (!this.#isExpired(x)) { + result.push(x.key); + } } return result; @@ -555,9 +554,10 @@ class LRU { * Remove expired items without affecting LRU order. * Unlike get(), this does not move items to the end. * + * @param {boolean} [fireOnEvict=false] - When true, invokes the onEvict callback for each removed item. * @returns {number} Number of expired items removed. */ - cleanup() { + cleanup(fireOnEvict = false) { if (this.ttl === 0 || this.size === 0) { return 0; } @@ -575,6 +575,13 @@ class LRU { this.#unlink(x); x.prev = null; x.next = null; + if (fireOnEvict && this.#onEvict !== null) { + this.#onEvict({ + key: x.key, + value: x.value, + expiry: x.expiry, + }); + } } } x = next; diff --git a/dist/tiny-lru.min.js b/dist/tiny-lru.min.js index ed10e42..9c87efa 100644 --- a/dist/tiny-lru.min.js +++ b/dist/tiny-lru.min.js @@ -1,5 +1,5 @@ /*! 2026 Jason Mulligan - @version 13.0.1 + @version 13.1.0 */ -class t{#t;#s;constructor(t=0,s=0,e=!1){if(!Number.isInteger(t)||t<0)throw new TypeError("Invalid max value");if(!Number.isInteger(s)||s<0)throw new TypeError("Invalid ttl value");if("boolean"!=typeof e)throw new TypeError("Invalid resetTTL value");this.first=null,this.items=Object.create(null),this.last=null,this.max=t,this.resetTTL=e,this.size=0,this.ttl=s,this.#t={hits:0,misses:0,sets:0,deletes:0,evictions:0},this.#s=null}clear(){for(let t=this.first;null!==t;){const s=t.next;t.prev=null,t.next=null,t=s}return this.first=null,this.items=Object.create(null),this.last=null,this.size=0,this.#t.hits=0,this.#t.misses=0,this.#t.sets=0,this.#t.deletes=0,this.#t.evictions=0,this}delete(t){const s=this.items[t];return void 0!==s&&(this.#e(s),this.#t.deletes++),this}entries(t){if(void 0===t){const t=[];for(let s=this.first;null!==s;s=s.next)this.#i(s)||t.push([s.key,s.value]);return t}if(!Array.isArray(t))throw new TypeError("keys must be an array");const s=Array.from({length:t.length});for(let e=0;e0&&this.size===this.max){const t=this.#r();e={key:t.key,value:t.value,expiry:t.expiry}}i=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=i:this.last.next=i,this.last=i}else i.value=s,this.resetTTL&&(i.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(i);return this.#t.sets++,e}set(t,s){let e=this.items[t];return void 0===e||this.#i(e)?(void 0!==e&&this.#e(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:s},1==++this.size?this.first=e:this.last.next=e,this.last=e):(e.value=s,this.resetTTL&&(e.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(e)),this.#t.sets++,this}values(t){if(void 0===t){const t=[];for(let s=this.first;null!==s;s=s.next)this.#i(s)||t.push(s.value);return t}if(!Array.isArray(t))throw new TypeError("keys must be an array");const s=Array.from({length:t.length});for(let e=0;e0&&this.#n(),t}toJSON(){const t=[];for(let s=this.first;null!==s;s=s.next)this.#i(s)||t.push({key:s.key,value:s.value,expiry:s.expiry});return t}stats(){return{...this.#t}}onEvict(t){if("function"!=typeof t)throw new TypeError("onEvict callback must be a function");return this.#s=t,this}sizeByTTL(){if(0===this.ttl)return{valid:this.size,expired:0,noTTL:this.size};const t=Date.now();let s=0,e=0;for(let i=this.first;null!==i;i=i.next)i.expiry>t?s++:e++;return{valid:s,expired:e,noTTL:0}}keysByTTL(){if(0===this.ttl)return{valid:this.keys(),expired:[],noTTL:this.keys()};const t=Date.now(),s=[],e=[];for(let i=this.first;null!==i;i=i.next)i.expiry>t?s.push(i.key):e.push(i.key);return{valid:s,expired:e,noTTL:[]}}valuesByTTL(){if(0===this.ttl)return{valid:this.values(),expired:[],noTTL:this.values()};const t=Date.now(),s=[],e=[];for(let i=this.first;null!==i;i=i.next)i.expiry>t?s.push(i.value):e.push(i.value);return{valid:s,expired:e,noTTL:[]}}#n(){if(0===this.size)return this.first=null,void(this.last=null);const t=this.keys();this.first=null,this.last=null;for(let s=0;s0&&this.size===this.max){const t=this.#r();e={key:t.key,value:t.value,expiry:t.expiry}}i=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=i:this.last.next=i,this.last=i}else i.value=s,this.resetTTL&&(i.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(i);return this.#t.sets++,e}set(t,s){let e=this.items[t];return void 0===e||this.#i(e)?(void 0!==e&&this.#e(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:s},1==++this.size?this.first=e:this.last.next=e,this.last=e):(e.value=s,this.resetTTL&&(e.expiry=this.ttl>0?Date.now()+this.ttl:this.ttl),this.moveToEnd(e)),this.#t.sets++,this}values(t){if(void 0===t){const t=[];for(let s=this.first;null!==s;s=s.next)this.#i(s)||t.push(s.value);return t}if(!Array.isArray(t))throw new TypeError("keys must be an array");const s=Array.from({length:t.length});for(let e=0;e0&&this.#n(),s}toJSON(){const t=[];for(let s=this.first;null!==s;s=s.next)this.#i(s)||t.push({key:s.key,value:s.value,expiry:s.expiry});return t}stats(){return{...this.#t}}onEvict(t){if("function"!=typeof t)throw new TypeError("onEvict callback must be a function");return this.#s=t,this}sizeByTTL(){if(0===this.ttl)return{valid:this.size,expired:0,noTTL:this.size};const t=Date.now();let s=0,e=0;for(let i=this.first;null!==i;i=i.next)i.expiry>t?s++:e++;return{valid:s,expired:e,noTTL:0}}keysByTTL(){if(0===this.ttl)return{valid:this.keys(),expired:[],noTTL:this.keys()};const t=Date.now(),s=[],e=[];for(let i=this.first;null!==i;i=i.next)i.expiry>t?s.push(i.key):e.push(i.key);return{valid:s,expired:e,noTTL:[]}}valuesByTTL(){if(0===this.ttl)return{valid:this.values(),expired:[],noTTL:this.values()};const t=Date.now(),s=[],e=[];for(let i=this.first;null!==i;i=i.next)i.expiry>t?s.push(i.value):e.push(i.value);return{valid:s,expired:e,noTTL:[]}}#n(){if(0===this.size)return this.first=null,void(this.last=null);const t=this.keys();this.first=null,this.last=null;for(let s=0;s>} Array of [key, value] pairs.\n\t */\n\tentries(keys) {\n\t\tif (keys === undefined) {\n\t\t\tconst result = [];\n\t\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\t\tresult.push([x.key, x.value]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\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 && !this.#isExpired(item) ? 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.#evictItem();\n\n\t\tif (this.#onEvict !== null) {\n\t\t\tthis.#onEvict({\n\t\t\t\tkey: item.key,\n\t\t\t\tvalue: item.value,\n\t\t\t\texpiry: item.expiry,\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 * @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 * Checks if an item has expired.\n\t *\n\t * @param {Object} item - The cache item to check.\n\t * @returns {boolean} True if the item has expired, false otherwise.\n\t * @private\n\t */\n\t#isExpired(item) {\n\t\tif (this.ttl === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn item.expiry <= Date.now();\n\t}\n\n\t/**\n\t * Retrieves a value from the cache by key without updating LRU order.\n\t * Note: Does not perform TTL checks or remove expired items.\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.\n\t */\n\tpeek(key) {\n\t\tconst item = this.items[key];\n\t\treturn item !== undefined ? item.value : 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\tif (!this.#isExpired(item)) {\n\t\t\t\tthis.moveToEnd(item);\n\t\t\t\tthis.#stats.hits++;\n\t\t\t\treturn item.value;\n\t\t\t}\n\n\t\t\tthis.#removeItem(item);\n\t\t\tthis.#stats.misses++;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis.#stats.misses++;\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Checks if a key exists in the cache.\n\t * Expired items are removed before returning false.\n\t *\n\t * @param {string} key - The key to check for.\n\t * @returns {boolean} True if the key exists and is not expired, false otherwise.\n\t */\n\thas(key) {\n\t\tconst item = this.items[key];\n\n\t\tif (item !== undefined && this.#isExpired(item)) {\n\t\t\tthis.#removeItem(item);\n\t\t\treturn false;\n\t\t}\n\n\t\treturn item !== undefined;\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 * Removes an item from the cache without incrementing the deletes stat.\n\t * Used internally by get()/has() when removing expired items.\n\t *\n\t * @param {Object} item - The cache item to remove.\n\t * @private\n\t */\n\t#removeItem(item) {\n\t\tdelete this.items[item.key];\n\t\tthis.size--;\n\t\tthis.#unlink(item);\n\t\titem.prev = null;\n\t\titem.next = null;\n\t}\n\n\t/**\n\t * Evicts the least recently used item from the cache without firing onEvict.\n\t * Used internally by setWithEvicted() to avoid double-notification.\n\t *\n\t * @returns {Object} The evicted item.\n\t * @private\n\t */\n\t#evictItem() {\n\t\tconst item = this.first;\n\n\t\tdelete this.items[item.key];\n\t\tthis.#stats.evictions++;\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.prev = null;\n\t\titem.next = null;\n\n\t\treturn item;\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 * Eviction is silent — onEvict is not fired for the returned 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 && !this.#isExpired(item)) {\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 (item !== undefined) {\n\t\t\t\tthis.#removeItem(item);\n\t\t\t}\n\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tconst evictedItem = this.#evictItem();\n\t\t\t\tevicted = {\n\t\t\t\t\tkey: evictedItem.key,\n\t\t\t\t\tvalue: evictedItem.value,\n\t\t\t\t\texpiry: evictedItem.expiry,\n\t\t\t\t};\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\tthis.#stats.sets++;\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 && !this.#isExpired(item)) {\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 (item !== undefined) {\n\t\t\t\tthis.#removeItem(item);\n\t\t\t}\n\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\tthis.#stats.sets++;\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] - 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\tconst result = [];\n\t\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\t\tresult.push(x.value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\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 && !this.#isExpired(item) ? item.value : undefined;\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Iterate over cache items in LRU order (least to most recent).\n\t * Note: This method directly accesses items from the linked list without calling\n\t * get() or peek(), so it does not update LRU order. Expired items are skipped.\n\t *\n\t * @param {function(*, any, LRU): void} callback - Function to call for each item. Signature: callback(value, key, cache)\n\t * @param {Object} [thisArg] - Value to use as `this` when executing callback.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tforEach(callback, thisArg) {\n\t\tfor (let x = this.first; x !== null; ) {\n\t\t\tconst next = x.next;\n\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\tcallback.call(thisArg, x.value, x.key, this);\n\t\t\t}\n\t\t\tx = next;\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Batch retrieve multiple items.\n\t *\n\t * @param {string[]} keys - Array of keys to retrieve.\n\t * @returns {Object} Object mapping keys to values (undefined for missing/expired keys).\n\t */\n\tgetMany(keys) {\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\n\t\t}\n\n\t\tconst result = Object.create(null);\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst key = keys[i];\n\t\t\tresult[key] = this.get(key);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Batch existence check - returns true if ALL keys exist.\n\t *\n\t * @param {string[]} keys - Array of keys to check.\n\t * @returns {boolean} True if all keys exist and are not expired.\n\t */\n\thasAll(keys) {\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\n\t\t}\n\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tif (!this.has(keys[i])) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Batch existence check - returns true if ANY key exists.\n\t *\n\t * @param {string[]} keys - Array of keys to check.\n\t * @returns {boolean} True if any key exists and is not expired.\n\t */\n\thasAny(keys) {\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\n\t\t}\n\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tif (this.has(keys[i])) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Remove expired items without affecting LRU order.\n\t * Unlike get(), this does not move items to the end.\n\t *\n\t * @returns {number} Number of expired items removed.\n\t */\n\tcleanup() {\n\t\tif (this.ttl === 0 || this.size === 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet removed = 0;\n\n\t\tfor (let x = this.first; x !== null; ) {\n\t\t\tconst next = x.next;\n\t\t\tif (this.#isExpired(x)) {\n\t\t\t\tconst key = x.key;\n\t\t\t\tif (this.items[key] !== undefined) {\n\t\t\t\t\tdelete this.items[key];\n\t\t\t\t\tthis.size--;\n\t\t\t\t\tremoved++;\n\t\t\t\t\tthis.#unlink(x);\n\t\t\t\t\tx.prev = null;\n\t\t\t\t\tx.next = null;\n\t\t\t\t}\n\t\t\t}\n\t\t\tx = next;\n\t\t}\n\n\t\tif (removed > 0) {\n\t\t\tthis.#rebuildList();\n\t\t}\n\n\t\treturn removed;\n\t}\n\n\t/**\n\t * Serialize cache to JSON-compatible format.\n\t *\n\t * @returns {Array<{key: any, value: *, expiry: number}>} Array of cache items.\n\t */\n\ttoJSON() {\n\t\tconst result = [];\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\tresult.push({\n\t\t\t\t\tkey: x.key,\n\t\t\t\t\tvalue: x.value,\n\t\t\t\t\texpiry: x.expiry,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get cache statistics.\n\t *\n\t * @returns {Object} Statistics object with hits, misses, sets, deletes, evictions counts.\n\t */\n\tstats() {\n\t\treturn { ...this.#stats };\n\t}\n\n\t/**\n\t * Register callback for evicted items.\n\t *\n\t * @param {function(Object): void} callback - Function called when item is evicted. Receives {key, value, expiry}.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tonEvict(callback) {\n\t\tif (typeof callback !== \"function\") {\n\t\t\tthrow new TypeError(\"onEvict callback must be a function\");\n\t\t}\n\n\t\tthis.#onEvict = callback;\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get counts of items by TTL status.\n\t *\n\t * @returns {Object} Object with valid, expired, and noTTL counts.\n\t */\n\tsizeByTTL() {\n\t\tif (this.ttl === 0) {\n\t\t\treturn { valid: this.size, expired: 0, noTTL: this.size };\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tlet valid = 0;\n\t\tlet expired = 0;\n\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (x.expiry > now) {\n\t\t\t\tvalid++;\n\t\t\t} else {\n\t\t\t\texpired++;\n\t\t\t}\n\t\t}\n\n\t\treturn { valid, expired, noTTL: 0 };\n\t}\n\n\t/**\n\t * Get keys filtered by TTL status.\n\t *\n\t * @returns {Object} Object with valid, expired, and noTTL arrays of keys.\n\t */\n\tkeysByTTL() {\n\t\tif (this.ttl === 0) {\n\t\t\treturn { valid: this.keys(), expired: [], noTTL: this.keys() };\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tconst valid = [];\n\t\tconst expired = [];\n\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (x.expiry > now) {\n\t\t\t\tvalid.push(x.key);\n\t\t\t} else {\n\t\t\t\texpired.push(x.key);\n\t\t\t}\n\t\t}\n\n\t\treturn { valid, expired, noTTL: [] };\n\t}\n\n\t/**\n\t * Get values filtered by TTL status.\n\t *\n\t * @returns {Object} Object with valid, expired, and noTTL arrays of values.\n\t */\n\tvaluesByTTL() {\n\t\tif (this.ttl === 0) {\n\t\t\treturn { valid: this.values(), expired: [], noTTL: this.values() };\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tconst valid = [];\n\t\tconst expired = [];\n\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (x.expiry > now) {\n\t\t\t\tvalid.push(x.value);\n\t\t\t} else {\n\t\t\t\texpired.push(x.value);\n\t\t\t}\n\t\t}\n\n\t\treturn { valid, expired, noTTL: [] };\n\t}\n\n\t/**\n\t * Rebuild the doubly-linked list after cleanup by deleting expired items.\n\t * This removes nodes that were deleted during cleanup.\n\t *\n\t * @private\n\t */\n\t#rebuildList() {\n\t\tif (this.size === 0) {\n\t\t\tthis.first = null;\n\t\t\tthis.last = null;\n\t\t\treturn;\n\t\t}\n\n\t\tconst keys = this.keys();\n\t\tthis.first = null;\n\t\tthis.last = null;\n\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst item = this.items[keys[i]];\n\t\t\tif (item !== null && item !== undefined) {\n\t\t\t\tif (this.first === null) {\n\t\t\t\t\tthis.first = item;\n\t\t\t\t\titem.prev = null;\n\t\t\t\t} else {\n\t\t\t\t\titem.prev = this.last;\n\t\t\t\t\tthis.last.next = item;\n\t\t\t\t}\n\t\t\t\titem.next = null;\n\t\t\t\tthis.last = item;\n\t\t\t}\n\t\t}\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 updating existing items via set().\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\treturn new LRU(max, ttl, resetTTL);\n}\n"],"names":["LRU","stats","onEvict","constructor","max","ttl","resetTTL","Number","isInteger","TypeError","this","first","items","Object","create","last","size","hits","misses","sets","deletes","evictions","clear","x","next","prev","key","item","undefined","removeItem","entries","keys","result","isExpired","push","value","Array","isArray","from","length","i","evict","evictItem","expiry","expiresAt","Date","now","peek","get","moveToEnd","has","unlink","setWithEvicted","evicted","evictedItem","set","values","forEach","callback","thisArg","call","getMany","hasAll","hasAny","cleanup","removed","rebuildList","toJSON","sizeByTTL","valid","expired","noTTL","keysByTTL","valuesByTTL","lru"],"mappings":"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"} \ 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#stats;\n\t#onEvict;\n\n\t/**\n\t * Creates a new LRU cache instance.\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 * @throws {TypeError} When parameters are invalid (negative numbers or wrong types).\n\t */\n\tconstructor(max = 0, ttl = 0, resetTTL = false) {\n\t\tif (!Number.isInteger(max) || max < 0) {\n\t\t\tthrow new TypeError(\"Invalid max value\");\n\t\t}\n\n\t\tif (!Number.isInteger(ttl) || ttl < 0) {\n\t\t\tthrow new TypeError(\"Invalid ttl value\");\n\t\t}\n\n\t\tif (typeof resetTTL !== \"boolean\") {\n\t\t\tthrow new TypeError(\"Invalid resetTTL value\");\n\t\t}\n\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\tthis.#stats = { hits: 0, misses: 0, sets: 0, deletes: 0, evictions: 0 };\n\t\tthis.#onEvict = null;\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\tfor (let x = this.first; x !== null; ) {\n\t\t\tconst next = x.next;\n\t\t\tx.prev = null;\n\t\t\tx.next = null;\n\t\t\tx = next;\n\t\t}\n\n\t\tthis.first = null;\n\t\tthis.items = Object.create(null);\n\t\tthis.last = null;\n\t\tthis.size = 0;\n\t\tthis.#stats.hits = 0;\n\t\tthis.#stats.misses = 0;\n\t\tthis.#stats.sets = 0;\n\t\tthis.#stats.deletes = 0;\n\t\tthis.#stats.evictions = 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\tthis.#removeItem(item);\n\t\t\tthis.#stats.deletes++;\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\tconst result = [];\n\t\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\t\tresult.push([x.key, x.value]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\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 && !this.#isExpired(item) ? 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.#evictItem();\n\n\t\tif (this.#onEvict !== null) {\n\t\t\tthis.#onEvict({\n\t\t\t\tkey: item.key,\n\t\t\t\tvalue: item.value,\n\t\t\t\texpiry: item.expiry,\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 * @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 * Checks if an item has expired.\n\t *\n\t * @param {Object} item - The cache item to check.\n\t * @returns {boolean} True if the item has expired, false otherwise.\n\t * @private\n\t */\n\t#isExpired(item) {\n\t\tif (this.ttl === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn item.expiry <= Date.now();\n\t}\n\n\t/**\n\t * Retrieves a value from the cache by key without updating LRU order.\n\t * Note: Does not perform TTL checks or remove expired items.\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.\n\t */\n\tpeek(key) {\n\t\tconst item = this.items[key];\n\t\treturn item !== undefined ? item.value : 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\tif (!this.#isExpired(item)) {\n\t\t\t\tthis.moveToEnd(item);\n\t\t\t\tthis.#stats.hits++;\n\t\t\t\treturn item.value;\n\t\t\t}\n\n\t\t\tthis.#removeItem(item);\n\t\t\tthis.#stats.misses++;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis.#stats.misses++;\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Checks if a key exists in the cache.\n\t * Expired items are removed before returning false.\n\t *\n\t * @param {string} key - The key to check for.\n\t * @returns {boolean} True if the key exists and is not expired, false otherwise.\n\t */\n\thas(key) {\n\t\tconst item = this.items[key];\n\n\t\tif (item !== undefined && this.#isExpired(item)) {\n\t\t\tthis.#removeItem(item);\n\t\t\treturn false;\n\t\t}\n\n\t\treturn item !== undefined;\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 * Removes an item from the cache without incrementing the deletes stat.\n\t * Used internally by get()/has() when removing expired items.\n\t *\n\t * @param {Object} item - The cache item to remove.\n\t * @private\n\t */\n\t#removeItem(item) {\n\t\tdelete this.items[item.key];\n\t\tthis.size--;\n\t\tthis.#unlink(item);\n\t\titem.prev = null;\n\t\titem.next = null;\n\t}\n\n\t/**\n\t * Evicts the least recently used item from the cache without firing onEvict.\n\t * Used internally by setWithEvicted() to avoid double-notification.\n\t *\n\t * @returns {Object} The evicted item.\n\t * @private\n\t */\n\t#evictItem() {\n\t\tconst item = this.first;\n\n\t\tdelete this.items[item.key];\n\t\tthis.#stats.evictions++;\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.prev = null;\n\t\titem.next = null;\n\n\t\treturn item;\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 * Expired items are skipped, consistent with entries(), values(), and toJSON().\n\t *\n\t * @returns {string[]} Array of keys in LRU order.\n\t */\n\tkeys() {\n\t\tconst result = [];\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\tresult.push(x.key);\n\t\t\t}\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 * Eviction is silent — onEvict is not fired for the returned 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 && !this.#isExpired(item)) {\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 (item !== undefined) {\n\t\t\t\tthis.#removeItem(item);\n\t\t\t}\n\n\t\t\tif (this.max > 0 && this.size === this.max) {\n\t\t\t\tconst evictedItem = this.#evictItem();\n\t\t\t\tevicted = {\n\t\t\t\t\tkey: evictedItem.key,\n\t\t\t\t\tvalue: evictedItem.value,\n\t\t\t\t\texpiry: evictedItem.expiry,\n\t\t\t\t};\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\tthis.#stats.sets++;\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 && !this.#isExpired(item)) {\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 (item !== undefined) {\n\t\t\t\tthis.#removeItem(item);\n\t\t\t}\n\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\tthis.#stats.sets++;\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] - 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\tconst result = [];\n\t\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\t\tresult.push(x.value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\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 && !this.#isExpired(item) ? item.value : undefined;\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Iterate over cache items in LRU order (least to most recent).\n\t * Note: This method directly accesses items from the linked list without calling\n\t * get() or peek(), so it does not update LRU order. Expired items are skipped.\n\t *\n\t * @param {function(*, any, LRU): void} callback - Function to call for each item. Signature: callback(value, key, cache)\n\t * @param {Object} [thisArg] - Value to use as `this` when executing callback.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tforEach(callback, thisArg) {\n\t\tfor (let x = this.first; x !== null; ) {\n\t\t\tconst next = x.next;\n\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\tcallback.call(thisArg, x.value, x.key, this);\n\t\t\t}\n\t\t\tx = next;\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Batch retrieve multiple items.\n\t *\n\t * @param {string[]} keys - Array of keys to retrieve.\n\t * @returns {Object} Object mapping keys to values (undefined for missing/expired keys).\n\t */\n\tgetMany(keys) {\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\n\t\t}\n\n\t\tconst result = Object.create(null);\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst key = keys[i];\n\t\t\tresult[key] = this.get(key);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Batch existence check - returns true if ALL keys exist.\n\t *\n\t * @param {string[]} keys - Array of keys to check.\n\t * @returns {boolean} True if all keys exist and are not expired.\n\t */\n\thasAll(keys) {\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\n\t\t}\n\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tif (!this.has(keys[i])) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Batch existence check - returns true if ANY key exists.\n\t *\n\t * @param {string[]} keys - Array of keys to check.\n\t * @returns {boolean} True if any key exists and is not expired.\n\t */\n\thasAny(keys) {\n\t\tif (!Array.isArray(keys)) {\n\t\t\tthrow new TypeError(\"keys must be an array\");\n\t\t}\n\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tif (this.has(keys[i])) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Remove expired items without affecting LRU order.\n\t * Unlike get(), this does not move items to the end.\n\t *\n\t * @param {boolean} [fireOnEvict=false] - When true, invokes the onEvict callback for each removed item.\n\t * @returns {number} Number of expired items removed.\n\t */\n\tcleanup(fireOnEvict = false) {\n\t\tif (this.ttl === 0 || this.size === 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet removed = 0;\n\n\t\tfor (let x = this.first; x !== null; ) {\n\t\t\tconst next = x.next;\n\t\t\tif (this.#isExpired(x)) {\n\t\t\t\tconst key = x.key;\n\t\t\t\tif (this.items[key] !== undefined) {\n\t\t\t\t\tdelete this.items[key];\n\t\t\t\t\tthis.size--;\n\t\t\t\t\tremoved++;\n\t\t\t\t\tthis.#unlink(x);\n\t\t\t\t\tx.prev = null;\n\t\t\t\t\tx.next = null;\n\t\t\t\t\tif (fireOnEvict && this.#onEvict !== null) {\n\t\t\t\t\t\tthis.#onEvict({\n\t\t\t\t\t\t\tkey: x.key,\n\t\t\t\t\t\t\tvalue: x.value,\n\t\t\t\t\t\t\texpiry: x.expiry,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tx = next;\n\t\t}\n\n\t\tif (removed > 0) {\n\t\t\tthis.#rebuildList();\n\t\t}\n\n\t\treturn removed;\n\t}\n\n\t/**\n\t * Serialize cache to JSON-compatible format.\n\t *\n\t * @returns {Array<{key: any, value: *, expiry: number}>} Array of cache items.\n\t */\n\ttoJSON() {\n\t\tconst result = [];\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (!this.#isExpired(x)) {\n\t\t\t\tresult.push({\n\t\t\t\t\tkey: x.key,\n\t\t\t\t\tvalue: x.value,\n\t\t\t\t\texpiry: x.expiry,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get cache statistics.\n\t *\n\t * @returns {Object} Statistics object with hits, misses, sets, deletes, evictions counts.\n\t */\n\tstats() {\n\t\treturn { ...this.#stats };\n\t}\n\n\t/**\n\t * Register callback for evicted items.\n\t *\n\t * @param {function(Object): void} callback - Function called when item is evicted. Receives {key, value, expiry}.\n\t * @returns {LRU} The LRU instance for method chaining.\n\t */\n\tonEvict(callback) {\n\t\tif (typeof callback !== \"function\") {\n\t\t\tthrow new TypeError(\"onEvict callback must be a function\");\n\t\t}\n\n\t\tthis.#onEvict = callback;\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get counts of items by TTL status.\n\t *\n\t * @returns {Object} Object with valid, expired, and noTTL counts.\n\t */\n\tsizeByTTL() {\n\t\tif (this.ttl === 0) {\n\t\t\treturn { valid: this.size, expired: 0, noTTL: this.size };\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tlet valid = 0;\n\t\tlet expired = 0;\n\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (x.expiry > now) {\n\t\t\t\tvalid++;\n\t\t\t} else {\n\t\t\t\texpired++;\n\t\t\t}\n\t\t}\n\n\t\treturn { valid, expired, noTTL: 0 };\n\t}\n\n\t/**\n\t * Get keys filtered by TTL status.\n\t *\n\t * @returns {Object} Object with valid, expired, and noTTL arrays of keys.\n\t */\n\tkeysByTTL() {\n\t\tif (this.ttl === 0) {\n\t\t\treturn { valid: this.keys(), expired: [], noTTL: this.keys() };\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tconst valid = [];\n\t\tconst expired = [];\n\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (x.expiry > now) {\n\t\t\t\tvalid.push(x.key);\n\t\t\t} else {\n\t\t\t\texpired.push(x.key);\n\t\t\t}\n\t\t}\n\n\t\treturn { valid, expired, noTTL: [] };\n\t}\n\n\t/**\n\t * Get values filtered by TTL status.\n\t *\n\t * @returns {Object} Object with valid, expired, and noTTL arrays of values.\n\t */\n\tvaluesByTTL() {\n\t\tif (this.ttl === 0) {\n\t\t\treturn { valid: this.values(), expired: [], noTTL: this.values() };\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tconst valid = [];\n\t\tconst expired = [];\n\n\t\tfor (let x = this.first; x !== null; x = x.next) {\n\t\t\tif (x.expiry > now) {\n\t\t\t\tvalid.push(x.value);\n\t\t\t} else {\n\t\t\t\texpired.push(x.value);\n\t\t\t}\n\t\t}\n\n\t\treturn { valid, expired, noTTL: [] };\n\t}\n\n\t/**\n\t * Rebuild the doubly-linked list after cleanup by deleting expired items.\n\t * This removes nodes that were deleted during cleanup.\n\t *\n\t * @private\n\t */\n\t#rebuildList() {\n\t\tif (this.size === 0) {\n\t\t\tthis.first = null;\n\t\t\tthis.last = null;\n\t\t\treturn;\n\t\t}\n\n\t\tconst keys = this.keys();\n\t\tthis.first = null;\n\t\tthis.last = null;\n\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tconst item = this.items[keys[i]];\n\t\t\tif (item !== null && item !== undefined) {\n\t\t\t\tif (this.first === null) {\n\t\t\t\t\tthis.first = item;\n\t\t\t\t\titem.prev = null;\n\t\t\t\t} else {\n\t\t\t\t\titem.prev = this.last;\n\t\t\t\t\tthis.last.next = item;\n\t\t\t\t}\n\t\t\t\titem.next = null;\n\t\t\t\tthis.last = item;\n\t\t\t}\n\t\t}\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 updating existing items via set().\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\treturn new LRU(max, ttl, resetTTL);\n}\n"],"names":["LRU","stats","onEvict","constructor","max","ttl","resetTTL","Number","isInteger","TypeError","this","first","items","Object","create","last","size","hits","misses","sets","deletes","evictions","clear","x","next","prev","key","item","undefined","removeItem","entries","keys","result","isExpired","push","value","Array","isArray","from","length","i","evict","evictItem","expiry","expiresAt","Date","now","peek","get","moveToEnd","has","unlink","setWithEvicted","evicted","evictedItem","set","values","forEach","callback","thisArg","call","getMany","hasAll","hasAny","cleanup","fireOnEvict","removed","rebuildList","toJSON","sizeByTTL","valid","expired","noTTL","keysByTTL","valuesByTTL","lru"],"mappings":"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"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c6e4cf1..de143fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tiny-lru", - "version": "13.0.1", + "version": "13.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tiny-lru", - "version": "13.0.1", + "version": "13.1.0", "license": "BSD-3-Clause", "devDependencies": { "@rollup/plugin-terser": "^1.0.0", diff --git a/package.json b/package.json index 59e1259..94e8b4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tiny-lru", - "version": "13.0.1", + "version": "13.1.0", "description": "A fast, lightweight LRU (Least Recently Used) cache for JavaScript with O(1) operations and optional TTL support.", "keywords": [ "LRU",