eventmodeler 0.1.0 → 0.2.1

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.
@@ -0,0 +1,180 @@
1
+ function escapeXml(str) {
2
+ return str
3
+ .replace(/&/g, '&')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;');
7
+ }
8
+ function flattenFields(fields, prefix = '') {
9
+ const result = [];
10
+ for (const field of fields) {
11
+ const path = prefix ? `${prefix}.${field.name}` : field.name;
12
+ result.push({ id: field.id, path, field });
13
+ if (field.fieldType === 'Custom' && field.subfields) {
14
+ result.push(...flattenFields(field.subfields, path));
15
+ }
16
+ }
17
+ return result;
18
+ }
19
+ function calculateFlowCompleteness(model, flow, targetFields, sourceFields) {
20
+ const flatSource = flattenFields(sourceFields);
21
+ const flatTarget = flattenFields(targetFields);
22
+ const manualMappings = flow.fieldMappings ?? [];
23
+ // Get source entity name
24
+ let sourceName = 'Unknown';
25
+ let sourceType = 'unknown';
26
+ const event = model.events.get(flow.sourceId);
27
+ const readModel = model.readModels.get(flow.sourceId);
28
+ const command = model.commands.get(flow.sourceId);
29
+ const screen = model.screens.get(flow.sourceId);
30
+ const processor = model.processors.get(flow.sourceId);
31
+ if (event) {
32
+ sourceName = event.name;
33
+ sourceType = 'event';
34
+ }
35
+ else if (readModel) {
36
+ sourceName = readModel.name;
37
+ sourceType = 'read-model';
38
+ }
39
+ else if (command) {
40
+ sourceName = command.name;
41
+ sourceType = 'command';
42
+ }
43
+ else if (screen) {
44
+ sourceName = screen.name;
45
+ sourceType = 'screen';
46
+ }
47
+ else if (processor) {
48
+ sourceName = processor.name;
49
+ sourceType = 'processor';
50
+ }
51
+ const fieldStatuses = flatTarget.map(({ id, path, field }) => {
52
+ // 1. Check if generated
53
+ if (field.isGenerated) {
54
+ return { fieldId: id, fieldName: path, status: 'generated' };
55
+ }
56
+ // 2. Check if user input
57
+ if (field.isUserInput) {
58
+ return { fieldId: id, fieldName: path, status: 'user-input' };
59
+ }
60
+ // 3. Check manual mapping
61
+ const manualMapping = manualMappings.find(m => m.targetFieldId === id);
62
+ if (manualMapping) {
63
+ const sourceField = flatSource.find(sf => sf.id === manualMapping.sourceFieldId);
64
+ return {
65
+ fieldId: id,
66
+ fieldName: path,
67
+ status: 'satisfied',
68
+ sourceFieldId: manualMapping.sourceFieldId,
69
+ sourceFieldName: sourceField?.path,
70
+ };
71
+ }
72
+ // 4. Check auto-mapping by path
73
+ const autoMatch = flatSource.find(sf => sf.path === path);
74
+ if (autoMatch) {
75
+ return {
76
+ fieldId: id,
77
+ fieldName: path,
78
+ status: 'satisfied',
79
+ sourceFieldId: autoMatch.id,
80
+ sourceFieldName: autoMatch.path,
81
+ };
82
+ }
83
+ // 5. Not satisfied - check if optional
84
+ if (field.isOptional) {
85
+ return { fieldId: id, fieldName: path, status: 'optional-missing' };
86
+ }
87
+ // 6. Unsatisfied required field
88
+ return { fieldId: id, fieldName: path, status: 'unsatisfied' };
89
+ });
90
+ const hasUnsatisfied = fieldStatuses.some(s => s.status === 'unsatisfied');
91
+ const hasOptionalMissing = fieldStatuses.some(s => s.status === 'optional-missing');
92
+ return {
93
+ flowId: flow.id,
94
+ sourceName,
95
+ sourceType,
96
+ isComplete: !hasUnsatisfied,
97
+ hasWarnings: hasOptionalMissing,
98
+ targetFields: fieldStatuses,
99
+ sourceFields: flatSource.map(sf => ({
100
+ id: sf.id,
101
+ name: sf.path,
102
+ type: sf.field.fieldType,
103
+ isList: sf.field.isList,
104
+ })),
105
+ };
106
+ }
107
+ export function showCompleteness(model, readModelName) {
108
+ // Find the read model
109
+ const nameLower = readModelName.toLowerCase();
110
+ const readModels = [...model.readModels.values()];
111
+ const readModel = readModels.find(rm => rm.name.toLowerCase() === nameLower || rm.name.toLowerCase().includes(nameLower));
112
+ if (!readModel) {
113
+ console.error(`Error: Read model not found: ${readModelName}`);
114
+ console.error('Available read models:');
115
+ for (const rm of readModels) {
116
+ console.error(` - ${rm.name}`);
117
+ }
118
+ process.exit(1);
119
+ }
120
+ // Find all flows into this read model (EventToReadModel flows)
121
+ const incomingFlows = [...model.flows.values()].filter(f => f.targetId === readModel.id && f.flowType === 'EventToReadModel');
122
+ if (incomingFlows.length === 0) {
123
+ console.log(`<completeness readModel="${escapeXml(readModel.name)}">`);
124
+ console.log(' <no-flows>No events flow into this read model</no-flows>');
125
+ console.log('</completeness>');
126
+ return;
127
+ }
128
+ // Calculate completeness for each flow
129
+ const completenessResults = [];
130
+ for (const flow of incomingFlows) {
131
+ const sourceEvent = model.events.get(flow.sourceId);
132
+ if (!sourceEvent)
133
+ continue;
134
+ const result = calculateFlowCompleteness(model, flow, readModel.fields, sourceEvent.fields);
135
+ completenessResults.push(result);
136
+ }
137
+ // Output XML
138
+ const overallComplete = completenessResults.every(r => r.isComplete);
139
+ const overallStatus = overallComplete ? 'complete' : 'incomplete';
140
+ console.log(`<completeness readModel="${escapeXml(readModel.name)}" status="${overallStatus}">`);
141
+ for (const result of completenessResults) {
142
+ const flowStatus = result.isComplete ? 'complete' : 'incomplete';
143
+ const warningAttr = result.hasWarnings ? ' hasWarnings="true"' : '';
144
+ console.log(` <flow id="${result.flowId}" from="${escapeXml(result.sourceName)}" type="${result.sourceType}" status="${flowStatus}"${warningAttr}>`);
145
+ // Target fields
146
+ console.log(' <target-fields>');
147
+ for (const field of result.targetFields) {
148
+ if (field.sourceFieldName) {
149
+ console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}" source="${escapeXml(field.sourceFieldName)}"/>`);
150
+ }
151
+ else {
152
+ console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}"/>`);
153
+ }
154
+ }
155
+ console.log(' </target-fields>');
156
+ // Available source fields (for AI to see what's available)
157
+ console.log(' <available-source-fields>');
158
+ for (const sf of result.sourceFields) {
159
+ const listAttr = sf.isList ? ' isList="true"' : '';
160
+ console.log(` <field id="${sf.id}" name="${escapeXml(sf.name)}" type="${sf.type}"${listAttr}/>`);
161
+ }
162
+ console.log(' </available-source-fields>');
163
+ console.log(' </flow>');
164
+ }
165
+ // Also show the read model's fields with their IDs (needed for mapping)
166
+ console.log(' <read-model-fields>');
167
+ const flatTargetFields = flattenFields(readModel.fields);
168
+ for (const { id, path, field } of flatTargetFields) {
169
+ const attrs = [`id="${id}"`, `name="${escapeXml(path)}"`, `type="${field.fieldType}"`];
170
+ if (field.isList)
171
+ attrs.push('isList="true"');
172
+ if (field.isOptional)
173
+ attrs.push('isOptional="true"');
174
+ if (field.isGenerated)
175
+ attrs.push('isGenerated="true"');
176
+ console.log(` <field ${attrs.join(' ')}/>`);
177
+ }
178
+ console.log(' </read-model-fields>');
179
+ console.log('</completeness>');
180
+ }
@@ -27,8 +27,27 @@ function formatFieldXml(field, indent) {
27
27
  }
