@k-4u/resource-mapper-core 0.0.1 → 0.1.0

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 (248) hide show
  1. package/coverage/junit.xml +17 -17
  2. package/coverage/test-results.json +1 -1
  3. package/data/services/api/api-gateway.yaml +18 -18
  4. package/data/services/api/group-info.yaml +7 -7
  5. package/data/services/api/lambda-orders.yaml +21 -21
  6. package/data/services/api/lambda-products.yaml +15 -15
  7. package/data/services/api/lambda-users.yaml +15 -15
  8. package/data/services/compute/alb.yaml +15 -15
  9. package/data/services/compute/ecs-inventory.yaml +16 -16
  10. package/data/services/compute/ecs-notification.yaml +15 -15
  11. package/data/services/compute/group-info.yaml +7 -7
  12. package/data/services/data/dynamodb-notifications.yaml +12 -12
  13. package/data/services/data/dynamodb-orders.yaml +9 -9
  14. package/data/services/data/dynamodb-products.yaml +9 -9
  15. package/data/services/data/dynamodb-users.yaml +9 -9
  16. package/data/services/data/group-info.yaml +7 -7
  17. package/data/services/data/rds-postgres.yaml +9 -9
  18. package/data/services/data/redis.yaml +10 -10
  19. package/data/services/frontend/cloudfront.yaml +12 -12
  20. package/data/services/frontend/group-info.yaml +7 -7
  21. package/data/services/frontend/route53.yaml +15 -15
  22. package/data/services/frontend/s3-website.yaml +9 -9
  23. package/data/teams/cloud-shepherds.yaml +15 -15
  24. package/data/teams/data-wizards.yaml +15 -15
  25. package/data/teams/interface-architects.yaml +18 -18
  26. package/e2e/demo.test.ts +54 -54
  27. package/e2e/header-toolbar.spec.ts +53 -53
  28. package/e2e/layout.spec.ts +30 -30
  29. package/package.json +74 -69
  30. package/playwright.config.ts +10 -10
  31. package/plugins/mapper-data-plugin.ts +32 -32
  32. package/project.json +22 -22
  33. package/src/app.css +125 -125
  34. package/src/app.d.ts +31 -31
  35. package/src/app.html +11 -11
  36. package/src/lib/assets/favicon.svg +18 -18
  37. package/src/lib/components/EmptyState.svelte +37 -37
  38. package/src/lib/components/ErrorDisplay.svelte +82 -82
  39. package/src/lib/components/FlowCanvas.svelte +223 -223
  40. package/src/lib/components/GenericSidebarCard.svelte +43 -43
  41. package/src/lib/components/GroupDetailSidebar.svelte +31 -31
  42. package/src/lib/components/Header.svelte +57 -57
  43. package/src/lib/components/Legend.svelte +25 -25
  44. package/src/lib/components/LoadingOverlay.svelte +42 -42
  45. package/src/lib/components/LoadingSpinner.svelte +10 -10
  46. package/src/lib/components/ServiceDetailSidebar.svelte +89 -89
  47. package/src/lib/components/TeamContactCard.svelte +166 -166
  48. package/src/lib/components/flow/ExternalNode.svelte +45 -45
  49. package/src/lib/components/flow/MainGroupNode.svelte +24 -24
  50. package/src/lib/components/flow/ServiceGroupNode.svelte +17 -17
  51. package/src/lib/components/flow/ServiceNode.svelte +40 -40
  52. package/src/lib/components/flow/SnakeEdge.svelte +206 -206
  53. package/src/lib/components/flow/index.ts +5 -5
  54. package/src/lib/components/index.ts +12 -12
  55. package/src/lib/data/connections.ts +26 -26
  56. package/src/lib/data/groups.ts +11 -11
  57. package/src/lib/data/services.ts +12 -12
  58. package/src/lib/data/teams.ts +11 -11
  59. package/src/lib/index.ts +1 -1
  60. package/src/lib/state/theme.svelte.ts +21 -21
  61. package/src/lib/stores/diagram.ts +6 -6
  62. package/src/lib/stores/routingStore.test.ts +133 -133
  63. package/src/lib/stores/routingStore.ts +232 -232
  64. package/src/lib/utils/awsIcons.ts +117 -117
  65. package/src/lib/utils/flow/groupOverviewGraph.ts +73 -73
  66. package/src/lib/utils/flow/helpers.ts +14 -14
  67. package/src/lib/utils/flow/layout.test.ts +271 -271
  68. package/src/lib/utils/flow/layout.ts +240 -240
  69. package/src/lib/utils/flow/serviceIds.ts +4 -4
  70. package/src/lib/utils/flow/servicesGraph.test.ts +119 -119
  71. package/src/lib/utils/flow/servicesGraph.ts +258 -258
  72. package/src/routes/+error.svelte +36 -36
  73. package/src/routes/+layout.svelte +27 -27
  74. package/src/routes/+page.svelte +81 -81
  75. package/src/routes/+page.ts +31 -31
  76. package/src/routes/group/[groupId]/+page.svelte +102 -102
  77. package/src/routes/group/[groupId]/+page.ts +48 -48
  78. package/static/static/robots.txt +3 -3
  79. package/svelte.config.js +27 -27
  80. package/tailwind.config.js +12 -12
  81. package/tsconfig.json +22 -22
  82. package/vite.config.ts +80 -80
  83. package/.aws-icons-last-updated +0 -1
  84. package/.svelte-kit/ambient.d.ts +0 -263
  85. package/.svelte-kit/generated/client/app.js +0 -31
  86. package/.svelte-kit/generated/client/matchers.js +0 -1
  87. package/.svelte-kit/generated/client/nodes/0.js +0 -1
  88. package/.svelte-kit/generated/client/nodes/1.js +0 -1
  89. package/.svelte-kit/generated/client/nodes/2.js +0 -3
  90. package/.svelte-kit/generated/client/nodes/3.js +0 -3
  91. package/.svelte-kit/generated/client-optimized/app.js +0 -31
  92. package/.svelte-kit/generated/client-optimized/matchers.js +0 -1
  93. package/.svelte-kit/generated/client-optimized/nodes/0.js +0 -1
  94. package/.svelte-kit/generated/client-optimized/nodes/1.js +0 -1
  95. package/.svelte-kit/generated/client-optimized/nodes/2.js +0 -3
  96. package/.svelte-kit/generated/client-optimized/nodes/3.js +0 -3
  97. package/.svelte-kit/generated/root.js +0 -3
  98. package/.svelte-kit/generated/root.svelte +0 -68
  99. package/.svelte-kit/generated/server/internal.js +0 -53
  100. package/.svelte-kit/non-ambient.d.ts +0 -43
  101. package/.svelte-kit/output/client/.vite/manifest.json +0 -175
  102. package/.svelte-kit/output/client/_app/immutable/assets/0.Czt_67iE.css +0 -1
  103. package/.svelte-kit/output/client/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +0 -1
  104. package/.svelte-kit/output/client/_app/immutable/assets/helpers.ysDrpaDf.css +0 -1
  105. package/.svelte-kit/output/client/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
  106. package/.svelte-kit/output/client/_app/immutable/chunks/BlLuv0eP.js +0 -46
  107. package/.svelte-kit/output/client/_app/immutable/chunks/CSBHmwYv.js +0 -1
  108. package/.svelte-kit/output/client/_app/immutable/chunks/CTCi5ueQ.js +0 -1
  109. package/.svelte-kit/output/client/_app/immutable/chunks/CfOzjaik.js +0 -2
  110. package/.svelte-kit/output/client/_app/immutable/chunks/D4PdvFNs.js +0 -1
  111. package/.svelte-kit/output/client/_app/immutable/chunks/DXgP-QUS.js +0 -2
  112. package/.svelte-kit/output/client/_app/immutable/chunks/DlbDC5An.js +0 -1
  113. package/.svelte-kit/output/client/_app/immutable/chunks/wRWe7aK9.js +0 -1
  114. package/.svelte-kit/output/client/_app/immutable/entry/app.ConrMuHl.js +0 -2
  115. package/.svelte-kit/output/client/_app/immutable/entry/start.Bm6FyGme.js +0 -1
  116. package/.svelte-kit/output/client/_app/immutable/nodes/0.d3cL-ETU.js +0 -1
  117. package/.svelte-kit/output/client/_app/immutable/nodes/1.D6z9rPGv.js +0 -1
  118. package/.svelte-kit/output/client/_app/immutable/nodes/2.CLD-8chl.js +0 -1
  119. package/.svelte-kit/output/client/_app/immutable/nodes/3.DXYeBoel.js +0 -1
  120. package/.svelte-kit/output/client/_app/version.json +0 -1
  121. package/.svelte-kit/output/client/libavoid.wasm +0 -0
  122. package/.svelte-kit/output/client/static/robots.txt +0 -3
  123. package/.svelte-kit/output/prerendered/dependencies/_app/env.js +0 -1
  124. package/.svelte-kit/output/server/.vite/manifest.json +0 -224
  125. package/.svelte-kit/output/server/_app/immutable/assets/LoadingOverlay.DBbe6V8W.css +0 -1
  126. package/.svelte-kit/output/server/_app/immutable/assets/_layout.Czt_67iE.css +0 -1
  127. package/.svelte-kit/output/server/_app/immutable/assets/_page.D9P41uDZ.css +0 -1
  128. package/.svelte-kit/output/server/chunks/ErrorDisplay.js +0 -59
  129. package/.svelte-kit/output/server/chunks/LoadingOverlay.js +0 -12
  130. package/.svelte-kit/output/server/chunks/LoadingOverlay.svelte_svelte_type_style_lang.js +0 -1671
  131. package/.svelte-kit/output/server/chunks/connections.js +0 -33
  132. package/.svelte-kit/output/server/chunks/diagram.js +0 -7
  133. package/.svelte-kit/output/server/chunks/environment.js +0 -34
  134. package/.svelte-kit/output/server/chunks/equality.js +0 -57
  135. package/.svelte-kit/output/server/chunks/exports.js +0 -174
  136. package/.svelte-kit/output/server/chunks/false.js +0 -4
  137. package/.svelte-kit/output/server/chunks/index.js +0 -59
  138. package/.svelte-kit/output/server/chunks/index2.js +0 -2939
  139. package/.svelte-kit/output/server/chunks/index3.js +0 -20
  140. package/.svelte-kit/output/server/chunks/internal.js +0 -1017
  141. package/.svelte-kit/output/server/chunks/shared.js +0 -770
  142. package/.svelte-kit/output/server/chunks/utils.js +0 -43
  143. package/.svelte-kit/output/server/entries/pages/_error.svelte.js +0 -64
  144. package/.svelte-kit/output/server/entries/pages/_layout.svelte.js +0 -65
  145. package/.svelte-kit/output/server/entries/pages/_page.svelte.js +0 -3991
  146. package/.svelte-kit/output/server/entries/pages/_page.ts.js +0 -30
  147. package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.svelte.js +0 -67
  148. package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.ts.js +0 -47
  149. package/.svelte-kit/output/server/index.js +0 -3747
  150. package/.svelte-kit/output/server/internal.js +0 -13
  151. package/.svelte-kit/output/server/manifest-full.js +0 -47
  152. package/.svelte-kit/output/server/manifest.js +0 -47
  153. package/.svelte-kit/output/server/nodes/0.js +0 -8
  154. package/.svelte-kit/output/server/nodes/1.js +0 -8
  155. package/.svelte-kit/output/server/nodes/2.js +0 -10
  156. package/.svelte-kit/output/server/nodes/3.js +0 -10
  157. package/.svelte-kit/output/server/remote-entry.js +0 -557
  158. package/.svelte-kit/tsconfig.json +0 -61
  159. package/.svelte-kit/types/route_meta_data.json +0 -8
  160. package/.svelte-kit/types/src/routes/$types.d.ts +0 -26
  161. package/.svelte-kit/types/src/routes/group/[groupId]/$types.d.ts +0 -21
  162. package/.svelte-kit/types/src/routes/group/[groupId]/proxy+page.ts +0 -49
  163. package/.svelte-kit/types/src/routes/proxy+page.ts +0 -33
  164. package/build/_app/env.js +0 -1
  165. package/build/_app/env.js.br +0 -1
  166. package/build/_app/env.js.gz +0 -0
  167. package/build/_app/immutable/assets/0.Czt_67iE.css +0 -1
  168. package/build/_app/immutable/assets/0.Czt_67iE.css.br +0 -0
  169. package/build/_app/immutable/assets/0.Czt_67iE.css.gz +0 -0
  170. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +0 -1
  171. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.br +0 -0
  172. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.gz +0 -0
  173. package/build/_app/immutable/assets/helpers.ysDrpaDf.css +0 -1
  174. package/build/_app/immutable/assets/helpers.ysDrpaDf.css.br +0 -0
  175. package/build/_app/immutable/assets/helpers.ysDrpaDf.css.gz +0 -0
  176. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
  177. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.br +0 -0
  178. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.gz +0 -0
  179. package/build/_app/immutable/chunks/BlLuv0eP.js +0 -46
  180. package/build/_app/immutable/chunks/BlLuv0eP.js.br +0 -0
  181. package/build/_app/immutable/chunks/BlLuv0eP.js.gz +0 -0
  182. package/build/_app/immutable/chunks/CSBHmwYv.js +0 -1
  183. package/build/_app/immutable/chunks/CSBHmwYv.js.br +0 -0
  184. package/build/_app/immutable/chunks/CSBHmwYv.js.gz +0 -0
  185. package/build/_app/immutable/chunks/CTCi5ueQ.js +0 -1
  186. package/build/_app/immutable/chunks/CTCi5ueQ.js.br +0 -0
  187. package/build/_app/immutable/chunks/CTCi5ueQ.js.gz +0 -0
  188. package/build/_app/immutable/chunks/CfOzjaik.js +0 -2
  189. package/build/_app/immutable/chunks/CfOzjaik.js.br +0 -0
  190. package/build/_app/immutable/chunks/CfOzjaik.js.gz +0 -0
  191. package/build/_app/immutable/chunks/D4PdvFNs.js +0 -1
  192. package/build/_app/immutable/chunks/D4PdvFNs.js.br +0 -0
  193. package/build/_app/immutable/chunks/D4PdvFNs.js.gz +0 -0
  194. package/build/_app/immutable/chunks/DXgP-QUS.js +0 -2
  195. package/build/_app/immutable/chunks/DXgP-QUS.js.br +0 -0
  196. package/build/_app/immutable/chunks/DXgP-QUS.js.gz +0 -0
  197. package/build/_app/immutable/chunks/DlbDC5An.js +0 -1
  198. package/build/_app/immutable/chunks/DlbDC5An.js.br +0 -0
  199. package/build/_app/immutable/chunks/DlbDC5An.js.gz +0 -0
  200. package/build/_app/immutable/chunks/wRWe7aK9.js +0 -1
  201. package/build/_app/immutable/chunks/wRWe7aK9.js.br +0 -0
  202. package/build/_app/immutable/chunks/wRWe7aK9.js.gz +0 -0
  203. package/build/_app/immutable/entry/app.ConrMuHl.js +0 -2
  204. package/build/_app/immutable/entry/app.ConrMuHl.js.br +0 -0
  205. package/build/_app/immutable/entry/app.ConrMuHl.js.gz +0 -0
  206. package/build/_app/immutable/entry/start.Bm6FyGme.js +0 -1
  207. package/build/_app/immutable/entry/start.Bm6FyGme.js.br +0 -2
  208. package/build/_app/immutable/entry/start.Bm6FyGme.js.gz +0 -0
  209. package/build/_app/immutable/nodes/0.d3cL-ETU.js +0 -1
  210. package/build/_app/immutable/nodes/0.d3cL-ETU.js.br +0 -0
  211. package/build/_app/immutable/nodes/0.d3cL-ETU.js.gz +0 -0
  212. package/build/_app/immutable/nodes/1.D6z9rPGv.js +0 -1
  213. package/build/_app/immutable/nodes/1.D6z9rPGv.js.br +0 -0
  214. package/build/_app/immutable/nodes/1.D6z9rPGv.js.gz +0 -0
  215. package/build/_app/immutable/nodes/2.CLD-8chl.js +0 -1
  216. package/build/_app/immutable/nodes/2.CLD-8chl.js.br +0 -0
  217. package/build/_app/immutable/nodes/2.CLD-8chl.js.gz +0 -0
  218. package/build/_app/immutable/nodes/3.DXYeBoel.js +0 -1
  219. package/build/_app/immutable/nodes/3.DXYeBoel.js.br +0 -0
  220. package/build/_app/immutable/nodes/3.DXYeBoel.js.gz +0 -0
  221. package/build/_app/version.json +0 -1
  222. package/build/_app/version.json.br +0 -0
  223. package/build/_app/version.json.gz +0 -0
  224. package/build/index.html +0 -34
  225. package/build/index.html.br +0 -0
  226. package/build/index.html.gz +0 -0
  227. package/build/libavoid.wasm +0 -0
  228. package/build/libavoid.wasm.br +0 -0
  229. package/build/libavoid.wasm.gz +0 -0
  230. package/build/static/robots.txt +0 -3
  231. package/coverage/coverage-final.json +0 -6
  232. package/coverage/coverage-summary.json +0 -7
  233. package/coverage/lcov-report/base.css +0 -224
  234. package/coverage/lcov-report/block-navigation.js +0 -87
  235. package/coverage/lcov-report/favicon.png +0 -0
  236. package/coverage/lcov-report/index.html +0 -131
  237. package/coverage/lcov-report/prettify.css +0 -1
  238. package/coverage/lcov-report/prettify.js +0 -2
  239. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  240. package/coverage/lcov-report/sorter.js +0 -210
  241. package/coverage/lcov-report/stores/index.html +0 -116
  242. package/coverage/lcov-report/stores/routingStore.ts.html +0 -781
  243. package/coverage/lcov-report/utils/flow/helpers.ts.html +0 -127
  244. package/coverage/lcov-report/utils/flow/index.html +0 -161
  245. package/coverage/lcov-report/utils/flow/layout.ts.html +0 -805
  246. package/coverage/lcov-report/utils/flow/serviceIds.ts.html +0 -97
  247. package/coverage/lcov-report/utils/flow/servicesGraph.ts.html +0 -859
  248. package/coverage/lcov.info +0 -646
