/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict * @format * @oncall recoil */ 'use strict'; const nullthrows = require('recoil-shared/util/Recoil_nullthrows'); type CacheNode = { key: K, value: V, left: ?CacheNode, right: ?CacheNode, }; type Options = { maxSize: number, mapKey?: K => mixed, }; class LRUCache { _maxSize: number; _size: number; _head: ?CacheNode; _tail: ?CacheNode; _map: Map>; _keyMapper: K => mixed; constructor(options: Options) { this._maxSize = options.maxSize; this._size = 0; this._head = null; this._tail = null; this._map = new Map>(); this._keyMapper = options.mapKey ?? (v => v); } head(): ?CacheNode { return this._head; } tail(): ?CacheNode { return this._tail; } size(): number { return this._size; } maxSize(): number { return this._maxSize; } has(key: K): boolean { return this._map.has(this._keyMapper(key)); } get(key: K): ?V { const mappedKey = this._keyMapper(key); const node = this._map.get(mappedKey); if (!node) { return undefined; } this.set(key, node.value); return node.value; } set(key: K, val: V): void { const mappedKey = this._keyMapper(key); const existingNode = this._map.get(mappedKey); if (existingNode) { this.delete(key); } const head = this.head(); const node = { key, right: head, left: null, value: val, }; if (head) { head.left = node; } else { this._tail = node; } this._map.set(mappedKey, node); this._head = node; this._size++; this._maybeDeleteLRU(); } _maybeDeleteLRU() { if (this.size() > this.maxSize()) { this.deleteLru(); } } deleteLru(): void { const tail = this.tail(); if (tail) { this.delete(tail.key); } } delete(key: K): void { const mappedKey = this._keyMapper(key); if (!this._size || !this._map.has(mappedKey)) { return; } const node = nullthrows(this._map.get(mappedKey)); const right = node.right; const left = node.left; if (right) { right.left = node.left; } if (left) { left.right = node.right; } if (node === this.head()) { this._head = right; } if (node === this.tail()) { this._tail = left; } this._map.delete(mappedKey); this._size--; } clear(): void { this._size = 0; this._head = null; this._tail = null; this._map = new Map>(); } } module.exports = {LRUCache};