@bian-womp/spark-graph 0.3.13 → 0.3.15
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 -233
- 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 -233
- 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,40 +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
|
-
inputs: node.inputs ? structuredClone(node.inputs) : undefined,
|
|
2011
|
-
outputs: node.outputs ? structuredClone(node.outputs) : undefined,
|
|
2012
|
-
});
|
|
2013
2420
|
if (onDone)
|
|
2014
2421
|
onDone();
|
|
2015
2422
|
}
|
|
@@ -2017,22 +2424,24 @@ class NodeExecutor {
|
|
|
2017
2424
|
* Cancel all active runs for a node
|
|
2018
2425
|
*/
|
|
2019
2426
|
cancelNodeActiveRuns(node, reason) {
|
|
2020
|
-
|
|
2021
|
-
|
|
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);
|
|
2022
2434
|
if (runId) {
|
|
2023
2435
|
// Track cancelled runIds for snapshot and user-cancelled operations
|
|
2024
2436
|
// (to drop emits from cancelled runs)
|
|
2025
2437
|
if (reason === "snapshot" || reason === "user-cancelled") {
|
|
2026
|
-
|
|
2027
|
-
node.snapshotCancelledRunIds = new Set();
|
|
2028
|
-
}
|
|
2029
|
-
node.snapshotCancelledRunIds.add(runId);
|
|
2438
|
+
this.graph.addSnapshotCancelledRunId(nodeId, runId);
|
|
2030
2439
|
}
|
|
2031
2440
|
// Emit cancellation event
|
|
2032
2441
|
this.eventEmitter.emit("stats", {
|
|
2033
2442
|
kind: "node-done",
|
|
2034
|
-
nodeId:
|
|
2035
|
-
typeId:
|
|
2443
|
+
nodeId: currentNode.nodeId,
|
|
2444
|
+
typeId: currentNode.typeId,
|
|
2036
2445
|
runId,
|
|
2037
2446
|
cancelled: true,
|
|
2038
2447
|
});
|
|
@@ -2044,10 +2453,9 @@ class NodeExecutor {
|
|
|
2044
2453
|
// ignore abort errors
|
|
2045
2454
|
}
|
|
2046
2455
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
node.queue = [];
|
|
2456
|
+
this.graph.clearNodeControllers(nodeId);
|
|
2457
|
+
this.graph.updateNodeStats(nodeId, { active: 0 });
|
|
2458
|
+
this.graph.clearNodeQueue(nodeId);
|
|
2051
2459
|
}
|
|
2052
2460
|
/**
|
|
2053
2461
|
* Cancel runs for multiple nodes.
|
|
@@ -2059,14 +2467,13 @@ class NodeExecutor {
|
|
|
2059
2467
|
const toCancel = new Set(nodeIds);
|
|
2060
2468
|
const visited = new Set();
|
|
2061
2469
|
const queue = [...nodeIds];
|
|
2062
|
-
const edges = this.graph.getEdges();
|
|
2063
2470
|
// Collect all downstream nodes to cancel
|
|
2064
2471
|
for (let i = 0; i < queue.length; i++) {
|
|
2065
2472
|
const nodeId = queue[i];
|
|
2066
2473
|
if (visited.has(nodeId))
|
|
2067
2474
|
continue;
|
|
2068
2475
|
visited.add(nodeId);
|
|
2069
|
-
|
|
2476
|
+
this.graph.forEachEdge((edge) => {
|
|
2070
2477
|
if (edge.source.nodeId === nodeId) {
|
|
2071
2478
|
const targetId = edge.target.nodeId;
|
|
2072
2479
|
if (!visited.has(targetId)) {
|
|
@@ -2074,7 +2481,7 @@ class NodeExecutor {
|
|
|
2074
2481
|
queue.push(targetId);
|
|
2075
2482
|
}
|
|
2076
2483
|
}
|
|
2077
|
-
}
|
|
2484
|
+
});
|
|
2078
2485
|
}
|
|
2079
2486
|
// Cancel runs for all affected nodes
|
|
2080
2487
|
for (const nodeId of toCancel) {
|
|
@@ -2082,10 +2489,10 @@ class NodeExecutor {
|
|
|
2082
2489
|
if (!node)
|
|
2083
2490
|
continue;
|
|
2084
2491
|
this.cancelNodeActiveRuns(node, reason);
|
|
2085
|
-
|
|
2492
|
+
const runSeq = this.graph.incrementNodeRunSeq(nodeId);
|
|
2086
2493
|
const now = Date.now();
|
|
2087
2494
|
const suffix = reason === "snapshot" ? "snapshot" : "cancelled";
|
|
2088
|
-
|
|
2495
|
+
this.graph.setNodeLatestRunId(nodeId, `${nodeId}:${runSeq}:${now}:${suffix}`);
|
|
2089
2496
|
}
|
|
2090
2497
|
// Cancel nodes in run-contexts (exclude them from active run-contexts)
|
|
2091
2498
|
for (const nodeId of toCancel) {
|
|
@@ -2170,7 +2577,11 @@ class GraphRuntime {
|
|
|
2170
2577
|
gr.graph.setNode(n.nodeId, rn);
|
|
2171
2578
|
}
|
|
2172
2579
|
// Instantiate edges
|
|
2173
|
-
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);
|
|
2174
2585
|
gr.graph.setEdges(edges);
|
|
2175
2586
|
// Schedule async recompute only for nodes that indicated Promise-based resolveHandles
|
|
2176
2587
|
for (const nodeId of initial.pending) {
|
|
@@ -2186,10 +2597,14 @@ class GraphRuntime {
|
|
|
2186
2597
|
if (!node)
|
|
2187
2598
|
throw new Error(`Node not found: ${nodeId}`);
|
|
2188
2599
|
let anyChanged = false;
|
|
2189
|
-
const edges = this.graph.getEdges();
|
|
2190
2600
|
const registry = this.graph.getRegistry();
|
|
2191
2601
|
for (const [handle, value] of Object.entries(inputs)) {
|
|
2192
|
-
|
|
2602
|
+
let hasInbound = false;
|
|
2603
|
+
this.graph.forEachEdge((e) => {
|
|
2604
|
+
if (e.target.nodeId === nodeId && e.target.handle === handle) {
|
|
2605
|
+
hasInbound = true;
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2193
2608
|
if (hasInbound)
|
|
2194
2609
|
continue;
|
|
2195
2610
|
// Validate input value against declared type
|
|
@@ -2223,12 +2638,7 @@ class GraphRuntime {
|
|
|
2223
2638
|
const prev = node.inputs[handle];
|
|
2224
2639
|
const same = valuesEqual(prev, value);
|
|
2225
2640
|
if (!same) {
|
|
2226
|
-
|
|
2227
|
-
delete node.inputs[handle];
|
|
2228
|
-
}
|
|
2229
|
-
else {
|
|
2230
|
-
node.inputs[handle] = value;
|
|
2231
|
-
}
|
|
2641
|
+
this.graph.updateNodeInput(nodeId, handle, value);
|
|
2232
2642
|
// Emit value event for input updates
|
|
2233
2643
|
this.eventEmitter.emit("value", { nodeId, handle, value, io: "input" });
|
|
2234
2644
|
anyChanged = true;
|
|
@@ -2250,7 +2660,7 @@ class GraphRuntime {
|
|
|
2250
2660
|
return node?.outputs[output];
|
|
2251
2661
|
}
|
|
2252
2662
|
launch(invalidate = false) {
|
|
2253
|
-
|
|
2663
|
+
this.graph.forEachNode((node) => {
|
|
2254
2664
|
const effectiveInputs = this.nodeExecutor.getEffectiveInputs(node.nodeId);
|
|
2255
2665
|
const ctrl = new AbortController();
|
|
2256
2666
|
const execCtx = this.nodeExecutor.createExecutionContext(node.nodeId, node, effectiveInputs, `${node.nodeId}:init`, ctrl.signal);
|
|
@@ -2260,9 +2670,9 @@ class GraphRuntime {
|
|
|
2260
2670
|
execCtx.log("debug", "prepare-done");
|
|
2261
2671
|
}
|
|
2262
2672
|
node.runtime.onActivated?.();
|
|
2263
|
-
}
|
|
2673
|
+
});
|
|
2264
2674
|
if (this.runMode === "auto" && invalidate) {
|
|
2265
|
-
for (const nodeId of this.graph.
|
|
2675
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2266
2676
|
if (this.graph.allInboundHaveValue(nodeId))
|
|
2267
2677
|
this.execute(nodeId);
|
|
2268
2678
|
}
|
|
@@ -2297,7 +2707,7 @@ class GraphRuntime {
|
|
|
2297
2707
|
this.nodeExecutor.cancelNodeRuns(nodeIds);
|
|
2298
2708
|
}
|
|
2299
2709
|
getNodeIds() {
|
|
2300
|
-
return
|
|
2710
|
+
return this.graph.getNodeIds();
|
|
2301
2711
|
}
|
|
2302
2712
|
getNodeData(nodeId) {
|
|
2303
2713
|
const node = this.graph.getNode(nodeId);
|
|
@@ -2318,26 +2728,30 @@ class GraphRuntime {
|
|
|
2318
2728
|
this.environment = { ...env };
|
|
2319
2729
|
this.handleResolver.setEnvironment(this.environment);
|
|
2320
2730
|
this.nodeExecutor.setEnvironment(this.environment);
|
|
2321
|
-
for (const nodeId of this.graph.
|
|
2731
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2322
2732
|
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
2323
2733
|
}
|
|
2324
2734
|
}
|
|
2325
2735
|
getGraphDef() {
|
|
2326
|
-
const nodes =
|
|
2736
|
+
const nodes = [];
|
|
2737
|
+
this.graph.forEachNode((n) => {
|
|
2327
2738
|
const resolved = this.graph.getResolvedHandles(n.nodeId);
|
|
2328
|
-
|
|
2739
|
+
nodes.push({
|
|
2329
2740
|
nodeId: n.nodeId,
|
|
2330
2741
|
typeId: n.typeId,
|
|
2331
2742
|
params: n.params ? { ...n.params } : undefined,
|
|
2332
2743
|
resolvedHandles: resolved ? { ...resolved } : undefined,
|
|
2333
|
-
};
|
|
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
|
+
});
|
|
2334
2754
|
});
|
|
2335
|
-
const edges = this.graph.getEdges().map((e) => ({
|
|
2336
|
-
id: e.id,
|
|
2337
|
-
source: { nodeId: e.source.nodeId, handle: e.source.handle },
|
|
2338
|
-
target: { nodeId: e.target.nodeId, handle: e.target.handle },
|
|
2339
|
-
typeId: e.typeId,
|
|
2340
|
-
}));
|
|
2341
2755
|
return { nodes, edges };
|
|
2342
2756
|
}
|
|
2343
2757
|
async whenIdle() {
|
|
@@ -2356,13 +2770,13 @@ class GraphRuntime {
|
|
|
2356
2770
|
});
|
|
2357
2771
|
}
|
|
2358
2772
|
const isIdle = () => {
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
}
|
|
2365
|
-
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;
|
|
2366
2780
|
};
|
|
2367
2781
|
if (isIdle())
|
|
2368
2782
|
return;
|
|
@@ -2382,7 +2796,7 @@ class GraphRuntime {
|
|
|
2382
2796
|
return;
|
|
2383
2797
|
return new Promise((resolve) => {
|
|
2384
2798
|
const id = this.runContextManager.createRunContext(startNodeId, resolve, options);
|
|
2385
|
-
|
|
2799
|
+
this.graph.addNodeRunContextId(startNodeId, id);
|
|
2386
2800
|
this.execute(startNodeId, new Set([id]));
|
|
2387
2801
|
});
|
|
2388
2802
|
}
|
|
@@ -2416,38 +2830,38 @@ class GraphRuntime {
|
|
|
2416
2830
|
try {
|
|
2417
2831
|
const ins = payload?.inputs || {};
|
|
2418
2832
|
for (const [nodeId, map] of Object.entries(ins)) {
|
|
2419
|
-
|
|
2420
|
-
if (!node)
|
|
2833
|
+
if (!this.graph.hasNode(nodeId))
|
|
2421
2834
|
continue;
|
|
2422
2835
|
for (const [h, v] of Object.entries(map || {})) {
|
|
2423
|
-
|
|
2836
|
+
const clonedValue = structuredClone(v);
|
|
2837
|
+
this.graph.updateNodeInput(nodeId, h, clonedValue);
|
|
2424
2838
|
this.eventEmitter.emit("value", {
|
|
2425
2839
|
nodeId,
|
|
2426
2840
|
handle: h,
|
|
2427
|
-
value:
|
|
2841
|
+
value: clonedValue,
|
|
2428
2842
|
io: "input",
|
|
2429
|
-
runtimeTypeId: getTypedOutputTypeId(
|
|
2843
|
+
runtimeTypeId: getTypedOutputTypeId(clonedValue),
|
|
2430
2844
|
});
|
|
2431
2845
|
}
|
|
2432
2846
|
}
|
|
2433
2847
|
const outs = payload?.outputs || {};
|
|
2434
2848
|
for (const [nodeId, map] of Object.entries(outs)) {
|
|
2435
|
-
|
|
2436
|
-
if (!node)
|
|
2849
|
+
if (!this.graph.hasNode(nodeId))
|
|
2437
2850
|
continue;
|
|
2438
2851
|
for (const [h, v] of Object.entries(map || {})) {
|
|
2439
|
-
|
|
2852
|
+
const clonedValue = structuredClone(v);
|
|
2853
|
+
this.graph.updateNodeOutput(nodeId, h, clonedValue);
|
|
2440
2854
|
this.eventEmitter.emit("value", {
|
|
2441
2855
|
nodeId,
|
|
2442
2856
|
handle: h,
|
|
2443
|
-
value:
|
|
2857
|
+
value: clonedValue,
|
|
2444
2858
|
io: "output",
|
|
2445
|
-
runtimeTypeId: getTypedOutputTypeId(
|
|
2859
|
+
runtimeTypeId: getTypedOutputTypeId(clonedValue),
|
|
2446
2860
|
});
|
|
2447
2861
|
}
|
|
2448
2862
|
}
|
|
2449
2863
|
if (opts?.invalidate) {
|
|
2450
|
-
for (const nodeId of this.graph.
|
|
2864
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2451
2865
|
this.invalidateDownstream(nodeId);
|
|
2452
2866
|
}
|
|
2453
2867
|
}
|
|
@@ -2460,17 +2874,19 @@ class GraphRuntime {
|
|
|
2460
2874
|
{
|
|
2461
2875
|
// Delete nodes that are no longer in the definition
|
|
2462
2876
|
const afterIds = new Set(def.nodes.map((n) => n.nodeId));
|
|
2463
|
-
const beforeIds = new Set(this.graph.
|
|
2877
|
+
const beforeIds = new Set(this.graph.getNodeIds());
|
|
2464
2878
|
for (const nodeId of Array.from(beforeIds)) {
|
|
2465
2879
|
if (!afterIds.has(nodeId)) {
|
|
2466
2880
|
const node = this.graph.getNode(nodeId);
|
|
2881
|
+
if (!node)
|
|
2882
|
+
continue;
|
|
2467
2883
|
this.nodeExecutor.cancelNodeActiveRuns(node, "node-deleted");
|
|
2468
2884
|
this.runContextManager.cancelNodeInRunContexts(nodeId, true);
|
|
2469
2885
|
node.runtime.onDeactivated?.();
|
|
2470
2886
|
node.runtime.dispose?.();
|
|
2471
2887
|
node.lifecycle?.dispose?.({
|
|
2472
2888
|
state: node.state,
|
|
2473
|
-
setState: (next) =>
|
|
2889
|
+
setState: (next) => this.graph.updateNodeState(node.nodeId, next),
|
|
2474
2890
|
});
|
|
2475
2891
|
this.graph.deleteNode(nodeId);
|
|
2476
2892
|
this.edgePropagator.clearArrayBuckets(nodeId);
|
|
@@ -2534,43 +2950,43 @@ class GraphRuntime {
|
|
|
2534
2950
|
newNode.runtime.onActivated?.();
|
|
2535
2951
|
}
|
|
2536
2952
|
else {
|
|
2537
|
-
|
|
2953
|
+
this.graph.updateNodeParams(n.nodeId, n.params);
|
|
2538
2954
|
// Re-merge policy when params change (params.policy can override descriptor/category policy)
|
|
2539
2955
|
const desc = registry.nodes.get(existing.typeId);
|
|
2540
2956
|
const cat = registry.categories.get(desc?.categoryId ?? "");
|
|
2541
|
-
|
|
2957
|
+
this.graph.updateNodePolicy(n.nodeId, {
|
|
2542
2958
|
...cat?.policy,
|
|
2543
2959
|
...desc?.policy,
|
|
2544
2960
|
...n.params?.policy,
|
|
2545
|
-
};
|
|
2961
|
+
});
|
|
2962
|
+
// Initialize stats if missing
|
|
2546
2963
|
if (!existing.stats) {
|
|
2547
|
-
|
|
2964
|
+
this.graph.updateNodeStats(n.nodeId, {
|
|
2548
2965
|
runs: 0,
|
|
2549
2966
|
active: 0,
|
|
2550
2967
|
queued: 0,
|
|
2551
2968
|
progress: 0,
|
|
2552
|
-
};
|
|
2969
|
+
});
|
|
2553
2970
|
}
|
|
2554
2971
|
}
|
|
2555
2972
|
}
|
|
2556
2973
|
}
|
|
2557
2974
|
{
|
|
2558
|
-
const beforeEdges = this.graph.getEdges();
|
|
2559
2975
|
const beforeInbound = new Map();
|
|
2560
|
-
|
|
2976
|
+
const beforeOutTargets = new Map();
|
|
2977
|
+
this.graph.forEachEdge((e) => {
|
|
2978
|
+
// Build beforeInbound map
|
|
2561
2979
|
const set = beforeInbound.get(e.target.nodeId) ?? new Set();
|
|
2562
2980
|
set.add(e.target.handle);
|
|
2563
2981
|
beforeInbound.set(e.target.nodeId, set);
|
|
2564
|
-
|
|
2565
|
-
const beforeOutTargets = new Map();
|
|
2566
|
-
for (const e of beforeEdges) {
|
|
2982
|
+
// Build beforeOutTargets map
|
|
2567
2983
|
const tmap = beforeOutTargets.get(e.source.nodeId) ??
|
|
2568
2984
|
new Map();
|
|
2569
2985
|
const tset = tmap.get(e.source.handle) ?? new Set();
|
|
2570
2986
|
tset.add(`${e.target.nodeId}.${e.target.handle}`);
|
|
2571
2987
|
tmap.set(e.source.handle, tset);
|
|
2572
2988
|
beforeOutTargets.set(e.source.nodeId, tmap);
|
|
2573
|
-
}
|
|
2989
|
+
});
|
|
2574
2990
|
{
|
|
2575
2991
|
// Update handles and edges
|
|
2576
2992
|
const result = tryHandleResolving(def, registry, this.environment);
|
|
@@ -2585,7 +3001,11 @@ class GraphRuntime {
|
|
|
2585
3001
|
for (const [nodeId, handles] of result.resolved) {
|
|
2586
3002
|
this.graph.setResolvedHandles(nodeId, handles);
|
|
2587
3003
|
}
|
|
2588
|
-
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);
|
|
2589
3009
|
this.graph.setEdges(afterEdges);
|
|
2590
3010
|
for (const nodeId of result.pending) {
|
|
2591
3011
|
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
@@ -2600,23 +3020,22 @@ class GraphRuntime {
|
|
|
2600
3020
|
{
|
|
2601
3021
|
// Update inputs and propagate changes
|
|
2602
3022
|
const afterInbound = new Map();
|
|
2603
|
-
|
|
2604
|
-
for (const e of afterEdges) {
|
|
3023
|
+
this.graph.forEachEdge((e) => {
|
|
2605
3024
|
const set = afterInbound.get(e.target.nodeId) ?? new Set();
|
|
2606
3025
|
set.add(e.target.handle);
|
|
2607
3026
|
afterInbound.set(e.target.nodeId, set);
|
|
2608
|
-
}
|
|
3027
|
+
});
|
|
2609
3028
|
// Propagate changes on edges removed
|
|
2610
3029
|
for (const [nodeId, beforeSet] of beforeInbound) {
|
|
2611
3030
|
const currSet = afterInbound.get(nodeId) ?? new Set();
|
|
2612
|
-
|
|
2613
|
-
if (!node)
|
|
3031
|
+
if (!this.graph.hasNode(nodeId))
|
|
2614
3032
|
continue;
|
|
2615
3033
|
let changed = false;
|
|
2616
3034
|
for (const handle of Array.from(beforeSet)) {
|
|
2617
3035
|
if (!currSet.has(handle)) {
|
|
2618
|
-
|
|
2619
|
-
|
|
3036
|
+
const node = this.graph.getNode(nodeId);
|
|
3037
|
+
if (node && handle in node.inputs) {
|
|
3038
|
+
this.graph.deleteNodeInput(nodeId, handle);
|
|
2620
3039
|
changed = true;
|
|
2621
3040
|
}
|
|
2622
3041
|
}
|
|
@@ -2631,14 +3050,14 @@ class GraphRuntime {
|
|
|
2631
3050
|
}
|
|
2632
3051
|
// Propagate changes on edges added
|
|
2633
3052
|
const afterOutTargets = new Map();
|
|
2634
|
-
|
|
3053
|
+
this.graph.forEachEdge((e) => {
|
|
2635
3054
|
const targetMap = afterOutTargets.get(e.source.nodeId) ??
|
|
2636
3055
|
new Map();
|
|
2637
3056
|
const targetSet = targetMap.get(e.source.handle) ?? new Set();
|
|
2638
3057
|
targetSet.add(`${e.target.nodeId}.${e.target.handle}`);
|
|
2639
3058
|
targetMap.set(e.source.handle, targetSet);
|
|
2640
3059
|
afterOutTargets.set(e.source.nodeId, targetMap);
|
|
2641
|
-
}
|
|
3060
|
+
});
|
|
2642
3061
|
const setsEqual = (a, b) => {
|
|
2643
3062
|
if (!a && !b)
|
|
2644
3063
|
return true;
|
|
@@ -2686,14 +3105,14 @@ class GraphRuntime {
|
|
|
2686
3105
|
}
|
|
2687
3106
|
dispose() {
|
|
2688
3107
|
this.runContextManager.resolveAll();
|
|
2689
|
-
|
|
3108
|
+
this.graph.forEachNode((node) => {
|
|
2690
3109
|
node.runtime.onDeactivated?.();
|
|
2691
3110
|
node.runtime.dispose?.();
|
|
2692
3111
|
node.lifecycle?.dispose?.({
|
|
2693
3112
|
state: node.state,
|
|
2694
|
-
setState: (next) =>
|
|
3113
|
+
setState: (next) => this.graph.updateNodeState(node.nodeId, next),
|
|
2695
3114
|
});
|
|
2696
|
-
}
|
|
3115
|
+
});
|
|
2697
3116
|
this.graph.clear();
|
|
2698
3117
|
}
|
|
2699
3118
|
execute(nodeId, runContextIds) {
|