@@ -1,240 +1,240 @@
1
- import ELK from 'elkjs/lib/elk.bundled.js';
2
- import {type Edge, type Node} from '@xyflow/svelte';
3
- import type {FlowEdgeData, FlowGraphInput, FlowGraphOutput, FlowNodeData} from '$shared/flow-types';
4
- import type {ElkNode} from "elkjs/lib/elk-api";
5
-
6
- const elk = new ELK();
7
- const PADDING = 10;
8
- export const OFFSET_STEP = 20;
9
-
10
- /**
11
- * Helper to calculate absolute position of a node by traversing its parent chain.
12
- */
13
- export function getAbsolutePosition(nodeId: string, nodes: Node<FlowNodeData>[]): { x: number, y: number } {
14
- const node = nodes.find(n => n.id === nodeId);
15
- if (!node) return {x: 0, y: 0};
16
-
17
- let x = node.position?.x ?? 0;
18
- let y = node.position?.y ?? 0;
19
-
20
- if (node.parentId) {
21
- const parentPos = getAbsolutePosition(node.parentId, nodes);
22
- x += parentPos.x;
23
- y += parentPos.y;
24
- }
25
-
26
- return {x, y};
27
- }
28
-
29
- /**
30
- * Calculates a deterministic offset for an edge to prevent overlapping.
31
- * TODO: Replace this method with elk's built-in edge routing and spacing options once we can guarantee it works well in all cases.
32
- * This probably means that we'll have to replace it once libavoid is integrated into elk: https://github.com/kieler/elkjs/issues/210
33
- * This function handles two types of offsets:
34
- * 1. Handle Offset: For multiple edges sharing the same handle on a node.
35
- * 2. Trunk Offset: For edges that don't share a handle but might have overlapping
36
- * paths (trunks) in the same "lane".
37
- */
38
- export function calculateEdgeOffset(
39
- edgeId: string,
40
- nodes: Node<FlowNodeData>[],
41
- edges: Edge<FlowEdgeData>[],
42
- isSource: boolean
43
- ): number {
44
- const edge = edges.find(e => e.id === edgeId);
45
- if (!edge) return 0;
46
-
47
- const nodeId = isSource ? edge.source : edge.target;
48
- const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
49
-
50
- const handle = isSource ? edge.sourceHandle : edge.targetHandle;
51
- const isVertical = handle?.toLowerCase().includes('top') || handle?.toLowerCase().includes('bottom');
52
-
53
- let finalSiblings: Edge<FlowEdgeData>[] = [];
54
- let usedAreaFallback = false;
55
-
56
- // HANDLE OFFSET: Siblings on the same node and same handle
57
- const siblings = edges.filter(e =>
58
- (isSource ? e.source : e.target) === nodeId &&
59
- (isSource ? e.sourceHandle : e.targetHandle) === handleId
60
- );
61
-
62
- if (siblings.length <= 1) {
63
- // Fallback for bidirectional or single edges between same nodes
64
- usedAreaFallback = true;
65
- const otherId = isSource ? edge.target : edge.source;
66
- finalSiblings = edges.filter(e => {
67
- const isSamePair = (e.source === nodeId && e.target === otherId) ||
68
- (e.source === otherId && e.target === nodeId);
69
- return isSamePair && (e.source === nodeId || e.target === nodeId);
70
- });
71
- } else {
72
- finalSiblings = siblings;
73
- }
74
-
75
- if (finalSiblings.length <= 1) return 0;
76
-
77
- // Sort:
78
- finalSiblings.sort((a, b) => {
79
- if (usedAreaFallback) {
80
- // For horizontal paths (Left/Right), we split them vertically by their overall Y midpoint
81
- const aSPos = getAbsolutePosition(a.source, nodes);
82
- const aTPos = getAbsolutePosition(a.target, nodes);
83
- const bSPos = getAbsolutePosition(b.source, nodes);
84
- const bTPos = getAbsolutePosition(b.target, nodes);
85
-
86
- if (isVertical) {
87
- const midA = (aSPos.x + aTPos.x) / 2;
88
- const midB = (bSPos.x + bTPos.x) / 2;
89
- if (midA !== midB) return midA - midB;
90
- } else {
91
- const midA = (aSPos.y + aTPos.y) / 2;
92
- const midB = (bSPos.y + bTPos.y) / 2;
93
- if (midA !== midB) return midA - midB;
94
- }
95
- return a.id.localeCompare(b.id);
96
- }
97
- const otherIdA = isSource ? a.target : a.source;
98
- const otherIdB = isSource ? b.target : b.source;
99
- const posA = getAbsolutePosition(otherIdA, nodes);
100
- const posB = getAbsolutePosition(otherIdB, nodes);
101
- const primary = isVertical ? (posA.x - posB.x) : (posA.y - posB.y);
102
- if (primary !== 0) return primary;
103
- return a.id.localeCompare(b.id);
104
- });
105
-
106
- const index = finalSiblings.findIndex(e => e.id === edgeId);
107
- return (index - (finalSiblings.length - 1) / 2) * OFFSET_STEP;
108
- }
109
-
110
- const ELK_OPTIONS: Record<string, string> = {
111
- 'elk.algorithm': 'layered',
112
- 'elk.direction': 'RIGHT',
113
- //Horizontal
114
- 'elk.spacing.nodeNodeBetweenLayers': '80',
115
- //Vertical
116
- 'elk.spacing.nodeNode': '80',
117
-
118
- 'elk.spacing.edgeNode': '60', // Gap between lines and boxes
119
- 'elk.spacing.edgeEdge': '40', // Gap between parallel lines
120
-
121
- 'elk.layered.spacing.edgeNodeBetweenLayers': '60',
122
- 'elk.edgeRouting': 'ORTHOGONAL',
123
- 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
124
- 'elk.portConstraints': 'FREE',
125
- 'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
126
- 'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',
127
- };
128
-
129
- function convertEdgesToElkEdges(input: FlowGraphInput) {
130
- return input.edges.map((edge) => ({
131
- id: edge.id,
132
- sources: [edge.source],
133
- targets: [edge.target],
134
- originalEdge: edge,
135
- }));
136
- }
137
-
138
- /**
139
- * Helper to get the dimensions of a node, prioritizing measured values from Svelte Flow.
140
- */
141
- export function getNodeDimensions(node: Node<FlowNodeData>): { w: number, h: number } {
142
- const w = Math.round(node.measured?.width ?? node.width ?? 150);
143
- const h = Math.round(node.measured?.height ?? node.height ?? 40);
144
- return {w, h};
145
- }
146
-
147
-
148
- export async function layoutFlowGraph(input: FlowGraphInput): Promise<FlowGraphOutput> {
149
- const elkEdges = convertEdgesToElkEdges(input);
150
- // console.debug('[layoutFlowGraph] Starting layout with input:', {input});
151
- // Helper: Build node properties and handle dimensions
152
- const prepareElkNode = (node: Node<FlowNodeData>) => {
153
- const {w, h} = getNodeDimensions(node);
154
- return {
155
- ...node,
156
- width: w,
157
- height: h
158
- };
159
- };
160
-
161
- // 1. Build the nested ELK structure
162
- const elkChildren: ElkNode[] = input.groupNodes.map(parent => ({
163
- ...prepareElkNode(parent),
164
- layoutOptions: {
165
- ...ELK_OPTIONS,
166
- 'elk.padding': `[top=60,left=${PADDING},bottom=${PADDING},right=${PADDING}]`,
167
- },
168
- children: input.serviceNodes.filter(child => child.parentId === parent.id).map(prepareElkNode)
169
- }));
170
-
171
- // Add nodes that aren't in any group
172
- input.serviceNodes
173
- .filter(child => child.parentId === undefined)
174
- .forEach(node => elkChildren.push(prepareElkNode(node)));
175
-
176
- const elkGraph = {
177
- id: 'root',
178
- layoutOptions: ELK_OPTIONS,
179
- children: elkChildren,
180
- edges: elkEdges
181
- };
182
-
183
- return elk.layout(elkGraph).then(async (layoutedGraph) => {
184
- console.debug('[layoutFlowGraph] Layout completed:', {layoutedGraph});
185
- const flattenedNodes: Node<FlowNodeData>[] = [];
186
- const flattenedEdges: Edge<FlowEdgeData>[] = [];
187
-
188
- // Lookup maps to handle the coordinate system bridge
189
- const parentPosLookup = new Map<string, { x: number, y: number }>();
190
- const nodeToParent = new Map<string, string>();
191
- const nodeLookup = new Map<String, Node<FlowNodeData>>();
192
-
193
- // 2. Process the results
194
- layoutedGraph.children?.forEach(parentOrOrphan => {
195
- const px = parentOrOrphan.x || 0;
196
- const py = parentOrOrphan.y || 0;
197
-
198
- // If it has children, it's a group node in our context
199
- if (parentOrOrphan.children) {
200
- parentPosLookup.set(parentOrOrphan.id, {x: px, y: py});
201
-
202
- flattenedNodes.push({
203
- ...parentOrOrphan,
204
- position: {x: px, y: py},
205
- draggable: true,
206
- } as Node<FlowNodeData>);
207
-
208
- // Map children and record their parent for edge offsetting
209
- parentOrOrphan.children.forEach(child => {
210
- nodeToParent.set(child.id, parentOrOrphan.id);
211
- const node = {
212
- ...child,
213
- position: {x: child.x || 0, y: child.y || 0},
214
- draggable: true,
215
- extent: [[PADDING, 50], [(parentOrOrphan.width || 0) - PADDING, (parentOrOrphan.height || 0) - PADDING]]
216
- } as Node<FlowNodeData>;
217
- flattenedNodes.push(node);
218
- nodeLookup.set(child.id, node);
219
- });
220
- } else {
221
- // It's a top-level orphan node
222
- const node = {
223
- ...parentOrOrphan,
224
- position: {x: px, y: py},
225
- draggable: true,
226
- } as Node<FlowNodeData>;
227
- flattenedNodes.push(node);
228
- nodeLookup.set(node.id, node);
229
- }
230
- });
231
-
232
- const edges = input.edges.filter(e => e.data.connectionType !== 'service-group');
233
-
234
- return {
235
- nodes: flattenedNodes,
236
- edges,
237
- signature: input.signature
238
- };
239
- });
240
- }
1
+ import ELK from 'elkjs/lib/elk.bundled.js';
2
+ import {type Edge, type Node} from '@xyflow/svelte';
3
+ import type {FlowEdgeData, FlowGraphInput, FlowGraphOutput, FlowNodeData} from '$shared/flow-types';
4
+ import type {ElkNode} from "elkjs/lib/elk-api";
5
+
6
+ const elk = new ELK();
7
+ const PADDING = 10;
8
+ export const OFFSET_STEP = 20;
9
+
10
+ /**
11
+ * Helper to calculate absolute position of a node by traversing its parent chain.
12
+ */
13
+ export function getAbsolutePosition(nodeId: string, nodes: Node<FlowNodeData>[]): { x: number, y: number } {
14
+ const node = nodes.find(n => n.id === nodeId);
15
+ if (!node) return {x: 0, y: 0};
16
+
17
+ let x = node.position?.x ?? 0;
18
+ let y = node.position?.y ?? 0;
19
+
20
+ if (node.parentId) {
21
+ const parentPos = getAbsolutePosition(node.parentId, nodes);
22
+ x += parentPos.x;
23
+ y += parentPos.y;
24
+ }
25
+
26
+ return {x, y};
27
+ }
28
+
29
+ /**
30
+ * Calculates a deterministic offset for an edge to prevent overlapping.
31
+ * TODO: Replace this method with elk's built-in edge routing and spacing options once we can guarantee it works well in all cases.
32
+ * This probably means that we'll have to replace it once libavoid is integrated into elk: https://github.com/kieler/elkjs/issues/210
33
+ * This function handles two types of offsets:
34
+ * 1. Handle Offset: For multiple edges sharing the same handle on a node.
35
+ * 2. Trunk Offset: For edges that don't share a handle but might have overlapping
36
+ * paths (trunks) in the same "lane".
37
+ */
38
+ export function calculateEdgeOffset(
39
+ edgeId: string,
40
+ nodes: Node<FlowNodeData>[],
41
+ edges: Edge<FlowEdgeData>[],
42
+ isSource: boolean
43
+ ): number {
44
+ const edge = edges.find(e => e.id === edgeId);
45
+ if (!edge) return 0;
46
+
47
+ const nodeId = isSource ? edge.source : edge.target;
48
+ const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
49
+
50
+ const handle = isSource ? edge.sourceHandle : edge.targetHandle;
51
+ const isVertical = handle?.toLowerCase().includes('top') || handle?.toLowerCase().includes('bottom');
52
+
53
+ let finalSiblings: Edge<FlowEdgeData>[] = [];
54
+ let usedAreaFallback = false;
55
+
56
+ // HANDLE OFFSET: Siblings on the same node and same handle
57
+ const siblings = edges.filter(e =>
58
+ (isSource ? e.source : e.target) === nodeId &&
59
+ (isSource ? e.sourceHandle : e.targetHandle) === handleId
60
+ );
61
+
62
+ if (siblings.length <= 1) {
63
+ // Fallback for bidirectional or single edges between same nodes
64
+ usedAreaFallback = true;
65
+ const otherId = isSource ? edge.target : edge.source;
66
+ finalSiblings = edges.filter(e => {
67
+ const isSamePair = (e.source === nodeId && e.target === otherId) ||
68
+ (e.source === otherId && e.target === nodeId);
69
+ return isSamePair && (e.source === nodeId || e.target === nodeId);
70
+ });
71
+ } else {
72
+ finalSiblings = siblings;
73
+ }
74
+
75
+ if (finalSiblings.length <= 1) return 0;
76
+
77
+ // Sort:
78
+ finalSiblings.sort((a, b) => {
79
+ if (usedAreaFallback) {
80
+ // For horizontal paths (Left/Right), we split them vertically by their overall Y midpoint
81
+ const aSPos = getAbsolutePosition(a.source, nodes);
82
+ const aTPos = getAbsolutePosition(a.target, nodes);
83
+ const bSPos = getAbsolutePosition(b.source, nodes);
84
+ const bTPos = getAbsolutePosition(b.target, nodes);
85
+
86
+ if (isVertical) {
87
+ const midA = (aSPos.x + aTPos.x) / 2;
88
+ const midB = (bSPos.x + bTPos.x) / 2;
89
+ if (midA !== midB) return midA - midB;
90
+ } else {
91
+ const midA = (aSPos.y + aTPos.y) / 2;
92
+ const midB = (bSPos.y + bTPos.y) / 2;
93
+ if (midA !== midB) return midA - midB;
94
+ }
95
+ return a.id.localeCompare(b.id);
96
+ }
97
+ const otherIdA = isSource ? a.target : a.source;
98
+ const otherIdB = isSource ? b.target : b.source;
99
+ const posA = getAbsolutePosition(otherIdA, nodes);
100
+ const posB = getAbsolutePosition(otherIdB, nodes);
101
+ const primary = isVertical ? (posA.x - posB.x) : (posA.y - posB.y);
102
+ if (primary !== 0) return primary;
103
+ return a.id.localeCompare(b.id);
104
+ });
105
+
106
+ const index = finalSiblings.findIndex(e => e.id === edgeId);
107
+ return (index - (finalSiblings.length - 1) / 2) * OFFSET_STEP;
108
+ }
109
+
110
+ const ELK_OPTIONS: Record<string, string> = {
111
+ 'elk.algorithm': 'layered',
112
+ 'elk.direction': 'RIGHT',
113
+ //Horizontal
114
+ 'elk.spacing.nodeNodeBetweenLayers': '80',
115
+ //Vertical
116
+ 'elk.spacing.nodeNode': '80',
117
+
118
+ 'elk.spacing.edgeNode': '60', // Gap between lines and boxes
119
+ 'elk.spacing.edgeEdge': '40', // Gap between parallel lines
120
+
121
+ 'elk.layered.spacing.edgeNodeBetweenLayers': '60',
122
+ 'elk.edgeRouting': 'ORTHOGONAL',
123
+ 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
124
+ 'elk.portConstraints': 'FREE',
125
+ 'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
126
+ 'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',
127
+ };
128
+
129
+ function convertEdgesToElkEdges(input: FlowGraphInput) {
130
+ return input.edges.map((edge) => ({
131
+ id: edge.id,
132
+ sources: [edge.source],
133
+ targets: [edge.target],
134
+ originalEdge: edge,
135
+ }));
136
+ }
137
+
138
+ /**
139
+ * Helper to get the dimensions of a node, prioritizing measured values from Svelte Flow.
140
+ */
141
+ export function getNodeDimensions(node: Node<FlowNodeData>): { w: number, h: number } {
142
+ const w = Math.round(node.measured?.width ?? node.width ?? 150);
143
+ const h = Math.round(node.measured?.height ?? node.height ?? 40);
144
+ return {w, h};
145
+ }
146
+
147
+
148
+ export async function layoutFlowGraph(input: FlowGraphInput): Promise<FlowGraphOutput> {
149
+ const elkEdges = convertEdgesToElkEdges(input);
150
+ // console.debug('[layoutFlowGraph] Starting layout with input:', {input});
151
+ // Helper: Build node properties and handle dimensions
152
+ const prepareElkNode = (node: Node<FlowNodeData>) => {
153
+ const {w, h} = getNodeDimensions(node);
154
+ return {
155
+ ...node,
156
+ width: w,
157
+ height: h
158
+ };
159
+ };
160
+
161
+ // 1. Build the nested ELK structure
162
+ const elkChildren: ElkNode[] = input.groupNodes.map(parent => ({
163
+ ...prepareElkNode(parent),
164
+ layoutOptions: {
165
+ ...ELK_OPTIONS,
166
+ 'elk.padding': `[top=60,left=${PADDING},bottom=${PADDING},right=${PADDING}]`,
167
+ },
168
+ children: input.serviceNodes.filter(child => child.parentId === parent.id).map(prepareElkNode)
169
+ }));
170
+
171
+ // Add nodes that aren't in any group
172
+ input.serviceNodes
173
+ .filter(child => child.parentId === undefined)
174
+ .forEach(node => elkChildren.push(prepareElkNode(node)));
175
+
176
+ const elkGraph = {
177
+ id: 'root',
178
+ layoutOptions: ELK_OPTIONS,
179
+ children: elkChildren,
180
+ edges: elkEdges
181
+ };
182
+
183
+ return elk.layout(elkGraph).then(async (layoutedGraph) => {
184
+ console.debug('[layoutFlowGraph] Layout completed:', {layoutedGraph});
185
+ const flattenedNodes: Node<FlowNodeData>[] = [];
186
+ const flattenedEdges: Edge<FlowEdgeData>[] = [];
187
+
188
+ // Lookup maps to handle the coordinate system bridge
189
+ const parentPosLookup = new Map<string, { x: number, y: number }>();
190
+ const nodeToParent = new Map<string, string>();
191
+ const nodeLookup = new Map<String, Node<FlowNodeData>>();
192
+
193
+ // 2. Process the results
194
+ layoutedGraph.children?.forEach(parentOrOrphan => {
195
+ const px = parentOrOrphan.x || 0;
196
+ const py = parentOrOrphan.y || 0;
197
+
198
+ // If it has children, it's a group node in our context
199
+ if (parentOrOrphan.children) {
200
+ parentPosLookup.set(parentOrOrphan.id, {x: px, y: py});
201
+
202
+ flattenedNodes.push({
203
+ ...parentOrOrphan,
204
+ position: {x: px, y: py},
205
+ draggable: true,
206
+ } as Node<FlowNodeData>);
207
+
208
+ // Map children and record their parent for edge offsetting
209
+ parentOrOrphan.children.forEach(child => {
210
+ nodeToParent.set(child.id, parentOrOrphan.id);
211
+ const node = {
212
+ ...child,
213
+ position: {x: child.x || 0, y: child.y || 0},
214
+ draggable: true,
215
+ extent: [[PADDING, 50], [(parentOrOrphan.width || 0) - PADDING, (parentOrOrphan.height || 0) - PADDING]]
216
+ } as Node<FlowNodeData>;
217
+ flattenedNodes.push(node);
218
+ nodeLookup.set(child.id, node);
219
+ });
220
+ } else {
221
+ // It's a top-level orphan node
222
+ const node = {
223
+ ...parentOrOrphan,
224
+ position: {x: px, y: py},
225
+ draggable: true,
226
+ } as Node<FlowNodeData>;
227
+ flattenedNodes.push(node);
228
+ nodeLookup.set(node.id, node);
229
+ }
230
+ });
231
+
232
+ const edges = input.edges.filter(e => e.data.connectionType !== 'service-group');
233
+
234
+ return {
235
+ nodes: flattenedNodes,
236
+ edges,
237
+ signature: input.signature
238
+ };
239
+ });
240
+ }
@@ -1,5 +1,5 @@
1
- import type { ServiceDefinition } from '$shared/types'
2
-
3
- //TODO: Do you need to exist, or can i put you somewhere else?
4
- export const getServiceNodeIdFromDefinition = (service: ServiceDefinition) =>
1
+ import type { ServiceDefinition } from '$shared/types'
2
+
3
+ //TODO: Do you need to exist, or can i put you somewhere else?
4
+ export const getServiceNodeIdFromDefinition = (service: ServiceDefinition) =>
5
5
  `svc::${service.groupId}::${service.identifier}`