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.
- package/README.md +175 -0
- package/dist/index.js +114 -19
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +26 -0
- package/dist/lib/element-lookup.d.ts +47 -0
- package/dist/lib/element-lookup.js +86 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +11 -0
- package/dist/lib/slice-utils.d.ts +83 -0
- package/dist/lib/slice-utils.js +135 -0
- package/dist/projection.js +161 -35
- package/dist/slices/add-field/index.js +4 -33
- package/dist/slices/add-scenario/index.js +7 -74
- package/dist/slices/create-automation-slice/index.d.ts +2 -0
- package/dist/slices/create-automation-slice/index.js +217 -0
- package/dist/slices/create-flow/index.d.ts +2 -0
- package/dist/slices/create-flow/index.js +177 -0
- package/dist/slices/create-state-change-slice/index.d.ts +2 -0
- package/dist/slices/create-state-change-slice/index.js +239 -0
- package/dist/slices/create-state-view-slice/index.d.ts +2 -0
- package/dist/slices/create-state-view-slice/index.js +120 -0
- package/dist/slices/list-chapters/index.d.ts +2 -1
- package/dist/slices/list-chapters/index.js +11 -12
- package/dist/slices/list-commands/index.d.ts +2 -1
- package/dist/slices/list-commands/index.js +10 -11
- package/dist/slices/list-events/index.d.ts +2 -1
- package/dist/slices/list-events/index.js +36 -15
- package/dist/slices/list-slices/index.d.ts +2 -1
- package/dist/slices/list-slices/index.js +10 -11
- package/dist/slices/mark-slice-status/index.js +2 -11
- package/dist/slices/remove-field/index.js +4 -33
- package/dist/slices/remove-scenario/index.js +45 -11
- package/dist/slices/search/index.d.ts +2 -1
- package/dist/slices/search/index.js +148 -21
- package/dist/slices/show-actor/index.d.ts +3 -2
- package/dist/slices/show-actor/index.js +46 -20
- package/dist/slices/show-aggregate-completeness/index.d.ts +3 -2
- package/dist/slices/show-aggregate-completeness/index.js +62 -20
- package/dist/slices/show-chapter/index.d.ts +2 -1
- package/dist/slices/show-chapter/index.js +14 -22
- package/dist/slices/show-command/index.d.ts +2 -1
- package/dist/slices/show-command/index.js +54 -19
- package/dist/slices/show-completeness/index.d.ts +3 -1
- package/dist/slices/show-completeness/index.js +313 -31
- package/dist/slices/show-event/index.d.ts +2 -1
- package/dist/slices/show-event/index.js +44 -20
- package/dist/slices/show-model-summary/index.d.ts +2 -1
- package/dist/slices/show-model-summary/index.js +18 -9
- package/dist/slices/show-slice/index.d.ts +2 -1
- package/dist/slices/show-slice/index.js +174 -24
- package/dist/slices/update-field/index.js +4 -33
- 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
|
+
}
|
package/dist/projection.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
}
|