eventmodeler 0.3.7 → 0.3.9
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/dist/projection.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
-
|
|
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
|
}
|
|
@@ -330,6 +330,18 @@ function formatSliceXml(model, slice) {
|
|
|
330
330
|
: ` <command type="${escapeXml(name)}"/>\n`;
|
|
331
331
|
xml += ' </when>\n';
|
|
332
332
|
}
|
|
333
|
+
else if (scenario.whenEvents && scenario.whenEvents.length > 0) {
|
|
334
|
+
xml += ' <when>\n';
|
|
335
|
+
for (const whenEvent of scenario.whenEvents) {
|
|
336
|
+
const evt = model.events.get(whenEvent.eventStickyId);
|
|
337
|
+
const name = evt?.name ?? 'UnknownEvent';
|
|
338
|
+
const values = formatFieldValues(whenEvent.fieldValues);
|
|
339
|
+
xml += values
|
|
340
|
+
? ` <event type="${escapeXml(name)}">${escapeXmlText(values)}</event>\n`
|
|
341
|
+
: ` <event type="${escapeXml(name)}"/>\n`;
|
|
342
|
+
}
|
|
343
|
+
xml += ' </when>\n';
|
|
344
|
+
}
|
|
333
345
|
xml += ' <then>\n';
|
|
334
346
|
if (scenario.then.type === 'error') {
|
|
335
347
|
xml += ` <error`;
|
|
@@ -347,6 +359,14 @@ function formatSliceXml(model, slice) {
|
|
|
347
359
|
: ` <event type="${escapeXml(name)}"/>\n`;
|
|
348
360
|
}
|
|
349
361
|
}
|
|
362
|
+
else if (scenario.then.type === 'command' && scenario.then.expectedCommand) {
|
|
363
|
+
const cmd = model.commands.get(scenario.then.expectedCommand.commandStickyId);
|
|
364
|
+
const name = cmd?.name ?? 'UnknownCommand';
|
|
365
|
+
const values = formatFieldValues(scenario.then.expectedCommand.fieldValues);
|
|
366
|
+
xml += values
|
|
367
|
+
? ` <command type="${escapeXml(name)}">${escapeXmlText(values)}</command>\n`
|
|
368
|
+
: ` <command type="${escapeXml(name)}"/>\n`;
|
|
369
|
+
}
|
|
350
370
|
else if (scenario.then.type === 'readModelAssertion' && scenario.then.readModelAssertion) {
|
|
351
371
|
const assertion = scenario.then.readModelAssertion;
|
|
352
372
|
const rm = model.readModels.get(assertion.readModelStickyId);
|
|
@@ -571,6 +591,17 @@ function formatSliceJson(model, slice) {
|
|
|
571
591
|
...(scenario.whenCommand.fieldValues && Object.keys(scenario.whenCommand.fieldValues).length > 0 ? { fieldValues: scenario.whenCommand.fieldValues } : {})
|
|
572
592
|
};
|
|
573
593
|
}
|
|
594
|
+
else if (scenario.whenEvents && scenario.whenEvents.length > 0) {
|
|
595
|
+
scenarioObj.when = {
|
|
596
|
+
events: scenario.whenEvents.map(whenEvent => {
|
|
597
|
+
const evt = model.events.get(whenEvent.eventStickyId);
|
|
598
|
+
return {
|
|
599
|
+
eventType: evt?.name ?? 'UnknownEvent',
|
|
600
|
+
...(whenEvent.fieldValues && Object.keys(whenEvent.fieldValues).length > 0 ? { fieldValues: whenEvent.fieldValues } : {})
|
|
601
|
+
};
|
|
602
|
+
})
|
|
603
|
+
};
|
|
604
|
+
}
|
|
574
605
|
if (scenario.then.type === 'error') {
|
|
575
606
|
scenarioObj.then = {
|
|
576
607
|
type: 'error',
|
|
@@ -590,6 +621,14 @@ function formatSliceJson(model, slice) {
|
|
|
590
621
|
})
|
|
591
622
|
};
|
|
592
623
|
}
|
|
624
|
+
else if (scenario.then.type === 'command' && scenario.then.expectedCommand) {
|
|
625
|
+
const cmd = model.commands.get(scenario.then.expectedCommand.commandStickyId);
|
|
626
|
+
scenarioObj.then = {
|
|
627
|
+
type: 'command',
|
|
628
|
+
commandType: cmd?.name ?? 'UnknownCommand',
|
|
629
|
+
...(scenario.then.expectedCommand.fieldValues && Object.keys(scenario.then.expectedCommand.fieldValues).length > 0 ? { fieldValues: scenario.then.expectedCommand.fieldValues } : {})
|
|
630
|
+
};
|
|
631
|
+
}
|
|
593
632
|
else if (scenario.then.type === 'readModelAssertion' && scenario.then.readModelAssertion) {
|
|
594
633
|
const assertion = scenario.then.readModelAssertion;
|
|
595
634
|
const rm = model.readModels.get(assertion.readModelStickyId);
|
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';
|