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,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 formatFieldValues(values) {
|
|
10
4
|
if (!values || Object.keys(values).length === 0)
|
|
11
5
|
return '';
|
|
@@ -127,7 +121,7 @@ function formatSliceXml(model, slice) {
|
|
|
127
121
|
model.processors.get(id)?.name ??
|
|
128
122
|
id);
|
|
129
123
|
}
|
|
130
|
-
let xml = `<slice name="${escapeXml(slice.name)}" status="${slice.status}">\n`;
|
|
124
|
+
let xml = `<slice id="${slice.id}" name="${escapeXml(slice.name)}" status="${slice.status}">\n`;
|
|
131
125
|
xml += ' <components>\n';
|
|
132
126
|
for (const screen of components.screens) {
|
|
133
127
|
// Check if this is a linked copy
|
|
@@ -142,7 +136,7 @@ function formatSliceXml(model, slice) {
|
|
|
142
136
|
// Check which actor this screen belongs to
|
|
143
137
|
const actor = findActorForScreen(model, screen);
|
|
144
138
|
const actorAttr = actor ? ` actor="${escapeXml(actor.name)}"` : '';
|
|
145
|
-
xml += ` <screen name="${escapeXml(screen.name)}"${copyAttr}${originAttr}${actorAttr}>\n`;
|
|
139
|
+
xml += ` <screen id="${screen.id}" name="${escapeXml(screen.name)}"${copyAttr}${originAttr}${actorAttr}>\n`;
|
|
146
140
|
if (screen.fields.length > 0) {
|
|
147
141
|
xml += ' <fields>\n';
|
|
148
142
|
for (const field of screen.fields) {
|
|
@@ -153,7 +147,7 @@ function formatSliceXml(model, slice) {
|
|
|
153
147
|
xml += ' </screen>\n';
|
|
154
148
|
}
|
|
155
149
|
for (const command of components.commands) {
|
|
156
|
-
xml += ` <command name="${escapeXml(command.name)}">\n`;
|
|
150
|
+
xml += ` <command id="${command.id}" name="${escapeXml(command.name)}">\n`;
|
|
157
151
|
if (command.fields.length > 0) {
|
|
158
152
|
xml += ' <fields>\n';
|
|
159
153
|
for (const field of command.fields) {
|
|
@@ -184,7 +178,7 @@ function formatSliceXml(model, slice) {
|
|
|
184
178
|
// Check which aggregate this event belongs to
|
|
185
179
|
const aggregate = findAggregateForEvent(model, event);
|
|
186
180
|
const aggregateAttr = aggregate ? ` aggregate="${escapeXml(aggregate.name)}"` : '';
|
|
187
|
-
xml += ` <event name="${escapeXml(event.name)}"${copyAttr}${originAttr}${aggregateAttr}>\n`;
|
|
181
|
+
xml += ` <event id="${event.id}" name="${escapeXml(event.name)}"${copyAttr}${originAttr}${aggregateAttr}>\n`;
|
|
188
182
|
if (event.fields.length > 0) {
|
|
189
183
|
xml += ' <fields>\n';
|
|
190
184
|
for (const field of event.fields) {
|
|
@@ -215,7 +209,7 @@ function formatSliceXml(model, slice) {
|
|
|
215
209
|
originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
|
|
216
210
|
}
|
|
217
211
|
}
|
|
218
|
-
xml += ` <read-model name="${escapeXml(readModel.name)}"${copyAttr}${originAttr}>\n`;
|
|
212
|
+
xml += ` <read-model id="${readModel.id}" name="${escapeXml(readModel.name)}"${copyAttr}${originAttr}>\n`;
|
|
219
213
|
if (readModel.fields.length > 0) {
|
|
220
214
|
xml += ' <fields>\n';
|
|
221
215
|
for (const field of readModel.fields) {
|
|
@@ -226,7 +220,7 @@ function formatSliceXml(model, slice) {
|
|
|
226
220
|
xml += ' </read-model>\n';
|
|
227
221
|
}
|
|
228
222
|
for (const processor of components.processors) {
|
|
229
|
-
xml += ` <processor name="${escapeXml(processor.name)}">\n`;
|
|
223
|
+
xml += ` <processor id="${processor.id}" name="${escapeXml(processor.name)}">\n`;
|
|
230
224
|
if (processor.fields.length > 0) {
|
|
231
225
|
xml += ' <fields>\n';
|
|
232
226
|
for (const field of processor.fields) {
|
|
@@ -306,17 +300,173 @@ function formatSliceXml(model, slice) {
|
|
|
306
300
|
xml += '</slice>';
|
|
307
301
|
return xml;
|
|
308
302
|
}
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
303
|
+
function fieldToJson(field) {
|
|
304
|
+
const result = {
|
|
305
|
+
name: field.name,
|
|
306
|
+
type: field.fieldType
|
|
307
|
+
};
|
|
308
|
+
if (field.isList)
|
|
309
|
+
result.list = true;
|
|
310
|
+
if (field.isGenerated)
|
|
311
|
+
result.generated = true;
|
|
312
|
+
if (field.isOptional)
|
|
313
|
+
result.optional = true;
|
|
314
|
+
if (field.isUserInput)
|
|
315
|
+
result.userInput = true;
|
|
316
|
+
if (field.subfields && field.subfields.length > 0) {
|
|
317
|
+
result.subfields = field.subfields.map(fieldToJson);
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
function formatSliceJson(model, slice) {
|
|
322
|
+
const components = getSliceComponents(model, slice);
|
|
323
|
+
const scenarios = [...model.scenarios.values()].filter(s => s.sliceId === slice.id);
|
|
324
|
+
const componentIds = new Set();
|
|
325
|
+
components.commands.forEach(c => componentIds.add(c.id));
|
|
326
|
+
components.events.forEach(e => componentIds.add(e.id));
|
|
327
|
+
components.readModels.forEach(rm => componentIds.add(rm.id));
|
|
328
|
+
components.screens.forEach(s => componentIds.add(s.id));
|
|
329
|
+
components.processors.forEach(p => componentIds.add(p.id));
|
|
330
|
+
const flows = [...model.flows.values()].filter(f => componentIds.has(f.sourceId) || componentIds.has(f.targetId));
|
|
331
|
+
const internalFlows = flows.filter(f => componentIds.has(f.sourceId) && componentIds.has(f.targetId));
|
|
332
|
+
function getName(id) {
|
|
333
|
+
return (model.commands.get(id)?.name ??
|
|
334
|
+
model.events.get(id)?.name ??
|
|
335
|
+
model.readModels.get(id)?.name ??
|
|
336
|
+
model.screens.get(id)?.name ??
|
|
337
|
+
model.processors.get(id)?.name ??
|
|
338
|
+
id);
|
|
339
|
+
}
|
|
340
|
+
const result = {
|
|
341
|
+
id: slice.id,
|
|
342
|
+
name: slice.name,
|
|
343
|
+
status: slice.status,
|
|
344
|
+
components: {
|
|
345
|
+
screens: components.screens.map(screen => {
|
|
346
|
+
const screenObj = {
|
|
347
|
+
id: screen.id,
|
|
348
|
+
name: screen.name,
|
|
349
|
+
fields: screen.fields.map(fieldToJson)
|
|
350
|
+
};
|
|
351
|
+
if (screen.originalNodeId) {
|
|
352
|
+
screenObj.linkedCopy = true;
|
|
353
|
+
const originSlice = findSliceForNode(model, screen.originalNodeId);
|
|
354
|
+
if (originSlice)
|
|
355
|
+
screenObj.originSlice = originSlice.name;
|
|
356
|
+
}
|
|
357
|
+
const actor = findActorForScreen(model, screen);
|
|
358
|
+
if (actor)
|
|
359
|
+
screenObj.actor = actor.name;
|
|
360
|
+
return screenObj;
|
|
361
|
+
}),
|
|
362
|
+
commands: components.commands.map(cmd => ({
|
|
363
|
+
id: cmd.id,
|
|
364
|
+
name: cmd.name,
|
|
365
|
+
fields: cmd.fields.map(fieldToJson)
|
|
366
|
+
})),
|
|
367
|
+
events: components.events.map(event => {
|
|
368
|
+
const eventObj = {
|
|
369
|
+
id: event.id,
|
|
370
|
+
name: event.name,
|
|
371
|
+
fields: event.fields.map(fieldToJson)
|
|
372
|
+
};
|
|
373
|
+
if (event.originalNodeId) {
|
|
374
|
+
eventObj.linkedCopy = true;
|
|
375
|
+
const originSlice = findSliceForNode(model, event.originalNodeId);
|
|
376
|
+
if (originSlice)
|
|
377
|
+
eventObj.originSlice = originSlice.name;
|
|
378
|
+
}
|
|
379
|
+
const aggregate = findAggregateForEvent(model, event);
|
|
380
|
+
if (aggregate)
|
|
381
|
+
eventObj.aggregate = aggregate.name;
|
|
382
|
+
return eventObj;
|
|
383
|
+
}),
|
|
384
|
+
readModels: components.readModels.map(rm => {
|
|
385
|
+
const rmObj = {
|
|
386
|
+
id: rm.id,
|
|
387
|
+
name: rm.name,
|
|
388
|
+
fields: rm.fields.map(fieldToJson)
|
|
389
|
+
};
|
|
390
|
+
if (rm.originalNodeId) {
|
|
391
|
+
rmObj.linkedCopy = true;
|
|
392
|
+
const originSlice = findSliceForNode(model, rm.originalNodeId);
|
|
393
|
+
if (originSlice)
|
|
394
|
+
rmObj.originSlice = originSlice.name;
|
|
395
|
+
}
|
|
396
|
+
return rmObj;
|
|
397
|
+
}),
|
|
398
|
+
processors: components.processors.map(proc => ({
|
|
399
|
+
id: proc.id,
|
|
400
|
+
name: proc.name,
|
|
401
|
+
fields: proc.fields.map(fieldToJson)
|
|
402
|
+
}))
|
|
318
403
|
}
|
|
319
|
-
|
|
404
|
+
};
|
|
405
|
+
if (internalFlows.length > 0) {
|
|
406
|
+
result.informationFlow = internalFlows.map(flow => ({
|
|
407
|
+
from: getName(flow.sourceId),
|
|
408
|
+
to: getName(flow.targetId)
|
|
409
|
+
}));
|
|
410
|
+
}
|
|
411
|
+
if (scenarios.length > 0) {
|
|
412
|
+
result.scenarios = scenarios.map(scenario => {
|
|
413
|
+
const scenarioObj = { name: scenario.name };
|
|
414
|
+
if (scenario.description)
|
|
415
|
+
scenarioObj.description = scenario.description;
|
|
416
|
+
if (scenario.givenEvents.length > 0) {
|
|
417
|
+
scenarioObj.given = scenario.givenEvents.map(given => {
|
|
418
|
+
const evt = model.events.get(given.eventStickyId);
|
|
419
|
+
return {
|
|
420
|
+
eventType: evt?.name ?? 'UnknownEvent',
|
|
421
|
+
...(given.fieldValues && Object.keys(given.fieldValues).length > 0 ? { fieldValues: given.fieldValues } : {})
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (scenario.whenCommand) {
|
|
426
|
+
const cmd = model.commands.get(scenario.whenCommand.commandStickyId);
|
|
427
|
+
scenarioObj.when = {
|
|
428
|
+
commandType: cmd?.name ?? 'UnknownCommand',
|
|
429
|
+
...(scenario.whenCommand.fieldValues && Object.keys(scenario.whenCommand.fieldValues).length > 0 ? { fieldValues: scenario.whenCommand.fieldValues } : {})
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (scenario.then.type === 'error') {
|
|
433
|
+
scenarioObj.then = {
|
|
434
|
+
type: 'error',
|
|
435
|
+
...(scenario.then.errorType ? { errorType: scenario.then.errorType } : {}),
|
|
436
|
+
...(scenario.then.errorMessage ? { errorMessage: scenario.then.errorMessage } : {})
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
else if (scenario.then.type === 'events' && scenario.then.expectedEvents) {
|
|
440
|
+
scenarioObj.then = {
|
|
441
|
+
type: 'events',
|
|
442
|
+
expectedEvents: scenario.then.expectedEvents.map(expected => {
|
|
443
|
+
const evt = model.events.get(expected.eventStickyId);
|
|
444
|
+
return {
|
|
445
|
+
eventType: evt?.name ?? 'UnknownEvent',
|
|
446
|
+
...(expected.fieldValues && Object.keys(expected.fieldValues).length > 0 ? { fieldValues: expected.fieldValues } : {})
|
|
447
|
+
};
|
|
448
|
+
})
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
else if (scenario.then.type === 'readModelAssertion' && scenario.then.readModelAssertion) {
|
|
452
|
+
const assertion = scenario.then.readModelAssertion;
|
|
453
|
+
const rm = model.readModels.get(assertion.readModelStickyId);
|
|
454
|
+
scenarioObj.then = {
|
|
455
|
+
type: 'readModelAssertion',
|
|
456
|
+
readModelType: rm?.name ?? 'UnknownReadModel',
|
|
457
|
+
expectedFieldValues: assertion.expectedFieldValues
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
return scenarioObj;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
export function showSlice(model, name, format) {
|
|
466
|
+
const slice = findElementOrExit(model.slices, name, 'slice');
|
|
467
|
+
if (format === 'json') {
|
|
468
|
+
outputJson(formatSliceJson(model, slice));
|
|
469
|
+
return;
|
|
320
470
|
}
|
|
321
471
|
console.log(formatSliceXml(model, slice));
|
|
322
472
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { appendEvent } from '../../lib/file-loader.js';
|
|
2
|
+
import { findElementOrExit } from '../../lib/element-lookup.js';
|
|
2
3
|
function findFieldByName(fields, fieldName) {
|
|
3
4
|
const nameLower = fieldName.toLowerCase();
|
|
4
5
|
for (const field of fields) {
|
|
@@ -52,17 +53,7 @@ export function updateField(model, filePath, options, fieldName, updates) {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
function updateCommandField(model, filePath, commandName, fieldName, updates) {
|
|
55
|
-
const
|
|
56
|
-
const commands = [...model.commands.values()];
|
|
57
|
-
const command = commands.find(c => c.name.toLowerCase() === nameLower || c.name.toLowerCase().includes(nameLower));
|
|
58
|
-
if (!command) {
|
|
59
|
-
console.error(`Error: Command not found: ${commandName}`);
|
|
60
|
-
console.error('Available commands:');
|
|
61
|
-
for (const c of commands) {
|
|
62
|
-
console.error(` - ${c.name}`);
|
|
63
|
-
}
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
56
|
+
const command = findElementOrExit(model.commands, commandName, 'command');
|
|
66
57
|
const field = findFieldByName(command.fields, fieldName);
|
|
67
58
|
if (!field) {
|
|
68
59
|
console.error(`Error: Field "${fieldName}" not found on command "${command.name}"`);
|
|
@@ -86,17 +77,7 @@ function updateCommandField(model, filePath, commandName, fieldName, updates) {
|
|
|
86
77
|
logUpdates(updates);
|
|
87
78
|
}
|
|
88
79
|
function updateEventField(model, filePath, eventName, fieldName, updates) {
|
|
89
|
-
const
|
|
90
|
-
const events = [...model.events.values()];
|
|
91
|
-
const event = events.find(e => e.name.toLowerCase() === nameLower || e.name.toLowerCase().includes(nameLower));
|
|
92
|
-
if (!event) {
|
|
93
|
-
console.error(`Error: Event not found: ${eventName}`);
|
|
94
|
-
console.error('Available events:');
|
|
95
|
-
for (const e of events) {
|
|
96
|
-
console.error(` - ${e.name}`);
|
|
97
|
-
}
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
80
|
+
const event = findElementOrExit(model.events, eventName, 'event');
|
|
100
81
|
const field = findFieldByName(event.fields, fieldName);
|
|
101
82
|
if (!field) {
|
|
102
83
|
console.error(`Error: Field "${fieldName}" not found on event "${event.name}"`);
|
|
@@ -120,17 +101,7 @@ function updateEventField(model, filePath, eventName, fieldName, updates) {
|
|
|
120
101
|
logUpdates(updates);
|
|
121
102
|
}
|
|
122
103
|
function updateReadModelField(model, filePath, readModelName, fieldName, updates) {
|
|
123
|
-
const
|
|
124
|
-
const readModels = [...model.readModels.values()];
|
|
125
|
-
const readModel = readModels.find(rm => rm.name.toLowerCase() === nameLower || rm.name.toLowerCase().includes(nameLower));
|
|
126
|
-
if (!readModel) {
|
|
127
|
-
console.error(`Error: Read model not found: ${readModelName}`);
|
|
128
|
-
console.error('Available read models:');
|
|
129
|
-
for (const rm of readModels) {
|
|
130
|
-
console.error(` - ${rm.name}`);
|
|
131
|
-
}
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
104
|
+
const readModel = findElementOrExit(model.readModels, readModelName, 'read model');
|
|
134
105
|
const field = findFieldByName(readModel.fields, fieldName);
|
|
135
106
|
if (!field) {
|
|
136
107
|
console.error(`Error: Field "${fieldName}" not found on read model "${readModel.name}"`);
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eventmodeler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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": {
|