npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tree-surgeon

v0.2.0

Published

Tools for editing tree structures using a relational model

Downloads

15

Readme

node-tree-surgeon

Build Status Coverage Status

Tools for editing tree structures using a relational model.

General purpose:

Trees are represented internally with two sets: (relational structure)

  • Relations: [{"Parent":id, "Child":id, "Kind":any, ...}, ...]
  • Nodes: [{... your data ... }, ...]

Functions given to split a POJO into this structure, and merge the structure into a POJO.

Names of object tree parts, as used below:

{ // this object is the parent, it has one property "I" = "am parent"

    "I" : "am parent", // properties (whose values are not objects) remain
                       //   on the containing object.

    "Kind" : // properties (whose values are objects) become relationships.
             // The key becomes the kind of the relation.
    {
        // this object is the child
        "Property" : "Value"
    }
}

Properties with array values are treated one of two ways:

{
    "JustAProperty" : ["hello", 1,2,3], // first element is NOT an object.
                                        // Entire array is a single value,
                                        //   one of the parent's properties
    "OneToMany" : [
        {"child":1}, // First element is an object. All elements are
                     //   considered children of the parent. Kind is 'OneToMany'
        {"child":2}  // There is no way to express many-to-one, and
                     //   putting this in the relational structure is not supported.
    ]
}

Operations on POJO structure:

Assuming var tree = require('tree-surgeon');

Input

  • [x] decompose -- turn a normal js object tree into the relational structure

    `tree.decompose(obj, excludedKinds, relationDecorator, useEmptyRelations);`
    - `obj` The object to be decomposed for relational operations. This should be a simple object, as returned by `JSON.parse`
    - `excludedKinds` An array of property names. These properties and their sub-trees will not be decomposed
    - `relationDecorator` A function to read node as they are decomposed and inject data into the relations table. This data can be used in subsequent operations.
    - `useEmptyRelations` Bool, default false. If `true`, empty arrays will be treated as object nodes with no children.
      
    Returns a relational structure.

Operations on the relational structure:

Assuming

    var tree = require('tree-surgeon');
    var relational = tree.decompose(my_object);

Output

  • [x] compose -- convert a relational structure back into a plain object. Any manipulations of the relational structure will take effect.

    `relational.compose()` or `tree.compose(relational)`
      
    This is the fastest output function.
  • [x] render -- pass each node through a function, and each kind name through a function and compose tree from the results. Manipulations of the relational structure will take effect, and both property names and object contents can be manipulated during output.

    `relational.render(renderNodeFunc, renderKindFunc)`
    - `renderNodeFunc` function that takes (node, path, id) and returns the rendered object contents
    - `renderKindFunc` function that takes (kind, path) and returns the output property name
  • [x] harvest -- return an object of composed sub-trees by kind, keyed by a parent node value

    `relational.harvest(kind, idSelector)`
    - `kind` the target property names to extract
    - `idSelector` a function that takes the object nodes and returns a unique new property name
  • [x] gather -- return an array of sub-trees

    • [x] gatherByKind -- subtrees selected by property name

      `relational.gatherByKind(kind)`
      - `kind` the target property names to extract
    • [x] gatherByNode -- subtrees selected by a function

      `relational.gatherByNode(selector)`
      - `selector` function that takes a node and returns `bool`. The subtrees of node that result in `true` will be returned.

Navigation

  • [x] parentIdOf -- get parent ID from child ID, or null if not found

    `relational.parentIdOf(childId)`
    - `childId` the ID of a node
    returns the parent ID, or `null` if this was the root node
  • [x] getChildrenOf -- get an array of node IDs for the given parent ID

    `relational.getChildrenOf(parentId)`
    - `parentId` the ID of a node
    returns an array of all child nodes' IDs. Returns empty array if a leaf node was passed.
  • [x] getChildrenByKindOf - get an array of child node IDs for a given parent where the child is a specified type. Kind can be a string, or a where predicate on the relationship (an object with exact value matches)

    `relational.getChildrenByKindOf(parentId, kind)`
    - `parentId` the ID of a node
    - `kind` selector for the children to be included
       - string: pick children with a matching property name
       - object: pick children whose relation object matches properties on the selector object
       - function: given the relation object, pick where the function returns `true`
  • [x] getNode -- return the node data for a given ID

    `relational.getNode(id)`
    - `id` the ID of a node
  • [x] getPathOf -- give the Kind path for a given node ID

    `relational.getPathOf(nodeId)`
    - `nodeId` the ID of a node
  • [x] forEachByKind -- given a Kind execute the supplied function for all nodes of that kind. Kind can be a string, or a where predicate

    `relational.forEachByKind(kind, actionFunc)`
    - `kind` selector for the children to be included
       - string: pick children with a matching property name
       - object: pick children whose relation object matches properties on the selector object
       - function: given the relation object, pick where the function returns `true`
    - `actionFunc` function of `(node, id)` to execute for each matching node. Any return is ignored. Any changes made to the node data is retained.

