Files

151 lines
3.7 KiB
JavaScript
Raw Permalink Normal View History

// 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
}
}