UNPKG

timed-kvs

Version:

Lightweight Key-value storage with built-in TTL expiration management

178 lines (175 loc) 4.46 kB
/** * linked-kvs.ts */ class LinkedKVS { constructor() { this.latest = null; this.items = {}; this.length = 0; } get(key) { const item = this.getItem(key); if (item) return item.value; } set(key, value) { this.setItem(key, { value: value }); } getItem(key) { const item = this.items[key]; if (item && !item.deleted) return item; } setItem(key, value) { const item = value; // remove duplicated item this.delete(key); this.items[key] = item; this.length++; item.key = key; // append at the end of the linked list const latest = this.latest; if (latest) { item.next = latest.deleted ? latest.next : latest; } this.latest = item; } size() { return this.length; } /** * restrict maximum number of items * it costs O(n) as parsing whole of items */ shrink(size) { let item = this.latest; while (item) { if (0 >= size) { this.truncate(item); return; } if (!item.deleted) { size--; } item = item.next; // next item } } /** * remove given item */ delete(key) { let item = this.getItem(key); if (item) this._delete(item); } _delete(item) { if (!item) return; if (!item.deleted) { delete this.items[item.key]; this.length--; item.key = item.value = null; item.deleted = true; } // shortcut link let next = item.next; while (next && next.deleted) { next = item.next = next.next; } } /** * remove given item and rest of items */ truncate(value) { let item = value; while (item) { this._delete(item); item = item.next; // next item } } /** * return an array containing all items in proper sequence */ all() { const array = []; let item = this.latest; while (item) { if (!item.deleted) { const it = this.getItem(item.key); if (it) array.push(it); } item = item.next; } return array.reverse(); } /** * return an array containing all keys in proper sequence */ keys() { return this.all().map((item) => item.key); } /** * return an array containing all values in proper sequence */ values() { return this.all().map(item => item.value); } } /** * timed-kvs.ts */ /** * Storage with TTL for each entries */ class TimedKVS extends LinkedKVS { constructor(options) { super(); const { expires, maxItems } = options || {}; this.expires = (expires > 0) && +expires || 0; this.maxItems = (maxItems > 0) && +maxItems || 0; } getItem(key) { const { expires } = this; const item = super.getItem(key); if (!item) return; if (expires) { const now = Date.now(); // if the cached item is expired, remove rest of items as expired as well. if (now > item.ttl) { this.truncate(item); return; } } return item; } setItem(key, item) { const { expires, maxItems } = this; if (expires) { if (this.firstKey) { // check the first item expired or not const expired = !this.getItem(this.firstKey); if (expired) this.firstKey = undefined; } else { this.firstKey = key; } const now = Date.now(); item.ttl = now + expires; } super.setItem(key, item); if (maxItems) { if (this.gcTimeout || maxItems >= this.size()) return; const gcDelay = Math.min(maxItems, 1000); // garbage collection in background this.gcTimeout = setTimeout(() => { this.shrink(maxItems); delete this.gcTimeout; }, gcDelay); } } } export { TimedKVS };