first commit
This commit is contained in:
195
node_modules/n8n-workflow/dist/cjs/graph/graph-utils.js
generated
vendored
Normal file
195
node_modules/n8n-workflow/dist/cjs/graph/graph-utils.js
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getInputEdges = getInputEdges;
|
||||
exports.getOutputEdges = getOutputEdges;
|
||||
exports.getRootNodes = getRootNodes;
|
||||
exports.getLeafNodes = getLeafNodes;
|
||||
exports.hasPath = hasPath;
|
||||
exports.buildAdjacencyList = buildAdjacencyList;
|
||||
exports.parseExtractableSubgraphSelection = parseExtractableSubgraphSelection;
|
||||
/**
|
||||
* Find all edges leading into the graph described in `graphIds`.
|
||||
*/
|
||||
function getInputEdges(graphIds, adjacencyList) {
|
||||
const result = [];
|
||||
for (const [from, tos] of adjacencyList.entries()) {
|
||||
if (graphIds.has(from))
|
||||
continue;
|
||||
for (const to of tos) {
|
||||
if (graphIds.has(to.node)) {
|
||||
result.push([from, to]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Find all edges leading out of the graph described in `graphIds`.
|
||||
*/
|
||||
function getOutputEdges(graphIds, adjacencyList) {
|
||||
const result = [];
|
||||
for (const [from, tos] of adjacencyList.entries()) {
|
||||
if (!graphIds.has(from))
|
||||
continue;
|
||||
for (const to of tos) {
|
||||
if (!graphIds.has(to.node)) {
|
||||
result.push([from, to]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function intersection(a, b) {
|
||||
const result = new Set();
|
||||
for (const x of a) {
|
||||
if (b.has(x))
|
||||
result.add(x);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function union(a, b) {
|
||||
const result = new Set();
|
||||
for (const x of a)
|
||||
result.add(x);
|
||||
for (const x of b)
|
||||
result.add(x);
|
||||
return result;
|
||||
}
|
||||
function difference(minuend, subtrahend) {
|
||||
const result = new Set(minuend.values());
|
||||
for (const x of subtrahend) {
|
||||
result.delete(x);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function getRootNodes(graphIds, adjacencyList) {
|
||||
// Inner nodes are all nodes with an incoming edge from another node in the graph
|
||||
let innerNodes = new Set();
|
||||
for (const nodeId of graphIds) {
|
||||
innerNodes = union(innerNodes, new Set([...(adjacencyList.get(nodeId) ?? [])]
|
||||
.filter((x) => x.type === 'main' && x.node !== nodeId)
|
||||
.map((x) => x.node)));
|
||||
}
|
||||
return difference(graphIds, innerNodes);
|
||||
}
|
||||
function getLeafNodes(graphIds, adjacencyList) {
|
||||
const result = new Set();
|
||||
for (const nodeId of graphIds) {
|
||||
if (intersection(new Set([...(adjacencyList.get(nodeId) ?? [])]
|
||||
.filter((x) => x.type === 'main' && x.node !== nodeId)
|
||||
.map((x) => x.node)), graphIds).size === 0) {
|
||||
result.add(nodeId);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function hasPath(start, end, adjacencyList) {
|
||||
const seen = new Set();
|
||||
const paths = [start];
|
||||
while (true) {
|
||||
const next = paths.pop();
|
||||
if (next === end)
|
||||
return true;
|
||||
if (next === undefined)
|
||||
return false;
|
||||
seen.add(next);
|
||||
paths.push(...difference(new Set([...(adjacencyList.get(next) ?? [])].filter((x) => x.type === 'main').map((x) => x.node)), seen));
|
||||
}
|
||||
}
|
||||
function buildAdjacencyList(connectionsBySourceNode) {
|
||||
const result = new Map();
|
||||
const addOrCreate = (k, v) => result.set(k, union(result.get(k) ?? new Set(), new Set([v])));
|
||||
for (const sourceNode of Object.keys(connectionsBySourceNode)) {
|
||||
for (const type of Object.keys(connectionsBySourceNode[sourceNode])) {
|
||||
for (const sourceIndex of Object.keys(connectionsBySourceNode[sourceNode][type])) {
|
||||
for (const connectionIndex of Object.keys(connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)] ?? [])) {
|
||||
const connection = connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)]?.[parseInt(connectionIndex, 10)];
|
||||
if (connection)
|
||||
addOrCreate(sourceNode, connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* A subgraph is considered extractable if the following properties hold:
|
||||
* - 0-1 input nodes from outside the subgraph, to a root node
|
||||
* - 0-1 output nodes to outside the subgraph, from a leaf node
|
||||
* - continuous path between input and output nodes if they exist
|
||||
*
|
||||
* This also covers the requirement that all "inner" nodes between the root node
|
||||
* and the output node are selected, since this would otherwise create extra
|
||||
* input or output nodes.
|
||||
*
|
||||
* @returns An object containing optional start and end nodeIds
|
||||
* indicating which nodes have outside connections, OR
|
||||
* An array of errors if the selection is not valid.
|
||||
*/
|
||||
function parseExtractableSubgraphSelection(graphIds, adjacencyList) {
|
||||
const errors = [];
|
||||
// 0-1 Input nodes
|
||||
const inputEdges = getInputEdges(graphIds, adjacencyList);
|
||||
// This filters out e.g. sub-nodes, which are technically parents
|
||||
const inputNodes = new Set(inputEdges.filter((x) => x[1].type === 'main').map((x) => x[1].node));
|
||||
let rootNodes = getRootNodes(graphIds, adjacencyList);
|
||||
// this enables supporting cases where we have one input and a loop back to it from within the selection
|
||||
if (rootNodes.size === 0 && inputNodes.size === 1)
|
||||
rootNodes = inputNodes;
|
||||
for (const inputNode of difference(inputNodes, rootNodes).values()) {
|
||||
errors.push({
|
||||
errorCode: 'Input Edge To Non-Root Node',
|
||||
node: inputNode,
|
||||
});
|
||||
}
|
||||
const rootInputNodes = intersection(rootNodes, inputNodes);
|
||||
if (rootInputNodes.size > 1) {
|
||||
errors.push({
|
||||
errorCode: 'Multiple Input Nodes',
|
||||
nodes: rootInputNodes,
|
||||
});
|
||||
}
|
||||
// 0-1 Output nodes
|
||||
const outputEdges = getOutputEdges(graphIds, adjacencyList);
|
||||
const outputNodes = new Set(outputEdges.filter((x) => x[1].type === 'main').map((x) => x[0]));
|
||||
let leafNodes = getLeafNodes(graphIds, adjacencyList);
|
||||
// If we have no leaf nodes, and only one output node, we can tolerate this output node
|
||||
// and connect to it.
|
||||
// Note that this is fairly theoretical, as return semantics in this case are not well-defined.
|
||||
if (leafNodes.size === 0 && outputNodes.size === 1)
|
||||
leafNodes = outputNodes;
|
||||
for (const outputNode of difference(outputNodes, leafNodes).values()) {
|
||||
errors.push({
|
||||
errorCode: 'Output Edge From Non-Leaf Node',
|
||||
node: outputNode,
|
||||
});
|
||||
}
|
||||
const leafOutputNodes = intersection(leafNodes, outputNodes);
|
||||
if (leafOutputNodes.size > 1) {
|
||||
errors.push({
|
||||
errorCode: 'Multiple Output Nodes',
|
||||
nodes: leafOutputNodes,
|
||||
});
|
||||
}
|
||||
const start = rootInputNodes.values().next().value;
|
||||
const end = leafOutputNodes.values().next().value;
|
||||
if (start && end && !hasPath(start, end, adjacencyList)) {
|
||||
errors.push({
|
||||
errorCode: 'No Continuous Path From Root To Leaf In Selection',
|
||||
start,
|
||||
end,
|
||||
});
|
||||
}
|
||||
return errors.length > 0 ? errors : { start, end };
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=graph-utils.js.map
|
||||
Reference in New Issue
Block a user