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