eventmodeler 0.4.7 → 0.6.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 (204) hide show
  1. package/dist/api/client-config.js +10 -0
  2. package/dist/api/generated/client/client.gen.js +235 -0
  3. package/dist/api/generated/client/index.js +6 -0
  4. package/dist/api/generated/client/types.gen.js +2 -0
  5. package/dist/api/generated/client/utils.gen.js +228 -0
  6. package/dist/api/generated/client.gen.js +4 -0
  7. package/dist/api/generated/core/auth.gen.js +14 -0
  8. package/dist/api/generated/core/bodySerializer.gen.js +57 -0
  9. package/dist/api/generated/core/params.gen.js +100 -0
  10. package/dist/api/generated/core/pathSerializer.gen.js +106 -0
  11. package/dist/api/generated/core/queryKeySerializer.gen.js +92 -0
  12. package/dist/api/generated/core/serverSentEvents.gen.js +133 -0
  13. package/dist/api/generated/core/types.gen.js +2 -0
  14. package/dist/api/generated/core/utils.gen.js +87 -0
  15. package/dist/api/generated/index.js +2 -0
  16. package/dist/api/generated/sdk.gen.js +4222 -0
  17. package/dist/api/generated/types.gen.js +2 -0
  18. package/dist/api/generated/zod.gen.js +7217 -0
  19. package/dist/commands/add.js +315 -0
  20. package/dist/commands/auth.js +14 -0
  21. package/dist/commands/create.js +192 -0
  22. package/dist/commands/design.js +108 -0
  23. package/dist/commands/guide.js +15 -0
  24. package/dist/commands/init.js +21 -0
  25. package/dist/commands/list-schemas.js +177 -0
  26. package/dist/commands/list.js +39 -0
  27. package/dist/commands/loop.js +101 -0
  28. package/dist/commands/map.js +40 -0
  29. package/dist/commands/mark.js +27 -0
  30. package/dist/commands/move.js +35 -0
  31. package/dist/commands/remove.js +170 -0
  32. package/dist/commands/rename.js +53 -0
  33. package/dist/commands/resize.js +30 -0
  34. package/dist/commands/search.js +14 -0
  35. package/dist/commands/set.js +199 -0
  36. package/dist/commands/show-schemas.js +259 -0
  37. package/dist/commands/show.js +56 -0
  38. package/dist/commands/summary.js +13 -0
  39. package/dist/commands/update.js +240 -0
  40. package/dist/index.js +46 -2379
  41. package/dist/lib/auth.js +1 -1
  42. package/dist/lib/config.js +0 -15
  43. package/dist/lib/excalidraw-schema.js +66 -0
  44. package/dist/lib/globals.js +8 -0
  45. package/dist/lib/model.js +11 -0
  46. package/dist/lib/project-config.js +20 -0
  47. package/dist/lib/resolve.js +59 -0
  48. package/dist/lib/scenario.js +15 -0
  49. package/dist/slices/add-scenario/index.js +2 -206
  50. package/dist/slices/guide/guides/codegen.js +1 -1
  51. package/dist/slices/guide/guides/connect-slices.js +12 -37
  52. package/dist/slices/guide/guides/create-slices.js +110 -140
  53. package/dist/slices/guide/guides/explore.js +37 -26
  54. package/dist/slices/guide/guides/information-flow.js +70 -82
  55. package/dist/slices/guide/guides/scenarios.js +82 -137
  56. package/dist/slices/guide/index.js +6 -6
  57. package/dist/slices/help/index.js +96 -0
  58. package/dist/slices/help/topics/build-codegen.js +109 -0
  59. package/dist/slices/help/topics/build-slice.js +147 -0
  60. package/dist/slices/help/topics/check-completeness.js +57 -0
  61. package/dist/slices/help/topics/connect-slices.js +99 -0
  62. package/dist/slices/help/topics/explore-model.js +112 -0
  63. package/dist/slices/help/topics/json-reference.js +188 -0
  64. package/dist/slices/help/topics/linked-copies.js +89 -0
  65. package/dist/slices/help/topics/manipulate-canvas.js +150 -0
  66. package/dist/slices/help/topics/write-scenarios.js +162 -0
  67. package/dist/slices/init/index.js +10 -4
  68. package/dist/slices/init/loop.js +60 -0
  69. package/dist/slices/login/index.js +2 -2
  70. package/dist/slices/logout/index.js +2 -2
  71. package/dist/slices/whoami/index.js +11 -36
  72. package/package.json +8 -3
  73. package/dist/api/index.d.ts +0 -285
  74. package/dist/api/index.js +0 -323
  75. package/dist/cloud/slices/index.d.ts +0 -276
  76. package/dist/cloud/slices/index.js +0 -406
  77. package/dist/eventmodeler.js +0 -5646
  78. package/dist/formatters.d.ts +0 -17
  79. package/dist/formatters.js +0 -482
  80. package/dist/index.d.ts +0 -2
  81. package/dist/lib/auth.d.ts +0 -24
  82. package/dist/lib/backend.d.ts +0 -43
  83. package/dist/lib/backend.js +0 -73
  84. package/dist/lib/chapter-utils.d.ts +0 -13
  85. package/dist/lib/chapter-utils.js +0 -71
  86. package/dist/lib/cloud-client.d.ts +0 -69
  87. package/dist/lib/cloud-client.js +0 -364
  88. package/dist/lib/config.d.ts +0 -30
  89. package/dist/lib/diff/merge-rules.d.ts +0 -45
  90. package/dist/lib/diff/merge-rules.js +0 -210
  91. package/dist/lib/diff/model-differ.d.ts +0 -8
  92. package/dist/lib/diff/model-differ.js +0 -568
  93. package/dist/lib/diff/three-way-merge.d.ts +0 -7
  94. package/dist/lib/diff/three-way-merge.js +0 -390
  95. package/dist/lib/diff/types.d.ts +0 -75
  96. package/dist/lib/diff/types.js +0 -1
  97. package/dist/lib/element-lookup.d.ts +0 -58
  98. package/dist/lib/element-lookup.js +0 -126
  99. package/dist/lib/file-loader.d.ts +0 -8
  100. package/dist/lib/file-loader.js +0 -108
  101. package/dist/lib/flow-utils.d.ts +0 -53
  102. package/dist/lib/flow-utils.js +0 -348
  103. package/dist/lib/format.d.ts +0 -10
  104. package/dist/lib/format.js +0 -23
  105. package/dist/lib/project-config.d.ts +0 -27
  106. package/dist/lib/slice-utils.d.ts +0 -59
  107. package/dist/lib/slice-utils.js +0 -140
  108. package/dist/local/slices/index.d.ts +0 -11
  109. package/dist/local/slices/index.js +0 -13
  110. package/dist/projection.d.ts +0 -3
  111. package/dist/projection.js +0 -828
  112. package/dist/slices/add-field/index.d.ts +0 -8
  113. package/dist/slices/add-field/index.js +0 -211
  114. package/dist/slices/add-scenario/index.d.ts +0 -27
  115. package/dist/slices/codegen-chapter-events/index.d.ts +0 -2
  116. package/dist/slices/codegen-chapter-events/index.js +0 -145
  117. package/dist/slices/codegen-slice/index.d.ts +0 -2
  118. package/dist/slices/codegen-slice/index.js +0 -448
  119. package/dist/slices/create-automation-slice/index.d.ts +0 -2
  120. package/dist/slices/create-automation-slice/index.js +0 -304
  121. package/dist/slices/create-flow/index.d.ts +0 -2
  122. package/dist/slices/create-flow/index.js +0 -183
  123. package/dist/slices/create-state-change-slice/index.d.ts +0 -2
  124. package/dist/slices/create-state-change-slice/index.js +0 -263
  125. package/dist/slices/create-state-view-slice/index.d.ts +0 -2
  126. package/dist/slices/create-state-view-slice/index.js +0 -128
  127. package/dist/slices/diff/index.d.ts +0 -11
  128. package/dist/slices/diff/index.js +0 -293
  129. package/dist/slices/export-eventmodel-to-json/index.d.ts +0 -2
  130. package/dist/slices/export-eventmodel-to-json/index.js +0 -355
  131. package/dist/slices/git/index.d.ts +0 -2
  132. package/dist/slices/git/index.js +0 -125
  133. package/dist/slices/guide/guides/codegen.d.ts +0 -5
  134. package/dist/slices/guide/guides/connect-slices.d.ts +0 -5
  135. package/dist/slices/guide/guides/create-slices.d.ts +0 -5
  136. package/dist/slices/guide/guides/explore.d.ts +0 -5
  137. package/dist/slices/guide/guides/information-flow.d.ts +0 -5
  138. package/dist/slices/guide/guides/scenarios.d.ts +0 -5
  139. package/dist/slices/guide/index.d.ts +0 -1
  140. package/dist/slices/import/index.d.ts +0 -8
  141. package/dist/slices/import/index.js +0 -63
  142. package/dist/slices/init/index.d.ts +0 -5
  143. package/dist/slices/list-chapters/index.d.ts +0 -3
  144. package/dist/slices/list-chapters/index.js +0 -21
  145. package/dist/slices/list-commands/index.d.ts +0 -3
  146. package/dist/slices/list-commands/index.js +0 -20
  147. package/dist/slices/list-events/index.d.ts +0 -3
  148. package/dist/slices/list-events/index.js +0 -98
  149. package/dist/slices/list-processors/index.d.ts +0 -3
  150. package/dist/slices/list-processors/index.js +0 -20
  151. package/dist/slices/list-readmodels/index.d.ts +0 -3
  152. package/dist/slices/list-readmodels/index.js +0 -21
  153. package/dist/slices/list-scenarios/index.d.ts +0 -3
  154. package/dist/slices/list-scenarios/index.js +0 -35
  155. package/dist/slices/list-screens/index.d.ts +0 -3
  156. package/dist/slices/list-screens/index.js +0 -47
  157. package/dist/slices/list-slices/index.d.ts +0 -3
  158. package/dist/slices/list-slices/index.js +0 -35
  159. package/dist/slices/login/index.d.ts +0 -1
  160. package/dist/slices/logout/index.d.ts +0 -1
  161. package/dist/slices/map-fields/index.d.ts +0 -2
  162. package/dist/slices/map-fields/index.js +0 -269
  163. package/dist/slices/mark-slice-status/index.d.ts +0 -2
  164. package/dist/slices/mark-slice-status/index.js +0 -31
  165. package/dist/slices/merge/index.d.ts +0 -19
  166. package/dist/slices/merge/index.js +0 -147
  167. package/dist/slices/open-app/index.d.ts +0 -1
  168. package/dist/slices/remove-field/index.d.ts +0 -8
  169. package/dist/slices/remove-field/index.js +0 -167
  170. package/dist/slices/remove-scenario/index.d.ts +0 -2
  171. package/dist/slices/remove-scenario/index.js +0 -77
  172. package/dist/slices/search/index.d.ts +0 -3
  173. package/dist/slices/search/index.js +0 -302
  174. package/dist/slices/show-actor/index.d.ts +0 -4
  175. package/dist/slices/show-actor/index.js +0 -115
  176. package/dist/slices/show-aggregate/index.d.ts +0 -3
  177. package/dist/slices/show-aggregate/index.js +0 -108
  178. package/dist/slices/show-aggregate-completeness/index.d.ts +0 -4
  179. package/dist/slices/show-aggregate-completeness/index.js +0 -181
  180. package/dist/slices/show-chapter/index.d.ts +0 -3
  181. package/dist/slices/show-chapter/index.js +0 -195
  182. package/dist/slices/show-command/index.d.ts +0 -3
  183. package/dist/slices/show-command/index.js +0 -133
  184. package/dist/slices/show-completeness/index.d.ts +0 -4
  185. package/dist/slices/show-completeness/index.js +0 -731
  186. package/dist/slices/show-event/index.d.ts +0 -3
  187. package/dist/slices/show-event/index.js +0 -118
  188. package/dist/slices/show-model-summary/index.d.ts +0 -3
  189. package/dist/slices/show-model-summary/index.js +0 -31
  190. package/dist/slices/show-processor/index.d.ts +0 -3
  191. package/dist/slices/show-processor/index.js +0 -111
  192. package/dist/slices/show-readmodel/index.d.ts +0 -3
  193. package/dist/slices/show-readmodel/index.js +0 -158
  194. package/dist/slices/show-scenario/index.d.ts +0 -3
  195. package/dist/slices/show-scenario/index.js +0 -196
  196. package/dist/slices/show-screen/index.d.ts +0 -3
  197. package/dist/slices/show-screen/index.js +0 -139
  198. package/dist/slices/show-slice/index.d.ts +0 -3
  199. package/dist/slices/show-slice/index.js +0 -696
  200. package/dist/slices/update-field/index.d.ts +0 -15
  201. package/dist/slices/update-field/index.js +0 -208
  202. package/dist/slices/whoami/index.d.ts +0 -2
  203. package/dist/types.d.ts +0 -195
  204. package/dist/types.js +0 -1
