project-graph-mcp 2.3.1 → 2.4.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/package.json +3 -2
- package/src/analysis/analysis-cache.ctx +9 -0
- package/src/analysis/analysis-cache.js +1 -1
- package/src/analysis/complexity.ctx +6 -0
- package/src/analysis/complexity.js +1 -1
- package/src/analysis/custom-rules.ctx +14 -0
- package/src/analysis/custom-rules.js +1 -1
- package/src/analysis/db-analysis.ctx +7 -0
- package/src/analysis/db-analysis.js +1 -1
- package/src/analysis/dead-code.ctx +6 -0
- package/src/analysis/dead-code.js +1 -1
- package/src/analysis/full-analysis.ctx +9 -0
- package/src/analysis/full-analysis.js +1 -1
- package/src/analysis/jsdoc-checker.ctx +10 -0
- package/src/analysis/jsdoc-checker.js +1 -1
- package/src/analysis/jsdoc-generator.ctx +9 -0
- package/src/analysis/jsdoc-generator.js +1 -1
- package/src/analysis/large-files.ctx +6 -0
- package/src/analysis/large-files.js +1 -1
- package/src/analysis/outdated-patterns.ctx +7 -0
- package/src/analysis/outdated-patterns.js +1 -1
- package/src/analysis/similar-functions.ctx +6 -0
- package/src/analysis/similar-functions.js +1 -1
- package/src/analysis/test-annotations.ctx +11 -0
- package/src/analysis/test-annotations.js +1 -1
- package/src/analysis/type-checker.ctx +6 -0
- package/src/analysis/type-checker.js +1 -1
- package/src/analysis/undocumented.ctx +8 -0
- package/src/analysis/undocumented.js +1 -1
- package/src/cli/cli-handlers.ctx +7 -0
- package/src/cli/cli-handlers.js +1 -1
- package/src/cli/cli.ctx +6 -0
- package/src/cli/cli.js +1 -1
- package/src/compact/ai-context.ctx +6 -0
- package/src/compact/ai-context.js +1 -1
- package/src/compact/compact-migrate.ctx +8 -0
- package/src/compact/compact-migrate.js +1 -1
- package/src/compact/compact.ctx +11 -0
- package/src/compact/compact.js +1 -1
- package/src/compact/compress.ctx +7 -0
- package/src/compact/compress.js +1 -1
- package/src/compact/ctx-resolver.ctx +2 -0
- package/src/compact/ctx-resolver.js +1 -1
- package/src/compact/ctx-to-jsdoc.ctx +11 -0
- package/src/compact/ctx-to-jsdoc.js +1 -1
- package/src/compact/doc-dialect.ctx +11 -0
- package/src/compact/doc-dialect.js +2 -2
- package/src/compact/expand.ctx +14 -0
- package/src/compact/expand.js +1 -1
- package/src/compact/framework-references.ctx +7 -0
- package/src/compact/framework-references.js +1 -1
- package/src/compact/instructions.ctx +6 -0
- package/src/compact/instructions.js +1 -1
- package/src/compact/jsdoc-builder.ctx +4 -0
- package/src/compact/jsdoc-builder.js +1 -1
- package/src/compact/mode-config.ctx +8 -0
- package/src/compact/mode-config.js +1 -1
- package/src/compact/split-declarations.ctx +6 -0
- package/src/compact/split-declarations.js +1 -1
- package/src/compact/validate-pipeline.ctx +12 -0
- package/src/compact/validate-pipeline.js +1 -1
- package/src/core/event-bus.ctx +9 -0
- package/src/core/event-bus.js +1 -1
- package/src/core/file-walker.ctx +1 -0
- package/src/core/file-walker.js +1 -1
- package/src/core/filters.ctx +12 -0
- package/src/core/filters.js +1 -1
- package/src/core/graph-builder.ctx +7 -0
- package/src/core/graph-builder.js +1 -1
- package/src/core/parser.ctx +12 -0
- package/src/core/parser.js +1 -1
- package/src/core/utils.ctx +1 -0
- package/src/core/utils.js +1 -1
- package/src/core/workspace.ctx +7 -0
- package/src/core/workspace.js +1 -1
- package/src/lang/lang-go.ctx +8 -0
- package/src/lang/lang-go.js +1 -1
- package/src/lang/lang-python.ctx +5 -0
- package/src/lang/lang-python.js +1 -1
- package/src/lang/lang-sql.ctx +10 -0
- package/src/lang/lang-sql.js +1 -1
- package/src/lang/lang-typescript.ctx +6 -0
- package/src/lang/lang-typescript.js +1 -1
- package/src/lang/lang-utils.ctx +5 -0
- package/src/lang/lang-utils.js +1 -1
- package/src/mcp/mcp-server.ctx +6 -0
- package/src/mcp/mcp-server.js +1 -1
- package/src/mcp/tool-defs.ctx +2 -0
- package/src/mcp/tool-defs.js +1 -1
- package/src/mcp/tools.ctx +13 -0
- package/src/mcp/tools.js +1 -1
- package/src/network/backend-lifecycle.ctx +10 -0
- package/src/network/backend-lifecycle.js +1 -1
- package/src/network/backend.ctx +5 -0
- package/src/network/backend.js +1 -1
- package/src/network/local-gateway.ctx +9 -0
- package/src/network/local-gateway.js +1 -1
- package/src/network/mdns.ctx +6 -0
- package/src/network/mdns.js +1 -1
- package/src/network/server.ctx +2 -0
- package/src/network/server.js +2 -2
- package/src/network/web-server.ctx +17 -0
- package/src/network/web-server.js +2 -2
- package/web/follow-controller.js +94 -25
- package/web/panels/dep-graph.js +207 -21
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/vendor/symbiote-node/CHANGELOG.md +0 -31
- package/vendor/symbiote-node/LICENSE +0 -21
- package/vendor/symbiote-node/README.md +0 -206
- package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
- package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
- package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
- package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
- package/vendor/symbiote-node/canvas/LODManager.js +0 -88
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
- package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
- package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
- package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
- package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
- package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
- package/vendor/symbiote-node/core/Connection.js +0 -45
- package/vendor/symbiote-node/core/Editor.js +0 -451
- package/vendor/symbiote-node/core/Frame.js +0 -31
- package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
- package/vendor/symbiote-node/core/GraphText.js +0 -210
- package/vendor/symbiote-node/core/Node.js +0 -143
- package/vendor/symbiote-node/core/Portal.js +0 -104
- package/vendor/symbiote-node/core/Socket.js +0 -185
- package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
- package/vendor/symbiote-node/index.js +0 -103
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
- package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
- package/vendor/symbiote-node/interactions/Drag.js +0 -102
- package/vendor/symbiote-node/interactions/Selector.js +0 -132
- package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
- package/vendor/symbiote-node/interactions/Zoom.js +0 -140
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
- package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
- package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
- package/vendor/symbiote-node/layout/index.js +0 -16
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
- package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
- package/vendor/symbiote-node/package.json +0 -59
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
- package/vendor/symbiote-node/plugins/History.js +0 -384
- package/vendor/symbiote-node/plugins/Readonly.js +0 -59
- package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
- package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
- package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
- package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
- package/vendor/symbiote-node/shapes/PillShape.js +0 -91
- package/vendor/symbiote-node/shapes/RectShape.js +0 -72
- package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
- package/vendor/symbiote-node/shapes/index.js +0 -53
- package/vendor/symbiote-node/themes/Palette.js +0 -32
- package/vendor/symbiote-node/themes/Skin.js +0 -113
- package/vendor/symbiote-node/themes/Theme.js +0 -84
- package/vendor/symbiote-node/themes/carbon.js +0 -137
- package/vendor/symbiote-node/themes/dark.js +0 -137
- package/vendor/symbiote-node/themes/ebook.js +0 -138
- package/vendor/symbiote-node/themes/grey.js +0 -137
- package/vendor/symbiote-node/themes/light.js +0 -137
- package/vendor/symbiote-node/themes/neon.js +0 -138
- package/vendor/symbiote-node/themes/pcb.js +0 -273
- package/vendor/symbiote-node/themes/synthwave.js +0 -137
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GraphMermaid — bidirectional Mermaid ↔ graph serialization
|
|
3
|
-
*
|
|
4
|
-
* Converts NodeEditor state to Mermaid flowchart syntax and back.
|
|
5
|
-
* Supports shapes, labeled connections, and subgraphs (frames).
|
|
6
|
-
*
|
|
7
|
-
* Mermaid shape mapping:
|
|
8
|
-
* circle → ((label))
|
|
9
|
-
* diamond → {label}
|
|
10
|
-
* pill → ([label])
|
|
11
|
-
* rect → [label]
|
|
12
|
-
* comment → >label]
|
|
13
|
-
*
|
|
14
|
-
* @module symbiote-node/core/GraphMermaid
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// --- Shape mapping ---
|
|
18
|
-
|
|
19
|
-
const SHAPE_TO_MERMAID = {
|
|
20
|
-
circle: (id, label) => `${id}((${label}))`,
|
|
21
|
-
diamond: (id, label) => `${id}{${label}}`,
|
|
22
|
-
pill: (id, label) => `${id}([${label}])`,
|
|
23
|
-
rect: (id, label) => `${id}[${label}]`,
|
|
24
|
-
comment: (id, label) => `${id}>${label}]`,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Pattern matchers for Mermaid node shapes.
|
|
29
|
-
* Order matters — more specific patterns first.
|
|
30
|
-
* @type {Array<{re: RegExp, shape: string}>}
|
|
31
|
-
*/
|
|
32
|
-
const MERMAID_SHAPE_PATTERNS = [
|
|
33
|
-
{ re: /^(\w+)\(\((.+?)\)\)$/, shape: 'circle' },
|
|
34
|
-
{ re: /^(\w+)\(\[(.+?)\]\)$/, shape: 'pill' },
|
|
35
|
-
{ re: /^(\w+)\{(.+?)\}$/, shape: 'diamond' },
|
|
36
|
-
{ re: /^(\w+)>(.+?)\]$/, shape: 'comment' },
|
|
37
|
-
{ re: /^(\w+)\[(.+?)\]$/, shape: 'rect' },
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
// --- Mermaid Arrow patterns ---
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Arrow patterns with optional label.
|
|
44
|
-
* Supports: -->, --->, -->|label|, -- label -->
|
|
45
|
-
* @type {Array<{re: RegExp}>}
|
|
46
|
-
*/
|
|
47
|
-
const ARROW_PATTERNS = [
|
|
48
|
-
// nodeA -->|label| nodeB
|
|
49
|
-
/^(.+?)\s*-->\|([^|]*)\|\s*(.+)$/,
|
|
50
|
-
// nodeA -- label --> nodeB
|
|
51
|
-
/^(.+?)\s*--\s+(.+?)\s+-->\s*(.+)$/,
|
|
52
|
-
// nodeA --> nodeB (no label)
|
|
53
|
-
/^(.+?)\s*-->\s*(.+)$/,
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Parse a node reference that might include inline shape definition.
|
|
58
|
-
*
|
|
59
|
-
* @param {string} raw - e.g. "trigger((Job Event))" or just "trigger"
|
|
60
|
-
* @returns {{ id: string, label: string|null, shape: string|null }}
|
|
61
|
-
*/
|
|
62
|
-
function parseNodeRef(raw) {
|
|
63
|
-
const trimmed = raw.trim();
|
|
64
|
-
for (const { re, shape } of MERMAID_SHAPE_PATTERNS) {
|
|
65
|
-
const m = trimmed.match(re);
|
|
66
|
-
if (m) return { id: m[1], label: m[2], shape };
|
|
67
|
-
}
|
|
68
|
-
// Plain id reference
|
|
69
|
-
return { id: trimmed, label: null, shape: null };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Convert a NodeEditor to Mermaid flowchart syntax
|
|
74
|
-
*
|
|
75
|
-
* @param {import('./Editor.js').NodeEditor} editor
|
|
76
|
-
* @param {object} [options]
|
|
77
|
-
* @param {'LR'|'TB'|'RL'|'BT'} [options.direction='LR']
|
|
78
|
-
* @returns {string}
|
|
79
|
-
*/
|
|
80
|
-
export function editorToMermaid(editor, options = {}) {
|
|
81
|
-
const { direction = 'LR' } = options;
|
|
82
|
-
const lines = [];
|
|
83
|
-
|
|
84
|
-
lines.push(`graph ${direction}`);
|
|
85
|
-
|
|
86
|
-
// Collect which nodes belong to which frame (by spatial containment)
|
|
87
|
-
const frames = editor.getFrames();
|
|
88
|
-
const nodeToFrame = new Map();
|
|
89
|
-
|
|
90
|
-
// We don't have positions in editor, so we rely on frame data
|
|
91
|
-
// Frames are matched by checking if any connection links nodes in the frame
|
|
92
|
-
// For simplicity, use frame label as subgraph name
|
|
93
|
-
|
|
94
|
-
// Build node declarations grouped by frame
|
|
95
|
-
const framedNodes = new Map(); // frameId -> [node]
|
|
96
|
-
const freeNodes = [];
|
|
97
|
-
|
|
98
|
-
// If frames exist, check node positions (stored in frame data)
|
|
99
|
-
// Since we don't have positions here, we serialize frames
|
|
100
|
-
// and let the user define membership via subgraph
|
|
101
|
-
|
|
102
|
-
// Collect all nodes
|
|
103
|
-
const allNodes = editor.getNodes();
|
|
104
|
-
const allConnections = editor.getConnections();
|
|
105
|
-
|
|
106
|
-
// Render nodes inside subgraphs (frames)
|
|
107
|
-
if (frames.length) {
|
|
108
|
-
// Frames without spatial data — output as subgraphs with all nodes listed
|
|
109
|
-
// In practice, frame membership is defined externally
|
|
110
|
-
for (const frame of frames) {
|
|
111
|
-
const nodeIds = frame._nodeIds || [];
|
|
112
|
-
if (nodeIds.length) {
|
|
113
|
-
for (const nid of nodeIds) {
|
|
114
|
-
nodeToFrame.set(nid, frame);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Separate framed vs free nodes
|
|
121
|
-
for (const node of allNodes) {
|
|
122
|
-
if (nodeToFrame.has(node.id)) {
|
|
123
|
-
const frame = nodeToFrame.get(node.id);
|
|
124
|
-
if (!framedNodes.has(frame.id)) framedNodes.set(frame.id, []);
|
|
125
|
-
framedNodes.get(frame.id).push(node);
|
|
126
|
-
} else {
|
|
127
|
-
freeNodes.push(node);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Render free nodes first
|
|
132
|
-
for (const node of freeNodes) {
|
|
133
|
-
lines.push(' ' + nodeToMermaid(node));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// If no frame membership data, put all nodes as free and create
|
|
137
|
-
// empty subgraphs as comments
|
|
138
|
-
if (framedNodes.size === 0 && frames.length > 0) {
|
|
139
|
-
// Output all nodes first
|
|
140
|
-
if (freeNodes.length === 0) {
|
|
141
|
-
for (const node of allNodes) {
|
|
142
|
-
lines.push(' ' + nodeToMermaid(node));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
// Frames as subgraphs with node references
|
|
146
|
-
for (const frame of frames) {
|
|
147
|
-
lines.push('');
|
|
148
|
-
lines.push(` subgraph ${sanitizeId(frame.label)}["${frame.label}"]`);
|
|
149
|
-
lines.push(' direction TB');
|
|
150
|
-
lines.push(' end');
|
|
151
|
-
}
|
|
152
|
-
} else {
|
|
153
|
-
// Render subgraphs with their nodes
|
|
154
|
-
for (const [frameId, nodes] of framedNodes) {
|
|
155
|
-
const frame = frames.find(f => f.id === frameId);
|
|
156
|
-
if (!frame) continue;
|
|
157
|
-
lines.push('');
|
|
158
|
-
lines.push(` subgraph ${sanitizeId(frame.label)}["${frame.label}"]`);
|
|
159
|
-
lines.push(' direction TB');
|
|
160
|
-
for (const node of nodes) {
|
|
161
|
-
lines.push(' ' + nodeToMermaid(node));
|
|
162
|
-
}
|
|
163
|
-
lines.push(' end');
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Render connections
|
|
168
|
-
lines.push('');
|
|
169
|
-
for (const conn of allConnections) {
|
|
170
|
-
const label = conn.out === 'exec' ? '' : conn.out;
|
|
171
|
-
if (label) {
|
|
172
|
-
lines.push(` ${conn.from} -->|${label}| ${conn.to}`);
|
|
173
|
-
} else {
|
|
174
|
-
lines.push(` ${conn.from} --> ${conn.to}`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return lines.join('\n');
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Convert a single node to Mermaid declaration
|
|
183
|
-
* @param {import('./Node.js').Node} node
|
|
184
|
-
* @returns {string}
|
|
185
|
-
*/
|
|
186
|
-
function nodeToMermaid(node) {
|
|
187
|
-
const shapeFn = SHAPE_TO_MERMAID[node.shape] || SHAPE_TO_MERMAID.rect;
|
|
188
|
-
return shapeFn(node.id, node.label);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Sanitize a string for use as Mermaid subgraph ID
|
|
193
|
-
* @param {string} str
|
|
194
|
-
* @returns {string}
|
|
195
|
-
*/
|
|
196
|
-
function sanitizeId(str) {
|
|
197
|
-
return str.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Parse Mermaid flowchart text into graph data structure.
|
|
202
|
-
* Supports: node shapes, labeled arrows, subgraphs.
|
|
203
|
-
*
|
|
204
|
-
* @param {string} text
|
|
205
|
-
* @returns {{ nodes: Array, connections: Array, frames: Array, direction: string }}
|
|
206
|
-
*/
|
|
207
|
-
export function mermaidToGraph(text) {
|
|
208
|
-
const nodes = new Map(); // id -> { id, name, shape, category }
|
|
209
|
-
const connections = [];
|
|
210
|
-
const frames = [];
|
|
211
|
-
const frameStack = []; // for nested subgraphs
|
|
212
|
-
|
|
213
|
-
let direction = 'LR';
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Register a node from a parsed reference
|
|
217
|
-
* @param {{ id: string, label: string|null, shape: string|null }} ref
|
|
218
|
-
*/
|
|
219
|
-
function registerNode(ref) {
|
|
220
|
-
if (!nodes.has(ref.id)) {
|
|
221
|
-
nodes.set(ref.id, {
|
|
222
|
-
id: ref.id,
|
|
223
|
-
name: ref.label || ref.id,
|
|
224
|
-
type: 'default',
|
|
225
|
-
shape: ref.shape || 'rect',
|
|
226
|
-
category: 'default',
|
|
227
|
-
});
|
|
228
|
-
} else if (ref.label && !nodes.get(ref.id).name) {
|
|
229
|
-
// Update label if first seen was bare reference
|
|
230
|
-
const existing = nodes.get(ref.id);
|
|
231
|
-
if (existing.name === existing.id) {
|
|
232
|
-
existing.name = ref.label;
|
|
233
|
-
}
|
|
234
|
-
if (ref.shape) existing.shape = ref.shape;
|
|
235
|
-
}
|
|
236
|
-
// Track frame membership
|
|
237
|
-
if (frameStack.length > 0) {
|
|
238
|
-
const currentFrame = frameStack[frameStack.length - 1];
|
|
239
|
-
if (!currentFrame._nodeIds) currentFrame._nodeIds = [];
|
|
240
|
-
if (!currentFrame._nodeIds.includes(ref.id)) {
|
|
241
|
-
currentFrame._nodeIds.push(ref.id);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
for (const rawLine of text.split('\n')) {
|
|
247
|
-
const line = rawLine.trim();
|
|
248
|
-
if (!line || line.startsWith('%%')) continue; // skip empty and comments
|
|
249
|
-
|
|
250
|
-
// Graph direction
|
|
251
|
-
const dirMatch = line.match(/^graph\s+(LR|RL|TB|BT|TD)\s*$/);
|
|
252
|
-
if (dirMatch) {
|
|
253
|
-
direction = dirMatch[1] === 'TD' ? 'TB' : dirMatch[1];
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Flowchart direction (alias)
|
|
258
|
-
const flowMatch = line.match(/^flowchart\s+(LR|RL|TB|BT|TD)\s*$/);
|
|
259
|
-
if (flowMatch) {
|
|
260
|
-
direction = flowMatch[1] === 'TD' ? 'TB' : flowMatch[1];
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Subgraph start
|
|
265
|
-
const subMatch = line.match(/^subgraph\s+(\w+)(?:\["(.+?)"\])?\s*$/);
|
|
266
|
-
if (subMatch) {
|
|
267
|
-
const frame = {
|
|
268
|
-
label: subMatch[2] || subMatch[1],
|
|
269
|
-
color: '#4a9eff',
|
|
270
|
-
x: 0, y: 0,
|
|
271
|
-
width: 400, height: 300,
|
|
272
|
-
_nodeIds: [],
|
|
273
|
-
};
|
|
274
|
-
frameStack.push(frame);
|
|
275
|
-
frames.push(frame);
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Subgraph end
|
|
280
|
-
if (line === 'end') {
|
|
281
|
-
frameStack.pop();
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Direction inside subgraph
|
|
286
|
-
if (line.match(/^direction\s+(LR|RL|TB|BT|TD)$/)) continue;
|
|
287
|
-
|
|
288
|
-
// Try arrow patterns (connection lines)
|
|
289
|
-
let matched = false;
|
|
290
|
-
for (const pattern of ARROW_PATTERNS) {
|
|
291
|
-
const m = line.match(pattern);
|
|
292
|
-
if (m) {
|
|
293
|
-
matched = true;
|
|
294
|
-
if (m.length === 4) {
|
|
295
|
-
// With label: source, label, target
|
|
296
|
-
const source = parseNodeRef(m[1]);
|
|
297
|
-
const label = m[2].trim();
|
|
298
|
-
const target = parseNodeRef(m[3]);
|
|
299
|
-
registerNode(source);
|
|
300
|
-
registerNode(target);
|
|
301
|
-
connections.push({
|
|
302
|
-
from: source.id,
|
|
303
|
-
out: label || 'exec',
|
|
304
|
-
to: target.id,
|
|
305
|
-
in: 'exec',
|
|
306
|
-
});
|
|
307
|
-
} else if (m.length === 3) {
|
|
308
|
-
// No label: source, target
|
|
309
|
-
const source = parseNodeRef(m[1]);
|
|
310
|
-
const target = parseNodeRef(m[2]);
|
|
311
|
-
registerNode(source);
|
|
312
|
-
registerNode(target);
|
|
313
|
-
connections.push({
|
|
314
|
-
from: source.id,
|
|
315
|
-
out: 'exec',
|
|
316
|
-
to: target.id,
|
|
317
|
-
in: 'exec',
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// If not a connection, try as standalone node declaration
|
|
325
|
-
if (!matched) {
|
|
326
|
-
// Handle "nodeA & nodeB & nodeC" syntax
|
|
327
|
-
const parts = line.split(/\s*&\s*/);
|
|
328
|
-
for (const part of parts) {
|
|
329
|
-
const ref = parseNodeRef(part.trim());
|
|
330
|
-
if (ref.id && ref.id !== 'end' && !ref.id.startsWith('style') && !ref.id.startsWith('class')) {
|
|
331
|
-
registerNode(ref);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
nodes: [...nodes.values()],
|
|
339
|
-
connections,
|
|
340
|
-
frames: frames.map(f => ({
|
|
341
|
-
label: f.label,
|
|
342
|
-
color: f.color,
|
|
343
|
-
x: f.x, y: f.y,
|
|
344
|
-
width: f.width, height: f.height,
|
|
345
|
-
})),
|
|
346
|
-
direction,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GraphText — bidirectional text ↔ graph serialization
|
|
3
|
-
*
|
|
4
|
-
* Converts NodeEditor state to human-readable text and back.
|
|
5
|
-
* Useful for debugging, LLM-based generation, and quick iteration.
|
|
6
|
-
*
|
|
7
|
-
* Text format:
|
|
8
|
-
* NODES:
|
|
9
|
-
* [○ trigger] Job Event: RU (queue/job-event) shape=circle
|
|
10
|
-
* [◇ switch_status] Status? (flow/switch) shape=diamond
|
|
11
|
-
*
|
|
12
|
-
* CONNECTIONS:
|
|
13
|
-
* trigger.exec --> switch_status.exec
|
|
14
|
-
* switch_status.created --> fmt_created.exec
|
|
15
|
-
*
|
|
16
|
-
* FRAMES:
|
|
17
|
-
* [Formatters] color=#5cd87a x=490 y=-10 w=260 h=520
|
|
18
|
-
*
|
|
19
|
-
* @module symbiote-node/core/GraphText
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
const SHAPE_ICONS = {
|
|
23
|
-
circle: '○',
|
|
24
|
-
diamond: '◇',
|
|
25
|
-
pill: '⊃',
|
|
26
|
-
rect: '□',
|
|
27
|
-
comment: '✎',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const ICON_SHAPES = Object.fromEntries(
|
|
31
|
-
Object.entries(SHAPE_ICONS).map(([k, v]) => [v, k])
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Convert a NodeEditor to human-readable text
|
|
36
|
-
*
|
|
37
|
-
* @param {import('./Editor.js').NodeEditor} editor
|
|
38
|
-
* @param {Object<string, number[]>} [positions] - {nodeId: [x, y]}
|
|
39
|
-
* @returns {string}
|
|
40
|
-
*/
|
|
41
|
-
export function editorToText(editor, positions = {}) {
|
|
42
|
-
const lines = [];
|
|
43
|
-
|
|
44
|
-
// --- NODES ---
|
|
45
|
-
lines.push('NODES:');
|
|
46
|
-
for (const node of editor.getNodes()) {
|
|
47
|
-
const icon = SHAPE_ICONS[node.shape] || '□';
|
|
48
|
-
const ins = Object.keys(node.inputs);
|
|
49
|
-
const outs = Object.keys(node.outputs);
|
|
50
|
-
let line = `[${icon} ${node.id}] ${node.label} (${node.type})`;
|
|
51
|
-
if (node.shape !== 'rect') line += ` shape=${node.shape}`;
|
|
52
|
-
if (node.category !== 'default') line += ` cat=${node.category}`;
|
|
53
|
-
if (ins.length) line += ` in=[${ins.join(',')}]`;
|
|
54
|
-
if (outs.length) line += ` out=[${outs.join(',')}]`;
|
|
55
|
-
const pos = positions[node.id];
|
|
56
|
-
if (pos) line += ` @${pos[0]},${pos[1]}`;
|
|
57
|
-
lines.push(' ' + line);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// --- CONNECTIONS ---
|
|
61
|
-
lines.push('');
|
|
62
|
-
lines.push('CONNECTIONS:');
|
|
63
|
-
for (const conn of editor.getConnections()) {
|
|
64
|
-
lines.push(` ${conn.from}.${conn.out} --> ${conn.to}.${conn.in}`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// --- FRAMES ---
|
|
68
|
-
const frames = editor.getFrames();
|
|
69
|
-
if (frames.length) {
|
|
70
|
-
lines.push('');
|
|
71
|
-
lines.push('FRAMES:');
|
|
72
|
-
for (const frame of frames) {
|
|
73
|
-
lines.push(` [${frame.label}] color=${frame.color} x=${frame.x} y=${frame.y} w=${frame.width} h=${frame.height}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return lines.join('\n');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Parse text representation back into graph data structure.
|
|
82
|
-
* Returns plain objects suitable for building a NodeEditor.
|
|
83
|
-
*
|
|
84
|
-
* @param {string} text
|
|
85
|
-
* @returns {{ nodes: Array, connections: Array, frames: Array, positions: Object }}
|
|
86
|
-
*/
|
|
87
|
-
export function textToGraph(text) {
|
|
88
|
-
const nodes = [];
|
|
89
|
-
const connections = [];
|
|
90
|
-
const frames = [];
|
|
91
|
-
const positions = {};
|
|
92
|
-
|
|
93
|
-
let section = '';
|
|
94
|
-
for (const raw of text.split('\n')) {
|
|
95
|
-
const line = raw.trim();
|
|
96
|
-
if (!line) continue;
|
|
97
|
-
|
|
98
|
-
if (line === 'NODES:') { section = 'nodes'; continue; }
|
|
99
|
-
if (line === 'CONNECTIONS:') { section = 'connections'; continue; }
|
|
100
|
-
if (line === 'FRAMES:') { section = 'frames'; continue; }
|
|
101
|
-
|
|
102
|
-
if (section === 'nodes') {
|
|
103
|
-
// [○ trigger] Job Event: RU (queue/job-event) shape=circle cat=server in=[exec] out=[exec,data] @50,200
|
|
104
|
-
const m = line.match(/^\[(.)\s+(\S+)\]\s+(.+?)\s+\(([^)]+)\)(.*)$/);
|
|
105
|
-
if (!m) continue;
|
|
106
|
-
|
|
107
|
-
const [, shapeIcon, id, name, type, rest] = m;
|
|
108
|
-
const shape = ICON_SHAPES[shapeIcon] || 'rect';
|
|
109
|
-
const category = rest.match(/cat=(\S+)/)?.[1] || 'default';
|
|
110
|
-
const posMatch = rest.match(/@(-?\d+),(-?\d+)/);
|
|
111
|
-
|
|
112
|
-
nodes.push({ id, name, type, shape, category });
|
|
113
|
-
if (posMatch) {
|
|
114
|
-
positions[id] = [parseInt(posMatch[1]), parseInt(posMatch[2])];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (section === 'connections') {
|
|
119
|
-
// trigger.exec --> switch_status.exec
|
|
120
|
-
const m = line.match(/^(\S+)\.(\S+)\s+-->\s+(\S+)\.(\S+)$/);
|
|
121
|
-
if (!m) continue;
|
|
122
|
-
const [, from, out, to, inp] = m;
|
|
123
|
-
connections.push({ from, out, to, in: inp });
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (section === 'frames') {
|
|
127
|
-
// [Formatters] color=#5cd87a x=490 y=-10 w=260 h=520
|
|
128
|
-
const m = line.match(/^\[([^\]]+)\]\s+(.*)$/);
|
|
129
|
-
if (!m) continue;
|
|
130
|
-
const [, label, rest] = m;
|
|
131
|
-
const color = rest.match(/color=(\S+)/)?.[1] || '#4a9eff';
|
|
132
|
-
const x = parseInt(rest.match(/x=(-?\d+)/)?.[1] || '0');
|
|
133
|
-
const y = parseInt(rest.match(/y=(-?\d+)/)?.[1] || '0');
|
|
134
|
-
const w = parseInt(rest.match(/w=(\d+)/)?.[1] || '400');
|
|
135
|
-
const h = parseInt(rest.match(/h=(\d+)/)?.[1] || '300');
|
|
136
|
-
frames.push({ label, color, x, y, width: w, height: h });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return { nodes, connections, frames, positions };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Build a NodeEditor from text representation
|
|
145
|
-
*
|
|
146
|
-
* @param {string} text
|
|
147
|
-
* @param {import('./Editor.js').NodeEditor} editor
|
|
148
|
-
* @param {{ Socket: Function, Node: Function, Input: Function, Output: Function, Connection: Function, Frame: Function }} classes
|
|
149
|
-
* @returns {{ editor: import('./Editor.js').NodeEditor, positions: Object }}
|
|
150
|
-
*/
|
|
151
|
-
export function textToEditor(text, editor, classes) {
|
|
152
|
-
const { Node, Connection, Socket, Input, Output, Frame } = classes;
|
|
153
|
-
const { nodes, connections, frames, positions } = textToGraph(text);
|
|
154
|
-
|
|
155
|
-
const execSocket = new Socket('exec', { color: '#ffffff' });
|
|
156
|
-
const dataSocket = new Socket('data', { color: '#5cb8ff' });
|
|
157
|
-
const nodeMap = new Map();
|
|
158
|
-
|
|
159
|
-
// Collect ports from connections
|
|
160
|
-
const inPorts = {};
|
|
161
|
-
const outPorts = {};
|
|
162
|
-
for (const conn of connections) {
|
|
163
|
-
if (!inPorts[conn.to]) inPorts[conn.to] = new Set();
|
|
164
|
-
inPorts[conn.to].add(conn.in);
|
|
165
|
-
if (!outPorts[conn.from]) outPorts[conn.from] = new Set();
|
|
166
|
-
outPorts[conn.from].add(conn.out);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
for (const n of nodes) {
|
|
170
|
-
const node = new Node(n.name, {
|
|
171
|
-
id: n.id,
|
|
172
|
-
type: n.type,
|
|
173
|
-
category: n.category,
|
|
174
|
-
shape: n.shape,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const ins = inPorts[n.id] || new Set();
|
|
178
|
-
const outs = outPorts[n.id] || new Set();
|
|
179
|
-
|
|
180
|
-
for (const port of ins) {
|
|
181
|
-
const isExec = port === 'exec' || port === 'trigger';
|
|
182
|
-
node.addInput(port, new Input(isExec ? execSocket : dataSocket, port === 'exec' ? '' : port));
|
|
183
|
-
}
|
|
184
|
-
for (const port of outs) {
|
|
185
|
-
const isExec = port === 'exec' || port === 'trigger';
|
|
186
|
-
node.addOutput(port, new Output(isExec ? execSocket : dataSocket, port === 'exec' ? '' : port));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
editor.addNode(node);
|
|
190
|
-
nodeMap.set(n.id, node);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
for (const conn of connections) {
|
|
194
|
-
const fromNode = nodeMap.get(conn.from);
|
|
195
|
-
const toNode = nodeMap.get(conn.to);
|
|
196
|
-
if (fromNode && toNode) {
|
|
197
|
-
editor.addConnection(new Connection(fromNode, conn.out, toNode, conn.in));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
for (const f of frames) {
|
|
202
|
-
editor.addFrame(new Frame(f.label, {
|
|
203
|
-
x: f.x, y: f.y,
|
|
204
|
-
width: f.width, height: f.height,
|
|
205
|
-
color: f.color,
|
|
206
|
-
}));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return { editor, positions };
|
|
210
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Node — graph node with typed ports and controls
|
|
3
|
-
*
|
|
4
|
-
* Compatible with symbiote-node GraphNode structure.
|
|
5
|
-
* Ports are explicit objects (unlike symbiote-node where they're implicit).
|
|
6
|
-
*
|
|
7
|
-
* @module symbiote-node/core/Node
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { uid, Input, Output } from './Socket.js';
|
|
11
|
-
|
|
12
|
-
export class Node {
|
|
13
|
-
/**
|
|
14
|
-
* @param {string} label - Display name
|
|
15
|
-
* @param {object} [options]
|
|
16
|
-
* @param {string} [options.id] - Custom ID (default: auto-generated)
|
|
17
|
-
* @param {string} [options.type] - Node type identifier (e.g. 'ai/llm')
|
|
18
|
-
* @param {string} [options.category] - Category for styling (server/instance/control)
|
|
19
|
-
* @param {string} [options.shape] - Shape name (rect/pill/circle/diamond/comment)
|
|
20
|
-
* @param {string} [options.icon] - Material icon name for visual rendering
|
|
21
|
-
*/
|
|
22
|
-
constructor(label, options = {}) {
|
|
23
|
-
/** @type {string} */
|
|
24
|
-
this.id = options.id || uid('nd');
|
|
25
|
-
|
|
26
|
-
/** @type {string} */
|
|
27
|
-
this.label = label;
|
|
28
|
-
|
|
29
|
-
/** @type {string} */
|
|
30
|
-
this.type = options.type || 'default';
|
|
31
|
-
|
|
32
|
-
/** @type {string} */
|
|
33
|
-
this.category = options.category || 'default';
|
|
34
|
-
|
|
35
|
-
/** @type {string} */
|
|
36
|
-
this.shape = options.shape || 'rect';
|
|
37
|
-
|
|
38
|
-
/** @type {string} */
|
|
39
|
-
this.icon = options.icon || '';
|
|
40
|
-
|
|
41
|
-
/** @type {Object<string, Input>} */
|
|
42
|
-
this.inputs = {};
|
|
43
|
-
|
|
44
|
-
/** @type {Object<string, Output>} */
|
|
45
|
-
this.outputs = {};
|
|
46
|
-
|
|
47
|
-
/** @type {Object<string, import('./Socket.js').Control>} */
|
|
48
|
-
this.controls = {};
|
|
49
|
-
|
|
50
|
-
/** @type {Object<string, *>} */
|
|
51
|
-
this.params = {};
|
|
52
|
-
|
|
53
|
-
/** @type {boolean} */
|
|
54
|
-
this.selected = false;
|
|
55
|
-
|
|
56
|
-
/** @type {boolean} */
|
|
57
|
-
this.collapsed = false;
|
|
58
|
-
|
|
59
|
-
/** @type {boolean} */
|
|
60
|
-
this.muted = false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Check if input exists
|
|
65
|
-
* @param {string} key
|
|
66
|
-
* @returns {boolean}
|
|
67
|
-
*/
|
|
68
|
-
hasInput(key) {
|
|
69
|
-
return key in this.inputs;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Add input port
|
|
74
|
-
* @param {string} key - Port key
|
|
75
|
-
* @param {Input} input - Input instance
|
|
76
|
-
*/
|
|
77
|
-
addInput(key, input) {
|
|
78
|
-
if (this.hasInput(key)) throw new Error(`input '${key}' already exists`);
|
|
79
|
-
this.inputs[key] = input;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Remove input port
|
|
84
|
-
* @param {string} key
|
|
85
|
-
*/
|
|
86
|
-
removeInput(key) {
|
|
87
|
-
delete this.inputs[key];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Check if output exists
|
|
92
|
-
* @param {string} key
|
|
93
|
-
* @returns {boolean}
|
|
94
|
-
*/
|
|
95
|
-
hasOutput(key) {
|
|
96
|
-
return key in this.outputs;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Add output port
|
|
101
|
-
* @param {string} key - Port key
|
|
102
|
-
* @param {Output} output - Output instance
|
|
103
|
-
*/
|
|
104
|
-
addOutput(key, output) {
|
|
105
|
-
if (this.hasOutput(key)) throw new Error(`output '${key}' already exists`);
|
|
106
|
-
this.outputs[key] = output;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Remove output port
|
|
111
|
-
* @param {string} key
|
|
112
|
-
*/
|
|
113
|
-
removeOutput(key) {
|
|
114
|
-
delete this.outputs[key];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Check if control exists
|
|
119
|
-
* @param {string} key
|
|
120
|
-
* @returns {boolean}
|
|
121
|
-
*/
|
|
122
|
-
hasControl(key) {
|
|
123
|
-
return key in this.controls;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Add control widget
|
|
128
|
-
* @param {string} key
|
|
129
|
-
* @param {import('./Socket.js').Control} control
|
|
130
|
-
*/
|
|
131
|
-
addControl(key, control) {
|
|
132
|
-
if (this.hasControl(key)) throw new Error(`control '${key}' already exists`);
|
|
133
|
-
this.controls[key] = control;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Remove control widget
|
|
138
|
-
* @param {string} key
|
|
139
|
-
*/
|
|
140
|
-
removeControl(key) {
|
|
141
|
-
delete this.controls[key];
|
|
142
|
-
}
|
|
143
|
-
}
|