@loken/hierarchies
Version:
Library for working with hierarchies of identifiers and identifiable objects.
416 lines (415 loc) • 17.2 kB
JavaScript
var __typeError = (msg) => {
throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)), __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value), __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
import { mapArgs, someToIterable, someToArray, MultiMap } from "@loken/utilities";
import { nodesToItems, nodesToIds } from "../nodes/node-conversion.js";
import { Nodes } from "../nodes/nodes.js";
import { ChildMap } from "../utilities/child-map.js";
import { Hierarchies } from "./hierarchies.js";
var _roots, _nodes, _debrand, _identify, _Hierarchy_instances, get_fn, addNodes_fn;
class Hierarchy {
/** Create a hierarchy using the provided `identify` function. */
constructor(identify) {
__privateAdd(this, _Hierarchy_instances);
//#region backing fields
__privateAdd(this, _roots, /* @__PURE__ */ new Map());
__privateAdd(this, _nodes, /* @__PURE__ */ new Map());
__privateAdd(this, _debrand, /* @__PURE__ */ new Map());
__privateAdd(this, _identify);
__privateSet(this, _identify, identify);
}
//#endregion
//#region accessors
/** Get a shallow clone of the roots. */
get roots() {
return [...__privateGet(this, _roots).values()];
}
/** Get a shallow clone of the root items. */
get rootItems() {
return this.roots.map((r) => r.item);
}
/** Get a shallow clone of the root IDs. */
get rootIds() {
return this.roots.map((r) => __privateGet(this, _identify).call(this, r.item));
}
/** Get a shallow clone of all nodes. */
get nodes() {
return [...__privateGet(this, _nodes).values()];
}
/** Get a shallow clone of all node items. */
get nodeItems() {
return this.nodes.map((n) => n.item);
}
/** Get a shallow clone of all node IDs. */
get nodeIds() {
return this.nodes.map((n) => __privateGet(this, _identify).call(this, n.item));
}
/** Means of getting an ID for an `item`. */
get identify() {
return __privateGet(this, _identify);
}
/** Map the `ids` to a boolean signifying their presence. */
has(...ids) {
return mapArgs(ids, (id) => __privateGet(this, _nodes).has(id), !0, !1);
}
/** Is every `Id` in the `ids` present? */
hasEvery(ids) {
for (const id of someToIterable(ids))
if (!__privateGet(this, _nodes).has(id))
return !1;
return !0;
}
/** Is some `Id` in the `ids` present? */
hasSome(ids) {
for (const id of someToIterable(ids))
if (__privateGet(this, _nodes).has(id))
return !0;
return !1;
}
/**
* Get node or nodes by their `Id`.
* @param ids The IDs of nodes to retrieve.
* @returns A single node when you pass a single ID and a fixed length tuple of nodes when you pass multiple IDs.
* @throws The 'id' must be a hierarchy member.
* @throws Must provide at least one argument.
*/
get(...ids) {
return mapArgs(ids, (id) => __privateMethod(this, _Hierarchy_instances, get_fn).call(this, id), !0, !1);
}
/**
* Get nodes by their `Id`s.
* @param ids The IDs of nodes to retrieve.
* @returns An array of nodes.
*/
getSome(ids) {
const nodes = [];
for (const id of someToIterable(ids)) {
const node = __privateGet(this, _nodes).get(id);
node && nodes.push(node);
}
return nodes;
}
/**
* Get item or items by `Id`.
* @param ids The IDs of items to retrieve.
* @throws The 'id' must be a hierarchy member.
* @throws Must provide at least one argument.
* @returns A single item when you pass a single ID and a fixed length tuple of items when you pass multiple IDs.
*/
getItems(...ids) {
return mapArgs(ids, (id) => __privateMethod(this, _Hierarchy_instances, get_fn).call(this, id).item, !0, !1);
}
/**
* Get item or items by `Id`.
* @param ids The IDs of items to retrieve.
* @returns An array of items.
*/
getSomeItems(ids) {
return this.getSome(ids).map((node) => node.item);
}
//#endregion
//#region links
/**
* Attach the provided `roots`.
* @param roots Nodes to attach.
*/
attachRoot(roots) {
const nodes = someToArray(roots);
if (!nodes.every((n) => n.isRoot))
throw new Error("The 'roots' all be roots!");
return __privateMethod(this, _Hierarchy_instances, addNodes_fn).call(this, nodes, !0), this;
}
/**
* Attach the provided `children` to the node of the provided `parentId`.
* @param parentId The ID of the node to attach to.
* @param children Nodes to attach.
*/
attach(parentId, children) {
if (!__privateGet(this, _nodes).has(parentId))
throw new Error("The 'parentId' must be a hierarchy member.");
return __privateMethod(this, _Hierarchy_instances, addNodes_fn).call(this, children), __privateGet(this, _nodes).get(parentId).attach(children), this;
}
/**
* Detach the provided `nodes`.
* @param nodes Nodes to detach.
*/
detach(nodes) {
for (const node of Nodes.getDescendants(nodes, !0)) {
const id = __privateGet(this, _identify).call(this, node.item);
__privateGet(this, _debrand).get(id)(), __privateGet(this, _debrand).delete(id), __privateGet(this, _nodes).delete(id), __privateGet(this, _roots).delete(id);
}
for (const node of someToIterable(nodes))
node.isRoot || node.detachSelf();
return this;
}
//#endregion
//#region traversal
/**
* Get the chain of ancestor nodes starting with the node for the item matching the `id`.
*/
getAncestors(ids, includeSelf = !1) {
const nodes = this.getSome(ids);
return nodes ? Nodes.getAncestors(nodes, includeSelf) : [];
}
/**
* Get the items from the chain of ancestor nodes starting with the node for the item matching the `id`.
*/
getAncestorItems(ids, includeSelf = !1) {
return nodesToItems(this.getAncestors(ids, includeSelf));
}
/**
* Get the IDs from the chain of ancestor nodes starting with the node for the item matching the `id`.
*/
getAncestorIds(ids, includeSelf = !1) {
return nodesToIds(this.getAncestors(ids, includeSelf), __privateGet(this, _identify));
}
/**
* Get the entries from the chain of ancestor nodes starting with the node for the item matching the `id`.
*/
getAncestorEntries(id, includeSelf = !1) {
return this.getAncestors(id, includeSelf).map((node) => [__privateGet(this, _identify).call(this, node.item), node.item, node]);
}
/**
* Get the chain of descendant nodes starting with the nodes for the items matching the `ids`.
*/
getDescendants(ids, includeSelf = !1) {
const roots = this.getSome(ids);
return roots.length === 0 ? [] : Nodes.getDescendants(roots, includeSelf);
}
/**
* Get the items from the chain of descendant nodes starting with the nodes for the items matching the `ids`.
*/
getDescendantItems(ids, includeSelf = !1) {
return nodesToItems(this.getDescendants(ids, includeSelf));
}
/**
* Get the IDs from the chain of descendant nodes starting with the nodes for the items matching the `ids`.
*/
getDescendantIds(ids, includeSelf = !1) {
return nodesToIds(this.getDescendants(ids, includeSelf), __privateGet(this, _identify));
}
/**
* Get the entries from the chain of descendant nodes starting with the nodes for the items matching the `ids`.
*/
getDescendantEntries(ids, includeSelf = !1) {
return this.getDescendants(ids, includeSelf).map((node) => [__privateGet(this, _identify).call(this, node.item), node.item, node]);
}
/**
* Get the chain of descendant nodes starting with the hierarchy `roots`.
*/
getAllDescendants(includeSelf = !1) {
return Nodes.getDescendants(this.roots, includeSelf);
}
/**
* Get the items from the chain of descendant nodes starting with the hierarchy `roots`.
*/
getAllDescendantItems(includeSelf = !1) {
return nodesToItems(Nodes.getDescendants(this.roots, includeSelf));
}
/**
* Get the IDs from the chain of descendant nodes starting with the hierarchy `roots`.
*/
getAllDescendantIds(includeSelf = !1) {
return nodesToIds(Nodes.getDescendants(this.roots, includeSelf), __privateGet(this, _identify));
}
/**
* Get the entries from the chain of descendant nodes starting with the hierarchy `roots`.
*/
getAllDescendantEntries(includeSelf = !1) {
return Nodes.getDescendants(this.roots, includeSelf).map((node) => [__privateGet(this, _identify).call(this, node.item), node.item, node]);
}
//#endregion
//#region search
/**
* Find nodes matching a list of `Id`s or a `HCNode<Item>` predicate.
*/
find(search) {
if (typeof search == "function") {
const result = [];
for (const node of __privateGet(this, _nodes).values())
search(node) && result.push(node);
return result;
}
return this.getSome(search);
}
/**
* Find nodes matching a list of `Id`s or a `HCNode<Item>` predicate.
*/
findItems(search) {
return this.find(search).map((node) => node.item);
}
/**
* Find `Id`s matching a list of `Id`s or a `HCNode<Item>` predicate.
*/
findIds(search) {
const result = [];
if (typeof search == "function")
for (const [id, node] of __privateGet(this, _nodes))
search(node) && result.push(id);
else
for (const id of someToIterable(search))
__privateGet(this, _nodes).has(id) && result.push(id);
return result;
}
/**
* Find entries matching a list of `Id`s or a `HCNode<Item>` predicate.
*/
findEntries(search) {
const result = [];
if (typeof search == "function")
for (const node of __privateGet(this, _nodes).values())
search(node) && result.push([__privateGet(this, _identify).call(this, node.item), node.item, node]);
else
for (const id of someToIterable(search)) {
const node = __privateGet(this, _nodes).get(id);
node && result.push([id, node.item, node]);
}
return result;
}
/** Find the common ancestor node which is the closest to the `ids`. */
findCommonAncestor(ids, includeSelf = !1) {
const nodes = this.getSome(ids);
return Nodes.findCommonAncestor(nodes, includeSelf);
}
/** Find the ancestor nodes common to the `ids`. */
findCommonAncestors(ids, includeSelf = !1) {
const nodes = this.getSome(ids);
return Nodes.findCommonAncestors(nodes, includeSelf);
}
/**
* Find a node matching the `search` which is an ancestor of a node with one of the `ids`.
*/
findAncestor(ids, search, includeSelf = !1) {
const roots = this.getSome(ids);
return Nodes.findAncestor(roots, this.normalizeSearch(search), includeSelf);
}
/**
* Find a node matching the `search` which is an ancestor of a node with one of the `ids`.
*/
findAncestors(ids, search, includeSelf = !1) {
const roots = this.getSome(ids);
return Nodes.findAncestors(roots, this.normalizeSearch(search), includeSelf);
}
/**
* Find a node matching the `search` which is an descendant of a node with one of the `ids`.
*/
findDescendant(ids, search, includeSelf = !1) {
const roots = this.getSome(ids);
return Nodes.findDescendant(roots, this.normalizeSearch(search), includeSelf);
}
/**
* Find nodes matching the `search` which are descendants of a node with one of the `ids`.
*/
findDescendants(ids, search, includeSelf = !1) {
const roots = this.getSome(ids);
return Nodes.findDescendants(roots, this.normalizeSearch(search), includeSelf);
}
/**
* Does a node with one of the `ids` have an ancestor node matching the `search`?
*/
hasAncestor(ids, search, includeSelf = !1) {
const roots = this.getSome(ids);
return Nodes.hasAncestor(roots, this.normalizeSearch(search), includeSelf);
}
/**
* Does a node with one of the `ids` have a descendant node matching the `search`?
*/
hasDescendant(ids, search, includeSelf = !1) {
const roots = this.getSome(ids);
return Nodes.hasDescendant(roots, this.normalizeSearch(search), includeSelf);
}
/**
* Create a new `Hierarchy` from matching items.
*
* @param search A list of `Id`s or a `HCNode<Item>` predicate.
* @param include Optional facets to include: The nodes that are `matches`, their `ancestors` and/or their `descendants` in the result.
* - When not specified you are getting all three facets as a default.
* - When the options object is specified you must opt in to the facets you want and must enable at least one.
* @returns A new `Hierarchy<Item, Id>` with new nodes wrapping the same `Item`s as in the searched hierarchy pruned to fit the search and `include` facets.
* @throws If you provide `include` options but enable no facets.
*/
search(search, include) {
if (include ?? (include = {
matches: !0,
ancestors: !0,
descendants: !0
}), !(include.matches || include.ancestors || include.descendants))
throw new Error("Must enable at least one facet to 'include'.");
const childMap = new MultiMap(), items = /* @__PURE__ */ new Map();
for (const [id, item, node] of this.findEntries(search)) {
if (include.ancestors) {
const ancestorIds = [];
for (const [ancestorId, ancestorItem] of this.getAncestorEntries(id, include.matches))
ancestorIds.push(ancestorId), items.has(ancestorId) || items.set(ancestorId, ancestorItem);
ChildMap.addAncestors(ancestorIds, childMap);
}
if (include.descendants) {
for (const [descendantId, descendantItem, descendantNode] of this.getDescendantEntries(id, include.matches))
if (items.has(descendantId) || (items.set(descendantId, descendantItem), descendantNode.isLeaf && childMap.getOrAdd(descendantId)), !descendantNode.isLeaf)
for (const childNode of descendantNode.getChildren()) {
const childItem = childNode.item, childId = __privateGet(this, _identify).call(this, childItem);
childMap.add(descendantId, childId), items.set(childId, childItem);
}
}
if (include.matches && !include.ancestors && !include.descendants) {
let addedToChildMap = !1;
if (!node.isRoot) {
const parentId = __privateGet(this, _identify).call(this, node.getParentItem());
items.has(parentId) && (childMap.add(parentId, id), addedToChildMap = !0);
}
if (!node.isLeaf) {
const includedChildIds = node.getChildItems().map(__privateGet(this, _identify)).filter((id2) => items.has(id2));
includedChildIds.length && (childMap.add(id, includedChildIds), addedToChildMap = !0);
}
addedToChildMap || childMap.getOrAdd(id), items.set(id, item);
}
}
return Hierarchies.createWithItems({
items: [...items.values()],
identify: __privateGet(this, _identify),
spec: childMap
});
}
normalizeSearch(search) {
return typeof search == "function" ? search : Array.isArray(search) ? (node) => search.includes(__privateGet(this, _identify).call(this, node.item)) : search instanceof Set ? (node) => search.has(__privateGet(this, _identify).call(this, node.item)) : (node) => __privateGet(this, _identify).call(this, node.item) === search;
}
//#endregion
//#region MultiMaps
/** Create a map of ids to child-ids by traversing the `hierarchy`. */
toChildMap() {
return Nodes.toChildMap(this.roots, __privateGet(this, _identify));
}
/** Create a map of ids to descendant-ids by traversing the `hierarchy`. */
toDescendantMap() {
return Nodes.toDescendantMap(this.roots, __privateGet(this, _identify));
}
/** Create a map of ids to ancestor-ids by traversing the `hierarchy`. */
toAncestorMap() {
return Nodes.toAncestorMap(this.roots, __privateGet(this, _identify));
}
/** Create a list of relations by traversing the graph of the `hierarchy`. */
toRelations() {
return Nodes.toRelations(this.roots, __privateGet(this, _identify));
}
//#endregion
}
_roots = new WeakMap(), _nodes = new WeakMap(), _debrand = new WeakMap(), _identify = new WeakMap(), _Hierarchy_instances = new WeakSet(), get_fn = function(id) {
const node = __privateGet(this, _nodes).get(id);
if (node === void 0)
throw new Error("The 'id' must be a hierarchy member.");
return node;
}, addNodes_fn = function(nodes, asRoot = !1) {
for (const node of Nodes.getDescendants(nodes, !0)) {
const id = __privateGet(this, _identify).call(this, node.item);
__privateGet(this, _debrand).set(id, node.brand(this)), __privateGet(this, _nodes).set(id, node);
}
if (asRoot)
for (const node of someToIterable(nodes))
__privateGet(this, _roots).set(__privateGet(this, _identify).call(this, node.item), node);
};
export {
Hierarchy
};
//# sourceMappingURL=hierarchy.js.map