@k-4u/resource-mapper-core 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 (250) hide show
  1. package/.aws-icons-last-updated +1 -0
  2. package/.svelte-kit/ambient.d.ts +263 -0
  3. package/.svelte-kit/generated/client/app.js +31 -0
  4. package/.svelte-kit/generated/client/matchers.js +1 -0
  5. package/.svelte-kit/generated/client/nodes/0.js +1 -0
  6. package/.svelte-kit/generated/client/nodes/1.js +1 -0
  7. package/.svelte-kit/generated/client/nodes/2.js +3 -0
  8. package/.svelte-kit/generated/client/nodes/3.js +3 -0
  9. package/.svelte-kit/generated/client-optimized/app.js +31 -0
  10. package/.svelte-kit/generated/client-optimized/matchers.js +1 -0
  11. package/.svelte-kit/generated/client-optimized/nodes/0.js +1 -0
  12. package/.svelte-kit/generated/client-optimized/nodes/1.js +1 -0
  13. package/.svelte-kit/generated/client-optimized/nodes/2.js +3 -0
  14. package/.svelte-kit/generated/client-optimized/nodes/3.js +3 -0
  15. package/.svelte-kit/generated/root.js +3 -0
  16. package/.svelte-kit/generated/root.svelte +68 -0
  17. package/.svelte-kit/generated/server/internal.js +53 -0
  18. package/.svelte-kit/non-ambient.d.ts +43 -0
  19. package/.svelte-kit/output/client/.vite/manifest.json +175 -0
  20. package/.svelte-kit/output/client/_app/immutable/assets/0.Czt_67iE.css +1 -0
  21. package/.svelte-kit/output/client/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +1 -0
  22. package/.svelte-kit/output/client/_app/immutable/assets/helpers.ysDrpaDf.css +1 -0
  23. package/.svelte-kit/output/client/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
  24. package/.svelte-kit/output/client/_app/immutable/chunks/BlLuv0eP.js +46 -0
  25. package/.svelte-kit/output/client/_app/immutable/chunks/CSBHmwYv.js +1 -0
  26. package/.svelte-kit/output/client/_app/immutable/chunks/CTCi5ueQ.js +1 -0
  27. package/.svelte-kit/output/client/_app/immutable/chunks/CfOzjaik.js +2 -0
  28. package/.svelte-kit/output/client/_app/immutable/chunks/D4PdvFNs.js +1 -0
  29. package/.svelte-kit/output/client/_app/immutable/chunks/DXgP-QUS.js +2 -0
  30. package/.svelte-kit/output/client/_app/immutable/chunks/DlbDC5An.js +1 -0
  31. package/.svelte-kit/output/client/_app/immutable/chunks/wRWe7aK9.js +1 -0
  32. package/.svelte-kit/output/client/_app/immutable/entry/app.ConrMuHl.js +2 -0
  33. package/.svelte-kit/output/client/_app/immutable/entry/start.Bm6FyGme.js +1 -0
  34. package/.svelte-kit/output/client/_app/immutable/nodes/0.d3cL-ETU.js +1 -0
  35. package/.svelte-kit/output/client/_app/immutable/nodes/1.D6z9rPGv.js +1 -0
  36. package/.svelte-kit/output/client/_app/immutable/nodes/2.CLD-8chl.js +1 -0
  37. package/.svelte-kit/output/client/_app/immutable/nodes/3.DXYeBoel.js +1 -0
  38. package/.svelte-kit/output/client/_app/version.json +1 -0
  39. package/.svelte-kit/output/client/libavoid.wasm +0 -0
  40. package/.svelte-kit/output/client/static/robots.txt +3 -0
  41. package/.svelte-kit/output/prerendered/dependencies/_app/env.js +1 -0
  42. package/.svelte-kit/output/server/.vite/manifest.json +224 -0
  43. package/.svelte-kit/output/server/_app/immutable/assets/LoadingOverlay.DBbe6V8W.css +1 -0
  44. package/.svelte-kit/output/server/_app/immutable/assets/_layout.Czt_67iE.css +1 -0
  45. package/.svelte-kit/output/server/_app/immutable/assets/_page.D9P41uDZ.css +1 -0
  46. package/.svelte-kit/output/server/chunks/ErrorDisplay.js +59 -0
  47. package/.svelte-kit/output/server/chunks/LoadingOverlay.js +12 -0
  48. package/.svelte-kit/output/server/chunks/LoadingOverlay.svelte_svelte_type_style_lang.js +1671 -0
  49. package/.svelte-kit/output/server/chunks/connections.js +33 -0
  50. package/.svelte-kit/output/server/chunks/diagram.js +7 -0
  51. package/.svelte-kit/output/server/chunks/environment.js +34 -0
  52. package/.svelte-kit/output/server/chunks/equality.js +57 -0
  53. package/.svelte-kit/output/server/chunks/exports.js +174 -0
  54. package/.svelte-kit/output/server/chunks/false.js +4 -0
  55. package/.svelte-kit/output/server/chunks/index.js +59 -0
  56. package/.svelte-kit/output/server/chunks/index2.js +2939 -0
  57. package/.svelte-kit/output/server/chunks/index3.js +20 -0
  58. package/.svelte-kit/output/server/chunks/internal.js +1017 -0
  59. package/.svelte-kit/output/server/chunks/shared.js +770 -0
  60. package/.svelte-kit/output/server/chunks/utils.js +43 -0
  61. package/.svelte-kit/output/server/entries/pages/_error.svelte.js +64 -0
  62. package/.svelte-kit/output/server/entries/pages/_layout.svelte.js +65 -0
  63. package/.svelte-kit/output/server/entries/pages/_page.svelte.js +3991 -0
  64. package/.svelte-kit/output/server/entries/pages/_page.ts.js +30 -0
  65. package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.svelte.js +67 -0
  66. package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.ts.js +47 -0
  67. package/.svelte-kit/output/server/index.js +3747 -0
  68. package/.svelte-kit/output/server/internal.js +13 -0
  69. package/.svelte-kit/output/server/manifest-full.js +47 -0
  70. package/.svelte-kit/output/server/manifest.js +47 -0
  71. package/.svelte-kit/output/server/nodes/0.js +8 -0
  72. package/.svelte-kit/output/server/nodes/1.js +8 -0
  73. package/.svelte-kit/output/server/nodes/2.js +10 -0
  74. package/.svelte-kit/output/server/nodes/3.js +10 -0
  75. package/.svelte-kit/output/server/remote-entry.js +557 -0
  76. package/.svelte-kit/tsconfig.json +61 -0
  77. package/.svelte-kit/types/route_meta_data.json +8 -0
  78. package/.svelte-kit/types/src/routes/$types.d.ts +26 -0
  79. package/.svelte-kit/types/src/routes/group/[groupId]/$types.d.ts +21 -0
  80. package/.svelte-kit/types/src/routes/group/[groupId]/proxy+page.ts +49 -0
  81. package/.svelte-kit/types/src/routes/proxy+page.ts +33 -0
  82. package/build/_app/env.js +1 -0
  83. package/build/_app/env.js.br +1 -0
  84. package/build/_app/env.js.gz +0 -0
  85. package/build/_app/immutable/assets/0.Czt_67iE.css +1 -0
  86. package/build/_app/immutable/assets/0.Czt_67iE.css.br +0 -0
  87. package/build/_app/immutable/assets/0.Czt_67iE.css.gz +0 -0
  88. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +1 -0
  89. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.br +0 -0
  90. package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.gz +0 -0
  91. package/build/_app/immutable/assets/helpers.ysDrpaDf.css +1 -0
  92. package/build/_app/immutable/assets/helpers.ysDrpaDf.css.br +0 -0
  93. package/build/_app/immutable/assets/helpers.ysDrpaDf.css.gz +0 -0
  94. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
  95. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.br +0 -0
  96. package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.gz +0 -0
  97. package/build/_app/immutable/chunks/BlLuv0eP.js +46 -0
  98. package/build/_app/immutable/chunks/BlLuv0eP.js.br +0 -0
  99. package/build/_app/immutable/chunks/BlLuv0eP.js.gz +0 -0
  100. package/build/_app/immutable/chunks/CSBHmwYv.js +1 -0
  101. package/build/_app/immutable/chunks/CSBHmwYv.js.br +0 -0
  102. package/build/_app/immutable/chunks/CSBHmwYv.js.gz +0 -0
  103. package/build/_app/immutable/chunks/CTCi5ueQ.js +1 -0
  104. package/build/_app/immutable/chunks/CTCi5ueQ.js.br +0 -0
  105. package/build/_app/immutable/chunks/CTCi5ueQ.js.gz +0 -0
  106. package/build/_app/immutable/chunks/CfOzjaik.js +2 -0
  107. package/build/_app/immutable/chunks/CfOzjaik.js.br +0 -0
  108. package/build/_app/immutable/chunks/CfOzjaik.js.gz +0 -0
  109. package/build/_app/immutable/chunks/D4PdvFNs.js +1 -0
  110. package/build/_app/immutable/chunks/D4PdvFNs.js.br +0 -0
  111. package/build/_app/immutable/chunks/D4PdvFNs.js.gz +0 -0
  112. package/build/_app/immutable/chunks/DXgP-QUS.js +2 -0
  113. package/build/_app/immutable/chunks/DXgP-QUS.js.br +0 -0
  114. package/build/_app/immutable/chunks/DXgP-QUS.js.gz +0 -0
  115. package/build/_app/immutable/chunks/DlbDC5An.js +1 -0
  116. package/build/_app/immutable/chunks/DlbDC5An.js.br +0 -0
  117. package/build/_app/immutable/chunks/DlbDC5An.js.gz +0 -0
  118. package/build/_app/immutable/chunks/wRWe7aK9.js +1 -0
  119. package/build/_app/immutable/chunks/wRWe7aK9.js.br +0 -0
  120. package/build/_app/immutable/chunks/wRWe7aK9.js.gz +0 -0
  121. package/build/_app/immutable/entry/app.ConrMuHl.js +2 -0
  122. package/build/_app/immutable/entry/app.ConrMuHl.js.br +0 -0
  123. package/build/_app/immutable/entry/app.ConrMuHl.js.gz +0 -0
  124. package/build/_app/immutable/entry/start.Bm6FyGme.js +1 -0
  125. package/build/_app/immutable/entry/start.Bm6FyGme.js.br +2 -0
  126. package/build/_app/immutable/entry/start.Bm6FyGme.js.gz +0 -0
  127. package/build/_app/immutable/nodes/0.d3cL-ETU.js +1 -0
  128. package/build/_app/immutable/nodes/0.d3cL-ETU.js.br +0 -0
  129. package/build/_app/immutable/nodes/0.d3cL-ETU.js.gz +0 -0
  130. package/build/_app/immutable/nodes/1.D6z9rPGv.js +1 -0
  131. package/build/_app/immutable/nodes/1.D6z9rPGv.js.br +0 -0
  132. package/build/_app/immutable/nodes/1.D6z9rPGv.js.gz +0 -0
  133. package/build/_app/immutable/nodes/2.CLD-8chl.js +1 -0
  134. package/build/_app/immutable/nodes/2.CLD-8chl.js.br +0 -0
  135. package/build/_app/immutable/nodes/2.CLD-8chl.js.gz +0 -0
  136. package/build/_app/immutable/nodes/3.DXYeBoel.js +1 -0
  137. package/build/_app/immutable/nodes/3.DXYeBoel.js.br +0 -0
  138. package/build/_app/immutable/nodes/3.DXYeBoel.js.gz +0 -0
  139. package/build/_app/version.json +1 -0
  140. package/build/_app/version.json.br +0 -0
  141. package/build/_app/version.json.gz +0 -0
  142. package/build/index.html +34 -0
  143. package/build/index.html.br +0 -0
  144. package/build/index.html.gz +0 -0
  145. package/build/libavoid.wasm +0 -0
  146. package/build/libavoid.wasm.br +0 -0
  147. package/build/libavoid.wasm.gz +0 -0
  148. package/build/static/robots.txt +3 -0
  149. package/coverage/coverage-final.json +6 -0
  150. package/coverage/coverage-summary.json +7 -0
  151. package/coverage/junit.xml +57 -0
  152. package/coverage/lcov-report/base.css +224 -0
  153. package/coverage/lcov-report/block-navigation.js +87 -0
  154. package/coverage/lcov-report/favicon.png +0 -0
  155. package/coverage/lcov-report/index.html +131 -0
  156. package/coverage/lcov-report/prettify.css +1 -0
  157. package/coverage/lcov-report/prettify.js +2 -0
  158. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  159. package/coverage/lcov-report/sorter.js +210 -0
  160. package/coverage/lcov-report/stores/index.html +116 -0
  161. package/coverage/lcov-report/stores/routingStore.ts.html +781 -0
  162. package/coverage/lcov-report/utils/flow/helpers.ts.html +127 -0
  163. package/coverage/lcov-report/utils/flow/index.html +161 -0
  164. package/coverage/lcov-report/utils/flow/layout.ts.html +805 -0
  165. package/coverage/lcov-report/utils/flow/serviceIds.ts.html +97 -0
  166. package/coverage/lcov-report/utils/flow/servicesGraph.ts.html +859 -0
  167. package/coverage/lcov.info +646 -0
  168. package/coverage/test-results.json +1 -0
  169. package/data/services/api/api-gateway.yaml +18 -0
  170. package/data/services/api/group-info.yaml +7 -0
  171. package/data/services/api/lambda-orders.yaml +21 -0
  172. package/data/services/api/lambda-products.yaml +15 -0
  173. package/data/services/api/lambda-users.yaml +15 -0
  174. package/data/services/compute/alb.yaml +15 -0
  175. package/data/services/compute/ecs-inventory.yaml +16 -0
  176. package/data/services/compute/ecs-notification.yaml +15 -0
  177. package/data/services/compute/group-info.yaml +7 -0
  178. package/data/services/data/dynamodb-notifications.yaml +12 -0
  179. package/data/services/data/dynamodb-orders.yaml +9 -0
  180. package/data/services/data/dynamodb-products.yaml +9 -0
  181. package/data/services/data/dynamodb-users.yaml +9 -0
  182. package/data/services/data/group-info.yaml +7 -0
  183. package/data/services/data/rds-postgres.yaml +9 -0
  184. package/data/services/data/redis.yaml +10 -0
  185. package/data/services/frontend/cloudfront.yaml +12 -0
  186. package/data/services/frontend/group-info.yaml +7 -0
  187. package/data/services/frontend/route53.yaml +15 -0
  188. package/data/services/frontend/s3-website.yaml +9 -0
  189. package/data/teams/cloud-shepherds.yaml +15 -0
  190. package/data/teams/data-wizards.yaml +15 -0
  191. package/data/teams/interface-architects.yaml +19 -0
  192. package/e2e/demo.test.ts +54 -0
  193. package/e2e/header-toolbar.simple.spec.ts +0 -0
  194. package/e2e/header-toolbar.spec.ts +53 -0
  195. package/e2e/layout.spec.ts +30 -0
  196. package/package.json +69 -0
  197. package/playwright.config.ts +10 -0
  198. package/plugins/mapper-data-plugin.ts +32 -0
  199. package/project.json +23 -0
  200. package/src/app.css +125 -0
  201. package/src/app.d.ts +31 -0
  202. package/src/app.html +11 -0
  203. package/src/lib/assets/favicon.svg +19 -0
  204. package/src/lib/components/EmptyState.svelte +37 -0
  205. package/src/lib/components/ErrorDisplay.svelte +82 -0
  206. package/src/lib/components/FlowCanvas.svelte +223 -0
  207. package/src/lib/components/GenericSidebarCard.svelte +44 -0
  208. package/src/lib/components/GroupDetailSidebar.svelte +31 -0
  209. package/src/lib/components/Header.svelte +57 -0
  210. package/src/lib/components/Legend.svelte +25 -0
  211. package/src/lib/components/LoadingOverlay.svelte +42 -0
  212. package/src/lib/components/LoadingSpinner.svelte +10 -0
  213. package/src/lib/components/ServiceDetailSidebar.svelte +90 -0
  214. package/src/lib/components/TeamContactCard.svelte +166 -0
  215. package/src/lib/components/flow/ExternalNode.svelte +45 -0
  216. package/src/lib/components/flow/MainGroupNode.svelte +24 -0
  217. package/src/lib/components/flow/ServiceGroupNode.svelte +17 -0
  218. package/src/lib/components/flow/ServiceNode.svelte +40 -0
  219. package/src/lib/components/flow/SnakeEdge.svelte +206 -0
  220. package/src/lib/components/flow/index.ts +6 -0
  221. package/src/lib/components/index.ts +12 -0
  222. package/src/lib/data/connections.ts +26 -0
  223. package/src/lib/data/groups.ts +11 -0
  224. package/src/lib/data/services.ts +12 -0
  225. package/src/lib/data/teams.ts +11 -0
  226. package/src/lib/index.ts +1 -0
  227. package/src/lib/state/theme.svelte.ts +21 -0
  228. package/src/lib/stores/diagram.ts +6 -0
  229. package/src/lib/stores/routingStore.test.ts +133 -0
  230. package/src/lib/stores/routingStore.ts +232 -0
  231. package/src/lib/utils/awsIcons.ts +117 -0
  232. package/src/lib/utils/flow/groupOverviewGraph.ts +73 -0
  233. package/src/lib/utils/flow/helpers.ts +14 -0
  234. package/src/lib/utils/flow/layout.test.ts +271 -0
  235. package/src/lib/utils/flow/layout.ts +240 -0
  236. package/src/lib/utils/flow/serviceIds.ts +5 -0
  237. package/src/lib/utils/flow/servicesGraph.test.ts +119 -0
  238. package/src/lib/utils/flow/servicesGraph.ts +258 -0
  239. package/src/routes/+error.svelte +36 -0
  240. package/src/routes/+layout.svelte +27 -0
  241. package/src/routes/+page.svelte +81 -0
  242. package/src/routes/+page.ts +31 -0
  243. package/src/routes/group/[groupId]/+page.svelte +102 -0
  244. package/src/routes/group/[groupId]/+page.ts +48 -0
  245. package/src/routes/layout.css +0 -0
  246. package/static/static/robots.txt +3 -0
  247. package/svelte.config.js +28 -0
  248. package/tailwind.config.js +12 -0
  249. package/tsconfig.json +22 -0
  250. package/vite.config.ts +81 -0
