eventmodeler 0.3.1 → 0.3.3
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 +5 -3
- package/dist/lib/flow-utils.d.ts +1 -0
- package/dist/lib/flow-utils.js +63 -20
- package/dist/slices/codegen-slice/index.js +34 -4
- package/dist/slices/show-slice/index.js +35 -90
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -282,14 +282,16 @@ async function main() {
|
|
|
282
282
|
break;
|
|
283
283
|
case 'mark': {
|
|
284
284
|
// mark <slice-name> <status> - status is last arg, slice name is everything before
|
|
285
|
+
// Note: subcommand contains first part of slice name, remainingArgs has the rest
|
|
285
286
|
const validStatuses = ['created', 'in-progress', 'blocked', 'done'];
|
|
286
|
-
const
|
|
287
|
-
|
|
287
|
+
const allArgs = subcommand ? [subcommand, ...remainingArgs] : remainingArgs;
|
|
288
|
+
const lastArg = allArgs[allArgs.length - 1];
|
|
289
|
+
if (allArgs.length < 2 || !validStatuses.includes(lastArg)) {
|
|
288
290
|
console.error('Usage: eventmodeler mark <slice-name> <status>');
|
|
289
291
|
console.error('Valid statuses: created, in-progress, blocked, done');
|
|
290
292
|
process.exit(1);
|
|
291
293
|
}
|
|
292
|
-
const sliceName =
|
|
294
|
+
const sliceName = allArgs.slice(0, -1).join(' ');
|
|
293
295
|
const status = lastArg;
|
|
294
296
|
markSliceStatus(model, filePath, sliceName, status);
|
|
295
297
|
break;
|
package/dist/lib/flow-utils.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface SliceFlowInfo extends FlowInfo {
|
|
|
26
26
|
name: string;
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
export declare function resolveToCanonical(model: EventModel, id: string): string;
|
|
29
30
|
export declare function getElementInfo(model: EventModel, id: string): ElementInfo | null;
|
|
30
31
|
export declare function getSliceComponentIds(model: EventModel, slice: Slice): Set<string>;
|
|
31
32
|
export declare function findSliceForComponent(model: EventModel, componentId: string): Slice | null;
|
package/dist/lib/flow-utils.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// Resolve a linked copy to its canonical original, or return the ID unchanged if not a copy
|
|
2
|
+
export function resolveToCanonical(model, id) {
|
|
3
|
+
const screen = model.screens.get(id);
|
|
4
|
+
if (screen?.originalNodeId)
|
|
5
|
+
return screen.originalNodeId;
|
|
6
|
+
const event = model.events.get(id);
|
|
7
|
+
if (event?.originalNodeId)
|
|
8
|
+
return event.originalNodeId;
|
|
9
|
+
const readModel = model.readModels.get(id);
|
|
10
|
+
if (readModel?.originalNodeId)
|
|
11
|
+
return readModel.originalNodeId;
|
|
12
|
+
return id;
|
|
13
|
+
}
|
|
1
14
|
// Get element info (name and type) by ID
|
|
2
15
|
export function getElementInfo(model, id) {
|
|
3
16
|
const screen = model.screens.get(id);
|
|
@@ -61,11 +74,12 @@ function isElementInSlice(slice, position, width, height) {
|
|
|
61
74
|
centerY >= slice.position.y &&
|
|
62
75
|
centerY <= slice.position.y + slice.size.height);
|
|
63
76
|
}
|
|
64
|
-
// Get all component IDs in a slice
|
|
77
|
+
// Get all component IDs in a slice (excludes linked copies - they are UI-only)
|
|
65
78
|
export function getSliceComponentIds(model, slice) {
|
|
66
79
|
const ids = new Set();
|
|
80
|
+
// Only include canonical elements (not linked copies) that are spatially in the slice
|
|
67
81
|
for (const screen of model.screens.values()) {
|
|
68
|
-
if (isElementInSlice(slice, screen.position, screen.width, screen.height)) {
|
|
82
|
+
if (!screen.originalNodeId && isElementInSlice(slice, screen.position, screen.width, screen.height)) {
|
|
69
83
|
ids.add(screen.id);
|
|
70
84
|
}
|
|
71
85
|
}
|
|
@@ -75,12 +89,12 @@ export function getSliceComponentIds(model, slice) {
|
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
for (const event of model.events.values()) {
|
|
78
|
-
if (isElementInSlice(slice, event.position, event.width, event.height)) {
|
|
92
|
+
if (!event.originalNodeId && isElementInSlice(slice, event.position, event.width, event.height)) {
|
|
79
93
|
ids.add(event.id);
|
|
80
94
|
}
|
|
81
95
|
}
|
|
82
96
|
for (const readModel of model.readModels.values()) {
|
|
83
|
-
if (isElementInSlice(slice, readModel.position, readModel.width, readModel.height)) {
|
|
97
|
+
if (!readModel.originalNodeId && isElementInSlice(slice, readModel.position, readModel.width, readModel.height)) {
|
|
84
98
|
ids.add(readModel.id);
|
|
85
99
|
}
|
|
86
100
|
}
|
|
@@ -136,10 +150,13 @@ export function findSliceForComponent(model, componentId) {
|
|
|
136
150
|
}
|
|
137
151
|
return null;
|
|
138
152
|
}
|
|
139
|
-
// Build FlowInfo from a Flow
|
|
153
|
+
// Build FlowInfo from a Flow (resolves linked copies to their canonical originals)
|
|
140
154
|
function buildFlowInfo(model, flow) {
|
|
141
|
-
|
|
142
|
-
const
|
|
155
|
+
// Resolve linked copies to canonical originals
|
|
156
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
157
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
158
|
+
const source = getElementInfo(model, canonicalSourceId);
|
|
159
|
+
const target = getElementInfo(model, canonicalTargetId);
|
|
143
160
|
if (!source || !target)
|
|
144
161
|
return null;
|
|
145
162
|
return {
|
|
@@ -150,13 +167,16 @@ function buildFlowInfo(model, flow) {
|
|
|
150
167
|
fieldMappings: enrichFieldMappings(model, flow),
|
|
151
168
|
};
|
|
152
169
|
}
|
|
153
|
-
// Build SliceFlowInfo from a Flow (includes slice info for source/target)
|
|
170
|
+
// Build SliceFlowInfo from a Flow (includes slice info for source/target, resolves linked copies)
|
|
154
171
|
function buildSliceFlowInfo(model, flow) {
|
|
155
172
|
const base = buildFlowInfo(model, flow);
|
|
156
173
|
if (!base)
|
|
157
174
|
return null;
|
|
158
|
-
|
|
159
|
-
const
|
|
175
|
+
// Resolve linked copies to canonical originals for slice lookup
|
|
176
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
177
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
178
|
+
const sourceSlice = findSliceForComponent(model, canonicalSourceId);
|
|
179
|
+
const targetSlice = findSliceForComponent(model, canonicalTargetId);
|
|
160
180
|
return {
|
|
161
181
|
...base,
|
|
162
182
|
sourceSlice: sourceSlice ? { id: sourceSlice.id, name: sourceSlice.name } : undefined,
|
|
@@ -168,7 +188,10 @@ export function getInboundFlows(model, slice) {
|
|
|
168
188
|
const componentIds = getSliceComponentIds(model, slice);
|
|
169
189
|
const flows = [];
|
|
170
190
|
for (const flow of model.flows.values()) {
|
|
171
|
-
|
|
191
|
+
// Resolve linked copies to canonical originals for containment check
|
|
192
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
193
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
194
|
+
if (componentIds.has(canonicalTargetId) && !componentIds.has(canonicalSourceId)) {
|
|
172
195
|
const flowInfo = buildSliceFlowInfo(model, flow);
|
|
173
196
|
if (flowInfo)
|
|
174
197
|
flows.push(flowInfo);
|
|
@@ -181,7 +204,10 @@ export function getOutboundFlows(model, slice) {
|
|
|
181
204
|
const componentIds = getSliceComponentIds(model, slice);
|
|
182
205
|
const flows = [];
|
|
183
206
|
for (const flow of model.flows.values()) {
|
|
184
|
-
|
|
207
|
+
// Resolve linked copies to canonical originals for containment check
|
|
208
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
209
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
210
|
+
if (componentIds.has(canonicalSourceId) && !componentIds.has(canonicalTargetId)) {
|
|
185
211
|
const flowInfo = buildSliceFlowInfo(model, flow);
|
|
186
212
|
if (flowInfo)
|
|
187
213
|
flows.push(flowInfo);
|
|
@@ -194,7 +220,10 @@ export function getInternalFlows(model, slice) {
|
|
|
194
220
|
const componentIds = getSliceComponentIds(model, slice);
|
|
195
221
|
const flows = [];
|
|
196
222
|
for (const flow of model.flows.values()) {
|
|
197
|
-
|
|
223
|
+
// Resolve linked copies to canonical originals for containment check
|
|
224
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
225
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
226
|
+
if (componentIds.has(canonicalSourceId) && componentIds.has(canonicalTargetId)) {
|
|
198
227
|
const flowInfo = buildFlowInfo(model, flow);
|
|
199
228
|
if (flowInfo)
|
|
200
229
|
flows.push(flowInfo);
|
|
@@ -202,17 +231,22 @@ export function getInternalFlows(model, slice) {
|
|
|
202
231
|
}
|
|
203
232
|
return flows;
|
|
204
233
|
}
|
|
205
|
-
// Get all flows for a specific element (both directions)
|
|
234
|
+
// Get all flows for a specific element (both directions, resolves linked copies)
|
|
206
235
|
export function getFlowsForElement(model, elementId) {
|
|
207
236
|
const incoming = [];
|
|
208
237
|
const outgoing = [];
|
|
238
|
+
// Resolve element ID to canonical if it's a linked copy
|
|
239
|
+
const canonicalElementId = resolveToCanonical(model, elementId);
|
|
209
240
|
for (const flow of model.flows.values()) {
|
|
210
|
-
|
|
241
|
+
// Resolve flow endpoints to canonical originals
|
|
242
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
243
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
244
|
+
if (canonicalTargetId === canonicalElementId) {
|
|
211
245
|
const flowInfo = buildFlowInfo(model, flow);
|
|
212
246
|
if (flowInfo)
|
|
213
247
|
incoming.push(flowInfo);
|
|
214
248
|
}
|
|
215
|
-
if (
|
|
249
|
+
if (canonicalSourceId === canonicalElementId) {
|
|
216
250
|
const flowInfo = buildFlowInfo(model, flow);
|
|
217
251
|
if (flowInfo)
|
|
218
252
|
outgoing.push(flowInfo);
|
|
@@ -230,13 +264,16 @@ export function findSliceToSliceFlows(model, slices) {
|
|
|
230
264
|
// Group flows by slice pair
|
|
231
265
|
const flowsBySlicePair = new Map();
|
|
232
266
|
for (const flow of model.flows.values()) {
|
|
267
|
+
// Resolve linked copies to canonical originals
|
|
268
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
269
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
233
270
|
let sourceSliceId = null;
|
|
234
271
|
let targetSliceId = null;
|
|
235
272
|
// Find which slice contains source and target
|
|
236
273
|
for (const [sliceId, componentIds] of sliceComponentMap.entries()) {
|
|
237
|
-
if (componentIds.has(
|
|
274
|
+
if (componentIds.has(canonicalSourceId))
|
|
238
275
|
sourceSliceId = sliceId;
|
|
239
|
-
if (componentIds.has(
|
|
276
|
+
if (componentIds.has(canonicalTargetId))
|
|
240
277
|
targetSliceId = sliceId;
|
|
241
278
|
}
|
|
242
279
|
// Only include flows between different slices in our set
|
|
@@ -277,7 +314,10 @@ export function findChapterInboundFlows(model, slices) {
|
|
|
277
314
|
}
|
|
278
315
|
const flows = [];
|
|
279
316
|
for (const flow of model.flows.values()) {
|
|
280
|
-
|
|
317
|
+
// Resolve linked copies to canonical originals
|
|
318
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
319
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
320
|
+
if (chapterComponentIds.has(canonicalTargetId) && !chapterComponentIds.has(canonicalSourceId)) {
|
|
281
321
|
const flowInfo = buildSliceFlowInfo(model, flow);
|
|
282
322
|
if (flowInfo)
|
|
283
323
|
flows.push(flowInfo);
|
|
@@ -295,7 +335,10 @@ export function findChapterOutboundFlows(model, slices) {
|
|
|
295
335
|
}
|
|
296
336
|
const flows = [];
|
|
297
337
|
for (const flow of model.flows.values()) {
|
|
298
|
-
|
|
338
|
+
// Resolve linked copies to canonical originals
|
|
339
|
+
const canonicalSourceId = resolveToCanonical(model, flow.sourceId);
|
|
340
|
+
const canonicalTargetId = resolveToCanonical(model, flow.targetId);
|
|
341
|
+
if (chapterComponentIds.has(canonicalSourceId) && !chapterComponentIds.has(canonicalTargetId)) {
|
|
299
342
|
const flowInfo = buildSliceFlowInfo(model, flow);
|
|
300
343
|
if (flowInfo)
|
|
301
344
|
flows.push(flowInfo);
|
|
@@ -292,12 +292,42 @@ export function codegenSlice(model, sliceName) {
|
|
|
292
292
|
const slice = findElementOrExit(model.slices, sliceName, 'slice');
|
|
293
293
|
// 2. Get components inside slice
|
|
294
294
|
const components = getSliceComponents(model, slice);
|
|
295
|
-
// 3. Build component ID set
|
|
295
|
+
// 3. Build component ID set (including canonical group members for linked copies)
|
|
296
296
|
const componentIds = new Set();
|
|
297
297
|
components.commands.forEach(c => componentIds.add(c.id));
|
|
298
|
-
components.events.forEach(e =>
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
components.events.forEach(e => {
|
|
299
|
+
componentIds.add(e.id);
|
|
300
|
+
// If this is a linked copy, also add all elements in the same canonical group
|
|
301
|
+
if (e.canonicalId) {
|
|
302
|
+
for (const evt of model.events.values()) {
|
|
303
|
+
if (evt.canonicalId === e.canonicalId) {
|
|
304
|
+
componentIds.add(evt.id);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
components.readModels.forEach(rm => {
|
|
310
|
+
componentIds.add(rm.id);
|
|
311
|
+
// If this is a linked copy, also add all elements in the same canonical group
|
|
312
|
+
if (rm.canonicalId) {
|
|
313
|
+
for (const r of model.readModels.values()) {
|
|
314
|
+
if (r.canonicalId === rm.canonicalId) {
|
|
315
|
+
componentIds.add(r.id);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
components.screens.forEach(s => {
|
|
321
|
+
componentIds.add(s.id);
|
|
322
|
+
// If this is a linked copy, also add all elements in the same canonical group
|
|
323
|
+
if (s.canonicalId) {
|
|
324
|
+
for (const scr of model.screens.values()) {
|
|
325
|
+
if (scr.canonicalId === s.canonicalId) {
|
|
326
|
+
componentIds.add(scr.id);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
301
331
|
components.processors.forEach(p => componentIds.add(p.id));
|
|
302
332
|
// 4. Determine slice type
|
|
303
333
|
const sliceType = determineSliceType(components.processors.length > 0, components.commands.length > 0, components.events.length > 0, components.readModels.length > 0, components.screens.length > 0);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { escapeXml, escapeXmlText, outputJson } from '../../lib/format.js';
|
|
2
2
|
import { findElementOrExit } from '../../lib/element-lookup.js';
|
|
3
3
|
import { findChapterForSlice, getChapterHierarchy } from '../../lib/chapter-utils.js';
|
|
4
|
-
import { getInboundFlows, getOutboundFlows, getFlowsForElement, } from '../../lib/flow-utils.js';
|
|
4
|
+
import { getInboundFlows, getOutboundFlows, getFlowsForElement, getSliceComponentIds, resolveToCanonical, } from '../../lib/flow-utils.js';
|
|
5
5
|
function formatFieldValues(values) {
|
|
6
6
|
if (!values || Object.keys(values).length === 0)
|
|
7
7
|
return '';
|
|
@@ -46,36 +46,16 @@ function getSliceComponents(model, slice) {
|
|
|
46
46
|
const centerY = pos.y + height / 2;
|
|
47
47
|
return centerX >= bounds.left && centerX <= bounds.right && centerY >= bounds.top && centerY <= bounds.bottom;
|
|
48
48
|
}
|
|
49
|
-
//
|
|
50
|
-
//
|
|
49
|
+
// Only include canonical elements - linked copies are UI-only conveniences
|
|
50
|
+
// and should not appear as slice contents in CLI output
|
|
51
51
|
return {
|
|
52
52
|
commands: [...model.commands.values()].filter(c => isInSlice(c.position, c.width, c.height)),
|
|
53
|
-
events: [...model.events.values()].filter(e => isInSlice(e.position, e.width, e.height)),
|
|
54
|
-
readModels: [...model.readModels.values()].filter(rm => isInSlice(rm.position, rm.width, rm.height)),
|
|
55
|
-
screens: [...model.screens.values()].filter(s => isInSlice(s.position, s.width, s.height)),
|
|
53
|
+
events: [...model.events.values()].filter(e => !e.originalNodeId && isInSlice(e.position, e.width, e.height)),
|
|
54
|
+
readModels: [...model.readModels.values()].filter(rm => !rm.originalNodeId && isInSlice(rm.position, rm.width, rm.height)),
|
|
55
|
+
screens: [...model.screens.values()].filter(s => !s.originalNodeId && isInSlice(s.position, s.width, s.height)),
|
|
56
56
|
processors: [...model.processors.values()].filter(p => isInSlice(p.position, p.width, p.height)),
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
-
// Find which slice contains the original of a linked copy
|
|
60
|
-
function findSliceForNode(model, nodeId) {
|
|
61
|
-
const event = model.events.get(nodeId);
|
|
62
|
-
const readModel = model.readModels.get(nodeId);
|
|
63
|
-
const screen = model.screens.get(nodeId);
|
|
64
|
-
const node = event ?? readModel ?? screen;
|
|
65
|
-
if (!node)
|
|
66
|
-
return null;
|
|
67
|
-
for (const slice of model.slices.values()) {
|
|
68
|
-
const centerX = node.position.x + node.width / 2;
|
|
69
|
-
const centerY = node.position.y + node.height / 2;
|
|
70
|
-
if (centerX >= slice.position.x &&
|
|
71
|
-
centerX <= slice.position.x + slice.size.width &&
|
|
72
|
-
centerY >= slice.position.y &&
|
|
73
|
-
centerY <= slice.position.y + slice.size.height) {
|
|
74
|
-
return slice;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
59
|
// Find which aggregate an event belongs to (center point inside aggregate bounds)
|
|
80
60
|
function findAggregateForEvent(model, event) {
|
|
81
61
|
const centerX = event.position.x + event.width / 2;
|
|
@@ -189,14 +169,19 @@ function formatSliceXml(model, slice) {
|
|
|
189
169
|
const components = getSliceComponents(model, slice);
|
|
190
170
|
const scenarios = [...model.scenarios.values()].filter(s => s.sliceId === slice.id);
|
|
191
171
|
const chapter = findChapterForSlice(model, slice);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
172
|
+
// Use shared function that handles canonical groups for linked copies
|
|
173
|
+
const componentIds = getSliceComponentIds(model, slice);
|
|
174
|
+
// Resolve linked copies to canonical originals when checking flow containment
|
|
175
|
+
const flows = [...model.flows.values()].filter(f => {
|
|
176
|
+
const canonicalSourceId = resolveToCanonical(model, f.sourceId);
|
|
177
|
+
const canonicalTargetId = resolveToCanonical(model, f.targetId);
|
|
178
|
+
return componentIds.has(canonicalSourceId) || componentIds.has(canonicalTargetId);
|
|
179
|
+
});
|
|
180
|
+
const internalFlows = flows.filter(f => {
|
|
181
|
+
const canonicalSourceId = resolveToCanonical(model, f.sourceId);
|
|
182
|
+
const canonicalTargetId = resolveToCanonical(model, f.targetId);
|
|
183
|
+
return componentIds.has(canonicalSourceId) && componentIds.has(canonicalTargetId);
|
|
184
|
+
});
|
|
200
185
|
// Get inbound and outbound flows for the slice
|
|
201
186
|
const inboundFlows = getInboundFlows(model, slice);
|
|
202
187
|
const outboundFlows = getOutboundFlows(model, slice);
|
|
@@ -214,19 +199,10 @@ function formatSliceXml(model, slice) {
|
|
|
214
199
|
}
|
|
215
200
|
xml += ' <components>\n';
|
|
216
201
|
for (const screen of components.screens) {
|
|
217
|
-
// Check if this is a linked copy
|
|
218
|
-
const copyAttr = screen.originalNodeId ? ' linked-copy="true"' : '';
|
|
219
|
-
let originAttr = '';
|
|
220
|
-
if (screen.originalNodeId) {
|
|
221
|
-
const originSlice = findSliceForNode(model, screen.originalNodeId);
|
|
222
|
-
if (originSlice) {
|
|
223
|
-
originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
202
|
// Check which actor this screen belongs to
|
|
227
203
|
const actor = findActorForScreen(model, screen);
|
|
228
204
|
const actorAttr = actor ? ` actor="${escapeXml(actor.name)}"` : '';
|
|
229
|
-
xml += ` <screen id="${screen.id}" name="${escapeXml(screen.name)}"${
|
|
205
|
+
xml += ` <screen id="${screen.id}" name="${escapeXml(screen.name)}"${actorAttr}>\n`;
|
|
230
206
|
// Add flow annotations
|
|
231
207
|
const screenFlows = getFlowsForElement(model, screen.id);
|
|
232
208
|
xml += formatFlowAnnotationsXml(screen.id, screenFlows.incoming, screenFlows.outgoing, componentIds, ' ');
|
|
@@ -262,19 +238,10 @@ function formatSliceXml(model, slice) {
|
|
|
262
238
|
xml += ' </command>\n';
|
|
263
239
|
}
|
|
264
240
|
for (const event of components.events) {
|
|
265
|
-
// Check if this is a linked copy
|
|
266
|
-
const copyAttr = event.originalNodeId ? ' linked-copy="true"' : '';
|
|
267
|
-
let originAttr = '';
|
|
268
|
-
if (event.originalNodeId) {
|
|
269
|
-
const originSlice = findSliceForNode(model, event.originalNodeId);
|
|
270
|
-
if (originSlice) {
|
|
271
|
-
originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
241
|
// Check which aggregate this event belongs to
|
|
275
242
|
const aggregate = findAggregateForEvent(model, event);
|
|
276
243
|
const aggregateAttr = aggregate ? ` aggregate="${escapeXml(aggregate.name)}"` : '';
|
|
277
|
-
xml += ` <event id="${event.id}" name="${escapeXml(event.name)}"${
|
|
244
|
+
xml += ` <event id="${event.id}" name="${escapeXml(event.name)}"${aggregateAttr}>\n`;
|
|
278
245
|
// Add flow annotations
|
|
279
246
|
const eventFlows = getFlowsForElement(model, event.id);
|
|
280
247
|
xml += formatFlowAnnotationsXml(event.id, eventFlows.incoming, eventFlows.outgoing, componentIds, ' ');
|
|
@@ -299,16 +266,7 @@ function formatSliceXml(model, slice) {
|
|
|
299
266
|
xml += ' </event>\n';
|
|
300
267
|
}
|
|
301
268
|
for (const readModel of components.readModels) {
|
|
302
|
-
|
|
303
|
-
const copyAttr = readModel.originalNodeId ? ' linked-copy="true"' : '';
|
|
304
|
-
let originAttr = '';
|
|
305
|
-
if (readModel.originalNodeId) {
|
|
306
|
-
const originSlice = findSliceForNode(model, readModel.originalNodeId);
|
|
307
|
-
if (originSlice) {
|
|
308
|
-
originAttr = ` origin-slice="${escapeXml(originSlice.name)}"`;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
xml += ` <read-model id="${readModel.id}" name="${escapeXml(readModel.name)}"${copyAttr}${originAttr}>\n`;
|
|
269
|
+
xml += ` <read-model id="${readModel.id}" name="${escapeXml(readModel.name)}">\n`;
|
|
312
270
|
// Add flow annotations
|
|
313
271
|
const rmFlows = getFlowsForElement(model, readModel.id);
|
|
314
272
|
xml += formatFlowAnnotationsXml(readModel.id, rmFlows.incoming, rmFlows.outgoing, componentIds, ' ');
|
|
@@ -430,14 +388,19 @@ function formatSliceJson(model, slice) {
|
|
|
430
388
|
const components = getSliceComponents(model, slice);
|
|
431
389
|
const scenarios = [...model.scenarios.values()].filter(s => s.sliceId === slice.id);
|
|
432
390
|
const chapter = findChapterForSlice(model, slice);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
391
|
+
// Use shared function that handles canonical groups for linked copies
|
|
392
|
+
const componentIds = getSliceComponentIds(model, slice);
|
|
393
|
+
// Resolve linked copies to canonical originals when checking flow containment
|
|
394
|
+
const flows = [...model.flows.values()].filter(f => {
|
|
395
|
+
const canonicalSourceId = resolveToCanonical(model, f.sourceId);
|
|
396
|
+
const canonicalTargetId = resolveToCanonical(model, f.targetId);
|
|
397
|
+
return componentIds.has(canonicalSourceId) || componentIds.has(canonicalTargetId);
|
|
398
|
+
});
|
|
399
|
+
const internalFlows = flows.filter(f => {
|
|
400
|
+
const canonicalSourceId = resolveToCanonical(model, f.sourceId);
|
|
401
|
+
const canonicalTargetId = resolveToCanonical(model, f.targetId);
|
|
402
|
+
return componentIds.has(canonicalSourceId) && componentIds.has(canonicalTargetId);
|
|
403
|
+
});
|
|
441
404
|
// Get inbound and outbound flows for the slice
|
|
442
405
|
const inboundFlows = getInboundFlows(model, slice);
|
|
443
406
|
const outboundFlows = getOutboundFlows(model, slice);
|
|
@@ -461,12 +424,6 @@ function formatSliceJson(model, slice) {
|
|
|
461
424
|
name: screen.name,
|
|
462
425
|
fields: screen.fields.map(fieldToJson)
|
|
463
426
|
};
|
|
464
|
-
if (screen.originalNodeId) {
|
|
465
|
-
screenObj.linkedCopy = true;
|
|
466
|
-
const originSlice = findSliceForNode(model, screen.originalNodeId);
|
|
467
|
-
if (originSlice)
|
|
468
|
-
screenObj.originSlice = originSlice.name;
|
|
469
|
-
}
|
|
470
427
|
const actor = findActorForScreen(model, screen);
|
|
471
428
|
if (actor)
|
|
472
429
|
screenObj.actor = actor.name;
|
|
@@ -517,12 +474,6 @@ function formatSliceJson(model, slice) {
|
|
|
517
474
|
name: event.name,
|
|
518
475
|
fields: event.fields.map(fieldToJson)
|
|
519
476
|
};
|
|
520
|
-
if (event.originalNodeId) {
|
|
521
|
-
eventObj.linkedCopy = true;
|
|
522
|
-
const originSlice = findSliceForNode(model, event.originalNodeId);
|
|
523
|
-
if (originSlice)
|
|
524
|
-
eventObj.originSlice = originSlice.name;
|
|
525
|
-
}
|
|
526
477
|
const aggregate = findAggregateForEvent(model, event);
|
|
527
478
|
if (aggregate)
|
|
528
479
|
eventObj.aggregate = aggregate.name;
|
|
@@ -550,12 +501,6 @@ function formatSliceJson(model, slice) {
|
|
|
550
501
|
name: rm.name,
|
|
551
502
|
fields: rm.fields.map(fieldToJson)
|
|
552
503
|
};
|
|
553
|
-
if (rm.originalNodeId) {
|
|
554
|
-
rmObj.linkedCopy = true;
|
|
555
|
-
const originSlice = findSliceForNode(model, rm.originalNodeId);
|
|
556
|
-
if (originSlice)
|
|
557
|
-
rmObj.originSlice = originSlice.name;
|
|
558
|
-
}
|
|
559
504
|
// Add flow annotations
|
|
560
505
|
const rmFlows = getFlowsForElement(model, rm.id);
|
|
561
506
|
if (rmFlows.incoming.length > 0) {
|