eventmodeler 0.2.3 → 0.2.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.
Files changed (31) hide show
  1. package/dist/index.js +63 -0
  2. package/dist/lib/element-lookup.d.ts +47 -0
  3. package/dist/lib/element-lookup.js +86 -0
  4. package/dist/lib/slice-utils.d.ts +83 -0
  5. package/dist/lib/slice-utils.js +135 -0
  6. package/dist/slices/add-field/index.js +4 -33
  7. package/dist/slices/add-scenario/index.js +15 -77
  8. package/dist/slices/create-automation-slice/index.d.ts +2 -0
  9. package/dist/slices/create-automation-slice/index.js +217 -0
  10. package/dist/slices/create-flow/index.d.ts +2 -0
  11. package/dist/slices/create-flow/index.js +177 -0
  12. package/dist/slices/create-state-change-slice/index.d.ts +2 -0
  13. package/dist/slices/create-state-change-slice/index.js +239 -0
  14. package/dist/slices/create-state-view-slice/index.d.ts +2 -0
  15. package/dist/slices/create-state-view-slice/index.js +120 -0
  16. package/dist/slices/list-chapters/index.js +2 -2
  17. package/dist/slices/list-commands/index.js +2 -2
  18. package/dist/slices/list-events/index.js +3 -2
  19. package/dist/slices/list-slices/index.js +2 -2
  20. package/dist/slices/mark-slice-status/index.js +2 -11
  21. package/dist/slices/remove-field/index.js +4 -33
  22. package/dist/slices/remove-scenario/index.js +45 -11
  23. package/dist/slices/show-actor/index.js +2 -11
  24. package/dist/slices/show-aggregate-completeness/index.js +2 -11
  25. package/dist/slices/show-chapter/index.js +6 -14
  26. package/dist/slices/show-command/index.js +4 -12
  27. package/dist/slices/show-completeness/index.js +378 -32
  28. package/dist/slices/show-event/index.js +4 -12
  29. package/dist/slices/show-slice/index.js +14 -17
  30. package/dist/slices/update-field/index.js +4 -33
  31. package/package.json +1 -1
