99 lines
3.0 KiB
JavaScript
99 lines
3.0 KiB
JavaScript
import { ObjectTree, Tree } from "../internal.js";
|
|
|
|
/**
|
|
* Caches values from a source tree in a second cache tree. Cache source tree
|
|
* keys in memory.
|
|
*
|
|
* If no second tree is supplied, an in-memory value cache is used.
|
|
*
|
|
* An optional third filter tree can be supplied. If a filter tree is supplied,
|
|
* only values for keys that match the filter will be cached.
|
|
*
|
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
*
|
|
* @param {Treelike} sourceTreelike
|
|
* @param {AsyncMutableTree} [cacheTreelike]
|
|
* @param {Treelike} [filterTreelike]
|
|
* @returns {AsyncTree & { description: string }}
|
|
*/
|
|
export default function treeCache(
|
|
sourceTreelike,
|
|
cacheTreelike,
|
|
filterTreelike
|
|
) {
|
|
if (!sourceTreelike) {
|
|
const error = new TypeError(`cache: The source tree isn't defined.`);
|
|
/** @type {any} */ (error).position = 0;
|
|
throw error;
|
|
}
|
|
|
|
const source = Tree.from(sourceTreelike);
|
|
const filter = filterTreelike ? Tree.from(filterTreelike) : undefined;
|
|
|
|
/** @type {AsyncMutableTree} */
|
|
let cache;
|
|
if (cacheTreelike) {
|
|
// @ts-ignore
|
|
cache = Tree.from(cacheTreelike);
|
|
if (!Tree.isAsyncMutableTree(cache)) {
|
|
throw new Error("Cache tree must define a set() method.");
|
|
}
|
|
} else {
|
|
cache = new ObjectTree({});
|
|
}
|
|
|
|
let keys;
|
|
return {
|
|
description: "cache",
|
|
|
|
async get(key) {
|
|
// Check cache tree first.
|
|
let cacheValue = await cache.get(key);
|
|
if (cacheValue !== undefined && !Tree.isAsyncTree(cacheValue)) {
|
|
// Leaf node cache hit
|
|
return cacheValue;
|
|
}
|
|
|
|
// Cache miss or interior node cache hit.
|
|
let value = await source.get(key);
|
|
if (value !== undefined) {
|
|
// If a filter is defined, does the key match the filter?
|
|
const filterValue = filter ? await filter.get(key) : undefined;
|
|
const filterMatch = !filter || filterValue !== undefined;
|
|
if (filterMatch) {
|
|
if (Tree.isAsyncTree(value)) {
|
|
// Construct merged tree for a tree result.
|
|
if (cacheValue === undefined) {
|
|
// Construct new empty container in cache
|
|
await cache.set(key, {});
|
|
cacheValue = await cache.get(key);
|
|
if (!Tree.isAsyncTree(cacheValue)) {
|
|
// Coerce to tree and then save it back to the cache. This is
|
|
// necessary, e.g., if cache is an ObjectTree; we want the
|
|
// subtree to also be an ObjectTree, not a plain object.
|
|
cacheValue = Tree.from(cacheValue);
|
|
await cache.set(key, cacheValue);
|
|
}
|
|
}
|
|
value = treeCache(value, cacheValue, filterValue);
|
|
} else {
|
|
// Save in cache before returning.
|
|
await cache.set(key, value);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
return undefined;
|
|
},
|
|
|
|
async keys() {
|
|
keys ??= await source.keys();
|
|
return keys;
|
|
},
|
|
};
|
|
}
|