eventmodeler 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -54,17 +54,19 @@ USAGE:
54
54
  eventmodeler <command> [options] Run a CLI command
55
55
 
56
56
  COMMANDS:
57
- list slices List all slices with their status
57
+ list slices [--chapter <name>] List all slices (optionally filtered by chapter)
58
58
  list events List all events
59
59
  list commands List all commands
60
60
  list chapters List all chapters
61
61
  list aggregates List all aggregates
62
62
  list actors List all actors
63
63
 
64
- show slice <name> Show detailed XML view of a slice
64
+ show slice <name> Show slice with components, flow annotations, and
65
+ inbound/outbound dependencies
65
66
  show event <name> Show detailed XML view of an event
66
67
  show command <name> Show detailed XML view of a command
67
- show chapter <name> Show chapter with its slices
68
+ show chapter <name> Show chapter with slices, flow graph, and
69
+ external dependencies
68
70
  show completeness <name> Show field mapping completeness for any element
69
71
  show model-completeness Show completeness of all flows in the model
70
72
  show aggregate-completeness <name>
@@ -78,18 +80,18 @@ COMMANDS:
78
80
 
79
81
  add scenario --slice <name> --json|--xml <data>
80
82
  Add a scenario to a slice
81
- add field --command|--event|--read-model <name> --json|--xml <data>
83
+ add field --command|--event|--read-model|--screen|--processor <name> --json|--xml <data>
82
84
  Add a field to an entity
83
85
 
84
86
  remove scenario <name> [--slice <name>]
85
87
  Remove a scenario by name
86
- remove field --command|--event|--read-model <name> --field <name>
88
+ remove field --command|--event|--read-model|--screen|--processor <name> --field <name>
87
89
  Remove a field from an entity
88
90
 
89
91
  map fields --flow <source→target> --json|--xml <mappings>
90
92
  Set field mappings on a flow
91
93
 
92
- update field --command|--event|--read-model <name> --field <name> [--optional true|false] [--generated true|false]
94
+ update field --command|--event|--read-model|--screen|--processor <name> --field <name> [--optional true|false] [--generated true|false]
93
95
  Update field properties
94
96
 
95
97
  create state-change-slice --xml <data>