@@ -0,0 +1,217 @@
1
+ import { appendEvent } from '../../lib/file-loader.js';
2
+ import { AUTOMATION_SLICE, SLICE_GAP, calculateSlicePosition, validateSliceNameUnique, inferFieldMappings, parseFieldsFromXml, getSlicesToShift, fieldInputToField, } from '../../lib/slice-utils.js';
3
+ function getAttr(attrs, name) {
4
+ const match = attrs.match(new RegExp(`${name}="([^"]*)"`));
5
+ return match ? match[1] : undefined;
6
+ }
7
+ function parseXmlInput(xml) {
8
+ // Parse <automation-slice name="..." after="..." before="...">
9
+ const sliceMatch = xml.match(/<automation-slice([^>]*)>/);
10
+ if (!sliceMatch) {
11
+ throw new Error('Invalid XML: missing <automation-slice> tag');
12
+ }
13
+ const sliceName = getAttr(sliceMatch[1], 'name');
14
+ if (!sliceName) {
15
+ throw new Error('Invalid XML: automation-slice must have a name attribute');
16
+ }
17
+ const after = getAttr(sliceMatch[1], 'after');
18
+ const before = getAttr(sliceMatch[1], 'before');
19
+ // Parse processor (no fields)
20
+ const processorMatch = xml.match(/<processor([^>]*?)(?:\/>|>([\s\S]*?)<\/processor>)/);
21
+ if (!processorMatch) {
22
+ throw new Error('Invalid XML: missing <processor> element');
23
+ }
24
+ const processorName = getAttr(processorMatch[1], 'name');
25
+ if (!processorName) {
26
+ throw new Error('Invalid XML: processor must have a name attribute');
27
+ }
28
+ // Parse command
29
+ const commandMatch = xml.match(/<command([^>]*)>([\s\S]*?)<\/command>/);
30
+ if (!commandMatch) {
31
+ throw new Error('Invalid XML: missing <command> element');
32
+ }
33
+ const commandName = getAttr(commandMatch[1], 'name');
34
+ if (!commandName) {
35
+ throw new Error('Invalid XML: command must have a name attribute');
36
+ }
37
+ const commandFields = parseFieldsFromXml(commandMatch[2]);
38
+ // Parse event
39
+ const eventMatch = xml.match(/<event([^>]*)>([\s\S]*?)<\/event>/);
40
+ if (!eventMatch) {
41
+ throw new Error('Invalid XML: missing <event> element');
42
+ }
43
+ const eventName = getAttr(eventMatch[1], 'name');
44
+ if (!eventName) {
45
+ throw new Error('Invalid XML: event must have a name attribute');
46
+ }
47
+ const eventFields = parseFieldsFromXml(eventMatch[2]);
48
+ return {
49
+ sliceName,
50
+ after,
51
+ before,
52
+ processor: { name: processorName },
53
+ command: { name: commandName, fields: commandFields },
54
+ event: { name: eventName, fields: eventFields },
55
+ };
56
+ }
57
+ export function createAutomationSlice(model, filePath, xmlInput) {
58
+ // Parse input
59
+ let input;
60
+ try {
61
+ input = parseXmlInput(xmlInput);
62
+ }
63
+ catch (err) {
64
+ console.error(`Error: ${err.message}`);
65
+ process.exit(1);
66
+ }
67
+ // Validate slice name is unique
68
+ try {
69
+ validateSliceNameUnique(model, input.sliceName);
70
+ }
71
+ catch (err) {
72
+ console.error(`Error: ${err.message}`);
73
+ process.exit(1);
74
+ }
75
+ // Calculate position
76
+ let slicePosition;
77
+ try {
78
+ slicePosition = calculateSlicePosition(model, AUTOMATION_SLICE.width, {
79
+ after: input.after,
80
+ before: input.before,
81
+ });
82
+ }
83
+ catch (err) {
84
+ console.error(`Error: ${err.message}`);
85
+ process.exit(1);
86
+ }
87
+ // Handle "before" positioning - shift existing slices
88
+ if (input.before) {
89
+ const shiftAmount = AUTOMATION_SLICE.width + SLICE_GAP;
90
+ const toShift = getSlicesToShift(model, slicePosition.x, shiftAmount);
91
+ for (const { sliceId, newX, currentY } of toShift) {
92
+ appendEvent(filePath, {
93
+ type: 'SliceMoved',
94
+ sliceId,
95
+ position: { x: newX, y: currentY },
96
+ timestamp: Date.now(),
97
+ });
98
+ }
99
+ }
100
+ // Generate IDs
101
+ const sliceId = crypto.randomUUID();
102
+ const processorId = crypto.randomUUID();
103
+ const commandId = crypto.randomUUID();
104
+ const eventId = crypto.randomUUID();
105
+ const processorToCommandFlowId = crypto.randomUUID();
106
+ const commandToEventFlowId = crypto.randomUUID();
107
+ // Convert field inputs to fields with IDs
108
+ const commandFields = input.command.fields.map(fieldInputToField);
109
+ const eventFields = input.event.fields.map(fieldInputToField);
110
+ // Infer field mappings (processor has no fields, so only command->event)
111
+ const commandToEventMappings = inferFieldMappings(commandFields, eventFields);
112
+ // Calculate absolute positions for components within the slice
113
+ const processorPosition = {
114
+ x: slicePosition.x + AUTOMATION_SLICE.processor.offsetX,
115
+ y: slicePosition.y + AUTOMATION_SLICE.processor.offsetY,
116
+ };
117
+ const commandPosition = {
118
+ x: slicePosition.x + AUTOMATION_SLICE.command.offsetX,
119
+ y: slicePosition.y + AUTOMATION_SLICE.command.offsetY,
120
+ };
121
+ const eventPosition = {
122
+ x: slicePosition.x + AUTOMATION_SLICE.event.offsetX,
123
+ y: slicePosition.y + AUTOMATION_SLICE.event.offsetY,
124
+ };
125
+ // Emit atomic events in sequence
126
+ // 1. Create the slice
127
+ appendEvent(filePath, {
128
+ type: 'SlicePlaced',
129
+ sliceId,
130
+ name: input.sliceName,
131
+ position: slicePosition,
132
+ size: { width: AUTOMATION_SLICE.width, height: AUTOMATION_SLICE.height },
133
+ timestamp: Date.now(),
134
+ });
135
+ // 2. Create the processor
136
+ appendEvent(filePath, {
137
+ type: 'ProcessorPlaced',
138
+ processorId,
139
+ name: input.processor.name,
140
+ position: processorPosition,
141
+ width: AUTOMATION_SLICE.processor.width,
142
+ height: AUTOMATION_SLICE.processor.height,
143
+ timestamp: Date.now(),
144
+ });
145
+ // 3. Create the command
146
+ appendEvent(filePath, {
147
+ type: 'CommandStickyPlaced',
148
+ commandStickyId: commandId,
149
+ name: input.command.name,
150
+ position: commandPosition,
151
+ width: AUTOMATION_SLICE.command.width,
152
+ height: AUTOMATION_SLICE.command.height,
153
+ timestamp: Date.now(),
154
+ });
155
+ // 4. Add command fields
156
+ for (const field of commandFields) {
157
+ appendEvent(filePath, {
158
+ type: 'CommandFieldAdded',
159
+ commandStickyId: commandId,
160
+ field,
161
+ timestamp: Date.now(),
162
+ });
163
+ }
164
+ // 5. Create the event
165
+ appendEvent(filePath, {
166
+ type: 'EventStickyPlaced',
167
+ eventStickyId: eventId,
168
+ name: input.event.name,
169
+ position: eventPosition,
170
+ width: AUTOMATION_SLICE.event.width,
171
+ height: AUTOMATION_SLICE.event.height,
172
+ timestamp: Date.now(),
173
+ });
174
+ // 6. Add event fields
175
+ for (const field of eventFields) {
176
+ appendEvent(filePath, {
177
+ type: 'EventFieldAdded',
178
+ eventStickyId: eventId,
179
+ field,
180
+ timestamp: Date.now(),
181
+ });
182
+ }
183
+ // 7. Create processor -> command flow
184
+ appendEvent(filePath, {
185
+ type: 'ProcessorToCommandFlowSpecified',
186
+ flowId: processorToCommandFlowId,
187
+ processorId,
188
+ commandStickyId: commandId,
189
+ sourceHandle: 'bottom-source',
190
+ targetHandle: 'top-target',
191
+ timestamp: Date.now(),
192
+ });
193
+ // 8. Create command -> event flow
194
+ appendEvent(filePath, {
195
+ type: 'CommandToEventFlowSpecified',
196
+ flowId: commandToEventFlowId,
197
+ commandStickyId: commandId,
198
+ eventStickyId: eventId,
199
+ sourceHandle: 'bottom-source',
200
+ targetHandle: 'top-target',
201
+ timestamp: Date.now(),
202
+ });
203
+ // 9. Add command -> event field mappings
204
+ if (commandToEventMappings.length > 0) {
205
+ appendEvent(filePath, {
206
+ type: 'FieldMappingSpecified',
207
+ flowId: commandToEventFlowId,
208
+ mappings: commandToEventMappings,
209
+ timestamp: Date.now(),
210
+ });
211
+ }
212
+ console.log(`Created automation slice "${input.sliceName}"`);
213
+ console.log(` Processor: ${input.processor.name}`);
214
+ console.log(` Command: ${input.command.name} (${commandFields.length} fields)`);
215
+ console.log(` Event: ${input.event.name} (${eventFields.length} fields)`);
216
+ console.log(` Command -> Event mappings: ${commandToEventMappings.length}`);
217
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function createFlow(model: EventModel, filePath: string, fromName: string, toName: string): void;
@@ -0,0 +1,177 @@
1
+ import { appendEvent } from '../../lib/file-loader.js';
2
+ import { findElement } from '../../lib/element-lookup.js';
3
+ function findSource(model, name) {
4
+ // Try events first
5
+ const eventResult = findElement(model.events, name);
6
+ if (eventResult.success) {
7
+ return { type: 'event', element: eventResult.element };
8
+ }
9
+ // Try read models
10
+ const readModelResult = findElement(model.readModels, name);
11
+ if (readModelResult.success) {
12
+ return { type: 'readModel', element: readModelResult.element };
13
+ }
14
+ return null;
15
+ }
16
+ function findTarget(model, name) {
17
+ // Try read models first
18
+ const readModelResult = findElement(model.readModels, name);
19
+ if (readModelResult.success) {
20
+ return { type: 'readModel', element: readModelResult.element };
21
+ }
22
+ // Try screens
23
+ const screenResult = findElement(model.screens, name);
24
+ if (screenResult.success) {
25
+ return { type: 'screen', element: screenResult.element };
26
+ }
27
+ // Try processors
28
+ const processorResult = findElement(model.processors, name);
29
+ if (processorResult.success) {
30
+ return { type: 'processor', element: processorResult.element };
31
+ }
32
+ return null;
33
+ }
34
+ function listAvailableSources(model) {
35
+ const sources = [];
36
+ for (const event of model.events.values()) {
37
+ sources.push(` - Event: "${event.name}"`);
38
+ }
39
+ for (const rm of model.readModels.values()) {
40
+ sources.push(` - ReadModel: "${rm.name}"`);
41
+ }
42
+ return sources;
43
+ }
44
+ function listAvailableTargets(model) {
45
+ const targets = [];
46
+ for (const rm of model.readModels.values()) {
47
+ targets.push(` - ReadModel: "${rm.name}"`);
48
+ }
49
+ for (const screen of model.screens.values()) {
50
+ targets.push(` - Screen: "${screen.name}"`);
51
+ }
52
+ for (const proc of model.processors.values()) {
53
+ targets.push(` - Processor: "${proc.name}"`);
54
+ }
55
+ return targets;
56
+ }
57
+ function flowExists(model, sourceId, targetId) {
58
+ for (const flow of model.flows.values()) {
59
+ if (flow.sourceId === sourceId && flow.targetId === targetId) {
60
+ return true;
61
+ }
62
+ }
63
+ return false;
64
+ }
65
+ export function createFlow(model, filePath, fromName, toName) {
66
+ // Find source element
67
+ const source = findSource(model, fromName);
68
+ if (!source) {
69
+ console.error(`Error: Source not found: "${fromName}"`);
70
+ console.error('Valid sources are events or read models.');
71
+ const available = listAvailableSources(model);
72
+ if (available.length > 0) {
73
+ console.error('Available sources:');
74
+ available.forEach(s => console.error(s));
75
+ }
76
+ process.exit(1);
77
+ }
78
+ // Find target element
79
+ const target = findTarget(model, toName);
80
+ if (!target) {
81
+ console.error(`Error: Target not found: "${toName}"`);
82
+ console.error('Valid targets are read models, screens, or processors.');
83
+ const available = listAvailableTargets(model);
84
+ if (available.length > 0) {
85
+ console.error('Available targets:');
86
+ available.forEach(t => console.error(t));
87
+ }
88
+ process.exit(1);
89
+ }
90
+ // Validate the flow combination
91
+ const flowId = crypto.randomUUID();
92
+ let event;
93
+ if (source.type === 'event' && target.type === 'readModel') {
94
+ // Event → ReadModel (event top → read model bottom)
95
+ if (flowExists(model, source.element.id, target.element.id)) {
96
+ console.error(`Error: Flow already exists from "${source.element.name}" to "${target.element.name}"`);
97
+ process.exit(1);
98
+ }
99
+ event = {
100
+ type: 'EventToReadModelFlowSpecified',
101
+ flowId,
102
+ eventStickyId: source.element.id,
103
+ readModelStickyId: target.element.id,
104
+ sourceHandle: 'top-source',
105
+ targetHandle: 'bottom-target',
106
+ timestamp: Date.now(),
107
+ };
108
+ appendEvent(filePath, event);
109
+ console.log(`Created flow: Event "${source.element.name}" → ReadModel "${target.element.name}"`);
110
+ }
111
+ else if (source.type === 'readModel' && target.type === 'screen') {
112
+ // ReadModel → Screen (read model top → screen bottom)
113
+ if (flowExists(model, source.element.id, target.element.id)) {
114
+ console.error(`Error: Flow already exists from "${source.element.name}" to "${target.element.name}"`);
115
+ process.exit(1);
116
+ }
117
+ event = {
118
+ type: 'ReadModelToScreenFlowSpecified',
119
+ flowId,
120
+ readModelStickyId: source.element.id,
121
+ screenId: target.element.id,
122
+ sourceHandle: 'top-source',
123
+ targetHandle: 'bottom-target',
124
+ timestamp: Date.now(),
125
+ };
126
+ appendEvent(filePath, event);
127
+ console.log(`Created flow: ReadModel "${source.element.name}" → Screen "${target.element.name}"`);
128
+ }
129
+ else if (source.type === 'readModel' && target.type === 'processor') {
130
+ // ReadModel → Processor (read model top → processor bottom)
131
+ if (flowExists(model, source.element.id, target.element.id)) {
132
+ console.error(`Error: Flow already exists from "${source.element.name}" to "${target.element.name}"`);
133
+ process.exit(1);
134
+ }
135
+ event = {
136
+ type: 'ReadModelToProcessorFlowSpecified',
137
+ flowId,
138
+ readModelStickyId: source.element.id,
139
+ processorId: target.element.id,
140
+ sourceHandle: 'top-source',
141
+ targetHandle: 'bottom-target',
142
+ timestamp: Date.now(),
143
+ };
144
+ appendEvent(filePath, event);
145
+ console.log(`Created flow: ReadModel "${source.element.name}" → Processor "${target.element.name}"`);
146
+ }
147
+ else if (source.type === 'event' && target.type === 'screen') {
148
+ console.error('Error: Cannot create flow directly from Event to Screen.');
149
+ console.error('Events flow to ReadModels, which then flow to Screens.');
150
+ console.error('Create a ReadModel first, then:');
151
+ console.error(` 1. eventmodeler create flow --from "${source.element.name}" --to "<ReadModelName>"`);
152
+ console.error(` 2. eventmodeler create flow --from "<ReadModelName>" --to "${target.element.name}"`);
153
+ process.exit(1);
154
+ }
155
+ else if (source.type === 'event' && target.type === 'processor') {
156
+ console.error('Error: Cannot create flow directly from Event to Processor.');
157
+ console.error('Events flow to ReadModels, which then flow to Processors.');
158
+ console.error('Create a ReadModel first, then:');
159
+ console.error(` 1. eventmodeler create flow --from "${source.element.name}" --to "<ReadModelName>"`);
160
+ console.error(` 2. eventmodeler create flow --from "<ReadModelName>" --to "${target.element.name}"`);
161
+ process.exit(1);
162
+ }
163
+ else if (source.type === 'readModel' && target.type === 'readModel') {
164
+ console.error('Error: Cannot create flow from ReadModel to ReadModel.');
165
+ console.error('ReadModels receive data from Events and provide data to Screens or Processors.');
166
+ process.exit(1);
167
+ }
168
+ else {
169
+ console.error(`Error: Invalid flow combination: ${source.type} → ${target.type}`);
170
+ console.error('Valid combinations:');
171
+ console.error(' - Event → ReadModel');
172
+ console.error(' - ReadModel → Screen');
173
+ console.error(' - ReadModel → Processor');
174
+ process.exit(1);
175
+ }
176
+ console.log(`Use 'eventmodeler map fields --flow "${fromName}→${toName}"' to set field mappings.`);
177
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function createStateChangeSlice(model: EventModel, filePath: string, xmlInput: string): void;
@@ -0,0 +1,239 @@
1
+ import { appendEvent } from '../../lib/file-loader.js';
2
+ import { STATE_CHANGE_SLICE, SLICE_GAP, calculateSlicePosition, validateSliceNameUnique, inferFieldMappings, parseFieldsFromXml, getSlicesToShift, fieldInputToField, } from '../../lib/slice-utils.js';
3
+ function getAttr(attrs, name) {
4
+ const match = attrs.match(new RegExp(`${name}="([^"]*)"`));
5
+ return match ? match[1] : undefined;
6
+ }
7
+ function parseXmlInput(xml) {
8
+ // Parse <state-change-slice name="..." after="..." before="...">
9
+ const sliceMatch = xml.match(/<state-change-slice([^>]*)>/);
10
+ if (!sliceMatch) {
11
+ throw new Error('Invalid XML: missing <state-change-slice> tag');
12
+ }
13
+ const sliceName = getAttr(sliceMatch[1], 'name');
14
+ if (!sliceName) {
15
+ throw new Error('Invalid XML: state-change-slice must have a name attribute');
16
+ }
17
+ const after = getAttr(sliceMatch[1], 'after');
18
+ const before = getAttr(sliceMatch[1], 'before');
19
+ // Parse screen
20
+ const screenMatch = xml.match(/<screen([^>]*)>([\s\S]*?)<\/screen>/);
21
+ if (!screenMatch) {
22
+ throw new Error('Invalid XML: missing <screen> element');
23
+ }
24
+ const screenName = getAttr(screenMatch[1], 'name');
25
+ if (!screenName) {
26
+ throw new Error('Invalid XML: screen must have a name attribute');
27
+ }
28
+ const screenFields = parseFieldsFromXml(screenMatch[2]);
29
+ // Parse command
30
+ const commandMatch = xml.match(/<command([^>]*)>([\s\S]*?)<\/command>/);
31
+ if (!commandMatch) {
32
+ throw new Error('Invalid XML: missing <command> element');
33
+ }
34
+ const commandName = getAttr(commandMatch[1], 'name');
35
+ if (!commandName) {
36
+ throw new Error('Invalid XML: command must have a name attribute');
37
+ }
38
+ const commandFields = parseFieldsFromXml(commandMatch[2]);
39
+ // Parse event
40
+ const eventMatch = xml.match(/<event([^>]*)>([\s\S]*?)<\/event>/);
41
+ if (!eventMatch) {
42
+ throw new Error('Invalid XML: missing <event> element');
43
+ }
44
+ const eventName = getAttr(eventMatch[1], 'name');
45
+ if (!eventName) {
46
+ throw new Error('Invalid XML: event must have a name attribute');
47
+ }
48
+ const eventFields = parseFieldsFromXml(eventMatch[2]);
49
+ return {
50
+ sliceName,
51
+ after,
52
+ before,
53
+ screen: { name: screenName, fields: screenFields },
54
+ command: { name: commandName, fields: commandFields },
55
+ event: { name: eventName, fields: eventFields },
56
+ };
57
+ }
58
+ export function createStateChangeSlice(model, filePath, xmlInput) {
59
+ // Parse input
60
+ let input;
61
+ try {
62
+ input = parseXmlInput(xmlInput);
63
+ }
64
+ catch (err) {
65
+ console.error(`Error: ${err.message}`);
66
+ process.exit(1);
67
+ }
68
+ // Validate slice name is unique
69
+ try {
70
+ validateSliceNameUnique(model, input.sliceName);
71
+ }
72
+ catch (err) {
73
+ console.error(`Error: ${err.message}`);
74
+ process.exit(1);
75
+ }
76
+ // Calculate position
77
+ let slicePosition;
78
+ try {
79
+ slicePosition = calculateSlicePosition(model, STATE_CHANGE_SLICE.width, {
80
+ after: input.after,
81
+ before: input.before,
82
+ });
83
+ }
84
+ catch (err) {
85
+ console.error(`Error: ${err.message}`);
86
+ process.exit(1);
87
+ }
88
+ // Handle "before" positioning - shift existing slices
89
+ if (input.before) {
90
+ const shiftAmount = STATE_CHANGE_SLICE.width + SLICE_GAP;
91
+ const toShift = getSlicesToShift(model, slicePosition.x, shiftAmount);
92
+ for (const { sliceId, newX, currentY } of toShift) {
93
+ appendEvent(filePath, {
94
+ type: 'SliceMoved',
95
+ sliceId,
96
+ position: { x: newX, y: currentY },
97
+ timestamp: Date.now(),
98
+ });
99
+ }
100
+ }
101
+ // Generate IDs
102
+ const sliceId = crypto.randomUUID();
103
+ const screenId = crypto.randomUUID();
104
+ const commandId = crypto.randomUUID();
105
+ const eventId = crypto.randomUUID();
106
+ const screenToCommandFlowId = crypto.randomUUID();
107
+ const commandToEventFlowId = crypto.randomUUID();
108
+ // Convert field inputs to fields with IDs
109
+ const screenFields = input.screen.fields.map(fieldInputToField);
110
+ const commandFields = input.command.fields.map(fieldInputToField);
111
+ const eventFields = input.event.fields.map(fieldInputToField);
112
+ // Infer field mappings
113
+ const screenToCommandMappings = inferFieldMappings(screenFields, commandFields);
114
+ const commandToEventMappings = inferFieldMappings(commandFields, eventFields);
115
+ // Calculate absolute positions for components within the slice
116
+ const screenPosition = {
117
+ x: slicePosition.x + STATE_CHANGE_SLICE.screen.offsetX,
118
+ y: slicePosition.y + STATE_CHANGE_SLICE.screen.offsetY,
119
+ };
120
+ const commandPosition = {
121
+ x: slicePosition.x + STATE_CHANGE_SLICE.command.offsetX,
122
+ y: slicePosition.y + STATE_CHANGE_SLICE.command.offsetY,
123
+ };
124
+ const eventPosition = {
125
+ x: slicePosition.x + STATE_CHANGE_SLICE.event.offsetX,
126
+ y: slicePosition.y + STATE_CHANGE_SLICE.event.offsetY,
127
+ };
128
+ // Emit atomic events in sequence
129
+ // 1. Create the slice
130
+ appendEvent(filePath, {
131
+ type: 'SlicePlaced',
132
+ sliceId,
133
+ name: input.sliceName,
134
+ position: slicePosition,
135
+ size: { width: STATE_CHANGE_SLICE.width, height: STATE_CHANGE_SLICE.height },
136
+ timestamp: Date.now(),
137
+ });
138
+ // 2. Create the screen
139
+ appendEvent(filePath, {
140
+ type: 'ScreenPlaced',
141
+ screenId,
142
+ name: input.screen.name,
143
+ position: screenPosition,
144
+ width: STATE_CHANGE_SLICE.screen.width,
145
+ height: STATE_CHANGE_SLICE.screen.height,
146
+ timestamp: Date.now(),
147
+ });
148
+ // 3. Add screen fields
149
+ for (const field of screenFields) {
150
+ appendEvent(filePath, {
151
+ type: 'ScreenFieldAdded',
152
+ screenId,
153
+ field,
154
+ timestamp: Date.now(),
155
+ });
156
+ }
157
+ // 4. Create the command
158
+ appendEvent(filePath, {
159
+ type: 'CommandStickyPlaced',
160
+ commandStickyId: commandId,
161
+ name: input.command.name,
162
+ position: commandPosition,
163
+ width: STATE_CHANGE_SLICE.command.width,
164
+ height: STATE_CHANGE_SLICE.command.height,
165
+ timestamp: Date.now(),
166
+ });
167
+ // 5. Add command fields
168
+ for (const field of commandFields) {
169
+ appendEvent(filePath, {
170
+ type: 'CommandFieldAdded',
171
+ commandStickyId: commandId,
172
+ field,
173
+ timestamp: Date.now(),
174
+ });
175
+ }
176
+ // 6. Create the event
177
+ appendEvent(filePath, {
178
+ type: 'EventStickyPlaced',
179
+ eventStickyId: eventId,
180
+ name: input.event.name,
181
+ position: eventPosition,
182
+ width: STATE_CHANGE_SLICE.event.width,
183
+ height: STATE_CHANGE_SLICE.event.height,
184
+ timestamp: Date.now(),
185
+ });
186
+ // 7. Add event fields
187
+ for (const field of eventFields) {
188
+ appendEvent(filePath, {
189
+ type: 'EventFieldAdded',
190
+ eventStickyId: eventId,
191
+ field,
192
+ timestamp: Date.now(),
193
+ });
194
+ }
195
+ // 8. Create screen -> command flow
196
+ appendEvent(filePath, {
197
+ type: 'ScreenToCommandFlowSpecified',
198
+ flowId: screenToCommandFlowId,
199
+ screenId,
200
+ commandStickyId: commandId,
201
+ sourceHandle: 'bottom-source',
202
+ targetHandle: 'top-target',
203
+ timestamp: Date.now(),
204
+ });
205
+ // 9. Add screen -> command field mappings
206
+ if (screenToCommandMappings.length > 0) {
207
+ appendEvent(filePath, {
208
+ type: 'FieldMappingSpecified',
209
+ flowId: screenToCommandFlowId,
210
+ mappings: screenToCommandMappings,
211
+ timestamp: Date.now(),
212
+ });
213
+ }
214
+ // 10. Create command -> event flow
215
+ appendEvent(filePath, {
216
+ type: 'CommandToEventFlowSpecified',
217
+ flowId: commandToEventFlowId,
218
+ commandStickyId: commandId,
219
+ eventStickyId: eventId,
220
+ sourceHandle: 'bottom-source',
221
+ targetHandle: 'top-target',
222
+ timestamp: Date.now(),
223
+ });
224
+ // 11. Add command -> event field mappings
225
+ if (commandToEventMappings.length > 0) {
226
+ appendEvent(filePath, {
227
+ type: 'FieldMappingSpecified',
228
+ flowId: commandToEventFlowId,
229
+ mappings: commandToEventMappings,
230
+ timestamp: Date.now(),
231
+ });
232
+ }
233
+ console.log(`Created state-change slice "${input.sliceName}"`);
234
+ console.log(` Screen: ${input.screen.name} (${screenFields.length} fields)`);
235
+ console.log(` Command: ${input.command.name} (${commandFields.length} fields)`);
236
+ console.log(` Event: ${input.event.name} (${eventFields.length} fields)`);
237
+ console.log(` Screen -> Command mappings: ${screenToCommandMappings.length}`);
238
+ console.log(` Command -> Event mappings: ${commandToEventMappings.length}`);
239
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function createStateViewSlice(model: EventModel, filePath: string, xmlInput: string): void;