UNPKG

@loken/hierarchies

Version:

Library for working with hierarchies of identifiers and identifiable objects.

416 lines (415 loc) 17.2 kB
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