28
28
  return `${indent}<field ${attrs.join(' ')}/>\n`;
29
29
  }
30
+ // Find which aggregate an event belongs to (center point inside aggregate bounds)
31
+ function findAggregateForEvent(model, event) {
32
+ const centerX = event.position.x + event.width / 2;
33
+ const centerY = event.position.y + event.height / 2;
34
+ for (const aggregate of model.aggregates.values()) {
35
+ const bounds = {
36
+ left: aggregate.position.x,
37
+ right: aggregate.position.x + aggregate.size.width,
38
+ top: aggregate.position.y,
39
+ bottom: aggregate.position.y + aggregate.size.height,
40
+ };
41
+ if (centerX >= bounds.left && centerX <= bounds.right && centerY >= bounds.top && centerY <= bounds.bottom) {
42
+ return aggregate;
43
+ }
44
+ }
45
+ return null;
46
+ }
30
47
  function formatEventXml(model, event) {
31
- let xml = `<event name="${escapeXml(event.name)}">\n`;
48
+ const aggregate = findAggregateForEvent(model, event);
49
+ const aggregateAttr = aggregate ? ` aggregate="${escapeXml(aggregate.name)}"` : '';
50
+ let xml = `<event name="${escapeXml(event.name)}"${aggregateAttr}>\n`;
32
51
  if (event.fields.length > 0) {
33
52
  xml += ' <fields>\n';
34
53
  for (const field of event.fields) {
@@ -14,6 +14,8 @@ export function showModelSummary(model) {
14
14
  console.log(` <read-models count="${model.readModels.size}"/>`);
15
15
  console.log(` <screens count="${model.screens.size}"/>`);
16
16
  console.log(` <processors count="${model.processors.size}"/>`);
17
+ console.log(` <aggregates count="${model.aggregates.size}"/>`);
18
+ console.log(` <actors count="${model.actors.size}"/>`);
17
19
  console.log(` <scenarios count="${model.scenarios.size}"/>`);
18
20
  console.log(` <flows count="${model.flows.size}"/>`);
19
21
  console.log('</model>');
@@ -50,6 +50,8 @@ function getSliceComponents(model, slice) {
50
50
  const centerY = pos.y + height / 2;
51
51
  return centerX >= bounds.left && centerX <= bounds.right && centerY >= bounds.top && centerY <= bounds.bottom;
52
52
  }
53
+ // Include all elements in the slice (including linked copies)
54
+ // Linked copies will be marked as such in the output
53
55
  return {
54
56
  commands: [...model.commands.values()].filter(c => isInSlice(c.position, c.width, c.height)),
55
57
  events: [...model.events.values()].filter(e => isInSlice(e.position, e.width, e.height)),
@@ -58,6 +60,54 @@ function getSliceComponents(model, slice) {
58
60
  processors: [...model.processors.values()].filter(p => isInSlice(p.position, p.width, p.height)),
59
61
  };
60
62
  }
63
+ // Find which slice contains the original of a linked copy
64
+ function findSliceForNode(model, nodeId) {
65
+ const event = model.events.get(nodeId);
66
+ const readModel = model.readModels.get(nodeId);
67
+ const screen = model.screens.get(nodeId);
68
+ const node = event ?? readModel ?? screen;
69
+ if (!node)
70
+ return null;
71
+ for (const slice of model.slices.values()) {
72
+ const centerX = node.position.x + node.width / 2;
73
+ const centerY = node.position.y + node.height / 2;
74
+ if (centerX >= slice.position.x &&
75
+ centerX <= slice.position.x + slice.size.width &&
76
+ centerY >= slice.position.y &&
77
+ centerY <= slice.position.y + slice.size.height) {
78
+ return slice;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ // Find which aggregate an event belongs to (center point inside aggregate bounds)
84
+ function findAggregateForEvent(model, event) {
85
+ const centerX = event.position.x + event.width / 2;
86
+ const centerY = event.position.y + event.height / 2;
87
+ for (const aggregate of model.aggregates.values()) {
88
+ if (centerX >= aggregate.position.x &&
89
+ centerX <= aggregate.position.x + aggregate.size.width &&
90
+ centerY >= aggregate.position.y &&
91
+ centerY <= aggregate.position.y + aggregate.size.height) {
92
+ return aggregate;
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ // Find which actor a screen belongs to (center point inside actor bounds)
98
+ function findActorForScreen(model, screen) {
99
+ const centerX = screen.position.x + screen.width / 2;
100
+ const centerY = screen.position.y + screen.height / 2;
101
+ for (const actor of model.actors.values()) {
102
+ if (centerX >= actor.position.x &&
103
+ centerX <= actor.position.x + actor.size.width &&
104
+ centerY >= actor.position.y &&
105
+ centerY <= actor.position.y + actor.size.height) {
106
+ return actor;
107
+ }
108
+ }
109
+ return null;
110
+ }
61
111
  function formatSliceXml(model, slice) {
62
112
  const components = getSliceComponents(model, slice);
63
113
  const scenarios = [...model.scenarios.values()].filter(s => s.sliceId === slice.id);
@@ -80,7 +130,19 @@ function formatSliceXml(model, slice) {
80
130
  let xml = `<slice name="${escapeXml(slice.name)}" status="${slice.status}">\n`;
81
131
  xml += ' <components>\n';
82
132
  for (const screen of components.screens) {
83
- xml += ` <screen name="${escapeXml(screen.name)}">\n`;
133
+ // Check if this is a linked copy
134
+ const copyAttr = screen.originalNodeId ? ' linked-copy="true"' : '';
135
+ let originAttr = '';
136
+ if (screen.originalNodeId) {
137
+ const originSlice = findSliceForNode(model, screen.originalNodeId);
138
+ if (originSlice) {
139
+ originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
140
+ }
141
+ }
142
+ // Check which actor this screen belongs to
143
+ const actor = findActorForScreen(model, screen);
144
+ const actorAttr = actor ? ` actor="${escapeXml(actor.name)}"` : '';
145
+ xml += ` <screen name="${escapeXml(screen.name)}"${copyAttr}${originAttr}${actorAttr}>\n`;
84
146
  if (screen.fields.length > 0) {
85
147
  xml += ' <fields>\n';
86
148
  for (const field of screen.fields) {
@@ -110,7 +172,19 @@ function formatSliceXml(model, slice) {
110
172
  xml += ' </command>\n';
111
173
  }
112
174
  for (const event of components.events) {
113
- xml += ` <event name="${escapeXml(event.name)}">\n`;
175
+ // Check if this is a linked copy
176
+ const copyAttr = event.originalNodeId ? ' linked-copy="true"' : '';
177
+ let originAttr = '';
178
+ if (event.originalNodeId) {
179
+ const originSlice = findSliceForNode(model, event.originalNodeId);
180
+ if (originSlice) {
181
+ originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
182
+ }
183
+ }
184
+ // Check which aggregate this event belongs to
185
+ const aggregate = findAggregateForEvent(model, event);
186
+ const aggregateAttr = aggregate ? ` aggregate="${escapeXml(aggregate.name)}"` : '';
187
+ xml += ` <event name="${escapeXml(event.name)}"${copyAttr}${originAttr}${aggregateAttr}>\n`;
114
188
  if (event.fields.length > 0) {
115
189
  xml += ' <fields>\n';
116
190
  for (const field of event.fields) {
@@ -132,7 +206,16 @@ function formatSliceXml(model, slice) {
132
206
  xml += ' </event>\n';
133
207
  }
134
208
  for (const readModel of components.readModels) {
135
- xml += ` <read-model name="${escapeXml(readModel.name)}">\n`;
209
+ // Check if this is a linked copy
210
+ const copyAttr = readModel.originalNodeId ? ' linked-copy="true"' : '';
211
+ let originAttr = '';
212
+ if (readModel.originalNodeId) {
213
+ const originSlice = findSliceForNode(model, readModel.originalNodeId);
214
+ if (originSlice) {
215
+ originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
216
+ }
217
+ }
218
+ xml += ` <read-model name="${escapeXml(readModel.name)}"${copyAttr}${originAttr}>\n`;
136
219
  if (readModel.fields.length > 0) {
137
220
  xml += ' <fields>\n';
138
221
  for (const field of readModel.fields) {
@@ -0,0 +1,12 @@
1
+ import type { EventModel } from '../../types.js';
2
+ interface UpdateOptions {
3
+ optional?: boolean;
4
+ generated?: boolean;
5
+ type?: string;
6
+ }
7
+ export declare function updateField(model: EventModel, filePath: string, options: {
8
+ command?: string;
9
+ event?: string;
10
+ readModel?: string;
11
+ }, fieldName: string, updates: UpdateOptions): void;
12
+ export {};
@@ -0,0 +1,166 @@
1
+ import { appendEvent } from '../../lib/file-loader.js';
2
+ function findFieldByName(fields, fieldName) {
3
+ const nameLower = fieldName.toLowerCase();
4
+ for (const field of fields) {
5
+ if (field.name.toLowerCase() === nameLower) {
6
+ return field;
7
+ }
8
+ // Check subfields for Custom types
9
+ if (field.fieldType === 'Custom' && field.subfields) {
10
+ // Check for dot notation path (e.g., "address.street")
11
+ if (fieldName.toLowerCase().startsWith(field.name.toLowerCase() + '.')) {
12
+ const subPath = fieldName.slice(field.name.length + 1);
13
+ const subField = findFieldByName(field.subfields, subPath);
14
+ if (subField)
15
+ return subField;
16
+ }
17
+ // Also check direct match in subfields
18
+ const subField = findFieldByName(field.subfields, fieldName);
19
+ if (subField)
20
+ return subField;
21
+ }
22
+ }
23
+ return undefined;
24
+ }
25
+ function createUpdatedField(original, updates) {
26
+ return {
27
+ ...original,
28
+ isOptional: updates.optional !== undefined ? updates.optional : original.isOptional,
29
+ isGenerated: updates.generated !== undefined ? updates.generated : original.isGenerated,
30
+ fieldType: updates.type !== undefined ? updates.type : original.fieldType,
31
+ };
32
+ }
33
+ export function updateField(model, filePath, options, fieldName, updates) {
34
+ // Determine which entity type
35
+ const entityCount = [options.command, options.event, options.readModel].filter(Boolean).length;
36
+ if (entityCount === 0) {
37
+ console.error('Error: Must specify one of --command, --event, or --read-model');
38
+ process.exit(1);
39
+ }
40
+ if (entityCount > 1) {
41
+ console.error('Error: Can only specify one of --command, --event, or --read-model');
42
+ process.exit(1);
43
+ }
44
+ if (options.command) {
45
+ updateCommandField(model, filePath, options.command, fieldName, updates);
46
+ }
47
+ else if (options.event) {
48
+ updateEventField(model, filePath, options.event, fieldName, updates);
49
+ }
50
+ else if (options.readModel) {
51
+ updateReadModelField(model, filePath, options.readModel, fieldName, updates);
52
+ }
53
+ }
54
+ function updateCommandField(model, filePath, commandName, fieldName, updates) {
55
+ const nameLower = commandName.toLowerCase();
56
+ const commands = [...model.commands.values()];
57
+ const command = commands.find(c => c.name.toLowerCase() === nameLower || c.name.toLowerCase().includes(nameLower));
58
+ if (!command) {
59
+ console.error(`Error: Command not found: ${commandName}`);
60
+ console.error('Available commands:');
61
+ for (const c of commands) {
62
+ console.error(` - ${c.name}`);
63
+ }
64
+ process.exit(1);
65
+ }
66
+ const field = findFieldByName(command.fields, fieldName);
67
+ if (!field) {
68
+ console.error(`Error: Field "${fieldName}" not found on command "${command.name}"`);
69
+ if (command.fields.length > 0) {
70
+ console.error('Available fields:');
71
+ for (const f of command.fields) {
72
+ console.error(` - ${f.name}`);
73
+ }
74
+ }
75
+ process.exit(1);
76
+ }
77
+ const updatedField = createUpdatedField(field, updates);
78
+ appendEvent(filePath, {
79
+ type: 'CommandFieldAdjusted',
80
+ commandStickyId: command.id,
81
+ fieldId: field.id,
82
+ field: updatedField,
83
+ timestamp: Date.now(),
84
+ });
85
+ console.log(`Updated field "${field.name}" on command "${command.name}"`);
86
+ logUpdates(updates);
87
+ }
88
+ function updateEventField(model, filePath, eventName, fieldName, updates) {
89
+ const nameLower = eventName.toLowerCase();
90
+ const events = [...model.events.values()];
91
+ const event = events.find(e => e.name.toLowerCase() === nameLower || e.name.toLowerCase().includes(nameLower));
92
+ if (!event) {
93
+ console.error(`Error: Event not found: ${eventName}`);
94
+ console.error('Available events:');
95
+ for (const e of events) {
96
+ console.error(` - ${e.name}`);
97
+ }
98
+ process.exit(1);
99
+ }
100
+ const field = findFieldByName(event.fields, fieldName);
101
+ if (!field) {
102
+ console.error(`Error: Field "${fieldName}" not found on event "${event.name}"`);
103
+ if (event.fields.length > 0) {
104
+ console.error('Available fields:');
105
+ for (const f of event.fields) {
106
+ console.error(` - ${f.name}`);
107
+ }
108
+ }
109
+ process.exit(1);
110
+ }
111
+ const updatedField = createUpdatedField(field, updates);
112
+ appendEvent(filePath, {
113
+ type: 'EventFieldAdjusted',
114
+ eventStickyId: event.id,
115
+ fieldId: field.id,
116
+ field: updatedField,
117
+ timestamp: Date.now(),
118
+ });
119
+ console.log(`Updated field "${field.name}" on event "${event.name}"`);
120
+ logUpdates(updates);
121
+ }
122
+ function updateReadModelField(model, filePath, readModelName, fieldName, updates) {
123
+ const nameLower = readModelName.toLowerCase();
124
+ const readModels = [...model.readModels.values()];
125
+ const readModel = readModels.find(rm => rm.name.toLowerCase() === nameLower || rm.name.toLowerCase().includes(nameLower));
126
+ if (!readModel) {
127
+ console.error(`Error: Read model not found: ${readModelName}`);
128
+ console.error('Available read models:');
129
+ for (const rm of readModels) {
130
+ console.error(` - ${rm.name}`);
131
+ }
132
+ process.exit(1);
133
+ }
134
+ const field = findFieldByName(readModel.fields, fieldName);
135
+ if (!field) {
136
+ console.error(`Error: Field "${fieldName}" not found on read model "${readModel.name}"`);
137
+ if (readModel.fields.length > 0) {
138
+ console.error('Available fields:');
139
+ for (const f of readModel.fields) {
140
+ console.error(` - ${f.name}`);
141
+ }
142
+ }
143
+ process.exit(1);
144
+ }
145
+ const updatedField = createUpdatedField(field, updates);
146
+ appendEvent(filePath, {
147
+ type: 'ReadModelFieldAdjusted',
148
+ readModelStickyId: readModel.id,
149
+ fieldId: field.id,
150
+ field: updatedField,
151
+ timestamp: Date.now(),
152
+ });
153
+ console.log(`Updated field "${field.name}" on read model "${readModel.name}"`);
154
+ logUpdates(updates);
155
+ }
156
+ function logUpdates(updates) {
157
+ if (updates.optional !== undefined) {
158
+ console.log(` isOptional: ${updates.optional}`);
159
+ }
160
+ if (updates.generated !== undefined) {
161
+ console.log(` isGenerated: ${updates.generated}`);
162
+ }
163
+ if (updates.type !== undefined) {
164
+ console.log(` fieldType: ${updates.type}`);
165
+ }
166
+ }
package/dist/types.d.ts CHANGED
@@ -56,6 +56,7 @@ export interface EventSticky {
56
56
  width: number;
57
57
  height: number;
58
58
  canonicalId?: string;
59
+ originalNodeId?: string;
59
60
  }
60
61
  export interface ReadModelSticky {
61
62
  id: string;
@@ -68,6 +69,7 @@ export interface ReadModelSticky {
68
69
  width: number;
69
70
  height: number;
70
71
  canonicalId?: string;
72
+ originalNodeId?: string;
71
73
  }
72
74
  export interface Screen {
73
75
  id: string;
@@ -80,6 +82,7 @@ export interface Screen {
80
82
  width: number;
81
83
  height: number;
82
84
  canonicalId?: string;
85
+ originalNodeId?: string;
83
86
  }
84
87
  export interface Processor {
85
88
  id: string;
@@ -118,6 +121,34 @@ export interface Chapter {
118
121
  height: number;
119
122
  };
120
123
  }
124
+ export interface Aggregate {
125
+ id: string;
126
+ name: string;
127
+ position: {
128
+ x: number;
129
+ y: number;
130
+ };
131
+ size: {
132
+ width: number;
133
+ height: number;
134
+ };
135
+ eventIds: string[];
136
+ aggregateIdFieldName?: string;
137
+ aggregateIdFieldType?: Field['fieldType'];
138
+ }
139
+ export interface Actor {
140
+ id: string;
141
+ name: string;
142
+ position: {
143
+ x: number;
144
+ y: number;
145
+ };
146
+ size: {
147
+ width: number;
148
+ height: number;
149
+ };
150
+ screenIds: string[];
151
+ }
121
152
  export interface Scenario {
122
153
  id: string;
123
154
  sliceId: string;
@@ -151,6 +182,8 @@ export interface EventModel {
151
182
  processors: Map<string, Processor>;
152
183
  slices: Map<string, Slice>;
153
184
  chapters: Map<string, Chapter>;
185
+ aggregates: Map<string, Aggregate>;
186
+ actors: Map<string, Actor>;
154
187
  scenarios: Map<string, Scenario>;
155
188
  flows: Map<string, Flow>;
156
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eventmodeler",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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": {