eventmodeler 0.5.0 → 0.6.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/api/client-config.js +10 -0
- package/dist/api/generated/client/client.gen.js +235 -0
- package/dist/api/generated/client/index.js +6 -0
- package/dist/api/generated/client/types.gen.js +2 -0
- package/dist/api/generated/client/utils.gen.js +228 -0
- package/dist/api/generated/client.gen.js +4 -0
- package/dist/api/generated/core/auth.gen.js +14 -0
- package/dist/api/generated/core/bodySerializer.gen.js +57 -0
- package/dist/api/generated/core/params.gen.js +100 -0
- package/dist/api/generated/core/pathSerializer.gen.js +106 -0
- package/dist/api/generated/core/queryKeySerializer.gen.js +92 -0
- package/dist/api/generated/core/serverSentEvents.gen.js +133 -0
- package/dist/api/generated/core/types.gen.js +2 -0
- package/dist/api/generated/core/utils.gen.js +87 -0
- package/dist/api/generated/index.js +2 -0
- package/dist/api/generated/sdk.gen.js +4222 -0
- package/dist/api/generated/types.gen.js +2 -0
- package/dist/api/generated/zod.gen.js +7217 -0
- package/dist/commands/add.js +315 -0
- package/dist/commands/auth.js +14 -0
- package/dist/commands/create.js +192 -0
- package/dist/commands/design.js +108 -0
- package/dist/commands/guide.js +15 -0
- package/dist/commands/init.js +21 -0
- package/dist/commands/list-schemas.js +177 -0
- package/dist/commands/list.js +39 -0
- package/dist/commands/loop.js +101 -0
- package/dist/commands/map.js +40 -0
- package/dist/commands/mark.js +27 -0
- package/dist/commands/move.js +35 -0
- package/dist/commands/remove.js +170 -0
- package/dist/commands/rename.js +53 -0
- package/dist/commands/resize.js +30 -0
- package/dist/commands/search.js +14 -0
- package/dist/commands/set.js +199 -0
- package/dist/commands/show-schemas.js +259 -0
- package/dist/commands/show.js +56 -0
- package/dist/commands/summary.js +13 -0
- package/dist/commands/update.js +240 -0
- package/dist/index.js +46 -2379
- package/dist/lib/auth.js +1 -1
- package/dist/lib/config.js +0 -15
- package/dist/lib/excalidraw-schema.js +66 -0
- package/dist/lib/globals.js +8 -0
- package/dist/lib/model.js +11 -0
- package/dist/lib/project-config.js +20 -0
- package/dist/lib/resolve.js +59 -0
- package/dist/lib/scenario.js +15 -0
- package/dist/slices/add-scenario/index.js +2 -206
- package/dist/slices/guide/guides/codegen.js +1 -1
- package/dist/slices/guide/guides/connect-slices.js +38 -38
- package/dist/slices/guide/guides/create-slices.js +110 -140
- package/dist/slices/guide/guides/explore.js +37 -50
- package/dist/slices/guide/guides/information-flow.js +70 -84
- package/dist/slices/guide/guides/scenarios.js +82 -137
- package/dist/slices/guide/index.js +6 -6
- package/dist/slices/help/index.js +96 -0
- package/dist/slices/help/topics/build-codegen.js +109 -0
- package/dist/slices/help/topics/build-slice.js +147 -0
- package/dist/slices/help/topics/check-completeness.js +57 -0
- package/dist/slices/help/topics/connect-slices.js +99 -0
- package/dist/slices/help/topics/explore-model.js +112 -0
- package/dist/slices/help/topics/json-reference.js +188 -0
- package/dist/slices/help/topics/linked-copies.js +89 -0
- package/dist/slices/help/topics/manipulate-canvas.js +150 -0
- package/dist/slices/help/topics/write-scenarios.js +162 -0
- package/dist/slices/init/index.js +10 -4
- package/dist/slices/init/loop.js +60 -0
- package/dist/slices/login/index.js +2 -2
- package/dist/slices/logout/index.js +2 -2
- package/dist/slices/whoami/index.js +11 -36
- package/package.json +8 -3
- package/dist/api/index.d.ts +0 -285
- package/dist/api/index.js +0 -323
- package/dist/cloud/slices/index.d.ts +0 -276
- package/dist/cloud/slices/index.js +0 -406
- package/dist/eventmodeler.js +0 -5646
- package/dist/formatters.d.ts +0 -17
- package/dist/formatters.js +0 -482
- package/dist/index.d.ts +0 -2
- package/dist/lib/auth.d.ts +0 -24
- package/dist/lib/backend.d.ts +0 -43
- package/dist/lib/backend.js +0 -73
- package/dist/lib/chapter-utils.d.ts +0 -13
- package/dist/lib/chapter-utils.js +0 -71
- package/dist/lib/cloud-client.d.ts +0 -69
- package/dist/lib/cloud-client.js +0 -364
- package/dist/lib/config.d.ts +0 -30
- package/dist/lib/diff/merge-rules.d.ts +0 -45
- package/dist/lib/diff/merge-rules.js +0 -210
- package/dist/lib/diff/model-differ.d.ts +0 -8
- package/dist/lib/diff/model-differ.js +0 -568
- package/dist/lib/diff/three-way-merge.d.ts +0 -7
- package/dist/lib/diff/three-way-merge.js +0 -390
- package/dist/lib/diff/types.d.ts +0 -75
- package/dist/lib/diff/types.js +0 -1
- package/dist/lib/element-lookup.d.ts +0 -58
- package/dist/lib/element-lookup.js +0 -126
- package/dist/lib/file-loader.d.ts +0 -8
- package/dist/lib/file-loader.js +0 -108
- package/dist/lib/flow-utils.d.ts +0 -53
- package/dist/lib/flow-utils.js +0 -348
- package/dist/lib/format.d.ts +0 -10
- package/dist/lib/format.js +0 -23
- package/dist/lib/project-config.d.ts +0 -27
- package/dist/lib/slice-utils.d.ts +0 -59
- package/dist/lib/slice-utils.js +0 -140
- package/dist/local/slices/index.d.ts +0 -11
- package/dist/local/slices/index.js +0 -13
- package/dist/projection.d.ts +0 -3
- package/dist/projection.js +0 -828
- package/dist/slices/add-field/index.d.ts +0 -8
- package/dist/slices/add-field/index.js +0 -211
- package/dist/slices/add-scenario/index.d.ts +0 -27
- package/dist/slices/codegen-chapter-events/index.d.ts +0 -2
- package/dist/slices/codegen-chapter-events/index.js +0 -145
- package/dist/slices/codegen-slice/index.d.ts +0 -2
- package/dist/slices/codegen-slice/index.js +0 -448
- package/dist/slices/create-automation-slice/index.d.ts +0 -2
- package/dist/slices/create-automation-slice/index.js +0 -304
- package/dist/slices/create-flow/index.d.ts +0 -2
- package/dist/slices/create-flow/index.js +0 -183
- package/dist/slices/create-state-change-slice/index.d.ts +0 -2
- package/dist/slices/create-state-change-slice/index.js +0 -263
- package/dist/slices/create-state-view-slice/index.d.ts +0 -2
- package/dist/slices/create-state-view-slice/index.js +0 -128
- package/dist/slices/diff/index.d.ts +0 -11
- package/dist/slices/diff/index.js +0 -293
- package/dist/slices/export-eventmodel-to-json/index.d.ts +0 -2
- package/dist/slices/export-eventmodel-to-json/index.js +0 -355
- package/dist/slices/git/index.d.ts +0 -2
- package/dist/slices/git/index.js +0 -125
- package/dist/slices/guide/guides/codegen.d.ts +0 -5
- package/dist/slices/guide/guides/connect-slices.d.ts +0 -5
- package/dist/slices/guide/guides/create-slices.d.ts +0 -5
- package/dist/slices/guide/guides/explore.d.ts +0 -5
- package/dist/slices/guide/guides/information-flow.d.ts +0 -5
- package/dist/slices/guide/guides/scenarios.d.ts +0 -5
- package/dist/slices/guide/index.d.ts +0 -1
- package/dist/slices/import/index.d.ts +0 -8
- package/dist/slices/import/index.js +0 -63
- package/dist/slices/init/index.d.ts +0 -5
- package/dist/slices/list-chapters/index.d.ts +0 -3
- package/dist/slices/list-chapters/index.js +0 -21
- package/dist/slices/list-commands/index.d.ts +0 -3
- package/dist/slices/list-commands/index.js +0 -20
- package/dist/slices/list-events/index.d.ts +0 -3
- package/dist/slices/list-events/index.js +0 -98
- package/dist/slices/list-processors/index.d.ts +0 -3
- package/dist/slices/list-processors/index.js +0 -20
- package/dist/slices/list-readmodels/index.d.ts +0 -3
- package/dist/slices/list-readmodels/index.js +0 -21
- package/dist/slices/list-scenarios/index.d.ts +0 -3
- package/dist/slices/list-scenarios/index.js +0 -35
- package/dist/slices/list-screens/index.d.ts +0 -3
- package/dist/slices/list-screens/index.js +0 -47
- package/dist/slices/list-slices/index.d.ts +0 -3
- package/dist/slices/list-slices/index.js +0 -35
- package/dist/slices/login/index.d.ts +0 -1
- package/dist/slices/logout/index.d.ts +0 -1
- package/dist/slices/map-fields/index.d.ts +0 -2
- package/dist/slices/map-fields/index.js +0 -269
- package/dist/slices/mark-slice-status/index.d.ts +0 -2
- package/dist/slices/mark-slice-status/index.js +0 -31
- package/dist/slices/merge/index.d.ts +0 -19
- package/dist/slices/merge/index.js +0 -147
- package/dist/slices/open-app/index.d.ts +0 -1
- package/dist/slices/remove-field/index.d.ts +0 -8
- package/dist/slices/remove-field/index.js +0 -167
- package/dist/slices/remove-scenario/index.d.ts +0 -2
- package/dist/slices/remove-scenario/index.js +0 -77
- package/dist/slices/search/index.d.ts +0 -3
- package/dist/slices/search/index.js +0 -302
- package/dist/slices/show-actor/index.d.ts +0 -4
- package/dist/slices/show-actor/index.js +0 -115
- package/dist/slices/show-aggregate/index.d.ts +0 -3
- package/dist/slices/show-aggregate/index.js +0 -108
- package/dist/slices/show-aggregate-completeness/index.d.ts +0 -4
- package/dist/slices/show-aggregate-completeness/index.js +0 -181
- package/dist/slices/show-chapter/index.d.ts +0 -3
- package/dist/slices/show-chapter/index.js +0 -195
- package/dist/slices/show-command/index.d.ts +0 -3
- package/dist/slices/show-command/index.js +0 -133
- package/dist/slices/show-completeness/index.d.ts +0 -4
- package/dist/slices/show-completeness/index.js +0 -731
- package/dist/slices/show-event/index.d.ts +0 -3
- package/dist/slices/show-event/index.js +0 -118
- package/dist/slices/show-model-summary/index.d.ts +0 -3
- package/dist/slices/show-model-summary/index.js +0 -31
- package/dist/slices/show-processor/index.d.ts +0 -3
- package/dist/slices/show-processor/index.js +0 -111
- package/dist/slices/show-readmodel/index.d.ts +0 -3
- package/dist/slices/show-readmodel/index.js +0 -158
- package/dist/slices/show-scenario/index.d.ts +0 -3
- package/dist/slices/show-scenario/index.js +0 -196
- package/dist/slices/show-screen/index.d.ts +0 -3
- package/dist/slices/show-screen/index.js +0 -139
- package/dist/slices/show-slice/index.d.ts +0 -3
- package/dist/slices/show-slice/index.js +0 -696
- package/dist/slices/update-field/index.d.ts +0 -15
- package/dist/slices/update-field/index.js +0 -208
- package/dist/slices/whoami/index.d.ts +0 -2
- package/dist/types.d.ts +0 -195
- package/dist/types.js +0 -1
|
@@ -1,731 +0,0 @@
|
|
|
1
|
-
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
-
const INCOMING_FLOW_TYPES = {
|
|
3
|
-
command: ['ScreenToCommand', 'ProcessorToCommand'],
|
|
4
|
-
event: ['CommandToEvent'],
|
|
5
|
-
readModel: ['EventToReadModel'],
|
|
6
|
-
screen: ['ReadModelToScreen'],
|
|
7
|
-
processor: ['ReadModelToProcessor'],
|
|
8
|
-
};
|
|
9
|
-
function findElementByName(model, name) {
|
|
10
|
-
// Check for UUID lookup (id:prefix or full UUID format)
|
|
11
|
-
if (name.startsWith('id:')) {
|
|
12
|
-
const idSearch = name.slice(3).toLowerCase();
|
|
13
|
-
// Search all element types by ID
|
|
14
|
-
for (const cmd of model.commands.values()) {
|
|
15
|
-
if (cmd.id.toLowerCase().startsWith(idSearch)) {
|
|
16
|
-
return { element: { id: cmd.id, name: cmd.name, fields: cmd.fields, type: 'command' }, ambiguous: [] };
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
for (const evt of model.events.values()) {
|
|
20
|
-
if (evt.id.toLowerCase().startsWith(idSearch)) {
|
|
21
|
-
return { element: { id: evt.id, name: evt.name, fields: evt.fields, type: 'event' }, ambiguous: [] };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
for (const rm of model.readModels.values()) {
|
|
25
|
-
if (rm.id.toLowerCase().startsWith(idSearch)) {
|
|
26
|
-
return { element: { id: rm.id, name: rm.name, fields: rm.fields, type: 'readModel', canonicalId: rm.canonicalId }, ambiguous: [] };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
for (const scr of model.screens.values()) {
|
|
30
|
-
if (scr.id.toLowerCase().startsWith(idSearch)) {
|
|
31
|
-
return { element: { id: scr.id, name: scr.name, fields: scr.fields, type: 'screen' }, ambiguous: [] };
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
for (const proc of model.processors.values()) {
|
|
35
|
-
if (proc.id.toLowerCase().startsWith(idSearch)) {
|
|
36
|
-
return { element: { id: proc.id, name: proc.name, fields: proc.fields, type: 'processor' }, ambiguous: [] };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return { element: null, ambiguous: [] };
|
|
40
|
-
}
|
|
41
|
-
// Check if it's a full UUID
|
|
42
|
-
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
43
|
-
if (uuidPattern.test(name)) {
|
|
44
|
-
const cmd = model.commands.get(name);
|
|
45
|
-
if (cmd)
|
|
46
|
-
return { element: { id: cmd.id, name: cmd.name, fields: cmd.fields, type: 'command' }, ambiguous: [] };
|
|
47
|
-
const evt = model.events.get(name);
|
|
48
|
-
if (evt)
|
|
49
|
-
return { element: { id: evt.id, name: evt.name, fields: evt.fields, type: 'event' }, ambiguous: [] };
|
|
50
|
-
const rm = model.readModels.get(name);
|
|
51
|
-
if (rm)
|
|
52
|
-
return { element: { id: rm.id, name: rm.name, fields: rm.fields, type: 'readModel', canonicalId: rm.canonicalId }, ambiguous: [] };
|
|
53
|
-
const scr = model.screens.get(name);
|
|
54
|
-
if (scr)
|
|
55
|
-
return { element: { id: scr.id, name: scr.name, fields: scr.fields, type: 'screen' }, ambiguous: [] };
|
|
56
|
-
const proc = model.processors.get(name);
|
|
57
|
-
if (proc)
|
|
58
|
-
return { element: { id: proc.id, name: proc.name, fields: proc.fields, type: 'processor' }, ambiguous: [] };
|
|
59
|
-
return { element: null, ambiguous: [] };
|
|
60
|
-
}
|
|
61
|
-
// Case-insensitive exact name match across all types
|
|
62
|
-
// Skip linked copies for events, readModels, and screens - they share identity with canonical original
|
|
63
|
-
const nameLower = name.toLowerCase();
|
|
64
|
-
const matches = [];
|
|
65
|
-
for (const cmd of model.commands.values()) {
|
|
66
|
-
if (cmd.name.toLowerCase() === nameLower) {
|
|
67
|
-
matches.push({ element: { id: cmd.id, name: cmd.name, fields: cmd.fields, type: 'command' }, type: 'command' });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
for (const evt of model.events.values()) {
|
|
71
|
-
if (!evt.originalNodeId && evt.name.toLowerCase() === nameLower) {
|
|
72
|
-
matches.push({ element: { id: evt.id, name: evt.name, fields: evt.fields, type: 'event' }, type: 'event' });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
for (const rm of model.readModels.values()) {
|
|
76
|
-
if (!rm.originalNodeId && rm.name.toLowerCase() === nameLower) {
|
|
77
|
-
matches.push({ element: { id: rm.id, name: rm.name, fields: rm.fields, type: 'readModel', canonicalId: rm.canonicalId }, type: 'readModel' });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
for (const scr of model.screens.values()) {
|
|
81
|
-
if (!scr.originalNodeId && scr.name.toLowerCase() === nameLower) {
|
|
82
|
-
matches.push({ element: { id: scr.id, name: scr.name, fields: scr.fields, type: 'screen' }, type: 'screen' });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
for (const proc of model.processors.values()) {
|
|
86
|
-
if (proc.name.toLowerCase() === nameLower) {
|
|
87
|
-
matches.push({ element: { id: proc.id, name: proc.name, fields: proc.fields, type: 'processor' }, type: 'processor' });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (matches.length === 1) {
|
|
91
|
-
return { element: matches[0].element, ambiguous: [] };
|
|
92
|
-
}
|
|
93
|
-
if (matches.length > 1) {
|
|
94
|
-
return { element: null, ambiguous: matches };
|
|
95
|
-
}
|
|
96
|
-
return { element: null, ambiguous: [] };
|
|
97
|
-
}
|
|
98
|
-
function getSourceFields(model, sourceId) {
|
|
99
|
-
const cmd = model.commands.get(sourceId);
|
|
100
|
-
if (cmd)
|
|
101
|
-
return cmd.fields;
|
|
102
|
-
const evt = model.events.get(sourceId);
|
|
103
|
-
if (evt)
|
|
104
|
-
return evt.fields;
|
|
105
|
-
const rm = model.readModels.get(sourceId);
|
|
106
|
-
if (rm)
|
|
107
|
-
return rm.fields;
|
|
108
|
-
const scr = model.screens.get(sourceId);
|
|
109
|
-
if (scr)
|
|
110
|
-
return scr.fields;
|
|
111
|
-
const proc = model.processors.get(sourceId);
|
|
112
|
-
if (proc)
|
|
113
|
-
return proc.fields;
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Flattens nested Custom fields into a flat list with dot-notation paths.
|
|
118
|
-
* When skipCustomParents is true, Custom fields with subfields are excluded.
|
|
119
|
-
*/
|
|
120
|
-
function flattenFields(fields, prefix = '', skipCustomParents = false) {
|
|
121
|
-
const result = [];
|
|
122
|
-
for (const field of fields) {
|
|
123
|
-
const path = prefix ? `${prefix}.${field.name}` : field.name;
|
|
124
|
-
const isCustomParent = field.fieldType === 'Custom' &&
|
|
125
|
-
field.subfields &&
|
|
126
|
-
field.subfields.length > 0;
|
|
127
|
-
if (!skipCustomParents || !isCustomParent) {
|
|
128
|
-
result.push({ id: field.id, path, field });
|
|
129
|
-
}
|
|
130
|
-
if (field.fieldType === 'Custom' && field.subfields) {
|
|
131
|
-
result.push(...flattenFields(field.subfields, path, skipCustomParents));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return result;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Calculates completeness for a read model using the union approach.
|
|
138
|
-
* A field is satisfied if ANY incoming event provides it.
|
|
139
|
-
* For linked copies, considers flows into ALL nodes in the canonical group.
|
|
140
|
-
*/
|
|
141
|
-
function calculateReadModelUnionCompleteness(model, readModelId, readModelFields, canonicalId) {
|
|
142
|
-
// Find all node IDs in the canonical group (original + all copies)
|
|
143
|
-
let targetNodeIds;
|
|
144
|
-
if (canonicalId) {
|
|
145
|
-
targetNodeIds = [...model.readModels.values()]
|
|
146
|
-
.filter(rm => rm.canonicalId === canonicalId)
|
|
147
|
-
.map(rm => rm.id);
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
targetNodeIds = [readModelId];
|
|
151
|
-
}
|
|
152
|
-
// Find all incoming EventToReadModel flows to ANY node in the group
|
|
153
|
-
const incomingFlows = [...model.flows.values()].filter(f => targetNodeIds.includes(f.targetId) && f.flowType === 'EventToReadModel');
|
|
154
|
-
// Collect all source fields from all incoming events (union)
|
|
155
|
-
const allSourceFields = [];
|
|
156
|
-
const allManualMappings = [];
|
|
157
|
-
const sourceEventNames = [];
|
|
158
|
-
for (const flow of incomingFlows) {
|
|
159
|
-
const sourceFields = getSourceFields(model, flow.sourceId);
|
|
160
|
-
allSourceFields.push(...flattenFields(sourceFields));
|
|
161
|
-
if (flow.fieldMappings) {
|
|
162
|
-
allManualMappings.push(...flow.fieldMappings);
|
|
163
|
-
}
|
|
164
|
-
// Get event name for reporting
|
|
165
|
-
const event = model.events.get(flow.sourceId);
|
|
166
|
-
if (event && !sourceEventNames.includes(event.name)) {
|
|
167
|
-
sourceEventNames.push(event.name);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// Check each target field against the union of all sources
|
|
171
|
-
// Skip custom parents - only check leaf fields
|
|
172
|
-
const flatTarget = flattenFields(readModelFields, '', true);
|
|
173
|
-
const untraceableFields = [];
|
|
174
|
-
const optionalMissingFields = [];
|
|
175
|
-
const fieldStatuses = [];
|
|
176
|
-
for (const { id, path, field } of flatTarget) {
|
|
177
|
-
// 1. Generated fields don't need a source
|
|
178
|
-
if (field.isGenerated) {
|
|
179
|
-
fieldStatuses.push({ fieldId: id, fieldName: path, status: 'generated' });
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
// 2. Check if there's a manual mapping from any flow
|
|
183
|
-
const manualMapping = allManualMappings.find(m => m.targetFieldId === id);
|
|
184
|
-
if (manualMapping) {
|
|
185
|
-
const sourceField = allSourceFields.find(sf => sf.id === manualMapping.sourceFieldId);
|
|
186
|
-
fieldStatuses.push({
|
|
187
|
-
fieldId: id,
|
|
188
|
-
fieldName: path,
|
|
189
|
-
status: 'satisfied',
|
|
190
|
-
sourceFieldId: manualMapping.sourceFieldId,
|
|
191
|
-
sourceFieldName: sourceField?.path,
|
|
192
|
-
});
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
// 3. Check auto-mapping by path match from any source
|
|
196
|
-
const autoMatch = allSourceFields.find(sf => sf.path === path);
|
|
197
|
-
if (autoMatch) {
|
|
198
|
-
fieldStatuses.push({
|
|
199
|
-
fieldId: id,
|
|
200
|
-
fieldName: path,
|
|
201
|
-
status: 'satisfied',
|
|
202
|
-
sourceFieldId: autoMatch.id,
|
|
203
|
-
sourceFieldName: autoMatch.path,
|
|
204
|
-
});
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
// 4. Optional fields missing is a warning, not an error
|
|
208
|
-
if (field.isOptional) {
|
|
209
|
-
optionalMissingFields.push(path);
|
|
210
|
-
fieldStatuses.push({ fieldId: id, fieldName: path, status: 'optional-missing' });
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
// Field is untraceable (required but no source)
|
|
214
|
-
untraceableFields.push(path);
|
|
215
|
-
fieldStatuses.push({ fieldId: id, fieldName: path, status: 'unsatisfied' });
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
isComplete: untraceableFields.length === 0,
|
|
219
|
-
hasWarnings: optionalMissingFields.length > 0,
|
|
220
|
-
untraceableFields,
|
|
221
|
-
optionalMissingFields,
|
|
222
|
-
sourceEvents: sourceEventNames,
|
|
223
|
-
fieldStatuses,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
function calculateFlowCompleteness(model, flow, targetFields, sourceFields) {
|
|
227
|
-
const flatSource = flattenFields(sourceFields);
|
|
228
|
-
// Skip custom parents for target fields - only check leaf fields
|
|
229
|
-
const flatTarget = flattenFields(targetFields, '', true);
|
|
230
|
-
const manualMappings = flow.fieldMappings ?? [];
|
|
231
|
-
// Get source entity name
|
|
232
|
-
let sourceName = 'Unknown';
|
|
233
|
-
let sourceType = 'unknown';
|
|
234
|
-
const event = model.events.get(flow.sourceId);
|
|
235
|
-
const readModel = model.readModels.get(flow.sourceId);
|
|
236
|
-
const command = model.commands.get(flow.sourceId);
|
|
237
|
-
const screen = model.screens.get(flow.sourceId);
|
|
238
|
-
const processor = model.processors.get(flow.sourceId);
|
|
239
|
-
if (event) {
|
|
240
|
-
sourceName = event.name;
|
|
241
|
-
sourceType = 'event';
|
|
242
|
-
}
|
|
243
|
-
else if (readModel) {
|
|
244
|
-
sourceName = readModel.name;
|
|
245
|
-
sourceType = 'read-model';
|
|
246
|
-
}
|
|
247
|
-
else if (command) {
|
|
248
|
-
sourceName = command.name;
|
|
249
|
-
sourceType = 'command';
|
|
250
|
-
}
|
|
251
|
-
else if (screen) {
|
|
252
|
-
sourceName = screen.name;
|
|
253
|
-
sourceType = 'screen';
|
|
254
|
-
}
|
|
255
|
-
else if (processor) {
|
|
256
|
-
sourceName = processor.name;
|
|
257
|
-
sourceType = 'processor';
|
|
258
|
-
}
|
|
259
|
-
const fieldStatuses = flatTarget.map(({ id, path, field }) => {
|
|
260
|
-
// 1. Check if generated
|
|
261
|
-
if (field.isGenerated) {
|
|
262
|
-
return { fieldId: id, fieldName: path, status: 'generated' };
|
|
263
|
-
}
|
|
264
|
-
// 2. Check if user input
|
|
265
|
-
if (field.isUserInput) {
|
|
266
|
-
return { fieldId: id, fieldName: path, status: 'user-input' };
|
|
267
|
-
}
|
|
268
|
-
// 3. Check manual mapping
|
|
269
|
-
const manualMapping = manualMappings.find(m => m.targetFieldId === id);
|
|
270
|
-
if (manualMapping) {
|
|
271
|
-
const sourceField = flatSource.find(sf => sf.id === manualMapping.sourceFieldId);
|
|
272
|
-
return {
|
|
273
|
-
fieldId: id,
|
|
274
|
-
fieldName: path,
|
|
275
|
-
status: 'satisfied',
|
|
276
|
-
sourceFieldId: manualMapping.sourceFieldId,
|
|
277
|
-
sourceFieldName: sourceField?.path,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
// 4. Check auto-mapping by path
|
|
281
|
-
const autoMatch = flatSource.find(sf => sf.path === path);
|
|
282
|
-
if (autoMatch) {
|
|
283
|
-
return {
|
|
284
|
-
fieldId: id,
|
|
285
|
-
fieldName: path,
|
|
286
|
-
status: 'satisfied',
|
|
287
|
-
sourceFieldId: autoMatch.id,
|
|
288
|
-
sourceFieldName: autoMatch.path,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
// 5. Not satisfied - check if optional
|
|
292
|
-
if (field.isOptional) {
|
|
293
|
-
return { fieldId: id, fieldName: path, status: 'optional-missing' };
|
|
294
|
-
}
|
|
295
|
-
// 6. Unsatisfied required field
|
|
296
|
-
return { fieldId: id, fieldName: path, status: 'unsatisfied' };
|
|
297
|
-
});
|
|
298
|
-
const hasUnsatisfied = fieldStatuses.some(s => s.status === 'unsatisfied');
|
|
299
|
-
const hasOptionalMissing = fieldStatuses.some(s => s.status === 'optional-missing');
|
|
300
|
-
return {
|
|
301
|
-
flowId: flow.id,
|
|
302
|
-
sourceName,
|
|
303
|
-
sourceType,
|
|
304
|
-
isComplete: !hasUnsatisfied,
|
|
305
|
-
hasWarnings: hasOptionalMissing,
|
|
306
|
-
targetFields: fieldStatuses,
|
|
307
|
-
sourceFields: flatSource.map(sf => ({
|
|
308
|
-
id: sf.id,
|
|
309
|
-
name: sf.path,
|
|
310
|
-
type: sf.field.fieldType,
|
|
311
|
-
isList: sf.field.isList,
|
|
312
|
-
})),
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
export function showCompleteness(model, elementName, format) {
|
|
316
|
-
// Find the element by name (searches all element types)
|
|
317
|
-
const { element, ambiguous } = findElementByName(model, elementName);
|
|
318
|
-
if (ambiguous.length > 0) {
|
|
319
|
-
console.error(`Error: Multiple elements found with name "${elementName}"`);
|
|
320
|
-
console.error('Please specify using the element ID:');
|
|
321
|
-
for (const match of ambiguous) {
|
|
322
|
-
console.error(` - "${match.element.name}" (${match.type}) (id: ${match.element.id})`);
|
|
323
|
-
}
|
|
324
|
-
console.error('');
|
|
325
|
-
console.error(`Usage: eventmodeler show completeness "id:${ambiguous[0].element.id.slice(0, 8)}"`);
|
|
326
|
-
process.exit(1);
|
|
327
|
-
}
|
|
328
|
-
if (!element) {
|
|
329
|
-
console.error(`Error: Element not found: "${elementName}"`);
|
|
330
|
-
// List all available elements with their IDs
|
|
331
|
-
const allElements = [];
|
|
332
|
-
for (const cmd of model.commands.values()) {
|
|
333
|
-
allElements.push({ name: cmd.name, type: 'command', id: cmd.id });
|
|
334
|
-
}
|
|
335
|
-
for (const evt of model.events.values()) {
|
|
336
|
-
allElements.push({ name: evt.name, type: 'event', id: evt.id });
|
|
337
|
-
}
|
|
338
|
-
for (const rm of model.readModels.values()) {
|
|
339
|
-
allElements.push({ name: rm.name, type: 'read model', id: rm.id });
|
|
340
|
-
}
|
|
341
|
-
for (const scr of model.screens.values()) {
|
|
342
|
-
allElements.push({ name: scr.name, type: 'screen', id: scr.id });
|
|
343
|
-
}
|
|
344
|
-
for (const proc of model.processors.values()) {
|
|
345
|
-
allElements.push({ name: proc.name, type: 'processor', id: proc.id });
|
|
346
|
-
}
|
|
347
|
-
if (allElements.length > 0) {
|
|
348
|
-
console.error('Available elements:');
|
|
349
|
-
for (const el of allElements) {
|
|
350
|
-
console.error(` - "${el.name}" (${el.type}) (id: ${el.id.slice(0, 8)})`);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
console.error('No elements exist in the model.');
|
|
355
|
-
}
|
|
356
|
-
process.exit(1);
|
|
357
|
-
}
|
|
358
|
-
const elementTypeLabel = element.type === 'readModel' ? 'read-model' : element.type;
|
|
359
|
-
// For read models, use union completeness (all incoming events combined)
|
|
360
|
-
if (element.type === 'readModel') {
|
|
361
|
-
const unionResult = calculateReadModelUnionCompleteness(model, element.id, element.fields, element.canonicalId);
|
|
362
|
-
// Check if there are any incoming flows at all
|
|
363
|
-
let targetNodeIds;
|
|
364
|
-
if (element.canonicalId) {
|
|
365
|
-
targetNodeIds = [...model.readModels.values()]
|
|
366
|
-
.filter(rm => rm.canonicalId === element.canonicalId)
|
|
367
|
-
.map(rm => rm.id);
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
targetNodeIds = [element.id];
|
|
371
|
-
}
|
|
372
|
-
const hasIncomingFlows = [...model.flows.values()].some(f => targetNodeIds.includes(f.targetId) && f.flowType === 'EventToReadModel');
|
|
373
|
-
if (!hasIncomingFlows) {
|
|
374
|
-
if (format === 'json') {
|
|
375
|
-
outputJson({
|
|
376
|
-
elementType: elementTypeLabel,
|
|
377
|
-
name: element.name,
|
|
378
|
-
message: `No flows into this ${elementTypeLabel}`
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}">`);
|
|
383
|
-
console.log(` <no-flows>No flows into this ${elementTypeLabel}</no-flows>`);
|
|
384
|
-
console.log('</completeness>');
|
|
385
|
-
}
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const overallStatus = unionResult.isComplete ? 'complete' : 'incomplete';
|
|
389
|
-
const flatTargetFields = flattenFields(element.fields);
|
|
390
|
-
if (format === 'json') {
|
|
391
|
-
outputJson({
|
|
392
|
-
elementType: elementTypeLabel,
|
|
393
|
-
name: element.name,
|
|
394
|
-
status: overallStatus,
|
|
395
|
-
...(unionResult.hasWarnings ? { hasWarnings: true } : {}),
|
|
396
|
-
sourceEvents: unionResult.sourceEvents,
|
|
397
|
-
...(unionResult.untraceableFields.length > 0 ? { untraceableFields: unionResult.untraceableFields } : {}),
|
|
398
|
-
...(unionResult.optionalMissingFields.length > 0 ? { optionalMissingFields: unionResult.optionalMissingFields } : {}),
|
|
399
|
-
fieldStatuses: unionResult.fieldStatuses.map(f => ({
|
|
400
|
-
name: f.fieldName,
|
|
401
|
-
status: f.status,
|
|
402
|
-
...(f.sourceFieldName ? { source: f.sourceFieldName } : {})
|
|
403
|
-
})),
|
|
404
|
-
elementFields: flatTargetFields.map(({ id, path, field }) => ({
|
|
405
|
-
id,
|
|
406
|
-
name: path,
|
|
407
|
-
type: field.fieldType,
|
|
408
|
-
...(field.isList ? { isList: true } : {}),
|
|
409
|
-
...(field.isOptional ? { isOptional: true } : {}),
|
|
410
|
-
...(field.isGenerated ? { isGenerated: true } : {})
|
|
411
|
-
}))
|
|
412
|
-
});
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
// XML output for read models
|
|
416
|
-
console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}" status="${overallStatus}"${unionResult.hasWarnings ? ' hasWarnings="true"' : ''}>`);
|
|
417
|
-
console.log(' <source-events>');
|
|
418
|
-
for (const eventName of unionResult.sourceEvents) {
|
|
419
|
-
console.log(` <event name="${escapeXml(eventName)}"/>`);
|
|
420
|
-
}
|
|
421
|
-
console.log(' </source-events>');
|
|
422
|
-
if (unionResult.untraceableFields.length > 0) {
|
|
423
|
-
console.log(' <untraceable-fields>');
|
|
424
|
-
for (const fieldName of unionResult.untraceableFields) {
|
|
425
|
-
console.log(` <field name="${escapeXml(fieldName)}"/>`);
|
|
426
|
-
}
|
|
427
|
-
console.log(' </untraceable-fields>');
|
|
428
|
-
}
|
|
429
|
-
if (unionResult.optionalMissingFields.length > 0) {
|
|
430
|
-
console.log(' <optional-missing-fields>');
|
|
431
|
-
for (const fieldName of unionResult.optionalMissingFields) {
|
|
432
|
-
console.log(` <field name="${escapeXml(fieldName)}"/>`);
|
|
433
|
-
}
|
|
434
|
-
console.log(' </optional-missing-fields>');
|
|
435
|
-
}
|
|
436
|
-
console.log(' <field-statuses>');
|
|
437
|
-
for (const field of unionResult.fieldStatuses) {
|
|
438
|
-
if (field.sourceFieldName) {
|
|
439
|
-
console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}" source="${escapeXml(field.sourceFieldName)}"/>`);
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}"/>`);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
console.log(' </field-statuses>');
|
|
446
|
-
console.log(` <${elementTypeLabel}-fields>`);
|
|
447
|
-
for (const { id, path, field } of flatTargetFields) {
|
|
448
|
-
const attrs = [`id="${id}"`, `name="${escapeXml(path)}"`, `type="${field.fieldType}"`];
|
|
449
|
-
if (field.isList)
|
|
450
|
-
attrs.push('isList="true"');
|
|
451
|
-
if (field.isOptional)
|
|
452
|
-
attrs.push('isOptional="true"');
|
|
453
|
-
if (field.isGenerated)
|
|
454
|
-
attrs.push('isGenerated="true"');
|
|
455
|
-
console.log(` <field ${attrs.join(' ')}/>`);
|
|
456
|
-
}
|
|
457
|
-
console.log(` </${elementTypeLabel}-fields>`);
|
|
458
|
-
console.log('</completeness>');
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
// For non-read-model elements, use per-flow completeness
|
|
462
|
-
const validFlowTypes = INCOMING_FLOW_TYPES[element.type];
|
|
463
|
-
const incomingFlows = [...model.flows.values()].filter(f => f.targetId === element.id && validFlowTypes.includes(f.flowType));
|
|
464
|
-
if (incomingFlows.length === 0) {
|
|
465
|
-
if (format === 'json') {
|
|
466
|
-
outputJson({
|
|
467
|
-
elementType: elementTypeLabel,
|
|
468
|
-
name: element.name,
|
|
469
|
-
message: `No flows into this ${elementTypeLabel}`
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}">`);
|
|
474
|
-
console.log(` <no-flows>No flows into this ${elementTypeLabel}</no-flows>`);
|
|
475
|
-
console.log('</completeness>');
|
|
476
|
-
}
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
// Calculate completeness for each flow
|
|
480
|
-
const completenessResults = [];
|
|
481
|
-
for (const flow of incomingFlows) {
|
|
482
|
-
const sourceFields = getSourceFields(model, flow.sourceId);
|
|
483
|
-
if (sourceFields.length === 0 && element.fields.length > 0) {
|
|
484
|
-
// Source has no fields but target expects some - still calculate
|
|
485
|
-
}
|
|
486
|
-
const result = calculateFlowCompleteness(model, flow, element.fields, sourceFields);
|
|
487
|
-
completenessResults.push(result);
|
|
488
|
-
}
|
|
489
|
-
const overallComplete = completenessResults.every(r => r.isComplete);
|
|
490
|
-
const overallStatus = overallComplete ? 'complete' : 'incomplete';
|
|
491
|
-
if (format === 'json') {
|
|
492
|
-
const flatTargetFields = flattenFields(element.fields);
|
|
493
|
-
outputJson({
|
|
494
|
-
elementType: elementTypeLabel,
|
|
495
|
-
name: element.name,
|
|
496
|
-
status: overallStatus,
|
|
497
|
-
flows: completenessResults.map(result => ({
|
|
498
|
-
flowId: result.flowId,
|
|
499
|
-
from: result.sourceName,
|
|
500
|
-
sourceType: result.sourceType,
|
|
501
|
-
status: result.isComplete ? 'complete' : 'incomplete',
|
|
502
|
-
...(result.hasWarnings ? { hasWarnings: true } : {}),
|
|
503
|
-
targetFields: result.targetFields.map(f => ({
|
|
504
|
-
name: f.fieldName,
|
|
505
|
-
status: f.status,
|
|
506
|
-
...(f.sourceFieldName ? { source: f.sourceFieldName } : {})
|
|
507
|
-
})),
|
|
508
|
-
availableSourceFields: result.sourceFields.map(sf => ({
|
|
509
|
-
id: sf.id,
|
|
510
|
-
name: sf.name,
|
|
511
|
-
type: sf.type,
|
|
512
|
-
...(sf.isList ? { isList: true } : {})
|
|
513
|
-
}))
|
|
514
|
-
})),
|
|
515
|
-
elementFields: flatTargetFields.map(({ id, path, field }) => ({
|
|
516
|
-
id,
|
|
517
|
-
name: path,
|
|
518
|
-
type: field.fieldType,
|
|
519
|
-
...(field.isList ? { isList: true } : {}),
|
|
520
|
-
...(field.isOptional ? { isOptional: true } : {}),
|
|
521
|
-
...(field.isGenerated ? { isGenerated: true } : {})
|
|
522
|
-
}))
|
|
523
|
-
});
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
// Output XML
|
|
527
|
-
console.log(`<completeness ${elementTypeLabel}="${escapeXml(element.name)}" status="${overallStatus}">`);
|
|
528
|
-
for (const result of completenessResults) {
|
|
529
|
-
const flowStatus = result.isComplete ? 'complete' : 'incomplete';
|
|
530
|
-
const warningAttr = result.hasWarnings ? ' hasWarnings="true"' : '';
|
|
531
|
-
console.log(` <flow id="${result.flowId}" from="${escapeXml(result.sourceName)}" type="${result.sourceType}" status="${flowStatus}"${warningAttr}>`);
|
|
532
|
-
// Target fields
|
|
533
|
-
console.log(' <target-fields>');
|
|
534
|
-
for (const field of result.targetFields) {
|
|
535
|
-
if (field.sourceFieldName) {
|
|
536
|
-
console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}" source="${escapeXml(field.sourceFieldName)}"/>`);
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
console.log(` <field name="${escapeXml(field.fieldName)}" status="${field.status}"/>`);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
console.log(' </target-fields>');
|
|
543
|
-
// Available source fields (for AI to see what's available)
|
|
544
|
-
console.log(' <available-source-fields>');
|
|
545
|
-
for (const sf of result.sourceFields) {
|
|
546
|
-
const listAttr = sf.isList ? ' isList="true"' : '';
|
|
547
|
-
console.log(` <field id="${sf.id}" name="${escapeXml(sf.name)}" type="${sf.type}"${listAttr}/>`);
|
|
548
|
-
}
|
|
549
|
-
console.log(' </available-source-fields>');
|
|
550
|
-
console.log(' </flow>');
|
|
551
|
-
}
|
|
552
|
-
// Also show the element's fields with their IDs (needed for mapping)
|
|
553
|
-
console.log(` <${elementTypeLabel}-fields>`);
|
|
554
|
-
const flatTargetFields = flattenFields(element.fields);
|
|
555
|
-
for (const { id, path, field } of flatTargetFields) {
|
|
556
|
-
const attrs = [`id="${id}"`, `name="${escapeXml(path)}"`, `type="${field.fieldType}"`];
|
|
557
|
-
if (field.isList)
|
|
558
|
-
attrs.push('isList="true"');
|
|
559
|
-
if (field.isOptional)
|
|
560
|
-
attrs.push('isOptional="true"');
|
|
561
|
-
if (field.isGenerated)
|
|
562
|
-
attrs.push('isGenerated="true"');
|
|
563
|
-
console.log(` <field ${attrs.join(' ')}/>`);
|
|
564
|
-
}
|
|
565
|
-
console.log(` </${elementTypeLabel}-fields>`);
|
|
566
|
-
console.log('</completeness>');
|
|
567
|
-
}
|
|
568
|
-
export function showModelCompleteness(model, format) {
|
|
569
|
-
const allFlows = [...model.flows.values()];
|
|
570
|
-
const incompleteFlows = [];
|
|
571
|
-
let completeCount = 0;
|
|
572
|
-
let totalCount = 0;
|
|
573
|
-
// For read models, we use union completeness - track which canonical groups we've processed
|
|
574
|
-
const processedReadModelGroups = new Set();
|
|
575
|
-
const incompleteReadModels = [];
|
|
576
|
-
let readModelCompleteCount = 0;
|
|
577
|
-
let readModelTotalCount = 0;
|
|
578
|
-
for (const flow of allFlows) {
|
|
579
|
-
// Skip EventToReadModel flows - we handle read models separately with union logic
|
|
580
|
-
if (flow.flowType === 'EventToReadModel') {
|
|
581
|
-
continue;
|
|
582
|
-
}
|
|
583
|
-
// Get target element info
|
|
584
|
-
let targetName = 'Unknown';
|
|
585
|
-
let targetType = 'unknown';
|
|
586
|
-
let targetFields = [];
|
|
587
|
-
const targetCmd = model.commands.get(flow.targetId);
|
|
588
|
-
const targetEvt = model.events.get(flow.targetId);
|
|
589
|
-
const targetScr = model.screens.get(flow.targetId);
|
|
590
|
-
const targetProc = model.processors.get(flow.targetId);
|
|
591
|
-
if (targetCmd) {
|
|
592
|
-
targetName = targetCmd.name;
|
|
593
|
-
targetType = 'command';
|
|
594
|
-
targetFields = targetCmd.fields;
|
|
595
|
-
}
|
|
596
|
-
else if (targetEvt) {
|
|
597
|
-
targetName = targetEvt.name;
|
|
598
|
-
targetType = 'event';
|
|
599
|
-
targetFields = targetEvt.fields;
|
|
600
|
-
}
|
|
601
|
-
else if (targetScr) {
|
|
602
|
-
targetName = targetScr.name;
|
|
603
|
-
targetType = 'screen';
|
|
604
|
-
targetFields = targetScr.fields;
|
|
605
|
-
}
|
|
606
|
-
else if (targetProc) {
|
|
607
|
-
targetName = targetProc.name;
|
|
608
|
-
targetType = 'processor';
|
|
609
|
-
targetFields = targetProc.fields;
|
|
610
|
-
}
|
|
611
|
-
// Get source fields
|
|
612
|
-
const sourceFields = getSourceFields(model, flow.sourceId);
|
|
613
|
-
// Calculate completeness
|
|
614
|
-
const result = calculateFlowCompleteness(model, flow, targetFields, sourceFields);
|
|
615
|
-
totalCount++;
|
|
616
|
-
if (result.isComplete) {
|
|
617
|
-
completeCount++;
|
|
618
|
-
}
|
|
619
|
-
else {
|
|
620
|
-
const unsatisfiedFields = result.targetFields
|
|
621
|
-
.filter(f => f.status === 'unsatisfied')
|
|
622
|
-
.map(f => f.fieldName);
|
|
623
|
-
incompleteFlows.push({
|
|
624
|
-
flow,
|
|
625
|
-
sourceName: result.sourceName,
|
|
626
|
-
targetName,
|
|
627
|
-
targetType,
|
|
628
|
-
unsatisfiedFields,
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
// Process read models with union completeness
|
|
633
|
-
// Group read models by canonicalId (or use id if no canonicalId)
|
|
634
|
-
for (const rm of model.readModels.values()) {
|
|
635
|
-
const groupKey = rm.canonicalId ?? rm.id;
|
|
636
|
-
// Skip if we've already processed this canonical group
|
|
637
|
-
if (processedReadModelGroups.has(groupKey)) {
|
|
638
|
-
continue;
|
|
639
|
-
}
|
|
640
|
-
processedReadModelGroups.add(groupKey);
|
|
641
|
-
// Check if this read model has any incoming EventToReadModel flows
|
|
642
|
-
let targetNodeIds;
|
|
643
|
-
if (rm.canonicalId) {
|
|
644
|
-
targetNodeIds = [...model.readModels.values()]
|
|
645
|
-
.filter(r => r.canonicalId === rm.canonicalId)
|
|
646
|
-
.map(r => r.id);
|
|
647
|
-
}
|
|
648
|
-
else {
|
|
649
|
-
targetNodeIds = [rm.id];
|
|
650
|
-
}
|
|
651
|
-
const hasIncomingFlows = allFlows.some(f => targetNodeIds.includes(f.targetId) && f.flowType === 'EventToReadModel');
|
|
652
|
-
if (!hasIncomingFlows) {
|
|
653
|
-
// No incoming flows - skip this read model
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
readModelTotalCount++;
|
|
657
|
-
const unionResult = calculateReadModelUnionCompleteness(model, rm.id, rm.fields, rm.canonicalId);
|
|
658
|
-
if (unionResult.isComplete) {
|
|
659
|
-
readModelCompleteCount++;
|
|
660
|
-
}
|
|
661
|
-
else {
|
|
662
|
-
incompleteReadModels.push({
|
|
663
|
-
name: rm.name,
|
|
664
|
-
untraceableFields: unionResult.untraceableFields,
|
|
665
|
-
sourceEvents: unionResult.sourceEvents,
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
const incompleteCount = totalCount - completeCount;
|
|
670
|
-
const readModelIncompleteCount = readModelTotalCount - readModelCompleteCount;
|
|
671
|
-
if (format === 'json') {
|
|
672
|
-
outputJson({
|
|
673
|
-
summary: {
|
|
674
|
-
complete: completeCount + readModelCompleteCount,
|
|
675
|
-
incomplete: incompleteCount + readModelIncompleteCount,
|
|
676
|
-
total: totalCount + readModelTotalCount
|
|
677
|
-
},
|
|
678
|
-
incompleteFlows: incompleteFlows.map(item => ({
|
|
679
|
-
from: item.sourceName,
|
|
680
|
-
to: item.targetName,
|
|
681
|
-
targetType: item.targetType,
|
|
682
|
-
flowType: item.flow.flowType,
|
|
683
|
-
unsatisfiedFields: item.unsatisfiedFields
|
|
684
|
-
})),
|
|
685
|
-
...(incompleteReadModels.length > 0 ? {
|
|
686
|
-
incompleteReadModels: incompleteReadModels.map(item => ({
|
|
687
|
-
name: item.name,
|
|
688
|
-
untraceableFields: item.untraceableFields,
|
|
689
|
-
sourceEvents: item.sourceEvents
|
|
690
|
-
}))
|
|
691
|
-
} : {})
|
|
692
|
-
});
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
console.log('<model-completeness>');
|
|
696
|
-
console.log(` <summary complete="${completeCount + readModelCompleteCount}" incomplete="${incompleteCount + readModelIncompleteCount}" total="${totalCount + readModelTotalCount}"/>`);
|
|
697
|
-
if (incompleteFlows.length > 0) {
|
|
698
|
-
console.log(' <incomplete-flows>');
|
|
699
|
-
for (const item of incompleteFlows) {
|
|
700
|
-
console.log(` <flow from="${escapeXml(item.sourceName)}" to="${escapeXml(item.targetName)}" target-type="${item.targetType}" flow-type="${item.flow.flowType}">`);
|
|
701
|
-
if (item.unsatisfiedFields.length > 0) {
|
|
702
|
-
console.log(' <unsatisfied-fields>');
|
|
703
|
-
for (const fieldName of item.unsatisfiedFields) {
|
|
704
|
-
console.log(` <field name="${escapeXml(fieldName)}"/>`);
|
|
705
|
-
}
|
|
706
|
-
console.log(' </unsatisfied-fields>');
|
|
707
|
-
}
|
|
708
|
-
console.log(' </flow>');
|
|
709
|
-
}
|
|
710
|
-
console.log(' </incomplete-flows>');
|
|
711
|
-
}
|
|
712
|
-
if (incompleteReadModels.length > 0) {
|
|
713
|
-
console.log(' <incomplete-read-models>');
|
|
714
|
-
for (const item of incompleteReadModels) {
|
|
715
|
-
console.log(` <read-model name="${escapeXml(item.name)}">`);
|
|
716
|
-
console.log(' <untraceable-fields>');
|
|
717
|
-
for (const fieldName of item.untraceableFields) {
|
|
718
|
-
console.log(` <field name="${escapeXml(fieldName)}"/>`);
|
|
719
|
-
}
|
|
720
|
-
console.log(' </untraceable-fields>');
|
|
721
|
-
console.log(' <source-events>');
|
|
722
|
-
for (const eventName of item.sourceEvents) {
|
|
723
|
-
console.log(` <event name="${escapeXml(eventName)}"/>`);
|
|
724
|
-
}
|
|
725
|
-
console.log(' </source-events>');
|
|
726
|
-
console.log(' </read-model>');
|
|
727
|
-
}
|
|
728
|
-
console.log(' </incomplete-read-models>');
|
|
729
|
-
}
|
|
730
|
-
console.log('</model-completeness>');
|
|
731
|
-
}
|