eventmodeler 0.2.1 → 0.2.3
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 +51 -19
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +26 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +11 -0
- package/dist/projection.js +29 -35
- package/dist/slices/list-chapters/index.d.ts +2 -1
- package/dist/slices/list-chapters/index.js +10 -11
- package/dist/slices/list-commands/index.d.ts +2 -1
- package/dist/slices/list-commands/index.js +9 -10
- package/dist/slices/list-events/index.d.ts +2 -1
- package/dist/slices/list-events/index.js +33 -13
- package/dist/slices/list-slices/index.d.ts +2 -1
- package/dist/slices/list-slices/index.js +9 -10
- 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 +44 -9
- package/dist/slices/show-aggregate-completeness/index.d.ts +3 -2
- package/dist/slices/show-aggregate-completeness/index.js +60 -9
- package/dist/slices/show-chapter/index.d.ts +2 -1
- package/dist/slices/show-chapter/index.js +9 -9
- package/dist/slices/show-command/index.d.ts +2 -1
- package/dist/slices/show-command/index.js +52 -9
- package/dist/slices/show-completeness/index.d.ts +3 -1
- package/dist/slices/show-completeness/index.js +225 -32
- package/dist/slices/show-event/index.d.ts +2 -1
- package/dist/slices/show-event/index.js +41 -9
- 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 +162 -9
- package/package.json +5 -3
|
@@ -1,9 +1,62 @@
|
|
|
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
|
+
const nameLower = name.toLowerCase();
|
|
11
|
+
// Search commands
|
|
12
|
+
for (const cmd of model.commands.values()) {
|
|
13
|
+
if (cmd.name.toLowerCase() === nameLower || cmd.name.toLowerCase().includes(nameLower)) {
|
|
14
|
+
return { id: cmd.id, name: cmd.name, fields: cmd.fields, type: 'command' };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Search events
|
|
18
|
+
for (const evt of model.events.values()) {
|
|
19
|
+
if (evt.name.toLowerCase() === nameLower || evt.name.toLowerCase().includes(nameLower)) {
|
|
20
|
+
return { id: evt.id, name: evt.name, fields: evt.fields, type: 'event' };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Search read models
|
|
24
|
+
for (const rm of model.readModels.values()) {
|
|
25
|
+
if (rm.name.toLowerCase() === nameLower || rm.name.toLowerCase().includes(nameLower)) {
|
|
26
|
+
return { id: rm.id, name: rm.name, fields: rm.fields, type: 'readModel' };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Search screens
|
|
30
|
+
for (const scr of model.screens.values()) {
|
|
31
|
+
if (scr.name.toLowerCase() === nameLower || scr.name.toLowerCase().includes(nameLower)) {
|
|
32
|
+
return { id: scr.id, name: scr.name, fields: scr.fields, type: 'screen' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Search processors
|
|
36
|
+
for (const proc of model.processors.values()) {
|
|
37
|
+
if (proc.name.toLowerCase() === nameLower || proc.name.toLowerCase().includes(nameLower)) {
|
|
38
|
+
return { id: proc.id, name: proc.name, fields: proc.fields, type: 'processor' };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function getSourceFields(model, sourceId) {
|
|
44
|
+
const cmd = model.commands.get(sourceId);
|
|
45
|
+
if (cmd)
|
|
46
|
+
return cmd.fields;
|
|
47
|
+
const evt = model.events.get(sourceId);
|
|
48
|
+
if (evt)
|
|
49
|
+
return evt.fields;
|
|
50
|
+
const rm = model.readModels.get(sourceId);
|
|
51
|
+
if (rm)
|
|
52
|
+
return rm.fields;
|
|
53
|
+
const scr = model.screens.get(sourceId);
|
|
54
|
+
if (scr)
|
|
55
|
+
return scr.fields;
|
|
56
|
+
const proc = model.processors.get(sourceId);
|
|
57
|
+
if (proc)
|
|
58
|
+
return proc.fields;
|
|
59
|
+
return [];
|
|
7
60
|
}
|
|
8
61
|
function flattenFields(fields, prefix = '') {
|
|
9
62
|
const result = [];
|
|
@@ -104,40 +157,82 @@ function calculateFlowCompleteness(model, flow, targetFields, sourceFields) {
|
|
|
104
157
|
})),
|
|
105
158
|
};
|
|
106
159
|
}
|
|
107
|
-
export function showCompleteness(model,
|
|
108
|
-
// Find the
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
console.error(`Error: Read model not found: ${readModelName}`);
|
|
114
|
-
console.error('Available read models:');
|
|
115
|
-
for (const rm of readModels) {
|
|
116
|
-
console.error(` - ${rm.name}`);
|
|
117
|
-
}
|
|
160
|
+
export function showCompleteness(model, elementName, format) {
|
|
161
|
+
// Find the element by name (searches all element types)
|
|
162
|
+
const element = findElementByName(model, elementName);
|
|
163
|
+
if (!element) {
|
|
164
|
+
console.error(`Error: Element not found: ${elementName}`);
|
|
165
|
+
console.error('Search looked in: commands, events, read models, screens, processors');
|
|
118
166
|
process.exit(1);
|
|
119
167
|
}
|
|
120
|
-
// Find all flows
|
|
121
|
-
const
|
|
168
|
+
// Find all incoming flows for this element type
|
|
169
|
+
const validFlowTypes = INCOMING_FLOW_TYPES[element.type];
|
|
170
|
+
const incomingFlows = [...model.flows.values()].filter(f => f.targetId === element.id && validFlowTypes.includes(f.flowType));
|
|
171
|
+
const elementTypeLabel = element.type === 'readModel' ? 'read-model' : element.type;
|
|
122
172
|
if (incomingFlows.length === 0) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
173
|
+
if (format === 'json') {
|
|
174
|
+
outputJson({
|
|
175
|
+
elementType: elementTypeLabel,
|
|
176
|
+
name: element.name,
|
|
177
|
+
message: `No flows into this ${elementTypeLabel}`
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}">`);
|
|
182
|
+
console.log(` <no-flows>No flows into this ${elementTypeLabel}</no-flows>`);
|
|
183
|
+
console.log('</completeness>');
|
|
184
|
+
}
|
|
126
185
|
return;
|
|
127
186
|
}
|
|
128
187
|
// Calculate completeness for each flow
|
|
129
188
|
const completenessResults = [];
|
|
130
189
|
for (const flow of incomingFlows) {
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
190
|
+
const sourceFields = getSourceFields(model, flow.sourceId);
|
|
191
|
+
if (sourceFields.length === 0 && element.fields.length > 0) {
|
|
192
|
+
// Source has no fields but target expects some - still calculate
|
|
193
|
+
}
|
|
194
|
+
const result = calculateFlowCompleteness(model, flow, element.fields, sourceFields);
|
|
135
195
|
completenessResults.push(result);
|
|
136
196
|
}
|
|
137
|
-
// Output XML
|
|
138
197
|
const overallComplete = completenessResults.every(r => r.isComplete);
|
|
139
198
|
const overallStatus = overallComplete ? 'complete' : 'incomplete';
|
|
140
|
-
|
|
199
|
+
if (format === 'json') {
|
|
200
|
+
const flatTargetFields = flattenFields(element.fields);
|
|
201
|
+
outputJson({
|
|
202
|
+
elementType: elementTypeLabel,
|
|
203
|
+
name: element.name,
|
|
204
|
+
status: overallStatus,
|
|
205
|
+
flows: completenessResults.map(result => ({
|
|
206
|
+
flowId: result.flowId,
|
|
207
|
+
from: result.sourceName,
|
|
208
|
+
sourceType: result.sourceType,
|
|
209
|
+
status: result.isComplete ? 'complete' : 'incomplete',
|
|
210
|
+
...(result.hasWarnings ? { hasWarnings: true } : {}),
|
|
211
|
+
targetFields: result.targetFields.map(f => ({
|
|
212
|
+
name: f.fieldName,
|
|
213
|
+
status: f.status,
|
|
214
|
+
...(f.sourceFieldName ? { source: f.sourceFieldName } : {})
|
|
215
|
+
})),
|
|
216
|
+
availableSourceFields: result.sourceFields.map(sf => ({
|
|
217
|
+
id: sf.id,
|
|
218
|
+
name: sf.name,
|
|
219
|
+
type: sf.type,
|
|
220
|
+
...(sf.isList ? { isList: true } : {})
|
|
221
|
+
}))
|
|
222
|
+
})),
|
|
223
|
+
elementFields: flatTargetFields.map(({ id, path, field }) => ({
|
|
224
|
+
id,
|
|
225
|
+
name: path,
|
|
226
|
+
type: field.fieldType,
|
|
227
|
+
...(field.isList ? { isList: true } : {}),
|
|
228
|
+
...(field.isOptional ? { isOptional: true } : {}),
|
|
229
|
+
...(field.isGenerated ? { isGenerated: true } : {})
|
|
230
|
+
}))
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Output XML
|
|
235
|
+
console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}" status="${overallStatus}">`);
|
|
141
236
|
for (const result of completenessResults) {
|
|
142
237
|
const flowStatus = result.isComplete ? 'complete' : 'incomplete';
|
|
143
238
|
const warningAttr = result.hasWarnings ? ' hasWarnings="true"' : '';
|
|
@@ -162,9 +257,9 @@ export function showCompleteness(model, readModelName) {
|
|
|
162
257
|
console.log(' </available-source-fields>');
|
|
163
258
|
console.log(' </flow>');
|
|
164
259
|
}
|
|
165
|
-
// Also show the
|
|
166
|
-
console.log(
|
|
167
|
-
const flatTargetFields = flattenFields(
|
|
260
|
+
// Also show the element's fields with their IDs (needed for mapping)
|
|
261
|
+
console.log(` <${elementTypeLabel}-fields>`);
|
|
262
|
+
const flatTargetFields = flattenFields(element.fields);
|
|
168
263
|
for (const { id, path, field } of flatTargetFields) {
|
|
169
264
|
const attrs = [`id="${id}"`, `name="${escapeXml(path)}"`, `type="${field.fieldType}"`];
|
|
170
265
|
if (field.isList)
|
|
@@ -175,6 +270,104 @@ export function showCompleteness(model, readModelName) {
|
|
|
175
270
|
attrs.push('isGenerated="true"');
|
|
176
271
|
console.log(` <field ${attrs.join(' ')}/>`);
|
|
177
272
|
}
|
|
178
|
-
console.log(
|
|
273
|
+
console.log(` </${elementTypeLabel}-fields>`);
|
|
179
274
|
console.log('</completeness>');
|
|
180
275
|
}
|
|
276
|
+
export function showModelCompleteness(model, format) {
|
|
277
|
+
const allFlows = [...model.flows.values()];
|
|
278
|
+
const incompleteFlows = [];
|
|
279
|
+
let completeCount = 0;
|
|
280
|
+
let totalCount = 0;
|
|
281
|
+
for (const flow of allFlows) {
|
|
282
|
+
// Get target element info
|
|
283
|
+
let targetName = 'Unknown';
|
|
284
|
+
let targetType = 'unknown';
|
|
285
|
+
let targetFields = [];
|
|
286
|
+
const targetCmd = model.commands.get(flow.targetId);
|
|
287
|
+
const targetEvt = model.events.get(flow.targetId);
|
|
288
|
+
const targetRm = model.readModels.get(flow.targetId);
|
|
289
|
+
const targetScr = model.screens.get(flow.targetId);
|
|
290
|
+
const targetProc = model.processors.get(flow.targetId);
|
|
291
|
+
if (targetCmd) {
|
|
292
|
+
targetName = targetCmd.name;
|
|
293
|
+
targetType = 'command';
|
|
294
|
+
targetFields = targetCmd.fields;
|
|
295
|
+
}
|
|
296
|
+
else if (targetEvt) {
|
|
297
|
+
targetName = targetEvt.name;
|
|
298
|
+
targetType = 'event';
|
|
299
|
+
targetFields = targetEvt.fields;
|
|
300
|
+
}
|
|
301
|
+
else if (targetRm) {
|
|
302
|
+
targetName = targetRm.name;
|
|
303
|
+
targetType = 'read-model';
|
|
304
|
+
targetFields = targetRm.fields;
|
|
305
|
+
}
|
|
306
|
+
else if (targetScr) {
|
|
307
|
+
targetName = targetScr.name;
|
|
308
|
+
targetType = 'screen';
|
|
309
|
+
targetFields = targetScr.fields;
|
|
310
|
+
}
|
|
311
|
+
else if (targetProc) {
|
|
312
|
+
targetName = targetProc.name;
|
|
313
|
+
targetType = 'processor';
|
|
314
|
+
targetFields = targetProc.fields;
|
|
315
|
+
}
|
|
316
|
+
// Get source fields
|
|
317
|
+
const sourceFields = getSourceFields(model, flow.sourceId);
|
|
318
|
+
// Calculate completeness
|
|
319
|
+
const result = calculateFlowCompleteness(model, flow, targetFields, sourceFields);
|
|
320
|
+
totalCount++;
|
|
321
|
+
if (result.isComplete) {
|
|
322
|
+
completeCount++;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
const unsatisfiedFields = result.targetFields
|
|
326
|
+
.filter(f => f.status === 'unsatisfied')
|
|
327
|
+
.map(f => f.fieldName);
|
|
328
|
+
incompleteFlows.push({
|
|
329
|
+
flow,
|
|
330
|
+
sourceName: result.sourceName,
|
|
331
|
+
targetName,
|
|
332
|
+
targetType,
|
|
333
|
+
unsatisfiedFields,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const incompleteCount = totalCount - completeCount;
|
|
338
|
+
if (format === 'json') {
|
|
339
|
+
outputJson({
|
|
340
|
+
summary: {
|
|
341
|
+
complete: completeCount,
|
|
342
|
+
incomplete: incompleteCount,
|
|
343
|
+
total: totalCount
|
|
344
|
+
},
|
|
345
|
+
incompleteFlows: incompleteFlows.map(item => ({
|
|
346
|
+
from: item.sourceName,
|
|
347
|
+
to: item.targetName,
|
|
348
|
+
targetType: item.targetType,
|
|
349
|
+
flowType: item.flow.flowType,
|
|
350
|
+
unsatisfiedFields: item.unsatisfiedFields
|
|
351
|
+
}))
|
|
352
|
+
});
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
console.log('<model-completeness>');
|
|
356
|
+
console.log(` <summary complete="${completeCount}" incomplete="${incompleteCount}" total="${totalCount}"/>`);
|
|
357
|
+
if (incompleteFlows.length > 0) {
|
|
358
|
+
console.log(' <incomplete-flows>');
|
|
359
|
+
for (const item of incompleteFlows) {
|
|
360
|
+
console.log(` <flow from="${escapeXml(item.sourceName)}" to="${escapeXml(item.targetName)}" target-type="${item.targetType}" flow-type="${item.flow.flowType}">`);
|
|
361
|
+
if (item.unsatisfiedFields.length > 0) {
|
|
362
|
+
console.log(' <unsatisfied-fields>');
|
|
363
|
+
for (const fieldName of item.unsatisfiedFields) {
|
|
364
|
+
console.log(` <field name="${escapeXml(fieldName)}"/>`);
|
|
365
|
+
}
|
|
366
|
+
console.log(' </unsatisfied-fields>');
|
|
367
|
+
}
|
|
368
|
+
console.log(' </flow>');
|
|
369
|
+
}
|
|
370
|
+
console.log(' </incomplete-flows>');
|
|
371
|
+
}
|
|
372
|
+
console.log('</model-completeness>');
|
|
373
|
+
}
|
|
@@ -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,4 @@
|
|
|
1
|
-
|
|
2
|
-
return str
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"')
|
|
7
|
-
.replace(/'/g, ''');
|
|
8
|
-
}
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
9
2
|
function formatFieldXml(field, indent) {
|
|
10
3
|
const attrs = [
|
|
11
4
|
`name="${escapeXml(field.name)}"`,
|
|
@@ -27,6 +20,22 @@ function formatFieldXml(field, indent) {
|
|
|
27
20
|
}
|
|
28
21
|
return `${indent}<field ${attrs.join(' ')}/>\n`;
|
|
29
22
|
}
|
|
23
|
+
function fieldToJson(field) {
|
|
24
|
+
const result = {
|
|
25
|
+
name: field.name,
|
|
26
|
+
type: field.fieldType
|
|
27
|
+
};
|
|
28
|
+
if (field.isList)
|
|
29
|
+
result.list = true;
|
|
30
|
+
if (field.isGenerated)
|
|
31
|
+
result.generated = true;
|
|
32
|
+
if (field.isOptional)
|
|
33
|
+
result.optional = true;
|
|
34
|
+
if (field.subfields && field.subfields.length > 0) {
|
|
35
|
+
result.subfields = field.subfields.map(fieldToJson);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
30
39
|
// Find which aggregate an event belongs to (center point inside aggregate bounds)
|
|
31
40
|
function findAggregateForEvent(model, event) {
|
|
32
41
|
const centerX = event.position.x + event.width / 2;
|
|
@@ -78,7 +87,7 @@ function formatEventXml(model, event) {
|
|
|
78
87
|
xml += '</event>';
|
|
79
88
|
return xml;
|
|
80
89
|
}
|
|
81
|
-
export function showEvent(model, name) {
|
|
90
|
+
export function showEvent(model, name, format) {
|
|
82
91
|
const events = [...model.events.values()];
|
|
83
92
|
const nameLower = name.toLowerCase();
|
|
84
93
|
const event = events.find(e => e.name.toLowerCase() === nameLower || e.name.toLowerCase().includes(nameLower));
|
|
@@ -90,5 +99,28 @@ export function showEvent(model, name) {
|
|
|
90
99
|
}
|
|
91
100
|
process.exit(1);
|
|
92
101
|
}
|
|
102
|
+
if (format === 'json') {
|
|
103
|
+
const aggregate = findAggregateForEvent(model, event);
|
|
104
|
+
const incomingFlows = [...model.flows.values()].filter(f => f.targetId === event.id);
|
|
105
|
+
const outgoingFlows = [...model.flows.values()].filter(f => f.sourceId === event.id);
|
|
106
|
+
const result = {
|
|
107
|
+
name: event.name,
|
|
108
|
+
fields: event.fields.map(fieldToJson)
|
|
109
|
+
};
|
|
110
|
+
if (aggregate)
|
|
111
|
+
result.aggregate = aggregate.name;
|
|
112
|
+
const producedBy = incomingFlows
|
|
113
|
+
.map(f => model.commands.get(f.sourceId)?.name)
|
|
114
|
+
.filter(Boolean);
|
|
115
|
+
if (producedBy.length > 0)
|
|
116
|
+
result.producedBy = producedBy;
|
|
117
|
+
const consumedBy = outgoingFlows
|
|
118
|
+
.map(f => model.readModels.get(f.targetId)?.name)
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
if (consumedBy.length > 0)
|
|
121
|
+
result.consumedBy = consumedBy;
|
|
122
|
+
outputJson(result);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
93
125
|
console.log(formatEventXml(model, event));
|
|
94
126
|
}
|
|
@@ -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;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return str
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"')
|
|
7
|
-
.replace(/'/g, ''');
|
|
8
|
-
}
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
9
2
|
function formatFieldValues(values) {
|
|
10
3
|
if (!values || Object.keys(values).length === 0)
|
|
11
4
|
return '';
|
|
@@ -306,7 +299,163 @@ function formatSliceXml(model, slice) {
|
|
|
306
299
|
xml += '</slice>';
|
|
307
300
|
return xml;
|
|
308
301
|
}
|
|
309
|
-
|
|
302
|
+
function fieldToJson(field) {
|
|
303
|
+
const result = {
|
|
304
|
+
name: field.name,
|
|
305
|
+
type: field.fieldType
|
|
306
|
+
};
|
|
307
|
+
if (field.isList)
|
|
308
|
+
result.list = true;
|
|
309
|
+
if (field.isGenerated)
|
|
310
|
+
result.generated = true;
|
|
311
|
+
if (field.isOptional)
|
|
312
|
+
result.optional = true;
|
|
313
|
+
if (field.isUserInput)
|
|
314
|
+
result.userInput = true;
|
|
315
|
+
if (field.subfields && field.subfields.length > 0) {
|
|
316
|
+
result.subfields = field.subfields.map(fieldToJson);
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
function formatSliceJson(model, slice) {
|
|
321
|
+
const components = getSliceComponents(model, slice);
|
|
322
|
+
const scenarios = [...model.scenarios.values()].filter(s => s.sliceId === slice.id);
|
|
323
|
+
const componentIds = new Set();
|
|
324
|
+
components.commands.forEach(c => componentIds.add(c.id));
|
|
325
|
+
components.events.forEach(e => componentIds.add(e.id));
|
|
326
|
+
components.readModels.forEach(rm => componentIds.add(rm.id));
|
|
327
|
+
components.screens.forEach(s => componentIds.add(s.id));
|
|
328
|
+
components.processors.forEach(p => componentIds.add(p.id));
|
|
329
|
+
const flows = [...model.flows.values()].filter(f => componentIds.has(f.sourceId) || componentIds.has(f.targetId));
|
|
330
|
+
const internalFlows = flows.filter(f => componentIds.has(f.sourceId) && componentIds.has(f.targetId));
|
|
331
|
+
function getName(id) {
|
|
332
|
+
return (model.commands.get(id)?.name ??
|
|
333
|
+
model.events.get(id)?.name ??
|
|
334
|
+
model.readModels.get(id)?.name ??
|
|
335
|
+
model.screens.get(id)?.name ??
|
|
336
|
+
model.processors.get(id)?.name ??
|
|
337
|
+
id);
|
|
338
|
+
}
|
|
339
|
+
const result = {
|
|
340
|
+
name: slice.name,
|
|
341
|
+
status: slice.status,
|
|
342
|
+
components: {
|
|
343
|
+
screens: components.screens.map(screen => {
|
|
344
|
+
const screenObj = {
|
|
345
|
+
name: screen.name,
|
|
346
|
+
fields: screen.fields.map(fieldToJson)
|
|
347
|
+
};
|
|
348
|
+
if (screen.originalNodeId) {
|
|
349
|
+
screenObj.linkedCopy = true;
|
|
350
|
+
const originSlice = findSliceForNode(model, screen.originalNodeId);
|
|
351
|
+
if (originSlice)
|
|
352
|
+
screenObj.originSlice = originSlice.name;
|
|
353
|
+
}
|
|
354
|
+
const actor = findActorForScreen(model, screen);
|
|
355
|
+
if (actor)
|
|
356
|
+
screenObj.actor = actor.name;
|
|
357
|
+
return screenObj;
|
|
358
|
+
}),
|
|
359
|
+
commands: components.commands.map(cmd => ({
|
|
360
|
+
name: cmd.name,
|
|
361
|
+
fields: cmd.fields.map(fieldToJson)
|
|
362
|
+
})),
|
|
363
|
+
events: components.events.map(event => {
|
|
364
|
+
const eventObj = {
|
|
365
|
+
name: event.name,
|
|
366
|
+
fields: event.fields.map(fieldToJson)
|
|
367
|
+
};
|
|
368
|
+
if (event.originalNodeId) {
|
|
369
|
+
eventObj.linkedCopy = true;
|
|
370
|
+
const originSlice = findSliceForNode(model, event.originalNodeId);
|
|
371
|
+
if (originSlice)
|
|
372
|
+
eventObj.originSlice = originSlice.name;
|
|
373
|
+
}
|
|
374
|
+
const aggregate = findAggregateForEvent(model, event);
|
|
375
|
+
if (aggregate)
|
|
376
|
+
eventObj.aggregate = aggregate.name;
|
|
377
|
+
return eventObj;
|
|
378
|
+
}),
|
|
379
|
+
readModels: components.readModels.map(rm => {
|
|
380
|
+
const rmObj = {
|
|
381
|
+
name: rm.name,
|
|
382
|
+
fields: rm.fields.map(fieldToJson)
|
|
383
|
+
};
|
|
384
|
+
if (rm.originalNodeId) {
|
|
385
|
+
rmObj.linkedCopy = true;
|
|
386
|
+
const originSlice = findSliceForNode(model, rm.originalNodeId);
|
|
387
|
+
if (originSlice)
|
|
388
|
+
rmObj.originSlice = originSlice.name;
|
|
389
|
+
}
|
|
390
|
+
return rmObj;
|
|
391
|
+
}),
|
|
392
|
+
processors: components.processors.map(proc => ({
|
|
393
|
+
name: proc.name,
|
|
394
|
+
fields: proc.fields.map(fieldToJson)
|
|
395
|
+
}))
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
if (internalFlows.length > 0) {
|
|
399
|
+
result.informationFlow = internalFlows.map(flow => ({
|
|
400
|
+
from: getName(flow.sourceId),
|
|
401
|
+
to: getName(flow.targetId)
|
|
402
|
+
}));
|
|
403
|
+
}
|
|
404
|
+
if (scenarios.length > 0) {
|
|
405
|
+
result.scenarios = scenarios.map(scenario => {
|
|
406
|
+
const scenarioObj = { name: scenario.name };
|
|
407
|
+
if (scenario.description)
|
|
408
|
+
scenarioObj.description = scenario.description;
|
|
409
|
+
if (scenario.givenEvents.length > 0) {
|
|
410
|
+
scenarioObj.given = scenario.givenEvents.map(given => {
|
|
411
|
+
const evt = model.events.get(given.eventStickyId);
|
|
412
|
+
return {
|
|
413
|
+
eventType: evt?.name ?? 'UnknownEvent',
|
|
414
|
+
...(given.fieldValues && Object.keys(given.fieldValues).length > 0 ? { fieldValues: given.fieldValues } : {})
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (scenario.whenCommand) {
|
|
419
|
+
const cmd = model.commands.get(scenario.whenCommand.commandStickyId);
|
|
420
|
+
scenarioObj.when = {
|
|
421
|
+
commandType: cmd?.name ?? 'UnknownCommand',
|
|
422
|
+
...(scenario.whenCommand.fieldValues && Object.keys(scenario.whenCommand.fieldValues).length > 0 ? { fieldValues: scenario.whenCommand.fieldValues } : {})
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (scenario.then.type === 'error') {
|
|
426
|
+
scenarioObj.then = {
|
|
427
|
+
type: 'error',
|
|
428
|
+
...(scenario.then.errorType ? { errorType: scenario.then.errorType } : {}),
|
|
429
|
+
...(scenario.then.errorMessage ? { errorMessage: scenario.then.errorMessage } : {})
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
else if (scenario.then.type === 'events' && scenario.then.expectedEvents) {
|
|
433
|
+
scenarioObj.then = {
|
|
434
|
+
type: 'events',
|
|
435
|
+
expectedEvents: scenario.then.expectedEvents.map(expected => {
|
|
436
|
+
const evt = model.events.get(expected.eventStickyId);
|
|
437
|
+
return {
|
|
438
|
+
eventType: evt?.name ?? 'UnknownEvent',
|
|
439
|
+
...(expected.fieldValues && Object.keys(expected.fieldValues).length > 0 ? { fieldValues: expected.fieldValues } : {})
|
|
440
|
+
};
|
|
441
|
+
})
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
else if (scenario.then.type === 'readModelAssertion' && scenario.then.readModelAssertion) {
|
|
445
|
+
const assertion = scenario.then.readModelAssertion;
|
|
446
|
+
const rm = model.readModels.get(assertion.readModelStickyId);
|
|
447
|
+
scenarioObj.then = {
|
|
448
|
+
type: 'readModelAssertion',
|
|
449
|
+
readModelType: rm?.name ?? 'UnknownReadModel',
|
|
450
|
+
expectedFieldValues: assertion.expectedFieldValues
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
return scenarioObj;
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
export function showSlice(model, name, format) {
|
|
310
459
|
const slices = [...model.slices.values()];
|
|
311
460
|
const nameLower = name.toLowerCase();
|
|
312
461
|
const slice = slices.find(s => s.name.toLowerCase() === nameLower || s.name.toLowerCase().includes(nameLower));
|
|
@@ -318,5 +467,9 @@ export function showSlice(model, name) {
|
|
|
318
467
|
}
|
|
319
468
|
process.exit(1);
|
|
320
469
|
}
|
|
470
|
+
if (format === 'json') {
|
|
471
|
+
outputJson(formatSliceJson(model, slice));
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
321
474
|
console.log(formatSliceXml(model, slice));
|
|
322
475
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eventmodeler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "CLI tool for interacting with Event Model files - query, update, and export event models from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"eventmodeler": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "tsc",
|
|
@@ -27,7 +28,8 @@
|
|
|
27
28
|
"license": "MIT",
|
|
28
29
|
"repository": {
|
|
29
30
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/
|
|
31
|
+
"url": "https://github.com/theoema/event-modeler",
|
|
32
|
+
"directory": "cli"
|
|
31
33
|
},
|
|
32
34
|
"homepage": "https://www.eventmodeling.app",
|
|
33
35
|
"engines": {
|