eventmodeler 0.3.7 → 0.3.8

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.
@@ -868,6 +868,7 @@ function applyEvent(model, event) {
868
868
  height: event.height,
869
869
  givenEvents: [],
870
870
  whenCommand: null,
871
+ whenEvents: [],
871
872
  then: { type: 'events', expectedEvents: [] },
872
873
  });
873
874
  break;
@@ -904,6 +905,12 @@ function applyEvent(model, event) {
904
905
  scenario.whenCommand = event.whenCommand;
905
906
  break;
906
907
  }
908
+ case 'ScenarioWhenEventsUpdated': {
909
+ const scenario = model.scenarios.get(event.scenarioId);
910
+ if (scenario)
911
+ scenario.whenEvents = event.whenEvents;
912
+ break;
913
+ }
907
914
  case 'ScenarioThenUpdated': {
908
915
  const scenario = model.scenarios.get(event.scenarioId);
909
916
  if (scenario)
@@ -42,10 +42,11 @@ function parseXmlInput(input) {
42
42
  }
43
43
  }
44
44
  }
45
- // Parse when command
45
+ // Parse when (can contain command or events)
46
46
  const whenContent = getContent(input, 'when');
47
47
  let when;
48
48
  if (whenContent) {
49
+ // Check for command
49
50
  const commandMatch = whenContent.match(/<command([^>]*?)(?:\/>|>([\s\S]*?)<\/command>)/);
50
51
  if (commandMatch) {
51
52
  const commandName = getAttr(commandMatch[1], 'name');
@@ -57,9 +58,29 @@ function parseXmlInput(input) {
57
58
  fieldValues[fieldMatch[1]] = parseFieldValue(fieldMatch[2]);
58
59
  }
59
60
  }
60
- when = { command: commandName, fieldValues: Object.keys(fieldValues).length > 0 ? fieldValues : undefined };
61
+ when = { command: commandName, commandFieldValues: Object.keys(fieldValues).length > 0 ? fieldValues : undefined };
61
62
  }
62
63
  }
64
+ // Check for events in when
65
+ const eventMatches = whenContent.matchAll(/<event([^>]*?)(?:\/>|>([\s\S]*?)<\/event>)/g);
66
+ const whenEvents = [];
67
+ for (const match of eventMatches) {
68
+ const eventName = getAttr(match[1], 'name');
69
+ if (eventName) {
70
+ const fieldValues = {};
71
+ if (match[2]) {
72
+ const fieldMatches = match[2].matchAll(/<field name="([^"]*)"[^>]*>([^<]*)<\/field>/g);
73
+ for (const fieldMatch of fieldMatches) {
74
+ fieldValues[fieldMatch[1]] = parseFieldValue(fieldMatch[2]);
75
+ }
76
+ }
77
+ whenEvents.push({ event: eventName, fieldValues: Object.keys(fieldValues).length > 0 ? fieldValues : undefined });
78
+ }
79
+ }
80
+ if (whenEvents.length > 0) {
81
+ when = when || {};
82
+ when.events = whenEvents;
83
+ }
63
84
  }
64
85
  // Parse then
65
86
  const thenMatch = input.match(/<then([^>]*)(?:\/>|>([\s\S]*?)<\/then>)/);
@@ -92,6 +113,20 @@ function parseXmlInput(input) {
92
113
  }
93
114
  }
94
115
  }
