@flow-scanner/lightning-flow-scanner-core 6.10.6 → 6.11.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.
@@ -0,0 +1,85 @@
1
+ import { FlowNode } from "./FlowNode";
2
+ /**
3
+ * FlowGraph: Pre-computed connectivity cache built using Compiler.
4
+ * Built once during Flow.preProcessNodes() to avoid repeated traversals.
5
+ *
6
+ * Uses the existing Compiler to build the graph - no duplicate logic!
7
+ */
8
+ export declare class FlowGraph {
9
+ private nodeMap;
10
+ private reachableFromStart;
11
+ private elementsInLoop;
12
+ private faultConnectors;
13
+ private normalConnectors;
14
+ private allConnectors;
15
+ private reverseConnectors;
16
+ constructor(nodes: FlowNode[], startReference?: string, startNode?: FlowNode);
17
+ /**
18
+ * Add START node connectors to the connector maps (for flows with explicit <start> element)
19
+ */
20
+ private addStartNodeConnectors;
21
+ /**
22
+ * Add START edge for newer flows that use startElementReference (no explicit <start> node)
23
+ */
24
+ private addStartEdgeFromReference;
25
+ /**
26
+ * Build node map for O(1) lookups
27
+ */
28
+ private buildNodeMaps;
29
+ /**
30
+ * Build connector maps by inspecting node connectors
31
+ */
32
+ private buildConnectorMaps;
33
+ /**
34
+ * Use Compiler to compute which elements are reachable from start.
35
+ * This reuses the existing IDDFS traversal logic!
36
+ */
37
+ private computeReachability;
38
+ /**
39
+ * Use Compiler to compute which elements are inside loops.
40
+ * Calls Compiler.traverseFlow() for each loop with endElementName.
41
+ */
42
+ private computeLoopBoundaries;
43
+ isReachable(elementName: string): boolean;
44
+ getReachableElements(): Set<string>;
45
+ isInLoop(elementName: string): boolean;
46
+ getContainingLoop(elementName: string): string | undefined;
47
+ getLoopElements(loopName: string): Set<string>;
48
+ hasFaultConnector(elementName: string): boolean;
49
+ getFaultTargets(elementName: string): string[];
50
+ getNextElements(elementName: string): string[];
51
+ getAllNextElements(elementName: string): string[];
52
+ getPreviousElements(elementName: string): string[];
53
+ getNode(elementName: string): FlowNode | undefined;
54
+ isPartOfFaultHandling(elementName: string): boolean;
55
+ getLoopNodes(): FlowNode[];
56
+ forEachReachable(callback: (node: FlowNode) => void): void;
57
+ /**
58
+ * Export the graph to Mermaid flowchart syntax with rich documentation.
59
+ */
60
+ toMermaid(options?: {
61
+ includeDetails?: boolean;
62
+ includeMarkdownDocs?: boolean;
63
+ collapsedDetails?: boolean;
64
+ flowMetadata?: any;
65
+ }): string;
66
+ private generateMermaidDiagram;
67
+ private generateStartNode;
68
+ private getNodeShape;
69
+ private generateEdges;
70
+ private findEndNodes;
71
+ private generateLoopSubgraphs;
72
+ private generateMermaidStyles;
73
+ private generateNodeDetailsMarkdown;
74
+ private nodeToMarkdownTable;
75
+ private prettifyValue;
76
+ /**
77
+ * Generate full markdown documentation with diagram and node details
78
+ */
79
+ private generateFullMarkdownDoc;
80
+ /**
81
+ * Export the graph to PlantUML syntax for UML-style diagrams.
82
+ * @returns PlantUML string.
83
+ */
84
+ toPlantUML(): string;
85
+ }
@@ -0,0 +1,532 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "FlowGraph", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return FlowGraph;
9
+ }
10
+ });
11
+ const _Compiler = require("../libs/Compiler");
12
+ function _define_property(obj, key, value) {
13
+ if (key in obj) {
14
+ Object.defineProperty(obj, key, {
15
+ value: value,
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true
19
+ });
20
+ } else {
21
+ obj[key] = value;
22
+ }
23
+ return obj;
24
+ }
25
+ let FlowGraph = class FlowGraph {
26
+ /**
27
+ * Add START node connectors to the connector maps (for flows with explicit <start> element)
28
+ */ addStartNodeConnectors(startNode) {
29
+ const startName = 'START';
30
+ this.faultConnectors.set(startName, new Set());
31
+ this.normalConnectors.set(startName, new Set());
32
+ this.allConnectors.set(startName, new Set());
33
+ if (!startNode.connectors || startNode.connectors.length === 0) return;
34
+ for (const connector of startNode.connectors){
35
+ var _connector_connectorTargetReference, // START node typically has normal connectors, not fault connectors
36
+ _this_normalConnectors_get, _this_allConnectors_get, _this_reverseConnectors_get;
37
+ var _connector_connectorTargetReference_targetReference;
38
+ const targetRef = (_connector_connectorTargetReference_targetReference = (_connector_connectorTargetReference = connector.connectorTargetReference) === null || _connector_connectorTargetReference === void 0 ? void 0 : _connector_connectorTargetReference.targetReference) !== null && _connector_connectorTargetReference_targetReference !== void 0 ? _connector_connectorTargetReference_targetReference : connector.reference;
39
+ if (!targetRef) continue;
40
+ (_this_normalConnectors_get = this.normalConnectors.get(startName)) === null || _this_normalConnectors_get === void 0 ? void 0 : _this_normalConnectors_get.add(targetRef);
41
+ (_this_allConnectors_get = this.allConnectors.get(startName)) === null || _this_allConnectors_get === void 0 ? void 0 : _this_allConnectors_get.add(targetRef);
42
+ // Build reverse map
43
+ if (!this.reverseConnectors.has(targetRef)) {
44
+ this.reverseConnectors.set(targetRef, new Set());
45
+ }
46
+ (_this_reverseConnectors_get = this.reverseConnectors.get(targetRef)) === null || _this_reverseConnectors_get === void 0 ? void 0 : _this_reverseConnectors_get.add(startName);
47
+ }
48
+ }
49
+ /**
50
+ * Add START edge for newer flows that use startElementReference (no explicit <start> node)
51
+ */ addStartEdgeFromReference(startReference) {
52
+ var // Direct edge: START --> first element
53
+ _this_normalConnectors_get, _this_allConnectors_get, _this_reverseConnectors_get;
54
+ const startName = 'START';
55
+ this.faultConnectors.set(startName, new Set());
56
+ this.normalConnectors.set(startName, new Set());
57
+ this.allConnectors.set(startName, new Set());
58
+ (_this_normalConnectors_get = this.normalConnectors.get(startName)) === null || _this_normalConnectors_get === void 0 ? void 0 : _this_normalConnectors_get.add(startReference);
59
+ (_this_allConnectors_get = this.allConnectors.get(startName)) === null || _this_allConnectors_get === void 0 ? void 0 : _this_allConnectors_get.add(startReference);
60
+ // Build reverse map
61
+ if (!this.reverseConnectors.has(startReference)) {
62
+ this.reverseConnectors.set(startReference, new Set());
63
+ }
64
+ (_this_reverseConnectors_get = this.reverseConnectors.get(startReference)) === null || _this_reverseConnectors_get === void 0 ? void 0 : _this_reverseConnectors_get.add(startName);
65
+ }
66
+ /**
67
+ * Build node map for O(1) lookups
68
+ */ buildNodeMaps(nodes) {
69
+ for (const node of nodes){
70
+ this.nodeMap.set(node.name, node);
71
+ }
72
+ }
73
+ /**
74
+ * Build connector maps by inspecting node connectors
75
+ */ buildConnectorMaps(nodes) {
76
+ for (const node of nodes){
77
+ this.faultConnectors.set(node.name, new Set());
78
+ this.normalConnectors.set(node.name, new Set());
79
+ this.allConnectors.set(node.name, new Set());
80
+ if (!node.connectors || node.connectors.length === 0) continue;
81
+ for (const connector of node.connectors){
82
+ var _connector_connectorTargetReference, _this_allConnectors_get, _this_reverseConnectors_get;
83
+ var _connector_connectorTargetReference_targetReference;
84
+ const targetRef = (_connector_connectorTargetReference_targetReference = (_connector_connectorTargetReference = connector.connectorTargetReference) === null || _connector_connectorTargetReference === void 0 ? void 0 : _connector_connectorTargetReference.targetReference) !== null && _connector_connectorTargetReference_targetReference !== void 0 ? _connector_connectorTargetReference_targetReference : connector.reference;
85
+ if (!targetRef) continue;
86
+ // Categorize by connector type
87
+ if (connector.type === "faultConnector") {
88
+ var _this_faultConnectors_get;
89
+ (_this_faultConnectors_get = this.faultConnectors.get(node.name)) === null || _this_faultConnectors_get === void 0 ? void 0 : _this_faultConnectors_get.add(targetRef);
90
+ } else {
91
+ var _this_normalConnectors_get;
92
+ (_this_normalConnectors_get = this.normalConnectors.get(node.name)) === null || _this_normalConnectors_get === void 0 ? void 0 : _this_normalConnectors_get.add(targetRef);
93
+ }
94
+ (_this_allConnectors_get = this.allConnectors.get(node.name)) === null || _this_allConnectors_get === void 0 ? void 0 : _this_allConnectors_get.add(targetRef);
95
+ // Build reverse map for "previous elements" queries
96
+ if (!this.reverseConnectors.has(targetRef)) {
97
+ this.reverseConnectors.set(targetRef, new Set());
98
+ }
99
+ (_this_reverseConnectors_get = this.reverseConnectors.get(targetRef)) === null || _this_reverseConnectors_get === void 0 ? void 0 : _this_reverseConnectors_get.add(node.name);
100
+ }
101
+ }
102
+ }
103
+ /**
104
+ * Use Compiler to compute which elements are reachable from start.
105
+ * This reuses the existing IDDFS traversal logic!
106
+ */ computeReachability(startReference) {
107
+ const compiler = new _Compiler.Compiler();
108
+ compiler.traverseFlow(startReference, (element)=>{
109
+ this.reachableFromStart.add(element.name);
110
+ }, this.nodeMap, this.allConnectors);
111
+ }
112
+ /**
113
+ * Use Compiler to compute which elements are inside loops.
114
+ * Calls Compiler.traverseFlow() for each loop with endElementName.
115
+ */ computeLoopBoundaries() {
116
+ const loopNodes = Array.from(this.nodeMap.values()).filter((n)=>n.subtype === "loops");
117
+ for (const loopNode of loopNodes){
118
+ var _loopNode_element_noMoreValuesConnector, _loopNode_element;
119
+ var _loopNode_element_noMoreValuesConnector_targetReference;
120
+ // Find loop end (noMoreValuesConnector)
121
+ const loopEnd = (_loopNode_element_noMoreValuesConnector_targetReference = (_loopNode_element = loopNode.element) === null || _loopNode_element === void 0 ? void 0 : (_loopNode_element_noMoreValuesConnector = _loopNode_element.noMoreValuesConnector) === null || _loopNode_element_noMoreValuesConnector === void 0 ? void 0 : _loopNode_element_noMoreValuesConnector.targetReference) !== null && _loopNode_element_noMoreValuesConnector_targetReference !== void 0 ? _loopNode_element_noMoreValuesConnector_targetReference : loopNode.name;
122
+ // Use Compiler to find all elements between loop start and end
123
+ const compiler = new _Compiler.Compiler();
124
+ compiler.traverseFlow(loopNode.name, (element)=>{
125
+ this.elementsInLoop.set(element.name, loopNode.name);
126
+ }, this.nodeMap, this.allConnectors, loopEnd); // Pass endElementName to stop at loop boundary
127
+ }
128
+ }
129
+ // ========== PUBLIC QUERY API ==========
130
+ isReachable(elementName) {
131
+ return this.reachableFromStart.has(elementName);
132
+ }
133
+ getReachableElements() {
134
+ return new Set(this.reachableFromStart);
135
+ }
136
+ isInLoop(elementName) {
137
+ return this.elementsInLoop.has(elementName);
138
+ }
139
+ getContainingLoop(elementName) {
140
+ return this.elementsInLoop.get(elementName);
141
+ }
142
+ getLoopElements(loopName) {
143
+ const result = new Set();
144
+ for (const [element, loop] of this.elementsInLoop){
145
+ if (loop === loopName) {
146
+ result.add(element);
147
+ }
148
+ }
149
+ return result;
150
+ }
151
+ hasFaultConnector(elementName) {
152
+ const faults = this.faultConnectors.get(elementName);
153
+ return faults ? faults.size > 0 : false;
154
+ }
155
+ getFaultTargets(elementName) {
156
+ return Array.from(this.faultConnectors.get(elementName) || []);
157
+ }
158
+ getNextElements(elementName) {
159
+ return Array.from(this.normalConnectors.get(elementName) || []);
160
+ }
161
+ getAllNextElements(elementName) {
162
+ return Array.from(this.allConnectors.get(elementName) || []);
163
+ }
164
+ getPreviousElements(elementName) {
165
+ return Array.from(this.reverseConnectors.get(elementName) || []);
166
+ }
167
+ getNode(elementName) {
168
+ return this.nodeMap.get(elementName);
169
+ }
170
+ isPartOfFaultHandling(elementName) {
171
+ const previous = this.getPreviousElements(elementName);
172
+ return previous.some((prev)=>{
173
+ const faultTargets = this.faultConnectors.get(prev);
174
+ var _faultTargets_has;
175
+ return (_faultTargets_has = faultTargets === null || faultTargets === void 0 ? void 0 : faultTargets.has(elementName)) !== null && _faultTargets_has !== void 0 ? _faultTargets_has : false;
176
+ });
177
+ }
178
+ getLoopNodes() {
179
+ return Array.from(this.nodeMap.values()).filter((n)=>n.subtype === "loops");
180
+ }
181
+ forEachReachable(callback) {
182
+ for (const elementName of this.reachableFromStart){
183
+ const node = this.nodeMap.get(elementName);
184
+ if (node) {
185
+ callback(node);
186
+ }
187
+ }
188
+ }
189
+ /**
190
+ * Export the graph to Mermaid flowchart syntax with rich documentation.
191
+ */ toMermaid(options = {}) {
192
+ let output = "";
193
+ const diagram = this.generateMermaidDiagram(options);
194
+ if (options.includeMarkdownDocs) {
195
+ output = this.generateFullMarkdownDoc(diagram, options);
196
+ } else {
197
+ output = `\`\`\`mermaid\n${diagram}\n\`\`\``;
198
+ }
199
+ return output;
200
+ }
201
+ generateMermaidDiagram(options) {
202
+ let mermaid = 'flowchart TB\n';
203
+ // START node with flow metadata
204
+ mermaid += this.generateStartNode(options.flowMetadata) + '\n\n';
205
+ // Define nodes using FlowNode helper methods
206
+ for (const [name, node] of this.nodeMap){
207
+ const icon = node.getIcon();
208
+ const typeLabel = node.getTypeLabel();
209
+ const summary = options.includeDetails ? node.getSummary() : '';
210
+ let label = `${icon} <em>${typeLabel}</em><br/>${node.label || node.name}`;
211
+ if (summary) {
212
+ label += `<br/><small>${summary}</small>`;
213
+ }
214
+ const shape = this.getNodeShape(node.subtype);
215
+ mermaid += ` ${name}${shape[0]}"${label}"${shape[1]}:::${node.subtype}\n`;
216
+ }
217
+ mermaid += '\n';
218
+ mermaid += this.generateEdges() + '\n';
219
+ mermaid += this.generateLoopSubgraphs() + '\n';
220
+ mermaid += this.generateMermaidStyles();
221
+ return mermaid;
222
+ }
223
+ generateStartNode(flowMetadata) {
224
+ if (!flowMetadata) {
225
+ return 'START(["\uD83D\uDE80 <b>START</b>"]):::startClass';
226
+ }
227
+ let label = '\uD83D\uDE80 <b>START</b>'; // ROCKET
228
+ if (flowMetadata.processType === 'Flow') {
229
+ label += '<br/><b>Screen Flow</b>';
230
+ } else if (flowMetadata.processType === 'AutoLaunchedFlow') {
231
+ label += '<br/><b>AutoLaunched Flow</b>';
232
+ if (flowMetadata.triggerType) {
233
+ label += `<br/>Type: <b>${this.prettifyValue(flowMetadata.triggerType)}</b>`;
234
+ }
235
+ } else if (flowMetadata.object) {
236
+ label += `<br/><b>${flowMetadata.object}</b>`;
237
+ if (flowMetadata.triggerType) {
238
+ label += `<br/>Type: <b>${this.prettifyValue(flowMetadata.triggerType)}</b>`;
239
+ }
240
+ }
241
+ if (flowMetadata.status) {
242
+ const statusIcon = flowMetadata.status === 'Active' ? '✅' : '⚠️';
243
+ label += `<br/>${statusIcon} ${flowMetadata.status}`;
244
+ }
245
+ return `START(["${label}"]):::startClass`;
246
+ }
247
+ getNodeShape(subtype) {
248
+ const shapeMap = {
249
+ decisions: [
250
+ '{',
251
+ '}'
252
+ ],
253
+ loops: [
254
+ '{{',
255
+ '}}'
256
+ ],
257
+ collectionProcessors: [
258
+ '{{',
259
+ '}}'
260
+ ],
261
+ transforms: [
262
+ '{{',
263
+ '}}'
264
+ ],
265
+ screens: [
266
+ '([',
267
+ '])'
268
+ ],
269
+ recordCreates: [
270
+ '[(',
271
+ ')]'
272
+ ],
273
+ recordDeletes: [
274
+ '[(',
275
+ ')]'
276
+ ],
277
+ recordLookups: [
278
+ '[(',
279
+ ')]'
280
+ ],
281
+ recordUpdates: [
282
+ '[(',
283
+ ')]'
284
+ ],
285
+ subflows: [
286
+ '[[',
287
+ ']]'
288
+ ],
289
+ assignments: [
290
+ '[\\',
291
+ '/]'
292
+ ],
293
+ default: [
294
+ '(',
295
+ ')'
296
+ ]
297
+ };
298
+ return shapeMap[subtype] || shapeMap.default;
299
+ }
300
+ generateEdges() {
301
+ let edges = '';
302
+ // Normal connectors
303
+ for (const [from, targets] of this.allConnectors){
304
+ for (const to of targets){
305
+ edges += ` ${from} --> ${to}\n`;
306
+ }
307
+ }
308
+ // Fault connectors (dashed)
309
+ for (const [from, faults] of this.faultConnectors){
310
+ for (const to of faults){
311
+ edges += ` ${from} -. Fault .-> ${to}\n`;
312
+ }
313
+ }
314
+ // Add END nodes
315
+ const endNodes = this.findEndNodes();
316
+ for (const endNode of endNodes){
317
+ edges += ` ${endNode}(( END )):::endClass\n`;
318
+ }
319
+ return edges;
320
+ }
321
+ findEndNodes() {
322
+ const endNodes = new Set();
323
+ for (const [from, targets] of this.allConnectors){
324
+ for (const to of targets){
325
+ // If target doesn't exist in nodeMap, it's an END
326
+ if (!this.nodeMap.has(to)) {
327
+ endNodes.add(to);
328
+ }
329
+ }
330
+ }
331
+ return endNodes;
332
+ }
333
+ generateLoopSubgraphs() {
334
+ let subgraphs = '';
335
+ for (const loopNode of this.getLoopNodes()){
336
+ const loopElems = this.getLoopElements(loopNode.name);
337
+ if (loopElems.size > 0) {
338
+ subgraphs += ` subgraph "${loopNode.label || loopNode.name} Loop"\n`;
339
+ for (const elem of loopElems){
340
+ subgraphs += ` ${elem}\n`;
341
+ }
342
+ subgraphs += ' end\n';
343
+ }
344
+ }
345
+ return subgraphs;
346
+ }
347
+ generateMermaidStyles() {
348
+ const styles = {
349
+ actionCalls: {
350
+ fill: '#D4E4FC',
351
+ color: 'black'
352
+ },
353
+ assignments: {
354
+ fill: '#FBEED7',
355
+ color: 'black'
356
+ },
357
+ collectionProcessors: {
358
+ fill: '#F0E3FA',
359
+ color: 'black'
360
+ },
361
+ customErrors: {
362
+ fill: '#FFE9E9',
363
+ color: 'black'
364
+ },
365
+ decisions: {
366
+ fill: '#FDEAF6',
367
+ color: 'black'
368
+ },
369
+ loops: {
370
+ fill: '#FDEAF6',
371
+ color: 'black'
372
+ },
373
+ recordCreates: {
374
+ fill: '#FFF8C9',
375
+ color: 'black'
376
+ },
377
+ recordDeletes: {
378
+ fill: '#FFF8C9',
379
+ color: 'black'
380
+ },
381
+ recordLookups: {
382
+ fill: '#EDEAFF',
383
+ color: 'black'
384
+ },
385
+ recordUpdates: {
386
+ fill: '#FFF8C9',
387
+ color: 'black'
388
+ },
389
+ screens: {
390
+ fill: '#DFF6FF',
391
+ color: 'black'
392
+ },
393
+ subflows: {
394
+ fill: '#D4E4FC',
395
+ color: 'black'
396
+ },
397
+ transforms: {
398
+ fill: '#FDEAF6',
399
+ color: 'black'
400
+ },
401
+ startClass: {
402
+ fill: '#D9F2E6',
403
+ color: 'black'
404
+ },
405
+ endClass: {
406
+ fill: '#F9BABA',
407
+ color: 'black'
408
+ }
409
+ };
410
+ let styleStr = '';
411
+ for (const [className, style] of Object.entries(styles)){
412
+ styleStr += ` classDef ${className} fill:${style.fill},color:${style.color},stroke:#333,stroke-width:2px\n`;
413
+ }
414
+ return styleStr;
415
+ }
416
+ generateNodeDetailsMarkdown(collapsed) {
417
+ let md = '## Flow Nodes Details\n\n';
418
+ if (collapsed) {
419
+ md += '<details><summary>NODE DETAILS (expand to view)</summary>\n\n';
420
+ }
421
+ for (const [name, node] of this.nodeMap){
422
+ md += `### ${name}\n\n`;
423
+ md += this.nodeToMarkdownTable(node);
424
+ md += '\n';
425
+ }
426
+ if (collapsed) {
427
+ md += '</details>\n\n';
428
+ }
429
+ return md;
430
+ }
431
+ nodeToMarkdownTable(node) {
432
+ let table = '| Property | Value |\n|:---|:---|\n';
433
+ // Use typed properties from FlowNode
434
+ if (node.label) table += `| Label | ${node.label} |\n`;
435
+ table += `| Type | ${node.getTypeLabel()} |\n`;
436
+ // Type-specific properties (now type-safe!)
437
+ if (node.actionType) table += `| Action Type | ${this.prettifyValue(node.actionType)} |\n`;
438
+ if (node.actionName) table += `| Action Name | ${node.actionName} |\n`;
439
+ if (node.object) table += `| Object | ${node.object} |\n`;
440
+ if (node.flowName) table += `| Subflow | ${node.flowName} |\n`;
441
+ if (node.collectionReference) table += `| Collection | ${node.collectionReference} |\n`;
442
+ if (node.elementSubtype) table += `| Subtype | ${this.prettifyValue(node.elementSubtype)} |\n`;
443
+ // Decision rules
444
+ if (node.rules && node.rules.length > 0) {
445
+ table += `| Rules | ${node.rules.length} |\n`;
446
+ for (const rule of node.rules){
447
+ const conditions = Array.isArray(rule.conditions) ? rule.conditions : rule.conditions ? [
448
+ rule.conditions
449
+ ] : [];
450
+ table += `| ↳ ${rule.label || rule.name} | ${conditions.length} condition(s) |\n`;
451
+ }
452
+ }
453
+ // Screen fields
454
+ if (node.fields && node.fields.length > 0) {
455
+ table += `| Fields | ${node.fields.length} |\n`;
456
+ }
457
+ if (node.description) table += `| Description | ${node.description} |\n`;
458
+ if (node.faultConnector) table += `| Has Fault Handler | ✅ |\n`;
459
+ return table;
460
+ }
461
+ prettifyValue(value) {
462
+ return value.replace(/([A-Z])/g, ' $1').replace(/^./, (str)=>str.toUpperCase()).trim();
463
+ }
464
+ /**
465
+ * Generate full markdown documentation with diagram and node details
466
+ */ generateFullMarkdownDoc(diagram, options) {
467
+ let md = '';
468
+ // Header with flow metadata would come from Flow class
469
+ md += '## Flow Diagram\n\n';
470
+ md += '```mermaid\n';
471
+ md += diagram;
472
+ md += '\n```\n\n';
473
+ // Node details section
474
+ if (options.includeDetails) {
475
+ md += this.generateNodeDetailsMarkdown(options.collapsedDetails);
476
+ }
477
+ return md;
478
+ }
479
+ /**
480
+ * Export the graph to PlantUML syntax for UML-style diagrams.
481
+ * @returns PlantUML string.
482
+ */ toPlantUML() {
483
+ let plantuml = '@startuml\nskinparam activityBackgroundColor #D4E4FC\n'; // Basic styling
484
+ // Nodes
485
+ for (const [name, node] of this.nodeMap){
486
+ plantuml += `activity "${node.subtype}: ${name}" as ${name}\n`;
487
+ }
488
+ // Edges
489
+ for (const [from, targets] of this.allConnectors){
490
+ for (const to of targets){
491
+ plantuml += `${from} --> ${to}\n`;
492
+ }
493
+ }
494
+ // Loops as groups
495
+ for (const loopNode of this.getLoopNodes()){
496
+ plantuml += `partition "${loopNode.name} Loop" {\n`;
497
+ const loopElems = this.getLoopElements(loopNode.name);
498
+ for (const elem of loopElems){
499
+ plantuml += ` ${elem}\n`;
500
+ }
501
+ plantuml += '}\n';
502
+ }
503
+ plantuml += '@enduml';
504
+ return plantuml;
505
+ }
506
+ constructor(nodes, startReference, startNode){
507
+ // Fast lookups by element name
508
+ _define_property(this, "nodeMap", new Map());
509
+ // Pre-computed sets for common queries (built using Compiler)
510
+ _define_property(this, "reachableFromStart", new Set());
511
+ _define_property(this, "elementsInLoop", new Map()); // element -> loop name
512
+ // Connector metadata (extracted during node processing)
513
+ _define_property(this, "faultConnectors", new Map());
514
+ _define_property(this, "normalConnectors", new Map());
515
+ _define_property(this, "allConnectors", new Map());
516
+ _define_property(this, "reverseConnectors", new Map());
517
+ this.buildNodeMaps(nodes);
518
+ this.buildConnectorMaps(nodes);
519
+ // ALWAYS ensure START node edges exist
520
+ if (startNode) {
521
+ // Old flows: use explicit <start> element connectors
522
+ this.addStartNodeConnectors(startNode);
523
+ } else if (startReference) {
524
+ // New flows: direct edge from START to startElementReference
525
+ this.addStartEdgeFromReference(startReference);
526
+ }
527
+ this.computeLoopBoundaries();
528
+ if (startReference) {
529
+ this.computeReachability(startReference);
530
+ }
531
+ }
532
+ };
@@ -1,9 +1,65 @@
1
1
  import { FlowElement } from "./FlowElement";
