@dialog-db/query
Version:
Datalog query engine inspired by Datomic
123 lines (107 loc) • 2.74 kB
JavaScript
import * as API from '../api.js'
import * as Constant from '../constant.js'
/**
* Generates a cache key from the selector components
* @param {API.FactsSelector} selector
* @returns {string}
*/
export const identify = ({ the, of, is }) =>
JSON.stringify({
the: the === undefined ? undefined : Constant.toString(the),
of: of === undefined ? undefined : Constant.toString(of),
is: is === undefined ? undefined : Constant.toString(is),
})
/**
* @param {API.Querier} source - The underlying data source
* @param {object} options
* @param {number} [options.capacity] - Maximum number of individual facts to store in cache
*/
export const create = (source, { capacity = 10_000 } = {}) =>
new LRUCache(source, capacity)
/**
* Implements a Least Recently Used (LRU) cache for facts with a capacity limit
* based on the total number of individual facts cached.
*/
class LRUCache {
#size
/**
* @param {API.Querier} source - The underlying data source
* @param {number} capacity - Maximum number of individual facts to store in cache
*/
constructor(source, capacity) {
this.source = source
this.capacity = capacity
/** @type {Map<string, API.Datum[]>} */
this.cache = new Map()
/** @type {number} */
this.#size = 0
}
/**
* Updates the LRU order by removing and re-adding the key
* @param {string} key
*/
touch(key) {
const value = this.cache.get(key)
if (value) {
this.cache.delete(key)
this.cache.set(key, value)
}
}
/**
* Evicts entries until cache is under capacity
*/
evict() {
for (const [key, facts] of this.cache) {
if (this.#size <= this.capacity) {
break
}
this.#size -= facts.length
this.cache.delete(key)
}
}
/**
* @param {API.FactsSelector} selector
*/
*select(selector) {
const key = identify(selector)
// Check if we have it in cache
const cached = this.cache.get(key)
if (cached) {
this.touch(key)
return cached
}
// Fetch from source
const facts = yield* this.source.select(selector)
// Skip caching if the result set is larger than our total capacity
if (facts.length > this.capacity) {
return facts
}
// Add to cache
this.cache.set(key, facts)
this.#size += facts.length
// Evict if we're over capacity
if (this.#size > this.capacity) {
this.evict()
}
return facts
}
/**
* Clears the cache
*/
clear() {
this.cache.clear()
this.#size = 0
}
/**
* Returns current number of cached facts
*/
size() {
return this.#size
}
/**
* Returns the number of cached queries
*/
get count() {
return this.cache.size
}
}