116
+ else if (thenType === 'command' && thenMatch[2]) {
117
+ const commandMatch = thenMatch[2].match(/<command([^>]*?)(?:\/>|>([\s\S]*?)<\/command>)/);
118
+ if (commandMatch) {
119
+ then.command = getAttr(commandMatch[1], 'name');
120
+ const fieldValues = {};
121
+ if (commandMatch[2]) {
122
+ const fieldMatches = commandMatch[2].matchAll(/<field name="([^"]*)"[^>]*>([^<]*)<\/field>/g);
123
+ for (const fieldMatch of fieldMatches) {
124
+ fieldValues[fieldMatch[1]] = parseFieldValue(fieldMatch[2]);
125
+ }
126
+ }
127
+ then.commandFieldValues = Object.keys(fieldValues).length > 0 ? fieldValues : undefined;
128
+ }
129
+ }
95
130
  else if (thenType === 'readModelAssertion' && thenMatch[2]) {
96
131
  const rmMatch = thenMatch[2].match(/<read-model([^>]*?)(?:\/>|>([\s\S]*?)<\/read-model>)/);
97
132
  if (rmMatch) {
@@ -159,13 +194,24 @@ export function addScenario(model, filePath, sliceName, input) {
159
194
  }
160
195
  // Resolve command reference in when
161
196
  let whenCommand = null;
162
- if (scenarioInput.when) {
197
+ if (scenarioInput.when?.command) {
163
198
  const command = findElementOrExit(model.commands, scenarioInput.when.command, 'command');
164
199
  whenCommand = {
165
200
  commandStickyId: command.id,
166
- fieldValues: scenarioInput.when.fieldValues,
201
+ fieldValues: scenarioInput.when.commandFieldValues,
167
202
  };
168
203
  }
204
+ // Resolve event references in when
205
+ const whenEvents = [];
206
+ if (scenarioInput.when?.events) {
207
+ for (const e of scenarioInput.when.events) {
208
+ const event = findElementOrExit(excludeLinkedCopies(model.events), e.event, 'event');
209
+ whenEvents.push({
210
+ eventStickyId: event.id,
211
+ fieldValues: e.fieldValues,
212
+ });
213
+ }
214
+ }
169
215
  // Resolve then clause
170
216
  const then = { type: scenarioInput.then.type };
171
217
  if (scenarioInput.then.type === 'error') {
@@ -184,6 +230,17 @@ export function addScenario(model, filePath, sliceName, input) {
184
230
  }
185
231
  }
186
232
  }
233
+ else if (scenarioInput.then.type === 'command') {
234
+ if (!scenarioInput.then.command) {
235
+ console.error('Error: command then type requires a command name');
236
+ process.exit(1);
237
+ }
238
+ const command = findElementOrExit(model.commands, scenarioInput.then.command, 'command');
239
+ then.expectedCommand = {
240
+ commandStickyId: command.id,
241
+ fieldValues: scenarioInput.then.commandFieldValues,
242
+ };
243
+ }
187
244
  else if (scenarioInput.then.type === 'readModelAssertion') {
188
245
  if (!scenarioInput.then.readModel) {
189
246
  console.error('Error: readModelAssertion requires a readModel name');
@@ -266,6 +323,15 @@ export function addScenario(model, filePath, sliceName, input) {
266
323
  timestamp: Date.now(),
267
324
  });
268
325
  }
326
+ // Append when events update if provided
327
+ if (whenEvents.length > 0) {
328
+ appendEvent(filePath, {
329
+ type: 'ScenarioWhenEventsUpdated',
330
+ scenarioId,
331
+ whenEvents,
332
+ timestamp: Date.now(),
333
+ });
334
+ }
269
335
  // Append then update
270
336
  appendEvent(filePath, {
271
337
  type: 'ScenarioThenUpdated',
@@ -249,6 +249,18 @@ function formatScenarioThen(model, then) {
249
249
  }),
250
250
  };
251
251
  }
252
+ if (then.type === 'command' && then.expectedCommand) {
253
+ const command = model.commands.get(then.expectedCommand.commandStickyId);
254
+ return {
255
+ type: 'command',
256
+ expectedCommand: {
257
+ commandId: then.expectedCommand.commandStickyId,
258
+ commandName: command?.name ?? 'UnknownCommand',
259
+ commandSchema: { fields: command?.fields.map(fieldToJson) ?? [] },
260
+ fieldValues: then.expectedCommand.fieldValues ?? {},
261
+ },
262
+ };
263
+ }
252
264
  if (then.type === 'readModelAssertion' && then.readModelAssertion) {
253
265
  const rm = model.readModels.get(then.readModelAssertion.readModelStickyId);
254
266
  return {
@@ -277,13 +289,24 @@ function formatScenarios(model, scenarios) {
277
289
  fieldValues: ref.fieldValues ?? {},
278
290
  };
279
291
  }),
280
- when: scenario.whenCommand
292
+ whenCommand: scenario.whenCommand
281
293
  ? {
282
294
  commandId: scenario.whenCommand.commandStickyId,
283
295
  commandName: model.commands.get(scenario.whenCommand.commandStickyId)?.name ?? 'UnknownCommand',
284
296
  fieldValues: scenario.whenCommand.fieldValues ?? {},
285
297
  }
286
298
  : null,
299
+ whenEvents: scenario.whenEvents.map(ref => {
300
+ const event = model.events.get(ref.eventStickyId);
301
+ const originSlice = findSliceForNode(model, ref.eventStickyId);
302
+ return {
303
+ eventId: ref.eventStickyId,
304
+ eventName: event?.name ?? 'UnknownEvent',
305
+ originSlice: originSlice?.name ?? null,
306
+ eventSchema: { fields: event?.fields.map(fieldToJson) ?? [] },
307
+ fieldValues: ref.fieldValues ?? {},
308
+ };
309
+ }),
287
310
  then: formatScenarioThen(model, scenario.then),
288
311
  }));
289
312
  }
package/dist/types.d.ts CHANGED
@@ -22,13 +22,14 @@ export interface ReadModelAssertion {
22
22
  givenEvents: EventReference[];
23
23
  expectedFieldValues: Record<string, unknown>;
24
24
  }
25
- export type ScenarioThenType = 'error' | 'events' | 'readModelAssertion';
25
+ export type ScenarioThenType = 'error' | 'events' | 'readModelAssertion' | 'command';
26
26
  export interface ScenarioThen {
27
27
  type: ScenarioThenType;
28
28
  errorMessage?: string;
29
29
  errorType?: string;
30
30
  expectedEvents?: EventReference[];
31
31
  readModelAssertion?: ReadModelAssertion;
32
+ expectedCommand?: CommandReference;
32
33
  }
33
34
  export interface FieldMapping {
34
35
  sourceFieldId: string;
@@ -162,6 +163,7 @@ export interface Scenario {
162
163
  height: number;
163
164
  givenEvents: EventReference[];
164
165
  whenCommand: CommandReference | null;
166
+ whenEvents: EventReference[];
165
167
  then: ScenarioThen;
166
168
  }
167
169
  export type FlowType = 'CommandToEvent' | 'EventToReadModel' | 'ReadModelToScreen' | 'ReadModelToProcessor' | 'ScreenToCommand' | 'ProcessorToCommand';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eventmodeler",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
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": {