2
2
  import { FlowElementConnector } from "./FlowElementConnector";
3
+ import { type NodeIconConfig } from "../config/NodeIcons";
3
4
  export declare class FlowNode extends FlowElement {
4
5
  connectors: FlowElementConnector[];
5
- locationX: string;
6
- locationY: string;
6
+ locationX?: string;
7
+ locationY?: string;
8
+ label?: string;
9
+ description?: string;
10
+ actionType?: string;
11
+ actionName?: string;
12
+ object?: string;
13
+ inputReference?: string;
14
+ outputReference?: string;
15
+ elementSubtype?: string;
16
+ collectionReference?: string;
17
+ flowName?: string;
18
+ rules?: any[];
19
+ defaultConnectorLabel?: string;
20
+ iterationOrder?: string;
21
+ fields?: any[];
22
+ allowPause?: boolean;
23
+ showFooter?: boolean;
24
+ faultConnector?: FlowElementConnector;
25
+ private static iconConfig;
26
+ /**
27
+ * Set custom icon configuration for all FlowNodes
28
+ * @example
29
+ * ```typescript
30
+ * // Use ASCII icons for old terminals
31
+ * FlowNode.setIconConfig(ASCII_ICONS);
32
+ *
33
+ * // Or provide custom icons
34
+ * FlowNode.setIconConfig({
35
+ * actionCalls: { default: '[ACTION]' },
36
+ * decisions: { default: '[IF]' }
37
+ * });
38
+ * ```
39
+ */
40
+ static setIconConfig(config: NodeIconConfig): void;
41
+ /**
42
+ * Use ASCII icons instead of emoji (for older browsers/terminals)
43
+ */
44
+ static useAsciiIcons(): void;
45
+ /**
46
+ * Reset to default emoji icons
47
+ */
48
+ static useDefaultIcons(): void;
7
49
  constructor(provName: string, subtype: string, element: object);
50
+ private extractTypeSpecificProperties;
51
+ /**
52
+ * Get a human-readable summary of this node
53
+ */
54
+ getSummary(): string;
55
+ /**
56
+ * Get the icon for this node type
57
+ */
58
+ getIcon(): string;
59
+ /**
60
+ * Get the display name for this node type
61
+ */
62
+ getTypeLabel(): string;
63
+ private prettifyValue;
8
64
  private getConnectors;
9
65
  }