151 lines
3.7 KiB
JavaScript
151 lines
3.7 KiB
JavaScript
// 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
|
||
|
|
}
|
||
|
|
}
|