@antelopejs/dms-automation 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 (305) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +95 -0
  3. package/dist/cluster/bus.d.ts +17 -0
  4. package/dist/cluster/bus.js +82 -0
  5. package/dist/cluster/bus.js.map +1 -0
  6. package/dist/cluster/instance.d.ts +1 -0
  7. package/dist/cluster/instance.js +6 -0
  8. package/dist/cluster/instance.js.map +1 -0
  9. package/dist/cluster/leader.d.ts +7 -0
  10. package/dist/cluster/leader.js +123 -0
  11. package/dist/cluster/leader.js.map +1 -0
  12. package/dist/cluster/mode.d.ts +3 -0
  13. package/dist/cluster/mode.js +25 -0
  14. package/dist/cluster/mode.js.map +1 -0
  15. package/dist/data/procedures-table.d.ts +66 -0
  16. package/dist/data/procedures-table.js +271 -0
  17. package/dist/data/procedures-table.js.map +1 -0
  18. package/dist/data/runs-table.d.ts +23 -0
  19. package/dist/data/runs-table.js +125 -0
  20. package/dist/data/runs-table.js.map +1 -0
  21. package/dist/db/index.d.ts +7 -0
  22. package/dist/db/index.js +24 -0
  23. package/dist/db/index.js.map +1 -0
  24. package/dist/db/models/automation_template.model.d.ts +58 -0
  25. package/dist/db/models/automation_template.model.js +134 -0
  26. package/dist/db/models/automation_template.model.js.map +1 -0
  27. package/dist/db/models/index.d.ts +3 -0
  28. package/dist/db/models/index.js +20 -0
  29. package/dist/db/models/index.js.map +1 -0
  30. package/dist/db/models/procedure.model.d.ts +25 -0
  31. package/dist/db/models/procedure.model.js +28 -0
  32. package/dist/db/models/procedure.model.js.map +1 -0
  33. package/dist/db/models/procedure_run.model.d.ts +34 -0
  34. package/dist/db/models/procedure_run.model.js +39 -0
  35. package/dist/db/models/procedure_run.model.js.map +1 -0
  36. package/dist/db/models/stats.model.d.ts +73 -0
  37. package/dist/db/models/stats.model.js +267 -0
  38. package/dist/db/models/stats.model.js.map +1 -0
  39. package/dist/db/models/type_config.model.d.ts +47 -0
  40. package/dist/db/models/type_config.model.js +51 -0
  41. package/dist/db/models/type_config.model.js.map +1 -0
  42. package/dist/db/tables/action_type_config.table.d.ts +7 -0
  43. package/dist/db/tables/action_type_config.table.js +35 -0
  44. package/dist/db/tables/action_type_config.table.js.map +1 -0
  45. package/dist/db/tables/automation_template.table.d.ts +11 -0
  46. package/dist/db/tables/automation_template.table.js +52 -0
  47. package/dist/db/tables/automation_template.table.js.map +1 -0
  48. package/dist/db/tables/index.d.ts +5 -0
  49. package/dist/db/tables/index.js +22 -0
  50. package/dist/db/tables/index.js.map +1 -0
  51. package/dist/db/tables/procedure.table.d.ts +10 -0
  52. package/dist/db/tables/procedure.table.js +48 -0
  53. package/dist/db/tables/procedure.table.js.map +1 -0
  54. package/dist/db/tables/procedure_run.table.d.ts +13 -0
  55. package/dist/db/tables/procedure_run.table.js +57 -0
  56. package/dist/db/tables/procedure_run.table.js.map +1 -0
  57. package/dist/db/tables/trigger_type_config.table.d.ts +7 -0
  58. package/dist/db/tables/trigger_type_config.table.js +35 -0
  59. package/dist/db/tables/trigger_type_config.table.js.map +1 -0
  60. package/dist/implementations/dms-automation/index.d.ts +19 -0
  61. package/dist/implementations/dms-automation/index.js +50 -0
  62. package/dist/implementations/dms-automation/index.js.map +1 -0
  63. package/dist/index.d.ts +14 -0
  64. package/dist/index.js +121 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/pages/builder.d.ts +10 -0
  67. package/dist/pages/builder.js +28 -0
  68. package/dist/pages/builder.js.map +1 -0
  69. package/dist/pages/category.d.ts +1 -0
  70. package/dist/pages/category.js +10 -0
  71. package/dist/pages/category.js.map +1 -0
  72. package/dist/pages/index.d.ts +6 -0
  73. package/dist/pages/index.js +9 -0
  74. package/dist/pages/index.js.map +1 -0
  75. package/dist/pages/library.d.ts +10 -0
  76. package/dist/pages/library.js +28 -0
  77. package/dist/pages/library.js.map +1 -0
  78. package/dist/pages/overview.d.ts +10 -0
  79. package/dist/pages/overview.js +28 -0
  80. package/dist/pages/overview.js.map +1 -0
  81. package/dist/pages/procedures.d.ts +10 -0
  82. package/dist/pages/procedures.js +72 -0
  83. package/dist/pages/procedures.js.map +1 -0
  84. package/dist/pages/runs.d.ts +11 -0
  85. package/dist/pages/runs.js +61 -0
  86. package/dist/pages/runs.js.map +1 -0
  87. package/dist/routes/action-types.d.ts +23 -0
  88. package/dist/routes/action-types.js +80 -0
  89. package/dist/routes/action-types.js.map +1 -0
  90. package/dist/routes/data-node-types.d.ts +6 -0
  91. package/dist/routes/data-node-types.js +36 -0
  92. package/dist/routes/data-node-types.js.map +1 -0
  93. package/dist/routes/graphSchema.d.ts +92 -0
  94. package/dist/routes/graphSchema.js +60 -0
  95. package/dist/routes/graphSchema.js.map +1 -0
  96. package/dist/routes/index.d.ts +8 -0
  97. package/dist/routes/index.js +11 -0
  98. package/dist/routes/index.js.map +1 -0
  99. package/dist/routes/node-kinds.d.ts +9 -0
  100. package/dist/routes/node-kinds.js +35 -0
  101. package/dist/routes/node-kinds.js.map +1 -0
  102. package/dist/routes/procedures.d.ts +23 -0
  103. package/dist/routes/procedures.js +181 -0
  104. package/dist/routes/procedures.js.map +1 -0
  105. package/dist/routes/runs.d.ts +7 -0
  106. package/dist/routes/runs.js +81 -0
  107. package/dist/routes/runs.js.map +1 -0
  108. package/dist/routes/stats.d.ts +6 -0
  109. package/dist/routes/stats.js +35 -0
  110. package/dist/routes/stats.js.map +1 -0
  111. package/dist/routes/templates.d.ts +20 -0
  112. package/dist/routes/templates.js +243 -0
  113. package/dist/routes/templates.js.map +1 -0
  114. package/dist/routes/trigger-types.d.ts +24 -0
  115. package/dist/routes/trigger-types.js +80 -0
  116. package/dist/routes/trigger-types.js.map +1 -0
  117. package/dist/routes/typeConfigShared.d.ts +22 -0
  118. package/dist/routes/typeConfigShared.js +37 -0
  119. package/dist/routes/typeConfigShared.js.map +1 -0
  120. package/dist/runtime/builtins/actions/http.d.ts +13 -0
  121. package/dist/runtime/builtins/actions/http.js +47 -0
  122. package/dist/runtime/builtins/actions/http.js.map +1 -0
  123. package/dist/runtime/builtins/actions/index.d.ts +2 -0
  124. package/dist/runtime/builtins/actions/index.js +7 -0
  125. package/dist/runtime/builtins/actions/index.js.map +1 -0
  126. package/dist/runtime/builtins/actions/log.d.ts +13 -0
  127. package/dist/runtime/builtins/actions/log.js +49 -0
  128. package/dist/runtime/builtins/actions/log.js.map +1 -0
  129. package/dist/runtime/builtins/data/boolean.d.ts +2 -0
  130. package/dist/runtime/builtins/data/boolean.js +39 -0
  131. package/dist/runtime/builtins/data/boolean.js.map +1 -0
  132. package/dist/runtime/builtins/data/compare.d.ts +2 -0
  133. package/dist/runtime/builtins/data/compare.js +29 -0
  134. package/dist/runtime/builtins/data/compare.js.map +1 -0
  135. package/dist/runtime/builtins/data/constant.d.ts +2 -0
  136. package/dist/runtime/builtins/data/constant.js +29 -0
  137. package/dist/runtime/builtins/data/constant.js.map +1 -0
  138. package/dist/runtime/builtins/data/index.d.ts +2 -0
  139. package/dist/runtime/builtins/data/index.js +20 -0
  140. package/dist/runtime/builtins/data/index.js.map +1 -0
  141. package/dist/runtime/builtins/data/json.d.ts +2 -0
  142. package/dist/runtime/builtins/data/json.js +46 -0
  143. package/dist/runtime/builtins/data/json.js.map +1 -0
  144. package/dist/runtime/builtins/data/math.d.ts +2 -0
  145. package/dist/runtime/builtins/data/math.js +53 -0
  146. package/dist/runtime/builtins/data/math.js.map +1 -0
  147. package/dist/runtime/builtins/data/object.d.ts +2 -0
  148. package/dist/runtime/builtins/data/object.js +121 -0
  149. package/dist/runtime/builtins/data/object.js.map +1 -0
  150. package/dist/runtime/builtins/data/string.d.ts +2 -0
  151. package/dist/runtime/builtins/data/string.js +143 -0
  152. package/dist/runtime/builtins/data/string.js.map +1 -0
  153. package/dist/runtime/builtins/index.d.ts +17 -0
  154. package/dist/runtime/builtins/index.js +24 -0
  155. package/dist/runtime/builtins/index.js.map +1 -0
  156. package/dist/runtime/builtins/nodes/action.d.ts +1 -0
  157. package/dist/runtime/builtins/nodes/action.js +36 -0
  158. package/dist/runtime/builtins/nodes/action.js.map +1 -0
  159. package/dist/runtime/builtins/nodes/data.d.ts +1 -0
  160. package/dist/runtime/builtins/nodes/data.js +28 -0
  161. package/dist/runtime/builtins/nodes/data.js.map +1 -0
  162. package/dist/runtime/builtins/nodes/delay.d.ts +1 -0
  163. package/dist/runtime/builtins/nodes/delay.js +41 -0
  164. package/dist/runtime/builtins/nodes/delay.js.map +1 -0
  165. package/dist/runtime/builtins/nodes/forRange.d.ts +1 -0
  166. package/dist/runtime/builtins/nodes/forRange.js +52 -0
  167. package/dist/runtime/builtins/nodes/forRange.js.map +1 -0
  168. package/dist/runtime/builtins/nodes/foreach.d.ts +1 -0
  169. package/dist/runtime/builtins/nodes/foreach.js +31 -0
  170. package/dist/runtime/builtins/nodes/foreach.js.map +1 -0
  171. package/dist/runtime/builtins/nodes/groupInput.d.ts +1 -0
  172. package/dist/runtime/builtins/nodes/groupInput.js +11 -0
  173. package/dist/runtime/builtins/nodes/groupInput.js.map +1 -0
  174. package/dist/runtime/builtins/nodes/groupOutput.d.ts +1 -0
  175. package/dist/runtime/builtins/nodes/groupOutput.js +11 -0
  176. package/dist/runtime/builtins/nodes/groupOutput.js.map +1 -0
  177. package/dist/runtime/builtins/nodes/if.d.ts +1 -0
  178. package/dist/runtime/builtins/nodes/if.js +23 -0
  179. package/dist/runtime/builtins/nodes/if.js.map +1 -0
  180. package/dist/runtime/builtins/nodes/parallel.d.ts +1 -0
  181. package/dist/runtime/builtins/nodes/parallel.js +34 -0
  182. package/dist/runtime/builtins/nodes/parallel.js.map +1 -0
  183. package/dist/runtime/builtins/nodes/retry.d.ts +1 -0
  184. package/dist/runtime/builtins/nodes/retry.js +46 -0
  185. package/dist/runtime/builtins/nodes/retry.js.map +1 -0
  186. package/dist/runtime/builtins/nodes/setVariable.d.ts +1 -0
  187. package/dist/runtime/builtins/nodes/setVariable.js +31 -0
  188. package/dist/runtime/builtins/nodes/setVariable.js.map +1 -0
  189. package/dist/runtime/builtins/nodes/switch.d.ts +1 -0
  190. package/dist/runtime/builtins/nodes/switch.js +33 -0
  191. package/dist/runtime/builtins/nodes/switch.js.map +1 -0
  192. package/dist/runtime/builtins/nodes/trigger.d.ts +1 -0
  193. package/dist/runtime/builtins/nodes/trigger.js +25 -0
  194. package/dist/runtime/builtins/nodes/trigger.js.map +1 -0
  195. package/dist/runtime/builtins/nodes/tryCatch.d.ts +1 -0
  196. package/dist/runtime/builtins/nodes/tryCatch.js +35 -0
  197. package/dist/runtime/builtins/nodes/tryCatch.js.map +1 -0
  198. package/dist/runtime/builtins/triggers/cron.d.ts +9 -0
  199. package/dist/runtime/builtins/triggers/cron.js +52 -0
  200. package/dist/runtime/builtins/triggers/cron.js.map +1 -0
  201. package/dist/runtime/builtins/triggers/index.d.ts +2 -0
  202. package/dist/runtime/builtins/triggers/index.js +12 -0
  203. package/dist/runtime/builtins/triggers/index.js.map +1 -0
  204. package/dist/runtime/builtins/triggers/manual.d.ts +3 -0
  205. package/dist/runtime/builtins/triggers/manual.js +20 -0
  206. package/dist/runtime/builtins/triggers/manual.js.map +1 -0
  207. package/dist/runtime/builtins/triggers/webhook.d.ts +14 -0
  208. package/dist/runtime/builtins/triggers/webhook.js +90 -0
  209. package/dist/runtime/builtins/triggers/webhook.js.map +1 -0
  210. package/dist/runtime/executionResult.d.ts +6 -0
  211. package/dist/runtime/executionResult.js +3 -0
  212. package/dist/runtime/executionResult.js.map +1 -0
  213. package/dist/runtime/executor.d.ts +9 -0
  214. package/dist/runtime/executor.js +300 -0
  215. package/dist/runtime/executor.js.map +1 -0
  216. package/dist/runtime/flatten.d.ts +3 -0
  217. package/dist/runtime/flatten.js +188 -0
  218. package/dist/runtime/flatten.js.map +1 -0
  219. package/dist/runtime/nodeKinds.d.ts +64 -0
  220. package/dist/runtime/nodeKinds.js +43 -0
  221. package/dist/runtime/nodeKinds.js.map +1 -0
  222. package/dist/runtime/reconcileDiff.d.ts +9 -0
  223. package/dist/runtime/reconcileDiff.js +26 -0
  224. package/dist/runtime/reconcileDiff.js.map +1 -0
  225. package/dist/runtime/registry.d.ts +19 -0
  226. package/dist/runtime/registry.js +47 -0
  227. package/dist/runtime/registry.js.map +1 -0
  228. package/dist/runtime/resolveInputs.d.ts +14 -0
  229. package/dist/runtime/resolveInputs.js +64 -0
  230. package/dist/runtime/resolveInputs.js.map +1 -0
  231. package/dist/runtime/resolveSubgraph.d.ts +7 -0
  232. package/dist/runtime/resolveSubgraph.js +15 -0
  233. package/dist/runtime/resolveSubgraph.js.map +1 -0
  234. package/dist/runtime/subscriptions.d.ts +16 -0
  235. package/dist/runtime/subscriptions.js +419 -0
  236. package/dist/runtime/subscriptions.js.map +1 -0
  237. package/dist/runtime/subwalk.d.ts +17 -0
  238. package/dist/runtime/subwalk.js +74 -0
  239. package/dist/runtime/subwalk.js.map +1 -0
  240. package/dist/runtime/topo.d.ts +3 -0
  241. package/dist/runtime/topo.js +75 -0
  242. package/dist/runtime/topo.js.map +1 -0
  243. package/dist/runtime/typeOrigin.d.ts +7 -0
  244. package/dist/runtime/typeOrigin.js +24 -0
  245. package/dist/runtime/typeOrigin.js.map +1 -0
  246. package/dist/runtime/validate.d.ts +10 -0
  247. package/dist/runtime/validate.js +185 -0
  248. package/dist/runtime/validate.js.map +1 -0
  249. package/dist/runtime/validateTemplate.d.ts +3 -0
  250. package/dist/runtime/validateTemplate.js +54 -0
  251. package/dist/runtime/validateTemplate.js.map +1 -0
  252. package/dist/types/constants.d.ts +6 -0
  253. package/dist/types/constants.js +10 -0
  254. package/dist/types/constants.js.map +1 -0
  255. package/dist/types/graph.d.ts +51 -0
  256. package/dist/types/graph.js +23 -0
  257. package/dist/types/graph.js.map +1 -0
  258. package/dist/types/pagination.d.ts +8 -0
  259. package/dist/types/pagination.js +3 -0
  260. package/dist/types/pagination.js.map +1 -0
  261. package/dist/types/runLog.d.ts +26 -0
  262. package/dist/types/runLog.js +3 -0
  263. package/dist/types/runLog.js.map +1 -0
  264. package/frontend-vue/.prettierignore +4 -0
  265. package/frontend-vue/app/components/Actions.vue +141 -0
  266. package/frontend-vue/app/components/Catalog/TypeConfigForm.vue +166 -0
  267. package/frontend-vue/app/components/Catalog/TypeList.vue +198 -0
  268. package/frontend-vue/app/components/DataNodes.vue +259 -0
  269. package/frontend-vue/app/components/Editor/BuilderPalette.vue +234 -0
  270. package/frontend-vue/app/components/Editor/GraphCanvas.vue +994 -0
  271. package/frontend-vue/app/components/Editor/GroupPortEditor.vue +289 -0
  272. package/frontend-vue/app/components/Editor/JsonField.vue +63 -0
  273. package/frontend-vue/app/components/Editor/NodeInspector.vue +356 -0
  274. package/frontend-vue/app/components/Editor/ProcedureList.vue +156 -0
  275. package/frontend-vue/app/components/Editor/RunsDrawer.vue +312 -0
  276. package/frontend-vue/app/components/Editor/WiredField.vue +42 -0
  277. package/frontend-vue/app/components/Editor/nodes/GenericNode.vue +161 -0
  278. package/frontend-vue/app/components/Editor/nodes/GroupInputNode.vue +19 -0
  279. package/frontend-vue/app/components/Editor/nodes/GroupNode.vue +200 -0
  280. package/frontend-vue/app/components/Editor/nodes/GroupOutputNode.vue +19 -0
  281. package/frontend-vue/app/components/Editor/nodes/GroupSentinel.vue +161 -0
  282. package/frontend-vue/app/components/Editor/nodes/styles.ts +53 -0
  283. package/frontend-vue/app/components/Editor.vue +2504 -0
  284. package/frontend-vue/app/components/Library.vue +67 -0
  285. package/frontend-vue/app/components/Overview.vue +503 -0
  286. package/frontend-vue/app/components/RunDetailDrawer.vue +165 -0
  287. package/frontend-vue/app/components/RunFireTree.vue +170 -0
  288. package/frontend-vue/app/components/RunTraceDrawer.vue +59 -0
  289. package/frontend-vue/app/components/RunsStats.vue +126 -0
  290. package/frontend-vue/app/components/RunsTimelineDisplay.vue +287 -0
  291. package/frontend-vue/app/components/Templates.vue +663 -0
  292. package/frontend-vue/app/components/Triggers.vue +137 -0
  293. package/frontend-vue/app/composables/useAutomationNodeKinds.ts +48 -0
  294. package/frontend-vue/app/composables/useAutomationRuns.ts +75 -0
  295. package/frontend-vue/app/composables/useGraphConnect.ts +542 -0
  296. package/frontend-vue/app/plugins/runs-timeline-display.client.ts +16 -0
  297. package/frontend-vue/app/utils/automation.ts +51 -0
  298. package/frontend-vue/dms.frontend.ts +26 -0
  299. package/frontend-vue/eslint.config.mjs +24 -0
  300. package/frontend-vue/i18n/locales/dms-automation-en-GB.json +369 -0
  301. package/frontend-vue/i18n/locales/dms-automation-fr-FR.json +369 -0
  302. package/frontend-vue/package.json +33 -0
  303. package/frontend-vue/pnpm-lock.yaml +6483 -0
  304. package/frontend-vue/pnpm-workspace.yaml +2 -0
  305. package/package.json +81 -0
