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.
@@ -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, positionWorkflowNodesAvoidOverlap } from '../utils/WorkflowNodePositioner.js';
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
- positionWorkflowNodesAvoidOverlap(this);
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
- positionWorkflowNodesAvoidOverlap(this);
35
+ positionWorkflowNodes(this);
36
36
  }
37
37
  addNodes(nodes) {
38
38
  this.nodes.push(...nodes);
39
- positionWorkflowNodesAvoidOverlap(this);
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
- positionWorkflowNodesAvoidOverlap(this);
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
- positionWorkflowNodesAvoidOverlap(this);
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
- positionWorkflowNodesAvoidOverlap(this);
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
- const emptyNode1 = new Action(ACTIONS.CORE.EMPTYBLOCK.EMPTYBLOCK);
145
- this.insertNode(emptyNode1, nodeToInsert, undefined, 'true', 'true');
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
- positionWorkflowNodesAvoidOverlap(this);
227
+ positionWorkflowNodes(this);
226
228
  }
227
229
  addEdge(edge) {
228
230
  this.edges.push(edge);
229
- positionWorkflowNodesAvoidOverlap(this);
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
- positionWorkflowNodesAvoidOverlap(this);
242
+ positionWorkflowNodes(this);
241
243
  }
242
244
  addEdges(edges) {
243
245
  this.edges.push(...edges);
244
- positionWorkflowNodesAvoidOverlap(this);
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
- positionWorkflowNodesAvoidOverlap(this);
375
+ positionWorkflowNodes(this);
374
376
  return this;
375
377
  }
376
378
  catch (error) {
@@ -1,146 +1,68 @@
1
- export const xSpacing = 500;
2
- export const ySpacing = 120;
3
- export const ROOT_X = 400;
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
- * Lays out the workflow nodes in a rough top-down manner,
41
- * then positions each node relative to its parent.
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
- try {
45
- // Step 1: Find the starting nodes
46
- const startingNodes = identityStartingNodes(workflow);
47
- // Step 2: Place the starting nodes
48
- let xPosition = ROOT_X;
49
- startingNodes.forEach((startNode) => {
50
- startNode.setPosition(xPosition, ROOT_Y);
51
- xPosition += xSpacing;
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
- const parentX = parents.reduce((sum, p) => { var _a, _b; return sum + ((_b = (_a = p.position) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : ROOT_X); }, 0) /
83
- parents.length;
84
- const parentY = Math.max(...parents.map((p) => { var _a, _b; return (_b = (_a = p.position) === null || _a === void 0 ? void 0 : _a.y) !== null && _b !== void 0 ? _b : ROOT_Y; }));
85
- if (children.length <= 1) {
86
- node.setPosition(parentX, parentY + ySpacing);
87
- }
88
- else {
89
- // This node’s index among siblings
90
- const index = children.indexOf(node);
91
- const offset = index - (children.length - 1) / 2;
92
- node.setPosition(parentX + offset * xSpacing, parentY + ySpacing);
93
- }
94
- }
95
- /**
96
- * Repositions each parent node so that its X = average of its children’s X.
97
- */
98
- function centerParentXPositions(workflow) {
99
- var _a, _b;
100
- // Identify the leaf nodes to start a bottom-up pass
101
- const queue = identifyLeafNodes(workflow);
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
- * A "leaf" node is one that has no children (it’s never the source of an edge).
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
+ }
@@ -1,4 +1,4 @@
1
1
  /*************************************
2
- * 4. Main Workflow Builder
2
+ * 6. Main Workflow Builder
3
3
  *************************************/
4
4
  export declare function aggregatorWorkflow(): Promise<void>;
@@ -0,0 +1,4 @@
1
+ /*************************************
2
+ * 5. Main Workflow Builder (No manual edges or pushes)
3
+ *************************************/
4
+ export declare function lendingAggregatorWorkflow(): Promise<void>;
@@ -0,0 +1,4 @@
1
+ /*************************************
2
+ * 6. Main Workflow Builder
3
+ *************************************/
4
+ export declare function aggregatorWorkflow(): Promise<void>;
@@ -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 = 500;
5
- export declare const ySpacing = 120;
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
- * Lays out the workflow nodes in a rough top-down manner,
11
- * then positions each node relative to its parent.
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
- * A "leaf" node is one that has no children (it’s never the source of an edge).
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.36",
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"