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.
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
|
}
|
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';
|