@paperbits/common
Version:
Paperbits common components.
208 lines (175 loc) • 4.94 kB
text/typescript
import { Bag } from "../bag";
interface ICacheItem<T> {
key: string;
value: T;
ttl?: number;
next: ICacheItem<T>;
prev: ICacheItem<T>;
}
/**
* Basic LRU cache utility.
*/
export class LruCache<T> {
private nodeMap: Bag<ICacheItem<T>> = {};
private tail: ICacheItem<T>;
private head: ICacheItem<T>;
constructor(
private maxSize: number = 10000,
private onEvict?: (key: string, value: T) => void,
private ttl?: number // time to live in seconds
) { }
/**
* Returns cached item with specified key.
* @param key {string} Item key.
*/
public getItem(key: string): T {
const item: ICacheItem<T> = this.nodeMap[key];
if (!item) {
return null;
}
if (item.ttl != undefined && new Date().getTime() > item.ttl) {
this.remove(item);
return null;
}
this.pop(item);
return item.value;
}
/**
* Puts specified into cache.
* @param key {string} Item key.
* @param value {string} Item value.
*/
public setItem(key: string, value: T): void {
const item: ICacheItem<T> = this.nodeMap[key];
const ttl = this.ttl ? new Date().getTime() + (this.ttl * 1000) : undefined;
if (item) {
item.value = value;
item.ttl = ttl;
this.pop(item);
}
else {
this.insert(key, value, ttl);
}
}
public clear(): void {
this.head = null;
this.nodeMap = {};
}
/**
* Removes item from the cache.
* @param key {string} Item key.
*/
public removeItem(key: string): void {
const item = this.nodeMap[key];
this.remove(item);
}
/**
* Removed item matching to predicate.
* @param predicate Predicate.
*/
public removeWhere(predicate: (key: string, value: T) => boolean): void {
let item: ICacheItem<T> = this.head;
if (!item) {
return;
}
do {
if (predicate(item.key, item.value)) {
this.removeItem(item.key);
}
}
while ((item = item.next) !== null);
}
/**
* Returns all keys of cached objects.
*/
public getKeys(): string[] {
const result: string[] = new Array<string>();
let item: ICacheItem<T> = this.head;
if (!item) {
return result;
}
do {
result.push(item.key);
}
while ((item = item.next) !== null);
return result;
}
/**
* Returns number of cached items.
*/
public size(): number {
return Object.keys(this.nodeMap).length;
}
private pop(item: ICacheItem<T>): void {
if (item === this.head) {
return;
}
item.prev.next = item.next;
if (item.next) {
item.next.prev = item.prev;
}
else {
this.tail = item.prev;
}
item.next = this.head;
this.head.prev = item;
item.prev = null;
this.head = item;
}
private insert(key: string, value: T, ttl?: number): void {
if (Object.keys(this.nodeMap).length === this.maxSize) {
const tail = this.tail;
this.remove(tail);
if (this.onEvict) {
this.onEvict(tail.key, tail.value);
}
}
if (!this.head) {
this.head = <ICacheItem<T>>{
key: key,
value: value,
ttl,
prev: null,
next: null
};
this.tail = this.head;
}
else {
this.head = <ICacheItem<T>>{
key: key,
value: value,
ttl,
prev: null,
next: this.head
};
this.head.next.prev = this.head;
}
this.nodeMap[key] = this.head;
}
private remove(item: ICacheItem<T>): void {
if (!item) {
return;
}
delete this.nodeMap[item.key];
if (!this.head) {
return;
}
if (this.head === item && this.tail === item) {
this.head = null;
this.tail = null;
return;
}
if (this.head === item) {
this.head = item.next;
this.head.prev = null;
return;
}
if (this.tail === item) {
this.tail = this.tail.prev;
this.tail.next = null;
return;
}
item.prev.next = item.next;
item.next.prev = item.prev;
}
}