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
@@ -0,0 +1,83 @@
1
+ import type { EventModel, Field, FieldMapping, Slice } from '../types.js';
2
+ export declare const STATE_CHANGE_SLICE: {
3
+ width: number;
4
+ height: number;
5
+ screen: {
6
+ width: number;
7
+ height: number;
8
+ offsetX: number;
9
+ offsetY: number;
10
+ };
11
+ command: {
12
+ width: number;
13
+ height: number;
14
+ offsetX: number;
15
+ offsetY: number;
16
+ };
17
+ event: {
18
+ width: number;
19
+ height: number;
20
+ offsetX: number;
21
+ offsetY: number;
22
+ };
23
+ };
24
+ export declare const AUTOMATION_SLICE: {
25
+ width: number;
26
+ height: number;
27
+ processor: {
28
+ width: number;
29
+ height: number;
30
+ offsetX: number;
31
+ offsetY: number;
32
+ };
33
+ command: {
34
+ width: number;
35
+ height: number;
36
+ offsetX: number;
37
+ offsetY: number;
38
+ };
39
+ event: {
40
+ width: number;
41
+ height: number;
42
+ offsetX: number;
43
+ offsetY: number;
44
+ };
45
+ };
46
+ export declare const STATE_VIEW_SLICE: {
47
+ width: number;
48
+ height: number;
49
+ readModel: {
50
+ width: number;
51
+ height: number;
52
+ offsetX: number;
53
+ offsetY: number;
54
+ };
55
+ };
56
+ export declare const SLICE_GAP = 20;
57
+ export interface PositionReference {
58
+ after?: string;
59
+ before?: string;
60
+ }
61
+ export declare function findSliceByName(model: EventModel, name: string): Slice | undefined;
62
+ export declare function validateSliceNameUnique(model: EventModel, name: string): void;
63
+ export declare function calculateSlicePosition(model: EventModel, sliceWidth: number, ref: PositionReference): {
64
+ x: number;
65
+ y: number;
66
+ };
67
+ export declare function getSlicesToShift(model: EventModel, insertX: number, shiftAmount: number): Array<{
68
+ sliceId: string;
69
+ newX: number;
70
+ currentY: number;
71
+ }>;
72
+ export declare function inferFieldMappings(sourceFields: Field[], targetFields: Field[]): FieldMapping[];
73
+ export interface FieldInput {
74
+ name: string;
75
+ type: string;
76
+ isList?: boolean;
77
+ isGenerated?: boolean;
78
+ isOptional?: boolean;
79
+ isUserInput?: boolean;
80
+ subfields?: FieldInput[];
81
+ }
82
+ export declare function parseFieldsFromXml(xml: string): FieldInput[];
83
+ export declare function fieldInputToField(input: FieldInput): Field;
@@ -0,0 +1,135 @@
1
+ // Fixed dimensions from requirements
2
+ export const STATE_CHANGE_SLICE = {
3
+ width: 560,
4
+ height: 1000,
5
+ screen: { width: 180, height: 120, offsetX: 40, offsetY: 120 },
6
+ command: { width: 160, height: 100, offsetX: 180, offsetY: 460 },
7
+ event: { width: 160, height: 100, offsetX: 360, offsetY: 860 },
8
+ };
9
+ export const AUTOMATION_SLICE = {
10
+ width: 560,
11
+ height: 1000,
12
+ processor: { width: 120, height: 120, offsetX: 40, offsetY: 120 },
13
+ command: { width: 160, height: 100, offsetX: 180, offsetY: 460 },
14
+ event: { width: 160, height: 100, offsetX: 360, offsetY: 860 },
15
+ };
16
+ export const STATE_VIEW_SLICE = {
17
+ width: 380,
18
+ height: 1000,
19
+ readModel: { width: 160, height: 100, offsetX: 110, offsetY: 460 },
20
+ };
21
+ export const SLICE_GAP = 20;
22
+ export function findSliceByName(model, name) {
23
+ const nameLower = name.toLowerCase();
24
+ return [...model.slices.values()].find(s => s.name.toLowerCase() === nameLower || s.name.toLowerCase().includes(nameLower));
25
+ }
26
+ export function validateSliceNameUnique(model, name) {
27
+ const existing = findSliceByName(model, name);
28
+ if (existing && existing.name.toLowerCase() === name.toLowerCase()) {
29
+ throw new Error(`Slice "${name}" already exists`);
30
+ }
31
+ }
32
+ export function calculateSlicePosition(model, sliceWidth, ref) {
33
+ const slices = [...model.slices.values()];
34
+ if (slices.length === 0) {
35
+ return { x: 0, y: 0 };
36
+ }
37
+ if (ref.after) {
38
+ const refSlice = findSliceByName(model, ref.after);
39
+ if (!refSlice) {
40
+ const available = slices.map(s => s.name).join(', ');
41
+ throw new Error(`Slice "${ref.after}" not found. Available: ${available}`);
42
+ }
43
+ return {
44
+ x: refSlice.position.x + refSlice.size.width + SLICE_GAP,
45
+ y: refSlice.position.y
46
+ };
47
+ }
48
+ if (ref.before) {
49
+ const refSlice = findSliceByName(model, ref.before);
50
+ if (!refSlice) {
51
+ const available = slices.map(s => s.name).join(', ');
52
+ throw new Error(`Slice "${ref.before}" not found. Available: ${available}`);
53
+ }
54
+ return {
55
+ x: refSlice.position.x,
56
+ y: refSlice.position.y
57
+ };
58
+ }
59
+ // Default: append to end (right of rightmost slice)
60
+ const rightmost = slices.reduce((max, s) => Math.max(max, s.position.x + s.size.width), 0);
61
+ const avgY = slices.reduce((sum, s) => sum + s.position.y, 0) / slices.length;
62
+ return {
63
+ x: rightmost + SLICE_GAP,
64
+ y: avgY
65
+ };
66
+ }
67
+ export function getSlicesToShift(model, insertX, shiftAmount) {
68
+ return [...model.slices.values()]
69
+ .filter(s => s.position.x >= insertX)
70
+ .map(s => ({
71
+ sliceId: s.id,
72
+ newX: s.position.x + shiftAmount,
73
+ currentY: s.position.y
74
+ }));
75
+ }
76
+ export function inferFieldMappings(sourceFields, targetFields) {
77
+ const mappings = [];
78
+ for (const targetField of targetFields) {
79
+ // Skip generated fields - they have no source
80
+ if (targetField.isGenerated)
81
+ continue;
82
+ // Find source field with matching name (case-insensitive)
83
+ const sourceField = sourceFields.find(sf => sf.name.toLowerCase() === targetField.name.toLowerCase());
84
+ if (sourceField) {
85
+ mappings.push({
86
+ sourceFieldId: sourceField.id,
87
+ targetFieldId: targetField.id
88
+ });
89
+ }
90
+ }
91
+ return mappings;
92
+ }
93
+ function getAttr(attrs, name) {
94
+ const match = attrs.match(new RegExp(`${name}="([^"]*)"`));
95
+ return match ? match[1] : undefined;
96
+ }
97
+ export function parseFieldsFromXml(xml) {
98
+ const fields = [];
99
+ // Match top-level field elements only (not nested inside other fields)
100
+ const fieldMatches = xml.matchAll(/<field([^>]*?)(?:\/>|>([\s\S]*?)<\/field>)/g);
101
+ for (const match of fieldMatches) {
102
+ const attrs = match[1];
103
+ const content = match[2];
104
+ const name = getAttr(attrs, 'name');
105
+ const type = getAttr(attrs, 'type');
106
+ if (!name || !type)
107
+ continue;
108
+ const field = {
109
+ name,
110
+ type,
111
+ isList: getAttr(attrs, 'list') === 'true',
112
+ isGenerated: getAttr(attrs, 'generated') === 'true',
113
+ isOptional: getAttr(attrs, 'optional') === 'true',
114
+ isUserInput: getAttr(attrs, 'user-input') === 'true',
115
+ };
116
+ // Parse nested subfields for Custom type
117
+ if (type === 'Custom' && content) {
118
+ field.subfields = parseFieldsFromXml(content);
119
+ }
120
+ fields.push(field);
121
+ }
122
+ return fields;
123
+ }
124
+ export function fieldInputToField(input) {
125
+ return {
126
+ id: crypto.randomUUID(),
127
+ name: input.name,
128
+ fieldType: input.type,
129
+ isList: input.isList ?? false,
130
+ isGenerated: input.isGenerated ?? false,
131
+ isOptional: input.isOptional,
132
+ isUserInput: input.isUserInput,
133
+ subfields: input.subfields?.map(fieldInputToField),
134
+ };
135
+ }
@@ -824,6 +824,157 @@ function applyEvent(model, event) {
824
824
  case 'ScreenDesignUpdated':
825
825
  // Screen designs are visual-only, not relevant for CLI
826
826
  break;
827
+ // Compound slice placement events - place entire slices atomically
828
+ case 'StateChangeSlicePlaced': {
829
+ // Create slice
830
+ model.slices.set(event.sliceId, {
831
+ id: event.sliceId,
832
+ name: event.sliceName,
833
+ status: 'created',
834
+ position: event.slicePosition,
835
+ size: event.sliceSize,
836
+ nodeIds: [event.screenId, event.commandId, event.eventId],
837
+ });
838
+ // Create screen
839
+ model.screens.set(event.screenId, {
840
+ id: event.screenId,
841
+ name: event.screenName,
842
+ fields: event.screenFields,
843
+ position: event.screenPosition,
844
+ width: event.screenSize.width,
845
+ height: event.screenSize.height,
846
+ });
847
+ // Create command
848
+ model.commands.set(event.commandId, {
849
+ id: event.commandId,
850
+ name: event.commandName,
851
+ fields: event.commandFields,
852
+ position: event.commandPosition,
853
+ width: event.commandSize.width,
854
+ height: event.commandSize.height,
855
+ });
856
+ // Create event
857
+ model.events.set(event.eventId, {
858
+ id: event.eventId,
859
+ name: event.eventName,
860
+ fields: event.eventFields,
861
+ position: event.eventPosition,
862
+ width: event.eventSize.width,
863
+ height: event.eventSize.height,
864
+ });
865
+ // Create screen->command flow
866
+ model.flows.set(event.screenToCommandFlowId, {
867
+ id: event.screenToCommandFlowId,
868
+ flowType: 'ScreenToCommand',
869
+ sourceId: event.screenId,
870
+ targetId: event.commandId,
871
+ fieldMappings: event.screenToCommandMappings,
872
+ });
873
+ // Create command->event flow
874
+ model.flows.set(event.commandToEventFlowId, {
875
+ id: event.commandToEventFlowId,
876
+ flowType: 'CommandToEvent',
877
+ sourceId: event.commandId,
878
+ targetId: event.eventId,
879
+ fieldMappings: event.commandToEventMappings,
880
+ });
881
+ break;
882
+ }
883
+ case 'AutomationSlicePlaced': {
884
+ // Create slice
885
+ model.slices.set(event.sliceId, {
886
+ id: event.sliceId,
887
+ name: event.sliceName,
888
+ status: 'created',
889
+ position: event.slicePosition,
890
+ size: event.sliceSize,
891
+ nodeIds: [event.processorId, event.commandId, event.eventId],
892
+ });
893
+ // Create processor
894
+ model.processors.set(event.processorId, {
895
+ id: event.processorId,
896
+ name: event.processorName,
897
+ fields: [],
898
+ position: event.processorPosition,
899
+ width: event.processorSize.width,
900
+ height: event.processorSize.height,
901
+ });
902
+ // Create command
903
+ model.commands.set(event.commandId, {
904
+ id: event.commandId,
905
+ name: event.commandName,
906
+ fields: event.commandFields,
907
+ position: event.commandPosition,
908
+ width: event.commandSize.width,
909
+ height: event.commandSize.height,
910
+ });
911
+ // Create event
912
+ model.events.set(event.eventId, {
913
+ id: event.eventId,
914
+ name: event.eventName,
915
+ fields: event.eventFields,
916
+ position: event.eventPosition,
917
+ width: event.eventSize.width,
918
+ height: event.eventSize.height,
919
+ });
920
+ // Create processor->command flow
921
+ model.flows.set(event.processorToCommandFlowId, {
922
+ id: event.processorToCommandFlowId,
923
+ flowType: 'ProcessorToCommand',
924
+ sourceId: event.processorId,
925
+ targetId: event.commandId,
926
+ fieldMappings: [],
927
+ });
928
+ // Create command->event flow
929
+ model.flows.set(event.commandToEventFlowId, {
930
+ id: event.commandToEventFlowId,
931
+ flowType: 'CommandToEvent',
932
+ sourceId: event.commandId,
933
+ targetId: event.eventId,
934
+ fieldMappings: event.commandToEventMappings,
935
+ });
936
+ break;
937
+ }
938
+ case 'StateViewSlicePlaced': {
939
+ // Create slice
940
+ model.slices.set(event.sliceId, {
941
+ id: event.sliceId,
942
+ name: event.sliceName,
943
+ status: 'created',
944
+ position: event.slicePosition,
945
+ size: event.sliceSize,
946
+ nodeIds: [event.readModelId],
947
+ });
948
+ // Create read model
949
+ model.readModels.set(event.readModelId, {
950
+ id: event.readModelId,
951
+ name: event.readModelName,
952
+ fields: event.readModelFields,
953
+ position: event.readModelPosition,
954
+ width: event.readModelSize.width,
955
+ height: event.readModelSize.height,
956
+ });
957
+ break;
958
+ }
959
+ }
960
+ }
961
+ // Merge propagated fields with existing fields:
962
+ // - If a field with the same name exists, update its subfields
963
+ // - If it doesn't exist, add as new field
964
+ function mergeFieldsInto(existingFields, propagatedFields) {
965
+ for (const propagatedField of propagatedFields) {
966
+ const existingIdx = existingFields.findIndex(f => f.name === propagatedField.name);
967
+ if (existingIdx !== -1) {
968
+ // Field exists - update its subfields
969
+ existingFields[existingIdx] = {
970
+ ...existingFields[existingIdx],
971
+ subfields: propagatedField.subfields,
972
+ };
973
+ }
974
+ else {
975
+ // New field - add it
976
+ existingFields.push(propagatedField);
977
+ }
827
978
  }
828
979
  }
