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.
Files changed (34) hide show
  1. package/README.md +175 -0
  2. package/dist/index.js +51 -19
  3. package/dist/lib/config.d.ts +2 -0
  4. package/dist/lib/config.js +26 -0
  5. package/dist/lib/format.d.ts +3 -0
  6. package/dist/lib/format.js +11 -0
  7. package/dist/projection.js +29 -35
  8. package/dist/slices/list-chapters/index.d.ts +2 -1
  9. package/dist/slices/list-chapters/index.js +10 -11
  10. package/dist/slices/list-commands/index.d.ts +2 -1
  11. package/dist/slices/list-commands/index.js +9 -10
  12. package/dist/slices/list-events/index.d.ts +2 -1
  13. package/dist/slices/list-events/index.js +33 -13
  14. package/dist/slices/list-slices/index.d.ts +2 -1
  15. package/dist/slices/list-slices/index.js +9 -10
  16. package/dist/slices/search/index.d.ts +2 -1
  17. package/dist/slices/search/index.js +148 -21
  18. package/dist/slices/show-actor/index.d.ts +3 -2
  19. package/dist/slices/show-actor/index.js +44 -9
  20. package/dist/slices/show-aggregate-completeness/index.d.ts +3 -2
  21. package/dist/slices/show-aggregate-completeness/index.js +60 -9
  22. package/dist/slices/show-chapter/index.d.ts +2 -1
  23. package/dist/slices/show-chapter/index.js +9 -9
  24. package/dist/slices/show-command/index.d.ts +2 -1
  25. package/dist/slices/show-command/index.js +52 -9
  26. package/dist/slices/show-completeness/index.d.ts +3 -1
  27. package/dist/slices/show-completeness/index.js +225 -32
  28. package/dist/slices/show-event/index.d.ts +2 -1
  29. package/dist/slices/show-event/index.js +41 -9
  30. package/dist/slices/show-model-summary/index.d.ts +2 -1
  31. package/dist/slices/show-model-summary/index.js +18 -9
  32. package/dist/slices/show-slice/index.d.ts +2 -1
  33. package/dist/slices/show-slice/index.js +162 -9
  34. package/package.json +5 -3
@@ -1,18 +1,17 @@
1
- function escapeXml(str) {
2
- return str
3
- .replace(/&/g, '&')
4
- .replace(/</g, '&lt;')
5
- .replace(/>/g, '&gt;')
6
- .replace(/"/g, '&quot;')
7
- .replace(/'/g, '&apos;');
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
- export declare function search(model: EventModel, term: string): void;
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
- function escapeXml(str) {
2
- return str
3
- .replace(/&/g, '&amp;')
4
- .replace(/</g, '&lt;')
5
- .replace(/>/g, '&gt;')
6
- .replace(/"/g, '&quot;')
7
- .replace(/'/g, '&apos;');
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 results = [];
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
- results.push(xml);
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
- results.push(xml);
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
- results.push(xml);
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
- results.push(xml);
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
- results.push(xml);
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
- let xml = ` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>`;
150
- results.push(xml);
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
- results.push(xml);
285
+ xmlResults.push(xml);
163
286
  }
164
287
  }
165
- if (results.length === 0) {
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="${results.length}">`);
170
- for (const result of results) {
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
- export declare function showActor(model: EventModel, actorName: string): void;
3
- export declare function listActors(model: EventModel): void;
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
- function escapeXml(str) {
2
- return str
3
- .replace(/&/g, '&amp;')
4
- .replace(/</g, '&lt;')
5
- .replace(/>/g, '&gt;')
6
- .replace(/"/g, '&quot;');
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
- export declare function showAggregateCompleteness(model: EventModel, aggregateName: string): void;
3
- export declare function listAggregates(model: EventModel): void;
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
- function escapeXml(str) {
2
- return str
3
- .replace(/&/g, '&amp;')
4
- .replace(/</g, '&lt;')
5
- .replace(/>/g, '&gt;')
6
- .replace(/"/g, '&quot;');
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
- export declare function showChapter(model: EventModel, name: string): void;
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
- function escapeXml(str) {
2
- return str
3
- .replace(/&/g, '&amp;')
4
- .replace(/</g, '&lt;')
5
- .replace(/>/g, '&gt;')
6
- .replace(/"/g, '&quot;')
7
- .replace(/'/g, '&apos;');
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
- export declare function showCommand(model: EventModel, name: string): void;
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
- function escapeXml(str) {
2
- return str
3
- .replace(/&/g, '&amp;')
4
- .replace(/</g, '&lt;')
5
- .replace(/>/g, '&gt;')
6
- .replace(/"/g, '&quot;')
7
- .replace(/'/g, '&apos;');
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
- export declare function showCompleteness(model: EventModel, readModelName: string): void;
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;