@bian-womp/spark-graph 0.3.12 → 0.3.14
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/lib/cjs/index.cjs +652 -231
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/EdgePropagator.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/Graph.d.ts +198 -6
- package/lib/cjs/src/runtime/components/Graph.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/HandleResolver.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts +0 -4
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/RunContextManager.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/types.d.ts +11 -0
- package/lib/cjs/src/runtime/components/types.d.ts.map +1 -1
- package/lib/esm/index.js +652 -231
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/EdgePropagator.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/Graph.d.ts +198 -6
- package/lib/esm/src/runtime/components/Graph.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/HandleResolver.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts +0 -4
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/RunContextManager.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/types.d.ts +11 -0
- package/lib/esm/src/runtime/components/types.d.ts.map +1 -1
- package/package.json +2 -2
package/lib/cjs/index.cjs
CHANGED
|
@@ -446,6 +446,7 @@ Registry.idCounter = 0;
|
|
|
446
446
|
|
|
447
447
|
/**
|
|
448
448
|
* Graph component - manages nodes, edges, and handle resolution
|
|
449
|
+
* This is the ONLY place where nodes, edges, and resolvedByNode are directly updated.
|
|
449
450
|
*/
|
|
450
451
|
class Graph {
|
|
451
452
|
constructor(registry) {
|
|
@@ -454,46 +455,423 @@ class Graph {
|
|
|
454
455
|
this.edges = [];
|
|
455
456
|
this.resolvedByNode = new Map();
|
|
456
457
|
}
|
|
457
|
-
// Node
|
|
458
|
+
// ==================== Node Accessors ====================
|
|
459
|
+
/**
|
|
460
|
+
* Get a node by ID (readonly to prevent accidental modifications)
|
|
461
|
+
*/
|
|
458
462
|
getNode(nodeId) {
|
|
459
463
|
return this.nodes.get(nodeId);
|
|
460
464
|
}
|
|
461
|
-
|
|
462
|
-
|
|
465
|
+
/**
|
|
466
|
+
* Get a node by ID (mutable, for internal use only)
|
|
467
|
+
* @internal
|
|
468
|
+
*/
|
|
469
|
+
getNodeMutable(nodeId) {
|
|
470
|
+
return this.nodes.get(nodeId);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Iterate over all nodes safely (readonly to prevent accidental modifications)
|
|
474
|
+
*/
|
|
475
|
+
forEachNode(callback) {
|
|
476
|
+
for (const [nodeId, node] of this.nodes.entries()) {
|
|
477
|
+
callback(node, nodeId);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Get all node IDs
|
|
482
|
+
*/
|
|
483
|
+
getNodeIds() {
|
|
484
|
+
return Array.from(this.nodes.keys());
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Check if a node exists
|
|
488
|
+
*/
|
|
489
|
+
hasNode(nodeId) {
|
|
490
|
+
return this.nodes.has(nodeId);
|
|
463
491
|
}
|
|
492
|
+
// ==================== Node Mutators ====================
|
|
493
|
+
/**
|
|
494
|
+
* Set a node (creates or replaces)
|
|
495
|
+
*/
|
|
464
496
|
setNode(nodeId, node) {
|
|
465
497
|
this.nodes.set(nodeId, node);
|
|
466
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Delete a node
|
|
501
|
+
*/
|
|
467
502
|
deleteNode(nodeId) {
|
|
468
503
|
this.nodes.delete(nodeId);
|
|
469
504
|
}
|
|
470
|
-
|
|
471
|
-
|
|
505
|
+
// ==================== Node Property Updates ====================
|
|
506
|
+
/**
|
|
507
|
+
* Update node inputs
|
|
508
|
+
*/
|
|
509
|
+
updateNodeInput(nodeId, handle, value) {
|
|
510
|
+
const node = this.getNodeMutable(nodeId);
|
|
511
|
+
if (!node)
|
|
512
|
+
return;
|
|
513
|
+
if (value === undefined) {
|
|
514
|
+
delete node.inputs[handle];
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
node.inputs[handle] = value;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Delete a node input handle
|
|
522
|
+
*/
|
|
523
|
+
deleteNodeInput(nodeId, handle) {
|
|
524
|
+
const node = this.nodes.get(nodeId);
|
|
525
|
+
if (!node)
|
|
526
|
+
return;
|
|
527
|
+
delete node.inputs[handle];
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Update node outputs
|
|
531
|
+
*/
|
|
532
|
+
updateNodeOutput(nodeId, handle, value) {
|
|
533
|
+
const node = this.nodes.get(nodeId);
|
|
534
|
+
if (!node)
|
|
535
|
+
return;
|
|
536
|
+
node.outputs[handle] = value;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Update node state
|
|
540
|
+
*/
|
|
541
|
+
updateNodeState(nodeId, updates) {
|
|
542
|
+
const node = this.nodes.get(nodeId);
|
|
543
|
+
if (!node)
|
|
544
|
+
return;
|
|
545
|
+
Object.assign(node.state, updates);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Update node params
|
|
549
|
+
*/
|
|
550
|
+
updateNodeParams(nodeId, params) {
|
|
551
|
+
const node = this.nodes.get(nodeId);
|
|
552
|
+
if (!node)
|
|
553
|
+
return;
|
|
554
|
+
node.params = params;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Update node policy
|
|
558
|
+
*/
|
|
559
|
+
updateNodePolicy(nodeId, policy) {
|
|
560
|
+
const node = this.nodes.get(nodeId);
|
|
561
|
+
if (!node)
|
|
562
|
+
return;
|
|
563
|
+
node.policy = { ...node.policy, ...policy };
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Update node stats
|
|
567
|
+
*/
|
|
568
|
+
updateNodeStats(nodeId, updates) {
|
|
569
|
+
const node = this.nodes.get(nodeId);
|
|
570
|
+
if (!node)
|
|
571
|
+
return;
|
|
572
|
+
Object.assign(node.stats, updates);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Increment node runSeq
|
|
576
|
+
*/
|
|
577
|
+
incrementNodeRunSeq(nodeId) {
|
|
578
|
+
const node = this.nodes.get(nodeId);
|
|
579
|
+
if (!node)
|
|
580
|
+
return 0;
|
|
581
|
+
node.runSeq += 1;
|
|
582
|
+
return node.runSeq;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Set node latestRunId
|
|
586
|
+
*/
|
|
587
|
+
setNodeLatestRunId(nodeId, runId) {
|
|
588
|
+
const node = this.nodes.get(nodeId);
|
|
589
|
+
if (!node)
|
|
590
|
+
return;
|
|
591
|
+
node.latestRunId = runId;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Set node lastScheduledAt
|
|
595
|
+
*/
|
|
596
|
+
setNodeLastScheduledAt(nodeId, timestamp) {
|
|
597
|
+
const node = this.nodes.get(nodeId);
|
|
598
|
+
if (!node)
|
|
599
|
+
return;
|
|
600
|
+
node.lastScheduledAt = timestamp;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Update node lastInputAt timestamp for a handle
|
|
604
|
+
*/
|
|
605
|
+
updateNodeLastInputAt(nodeId, handle, timestamp) {
|
|
606
|
+
const node = this.nodes.get(nodeId);
|
|
607
|
+
if (!node)
|
|
608
|
+
return;
|
|
609
|
+
if (!node.lastInputAt) {
|
|
610
|
+
node.lastInputAt = {};
|
|
611
|
+
}
|
|
612
|
+
node.lastInputAt[handle] = timestamp;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Set node lastSuccessAt timestamp
|
|
616
|
+
*/
|
|
617
|
+
setNodeLastSuccessAt(nodeId, timestamp) {
|
|
618
|
+
const node = this.nodes.get(nodeId);
|
|
619
|
+
if (!node)
|
|
620
|
+
return;
|
|
621
|
+
node.lastSuccessAt = timestamp;
|
|
622
|
+
}
|
|
623
|
+
// ==================== Node Queue Operations ====================
|
|
624
|
+
/**
|
|
625
|
+
* Add item to node queue
|
|
626
|
+
*/
|
|
627
|
+
addToNodeQueue(nodeId, item) {
|
|
628
|
+
const node = this.nodes.get(nodeId);
|
|
629
|
+
if (!node)
|
|
630
|
+
return;
|
|
631
|
+
node.queue.push(item);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Remove first item from node queue
|
|
635
|
+
*/
|
|
636
|
+
shiftNodeQueue(nodeId) {
|
|
637
|
+
const node = this.nodes.get(nodeId);
|
|
638
|
+
if (!node)
|
|
639
|
+
return undefined;
|
|
640
|
+
return node.queue.shift();
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Clear node queue
|
|
644
|
+
*/
|
|
645
|
+
clearNodeQueue(nodeId) {
|
|
646
|
+
const node = this.nodes.get(nodeId);
|
|
647
|
+
if (!node)
|
|
648
|
+
return;
|
|
649
|
+
node.queue = [];
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Replace node queue
|
|
653
|
+
*/
|
|
654
|
+
replaceNodeQueue(nodeId, items) {
|
|
655
|
+
const node = this.nodes.get(nodeId);
|
|
656
|
+
if (!node)
|
|
657
|
+
return;
|
|
658
|
+
node.queue = items;
|
|
659
|
+
}
|
|
660
|
+
// ==================== Node Controller Operations ====================
|
|
661
|
+
/**
|
|
662
|
+
* Add controller to node
|
|
663
|
+
*/
|
|
664
|
+
addNodeController(nodeId, controller, runId) {
|
|
665
|
+
const node = this.nodes.get(nodeId);
|
|
666
|
+
if (!node)
|
|
667
|
+
return;
|
|
668
|
+
node.activeControllers.add(controller);
|
|
669
|
+
node.controllerRunIds.set(controller, runId);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Remove controller from node
|
|
673
|
+
*/
|
|
674
|
+
removeNodeController(nodeId, controller) {
|
|
675
|
+
const node = this.nodes.get(nodeId);
|
|
676
|
+
if (!node)
|
|
677
|
+
return;
|
|
678
|
+
node.activeControllers.delete(controller);
|
|
679
|
+
node.controllerRunIds.delete(controller);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Clear all controllers from node
|
|
683
|
+
*/
|
|
684
|
+
clearNodeControllers(nodeId) {
|
|
685
|
+
const node = this.nodes.get(nodeId);
|
|
686
|
+
if (!node)
|
|
687
|
+
return;
|
|
688
|
+
node.activeControllers.clear();
|
|
689
|
+
node.controllerRunIds.clear();
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get all controllers for a node
|
|
693
|
+
*/
|
|
694
|
+
getNodeControllers(nodeId) {
|
|
695
|
+
const node = this.nodes.get(nodeId);
|
|
696
|
+
if (!node)
|
|
697
|
+
return new Set();
|
|
698
|
+
return new Set(node.activeControllers);
|
|
699
|
+
}
|
|
700
|
+
// ==================== Node Run Context Operations ====================
|
|
701
|
+
/**
|
|
702
|
+
* Add run context ID to node
|
|
703
|
+
*/
|
|
704
|
+
addNodeRunContextId(nodeId, runContextId) {
|
|
705
|
+
const node = this.nodes.get(nodeId);
|
|
706
|
+
if (!node)
|
|
707
|
+
return;
|
|
708
|
+
node.activeRunContextIds.add(runContextId);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Add multiple run context IDs to node
|
|
712
|
+
*/
|
|
713
|
+
addNodeRunContextIds(nodeId, runContextIds) {
|
|
714
|
+
const node = this.nodes.get(nodeId);
|
|
715
|
+
if (!node)
|
|
716
|
+
return;
|
|
717
|
+
for (const id of runContextIds) {
|
|
718
|
+
node.activeRunContextIds.add(id);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Remove run context ID from node
|
|
723
|
+
*/
|
|
724
|
+
removeNodeRunContextId(nodeId, runContextId) {
|
|
725
|
+
const node = this.nodes.get(nodeId);
|
|
726
|
+
if (!node)
|
|
727
|
+
return;
|
|
728
|
+
node.activeRunContextIds.delete(runContextId);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Clear all run context IDs from node
|
|
732
|
+
*/
|
|
733
|
+
clearNodeRunContextIds(nodeId) {
|
|
734
|
+
const node = this.nodes.get(nodeId);
|
|
735
|
+
if (!node)
|
|
736
|
+
return;
|
|
737
|
+
node.activeRunContextIds.clear();
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Get run context IDs for a node
|
|
741
|
+
*/
|
|
742
|
+
getNodeRunContextIds(nodeId) {
|
|
743
|
+
const node = this.nodes.get(nodeId);
|
|
744
|
+
if (!node)
|
|
745
|
+
return new Set();
|
|
746
|
+
return new Set(node.activeRunContextIds);
|
|
747
|
+
}
|
|
748
|
+
// ==================== Node Snapshot Cancelled Run IDs ====================
|
|
749
|
+
/**
|
|
750
|
+
* Add snapshot cancelled run ID to node
|
|
751
|
+
*/
|
|
752
|
+
addSnapshotCancelledRunId(nodeId, runId) {
|
|
753
|
+
const node = this.nodes.get(nodeId);
|
|
754
|
+
if (!node)
|
|
755
|
+
return;
|
|
756
|
+
if (!node.snapshotCancelledRunIds) {
|
|
757
|
+
node.snapshotCancelledRunIds = new Set();
|
|
758
|
+
}
|
|
759
|
+
node.snapshotCancelledRunIds.add(runId);
|
|
760
|
+
}
|
|
761
|
+
// ==================== Edge Accessors ====================
|
|
762
|
+
/**
|
|
763
|
+
* Iterate over all edges safely
|
|
764
|
+
*/
|
|
765
|
+
forEachEdge(callback) {
|
|
766
|
+
this.edges.forEach(callback);
|
|
472
767
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
768
|
+
/**
|
|
769
|
+
* Find edges matching a predicate
|
|
770
|
+
*/
|
|
771
|
+
findEdges(predicate) {
|
|
772
|
+
return this.edges.filter(predicate);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Get edges by source node and handle
|
|
776
|
+
*/
|
|
777
|
+
getEdgesBySource(srcNodeId, srcHandle) {
|
|
778
|
+
return this.edges.filter((e) => e.source.nodeId === srcNodeId && e.source.handle === srcHandle);
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Get edges by target node and handle
|
|
782
|
+
*/
|
|
783
|
+
getEdgesByTarget(targetNodeId, targetHandle) {
|
|
784
|
+
return this.edges.filter((e) => e.target.nodeId === targetNodeId && e.target.handle === targetHandle);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Get inbound edges for a node
|
|
788
|
+
*/
|
|
789
|
+
getInboundEdges(nodeId) {
|
|
790
|
+
return this.edges.filter((e) => e.target.nodeId === nodeId);
|
|
476
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* Get outbound edges for a node
|
|
794
|
+
*/
|
|
795
|
+
getOutboundEdges(nodeId) {
|
|
796
|
+
return this.edges.filter((e) => e.source.nodeId === nodeId);
|
|
797
|
+
}
|
|
798
|
+
// ==================== Edge Mutators ====================
|
|
799
|
+
/**
|
|
800
|
+
* Set all edges (replaces existing)
|
|
801
|
+
*/
|
|
477
802
|
setEdges(edges) {
|
|
478
|
-
this.edges = edges;
|
|
803
|
+
this.edges = [...edges];
|
|
479
804
|
}
|
|
480
|
-
|
|
805
|
+
/**
|
|
806
|
+
* Update an edge by ID
|
|
807
|
+
*/
|
|
808
|
+
updateEdge(edgeId, updates) {
|
|
809
|
+
const edge = this.edges.find((e) => e.id === edgeId);
|
|
810
|
+
if (!edge)
|
|
811
|
+
return;
|
|
812
|
+
Object.assign(edge, updates);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Update edge properties (convert, convertAsync, types, etc.)
|
|
816
|
+
*/
|
|
817
|
+
updateEdgeProperties(edgeId, updates) {
|
|
818
|
+
const edge = this.edges.find((e) => e.id === edgeId);
|
|
819
|
+
if (!edge)
|
|
820
|
+
return;
|
|
821
|
+
if (updates.effectiveTypeId !== undefined) {
|
|
822
|
+
edge.effectiveTypeId = updates.effectiveTypeId;
|
|
823
|
+
}
|
|
824
|
+
if (updates.dstDeclared !== undefined) {
|
|
825
|
+
edge.dstDeclared = updates.dstDeclared;
|
|
826
|
+
}
|
|
827
|
+
if (updates.srcUnionTypes !== undefined) {
|
|
828
|
+
edge.srcUnionTypes = updates.srcUnionTypes;
|
|
829
|
+
}
|
|
830
|
+
if (updates.convert !== undefined) {
|
|
831
|
+
edge.convert = updates.convert;
|
|
832
|
+
}
|
|
833
|
+
if (updates.convertAsync !== undefined) {
|
|
834
|
+
edge.convertAsync = updates.convertAsync;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Update edge stats
|
|
839
|
+
*/
|
|
840
|
+
updateEdgeStats(edgeId, updates) {
|
|
841
|
+
const edge = this.edges.find((e) => e.id === edgeId);
|
|
842
|
+
if (!edge)
|
|
843
|
+
return;
|
|
844
|
+
Object.assign(edge.stats, updates);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Get edge by ID
|
|
848
|
+
*/
|
|
849
|
+
getEdge(edgeId) {
|
|
850
|
+
return this.edges.find((e) => e.id === edgeId);
|
|
851
|
+
}
|
|
852
|
+
// ==================== Registry Accessors ====================
|
|
481
853
|
getRegistry() {
|
|
482
854
|
return this.registry;
|
|
483
855
|
}
|
|
484
856
|
setRegistry(registry) {
|
|
485
857
|
this.registry = registry;
|
|
486
858
|
}
|
|
487
|
-
// Resolved
|
|
859
|
+
// ==================== Resolved Handles Accessors ====================
|
|
488
860
|
getResolvedHandles(nodeId) {
|
|
489
861
|
return this.resolvedByNode.get(nodeId);
|
|
490
862
|
}
|
|
491
863
|
setResolvedHandles(nodeId, handles) {
|
|
492
864
|
this.resolvedByNode.set(nodeId, handles);
|
|
493
865
|
}
|
|
494
|
-
|
|
495
|
-
|
|
866
|
+
/**
|
|
867
|
+
* Iterate over resolved handles safely
|
|
868
|
+
*/
|
|
869
|
+
forEachResolvedHandles(callback) {
|
|
870
|
+
for (const [nodeId, handles] of this.resolvedByNode.entries()) {
|
|
871
|
+
callback(handles, nodeId);
|
|
872
|
+
}
|
|
496
873
|
}
|
|
874
|
+
// ==================== Utility Methods ====================
|
|
497
875
|
/**
|
|
498
876
|
* Check if all inbound edges for a node have values
|
|
499
877
|
*/
|
|
@@ -510,7 +888,10 @@ class Graph {
|
|
|
510
888
|
}
|
|
511
889
|
return true;
|
|
512
890
|
}
|
|
513
|
-
// Clear
|
|
891
|
+
// ==================== Clear Operations ====================
|
|
892
|
+
/**
|
|
893
|
+
* Clear all data
|
|
894
|
+
*/
|
|
514
895
|
clear() {
|
|
515
896
|
this.nodes.clear();
|
|
516
897
|
this.edges = [];
|
|
@@ -650,9 +1031,9 @@ class RunContextManager {
|
|
|
650
1031
|
return; // Still has pending work
|
|
651
1032
|
}
|
|
652
1033
|
// Clean up activeRunContexts from all nodes
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
1034
|
+
this.graph.forEachNode((node) => {
|
|
1035
|
+
this.graph.removeNodeRunContextId(node.nodeId, id);
|
|
1036
|
+
});
|
|
656
1037
|
this.runContexts.delete(id);
|
|
657
1038
|
if (ctx.resolve)
|
|
658
1039
|
ctx.resolve();
|
|
@@ -675,7 +1056,7 @@ class RunContextManager {
|
|
|
675
1056
|
if (visited.has(cur))
|
|
676
1057
|
continue;
|
|
677
1058
|
visited.add(cur);
|
|
678
|
-
|
|
1059
|
+
this.graph.forEachEdge((e) => {
|
|
679
1060
|
if (e.source.nodeId === cur) {
|
|
680
1061
|
const targetId = e.target.nodeId;
|
|
681
1062
|
if (!visited.has(targetId)) {
|
|
@@ -683,7 +1064,7 @@ class RunContextManager {
|
|
|
683
1064
|
queue.push(targetId);
|
|
684
1065
|
}
|
|
685
1066
|
}
|
|
686
|
-
}
|
|
1067
|
+
});
|
|
687
1068
|
}
|
|
688
1069
|
}
|
|
689
1070
|
// Mark nodes as cancelled in all run-contexts
|
|
@@ -694,9 +1075,7 @@ class RunContextManager {
|
|
|
694
1075
|
}
|
|
695
1076
|
// Clear activeRunContexts for cancelled nodes
|
|
696
1077
|
for (const id of toCancel) {
|
|
697
|
-
|
|
698
|
-
if (node)
|
|
699
|
-
node.activeRunContextIds.clear();
|
|
1078
|
+
this.graph.clearNodeRunContextIds(id);
|
|
700
1079
|
}
|
|
701
1080
|
}
|
|
702
1081
|
/**
|
|
@@ -974,13 +1353,14 @@ class HandleResolver {
|
|
|
974
1353
|
if (!node)
|
|
975
1354
|
return;
|
|
976
1355
|
// Track resolver start for all active run-contexts
|
|
977
|
-
|
|
978
|
-
|
|
1356
|
+
const activeRunContextIds = this.graph.getNodeRunContextIds(nodeId);
|
|
1357
|
+
if (activeRunContextIds.size > 0) {
|
|
1358
|
+
for (const runContextId of activeRunContextIds) {
|
|
979
1359
|
this.runContextManager.startHandleResolution(runContextId, nodeId);
|
|
980
1360
|
}
|
|
981
1361
|
}
|
|
982
1362
|
setTimeout(() => {
|
|
983
|
-
void this.recomputeHandlesForNode(nodeId,
|
|
1363
|
+
void this.recomputeHandlesForNode(nodeId, activeRunContextIds.size > 0 ? activeRunContextIds : undefined);
|
|
984
1364
|
}, 0);
|
|
985
1365
|
}
|
|
986
1366
|
// Update resolved handles for a single node and refresh edge converters/types that touch it
|
|
@@ -991,30 +1371,33 @@ class HandleResolver {
|
|
|
991
1371
|
if (!node)
|
|
992
1372
|
return;
|
|
993
1373
|
this.graph.setResolvedHandles(nodeId, handles);
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
1374
|
+
const resolvedByNode = new Map();
|
|
1375
|
+
this.graph.forEachResolvedHandles((handles, nodeId) => {
|
|
1376
|
+
resolvedByNode.set(nodeId, handles);
|
|
1377
|
+
});
|
|
1378
|
+
const registry = this.registry; // Store for use in callback
|
|
1379
|
+
this.graph.forEachEdge((e, _index) => {
|
|
997
1380
|
// Only update edges that touch the changed node
|
|
998
1381
|
const touchesChangedNode = e.source.nodeId === nodeId || e.target.nodeId === nodeId;
|
|
999
1382
|
if (!touchesChangedNode)
|
|
1000
|
-
|
|
1383
|
+
return;
|
|
1001
1384
|
const srcNode = this.graph.getNode(e.source.nodeId);
|
|
1002
1385
|
const dstNode = this.graph.getNode(e.target.nodeId);
|
|
1003
1386
|
const oldDstDeclared = e.dstDeclared;
|
|
1004
1387
|
// Extract edge types using shared helper (handles both source and target updates)
|
|
1005
1388
|
const { srcDeclared, dstDeclared, effectiveTypeId } = extractEdgeTypes(e.source.nodeId, e.source.handle, e.target.nodeId, e.target.handle, resolvedByNode, e.typeId);
|
|
1006
|
-
// Update edge properties
|
|
1007
|
-
if (!e.typeId) {
|
|
1008
|
-
e.effectiveTypeId = effectiveTypeId;
|
|
1009
|
-
}
|
|
1010
|
-
e.dstDeclared = dstDeclared;
|
|
1011
|
-
e.srcUnionTypes = Array.isArray(srcDeclared)
|
|
1012
|
-
? [...srcDeclared]
|
|
1013
|
-
: undefined;
|
|
1014
1389
|
// Update converters
|
|
1015
|
-
const conv = buildEdgeConverters(srcDeclared, dstDeclared,
|
|
1016
|
-
|
|
1017
|
-
e.
|
|
1390
|
+
const conv = buildEdgeConverters(srcDeclared, dstDeclared, registry, `updateNodeHandles: ${srcNode?.typeId || ""}.${e.source.nodeId}.${e.source.handle} -> ${dstNode?.typeId || ""}.${e.target.nodeId}.${e.target.handle}`);
|
|
1391
|
+
// Update edge properties via Graph
|
|
1392
|
+
this.graph.updateEdgeProperties(e.id, {
|
|
1393
|
+
effectiveTypeId: !e.typeId ? effectiveTypeId : undefined,
|
|
1394
|
+
dstDeclared,
|
|
1395
|
+
srcUnionTypes: Array.isArray(srcDeclared)
|
|
1396
|
+
? [...srcDeclared]
|
|
1397
|
+
: undefined,
|
|
1398
|
+
convert: conv.convert,
|
|
1399
|
+
convertAsync: conv.convertAsync,
|
|
1400
|
+
});
|
|
1018
1401
|
if (e.target.nodeId === nodeId &&
|
|
1019
1402
|
oldDstDeclared === undefined &&
|
|
1020
1403
|
dstDeclared !== undefined) {
|
|
@@ -1022,11 +1405,12 @@ class HandleResolver {
|
|
|
1022
1405
|
if (srcNode) {
|
|
1023
1406
|
const srcValue = srcNode.outputs[e.source.handle];
|
|
1024
1407
|
if (srcValue !== undefined) {
|
|
1025
|
-
this.
|
|
1408
|
+
const activeRunContextIds = this.graph.getNodeRunContextIds(e.source.nodeId);
|
|
1409
|
+
this.edgePropagator.propagate(e.source.nodeId, e.source.handle, srcValue, activeRunContextIds.size > 0 ? activeRunContextIds : undefined);
|
|
1026
1410
|
}
|
|
1027
1411
|
}
|
|
1028
1412
|
}
|
|
1029
|
-
}
|
|
1413
|
+
});
|
|
1030
1414
|
this.edgePropagator.invalidateDownstream(nodeId);
|
|
1031
1415
|
}
|
|
1032
1416
|
/**
|
|
@@ -1165,12 +1549,11 @@ class EdgePropagator {
|
|
|
1165
1549
|
* Set source output value and emit event
|
|
1166
1550
|
*/
|
|
1167
1551
|
setSourceOutput(srcNodeId, srcHandle, value) {
|
|
1168
|
-
|
|
1169
|
-
if (!srcNode) {
|
|
1552
|
+
if (!this.graph.hasNode(srcNodeId)) {
|
|
1170
1553
|
// Node was removed (e.g., graph updated) but an async emit arrived late; ignore
|
|
1171
1554
|
return false;
|
|
1172
1555
|
}
|
|
1173
|
-
|
|
1556
|
+
this.graph.updateNodeOutput(srcNodeId, srcHandle, value);
|
|
1174
1557
|
this.eventEmitter.emit("value", {
|
|
1175
1558
|
nodeId: srcNodeId,
|
|
1176
1559
|
handle: srcHandle,
|
|
@@ -1184,8 +1567,7 @@ class EdgePropagator {
|
|
|
1184
1567
|
* Find all outgoing edges from a source node handle
|
|
1185
1568
|
*/
|
|
1186
1569
|
findOutgoingEdges(srcNodeId, srcHandle) {
|
|
1187
|
-
|
|
1188
|
-
return edges.filter((e) => e.source.nodeId === srcNodeId && e.source.handle === srcHandle);
|
|
1570
|
+
return this.graph.getEdgesBySource(srcNodeId, srcHandle);
|
|
1189
1571
|
}
|
|
1190
1572
|
/**
|
|
1191
1573
|
* Propagate value to a single edge
|
|
@@ -1284,9 +1666,12 @@ class EdgePropagator {
|
|
|
1284
1666
|
});
|
|
1285
1667
|
const controller = new AbortController();
|
|
1286
1668
|
const startAt = Date.now();
|
|
1287
|
-
edge.stats
|
|
1288
|
-
edge.
|
|
1289
|
-
|
|
1669
|
+
const currentStats = edge.stats;
|
|
1670
|
+
this.graph.updateEdgeStats(edge.id, {
|
|
1671
|
+
runs: currentStats.runs + 1,
|
|
1672
|
+
inFlight: true,
|
|
1673
|
+
progress: 0,
|
|
1674
|
+
});
|
|
1290
1675
|
edge
|
|
1291
1676
|
.convertAsync(value, controller.signal)
|
|
1292
1677
|
.then((converted) => {
|
|
@@ -1342,10 +1727,7 @@ class EdgePropagator {
|
|
|
1342
1727
|
else if (shouldSetValue && !valueChanged) {
|
|
1343
1728
|
// Even if value didn't change, update timestamp if we're forcing execution
|
|
1344
1729
|
const now = Date.now();
|
|
1345
|
-
|
|
1346
|
-
dstNode.lastInputAt = {};
|
|
1347
|
-
}
|
|
1348
|
-
dstNode.lastInputAt[edge.target.handle] = now;
|
|
1730
|
+
this.graph.updateNodeLastInputAt(edge.target.nodeId, edge.target.handle, now);
|
|
1349
1731
|
}
|
|
1350
1732
|
// Schedule downstream execution
|
|
1351
1733
|
this.executeDownstream(edge.target.nodeId, effectiveRunContexts);
|
|
@@ -1371,15 +1753,12 @@ class EdgePropagator {
|
|
|
1371
1753
|
}
|
|
1372
1754
|
forHandle.set(edge.id, toArray(value));
|
|
1373
1755
|
// Merge all parts for this handle
|
|
1374
|
-
const
|
|
1756
|
+
const targetEdges = this.graph.getEdgesByTarget(edge.target.nodeId, edge.target.handle);
|
|
1375
1757
|
const merged = [];
|
|
1376
|
-
for (const ed of
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
if (part && part.length)
|
|
1381
|
-
merged.push(...part);
|
|
1382
|
-
}
|
|
1758
|
+
for (const ed of targetEdges) {
|
|
1759
|
+
const part = forHandle.get(ed.id);
|
|
1760
|
+
if (part && part.length)
|
|
1761
|
+
merged.push(...part);
|
|
1383
1762
|
}
|
|
1384
1763
|
return merged;
|
|
1385
1764
|
}
|
|
@@ -1404,12 +1783,8 @@ class EdgePropagator {
|
|
|
1404
1783
|
*/
|
|
1405
1784
|
setTargetInput(edge, dstNode, value) {
|
|
1406
1785
|
const now = Date.now();
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
if (!dstNode.lastInputAt) {
|
|
1410
|
-
dstNode.lastInputAt = {};
|
|
1411
|
-
}
|
|
1412
|
-
dstNode.lastInputAt[edge.target.handle] = now;
|
|
1786
|
+
this.graph.updateNodeInput(edge.target.nodeId, edge.target.handle, value);
|
|
1787
|
+
this.graph.updateNodeLastInputAt(edge.target.nodeId, edge.target.handle, now);
|
|
1413
1788
|
this.eventEmitter.emit("value", {
|
|
1414
1789
|
nodeId: edge.target.nodeId,
|
|
1415
1790
|
handle: edge.target.handle,
|
|
@@ -1449,9 +1824,13 @@ class EdgePropagator {
|
|
|
1449
1824
|
* Update edge stats on successful conversion
|
|
1450
1825
|
*/
|
|
1451
1826
|
updateEdgeStatsOnSuccess(edge, startAt) {
|
|
1452
|
-
edge.stats.inFlight = false;
|
|
1453
1827
|
const duration = Date.now() - startAt;
|
|
1454
|
-
edge.
|
|
1828
|
+
this.graph.updateEdgeStats(edge.id, {
|
|
1829
|
+
inFlight: false,
|
|
1830
|
+
lastDurationMs: duration,
|
|
1831
|
+
lastEndAt: Date.now(),
|
|
1832
|
+
lastError: undefined,
|
|
1833
|
+
});
|
|
1455
1834
|
this.eventEmitter.emit("stats", {
|
|
1456
1835
|
kind: "edge-done",
|
|
1457
1836
|
edgeId: edge.id,
|
|
@@ -1460,15 +1839,15 @@ class EdgePropagator {
|
|
|
1460
1839
|
target: { nodeId: edge.target.nodeId, handle: edge.target.handle },
|
|
1461
1840
|
durationMs: duration,
|
|
1462
1841
|
});
|
|
1463
|
-
edge.stats.lastEndAt = Date.now();
|
|
1464
|
-
edge.stats.lastError = undefined;
|
|
1465
1842
|
}
|
|
1466
1843
|
/**
|
|
1467
1844
|
* Handle edge conversion error
|
|
1468
1845
|
*/
|
|
1469
1846
|
handleEdgeConversionError(edge, err) {
|
|
1470
|
-
edge.
|
|
1471
|
-
|
|
1847
|
+
this.graph.updateEdgeStats(edge.id, {
|
|
1848
|
+
inFlight: false,
|
|
1849
|
+
lastError: err,
|
|
1850
|
+
});
|
|
1472
1851
|
this.eventEmitter.emit("error", {
|
|
1473
1852
|
kind: "edge-convert",
|
|
1474
1853
|
edgeId: edge.id,
|
|
@@ -1501,10 +1880,11 @@ class EdgePropagator {
|
|
|
1501
1880
|
? new Set(Object.keys(resolved.outputs))
|
|
1502
1881
|
: new Set();
|
|
1503
1882
|
// Use node's activeRunContexts to propagate to new nodes that were added
|
|
1883
|
+
const activeRunContextIds = this.graph.getNodeRunContextIds(nodeId);
|
|
1504
1884
|
for (const [handle, value] of Object.entries(node.outputs)) {
|
|
1505
1885
|
// Only re-emit if this handle is still valid
|
|
1506
1886
|
if (validOutputHandles.has(handle)) {
|
|
1507
|
-
this.propagate(nodeId, handle, value,
|
|
1887
|
+
this.propagate(nodeId, handle, value, activeRunContextIds.size > 0 ? activeRunContextIds : undefined);
|
|
1508
1888
|
}
|
|
1509
1889
|
}
|
|
1510
1890
|
}
|
|
@@ -1565,10 +1945,8 @@ class NodeExecutor {
|
|
|
1565
1945
|
// Start with real inputs only (no defaults)
|
|
1566
1946
|
const effective = { ...node.inputs };
|
|
1567
1947
|
// Build set of inbound handles (wired inputs)
|
|
1568
|
-
const
|
|
1569
|
-
const inbound = new Set(
|
|
1570
|
-
.filter((e) => e.target.nodeId === nodeId)
|
|
1571
|
-
.map((e) => e.target.handle));
|
|
1948
|
+
const inboundEdges = this.graph.getInboundEdges(nodeId);
|
|
1949
|
+
const inbound = new Set(inboundEdges.map((e) => e.target.handle));
|
|
1572
1950
|
// Apply defaults only for:
|
|
1573
1951
|
// 1. Unbound handles that have no explicit value
|
|
1574
1952
|
// 2. Static handles (not dynamically resolved)
|
|
@@ -1596,7 +1974,9 @@ class NodeExecutor {
|
|
|
1596
1974
|
});
|
|
1597
1975
|
const reportProgress = options?.reportProgress ??
|
|
1598
1976
|
((p) => {
|
|
1599
|
-
|
|
1977
|
+
this.graph.updateNodeStats(nodeId, {
|
|
1978
|
+
progress: Math.max(0, Math.min(1, Number(p) || 0)),
|
|
1979
|
+
});
|
|
1600
1980
|
});
|
|
1601
1981
|
// Create log function that respects node's logLevel
|
|
1602
1982
|
const log = (level, message, context) => {
|
|
@@ -1630,7 +2010,7 @@ class NodeExecutor {
|
|
|
1630
2010
|
return {
|
|
1631
2011
|
nodeId,
|
|
1632
2012
|
state: node.state,
|
|
1633
|
-
setState: (next) =>
|
|
2013
|
+
setState: (next) => this.graph.updateNodeState(nodeId, next),
|
|
1634
2014
|
emit: emitHandler,
|
|
1635
2015
|
invalidateDownstream: () => {
|
|
1636
2016
|
this.edgePropagator.invalidateDownstream(nodeId);
|
|
@@ -1690,10 +2070,12 @@ class NodeExecutor {
|
|
|
1690
2070
|
if (this.runtime.isPaused())
|
|
1691
2071
|
return;
|
|
1692
2072
|
// Attach run-context IDs if provided
|
|
1693
|
-
|
|
2073
|
+
if (runContextIds) {
|
|
2074
|
+
this.graph.addNodeRunContextIds(nodeId, runContextIds);
|
|
2075
|
+
}
|
|
1694
2076
|
// Handle debouncing
|
|
1695
2077
|
const now = Date.now();
|
|
1696
|
-
if (this.shouldDebounce(node, now)) {
|
|
2078
|
+
if (this.shouldDebounce(nodeId, node, now)) {
|
|
1697
2079
|
this.handleDebouncedSchedule(node, nodeId, now);
|
|
1698
2080
|
return;
|
|
1699
2081
|
}
|
|
@@ -1702,44 +2084,35 @@ class NodeExecutor {
|
|
|
1702
2084
|
// Route to appropriate concurrency handler
|
|
1703
2085
|
this.routeToConcurrencyHandler(node, nodeId, executionPlan);
|
|
1704
2086
|
}
|
|
1705
|
-
/**
|
|
1706
|
-
* Attach run-context IDs to the node
|
|
1707
|
-
*/
|
|
1708
|
-
attachRunContexts(node, runContextIds) {
|
|
1709
|
-
if (!runContextIds)
|
|
1710
|
-
return;
|
|
1711
|
-
node.activeRunContextIds = new Set([
|
|
1712
|
-
...node.activeRunContextIds,
|
|
1713
|
-
...runContextIds,
|
|
1714
|
-
]);
|
|
1715
|
-
}
|
|
1716
2087
|
/**
|
|
1717
2088
|
* Check if execution should be debounced
|
|
1718
2089
|
*/
|
|
1719
|
-
shouldDebounce(node, now) {
|
|
2090
|
+
shouldDebounce(nodeId, node, now) {
|
|
1720
2091
|
const policy = node.policy ?? {};
|
|
2092
|
+
const lastScheduledAt = node.lastScheduledAt;
|
|
1721
2093
|
return !!(policy.debounceMs &&
|
|
1722
|
-
|
|
1723
|
-
now -
|
|
2094
|
+
lastScheduledAt &&
|
|
2095
|
+
now - lastScheduledAt < policy.debounceMs);
|
|
1724
2096
|
}
|
|
1725
2097
|
/**
|
|
1726
2098
|
* Handle debounced scheduling by replacing the latest queued item
|
|
1727
2099
|
*/
|
|
1728
2100
|
handleDebouncedSchedule(node, nodeId, now) {
|
|
1729
2101
|
const effectiveInputs = this.getEffectiveInputs(nodeId);
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
2102
|
+
const runSeq = this.graph.incrementNodeRunSeq(nodeId);
|
|
2103
|
+
const rid = `${nodeId}:${runSeq}:${now}`;
|
|
2104
|
+
this.graph.replaceNodeQueue(nodeId, [
|
|
2105
|
+
{ runId: rid, inputs: effectiveInputs },
|
|
2106
|
+
]);
|
|
1734
2107
|
}
|
|
1735
2108
|
/**
|
|
1736
2109
|
* Prepare execution plan with all necessary information
|
|
1737
2110
|
*/
|
|
1738
2111
|
prepareExecutionPlan(node, nodeId, runContextIds, now) {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
const runId = `${nodeId}:${
|
|
1742
|
-
|
|
2112
|
+
this.graph.setNodeLastScheduledAt(nodeId, now);
|
|
2113
|
+
const runSeq = this.graph.incrementNodeRunSeq(nodeId);
|
|
2114
|
+
const runId = `${nodeId}:${runSeq}:${now}`;
|
|
2115
|
+
this.graph.setNodeLatestRunId(nodeId, runId);
|
|
1743
2116
|
const effectiveInputs = this.getEffectiveInputs(nodeId);
|
|
1744
2117
|
// Take a shallow snapshot of the current policy for this run
|
|
1745
2118
|
const policySnapshot = node.policy ? { ...node.policy } : undefined;
|
|
@@ -1786,9 +2159,16 @@ class NodeExecutor {
|
|
|
1786
2159
|
*/
|
|
1787
2160
|
handleQueueMode(node, nodeId, plan) {
|
|
1788
2161
|
const maxQ = plan.policy?.maxQueue ?? 8;
|
|
1789
|
-
|
|
1790
|
-
if (
|
|
1791
|
-
|
|
2162
|
+
const currentNode = this.graph.getNode(nodeId);
|
|
2163
|
+
if (!currentNode)
|
|
2164
|
+
return;
|
|
2165
|
+
this.graph.addToNodeQueue(nodeId, {
|
|
2166
|
+
runId: plan.runId,
|
|
2167
|
+
inputs: plan.effectiveInputs,
|
|
2168
|
+
});
|
|
2169
|
+
if (currentNode.queue.length > maxQ) {
|
|
2170
|
+
this.graph.shiftNodeQueue(nodeId);
|
|
2171
|
+
}
|
|
1792
2172
|
this.processQueue(node, nodeId);
|
|
1793
2173
|
}
|
|
1794
2174
|
/**
|
|
@@ -1796,17 +2176,21 @@ class NodeExecutor {
|
|
|
1796
2176
|
*/
|
|
1797
2177
|
processQueue(node, nodeId) {
|
|
1798
2178
|
const processNext = () => {
|
|
2179
|
+
const node = this.graph.getNode(nodeId);
|
|
2180
|
+
if (!node)
|
|
2181
|
+
return;
|
|
1799
2182
|
if (node.activeControllers.size > 0)
|
|
1800
2183
|
return;
|
|
1801
|
-
const next =
|
|
2184
|
+
const next = this.graph.shiftNodeQueue(nodeId);
|
|
1802
2185
|
if (!next)
|
|
1803
2186
|
return;
|
|
1804
|
-
|
|
2187
|
+
this.graph.setNodeLatestRunId(nodeId, next.runId);
|
|
1805
2188
|
const policySnapshot = node.policy ? { ...node.policy } : undefined;
|
|
2189
|
+
const activeRunContextIds = this.graph.getNodeRunContextIds(nodeId);
|
|
1806
2190
|
const plan = {
|
|
1807
2191
|
runId: next.runId,
|
|
1808
2192
|
effectiveInputs: next.inputs,
|
|
1809
|
-
runContextIdsForRun:
|
|
2193
|
+
runContextIdsForRun: activeRunContextIds.size > 0 ? activeRunContextIds : undefined,
|
|
1810
2194
|
timestamp: Date.now(),
|
|
1811
2195
|
policy: policySnapshot,
|
|
1812
2196
|
};
|
|
@@ -1823,9 +2207,9 @@ class NodeExecutor {
|
|
|
1823
2207
|
// Track run-contexts
|
|
1824
2208
|
this.trackRunContextStart(nodeId, plan.runContextIdsForRun);
|
|
1825
2209
|
// Setup execution controller
|
|
1826
|
-
const controller = this.createExecutionController(node, plan.runId);
|
|
2210
|
+
const controller = this.createExecutionController(nodeId, node, plan.runId);
|
|
1827
2211
|
// Handle concurrency mode
|
|
1828
|
-
this.applyConcurrencyMode(node, controller, plan);
|
|
2212
|
+
this.applyConcurrencyMode(nodeId, node, controller, plan);
|
|
1829
2213
|
// Setup timeout if needed
|
|
1830
2214
|
const timeoutId = this.setupTimeout(node, controller, plan);
|
|
1831
2215
|
// Create execution context
|
|
@@ -1846,23 +2230,26 @@ class NodeExecutor {
|
|
|
1846
2230
|
/**
|
|
1847
2231
|
* Create execution controller and update node stats
|
|
1848
2232
|
*/
|
|
1849
|
-
createExecutionController(node, runId) {
|
|
2233
|
+
createExecutionController(nodeId, node, runId) {
|
|
1850
2234
|
const controller = new AbortController();
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
2235
|
+
const now = Date.now();
|
|
2236
|
+
this.graph.updateNodeStats(nodeId, {
|
|
2237
|
+
runs: node.stats.runs + 1,
|
|
2238
|
+
active: node.stats.active + 1,
|
|
2239
|
+
lastStartAt: now,
|
|
2240
|
+
progress: 0,
|
|
2241
|
+
});
|
|
2242
|
+
this.graph.addNodeController(nodeId, controller, runId);
|
|
1857
2243
|
return controller;
|
|
1858
2244
|
}
|
|
1859
2245
|
/**
|
|
1860
2246
|
* Apply concurrency mode (switch mode aborts other controllers)
|
|
1861
2247
|
*/
|
|
1862
|
-
applyConcurrencyMode(node, controller, plan) {
|
|
2248
|
+
applyConcurrencyMode(nodeId, node, controller, plan) {
|
|
1863
2249
|
const mode = plan.policy?.asyncConcurrency ?? "switch";
|
|
1864
2250
|
if (mode === "switch") {
|
|
1865
|
-
|
|
2251
|
+
const controllers = this.graph.getNodeControllers(nodeId);
|
|
2252
|
+
for (const c of controllers) {
|
|
1866
2253
|
if (c !== controller)
|
|
1867
2254
|
c.abort("switch");
|
|
1868
2255
|
}
|
|
@@ -1885,6 +2272,9 @@ class NodeExecutor {
|
|
|
1885
2272
|
const policy = plan.policy ?? {};
|
|
1886
2273
|
return {
|
|
1887
2274
|
emitHandler: (handle, value) => {
|
|
2275
|
+
const node = this.graph.getNode(nodeId);
|
|
2276
|
+
if (!node)
|
|
2277
|
+
return;
|
|
1888
2278
|
const m = policy.asyncConcurrency ?? "switch";
|
|
1889
2279
|
// Drop emits from runs that were explicitly cancelled due to a
|
|
1890
2280
|
// snapshot/undo/redo operation, regardless of asyncConcurrency.
|
|
@@ -1895,13 +2285,17 @@ class NodeExecutor {
|
|
|
1895
2285
|
this.edgePropagator.propagate(nodeId, handle, value, plan.runContextIdsForRun);
|
|
1896
2286
|
},
|
|
1897
2287
|
reportProgress: (p) => {
|
|
1898
|
-
|
|
2288
|
+
const progress = Math.max(0, Math.min(1, Number(p) || 0));
|
|
2289
|
+
this.graph.updateNodeStats(nodeId, { progress });
|
|
2290
|
+
const node = this.graph.getNode(nodeId);
|
|
2291
|
+
if (!node)
|
|
2292
|
+
return;
|
|
1899
2293
|
this.eventEmitter.emit("stats", {
|
|
1900
2294
|
kind: "node-progress",
|
|
1901
2295
|
nodeId,
|
|
1902
2296
|
typeId: node.typeId,
|
|
1903
2297
|
runId: plan.runId,
|
|
1904
|
-
progress
|
|
2298
|
+
progress,
|
|
1905
2299
|
});
|
|
1906
2300
|
},
|
|
1907
2301
|
};
|
|
@@ -1940,7 +2334,7 @@ class NodeExecutor {
|
|
|
1940
2334
|
}
|
|
1941
2335
|
}
|
|
1942
2336
|
hadError = true;
|
|
1943
|
-
|
|
2337
|
+
this.graph.updateNodeStats(nodeId, { lastError: err });
|
|
1944
2338
|
const retry = plan.policy?.retry;
|
|
1945
2339
|
if (retry && attempt < (retry.attempts ?? 0)) {
|
|
1946
2340
|
const delay = retry.backoffMs ? retry.backoffMs(attempt) : 0;
|
|
@@ -1976,38 +2370,53 @@ class NodeExecutor {
|
|
|
1976
2370
|
}
|
|
1977
2371
|
if (timeoutId)
|
|
1978
2372
|
clearTimeout(timeoutId);
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2373
|
+
this.graph.removeNodeController(nodeId, controller);
|
|
2374
|
+
const currentNode = this.graph.getNode(nodeId);
|
|
2375
|
+
if (!currentNode)
|
|
2376
|
+
return;
|
|
2377
|
+
const controllers = this.graph.getNodeControllers(nodeId);
|
|
2378
|
+
const lastEndAt = Date.now();
|
|
2379
|
+
const lastDurationMs = currentNode.stats.lastStartAt && lastEndAt
|
|
2380
|
+
? lastEndAt - currentNode.stats.lastStartAt
|
|
2381
|
+
: undefined;
|
|
2382
|
+
this.graph.updateNodeStats(nodeId, {
|
|
2383
|
+
active: Math.max(0, controllers.size),
|
|
2384
|
+
lastEndAt,
|
|
2385
|
+
lastDurationMs,
|
|
2386
|
+
lastError: hadError ? currentNode.stats.lastError : undefined,
|
|
2387
|
+
});
|
|
1989
2388
|
// Track successful completion time (for detecting stale inputs)
|
|
1990
2389
|
const isCancelled = controller.signal.aborted &&
|
|
1991
2390
|
(controller.signal.reason === "snapshot" ||
|
|
1992
2391
|
controller.signal.reason === "node-deleted" ||
|
|
1993
2392
|
controller.signal.reason === "user-cancelled");
|
|
1994
2393
|
if (!hadError && !isCancelled) {
|
|
1995
|
-
|
|
2394
|
+
this.graph.setNodeLastSuccessAt(nodeId, Date.now());
|
|
1996
2395
|
}
|
|
1997
2396
|
// Only emit node-done if not cancelled (cancellation events emitted separately)
|
|
1998
2397
|
if (!isCancelled) {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2398
|
+
if (currentNode) {
|
|
2399
|
+
this.eventEmitter.emit("stats", {
|
|
2400
|
+
kind: "node-done",
|
|
2401
|
+
nodeId,
|
|
2402
|
+
typeId: currentNode.typeId,
|
|
2403
|
+
runId: plan.runId,
|
|
2404
|
+
durationMs: currentNode.stats.lastDurationMs,
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
if (currentNode) {
|
|
2409
|
+
ctx.log("debug", "node-done", {
|
|
2410
|
+
durationMs: currentNode.stats.lastDurationMs,
|
|
2411
|
+
hadError,
|
|
2412
|
+
inputs: currentNode.inputs
|
|
2413
|
+
? structuredClone(currentNode.inputs)
|
|
2414
|
+
: undefined,
|
|
2415
|
+
outputs: currentNode.outputs
|
|
2416
|
+
? structuredClone(currentNode.outputs)
|
|
2417
|
+
: undefined,
|
|
2005
2418
|
});
|
|
2006
2419
|
}
|
|
2007
|
-
ctx.log("debug", "node-done", {
|
|
2008
|
-
durationMs: node.stats.lastDurationMs,
|
|
2009
|
-
hadError,
|
|
2010
|
-
});
|
|
2011
2420
|
if (onDone)
|
|
2012
2421
|
onDone();
|
|
2013
2422
|
}
|
|
@@ -2015,22 +2424,24 @@ class NodeExecutor {
|
|
|
2015
2424
|
* Cancel all active runs for a node
|
|
2016
2425
|
*/
|
|
2017
2426
|
cancelNodeActiveRuns(node, reason) {
|
|
2018
|
-
|
|
2019
|
-
|
|
2427
|
+
const nodeId = node.nodeId;
|
|
2428
|
+
const controllers = this.graph.getNodeControllers(nodeId);
|
|
2429
|
+
for (const controller of controllers) {
|
|
2430
|
+
const currentNode = this.graph.getNode(nodeId);
|
|
2431
|
+
if (!currentNode)
|
|
2432
|
+
continue;
|
|
2433
|
+
const runId = currentNode.controllerRunIds.get(controller);
|
|
2020
2434
|
if (runId) {
|
|
2021
2435
|
// Track cancelled runIds for snapshot and user-cancelled operations
|
|
2022
2436
|
// (to drop emits from cancelled runs)
|
|
2023
2437
|
if (reason === "snapshot" || reason === "user-cancelled") {
|
|
2024
|
-
|
|
2025
|
-
node.snapshotCancelledRunIds = new Set();
|
|
2026
|
-
}
|
|
2027
|
-
node.snapshotCancelledRunIds.add(runId);
|
|
2438
|
+
this.graph.addSnapshotCancelledRunId(nodeId, runId);
|
|
2028
2439
|
}
|
|
2029
2440
|
// Emit cancellation event
|
|
2030
2441
|
this.eventEmitter.emit("stats", {
|
|
2031
2442
|
kind: "node-done",
|
|
2032
|
-
nodeId:
|
|
2033
|
-
typeId:
|
|
2443
|
+
nodeId: currentNode.nodeId,
|
|
2444
|
+
typeId: currentNode.typeId,
|
|
2034
2445
|
runId,
|
|
2035
2446
|
cancelled: true,
|
|
2036
2447
|
});
|
|
@@ -2042,10 +2453,9 @@ class NodeExecutor {
|
|
|
2042
2453
|
// ignore abort errors
|
|
2043
2454
|
}
|
|
2044
2455
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
node.queue = [];
|
|
2456
|
+
this.graph.clearNodeControllers(nodeId);
|
|
2457
|
+
this.graph.updateNodeStats(nodeId, { active: 0 });
|
|
2458
|
+
this.graph.clearNodeQueue(nodeId);
|
|
2049
2459
|
}
|
|
2050
2460
|
/**
|
|
2051
2461
|
* Cancel runs for multiple nodes.
|
|
@@ -2057,14 +2467,13 @@ class NodeExecutor {
|
|
|
2057
2467
|
const toCancel = new Set(nodeIds);
|
|
2058
2468
|
const visited = new Set();
|
|
2059
2469
|
const queue = [...nodeIds];
|
|
2060
|
-
const edges = this.graph.getEdges();
|
|
2061
2470
|
// Collect all downstream nodes to cancel
|
|
2062
2471
|
for (let i = 0; i < queue.length; i++) {
|
|
2063
2472
|
const nodeId = queue[i];
|
|
2064
2473
|
if (visited.has(nodeId))
|
|
2065
2474
|
continue;
|
|
2066
2475
|
visited.add(nodeId);
|
|
2067
|
-
|
|
2476
|
+
this.graph.forEachEdge((edge) => {
|
|
2068
2477
|
if (edge.source.nodeId === nodeId) {
|
|
2069
2478
|
const targetId = edge.target.nodeId;
|
|
2070
2479
|
if (!visited.has(targetId)) {
|
|
@@ -2072,7 +2481,7 @@ class NodeExecutor {
|
|
|
2072
2481
|
queue.push(targetId);
|
|
2073
2482
|
}
|
|
2074
2483
|
}
|
|
2075
|
-
}
|
|
2484
|
+
});
|
|
2076
2485
|
}
|
|
2077
2486
|
// Cancel runs for all affected nodes
|
|
2078
2487
|
for (const nodeId of toCancel) {
|
|
@@ -2080,10 +2489,10 @@ class NodeExecutor {
|
|
|
2080
2489
|
if (!node)
|
|
2081
2490
|
continue;
|
|
2082
2491
|
this.cancelNodeActiveRuns(node, reason);
|
|
2083
|
-
|
|
2492
|
+
const runSeq = this.graph.incrementNodeRunSeq(nodeId);
|
|
2084
2493
|
const now = Date.now();
|
|
2085
2494
|
const suffix = reason === "snapshot" ? "snapshot" : "cancelled";
|
|
2086
|
-
|
|
2495
|
+
this.graph.setNodeLatestRunId(nodeId, `${nodeId}:${runSeq}:${now}:${suffix}`);
|
|
2087
2496
|
}
|
|
2088
2497
|
// Cancel nodes in run-contexts (exclude them from active run-contexts)
|
|
2089
2498
|
for (const nodeId of toCancel) {
|
|
@@ -2168,7 +2577,11 @@ class GraphRuntime {
|
|
|
2168
2577
|
gr.graph.setNode(n.nodeId, rn);
|
|
2169
2578
|
}
|
|
2170
2579
|
// Instantiate edges
|
|
2171
|
-
const
|
|
2580
|
+
const resolvedByNode = new Map();
|
|
2581
|
+
gr.graph.forEachResolvedHandles((handles, nodeId) => {
|
|
2582
|
+
resolvedByNode.set(nodeId, handles);
|
|
2583
|
+
});
|
|
2584
|
+
const edges = buildEdges(def, registry, resolvedByNode);
|
|
2172
2585
|
gr.graph.setEdges(edges);
|
|
2173
2586
|
// Schedule async recompute only for nodes that indicated Promise-based resolveHandles
|
|
2174
2587
|
for (const nodeId of initial.pending) {
|
|
@@ -2184,10 +2597,14 @@ class GraphRuntime {
|
|
|
2184
2597
|
if (!node)
|
|
2185
2598
|
throw new Error(`Node not found: ${nodeId}`);
|
|
2186
2599
|
let anyChanged = false;
|
|
2187
|
-
const edges = this.graph.getEdges();
|
|
2188
2600
|
const registry = this.graph.getRegistry();
|
|
2189
2601
|
for (const [handle, value] of Object.entries(inputs)) {
|
|
2190
|
-
|
|
2602
|
+
let hasInbound = false;
|
|
2603
|
+
this.graph.forEachEdge((e) => {
|
|
2604
|
+
if (e.target.nodeId === nodeId && e.target.handle === handle) {
|
|
2605
|
+
hasInbound = true;
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2191
2608
|
if (hasInbound)
|
|
2192
2609
|
continue;
|
|
2193
2610
|
// Validate input value against declared type
|
|
@@ -2221,12 +2638,7 @@ class GraphRuntime {
|
|
|
2221
2638
|
const prev = node.inputs[handle];
|
|
2222
2639
|
const same = valuesEqual(prev, value);
|
|
2223
2640
|
if (!same) {
|
|
2224
|
-
|
|
2225
|
-
delete node.inputs[handle];
|
|
2226
|
-
}
|
|
2227
|
-
else {
|
|
2228
|
-
node.inputs[handle] = value;
|
|
2229
|
-
}
|
|
2641
|
+
this.graph.updateNodeInput(nodeId, handle, value);
|
|
2230
2642
|
// Emit value event for input updates
|
|
2231
2643
|
this.eventEmitter.emit("value", { nodeId, handle, value, io: "input" });
|
|
2232
2644
|
anyChanged = true;
|
|
@@ -2248,7 +2660,7 @@ class GraphRuntime {
|
|
|
2248
2660
|
return node?.outputs[output];
|
|
2249
2661
|
}
|
|
2250
2662
|
launch(invalidate = false) {
|
|
2251
|
-
|
|
2663
|
+
this.graph.forEachNode((node) => {
|
|
2252
2664
|
const effectiveInputs = this.nodeExecutor.getEffectiveInputs(node.nodeId);
|
|
2253
2665
|
const ctrl = new AbortController();
|
|
2254
2666
|
const execCtx = this.nodeExecutor.createExecutionContext(node.nodeId, node, effectiveInputs, `${node.nodeId}:init`, ctrl.signal);
|
|
@@ -2258,9 +2670,9 @@ class GraphRuntime {
|
|
|
2258
2670
|
execCtx.log("debug", "prepare-done");
|
|
2259
2671
|
}
|
|
2260
2672
|
node.runtime.onActivated?.();
|
|
2261
|
-
}
|
|
2673
|
+
});
|
|
2262
2674
|
if (this.runMode === "auto" && invalidate) {
|
|
2263
|
-
for (const nodeId of this.graph.
|
|
2675
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2264
2676
|
if (this.graph.allInboundHaveValue(nodeId))
|
|
2265
2677
|
this.execute(nodeId);
|
|
2266
2678
|
}
|
|
@@ -2295,7 +2707,7 @@ class GraphRuntime {
|
|
|
2295
2707
|
this.nodeExecutor.cancelNodeRuns(nodeIds);
|
|
2296
2708
|
}
|
|
2297
2709
|
getNodeIds() {
|
|
2298
|
-
return
|
|
2710
|
+
return this.graph.getNodeIds();
|
|
2299
2711
|
}
|
|
2300
2712
|
getNodeData(nodeId) {
|
|
2301
2713
|
const node = this.graph.getNode(nodeId);
|
|
@@ -2316,26 +2728,30 @@ class GraphRuntime {
|
|
|
2316
2728
|
this.environment = { ...env };
|
|
2317
2729
|
this.handleResolver.setEnvironment(this.environment);
|
|
2318
2730
|
this.nodeExecutor.setEnvironment(this.environment);
|
|
2319
|
-
for (const nodeId of this.graph.
|
|
2731
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2320
2732
|
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
2321
2733
|
}
|
|
2322
2734
|
}
|
|
2323
2735
|
getGraphDef() {
|
|
2324
|
-
const nodes =
|
|
2736
|
+
const nodes = [];
|
|
2737
|
+
this.graph.forEachNode((n) => {
|
|
2325
2738
|
const resolved = this.graph.getResolvedHandles(n.nodeId);
|
|
2326
|
-
|
|
2739
|
+
nodes.push({
|
|
2327
2740
|
nodeId: n.nodeId,
|
|
2328
2741
|
typeId: n.typeId,
|
|
2329
2742
|
params: n.params ? { ...n.params } : undefined,
|
|
2330
2743
|
resolvedHandles: resolved ? { ...resolved } : undefined,
|
|
2331
|
-
};
|
|
2744
|
+
});
|
|
2745
|
+
});
|
|
2746
|
+
const edges = [];
|
|
2747
|
+
this.graph.forEachEdge((e) => {
|
|
2748
|
+
edges.push({
|
|
2749
|
+
id: e.id,
|
|
2750
|
+
source: { nodeId: e.source.nodeId, handle: e.source.handle },
|
|
2751
|
+
target: { nodeId: e.target.nodeId, handle: e.target.handle },
|
|
2752
|
+
typeId: e.typeId,
|
|
2753
|
+
});
|
|
2332
2754
|
});
|
|
2333
|
-
const edges = this.graph.getEdges().map((e) => ({
|
|
2334
|
-
id: e.id,
|
|
2335
|
-
source: { nodeId: e.source.nodeId, handle: e.source.handle },
|
|
2336
|
-
target: { nodeId: e.target.nodeId, handle: e.target.handle },
|
|
2337
|
-
typeId: e.typeId,
|
|
2338
|
-
}));
|
|
2339
2755
|
return { nodes, edges };
|
|
2340
2756
|
}
|
|
2341
2757
|
async whenIdle() {
|
|
@@ -2354,13 +2770,13 @@ class GraphRuntime {
|
|
|
2354
2770
|
});
|
|
2355
2771
|
}
|
|
2356
2772
|
const isIdle = () => {
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
}
|
|
2363
|
-
return
|
|
2773
|
+
let idle = true;
|
|
2774
|
+
this.graph.forEachNode((n) => {
|
|
2775
|
+
if (n.activeControllers.size > 0 || n.queue.length > 0) {
|
|
2776
|
+
idle = false;
|
|
2777
|
+
}
|
|
2778
|
+
});
|
|
2779
|
+
return idle;
|
|
2364
2780
|
};
|
|
2365
2781
|
if (isIdle())
|
|
2366
2782
|
return;
|
|
@@ -2380,7 +2796,7 @@ class GraphRuntime {
|
|
|
2380
2796
|
return;
|
|
2381
2797
|
return new Promise((resolve) => {
|
|
2382
2798
|
const id = this.runContextManager.createRunContext(startNodeId, resolve, options);
|
|
2383
|
-
|
|
2799
|
+
this.graph.addNodeRunContextId(startNodeId, id);
|
|
2384
2800
|
this.execute(startNodeId, new Set([id]));
|
|
2385
2801
|
});
|
|
2386
2802
|
}
|
|
@@ -2414,38 +2830,38 @@ class GraphRuntime {
|
|
|
2414
2830
|
try {
|
|
2415
2831
|
const ins = payload?.inputs || {};
|
|
2416
2832
|
for (const [nodeId, map] of Object.entries(ins)) {
|
|
2417
|
-
|
|
2418
|
-
if (!node)
|
|
2833
|
+
if (!this.graph.hasNode(nodeId))
|
|
2419
2834
|
continue;
|
|
2420
2835
|
for (const [h, v] of Object.entries(map || {})) {
|
|
2421
|
-
|
|
2836
|
+
const clonedValue = structuredClone(v);
|
|
2837
|
+
this.graph.updateNodeInput(nodeId, h, clonedValue);
|
|
2422
2838
|
this.eventEmitter.emit("value", {
|
|
2423
2839
|
nodeId,
|
|
2424
2840
|
handle: h,
|
|
2425
|
-
value:
|
|
2841
|
+
value: clonedValue,
|
|
2426
2842
|
io: "input",
|
|
2427
|
-
runtimeTypeId: getTypedOutputTypeId(
|
|
2843
|
+
runtimeTypeId: getTypedOutputTypeId(clonedValue),
|
|
2428
2844
|
});
|
|
2429
2845
|
}
|
|
2430
2846
|
}
|
|
2431
2847
|
const outs = payload?.outputs || {};
|
|
2432
2848
|
for (const [nodeId, map] of Object.entries(outs)) {
|
|
2433
|
-
|
|
2434
|
-
if (!node)
|
|
2849
|
+
if (!this.graph.hasNode(nodeId))
|
|
2435
2850
|
continue;
|
|
2436
2851
|
for (const [h, v] of Object.entries(map || {})) {
|
|
2437
|
-
|
|
2852
|
+
const clonedValue = structuredClone(v);
|
|
2853
|
+
this.graph.updateNodeOutput(nodeId, h, clonedValue);
|
|
2438
2854
|
this.eventEmitter.emit("value", {
|
|
2439
2855
|
nodeId,
|
|
2440
2856
|
handle: h,
|
|
2441
|
-
value:
|
|
2857
|
+
value: clonedValue,
|
|
2442
2858
|
io: "output",
|
|
2443
|
-
runtimeTypeId: getTypedOutputTypeId(
|
|
2859
|
+
runtimeTypeId: getTypedOutputTypeId(clonedValue),
|
|
2444
2860
|
});
|
|
2445
2861
|
}
|
|
2446
2862
|
}
|
|
2447
2863
|
if (opts?.invalidate) {
|
|
2448
|
-
for (const nodeId of this.graph.
|
|
2864
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2449
2865
|
this.invalidateDownstream(nodeId);
|
|
2450
2866
|
}
|
|
2451
2867
|
}
|
|
@@ -2458,17 +2874,19 @@ class GraphRuntime {
|
|
|
2458
2874
|
{
|
|
2459
2875
|
// Delete nodes that are no longer in the definition
|
|
2460
2876
|
const afterIds = new Set(def.nodes.map((n) => n.nodeId));
|
|
2461
|
-
const beforeIds = new Set(this.graph.
|
|
2877
|
+
const beforeIds = new Set(this.graph.getNodeIds());
|
|
2462
2878
|
for (const nodeId of Array.from(beforeIds)) {
|
|
2463
2879
|
if (!afterIds.has(nodeId)) {
|
|
2464
2880
|
const node = this.graph.getNode(nodeId);
|
|
2881
|
+
if (!node)
|
|
2882
|
+
continue;
|
|
2465
2883
|
this.nodeExecutor.cancelNodeActiveRuns(node, "node-deleted");
|
|
2466
2884
|
this.runContextManager.cancelNodeInRunContexts(nodeId, true);
|
|
2467
2885
|
node.runtime.onDeactivated?.();
|
|
2468
2886
|
node.runtime.dispose?.();
|
|
2469
2887
|
node.lifecycle?.dispose?.({
|
|
2470
2888
|
state: node.state,
|
|
2471
|
-
setState: (next) =>
|
|
2889
|
+
setState: (next) => this.graph.updateNodeState(node.nodeId, next),
|
|
2472
2890
|
});
|
|
2473
2891
|
this.graph.deleteNode(nodeId);
|
|
2474
2892
|
this.edgePropagator.clearArrayBuckets(nodeId);
|
|
@@ -2532,43 +2950,43 @@ class GraphRuntime {
|
|
|
2532
2950
|
newNode.runtime.onActivated?.();
|
|
2533
2951
|
}
|
|
2534
2952
|
else {
|
|
2535
|
-
|
|
2953
|
+
this.graph.updateNodeParams(n.nodeId, n.params);
|
|
2536
2954
|
// Re-merge policy when params change (params.policy can override descriptor/category policy)
|
|
2537
2955
|
const desc = registry.nodes.get(existing.typeId);
|
|
2538
2956
|
const cat = registry.categories.get(desc?.categoryId ?? "");
|
|
2539
|
-
|
|
2957
|
+
this.graph.updateNodePolicy(n.nodeId, {
|
|
2540
2958
|
...cat?.policy,
|
|
2541
2959
|
...desc?.policy,
|
|
2542
2960
|
...n.params?.policy,
|
|
2543
|
-
};
|
|
2961
|
+
});
|
|
2962
|
+
// Initialize stats if missing
|
|
2544
2963
|
if (!existing.stats) {
|
|
2545
|
-
|
|
2964
|
+
this.graph.updateNodeStats(n.nodeId, {
|
|
2546
2965
|
runs: 0,
|
|
2547
2966
|
active: 0,
|
|
2548
2967
|
queued: 0,
|
|
2549
2968
|
progress: 0,
|
|
2550
|
-
};
|
|
2969
|
+
});
|
|
2551
2970
|
}
|
|
2552
2971
|
}
|
|
2553
2972
|
}
|
|
2554
2973
|
}
|
|
2555
2974
|
{
|
|
2556
|
-
const beforeEdges = this.graph.getEdges();
|
|
2557
2975
|
const beforeInbound = new Map();
|
|
2558
|
-
|
|
2976
|
+
const beforeOutTargets = new Map();
|
|
2977
|
+
this.graph.forEachEdge((e) => {
|
|
2978
|
+
// Build beforeInbound map
|
|
2559
2979
|
const set = beforeInbound.get(e.target.nodeId) ?? new Set();
|
|
2560
2980
|
set.add(e.target.handle);
|
|
2561
2981
|
beforeInbound.set(e.target.nodeId, set);
|
|
2562
|
-
|
|
2563
|
-
const beforeOutTargets = new Map();
|
|
2564
|
-
for (const e of beforeEdges) {
|
|
2982
|
+
// Build beforeOutTargets map
|
|
2565
2983
|
const tmap = beforeOutTargets.get(e.source.nodeId) ??
|
|
2566
2984
|
new Map();
|
|
2567
2985
|
const tset = tmap.get(e.source.handle) ?? new Set();
|
|
2568
2986
|
tset.add(`${e.target.nodeId}.${e.target.handle}`);
|
|
2569
2987
|
tmap.set(e.source.handle, tset);
|
|
2570
2988
|
beforeOutTargets.set(e.source.nodeId, tmap);
|
|
2571
|
-
}
|
|
2989
|
+
});
|
|
2572
2990
|
{
|
|
2573
2991
|
// Update handles and edges
|
|
2574
2992
|
const result = tryHandleResolving(def, registry, this.environment);
|
|
@@ -2583,7 +3001,11 @@ class GraphRuntime {
|
|
|
2583
3001
|
for (const [nodeId, handles] of result.resolved) {
|
|
2584
3002
|
this.graph.setResolvedHandles(nodeId, handles);
|
|
2585
3003
|
}
|
|
2586
|
-
const
|
|
3004
|
+
const resolvedByNode = new Map();
|
|
3005
|
+
this.graph.forEachResolvedHandles((handles, nodeId) => {
|
|
3006
|
+
resolvedByNode.set(nodeId, handles);
|
|
3007
|
+
});
|
|
3008
|
+
const afterEdges = buildEdges(def, registry, resolvedByNode);
|
|
2587
3009
|
this.graph.setEdges(afterEdges);
|
|
2588
3010
|
for (const nodeId of result.pending) {
|
|
2589
3011
|
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
@@ -2598,23 +3020,22 @@ class GraphRuntime {
|
|
|
2598
3020
|
{
|
|
2599
3021
|
// Update inputs and propagate changes
|
|
2600
3022
|
const afterInbound = new Map();
|
|
2601
|
-
|
|
2602
|
-
for (const e of afterEdges) {
|
|
3023
|
+
this.graph.forEachEdge((e) => {
|
|
2603
3024
|
const set = afterInbound.get(e.target.nodeId) ?? new Set();
|
|
2604
3025
|
set.add(e.target.handle);
|
|
2605
3026
|
afterInbound.set(e.target.nodeId, set);
|
|
2606
|
-
}
|
|
3027
|
+
});
|
|
2607
3028
|
// Propagate changes on edges removed
|
|
2608
3029
|
for (const [nodeId, beforeSet] of beforeInbound) {
|
|
2609
3030
|
const currSet = afterInbound.get(nodeId) ?? new Set();
|
|
2610
|
-
|
|
2611
|
-
if (!node)
|
|
3031
|
+
if (!this.graph.hasNode(nodeId))
|
|
2612
3032
|
continue;
|
|
2613
3033
|
let changed = false;
|
|
2614
3034
|
for (const handle of Array.from(beforeSet)) {
|
|
2615
3035
|
if (!currSet.has(handle)) {
|
|
2616
|
-
|
|
2617
|
-
|
|
3036
|
+
const node = this.graph.getNode(nodeId);
|
|
3037
|
+
if (node && handle in node.inputs) {
|
|
3038
|
+
this.graph.deleteNodeInput(nodeId, handle);
|
|
2618
3039
|
changed = true;
|
|
2619
3040
|
}
|
|
2620
3041
|
}
|
|
@@ -2629,14 +3050,14 @@ class GraphRuntime {
|
|
|
2629
3050
|
}
|
|
2630
3051
|
// Propagate changes on edges added
|
|
2631
3052
|
const afterOutTargets = new Map();
|
|
2632
|
-
|
|
3053
|
+
this.graph.forEachEdge((e) => {
|
|
2633
3054
|
const targetMap = afterOutTargets.get(e.source.nodeId) ??
|
|
2634
3055
|
new Map();
|
|
2635
3056
|
const targetSet = targetMap.get(e.source.handle) ?? new Set();
|
|
2636
3057
|
targetSet.add(`${e.target.nodeId}.${e.target.handle}`);
|
|
2637
3058
|
targetMap.set(e.source.handle, targetSet);
|
|
2638
3059
|
afterOutTargets.set(e.source.nodeId, targetMap);
|
|
2639
|
-
}
|
|
3060
|
+
});
|
|
2640
3061
|
const setsEqual = (a, b) => {
|
|
2641
3062
|
if (!a && !b)
|
|
2642
3063
|
return true;
|
|
@@ -2684,14 +3105,14 @@ class GraphRuntime {
|
|
|
2684
3105
|
}
|
|
2685
3106
|
dispose() {
|
|
2686
3107
|
this.runContextManager.resolveAll();
|
|
2687
|
-
|
|
3108
|
+
this.graph.forEachNode((node) => {
|
|
2688
3109
|
node.runtime.onDeactivated?.();
|
|
2689
3110
|
node.runtime.dispose?.();
|
|
2690
3111
|
node.lifecycle?.dispose?.({
|
|
2691
3112
|
state: node.state,
|
|
2692
|
-
setState: (next) =>
|
|
3113
|
+
setState: (next) => this.graph.updateNodeState(node.nodeId, next),
|
|
2693
3114
|
});
|
|
2694
|
-
}
|
|
3115
|
+
});
|
|
2695
3116
|
this.graph.clear();
|
|
2696
3117
|
}
|
|
2697
3118
|
execute(nodeId, runContextIds) {
|