eventmodeler 0.2.3 → 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.
Files changed (32) 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/projection.js +132 -0
  7. package/dist/slices/add-field/index.js +4 -33
  8. package/dist/slices/add-scenario/index.js +7 -74
  9. package/dist/slices/create-automation-slice/index.d.ts +2 -0
  10. package/dist/slices/create-automation-slice/index.js +217 -0
  11. package/dist/slices/create-flow/index.d.ts +2 -0
  12. package/dist/slices/create-flow/index.js +177 -0
  13. package/dist/slices/create-state-change-slice/index.d.ts +2 -0
  14. package/dist/slices/create-state-change-slice/index.js +239 -0
  15. package/dist/slices/create-state-view-slice/index.d.ts +2 -0
  16. package/dist/slices/create-state-view-slice/index.js +120 -0
  17. package/dist/slices/list-chapters/index.js +2 -2
  18. package/dist/slices/list-commands/index.js +2 -2
  19. package/dist/slices/list-events/index.js +3 -2
  20. package/dist/slices/list-slices/index.js +2 -2
  21. package/dist/slices/mark-slice-status/index.js +2 -11
  22. package/dist/slices/remove-field/index.js +4 -33
  23. package/dist/slices/remove-scenario/index.js +45 -11
  24. package/dist/slices/show-actor/index.js +2 -11
  25. package/dist/slices/show-aggregate-completeness/index.js +2 -11
  26. package/dist/slices/show-chapter/index.js +6 -14
  27. package/dist/slices/show-command/index.js +4 -12
  28. package/dist/slices/show-completeness/index.js +108 -19
  29. package/dist/slices/show-event/index.js +4 -12
  30. package/dist/slices/show-slice/index.js +14 -17
  31. package/dist/slices/update-field/index.js +4 -33
  32. package/package.json +1 -1
@@ -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 slices = [...model.slices.values()];
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 = findEventByName(model, g.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 = findCommandByName(model, scenarioInput.when.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 = findEventByName(model, e.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 = findReadModelByName(model, scenarioInput.then.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 = findEventByName(model, g.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
- }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function createAutomationSlice(model: EventModel, filePath: string, xmlInput: string): void;
@@ -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;