UNPKG

@loken/hierarchies

Version:

Library for working with hierarchies of identifiers and identifiable objects.

267 lines (266 loc) 11.3 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 { someToArray, isSomeItem } from "@loken/utilities"; import { traverseFullGraph } from "../traversal/graph-traverse.js"; import { traverseSequence } from "../traversal/sequence-traverse.js"; import { nodesToItems } from "./node-conversion.js"; import { flattenGraph } from "../traversal/graph-flatten.js"; import { flattenSequence } from "../traversal/sequence-flatten.js"; import { searchSequence, searchSequenceMany } from "../traversal/sequence-search.js"; import { searchGraph, searchGraphMany } from "../traversal/graph-search.js"; var _item, _parent, _children, _brand, _HCNode_instances, getRoots_fn; class HCNode { /** Create a node wrapping the `item`. */ constructor(item) { __privateAdd(this, _HCNode_instances); //#region backing fields __privateAdd(this, _item); __privateAdd(this, _parent); __privateAdd(this, _children); /** The brand is used to lock the node to a specific owner. */ __privateAdd(this, _brand); __privateSet(this, _item, item); } //#endregion //#region predicates /** A node is a "root" when there is no `parent`. */ get isRoot() { return __privateGet(this, _parent) === void 0; } /** A node is a "leaf" when there are no `children`. */ get isLeaf() { return __privateGet(this, _children) === void 0 || __privateGet(this, _children).size === 0; } /** A node is "internal" when it has `children`, meaning it's either "internal" or a "leaf". */ get isInternal() { return !this.isLeaf; } /** * A node is "linked" when it is neither a root nor a child. * * A node is "linked" when it has a parent or at least one child. */ get isLinked() { return !this.isRoot || !this.isLeaf; } /** Having a brand means that some other entity "owns" the node. */ get isBranded() { return __privateGet(this, _brand) !== void 0; } //#endregion //#region brands /** * When the brands of two nodes are compatible, they may be linked/attached * in a parent-child relationship. */ isBrandCompatible(other) { return __privateGet(this, _brand) === void 0 ? __privateGet(other, _brand) === void 0 : __privateGet(other, _brand) === void 0 ? !1 : __privateGet(this, _brand) === __privateGet(other, _brand); } /** * Adds the provided `brand` to the node, * providing an `DeBrand` delegate for removing/clearing the brand. */ brand(brand) { if (brand === void 0) throw new Error("The brand cannot be 'undefined'."); if (__privateGet(this, _brand) !== void 0) throw new Error("Must clear existing brand using the 'DeBrand' delegate before you can re-brand a node."); return __privateSet(this, _brand, brand), () => __privateSet(this, _brand, void 0); } //#endregion //#region links /** * Attach the provided `children`. */ attach(children) { const nodes = someToArray(children); if (nodes.length === 0) throw new Error("Must provide one or more 'children'."); if (!nodes.every((node) => node.isRoot)) throw new Error("Must all be without a 'parent' before attaching to another."); if (!nodes.every((node) => node.isBrandCompatible(this))) throw new Error("Must all have a compatible brand."); __privateGet(this, _children) ?? __privateSet(this, _children, /* @__PURE__ */ new Set()); for (const child of nodes) __privateGet(this, _children).add(child), __privateSet(child, _parent, this); return this; } /** Detach the provided `children`. */ detach(children) { const nodes = someToArray(children); if (nodes.length === 0) throw new Error("Must provide one or more 'children'."); if (this.isLeaf || !nodes.every((child) => __privateGet(this, _children).has(child))) throw new Error("Must all be 'children'."); if (nodes.some((child) => child.isBranded)) throw new Error("Must clear brand using the 'DeBrand' delegate before you can detach a branded node."); for (const node of nodes) __privateGet(this, _children).delete(node), __privateSet(node, _parent, void 0); return this.isLeaf && __privateSet(this, _children, void 0), this; } /** Detach the node from it's `parent`. */ detachSelf() { if (this.isRoot) throw new Error("Can't detach a root node as there's nothing to detach it from."); if (this.isBranded) throw new Error("Must clear brand using the 'DeBrand' delegate before you can detach a branded node."); if (__privateGet(this, _parent).isLeaf || !__privateGet(__privateGet(this, _parent), _children).has(this)) throw new Error("Invalid object state: It should not be possible for the node not to be a child of its parent!."); return __privateGet(__privateGet(this, _parent), _children).delete(this), __privateGet(this, _parent).isLeaf && __privateSet(__privateGet(this, _parent), _children, void 0), __privateSet(this, _parent, void 0), this; } /** * Dismantling a node means to cascade detach it. * We always cascade detach the nodes. * We may also cascade up the ancestry, in which case the node is detached, * and then the parent is dismantled, leading to the whole linked structure * ending up unlinked. * @param includeAncestry * Should we cascade through the ancestry (true) or only cascade through the nodes (false)? * No default value is because the caller should always make an active choice. */ dismantle(includeAncestry) { if (!this.isRoot && includeAncestry) { const parent = __privateGet(this, _parent); this.detachSelf(), parent.dismantle(!0); } for (const descendant of this.getDescendants(!1)) descendant.detachSelf(); return this; } //#endregion //#region accessors /** The item is the subject/content of the node. */ get item() { return __privateGet(this, _item); } /** Get the parent node, if any. */ getParent() { return __privateGet(this, _parent); } /** Get the parent item, if any. */ getParentItem() { var _a; return (_a = __privateGet(this, _parent)) == null ? void 0 : _a.item; } /** Get all child nodes. */ getChildren() { return __privateGet(this, _children) ? [...__privateGet(this, _children)] : []; } /** Get all child items. */ getChildItems() { return __privateGet(this, _children) ? nodesToItems(__privateGet(this, _children).values()) : []; } /** Get ancestor nodes by traversing according to the options. */ getAncestors(includeSelf = !1) { return flattenSequence({ first: includeSelf ? this : __privateGet(this, _parent), next: (node) => node == null ? void 0 : node.getParent() }); } /** Get ancestor items by traversing according to the options. */ getAncestorItems(includeSelf = !1) { return this.getAncestors(includeSelf).map((node) => node.item); } /** Get descendant nodes by traversing according to the options. */ getDescendants(includeSelf = !1, type = "breadth-first") { return flattenGraph({ roots: __privateMethod(this, _HCNode_instances, getRoots_fn).call(this, includeSelf), next: (node) => __privateGet(node, _children), type }); } /** Get descendant items by traversing according to the options. */ getDescendantItems(includeSelf = !1, type = "breadth-first") { return this.getDescendants(includeSelf, type).map((node) => node.item); } /** Find the first ancestor node matching the `search`. */ findAncestor(search, includeSelf = !1) { return searchSequence({ first: includeSelf ? this : __privateGet(this, _parent), next: (node) => node.getParent(), search }); } /** Find the ancestor nodes matching the `search`. */ findAncestors(search, includeSelf = !1) { return searchSequenceMany({ first: includeSelf ? this : __privateGet(this, _parent), next: (node) => node.getParent(), search }); } /** Find the first descendant node matching the `search`. */ findDescendant(search, includeSelf = !1, type = "breadth-first") { return searchGraph({ roots: __privateMethod(this, _HCNode_instances, getRoots_fn).call(this, includeSelf), next: (node) => __privateGet(node, _children), search, type }); } /** Find the descendant nodes matching the `search`. */ findDescendants(search, includeSelf = !1, type = "breadth-first") { return searchGraphMany({ roots: __privateMethod(this, _HCNode_instances, getRoots_fn).call(this, includeSelf), next: (node) => __privateGet(node, _children), search, type }); } /** Does an ancestor node matching the `search` exist? */ hasAncestor(search, includeSelf = !1) { return this.findAncestor(search, includeSelf) !== void 0; } /** Does a descendant node matching the `search` exist? */ hasDescendant(search, includeSelf = !1, type = "breadth-first") { return this.findDescendant(search, includeSelf, type) !== void 0; } //#endregion //#region traversal /** Generate a sequence of ancestor nodes by traversing according to the options. */ traverseAncestors(includeSelf = !1) { return traverseSequence({ first: includeSelf ? this : __privateGet(this, _parent), next: (node) => node == null ? void 0 : node.getParent() }); } /** Generate a sequence of descendant nodes by traversing according to the options. */ traverseDescendants(includeSelf = !1, type = "breadth-first") { return traverseFullGraph({ roots: __privateMethod(this, _HCNode_instances, getRoots_fn).call(this, includeSelf), next: (node) => __privateGet(node, _children), type }); } /** * Get nodes to use as roots. */ static getRoots(roots, includeSelf = !1) { if (includeSelf) return roots; if (isSomeItem(roots)) return __privateGet(roots, _children) ? [...__privateGet(roots, _children)] : []; const children = []; for (const root of roots) __privateGet(root, _children) && children.push(...__privateGet(root, _children)); return children; } //#endregion } _item = new WeakMap(), _parent = new WeakMap(), _children = new WeakMap(), _brand = new WeakMap(), _HCNode_instances = new WeakSet(), //#endregion //#region helpers /** * Get nodes to use as roots. * * Note: This must be a private method as it could otherwise be exploited to modify the `#children`. */ getRoots_fn = function(includeSelf = !1) { return includeSelf ? this : __privateGet(this, _children) ?? []; }; export { HCNode }; //# sourceMappingURL=node.js.map