@@ -134,6 +136,7 @@ EXAMPLES:
134
136
  async function main() {
135
137
  let fileArg = null;
136
138
  let formatArg = null;
139
+ let chapterArg = null;
137
140
  const filteredArgs = [];
138
141
  for (let i = 0; i < args.length; i++) {
139
142
  if (args[i] === '-f' || args[i] === '--file') {
@@ -142,6 +145,9 @@ async function main() {
142
145
  else if (args[i] === '--format') {
143
146
  formatArg = args[++i];
144
147
  }
148
+ else if (args[i] === '--chapter') {
149
+ chapterArg = args[++i];
150
+ }
145
151
  else if (args[i] === '-h' || args[i] === '--help') {
146
152
  printHelp();
147
153
  process.exit(0);
@@ -183,7 +189,7 @@ async function main() {
183
189
  case 'list':
184
190
  switch (subcommand) {
185
191
  case 'slices':
186
- listSlices(model, format);
192
+ listSlices(model, format, chapterArg ?? undefined);
187
193
  break;
188
194
  case 'events':
189
195
  listEvents(model, format);
@@ -326,15 +332,17 @@ async function main() {
326
332
  const commandArg = getNamedArg(filteredArgs, '--command');
327
333
  const eventArg = getNamedArg(filteredArgs, '--event');
328
334
  const readModelArg = getNamedArg(filteredArgs, '--read-model');
335
+ const screenArg = getNamedArg(filteredArgs, '--screen');
336
+ const processorArg = getNamedArg(filteredArgs, '--processor');
329
337
  const jsonArg = getNamedArg(filteredArgs, '--json');
330
338
  const xmlArg = getNamedArg(filteredArgs, '--xml');
331
339
  const inputData = jsonArg ?? xmlArg;
332
340
  if (!inputData) {
333
341
  console.error('Error: --json or --xml is required');
334
- console.error('Usage: eventmodeler add field --command|--event|--read-model <name> --json|--xml <data>');
342
+ console.error('Usage: eventmodeler add field --command|--event|--read-model|--screen|--processor <name> --json|--xml <data>');
335
343
  process.exit(1);
336
344
  }
337
- addField(model, filePath, { command: commandArg, event: eventArg, readModel: readModelArg }, inputData);
345
+ addField(model, filePath, { command: commandArg, event: eventArg, readModel: readModelArg, screen: screenArg, processor: processorArg }, inputData);
338
346
  break;
339
347
  }
340
348
  default:
@@ -359,13 +367,15 @@ async function main() {
359
367
  const commandArg = getNamedArg(filteredArgs, '--command');
360
368
  const eventArg = getNamedArg(filteredArgs, '--event');
361
369
  const readModelArg = getNamedArg(filteredArgs, '--read-model');
370
+ const screenArg = getNamedArg(filteredArgs, '--screen');
371
+ const processorArg = getNamedArg(filteredArgs, '--processor');
362
372
  const fieldArg = getNamedArg(filteredArgs, '--field');
363
373
  if (!fieldArg) {
364
374
  console.error('Error: --field is required');
365
- console.error('Usage: eventmodeler remove field --command|--event|--read-model <name> --field <field-name>');
375
+ console.error('Usage: eventmodeler remove field --command|--event|--read-model|--screen|--processor <name> --field <field-name>');
366
376
  process.exit(1);
367
377
  }
368
- removeField(model, filePath, { command: commandArg, event: eventArg, readModel: readModelArg }, fieldArg);
378
+ removeField(model, filePath, { command: commandArg, event: eventArg, readModel: readModelArg, screen: screenArg, processor: processorArg }, fieldArg);
369
379
  break;
370
380
  }
371
381
  default:
@@ -406,13 +416,15 @@ async function main() {
406
416
  const commandArg = getNamedArg(filteredArgs, '--command');
407
417
  const eventArg = getNamedArg(filteredArgs, '--event');
408
418
  const readModelArg = getNamedArg(filteredArgs, '--read-model');
419
+ const screenArg = getNamedArg(filteredArgs, '--screen');
420
+ const processorArg = getNamedArg(filteredArgs, '--processor');
409
421
  const fieldArg = getNamedArg(filteredArgs, '--field');
410
422
  const optionalArg = getNamedArg(filteredArgs, '--optional');
411
423
  const generatedArg = getNamedArg(filteredArgs, '--generated');
412
424
  const typeArg = getNamedArg(filteredArgs, '--type');
413
425
  if (!fieldArg) {
414
426
  console.error('Error: --field is required');
415
- console.error('Usage: eventmodeler update field --command|--event|--read-model <name> --field <field-name> [--optional true|false] [--generated true|false]');
427
+ console.error('Usage: eventmodeler update field --command|--event|--read-model|--screen|--processor <name> --field <field-name> [--optional true|false] [--generated true|false]');
416
428
  process.exit(1);
417
429
  }
418
430
  const updates = {};
@@ -429,7 +441,7 @@ async function main() {
429
441
  console.error('Error: Must specify at least one update (--optional, --generated, or --type)');
430
442
  process.exit(1);
431
443
  }
432
- updateField(model, filePath, { command: commandArg, event: eventArg, readModel: readModelArg }, fieldArg, updates);
444
+ updateField(model, filePath, { command: commandArg, event: eventArg, readModel: readModelArg, screen: screenArg, processor: processorArg }, fieldArg, updates);
433
445
  break;
434
446
  }
435
447
  default:
@@ -0,0 +1,13 @@
1
+ import type { Chapter, EventModel, Slice } from '../types.js';
2
+ export interface ChapterWithHierarchy {
3
+ id: string;
4
+ name: string;
5
+ parent?: ChapterWithHierarchy;
6
+ }
7
+ export declare function findParentChapter(model: EventModel, chapter: Chapter): Chapter | null;
8
+ export declare function getChapterHierarchy(model: EventModel, chapter: Chapter): ChapterWithHierarchy;
9
+ export declare function hierarchyToArray(hierarchy: ChapterWithHierarchy): Array<{
10
+ id: string;
11
+ name: string;
12
+ }>;
13
+ export declare function findChapterForSlice(model: EventModel, slice: Slice): Chapter | null;
@@ -0,0 +1,71 @@
1
+ // Check if two chapters have X-overlap
2
+ function hasXOverlap(parent, child) {
3
+ const parentLeft = parent.position.x;
4
+ const parentRight = parent.position.x + parent.size.width;
5
+ const childLeft = child.position.x;
6
+ const childRight = child.position.x + child.size.width;
7
+ return childLeft < parentRight && childRight > parentLeft;
8
+ }
9
+ // Find the immediate parent of a chapter (nearest chapter above with X-overlap)
10
+ export function findParentChapter(model, chapter) {
11
+ let bestParent = null;
12
+ let bestParentY = -Infinity;
13
+ for (const candidate of model.chapters.values()) {
14
+ if (candidate.id === chapter.id)
15
+ continue;
16
+ // Parent must be above (lower y value)
17
+ if (candidate.position.y >= chapter.position.y)
18
+ continue;
19
+ // Must have X-overlap
20
+ if (!hasXOverlap(candidate, chapter))
21
+ continue;
22
+ // Take the closest parent (highest y value that's still above)
23
+ if (candidate.position.y > bestParentY) {
24
+ bestParent = candidate;
25
+ bestParentY = candidate.position.y;
26
+ }
27
+ }
28
+ return bestParent;
29
+ }
30
+ // Build full hierarchy for a chapter (from root to this chapter)
31
+ export function getChapterHierarchy(model, chapter) {
32
+ const parent = findParentChapter(model, chapter);
33
+ const result = {
34
+ id: chapter.id,
35
+ name: chapter.name,
36
+ };
37
+ if (parent) {
38
+ result.parent = getChapterHierarchy(model, parent);
39
+ }
40
+ return result;
41
+ }
42
+ // Convert hierarchy to array (root first)
43
+ export function hierarchyToArray(hierarchy) {
44
+ const result = [];
45
+ function collect(h) {
46
+ if (h.parent)
47
+ collect(h.parent);
48
+ result.push({ id: h.id, name: h.name });
49
+ }
50
+ collect(hierarchy);
51
+ return result;
52
+ }
53
+ // Find which chapter contains a slice (based on horizontal center)
54
+ // Returns the deepest (highest y-value) chapter that contains the slice
55
+ export function findChapterForSlice(model, slice) {
56
+ const sliceCenterX = slice.position.x + slice.size.width / 2;
57
+ let bestChapter = null;
58
+ let bestY = -Infinity;
59
+ for (const chapter of model.chapters.values()) {
60
+ const chapterLeft = chapter.position.x;
61
+ const chapterRight = chapter.position.x + chapter.size.width;
62
+ if (sliceCenterX >= chapterLeft && sliceCenterX <= chapterRight) {
63
+ // Take the deepest (highest y) chapter
64
+ if (chapter.position.y > bestY) {
65
+ bestChapter = chapter;
66
+ bestY = chapter.position.y;
67
+ }
68
+ }
69
+ }
70
+ return bestChapter;
71
+ }
@@ -0,0 +1,52 @@
1
+ import type { EventModel, Slice } from '../types.js';
2
+ export type ElementType = 'screen' | 'command' | 'event' | 'read-model' | 'processor';
3
+ export interface ElementInfo {
4
+ id: string;
5
+ name: string;
6
+ type: ElementType;
7
+ }
8
+ export interface FieldMappingInfo {
9
+ from: string;
10
+ to: string;
11
+ }
12
+ export interface FlowInfo {
13
+ flowId: string;
14
+ flowType: string;
15
+ source: ElementInfo;
16
+ target: ElementInfo;
17
+ fieldMappings: FieldMappingInfo[];
18
+ }
19
+ export interface SliceFlowInfo extends FlowInfo {
20
+ sourceSlice?: {
21
+ id: string;
22
+ name: string;
23
+ };
24
+ targetSlice?: {
25
+ id: string;
26
+ name: string;
27
+ };
28
+ }
29
+ export declare function getElementInfo(model: EventModel, id: string): ElementInfo | null;
30
+ export declare function getSliceComponentIds(model: EventModel, slice: Slice): Set<string>;
31
+ export declare function findSliceForComponent(model: EventModel, componentId: string): Slice | null;
32
+ export declare function getInboundFlows(model: EventModel, slice: Slice): SliceFlowInfo[];
33
+ export declare function getOutboundFlows(model: EventModel, slice: Slice): SliceFlowInfo[];
34
+ export declare function getInternalFlows(model: EventModel, slice: Slice): FlowInfo[];
35
+ export declare function getFlowsForElement(model: EventModel, elementId: string): {
36
+ incoming: FlowInfo[];
37
+ outgoing: FlowInfo[];
38
+ };
39
+ export interface SliceToSliceFlow {
40
+ fromSlice: {
41
+ id: string;
42
+ name: string;
43
+ };
44
+ toSlice: {
45
+ id: string;
46
+ name: string;
47
+ };
48
+ flows: SliceFlowInfo[];
49
+ }
50
+ export declare function findSliceToSliceFlows(model: EventModel, slices: Slice[]): SliceToSliceFlow[];
51
+ export declare function findChapterInboundFlows(model: EventModel, slices: Slice[]): SliceFlowInfo[];
52
+ export declare function findChapterOutboundFlows(model: EventModel, slices: Slice[]): SliceFlowInfo[];
@@ -0,0 +1,305 @@
1
+ // Get element info (name and type) by ID
2
+ export function getElementInfo(model, id) {
3
+ const screen = model.screens.get(id);
4
+ if (screen)
5
+ return { id, name: screen.name, type: 'screen' };
6
+ const command = model.commands.get(id);
7
+ if (command)
8
+ return { id, name: command.name, type: 'command' };
9
+ const event = model.events.get(id);
10
+ if (event)
11
+ return { id, name: event.name, type: 'event' };
12
+ const readModel = model.readModels.get(id);
13
+ if (readModel)
14
+ return { id, name: readModel.name, type: 'read-model' };
15
+ const processor = model.processors.get(id);
16
+ if (processor)
17
+ return { id, name: processor.name, type: 'processor' };
18
+ return null;
19
+ }
20
+ // Get fields for an element
21
+ function getElementFields(model, id) {
22
+ return (model.screens.get(id)?.fields ??
23
+ model.commands.get(id)?.fields ??
24
+ model.events.get(id)?.fields ??
25
+ model.readModels.get(id)?.fields ??
26
+ model.processors.get(id)?.fields ??
27
+ []);
28
+ }
29
+ // Find a field by ID in a fields array (recursive for subfields)
30
+ function findFieldById(fields, fieldId) {
31
+ for (const field of fields) {
32
+ if (field.id === fieldId)
33
+ return field;
34
+ if (field.subfields) {
35
+ const found = findFieldById(field.subfields, fieldId);
36
+ if (found)
37
+ return found;
38
+ }
39
+ }
40
+ return undefined;
41
+ }
42
+ // Enrich field mappings with field names
43
+ function enrichFieldMappings(model, flow) {
44
+ const sourceFields = getElementFields(model, flow.sourceId);
45
+ const targetFields = getElementFields(model, flow.targetId);
46
+ return flow.fieldMappings.map(mapping => {
47
+ const sourceField = findFieldById(sourceFields, mapping.sourceFieldId);
48
+ const targetField = findFieldById(targetFields, mapping.targetFieldId);
49
+ return {
50
+ from: sourceField?.name ?? 'unknown',
51
+ to: targetField?.name ?? 'unknown',
52
+ };
53
+ });
54
+ }
55
+ // Check if an element's center point is within a slice's bounds
56
+ function isElementInSlice(slice, position, width, height) {
57
+ const centerX = position.x + width / 2;
58
+ const centerY = position.y + height / 2;
59
+ return (centerX >= slice.position.x &&
60
+ centerX <= slice.position.x + slice.size.width &&
61
+ centerY >= slice.position.y &&
62
+ centerY <= slice.position.y + slice.size.height);
63
+ }
64
+ // Get all component IDs in a slice
65
+ export function getSliceComponentIds(model, slice) {
66
+ const ids = new Set();
67
+ for (const screen of model.screens.values()) {
68
+ if (isElementInSlice(slice, screen.position, screen.width, screen.height)) {
69
+ ids.add(screen.id);
70
+ }
71
+ }
72
+ for (const command of model.commands.values()) {
73
+ if (isElementInSlice(slice, command.position, command.width, command.height)) {
74
+ ids.add(command.id);
75
+ }
76
+ }
77
+ for (const event of model.events.values()) {
78
+ if (isElementInSlice(slice, event.position, event.width, event.height)) {
79
+ ids.add(event.id);
80
+ }
81
+ }
82
+ for (const readModel of model.readModels.values()) {
83
+ if (isElementInSlice(slice, readModel.position, readModel.width, readModel.height)) {
84
+ ids.add(readModel.id);
85
+ }
86
+ }
87
+ for (const processor of model.processors.values()) {
88
+ if (isElementInSlice(slice, processor.position, processor.width, processor.height)) {
89
+ ids.add(processor.id);
90
+ }
91
+ }
92
+ return ids;
93
+ }
94
+ // Find which slice contains a component
95
+ export function findSliceForComponent(model, componentId) {
96
+ // Get element position and size
97
+ let position = null;
98
+ let width = 0;
99
+ let height = 0;
100
+ const screen = model.screens.get(componentId);
101
+ if (screen) {
102
+ position = screen.position;
103
+ width = screen.width;
104
+ height = screen.height;
105
+ }
106
+ const command = model.commands.get(componentId);
107
+ if (command) {
108
+ position = command.position;
109
+ width = command.width;
110
+ height = command.height;
111
+ }
112
+ const event = model.events.get(componentId);
113
+ if (event) {
114
+ position = event.position;
115
+ width = event.width;
116
+ height = event.height;
117
+ }
118
+ const readModel = model.readModels.get(componentId);
119
+ if (readModel) {
120
+ position = readModel.position;
121
+ width = readModel.width;
122
+ height = readModel.height;
123
+ }
124
+ const processor = model.processors.get(componentId);
125
+ if (processor) {
126
+ position = processor.position;
127
+ width = processor.width;
128
+ height = processor.height;
129
+ }
130
+ if (!position)
131
+ return null;
132
+ for (const slice of model.slices.values()) {
133
+ if (isElementInSlice(slice, position, width, height)) {
134
+ return slice;
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+ // Build FlowInfo from a Flow
140
+ function buildFlowInfo(model, flow) {
141
+ const source = getElementInfo(model, flow.sourceId);
142
+ const target = getElementInfo(model, flow.targetId);
143
+ if (!source || !target)
144
+ return null;
145
+ return {
146
+ flowId: flow.id,
147
+ flowType: flow.flowType,
148
+ source,
149
+ target,
150
+ fieldMappings: enrichFieldMappings(model, flow),
151
+ };
152
+ }
153
+ // Build SliceFlowInfo from a Flow (includes slice info for source/target)
154
+ function buildSliceFlowInfo(model, flow) {
155
+ const base = buildFlowInfo(model, flow);
156
+ if (!base)
157
+ return null;
158
+ const sourceSlice = findSliceForComponent(model, flow.sourceId);
159
+ const targetSlice = findSliceForComponent(model, flow.targetId);
160
+ return {
161
+ ...base,
162
+ sourceSlice: sourceSlice ? { id: sourceSlice.id, name: sourceSlice.name } : undefined,
163
+ targetSlice: targetSlice ? { id: targetSlice.id, name: targetSlice.name } : undefined,
164
+ };
165
+ }
166
+ // Get flows entering a slice (target in slice, source outside)
167
+ export function getInboundFlows(model, slice) {
168
+ const componentIds = getSliceComponentIds(model, slice);
169
+ const flows = [];
170
+ for (const flow of model.flows.values()) {
171
+ if (componentIds.has(flow.targetId) && !componentIds.has(flow.sourceId)) {
172
+ const flowInfo = buildSliceFlowInfo(model, flow);
173
+ if (flowInfo)
174
+ flows.push(flowInfo);
175
+ }
176
+ }
177
+ return flows;
178
+ }
179
+ // Get flows leaving a slice (source in slice, target outside)
180
+ export function getOutboundFlows(model, slice) {
181
+ const componentIds = getSliceComponentIds(model, slice);
182
+ const flows = [];
183
+ for (const flow of model.flows.values()) {
184
+ if (componentIds.has(flow.sourceId) && !componentIds.has(flow.targetId)) {
185
+ const flowInfo = buildSliceFlowInfo(model, flow);
186
+ if (flowInfo)
187
+ flows.push(flowInfo);
188
+ }
189
+ }
190
+ return flows;
191
+ }
192
+ // Get flows within a slice (both source and target in slice)
193
+ export function getInternalFlows(model, slice) {
194
+ const componentIds = getSliceComponentIds(model, slice);
195
+ const flows = [];
196
+ for (const flow of model.flows.values()) {
197
+ if (componentIds.has(flow.sourceId) && componentIds.has(flow.targetId)) {
198
+ const flowInfo = buildFlowInfo(model, flow);
199
+ if (flowInfo)
200
+ flows.push(flowInfo);
201
+ }
202
+ }
203
+ return flows;
204
+ }
205
+ // Get all flows for a specific element (both directions)
206
+ export function getFlowsForElement(model, elementId) {
207
+ const incoming = [];
208
+ const outgoing = [];
209
+ for (const flow of model.flows.values()) {
210
+ if (flow.targetId === elementId) {
211
+ const flowInfo = buildFlowInfo(model, flow);
212
+ if (flowInfo)
213
+ incoming.push(flowInfo);
214
+ }
215
+ if (flow.sourceId === elementId) {
216
+ const flowInfo = buildFlowInfo(model, flow);
217
+ if (flowInfo)
218
+ outgoing.push(flowInfo);
219
+ }
220
+ }
221
+ return { incoming, outgoing };
222
+ }
223
+ export function findSliceToSliceFlows(model, slices) {
224
+ const sliceMap = new Map(slices.map(s => [s.id, s]));
225
+ const sliceComponentMap = new Map();
226
+ // Build component ID sets for each slice
227
+ for (const slice of slices) {
228
+ sliceComponentMap.set(slice.id, getSliceComponentIds(model, slice));
229
+ }
230
+ // Group flows by slice pair
231
+ const flowsBySlicePair = new Map();
232
+ for (const flow of model.flows.values()) {
233
+ let sourceSliceId = null;
234
+ let targetSliceId = null;
235
+ // Find which slice contains source and target
236
+ for (const [sliceId, componentIds] of sliceComponentMap.entries()) {
237
+ if (componentIds.has(flow.sourceId))
238
+ sourceSliceId = sliceId;
239
+ if (componentIds.has(flow.targetId))
240
+ targetSliceId = sliceId;
241
+ }
242
+ // Only include flows between different slices in our set
243
+ if (sourceSliceId && targetSliceId && sourceSliceId !== targetSliceId) {
244
+ const key = `${sourceSliceId}:${targetSliceId}`;
245
+ const flowInfo = buildSliceFlowInfo(model, flow);
246
+ if (flowInfo) {
247
+ if (!flowsBySlicePair.has(key)) {
248
+ flowsBySlicePair.set(key, []);
249
+ }
250
+ flowsBySlicePair.get(key).push(flowInfo);
251
+ }
252
+ }
253
+ }
254
+ // Convert to result format
255
+ const result = [];
256
+ for (const [key, flows] of flowsBySlicePair.entries()) {
257
+ const [fromId, toId] = key.split(':');
258
+ const fromSlice = sliceMap.get(fromId);
259
+ const toSlice = sliceMap.get(toId);
260
+ if (fromSlice && toSlice) {
261
+ result.push({
262
+ fromSlice: { id: fromSlice.id, name: fromSlice.name },
263
+ toSlice: { id: toSlice.id, name: toSlice.name },
264
+ flows,
265
+ });
266
+ }
267
+ }
268
+ return result;
269
+ }
270
+ // Find flows entering a chapter (target in chapter slices, source outside)
271
+ export function findChapterInboundFlows(model, slices) {
272
+ const chapterComponentIds = new Set();
273
+ for (const slice of slices) {
274
+ for (const id of getSliceComponentIds(model, slice)) {
275
+ chapterComponentIds.add(id);
276
+ }
277
+ }
278
+ const flows = [];
279
+ for (const flow of model.flows.values()) {
280
+ if (chapterComponentIds.has(flow.targetId) && !chapterComponentIds.has(flow.sourceId)) {
281
+ const flowInfo = buildSliceFlowInfo(model, flow);
282
+ if (flowInfo)
283
+ flows.push(flowInfo);
284
+ }
285
+ }
286
+ return flows;
287
+ }
288
+ // Find flows leaving a chapter (source in chapter slices, target outside)
289
+ export function findChapterOutboundFlows(model, slices) {
290
+ const chapterComponentIds = new Set();
291
+ for (const slice of slices) {
292
+ for (const id of getSliceComponentIds(model, slice)) {
293
+ chapterComponentIds.add(id);
294
+ }
295
+ }
296
+ const flows = [];
297
+ for (const flow of model.flows.values()) {
298
+ if (chapterComponentIds.has(flow.sourceId) && !chapterComponentIds.has(flow.targetId)) {
299
+ const flowInfo = buildSliceFlowInfo(model, flow);
300
+ if (flowInfo)
301
+ flows.push(flowInfo);
302
+ }
303
+ }
304
+ return flows;
305
+ }
@@ -91,6 +91,17 @@ export function inferFieldMappings(sourceFields, targetFields) {
91
91
  }
92
92
  return mappings;
93
93
  }
94
+ const VALID_FIELD_TYPES = ['UUID', 'Boolean', 'Double', 'Decimal', 'Date', 'DateTime', 'Long', 'Int', 'String', 'Custom'];
95
+ function validateFieldType(type, fieldName) {
96
+ if (!VALID_FIELD_TYPES.includes(type)) {
97
+ const typeLower = type.toLowerCase();
98
+ let hint = '';
99
+ if (typeLower === 'list' || typeLower === 'array') {
100
+ hint = `\n\nHint: To make a field a list/array, use isList="true" with a valid type:\n <field name="${fieldName}" type="String" isList="true"/>`;
101
+ }
102
+ throw new Error(`Invalid field type "${type}" for field "${fieldName}". Valid types: ${VALID_FIELD_TYPES.join(', ')}${hint}`);
103
+ }
104
+ }
94
105
  function getAttr(attrs, name) {
95
106
  const match = attrs.match(new RegExp(`${name}="([^"]*)"`));
96
107
  return match ? match[1] : undefined;
@@ -106,13 +117,15 @@ export function parseFieldsFromXml(xml) {
106
117
  const type = getAttr(attrs, 'type');
107
118
  if (!name || !type)
108
119
  continue;
120
+ // Validate the field type
121
+ validateFieldType(type, name);
109
122
  const field = {
110
123
  name,
111
124
  type,
112
- isList: getAttr(attrs, 'list') === 'true',
113
- isGenerated: getAttr(attrs, 'generated') === 'true',
114
- isOptional: getAttr(attrs, 'optional') === 'true',
115
- isUserInput: getAttr(attrs, 'user-input') === 'true',
125
+ isList: getAttr(attrs, 'isList') === 'true',
126
+ isGenerated: getAttr(attrs, 'isGenerated') === 'true',
127
+ isOptional: getAttr(attrs, 'isOptional') === 'true',
128
+ isUserInput: getAttr(attrs, 'isUserInput') === 'true',
116
129
  };
117
130
  // Parse nested subfields for Custom type
118
131
  if (type === 'Custom' && content) {
@@ -3,4 +3,6 @@ export declare function addField(model: EventModel, filePath: string, options: {
3
3
  command?: string;
4
4
  event?: string;
5
5
  readModel?: string;
6
+ screen?: string;
7
+ processor?: string;
6
8
  }, input: string): void;