eventmodeler 0.2.1 → 0.2.4
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.
- package/README.md +175 -0
- package/dist/index.js +114 -19
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +26 -0
- package/dist/lib/element-lookup.d.ts +47 -0
- package/dist/lib/element-lookup.js +86 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +11 -0
- package/dist/lib/slice-utils.d.ts +83 -0
- package/dist/lib/slice-utils.js +135 -0
- package/dist/projection.js +161 -35
- package/dist/slices/add-field/index.js +4 -33
- package/dist/slices/add-scenario/index.js +7 -74
- package/dist/slices/create-automation-slice/index.d.ts +2 -0
- package/dist/slices/create-automation-slice/index.js +217 -0
- package/dist/slices/create-flow/index.d.ts +2 -0
- package/dist/slices/create-flow/index.js +177 -0
- package/dist/slices/create-state-change-slice/index.d.ts +2 -0
- package/dist/slices/create-state-change-slice/index.js +239 -0
- package/dist/slices/create-state-view-slice/index.d.ts +2 -0
- package/dist/slices/create-state-view-slice/index.js +120 -0
- package/dist/slices/list-chapters/index.d.ts +2 -1
- package/dist/slices/list-chapters/index.js +11 -12
- package/dist/slices/list-commands/index.d.ts +2 -1
- package/dist/slices/list-commands/index.js +10 -11
- package/dist/slices/list-events/index.d.ts +2 -1
- package/dist/slices/list-events/index.js +36 -15
- package/dist/slices/list-slices/index.d.ts +2 -1
- package/dist/slices/list-slices/index.js +10 -11
- package/dist/slices/mark-slice-status/index.js +2 -11
- package/dist/slices/remove-field/index.js +4 -33
- package/dist/slices/remove-scenario/index.js +45 -11
- package/dist/slices/search/index.d.ts +2 -1
- package/dist/slices/search/index.js +148 -21
- package/dist/slices/show-actor/index.d.ts +3 -2
- package/dist/slices/show-actor/index.js +46 -20
- package/dist/slices/show-aggregate-completeness/index.d.ts +3 -2
- package/dist/slices/show-aggregate-completeness/index.js +62 -20
- package/dist/slices/show-chapter/index.d.ts +2 -1
- package/dist/slices/show-chapter/index.js +14 -22
- package/dist/slices/show-command/index.d.ts +2 -1
- package/dist/slices/show-command/index.js +54 -19
- package/dist/slices/show-completeness/index.d.ts +3 -1
- package/dist/slices/show-completeness/index.js +313 -31
- package/dist/slices/show-event/index.d.ts +2 -1
- package/dist/slices/show-event/index.js +44 -20
- package/dist/slices/show-model-summary/index.d.ts +2 -1
- package/dist/slices/show-model-summary/index.js +18 -9
- package/dist/slices/show-slice/index.d.ts +2 -1
- package/dist/slices/show-slice/index.js +174 -24
- package/dist/slices/update-field/index.js +4 -33
- package/package.json +5 -3
|
@@ -1,9 +1,116 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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' }, 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' }, 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
|
+
const nameLower = name.toLowerCase();
|
|
63
|
+
const matches = [];
|
|
64
|
+
for (const cmd of model.commands.values()) {
|
|
65
|
+
if (cmd.name.toLowerCase() === nameLower) {
|
|
66
|
+
matches.push({ element: { id: cmd.id, name: cmd.name, fields: cmd.fields, type: 'command' }, type: 'command' });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const evt of model.events.values()) {
|
|
70
|
+
if (evt.name.toLowerCase() === nameLower) {
|
|
71
|
+
matches.push({ element: { id: evt.id, name: evt.name, fields: evt.fields, type: 'event' }, type: 'event' });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const rm of model.readModels.values()) {
|
|
75
|
+
if (rm.name.toLowerCase() === nameLower) {
|
|
76
|
+
matches.push({ element: { id: rm.id, name: rm.name, fields: rm.fields, type: 'readModel' }, type: 'readModel' });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const scr of model.screens.values()) {
|
|
80
|
+
if (scr.name.toLowerCase() === nameLower) {
|
|
81
|
+
matches.push({ element: { id: scr.id, name: scr.name, fields: scr.fields, type: 'screen' }, type: 'screen' });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const proc of model.processors.values()) {
|
|
85
|
+
if (proc.name.toLowerCase() === nameLower) {
|
|
86
|
+
matches.push({ element: { id: proc.id, name: proc.name, fields: proc.fields, type: 'processor' }, type: 'processor' });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (matches.length === 1) {
|
|
90
|
+
return { element: matches[0].element, ambiguous: [] };
|
|
91
|
+
}
|
|
92
|
+
if (matches.length > 1) {
|
|
93
|
+
return { element: null, ambiguous: matches };
|
|
94
|
+
}
|
|
95
|
+
return { element: null, ambiguous: [] };
|
|
96
|
+
}
|
|
97
|
+
function getSourceFields(model, sourceId) {
|
|
98
|
+
const cmd = model.commands.get(sourceId);
|
|
99
|
+
if (cmd)
|
|
100
|
+
return cmd.fields;
|
|
101
|
+
const evt = model.events.get(sourceId);
|
|
102
|
+
if (evt)
|
|
103
|
+
return evt.fields;
|
|
104
|
+
const rm = model.readModels.get(sourceId);
|
|
105
|
+
if (rm)
|
|
106
|
+
return rm.fields;
|
|
107
|
+
const scr = model.screens.get(sourceId);
|
|
108
|
+
if (scr)
|
|
109
|
+
return scr.fields;
|
|
110
|
+
const proc = model.processors.get(sourceId);
|
|
111
|
+
if (proc)
|
|
112
|
+
return proc.fields;
|
|
113
|
+
return [];
|
|
7
114
|
}
|
|
8
115
|
function flattenFields(fields, prefix = '') {
|
|
9
116
|
const result = [];
|
|
@@ -104,40 +211,117 @@ function calculateFlowCompleteness(model, flow, targetFields, sourceFields) {
|
|
|
104
211
|
})),
|
|
105
212
|
};
|
|
106
213
|
}
|
|
107
|
-
export function showCompleteness(model,
|
|
108
|
-
// Find the
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
214
|
+
export function showCompleteness(model, elementName, format) {
|
|
215
|
+
// Find the element by name (searches all element types)
|
|
216
|
+
const { element, ambiguous } = findElementByName(model, elementName);
|
|
217
|
+
if (ambiguous.length > 0) {
|
|
218
|
+
console.error(`Error: Multiple elements found with name "${elementName}"`);
|
|
219
|
+
console.error('Please specify using the element ID:');
|
|
220
|
+
for (const match of ambiguous) {
|
|
221
|
+
console.error(` - "${match.element.name}" (${match.type}) (id: ${match.element.id})`);
|
|
222
|
+
}
|
|
223
|
+
console.error('');
|
|
224
|
+
console.error(`Usage: eventmodeler show completeness "id:${ambiguous[0].element.id.slice(0, 8)}"`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
if (!element) {
|
|
228
|
+
console.error(`Error: Element not found: "${elementName}"`);
|
|
229
|
+
// List all available elements with their IDs
|
|
230
|
+
const allElements = [];
|
|
231
|
+
for (const cmd of model.commands.values()) {
|
|
232
|
+
allElements.push({ name: cmd.name, type: 'command', id: cmd.id });
|
|
233
|
+
}
|
|
234
|
+
for (const evt of model.events.values()) {
|
|
235
|
+
allElements.push({ name: evt.name, type: 'event', id: evt.id });
|
|
236
|
+
}
|
|
237
|
+
for (const rm of model.readModels.values()) {
|
|
238
|
+
allElements.push({ name: rm.name, type: 'read model', id: rm.id });
|
|
239
|
+
}
|
|
240
|
+
for (const scr of model.screens.values()) {
|
|
241
|
+
allElements.push({ name: scr.name, type: 'screen', id: scr.id });
|
|
242
|
+
}
|
|
243
|
+
for (const proc of model.processors.values()) {
|
|
244
|
+
allElements.push({ name: proc.name, type: 'processor', id: proc.id });
|
|
245
|
+
}
|
|
246
|
+
if (allElements.length > 0) {
|
|
247
|
+
console.error('Available elements:');
|
|
248
|
+
for (const el of allElements) {
|
|
249
|
+
console.error(` - "${el.name}" (${el.type}) (id: ${el.id.slice(0, 8)})`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
console.error('No elements exist in the model.');
|
|
117
254
|
}
|
|
118
255
|
process.exit(1);
|
|
119
256
|
}
|
|
120
|
-
// Find all flows
|
|
121
|
-
const
|
|
257
|
+
// Find all incoming flows for this element type
|
|
258
|
+
const validFlowTypes = INCOMING_FLOW_TYPES[element.type];
|
|
259
|
+
const incomingFlows = [...model.flows.values()].filter(f => f.targetId === element.id && validFlowTypes.includes(f.flowType));
|
|
260
|
+
const elementTypeLabel = element.type === 'readModel' ? 'read-model' : element.type;
|
|
122
261
|
if (incomingFlows.length === 0) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
262
|
+
if (format === 'json') {
|
|
263
|
+
outputJson({
|
|
264
|
+
elementType: elementTypeLabel,
|
|
265
|
+
name: element.name,
|
|
266
|
+
message: `No flows into this ${elementTypeLabel}`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}">`);
|
|
271
|
+
console.log(` <no-flows>No flows into this ${elementTypeLabel}</no-flows>`);
|
|
272
|
+
console.log('</completeness>');
|
|
273
|
+
}
|
|
126
274
|
return;
|
|
127
275
|
}
|
|
128
276
|
// Calculate completeness for each flow
|
|
129
277
|
const completenessResults = [];
|
|
130
278
|
for (const flow of incomingFlows) {
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
279
|
+
const sourceFields = getSourceFields(model, flow.sourceId);
|
|
280
|
+
if (sourceFields.length === 0 && element.fields.length > 0) {
|
|
281
|
+
// Source has no fields but target expects some - still calculate
|
|
282
|
+
}
|
|
283
|
+
const result = calculateFlowCompleteness(model, flow, element.fields, sourceFields);
|
|
135
284
|
completenessResults.push(result);
|
|
136
285
|
}
|
|
137
|
-
// Output XML
|
|
138
286
|
const overallComplete = completenessResults.every(r => r.isComplete);
|
|
139
287
|
const overallStatus = overallComplete ? 'complete' : 'incomplete';
|
|
140
|
-
|
|
288
|
+
if (format === 'json') {
|
|
289
|
+
const flatTargetFields = flattenFields(element.fields);
|
|
290
|
+
outputJson({
|
|
291
|
+
elementType: elementTypeLabel,
|
|
292
|
+
name: element.name,
|
|
293
|
+
status: overallStatus,
|
|
294
|
+
flows: completenessResults.map(result => ({
|
|
295
|
+
flowId: result.flowId,
|
|
296
|
+
from: result.sourceName,
|
|
297
|
+
sourceType: result.sourceType,
|
|
298
|
+
status: result.isComplete ? 'complete' : 'incomplete',
|
|
299
|
+
...(result.hasWarnings ? { hasWarnings: true } : {}),
|
|
300
|
+
targetFields: result.targetFields.map(f => ({
|
|
301
|
+
name: f.fieldName,
|
|
302
|
+
status: f.status,
|
|
303
|
+
...(f.sourceFieldName ? { source: f.sourceFieldName } : {})
|
|
304
|
+
})),
|
|
305
|
+
availableSourceFields: result.sourceFields.map(sf => ({
|
|
306
|
+
id: sf.id,
|
|
307
|
+
name: sf.name,
|
|
308
|
+
type: sf.type,
|
|
309
|
+
...(sf.isList ? { isList: true } : {})
|
|
310
|
+
}))
|
|
311
|
+
})),
|
|
312
|
+
elementFields: flatTargetFields.map(({ id, path, field }) => ({
|
|
313
|
+
id,
|
|
314
|
+
name: path,
|
|
315
|
+
type: field.fieldType,
|
|
316
|
+
...(field.isList ? { isList: true } : {}),
|
|
317
|
+
...(field.isOptional ? { isOptional: true } : {}),
|
|
318
|
+
...(field.isGenerated ? { isGenerated: true } : {})
|
|
319
|
+
}))
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
// Output XML
|
|
324
|
+
console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}" status="${overallStatus}">`);
|
|
141
325
|
for (const result of completenessResults) {
|
|
142
326
|
const flowStatus = result.isComplete ? 'complete' : 'incomplete';
|
|
143
327
|
const warningAttr = result.hasWarnings ? ' hasWarnings="true"' : '';
|
|
@@ -162,9 +346,9 @@ export function showCompleteness(model, readModelName) {
|
|
|
162
346
|
console.log(' </available-source-fields>');
|
|
163
347
|
console.log(' </flow>');
|
|
164
348
|
}
|
|
165
|
-
// Also show the
|
|
166
|
-
console.log(
|
|
167
|
-
const flatTargetFields = flattenFields(
|
|
349
|
+
// Also show the element's fields with their IDs (needed for mapping)
|
|
350
|
+
console.log(` <${elementTypeLabel}-fields>`);
|
|
351
|
+
const flatTargetFields = flattenFields(element.fields);
|
|
168
352
|
for (const { id, path, field } of flatTargetFields) {
|
|
169
353
|
const attrs = [`id="${id}"`, `name="${escapeXml(path)}"`, `type="${field.fieldType}"`];
|
|
170
354
|
if (field.isList)
|
|
@@ -175,6 +359,104 @@ export function showCompleteness(model, readModelName) {
|
|
|
175
359
|
attrs.push('isGenerated="true"');
|
|
176
360
|
console.log(` <field ${attrs.join(' ')}/>`);
|
|
177
361
|
}
|
|
178
|
-
console.log(
|
|
362
|
+
console.log(` </${elementTypeLabel}-fields>`);
|
|
179
363
|
console.log('</completeness>');
|
|
180
364
|
}
|
|
365
|
+
export function showModelCompleteness(model, format) {
|
|
366
|
+
const allFlows = [...model.flows.values()];
|
|
367
|
+
const incompleteFlows = [];
|
|
368
|
+
let completeCount = 0;
|
|
369
|
+
let totalCount = 0;
|
|
370
|
+
for (const flow of allFlows) {
|
|
371
|
+
// Get target element info
|
|
372
|
+
let targetName = 'Unknown';
|
|
373
|
+
let targetType = 'unknown';
|
|
374
|
+
let targetFields = [];
|
|
375
|
+
const targetCmd = model.commands.get(flow.targetId);
|
|
376
|
+
const targetEvt = model.events.get(flow.targetId);
|
|
377
|
+
const targetRm = model.readModels.get(flow.targetId);
|
|
378
|
+
const targetScr = model.screens.get(flow.targetId);
|
|
379
|
+
const targetProc = model.processors.get(flow.targetId);
|
|
380
|
+
if (targetCmd) {
|
|
381
|
+
targetName = targetCmd.name;
|
|
382
|
+
targetType = 'command';
|
|
383
|
+
targetFields = targetCmd.fields;
|
|
384
|
+
}
|
|
385
|
+
else if (targetEvt) {
|
|
386
|
+
targetName = targetEvt.name;
|
|
387
|
+
targetType = 'event';
|
|
388
|
+
targetFields = targetEvt.fields;
|
|
389
|
+
}
|
|
390
|
+
else if (targetRm) {
|
|
391
|
+
targetName = targetRm.name;
|
|
392
|
+
targetType = 'read-model';
|
|
393
|
+
targetFields = targetRm.fields;
|
|
394
|
+
}
|
|
395
|
+
else if (targetScr) {
|
|
396
|
+
targetName = targetScr.name;
|
|
397
|
+
targetType = 'screen';
|
|
398
|
+
targetFields = targetScr.fields;
|
|
399
|
+
}
|
|
400
|
+
else if (targetProc) {
|
|
401
|
+
targetName = targetProc.name;
|
|
402
|
+
targetType = 'processor';
|
|
403
|
+
targetFields = targetProc.fields;
|
|
404
|
+
}
|
|
405
|
+
// Get source fields
|
|
406
|
+
const sourceFields = getSourceFields(model, flow.sourceId);
|
|
407
|
+
// Calculate completeness
|
|
408
|
+
const result = calculateFlowCompleteness(model, flow, targetFields, sourceFields);
|
|
409
|
+
totalCount++;
|
|
410
|
+
if (result.isComplete) {
|
|
411
|
+
completeCount++;
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
const unsatisfiedFields = result.targetFields
|
|
415
|
+
.filter(f => f.status === 'unsatisfied')
|
|
416
|
+
.map(f => f.fieldName);
|
|
417
|
+
incompleteFlows.push({
|
|
418
|
+
flow,
|
|
419
|
+
sourceName: result.sourceName,
|
|
420
|
+
targetName,
|
|
421
|
+
targetType,
|
|
422
|
+
unsatisfiedFields,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const incompleteCount = totalCount - completeCount;
|
|
427
|
+
if (format === 'json') {
|
|
428
|
+
outputJson({
|
|
429
|
+
summary: {
|
|
430
|
+
complete: completeCount,
|
|
431
|
+
incomplete: incompleteCount,
|
|
432
|
+
total: totalCount
|
|
433
|
+
},
|
|
434
|
+
incompleteFlows: incompleteFlows.map(item => ({
|
|
435
|
+
from: item.sourceName,
|
|
436
|
+
to: item.targetName,
|
|
437
|
+
targetType: item.targetType,
|
|
438
|
+
flowType: item.flow.flowType,
|
|
439
|
+
unsatisfiedFields: item.unsatisfiedFields
|
|
440
|
+
}))
|
|
441
|
+
});
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
console.log('<model-completeness>');
|
|
445
|
+
console.log(` <summary complete="${completeCount}" incomplete="${incompleteCount}" total="${totalCount}"/>`);
|
|
446
|
+
if (incompleteFlows.length > 0) {
|
|
447
|
+
console.log(' <incomplete-flows>');
|
|
448
|
+
for (const item of incompleteFlows) {
|
|
449
|
+
console.log(` <flow from="${escapeXml(item.sourceName)}" to="${escapeXml(item.targetName)}" target-type="${item.targetType}" flow-type="${item.flow.flowType}">`);
|
|
450
|
+
if (item.unsatisfiedFields.length > 0) {
|
|
451
|
+
console.log(' <unsatisfied-fields>');
|
|
452
|
+
for (const fieldName of item.unsatisfiedFields) {
|
|
453
|
+
console.log(` <field name="${escapeXml(fieldName)}"/>`);
|
|
454
|
+
}
|
|
455
|
+
console.log(' </unsatisfied-fields>');
|
|
456
|
+
}
|
|
457
|
+
console.log(' </flow>');
|
|
458
|
+
}
|
|
459
|
+
console.log(' </incomplete-flows>');
|
|
460
|
+
}
|
|
461
|
+
console.log('</model-completeness>');
|
|
462
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { EventModel } from '../../types.js';
|
|
2
|
-
|
|
2
|
+
import { type OutputFormat } from '../../lib/format.js';
|
|
3
|
+
export declare function showEvent(model: EventModel, name: string, format: OutputFormat): void;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"')
|
|
7
|
-
.replace(/'/g, ''');
|
|
8
|
-
}
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
+
import { findElementOrExit } from '../../lib/element-lookup.js';
|
|
9
3
|
function formatFieldXml(field, indent) {
|
|
10
4
|
const attrs = [
|
|
11
5
|
`name="${escapeXml(field.name)}"`,
|
|
@@ -27,6 +21,22 @@ function formatFieldXml(field, indent) {
|
|
|
27
21
|
}
|
|
28
22
|
return `${indent}<field ${attrs.join(' ')}/>\n`;
|
|
29
23
|
}
|
|
24
|
+
function fieldToJson(field) {
|
|
25
|
+
const result = {
|
|
26
|
+
name: field.name,
|
|
27
|
+
type: field.fieldType
|
|
28
|
+
};
|
|
29
|
+
if (field.isList)
|
|
30
|
+
result.list = true;
|
|
31
|
+
if (field.isGenerated)
|
|
32
|
+
result.generated = true;
|
|
33
|
+
if (field.isOptional)
|
|
34
|
+
result.optional = true;
|
|
35
|
+
if (field.subfields && field.subfields.length > 0) {
|
|
36
|
+
result.subfields = field.subfields.map(fieldToJson);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
30
40
|
// Find which aggregate an event belongs to (center point inside aggregate bounds)
|
|
31
41
|
function findAggregateForEvent(model, event) {
|
|
32
42
|
const centerX = event.position.x + event.width / 2;
|
|
@@ -47,7 +57,7 @@ function findAggregateForEvent(model, event) {
|
|
|
47
57
|
function formatEventXml(model, event) {
|
|
48
58
|
const aggregate = findAggregateForEvent(model, event);
|
|
49
59
|
const aggregateAttr = aggregate ? ` aggregate="${escapeXml(aggregate.name)}"` : '';
|
|
50
|
-
let xml = `<event name="${escapeXml(event.name)}"${aggregateAttr}>\n`;
|
|
60
|
+
let xml = `<event id="${event.id}" name="${escapeXml(event.name)}"${aggregateAttr}>\n`;
|
|
51
61
|
if (event.fields.length > 0) {
|
|
52
62
|
xml += ' <fields>\n';
|
|
53
63
|
for (const field of event.fields) {
|
|
@@ -78,17 +88,31 @@ function formatEventXml(model, event) {
|
|
|
78
88
|
xml += '</event>';
|
|
79
89
|
return xml;
|
|
80
90
|
}
|
|
81
|
-
export function showEvent(model, name) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
export function showEvent(model, name, format) {
|
|
92
|
+
const event = findElementOrExit(model.events, name, 'event');
|
|
93
|
+
if (format === 'json') {
|
|
94
|
+
const aggregate = findAggregateForEvent(model, event);
|
|
95
|
+
const incomingFlows = [...model.flows.values()].filter(f => f.targetId === event.id);
|
|
96
|
+
const outgoingFlows = [...model.flows.values()].filter(f => f.sourceId === event.id);
|
|
97
|
+
const result = {
|
|
98
|
+
id: event.id,
|
|
99
|
+
name: event.name,
|
|
100
|
+
fields: event.fields.map(fieldToJson)
|
|
101
|
+
};
|
|
102
|
+
if (aggregate)
|
|
103
|
+
result.aggregate = aggregate.name;
|
|
104
|
+
const producedBy = incomingFlows
|
|
105
|
+
.map(f => model.commands.get(f.sourceId)?.name)
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
if (producedBy.length > 0)
|
|
108
|
+
result.producedBy = producedBy;
|
|
109
|
+
const consumedBy = outgoingFlows
|
|
110
|
+
.map(f => model.readModels.get(f.targetId)?.name)
|
|
111
|
+
.filter(Boolean);
|
|
112
|
+
if (consumedBy.length > 0)
|
|
113
|
+
result.consumedBy = consumedBy;
|
|
114
|
+
outputJson(result);
|
|
115
|
+
return;
|
|
92
116
|
}
|
|
93
117
|
console.log(formatEventXml(model, event));
|
|
94
118
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
+
export function showModelSummary(model, format) {
|
|
3
|
+
if (format === 'json') {
|
|
4
|
+
outputJson({
|
|
5
|
+
name: model.name,
|
|
6
|
+
slices: model.slices.size,
|
|
7
|
+
commands: model.commands.size,
|
|
8
|
+
events: model.events.size,
|
|
9
|
+
readModels: model.readModels.size,
|
|
10
|
+
screens: model.screens.size,
|
|
11
|
+
processors: model.processors.size,
|
|
12
|
+
aggregates: model.aggregates.size,
|
|
13
|
+
actors: model.actors.size,
|
|
14
|
+
scenarios: model.scenarios.size,
|
|
15
|
+
flows: model.flows.size
|
|
16
|
+
});
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
10
19
|
console.log(`<model name="${escapeXml(model.name)}">`);
|
|
11
20
|
console.log(` <slices count="${model.slices.size}"/>`);
|
|
12
21
|
console.log(` <commands count="${model.commands.size}"/>`);
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { EventModel } from '../../types.js';
|
|
2
|
-
|
|
2
|
+
import { type OutputFormat } from '../../lib/format.js';
|
|
3
|
+
export declare function showSlice(model: EventModel, name: string, format: OutputFormat): void;
|