"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.LRUCache = exports.LinkedMap = exports.Touch = void 0; var Touch; (function (Touch) { Touch.None = 0; Touch.First = 1; Touch.AsOld = Touch.First; Touch.Last = 2; Touch.AsNew = Touch.Last; })(Touch || (exports.Touch = Touch = {})); class LinkedMap { constructor() { this[_a] = 'LinkedMap'; this._map = new Map(); this._head = undefined; this._tail = undefined; this._size = 0; this._state = 0; } clear() { this._map.clear(); this._head = undefined; this._tail = undefined; this._size = 0; this._state++; } isEmpty() { return !this._head && !this._tail; } get size() { return this._size; } get first() { return this._head?.value; } get last() { return this._tail?.value; } has(key) { return this._map.has(key); } get(key, touch = Touch.None) { const item = this._map.get(key); if (!item) { return undefined; } if (touch !== Touch.None) { this.touch(item, touch); } return item.value; } set(key, value, touch = Touch.None) { let item = this._map.get(key); if (item) { item.value = value; if (touch !== Touch.None) { this.touch(item, touch); } } else { item = { key, value, next: undefined, previous: undefined }; switch (touch) { case Touch.None: this.addItemLast(item); break; case Touch.First: this.addItemFirst(item); break; case Touch.Last: this.addItemLast(item); break; default: this.addItemLast(item); break; } this._map.set(key, item); this._size++; } return this; } delete(key) { return !!this.remove(key); } remove(key) { const item = this._map.get(key); if (!item) { return undefined; } this._map.delete(key); this.removeItem(item); this._size--; return item.value; } shift() { if (!this._head && !this._tail) { return undefined; } if (!this._head || !this._tail) { throw new Error('Invalid list'); } const item = this._head; this._map.delete(item.key); this.removeItem(item); this._size--; return item.value; } forEach(callbackfn, thisArg) { const state = this._state; let current = this._head; while (current) { if (thisArg) { callbackfn.bind(thisArg)(current.value, current.key, this); } else { callbackfn(current.value, current.key, this); } if (this._state !== state) { throw new Error(`LinkedMap got modified during iteration.`); } current = current.next; } } keys() { const state = this._state; let current = this._head; const iterator = { [Symbol.iterator]: () => { return iterator; }, next: () => { if (this._state !== state) { throw new Error(`LinkedMap got modified during iteration.`); } if (current) { const result = { value: current.key, done: false }; current = current.next; return result; } else { return { value: undefined, done: true }; } } }; return iterator; } values() { const state = this._state; let current = this._head; const iterator = { [Symbol.iterator]: () => { return iterator; }, next: () => { if (this._state !== state) { throw new Error(`LinkedMap got modified during iteration.`); } if (current) { const result = { value: current.value, done: false }; current = current.next; return result; } else { return { value: undefined, done: true }; } } }; return iterator; } entries() { const state = this._state; let current = this._head; const iterator = { [Symbol.iterator]: () => { return iterator; }, next: () => { if (this._state !== state) { throw new Error(`LinkedMap got modified during iteration.`); } if (current) { const result = { value: [current.key, current.value], done: false }; current = current.next; return result; } else { return { value: undefined, done: true }; } } }; return iterator; } [(_a = Symbol.toStringTag, Symbol.iterator)]() { return this.entries(); } trimOld(newSize) { if (newSize >= this.size) { return; } if (newSize === 0) { this.clear(); return; } let current = this._head; let currentSize = this.size; while (current && currentSize > newSize) { this._map.delete(current.key); current = current.next; currentSize--; } this._head = current; this._size = currentSize; if (current) { current.previous = undefined; } this._state++; } addItemFirst(item) { // First time Insert if (!this._head && !this._tail) { this._tail = item; } else if (!this._head) { throw new Error('Invalid list'); } else { item.next = this._head; this._head.previous = item; } this._head = item; this._state++; } addItemLast(item) { // First time Insert if (!this._head && !this._tail) { this._head = item; } else if (!this._tail) { throw new Error('Invalid list'); } else { item.previous = this._tail; this._tail.next = item; } this._tail = item; this._state++; } removeItem(item) { if (item === this._head && item === this._tail) { this._head = undefined; this._tail = undefined; } else if (item === this._head) { // This can only happened if size === 1 which is handle // by the case above. if (!item.next) { throw new Error('Invalid list'); } item.next.previous = undefined; this._head = item.next; } else if (item === this._tail) { // This can only happened if size === 1 which is handle // by the case above. if (!item.previous) { throw new Error('Invalid list'); } item.previous.next = undefined; this._tail = item.previous; } else { const next = item.next; const previous = item.previous; if (!next || !previous) { throw new Error('Invalid list'); } next.previous = previous; previous.next = next; } item.next = undefined; item.previous = undefined; this._state++; } touch(item, touch) { if (!this._head || !this._tail) { throw new Error('Invalid list'); } if ((touch !== Touch.First && touch !== Touch.Last)) { return; } if (touch === Touch.First) { if (item === this._head) { return; } const next = item.next; const previous = item.previous; // Unlink the item if (item === this._tail) { // previous must be defined since item was not head but is tail // So there are more than on item in the map previous.next = undefined; this._tail = previous; } else { // Both next and previous are not undefined since item was neither head nor tail. next.previous = previous; previous.next = next; } // Insert the node at head item.previous = undefined; item.next = this._head; this._head.previous = item; this._head = item; this._state++; } else if (touch === Touch.Last) { if (item === this._tail) { return; } const next = item.next; const previous = item.previous; // Unlink the item. if (item === this._head) { // next must be defined since item was not tail but is head // So there are more than on item in the map next.previous = undefined; this._head = next; } else { // Both next and previous are not undefined since item was neither head nor tail. next.previous = previous; previous.next = next; } item.next = undefined; item.previous = this._tail; this._tail.next = item; this._tail = item; this._state++; } } toJSON() { const data = []; this.forEach((value, key) => { data.push([key, value]); }); return data; } fromJSON(data) { this.clear(); for (const [key, value] of data) { this.set(key, value); } } } exports.LinkedMap = LinkedMap; class LRUCache extends LinkedMap { constructor(limit, ratio = 1) { super(); this._limit = limit; this._ratio = Math.min(Math.max(0, ratio), 1); } get limit() { return this._limit; } set limit(limit) { this._limit = limit; this.checkTrim(); } get ratio() { return this._ratio; } set ratio(ratio) { this._ratio = Math.min(Math.max(0, ratio), 1); this.checkTrim(); } get(key, touch = Touch.AsNew) { return super.get(key, touch); } peek(key) { return super.get(key, Touch.None); } set(key, value) { super.set(key, value, Touch.Last); this.checkTrim(); return this; } checkTrim() { if (this.size > this._limit) { this.trimOld(Math.round(this._limit * this._ratio)); } } } exports.LRUCache = LRUCache;