@@ -1,731 +0,0 @@
1
- import { escapeXml, outputJson } from '../../lib/format.js';
2
- const INCOMING_FLOW_TYPES = {
3
- command: ['ScreenToCommand', 'ProcessorToCommand'],
4
- event: ['CommandToEvent'],
5
- readModel: ['EventToReadModel'],
6
- screen: ['ReadModelToScreen'],
7
- processor: ['ReadModelToProcessor'],
8
- };
9
- function findElementByName(model, name) {
10
- // Check for UUID lookup (id:prefix or full UUID format)
11
- if (name.startsWith('id:')) {
12
- const idSearch = name.slice(3).toLowerCase();
13
- // Search all element types by ID
14
- for (const cmd of model.commands.values()) {
15
- if (cmd.id.toLowerCase().startsWith(idSearch)) {
16
- return { element: { id: cmd.id, name: cmd.name, fields: cmd.fields, type: 'command' }, ambiguous: [] };
17
- }
18
- }
19
- for (const evt of model.events.values()) {
20
- if (evt.id.toLowerCase().startsWith(idSearch)) {
21
- return { element: { id: evt.id, name: evt.name, fields: evt.fields, type: 'event' }, ambiguous: [] };
22
- }
23
- }
24
- for (const rm of model.readModels.values()) {
25
- if (rm.id.toLowerCase().startsWith(idSearch)) {
26
- return { element: { id: rm.id, name: rm.name, fields: rm.fields, type: 'readModel', canonicalId: rm.canonicalId }, ambiguous: [] };
27
- }
28
- }
29
- for (const scr of model.screens.values()) {
30
- if (scr.id.toLowerCase().startsWith(idSearch)) {
31
- return { element: { id: scr.id, name: scr.name, fields: scr.fields, type: 'screen' }, ambiguous: [] };
32
- }
33
- }
34
- for (const proc of model.processors.values()) {
35
- if (proc.id.toLowerCase().startsWith(idSearch)) {
36
- return { element: { id: proc.id, name: proc.name, fields: proc.fields, type: 'processor' }, ambiguous: [] };
37
- }
38
- }
39
- return { element: null, ambiguous: [] };
40
- }
41
- // Check if it's a full UUID
42
- const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
43
- if (uuidPattern.test(name)) {
44
- const cmd = model.commands.get(name);
45
- if (cmd)
46
- return { element: { id: cmd.id, name: cmd.name, fields: cmd.fields, type: 'command' }, ambiguous: [] };
47
- const evt = model.events.get(name);
48
- if (evt)
49
- return { element: { id: evt.id, name: evt.name, fields: evt.fields, type: 'event' }, ambiguous: [] };
50
- const rm = model.readModels.get(name);
51
- if (rm)
52
- return { element: { id: rm.id, name: rm.name, fields: rm.fields, type: 'readModel', canonicalId: rm.canonicalId }, ambiguous: [] };
53
- const scr = model.screens.get(name);
54
- if (scr)
55
- return { element: { id: scr.id, name: scr.name, fields: scr.fields, type: 'screen' }, ambiguous: [] };
56
- const proc = model.processors.get(name);
57
- if (proc)
58
- return { element: { id: proc.id, name: proc.name, fields: proc.fields, type: 'processor' }, ambiguous: [] };
59
- return { element: null, ambiguous: [] };
60
- }
61
- // Case-insensitive exact name match across all types
62
- // Skip linked copies for events, readModels, and screens - they share identity with canonical original
63
- const nameLower = name.toLowerCase();
64
- const matches = [];
65
- for (const cmd of model.commands.values()) {
66
- if (cmd.name.toLowerCase() === nameLower) {
67
- matches.push({ element: { id: cmd.id, name: cmd.name, fields: cmd.fields, type: 'command' }, type: 'command' });
68
- }
69
- }
70
- for (const evt of model.events.values()) {
71
- if (!evt.originalNodeId && evt.name.toLowerCase() === nameLower) {
72
- matches.push({ element: { id: evt.id, name: evt.name, fields: evt.fields, type: 'event' }, type: 'event' });
73
- }
74
- }
75
- for (const rm of model.readModels.values()) {
76
- if (!rm.originalNodeId && rm.name.toLowerCase() === nameLower) {
77
- matches.push({ element: { id: rm.id, name: rm.name, fields: rm.fields, type: 'readModel', canonicalId: rm.canonicalId }, type: 'readModel' });
78
- }
79
- }
80
- for (const scr of model.screens.values()) {
81
- if (!scr.originalNodeId && scr.name.toLowerCase() === nameLower) {
82
- matches.push({ element: { id: scr.id, name: scr.name, fields: scr.fields, type: 'screen' }, type: 'screen' });
83
- }
84
- }
85
- for (const proc of model.processors.values()) {
86
- if (proc.name.toLowerCase() === nameLower) {
87
- matches.push({ element: { id: proc.id, name: proc.name, fields: proc.fields, type: 'processor' }, type: 'processor' });
88
- }
89
- }
90
- if (matches.length === 1) {
91
- return { element: matches[0].element, ambiguous: [] };
92
- }
93
- if (matches.length > 1) {
94
- return { element: null, ambiguous: matches };
95
- }
96
- return { element: null, ambiguous: [] };
97
- }
98
- function getSourceFields(model, sourceId) {
99
- const cmd = model.commands.get(sourceId);
100
- if (cmd)
101
- return cmd.fields;
102
- const evt = model.events.get(sourceId);
103
- if (evt)
104
- return evt.fields;
105
- const rm = model.readModels.get(sourceId);
106
- if (rm)
107
- return rm.fields;
108
- const scr = model.screens.get(sourceId);
109
- if (scr)
110
- return scr.fields;
111
- const proc = model.processors.get(sourceId);
112
- if (proc)
113
- return proc.fields;
114
- return [];
115
- }
116
- /**
117
- * Flattens nested Custom fields into a flat list with dot-notation paths.
118
- * When skipCustomParents is true, Custom fields with subfields are excluded.
119
- */
120
- function flattenFields(fields, prefix = '', skipCustomParents = false) {
121
- const result = [];
122
- for (const field of fields) {
123
- const path = prefix ? `${prefix}.${field.name}` : field.name;
124
- const isCustomParent = field.fieldType === 'Custom' &&
125
- field.subfields &&
126
- field.subfields.length > 0;
127
- if (!skipCustomParents || !isCustomParent) {
128
- result.push({ id: field.id, path, field });
129
- }
130
- if (field.fieldType === 'Custom' && field.subfields) {
131
- result.push(...flattenFields(field.subfields, path, skipCustomParents));
132
- }
133
- }
134
- return result;
135
- }
136
- /**
137
- * Calculates completeness for a read model using the union approach.
138
- * A field is satisfied if ANY incoming event provides it.
139
- * For linked copies, considers flows into ALL nodes in the canonical group.
140
- */
141
- function calculateReadModelUnionCompleteness(model, readModelId, readModelFields, canonicalId) {
142
- // Find all node IDs in the canonical group (original + all copies)
143
- let targetNodeIds;
144
- if (canonicalId) {
145
- targetNodeIds = [...model.readModels.values()]
146
- .filter(rm => rm.canonicalId === canonicalId)
147
- .map(rm => rm.id);
148
- }
149
- else {
150
- targetNodeIds = [readModelId];
151
- }
152
- // Find all incoming EventToReadModel flows to ANY node in the group
153
- const incomingFlows = [...model.flows.values()].filter(f => targetNodeIds.includes(f.targetId) && f.flowType === 'EventToReadModel');
154
- // Collect all source fields from all incoming events (union)
155
- const allSourceFields = [];
156
- const allManualMappings = [];
157
- const sourceEventNames = [];
158
- for (const flow of incomingFlows) {
159
- const sourceFields = getSourceFields(model, flow.sourceId);
160
- allSourceFields.push(...flattenFields(sourceFields));
161
- if (flow.fieldMappings) {
162
- allManualMappings.push(...flow.fieldMappings);
163
- }
164
- // Get event name for reporting
165
- const event = model.events.get(flow.sourceId);
166
- if (event && !sourceEventNames.includes(event.name)) {
167
- sourceEventNames.push(event.name);
168
- }
169
- }
170
- // Check each target field against the union of all sources
171
- // Skip custom parents - only check leaf fields
172
- const flatTarget = flattenFields(readModelFields, '', true);
173
- const untraceableFields = [];
174
- const optionalMissingFields = [];
175
- const fieldStatuses = [];
176
- for (const { id, path, field } of flatTarget) {
177
- // 1. Generated fields don't need a source
178
- if (field.isGenerated) {
179
- fieldStatuses.push({ fieldId: id, fieldName: path, status: 'generated' });
180
- continue;
181
- }
182
- // 2. Check if there's a manual mapping from any flow
183
- const manualMapping = allManualMappings.find(m => m.targetFieldId === id);
184
- if (manualMapping) {
185
- const sourceField = allSourceFields.find(sf => sf.id === manualMapping.sourceFieldId);
186
- fieldStatuses.push({
187
- fieldId: id,
188
- fieldName: path,
189
- status: 'satisfied',
190
- sourceFieldId: manualMapping.sourceFieldId,
191
- sourceFieldName: sourceField?.path,
192
- });
193
- continue;
194
- }
195
- // 3. Check auto-mapping by path match from any source
196
- const autoMatch = allSourceFields.find(sf => sf.path === path);
197
- if (autoMatch) {
198
- fieldStatuses.push({
199
- fieldId: id,
200
- fieldName: path,
201
- status: 'satisfied',
202
- sourceFieldId: autoMatch.id,
203
- sourceFieldName: autoMatch.path,
204
- });
205
- continue;
206
- }
207
- // 4. Optional fields missing is a warning, not an error
208
- if (field.isOptional) {
209
- optionalMissingFields.push(path);
210
- fieldStatuses.push({ fieldId: id, fieldName: path, status: 'optional-missing' });
211
- continue;
212
- }
213
- // Field is untraceable (required but no source)
214
- untraceableFields.push(path);
215
- fieldStatuses.push({ fieldId: id, fieldName: path, status: 'unsatisfied' });
216
- }
217
- return {
218
- isComplete: untraceableFields.length === 0,
219
- hasWarnings: optionalMissingFields.length > 0,
220
- untraceableFields,
221
- optionalMissingFields,
222
- sourceEvents: sourceEventNames,
223
- fieldStatuses,
224
- };
225
- }
226
- function calculateFlowCompleteness(model, flow, targetFields, sourceFields) {
227
- const flatSource = flattenFields(sourceFields);
228
- // Skip custom parents for target fields - only check leaf fields
229
- const flatTarget = flattenFields(targetFields, '', true);
230
- const manualMappings = flow.fieldMappings ?? [];
231
- // Get source entity name
232
- let sourceName = 'Unknown';
233
- let sourceType = 'unknown';
234
- const event = model.events.get(flow.sourceId);
235
- const readModel = model.readModels.get(flow.sourceId);
236
- const command = model.commands.get(flow.sourceId);
237
- const screen = model.screens.get(flow.sourceId);
238
- const processor = model.processors.get(flow.sourceId);
239
- if (event) {
240
- sourceName = event.name;
241
- sourceType = 'event';
242
- }
243
- else if (readModel) {
244
- sourceName = readModel.name;
245
- sourceType = 'read-model';
246
- }
247
- else if (command) {
248
- sourceName = command.name;
249
- sourceType = 'command';
250
- }
251
- else if (screen) {
252
- sourceName = screen.name;
253
- sourceType = 'screen';
254
- }
255
- else if (processor) {
256
- sourceName = processor.name;
257
- sourceType = 'processor';
258
- }
259
- const fieldStatuses = flatTarget.map(({ id, path, field }) => {
260
- // 1. Check if generated
261
- if (field.isGenerated) {
262
- return { fieldId: id, fieldName: path, status: 'generated' };
263
- }
264
- // 2. Check if user input
265
- if (field.isUserInput) {
266
- return { fieldId: id, fieldName: path, status: 'user-input' };
267
- }
268
- // 3. Check manual mapping
269
- const manualMapping = manualMappings.find(m => m.targetFieldId === id);
270
- if (manualMapping) {
271
- const sourceField = flatSource.find(sf => sf.id === manualMapping.sourceFieldId);
272
- return {
273
- fieldId: id,
274
- fieldName: path,
275
- status: 'satisfied',
276
- sourceFieldId: manualMapping.sourceFieldId,
277
- sourceFieldName: sourceField?.path,
278
- };
279
- }
280
- // 4. Check auto-mapping by path
281
- const autoMatch = flatSource.find(sf => sf.path === path);
282
- if (autoMatch) {
283
- return {
284
- fieldId: id,
285
- fieldName: path,
286
- status: 'satisfied',
287
- sourceFieldId: autoMatch.id,
288
- sourceFieldName: autoMatch.path,
289
- };
290
- }
291
- // 5. Not satisfied - check if optional
292
- if (field.isOptional) {
293
- return { fieldId: id, fieldName: path, status: 'optional-missing' };
294
- }
295
- // 6. Unsatisfied required field
296
- return { fieldId: id, fieldName: path, status: 'unsatisfied' };
297
- });
298
- const hasUnsatisfied = fieldStatuses.some(s => s.status === 'unsatisfied');
299
- const hasOptionalMissing = fieldStatuses.some(s => s.status === 'optional-missing');
300
- return {
301
- flowId: flow.id,
302
- sourceName,
303
- sourceType,
304
- isComplete: !hasUnsatisfied,
305
- hasWarnings: hasOptionalMissing,
306
- targetFields: fieldStatuses,
307
- sourceFields: flatSource.map(sf => ({
308
- id: sf.id,
309
- name: sf.path,
310
- type: sf.field.fieldType,
311
- isList: sf.field.isList,
312
- })),
313
- };
314
- }
315
- export function showCompleteness(model, elementName, format) {
316
- // Find the element by name (searches all element types)
317
- const { element, ambiguous } = findElementByName(model, elementName);
318
- if (ambiguous.length > 0) {
319
- console.error(`Error: Multiple elements found with name "${elementName}"`);
320
- console.error('Please specify using the element ID:');
321
- for (const match of ambiguous) {
322
- console.error(` - "${match.element.name}" (${match.type}) (id: ${match.element.id})`);
323
- }
324
- console.error('');
325
- console.error(`Usage: eventmodeler show completeness "id:${ambiguous[0].element.id.slice(0, 8)}"`);
326
- process.exit(1);
327
- }
328
- if (!element) {
329
- console.error(`Error: Element not found: "${elementName}"`);
330
- // List all available elements with their IDs
331
- const allElements = [];
332
- for (const cmd of model.commands.values()) {
333
- allElements.push({ name: cmd.name, type: 'command', id: cmd.id });
334
- }
335
- for (const evt of model.events.values()) {
336
- allElements.push({ name: evt.name, type: 'event', id: evt.id });
337
- }
338
- for (const rm of model.readModels.values()) {
339
- allElements.push({ name: rm.name, type: 'read model', id: rm.id });
340
- }
341
- for (const scr of model.screens.values()) {
342
- allElements.push({ name: scr.name, type: 'screen', id: scr.id });
343
- }
344
- for (const proc of model.processors.values()) {
345
- allElements.push({ name: proc.name, type: 'processor', id: proc.id });
346
- }
347
- if (allElements.length > 0) {
348
- console.error('Available elements:');
349
- for (const el of allElements) {
350
- console.error(` - "${el.name}" (${el.type}) (id: ${el.id.slice(0, 8)})`);
351
- }
352
- }
353
- else {
354
- console.error('No elements exist in the model.');
355
- }
356
- process.exit(1);
357
- }
358
- const elementTypeLabel = element.type === 'readModel' ? 'read-model' : element.type;
359
- // For read models, use union completeness (all incoming events combined)
360
- if (element.type === 'readModel') {
361
- const unionResult = calculateReadModelUnionCompleteness(model, element.id, element.fields, element.canonicalId);
362
- // Check if there are any incoming flows at all
363
- let targetNodeIds;
364
- if (element.canonicalId) {
365
- targetNodeIds = [...model.readModels.values()]
366
- .filter(rm => rm.canonicalId === element.canonicalId)
367
- .map(rm => rm.id);
368
- }
369
- else {
370
- targetNodeIds = [element.id];
371
- }
372
- const hasIncomingFlows = [...model.flows.values()].some(f => targetNodeIds.includes(f.targetId) && f.flowType === 'EventToReadModel');
373
- if (!hasIncomingFlows) {
374
- if (format === 'json') {
375
- outputJson({
376
- elementType: elementTypeLabel,
377
- name: element.name,
378
- message: `No flows into this ${elementTypeLabel}`
379
- });
380
- }
381
- else {
382
- console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}">`);
383
- console.log(` <no-flows>No flows into this ${elementTypeLabel}</no-flows>`);
384
- console.log('</completeness>');
385
- }
386
- return;
387
- }
388
- const overallStatus = unionResult.isComplete ? 'complete' : 'incomplete';
389
- const flatTargetFields = flattenFields(element.fields);
390
- if (format === 'json') {
391
- outputJson({
392
- elementType: elementTypeLabel,
393
- name: element.name,
394
- status: overallStatus,
395
- ...(unionResult.hasWarnings ? { hasWarnings: true } : {}),
396
- sourceEvents: unionResult.sourceEvents,
397
- ...(unionResult.untraceableFields.length > 0 ? { untraceableFields: unionResult.untraceableFields } : {}),
398
- ...(unionResult.optionalMissingFields.length > 0 ? { optionalMissingFields: unionResult.optionalMissingFields } : {}),
399
- fieldStatuses: unionResult.fieldStatuses.map(f => ({
400
- name: f.fieldName,
401
- status: f.status,
402
- ...(f.sourceFieldName ? { source: f.sourceFieldName } : {})
403
- })),
404
- elementFields: flatTargetFields.map(({ id, path, field }) => ({
405
- id,
406
- name: path,
407
- type: field.fieldType,
408
- ...(field.isList ? { isList: true } : {}),
409
- ...(field.isOptional ? { isOptional: true } : {}),
410
- ...(field.isGenerated ? { isGenerated: true } : {})
411
- }))
412
- });
413
- return;
414
- }
415
- // XML output for read models
416
- console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}" status="${overallStatus}"${unionResult.hasWarnings ? ' hasWarnings="true"' : ''}>`);
417
- console.log(' <source-events>');
418
- for (const eventName of unionResult.sourceEvents) {
419
- console.log(` <event name="${escapeXml(eventName)}"/>`);
420
- }
421
- console.log(' </source-events>');
422
- if (unionResult.untraceableFields.length > 0) {
423
- console.log(' <untraceable-fields>');
424
- for (const fieldName of unionResult.untraceableFields) {
425
- console.log(` <field name="${escapeXml(fieldName)}"/>`);
426
- }
427
- console.log(' </untraceable-fields>');
428
- }
429
- if (unionResult.optionalMissingFields.length > 0) {
430
- console.log(' <optional-missing-fields>');
431
- for (const fieldName of unionResult.optionalMissingFields) {
432
- console.log(` <field name="${escapeXml(fieldName)}"/>`);
433
- }
434
- console.log(' </optional-missing-fields>');
435
- }
436
- console.log(' <field-statuses>');
437
- for (const field of unionResult.fieldStatuses) {
438
- if (field.sourceFieldName) {
439
- console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}" source="${escapeXml(field.sourceFieldName)}"/>`);
440
- }
441
- else {
442
- console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}"/>`);
443
- }
444
- }
445
- console.log(' </field-statuses>');
446
- console.log(` <${elementTypeLabel}-fields>`);
447
- for (const { id, path, field } of flatTargetFields) {
448
- const attrs = [`id="${id}"`, `name="${escapeXml(path)}"`, `type="${field.fieldType}"`];
449
- if (field.isList)
450
- attrs.push('isList="true"');
451
- if (field.isOptional)
452
- attrs.push('isOptional="true"');
453
- if (field.isGenerated)
454
- attrs.push('isGenerated="true"');
455
- console.log(` <field ${attrs.join(' ')}/>`);
456
- }
457
- console.log(` </${elementTypeLabel}-fields>`);
458
- console.log('</completeness>');
459
- return;
460
- }
461
- // For non-read-model elements, use per-flow completeness
462
- const validFlowTypes = INCOMING_FLOW_TYPES[element.type];
463
- const incomingFlows = [...model.flows.values()].filter(f => f.targetId === element.id && validFlowTypes.includes(f.flowType));
464
- if (incomingFlows.length === 0) {
465
- if (format === 'json') {
466
- outputJson({
467
- elementType: elementTypeLabel,
468
- name: element.name,
469
- message: `No flows into this ${elementTypeLabel}`
470
- });
471
- }
472
- else {
473
- console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}">`);
474
- console.log(` <no-flows>No flows into this ${elementTypeLabel}</no-flows>`);
475
- console.log('</completeness>');
476
- }
477
- return;
478
- }
479
- // Calculate completeness for each flow
480
- const completenessResults = [];
481
- for (const flow of incomingFlows) {
482
- const sourceFields = getSourceFields(model, flow.sourceId);
483
- if (sourceFields.length === 0 && element.fields.length > 0) {
484
- // Source has no fields but target expects some - still calculate
485
- }
486
- const result = calculateFlowCompleteness(model, flow, element.fields, sourceFields);
487
- completenessResults.push(result);
488
- }
489
- const overallComplete = completenessResults.every(r => r.isComplete);
490
- const overallStatus = overallComplete ? 'complete' : 'incomplete';
491
- if (format === 'json') {
492
- const flatTargetFields = flattenFields(element.fields);
493
- outputJson({
494
- elementType: elementTypeLabel,
495
- name: element.name,
496
- status: overallStatus,
497
- flows: completenessResults.map(result => ({
498
- flowId: result.flowId,
499
- from: result.sourceName,
500
- sourceType: result.sourceType,
501
- status: result.isComplete ? 'complete' : 'incomplete',
502
- ...(result.hasWarnings ? { hasWarnings: true } : {}),
503
- targetFields: result.targetFields.map(f => ({
504
- name: f.fieldName,
505
- status: f.status,
506
- ...(f.sourceFieldName ? { source: f.sourceFieldName } : {})
507
- })),
508
- availableSourceFields: result.sourceFields.map(sf => ({
509
- id: sf.id,
510
- name: sf.name,
511
- type: sf.type,
512
- ...(sf.isList ? { isList: true } : {})
513
- }))
514
- })),
515
- elementFields: flatTargetFields.map(({ id, path, field }) => ({
516
- id,
517
- name: path,
518
- type: field.fieldType,
519
- ...(field.isList ? { isList: true } : {}),
520
- ...(field.isOptional ? { isOptional: true } : {}),
521
- ...(field.isGenerated ? { isGenerated: true } : {})
522
- }))
523
- });
524
- return;
525
- }
526
- // Output XML
527
- console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}" status="${overallStatus}">`);
528
- for (const result of completenessResults) {
529
- const flowStatus = result.isComplete ? 'complete' : 'incomplete';
530
- const warningAttr = result.hasWarnings ? ' hasWarnings="true"' : '';
531
- console.log(` <flow id="${result.flowId}" from="${escapeXml(result.sourceName)}" type="${result.sourceType}" status="${flowStatus}"${warningAttr}>`);
532
- // Target fields
533
- console.log(' <target-fields>');
534
- for (const field of result.targetFields) {
535
- if (field.sourceFieldName) {
536
- console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}" source="${escapeXml(field.sourceFieldName)}"/>`);
537
- }
538
- else {
539
- console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}"/>`);
540
- }
541
- }
542
- console.log(' </target-fields>');
543
- // Available source fields (for AI to see what's available)
544
- console.log(' <available-source-fields>');
545
- for (const sf of result.sourceFields) {
546
- const listAttr = sf.isList ? ' isList="true"' : '';
547
- console.log(` <field id="${sf.id}" name="${escapeXml(sf.name)}" type="${sf.type}"${listAttr}/>`);
548
- }
549
- console.log(' </available-source-fields>');
550
- console.log(' </flow>');
551
- }
552
- // Also show the element's fields with their IDs (needed for mapping)
553
- console.log(` <${elementTypeLabel}-fields>`);
554
- const flatTargetFields = flattenFields(element.fields);
555
- for (const { id, path, field } of flatTargetFields) {
556
- const attrs = [`id="${id}"`, `name="${escapeXml(path)}"`, `type="${field.fieldType}"`];
557
- if (field.isList)
558
- attrs.push('isList="true"');
559
- if (field.isOptional)
560
- attrs.push('isOptional="true"');
561
- if (field.isGenerated)
562
- attrs.push('isGenerated="true"');
563
- console.log(` <field ${attrs.join(' ')}/>`);
564
- }
565
- console.log(` </${elementTypeLabel}-fields>`);
566
- console.log('</completeness>');
567
- }
568
- export function showModelCompleteness(model, format) {
569
- const allFlows = [...model.flows.values()];
570
- const incompleteFlows = [];
571
- let completeCount = 0;
572
- let totalCount = 0;
573
- // For read models, we use union completeness - track which canonical groups we've processed
574
- const processedReadModelGroups = new Set();
575
- const incompleteReadModels = [];
576
- let readModelCompleteCount = 0;
577
- let readModelTotalCount = 0;
578
- for (const flow of allFlows) {
579
- // Skip EventToReadModel flows - we handle read models separately with union logic
580
- if (flow.flowType === 'EventToReadModel') {
581
- continue;
582
- }
583
- // Get target element info
584
- let targetName = 'Unknown';
585
- let targetType = 'unknown';
586
- let targetFields = [];
587
- const targetCmd = model.commands.get(flow.targetId);
588
- const targetEvt = model.events.get(flow.targetId);
589
- const targetScr = model.screens.get(flow.targetId);
590
- const targetProc = model.processors.get(flow.targetId);
591
- if (targetCmd) {
592
- targetName = targetCmd.name;
593
- targetType = 'command';
594
- targetFields = targetCmd.fields;
595
- }
596
- else if (targetEvt) {
597
- targetName = targetEvt.name;
598
- targetType = 'event';
599
- targetFields = targetEvt.fields;
600
- }
601
- else if (targetScr) {
602
- targetName = targetScr.name;
603
- targetType = 'screen';
604
- targetFields = targetScr.fields;
605
- }
606
- else if (targetProc) {
607
- targetName = targetProc.name;
608
- targetType = 'processor';
609
- targetFields = targetProc.fields;
610
- }
611
- // Get source fields
612
- const sourceFields = getSourceFields(model, flow.sourceId);
613
- // Calculate completeness
614
- const result = calculateFlowCompleteness(model, flow, targetFields, sourceFields);
615
- totalCount++;
616
- if (result.isComplete) {
617
- completeCount++;
618
- }
619
- else {
620
- const unsatisfiedFields = result.targetFields
621
- .filter(f => f.status === 'unsatisfied')
622
- .map(f => f.fieldName);
623
- incompleteFlows.push({
624
- flow,
625
- sourceName: result.sourceName,
626
- targetName,
627
- targetType,
628
- unsatisfiedFields,
629
- });
630
- }
631
- }
632
- // Process read models with union completeness
633
- // Group read models by canonicalId (or use id if no canonicalId)
634
- for (const rm of model.readModels.values()) {
635
- const groupKey = rm.canonicalId ?? rm.id;
636
- // Skip if we've already processed this canonical group
637
- if (processedReadModelGroups.has(groupKey)) {
638
- continue;
639
- }
640
- processedReadModelGroups.add(groupKey);
641
- // Check if this read model has any incoming EventToReadModel flows
642
- let targetNodeIds;
643
- if (rm.canonicalId) {
644
- targetNodeIds = [...model.readModels.values()]
645
- .filter(r => r.canonicalId === rm.canonicalId)
646
- .map(r => r.id);
647
- }
648
- else {
649
- targetNodeIds = [rm.id];
650
- }
651
- const hasIncomingFlows = allFlows.some(f => targetNodeIds.includes(f.targetId) && f.flowType === 'EventToReadModel');
652
- if (!hasIncomingFlows) {
653
- // No incoming flows - skip this read model
654
- continue;
655
- }
656
- readModelTotalCount++;
657
- const unionResult = calculateReadModelUnionCompleteness(model, rm.id, rm.fields, rm.canonicalId);
658
- if (unionResult.isComplete) {
659
- readModelCompleteCount++;
660
- }
661
- else {
662
- incompleteReadModels.push({
663
- name: rm.name,
664
- untraceableFields: unionResult.untraceableFields,
665
- sourceEvents: unionResult.sourceEvents,
666
- });
667
- }
668
- }
669
- const incompleteCount = totalCount - completeCount;
670
- const readModelIncompleteCount = readModelTotalCount - readModelCompleteCount;
671
- if (format === 'json') {
672
- outputJson({
673
- summary: {
674
- complete: completeCount + readModelCompleteCount,
675
- incomplete: incompleteCount + readModelIncompleteCount,
676
- total: totalCount + readModelTotalCount
677
- },
678
- incompleteFlows: incompleteFlows.map(item => ({
679
- from: item.sourceName,
680
- to: item.targetName,
681
- targetType: item.targetType,
682
- flowType: item.flow.flowType,
683
- unsatisfiedFields: item.unsatisfiedFields
684
- })),
685
- ...(incompleteReadModels.length > 0 ? {
686
- incompleteReadModels: incompleteReadModels.map(item => ({
687
- name: item.name,
688
- untraceableFields: item.untraceableFields,
689
- sourceEvents: item.sourceEvents
690
- }))
691
- } : {})
692
- });
693
- return;
694
- }
695
- console.log('<model-completeness>');
696
- console.log(` <summary complete="${completeCount + readModelCompleteCount}" incomplete="${incompleteCount + readModelIncompleteCount}" total="${totalCount + readModelTotalCount}"/>`);
697
- if (incompleteFlows.length > 0) {
698
- console.log(' <incomplete-flows>');
699
- for (const item of incompleteFlows) {
700
- console.log(` <flow from="${escapeXml(item.sourceName)}" to="${escapeXml(item.targetName)}" target-type="${item.targetType}" flow-type="${item.flow.flowType}">`);
701
- if (item.unsatisfiedFields.length > 0) {
702
- console.log(' <unsatisfied-fields>');
703
- for (const fieldName of item.unsatisfiedFields) {
704
- console.log(` <field name="${escapeXml(fieldName)}"/>`);
705
- }
706
- console.log(' </unsatisfied-fields>');
707
- }
708
- console.log(' </flow>');
709
- }
710
- console.log(' </incomplete-flows>');
711
- }
712
- if (incompleteReadModels.length > 0) {
713
- console.log(' <incomplete-read-models>');
714
- for (const item of incompleteReadModels) {
715
- console.log(` <read-model name="${escapeXml(item.name)}">`);
716
- console.log(' <untraceable-fields>');
717
- for (const fieldName of item.untraceableFields) {
718
- console.log(` <field name="${escapeXml(fieldName)}"/>`);
719
- }
720
- console.log(' </untraceable-fields>');
721
- console.log(' <source-events>');
722
- for (const eventName of item.sourceEvents) {
723
- console.log(` <event name="${escapeXml(eventName)}"/>`);
724
- }
725
- console.log(' </source-events>');
726
- console.log(' </read-model>');
727
- }
728
- console.log(' </incomplete-read-models>');
729
- }
730
- console.log('</model-completeness>');
731
- }