otomato-sdk 2.0.15 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/constants/version.js +1 -1
- package/dist/src/models/Workflow.js +88 -1
- package/dist/src/utils/WorkflowNodePositioner.js +202 -0
- package/dist/types/src/constants/version.d.ts +1 -1
- package/dist/types/src/models/Workflow.d.ts +3 -0
- package/dist/types/src/utils/WorkflowNodePositioner.d.ts +30 -0
- package/dist/types/test/nodePositioning.spec.d.ts +1 -0
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import { Node } from './Node.js';
|
|
|
11
11
|
import { Edge } from './Edge.js';
|
|
12
12
|
import { apiServices } from '../services/ApiService.js';
|
|
13
13
|
import { Note } from './Note.js';
|
|
14
|
+
import { positionWorkflowNodesAvoidOverlap } from '../utils/WorkflowNodePositioner.js';
|
|
14
15
|
export class Workflow {
|
|
15
16
|
constructor(name = '', nodes = [], edges = []) {
|
|
16
17
|
this.id = null;
|
|
@@ -22,18 +23,101 @@ export class Workflow {
|
|
|
22
23
|
this.nodes = nodes;
|
|
23
24
|
this.edges = edges;
|
|
24
25
|
this.state = 'inactive';
|
|
26
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
25
27
|
}
|
|
26
28
|
setName(name) {
|
|
27
29
|
this.name = name;
|
|
28
30
|
}
|
|
29
31
|
addNode(node) {
|
|
30
32
|
this.nodes.push(node);
|
|
33
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
31
34
|
}
|
|
32
35
|
addNodes(nodes) {
|
|
33
36
|
this.nodes.push(...nodes);
|
|
37
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
38
|
+
}
|
|
39
|
+
deleteNode(nodeToDelete) {
|
|
40
|
+
// Remove the node from the nodes array
|
|
41
|
+
const nodeIndex = this.nodes.findIndex(node => node === nodeToDelete);
|
|
42
|
+
if (nodeIndex === -1) {
|
|
43
|
+
throw new Error(`Node not found in the workflow.`);
|
|
44
|
+
}
|
|
45
|
+
this.nodes.splice(nodeIndex, 1);
|
|
46
|
+
// Collect incoming and outgoing edges
|
|
47
|
+
const incomingEdges = this.edges.filter(edge => edge.target === nodeToDelete);
|
|
48
|
+
const outgoingEdges = this.edges.filter(edge => edge.source === nodeToDelete);
|
|
49
|
+
// Create new edges to replace the deleted node's connections
|
|
50
|
+
const newEdges = [];
|
|
51
|
+
incomingEdges.forEach(inEdge => {
|
|
52
|
+
outgoingEdges.forEach(outEdge => {
|
|
53
|
+
newEdges.push(new Edge({ source: inEdge.source, target: outEdge.target }));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
// Update the edges array: remove edges involving the deleted node and add the new ones
|
|
57
|
+
this.edges = this.edges.filter(edge => edge.source !== nodeToDelete && edge.target !== nodeToDelete);
|
|
58
|
+
this.edges.push(...newEdges);
|
|
59
|
+
// Recalculate positions
|
|
60
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
61
|
+
}
|
|
62
|
+
insertNode(nodeToInsert, nodeBefore, nodeAfter) {
|
|
63
|
+
// Ensure nodeBefore exists in the workflow
|
|
64
|
+
if (!this.nodes.includes(nodeBefore)) {
|
|
65
|
+
throw new Error('The nodeBefore must exist in the workflow.');
|
|
66
|
+
}
|
|
67
|
+
// If nodeAfter is not provided, insert the new node as a child of nodeBefore
|
|
68
|
+
if (!nodeAfter) {
|
|
69
|
+
// Add the new node to the workflow
|
|
70
|
+
this.addNode(nodeToInsert);
|
|
71
|
+
// Add a new edge between nodeBefore and nodeToInsert
|
|
72
|
+
const newEdge = new Edge({ source: nodeBefore, target: nodeToInsert });
|
|
73
|
+
this.addEdge(newEdge);
|
|
74
|
+
// Recalculate positions
|
|
75
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// If nodeAfter is provided, ensure both nodes exist in the workflow
|
|
79
|
+
if (!this.nodes.includes(nodeAfter)) {
|
|
80
|
+
throw new Error('The nodeAfter must exist in the workflow.');
|
|
81
|
+
}
|
|
82
|
+
// Check if an edge exists between nodeBefore and nodeAfter
|
|
83
|
+
const edgeBetween = this.edges.find(edge => edge.source === nodeBefore && edge.target === nodeAfter);
|
|
84
|
+
if (!edgeBetween) {
|
|
85
|
+
throw new Error('No edge exists between nodeBefore and nodeAfter.');
|
|
86
|
+
}
|
|
87
|
+
// Add the new node to the workflow
|
|
88
|
+
this.addNode(nodeToInsert);
|
|
89
|
+
// Remove the existing edge between nodeBefore and nodeAfter
|
|
90
|
+
this.edges = this.edges.filter(edge => edge !== edgeBetween);
|
|
91
|
+
// Add new edges
|
|
92
|
+
const newEdge1 = new Edge({ source: nodeBefore, target: nodeToInsert });
|
|
93
|
+
const newEdge2 = new Edge({ source: nodeToInsert, target: nodeAfter });
|
|
94
|
+
this.addEdges([newEdge1, newEdge2]);
|
|
95
|
+
// Recalculate positions
|
|
96
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
97
|
+
}
|
|
98
|
+
swapNode(oldNode, newNode) {
|
|
99
|
+
// Find the index of the node to replace
|
|
100
|
+
const nodeIndex = this.nodes.findIndex(node => node === oldNode);
|
|
101
|
+
if (nodeIndex === -1) {
|
|
102
|
+
throw new Error(`Node to swap not found in the workflow.`);
|
|
103
|
+
}
|
|
104
|
+
// Replace the old node with the new node in the nodes array
|
|
105
|
+
this.nodes[nodeIndex] = newNode;
|
|
106
|
+
// Update edges to point to the new node
|
|
107
|
+
this.edges.forEach(edge => {
|
|
108
|
+
if (edge.source === oldNode) {
|
|
109
|
+
edge.source = newNode;
|
|
110
|
+
}
|
|
111
|
+
if (edge.target === oldNode) {
|
|
112
|
+
edge.target = newNode;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// Recalculate positions
|
|
116
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
34
117
|
}
|
|
35
118
|
addEdge(edge) {
|
|
36
119
|
this.edges.push(edge);
|
|
120
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
37
121
|
}
|
|
38
122
|
updateEdge(edgeId, newEdge) {
|
|
39
123
|
const edgeToUpdate = this.edges.find(e => e.id === edgeId);
|
|
@@ -44,9 +128,11 @@ export class Workflow {
|
|
|
44
128
|
else {
|
|
45
129
|
throw new Error(`Edge with id ${edgeId} not found`);
|
|
46
130
|
}
|
|
131
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
47
132
|
}
|
|
48
133
|
addEdges(edges) {
|
|
49
134
|
this.edges.push(...edges);
|
|
135
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
50
136
|
}
|
|
51
137
|
getState() {
|
|
52
138
|
return this.state;
|
|
@@ -90,7 +176,7 @@ export class Workflow {
|
|
|
90
176
|
executionId: this.executionId,
|
|
91
177
|
nodes: this.nodes.map(node => node.toJSON()),
|
|
92
178
|
edges: this.edges.map(edge => edge.toJSON()),
|
|
93
|
-
notes: this.getNotes(),
|
|
179
|
+
notes: this.getNotes(),
|
|
94
180
|
};
|
|
95
181
|
}
|
|
96
182
|
create() {
|
|
@@ -175,6 +261,7 @@ export class Workflow {
|
|
|
175
261
|
this.nodes = yield Promise.all(response.nodes.map((nodeData) => __awaiter(this, void 0, void 0, function* () { return yield Node.fromJSON(nodeData); })));
|
|
176
262
|
this.edges = response.edges.map((edgeData) => Edge.fromJSON(edgeData, this.nodes));
|
|
177
263
|
this.notes = response.notes.map((noteData) => Note.fromJSON(noteData));
|
|
264
|
+
positionWorkflowNodesAvoidOverlap(this);
|
|
178
265
|
return this;
|
|
179
266
|
}
|
|
180
267
|
catch (error) {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// workflowNodePositioner.ts
|
|
2
|
+
/**
|
|
3
|
+
* Positions nodes in a BFS manner, assuming exactly one root node.
|
|
4
|
+
* - The root is placed at (400, 120) unless it already has a position.
|
|
5
|
+
* - If a parent has exactly 1 child, that child is placed directly below the parent
|
|
6
|
+
* (same x, with an offset in y).
|
|
7
|
+
* - If a parent has multiple children, they are horizontally spread around the parent's X.
|
|
8
|
+
* - Existing positions are not overwritten.
|
|
9
|
+
*/
|
|
10
|
+
/*export function autoPositionNodes(workflow: Workflow): void {
|
|
11
|
+
// 1. Build adjacency & incoming edge count
|
|
12
|
+
const adjacency = new Map<string, Node[]>();
|
|
13
|
+
const incomingCount = new Map<string, number>();
|
|
14
|
+
|
|
15
|
+
// Constants you can tweak or increase if you see overlap or crossing edges
|
|
16
|
+
const ROOT_X = 400;
|
|
17
|
+
const ROOT_Y = 120;
|
|
18
|
+
const MIN_SPACING_X = 500; // Horizontal spacing between siblings
|
|
19
|
+
const MIN_SPACING_Y = 120; // Vertical spacing from parent to child
|
|
20
|
+
|
|
21
|
+
// Initialize adjacency and incoming counts
|
|
22
|
+
workflow.nodes.forEach((node) => {
|
|
23
|
+
adjacency.set(node.getRef(), []);
|
|
24
|
+
incomingCount.set(node.getRef(), 0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Fill adjacency list (source -> array of targets) and count incoming edges
|
|
28
|
+
workflow.edges.forEach((edge: Edge) => {
|
|
29
|
+
const sourceRef = edge.source.getRef();
|
|
30
|
+
const targetRef = edge.target.getRef();
|
|
31
|
+
adjacency.get(sourceRef)?.push(edge.target);
|
|
32
|
+
incomingCount.set(targetRef, (incomingCount.get(targetRef) ?? 0) + 1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 2. Find all root nodes (no incoming edges). If multiple, just pick the first.
|
|
36
|
+
const rootNodes = workflow.nodes.filter(
|
|
37
|
+
(n) => (incomingCount.get(n.getRef()) || 0) === 0
|
|
38
|
+
);
|
|
39
|
+
if (rootNodes.length === 0) {
|
|
40
|
+
// No root found; nothing to do
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// We'll assume the first root is "the" root for this layout
|
|
45
|
+
const root = rootNodes[0];
|
|
46
|
+
if (!hasPosition(root)) {
|
|
47
|
+
root.setPosition(ROOT_X, ROOT_Y);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. BFS from that single root
|
|
51
|
+
const queue = [root];
|
|
52
|
+
while (queue.length > 0) {
|
|
53
|
+
const parent = queue.shift()!;
|
|
54
|
+
const parentRef = parent.getRef();
|
|
55
|
+
|
|
56
|
+
// Identify the children
|
|
57
|
+
const children = adjacency.get(parentRef) || [];
|
|
58
|
+
|
|
59
|
+
// Among those children, find any that do NOT already have positions
|
|
60
|
+
const unpositionedKids = children.filter((c) => !hasPosition(c));
|
|
61
|
+
|
|
62
|
+
if (unpositionedKids.length > 0) {
|
|
63
|
+
const parentX = parent.position?.x ?? 0;
|
|
64
|
+
const parentY = parent.position?.y ?? 0;
|
|
65
|
+
|
|
66
|
+
if (unpositionedKids.length === 1) {
|
|
67
|
+
// Single child: place it straight below the parent
|
|
68
|
+
const onlyChild = unpositionedKids[0];
|
|
69
|
+
onlyChild.setPosition(parentX, parentY + MIN_SPACING_Y);
|
|
70
|
+
} else {
|
|
71
|
+
// Multiple children: spread them horizontally around the parent's X
|
|
72
|
+
const count = unpositionedKids.length;
|
|
73
|
+
unpositionedKids.forEach((child, i) => {
|
|
74
|
+
const offset = i - (count - 1) / 2;
|
|
75
|
+
const childX = parentX + offset * MIN_SPACING_X;
|
|
76
|
+
const childY = parentY + MIN_SPACING_Y;
|
|
77
|
+
child.setPosition(childX, childY);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Decrement incoming edges for each child. Once a child hits 0, enqueue it
|
|
83
|
+
children.forEach((c) => {
|
|
84
|
+
const cRef = c.getRef();
|
|
85
|
+
const oldCount = incomingCount.get(cRef) ?? 0;
|
|
86
|
+
const newCount = oldCount - 1;
|
|
87
|
+
incomingCount.set(cRef, newCount);
|
|
88
|
+
if (newCount === 0) {
|
|
89
|
+
queue.push(c);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}*/
|
|
94
|
+
export const xSpacing = 400;
|
|
95
|
+
export const ySpacing = 120;
|
|
96
|
+
export const ROOT_X = 400;
|
|
97
|
+
export const ROOT_Y = 120;
|
|
98
|
+
export function positionWorkflowNodes(workflow) {
|
|
99
|
+
try {
|
|
100
|
+
// Step 1: Find the starting nodes using identityStartingNodes function
|
|
101
|
+
const startingNodes = identityStartingNodes(workflow);
|
|
102
|
+
// Step 2: Place the starting nodes
|
|
103
|
+
let xPosition = ROOT_X;
|
|
104
|
+
startingNodes.forEach((startNode) => {
|
|
105
|
+
startNode.setPosition(xPosition, ROOT_Y);
|
|
106
|
+
xPosition += xSpacing;
|
|
107
|
+
});
|
|
108
|
+
// Step 3: Place all other nodes relative to their parents
|
|
109
|
+
const nodesToPosition = workflow.nodes.filter((node) => !startingNodes.includes(node));
|
|
110
|
+
nodesToPosition.forEach((node) => positionNode(node, workflow.edges, xSpacing, ySpacing, workflow));
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
console.error(e);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function positionNode(node, edges, xSpacing, ySpacing, workflow) {
|
|
117
|
+
// Get children of the node
|
|
118
|
+
const parents = getParents(node, edges);
|
|
119
|
+
// todo: what if we have multiple parents?
|
|
120
|
+
const children = getChildren(parents[0], edges);
|
|
121
|
+
const childrenCountOfParent = children.length;
|
|
122
|
+
const parentX = parents.reduce((sum, parent) => { var _a, _b; return sum + ((_b = (_a = parent.position) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : ROOT_X); }, 0) / parents.length;
|
|
123
|
+
const parentY = Math.max(...parents.map(parent => { var _a, _b; return (_b = (_a = parent.position) === null || _a === void 0 ? void 0 : _a.y) !== null && _b !== void 0 ? _b : ROOT_Y; }));
|
|
124
|
+
// Compute position based on parent children count
|
|
125
|
+
if (childrenCountOfParent === 1) {
|
|
126
|
+
node.setPosition(parentX, parentY + ySpacing);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const index = children.indexOf(node); // Get the position of this node among its siblings
|
|
130
|
+
const totalChildren = children.length;
|
|
131
|
+
// Compute the x position for this node
|
|
132
|
+
const offset = index - (totalChildren - 1) / 2; // Center the children around the parent
|
|
133
|
+
node.setPosition(parentX + offset * xSpacing, parentY + ySpacing);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function positionWorkflowNodesAvoidOverlap(workflow) {
|
|
137
|
+
const levels = new Map();
|
|
138
|
+
// Helper: Add node to its level
|
|
139
|
+
function addToLevel(node) {
|
|
140
|
+
const level = Math.round(node.position.y / ySpacing);
|
|
141
|
+
if (!levels.has(level)) {
|
|
142
|
+
levels.set(level, []);
|
|
143
|
+
}
|
|
144
|
+
levels.get(level).push(node);
|
|
145
|
+
}
|
|
146
|
+
// Step 1: Position nodes using the existing logic
|
|
147
|
+
positionWorkflowNodes(workflow);
|
|
148
|
+
// Step 2: Populate levels
|
|
149
|
+
workflow.nodes.forEach((node) => {
|
|
150
|
+
if (node.position) {
|
|
151
|
+
addToLevel(node);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// Step 3: Resolve overlaps for each level
|
|
155
|
+
levels.forEach((nodes, level) => {
|
|
156
|
+
// Sort nodes by X position
|
|
157
|
+
nodes.sort((a, b) => { var _a, _b; return ((_a = a.position.x) !== null && _a !== void 0 ? _a : 0) - ((_b = b.position.x) !== null && _b !== void 0 ? _b : 0); });
|
|
158
|
+
// Adjust overlapping nodes
|
|
159
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
160
|
+
const prevNode = nodes[i - 1];
|
|
161
|
+
const currentNode = nodes[i];
|
|
162
|
+
if (currentNode.position.x - prevNode.position.x < xSpacing) {
|
|
163
|
+
const shift = xSpacing - (currentNode.position.x - prevNode.position.x);
|
|
164
|
+
moveNodeAndChildren(currentNode, shift, workflow.edges);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function moveNodeAndChildren(node, shift, edges) {
|
|
170
|
+
// Move the node
|
|
171
|
+
node.setPosition(node.position.x + shift, node.position.y);
|
|
172
|
+
// Propagate to children
|
|
173
|
+
edges
|
|
174
|
+
.filter((edge) => edge.source === node)
|
|
175
|
+
.forEach((edge) => {
|
|
176
|
+
moveNodeAndChildren(edge.target, shift, edges);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
export function identifyLeafNodes(workflow) {
|
|
180
|
+
const nonLeafNodes = new Set(workflow.edges.map(edge => edge.source.getRef()));
|
|
181
|
+
return workflow.nodes.filter(node => !nonLeafNodes.has(node.getRef()));
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Identifies starting nodes (nodes with no incoming edges).
|
|
185
|
+
* A starting node is defined as one that is not a target of any edge.
|
|
186
|
+
*
|
|
187
|
+
* @param workflow The workflow to analyze.
|
|
188
|
+
* @returns An array of nodes that have no incoming edges.
|
|
189
|
+
*/
|
|
190
|
+
export function identityStartingNodes(workflow) {
|
|
191
|
+
const childRefs = new Set(workflow.edges.map((edge) => edge.target.getRef()));
|
|
192
|
+
return workflow.nodes.filter((node) => !childRefs.has(node.getRef()));
|
|
193
|
+
}
|
|
194
|
+
export function getChildren(node, edges) {
|
|
195
|
+
return edges.filter(edge => edge.source === node).map(edge => edge.target);
|
|
196
|
+
}
|
|
197
|
+
export function getParents(node, edges) {
|
|
198
|
+
return edges.filter(edge => edge.target === node).map(edge => edge.source);
|
|
199
|
+
}
|
|
200
|
+
export function getEdges(node, edges) {
|
|
201
|
+
return edges.filter(edge => edge.source === node || edge.target === node);
|
|
202
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "2.0.
|
|
1
|
+
export declare const SDK_VERSION = "2.0.16";
|
|
2
2
|
export declare function compareVersions(v1: string, v2: string): number;
|
|
@@ -17,6 +17,9 @@ export declare class Workflow {
|
|
|
17
17
|
setName(name: string): void;
|
|
18
18
|
addNode(node: Node): void;
|
|
19
19
|
addNodes(nodes: Node[]): void;
|
|
20
|
+
deleteNode(nodeToDelete: Node): void;
|
|
21
|
+
insertNode(nodeToInsert: Node, nodeBefore: Node, nodeAfter?: Node): void;
|
|
22
|
+
swapNode(oldNode: Node, newNode: Node): void;
|
|
20
23
|
addEdge(edge: Edge): void;
|
|
21
24
|
updateEdge(edgeId: string, newEdge: Edge): void;
|
|
22
25
|
addEdges(edges: Edge[]): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Workflow } from '../models/Workflow.js';
|
|
2
|
+
import { Node } from '../models/Node.js';
|
|
3
|
+
import { Edge } from '../models/Edge.js';
|
|
4
|
+
/**
|
|
5
|
+
* Positions nodes in a BFS manner, assuming exactly one root node.
|
|
6
|
+
* - The root is placed at (400, 120) unless it already has a position.
|
|
7
|
+
* - If a parent has exactly 1 child, that child is placed directly below the parent
|
|
8
|
+
* (same x, with an offset in y).
|
|
9
|
+
* - If a parent has multiple children, they are horizontally spread around the parent's X.
|
|
10
|
+
* - Existing positions are not overwritten.
|
|
11
|
+
*/
|
|
12
|
+
export declare const xSpacing = 400;
|
|
13
|
+
export declare const ySpacing = 120;
|
|
14
|
+
export declare const ROOT_X = 400;
|
|
15
|
+
export declare const ROOT_Y = 120;
|
|
16
|
+
export declare function positionWorkflowNodes(workflow: Workflow): void;
|
|
17
|
+
export declare function positionNode(node: Node, edges: Edge[], xSpacing: number, ySpacing: number, workflow: Workflow): void;
|
|
18
|
+
export declare function positionWorkflowNodesAvoidOverlap(workflow: Workflow): void;
|
|
19
|
+
export declare function identifyLeafNodes(workflow: Workflow): Node[];
|
|
20
|
+
/**
|
|
21
|
+
* Identifies starting nodes (nodes with no incoming edges).
|
|
22
|
+
* A starting node is defined as one that is not a target of any edge.
|
|
23
|
+
*
|
|
24
|
+
* @param workflow The workflow to analyze.
|
|
25
|
+
* @returns An array of nodes that have no incoming edges.
|
|
26
|
+
*/
|
|
27
|
+
export declare function identityStartingNodes(workflow: Workflow): Node[];
|
|
28
|
+
export declare function getChildren(node: Node, edges: Edge[]): Node[];
|
|
29
|
+
export declare function getParents(node: Node, edges: Edge[]): Node[];
|
|
30
|
+
export declare function getEdges(node: Node, edges: Edge[]): Edge[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|