@q1k-oss/btree-workflows 0.0.1

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.
Files changed (203) hide show
  1. package/.claude/settings.local.json +31 -0
  2. package/CLAUDE.md +181 -0
  3. package/LICENSE +21 -0
  4. package/README.md +920 -0
  5. package/behaviour-tree-workflows-landing/index.html +16 -0
  6. package/behaviour-tree-workflows-landing/package-lock.json +2074 -0
  7. package/behaviour-tree-workflows-landing/package.json +31 -0
  8. package/behaviour-tree-workflows-landing/public/favicon.svg +17 -0
  9. package/behaviour-tree-workflows-landing/src/App.css +103 -0
  10. package/behaviour-tree-workflows-landing/src/App.tsx +176 -0
  11. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +89 -0
  12. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +64 -0
  13. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +64 -0
  14. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +34 -0
  15. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +107 -0
  16. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +85 -0
  17. package/behaviour-tree-workflows-landing/src/components/Header.css +50 -0
  18. package/behaviour-tree-workflows-landing/src/components/Header.tsx +26 -0
  19. package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +45 -0
  20. package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +15 -0
  21. package/behaviour-tree-workflows-landing/src/components/Toolbar.css +74 -0
  22. package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +53 -0
  23. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +67 -0
  24. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +192 -0
  25. package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +18 -0
  26. package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +96 -0
  27. package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +11 -0
  28. package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +96 -0
  29. package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +136 -0
  30. package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +549 -0
  31. package/behaviour-tree-workflows-landing/src/main.tsx +9 -0
  32. package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +18 -0
  33. package/behaviour-tree-workflows-landing/src/stubs/fs.ts +24 -0
  34. package/behaviour-tree-workflows-landing/src/stubs/path.ts +16 -0
  35. package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +6 -0
  36. package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +22 -0
  37. package/behaviour-tree-workflows-landing/tsconfig.json +25 -0
  38. package/behaviour-tree-workflows-landing/vite.config.ts +40 -0
  39. package/demo-google-sheets.ts +181 -0
  40. package/demo-runtime-variables.ts +174 -0
  41. package/demo-template.ts +208 -0
  42. package/docs/ARCHITECTURE_SUMMARY.md +613 -0
  43. package/docs/NODE_REFERENCE.md +504 -0
  44. package/docs/README.md +53 -0
  45. package/docs/custom-nodes-architecture.md +826 -0
  46. package/docs/observability.md +175 -0
  47. package/docs/yaml-specification.md +990 -0
  48. package/examples/temporal/README.md +117 -0
  49. package/examples/temporal/activities.ts +373 -0
  50. package/examples/temporal/client.ts +115 -0
  51. package/examples/temporal/python-worker/activities.py +339 -0
  52. package/examples/temporal/python-worker/requirements.txt +12 -0
  53. package/examples/temporal/python-worker/worker.py +106 -0
  54. package/examples/temporal/worker.ts +66 -0
  55. package/examples/temporal/workflows.ts +6 -0
  56. package/examples/temporal/yaml-workflow-loader.ts +105 -0
  57. package/examples/yaml-test.ts +97 -0
  58. package/examples/yaml-workflows/01-simple-sequence.yaml +25 -0
  59. package/examples/yaml-workflows/02-parallel-timeout.yaml +45 -0
  60. package/examples/yaml-workflows/03-ecommerce-checkout.yaml +94 -0
  61. package/examples/yaml-workflows/04-ai-agent-workflow.yaml +346 -0
  62. package/examples/yaml-workflows/05-order-processing.yaml +146 -0
  63. package/examples/yaml-workflows/06-activity-test.yaml +71 -0
  64. package/examples/yaml-workflows/07-activity-simple-test.yaml +43 -0
  65. package/examples/yaml-workflows/08-file-processing.yaml +141 -0
  66. package/examples/yaml-workflows/09-http-request.yaml +137 -0
  67. package/examples/yaml-workflows/README.md +211 -0
  68. package/package.json +38 -0
  69. package/src/actions/code-execution.schema.ts +27 -0
  70. package/src/actions/code-execution.ts +218 -0
  71. package/src/actions/generate-file.test.ts +516 -0
  72. package/src/actions/generate-file.ts +166 -0
  73. package/src/actions/http-request.test.ts +784 -0
  74. package/src/actions/http-request.ts +228 -0
  75. package/src/actions/index.ts +20 -0
  76. package/src/actions/parse-file.test.ts +448 -0
  77. package/src/actions/parse-file.ts +139 -0
  78. package/src/actions/python-script.test.ts +439 -0
  79. package/src/actions/python-script.ts +154 -0
  80. package/src/base-node.test.ts +511 -0
  81. package/src/base-node.ts +605 -0
  82. package/src/behavior-tree.test.ts +431 -0
  83. package/src/behavior-tree.ts +283 -0
  84. package/src/blackboard.test.ts +222 -0
  85. package/src/blackboard.ts +192 -0
  86. package/src/composites/conditional.schema.ts +19 -0
  87. package/src/composites/conditional.test.ts +309 -0
  88. package/src/composites/conditional.ts +129 -0
  89. package/src/composites/for-each.schema.ts +23 -0
  90. package/src/composites/for-each.test.ts +254 -0
  91. package/src/composites/for-each.ts +132 -0
  92. package/src/composites/index.ts +15 -0
  93. package/src/composites/memory-sequence.schema.ts +19 -0
  94. package/src/composites/memory-sequence.test.ts +223 -0
  95. package/src/composites/memory-sequence.ts +98 -0
  96. package/src/composites/parallel.schema.ts +28 -0
  97. package/src/composites/parallel.test.ts +502 -0
  98. package/src/composites/parallel.ts +157 -0
  99. package/src/composites/reactive-sequence.schema.ts +19 -0
  100. package/src/composites/reactive-sequence.test.ts +170 -0
  101. package/src/composites/reactive-sequence.ts +85 -0
  102. package/src/composites/recovery.schema.ts +19 -0
  103. package/src/composites/recovery.test.ts +366 -0
  104. package/src/composites/recovery.ts +90 -0
  105. package/src/composites/selector.schema.ts +19 -0
  106. package/src/composites/selector.test.ts +387 -0
  107. package/src/composites/selector.ts +85 -0
  108. package/src/composites/sequence.schema.ts +19 -0
  109. package/src/composites/sequence.test.ts +337 -0
  110. package/src/composites/sequence.ts +72 -0
  111. package/src/composites/sub-tree.schema.ts +21 -0
  112. package/src/composites/sub-tree.test.ts +893 -0
  113. package/src/composites/sub-tree.ts +177 -0
  114. package/src/composites/while.schema.ts +24 -0
  115. package/src/composites/while.test.ts +381 -0
  116. package/src/composites/while.ts +149 -0
  117. package/src/data-store/index.ts +10 -0
  118. package/src/data-store/memory-store.ts +161 -0
  119. package/src/data-store/types.ts +94 -0
  120. package/src/debug/breakpoint.test.ts +47 -0
  121. package/src/debug/breakpoint.ts +30 -0
  122. package/src/debug/index.ts +17 -0
  123. package/src/debug/resume-point.test.ts +49 -0
  124. package/src/debug/resume-point.ts +29 -0
  125. package/src/decorators/delay.schema.ts +21 -0
  126. package/src/decorators/delay.test.ts +261 -0
  127. package/src/decorators/delay.ts +140 -0
  128. package/src/decorators/force-result.schema.ts +32 -0
  129. package/src/decorators/force-result.test.ts +133 -0
  130. package/src/decorators/force-result.ts +63 -0
  131. package/src/decorators/index.ts +13 -0
  132. package/src/decorators/invert.schema.ts +19 -0
  133. package/src/decorators/invert.test.ts +135 -0
  134. package/src/decorators/invert.ts +42 -0
  135. package/src/decorators/keep-running.schema.ts +20 -0
  136. package/src/decorators/keep-running.test.ts +105 -0
  137. package/src/decorators/keep-running.ts +49 -0
  138. package/src/decorators/precondition.schema.ts +19 -0
  139. package/src/decorators/precondition.test.ts +351 -0
  140. package/src/decorators/precondition.ts +139 -0
  141. package/src/decorators/repeat.schema.ts +21 -0
  142. package/src/decorators/repeat.test.ts +187 -0
  143. package/src/decorators/repeat.ts +94 -0
  144. package/src/decorators/run-once.schema.ts +19 -0
  145. package/src/decorators/run-once.test.ts +140 -0
  146. package/src/decorators/run-once.ts +61 -0
  147. package/src/decorators/soft-assert.schema.ts +19 -0
  148. package/src/decorators/soft-assert.test.ts +107 -0
  149. package/src/decorators/soft-assert.ts +68 -0
  150. package/src/decorators/timeout.schema.ts +21 -0
  151. package/src/decorators/timeout.test.ts +274 -0
  152. package/src/decorators/timeout.ts +159 -0
  153. package/src/errors.test.ts +63 -0
  154. package/src/errors.ts +34 -0
  155. package/src/events.test.ts +347 -0
  156. package/src/events.ts +183 -0
  157. package/src/index.ts +80 -0
  158. package/src/integrations/index.ts +30 -0
  159. package/src/integrations/integration-action.test.ts +571 -0
  160. package/src/integrations/integration-action.ts +233 -0
  161. package/src/integrations/piece-executor.ts +320 -0
  162. package/src/observability/execution-tracker.ts +320 -0
  163. package/src/observability/index.ts +23 -0
  164. package/src/observability/sinks.ts +138 -0
  165. package/src/observability/types.ts +130 -0
  166. package/src/registry-utils.ts +147 -0
  167. package/src/registry.test.ts +466 -0
  168. package/src/registry.ts +334 -0
  169. package/src/schemas/base.schema.ts +104 -0
  170. package/src/schemas/index.ts +223 -0
  171. package/src/schemas/integration.test.ts +238 -0
  172. package/src/schemas/tree-definition.schema.ts +170 -0
  173. package/src/schemas/validation.test.ts +146 -0
  174. package/src/schemas/validation.ts +122 -0
  175. package/src/scripting/index.ts +22 -0
  176. package/src/templates/template-loader.test.ts +281 -0
  177. package/src/templates/template-loader.ts +152 -0
  178. package/src/temporal-integration.test.ts +213 -0
  179. package/src/test-nodes.ts +259 -0
  180. package/src/types.ts +503 -0
  181. package/src/utilities/index.ts +17 -0
  182. package/src/utilities/log-message.test.ts +275 -0
  183. package/src/utilities/log-message.ts +134 -0
  184. package/src/utilities/regex-extract.test.ts +138 -0
  185. package/src/utilities/regex-extract.ts +108 -0
  186. package/src/utilities/variable-resolver.test.ts +416 -0
  187. package/src/utilities/variable-resolver.ts +318 -0
  188. package/src/utils/error-handler.test.ts +117 -0
  189. package/src/utils/error-handler.ts +48 -0
  190. package/src/utils/signal-check.test.ts +234 -0
  191. package/src/utils/signal-check.ts +140 -0
  192. package/src/yaml/errors.ts +143 -0
  193. package/src/yaml/index.ts +30 -0
  194. package/src/yaml/loader.ts +39 -0
  195. package/src/yaml/parser.ts +286 -0
  196. package/src/yaml/validation/semantic-validator.ts +196 -0
  197. package/templates/google-sheets/insert-row.yaml +76 -0
  198. package/templates/notification-sender.yaml +33 -0
  199. package/templates/order-validation.yaml +44 -0
  200. package/tsconfig.json +24 -0
  201. package/vitest.config.ts +25 -0
  202. package/workflows/order-processor.yaml +59 -0
  203. package/workflows/process-order-workflow.yaml +142 -0
