project-graph-mcp 2.3.2 → 2.4.1
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/engine/AgentUICommands.js +0 -100
- package/vendor/symbiote-node/engine/Executor.js +0 -371
- package/vendor/symbiote-node/engine/Graph.js +0 -314
- package/vendor/symbiote-node/engine/GraphServer.js +0 -353
- package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
- package/vendor/symbiote-node/engine/History.js +0 -83
- package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
- package/vendor/symbiote-node/engine/Persistence.js +0 -84
- package/vendor/symbiote-node/engine/Registry.js +0 -264
- package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
- package/vendor/symbiote-node/engine/cli.js +0 -404
- package/vendor/symbiote-node/engine/index.js +0 -56
- package/vendor/symbiote-node/engine/nanoid.js +0 -28
- package/vendor/symbiote-node/engine/package.json +0 -26
- package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
- package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
- package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
- package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
- package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
- package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
- package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
- package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
- package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
- package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
- package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
- package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
- package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
- package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
- package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
- package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
- package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
- package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
- package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
- package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
- package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
- package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
- package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
- package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
- package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
- package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
- package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
- package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
- package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
- package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
- package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
- package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
- package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
- package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
- package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
- 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,371 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Executor.js - Topological sort execution engine
|
|
3
|
-
*
|
|
4
|
-
* Executes a directed acyclic graph (DAG) of nodes using
|
|
5
|
-
* Kahn's algorithm. Supports incremental execution,
|
|
6
|
-
* cache, async node processing, dynamic sockets,
|
|
7
|
-
* and compound node sub-graph execution.
|
|
8
|
-
*
|
|
9
|
-
* @module symbiote-node/Executor
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { getNodeType } from './Registry.js';
|
|
13
|
-
import { Graph } from './Graph.js';
|
|
14
|
-
import { runLifecycle } from './Lifecycle.js';
|
|
15
|
-
|
|
16
|
-
export class Executor {
|
|
17
|
-
|
|
18
|
-
constructor() {
|
|
19
|
-
/** @type {Map<string, any>} Cached outputs per node ID */
|
|
20
|
-
this._cache = new Map();
|
|
21
|
-
|
|
22
|
-
/** @type {Map<string, {key: string, outputs: object}>} Lifecycle cache store */
|
|
23
|
-
this._lifecycleCache = new Map();
|
|
24
|
-
|
|
25
|
-
/** @type {Set<string>} Nodes marked dirty (need re-execution) */
|
|
26
|
-
this._dirty = new Set();
|
|
27
|
-
|
|
28
|
-
/** @type {string|null} Currently executing node ID */
|
|
29
|
-
this.currentNode = null;
|
|
30
|
-
|
|
31
|
-
/** @type {Array<{nodeId: string, time: number, skipped: boolean, cached?: boolean, error?: string|null}>} */
|
|
32
|
-
this.executionLog = [];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Execute a graph
|
|
37
|
-
* @param {import('./Graph.js').Graph} graph
|
|
38
|
-
* @param {object} [options={}]
|
|
39
|
-
* @param {boolean} [options.cache=false] - Use incremental execution (skip unchanged)
|
|
40
|
-
* @param {function} [options.onNodeStart] - Callback(nodeId, node)
|
|
41
|
-
* @param {function} [options.onNodeComplete] - Callback(nodeId, output, timeMs)
|
|
42
|
-
* @param {function} [options.onNodeSkipped] - Callback(nodeId) for cached nodes
|
|
43
|
-
* @param {function} [options.onNodeCached] - Callback(nodeId, cacheHash) for lifecycle-cached
|
|
44
|
-
* @returns {Promise<{outputs: object, executionOrder: string[], log: Array, totalTime: number}>}
|
|
45
|
-
*/
|
|
46
|
-
async run(graph, options = {}) {
|
|
47
|
-
const { cache = false, onNodeStart, onNodeComplete, onNodeSkipped, onNodeCached } = options;
|
|
48
|
-
const nodes = graph.nodes;
|
|
49
|
-
// Duck-typing: Editor has connections as Map, Graph has array
|
|
50
|
-
const connections = graph.connections instanceof Map
|
|
51
|
-
? [...graph.connections.values()]
|
|
52
|
-
: graph.connections;
|
|
53
|
-
|
|
54
|
-
// Topological sort
|
|
55
|
-
const order = this._topologicalSort(nodes, connections);
|
|
56
|
-
|
|
57
|
-
// Execute in order
|
|
58
|
-
const results = new Map();
|
|
59
|
-
this.executionLog = [];
|
|
60
|
-
|
|
61
|
-
for (const nodeId of order) {
|
|
62
|
-
const node = nodes.get(nodeId);
|
|
63
|
-
|
|
64
|
-
// Skip cached clean nodes
|
|
65
|
-
if (cache && !this._dirty.has(nodeId) && this._cache.has(nodeId)) {
|
|
66
|
-
results.set(nodeId, this._cache.get(nodeId));
|
|
67
|
-
this.executionLog.push({ nodeId, time: 0, skipped: true });
|
|
68
|
-
if (onNodeSkipped) onNodeSkipped(nodeId);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (onNodeStart) onNodeStart(nodeId, node);
|
|
73
|
-
this.currentNode = nodeId;
|
|
74
|
-
const startTime = performance.now();
|
|
75
|
-
|
|
76
|
-
// Resolve inputs from upstream connections
|
|
77
|
-
const inputs = this._resolveInputs(nodeId, connections, results);
|
|
78
|
-
|
|
79
|
-
// P22: Branch skipping — if node has incoming connections and
|
|
80
|
-
// all connected inputs are null, this node is on an inactive branch
|
|
81
|
-
const incomingConns = connections.filter(c => c.to === nodeId);
|
|
82
|
-
if (incomingConns.length > 0) {
|
|
83
|
-
const allNull = incomingConns.every(c => inputs[c.in] === null || inputs[c.in] === undefined);
|
|
84
|
-
// Skip merge nodes — they expect null from one branch
|
|
85
|
-
const isMergeType = node.type === 'flow/merge' || node.type === 'flow/wait-all';
|
|
86
|
-
if (allNull && !isMergeType) {
|
|
87
|
-
node._output = null;
|
|
88
|
-
results.set(nodeId, null);
|
|
89
|
-
const elapsed = performance.now() - startTime;
|
|
90
|
-
this.executionLog.push({ nodeId, time: elapsed, skipped: true, branchSkipped: true });
|
|
91
|
-
if (onNodeSkipped) onNodeSkipped(nodeId);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Execute node processor
|
|
97
|
-
// Check for lifecycle hooks first, then fall back to process()
|
|
98
|
-
let output;
|
|
99
|
-
const typeDef = getNodeType(node.type);
|
|
100
|
-
const lifecycleHooks = typeDef?.lifecycle;
|
|
101
|
-
|
|
102
|
-
if (lifecycleHooks) {
|
|
103
|
-
// Lifecycle path: validate → cache → execute → postProcess
|
|
104
|
-
const cacheState = {
|
|
105
|
-
mode: node.cacheMode || 'auto',
|
|
106
|
-
store: this._lifecycleCache,
|
|
107
|
-
nodeId,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const lifecycleResult = await runLifecycle(lifecycleHooks, inputs, node.params, cacheState);
|
|
111
|
-
|
|
112
|
-
if (lifecycleResult.error) {
|
|
113
|
-
node._output = { _error: lifecycleResult.error };
|
|
114
|
-
node._cacheHash = lifecycleResult.cacheHash;
|
|
115
|
-
results.set(nodeId, node._output);
|
|
116
|
-
const elapsed = performance.now() - startTime;
|
|
117
|
-
this.executionLog.push({ nodeId, time: elapsed, skipped: false, cached: false, error: lifecycleResult.error });
|
|
118
|
-
if (onNodeComplete) onNodeComplete(nodeId, node._output, elapsed);
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
output = lifecycleResult.outputs;
|
|
123
|
-
node._cacheHash = lifecycleResult.cacheHash;
|
|
124
|
-
|
|
125
|
-
if (lifecycleResult.cached) {
|
|
126
|
-
if (onNodeCached) onNodeCached(nodeId, lifecycleResult.cacheHash);
|
|
127
|
-
}
|
|
128
|
-
} else {
|
|
129
|
-
// Legacy path: direct process() call
|
|
130
|
-
// Node-level process overrides type-level (for per-instance composition)
|
|
131
|
-
const processFn = node.process || typeDef?.process;
|
|
132
|
-
|
|
133
|
-
if (typeof processFn === 'function') {
|
|
134
|
-
output = await processFn(inputs, node.params);
|
|
135
|
-
} else {
|
|
136
|
-
// Passthrough: merge params with inputs
|
|
137
|
-
output = { ...node.params, ...inputs };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Compound node: execute sub-graph if returned
|
|
142
|
-
if (output && output._subGraph) {
|
|
143
|
-
output = await this._executeSubGraph(output._subGraph, inputs, node.params);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Dynamic sockets: process exposes runtime-generated outputs
|
|
147
|
-
if (output && output.dynamicOutputs && Array.isArray(output.dynamicOutputs)) {
|
|
148
|
-
node._dynamicSockets = output.dynamicOutputs;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Store output in node
|
|
152
|
-
node._output = output;
|
|
153
|
-
|
|
154
|
-
results.set(nodeId, output);
|
|
155
|
-
this._cache.set(nodeId, output);
|
|
156
|
-
this._dirty.delete(nodeId);
|
|
157
|
-
|
|
158
|
-
const elapsed = performance.now() - startTime;
|
|
159
|
-
this.executionLog.push({ nodeId, time: elapsed, skipped: false });
|
|
160
|
-
|
|
161
|
-
if (onNodeComplete) onNodeComplete(nodeId, output, elapsed);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
this.currentNode = null;
|
|
165
|
-
|
|
166
|
-
// Collect output nodes (no outgoing connections)
|
|
167
|
-
const outputNodeIds = this._findOutputNodes(nodes, connections);
|
|
168
|
-
const outputs = {};
|
|
169
|
-
for (const id of outputNodeIds) {
|
|
170
|
-
outputs[id] = results.get(id);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
outputs,
|
|
175
|
-
executionOrder: order,
|
|
176
|
-
log: this.executionLog,
|
|
177
|
-
totalTime: this.executionLog.reduce((sum, e) => sum + e.time, 0),
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Mark a node as dirty (needs re-execution)
|
|
183
|
-
* Propagates downstream
|
|
184
|
-
* @param {string} nodeId
|
|
185
|
-
* @param {import('./Graph.js').Connection[]} connections
|
|
186
|
-
*/
|
|
187
|
-
markDirty(nodeId, connections) {
|
|
188
|
-
if (this._dirty.has(nodeId)) return;
|
|
189
|
-
this._dirty.add(nodeId);
|
|
190
|
-
for (const conn of connections) {
|
|
191
|
-
if (conn.from === nodeId) {
|
|
192
|
-
this.markDirty(conn.to, connections);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Clear all caches
|
|
199
|
-
*/
|
|
200
|
-
clearCache() {
|
|
201
|
-
this._cache.clear();
|
|
202
|
-
this._lifecycleCache.clear();
|
|
203
|
-
this._dirty.clear();
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Topological sort using Kahn's algorithm
|
|
208
|
-
* @param {Map<string, object>} nodes
|
|
209
|
-
* @param {Array<{from: string, to: string}>} connections
|
|
210
|
-
* @returns {string[]} Node IDs in execution order
|
|
211
|
-
* @private
|
|
212
|
-
*/
|
|
213
|
-
_topologicalSort(nodes, connections) {
|
|
214
|
-
const inDegree = new Map();
|
|
215
|
-
const adjacency = new Map();
|
|
216
|
-
|
|
217
|
-
// Only include connected nodes (skip orphans)
|
|
218
|
-
const connectedIds = new Set();
|
|
219
|
-
for (const conn of connections) {
|
|
220
|
-
connectedIds.add(conn.from);
|
|
221
|
-
connectedIds.add(conn.to);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Include source nodes (no incoming connections but exist in graph)
|
|
225
|
-
for (const id of nodes.keys()) {
|
|
226
|
-
if (connectedIds.has(id) || !connections.some(c => c.to === id || c.from === id)) {
|
|
227
|
-
// Include connected nodes; orphans are skipped
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
for (const id of connectedIds) {
|
|
232
|
-
if (!nodes.has(id)) continue;
|
|
233
|
-
inDegree.set(id, 0);
|
|
234
|
-
adjacency.set(id, []);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Also include source nodes (nodes with outgoing but no incoming)
|
|
238
|
-
for (const id of nodes.keys()) {
|
|
239
|
-
if (!connectedIds.has(id)) continue;
|
|
240
|
-
if (!inDegree.has(id)) {
|
|
241
|
-
inDegree.set(id, 0);
|
|
242
|
-
adjacency.set(id, []);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
for (const conn of connections) {
|
|
247
|
-
if (!adjacency.has(conn.from) || !inDegree.has(conn.to)) continue;
|
|
248
|
-
adjacency.get(conn.from).push(conn.to);
|
|
249
|
-
inDegree.set(conn.to, (inDegree.get(conn.to) || 0) + 1);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Kahn's algorithm
|
|
253
|
-
const queue = [];
|
|
254
|
-
for (const [id, degree] of inDegree) {
|
|
255
|
-
if (degree === 0) queue.push(id);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const result = [];
|
|
259
|
-
while (queue.length > 0) {
|
|
260
|
-
const nodeId = queue.shift();
|
|
261
|
-
result.push(nodeId);
|
|
262
|
-
for (const neighbor of (adjacency.get(nodeId) || [])) {
|
|
263
|
-
const nd = inDegree.get(neighbor) - 1;
|
|
264
|
-
inDegree.set(neighbor, nd);
|
|
265
|
-
if (nd === 0) queue.push(neighbor);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Cycle detection
|
|
270
|
-
const connectedCount = inDegree.size;
|
|
271
|
-
if (result.length < connectedCount) {
|
|
272
|
-
const remaining = [...inDegree.keys()].filter(id => !result.includes(id));
|
|
273
|
-
throw new Error(`Graph contains cycle(s). Nodes involved: ${remaining.join(', ')}`);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return result;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Resolve inputs for a node from upstream connections
|
|
281
|
-
* @param {string} nodeId
|
|
282
|
-
* @param {Array<{from: string, out: string, to: string, in: string}>} connections
|
|
283
|
-
* @param {Map<string, any>} results
|
|
284
|
-
* @returns {object}
|
|
285
|
-
* @private
|
|
286
|
-
*/
|
|
287
|
-
_resolveInputs(nodeId, connections, results) {
|
|
288
|
-
const inputs = {};
|
|
289
|
-
for (const conn of connections) {
|
|
290
|
-
if (conn.to !== nodeId) continue;
|
|
291
|
-
const upstream = results.get(conn.from);
|
|
292
|
-
if (upstream === undefined) continue;
|
|
293
|
-
|
|
294
|
-
let value;
|
|
295
|
-
if (upstream && typeof upstream === 'object' && conn.out in upstream) {
|
|
296
|
-
value = upstream[conn.out];
|
|
297
|
-
} else if (upstream && typeof upstream === 'object' && upstream.dynamicOutputs) {
|
|
298
|
-
// Dynamic routing node (switch): missing key = inactive branch
|
|
299
|
-
value = null;
|
|
300
|
-
} else {
|
|
301
|
-
value = upstream;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Multiple connections to same input: first non-null wins
|
|
305
|
-
if (inputs[conn.in] !== undefined && inputs[conn.in] !== null) continue;
|
|
306
|
-
inputs[conn.in] = value;
|
|
307
|
-
}
|
|
308
|
-
return inputs;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Find output nodes (no outgoing connections)
|
|
313
|
-
* @param {Map<string, object>} nodes
|
|
314
|
-
* @param {Array<{from: string}>} connections
|
|
315
|
-
* @returns {string[]}
|
|
316
|
-
* @private
|
|
317
|
-
*/
|
|
318
|
-
_findOutputNodes(nodes, connections) {
|
|
319
|
-
const hasOutgoing = new Set();
|
|
320
|
-
for (const conn of connections) {
|
|
321
|
-
hasOutgoing.add(conn.from);
|
|
322
|
-
}
|
|
323
|
-
const connected = new Set();
|
|
324
|
-
for (const conn of connections) {
|
|
325
|
-
connected.add(conn.from);
|
|
326
|
-
connected.add(conn.to);
|
|
327
|
-
}
|
|
328
|
-
return [...connected].filter(id => !hasOutgoing.has(id) && nodes.has(id));
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Execute a compound node's sub-graph
|
|
333
|
-
* @param {object} subGraphData - Sub-graph JSON definition
|
|
334
|
-
* @param {object} parentInputs - Inputs from parent graph
|
|
335
|
-
* @param {object} parentParams - Parent node params
|
|
336
|
-
* @returns {Promise<object>} Merged outputs from sub-graph output nodes
|
|
337
|
-
* @private
|
|
338
|
-
*/
|
|
339
|
-
async _executeSubGraph(subGraphData, parentInputs, parentParams) {
|
|
340
|
-
const subGraph = new Graph(subGraphData);
|
|
341
|
-
|
|
342
|
-
// Inject parent inputs into sub-graph input nodes
|
|
343
|
-
for (const node of subGraph.nodes.values()) {
|
|
344
|
-
if (node.type === 'compound/input') {
|
|
345
|
-
const injectedOutput = { ...parentInputs, ...parentParams };
|
|
346
|
-
node._output = injectedOutput;
|
|
347
|
-
node.process = () => injectedOutput;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Execute sub-graph with a fresh executor
|
|
352
|
-
const subExecutor = new Executor();
|
|
353
|
-
const result = await subExecutor.run(subGraph);
|
|
354
|
-
|
|
355
|
-
// Merge all output node results
|
|
356
|
-
const merged = {};
|
|
357
|
-
for (const [id, output] of Object.entries(result.outputs)) {
|
|
358
|
-
const node = subGraph.getNode(id);
|
|
359
|
-
if (node.type === 'compound/output' && output) {
|
|
360
|
-
Object.assign(merged, output);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Include dynamic socket info if sub-graph produces segments
|
|
365
|
-
if (Object.keys(merged).length > 0) {
|
|
366
|
-
merged.dynamicOutputs = Object.keys(merged);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return merged;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Graph.js - Universal graph data model
|
|
3
|
-
*
|
|
4
|
-
* Stores nodes and connections. Provides CRUD operations
|
|
5
|
-
* for AI agents and programmatic graph construction.
|
|
6
|
-
*
|
|
7
|
-
* @module symbiote-node/Graph */
|
|
8
|
-
|
|
9
|
-
import { nanoid } from './nanoid.js';
|
|
10
|
-
import { getNodeType, registerCustomDrivers } from './Registry.js';
|
|
11
|
-
import { areSocketsCompatible } from './SocketTypes.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @typedef {object} GraphNode
|
|
15
|
-
* @property {string} id - Unique node ID (nd_ prefix)
|
|
16
|
-
* @property {string} type - Node type identifier
|
|
17
|
-
* @property {string} [name] - Human-readable label
|
|
18
|
-
* @property {object} params - Node parameters
|
|
19
|
-
* @property {'auto'|'freeze'|'force'} [cacheMode='auto'] - Cache behavior mode
|
|
20
|
-
* @property {object} [_output] - Cached execution output
|
|
21
|
-
* @property {object} [_meta] - Metadata (variant flags, etc.)
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @typedef {object} Connection
|
|
26
|
-
* @property {string} from - Source node ID
|
|
27
|
-
* @property {string} out - Source output socket name
|
|
28
|
-
* @property {string} to - Target node ID
|
|
29
|
-
* @property {string} in - Target input socket name
|
|
30
|
-
* @property {string} [type] - Semantic connection type (for knowledge graphs)
|
|
31
|
-
* @property {string} [label] - Human-readable connection label
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
export class Graph {
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @param {object} [data] - Optional workflow JSON to load
|
|
38
|
-
*/
|
|
39
|
-
constructor(data) {
|
|
40
|
-
/** @type {string} */
|
|
41
|
-
this.id = `wf_${nanoid()}`;
|
|
42
|
-
|
|
43
|
-
/** @type {string} */
|
|
44
|
-
this.name = 'Untitled';
|
|
45
|
-
|
|
46
|
-
/** @type {number} */
|
|
47
|
-
this.version = 1;
|
|
48
|
-
|
|
49
|
-
/** @type {object} */
|
|
50
|
-
this.execution = { mode: 'sync', cache: true };
|
|
51
|
-
|
|
52
|
-
/** @type {Map<string, GraphNode>} */
|
|
53
|
-
this.nodes = new Map();
|
|
54
|
-
|
|
55
|
-
/** @type {Connection[]} */
|
|
56
|
-
this.connections = [];
|
|
57
|
-
|
|
58
|
-
/** @type {object} */
|
|
59
|
-
this.ui = { positions: {}, zoom: 1.0, pan: [0, 0] };
|
|
60
|
-
|
|
61
|
-
if (data) {
|
|
62
|
-
this.fromJSON(data);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Add a node to the graph
|
|
68
|
-
* @param {string} type - Node type (e.g., 'ai/llm')
|
|
69
|
-
* @param {object} [params={}] - Node parameters
|
|
70
|
-
* @param {object} [options={}] - Additional options
|
|
71
|
-
* @param {string} [options.id] - Custom node ID
|
|
72
|
-
* @param {string} [options.name] - Human-readable name
|
|
73
|
-
* @param {number[]} [options.position] - [x, y] position for UI
|
|
74
|
-
* @param {'auto'|'freeze'|'force'} [options.cacheMode='auto'] - Cache mode
|
|
75
|
-
* @returns {string} Node ID
|
|
76
|
-
*/
|
|
77
|
-
addNode(type, params = {}, options = {}) {
|
|
78
|
-
const id = options.id || `nd_${nanoid()}`;
|
|
79
|
-
|
|
80
|
-
const typeDef = getNodeType(type);
|
|
81
|
-
|
|
82
|
-
// Merge defaults from driver
|
|
83
|
-
let mergedParams = { ...params };
|
|
84
|
-
if (typeDef?.driver?.params) {
|
|
85
|
-
for (const [key, paramDef] of Object.entries(typeDef.driver.params)) {
|
|
86
|
-
if (mergedParams[key] === undefined && paramDef.default !== undefined) {
|
|
87
|
-
mergedParams[key] = paramDef.default;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** @type {GraphNode} */
|
|
93
|
-
const node = {
|
|
94
|
-
id,
|
|
95
|
-
type,
|
|
96
|
-
name: options.name || typeDef?.driver?.description?.slice(0, 30) || type,
|
|
97
|
-
params: mergedParams,
|
|
98
|
-
cacheMode: options.cacheMode || 'auto',
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
this.nodes.set(id, node);
|
|
102
|
-
|
|
103
|
-
if (options.position) {
|
|
104
|
-
this.ui.positions[id] = options.position;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return id;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Remove a node and all its connections
|
|
112
|
-
* @param {string} id - Node ID
|
|
113
|
-
* @returns {boolean}
|
|
114
|
-
*/
|
|
115
|
-
removeNode(id) {
|
|
116
|
-
if (!this.nodes.has(id)) return false;
|
|
117
|
-
this.nodes.delete(id);
|
|
118
|
-
this.connections = this.connections.filter(c => c.from !== id && c.to !== id);
|
|
119
|
-
delete this.ui.positions[id];
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Connect two nodes
|
|
125
|
-
* @param {string} fromNode - Source node ID
|
|
126
|
-
* @param {string} fromSocket - Source output socket name
|
|
127
|
-
* @param {string} toNode - Target node ID
|
|
128
|
-
* @param {string} toSocket - Target input socket name
|
|
129
|
-
* @param {object} [options={}]
|
|
130
|
-
* @param {string} [options.type] - Semantic connection type
|
|
131
|
-
* @param {string} [options.label] - Human-readable label
|
|
132
|
-
* @returns {Connection}
|
|
133
|
-
*/
|
|
134
|
-
connect(fromNode, fromSocket, toNode, toSocket, options = {}) {
|
|
135
|
-
if (!this.nodes.has(fromNode)) throw new Error(`Source node "${fromNode}" not found`);
|
|
136
|
-
if (!this.nodes.has(toNode)) throw new Error(`Target node "${toNode}" not found`);
|
|
137
|
-
|
|
138
|
-
// Validate socket compatibility if drivers available
|
|
139
|
-
const fromType = getNodeType(this.nodes.get(fromNode).type);
|
|
140
|
-
const toType = getNodeType(this.nodes.get(toNode).type);
|
|
141
|
-
|
|
142
|
-
if (fromType?.driver?.outputs && toType?.driver?.inputs) {
|
|
143
|
-
const outDef = fromType.driver.outputs.find(o => o.name === fromSocket);
|
|
144
|
-
const inDef = toType.driver.inputs.find(i => i.name === toSocket);
|
|
145
|
-
|
|
146
|
-
if (outDef && inDef && !areSocketsCompatible(outDef.type, inDef.type)) {
|
|
147
|
-
throw new Error(`Socket type mismatch: ${outDef.type} → ${inDef.type} (${fromNode}.${fromSocket} → ${toNode}.${toSocket})`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** @type {Connection} */
|
|
152
|
-
const conn = {
|
|
153
|
-
from: fromNode,
|
|
154
|
-
out: fromSocket,
|
|
155
|
-
to: toNode,
|
|
156
|
-
in: toSocket,
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
if (options.type) conn.type = options.type;
|
|
160
|
-
if (options.label) conn.label = options.label;
|
|
161
|
-
|
|
162
|
-
this.connections.push(conn);
|
|
163
|
-
return conn;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Disconnect two nodes
|
|
168
|
-
* @param {string} fromNode
|
|
169
|
-
* @param {string} fromSocket
|
|
170
|
-
* @param {string} toNode
|
|
171
|
-
* @param {string} toSocket
|
|
172
|
-
* @returns {boolean}
|
|
173
|
-
*/
|
|
174
|
-
disconnect(fromNode, fromSocket, toNode, toSocket) {
|
|
175
|
-
const idx = this.connections.findIndex(c =>
|
|
176
|
-
c.from === fromNode && c.out === fromSocket &&
|
|
177
|
-
c.to === toNode && c.in === toSocket
|
|
178
|
-
);
|
|
179
|
-
if (idx === -1) return false;
|
|
180
|
-
this.connections.splice(idx, 1);
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Get a node by ID
|
|
186
|
-
* @param {string} id
|
|
187
|
-
* @returns {GraphNode|undefined}
|
|
188
|
-
*/
|
|
189
|
-
getNode(id) {
|
|
190
|
-
return this.nodes.get(id);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Update node parameters
|
|
195
|
-
* @param {string} id
|
|
196
|
-
* @param {object} params - Params to merge
|
|
197
|
-
* @returns {GraphNode}
|
|
198
|
-
*/
|
|
199
|
-
updateParams(id, params) {
|
|
200
|
-
const node = this.nodes.get(id);
|
|
201
|
-
if (!node) throw new Error(`Node "${id}" not found`);
|
|
202
|
-
node.params = { ...node.params, ...params };
|
|
203
|
-
return node;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Set cache mode for a node
|
|
208
|
-
* @param {string} id - Node ID
|
|
209
|
-
* @param {'auto'|'freeze'|'force'} mode
|
|
210
|
-
*/
|
|
211
|
-
setCacheMode(id, mode) {
|
|
212
|
-
const node = this.nodes.get(id);
|
|
213
|
-
if (!node) throw new Error(`Node "${id}" not found`);
|
|
214
|
-
if (!['auto', 'freeze', 'force'].includes(mode)) {
|
|
215
|
-
throw new Error(`Invalid cache mode: ${mode}. Must be auto, freeze, or force`);
|
|
216
|
-
}
|
|
217
|
-
node.cacheMode = mode;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Get orphan nodes (not connected to anything)
|
|
222
|
-
* @returns {GraphNode[]}
|
|
223
|
-
*/
|
|
224
|
-
getOrphans() {
|
|
225
|
-
const connected = new Set();
|
|
226
|
-
for (const c of this.connections) {
|
|
227
|
-
connected.add(c.from);
|
|
228
|
-
connected.add(c.to);
|
|
229
|
-
}
|
|
230
|
-
return [...this.nodes.values()].filter(n => !connected.has(n.id));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Serialize graph to JSON
|
|
235
|
-
* @returns {object}
|
|
236
|
-
*/
|
|
237
|
-
toJSON() {
|
|
238
|
-
return {
|
|
239
|
-
version: this.version,
|
|
240
|
-
id: this.id,
|
|
241
|
-
name: this.name,
|
|
242
|
-
execution: this.execution,
|
|
243
|
-
nodes: [...this.nodes.values()].map(n => {
|
|
244
|
-
const obj = { id: n.id, type: n.type, name: n.name, params: n.params };
|
|
245
|
-
if (n.cacheMode && n.cacheMode !== 'auto') obj.cacheMode = n.cacheMode;
|
|
246
|
-
if (n._output) obj._output = n._output;
|
|
247
|
-
if (n._meta) obj._meta = n._meta;
|
|
248
|
-
if (n.driver) obj.driver = n.driver;
|
|
249
|
-
if (n.subgraph) obj.subgraph = n.subgraph;
|
|
250
|
-
return obj;
|
|
251
|
-
}),
|
|
252
|
-
connections: this.connections,
|
|
253
|
-
ui: this.ui,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Load graph from JSON
|
|
259
|
-
* @param {object} data - Workflow JSON
|
|
260
|
-
* @returns {Graph} this
|
|
261
|
-
*/
|
|
262
|
-
fromJSON(data) {
|
|
263
|
-
this.version = data.version || 1;
|
|
264
|
-
this.id = data.id || this.id;
|
|
265
|
-
this.name = data.name || 'Untitled';
|
|
266
|
-
this.execution = data.execution || { mode: 'sync', cache: true };
|
|
267
|
-
this.ui = data.ui || { positions: {}, zoom: 1.0, pan: [0, 0] };
|
|
268
|
-
|
|
269
|
-
// Register custom drivers if present
|
|
270
|
-
if (data.customDrivers) {
|
|
271
|
-
registerCustomDrivers(data.customDrivers);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Load nodes
|
|
275
|
-
this.nodes.clear();
|
|
276
|
-
for (const n of (data.nodes || [])) {
|
|
277
|
-
const node = { id: n.id, type: n.type, name: n.name, params: n.params || {}, cacheMode: n.cacheMode || 'auto' };
|
|
278
|
-
if (n._output) node._output = n._output;
|
|
279
|
-
if (n._meta) node._meta = n._meta;
|
|
280
|
-
if (n.driver) node.driver = n.driver;
|
|
281
|
-
if (n.subgraph) node.subgraph = n.subgraph;
|
|
282
|
-
this.nodes.set(n.id, node);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Load connections (handle both {out, in} and {fromSocket, toSocket} DB formats)
|
|
286
|
-
this.connections = (data.connections || []).map(c => {
|
|
287
|
-
const conn = {
|
|
288
|
-
from: c.from,
|
|
289
|
-
out: c.out || c.fromSocket,
|
|
290
|
-
to: c.to,
|
|
291
|
-
in: c.in || c.toSocket,
|
|
292
|
-
}; if (c.type) conn.type = c.type;
|
|
293
|
-
if (c.label) conn.label = c.label;
|
|
294
|
-
return conn;
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
return this;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Get execution statistics
|
|
302
|
-
* @returns {object}
|
|
303
|
-
*/
|
|
304
|
-
stats() {
|
|
305
|
-
const types = new Set();
|
|
306
|
-
for (const n of this.nodes.values()) types.add(n.type);
|
|
307
|
-
return {
|
|
308
|
-
totalNodes: this.nodes.size,
|
|
309
|
-
totalConnections: this.connections.length,
|
|
310
|
-
orphans: this.getOrphans().length,
|
|
311
|
-
uniqueTypes: types.size,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
}
|