@@ -0,0 +1,258 @@
1
+ import {
2
+ type ConnectionType,
3
+ type ExternalConnectionDirection,
4
+ type ExternalGroupServices,
5
+ type GroupConnection,
6
+ type GroupInfo,
7
+ type ServiceDefinition,
8
+ type ServiceIncomingLink,
9
+ type ServiceLink
10
+ } from '$shared/types'
11
+ import type {FlowEdgeData, FlowNodeData, ServicesGraphResult,} from '$shared/flow-types'
12
+ import {createGraphSignature} from '$lib/utils/flow/helpers'
13
+ import {getServiceNodeIdFromDefinition} from '$lib/utils/flow/serviceIds'
14
+ import type {Edge, Node} from "@xyflow/svelte";
15
+
16
+ const position = {x: 0, y: 0};
17
+
18
+ function createServiceNode(nodeId: string, service: ServiceDefinition): Node<FlowNodeData> {
19
+ return {
20
+ id: nodeId,
21
+ type: 'service',
22
+ width: 220,
23
+ height: 120,
24
+ data: {
25
+ label: service.friendlyName,
26
+ subLabel: service.description ?? undefined,
27
+ groupId: service.groupId,
28
+ serviceId: service.identifier,
29
+ serviceType: service.serviceType,
30
+ kind: 'service'
31
+ },
32
+ parentId: `group_${service.groupId}`,
33
+ position
34
+ };
35
+ }
36
+
37
+
38
+ export function buildGroupServicesGraph(
39
+ currentGroup: GroupInfo,
40
+ currentServices: ServiceDefinition[],
41
+ allGroups: Map<string, GroupInfo>,
42
+ groupConnections: GroupConnection[],
43
+ externalGroups: ExternalGroupServices[] = []
44
+ ): ServicesGraphResult {
45
+ const nodes: Node<FlowNodeData>[] = []
46
+ const edges: Edge<FlowEdgeData>[] = []
47
+ const serviceNodeLookup = new Map<string, string>()
48
+ const externalNodeMap = new Map<string, Node<FlowNodeData>>()
49
+ const serviceNodeRecord: Record<string, ServiceDefinition> = {}
50
+ const externalNodeRecord: Record<string, { service: ServiceDefinition; group: GroupInfo }> = {}
51
+
52
+ if (!currentServices.length) {
53
+ nodes.push({
54
+ id: `placeholder_${currentGroup.groupName}`,
55
+ data: {
56
+ label: 'No services defined',
57
+ subLabel: 'Add services to see relationships',
58
+ kind: 'service',
59
+ groupId: currentGroup.groupName,
60
+ isPlaceholder: true
61
+ },
62
+ position
63
+ })
64
+ return {
65
+ graph: {
66
+ groupNodes: [],
67
+ serviceNodes: nodes,
68
+ edges,
69
+ signature: createGraphSignature('group-services', {group: currentGroup.groupName, placeholder: true})
70
+ },
71
+ serviceNodes: serviceNodeRecord,
72
+ externalNodes: externalNodeRecord
73
+ }
74
+ }
75
+
76
+ // 2. Add Connections
77
+ const externalLookup = createDirectionLookup(externalGroups)
78
+
79
+ const addConnections = (
80
+ serviceNodeId: string,
81
+ connections: (ServiceLink | ServiceIncomingLink)[] | undefined
82
+ ) => {
83
+ connections?.forEach(conn => {
84
+ const isOutgoing = 'targetIdentifier' in conn
85
+ const remoteIdentifier = isOutgoing ? conn.targetIdentifier : conn.sourceIdentifier
86
+ if (remoteIdentifier.groupId === currentGroup.groupName) {
87
+ const remoteNodeId = remoteIdentifier.serviceId ? serviceNodeLookup.get(remoteIdentifier.serviceId) : undefined
88
+ if (remoteNodeId) {
89
+ const sourceId = isOutgoing ? serviceNodeId : remoteNodeId
90
+ const targetId = isOutgoing ? remoteNodeId : serviceNodeId
91
+ edges.push(buildEdge(sourceId, targetId, conn.connectionType, 'internal'))
92
+ }
93
+ } else if (remoteIdentifier.groupId && remoteIdentifier.serviceId) {
94
+ const extNodeId = ensureExternalNode(remoteIdentifier.groupId, remoteIdentifier.serviceId, externalLookup, externalNodeMap, externalNodeRecord)
95
+ if (extNodeId) {
96
+ const sourceId = isOutgoing ? serviceNodeId : extNodeId
97
+ const targetId = isOutgoing ? extNodeId : serviceNodeId
98
+ edges.push(buildEdge(sourceId, targetId, conn.connectionType, 'external'))
99
+ }
100
+ }
101
+ })
102
+ }
103
+
104
+ currentServices.forEach(service => {
105
+ const nodeId = getServiceNodeIdFromDefinition(service)
106
+ serviceNodeLookup.set(service.identifier, nodeId)
107
+ serviceNodeRecord[nodeId] = service
108
+ nodes.push(createServiceNode(nodeId, service))
109
+ })
110
+
111
+ currentServices.forEach(service => {
112
+ const currentServiceNodeId = serviceNodeLookup.get(service.identifier)
113
+ if (!currentServiceNodeId) return
114
+
115
+ addConnections(currentServiceNodeId, service.outgoingConnections)
116
+ addConnections(currentServiceNodeId, service.incomingConnections)
117
+ })
118
+
119
+ // 4. Create Group Container Nodes
120
+ const groupNodes: Node<FlowNodeData>[] = Array.from(allGroups.values()).map(g => ({
121
+ id: `group_${g.groupName}`,
122
+ type: 'serviceGroup',
123
+ data: {
124
+ label: g.name,
125
+ subLabel: g.description ?? undefined,
126
+ groupId: g.groupName,
127
+ kind: 'group'
128
+ },
129
+ position,
130
+ class: g.groupName === currentGroup.groupName ? 'current-group' : 'external-group'
131
+ }))
132
+
133
+ // 5. Add Group-to-Group Edges
134
+ groupConnections.forEach(conn => {
135
+ edges.push(buildEdge(`group_${conn.sourceGroup}`, `group_${conn.targetGroup}`, 'service-group'))
136
+ })
137
+
138
+ const signature = createGraphSignature('group-services', {
139
+ group: currentGroup.groupName,
140
+ services: currentServices.map(service => ({identifier: service.identifier, type: service.serviceType})).sort((a, b) =>
141
+ a.identifier.localeCompare(b.identifier)
142
+ ),
143
+ external: externalGroups.map(entry => ({
144
+ direction: entry.direction,
145
+ group: entry.group.groupName,
146
+ services: entry.services.map(service => service.identifier).sort()
147
+ }))
148
+ })
149
+
150
+ return {
151
+ graph: {
152
+ groupNodes,
153
+ serviceNodes: nodes.concat(Array.from(externalNodeMap.values())),
154
+ edges,
155
+ signature
156
+ },
157
+ serviceNodes: serviceNodeRecord,
158
+ externalNodes: externalNodeRecord
159
+ }
160
+ }
161
+
162
+ function createDirectionLookup(
163
+ entries: ExternalGroupServices[]
164
+ ): Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }> {
165
+ return entries.reduce((lookup, entry) => {
166
+ const key = entry.group?.groupName
167
+ if (!key) return lookup
168
+ const existing = lookup.get(key) || {}
169
+ if (entry.direction === 'incoming') {
170
+ existing.incoming = entry
171
+ } else if (entry.direction === 'outgoing') {
172
+ existing.outgoing = entry
173
+ }
174
+ lookup.set(key, existing)
175
+ return lookup
176
+ }, new Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>())
177
+ }
178
+
179
+ // Registers an external node in the nodeMap and externalNodeRecord.
180
+ // It uses a consistent svc::<groupId>::<serviceId> naming scheme.
181
+ function ensureExternalNode(
182
+ groupId: string,
183
+ serviceId: string,
184
+ lookup: Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>,
185
+ nodeMap: Map<string, Node<FlowNodeData>>,
186
+ record: Record<string, { service: ServiceDefinition; group: GroupInfo }>
187
+ ): string | null {
188
+ const key = `svc::${groupId}::${serviceId}`
189
+ if (nodeMap.has(key)) {
190
+ return key
191
+ }
192
+
193
+ // Try to find the service in either direction
194
+ const externalMeta = findExternalService('outgoing', groupId, serviceId, lookup) ||
195
+ findExternalService('incoming', groupId, serviceId, lookup)
196
+
197
+ if (!externalMeta) {
198
+ return null
199
+ }
200
+
201
+ nodeMap.set(key, {
202
+ id: key,
203
+ width: 220,
204
+ height: 70,
205
+ type: 'external',
206
+ data: {
207
+ label: externalMeta.service.friendlyName,
208
+ serviceType: externalMeta.service.serviceType,
209
+ subLabel: externalMeta.group.name,
210
+ groupId: groupId,
211
+ serviceId: externalMeta.service.identifier,
212
+ kind: 'external',
213
+ },
214
+ extent: "parent",
215
+ position: {x: 0, y: 0},
216
+ parentId: `group_${groupId}`,
217
+ })
218
+ record[key] = {service: externalMeta.service, group: externalMeta.group}
219
+
220
+ return key
221
+ }
222
+
223
+ function findExternalService(
224
+ direction: ExternalConnectionDirection,
225
+ groupId: string,
226
+ serviceId: string,
227
+ lookup: Map<string, { incoming?: ExternalGroupServices; outgoing?: ExternalGroupServices }>
228
+ ) {
229
+ const entries = lookup.get(groupId)
230
+ if (!entries) return null
231
+ const dirEntry = entries[direction]
232
+ if (!dirEntry) return null
233
+ const service = dirEntry.services.find(item => item.identifier === serviceId)
234
+ if (!service) return null
235
+ return { group: dirEntry.group, service }
236
+ }
237
+
238
+ function buildEdge(
239
+ sourceId: string,
240
+ targetId: string,
241
+ connectionType?: ConnectionType | 'service-group',
242
+ edgeType: 'external' | 'internal' = 'internal' //TODO: Name me better
243
+ ): Edge<FlowEdgeData> {
244
+ console.debug(`Creating edge from ${sourceId} to ${targetId} of type ${connectionType} (${edgeType})`)
245
+ return {
246
+ id: `edge_${sourceId}_${targetId}`,
247
+ source: sourceId,
248
+ sourceHandle: 'output',
249
+ target: targetId,
250
+ targetHandle: 'input',
251
+ type: 'snake',
252
+ // type: 'smoothstep',
253
+ markerEnd: 'arrow',
254
+ class: `edge-${edgeType}`,
255
+ label: connectionType || "",
256
+ data: {label: connectionType, connectionType}
257
+ }
258
+ }
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import {ErrorDisplay} from '$lib/components';
3
+ import { page } from '$app/stores';
4
+
5
+ let { error, status } = $props<{ error: Error; status: number }>();
6
+
7
+ const checkList = [
8
+ "Check your internet connection",
9
+ "Try refreshing the page",
10
+ "Contact support if the issue persists"
11
+ ];
12
+
13
+ const technicalDetails = $derived({
14
+ status,
15
+ message: error?.message,
16
+ stack: error?.stack,
17
+ url: $page.url.pathname
18
+ });
19
+
20
+ function handleRetry() {
21
+ location.reload();
22
+ }
23
+ function handleBack() {
24
+ history.back();
25
+ }
26
+ </script>
27
+
28
+ <ErrorDisplay
29
+ title={status ? `Error ${status}` : 'Error'}
30
+ message={error?.message ?? 'An unexpected error occurred.'}
31
+ checkList={checkList}
32
+ technicalDetails={technicalDetails}
33
+ onRetry={handleRetry}
34
+ onBack={handleBack}
35
+ />
36
+
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import {Header, LoadingOverlay} from '$lib/components';
3
+ import {navigating} from '$app/state';
4
+ import './layout.css';
5
+ import '../app.css';
6
+ import favicon from '$lib/assets/favicon.svg';
7
+ import {theme} from '$lib/state/theme.svelte';
8
+
9
+ let { children } = $props();
10
+
11
+ $effect(() => {
12
+ if (typeof document !== 'undefined') {
13
+ document.documentElement.classList.toggle('dark', theme.isDark);
14
+ }
15
+ });
16
+ </script>
17
+
18
+ <svelte:head><link rel="icon" href={favicon} /></svelte:head>
19
+ <Header />
20
+
21
+ {#if navigating.to}
22
+ <LoadingOverlay message="Loading..." />
23
+ {/if}
24
+
25
+ <main class="w-full h-full px-6 py-6">
26
+ {@render children()}
27
+ </main>
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ import type {PageData} from './$types'
3
+ import {FlowCanvas, GroupDetailSidebar, LoadingSpinner, EmptyState, ErrorDisplay} from '$lib/components'
4
+ import {buildGroupOverviewGraph} from '$lib/utils/flow/groupOverviewGraph'
5
+ import {goto, invalidateAll} from '$app/navigation'
6
+ import {SvelteFlowProvider} from "@xyflow/svelte";
7
+
8
+ let { data } = $props<{ data: PageData }>()
9
+
10
+ const groups = $derived(data.groups)
11
+ const groupConnections = $derived(data.groupConnections)
12
+ const errorMessage = $derived(data.errorMessage)
13
+
14
+ let { graphInput, nodeGroupMap } = $derived.by(() => {
15
+ if (groups && groupConnections) {
16
+ const {graph, nodeToGroupMap} = buildGroupOverviewGraph(groups, groupConnections)
17
+ console.debug('[overview.svelte] built flow graph', {
18
+ groups: Object.keys(groups).length,
19
+ connections: groupConnections.length,
20
+ nodeCount: Object.keys(nodeToGroupMap).length,
21
+ hasGraph: !!graph
22
+ })
23
+ return { graphInput: graph, nodeGroupMap: nodeToGroupMap }
24
+ }
25
+ return { graphInput: null, nodeGroupMap: {} }
26
+ })
27
+
28
+ let isLoading = $derived(!groups || !groupConnections)
29
+ let hasDiagramContent = $derived(true)
30
+
31
+ function handleNodeDoubleClick(event: CustomEvent<string>) {
32
+ const groupId = nodeGroupMap[event.detail]
33
+ console.debug('[overview.svelte] nodeDoubleClick', {nodeId: event.detail, groupId})
34
+ if (groupId) {
35
+ goto(`/group/${groupId}`)
36
+ }
37
+ }
38
+
39
+ async function refreshData() {
40
+ await invalidateAll()
41
+ }
42
+ </script>
43
+
44
+ <svelte:head>
45
+ <title>Resource Mapper – Overview</title>
46
+ </svelte:head>
47
+
48
+ {#if errorMessage}
49
+ <ErrorDisplay
50
+ title="Error loading groups"
51
+ message="Failed to load the group overview diagram."
52
+ checkList={[
53
+ 'Ensure YAML data exists under /data/services',
54
+ 'Check the browser console for more details'
55
+ ]}
56
+ technicalDetails={errorMessage}
57
+ onRetry={refreshData}
58
+ />
59
+ {:else if isLoading}
60
+ <LoadingSpinner message="Loading group overview..."/>
61
+ {:else if !hasDiagramContent}
62
+ <EmptyState title="No Groups Found" message="No group definitions were discovered in the static data set."/>
63
+ {:else}
64
+ <div class="flex h-full w-full gap-1 lg:flex-row">
65
+ <SvelteFlowProvider>
66
+ <!-- Todo: make the pending variable dependant on the actual loading of data -->
67
+ <FlowCanvas
68
+ graph={graphInput}
69
+ pending={isLoading}
70
+ on:nodeDoubleClick={handleNodeDoubleClick}
71
+ />
72
+ <!-- Todo: Check if we can resize this sidebar on drag -->
73
+ <!-- Todo: Check if we can close the sidebar with a button in the center left of the sidebar when open -->
74
+ <GroupDetailSidebar
75
+ groupMap={nodeGroupMap}
76
+ groups={groups ?? {}}
77
+ placeholderMessage="Select a group to see details"
78
+ />
79
+ </SvelteFlowProvider>
80
+ </div>
81
+ {/if}
@@ -0,0 +1,31 @@
1
+ import type {PageLoad} from './$types'
2
+ import {getAllGroups} from '$lib/data/groups'
3
+ import {getAllGroupConnections} from '$lib/data/connections'
4
+ import {getAllTeams} from '$lib/data/teams'
5
+ import {selectedGroup} from "$lib/stores/diagram";
6
+
7
+ export const load: PageLoad = async () => {
8
+ try {
9
+ console.debug('[overview.load] start fetching data')
10
+ const [groups, groupConnections, teams] = await Promise.all([
11
+ getAllGroups(),
12
+ getAllGroupConnections(),
13
+ getAllTeams()
14
+ ])
15
+ console.debug('[overview.load] data fetched', {
16
+ groups,
17
+ groupConnections,
18
+ teams
19
+ })
20
+ selectedGroup.set(null);
21
+ return { groups, groupConnections, teams }
22
+ } catch (error) {
23
+ console.error('[overview.load] failed to fetch data', error)
24
+ return {
25
+ groups: null,
26
+ groupConnections: null,
27
+ teams: null,
28
+ errorMessage: error instanceof Error ? error.message : 'Failed to load overview data'
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,102 @@
1
+ <script lang="ts">
2
+ import type {PageData} from './$types'
3
+ import type {ExternalGroupServices, GroupInfo, ServiceDefinition} from '$shared/types'
4
+ import {FlowCanvas, LoadingOverlay, ErrorDisplay, EmptyState, ServiceDetailSidebar} from '$lib/components';
5
+ import type {FlowGraphInput} from '$shared/flow-types'
6
+ import {buildGroupServicesGraph} from '$lib/utils/flow/servicesGraph'
7
+ import {goto} from '$app/navigation'
8
+ import {SvelteFlowProvider} from '@xyflow/svelte';
9
+ import {selectedGroup} from "$lib/stores/diagram";
10
+
11
+ let {data}: { data: PageData } = $props()
12
+
13
+ let group = $derived(data.group)
14
+ let allGroups = $derived(data.groups)
15
+ let groupConnections = $derived(data.connections)
16
+ let services: ServiceDefinition[] = $derived(data.services ?? [])
17
+ let externalServices: ExternalGroupServices[] = $derived(data.externalServices ?? [])
18
+ let groupId = $derived(data.groupId)
19
+
20
+ let serviceNodeLookup = $state<Record<string, ServiceDefinition>>({})
21
+ let externalServiceLookup = $state<Record<string, { service: ServiceDefinition; group: GroupInfo }>>({})
22
+ let externalGroupNodeLookup = $state<Record<string, string>>({})
23
+ let groupGraph = $state<FlowGraphInput | null>(null)
24
+ let loadError = $state<string | null>(null)
25
+ let hasDiagramContent = $derived(!!(groupGraph && groupGraph.serviceNodes.length > 0))
26
+
27
+
28
+ $effect(() => {
29
+ if (!group) {
30
+ loadError = groupId ? `Unable to load group '${groupId}'.` : 'Missing group identifier.'
31
+ } else {
32
+ loadError = null
33
+ selectedGroup.set(data.group)
34
+ }
35
+ })
36
+
37
+ $effect(() => {
38
+ if (group) {
39
+ const result = buildGroupServicesGraph(group, services, allGroups, groupConnections, externalServices)
40
+ groupGraph = result.graph
41
+ serviceNodeLookup = result.serviceNodes
42
+ externalServiceLookup = result.externalNodes
43
+ externalGroupNodeLookup = Object.entries(result.externalNodes).reduce<Record<string, string>>((acc, [nodeId, entry]) => {
44
+ acc[nodeId] = entry.group.groupName
45
+ return acc
46
+ }, {})
47
+ console.debug('[group.svelte] built flow graph', {
48
+ group: group.groupName,
49
+ services: services.length,
50
+ externalEntries: externalServices.length,
51
+ })
52
+ } else {
53
+ groupGraph = null
54
+ serviceNodeLookup = {}
55
+ externalServiceLookup = {}
56
+ externalGroupNodeLookup = {}
57
+ }
58
+ })
59
+
60
+ function handleNodeDoubleClick(event: CustomEvent<string>) {
61
+ const nodeId = event.detail
62
+ const externalGroupId = externalGroupNodeLookup[nodeId]
63
+ console.debug('[group.svelte] nodeDoubleClick', {nodeId, externalGroupId})
64
+ if (externalGroupId) {
65
+ goto(`/group/${externalGroupId}`)
66
+ }
67
+ }
68
+ </script>
69
+
70
+ <svelte:head>
71
+ <title>Resource Mapper – {group?.name ?? 'Group'}</title>
72
+ </svelte:head>
73
+
74
+ {#if loadError}
75
+ <ErrorDisplay
76
+ title="Unable to load group"
77
+ message={loadError}
78
+ checkList={['Verify the group identifier in the URL', 'Ensure the YAML definition exists']}
79
+ onBack={() => goto('/')}
80
+ />
81
+ {:else if !group}
82
+ <LoadingOverlay message="Loading group..."/>
83
+ {:else if !hasDiagramContent}
84
+ <EmptyState title="No services" message="This group has no service diagram to display yet."/>
85
+ {:else}
86
+ <div class="flex h-full w-full gap-1 lg:flex-row">
87
+ <SvelteFlowProvider>
88
+ <FlowCanvas
89
+ graph={groupGraph}
90
+ pending={false}
91
+ on:nodeDoubleClick={handleNodeDoubleClick}
92
+ />
93
+ <!-- on:nodeClick={handleNodeClick}-->
94
+
95
+ <ServiceDetailSidebar
96
+ {group}
97
+ {serviceNodeLookup}
98
+ {externalServiceLookup}
99
+ />
100
+ </SvelteFlowProvider>
101
+ </div>
102
+ {/if}
@@ -0,0 +1,48 @@
1
+ import type {PageLoad} from './$types'
2
+ import {error} from '@sveltejs/kit'
3
+ import {getGroup} from '$lib/data/groups'
4
+ import {getExternalServicesForGroup, getServicesByGroup} from '$lib/data/services'
5
+ import {getAllTeams} from '$lib/data/teams'
6
+ import type {GroupInfo} from "$shared/types";
7
+ import {getConnectionsFromGroup, getConnectionsToGroup} from "$lib/data/connections";
8
+
9
+ export const load: PageLoad = async ({params}) => {
10
+ const {groupId} = params
11
+ if (!groupId) {
12
+ throw error(400, 'Missing group identifier')
13
+ }
14
+
15
+ const [services, externalServices, teams, connectionsFrom, connectionsTo] = await Promise.all([
16
+ getServicesByGroup(groupId),
17
+ getExternalServicesForGroup(groupId),
18
+ getAllTeams(),
19
+ getConnectionsFromGroup(groupId),
20
+ getConnectionsToGroup(groupId)
21
+ ])
22
+
23
+ const groupInfo = await getGroup(groupId);
24
+ if (!groupInfo) {
25
+ throw error(404, `Unknown group '${groupId}'`)
26
+ }
27
+
28
+ const allGroups: Map<string, GroupInfo> = new Map();
29
+ allGroups.set(groupInfo.id, groupInfo)
30
+
31
+ externalServices.forEach((externalService) => {
32
+ if (!allGroups.has(externalService.group.id)) {
33
+ allGroups.set(externalService.group.id, externalService.group)
34
+ }
35
+ })
36
+ const allConnections = connectionsFrom.concat(connectionsTo);
37
+
38
+ return {
39
+ groupId,
40
+ group: groupInfo,
41
+ groups: allGroups,
42
+ connections: allConnections,
43
+ services,
44
+ externalServices,
45
+ teams
46
+ }
47
+ }
48
+
File without changes
@@ -0,0 +1,3 @@
1
+ # allow crawling everything by default
2
+ User-agent: *
3
+ Disallow:
@@ -0,0 +1,28 @@
1
+ import adapter from '@sveltejs/adapter-static';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ const outDir = process.env.MAPPER_BUILD_OUT || 'build';
8
+
9
+ /** @type {import('@sveltejs/kit').Config} */
10
+ const config = {
11
+ kit: {
12
+ adapter: adapter({
13
+ pages: outDir,
14
+ assets: outDir,
15
+ fallback: "index.html",
16
+ precompress: true,
17
+ strict: true
18
+ }),
19
+ prerender: {
20
+ entries: ['*']
21
+ },
22
+ alias: {
23
+ $shared: path.resolve(dirname, '../shared/src'),
24
+ }
25
+ }
26
+ };
27
+
28
+ export default config;
@@ -0,0 +1,12 @@
1
+ module.exports = {
2
+ darkMode: 'class',
3
+ content: [
4
+ './src/**/*.{html,js,svelte,ts}',
5
+ '../shared/src/**/*.{ts}',
6
+ '../engine/src/**/*.{ts}'
7
+ ],
8
+ theme: {
9
+ extend: {},
10
+ },
11
+ plugins: [],
12
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": [
3
+ "../../tsconfig.base.json",
4
+ "./.svelte-kit/tsconfig.json"
5
+ ],
6
+ "compilerOptions": {
7
+ "outDir": "dist"
8
+ },
9
+ "include": [
10
+ "src",
11
+ ".",
12
+ "src/app.d.ts"
13
+ ],
14
+ "references": [
15
+ {
16
+ "path": "../shared"
17
+ },
18
+ {
19
+ "path": "../engine"
20
+ }
21
+ ]
22
+ }