eventmodeler 0.1.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.
Files changed (37) hide show
  1. package/dist/formatters.d.ts +17 -0
  2. package/dist/formatters.js +482 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +186 -0
  5. package/dist/lib/file-loader.d.ts +5 -0
  6. package/dist/lib/file-loader.js +53 -0
  7. package/dist/projection.d.ts +3 -0
  8. package/dist/projection.js +781 -0
  9. package/dist/slices/export-eventmodel-to-json/index.d.ts +2 -0
  10. package/dist/slices/export-eventmodel-to-json/index.js +296 -0
  11. package/dist/slices/list-chapters/index.d.ts +2 -0
  12. package/dist/slices/list-chapters/index.js +22 -0
  13. package/dist/slices/list-commands/index.d.ts +2 -0
  14. package/dist/slices/list-commands/index.js +21 -0
  15. package/dist/slices/list-events/index.d.ts +2 -0
  16. package/dist/slices/list-events/index.js +21 -0
  17. package/dist/slices/list-slices/index.d.ts +2 -0
  18. package/dist/slices/list-slices/index.js +21 -0
  19. package/dist/slices/mark-slice-status/index.d.ts +2 -0
  20. package/dist/slices/mark-slice-status/index.js +38 -0
  21. package/dist/slices/open-app/index.d.ts +1 -0
  22. package/dist/slices/open-app/index.js +36 -0
  23. package/dist/slices/search/index.d.ts +2 -0
  24. package/dist/slices/search/index.js +175 -0
  25. package/dist/slices/show-chapter/index.d.ts +2 -0
  26. package/dist/slices/show-chapter/index.js +43 -0
  27. package/dist/slices/show-command/index.d.ts +2 -0
  28. package/dist/slices/show-command/index.js +78 -0
  29. package/dist/slices/show-event/index.d.ts +2 -0
  30. package/dist/slices/show-event/index.js +75 -0
  31. package/dist/slices/show-model-summary/index.d.ts +2 -0
  32. package/dist/slices/show-model-summary/index.js +20 -0
  33. package/dist/slices/show-slice/index.d.ts +2 -0
  34. package/dist/slices/show-slice/index.js +239 -0
  35. package/dist/types.d.ts +161 -0
  36. package/dist/types.js +1 -0
  37. package/package.json +40 -0
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function exportEventmodelToJson(model: EventModel): void;
@@ -0,0 +1,296 @@
1
+ function mapFieldType(fieldType) {
2
+ // Map our field types to spec field types
3
+ const typeMap = {
4
+ 'UUID': 'UUID',
5
+ 'Boolean': 'Boolean',
6
+ 'Double': 'Double',
7
+ 'Decimal': 'Decimal',
8
+ 'Date': 'Date',
9
+ 'DateTime': 'DateTime',
10
+ 'Long': 'Long',
11
+ 'Int': 'Int',
12
+ 'String': 'String',
13
+ 'Custom': 'Custom',
14
+ };
15
+ return typeMap[fieldType] || 'String';
16
+ }
17
+ function mapStatus(status) {
18
+ const statusMap = {
19
+ 'created': 'Created',
20
+ 'done': 'Done',
21
+ 'in-progress': 'InProgress',
22
+ 'blocked': 'InProgress', // No blocked in spec, map to InProgress
23
+ };
24
+ return statusMap[status];
25
+ }
26
+ function convertField(field) {
27
+ const specField = {
28
+ name: field.name,
29
+ type: mapFieldType(field.fieldType),
30
+ };
31
+ if (field.isOptional)
32
+ specField.optional = true;
33
+ if (field.isGenerated)
34
+ specField.generated = true;
35
+ if (field.subfields && field.subfields.length > 0) {
36
+ specField.subfields = field.subfields.map(convertField);
37
+ }
38
+ return specField;
39
+ }
40
+ function isInSlice(slice, pos, width, height) {
41
+ const centerX = pos.x + width / 2;
42
+ const centerY = pos.y + height / 2;
43
+ return (centerX >= slice.position.x &&
44
+ centerX <= slice.position.x + slice.size.width &&
45
+ centerY >= slice.position.y &&
46
+ centerY <= slice.position.y + slice.size.height);
47
+ }
48
+ function getInboundDependencies(model, entityId, entityType) {
49
+ const deps = [];
50
+ for (const flow of model.flows.values()) {
51
+ if (flow.targetId === entityId) {
52
+ let sourceTitle = '';
53
+ let sourceType = 'COMMAND';
54
+ const cmd = model.commands.get(flow.sourceId);
55
+ const evt = model.events.get(flow.sourceId);
56
+ const rm = model.readModels.get(flow.sourceId);
57
+ const scr = model.screens.get(flow.sourceId);
58
+ const proc = model.processors.get(flow.sourceId);
59
+ if (cmd) {
60
+ sourceTitle = cmd.name;
61
+ sourceType = 'COMMAND';
62
+ }
63
+ else if (evt) {
64
+ sourceTitle = evt.name;
65
+ sourceType = 'EVENT';
66
+ }
67
+ else if (rm) {
68
+ sourceTitle = rm.name;
69
+ sourceType = 'READMODEL';
70
+ }
71
+ else if (scr) {
72
+ sourceTitle = scr.name;
73
+ sourceType = 'SCREEN';
74
+ }
75
+ else if (proc) {
76
+ sourceTitle = proc.name;
77
+ sourceType = 'AUTOMATION';
78
+ }
79
+ if (sourceTitle) {
80
+ deps.push({
81
+ id: flow.id,
82
+ title: sourceTitle,
83
+ type: 'INBOUND',
84
+ elementType: sourceType,
85
+ });
86
+ }
87
+ }
88
+ }
89
+ return deps;
90
+ }
91
+ function getOutboundDependencies(model, entityId, entityType) {
92
+ const deps = [];
93
+ for (const flow of model.flows.values()) {
94
+ if (flow.sourceId === entityId) {
95
+ let targetTitle = '';
96
+ let targetType = 'EVENT';
97
+ const cmd = model.commands.get(flow.targetId);
98
+ const evt = model.events.get(flow.targetId);
99
+ const rm = model.readModels.get(flow.targetId);
100
+ const scr = model.screens.get(flow.targetId);
101
+ const proc = model.processors.get(flow.targetId);
102
+ if (cmd) {
103
+ targetTitle = cmd.name;
104
+ targetType = 'COMMAND';
105
+ }
106
+ else if (evt) {
107
+ targetTitle = evt.name;
108
+ targetType = 'EVENT';
109
+ }
110
+ else if (rm) {
111
+ targetTitle = rm.name;
112
+ targetType = 'READMODEL';
113
+ }
114
+ else if (scr) {
115
+ targetTitle = scr.name;
116
+ targetType = 'SCREEN';
117
+ }
118
+ else if (proc) {
119
+ targetTitle = proc.name;
120
+ targetType = 'AUTOMATION';
121
+ }
122
+ if (targetTitle) {
123
+ deps.push({
124
+ id: flow.id,
125
+ title: targetTitle,
126
+ type: 'OUTBOUND',
127
+ elementType: targetType,
128
+ });
129
+ }
130
+ }
131
+ }
132
+ return deps;
133
+ }
134
+ function determineSliceType(commands, events, processors) {
135
+ // If there are processors, it's an automation slice
136
+ if (processors.length > 0)
137
+ return 'AUTOMATION';
138
+ // If there are commands and events, it's a state change
139
+ if (commands.length > 0 && events.length > 0)
140
+ return 'STATE_CHANGE';
141
+ // Otherwise it's a state view (read-only)
142
+ return 'STATE_VIEW';
143
+ }
144
+ export function exportEventmodelToJson(model) {
145
+ const specSlices = [];
146
+ // Sort slices by position
147
+ const sortedSlices = [...model.slices.values()].sort((a, b) => a.position.x - b.position.x);
148
+ for (const slice of sortedSlices) {
149
+ // Find components in this slice
150
+ const commands = [];
151
+ const events = [];
152
+ const readModels = [];
153
+ const screens = [];
154
+ const processors = [];
155
+ for (const cmd of model.commands.values()) {
156
+ if (isInSlice(slice, cmd.position, cmd.width, cmd.height)) {
157
+ commands.push(cmd);
158
+ }
159
+ }
160
+ for (const evt of model.events.values()) {
161
+ if (isInSlice(slice, evt.position, evt.width, evt.height)) {
162
+ events.push(evt);
163
+ }
164
+ }
165
+ for (const rm of model.readModels.values()) {
166
+ if (isInSlice(slice, rm.position, rm.width, rm.height)) {
167
+ readModels.push(rm);
168
+ }
169
+ }
170
+ for (const scr of model.screens.values()) {
171
+ if (isInSlice(slice, scr.position, scr.width, scr.height)) {
172
+ screens.push(scr);
173
+ }
174
+ }
175
+ for (const proc of model.processors.values()) {
176
+ if (isInSlice(slice, proc.position, proc.width, proc.height)) {
177
+ processors.push(proc);
178
+ }
179
+ }
180
+ // Find scenarios for this slice
181
+ const scenarios = [...model.scenarios.values()].filter(s => s.sliceId === slice.id);
182
+ // Convert to spec format
183
+ const specSlice = {
184
+ id: slice.id,
185
+ title: slice.name,
186
+ sliceType: determineSliceType(commands, events, processors),
187
+ status: mapStatus(slice.status),
188
+ commands: commands.map(cmd => ({
189
+ id: cmd.id,
190
+ title: cmd.name,
191
+ type: 'COMMAND',
192
+ fields: cmd.fields.map(convertField),
193
+ dependencies: [
194
+ ...getInboundDependencies(model, cmd.id, 'COMMAND'),
195
+ ...getOutboundDependencies(model, cmd.id, 'COMMAND'),
196
+ ],
197
+ })),
198
+ events: events.map(evt => ({
199
+ id: evt.id,
200
+ title: evt.name,
201
+ type: 'EVENT',
202
+ fields: evt.fields.map(convertField),
203
+ dependencies: [
204
+ ...getInboundDependencies(model, evt.id, 'EVENT'),
205
+ ...getOutboundDependencies(model, evt.id, 'EVENT'),
206
+ ],
207
+ })),
208
+ readmodels: readModels.map(rm => ({
209
+ id: rm.id,
210
+ title: rm.name,
211
+ type: 'READMODEL',
212
+ fields: rm.fields.map(convertField),
213
+ dependencies: [
214
+ ...getInboundDependencies(model, rm.id, 'READMODEL'),
215
+ ...getOutboundDependencies(model, rm.id, 'READMODEL'),
216
+ ],
217
+ })),
218
+ screens: screens.map(scr => ({
219
+ id: scr.id,
220
+ title: scr.name,
221
+ type: 'SCREEN',
222
+ fields: scr.fields.map(convertField),
223
+ dependencies: [
224
+ ...getInboundDependencies(model, scr.id, 'SCREEN'),
225
+ ...getOutboundDependencies(model, scr.id, 'SCREEN'),
226
+ ],
227
+ })),
228
+ processors: processors.map(proc => ({
229
+ id: proc.id,
230
+ title: proc.name,
231
+ type: 'AUTOMATION',
232
+ fields: proc.fields.map(convertField),
233
+ dependencies: [
234
+ ...getInboundDependencies(model, proc.id, 'AUTOMATION'),
235
+ ...getOutboundDependencies(model, proc.id, 'AUTOMATION'),
236
+ ],
237
+ })),
238
+ tables: [],
239
+ specifications: scenarios.map(scenario => {
240
+ const givenSteps = scenario.givenEvents.map(given => {
241
+ const evt = model.events.get(given.eventStickyId);
242
+ return {
243
+ id: given.eventStickyId,
244
+ title: evt?.name ?? 'Unknown',
245
+ elementType: 'EVENT',
246
+ fields: given.fieldValues,
247
+ };
248
+ });
249
+ const whenSteps = [];
250
+ if (scenario.whenCommand) {
251
+ const cmd = model.commands.get(scenario.whenCommand.commandStickyId);
252
+ whenSteps.push({
253
+ id: scenario.whenCommand.commandStickyId,
254
+ title: cmd?.name ?? 'Unknown',
255
+ elementType: 'COMMAND',
256
+ fields: scenario.whenCommand.fieldValues,
257
+ });
258
+ }
259
+ const thenSteps = [];
260
+ if (scenario.then.type === 'events' && scenario.then.expectedEvents) {
261
+ for (const expected of scenario.then.expectedEvents) {
262
+ const evt = model.events.get(expected.eventStickyId);
263
+ thenSteps.push({
264
+ id: expected.eventStickyId,
265
+ title: evt?.name ?? 'Unknown',
266
+ elementType: 'EVENT',
267
+ fields: expected.fieldValues,
268
+ });
269
+ }
270
+ }
271
+ else if (scenario.then.type === 'readModelAssertion' && scenario.then.readModelAssertion) {
272
+ const rm = model.readModels.get(scenario.then.readModelAssertion.readModelStickyId);
273
+ thenSteps.push({
274
+ id: scenario.then.readModelAssertion.readModelStickyId,
275
+ title: rm?.name ?? 'Unknown',
276
+ elementType: 'READMODEL',
277
+ fields: scenario.then.readModelAssertion.expectedFieldValues,
278
+ });
279
+ }
280
+ return {
281
+ id: scenario.id,
282
+ title: scenario.name,
283
+ linkedId: slice.id,
284
+ given: givenSteps,
285
+ when: whenSteps,
286
+ then: thenSteps,
287
+ };
288
+ }),
289
+ };
290
+ specSlices.push(specSlice);
291
+ }
292
+ const exportData = {
293
+ slices: specSlices,
294
+ };
295
+ console.log(JSON.stringify(exportData, null, 2));
296
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function listChapters(model: EventModel): void;
@@ -0,0 +1,22 @@
1
+ function escapeXml(str) {
2
+ return str
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&apos;');
8
+ }
9
+ export function listChapters(model) {
10
+ const chapters = [...model.chapters.values()];
11
+ if (chapters.length === 0) {
12
+ console.log('<chapters/>');
13
+ return;
14
+ }
15
+ // Sort by x position (left to right on timeline)
16
+ const sorted = [...chapters].sort((a, b) => a.position.x - b.position.x);
17
+ console.log('<chapters>');
18
+ for (const chapter of sorted) {
19
+ console.log(` <chapter name="${escapeXml(chapter.name)}"/>`);
20
+ }
21
+ console.log('</chapters>');
22
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function listCommands(model: EventModel): void;
@@ -0,0 +1,21 @@
1
+ function escapeXml(str) {
2
+ return str
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&apos;');
8
+ }
9
+ export function listCommands(model) {
10
+ const commands = [...model.commands.values()];
11
+ if (commands.length === 0) {
12
+ console.log('<commands/>');
13
+ return;
14
+ }
15
+ const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
16
+ console.log('<commands>');
17
+ for (const cmd of sorted) {
18
+ console.log(` <command name="${escapeXml(cmd.name)}" fields="${cmd.fields.length}"/>`);
19
+ }
20
+ console.log('</commands>');
21
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function listEvents(model: EventModel): void;
@@ -0,0 +1,21 @@
1
+ function escapeXml(str) {
2
+ return str
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&apos;');
8
+ }
9
+ export function listEvents(model) {
10
+ const events = [...model.events.values()];
11
+ if (events.length === 0) {
12
+ console.log('<events/>');
13
+ return;
14
+ }
15
+ const sorted = [...events].sort((a, b) => a.name.localeCompare(b.name));
16
+ console.log('<events>');
17
+ for (const evt of sorted) {
18
+ console.log(` <event name="${escapeXml(evt.name)}" fields="${evt.fields.length}"/>`);
19
+ }
20
+ console.log('</events>');
21
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function listSlices(model: EventModel): void;
@@ -0,0 +1,21 @@
1
+ function escapeXml(str) {
2
+ return str
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&apos;');
8
+ }
9
+ export function listSlices(model) {
10
+ const slices = [...model.slices.values()];
11
+ if (slices.length === 0) {
12
+ console.log('<slices/>');
13
+ return;
14
+ }
15
+ const sorted = [...slices].sort((a, b) => a.position.x - b.position.x);
16
+ console.log('<slices>');
17
+ for (const slice of sorted) {
18
+ console.log(` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>`);
19
+ }
20
+ console.log('</slices>');
21
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function markSliceStatus(model: EventModel, filePath: string, sliceName: string, status: string): void;
@@ -0,0 +1,38 @@
1
+ import { appendEvent } from '../../lib/file-loader.js';
2
+ const validStatuses = ['created', 'in-progress', 'blocked', 'done'];
3
+ const eventTypeMap = {
4
+ 'created': 'SliceMarkedAsCreated',
5
+ 'in-progress': 'SliceMarkedAsInProgress',
6
+ 'blocked': 'SliceMarkedAsBlocked',
7
+ 'done': 'SliceMarkedAsDone',
8
+ };
9
+ export function markSliceStatus(model, filePath, sliceName, status) {
10
+ if (!validStatuses.includes(status)) {
11
+ console.error(`Error: Invalid status: ${status}`);
12
+ console.error('Valid statuses: created, in-progress, blocked, done');
13
+ process.exit(1);
14
+ }
15
+ const newStatus = status;
16
+ const slices = [...model.slices.values()];
17
+ const sliceNameLower = sliceName.toLowerCase();
18
+ const slice = slices.find(s => s.name.toLowerCase() === sliceNameLower || s.name.toLowerCase().includes(sliceNameLower));
19
+ if (!slice) {
20
+ console.error(`Error: Slice not found: ${sliceName}`);
21
+ console.error('Available slices:');
22
+ for (const s of slices) {
23
+ console.error(` - ${s.name}`);
24
+ }
25
+ process.exit(1);
26
+ }
27
+ if (slice.status === newStatus) {
28
+ console.log(`Slice "${slice.name}" is already marked as ${newStatus}`);
29
+ return;
30
+ }
31
+ const event = {
32
+ type: eventTypeMap[newStatus],
33
+ sliceId: slice.id,
34
+ timestamp: Date.now(),
35
+ };
36
+ appendEvent(filePath, event);
37
+ console.log(`Marked slice "${slice.name}" as ${newStatus}`);
38
+ }
@@ -0,0 +1 @@
1
+ export declare function openApp(): void;
@@ -0,0 +1,36 @@
1
+ import { exec } from 'node:child_process';
2
+ import { platform } from 'node:os';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ const APP_URL = 'https://www.eventmodeling.app';
6
+ function getOpenCommand() {
7
+ switch (platform()) {
8
+ case 'darwin':
9
+ return 'open';
10
+ case 'win32':
11
+ return 'start';
12
+ default:
13
+ return 'xdg-open';
14
+ }
15
+ }
16
+ function findLocalEventModel() {
17
+ const files = fs.readdirSync(process.cwd());
18
+ const eventModelFiles = files.filter(f => f.endsWith('.eventmodel'));
19
+ if (eventModelFiles.length === 1) {
20
+ return path.join(process.cwd(), eventModelFiles[0]);
21
+ }
22
+ return null;
23
+ }
24
+ export function openApp() {
25
+ const command = getOpenCommand();
26
+ const localFile = findLocalEventModel();
27
+ console.log(`Opening ${APP_URL}...`);
28
+ if (localFile) {
29
+ console.log(`Then open: ${localFile}`);
30
+ }
31
+ exec(`${command} ${APP_URL}`, (error) => {
32
+ if (error) {
33
+ console.error(`Could not open browser. Please visit: ${APP_URL}`);
34
+ }
35
+ });
36
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function search(model: EventModel, term: string): void;
@@ -0,0 +1,175 @@
1
+ function escapeXml(str) {
2
+ return str
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&apos;');
8
+ }
9
+ function isInSlice(slice, pos, width, height) {
10
+ const centerX = pos.x + width / 2;
11
+ const centerY = pos.y + height / 2;
12
+ return (centerX >= slice.position.x &&
13
+ centerX <= slice.position.x + slice.size.width &&
14
+ centerY >= slice.position.y &&
15
+ centerY <= slice.position.y + slice.size.height);
16
+ }
17
+ function findSlicesContaining(model, pos, width, height) {
18
+ return [...model.slices.values()].filter(slice => isInSlice(slice, pos, width, height));
19
+ }
20
+ export function search(model, term) {
21
+ const termLower = term.toLowerCase();
22
+ const results = [];
23
+ // Search commands
24
+ for (const cmd of model.commands.values()) {
25
+ if (cmd.name.toLowerCase().includes(termLower)) {
26
+ const slices = findSlicesContaining(model, cmd.position, cmd.width, cmd.height);
27
+ const incomingFlows = [...model.flows.values()].filter(f => f.targetId === cmd.id);
28
+ const outgoingFlows = [...model.flows.values()].filter(f => f.sourceId === cmd.id);
29
+ let xml = ` <command name="${escapeXml(cmd.name)}" fields="${cmd.fields.length}">\n`;
30
+ if (slices.length > 0) {
31
+ xml += ' <in-slices>\n';
32
+ for (const slice of slices) {
33
+ xml += ` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>\n`;
34
+ }
35
+ xml += ' </in-slices>\n';
36
+ }
37
+ if (incomingFlows.length > 0) {
38
+ xml += ' <triggered-by>\n';
39
+ for (const flow of incomingFlows) {
40
+ const screen = model.screens.get(flow.sourceId);
41
+ const processor = model.processors.get(flow.sourceId);
42
+ if (screen)
43
+ xml += ` <screen name="${escapeXml(screen.name)}"/>\n`;
44
+ if (processor)
45
+ xml += ` <processor name="${escapeXml(processor.name)}"/>\n`;
46
+ }
47
+ xml += ' </triggered-by>\n';
48
+ }
49
+ if (outgoingFlows.length > 0) {
50
+ xml += ' <produces>\n';
51
+ for (const flow of outgoingFlows) {
52
+ const evt = model.events.get(flow.targetId);
53
+ if (evt)
54
+ xml += ` <event name="${escapeXml(evt.name)}"/>\n`;
55
+ }
56
+ xml += ' </produces>\n';
57
+ }
58
+ xml += ' </command>';
59
+ results.push(xml);
60
+ }
61
+ }
62
+ // Search events
63
+ for (const evt of model.events.values()) {
64
+ if (evt.name.toLowerCase().includes(termLower)) {
65
+ const slices = findSlicesContaining(model, evt.position, evt.width, evt.height);
66
+ const incomingFlows = [...model.flows.values()].filter(f => f.targetId === evt.id);
67
+ const outgoingFlows = [...model.flows.values()].filter(f => f.sourceId === evt.id);
68
+ let xml = ` <event name="${escapeXml(evt.name)}" fields="${evt.fields.length}">\n`;
69
+ if (slices.length > 0) {
70
+ xml += ' <in-slices>\n';
71
+ for (const slice of slices) {
72
+ xml += ` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>\n`;
73
+ }
74
+ xml += ' </in-slices>\n';
75
+ }
76
+ if (incomingFlows.length > 0) {
77
+ xml += ' <produced-by>\n';
78
+ for (const flow of incomingFlows) {
79
+ const cmd = model.commands.get(flow.sourceId);
80
+ if (cmd)
81
+ xml += ` <command name="${escapeXml(cmd.name)}"/>\n`;
82
+ }
83
+ xml += ' </produced-by>\n';
84
+ }
85
+ if (outgoingFlows.length > 0) {
86
+ xml += ' <consumed-by>\n';
87
+ for (const flow of outgoingFlows) {
88
+ const rm = model.readModels.get(flow.targetId);
89
+ if (rm)
90
+ xml += ` <read-model name="${escapeXml(rm.name)}"/>\n`;
91
+ }
92
+ xml += ' </consumed-by>\n';
93
+ }
94
+ xml += ' </event>';
95
+ results.push(xml);
96
+ }
97
+ }
98
+ // Search read models
99
+ for (const rm of model.readModels.values()) {
100
+ if (rm.name.toLowerCase().includes(termLower)) {
101
+ const slices = findSlicesContaining(model, rm.position, rm.width, rm.height);
102
+ let xml = ` <read-model name="${escapeXml(rm.name)}" fields="${rm.fields.length}">\n`;
103
+ if (slices.length > 0) {
104
+ xml += ' <in-slices>\n';
105
+ for (const slice of slices) {
106
+ xml += ` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>\n`;
107
+ }
108
+ xml += ' </in-slices>\n';
109
+ }
110
+ xml += ' </read-model>';
111
+ results.push(xml);
112
+ }
113
+ }
114
+ // Search screens
115
+ for (const scr of model.screens.values()) {
116
+ if (scr.name.toLowerCase().includes(termLower)) {
117
+ const slices = findSlicesContaining(model, scr.position, scr.width, scr.height);
118
+ let xml = ` <screen name="${escapeXml(scr.name)}" fields="${scr.fields.length}">\n`;
119
+ if (slices.length > 0) {
120
+ xml += ' <in-slices>\n';
121
+ for (const slice of slices) {
122
+ xml += ` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>\n`;
123
+ }
124
+ xml += ' </in-slices>\n';
125
+ }
126
+ xml += ' </screen>';
127
+ results.push(xml);
128
+ }
129
+ }
130
+ // Search processors
131
+ for (const proc of model.processors.values()) {
132
+ if (proc.name.toLowerCase().includes(termLower)) {
133
+ const slices = findSlicesContaining(model, proc.position, proc.width, proc.height);
134
+ let xml = ` <processor name="${escapeXml(proc.name)}" fields="${proc.fields.length}">\n`;
135
+ if (slices.length > 0) {
136
+ xml += ' <in-slices>\n';
137
+ for (const slice of slices) {
138
+ xml += ` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>\n`;
139
+ }
140
+ xml += ' </in-slices>\n';
141
+ }
142
+ xml += ' </processor>';
143
+ results.push(xml);
144
+ }
145
+ }
146
+ // Search slices
147
+ for (const slice of model.slices.values()) {
148
+ if (slice.name.toLowerCase().includes(termLower)) {
149
+ let xml = ` <slice name="${escapeXml(slice.name)}" status="${slice.status}"/>`;
150
+ results.push(xml);
151
+ }
152
+ }
153
+ // Search scenarios
154
+ for (const scenario of model.scenarios.values()) {
155
+ if (scenario.name.toLowerCase().includes(termLower)) {
156
+ const slice = model.slices.get(scenario.sliceId);
157
+ let xml = ` <scenario name="${escapeXml(scenario.name)}">\n`;
158
+ if (slice) {
159
+ xml += ` <in-slice name="${escapeXml(slice.name)}" status="${slice.status}"/>\n`;
160
+ }
161
+ xml += ' </scenario>';
162
+ results.push(xml);
163
+ }
164
+ }
165
+ if (results.length === 0) {
166
+ console.log(`<search-results query="${escapeXml(term)}" count="0"/>`);
167
+ }
168
+ else {
169
+ console.log(`<search-results query="${escapeXml(term)}" count="${results.length}">`);
170
+ for (const result of results) {
171
+ console.log(result);
172
+ }
173
+ console.log('</search-results>');
174
+ }
175
+ }
@@ -0,0 +1,2 @@
1
+ import type { EventModel } from '../../types.js';
2
+ export declare function showChapter(model: EventModel, name: string): void;