secondary-cache
Advanced tools
| import chai from 'chai' | ||
| const should = chai.should(); | ||
| import Cache from '../src/cache' | ||
| import LRUCache from '../src/lru-cache' | ||
| describe("Weight-based LRUCache", function() { | ||
| describe("LRUCache with weightOf function", function() { | ||
| it('should support custom weightOf function for size-based capacity', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 20, | ||
| capacity: 0, // unlimited by count | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', 'hello'); // weight: 5 | ||
| cache.set('b', 'world'); // weight: 5 | ||
| cache.set('c', 'test'); // weight: 4 | ||
| cache.length().should.be.equal(3); | ||
| // Total weight is 14, now adding 'd' with weight 21 | ||
| // Item weight exceeds maxWeight, should throw error | ||
| (function() { | ||
| cache.set('d', 'this is a long string'); // weight: 21 | ||
| }).should.throw('Item weight 21 exceeds maxWeight 20'); | ||
| // Cache should remain unchanged | ||
| cache.length().should.be.equal(3); | ||
| cache.get('a').should.be.equal('hello'); | ||
| cache.get('b').should.be.equal('world'); | ||
| cache.get('c').should.be.equal('test'); | ||
| }); | ||
| it('should work with both capacity and maxWeight limits', function() { | ||
| const cache = LRUCache({ | ||
| capacity: 3, | ||
| maxWeight: 15, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', 'hello'); // weight: 5 | ||
| cache.set('b', 'world'); // weight: 5 | ||
| cache.set('c', 'test'); // weight: 4 | ||
| cache.length().should.be.equal(3); | ||
| // Total weight: 14, under maxWeight 15 | ||
| cache.set('d', 'hi'); // weight: 2, triggers capacity eviction | ||
| // After eviction, should have 3 items (capacity limit) | ||
| cache.length().should.be.equal(3); | ||
| }); | ||
| it('should update weight when value is updated', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 20, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', 'hello'); // weight: 5 | ||
| cache.set('b', 'world'); // weight: 5 | ||
| cache._totalWeight.should.be.equal(10); | ||
| // Update 'a' with a longer string | ||
| cache.set('a', 'hello world!!'); // weight: 13 | ||
| cache._totalWeight.should.be.equal(18); // 13 + 5 | ||
| }); | ||
| it('should use default weightOf (return 1) when not provided', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 5, | ||
| capacity: 0 | ||
| }); | ||
| // Default weightOf returns 1, so maxWeight acts like max count | ||
| cache.set('a', 1); | ||
| cache.set('b', 2); | ||
| cache.set('c', 3); | ||
| cache.set('d', 4); | ||
| cache.set('e', 5); | ||
| cache.length().should.be.equal(5); | ||
| cache.set('f', 6); // should evict one item | ||
| cache.length().should.be.equal(5); | ||
| should.not.exist(cache.get('a')); | ||
| }); | ||
| it('should evict LRU items when weight exceeds maxWeight', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 10, | ||
| weightOf: function(value) { | ||
| return value.size || 1; | ||
| } | ||
| }); | ||
| cache.set('a', { size: 4 }); | ||
| cache.set('b', { size: 4 }); | ||
| cache.set('c', { size: 4 }); | ||
| // Total weight: 12, should evict 'a' (LRU) | ||
| cache.length().should.be.equal(2); | ||
| should.not.exist(cache.get('a')); | ||
| cache.get('b').should.deep.equal({ size: 4 }); | ||
| cache.get('c').should.deep.equal({ size: 4 }); | ||
| }); | ||
| it('should handle weight calculation via prototype method', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 100, | ||
| capacity: 0 | ||
| }); | ||
| // Override prototype method | ||
| cache.weightOf = function(value) { | ||
| return Buffer.byteLength(JSON.stringify(value), 'utf8'); | ||
| }; | ||
| cache.set('obj', { name: 'test', data: 'hello' }); | ||
| cache.set('str', 'a'.repeat(50)); | ||
| // Both should fit under 100 | ||
| cache.length().should.be.equal(2); | ||
| }); | ||
| it('should correctly track totalWeight after delete', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 20, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', 'hello'); // weight: 5 | ||
| cache.set('b', 'world'); // weight: 5 | ||
| cache.set('c', 'test'); // weight: 4 | ||
| cache._totalWeight.should.be.equal(14); | ||
| cache.del('b'); | ||
| cache._totalWeight.should.be.equal(9); | ||
| }); | ||
| it('should correctly track totalWeight after clear', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 20, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', 'hello'); | ||
| cache.set('b', 'world'); | ||
| cache._totalWeight.should.be.equal(10); | ||
| cache.clear(); | ||
| cache._totalWeight.should.be.equal(0); | ||
| cache.length().should.be.equal(0); | ||
| }); | ||
| it('should evict LRU items based on usage order', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 10, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', '1234'); // weight: 4 | ||
| cache.set('b', '5678'); // weight: 4 | ||
| cache.set('c', '90'); // weight: 2 | ||
| // Total: 10, LRU order: a, b, c | ||
| cache.get('a'); // Access 'a', now LRU order: b, c, a | ||
| cache.set('d', 'AB'); // weight: 2, total would be 12, evict 'b' | ||
| cache.length().should.be.equal(3); | ||
| should.not.exist(cache.get('b')); | ||
| cache.get('a').should.be.equal('1234'); | ||
| }); | ||
| it('should evict multiple items when weight greatly exceeded', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 5, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', '12'); // weight: 2 | ||
| cache.set('b', '34'); // weight: 2 | ||
| cache.set('c', '56'); // weight: 2, total: 6 > 5, evict 'a' | ||
| cache.length().should.be.equal(2); | ||
| should.not.exist(cache.get('a')); | ||
| cache.get('b').should.be.equal('34'); | ||
| cache.get('c').should.be.equal('56'); | ||
| }); | ||
| it('should handle update that triggers eviction', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 10, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', '123'); // weight: 3 | ||
| cache.set('b', '456'); // weight: 3 | ||
| cache.set('c', '78'); // weight: 2, total: 8 | ||
| // Update 'a' to a much larger value | ||
| cache.set('a', '1234567890'); // weight: 10, total would be 15 > 10 | ||
| // Should evict LRU items (b, then c if needed) | ||
| cache.length().should.be.equal(1); | ||
| cache.get('a').should.be.equal('1234567890'); | ||
| }); | ||
| it('should work with maxWeight = 0 (no weight limit)', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 0, | ||
| capacity: 3, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', 'hello'); // weight: 5 | ||
| cache.set('b', 'world'); // weight: 5 | ||
| cache._totalWeight.should.be.equal(10); | ||
| cache.set('c', 'test'); // weight: 4, evict by capacity | ||
| cache.length().should.be.equal(3); | ||
| cache.set('d', 'latest'); // the a should be pop for capacity 3 | ||
| should.not.exist(cache.get('a')); | ||
| }); | ||
| it('should emit del event for evicted items', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 10, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| const evicted = []; | ||
| cache.on('del', (id) => evicted.push(id)); | ||
| cache.set('a', '1234'); // weight: 4 | ||
| cache.set('b', '5678'); // weight: 4 | ||
| cache.set('c', '90'); // weight: 2, total: 10 | ||
| evicted.length.should.be.equal(0); | ||
| cache.set('d', 'AB'); // weight: 2, evict 'a' | ||
| evicted.should.deep.equal(['a']); | ||
| }); | ||
| it('should throw error when update exceeds maxWeight', function() { | ||
| const cache = LRUCache({ | ||
| maxWeight: 10, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', '123'); // weight: 3 | ||
| cache.set('b', '456'); // weight: 3 | ||
| cache._totalWeight.should.be.equal(6); | ||
| // Update 'a' to exceed maxWeight | ||
| (function() { | ||
| cache.set('a', '12345678901'); // weight: 11 | ||
| }).should.throw('Item weight 11 exceeds maxWeight 10'); | ||
| // Cache should remain unchanged | ||
| cache.get('a').should.be.equal('123'); | ||
| cache._totalWeight.should.be.equal(6); | ||
| }); | ||
| }); | ||
| describe("Cache with weightOf function", function() { | ||
| it('should support weight-based capacity in LRU portion', function() { | ||
| const cache = Cache({ | ||
| maxWeight: 10, | ||
| capacity: 0, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| cache.set('a', '1234'); // weight: 4 | ||
| cache.set('b', '5678'); // weight: 4 | ||
| // Total weight: 8 | ||
| cache.length().should.be.equal(2); | ||
| cache.set('c', '9012'); // weight: 4, total: 12 > 10, evict 'a' | ||
| cache.length().should.be.equal(2); | ||
| should.not.exist(cache.get('a')); | ||
| }); | ||
| it('should handle fixed cache separately from weight-based LRU', function() { | ||
| const cache = Cache({ | ||
| maxWeight: 10, | ||
| weightOf: function(value) { | ||
| return String(value).length; | ||
| } | ||
| }); | ||
| // Fixed cache should not be affected by weight | ||
| cache.set('fixed', 'fixed-value', { fixed: true }); | ||
| cache.set('lru', '1234567890'); // weight: 10 | ||
| cache.length().should.be.equal(2); | ||
| // Fixed cache should have its own capacity tracking | ||
| cache.fixedCapacity.should.be.equal(1); | ||
| }); | ||
| }); | ||
| }); |
+38
-0
@@ -9,2 +9,7 @@ import {LRUQueue} from './lru-queue'; | ||
| /** | ||
| * the maximum weight of items the cache can hold. 0 means no limit. | ||
| * Used with weightOf function for custom capacity control (e.g., by size). | ||
| */ | ||
| maxWeight?: number; | ||
| /** | ||
| * the default expires time (millisecond), defaults to no expires time(<=0). | ||
@@ -17,2 +22,12 @@ */ | ||
| cleanInterval?: number; | ||
| /** | ||
| * Custom function to calculate the weight of a value. | ||
| * @param value - The value to calculate weight for. | ||
| * @param id - The id of the value. | ||
| * @returns The weight of the value. Return 1 for count-based capacity (default). | ||
| * @example | ||
| * // Size-based capacity (in bytes) | ||
| * weightOf: (value) => JSON.stringify(value).length | ||
| */ | ||
| weightOf?: (value: any, id?: any) => number; | ||
| } | ||
@@ -24,2 +39,6 @@ | ||
| expires?: number; | ||
| /** | ||
| * The weight of the item for capacity control. | ||
| */ | ||
| weight?: number; | ||
| } | ||
@@ -45,2 +64,3 @@ | ||
| maxCapacity: number; | ||
| maxWeight: number; | ||
| maxAge: number; | ||
@@ -51,4 +71,11 @@ cleanInterval: number; | ||
| _lruQueue: LRUQueue | ||
| _totalWeight: number; | ||
| /** | ||
| * The total weight of all items in the cache (read-only). | ||
| * Only meaningful when using weight-based capacity (maxWeight > 0). | ||
| */ | ||
| readonly totalWeight: number; | ||
| /** | ||
| * Represents a Least Recently Used (LRU) Cache. | ||
@@ -98,2 +125,13 @@ * @constructor | ||
| /** | ||
| * Calculate the weight of a value. Override this method to customize capacity calculation. | ||
| * @param value - The value to calculate weight for. | ||
| * @param id - The id of the value. | ||
| * @returns The weight of the value. Default returns 1 (count-based). | ||
| * @example | ||
| * // Size-based capacity (in bytes) | ||
| * cache.weightOf = (value) => JSON.stringify(value).length; | ||
| */ | ||
| weightOf(value: any, id?: any): number; | ||
| /** | ||
| * Deletes an item from the cache. | ||
@@ -100,0 +138,0 @@ * @param id - The key of the item to delete. |
+65
-4
@@ -13,6 +13,7 @@ "use strict"; | ||
| const MAX_CAPACITY = 1024; | ||
| function LRUCacheItem(id, value, expires) { | ||
| function LRUCacheItem(id, value, expires, weight) { | ||
| this.id = id; | ||
| this.value = value; | ||
| this.expires = expires; | ||
| this.weight = weight || 0; | ||
| } | ||
@@ -23,2 +24,3 @@ function LRUCache(options) { | ||
| } | ||
| this._totalWeight = 0; | ||
| this.reset(options); | ||
@@ -28,2 +30,17 @@ } | ||
| (0, _eventsEx.eventable)(LRUCache); | ||
| /** | ||
| * Calculate the weight of a value. Override this method to customize capacity calculation. | ||
| * @param {*} value - The value to calculate weight for. | ||
| * @param {string} id - The id of the value. | ||
| * @returns {number} The weight of the value. Default returns 1 (count-based). | ||
| */ | ||
| LRUCache.prototype.weightOf = function (value, id) { | ||
| // Default: return 1 (count-based, same as original behavior) | ||
| // Override this method to implement size-based or custom capacity control | ||
| if (this._weightOfFn) { | ||
| return this._weightOfFn(value, id); | ||
| } | ||
| return 1; | ||
| }; | ||
| LRUCache.prototype.delListener = LRUCache.prototype.off; | ||
@@ -42,2 +59,3 @@ LRUCache.prototype.has = function (id) { | ||
| if (result !== undefined) { | ||
| this._totalWeight -= result.weight || 0; | ||
| delete this._cacheLRU[id]; | ||
@@ -97,3 +115,11 @@ if (this._lruQueue && isInternal !== true) { | ||
| if (item !== undefined) { | ||
| // Update existing item | ||
| const newWeight = this.weightOf(value, id); | ||
| if (this.maxWeight > 0 && newWeight > this.maxWeight) { | ||
| throw new Error('Item weight ' + newWeight + ' exceeds maxWeight ' + this.maxWeight); | ||
| } | ||
| this._totalWeight -= item.weight || 0; | ||
| item.value = value; | ||
| item.weight = newWeight; | ||
| this._totalWeight += newWeight; | ||
| if (expires <= 0) { | ||
@@ -110,2 +136,7 @@ delete item.expires; | ||
| } else { | ||
| // Add new item | ||
| const weight = this.weightOf(value, id); | ||
| if (this.maxWeight > 0 && weight > this.maxWeight) { | ||
| throw new Error('Item weight ' + weight + ' exceeds maxWeight ' + this.maxWeight); | ||
| } | ||
| if (expires > 0) { | ||
@@ -118,4 +149,5 @@ expires = Date.now() + expires; | ||
| } | ||
| item = new LRUCacheItem(id, value, expires); | ||
| item = new LRUCacheItem(id, value, expires, weight); | ||
| this._cacheLRU[id] = item; | ||
| this._totalWeight += weight; | ||
| if (this._lruQueue) { | ||
@@ -128,2 +160,16 @@ const delItem = this._lruQueue.add(item); | ||
| } | ||
| if (this._lruQueue) { | ||
| // Check weight-based eviction (LRUQueue only handles capacity, LRUCache handles weight) | ||
| if (this.maxWeight > 0 && this._totalWeight > this.maxWeight) { | ||
| let popItem; | ||
| while (this._totalWeight > this.maxWeight && this._lruQueue.length > 0) { | ||
| popItem = this._lruQueue.pop(); | ||
| if (popItem) { | ||
| this.del(popItem.id, true); | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return this.emit(event, id, value, oldValue); | ||
@@ -134,2 +180,3 @@ }; | ||
| this._cacheLRU = create(null); | ||
| this._totalWeight = 0; | ||
| for (const k in oldCache) { | ||
@@ -157,2 +204,3 @@ const v = oldCache[k]; | ||
| this._cacheLRU = null; | ||
| this._totalWeight = 0; | ||
| this._lruQueue = null; | ||
@@ -198,2 +246,4 @@ return this.lastCleanTime = 0; | ||
| this.cleanInterval = 0; | ||
| this.maxWeight = 0; | ||
| this._weightOfFn = null; | ||
| } else if (options) { | ||
@@ -203,2 +253,4 @@ this.maxCapacity = options.capacity || MAX_CAPACITY; | ||
| this.cleanInterval = options.cleanInterval; | ||
| this.maxWeight = options.maxWeight || 0; | ||
| this._weightOfFn = options.weightOf || null; | ||
| if (this.cleanInterval > 0) { | ||
@@ -209,7 +261,16 @@ this.cleanInterval = this.cleanInterval * 1000; | ||
| this.maxCapacity = MAX_CAPACITY; | ||
| this.maxWeight = 0; | ||
| this._weightOfFn = null; | ||
| } | ||
| if (this._lruQueue && this._lruQueue.maxCapacity !== this.maxCapacity) { | ||
| this._lruQueue.maxCapacity = this.maxCapacity; | ||
| if (this._lruQueue) { | ||
| if (this._lruQueue.maxCapacity !== this.maxCapacity) { | ||
| this._lruQueue.maxCapacity = this.maxCapacity; | ||
| } | ||
| } | ||
| }; | ||
| Object.defineProperty(LRUCache.prototype, 'totalWeight', { | ||
| get: function () { | ||
| return this._totalWeight; | ||
| } | ||
| }); | ||
| LRUCache.prototype.length = function () { | ||
@@ -216,0 +277,0 @@ return Object.keys(this._cacheLRU).length; |
+18
-18
| { | ||
| "name": "secondary-cache", | ||
| "version": "2.0.1", | ||
| "version": "2.1.0", | ||
| "description": "support secondary cache mechanism. the first level cache is fixed memory-resident always with the highest priority. the second level is the LRU cache.", | ||
@@ -17,18 +17,2 @@ "homepage": "https://github.com/snowyu/secondary-cache.js", | ||
| ], | ||
| "scripts": { | ||
| "build": "npm run build.cjs && npm run build.ts && npm run doc.md", | ||
| "build.cjs": "babel src --out-dir lib --config-file ./.babelrc", | ||
| "build.ts": "cp src/*.d.ts lib/", | ||
| "clean": "rm -fr web docs lib", | ||
| "clean.doc": "rm -fr web docs", | ||
| "clean.ts": "rm -fr lib/*.d.ts", | ||
| "clean.lib": "rm -fr lib", | ||
| "doc": "typedoc --plugin none --out web ./src", | ||
| "doc.md": "typedoc --plugin typedoc-plugin-markdown --out docs ./src", | ||
| "lint": "npx eslint --config .eslintrc.yml src", | ||
| "lint.fix": "npm run lint -- --fix", | ||
| "release": "npm run clean && npm run build && git add docs && git ci -m 'docs: update API docs' && npx commit-and-tag-version -s", | ||
| "release.alpha": "npm run release -- --prerelease alpha", | ||
| "test": "mocha" | ||
| }, | ||
| "dependencies": { | ||
@@ -79,3 +63,19 @@ "abstract-object": "^3.0.0", | ||
| "url": "https://github.com/snowyu/secondary-cache.js/issues" | ||
| }, | ||
| "scripts": { | ||
| "build": "npm run build.cjs && npm run build.ts && npm run doc.md", | ||
| "build.cjs": "babel src --out-dir lib --config-file ./.babelrc", | ||
| "build.ts": "cp src/*.d.ts lib/", | ||
| "clean": "rm -fr web docs lib", | ||
| "clean.doc": "rm -fr web docs", | ||
| "clean.ts": "rm -fr lib/*.d.ts", | ||
| "clean.lib": "rm -fr lib", | ||
| "doc": "typedoc --plugin none --out web ./src", | ||
| "doc.md": "typedoc --plugin typedoc-plugin-markdown --out docs ./src", | ||
| "lint": "npx eslint --config .eslintrc.yml src", | ||
| "lint.fix": "npm run lint -- --fix", | ||
| "release": "npm run clean && npm run build && git add docs && git ci -m 'docs: update API docs' && npx commit-and-tag-version -s", | ||
| "release.alpha": "npm run release -- --prerelease alpha", | ||
| "test": "mocha" | ||
| } | ||
| } | ||
| } |
+22
-1
@@ -15,2 +15,6 @@ ## Secondary Cache [](https://npmjs.org/package/secondary-cache) [](https://npmjs.org/package/secondary-cache) [](https://npmjs.org/package/secondary-cache) | ||
| capacity > 0 to enable the secondary LRU cache. | ||
| * maxWeight: the maximum total weight of all items in the LRU cache. | ||
| defaults to 0 (no weight limit). | ||
| * weightOf: a function(value, id) to calculate the weight of a value. | ||
| defaults to returning 1 (count-based). | ||
| * expires: the default expires time (milliscond), defaults to no expires time(<=0). | ||
@@ -32,2 +36,8 @@ it will be put into LRU Cache if has expires time | ||
| capacity > 0 to enable the LRU. | ||
| * maxWeight: the maximum total weight of all items in cache. | ||
| defaults to 0 (no weight limit). When weight limit is exceeded, | ||
| LRU items are evicted. If a single item's weight exceeds maxWeight, | ||
| an error is thrown. | ||
| * weightOf: a function(value, id) to calculate the weight of a value. | ||
| defaults to returning 1 (count-based). Override to implement size-based limits. | ||
| * expires: the default expires time (milliscond), defaults to no expires time(<=0). | ||
@@ -81,3 +91,14 @@ it will be put into LRU Cache if has expires time | ||
| ... | ||
| // Weight-based LRU Cache | ||
| const lruCache = new LRUCache({ | ||
| maxWeight: 1000, // total weight limit | ||
| capacity: 0, // unlimited by count | ||
| weightOf: function(value, key) { | ||
| // Calculate weight based on value size (e.g., byte length) | ||
| return Buffer.byteLength(JSON.stringify(value), 'utf8'); | ||
| } | ||
| }); | ||
| lruCache.set('key', 'some data'); // weight is calculated automatically | ||
| console.log(lruCache.totalWeight); // get total weight of all items | ||
| // If weight exceeds maxWeight, throws Error: 'Item weight X exceeds maxWeight Y' | ||
| ``` | ||
@@ -84,0 +105,0 @@ |
+38
-0
@@ -9,2 +9,7 @@ import {LRUQueue} from './lru-queue'; | ||
| /** | ||
| * the maximum weight of items the cache can hold. 0 means no limit. | ||
| * Used with weightOf function for custom capacity control (e.g., by size). | ||
| */ | ||
| maxWeight?: number; | ||
| /** | ||
| * the default expires time (millisecond), defaults to no expires time(<=0). | ||
@@ -17,2 +22,12 @@ */ | ||
| cleanInterval?: number; | ||
| /** | ||
| * Custom function to calculate the weight of a value. | ||
| * @param value - The value to calculate weight for. | ||
| * @param id - The id of the value. | ||
| * @returns The weight of the value. Return 1 for count-based capacity (default). | ||
| * @example | ||
| * // Size-based capacity (in bytes) | ||
| * weightOf: (value) => JSON.stringify(value).length | ||
| */ | ||
| weightOf?: (value: any, id?: any) => number; | ||
| } | ||
@@ -24,2 +39,6 @@ | ||
| expires?: number; | ||
| /** | ||
| * The weight of the item for capacity control. | ||
| */ | ||
| weight?: number; | ||
| } | ||
@@ -45,2 +64,3 @@ | ||
| maxCapacity: number; | ||
| maxWeight: number; | ||
| maxAge: number; | ||
@@ -51,4 +71,11 @@ cleanInterval: number; | ||
| _lruQueue: LRUQueue | ||
| _totalWeight: number; | ||
| /** | ||
| * The total weight of all items in the cache (read-only). | ||
| * Only meaningful when using weight-based capacity (maxWeight > 0). | ||
| */ | ||
| readonly totalWeight: number; | ||
| /** | ||
| * Represents a Least Recently Used (LRU) Cache. | ||
@@ -98,2 +125,13 @@ * @constructor | ||
| /** | ||
| * Calculate the weight of a value. Override this method to customize capacity calculation. | ||
| * @param value - The value to calculate weight for. | ||
| * @param id - The id of the value. | ||
| * @returns The weight of the value. Default returns 1 (count-based). | ||
| * @example | ||
| * // Size-based capacity (in bytes) | ||
| * cache.weightOf = (value) => JSON.stringify(value).length; | ||
| */ | ||
| weightOf(value: any, id?: any): number; | ||
| /** | ||
| * Deletes an item from the cache. | ||
@@ -100,0 +138,0 @@ * @param id - The key of the item to delete. |
+68
-4
@@ -7,6 +7,7 @@ import {eventable} from 'events-ex' | ||
| function LRUCacheItem(id, value, expires) { | ||
| function LRUCacheItem(id, value, expires, weight) { | ||
| this.id = id; | ||
| this.value = value; | ||
| this.expires = expires; | ||
| this.weight = weight || 0; | ||
| } | ||
@@ -18,2 +19,3 @@ | ||
| } | ||
| this._totalWeight = 0; | ||
| this.reset(options); | ||
@@ -24,2 +26,17 @@ } | ||
| /** | ||
| * Calculate the weight of a value. Override this method to customize capacity calculation. | ||
| * @param {*} value - The value to calculate weight for. | ||
| * @param {string} id - The id of the value. | ||
| * @returns {number} The weight of the value. Default returns 1 (count-based). | ||
| */ | ||
| LRUCache.prototype.weightOf = function(value, id) { | ||
| // Default: return 1 (count-based, same as original behavior) | ||
| // Override this method to implement size-based or custom capacity control | ||
| if (this._weightOfFn) { | ||
| return this._weightOfFn(value, id); | ||
| } | ||
| return 1; | ||
| }; | ||
| LRUCache.prototype.delListener = LRUCache.prototype.off; | ||
@@ -42,2 +59,3 @@ | ||
| if (result !== undefined) { | ||
| this._totalWeight -= result.weight || 0; | ||
| delete this._cacheLRU[id]; | ||
@@ -102,3 +120,11 @@ if (this._lruQueue && isInternal !== true) { | ||
| if (item !== undefined) { | ||
| // Update existing item | ||
| const newWeight = this.weightOf(value, id); | ||
| if (this.maxWeight > 0 && newWeight > this.maxWeight) { | ||
| throw new Error('Item weight ' + newWeight + ' exceeds maxWeight ' + this.maxWeight); | ||
| } | ||
| this._totalWeight -= item.weight || 0; | ||
| item.value = value; | ||
| item.weight = newWeight; | ||
| this._totalWeight += newWeight; | ||
| if (expires <= 0) { | ||
@@ -115,2 +141,7 @@ delete item.expires; | ||
| } else { | ||
| // Add new item | ||
| const weight = this.weightOf(value, id); | ||
| if (this.maxWeight > 0 && weight > this.maxWeight) { | ||
| throw new Error('Item weight ' + weight + ' exceeds maxWeight ' + this.maxWeight); | ||
| } | ||
| if (expires > 0) { | ||
@@ -123,4 +154,5 @@ expires = Date.now() + expires; | ||
| } | ||
| item = new LRUCacheItem(id, value, expires); | ||
| item = new LRUCacheItem(id, value, expires, weight); | ||
| this._cacheLRU[id] = item; | ||
| this._totalWeight += weight; | ||
| if (this._lruQueue) { | ||
@@ -133,2 +165,18 @@ const delItem = this._lruQueue.add(item); | ||
| } | ||
| if (this._lruQueue) { | ||
| // Check weight-based eviction (LRUQueue only handles capacity, LRUCache handles weight) | ||
| if (this.maxWeight > 0 && this._totalWeight > this.maxWeight) { | ||
| let popItem; | ||
| while (this._totalWeight > this.maxWeight && this._lruQueue.length > 0) { | ||
| popItem = this._lruQueue.pop(); | ||
| if (popItem) { | ||
| this.del(popItem.id, true); | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return this.emit(event, id, value, oldValue); | ||
@@ -140,2 +188,3 @@ }; | ||
| this._cacheLRU = create(null); | ||
| this._totalWeight = 0; | ||
| for (const k in oldCache) { | ||
@@ -165,2 +214,3 @@ const v = oldCache[k]; | ||
| this._cacheLRU = null; | ||
| this._totalWeight = 0; | ||
| this._lruQueue = null; | ||
@@ -209,2 +259,4 @@ return this.lastCleanTime = 0; | ||
| this.cleanInterval = 0; | ||
| this.maxWeight = 0; | ||
| this._weightOfFn = null; | ||
| } else if (options) { | ||
@@ -214,2 +266,4 @@ this.maxCapacity = options.capacity || MAX_CAPACITY; | ||
| this.cleanInterval = options.cleanInterval; | ||
| this.maxWeight = options.maxWeight || 0; | ||
| this._weightOfFn = options.weightOf || null; | ||
| if (this.cleanInterval > 0) { | ||
@@ -220,9 +274,19 @@ this.cleanInterval = this.cleanInterval * 1000; | ||
| this.maxCapacity = MAX_CAPACITY; | ||
| this.maxWeight = 0; | ||
| this._weightOfFn = null; | ||
| } | ||
| if (this._lruQueue && this._lruQueue.maxCapacity !== this.maxCapacity) { | ||
| this._lruQueue.maxCapacity = this.maxCapacity; | ||
| if (this._lruQueue) { | ||
| if (this._lruQueue.maxCapacity !== this.maxCapacity) { | ||
| this._lruQueue.maxCapacity = this.maxCapacity; | ||
| } | ||
| } | ||
| }; | ||
| Object.defineProperty(LRUCache.prototype, 'totalWeight', { | ||
| get: function() { | ||
| return this._totalWeight; | ||
| } | ||
| }); | ||
| LRUCache.prototype.length = function() { | ||
@@ -229,0 +293,0 @@ return Object.keys(this._cacheLRU).length; |
113477
18.48%25
4.17%2971
18.23%223
10.4%