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,18 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"')
|
|
7
|
-
.replace(/'/g, ''');
|
|
8
|
-
}
|
|
9
|
-
export function listSlices(model) {
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
+
export function listSlices(model, format) {
|
|
10
3
|
const slices = [...model.slices.values()];
|
|
4
|
+
const sorted = [...slices].sort((a, b) => a.position.x - b.position.x);
|
|
5
|
+
if (format === 'json') {
|
|
6
|
+
outputJson({
|
|
7
|
+
slices: sorted.map(s => ({ name: s.name, status: s.status }))
|
|
8
|
+
});
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
11
|
if (slices.length === 0) {
|
|
12
12
|
console.log('<slices/>');
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
const sorted = [...slices].sort((a, b) => a.position.x - b.position.x);
|
|
16
15
|
console.log('<slices>');
|
|
17
16
|
for (const slice of sorted) {
|
|
18
17
|
console.log(` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>`);
|
|
@@ -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 search(model: EventModel, term: 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 isInSlice(slice, pos, width, height) {
|
|
10
3
|
const centerX = pos.x + width / 2;
|
|
11
4
|
const centerY = pos.y + height / 2;
|
|
@@ -17,15 +10,42 @@ function isInSlice(slice, pos, width, height) {
|
|
|
17
10
|
function findSlicesContaining(model, pos, width, height) {
|
|
18
11
|
return [...model.slices.values()].filter(slice => isInSlice(slice, pos, width, height));
|
|
19
12
|
}
|
|
20
|
-
export function search(model, term) {
|
|
13
|
+
export function search(model, term, format) {
|
|
21
14
|
const termLower = term.toLowerCase();
|
|
22
|
-
const
|
|
15
|
+
const xmlResults = [];
|
|
16
|
+
const jsonResults = [];
|
|
23
17
|
// Search commands
|
|
24
18
|
for (const cmd of model.commands.values()) {
|
|
25
19
|
if (cmd.name.toLowerCase().includes(termLower)) {
|
|
26
20
|
const slices = findSlicesContaining(model, cmd.position, cmd.width, cmd.height);
|
|
27
21
|
const incomingFlows = [...model.flows.values()].filter(f => f.targetId === cmd.id);
|
|
28
22
|
const outgoingFlows = [...model.flows.values()].filter(f => f.sourceId === cmd.id);
|
|
23
|
+
// JSON result
|
|
24
|
+
const jsonResult = { type: 'command', name: cmd.name, fields: cmd.fields.length };
|
|
25
|
+
if (slices.length > 0) {
|
|
26
|
+
jsonResult.inSlices = slices.map(s => ({ name: s.name, status: s.status }));
|
|
27
|
+
}
|
|
28
|
+
const triggeredBy = [];
|
|
29
|
+
for (const flow of incomingFlows) {
|
|
30
|
+
const screen = model.screens.get(flow.sourceId);
|
|
31
|
+
const processor = model.processors.get(flow.sourceId);
|
|
32
|
+
if (screen)
|
|
33
|
+
triggeredBy.push(screen.name);
|
|
34
|
+
if (processor)
|
|
35
|
+
triggeredBy.push(processor.name);
|
|
36
|
+
}
|
|
37
|
+
if (triggeredBy.length > 0)
|
|
38
|
+
jsonResult.triggeredBy = triggeredBy;
|
|
39
|
+
const produces = [];
|
|
40
|
+
for (const flow of outgoingFlows) {
|
|
41
|
+
const evt = model.events.get(flow.targetId);
|
|
42
|
+
if (evt)
|
|
43
|
+
produces.push(evt.name);
|
|
44
|
+
}
|
|
45
|
+
if (produces.length > 0)
|
|
46
|
+
jsonResult.produces = produces;
|
|
47
|
+
jsonResults.push(jsonResult);
|
|
48
|
+
// XML result
|
|
29
49
|
let xml = ` <command name="${escapeXml(cmd.name)}" fields="${cmd.fields.length}">\n`;
|
|
30
50
|
if (slices.length > 0) {
|
|
31
51
|
xml += ' <in-slices>\n';
|
|
@@ -56,7 +76,7 @@ export function search(model, term) {
|
|
|
56
76
|
xml += ' </produces>\n';
|
|
57
77
|
}
|
|
58
78
|
xml += ' </command>';
|
|
59
|
-
|
|
79
|
+
xmlResults.push(xml);
|
|
60
80
|
}
|
|
61
81
|
}
|
|
62
82
|
// Search events
|
|
@@ -65,6 +85,29 @@ export function search(model, term) {
|
|
|
65
85
|
const slices = findSlicesContaining(model, evt.position, evt.width, evt.height);
|
|
66
86
|
const incomingFlows = [...model.flows.values()].filter(f => f.targetId === evt.id);
|
|
67
87
|
const outgoingFlows = [...model.flows.values()].filter(f => f.sourceId === evt.id);
|
|
88
|
+
// JSON result
|
|
89
|
+
const jsonResult = { type: 'event', name: evt.name, fields: evt.fields.length };
|
|
90
|
+
if (slices.length > 0) {
|
|
91
|
+
jsonResult.inSlices = slices.map(s => ({ name: s.name, status: s.status }));
|
|
92
|
+
}
|
|
93
|
+
const producedBy = [];
|
|
94
|
+
for (const flow of incomingFlows) {
|
|
95
|
+
const cmd = model.commands.get(flow.sourceId);
|
|
96
|
+
if (cmd)
|
|
97
|
+
producedBy.push(cmd.name);
|
|
98
|
+
}
|
|
99
|
+
if (producedBy.length > 0)
|
|
100
|
+
jsonResult.producedBy = producedBy;
|
|
101
|
+
const consumedBy = [];
|
|
102
|
+
for (const flow of outgoingFlows) {
|
|
103
|
+
const rm = model.readModels.get(flow.targetId);
|
|
104
|
+
if (rm)
|
|
105
|
+
consumedBy.push(rm.name);
|
|
106
|
+
}
|
|
107
|
+
if (consumedBy.length > 0)
|
|
108
|
+
jsonResult.consumedBy = consumedBy;
|
|
109
|
+
jsonResults.push(jsonResult);
|
|
110
|
+
// XML result
|
|
68
111
|
let xml = ` <event name="${escapeXml(evt.name)}" fields="${evt.fields.length}">\n`;
|
|
69
112
|
if (slices.length > 0) {
|
|
70
113
|
xml += ' <in-slices>\n';
|
|
@@ -92,13 +135,20 @@ export function search(model, term) {
|
|
|
92
135
|
xml += ' </consumed-by>\n';
|
|
93
136
|
}
|
|
94
137
|
xml += ' </event>';
|
|
95
|
-
|
|
138
|
+
xmlResults.push(xml);
|
|
96
139
|
}
|
|
97
140
|
}
|
|
98
141
|
// Search read models
|
|
99
142
|
for (const rm of model.readModels.values()) {
|
|
100
143
|
if (rm.name.toLowerCase().includes(termLower)) {
|
|
101
144
|
const slices = findSlicesContaining(model, rm.position, rm.width, rm.height);
|
|
145
|
+
// JSON result
|
|
146
|
+
const jsonResult = { type: 'read-model', name: rm.name, fields: rm.fields.length };
|
|
147
|
+
if (slices.length > 0) {
|
|
148
|
+
jsonResult.inSlices = slices.map(s => ({ name: s.name, status: s.status }));
|
|
149
|
+
}
|
|
150
|
+
jsonResults.push(jsonResult);
|
|
151
|
+
// XML result
|
|
102
152
|
let xml = ` <read-model name="${escapeXml(rm.name)}" fields="${rm.fields.length}">\n`;
|
|
103
153
|
if (slices.length > 0) {
|
|
104
154
|
xml += ' <in-slices>\n';
|
|
@@ -108,13 +158,20 @@ export function search(model, term) {
|
|
|
108
158
|
xml += ' </in-slices>\n';
|
|
109
159
|
}
|
|
110
160
|
xml += ' </read-model>';
|
|
111
|
-
|
|
161
|
+
xmlResults.push(xml);
|
|
112
162
|
}
|
|
113
163
|
}
|
|
114
164
|
// Search screens
|
|
115
165
|
for (const scr of model.screens.values()) {
|
|
116
166
|
if (scr.name.toLowerCase().includes(termLower)) {
|
|
117
167
|
const slices = findSlicesContaining(model, scr.position, scr.width, scr.height);
|
|
168
|
+
// JSON result
|
|
169
|
+
const jsonResult = { type: 'screen', name: scr.name, fields: scr.fields.length };
|
|
170
|
+
if (slices.length > 0) {
|
|
171
|
+
jsonResult.inSlices = slices.map(s => ({ name: s.name, status: s.status }));
|
|
172
|
+
}
|
|
173
|
+
jsonResults.push(jsonResult);
|
|
174
|
+
// XML result
|
|
118
175
|
let xml = ` <screen name="${escapeXml(scr.name)}" fields="${scr.fields.length}">\n`;
|
|
119
176
|
if (slices.length > 0) {
|
|
120
177
|
xml += ' <in-slices>\n';
|
|
@@ -124,13 +181,20 @@ export function search(model, term) {
|
|
|
124
181
|
xml += ' </in-slices>\n';
|
|
125
182
|
}
|
|
126
183
|
xml += ' </screen>';
|
|
127
|
-
|
|
184
|
+
xmlResults.push(xml);
|
|
128
185
|
}
|
|
129
186
|
}
|
|
130
187
|
// Search processors
|
|
131
188
|
for (const proc of model.processors.values()) {
|
|
132
189
|
if (proc.name.toLowerCase().includes(termLower)) {
|
|
133
190
|
const slices = findSlicesContaining(model, proc.position, proc.width, proc.height);
|
|
191
|
+
// JSON result
|
|
192
|
+
const jsonResult = { type: 'processor', name: proc.name, fields: proc.fields.length };
|
|
193
|
+
if (slices.length > 0) {
|
|
194
|
+
jsonResult.inSlices = slices.map(s => ({ name: s.name, status: s.status }));
|
|
195
|
+
}
|
|
196
|
+
jsonResults.push(jsonResult);
|
|
197
|
+
// XML result
|
|
134
198
|
let xml = ` <processor name="${escapeXml(proc.name)}" fields="${proc.fields.length}">\n`;
|
|
135
199
|
if (slices.length > 0) {
|
|
136
200
|
xml += ' <in-slices>\n';
|
|
@@ -140,34 +204,97 @@ export function search(model, term) {
|
|
|
140
204
|
xml += ' </in-slices>\n';
|
|
141
205
|
}
|
|
142
206
|
xml += ' </processor>';
|
|
143
|
-
|
|
207
|
+
xmlResults.push(xml);
|
|
144
208
|
}
|
|
145
209
|
}
|
|
146
210
|
// Search slices
|
|
147
211
|
for (const slice of model.slices.values()) {
|
|
148
212
|
if (slice.name.toLowerCase().includes(termLower)) {
|
|
149
|
-
|
|
150
|
-
|
|
213
|
+
// JSON result
|
|
214
|
+
jsonResults.push({ type: 'slice', name: slice.name });
|
|
215
|
+
// XML result
|
|
216
|
+
xmlResults.push(` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Search aggregates
|
|
220
|
+
for (const agg of model.aggregates.values()) {
|
|
221
|
+
if (agg.name.toLowerCase().includes(termLower)) {
|
|
222
|
+
const events = agg.eventIds
|
|
223
|
+
.map(id => model.events.get(id))
|
|
224
|
+
.filter((e) => e !== undefined);
|
|
225
|
+
// JSON result
|
|
226
|
+
const jsonResult = { type: 'aggregate', name: agg.name, events: events.length };
|
|
227
|
+
if (events.length > 0) {
|
|
228
|
+
jsonResult.contains = events.map(e => e.name);
|
|
229
|
+
}
|
|
230
|
+
jsonResults.push(jsonResult);
|
|
231
|
+
// XML result
|
|
232
|
+
let xml = ` <aggregate name="${escapeXml(agg.name)}" events="${events.length}">\n`;
|
|
233
|
+
if (events.length > 0) {
|
|
234
|
+
xml += ' <contains>\n';
|
|
235
|
+
for (const evt of events) {
|
|
236
|
+
xml += ` <event name="${escapeXml(evt.name)}"/>\n`;
|
|
237
|
+
}
|
|
238
|
+
xml += ' </contains>\n';
|
|
239
|
+
}
|
|
240
|
+
xml += ' </aggregate>';
|
|
241
|
+
xmlResults.push(xml);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Search actors
|
|
245
|
+
for (const actor of model.actors.values()) {
|
|
246
|
+
if (actor.name.toLowerCase().includes(termLower)) {
|
|
247
|
+
const screens = actor.screenIds
|
|
248
|
+
.map(id => model.screens.get(id))
|
|
249
|
+
.filter((s) => s !== undefined);
|
|
250
|
+
// JSON result
|
|
251
|
+
const jsonResult = { type: 'actor', name: actor.name, screens: screens.length };
|
|
252
|
+
if (screens.length > 0) {
|
|
253
|
+
jsonResult.contains = screens.map(s => s.name);
|
|
254
|
+
}
|
|
255
|
+
jsonResults.push(jsonResult);
|
|
256
|
+
// XML result
|
|
257
|
+
let xml = ` <actor name="${escapeXml(actor.name)}" screens="${screens.length}">\n`;
|
|
258
|
+
if (screens.length > 0) {
|
|
259
|
+
xml += ' <contains>\n';
|
|
260
|
+
for (const scr of screens) {
|
|
261
|
+
xml += ` <screen name="${escapeXml(scr.name)}"/>\n`;
|
|
262
|
+
}
|
|
263
|
+
xml += ' </contains>\n';
|
|
264
|
+
}
|
|
265
|
+
xml += ' </actor>';
|
|
266
|
+
xmlResults.push(xml);
|
|
151
267
|
}
|
|
152
268
|
}
|
|
153
269
|
// Search scenarios
|
|
154
270
|
for (const scenario of model.scenarios.values()) {
|
|
155
271
|
if (scenario.name.toLowerCase().includes(termLower)) {
|
|
156
272
|
const slice = model.slices.get(scenario.sliceId);
|
|
273
|
+
// JSON result
|
|
274
|
+
const jsonResult = { type: 'scenario', name: scenario.name };
|
|
275
|
+
if (slice) {
|
|
276
|
+
jsonResult.inSlice = { name: slice.name, status: slice.status };
|
|
277
|
+
}
|
|
278
|
+
jsonResults.push(jsonResult);
|
|
279
|
+
// XML result
|
|
157
280
|
let xml = ` <scenario name="${escapeXml(scenario.name)}">\n`;
|
|
158
281
|
if (slice) {
|
|
159
282
|
xml += ` <in-slice name="${escapeXml(slice.name)}" status="${slice.status}"/>\n`;
|
|
160
283
|
}
|
|
161
284
|
xml += ' </scenario>';
|
|
162
|
-
|
|
285
|
+
xmlResults.push(xml);
|
|
163
286
|
}
|
|
164
287
|
}
|
|
165
|
-
if (
|
|
288
|
+
if (format === 'json') {
|
|
289
|
+
outputJson({ query: term, count: jsonResults.length, results: jsonResults });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (xmlResults.length === 0) {
|
|
166
293
|
console.log(`<search-results query="${escapeXml(term)}" count="0"/>`);
|
|
167
294
|
}
|
|
168
295
|
else {
|
|
169
|
-
console.log(`<search-results query="${escapeXml(term)}" count="${
|
|
170
|
-
for (const result of
|
|
296
|
+
console.log(`<search-results query="${escapeXml(term)}" count="${xmlResults.length}">`);
|
|
297
|
+
for (const result of xmlResults) {
|
|
171
298
|
console.log(result);
|
|
172
299
|
}
|
|
173
300
|
console.log('</search-results>');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { EventModel } from '../../types.js';
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
2
|
+
import { type OutputFormat } from '../../lib/format.js';
|
|
3
|
+
export declare function showActor(model: EventModel, actorName: string, format: OutputFormat): void;
|
|
4
|
+
export declare function listActors(model: EventModel, format: OutputFormat): void;
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return str
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"');
|
|
7
|
-
}
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
8
2
|
// Get screens whose center point is inside the actor bounds
|
|
9
3
|
function getScreensInActor(model, actor) {
|
|
10
4
|
const bounds = {
|
|
@@ -34,7 +28,7 @@ function findSliceForScreen(model, screen) {
|
|
|
34
28
|
}
|
|
35
29
|
return null;
|
|
36
30
|
}
|
|
37
|
-
export function showActor(model, actorName) {
|
|
31
|
+
export function showActor(model, actorName, format) {
|
|
38
32
|
// Find the actor
|
|
39
33
|
const nameLower = actorName.toLowerCase();
|
|
40
34
|
const actors = [...model.actors.values()];
|
|
@@ -49,6 +43,29 @@ export function showActor(model, actorName) {
|
|
|
49
43
|
}
|
|
50
44
|
// Get screens dynamically based on position (center point inside actor bounds)
|
|
51
45
|
const screensInActor = getScreensInActor(model, actor);
|
|
46
|
+
if (format === 'json') {
|
|
47
|
+
outputJson({
|
|
48
|
+
id: actor.id,
|
|
49
|
+
name: actor.name,
|
|
50
|
+
screensCount: screensInActor.length,
|
|
51
|
+
screens: screensInActor.map(screen => {
|
|
52
|
+
const result = {
|
|
53
|
+
id: screen.id,
|
|
54
|
+
name: screen.name,
|
|
55
|
+
fields: screen.fields.length
|
|
56
|
+
};
|
|
57
|
+
if (screen.originalNodeId) {
|
|
58
|
+
result.linkedCopy = true;
|
|
59
|
+
const original = model.screens.get(screen.originalNodeId);
|
|
60
|
+
const originSlice = original ? findSliceForScreen(model, original) : null;
|
|
61
|
+
if (originSlice)
|
|
62
|
+
result.originSlice = originSlice;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
})
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
52
69
|
console.log(`<actor id="${actor.id}" name="${escapeXml(actor.name)}">`);
|
|
53
70
|
console.log(` <screens-count>${screensInActor.length}</screens-count>`);
|
|
54
71
|
if (screensInActor.length > 0) {
|
|
@@ -70,8 +87,26 @@ export function showActor(model, actorName) {
|
|
|
70
87
|
}
|
|
71
88
|
console.log('</actor>');
|
|
72
89
|
}
|
|
73
|
-
export function listActors(model) {
|
|
90
|
+
export function listActors(model, format) {
|
|
74
91
|
const actors = [...model.actors.values()];
|
|
92
|
+
if (format === 'json') {
|
|
93
|
+
outputJson({
|
|
94
|
+
actors: actors.map(actor => {
|
|
95
|
+
const screensInActor = getScreensInActor(model, actor);
|
|
96
|
+
const originalCount = screensInActor.filter(s => !s.originalNodeId).length;
|
|
97
|
+
const copyCount = screensInActor.filter(s => s.originalNodeId).length;
|
|
98
|
+
const result = {
|
|
99
|
+
id: actor.id,
|
|
100
|
+
name: actor.name,
|
|
101
|
+
screens: originalCount
|
|
102
|
+
};
|
|
103
|
+
if (copyCount > 0)
|
|
104
|
+
result.copies = copyCount;
|
|
105
|
+
return result;
|
|
106
|
+
})
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
75
110
|
if (actors.length === 0) {
|
|
76
111
|
console.log('<actors/>');
|
|
77
112
|
return;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { EventModel } from '../../types.js';
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
2
|
+
import { type OutputFormat } from '../../lib/format.js';
|
|
3
|
+
export declare function showAggregateCompleteness(model: EventModel, aggregateName: string, format: OutputFormat): void;
|
|
4
|
+
export declare function listAggregates(model: EventModel, format: OutputFormat): void;
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return str
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"');
|
|
7
|
-
}
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
8
2
|
function findMatchingField(fields, targetName, targetType) {
|
|
9
3
|
for (const field of fields) {
|
|
10
4
|
// Check if name and type match
|
|
@@ -38,7 +32,7 @@ function getEventsInAggregate(model, aggregate) {
|
|
|
38
32
|
// Linked copies have originalNodeId set, originals do not
|
|
39
33
|
return [...model.events.values()].filter(e => !e.originalNodeId && isInAggregate(e.position, e.width, e.height));
|
|
40
34
|
}
|
|
41
|
-
export function showAggregateCompleteness(model, aggregateName) {
|
|
35
|
+
export function showAggregateCompleteness(model, aggregateName, format) {
|
|
42
36
|
// Find the aggregate
|
|
43
37
|
const nameLower = aggregateName.toLowerCase();
|
|
44
38
|
const aggregates = [...model.aggregates.values()];
|
|
@@ -55,6 +49,16 @@ export function showAggregateCompleteness(model, aggregateName) {
|
|
|
55
49
|
const eventsInAggregate = getEventsInAggregate(model, aggregate);
|
|
56
50
|
// Check if aggregate has ID field configured
|
|
57
51
|
if (!aggregate.aggregateIdFieldName || !aggregate.aggregateIdFieldType) {
|
|
52
|
+
if (format === 'json') {
|
|
53
|
+
outputJson({
|
|
54
|
+
aggregate: aggregate.name,
|
|
55
|
+
status: 'unconfigured',
|
|
56
|
+
eventsCount: eventsInAggregate.length,
|
|
57
|
+
events: eventsInAggregate.map(e => ({ id: e.id, name: e.name })),
|
|
58
|
+
message: 'Aggregate ID field not configured. Set aggregateIdFieldName and aggregateIdFieldType.'
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
58
62
|
console.log(`<aggregate-completeness aggregate="${escapeXml(aggregate.name)}" status="unconfigured">`);
|
|
59
63
|
console.log(` <events-count>${eventsInAggregate.length}</events-count>`);
|
|
60
64
|
console.log(' <events>');
|
|
@@ -69,6 +73,15 @@ export function showAggregateCompleteness(model, aggregateName) {
|
|
|
69
73
|
const idFieldName = aggregate.aggregateIdFieldName;
|
|
70
74
|
const idFieldType = aggregate.aggregateIdFieldType;
|
|
71
75
|
if (eventsInAggregate.length === 0) {
|
|
76
|
+
if (format === 'json') {
|
|
77
|
+
outputJson({
|
|
78
|
+
aggregate: aggregate.name,
|
|
79
|
+
status: 'empty',
|
|
80
|
+
idField: { name: idFieldName, type: idFieldType },
|
|
81
|
+
message: 'No events in this aggregate.'
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
72
85
|
console.log(`<aggregate-completeness aggregate="${escapeXml(aggregate.name)}" status="empty">`);
|
|
73
86
|
console.log(` <id-field name="${escapeXml(idFieldName)}" type="${idFieldType}"/>`);
|
|
74
87
|
console.log(' <message>No events in this aggregate.</message>');
|
|
@@ -97,6 +110,26 @@ export function showAggregateCompleteness(model, aggregateName) {
|
|
|
97
110
|
const completionPercent = eventResults.length > 0
|
|
98
111
|
? Math.round((completeEvents.length / eventResults.length) * 100)
|
|
99
112
|
: 100;
|
|
113
|
+
if (format === 'json') {
|
|
114
|
+
outputJson({
|
|
115
|
+
aggregate: aggregate.name,
|
|
116
|
+
status: overallStatus,
|
|
117
|
+
completion: `${completionPercent}%`,
|
|
118
|
+
idField: { name: idFieldName, type: idFieldType },
|
|
119
|
+
summary: {
|
|
120
|
+
total: eventResults.length,
|
|
121
|
+
complete: completeEvents.length,
|
|
122
|
+
incomplete: incompleteEvents.length
|
|
123
|
+
},
|
|
124
|
+
incompleteEvents: incompleteEvents.map(e => ({ id: e.eventId, name: e.eventName })),
|
|
125
|
+
completeEvents: completeEvents.map(e => ({
|
|
126
|
+
id: e.eventId,
|
|
127
|
+
name: e.eventName,
|
|
128
|
+
fieldId: e.matchingField?.fieldId
|
|
129
|
+
}))
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
100
133
|
console.log(`<aggregate-completeness aggregate="${escapeXml(aggregate.name)}" status="${overallStatus}" completion="${completionPercent}%">`);
|
|
101
134
|
console.log(` <id-field name="${escapeXml(idFieldName)}" type="${idFieldType}"/>`);
|
|
102
135
|
console.log(` <summary total="${eventResults.length}" complete="${completeEvents.length}" incomplete="${incompleteEvents.length}"/>`);
|
|
@@ -119,8 +152,26 @@ export function showAggregateCompleteness(model, aggregateName) {
|
|
|
119
152
|
}
|
|
120
153
|
console.log('</aggregate-completeness>');
|
|
121
154
|
}
|
|
122
|
-
export function listAggregates(model) {
|
|
155
|
+
export function listAggregates(model, format) {
|
|
123
156
|
const aggregates = [...model.aggregates.values()];
|
|
157
|
+
if (format === 'json') {
|
|
158
|
+
outputJson({
|
|
159
|
+
aggregates: aggregates.map(aggregate => {
|
|
160
|
+
const eventsInAggregate = getEventsInAggregate(model, aggregate);
|
|
161
|
+
const result = {
|
|
162
|
+
id: aggregate.id,
|
|
163
|
+
name: aggregate.name,
|
|
164
|
+
events: eventsInAggregate.length
|
|
165
|
+
};
|
|
166
|
+
if (aggregate.aggregateIdFieldName) {
|
|
167
|
+
result.idField = aggregate.aggregateIdFieldName;
|
|
168
|
+
result.idType = aggregate.aggregateIdFieldType ?? 'Unknown';
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
124
175
|
if (aggregates.length === 0) {
|
|
125
176
|
console.log('<aggregates/>');
|
|
126
177
|
return;
|
|
@@ -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 showChapter(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 getSlicesUnderChapter(model, chapter) {
|
|
10
3
|
// A slice is "under" a chapter if its horizontal center falls within the chapter's x range
|
|
11
4
|
const chapterLeft = chapter.position.x;
|
|
@@ -15,7 +8,7 @@ function getSlicesUnderChapter(model, chapter) {
|
|
|
15
8
|
return sliceCenterX >= chapterLeft && sliceCenterX <= chapterRight;
|
|
16
9
|
}).sort((a, b) => a.position.x - b.position.x);
|
|
17
10
|
}
|
|
18
|
-
export function showChapter(model, name) {
|
|
11
|
+
export function showChapter(model, name, format) {
|
|
19
12
|
const chapters = [...model.chapters.values()];
|
|
20
13
|
const nameLower = name.toLowerCase();
|
|
21
14
|
const chapter = chapters.find(c => c.name.toLowerCase() === nameLower || c.name.toLowerCase().includes(nameLower));
|
|
@@ -28,6 +21,13 @@ export function showChapter(model, name) {
|
|
|
28
21
|
process.exit(1);
|
|
29
22
|
}
|
|
30
23
|
const slices = getSlicesUnderChapter(model, chapter);
|
|
24
|
+
if (format === 'json') {
|
|
25
|
+
outputJson({
|
|
26
|
+
name: chapter.name,
|
|
27
|
+
slices: slices.map(s => ({ name: s.name, status: s.status }))
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
31
|
console.log(`<chapter name="${escapeXml(chapter.name)}">`);
|
|
32
32
|
if (slices.length === 0) {
|
|
33
33
|
console.log(' <slices/>');
|
|
@@ -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 showCommand(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;
|
|
@@ -82,7 +91,7 @@ function formatCommandXml(model, command) {
|
|
|
82
91
|
xml += '</command>';
|
|
83
92
|
return xml;
|
|
84
93
|
}
|
|
85
|
-
export function showCommand(model, name) {
|
|
94
|
+
export function showCommand(model, name, format) {
|
|
86
95
|
const commands = [...model.commands.values()];
|
|
87
96
|
const nameLower = name.toLowerCase();
|
|
88
97
|
const command = commands.find(c => c.name.toLowerCase() === nameLower || c.name.toLowerCase().includes(nameLower));
|
|
@@ -94,5 +103,39 @@ export function showCommand(model, name) {
|
|
|
94
103
|
}
|
|
95
104
|
process.exit(1);
|
|
96
105
|
}
|
|
106
|
+
if (format === 'json') {
|
|
107
|
+
const incomingFlows = [...model.flows.values()].filter(f => f.targetId === command.id);
|
|
108
|
+
const outgoingFlows = [...model.flows.values()].filter(f => f.sourceId === command.id);
|
|
109
|
+
const result = {
|
|
110
|
+
name: command.name,
|
|
111
|
+
fields: command.fields.map(fieldToJson)
|
|
112
|
+
};
|
|
113
|
+
const triggeredBy = [];
|
|
114
|
+
for (const flow of incomingFlows) {
|
|
115
|
+
const screen = model.screens.get(flow.sourceId);
|
|
116
|
+
const processor = model.processors.get(flow.sourceId);
|
|
117
|
+
if (screen)
|
|
118
|
+
triggeredBy.push(screen.name);
|
|
119
|
+
if (processor)
|
|
120
|
+
triggeredBy.push(processor.name);
|
|
121
|
+
}
|
|
122
|
+
if (triggeredBy.length > 0)
|
|
123
|
+
result.triggeredBy = triggeredBy;
|
|
124
|
+
const produces = [];
|
|
125
|
+
for (const flow of outgoingFlows) {
|
|
126
|
+
const target = model.events.get(flow.targetId);
|
|
127
|
+
if (target) {
|
|
128
|
+
const aggregate = findAggregateForEvent(model, target);
|
|
129
|
+
produces.push({
|
|
130
|
+
name: target.name,
|
|
131
|
+
...(aggregate ? { aggregate: aggregate.name } : {})
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (produces.length > 0)
|
|
136
|
+
result.produces = produces;
|
|
137
|
+
outputJson(result);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
97
140
|
console.log(formatCommandXml(model, command));
|
|
98
141
|
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import type { EventModel } from '../../types.js';
|
|
2
|
-
|
|
2
|
+
import { type OutputFormat } from '../../lib/format.js';
|
|
3
|
+
export declare function showCompleteness(model: EventModel, elementName: string, format: OutputFormat): void;
|
|
4
|
+
export declare function showModelCompleteness(model: EventModel, format: OutputFormat): void;
|