@kolodny/lru-ttl-cache
Version:
Fast LRU and TTL cache with upsert and promise option
1 lines • 15.8 kB
Source Map (JSON)
{"version":3,"sources":["index.ts"],"names":[],"mappings":"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","file":"index.d.mjs","sourcesContent":["import MS from 'ms';\nimport Bytes from 'bytes';\n/**\n * LRU & TTL fast in-mermory cache\n * Promise supported\n * Upsert supported\n * Permanent items supported\n */\n\n/** Options */\nexport interface ConstOptions<K,V>{\n /** Max entries @default Infinity */\n max?: number\n /** Max bytes @default Infinity */\n maxBytes?: number|string\n /** Time to live @default Infinity */\n ttl?: number|string\n /** TTL check interval. @default 60s */\n ttlInterval?: number|string\n /** Upsert callback: enables to create missing elements */\n upsert?: ((key: K, additionalArgs?: any[])=> UpserResult<V> | Promise<UpserResult<V>>) | undefined\n}\n\n/** Upsert result */\nexport interface UpserResult<V>{\n value: V,\n bytes?: number,\n isPermanent?: boolean\n}\n\n/** NodeChain */\ninterface NodeChain{\n /** Previous element */\n _prev?: NodeChain,\n /** Next element */\n _next?: NodeChain\n}\n\n/** Node interface for LRU */\ninterface Node<K, V> extends NodeChain{\n value: V | Promise<V>\n key: K\n /** Object size */\n bytes: number\n /** Created timestamp */\n createdAt: number\n /** Last access */\n lastAccess: number\n /** Is permanent node */\n isPermanent: Boolean\n}\n\ntype NodeReadOnly<K,V>= Readonly<Node<K,V>>\n\n/** Main interface */\nexport default class LRU_TTL<K, V> implements NodeChain{\n private _map: Map<K, Node<K, V>>= new Map<K, Node<K, V>>();\n\t/** Max temp elements */\n private _max: number\n private _maxBytes: number // max bytes for temp entries\n /** TLL */\n private _ttl: number\n private _ttlInterval: number\n private _ttlP?: NodeJS.Timeout= undefined;\n private _upsert?: ConstOptions<K,V>[\"upsert\"]\n\n\t/** Temp elements count */\n\tprivate _tmpSize:number = 0;\n /** Total bytes inside the cache */\n private _totalBytes: number= 0\n /** Temp entries bytes */\n private _tmpBytes: number= 0\n /** Last used element */\n _next: NodeChain= this\n /** Least used element */\n _prev: NodeChain= this\n\n constructor(options?: ConstOptions<K, V>){\n // Set config\n if(options){\n // max entries\n this._max= options.max==null? Infinity : options.max;\n // max bytes\n var a: string|number|undefined= options.maxBytes;\n this._maxBytes= a==null? Infinity : typeof a=== 'number' ? a : Bytes.parse(a);\n // TTL\n a= options.ttl;\n this._ttl= a==null? Infinity : typeof a=== 'number' ? a : MS(a);\n // TTL interval\n a= options.ttlInterval;\n this._ttlInterval= a==null? Infinity : typeof a=== 'number' ? a : MS(a);\n this._upsert= options.upsert;\n } else {\n this._max= Infinity;\n this._maxBytes= Infinity;\n this._ttl= Infinity;\n this._ttlInterval= 60000;\n }\n\t\t// fix ttl interval\n\t\tif(this.ttlInterval > this.ttl)\n\t\t\tthis.ttlInterval= this.ttl;\n\t\t// init chain\n\t\tthis._prev= this._next= this;\n }\n\n /** Set max */\n get max(){return this._max}\n set max(max: number){\n this._max= max\n }\n\n get maxBytes(){ return this._maxBytes }\n set maxBytes(maxBytes: number|string){\n this._maxBytes= typeof maxBytes === 'number'? maxBytes : Bytes.parse(maxBytes);\n }\n\n get ttl(){ return this._ttl }\n set ttl(ttl: number|string){\n this._ttl= typeof ttl ==='number' ? ttl : MS(ttl);\n }\n\n get ttlInterval(){ return this._ttlInterval }\n set ttlInterval(ttlInterval: number|string){\n this._ttlInterval= typeof ttlInterval==='number' ? ttlInterval : MS(ttlInterval);\n\t\t// fix ttl interval\n\t\tif(this.ttlInterval > this.ttl)\n\t\t\tthis.ttlInterval= this.ttl;\n\t\t// reload cleaner\n if(this._ttlP){\n clearInterval(this._ttlP);\n this._ttlP= setInterval(this._ttlClean.bind(this), this._ttlInterval);\n }\n }\n\n get upsertCb(){ return this._upsert }\n set upsertCb(cb: ConstOptions<K,V>[\"upsert\"]){ this._upsert= cb; }\n\n /** Get total bytes */\n get bytes(){ return this._totalBytes }\n /** Temp entries bytes */\n get tmpBytes(){ return this._tmpBytes }\n\n /** Get cache size */\n get size(): number{ return this._map.size }\n\n\t/** Get temp elements count */\n\tget tmpSize(): number{ return this._tmpSize }\n\n /** Check if cache has key */\n has(key: K){ return this._map.has(key) }\n\n /** Set value */\n set(key: K, value: V|Promise<V>, bytes:number=0, isPermanent= false): this{\n var item;\n if(item= this._map.get(key)){\n if(item.value === value && item.bytes=== bytes && item.isPermanent=== isPermanent){\n item.lastAccess= Date.now();\n return this\n }\n this._delete(item);\n }\n this._set(key, value, bytes, isPermanent);\n return this;\n }\n\n /** Add permanent element to the cache (will persist until user removes it manually) */\n setPermanent(key: K, value: V, bytes:number=0): this{\n return this.set(key, value, bytes, true);\n }\n\n /** @private Insert new item */\n private _set(key: K, value: V|Promise<V>, bytes:number, isPermanent:boolean): Node<K, V>{\n var now= Date.now();\n var ele: Node<K, V>={\n key, value, bytes,\n createdAt: now,\n lastAccess: now,\n isPermanent: isPermanent,\n _prev: undefined,\n _next: undefined\n }\n // add to map\n this._map.set(key, ele);\n // Flags\n this._totalBytes+= bytes;\n // add to chain\n if(!isPermanent){\n var p= this._next;\n\t\t\tp._prev= ele;\n\t\t\tele._next= p;\n ele._prev= this;\n this._next= ele;\n // Flags\n\t\t\tthis._tmpSize++; // inc tmp counter\n this._tmpBytes+= bytes;\n // remove last permanent element\n if(this._tmpSize > this._max)\n this._delete(this._prev as Node<K,V>) // Remove least used element\n // remove until maxBytes\n while(this._tmpBytes > this._maxBytes && this._prev!= this){\n this._delete(this._prev as Node<K,V>);\n }\n // Run TTL\n if(!this._ttlP)\n this._ttlP= setInterval(this._ttlClean.bind(this), this._ttlInterval)\n }\n return ele;\n }\n\n /** Get element from the cache */\n get(key: K, upsert?: boolean, additionalUpsertCbArgs?: any[]):V|Promise<V>|undefined{\n var ele: Node<K, V>|undefined;\n var p: NodeChain;\n var p2: NodeChain;\n if(ele= this._map.get(key)){\n ele.lastAccess= Date.now();\n if(!ele.isPermanent && ele._prev!== this){\n // Remove from chain\n p= ele._next!;\n p2= ele._prev!;\n p2!._next= p\n p._prev= p2\n // bring forword\n p= this._next\n\t\t\t\tp._prev= ele\n ele._next= p;\n ele._prev= this;\n this._next= ele\n }\n return ele.value;\n } else if (upsert){\n if(typeof this._upsert !== 'function')\n throw new Error('Missing upsert callback!');\n var upsertResult= this._upsert(key, additionalUpsertCbArgs);\n if(upsertResult instanceof Promise){\n ele= this._set(key, upsertResult.then(({value})=> value), 0, true);\n return upsertResult.then((r: UpserResult<V>)=>{\n // Check object not modified\n if(ele=== this._map.get(key)){\n this._delete(ele!);\n this._set(key, r.value, r.bytes||0, !!r.isPermanent);\n }\n return r.value;\n });\n }\n else {\n this._set(key, upsertResult.value, upsertResult.bytes||0, !!upsertResult.isPermanent);\n return upsertResult.value;\n }\n }\n else return undefined;\n }\n /** Get element from the cache without changing it's timeout and LRU */\n peek(key: K): V|Promise<V>|undefined{\n return this._map.get(key)?.value;\n }\n\n /** Get and remove Least used element */\n pop(): V | Promise<V> |undefined{\n if(this._prev!==this){\n var oldest= this._prev as Node<K, V>;\n this._delete(oldest);\n return oldest.value;\n }\n return undefined;\n }\n \n /** Get least recently used item */\n getLRU(){\n return this._prev !== this ? (this._prev as Node<K,V>).value : undefined\n }\n\n /** Upsert element in the cache */\n upsert(\n\t\t/** Cache key */\n\t\tkey: K,\n\t\t/** Additional args for upsert callback */\n\t\t... args: any[]\n\t): V|Promise<V>{\n return this.get(key, true, args)!;\n }\n\n /** Delete element from the cache */\n delete(key: K): this{\n var el;\n if(el= this._map.get(key))\n this._delete(el);\n return this;\n }\n\n /** @private remove element from cache */\n private _delete(ele: Node<K, V>){\n // remove from map\n this._map.delete(ele.key);\n // remove from chain\n var bytes= ele.bytes;\n if(!ele.isPermanent){\n var p = ele._next!;\n\t\t\tvar p2= ele._prev!;\n p._prev= p2;\n p2._next= p;\n // adjust cache bytes\n this._tmpBytes-= bytes;\n\t\t\tthis._tmpSize--;\n }\n // Adjust total bytes\n this._totalBytes-= bytes;\n }\n\n /** Clear all the cache excluding permanent items */\n clearTemp(){\n var el= this._prev as NodeChain;\n var map= this._map;\n // @ts-ignore\n while(el !== this){\n map.delete((el as Node<K, V>).key);\n el= el._next!;\n }\n this._next= this._prev= this;\n this._totalBytes-= this._tmpBytes;\n this._tmpBytes= 0;\n\t\tthis._tmpSize= 0;\n }\n\n /** Clear all items in the cache including permanent items */\n clearAll(){\n this._next= this._prev= this;\n this._map.clear();\n this._tmpBytes= this._tmpSize= this._totalBytes= 0;\n }\n\n /** Get entries */\n *entries():IterableIterator<[K, V|Promise<V>]>{\n var it= this._map.entries();\n var p= it.next();\n var v;\n while(!p.done){\n v= p.value;\n yield [v[0], v[1].value];\n p= it.next();\n }\n }\n\n /** Get all keys */\n keys(): IterableIterator<K>{ return this._map.keys() }\n\n /** Values */\n *values():IterableIterator<V|Promise<V>>{\n var it= this._map.values();\n var p= it.next()\n while(!p.done){\n yield p.value.value;\n }\n }\n\n /** ForEach */\n forEach(cb: (value: V|Promise<V>, key: K) => void, thisArg: any){\n var it= this._map.values();\n var p= it.next()\n if(arguments.length===1) thisArg= this;\n while(!p.done){\n var e= p.value;\n cb.call(thisArg, e.value, e.key);\n }\n }\n\n /** TTL CLean */\n _ttlClean(){\n // Find last node to keep\n var expires= Date.now() - this._ttl;\n var p= this._prev as Node<K,V>;\n var bytes= 0;\n var map= this._map;\n while((p as NodeChain)!== this && p.lastAccess < expires){\n bytes+= p.bytes;\n map.delete(p.key);\n p= p._prev as Node<K,V>;\n }\n // Remove other nodes\n if((p as NodeChain)===this){\n // Remove all nodes\n this._prev= this._next= this\n this._totalBytes-= bytes;\n if(this._totalBytes<0) this._totalBytes= 0\n this._tmpBytes= this._tmpSize= 0;\n // Break ttl\n clearInterval(this._ttlP!);\n this._ttlP= undefined;\n } else {\n this._prev= p;\n p._next= this;\n }\n }\n\n /** For(of) */\n *[Symbol.iterator](){\n var it= this._map.values();\n var v= it.next();\n while(!v.done){\n var entry= v.value\n yield [entry.key, entry.value];\n }\n }\n\n\t/** Get element metadata */\n\tgetMetadata(key: K): NodeReadOnly<K,V>|undefined{\n\t\treturn this._map.get(key);\n\t}\n}"]}