typescript-lru-cache
Advanced tools
Comparing version 1.0.1 to 1.1.0
@@ -1,7 +0,25 @@ | ||
export interface LRUCacheOptions { | ||
export interface LRUCacheOptions<TKey, TValue> { | ||
maxSize?: number; | ||
entryExpirationTimeInMS?: number | null; | ||
onEntryEvicted?: (evictedEntry: { | ||
key: TKey; | ||
value: TValue; | ||
isExpired: boolean; | ||
}) => void; | ||
onEntryMarkedAsMostRecentlyUsed?: (entry: { | ||
key: TKey; | ||
value: TValue; | ||
}) => void; | ||
} | ||
export interface LRUCacheSetEntryOptions { | ||
export interface LRUCacheSetEntryOptions<TKey, TValue> { | ||
entryExpirationTimeInMS?: number | null; | ||
onEntryEvicted?: (evictedEntry: { | ||
key: TKey; | ||
value: TValue; | ||
isExpired: boolean; | ||
}) => void; | ||
onEntryMarkedAsMostRecentlyUsed?: (entry: { | ||
key: TKey; | ||
value: TValue; | ||
}) => void; | ||
} | ||
@@ -15,2 +33,4 @@ export interface LRUCacheEntry<TKey, TValue> { | ||
private readonly entryExpirationTimeInMS; | ||
private readonly onEntryEvicted?; | ||
private readonly onEntryMarkedAsMostRecentlyUsed?; | ||
private maxSizeInternal; | ||
@@ -24,3 +44,3 @@ private head; | ||
*/ | ||
constructor(options?: LRUCacheOptions); | ||
constructor(options?: LRUCacheOptions<TKey, TValue>); | ||
/** | ||
@@ -76,3 +96,3 @@ * Returns the number of entries in the LRUCache object. | ||
*/ | ||
set(key: TKey, value: TValue, entryOptions?: LRUCacheSetEntryOptions): LRUCache<TKey, TValue>; | ||
set(key: TKey, value: TValue, entryOptions?: LRUCacheSetEntryOptions<TKey, TValue>): LRUCache<TKey, TValue>; | ||
/** | ||
@@ -79,0 +99,0 @@ * Returns the value associated to the key, or null if there is none or if the entry is expired. |
@@ -15,3 +15,3 @@ "use strict"; | ||
this.tail = null; | ||
const { maxSize = 25, entryExpirationTimeInMS = null } = options || {}; | ||
const { maxSize = 25, entryExpirationTimeInMS = null, onEntryEvicted, onEntryMarkedAsMostRecentlyUsed } = options || {}; | ||
if (Number.isNaN(maxSize) || maxSize <= 0) { | ||
@@ -26,2 +26,4 @@ throw new Error('maxSize must be greater than 0.'); | ||
this.entryExpirationTimeInMS = entryExpirationTimeInMS; | ||
this.onEntryEvicted = onEntryEvicted; | ||
this.onEntryMarkedAsMostRecentlyUsed = onEntryMarkedAsMostRecentlyUsed; | ||
} | ||
@@ -107,2 +109,4 @@ /** | ||
entryExpirationTimeInMS: this.entryExpirationTimeInMS, | ||
onEntryEvicted: this.onEntryEvicted, | ||
onEntryMarkedAsMostRecentlyUsed: this.onEntryMarkedAsMostRecentlyUsed, | ||
...entryOptions | ||
@@ -344,2 +348,3 @@ }); | ||
} | ||
node.invokeOnEntryMarkedAsMostRecentlyUsed(); | ||
} | ||
@@ -363,2 +368,3 @@ removeNodeFromList(node) { | ||
removeNodeFromListAndLookupTable(node) { | ||
node.invokeOnEvicted(); | ||
this.removeNodeFromList(node); | ||
@@ -365,0 +371,0 @@ return this.lookupTable.delete(node.key); |
@@ -5,2 +5,11 @@ export interface LRUCacheNodeOptions<TKey, TValue> { | ||
entryExpirationTimeInMS?: number | null; | ||
onEntryEvicted?: (evictedEntry: { | ||
key: TKey; | ||
value: TValue; | ||
isExpired: boolean; | ||
}) => void; | ||
onEntryMarkedAsMostRecentlyUsed?: (entry: { | ||
key: TKey; | ||
value: TValue; | ||
}) => void; | ||
} | ||
@@ -14,5 +23,9 @@ export declare class LRUCacheNode<TKey, TValue> { | ||
prev: LRUCacheNode<TKey, TValue> | null; | ||
private readonly onEntryEvicted?; | ||
private readonly onEntryMarkedAsMostRecentlyUsed?; | ||
constructor(key: TKey, value: TValue, options?: LRUCacheNodeOptions<TKey, TValue>); | ||
get isExpired(): boolean; | ||
invokeOnEvicted(): void; | ||
invokeOnEntryMarkedAsMostRecentlyUsed(): void; | ||
} | ||
//# sourceMappingURL=LRUCacheNode.d.ts.map |
@@ -6,3 +6,3 @@ "use strict"; | ||
constructor(key, value, options) { | ||
const { entryExpirationTimeInMS = null, next = null, prev = null } = options || {}; | ||
const { entryExpirationTimeInMS = null, next = null, prev = null, onEntryEvicted, onEntryMarkedAsMostRecentlyUsed } = options || {}; | ||
if (typeof entryExpirationTimeInMS === 'number' && | ||
@@ -18,2 +18,4 @@ (entryExpirationTimeInMS <= 0 || Number.isNaN(entryExpirationTimeInMS))) { | ||
this.prev = prev; | ||
this.onEntryEvicted = onEntryEvicted; | ||
this.onEntryMarkedAsMostRecentlyUsed = onEntryMarkedAsMostRecentlyUsed; | ||
} | ||
@@ -23,4 +25,16 @@ get isExpired() { | ||
} | ||
invokeOnEvicted() { | ||
if (this.onEntryEvicted) { | ||
const { key, value, isExpired } = this; | ||
this.onEntryEvicted({ key, value, isExpired }); | ||
} | ||
} | ||
invokeOnEntryMarkedAsMostRecentlyUsed() { | ||
if (this.onEntryMarkedAsMostRecentlyUsed) { | ||
const { key, value } = this; | ||
this.onEntryMarkedAsMostRecentlyUsed({ key, value }); | ||
} | ||
} | ||
} | ||
exports.LRUCacheNode = LRUCacheNode; | ||
//# sourceMappingURL=LRUCacheNode.js.map |
{ | ||
"name": "typescript-lru-cache", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "LRU Cache", | ||
@@ -5,0 +5,0 @@ "author": "Robert Herber", |
@@ -13,2 +13,6 @@ # typescript-lru-cache | ||
## Why use this LRU Cache? | ||
This library was written in Typescript so type definitions are included out of the box and are always up to date. The Typescript source code is included in the package so users can easily look at the implementation. This cache uses a `Map` object for internal entry tracking, so any type can be used as a key (including reference types!). | ||
## Usage: | ||
@@ -40,2 +44,4 @@ | ||
- `entryExpirationTimeInMS` The time to live for cache entries. Setting this to `null` will make entries never expire. Default value is `null`. | ||
- `onEntryEvicted` Function to be called whenever an entry is evicted from the cache (when evicted due to needing to make room, is expired, or deleted using delete()). Passed arguments are (key, value, isExpired) | ||
- `onEntryMarkedAsMostRecentlyUsed` Function to be called whenever an entry is marked as recently used (on set, get, find, etc). Passed arguments are (key, value) | ||
@@ -49,2 +55,4 @@ ### LRUCache Set Entry Options: | ||
- `entryExpirationTimeInMS` The time to live for the entry. Setting this to `null` will make the entry never expire. | ||
- `onEntryEvicted` Function to be called whenever an entry is evicted from the cache (when evicted due to needing to make room, is expired, or deleted using delete()). Passed arguments are (key, value, isExpired) | ||
- `onEntryMarkedAsMostRecentlyUsed` Function to be called whenever an entry is marked as recently used (on set, get, find, etc). Passed arguments are (key, value) | ||
@@ -51,0 +59,0 @@ Example: |
@@ -452,2 +452,110 @@ import { LRUCache, LRUCacheEntry } from '../LRUCache'; | ||
it('should call the cache onEntryEvicted function', () => { | ||
const onEntryEvicted = jest.fn(); | ||
const cache = new LRUCache({ maxSize: 2, onEntryEvicted }); | ||
const lastAccessedKey = 'lastAccessedKey'; | ||
const lastAccessedValue = 'lastAccessedValue'; | ||
cache.set('key1', 'value1'); | ||
cache.set(lastAccessedKey, lastAccessedValue); | ||
// At this point, lastAccessedKey is the most recently accessed. | ||
// Access the other to make lastAccessedKey last accessed | ||
cache.get('key1'); | ||
// Adding a new value will now evict lastAccessedKey from cache | ||
cache.set('key2', 'value2'); | ||
expect(onEntryEvicted).toBeCalledTimes(1); | ||
expect(onEntryEvicted).toBeCalledWith({ key: lastAccessedKey, value: lastAccessedValue, isExpired: false }); | ||
}); | ||
it('should call the node onEntryEvicted function and not cache function', () => { | ||
const cacheOnEntryEvicted = jest.fn(); | ||
const entryOnEntryEvicted = jest.fn(); | ||
const cache = new LRUCache({ maxSize: 2, onEntryEvicted: cacheOnEntryEvicted }); | ||
const lastAccessedKey = 'lastAccessedKey'; | ||
const lastAccessedValue = 'lastAccessedValue'; | ||
cache.set('key1', 'value1'); | ||
cache.set(lastAccessedKey, lastAccessedValue, { onEntryEvicted: entryOnEntryEvicted }); | ||
// At this point, lastAccessedKey is the most recently accessed. | ||
// Access the other to make lastAccessedKey last accessed | ||
cache.get('key1'); | ||
// Adding a new value will now evict lastAccessedKey from cache | ||
cache.set('key2', 'value2'); | ||
expect(cacheOnEntryEvicted).not.toBeCalled(); | ||
expect(entryOnEntryEvicted).toBeCalledTimes(1); | ||
expect(entryOnEntryEvicted).toBeCalledWith({ key: lastAccessedKey, value: lastAccessedValue, isExpired: false }); | ||
}); | ||
it('should call the cache onEntryMarkedAsMostRecentlyUsed function many times', () => { | ||
const onEntryMarkedAsMostRecentlyUsed = jest.fn(); | ||
const cache = new LRUCache({ maxSize: 2, onEntryMarkedAsMostRecentlyUsed }); | ||
const lastAccessedKey = 'lastAccessedKey'; | ||
const lastAccessedValue = 'lastAccessedValue'; | ||
cache.set('key1', 'value1'); | ||
expect(onEntryMarkedAsMostRecentlyUsed).toHaveBeenLastCalledWith({ key: 'key1', value: 'value1' }); | ||
cache.set(lastAccessedKey, lastAccessedValue); | ||
expect(onEntryMarkedAsMostRecentlyUsed).toHaveBeenLastCalledWith({ | ||
key: lastAccessedKey, | ||
value: lastAccessedValue | ||
}); | ||
// At this point, lastAccessedKey is the most recently accessed. | ||
// Access the other to make lastAccessedKey last accessed | ||
cache.get('key1'); | ||
expect(onEntryMarkedAsMostRecentlyUsed).toHaveBeenLastCalledWith({ key: 'key1', value: 'value1' }); | ||
// Adding a new value will now evict lastAccessedKey from cache | ||
cache.set('key2', 'value2'); | ||
expect(onEntryMarkedAsMostRecentlyUsed).toHaveBeenLastCalledWith({ key: 'key2', value: 'value2' }); | ||
expect(onEntryMarkedAsMostRecentlyUsed).toBeCalledTimes(4); | ||
}); | ||
it('should call the node onEntryMarkedAsMostRecentlyUsed function and not cache function when entry function is set', () => { | ||
const cacheOnEntryMarkedAsMostRecentlyUsed = jest.fn(); | ||
const entryOnEntryMarkedAsMostRecentlyUsed = jest.fn(); | ||
const cache = new LRUCache({ maxSize: 2, onEntryMarkedAsMostRecentlyUsed: cacheOnEntryMarkedAsMostRecentlyUsed }); | ||
cache.set('key1', 'value1'); | ||
expect(cacheOnEntryMarkedAsMostRecentlyUsed).toHaveBeenCalledTimes(1); | ||
expect(entryOnEntryMarkedAsMostRecentlyUsed).toHaveBeenCalledTimes(0); | ||
expect(cacheOnEntryMarkedAsMostRecentlyUsed).toHaveBeenLastCalledWith({ key: 'key1', value: 'value1' }); | ||
cache.set('key2', 'value2', { onEntryMarkedAsMostRecentlyUsed: entryOnEntryMarkedAsMostRecentlyUsed }); | ||
expect(cacheOnEntryMarkedAsMostRecentlyUsed).toHaveBeenCalledTimes(1); | ||
expect(entryOnEntryMarkedAsMostRecentlyUsed).toHaveBeenCalledTimes(1); | ||
expect(entryOnEntryMarkedAsMostRecentlyUsed).toHaveBeenLastCalledWith({ key: 'key2', value: 'value2' }); | ||
cache.get('key1'); | ||
expect(cacheOnEntryMarkedAsMostRecentlyUsed).toHaveBeenCalledTimes(2); | ||
expect(entryOnEntryMarkedAsMostRecentlyUsed).toHaveBeenCalledTimes(1); | ||
expect(cacheOnEntryMarkedAsMostRecentlyUsed).toHaveBeenLastCalledWith({ key: 'key1', value: 'value1' }); | ||
cache.get('key2'); | ||
expect(cacheOnEntryMarkedAsMostRecentlyUsed).toHaveBeenCalledTimes(2); | ||
expect(entryOnEntryMarkedAsMostRecentlyUsed).toHaveBeenCalledTimes(2); | ||
expect(entryOnEntryMarkedAsMostRecentlyUsed).toHaveBeenLastCalledWith({ key: 'key2', value: 'value2' }); | ||
}); | ||
it('should not evict the least recently accessed entry when setting a key that is already in cache', () => { | ||
@@ -503,2 +611,17 @@ const cache = new LRUCache({ maxSize: 2 }); | ||
it.each([{ foo: 'bar' }, NaN, null, false, true, 3.14, 42, -100, Infinity, undefined, 'key'])( | ||
'should allow any type for key', | ||
key => { | ||
const cache = new LRUCache<any, any>(); | ||
const value = 'someValue'; | ||
cache.set(key, value); | ||
expect(cache.has(key)).toBe(true); | ||
expect(cache.get(key)).toEqual(value); | ||
expect(cache.size).toBe(1); | ||
expect(() => validateCacheInternals(cache)).not.toThrow(); | ||
} | ||
); | ||
it('should exercise the cache', () => { | ||
@@ -608,2 +731,32 @@ const cache = new LRUCache({ maxSize: 50 }); | ||
}); | ||
it('should use reference of object for key', () => { | ||
const cache = new LRUCache<any, any>(); | ||
const key = { | ||
foo: 'bar' | ||
}; | ||
const value = 'someValue'; | ||
cache.set(key, value); | ||
const item = cache.get(key); | ||
expect(item).toEqual(value); | ||
const keyClone = { ...key }; | ||
const itemWithCloneKey = cache.get(keyClone); | ||
expect(itemWithCloneKey).toBeNull(); | ||
const value2 = 'someOtherValue'; | ||
cache.set(keyClone, value2); | ||
const item2 = cache.get(key); | ||
const item3 = cache.get(keyClone); | ||
expect(item2).toEqual(value); | ||
expect(item3).toEqual(value2); | ||
}); | ||
}); | ||
@@ -610,0 +763,0 @@ |
@@ -85,2 +85,34 @@ import { LRUCacheNode } from '../LRUCacheNode'; | ||
}); | ||
describe('invokeOnEvicted', () => { | ||
it('should call the passed in onEvicted function', () => { | ||
const onEntryEvicted = jest.fn(); | ||
const key = 'key'; | ||
const value = 'value'; | ||
const node = new LRUCacheNode(key, value, { onEntryEvicted }); | ||
node.invokeOnEvicted(); | ||
expect(onEntryEvicted).toBeCalledTimes(1); | ||
expect(onEntryEvicted).toBeCalledWith({ key, value, isExpired: false }); | ||
}); | ||
}); | ||
describe('invokeOnEntryMarkedAsMostRecentlyUsed', () => { | ||
it('should call the passed in onEvicted function', () => { | ||
const onEntryMarkedAsMostRecentlyUsed = jest.fn(); | ||
const key = 'key'; | ||
const value = 'value'; | ||
const node = new LRUCacheNode(key, value, { onEntryMarkedAsMostRecentlyUsed }); | ||
node.invokeOnEntryMarkedAsMostRecentlyUsed(); | ||
expect(onEntryMarkedAsMostRecentlyUsed).toBeCalledTimes(1); | ||
expect(onEntryMarkedAsMostRecentlyUsed).toBeCalledWith({ key, value }); | ||
}); | ||
}); | ||
}); |
import { LRUCacheNode } from './LRUCacheNode'; | ||
export interface LRUCacheOptions { | ||
export interface LRUCacheOptions<TKey, TValue> { | ||
maxSize?: number; | ||
entryExpirationTimeInMS?: number | null; | ||
onEntryEvicted?: (evictedEntry: { key: TKey; value: TValue; isExpired: boolean }) => void; | ||
onEntryMarkedAsMostRecentlyUsed?: (entry: { key: TKey; value: TValue }) => void; | ||
} | ||
export interface LRUCacheSetEntryOptions { | ||
export interface LRUCacheSetEntryOptions<TKey, TValue> { | ||
entryExpirationTimeInMS?: number | null; | ||
onEntryEvicted?: (evictedEntry: { key: TKey; value: TValue; isExpired: boolean }) => void; | ||
onEntryMarkedAsMostRecentlyUsed?: (entry: { key: TKey; value: TValue }) => void; | ||
} | ||
@@ -22,2 +26,6 @@ | ||
private readonly onEntryEvicted?: (evictedEntry: { key: TKey; value: TValue; isExpired: boolean }) => void; | ||
private readonly onEntryMarkedAsMostRecentlyUsed?: (entry: { key: TKey; value: TValue }) => void; | ||
private maxSizeInternal: number; | ||
@@ -34,4 +42,5 @@ | ||
*/ | ||
public constructor(options?: LRUCacheOptions) { | ||
const { maxSize = 25, entryExpirationTimeInMS = null } = options || {}; | ||
public constructor(options?: LRUCacheOptions<TKey, TValue>) { | ||
const { maxSize = 25, entryExpirationTimeInMS = null, onEntryEvicted, onEntryMarkedAsMostRecentlyUsed } = | ||
options || {}; | ||
@@ -51,2 +60,4 @@ if (Number.isNaN(maxSize) || maxSize <= 0) { | ||
this.entryExpirationTimeInMS = entryExpirationTimeInMS; | ||
this.onEntryEvicted = onEntryEvicted; | ||
this.onEntryMarkedAsMostRecentlyUsed = onEntryMarkedAsMostRecentlyUsed; | ||
} | ||
@@ -136,3 +147,3 @@ | ||
*/ | ||
public set(key: TKey, value: TValue, entryOptions?: LRUCacheSetEntryOptions): LRUCache<TKey, TValue> { | ||
public set(key: TKey, value: TValue, entryOptions?: LRUCacheSetEntryOptions<TKey, TValue>): LRUCache<TKey, TValue> { | ||
const currentNodeForKey = this.lookupTable.get(key); | ||
@@ -146,2 +157,4 @@ | ||
entryExpirationTimeInMS: this.entryExpirationTimeInMS, | ||
onEntryEvicted: this.onEntryEvicted, | ||
onEntryMarkedAsMostRecentlyUsed: this.onEntryMarkedAsMostRecentlyUsed, | ||
...entryOptions | ||
@@ -425,2 +438,4 @@ }); | ||
} | ||
node.invokeOnEntryMarkedAsMostRecentlyUsed(); | ||
} | ||
@@ -450,2 +465,3 @@ | ||
private removeNodeFromListAndLookupTable(node: LRUCacheNode<TKey, TValue>): boolean { | ||
node.invokeOnEvicted(); | ||
this.removeNodeFromList(node); | ||
@@ -452,0 +468,0 @@ |
@@ -5,2 +5,4 @@ export interface LRUCacheNodeOptions<TKey, TValue> { | ||
entryExpirationTimeInMS?: number | null; | ||
onEntryEvicted?: (evictedEntry: { key: TKey; value: TValue; isExpired: boolean }) => void; | ||
onEntryMarkedAsMostRecentlyUsed?: (entry: { key: TKey; value: TValue }) => void; | ||
} | ||
@@ -21,4 +23,14 @@ | ||
private readonly onEntryEvicted?: (evictedEntry: { key: TKey; value: TValue; isExpired: boolean }) => void; | ||
private readonly onEntryMarkedAsMostRecentlyUsed?: (entry: { key: TKey; value: TValue }) => void; | ||
public constructor(key: TKey, value: TValue, options?: LRUCacheNodeOptions<TKey, TValue>) { | ||
const { entryExpirationTimeInMS = null, next = null, prev = null } = options || {}; | ||
const { | ||
entryExpirationTimeInMS = null, | ||
next = null, | ||
prev = null, | ||
onEntryEvicted, | ||
onEntryMarkedAsMostRecentlyUsed | ||
} = options || {}; | ||
@@ -38,2 +50,4 @@ if ( | ||
this.prev = prev; | ||
this.onEntryEvicted = onEntryEvicted; | ||
this.onEntryMarkedAsMostRecentlyUsed = onEntryMarkedAsMostRecentlyUsed; | ||
} | ||
@@ -44,2 +58,16 @@ | ||
} | ||
public invokeOnEvicted(): void { | ||
if (this.onEntryEvicted) { | ||
const { key, value, isExpired } = this; | ||
this.onEntryEvicted({ key, value, isExpired }); | ||
} | ||
} | ||
public invokeOnEntryMarkedAsMostRecentlyUsed(): void { | ||
if (this.onEntryMarkedAsMostRecentlyUsed) { | ||
const { key, value } = this; | ||
this.onEntryMarkedAsMostRecentlyUsed({ key, value }); | ||
} | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
105177
2116
311