// Component instance cache for state.cache(). // LRU ported from nanolru 1.0.0 (MIT) — https://github.com/s3ththompson/nanolru // Cache wrapper ported from choo 7.1.0 component/cache.js (MIT) import { ok, equal } from './assert.js' export class LRU { constructor (opts) { if (typeof opts === 'number') opts = { max: opts } if (!opts) opts = {} this.cache = {} this.head = this.tail = null this.length = 0 this.max = opts.max || 1000 } get keys () { return Object.keys(this.cache) } clear () { this.cache = {} this.head = this.tail = null this.length = 0 } remove (key) { if (typeof key !== 'string') key = '' + key if (!Object.hasOwn(this.cache, key)) return const element = this.cache[key] delete this.cache[key] this._unlink(key, element.prev, element.next) return element.value } _unlink (key, prev, next) { this.length-- if (this.length === 0) { this.head = this.tail = null } else { if (this.head === key) { this.head = prev this.cache[this.head].next = null } else if (this.tail === key) { this.tail = next this.cache[this.tail].prev = null } else { this.cache[prev].next = next this.cache[next].prev = prev } } } peek (key) { if (!Object.hasOwn(this.cache, key)) return return this.cache[key].value } set (key, value) { if (typeof key !== 'string') key = '' + key let element if (Object.hasOwn(this.cache, key)) { element = this.cache[key] element.value = value // If it's already the head, there's nothing more to do: if (key === this.head) return value this._unlink(key, element.prev, element.next) } else { element = { value, next: null, prev: null } this.cache[key] = element // Eviction is only possible if the key didn't already exist: if (this.length === this.max) this.evict() } this.length++ element.next = null element.prev = this.head if (this.head) this.cache[this.head].next = key this.head = key if (!this.tail) this.tail = key return value } get (key) { if (typeof key !== 'string') key = '' + key if (!Object.hasOwn(this.cache, key)) return const element = this.cache[key] if (this.head !== key) { if (key === this.tail) { this.tail = element.next this.cache[this.tail].prev = null } else { // Set prev.next -> element.next: this.cache[element.prev].next = element.next } // Set element.next.prev -> element.prev: this.cache[element.next].prev = element.prev // Element is the new head this.cache[this.head].next = key element.prev = this.head element.next = null this.head = key } return element.value } evict () { if (!this.tail) return this.remove(this.tail) } } export default class ComponentCache { constructor (state, emit, lru) { equal(typeof state, 'object', 'ComponentCache: state should be type object') equal(typeof emit, 'function', 'ComponentCache: emit should be type function') if (typeof lru === 'number') this.cache = new LRU(lru) else this.cache = lru || new LRU(100) this.state = state this.emit = emit } // Get & create component instances. render (Component, id, ...args) { equal(typeof Component, 'function', 'ComponentCache.render: Component should be type function') ok(typeof id === 'string' || typeof id === 'number', 'ComponentCache.render: id should be type string or type number') let el = this.cache.get(id) if (!el) { el = new Component(id, this.state, this.emit, ...args) this.cache.set(id, el) } return el } }