eventmodeler 0.4.4 → 0.4.5

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.
@@ -1,4 +1,4 @@
1
- import type { OutputFormat } from './format.js';
1
+ type OutputFormat = 'xml' | 'json';
2
2
  export interface AuthTokens {
3
3
  accessToken: string;
4
4
  refreshToken: string;
@@ -27,3 +27,4 @@ export declare function setBackendUrl(url: string): void;
27
27
  export declare function getKeycloakUrl(): string;
28
28
  export declare function setKeycloakUrl(url: string): void;
29
29
  export declare function getDefaultFormat(): OutputFormat;
30
+ export {};
@@ -2,9 +2,6 @@ export type ProjectConfig = {
2
2
  type: 'cloud';
3
3
  modelId: string;
4
4
  modelName: string;
5
- } | {
6
- type: 'local';
7
- file: string;
8
5
  };
9
6
  /**
10
7
  * Find .eventmodeler.json by walking up from cwd to find the repo root.
@@ -37,11 +37,6 @@ export function loadProjectConfig(startDir) {
37
37
  if (config.type === 'cloud' && config.modelId && config.modelName) {
38
38
  return { type: 'cloud', modelId: config.modelId, modelName: config.modelName };
39
39
  }
40
- if (config.type === 'local' && config.file) {
41
- // Resolve file path relative to the config file location
42
- const configDir = path.dirname(configPath);
43
- return { type: 'local', file: path.resolve(configDir, config.file) };
44
- }
45
40
  return null;
46
41
  }
47
42
  catch {
@@ -53,9 +48,7 @@ export function loadProjectConfig(startDir) {
53
48
  */
54
49
  export function saveProjectConfig(config, targetDir = process.cwd()) {
55
50
  const configPath = path.join(targetDir, PROJECT_CONFIG_FILENAME);
56
- const content = config.type === 'cloud'
57
- ? { type: 'cloud', modelId: config.modelId, modelName: config.modelName }
58
- : { type: 'local', file: config.file };
51
+ const content = { type: 'cloud', modelId: config.modelId, modelName: config.modelName };
59
52
  fs.writeFileSync(configPath, JSON.stringify(content, null, 2) + '\n');
60
53
  return configPath;
61
54
  }
@@ -1,81 +1,3 @@
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
- readModel: {
28
- width: number;
29
- height: number;
30
- offsetX: number;
31
- offsetY: number;
32
- };
33
- processor: {
34
- width: number;
35
- height: number;
36
- offsetX: number;
37
- offsetY: number;
38
- };
39
- command: {
40
- width: number;
41
- height: number;
42
- offsetX: number;
43
- offsetY: number;
44
- };
45
- event: {
46
- width: number;
47
- height: number;
48
- offsetX: number;
49
- offsetY: number;
50
- };
51
- };
52
- export declare const STATE_VIEW_SLICE: {
53
- width: number;
54
- height: number;
55
- readModel: {
56
- width: number;
57
- height: number;
58
- offsetX: number;
59
- offsetY: number;
60
- };
61
- };
62
- export declare const SLICE_GAP = 20;
63
- export interface PositionReference {
64
- after?: string;
65
- before?: string;
66
- }
67
- export declare function findSliceByName(model: EventModel, name: string): Slice | undefined;
68
- export declare function validateSliceNameUnique(model: EventModel, name: string): void;
69
- export declare function calculateSlicePosition(model: EventModel, sliceWidth: number, ref: PositionReference): {
70
- x: number;
71
- y: number;
72
- };
73
- export declare function getSlicesToShift(model: EventModel, insertX: number, shiftAmount: number): Array<{
74
- sliceId: string;
75
- newX: number;
76
- currentY: number;
77
- }>;
78
- export declare function inferFieldMappings(sourceFields: Field[], targetFields: Field[]): FieldMapping[];
79
1
  export interface FieldInput {
80
2
  name: string;
81
3
  type: string;
@@ -86,7 +8,6 @@ export interface FieldInput {
86
8
  subfields?: FieldInput[];
87
9
  }
88
10
  export declare function parseFieldsFromXml(xml: string): FieldInput[];
89
- export declare function fieldInputToField(input: FieldInput): Field;
90
11
  export interface StateChangeSliceInput {
91
12
  sliceName: string;
92
13
  after?: string;
@@ -1,96 +1,4 @@
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: 880,
11
- height: 1000,
12
- readModel: { width: 160, height: 100, offsetX: 40, offsetY: 460 },
13
- processor: { width: 120, height: 120, offsetX: 280, offsetY: 120 },
14
- command: { width: 160, height: 100, offsetX: 440, offsetY: 460 },
15
- event: { width: 160, height: 100, offsetX: 680, offsetY: 860 },
16
- };
17
- export const STATE_VIEW_SLICE = {
18
- width: 380,
19
- height: 1000,
20
- readModel: { width: 160, height: 100, offsetX: 110, offsetY: 460 },
21
- };
22
- export const SLICE_GAP = 20;
23
- export function findSliceByName(model, name) {
24
- const nameLower = name.toLowerCase();
25
- return [...model.slices.values()].find(s => s.name.toLowerCase() === nameLower || s.name.toLowerCase().includes(nameLower));
26
- }
27
- export function validateSliceNameUnique(model, name) {
28
- const existing = findSliceByName(model, name);
29
- if (existing && existing.name.toLowerCase() === name.toLowerCase()) {
30
- throw new Error(`Slice "${name}" already exists`);
31
- }
32
- }
33
- export function calculateSlicePosition(model, sliceWidth, ref) {
34
- const slices = [...model.slices.values()];
35
- if (slices.length === 0) {
36
- return { x: 0, y: 0 };
37
- }
38
- if (ref.after) {
39
- const refSlice = findSliceByName(model, ref.after);
40
- if (!refSlice) {
41
- const available = slices.map(s => s.name).join(', ');
42
- throw new Error(`Slice "${ref.after}" not found. Available: ${available}`);
43
- }
44
- return {
45
- x: refSlice.position.x + refSlice.size.width + SLICE_GAP,
46
- y: refSlice.position.y
47
- };
48
- }
49
- if (ref.before) {
50
- const refSlice = findSliceByName(model, ref.before);
51
- if (!refSlice) {
52
- const available = slices.map(s => s.name).join(', ');
53
- throw new Error(`Slice "${ref.before}" not found. Available: ${available}`);
54
- }
55
- return {
56
- x: refSlice.position.x,
57
- y: refSlice.position.y
58
- };
59
- }
60
- // Default: append to end (right of rightmost slice)
61
- const rightmost = slices.reduce((max, s) => Math.max(max, s.position.x + s.size.width), 0);
62
- const avgY = slices.reduce((sum, s) => sum + s.position.y, 0) / slices.length;
63
- return {
64
- x: rightmost + SLICE_GAP,
65
- y: avgY
66
- };
67
- }
68
- export function getSlicesToShift(model, insertX, shiftAmount) {
69
- return [...model.slices.values()]
70
- .filter(s => s.position.x >= insertX)
71
- .map(s => ({
72
- sliceId: s.id,
73
- newX: s.position.x + shiftAmount,
74
- currentY: s.position.y
75
- }));
76
- }
77
- export function inferFieldMappings(sourceFields, targetFields) {
78
- const mappings = [];
79
- for (const targetField of targetFields) {
80
- // Skip generated fields - they have no source
81
- if (targetField.isGenerated)
82
- continue;
83
- // Find source field with matching name (case-insensitive)
84
- const sourceField = sourceFields.find(sf => sf.name.toLowerCase() === targetField.name.toLowerCase());
85
- if (sourceField) {
86
- mappings.push({
87
- sourceFieldId: sourceField.id,
88
- targetFieldId: targetField.id
89
- });
90
- }
91
- }
92
- return mappings;
93
- }
1
+ import { XMLParser } from 'fast-xml-parser';
94
2
  const VALID_FIELD_TYPES = ['UUID', 'Boolean', 'Double', 'Decimal', 'Date', 'DateTime', 'Long', 'Int', 'String', 'Custom'];
95
3
  function validateFieldType(type, fieldName) {
96
4
  if (!VALID_FIELD_TYPES.includes(type)) {
@@ -102,189 +10,131 @@ function validateFieldType(type, fieldName) {
102
10
  throw new Error(`Invalid field type "${type}" for field "${fieldName}". Valid types: ${VALID_FIELD_TYPES.join(', ')}${hint}`);
103
11
  }
104
12
  }
105
- function getAttr(attrs, name) {
106
- const match = attrs.match(new RegExp(`${name}="([^"]*)"`));
107
- return match ? match[1] : undefined;
13
+ const xmlParser = new XMLParser({
14
+ ignoreAttributes: false,
15
+ attributeNamePrefix: '@_',
16
+ allowBooleanAttributes: false,
17
+ parseAttributeValue: false,
18
+ isArray: (name) => name === 'field',
19
+ });
20
+ function asArray(value) {
21
+ if (value === undefined || value === null)
22
+ return [];
23
+ return Array.isArray(value) ? value : [value];
108
24
  }
109
- export function parseFieldsFromXml(xml) {
25
+ function parseFieldNodes(nodes) {
110
26
  const fields = [];
111
- // Match top-level field elements only (not nested inside other fields)
112
- const fieldMatches = xml.matchAll(/<field([^>]*?)(?:\/>|>([\s\S]*?)<\/field>)/g);
113
- for (const match of fieldMatches) {
114
- const attrs = match[1];
115
- const content = match[2];
116
- const name = getAttr(attrs, 'name');
117
- const type = getAttr(attrs, 'type');
27
+ for (const node of nodes) {
28
+ const n = node;
29
+ const name = n['@_name'];
30
+ const type = n['@_type'];
118
31
  if (!name || !type)
119
32
  continue;
120
- // Validate the field type
121
33
  validateFieldType(type, name);
122
34
  const field = {
123
35
  name,
124
36
  type,
125
- isList: getAttr(attrs, 'isList') === 'true',
126
- isGenerated: getAttr(attrs, 'isGenerated') === 'true',
127
- isOptional: getAttr(attrs, 'isOptional') === 'true',
128
- isUserInput: getAttr(attrs, 'isUserInput') === 'true',
37
+ isList: n['@_isList'] === 'true',
38
+ isGenerated: n['@_isGenerated'] === 'true',
39
+ isOptional: n['@_isOptional'] === 'true',
40
+ isUserInput: n['@_isUserInput'] === 'true',
129
41
  };
130
- // Parse nested subfields for Custom type
131
- if (type === 'Custom' && content) {
132
- field.subfields = parseFieldsFromXml(content);
42
+ if (type === 'Custom') {
43
+ const childFields = asArray(n['field']);
44
+ if (childFields.length > 0) {
45
+ field.subfields = parseFieldNodes(childFields);
46
+ }
133
47
  }
134
48
  fields.push(field);
135
49
  }
136
50
  return fields;
137
51
  }
138
- export function fieldInputToField(input) {
139
- return {
140
- id: crypto.randomUUID(),
141
- name: input.name,
142
- fieldType: input.type,
143
- isList: input.isList ?? false,
144
- isGenerated: input.isGenerated ?? false,
145
- isOptional: input.isOptional,
146
- isUserInput: input.isUserInput,
147
- subfields: input.subfields?.map(fieldInputToField),
148
- };
52
+ export function parseFieldsFromXml(xml) {
53
+ const wrapper = `<root>${xml}</root>`;
54
+ const parsed = xmlParser.parse(wrapper);
55
+ const root = parsed['root'];
56
+ if (!root)
57
+ return [];
58
+ return parseFieldNodes(asArray(root['field']));
149
59
  }
150
- export function parseStateChangeSliceXml(xml) {
151
- // Parse <state-change-slice name="..." after="..." before="...">
152
- const sliceMatch = xml.match(/<state-change-slice([^>]*)>/);
153
- if (!sliceMatch) {
154
- throw new Error('Invalid XML: missing <state-change-slice> tag');
60
+ function requireAttr(node, attr, tagName) {
61
+ const value = node[`@_${attr}`];
62
+ if (!value) {
63
+ throw new Error(`Invalid XML: ${tagName} must have a ${attr} attribute`);
155
64
  }
156
- const sliceName = getAttr(sliceMatch[1], 'name');
157
- if (!sliceName) {
158
- throw new Error('Invalid XML: state-change-slice must have a name attribute');
159
- }
160
- const after = getAttr(sliceMatch[1], 'after');
161
- const before = getAttr(sliceMatch[1], 'before');
162
- // Parse screen
163
- const screenMatch = xml.match(/<screen([^>]*)>([\s\S]*?)<\/screen>/);
164
- if (!screenMatch) {
165
- throw new Error('Invalid XML: missing <screen> element');
166
- }
167
- const screenName = getAttr(screenMatch[1], 'name');
168
- if (!screenName) {
169
- throw new Error('Invalid XML: screen must have a name attribute');
170
- }
171
- const screenFields = parseFieldsFromXml(screenMatch[2]);
172
- // Parse command
173
- const commandMatch = xml.match(/<command([^>]*)>([\s\S]*?)<\/command>/);
174
- if (!commandMatch) {
175
- throw new Error('Invalid XML: missing <command> element');
176
- }
177
- const commandName = getAttr(commandMatch[1], 'name');
178
- if (!commandName) {
179
- throw new Error('Invalid XML: command must have a name attribute');
180
- }
181
- const commandFields = parseFieldsFromXml(commandMatch[2]);
182
- // Parse event
183
- const eventMatch = xml.match(/<event([^>]*)>([\s\S]*?)<\/event>/);
184
- if (!eventMatch) {
185
- throw new Error('Invalid XML: missing <event> element');
65
+ return value;
66
+ }
67
+ function optAttr(node, attr) {
68
+ return node[`@_${attr}`];
69
+ }
70
+ function getElement(parent, tag) {
71
+ const el = parent[tag];
72
+ if (!el) {
73
+ throw new Error(`Invalid XML: missing <${tag}> element`);
186
74
  }
187
- const eventName = getAttr(eventMatch[1], 'name');
188
- if (!eventName) {
189
- throw new Error('Invalid XML: event must have a name attribute');
75
+ return el;
76
+ }
77
+ function extractFields(node) {
78
+ return parseFieldNodes(asArray(node['field']));
79
+ }
80
+ export function parseStateChangeSliceXml(xml) {
81
+ const parsed = xmlParser.parse(xml);
82
+ const slice = parsed['state-change-slice'];
83
+ if (!slice) {
84
+ throw new Error('Invalid XML: missing <state-change-slice> tag');
190
85
  }
191
- const eventFields = parseFieldsFromXml(eventMatch[2]);
86
+ const sliceName = requireAttr(slice, 'name', 'state-change-slice');
87
+ const after = optAttr(slice, 'after');
88
+ const before = optAttr(slice, 'before');
89
+ const screen = getElement(slice, 'screen');
90
+ const command = getElement(slice, 'command');
91
+ const event = getElement(slice, 'event');
192
92
  return {
193
93
  sliceName,
194
94
  after,
195
95
  before,
196
- screen: { name: screenName, fields: screenFields },
197
- command: { name: commandName, fields: commandFields },
198
- event: { name: eventName, fields: eventFields },
96
+ screen: { name: requireAttr(screen, 'name', 'screen'), fields: extractFields(screen) },
97
+ command: { name: requireAttr(command, 'name', 'command'), fields: extractFields(command) },
98
+ event: { name: requireAttr(event, 'name', 'event'), fields: extractFields(event) },
199
99
  };
200
100
  }
201
101
  export function parseAutomationSliceXml(xml) {
202
- // Parse <automation-slice name="..." after="..." before="...">
203
- const sliceMatch = xml.match(/<automation-slice([^>]*)>/);
204
- if (!sliceMatch) {
102
+ const parsed = xmlParser.parse(xml);
103
+ const slice = parsed['automation-slice'];
104
+ if (!slice) {
205
105
  throw new Error('Invalid XML: missing <automation-slice> tag');
206
106
  }
207
- const sliceName = getAttr(sliceMatch[1], 'name');
208
- if (!sliceName) {
209
- throw new Error('Invalid XML: automation-slice must have a name attribute');
210
- }
211
- const after = getAttr(sliceMatch[1], 'after');
212
- const before = getAttr(sliceMatch[1], 'before');
213
- // Parse read-model
214
- const readModelMatch = xml.match(/<read-model([^>]*)>([\s\S]*?)<\/read-model>/);
215
- if (!readModelMatch) {
216
- throw new Error('Invalid XML: missing <read-model> element');
217
- }
218
- const readModelName = getAttr(readModelMatch[1], 'name');
219
- if (!readModelName) {
220
- throw new Error('Invalid XML: read-model must have a name attribute');
221
- }
222
- const readModelFields = parseFieldsFromXml(readModelMatch[2]);
223
- // Parse processor (fields are not supported for processor in this slice type)
224
- const processorMatch = xml.match(/<processor([^>]*?)(?:\/>|>([\s\S]*?)<\/processor>)/);
225
- if (!processorMatch) {
226
- throw new Error('Invalid XML: missing <processor> element');
227
- }
228
- const processorName = getAttr(processorMatch[1], 'name');
229
- if (!processorName) {
230
- throw new Error('Invalid XML: processor must have a name attribute');
231
- }
232
- // Parse command
233
- const commandMatch = xml.match(/<command([^>]*)>([\s\S]*?)<\/command>/);
234
- if (!commandMatch) {
235
- throw new Error('Invalid XML: missing <command> element');
236
- }
237
- const commandName = getAttr(commandMatch[1], 'name');
238
- if (!commandName) {
239
- throw new Error('Invalid XML: command must have a name attribute');
240
- }
241
- const commandFields = parseFieldsFromXml(commandMatch[2]);
242
- // Parse event
243
- const eventMatch = xml.match(/<event([^>]*)>([\s\S]*?)<\/event>/);
244
- if (!eventMatch) {
245
- throw new Error('Invalid XML: missing <event> element');
246
- }
247
- const eventName = getAttr(eventMatch[1], 'name');
248
- if (!eventName) {
249
- throw new Error('Invalid XML: event must have a name attribute');
250
- }
251
- const eventFields = parseFieldsFromXml(eventMatch[2]);
107
+ const sliceName = requireAttr(slice, 'name', 'automation-slice');
108
+ const after = optAttr(slice, 'after');
109
+ const before = optAttr(slice, 'before');
110
+ const readModel = getElement(slice, 'read-model');
111
+ const processor = getElement(slice, 'processor');
112
+ const command = getElement(slice, 'command');
113
+ const event = getElement(slice, 'event');
252
114
  return {
253
115
  sliceName,
254
116
  after,
255
117
  before,
256
- readModel: { name: readModelName, fields: readModelFields },
257
- processor: { name: processorName },
258
- command: { name: commandName, fields: commandFields },
259
- event: { name: eventName, fields: eventFields },
118
+ readModel: { name: requireAttr(readModel, 'name', 'read-model'), fields: extractFields(readModel) },
119
+ processor: { name: requireAttr(processor, 'name', 'processor') },
120
+ command: { name: requireAttr(command, 'name', 'command'), fields: extractFields(command) },
121
+ event: { name: requireAttr(event, 'name', 'event'), fields: extractFields(event) },
260
122
  };
261
123
  }
262
124
  export function parseStateViewSliceXml(xml) {
263
- // Parse <state-view-slice name="..." after="..." before="...">
264
- const sliceMatch = xml.match(/<state-view-slice([^>]*)>/);
265
- if (!sliceMatch) {
125
+ const parsed = xmlParser.parse(xml);
126
+ const slice = parsed['state-view-slice'];
127
+ if (!slice) {
266
128
  throw new Error('Invalid XML: missing <state-view-slice> tag');
267
129
  }
268
- const sliceName = getAttr(sliceMatch[1], 'name');
269
- if (!sliceName) {
270
- throw new Error('Invalid XML: state-view-slice must have a name attribute');
271
- }
272
- const after = getAttr(sliceMatch[1], 'after');
273
- const before = getAttr(sliceMatch[1], 'before');
274
- // Parse read-model
275
- const readModelMatch = xml.match(/<read-model([^>]*)>([\s\S]*?)<\/read-model>/);
276
- if (!readModelMatch) {
277
- throw new Error('Invalid XML: missing <read-model> element');
278
- }
279
- const readModelName = getAttr(readModelMatch[1], 'name');
280
- if (!readModelName) {
281
- throw new Error('Invalid XML: read-model must have a name attribute');
282
- }
283
- const readModelFields = parseFieldsFromXml(readModelMatch[2]);
130
+ const sliceName = requireAttr(slice, 'name', 'state-view-slice');
131
+ const after = optAttr(slice, 'after');
132
+ const before = optAttr(slice, 'before');
133
+ const readModel = getElement(slice, 'read-model');
284
134
  return {
285
135
  sliceName,
286
136
  after,
287
137
  before,
288
- readModel: { name: readModelName, fields: readModelFields },
138
+ readModel: { name: requireAttr(readModel, 'name', 'read-model'), fields: extractFields(readModel) },
289
139
  };
290
140
  }
@@ -450,16 +450,6 @@ function applyEvent(model, event) {
450
450
  nodeIds: [],
451
451
  });
452
452
  break;
453
- case 'NodesGroupedIntoSlice':
454
- model.slices.set(d.sliceId, {
455
- id: d.sliceId,
456
- name: d.name,
457
- status: 'created',
458
- position: d.position,
459
- size: d.size,
460
- nodeIds: d.nodeIds,
461
- });
462
- break;
463
453
  case 'SliceMoved': {
464
454
  const slice = model.slices.get(d.sliceId);
465
455
  if (slice)
@@ -540,17 +530,6 @@ function applyEvent(model, event) {
540
530
  aggregateIdFieldType: d.aggregateIdFieldType,
541
531
  });
542
532
  break;
543
- case 'EventsGroupedIntoAggregate':
544
- model.aggregates.set(d.aggregateId, {
545
- id: d.aggregateId,
546
- name: d.name,
547
- position: d.position,
548
- size: d.size,
549
- eventIds: d.eventIds,
550
- aggregateIdFieldName: d.aggregateIdFieldName,
551
- aggregateIdFieldType: d.aggregateIdFieldType,
552
- });
553
- break;
554
533
  case 'AggregateMoved': {
555
534
  const aggregate = model.aggregates.get(d.aggregateId);
556
535
  if (aggregate)
@@ -592,15 +571,6 @@ function applyEvent(model, event) {
592
571
  screenIds: [],
593
572
  });
594
573
  break;
595
- case 'ScreensGroupedIntoActor':
596
- model.actors.set(d.actorId, {
597
- id: d.actorId,
598
- name: d.name,
599
- position: d.position,
600
- size: d.size,
601
- screenIds: d.screenIds,
602
- });
603
- break;
604
574
  case 'ActorMoved': {
605
575
  const actor = model.actors.get(d.actorId);
606
576
  if (actor)
@@ -1,4 +1,3 @@
1
- import type { EventModel } from '../../types.js';
2
1
  export interface ScenarioInput {
3
2
  name: string;
4
3
  description?: string;
@@ -25,5 +24,4 @@ interface EventInput {
25
24
  fieldValues?: Record<string, unknown>;
26
25
  }
27
26
  export declare function parseScenarioInput(input: string): ScenarioInput;
28
- export declare function addScenario(model: EventModel, filePath: string, sliceName: string, input: string): void;
29
27
  export {};