@@ -0,0 +1,542 @@
1
+ import type { NodeKindEntry } from "./useAutomationNodeKinds";
2
+
3
+ /**
4
+ * Node kind is a free-form string keyed off the runtime kind registry
5
+ * (`/api/automation/node-kinds`). The few kinds the frontend has special
6
+ * Vue components for (`group`, `groupInput`, `groupOutput`) are still
7
+ * compared as string literals — they're not enumerated here.
8
+ */
9
+ export type NodeKind = string;
10
+
11
+ export interface GraphNode {
12
+ id: string;
13
+ kind: NodeKind;
14
+ typeId?: string;
15
+ config: Record<string, unknown>;
16
+ position: { x: number; y: number };
17
+ }
18
+
19
+ export interface TriggerEdge {
20
+ id: string;
21
+ from: { node: string; branch?: string };
22
+ // `to.branch` carries the target's trigger-in port name when the target is
23
+ // a group node or groupOutput sentinel. Absent for ordinary nodes whose
24
+ // trigger-in is a fixed single handle.
25
+ to: { node: string; branch?: string };
26
+ }
27
+
28
+ export interface DataEdge {
29
+ id: string;
30
+ from: { node: string; port: string };
31
+ to: { node: string; field: string };
32
+ }
33
+
34
+ export interface ProcedureGraph {
35
+ nodes: GraphNode[];
36
+ triggerEdges: TriggerEdge[];
37
+ dataEdges: DataEdge[];
38
+ }
39
+
40
+ export interface JsonSchemaProperty {
41
+ type?: string | string[];
42
+ title?: string;
43
+ description?: string;
44
+ enum?: unknown[];
45
+ default?: unknown;
46
+ [key: string]: unknown;
47
+ }
48
+
49
+ export interface JsonSchema {
50
+ type?: string;
51
+ properties?: Record<string, JsonSchemaProperty>;
52
+ required?: string[];
53
+ [key: string]: unknown;
54
+ }
55
+
56
+ // `module` names the AntelopeJS module that registered the type, and is set
57
+ // only for types coming from ANOTHER module (see src/runtime/typeOrigin.ts).
58
+ // The builder palette groups catalog entries by it, listing the unattributed
59
+ // ones — our own built-ins — first.
60
+ export interface TriggerType {
61
+ id: string;
62
+ name: string;
63
+ description?: string;
64
+ icon?: string;
65
+ module?: string;
66
+ configSchema?: JsonSchema;
67
+ outputSchema?: JsonSchema;
68
+ }
69
+
70
+ export interface ActionType {
71
+ id: string;
72
+ name: string;
73
+ description?: string;
74
+ icon?: string;
75
+ module?: string;
76
+ inputSchema?: JsonSchema;
77
+ outputSchema?: JsonSchema;
78
+ }
79
+
80
+ export interface DataNodeType {
81
+ id: string;
82
+ category: string;
83
+ name: string;
84
+ description: string;
85
+ icon: string;
86
+ module?: string;
87
+ inputSchema: JsonSchema;
88
+ outputSchema: JsonSchema;
89
+ // Optional editor-only config form. When set, the inspector renders this
90
+ // (not `inputSchema`) as the node's editable fields; data-in ports still
91
+ // derive from `inputSchema`. Used by constant nodes (value typed, not wired).
92
+ configSchema?: JsonSchema;
93
+ }
94
+
95
+ // Mirror of backend `GroupPort`. Local copy so the composable doesn't import
96
+ // from `src/types/graph.ts` (server-only path).
97
+ export interface GroupPort {
98
+ name: string;
99
+ kind: "data" | "trigger";
100
+ direction: "in" | "out";
101
+ schema?: JsonSchema;
102
+ }
103
+
104
+ /**
105
+ * A group node carries the ports its subgraph exposes, as a sibling field of
106
+ * `config`/`position` — mirrors `GroupNode` on the backend. `ports` is optional
107
+ * here because the value crosses the API: a group saved before it had any port,
108
+ * or a hand-written graph, simply omits it.
109
+ */
110
+ export interface GroupNode extends GraphNode {
111
+ kind: "group";
112
+ ports?: GroupPort[];
113
+ }
114
+
115
+ export function isGroupNode(node: GraphNode): node is GroupNode {
116
+ return node.kind === "group";
117
+ }
118
+
119
+ export type ConnectKind = "trigger" | "data";
120
+
121
+ export interface ConnectionLike {
122
+ id?: string;
123
+ source: string;
124
+ target: string;
125
+ sourceHandle?: string | null;
126
+ targetHandle?: string | null;
127
+ }
128
+
129
+ export type ConnectResult = { ok: true } | { ok: false; reason: string };
130
+
131
+ // Optional context for canConnect / schema lookups. `parentGroupPorts` is the
132
+ // declared port list of the outer group when the canvas is editing that
133
+ // group's inner subgraph — the sentinel nodes (`groupInput` / `groupOutput`)
134
+ // don't carry ports themselves, so their schemas must be looked up against
135
+ // the parent group. Undefined at the procedure-graph root.
136
+ export interface ConnectContext {
137
+ parentGroupPorts?: GroupPort[];
138
+ }
139
+
140
+ // --- handle prefix table ---------------------------------------------------
141
+ //
142
+ // Ordinary nodes:
143
+ // trigger-in (single fixed handle id)
144
+ // trigger-out-<branch>
145
+ // data-out-<port> / data-in-<field>
146
+ //
147
+ // Outer group node (kind === "group"):
148
+ // group-trigger-in-<port> / group-trigger-out-<port>
149
+ // group-data-in-<port> / group-data-out-<port>
150
+ //
151
+ // Sentinels inside a group's subgraph:
152
+ // groupInput-trigger-out-<port> (source side)
153
+ // groupInput-data-out-<port> (source side)
154
+ // groupOutput-trigger-in-<port> (target side)
155
+ // groupOutput-data-in-<port> (target side)
156
+ //
157
+ // Ordered longest-first so a prefix scan returns the most specific match.
158
+ const TRIGGER_PREFIX = "trigger-";
159
+ const DATA_OUT_PREFIX = "data-out-";
160
+ const DATA_IN_PREFIX = "data-in-";
161
+ const TRIGGER_OUT_PREFIX = "trigger-out-";
162
+
163
+ const GROUP_DATA_OUT_PREFIX = "group-data-out-";
164
+ const GROUP_DATA_IN_PREFIX = "group-data-in-";
165
+ const GROUP_TRIGGER_OUT_PREFIX = "group-trigger-out-";
166
+ const GROUP_TRIGGER_IN_PREFIX = "group-trigger-in-";
167
+
168
+ const GROUPINPUT_DATA_OUT_PREFIX = "groupInput-data-out-";
169
+ const GROUPINPUT_TRIGGER_OUT_PREFIX = "groupInput-trigger-out-";
170
+ const GROUPOUTPUT_DATA_IN_PREFIX = "groupOutput-data-in-";
171
+ const GROUPOUTPUT_TRIGGER_IN_PREFIX = "groupOutput-trigger-in-";
172
+
173
+ // Match longest-first — `group-trigger-out-` must beat `trigger-`.
174
+ const DATA_OUT_PREFIXES = [
175
+ GROUPINPUT_DATA_OUT_PREFIX,
176
+ GROUP_DATA_OUT_PREFIX,
177
+ DATA_OUT_PREFIX,
178
+ ] as const;
179
+ const DATA_IN_PREFIXES = [
180
+ GROUPOUTPUT_DATA_IN_PREFIX,
181
+ GROUP_DATA_IN_PREFIX,
182
+ DATA_IN_PREFIX,
183
+ ] as const;
184
+ const TRIGGER_OUT_PREFIXES = [
185
+ GROUPINPUT_TRIGGER_OUT_PREFIX,
186
+ GROUP_TRIGGER_OUT_PREFIX,
187
+ TRIGGER_OUT_PREFIX,
188
+ ] as const;
189
+ const TRIGGER_IN_PREFIXES = [
190
+ GROUPOUTPUT_TRIGGER_IN_PREFIX,
191
+ GROUP_TRIGGER_IN_PREFIX,
192
+ ] as const;
193
+
194
+ function stripPrefix(
195
+ handle: string | null | undefined,
196
+ prefixes: readonly string[],
197
+ ): string | null {
198
+ if (!handle) return null;
199
+ for (const p of prefixes) {
200
+ if (handle.startsWith(p)) {
201
+ const rest = handle.slice(p.length);
202
+ return rest || null;
203
+ }
204
+ }
205
+ return null;
206
+ }
207
+
208
+ export function connectionKind(
209
+ handle: string | null | undefined,
210
+ ): ConnectKind | null {
211
+ if (!handle) return null;
212
+ // Data first — `group-data-*` / `groupInput-data-*` / `groupOutput-data-*`
213
+ // would otherwise be ambiguous with the unrelated `trigger-` prefix only by
214
+ // length, but they don't share a leading token. Safe either way.
215
+ for (const p of DATA_OUT_PREFIXES) if (handle.startsWith(p)) return "data";
216
+ for (const p of DATA_IN_PREFIXES) if (handle.startsWith(p)) return "data";
217
+ for (const p of TRIGGER_OUT_PREFIXES)
218
+ if (handle.startsWith(p)) return "trigger";
219
+ for (const p of TRIGGER_IN_PREFIXES)
220
+ if (handle.startsWith(p)) return "trigger";
221
+ // Ordinary `trigger-in` / `trigger-out-…` fall through to this guard.
222
+ if (handle.startsWith(TRIGGER_PREFIX)) return "trigger";
223
+ return null;
224
+ }
225
+
226
+ export function parseSourceBranch(
227
+ sourceHandle: string | null | undefined,
228
+ ): string {
229
+ // Any of the trigger-out flavors yields a branch name. Ordinary edges
230
+ // without a recognized prefix default to "main" (`from.branch` is optional
231
+ // and stored as "main" elsewhere).
232
+ const branch = stripPrefix(sourceHandle, TRIGGER_OUT_PREFIXES);
233
+ return branch ?? "main";
234
+ }
235
+
236
+ // Pull the target port name out of a parametric trigger-in handle
237
+ // (`group-trigger-in-<port>` / `groupOutput-trigger-in-<port>`). Returns
238
+ // `undefined` for the fixed single `trigger-in` handle since ordinary
239
+ // nodes have no per-target port name.
240
+ export function parseTargetBranch(
241
+ targetHandle: string | null | undefined,
242
+ ): string | undefined {
243
+ const branch = stripPrefix(targetHandle, TRIGGER_IN_PREFIXES);
244
+ return branch ?? undefined;
245
+ }
246
+
247
+ export function parseDataPort(
248
+ sourceHandle: string | null | undefined,
249
+ ): string | null {
250
+ return stripPrefix(sourceHandle, DATA_OUT_PREFIXES);
251
+ }
252
+
253
+ export function parseDataField(
254
+ targetHandle: string | null | undefined,
255
+ ): string | null {
256
+ return stripPrefix(targetHandle, DATA_IN_PREFIXES);
257
+ }
258
+
259
+ function findNode(graph: ProcedureGraph, id: string): GraphNode | undefined {
260
+ return graph.nodes.find((n) => n.id === id);
261
+ }
262
+
263
+ // Build a JsonSchema synthesizing one property per port. Each property carries
264
+ // the port's declared schema verbatim — this lets the standard
265
+ // `schema.properties[port]` lookup work uniformly for ordinary nodes and
266
+ // group/sentinel kinds.
267
+ function portsToSchema(
268
+ ports: GroupPort[] | undefined,
269
+ direction: "in" | "out",
270
+ ): JsonSchema | undefined {
271
+ if (!Array.isArray(ports)) return undefined;
272
+ const properties: Record<string, JsonSchemaProperty> = {};
273
+ for (const p of ports) {
274
+ if (p.kind !== "data") continue;
275
+ if (p.direction !== direction) continue;
276
+ properties[p.name] = (p.schema as JsonSchemaProperty | undefined) ?? {};
277
+ }
278
+ return { properties };
279
+ }
280
+
281
+ // `group` nodes carry `ports` directly on the node.
282
+ function getNodePorts(node: GraphNode): GroupPort[] | undefined {
283
+ if (!isGroupNode(node)) return undefined;
284
+ return Array.isArray(node.ports) ? node.ports : undefined;
285
+ }
286
+
287
+ // Synthesize a JsonSchema from a kind's declared static port list. Each
288
+ // port becomes a property keyed by name; its declared `type`, if any,
289
+ // drops straight into the `type` field so the existing
290
+ // `typesCompatible(outProp.type, inProp.type)` check keeps working.
291
+ function portsToSyntheticSchema(ports: PortRef[]): JsonSchema {
292
+ const properties: Record<string, JsonSchemaProperty> = {};
293
+ for (const p of ports) {
294
+ properties[p.name] = p.type ? { type: p.type } : {};
295
+ }
296
+ return { properties };
297
+ }
298
+
299
+ interface PortRef {
300
+ name: string;
301
+ type?: string;
302
+ }
303
+
304
+ // Type-registry schemas have a small set of source schemas
305
+ // (`inputSchema`, `outputSchema`, `configSchema`); the per-kind UI
306
+ // metadata picks one via the `dataInsFromTypeSchema`/`dataOutsFromTypeSchema`
307
+ // hints.
308
+ function pickTypeSchema(
309
+ hint: "inputSchema" | "outputSchema" | "configSchema" | undefined,
310
+ type: TriggerType | ActionType | DataNodeType | undefined,
311
+ ): JsonSchema | undefined {
312
+ if (!hint || !type) return undefined;
313
+ const t = type as TriggerType & ActionType & Partial<DataNodeType>;
314
+ if (hint === "inputSchema") return t.inputSchema;
315
+ if (hint === "outputSchema") return t.outputSchema;
316
+ return t.configSchema;
317
+ }
318
+
319
+ function lookupTypeForRegistry(
320
+ registry: "triggers" | "actions" | "dataNodes" | undefined,
321
+ typeId: string | undefined,
322
+ triggerTypes: TriggerType[],
323
+ actionTypes: ActionType[],
324
+ dataNodeTypes: DataNodeType[],
325
+ ): TriggerType | ActionType | DataNodeType | undefined {
326
+ if (!registry || !typeId) return undefined;
327
+ if (registry === "triggers") return triggerTypes.find((t) => t.id === typeId);
328
+ if (registry === "actions") return actionTypes.find((a) => a.id === typeId);
329
+ return dataNodeTypes.find((d) => d.id === typeId);
330
+ }
331
+
332
+ function getOutputSchema(
333
+ node: GraphNode,
334
+ nodeKinds: NodeKindEntry[],
335
+ triggerTypes: TriggerType[],
336
+ actionTypes: ActionType[],
337
+ dataNodeTypes: DataNodeType[],
338
+ context?: ConnectContext,
339
+ ): JsonSchema | undefined {
340
+ // `group`/`groupInput` carry their port lists out-of-band: the outer
341
+ // group node stores `ports` directly, and `groupInput` sentinels reflect
342
+ // the enclosing group's data-in ports. Neither resolves through the `ui`
343
+ // lookup below (`group` isn't in the kind registry at all; `groupInput`
344
+ // registers without a `ui` block), so they need their own branches.
345
+ if (node.kind === "group") {
346
+ return portsToSchema(getNodePorts(node), "out");
347
+ }
348
+ if (node.kind === "groupInput") {
349
+ return portsToSchema(context?.parentGroupPorts, "in");
350
+ }
351
+
352
+ const ui = nodeKinds.find((k) => k.kind === node.kind)?.meta.ui;
353
+ if (!ui) return undefined;
354
+
355
+ // Type-registry-backed kinds pull their data-out shape from the
356
+ // selected type's schema. Falling back to the static port list is
357
+ // intentional — a `trigger` node with no type yet still exposes
358
+ // whatever staticDataOuts it declared (usually none).
359
+ if (ui.typeRegistry && ui.dataOutsFromTypeSchema) {
360
+ const type = lookupTypeForRegistry(
361
+ ui.typeRegistry,
362
+ node.typeId,
363
+ triggerTypes,
364
+ actionTypes,
365
+ dataNodeTypes,
366
+ );
367
+ const schema = pickTypeSchema(ui.dataOutsFromTypeSchema, type);
368
+ if (schema) return schema;
369
+ }
370
+
371
+ if (ui.staticDataOuts.length === 0) return undefined;
372
+ return portsToSyntheticSchema(ui.staticDataOuts);
373
+ }
374
+
375
+ function getInputSchema(
376
+ node: GraphNode,
377
+ nodeKinds: NodeKindEntry[],
378
+ triggerTypes: TriggerType[],
379
+ actionTypes: ActionType[],
380
+ dataNodeTypes: DataNodeType[],
381
+ context?: ConnectContext,
382
+ ): JsonSchema | undefined {
383
+ if (node.kind === "group") {
384
+ return portsToSchema(getNodePorts(node), "in");
385
+ }
386
+ if (node.kind === "groupOutput") {
387
+ return portsToSchema(context?.parentGroupPorts, "out");
388
+ }
389
+
390
+ const ui = nodeKinds.find((k) => k.kind === node.kind)?.meta.ui;
391
+ if (!ui) return undefined;
392
+
393
+ if (ui.typeRegistry && ui.dataInsFromTypeSchema) {
394
+ const type = lookupTypeForRegistry(
395
+ ui.typeRegistry,
396
+ node.typeId,
397
+ triggerTypes,
398
+ actionTypes,
399
+ dataNodeTypes,
400
+ );
401
+ const schema = pickTypeSchema(ui.dataInsFromTypeSchema, type);
402
+ if (schema) return schema;
403
+ }
404
+
405
+ if (ui.staticDataIns.length === 0) return undefined;
406
+ return portsToSyntheticSchema(ui.staticDataIns);
407
+ }
408
+
409
+ function typesCompatible(
410
+ a: string | string[] | undefined,
411
+ b: string | string[] | undefined,
412
+ ): boolean {
413
+ // Treat unknown / unspecified types as compatible.
414
+ if (!a || !b) return true;
415
+ const sa = Array.isArray(a) ? a : [a];
416
+ const sb = Array.isArray(b) ? b : [b];
417
+ if (sa.length === 0 || sb.length === 0) return true;
418
+ // Permissive: connect if there is any overlap between what the source can
419
+ // produce and what the target accepts. JSON Schema `type` may be a string
420
+ // or an array of strings (union).
421
+ return sa.some((t) => sb.includes(t));
422
+ }
423
+
424
+ // A trigger source handle is any of the recognized trigger-out flavors.
425
+ function isTriggerSourceHandle(handle: string | null | undefined): boolean {
426
+ if (!handle) return false;
427
+ return TRIGGER_OUT_PREFIXES.some((p) => handle.startsWith(p));
428
+ }
429
+
430
+ // A trigger target handle is either the fixed single `trigger-in` (ordinary
431
+ // nodes) or one of the parametric `group-trigger-in-<port>` /
432
+ // `groupOutput-trigger-in-<port>` flavors.
433
+ function isTriggerTargetHandle(handle: string | null | undefined): boolean {
434
+ if (!handle) return false;
435
+ if (handle === "trigger-in") return true;
436
+ return TRIGGER_IN_PREFIXES.some((p) => handle.startsWith(p));
437
+ }
438
+
439
+ export function canConnect(
440
+ graph: ProcedureGraph,
441
+ nodeKinds: NodeKindEntry[],
442
+ triggerTypes: TriggerType[],
443
+ actionTypes: ActionType[],
444
+ dataNodeTypes: DataNodeType[],
445
+ connection: ConnectionLike,
446
+ context?: ConnectContext,
447
+ ): ConnectResult {
448
+ const { source, target, sourceHandle, targetHandle } = connection;
449
+
450
+ // Defensive: Vue Flow may invoke `isValidConnection` during prop sync
451
+ // before the connection is fully populated. Don't reject these — return
452
+ // `ok` so the existing edge keeps rendering. The real connect path
453
+ // always supplies both endpoints.
454
+ if (!source || !target) return { ok: true };
455
+ if (source === target) return { ok: true };
456
+
457
+ const fromKind = connectionKind(sourceHandle);
458
+ const toKind = connectionKind(targetHandle);
459
+ if (!fromKind || !toKind) return { ok: false, reason: "Unrecognized handle" };
460
+ if (fromKind !== toKind)
461
+ return {
462
+ ok: false,
463
+ reason: "Cannot connect trigger handle to data handle",
464
+ };
465
+
466
+ const sourceNode = findNode(graph, source);
467
+ const targetNode = findNode(graph, target);
468
+ if (!sourceNode || !targetNode)
469
+ return { ok: false, reason: "Unknown node id" };
470
+
471
+ const selfId = connection.id;
472
+ // If the edge being validated is already in the graph (Vue Flow calls
473
+ // isValidConnection on every edge during prop sync / setEdges), it's a
474
+ // re-validation — trust the persisted state and skip stateful checks.
475
+ const isReValidation =
476
+ !!selfId &&
477
+ (graph.triggerEdges.some((e) => e.id === selfId) ||
478
+ graph.dataEdges.some((e) => e.id === selfId));
479
+
480
+ if (fromKind === "trigger") {
481
+ // Source must be one of the trigger-out flavors; target one of the
482
+ // trigger-in flavors. (No literal-handle check — the new prefixes
483
+ // `group-trigger-in-<port>` / `groupOutput-trigger-in-<port>` are
484
+ // valid in addition to the fixed `trigger-in`.)
485
+ if (!isTriggerSourceHandle(sourceHandle))
486
+ return { ok: false, reason: "Trigger source must be an out handle" };
487
+ if (!isTriggerTargetHandle(targetHandle))
488
+ return {
489
+ ok: false,
490
+ reason: "Trigger target must be a trigger-in handle",
491
+ };
492
+ // Stateful rules (divergence, duplicate) only apply to NEW connections.
493
+ // During re-validation of stored edges, trust the persisted state.
494
+ // Replacement of an existing outgoing edge is handled by the connect
495
+ // handler, not by rejection here.
496
+ //
497
+ // Note: divergence is enforced server-side by `validate.ts` via
498
+ // `nodeKinds.mayDiverge(kind)`. The frontend connect path replaces
499
+ // existing outgoing edges per source/branch in GraphCanvas.vue so
500
+ // divergence never actually occurs from non-whitelisted sources at
501
+ // the editor level.
502
+ return { ok: true };
503
+ }
504
+
505
+ // data edge — overwrite is allowed; replacement is handled by the caller.
506
+ const port = parseDataPort(sourceHandle);
507
+ const field = parseDataField(targetHandle);
508
+ if (!port || !field) return { ok: false, reason: "Invalid data handle id" };
509
+
510
+ if (isReValidation) return { ok: true };
511
+
512
+ const outSchema = getOutputSchema(
513
+ sourceNode,
514
+ nodeKinds,
515
+ triggerTypes,
516
+ actionTypes,
517
+ dataNodeTypes,
518
+ context,
519
+ );
520
+ const inSchema = getInputSchema(
521
+ targetNode,
522
+ nodeKinds,
523
+ triggerTypes,
524
+ actionTypes,
525
+ dataNodeTypes,
526
+ context,
527
+ );
528
+ const outProp = outSchema?.properties?.[port];
529
+ const inProp = inSchema?.properties?.[field];
530
+ if (outSchema && !outProp)
531
+ return { ok: false, reason: `Unknown source port "${port}"` };
532
+ if (inSchema && !inProp)
533
+ return { ok: false, reason: `Unknown target field "${field}"` };
534
+
535
+ if (!typesCompatible(outProp?.type, inProp?.type))
536
+ return {
537
+ ok: false,
538
+ reason: `Type mismatch: ${outProp?.type} → ${inProp?.type}`,
539
+ };
540
+
541
+ return { ok: true };
542
+ }
@@ -0,0 +1,16 @@
1
+ import { defineDmsPlugin } from "#dms-inertia/frontend-module";
2
+ import RunsTimelineDisplay from "../components/RunsTimelineDisplay.vue";
3
+
4
+ // Registers the "timeline" TableView display used by the Runs page
5
+ // (src/pages/runs.ts declares `displays: [{ id: 'timeline' }]`). Displays carry
6
+ // callables stripped from the SSR payload, so registration is client-only.
7
+ // `registerTableViewDisplay` is auto-imported from the dms-ui layer.
8
+ export default defineDmsPlugin(() => {
9
+ registerTableViewDisplay({
10
+ id: "timeline",
11
+ label: "dms_automation.runs.display.timeline",
12
+ icon: "i-ph-list-bullets",
13
+ order: 10,
14
+ component: RunsTimelineDisplay,
15
+ });
16
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Stable UUID for graph nodes and sentinel ids. Falls back to a short
3
+ * pseudo-random id when crypto.randomUUID is unavailable (older browsers
4
+ * over HTTP, jsdom in tests, …).
5
+ */
6
+ export function newId(): string {
7
+ if (
8
+ typeof globalThis.crypto !== "undefined" &&
9
+ typeof globalThis.crypto.randomUUID === "function"
10
+ )
11
+ return globalThis.crypto.randomUUID();
12
+ return `id-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
13
+ }
14
+
15
+ /**
16
+ * Backend list endpoints return one of:
17
+ * - a plain T[]
18
+ * - { results: T[] }
19
+ * - { items: T[] }
20
+ * This shrinks the per-call envelope-unwrap noise.
21
+ */
22
+ export interface ListEnvelope<T> {
23
+ results?: T[];
24
+ items?: T[];
25
+ }
26
+
27
+ export function unwrapList<T>(
28
+ data: T[] | ListEnvelope<T> | null | undefined,
29
+ ): T[] {
30
+ if (Array.isArray(data)) return data;
31
+ if (data && Array.isArray((data as ListEnvelope<T>).results))
32
+ return (data as ListEnvelope<T>).results ?? [];
33
+ if (data && Array.isArray((data as ListEnvelope<T>).items))
34
+ return (data as ListEnvelope<T>).items ?? [];
35
+ return [];
36
+ }
37
+
38
+ /**
39
+ * Human-readable duration from a millisecond count: `0ms` / `850ms` / `1.2s` /
40
+ * `3m 4s`. Shared by the runs KPI header and the runs timeline so the format
41
+ * stays in one place.
42
+ */
43
+ export function formatDuration(ms: number): string {
44
+ if (!ms || !Number.isFinite(ms)) return "0ms";
45
+ if (ms < 1000) return `${Math.round(ms)}ms`;
46
+ const sec = ms / 1000;
47
+ if (sec < 60) return `${sec.toFixed(1)}s`;
48
+ const m = Math.floor(sec / 60);
49
+ const rem = Math.round(sec % 60);
50
+ return `${m}m ${rem}s`;
51
+ }
@@ -0,0 +1,26 @@
1
+ import { defineAsyncComponent, type Component } from "vue";
2
+ import type { DmsFrontendModule } from "#dms-inertia/frontend-module";
3
+ import timelinePlugin from "./app/plugins/runs-timeline-display.client";
4
+
5
+ interface VueModule {
6
+ default: Component;
7
+ }
8
+
9
+ const components = import.meta.glob<VueModule>("./app/components/**/*.vue");
10
+
11
+ const frontendModule: DmsFrontendModule = {
12
+ setup(sdk) {
13
+ Object.entries(components)
14
+ .sort(([left], [right]) => left.localeCompare(right))
15
+ .forEach(([path, loader]) => {
16
+ const name = `DmsAutomation${path
17
+ .split("/")
18
+ .at(-1)!
19
+ .replace(/\.vue$/, "")}`;
20
+ sdk.registerComponent(name, defineAsyncComponent(loader));
21
+ });
22
+ sdk.registerPlugin(timelinePlugin, { clientOnly: true });
23
+ },
24
+ };
25
+
26
+ export default frontendModule;
@@ -0,0 +1,24 @@
1
+ import eslint from "@eslint/js";
2
+ import ts from "typescript-eslint";
3
+ import vue from "eslint-plugin-vue";
4
+ import globals from "globals";
5
+
6
+ export default ts.config(
7
+ { ignores: ["dist/**", "node_modules/**"] },
8
+ eslint.configs.recommended,
9
+ ...vue.configs["flat/essential"],
10
+ {
11
+ files: ["**/*.{ts,vue}"],
12
+ languageOptions: {
13
+ parserOptions: { parser: ts.parser },
14
+ globals: { ...globals.browser, ...globals.node },
15
+ },
16
+ rules: {
17
+ "no-undef": "off",
18
+ "no-unused-vars": "off",
19
+ "no-redeclare": "off",
20
+ "vue/multi-word-component-names": "off",
21
+ },
22
+ },
23
+ { files: ["**/*.ts"], languageOptions: { parser: ts.parser } },
24
+ );