829
980
  function handleFieldPropagation(model, event) {
@@ -831,52 +982,27 @@ function handleFieldPropagation(model, event) {
831
982
  // Determine target based on event type
832
983
  if ('eventStickyId' in event) {
833
984
  const evt = model.events.get(event.eventStickyId);
834
- if (evt) {
835
- for (const field of fields) {
836
- if (!evt.fields.some(f => f.name === field.name)) {
837
- evt.fields.push(field);
838
- }
839
- }
840
- }
985
+ if (evt)
986
+ mergeFieldsInto(evt.fields, fields);
841
987
  }
842
988
  else if ('commandStickyId' in event) {
843
989
  const cmd = model.commands.get(event.commandStickyId);
844
- if (cmd) {
845
- for (const field of fields) {
846
- if (!cmd.fields.some(f => f.name === field.name)) {
847
- cmd.fields.push(field);
848
- }
849
- }
850
- }
990
+ if (cmd)
991
+ mergeFieldsInto(cmd.fields, fields);
851
992
  }
852
993
  else if ('readModelStickyId' in event) {
853
994
  const rm = model.readModels.get(event.readModelStickyId);
854
- if (rm) {
855
- for (const field of fields) {
856
- if (!rm.fields.some(f => f.name === field.name)) {
857
- rm.fields.push(field);
858
- }
859
- }
860
- }
995
+ if (rm)
996
+ mergeFieldsInto(rm.fields, fields);
861
997
  }
862
998
  else if ('screenId' in event) {
863
999
  const scr = model.screens.get(event.screenId);
864
- if (scr) {
865
- for (const field of fields) {
866
- if (!scr.fields.some(f => f.name === field.name)) {
867
- scr.fields.push(field);
868
- }
869
- }
870
- }
1000
+ if (scr)
1001
+ mergeFieldsInto(scr.fields, fields);
871
1002
  }
872
1003
  else if ('processorId' in event) {
873
1004
  const proc = model.processors.get(event.processorId);
874
- if (proc) {
875
- for (const field of fields) {
876
- if (!proc.fields.some(f => f.name === field.name)) {
877
- proc.fields.push(field);
878
- }
879
- }
880
- }
1005
+ if (proc)
1006
+ mergeFieldsInto(proc.fields, fields);
881
1007
  }
882
1008
  }
@@ -1,5 +1,6 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import { appendEvent } from '../../lib/file-loader.js';
3
+ import { findElementOrExit } from '../../lib/element-lookup.js';
3
4
  const validFieldTypes = ['UUID', 'Boolean', 'Double', 'Decimal', 'Date', 'DateTime', 'Long', 'Int', 'String', 'Custom'];
4
5
  function parseJsonInput(input) {
5
6
  return JSON.parse(input);
@@ -103,17 +104,7 @@ export function addField(model, filePath, options, input) {
103
104
  }
104
105
  }
105
106
  function addFieldToCommand(model, filePath, commandName, fieldInput) {
106
- const nameLower = commandName.toLowerCase();
107
- const commands = [...model.commands.values()];
108
- const command = commands.find(c => c.name.toLowerCase() === nameLower || c.name.toLowerCase().includes(nameLower));
109
- if (!command) {
110
- console.error(`Error: Command not found: ${commandName}`);
111
- console.error('Available commands:');
112
- for (const c of commands) {
113
- console.error(` - ${c.name}`);
114
- }
115
- process.exit(1);
116
- }
107
+ const command = findElementOrExit(model.commands, commandName, 'command');
117
108
  // Check for duplicate field name
118
109
  if (command.fields.some(f => f.name.toLowerCase() === fieldInput.name.toLowerCase())) {
119
110
  console.error(`Error: Field "${fieldInput.name}" already exists on command "${command.name}"`);
@@ -129,17 +120,7 @@ function addFieldToCommand(model, filePath, commandName, fieldInput) {
129
120
  console.log(`Added field "${field.name}" to command "${command.name}"`);
130
121
  }
131
122
  function addFieldToEvent(model, filePath, eventName, fieldInput) {
132
- const nameLower = eventName.toLowerCase();
133
- const events = [...model.events.values()];
134
- const event = events.find(e => e.name.toLowerCase() === nameLower || e.name.toLowerCase().includes(nameLower));
135
- if (!event) {
136
- console.error(`Error: Event not found: ${eventName}`);
137
- console.error('Available events:');
138
- for (const e of events) {
139
- console.error(` - ${e.name}`);
140
- }
141
- process.exit(1);
142
- }
123
+ const event = findElementOrExit(model.events, eventName, 'event');
143
124
  // Check for duplicate field name
144
125
  if (event.fields.some(f => f.name.toLowerCase() === fieldInput.name.toLowerCase())) {
145
126
  console.error(`Error: Field "${fieldInput.name}" already exists on event "${event.name}"`);
@@ -155,17 +136,7 @@ function addFieldToEvent(model, filePath, eventName, fieldInput) {
155
136
  console.log(`Added field "${field.name}" to event "${event.name}"`);
156
137
  }
157
138
  function addFieldToReadModel(model, filePath, readModelName, fieldInput) {
158
- const nameLower = readModelName.toLowerCase();
159
- const readModels = [...model.readModels.values()];
160
- const readModel = readModels.find(rm => rm.name.toLowerCase() === nameLower || rm.name.toLowerCase().includes(nameLower));
161
- if (!readModel) {
162
- console.error(`Error: Read model not found: ${readModelName}`);
163
- console.error('Available read models:');
164
- for (const rm of readModels) {
165
- console.error(` - ${rm.name}`);
166
- }
167
- process.exit(1);
168
- }
139
+ const readModel = findElementOrExit(model.readModels, readModelName, 'read model');
169
140
  // Check for duplicate field name
170
141
  if (readModel.fields.some(f => f.name.toLowerCase() === fieldInput.name.toLowerCase())) {
171
142
  console.error(`Error: Field "${fieldInput.name}" already exists on read model "${readModel.name}"`);
@@ -1,5 +1,6 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import { appendEvent } from '../../lib/file-loader.js';
3
+ import { findElementOrExit } from '../../lib/element-lookup.js';
3
4
  function parseJsonInput(input) {
4
5
  return JSON.parse(input);
5
6
  }
@@ -144,27 +145,12 @@ export function addScenario(model, filePath, sliceName, input) {
144
145
  process.exit(1);
145
146
  }
146
147
  // Find slice by name
147
- const slices = [...model.slices.values()];
148
- const sliceNameLower = sliceName.toLowerCase();
149
- const slice = slices.find(s => s.name.toLowerCase() === sliceNameLower || s.name.toLowerCase().includes(sliceNameLower));
150
- if (!slice) {
151
- console.error(`Error: Slice not found: ${sliceName}`);
152
- console.error('Available slices:');
153
- for (const s of slices) {
154
- console.error(` - ${s.name}`);
155
- }
156
- process.exit(1);
157
- }
148
+ const slice = findElementOrExit(model.slices, sliceName, 'slice');
158
149
  // Resolve event references in given
159
150
  const givenEvents = [];
160
151
  if (scenarioInput.given) {
161
152
  for (const g of scenarioInput.given) {
162
- const event = findEventByName(model, g.event);
163
- if (!event) {
164
- console.error(`Error: Event not found in "given": ${g.event}`);
165
- listAvailableEvents(model);
166
- process.exit(1);
167
- }
153
+ const event = findElementOrExit(model.events, g.event, 'event');
168
154
  givenEvents.push({
169
155
  eventStickyId: event.id,
170
156
  fieldValues: g.fieldValues,
@@ -174,12 +160,7 @@ export function addScenario(model, filePath, sliceName, input) {
174
160
  // Resolve command reference in when
175
161
  let whenCommand = null;
176
162
  if (scenarioInput.when) {
177
- const command = findCommandByName(model, scenarioInput.when.command);
178
- if (!command) {
179
- console.error(`Error: Command not found in "when": ${scenarioInput.when.command}`);
180
- listAvailableCommands(model);
181
- process.exit(1);
182
- }
163
+ const command = findElementOrExit(model.commands, scenarioInput.when.command, 'command');
183
164
  whenCommand = {
184
165
  commandStickyId: command.id,
185
166
  fieldValues: scenarioInput.when.fieldValues,
@@ -195,12 +176,7 @@ export function addScenario(model, filePath, sliceName, input) {
195
176
  then.expectedEvents = [];
196
177
  if (scenarioInput.then.events) {
197
178
  for (const e of scenarioInput.then.events) {
198
- const event = findEventByName(model, e.event);
199
- if (!event) {
200
- console.error(`Error: Event not found in "then.events": ${e.event}`);
201
- listAvailableEvents(model);
202
- process.exit(1);
203
- }
179
+ const event = findElementOrExit(model.events, e.event, 'event');
204
180
  then.expectedEvents.push({
205
181
  eventStickyId: event.id,
206
182
  fieldValues: e.fieldValues,
@@ -213,21 +189,11 @@ export function addScenario(model, filePath, sliceName, input) {
213
189
  console.error('Error: readModelAssertion requires a readModel name');
214
190
  process.exit(1);
215
191
  }
216
- const readModel = findReadModelByName(model, scenarioInput.then.readModel);
217
- if (!readModel) {
218
- console.error(`Error: Read model not found: ${scenarioInput.then.readModel}`);
219
- listAvailableReadModels(model);
220
- process.exit(1);
221
- }
192
+ const readModel = findElementOrExit(model.readModels, scenarioInput.then.readModel, 'read model');
222
193
  const assertionGivenEvents = [];
223
194
  if (scenarioInput.then.givenEvents) {
224
195
  for (const g of scenarioInput.then.givenEvents) {
225
- const event = findEventByName(model, g.event);
226
- if (!event) {
227
- console.error(`Error: Event not found in "then.givenEvents": ${g.event}`);
228
- listAvailableEvents(model);
229
- process.exit(1);
230
- }
196
+ const event = findElementOrExit(model.events, g.event, 'event');
231
197
  assertionGivenEvents.push({
232
198
  eventStickyId: event.id,
233
199
  fieldValues: g.fieldValues,
@@ -304,36 +270,3 @@ export function addScenario(model, filePath, sliceName, input) {
304
270
  });
305
271
  console.log(`Added scenario "${scenarioInput.name}" to slice "${slice.name}"`);
306
272
  }
307
- function findEventByName(model, name) {
308
- const nameLower = name.toLowerCase();
309
- const events = [...model.events.values()];
310
- return events.find(e => e.name.toLowerCase() === nameLower || e.name.toLowerCase().includes(nameLower));
311
- }
312
- function findCommandByName(model, name) {
313
- const nameLower = name.toLowerCase();
314
- const commands = [...model.commands.values()];
315
- return commands.find(c => c.name.toLowerCase() === nameLower || c.name.toLowerCase().includes(nameLower));
316
- }
317
- function findReadModelByName(model, name) {
318
- const nameLower = name.toLowerCase();
319
- const readModels = [...model.readModels.values()];
320
- return readModels.find(rm => rm.name.toLowerCase() === nameLower || rm.name.toLowerCase().includes(nameLower));
321
- }
322
- function listAvailableEvents(model) {
323
- console.error('Available events:');
324
- for (const e of model.events.values()) {
325
- console.error(` - ${e.name}`);
326
- }
327
- }
328
- function listAvailableCommands(model) {
329
- console.error('Available commands:');
330
- for (const c of model.commands.values()) {
331
- console.error(` - ${c.name}`);
332
- }
333
- }
334
- function listAvailableReadModels(model) {
335
- console.error('Available read models:');
336
- for (const rm of model.readModels.values()) {
337
- console.error(` - ${rm.name}`);
338
- }
339
- }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function createAutomationSlice(model: EventModel, filePath: string, xmlInput: string): void;