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.
Files changed (52) hide show
  1. package/README.md +175 -0
  2. package/dist/index.js +114 -19
  3. package/dist/lib/config.d.ts +2 -0
  4. package/dist/lib/config.js +26 -0
  5. package/dist/lib/element-lookup.d.ts +47 -0
  6. package/dist/lib/element-lookup.js +86 -0
  7. package/dist/lib/format.d.ts +3 -0
  8. package/dist/lib/format.js +11 -0
  9. package/dist/lib/slice-utils.d.ts +83 -0
  10. package/dist/lib/slice-utils.js +135 -0
  11. package/dist/projection.js +161 -35
  12. package/dist/slices/add-field/index.js +4 -33
  13. package/dist/slices/add-scenario/index.js +7 -74
  14. package/dist/slices/create-automation-slice/index.d.ts +2 -0
  15. package/dist/slices/create-automation-slice/index.js +217 -0
  16. package/dist/slices/create-flow/index.d.ts +2 -0
  17. package/dist/slices/create-flow/index.js +177 -0
  18. package/dist/slices/create-state-change-slice/index.d.ts +2 -0
  19. package/dist/slices/create-state-change-slice/index.js +239 -0
  20. package/dist/slices/create-state-view-slice/index.d.ts +2 -0
  21. package/dist/slices/create-state-view-slice/index.js +120 -0
  22. package/dist/slices/list-chapters/index.d.ts +2 -1
  23. package/dist/slices/list-chapters/index.js +11 -12
  24. package/dist/slices/list-commands/index.d.ts +2 -1
  25. package/dist/slices/list-commands/index.js +10 -11
  26. package/dist/slices/list-events/index.d.ts +2 -1
  27. package/dist/slices/list-events/index.js +36 -15
  28. package/dist/slices/list-slices/index.d.ts +2 -1
  29. package/dist/slices/list-slices/index.js +10 -11
  30. package/dist/slices/mark-slice-status/index.js +2 -11
  31. package/dist/slices/remove-field/index.js +4 -33
  32. package/dist/slices/remove-scenario/index.js +45 -11
  33. package/dist/slices/search/index.d.ts +2 -1
  34. package/dist/slices/search/index.js +148 -21
  35. package/dist/slices/show-actor/index.d.ts +3 -2
  36. package/dist/slices/show-actor/index.js +46 -20
  37. package/dist/slices/show-aggregate-completeness/index.d.ts +3 -2
  38. package/dist/slices/show-aggregate-completeness/index.js +62 -20
  39. package/dist/slices/show-chapter/index.d.ts +2 -1
  40. package/dist/slices/show-chapter/index.js +14 -22
  41. package/dist/slices/show-command/index.d.ts +2 -1
  42. package/dist/slices/show-command/index.js +54 -19
  43. package/dist/slices/show-completeness/index.d.ts +3 -1
  44. package/dist/slices/show-completeness/index.js +313 -31
  45. package/dist/slices/show-event/index.d.ts +2 -1
  46. package/dist/slices/show-event/index.js +44 -20
  47. package/dist/slices/show-model-summary/index.d.ts +2 -1
  48. package/dist/slices/show-model-summary/index.js +18 -9
  49. package/dist/slices/show-slice/index.d.ts +2 -1
  50. package/dist/slices/show-slice/index.js +174 -24
  51. package/dist/slices/update-field/index.js +4 -33
  52. package/package.json +5 -3
@@ -1,11 +1,5 @@
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
- }
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
- export function showSlice(model, name) {
310
- const slices = [...model.slices.values()];
311
- const nameLower = name.toLowerCase();
312
- const slice = slices.find(s => s.name.toLowerCase() === nameLower || s.name.toLowerCase().includes(nameLower));
313
- if (!slice) {
314
- console.error(`Error: Slice not found: ${name}`);
315
- console.error('Available slices:');
316
- for (const s of slices) {
317
- console.error(` - ${s.name}`);
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
- process.exit(1);
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 nameLower = commandName.toLowerCase();
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 nameLower = eventName.toLowerCase();
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 nameLower = readModelName.toLowerCase();
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.1",
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/your-username/event-modeler"
31
+ "url": "https://github.com/theoema/event-modeler",
32
+ "directory": "cli"
31
33
  },
32
34
  "homepage": "https://www.eventmodeling.app",
33
35
  "engines": {