otomato-sdk 2.0.36 → 2.0.37
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/models/Workflow.js +18 -16
- package/dist/src/utils/WorkflowNodePositioner.js +68 -133
- package/dist/types/examples/UseCases/LendingAggregator/lending-aggregator-2-protocols.d.ts +1 -1
- package/dist/types/examples/UseCases/LendingAggregator/lending-aggregator-3-protocols.d.ts +4 -0
- package/dist/types/examples/UseCases/LendingAggregator/lending-aggregator-v3.d.ts +4 -0
- package/dist/types/src/models/Workflow.d.ts +1 -1
- package/dist/types/src/utils/WorkflowNodePositioner.d.ts +17 -12
- package/package.json +3 -1
|
@@ -12,7 +12,7 @@ import { Edge } from './Edge.js';
|
|
|
12
12
|
import { apiServices } from '../services/ApiService.js';
|
|
13
13
|
import { Action } from './Action.js';
|
|
14
14
|
import { Note } from './Note.js';
|
|
15
|
-
import { getEndNodePositions,
|
|
15
|
+
import { getEndNodePositions, positionWorkflowNodes } from '../utils/WorkflowNodePositioner.js';
|
|
16
16
|
import { ACTIONS } from '../constants/Blocks.js';
|
|
17
17
|
export class Workflow {
|
|
18
18
|
constructor(name = '', nodes = [], edges = []) {
|
|
@@ -25,18 +25,18 @@ export class Workflow {
|
|
|
25
25
|
this.nodes = nodes;
|
|
26
26
|
this.edges = edges;
|
|
27
27
|
this.state = 'inactive';
|
|
28
|
-
|
|
28
|
+
positionWorkflowNodes(this);
|
|
29
29
|
}
|
|
30
30
|
setName(name) {
|
|
31
31
|
this.name = name;
|
|
32
32
|
}
|
|
33
33
|
addNode(node) {
|
|
34
34
|
this.nodes.push(node);
|
|
35
|
-
|
|
35
|
+
positionWorkflowNodes(this);
|
|
36
36
|
}
|
|
37
37
|
addNodes(nodes) {
|
|
38
38
|
this.nodes.push(...nodes);
|
|
39
|
-
|
|
39
|
+
positionWorkflowNodes(this);
|
|
40
40
|
}
|
|
41
41
|
deleteNode(nodeToDelete) {
|
|
42
42
|
// Remove the node from the nodes array
|
|
@@ -59,7 +59,7 @@ export class Workflow {
|
|
|
59
59
|
this.edges = this.edges.filter(edge => edge.source !== nodeToDelete && edge.target !== nodeToDelete);
|
|
60
60
|
this.edges.push(...newEdges);
|
|
61
61
|
// Recalculate positions
|
|
62
|
-
|
|
62
|
+
positionWorkflowNodes(this);
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* Inserts a new node into the workflow, potentially splitting an existing edge.
|
|
@@ -98,7 +98,7 @@ export class Workflow {
|
|
|
98
98
|
});
|
|
99
99
|
this.addEdge(newEdge);
|
|
100
100
|
// Recalculate positions
|
|
101
|
-
|
|
101
|
+
positionWorkflowNodes(this);
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
// CASE B: nodeAfter is provided => we expect an edge nodeBefore->nodeAfter to exist
|
|
@@ -131,9 +131,9 @@ export class Workflow {
|
|
|
131
131
|
});
|
|
132
132
|
this.addEdges([newEdge1, newEdge2]);
|
|
133
133
|
// Recalculate positions
|
|
134
|
-
|
|
134
|
+
positionWorkflowNodes(this);
|
|
135
135
|
}
|
|
136
|
-
insertCondition(nodeToInsert, nodeBefore, nodeAfter, addElseCase) {
|
|
136
|
+
insertCondition(nodeToInsert, nodeBefore, nodeAfter, addElseCase, addEmptyNodes = true) {
|
|
137
137
|
if (nodeAfter) {
|
|
138
138
|
this.insertNode(nodeToInsert, nodeBefore, nodeAfter, null, null, 'true', 'true');
|
|
139
139
|
}
|
|
@@ -141,13 +141,15 @@ export class Workflow {
|
|
|
141
141
|
// 1) "true" path: nodeBefore -> nodeToInsert -> nodeAfter
|
|
142
142
|
if (!nodeAfter) {
|
|
143
143
|
this.insertNode(nodeToInsert, nodeBefore, undefined);
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
if (addEmptyNodes) {
|
|
145
|
+
const emptyNode1 = new Action(ACTIONS.CORE.EMPTYBLOCK.EMPTYBLOCK);
|
|
146
|
+
this.insertNode(emptyNode1, nodeToInsert, undefined, 'true', 'true');
|
|
147
|
+
}
|
|
146
148
|
}
|
|
147
149
|
// 2) "false" path: nodeBefore -> nodeToInsert,
|
|
148
150
|
// but here we pass no nodeAfter (so nodeAfter=null),
|
|
149
151
|
// and label/value are "false".
|
|
150
|
-
if (addElseCase) {
|
|
152
|
+
if (addElseCase && addEmptyNodes) {
|
|
151
153
|
const emptyNode2 = new Action(ACTIONS.CORE.EMPTYBLOCK.EMPTYBLOCK);
|
|
152
154
|
this.insertNode(emptyNode2, nodeToInsert, undefined, 'false', 'false');
|
|
153
155
|
}
|
|
@@ -222,11 +224,11 @@ export class Workflow {
|
|
|
222
224
|
}
|
|
223
225
|
});
|
|
224
226
|
// Recalculate positions
|
|
225
|
-
|
|
227
|
+
positionWorkflowNodes(this);
|
|
226
228
|
}
|
|
227
229
|
addEdge(edge) {
|
|
228
230
|
this.edges.push(edge);
|
|
229
|
-
|
|
231
|
+
positionWorkflowNodes(this);
|
|
230
232
|
}
|
|
231
233
|
updateEdge(edgeId, newEdge) {
|
|
232
234
|
const edgeToUpdate = this.edges.find(e => e.id === edgeId);
|
|
@@ -237,11 +239,11 @@ export class Workflow {
|
|
|
237
239
|
else {
|
|
238
240
|
throw new Error(`Edge with id ${edgeId} not found`);
|
|
239
241
|
}
|
|
240
|
-
|
|
242
|
+
positionWorkflowNodes(this);
|
|
241
243
|
}
|
|
242
244
|
addEdges(edges) {
|
|
243
245
|
this.edges.push(...edges);
|
|
244
|
-
|
|
246
|
+
positionWorkflowNodes(this);
|
|
245
247
|
}
|
|
246
248
|
getState() {
|
|
247
249
|
return this.state;
|
|
@@ -370,7 +372,7 @@ export class Workflow {
|
|
|
370
372
|
this.nodes = yield Promise.all(response.nodes.map((nodeData) => __awaiter(this, void 0, void 0, function* () { return yield Node.fromJSON(nodeData); })));
|
|
371
373
|
this.edges = response.edges.map((edgeData) => Edge.fromJSON(edgeData, this.nodes));
|
|
372
374
|
this.notes = response.notes.map((noteData) => Note.fromJSON(noteData));
|
|
373
|
-
|
|
375
|
+
positionWorkflowNodes(this);
|
|
374
376
|
return this;
|
|
375
377
|
}
|
|
376
378
|
catch (error) {
|
|
@@ -1,146 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Note: Using 'dagre' for layered DAG layout
|
|
2
|
+
import dagre from 'dagre';
|
|
3
|
+
// or: import * as dagre from 'dagre';
|
|
4
|
+
export const xSpacing = 700; // used by dagre as node separation
|
|
5
|
+
export const ySpacing = 75;
|
|
6
|
+
export const ROOT_X = 400; // we’ll keep references, but Dagre decides actual positions
|
|
4
7
|
export const ROOT_Y = 120;
|
|
5
|
-
export function positionWorkflowNodesAvoidOverlap(workflow) {
|
|
6
|
-
// 1) Lay out nodes using existing logic
|
|
7
|
-
positionWorkflowNodes(workflow);
|
|
8
|
-
// 2) Group nodes by 'level' based on vertical position
|
|
9
|
-
const levels = new Map();
|
|
10
|
-
workflow.nodes.forEach((node) => {
|
|
11
|
-
if (node.position) {
|
|
12
|
-
const level = Math.round(node.position.y / ySpacing);
|
|
13
|
-
if (!levels.has(level)) {
|
|
14
|
-
levels.set(level, []);
|
|
15
|
-
}
|
|
16
|
-
levels.get(level).push(node);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
// 3) Resolve horizontal overlaps among *all* nodes in each level
|
|
20
|
-
levels.forEach((levelNodes) => {
|
|
21
|
-
// Sort the nodes in this level by X
|
|
22
|
-
levelNodes.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); });
|
|
23
|
-
// Walk left-to-right, shifting nodes that would collide with the previous one
|
|
24
|
-
for (let i = 1; i < levelNodes.length; i++) {
|
|
25
|
-
const prev = levelNodes[i - 1];
|
|
26
|
-
const current = levelNodes[i];
|
|
27
|
-
// Compute how close they are
|
|
28
|
-
const dx = current.position.x - prev.position.x;
|
|
29
|
-
// If they are too close, shift the current node (and its children) to the right
|
|
30
|
-
if (dx < xSpacing) {
|
|
31
|
-
const shift = xSpacing - dx;
|
|
32
|
-
moveNodeAndChildren(current, shift, workflow.edges);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
// 4) Re-center parents above their children in a bottom-up pass
|
|
37
|
-
centerParentXPositions(workflow);
|
|
38
|
-
}
|
|
39
8
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
9
|
+
* Dagre-based layout for the “top-down” pass. We’re effectively ignoring the old
|
|
10
|
+
* manual code. Instead, we:
|
|
11
|
+
* 1) Build a dagre graph
|
|
12
|
+
* 2) Add all nodes & edges
|
|
13
|
+
* 3) dagre.layout(g)
|
|
14
|
+
* 4) Extract positions & write them back to workflow.nodes
|
|
42
15
|
*/
|
|
43
16
|
export function positionWorkflowNodes(workflow) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
17
|
+
// 1) Create a new directed graph
|
|
18
|
+
// doc: https://github.com/dagrejs/dagre
|
|
19
|
+
const g = new dagre.graphlib.Graph({ multigraph: false, compound: false });
|
|
20
|
+
g.setGraph({
|
|
21
|
+
// Some layout options:
|
|
22
|
+
rankdir: 'TB', // "top-bottom" layering
|
|
23
|
+
nodesep: xSpacing * 0.5, // horizontal spacing
|
|
24
|
+
ranksep: ySpacing, // vertical spacing between levels
|
|
25
|
+
marginx: 20, // how much margin to leave around the left/right
|
|
26
|
+
marginy: 20, // how much margin to leave around the top/bottom
|
|
27
|
+
});
|
|
28
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
29
|
+
// 2) Add nodes to the graph
|
|
30
|
+
// Dagre requires each node have an id and some approximate width/height
|
|
31
|
+
workflow.nodes.forEach((node) => {
|
|
32
|
+
const nodeId = node.getRef();
|
|
33
|
+
// For a typical text-based node, approximate width & height
|
|
34
|
+
g.setNode(nodeId, {
|
|
35
|
+
label: nodeId,
|
|
36
|
+
width: 100,
|
|
37
|
+
height: 50
|
|
52
38
|
});
|
|
53
|
-
// Step 3: For all other nodes, position them based on their parent(s)
|
|
54
|
-
const nodesToPosition = workflow.nodes.filter((node) => !startingNodes.includes(node));
|
|
55
|
-
nodesToPosition.forEach((node) => positionNode(node, workflow.edges, xSpacing, ySpacing, workflow));
|
|
56
|
-
}
|
|
57
|
-
catch (e) {
|
|
58
|
-
console.error(e);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
export function positionNode(node, edges, xSpacing, ySpacing, workflow) {
|
|
62
|
-
const parents = getParents(node, edges);
|
|
63
|
-
if (!parents.length)
|
|
64
|
-
return; // Edge case: no parents?
|
|
65
|
-
// Sort children of the first parent by edge labels (true/false) so "true" is left, "false" is right
|
|
66
|
-
const children = getChildren(parents[0], edges).sort((a, b) => {
|
|
67
|
-
var _a, _b;
|
|
68
|
-
const edgeA = edges.find((edge) => edge.source === parents[0] && edge.target === a);
|
|
69
|
-
const edgeB = edges.find((edge) => edge.source === parents[0] && edge.target === b);
|
|
70
|
-
const labelA = (_a = edgeA === null || edgeA === void 0 ? void 0 : edgeA.label) !== null && _a !== void 0 ? _a : "";
|
|
71
|
-
const labelB = (_b = edgeB === null || edgeB === void 0 ? void 0 : edgeB.label) !== null && _b !== void 0 ? _b : "";
|
|
72
|
-
if (labelA === "true" && labelB !== "true")
|
|
73
|
-
return -1;
|
|
74
|
-
if (labelB === "true" && labelA !== "true")
|
|
75
|
-
return 1;
|
|
76
|
-
if (labelA === "false" && labelB !== "false")
|
|
77
|
-
return 1;
|
|
78
|
-
if (labelB === "false" && labelA !== "false")
|
|
79
|
-
return -1;
|
|
80
|
-
return 0;
|
|
81
39
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
while (queue.length > 0) {
|
|
103
|
-
const child = queue.shift();
|
|
104
|
-
const parents = getParents(child, workflow.edges);
|
|
105
|
-
for (const parent of parents) {
|
|
106
|
-
const children = getChildren(parent, workflow.edges);
|
|
107
|
-
if (children.length) {
|
|
108
|
-
const sumX = children.reduce((acc, c) => { var _a, _b; return acc + ((_b = (_a = c.position) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : 0); }, 0);
|
|
109
|
-
const avgX = sumX / children.length;
|
|
110
|
-
parent.setPosition(avgX, (_b = (_a = parent.position) === null || _a === void 0 ? void 0 : _a.y) !== null && _b !== void 0 ? _b : 0);
|
|
111
|
-
}
|
|
112
|
-
// Push this parent upward in the queue to continue up the chain
|
|
113
|
-
if (!queue.includes(parent)) {
|
|
114
|
-
queue.push(parent);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Recursively shifts a node and all its descendants by `shift` in the x-direction.
|
|
121
|
-
*/
|
|
122
|
-
function moveNodeAndChildren(node, shift, edges) {
|
|
123
|
-
node.setPosition(node.position.x + shift, node.position.y);
|
|
124
|
-
edges
|
|
125
|
-
.filter((edge) => edge.source === node)
|
|
126
|
-
.forEach((edge) => {
|
|
127
|
-
moveNodeAndChildren(edge.target, shift, edges);
|
|
40
|
+
// 3) Add edges
|
|
41
|
+
// Dagre identifies edges by (sourceId, targetId). We don’t need labels for layout,
|
|
42
|
+
// but we can store them if we want. If you rely on “true/false” ordering, we can tweak
|
|
43
|
+
// e.g. use edge label as a tie-break. (See advanced docs.)
|
|
44
|
+
workflow.edges.forEach((edge) => {
|
|
45
|
+
var _a;
|
|
46
|
+
const fromId = edge.source.getRef();
|
|
47
|
+
const toId = edge.target.getRef();
|
|
48
|
+
g.setEdge(fromId, toId, { label: (_a = edge.label) !== null && _a !== void 0 ? _a : '' });
|
|
49
|
+
});
|
|
50
|
+
// 4) Run Dagre layout
|
|
51
|
+
dagre.layout(g);
|
|
52
|
+
// 5) Extract positions from Dagre graph and store them back to your Node objects
|
|
53
|
+
g.nodes().forEach((nodeId) => {
|
|
54
|
+
const dagreNode = g.node(nodeId); // { x, y, width, height, ... }
|
|
55
|
+
const nodeObj = workflow.nodes.find((n) => n.getRef() === nodeId);
|
|
56
|
+
if (!nodeObj || !dagreNode)
|
|
57
|
+
return;
|
|
58
|
+
// Store x,y in your node’s position
|
|
59
|
+
nodeObj.setPosition(dagreNode.x, dagreNode.y);
|
|
128
60
|
});
|
|
129
61
|
}
|
|
130
62
|
/**
|
|
131
|
-
*
|
|
132
|
-
|
|
133
|
-
export function identifyLeafNodes(workflow) {
|
|
134
|
-
const sources = new Set(workflow.edges.map((edge) => edge.source.getRef()));
|
|
135
|
-
return workflow.nodes.filter((node) => !sources.has(node.getRef()));
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Identify starting nodes (no incoming edges).
|
|
63
|
+
* We keep these helper functions the same for external usage (if your code or tests need them),
|
|
64
|
+
* but we’re not actively using them in the new Dagre-based layout.
|
|
139
65
|
*/
|
|
140
|
-
export function identityStartingNodes(workflow) {
|
|
141
|
-
const childRefs = new Set(workflow.edges.map((edge) => edge.target.getRef()));
|
|
142
|
-
return workflow.nodes.filter((node) => !childRefs.has(node.getRef()));
|
|
143
|
-
}
|
|
144
66
|
export function getChildren(node, edges) {
|
|
145
67
|
return edges
|
|
146
68
|
.filter((edge) => edge.source === node)
|
|
@@ -165,3 +87,16 @@ export function getEndNodePositions(workflow) {
|
|
|
165
87
|
});
|
|
166
88
|
});
|
|
167
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* In Dagre, we no longer do an explicit “identify leaf nodes” or “center parents by average,”
|
|
92
|
+
* because the layout library handles that.
|
|
93
|
+
* But here’s your old method if something else depends on it.
|
|
94
|
+
*/
|
|
95
|
+
export function identifyLeafNodes(workflow) {
|
|
96
|
+
const sources = new Set(workflow.edges.map((edge) => edge.source.getRef()));
|
|
97
|
+
return workflow.nodes.filter((node) => !sources.has(node.getRef()));
|
|
98
|
+
}
|
|
99
|
+
export function identityStartingNodes(workflow) {
|
|
100
|
+
const childRefs = new Set(workflow.edges.map((edge) => edge.target.getRef()));
|
|
101
|
+
return workflow.nodes.filter((node) => !childRefs.has(node.getRef()));
|
|
102
|
+
}
|
|
@@ -38,7 +38,7 @@ export declare class Workflow {
|
|
|
38
38
|
* @param edgeValueAfter (Optional) Value for the edge nodeToInsert->nodeAfter
|
|
39
39
|
*/
|
|
40
40
|
insertNode(nodeToInsert: Node, nodeBefore: Node, nodeAfter?: Node, edgeLabelBefore?: string | null, edgeValueBefore?: any | null, edgeLabelAfter?: string | null, edgeValueAfter?: any | null): void;
|
|
41
|
-
insertCondition(nodeToInsert: Node, nodeBefore: Node, nodeAfter?: Node, addElseCase?: boolean): void;
|
|
41
|
+
insertCondition(nodeToInsert: Node, nodeBefore: Node, nodeAfter?: Node, addElseCase?: boolean, addEmptyNodes?: boolean): void;
|
|
42
42
|
/**
|
|
43
43
|
* Inserts a "split" node, creating `numberOfBranches` parallel branches.
|
|
44
44
|
*
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import { Workflow } from '../models/Workflow.js';
|
|
2
2
|
import { Node } from '../models/Node.js';
|
|
3
3
|
import { Edge } from '../models/Edge.js';
|
|
4
|
-
export declare const xSpacing =
|
|
5
|
-
export declare const ySpacing =
|
|
4
|
+
export declare const xSpacing = 700;
|
|
5
|
+
export declare const ySpacing = 75;
|
|
6
6
|
export declare const ROOT_X = 400;
|
|
7
7
|
export declare const ROOT_Y = 120;
|
|
8
|
-
export declare function positionWorkflowNodesAvoidOverlap(workflow: Workflow): void;
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Dagre-based layout for the “top-down” pass. We’re effectively ignoring the old
|
|
10
|
+
* manual code. Instead, we:
|
|
11
|
+
* 1) Build a dagre graph
|
|
12
|
+
* 2) Add all nodes & edges
|
|
13
|
+
* 3) dagre.layout(g)
|
|
14
|
+
* 4) Extract positions & write them back to workflow.nodes
|
|
12
15
|
*/
|
|
13
16
|
export declare function positionWorkflowNodes(workflow: Workflow): void;
|
|
14
|
-
export declare function positionNode(node: Node, edges: Edge[], xSpacing: number, ySpacing: number, workflow: Workflow): void;
|
|
15
17
|
/**
|
|
16
|
-
*
|
|
18
|
+
* We keep these helper functions the same for external usage (if your code or tests need them),
|
|
19
|
+
* but we’re not actively using them in the new Dagre-based layout.
|
|
17
20
|
*/
|
|
18
|
-
export declare function identifyLeafNodes(workflow: Workflow): Node[];
|
|
19
|
-
/**
|
|
20
|
-
* Identify starting nodes (no incoming edges).
|
|
21
|
-
*/
|
|
22
|
-
export declare function identityStartingNodes(workflow: Workflow): Node[];
|
|
23
21
|
export declare function getChildren(node: Node, edges: Edge[]): Node[];
|
|
24
22
|
export declare function getParents(node: Node, edges: Edge[]): Node[];
|
|
25
23
|
export declare function getEdges(node: Node, edges: Edge[]): Edge[];
|
|
@@ -27,3 +25,10 @@ export declare function getEndNodePositions(workflow: Workflow): {
|
|
|
27
25
|
x: number;
|
|
28
26
|
y: number;
|
|
29
27
|
}[];
|
|
28
|
+
/**
|
|
29
|
+
* In Dagre, we no longer do an explicit “identify leaf nodes” or “center parents by average,”
|
|
30
|
+
* because the layout library handles that.
|
|
31
|
+
* But here’s your old method if something else depends on it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function identifyLeafNodes(workflow: Workflow): Node[];
|
|
34
|
+
export declare function identityStartingNodes(workflow: Workflow): Node[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "otomato-sdk",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.37",
|
|
4
4
|
"description": "An SDK for building and managing automations on Otomato",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/axios": "^0.14.0",
|
|
33
33
|
"@types/chai": "^4.3.16",
|
|
34
|
+
"@types/dagre": "^0.7.52",
|
|
34
35
|
"@types/jsonwebtoken": "^9.0.6",
|
|
35
36
|
"@types/mocha": "^10.0.6",
|
|
36
37
|
"@types/mustache": "^4.2.5",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"axios": "^1.7.2",
|
|
47
|
+
"dagre": "^0.8.5",
|
|
46
48
|
"ethers": "^6.13.1",
|
|
47
49
|
"jsonwebtoken": "^9.0.2",
|
|
48
50
|
"mustache": "^4.2.0"
|