UNPKG

resolved-graph

Version:

Generates and updates a graph of nodes and links with resolved relationships for ease of traversal and extraction

252 lines (251 loc) 8.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResolvedGraph = void 0; const mutateDeepLeft_1 = require("../utils/mutateDeepLeft"); const matchDeepRight_1 = require("../utils/matchDeepRight"); class ResolvedGraph { /** * Creates a graph where all the nodes and links have references to its connected entitites * @param graph The graph to resolve on creation */ constructor(graph) { this._nodes = {}; this._links = {}; if (graph) this.mergeGraph(graph); } /** * An array of all the resolved nodes in the graph */ get nodes() { return Object.values(this._nodes); } /** * An array of all the resolved links in the graph */ get links() { return Object.values(this._links); } resolveNode(node) { this.node(node.id).to = Object.values(this._links).filter((l) => l.to === this.node(node.id)); this.node(node.id).from = Object.values(this._links).filter((l) => l.from === this.node(node.id)); } resolveLink(link) { this._links[link.id].to = this._nodes[typeof link.to === 'string' ? link.to : link.to.id]; this._links[link.id].from = this._nodes[typeof link.from === 'string' ? link.from : link.from.id]; } /** * Merges a simple graph into the existing resolved graph, resolving all links and nodes affected in the process * @param graph The graph to merge into the existing resolved graph */ mergeGraph(graph) { mutateDeepLeft_1.mutateDeepLeft(this._links, graph.links.reduce((acc, curr) => (Object.assign(Object.assign({}, acc), { [curr.id]: curr })), {})); mutateDeepLeft_1.mutateDeepLeft(this._nodes, graph.nodes.reduce((acc, curr) => (Object.assign(Object.assign({}, acc), { [curr.id]: curr })), {})); graph.links.forEach((link) => this.resolveLink(link)); graph.nodes.forEach((node) => this.resolveNode(node)); } /** * Either creates a new node or sets a node, deleting any non-vital properties not included in the argument * @param node The node to create or set */ setNode(node) { if (!this._nodes[node.id]) { this._nodes[node.id] = node; } else { if (this._nodes[node.id] === node) return; //<-- Entity is not a clone and has already been mutated outside the graph. const oldKeys = Object.keys(this._nodes[node.id]); const newKeys = Object.keys(node); for (const newKey of newKeys) { this._nodes[node.id][newKey] = node[newKey]; } for (const oldKey of oldKeys) { if (oldKey === 'from' || oldKey === 'to' || oldKey === 'id') continue; if (!newKeys.includes(oldKey)) { delete this._nodes[node.id][oldKey]; } } } this.resolveNode(node); } /** * Merges or creates a node, overwriting any properties in the node argument while not changing any other ones * @param node The node to merge */ mergeNode(node) { mutateDeepLeft_1.mutateDeepLeft(this._nodes, { [node.id]: node }); this.resolveNode(node); } /** * Returns the first node that matches the query object, including nested links and their nodes * @param query Contains properties that must be matched by the node * @example * resolvedGraph.findNode({ data: { answer: 42 } }) * > { id: "Adams", data: { answer: 42, question: "What do you get when you multiply six by nine?" }, from: [...], to: [...]} } * @example * resolvedGraph.findNode({ id: "A", from: [{ to: { id: "B"} }]}) * > { id: "A", from: [ { id: "1", from: [Circular] , to: { id: "B", from: [...], to: [ [Circular] , ...] }}, ...], to: [...] } */ findNode(query) { return this.nodes.find((node) => matchDeepRight_1.matchDeepRight(node, query)); } /** * Returns an array of nodes that matches the query object, including nested links and their nodes * @param query Contains properties that must be matched by the nodes * @example * resolvedGraph.findNodes({ data: { type: "Movie" } }) * > [ * { * id: "TMTRX", * data: { * name: "The Matrix" * type: "Movie", * rating: "Breathtaking!" * }, * from: [...], * to: [...] * }, * { * id: "PRDTR", * data: { * name: "Predator" * type: "Movie", * rating: "Auuuagh! Auugh!" * }, * from: [...], * to: [...] * } * ] */ findNodes(query) { return this.nodes.filter((node) => matchDeepRight_1.matchDeepRight(node, query)); } /** * Returns a node with a given id * @param id The id of the node */ node(id) { return this._nodes[id]; } /** * Either creates a new link or sets a link, deleting any non-vital properties not included in the argument * @param link The link to create or set */ setLink(link) { if (!this._links[link.id]) { this._links[link.id] = link; } else { if (this._links[link.id] === link) return; //<-- Entity is not a clone and has already been mutated outside the graph. const oldKeys = Object.keys(this._links[link.id]); const newKeys = Object.keys(link); for (const newKey of newKeys) { this._links[link.id][newKey] = link[newKey]; } for (const oldKey of oldKeys) { if (oldKey === 'from' || oldKey === 'to' || oldKey === 'id') continue; if (!newKeys.includes(oldKey)) { delete this._links[link.id][oldKey]; } } } this.resolveLink(link); } /** * Merges or creates a link, overwriting any properties in the link argument while not changing any other ones * @param link The link to merge */ mergeLink(link) { mutateDeepLeft_1.mutateDeepLeft(this._links, { [link.id]: link }); this.resolveLink(link); } /** * Returns the first link that matches the query object, including nested nodes and their links * @param query Contains properties that must be matched by the link * @example * resolvedGraph.findLink({ data: { type: "Acted in" } to: { id: 'TMTRX' }}) * > * { * id: "123", * data: { type: "Acted in" }, * from: { * id: "KNU", * data: { name: "Keanu Reeves" }, * from: [ [Circular], ...], * to: [...] * }, * to: { * id: "TMTRX", * data: { * name: "The Matrix" * type: "Movie", * rating: "Breathtaking!" * }, * from: [...], * to: [ [Circular], ...] * }, * } */ findLink(query) { return this.links.find((link) => matchDeepRight_1.matchDeepRight(link, query)); } /** * Returns any links that match the query object, including nested nodes and their links * @param query Contains properties that must be matched by the links * @example * resolvedGraph.findLinks({ from: { id: "A" } }) * > * [ * { * id: "AB", * from: { * id: "A", * from: [[Circular], ...], * to: [...] * }, * to: { * id: "B" * from: [[Circular], ...], * to: [...] * } * }, * { * id: "AC", * from: { * id: "A", * from: [[Circular], ...], * to: [...] * }, * to: { * id: "C" * from: [[Circular], ...], * to: [...] * } * } * ] */ findLinks(query) { return this.links.filter((link) => matchDeepRight_1.matchDeepRight(link, query)); } /** * Returns a link with a given id * @param id The id of the link */ link(id) { return this._links[id]; } /** * Creates an unresolved Graph object out of a the Resolved Graph */ dissolve() { return { nodes: this.nodes.map((node) => (Object.assign(Object.assign({}, node), { from: undefined, to: undefined }))), links: this.links.map((link) => (Object.assign(Object.assign({}, link), { from: link.from.id, to: link.to.id }))), }; } } exports.ResolvedGraph = ResolvedGraph;