Manipulation

  • [x] Normalise -- removes any relationships or nodes that are not reachable from the root, but keeps node and relation indexes consistent.

    `relational.normalise()`
  • [x] prune -- remove subtrees by relationship kind. Kind can be a string, or a where predicate on the relationship (an object with exact value matches)

    `relational.prune(kind)`
    - `kind` property name or relation match to remove.
    • [x] pruneAfter -- remove subtrees by relationship kind, but keep the immediate children. Kind can be a string, or a where predicate

      `relational.pruneAfter(kind)`
      - `kind` property name or relation match. The children of matches will be removed
    • [x] pruneAllBut -- remove subtrees that don't match a set of kinds. Supports only array of string kinds.

      `relational.pruneAllBut(kind)`
      - `kind` property name or relation match. All non-matching relations will be removed
  • [x] chop -- remove subtrees by data predicate

    `relational.chop(filterFunc)`
    - `filterFunc` function of `(node, id)`. If this returns a truthy value, node will be removed, else node will be kept
    • [x] chopAfter -- remove subtrees by data predicate, but keep the matched children

      `relational.chopAfter(filterFunc)`
      - `filterFunc` function of `(node, id)`. If this returns a truthy value, all children of the matched node will be removed
    • [x] chopByKind -- remove subtrees of a specified 'kind' by data predicate. Kind can be a string, or a where predicate

      `relational.chopByKind(kind, filterFunc)`
      - `kind` property name or relation match. Matched nodes will be included in the filter
      - `filterFunc` function of `(node, id)`. If this returns a truthy value, node will be removed, else node will be kept
    • [x] chopChildless -- remove nodes which have no children (ie. leaves) by data predicate

      `relational.chopChildless(filterFunc)`
      - `filterFunc` function of `(node, id)`. All leaf nodes will be passed to this function. If this returns a truthy value, node will be removed.
    • [x] chopNodesByIds -- remove nodes and their subtrees by their IDs

      `relational.chopNodesByIds(ids)`
      - `ids` an array of node ids. All these nodes and their subtrees will be removed.
  • MergeUp -- remove a relationship and one node by merging data from child to parent. Subtree remains

    • [x] mergeUpByKind -- select merge targets by relationship kind. Kind can be a string or a where predicate

      `relational.mergeUpByKind(kind)`
      - `kind` target property or relation match. Matching nodes will be merged into their parents
    • [x] mergeUpByNode -- select merge targets by applying a predicate to nodes

      `relational.mergeUpByNode(predFunc)`
      - `predFunc` function to select nodes. Matching nodes will be merged into their parents
  • MergeDown -- remove a relationship and one node by merging data from parent to child. Subtree remains

    • [x] mergeDownByKind -- select merge targets by relationship kind. Kind can be a string or a where predicate

      `relational.mergeDownByKind(kind)`
      - `kind` target property or relation match. Matching nodes' data will be copied into their parents, then the matching node removed.
    • [x] mergeDownByNode -- select merge targets by applying a predicate to nodes

      `relational.mergeUpByNode(predFunc)`
      - `predFunc` function to select nodes. Matching nodes' data will be copied into their parents, then the matching node removed.
  • Fuse -- remove a node by merging into it's parent and child (by supplied function). This is a generalisation of merge up/down.

    • [x] fuseByNode -- remove a node picked by a predicate on that node

      `relational.fuseByNode(nodeFunc, pickForParentFunc, pickForChildFunc)`
      - `nodeFunc` function to pick nodes to fuse
      - `pickForParentFunc` function that is given node data, and returns the data to copy into the parent node
      - `pickForChildFunc` function that is given node data, and returns the data to copy into the child node.
    • [x] fuseByKind -- remove a node picked by kind. Kind can be a string or a where predicate

      `relational.fuseByKind(kind, pickForParentFunc, pickForChildFunc)`
      - `kind` target property or relation match. Matching nodes will be removed, but sub-trees will remain
      - `pickForParentFunc` function that is given node data, and returns the data to copy into the parent node
      - `pickForChildFunc` function that is given node data, and returns the data to copy into the child node.
  • [x] flipRelationship -- given a parent kind, a child kind, and an equality function for children; swap parents⇔children, grouping children by equality. The new child kind can be a string or a where predicate, but the new parent kind can only be a string.

    `relational.flipRelationship(newChildKind, newParentKind, newParentHashFunc)`
    - `newChildKind` property name that is currently the parent node, and should be flipped to being a parent
    - `newParentKind` property name that is currently the child node, and should be flipped to being a child
    - `newParentHashFunc` function that takes a node and returns a comparable value (preferable a number)
  • [x] reverseByRelation -- make children into parents and parents into children. This is a more complex and powerful version of flipRelationship. Have a look in the test cases for examples.

  • [x] reduce -- reduce objects to a single value from inside them, by kind or node predicate ({a:[{x:1},{x:2}]} -> {a:[1,2]})

  • [x] editByKind -- given a kind name and an editor function, change all immediate children of that kind. Kind can be a string or a where predicate

  • [x] removeEmptyNodes -- recursively remove nodes which contain only null or undefined. This can remove entire subtrees that contain only empty children

Unimplemented

  • [ ] graft -- insert new subtrees
  • [ ] disconnect -- the opposite of Fuse, place a new node between a parent and child
  • [ ] fork -- move some of the values of a node into a new or existing sibling
  • [ ] move -- move some of the values of a node into an existing sibling, or do nothing
  • [ ] editPath -- given a path of kinds and a func node→node, replace data at those paths
  • [ ] fuseAway -- remove a node by connecting it's parents to it's children, losing the data in the selected nodes
  • [ ] fuseAwayByNode
  • [ ] fuseAwayByKind

Note:

  • To run istanbul on Windows, use istanbul cover C:\Users\[username]\AppData\Roaming\npm\node_modules\mocha\bin\_mocha -- -R spec

Todo:

  • optimisations
  • a good way to find subtrees based on paths, and perform operations based on results
  • bring .d.ts file up-to-date with available features
  • syntax should allow chaining of functions
  • extend with kind and predicate functions
  • some way of mutating kind when fusing/merging?