@@ -0,0 +1,96 @@
1
+ import React, { useRef, useEffect } from 'react';
2
+ import { EditorView, keymap } from '@codemirror/view';
3
+ import { EditorState } from '@codemirror/state';
4
+ import { basicSetup } from 'codemirror';
5
+ import { yaml } from '@codemirror/lang-yaml';
6
+ import { oneDark } from '@codemirror/theme-one-dark';
7
+ import { linter, type Diagnostic } from '@codemirror/lint';
8
+ import { validateYamlString } from '../lib/execution-engine';
9
+ import { indentWithTab } from '@codemirror/commands';
10
+ import './YamlEditor.css';
11
+
12
+ interface YamlEditorProps {
13
+ value: string;
14
+ onChange: (value: string) => void;
15
+ }
16
+
17
+ export const YamlEditor: React.FC<YamlEditorProps> = ({ value, onChange }) => {
18
+ const containerRef = useRef<HTMLDivElement>(null);
19
+ const viewRef = useRef<EditorView | null>(null);
20
+ const valueRef = useRef(value);
21
+
22
+ // Keep ref current
23
+ valueRef.current = value;
24
+
25
+ useEffect(() => {
26
+ if (!containerRef.current) return;
27
+
28
+ const yamlLinter = linter((view) => {
29
+ const text = view.state.doc.toString();
30
+ if (!text.trim()) return [];
31
+
32
+ const result = validateYamlString(text);
33
+ if (result.valid) return [];
34
+
35
+ const diagnostics: Diagnostic[] = result.errors.map((errMsg) => ({
36
+ from: 0,
37
+ to: Math.min(text.length, 1),
38
+ severity: 'error' as const,
39
+ message: errMsg,
40
+ }));
41
+ return diagnostics;
42
+ }, { delay: 500 });
43
+
44
+ const state = EditorState.create({
45
+ doc: value,
46
+ extensions: [
47
+ basicSetup,
48
+ yaml(),
49
+ oneDark,
50
+ yamlLinter,
51
+ keymap.of([indentWithTab]),
52
+ EditorView.updateListener.of((update) => {
53
+ if (update.docChanged) {
54
+ const newVal = update.state.doc.toString();
55
+ onChange(newVal);
56
+ }
57
+ }),
58
+ EditorView.theme({
59
+ '&': { height: '100%', fontSize: '13px' },
60
+ '.cm-scroller': { fontFamily: "'JetBrains Mono', monospace" },
61
+ '.cm-content': { padding: '8px 0' },
62
+ }),
63
+ ],
64
+ });
65
+
66
+ const view = new EditorView({
67
+ state,
68
+ parent: containerRef.current,
69
+ });
70
+
71
+ viewRef.current = view;
72
+
73
+ return () => {
74
+ view.destroy();
75
+ viewRef.current = null;
76
+ };
77
+ // Only run on mount
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, []);
80
+
81
+ // Sync external value changes
82
+ useEffect(() => {
83
+ const view = viewRef.current;
84
+ if (!view) return;
85
+ const currentText = view.state.doc.toString();
86
+ if (currentText !== value) {
87
+ view.dispatch({
88
+ changes: { from: 0, to: currentText.length, insert: value },
89
+ });
90
+ }
91
+ }, [value]);
92
+
93
+ return (
94
+ <div className="yaml-editor" ref={containerRef} />
95
+ );
96
+ };
@@ -0,0 +1,11 @@
1
+ import type { TreeNode } from '@btree/types';
2
+
3
+ export function countNodes(node: TreeNode): number {
4
+ let count = 1;
5
+ if (node.children) {
6
+ for (const child of node.children) {
7
+ count += countNodes(child);
8
+ }
9
+ }
10
+ return count;
11
+ }
@@ -0,0 +1,96 @@
1
+ import { Registry, registerStandardNodes } from '@btree/index';
2
+ import { ScopedBlackboard } from '@btree/blackboard';
3
+ import { NodeEventEmitter } from '@btree/events';
4
+ import { loadTreeFromYaml, validateYaml } from '@btree/yaml/index';
5
+ import type { TreeNode, TemporalContext } from '@btree/types';
6
+ import { NodeStatus } from '@btree/types';
7
+ import type { NodeEvent } from '@btree/events';
8
+ import { countNodes } from './count-nodes';
9
+
10
+ let _registry: Registry | null = null;
11
+
12
+ export function getRegistry(): Registry {
13
+ if (!_registry) {
14
+ _registry = new Registry();
15
+ registerStandardNodes(_registry);
16
+ }
17
+ return _registry;
18
+ }
19
+
20
+ export interface ParseResult {
21
+ tree: TreeNode | null;
22
+ error: string | null;
23
+ nodeCount: number;
24
+ }
25
+
26
+ export function parseYamlToTree(yamlString: string): ParseResult {
27
+ const registry = getRegistry();
28
+ try {
29
+ const tree = loadTreeFromYaml(yamlString, registry);
30
+ return { tree, error: null, nodeCount: countNodes(tree) };
31
+ } catch (err) {
32
+ return {
33
+ tree: null,
34
+ error: err instanceof Error ? err.message : String(err),
35
+ nodeCount: 0,
36
+ };
37
+ }
38
+ }
39
+
40
+ export function validateYamlString(yamlString: string): { valid: boolean; errors: string[] } {
41
+ const registry = getRegistry();
42
+ const result = validateYaml(yamlString, registry, { collectAllErrors: true });
43
+ return {
44
+ valid: result.valid,
45
+ errors: result.errors.map((e) => e.message),
46
+ };
47
+ }
48
+
49
+ export interface ExecutionResult {
50
+ status: NodeStatus;
51
+ blackboard: Record<string, unknown>;
52
+ events: NodeEvent<unknown>[];
53
+ error?: string;
54
+ }
55
+
56
+ export type EventCallback = (event: NodeEvent<unknown>) => void;
57
+
58
+ export async function executeTree(
59
+ tree: TreeNode,
60
+ onEvent?: EventCallback,
61
+ ): Promise<ExecutionResult> {
62
+ const blackboard = new ScopedBlackboard();
63
+ const eventEmitter = new NodeEventEmitter();
64
+ const events: NodeEvent<unknown>[] = [];
65
+
66
+ eventEmitter.onAll((event: NodeEvent<unknown>) => {
67
+ events.push(event);
68
+ onEvent?.(event);
69
+ });
70
+
71
+ const registry = getRegistry();
72
+
73
+ const context: TemporalContext = {
74
+ blackboard,
75
+ treeRegistry: registry,
76
+ timestamp: Date.now(),
77
+ eventEmitter,
78
+ sessionId: `playground-${Date.now()}`,
79
+ };
80
+
81
+ try {
82
+ const status = await tree.tick(context);
83
+ return {
84
+ status,
85
+ blackboard: blackboard.toJSON(),
86
+ events,
87
+ };
88
+ } catch (err) {
89
+ return {
90
+ status: NodeStatus.FAILURE,
91
+ blackboard: blackboard.toJSON(),
92
+ events,
93
+ error: err instanceof Error ? err.message : String(err),
94
+ };
95
+ }
96
+ }
@@ -0,0 +1,136 @@
1
+ import type { TreeNode } from '@btree/types';
2
+
3
+ export interface LayoutNode {
4
+ id: string;
5
+ name: string;
6
+ type: string;
7
+ category: 'composite' | 'decorator' | 'action' | 'condition';
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ children: LayoutNode[];
13
+ status: string;
14
+ }
15
+
16
+ export interface LayoutEdge {
17
+ fromX: number;
18
+ fromY: number;
19
+ toX: number;
20
+ toY: number;
21
+ }
22
+
23
+ const NODE_WIDTH = 160;
24
+ const NODE_HEIGHT = 44;
25
+ const H_GAP = 16;
26
+ const V_GAP = 56;
27
+
28
+ const COMPOSITE_TYPES = new Set([
29
+ 'Sequence', 'Selector', 'Parallel', 'Conditional', 'ForEach',
30
+ 'While', 'Recovery', 'ReactiveSequence', 'MemorySequence', 'SubTree',
31
+ ]);
32
+
33
+ const DECORATOR_TYPES = new Set([
34
+ 'Timeout', 'Delay', 'Repeat', 'Invert', 'ForceSuccess', 'ForceFailure',
35
+ 'RunOnce', 'KeepRunningUntilFailure', 'Precondition', 'SoftAssert',
36
+ ]);
37
+
38
+ const CONDITION_TYPES = new Set([
39
+ 'CheckCondition', 'AlwaysCondition',
40
+ ]);
41
+
42
+ function getCategory(type: string): LayoutNode['category'] {
43
+ if (COMPOSITE_TYPES.has(type)) return 'composite';
44
+ if (DECORATOR_TYPES.has(type)) return 'decorator';
45
+ if (CONDITION_TYPES.has(type)) return 'condition';
46
+ return 'action';
47
+ }
48
+
49
+ function computeSubtreeWidth(node: TreeNode): number {
50
+ if (!node.children || node.children.length === 0) {
51
+ return NODE_WIDTH;
52
+ }
53
+ const childrenWidth = node.children.reduce(
54
+ (sum, child) => sum + computeSubtreeWidth(child),
55
+ 0,
56
+ );
57
+ return Math.max(NODE_WIDTH, childrenWidth + H_GAP * (node.children.length - 1));
58
+ }
59
+
60
+ function layoutNode(
61
+ node: TreeNode,
62
+ x: number,
63
+ y: number,
64
+ nodeStates: Map<string, string>,
65
+ ): LayoutNode {
66
+ const subtreeWidth = computeSubtreeWidth(node);
67
+ const nodeX = x + subtreeWidth / 2 - NODE_WIDTH / 2;
68
+
69
+ const layoutChildren: LayoutNode[] = [];
70
+ if (node.children && node.children.length > 0) {
71
+ let childX = x;
72
+ const childY = y + NODE_HEIGHT + V_GAP;
73
+ for (const child of node.children) {
74
+ const childWidth = computeSubtreeWidth(child);
75
+ layoutChildren.push(layoutNode(child, childX, childY, nodeStates));
76
+ childX += childWidth + H_GAP;
77
+ }
78
+ }
79
+
80
+ return {
81
+ id: node.id,
82
+ name: node.name || node.id,
83
+ type: node.type,
84
+ category: getCategory(node.type),
85
+ x: nodeX,
86
+ y,
87
+ width: NODE_WIDTH,
88
+ height: NODE_HEIGHT,
89
+ children: layoutChildren,
90
+ status: nodeStates.get(node.id) || 'idle',
91
+ };
92
+ }
93
+
94
+ export function layoutTree(
95
+ root: TreeNode,
96
+ nodeStates: Map<string, string> = new Map(),
97
+ ): { root: LayoutNode; width: number; height: number; edges: LayoutEdge[] } {
98
+ const layoutRoot = layoutNode(root, 0, 0, nodeStates);
99
+
100
+ function getMaxY(node: LayoutNode): number {
101
+ let maxY = node.y + node.height;
102
+ for (const child of node.children) {
103
+ maxY = Math.max(maxY, getMaxY(child));
104
+ }
105
+ return maxY;
106
+ }
107
+
108
+ function getMaxX(node: LayoutNode): number {
109
+ let maxX = node.x + node.width;
110
+ for (const child of node.children) {
111
+ maxX = Math.max(maxX, getMaxX(child));
112
+ }
113
+ return maxX;
114
+ }
115
+
116
+ const edges: LayoutEdge[] = [];
117
+ function collectEdges(node: LayoutNode) {
118
+ for (const child of node.children) {
119
+ edges.push({
120
+ fromX: node.x + node.width / 2,
121
+ fromY: node.y + node.height,
122
+ toX: child.x + child.width / 2,
123
+ toY: child.y,
124
+ });
125
+ collectEdges(child);
126
+ }
127
+ }
128
+ collectEdges(layoutRoot);
129
+
130
+ return {
131
+ root: layoutRoot,
132
+ width: getMaxX(layoutRoot),
133
+ height: getMaxY(layoutRoot),
134
+ edges,
135
+ };
136
+ }