@bian-womp/spark-graph 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/index.cjs +652 -231
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/EdgePropagator.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/Graph.d.ts +198 -6
- package/lib/cjs/src/runtime/components/Graph.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/HandleResolver.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts +0 -4
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/RunContextManager.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/types.d.ts +11 -0
- package/lib/cjs/src/runtime/components/types.d.ts.map +1 -1
- package/lib/esm/index.js +652 -231
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/EdgePropagator.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/Graph.d.ts +198 -6
- package/lib/esm/src/runtime/components/Graph.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/HandleResolver.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts +0 -4
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/RunContextManager.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/types.d.ts +11 -0
- package/lib/esm/src/runtime/components/types.d.ts.map +1 -1
- package/package.json +2 -2
package/lib/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,38 +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
|
-
});
|
|
2009
2418
|
if (onDone)
|
|
2010
2419
|
onDone();
|
|
2011
2420
|
}
|
|
@@ -2013,22 +2422,24 @@ class NodeExecutor {
|
|
|
2013
2422
|
* Cancel all active runs for a node
|
|
2014
2423
|
*/
|
|
2015
2424
|
cancelNodeActiveRuns(node, reason) {
|
|
2016
|
-
|
|
2017
|
-
|
|
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);
|
|
2018
2432
|
if (runId) {
|
|
2019
2433
|
// Track cancelled runIds for snapshot and user-cancelled operations
|
|
2020
2434
|
// (to drop emits from cancelled runs)
|
|
2021
2435
|
if (reason === "snapshot" || reason === "user-cancelled") {
|
|
2022
|
-
|
|
2023
|
-
node.snapshotCancelledRunIds = new Set();
|
|
2024
|
-
}
|
|
2025
|
-
node.snapshotCancelledRunIds.add(runId);
|
|
2436
|
+
this.graph.addSnapshotCancelledRunId(nodeId, runId);
|
|
2026
2437
|
}
|
|
2027
2438
|
// Emit cancellation event
|
|
2028
2439
|
this.eventEmitter.emit("stats", {
|
|
2029
2440
|
kind: "node-done",
|
|
2030
|
-
nodeId:
|
|
2031
|
-
typeId:
|
|
2441
|
+
nodeId: currentNode.nodeId,
|
|
2442
|
+
typeId: currentNode.typeId,
|
|
2032
2443
|
runId,
|
|
2033
2444
|
cancelled: true,
|
|
2034
2445
|
});
|
|
@@ -2040,10 +2451,9 @@ class NodeExecutor {
|
|
|
2040
2451
|
// ignore abort errors
|
|
2041
2452
|
}
|
|
2042
2453
|
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
node.queue = [];
|
|
2454
|
+
this.graph.clearNodeControllers(nodeId);
|
|
2455
|
+
this.graph.updateNodeStats(nodeId, { active: 0 });
|
|
2456
|
+
this.graph.clearNodeQueue(nodeId);
|
|
2047
2457
|
}
|
|
2048
2458
|
/**
|
|
2049
2459
|
* Cancel runs for multiple nodes.
|
|
@@ -2055,14 +2465,13 @@ class NodeExecutor {
|
|
|
2055
2465
|
const toCancel = new Set(nodeIds);
|
|
2056
2466
|
const visited = new Set();
|
|
2057
2467
|
const queue = [...nodeIds];
|
|
2058
|
-
const edges = this.graph.getEdges();
|
|
2059
2468
|
// Collect all downstream nodes to cancel
|
|
2060
2469
|
for (let i = 0; i < queue.length; i++) {
|
|
2061
2470
|
const nodeId = queue[i];
|
|
2062
2471
|
if (visited.has(nodeId))
|
|
2063
2472
|
continue;
|
|
2064
2473
|
visited.add(nodeId);
|
|
2065
|
-
|
|
2474
|
+
this.graph.forEachEdge((edge) => {
|
|
2066
2475
|
if (edge.source.nodeId === nodeId) {
|
|
2067
2476
|
const targetId = edge.target.nodeId;
|
|
2068
2477
|
if (!visited.has(targetId)) {
|
|
@@ -2070,7 +2479,7 @@ class NodeExecutor {
|
|
|
2070
2479
|
queue.push(targetId);
|
|
2071
2480
|
}
|
|
2072
2481
|
}
|
|
2073
|
-
}
|
|
2482
|
+
});
|
|
2074
2483
|
}
|
|
2075
2484
|
// Cancel runs for all affected nodes
|
|
2076
2485
|
for (const nodeId of toCancel) {
|
|
@@ -2078,10 +2487,10 @@ class NodeExecutor {
|
|
|
2078
2487
|
if (!node)
|
|
2079
2488
|
continue;
|
|
2080
2489
|
this.cancelNodeActiveRuns(node, reason);
|
|
2081
|
-
|
|
2490
|
+
const runSeq = this.graph.incrementNodeRunSeq(nodeId);
|
|
2082
2491
|
const now = Date.now();
|
|
2083
2492
|
const suffix = reason === "snapshot" ? "snapshot" : "cancelled";
|
|
2084
|
-
|
|
2493
|
+
this.graph.setNodeLatestRunId(nodeId, `${nodeId}:${runSeq}:${now}:${suffix}`);
|
|
2085
2494
|
}
|
|
2086
2495
|
// Cancel nodes in run-contexts (exclude them from active run-contexts)
|
|
2087
2496
|
for (const nodeId of toCancel) {
|
|
@@ -2166,7 +2575,11 @@ class GraphRuntime {
|
|
|
2166
2575
|
gr.graph.setNode(n.nodeId, rn);
|
|
2167
2576
|
}
|
|
2168
2577
|
// Instantiate edges
|
|
2169
|
-
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);
|
|
2170
2583
|
gr.graph.setEdges(edges);
|
|
2171
2584
|
// Schedule async recompute only for nodes that indicated Promise-based resolveHandles
|
|
2172
2585
|
for (const nodeId of initial.pending) {
|
|
@@ -2182,10 +2595,14 @@ class GraphRuntime {
|
|
|
2182
2595
|
if (!node)
|
|
2183
2596
|
throw new Error(`Node not found: ${nodeId}`);
|
|
2184
2597
|
let anyChanged = false;
|
|
2185
|
-
const edges = this.graph.getEdges();
|
|
2186
2598
|
const registry = this.graph.getRegistry();
|
|
2187
2599
|
for (const [handle, value] of Object.entries(inputs)) {
|
|
2188
|
-
|
|
2600
|
+
let hasInbound = false;
|
|
2601
|
+
this.graph.forEachEdge((e) => {
|
|
2602
|
+
if (e.target.nodeId === nodeId && e.target.handle === handle) {
|
|
2603
|
+
hasInbound = true;
|
|
2604
|
+
}
|
|
2605
|
+
});
|
|
2189
2606
|
if (hasInbound)
|
|
2190
2607
|
continue;
|
|
2191
2608
|
// Validate input value against declared type
|
|
@@ -2219,12 +2636,7 @@ class GraphRuntime {
|
|
|
2219
2636
|
const prev = node.inputs[handle];
|
|
2220
2637
|
const same = valuesEqual(prev, value);
|
|
2221
2638
|
if (!same) {
|
|
2222
|
-
|
|
2223
|
-
delete node.inputs[handle];
|
|
2224
|
-
}
|
|
2225
|
-
else {
|
|
2226
|
-
node.inputs[handle] = value;
|
|
2227
|
-
}
|
|
2639
|
+
this.graph.updateNodeInput(nodeId, handle, value);
|
|
2228
2640
|
// Emit value event for input updates
|
|
2229
2641
|
this.eventEmitter.emit("value", { nodeId, handle, value, io: "input" });
|
|
2230
2642
|
anyChanged = true;
|
|
@@ -2246,7 +2658,7 @@ class GraphRuntime {
|
|
|
2246
2658
|
return node?.outputs[output];
|
|
2247
2659
|
}
|
|
2248
2660
|
launch(invalidate = false) {
|
|
2249
|
-
|
|
2661
|
+
this.graph.forEachNode((node) => {
|
|
2250
2662
|
const effectiveInputs = this.nodeExecutor.getEffectiveInputs(node.nodeId);
|
|
2251
2663
|
const ctrl = new AbortController();
|
|
2252
2664
|
const execCtx = this.nodeExecutor.createExecutionContext(node.nodeId, node, effectiveInputs, `${node.nodeId}:init`, ctrl.signal);
|
|
@@ -2256,9 +2668,9 @@ class GraphRuntime {
|
|
|
2256
2668
|
execCtx.log("debug", "prepare-done");
|
|
2257
2669
|
}
|
|
2258
2670
|
node.runtime.onActivated?.();
|
|
2259
|
-
}
|
|
2671
|
+
});
|
|
2260
2672
|
if (this.runMode === "auto" && invalidate) {
|
|
2261
|
-
for (const nodeId of this.graph.
|
|
2673
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2262
2674
|
if (this.graph.allInboundHaveValue(nodeId))
|
|
2263
2675
|
this.execute(nodeId);
|
|
2264
2676
|
}
|
|
@@ -2293,7 +2705,7 @@ class GraphRuntime {
|
|
|
2293
2705
|
this.nodeExecutor.cancelNodeRuns(nodeIds);
|
|
2294
2706
|
}
|
|
2295
2707
|
getNodeIds() {
|
|
2296
|
-
return
|
|
2708
|
+
return this.graph.getNodeIds();
|
|
2297
2709
|
}
|
|
2298
2710
|
getNodeData(nodeId) {
|
|
2299
2711
|
const node = this.graph.getNode(nodeId);
|
|
@@ -2314,26 +2726,30 @@ class GraphRuntime {
|
|
|
2314
2726
|
this.environment = { ...env };
|
|
2315
2727
|
this.handleResolver.setEnvironment(this.environment);
|
|
2316
2728
|
this.nodeExecutor.setEnvironment(this.environment);
|
|
2317
|
-
for (const nodeId of this.graph.
|
|
2729
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2318
2730
|
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
2319
2731
|
}
|
|
2320
2732
|
}
|
|
2321
2733
|
getGraphDef() {
|
|
2322
|
-
const nodes =
|
|
2734
|
+
const nodes = [];
|
|
2735
|
+
this.graph.forEachNode((n) => {
|
|
2323
2736
|
const resolved = this.graph.getResolvedHandles(n.nodeId);
|
|
2324
|
-
|
|
2737
|
+
nodes.push({
|
|
2325
2738
|
nodeId: n.nodeId,
|
|
2326
2739
|
typeId: n.typeId,
|
|
2327
2740
|
params: n.params ? { ...n.params } : undefined,
|
|
2328
2741
|
resolvedHandles: resolved ? { ...resolved } : undefined,
|
|
2329
|
-
};
|
|
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
|
+
});
|
|
2330
2752
|
});
|
|
2331
|
-
const edges = this.graph.getEdges().map((e) => ({
|
|
2332
|
-
id: e.id,
|
|
2333
|
-
source: { nodeId: e.source.nodeId, handle: e.source.handle },
|
|
2334
|
-
target: { nodeId: e.target.nodeId, handle: e.target.handle },
|
|
2335
|
-
typeId: e.typeId,
|
|
2336
|
-
}));
|
|
2337
2753
|
return { nodes, edges };
|
|
2338
2754
|
}
|
|
2339
2755
|
async whenIdle() {
|
|
@@ -2352,13 +2768,13 @@ class GraphRuntime {
|
|
|
2352
2768
|
});
|
|
2353
2769
|
}
|
|
2354
2770
|
const isIdle = () => {
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
}
|
|
2361
|
-
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;
|
|
2362
2778
|
};
|
|
2363
2779
|
if (isIdle())
|
|
2364
2780
|
return;
|
|
@@ -2378,7 +2794,7 @@ class GraphRuntime {
|
|
|
2378
2794
|
return;
|
|
2379
2795
|
return new Promise((resolve) => {
|
|
2380
2796
|
const id = this.runContextManager.createRunContext(startNodeId, resolve, options);
|
|
2381
|
-
|
|
2797
|
+
this.graph.addNodeRunContextId(startNodeId, id);
|
|
2382
2798
|
this.execute(startNodeId, new Set([id]));
|
|
2383
2799
|
});
|
|
2384
2800
|
}
|
|
@@ -2412,38 +2828,38 @@ class GraphRuntime {
|
|
|
2412
2828
|
try {
|
|
2413
2829
|
const ins = payload?.inputs || {};
|
|
2414
2830
|
for (const [nodeId, map] of Object.entries(ins)) {
|
|
2415
|
-
|
|
2416
|
-
if (!node)
|
|
2831
|
+
if (!this.graph.hasNode(nodeId))
|
|
2417
2832
|
continue;
|
|
2418
2833
|
for (const [h, v] of Object.entries(map || {})) {
|
|
2419
|
-
|
|
2834
|
+
const clonedValue = structuredClone(v);
|
|
2835
|
+
this.graph.updateNodeInput(nodeId, h, clonedValue);
|
|
2420
2836
|
this.eventEmitter.emit("value", {
|
|
2421
2837
|
nodeId,
|
|
2422
2838
|
handle: h,
|
|
2423
|
-
value:
|
|
2839
|
+
value: clonedValue,
|
|
2424
2840
|
io: "input",
|
|
2425
|
-
runtimeTypeId: getTypedOutputTypeId(
|
|
2841
|
+
runtimeTypeId: getTypedOutputTypeId(clonedValue),
|
|
2426
2842
|
});
|
|
2427
2843
|
}
|
|
2428
2844
|
}
|
|
2429
2845
|
const outs = payload?.outputs || {};
|
|
2430
2846
|
for (const [nodeId, map] of Object.entries(outs)) {
|
|
2431
|
-
|
|
2432
|
-
if (!node)
|
|
2847
|
+
if (!this.graph.hasNode(nodeId))
|
|
2433
2848
|
continue;
|
|
2434
2849
|
for (const [h, v] of Object.entries(map || {})) {
|
|
2435
|
-
|
|
2850
|
+
const clonedValue = structuredClone(v);
|
|
2851
|
+
this.graph.updateNodeOutput(nodeId, h, clonedValue);
|
|
2436
2852
|
this.eventEmitter.emit("value", {
|
|
2437
2853
|
nodeId,
|
|
2438
2854
|
handle: h,
|
|
2439
|
-
value:
|
|
2855
|
+
value: clonedValue,
|
|
2440
2856
|
io: "output",
|
|
2441
|
-
runtimeTypeId: getTypedOutputTypeId(
|
|
2857
|
+
runtimeTypeId: getTypedOutputTypeId(clonedValue),
|
|
2442
2858
|
});
|
|
2443
2859
|
}
|
|
2444
2860
|
}
|
|
2445
2861
|
if (opts?.invalidate) {
|
|
2446
|
-
for (const nodeId of this.graph.
|
|
2862
|
+
for (const nodeId of this.graph.getNodeIds()) {
|
|
2447
2863
|
this.invalidateDownstream(nodeId);
|
|
2448
2864
|
}
|
|
2449
2865
|
}
|
|
@@ -2456,17 +2872,19 @@ class GraphRuntime {
|
|
|
2456
2872
|
{
|
|
2457
2873
|
// Delete nodes that are no longer in the definition
|
|
2458
2874
|
const afterIds = new Set(def.nodes.map((n) => n.nodeId));
|
|
2459
|
-
const beforeIds = new Set(this.graph.
|
|
2875
|
+
const beforeIds = new Set(this.graph.getNodeIds());
|
|
2460
2876
|
for (const nodeId of Array.from(beforeIds)) {
|
|
2461
2877
|
if (!afterIds.has(nodeId)) {
|
|
2462
2878
|
const node = this.graph.getNode(nodeId);
|
|
2879
|
+
if (!node)
|
|
2880
|
+
continue;
|
|
2463
2881
|
this.nodeExecutor.cancelNodeActiveRuns(node, "node-deleted");
|
|
2464
2882
|
this.runContextManager.cancelNodeInRunContexts(nodeId, true);
|
|
2465
2883
|
node.runtime.onDeactivated?.();
|
|
2466
2884
|
node.runtime.dispose?.();
|
|
2467
2885
|
node.lifecycle?.dispose?.({
|
|
2468
2886
|
state: node.state,
|
|
2469
|
-
setState: (next) =>
|
|
2887
|
+
setState: (next) => this.graph.updateNodeState(node.nodeId, next),
|
|
2470
2888
|
});
|
|
2471
2889
|
this.graph.deleteNode(nodeId);
|
|
2472
2890
|
this.edgePropagator.clearArrayBuckets(nodeId);
|
|
@@ -2530,43 +2948,43 @@ class GraphRuntime {
|
|
|
2530
2948
|
newNode.runtime.onActivated?.();
|
|
2531
2949
|
}
|
|
2532
2950
|
else {
|
|
2533
|
-
|
|
2951
|
+
this.graph.updateNodeParams(n.nodeId, n.params);
|
|
2534
2952
|
// Re-merge policy when params change (params.policy can override descriptor/category policy)
|
|
2535
2953
|
const desc = registry.nodes.get(existing.typeId);
|
|
2536
2954
|
const cat = registry.categories.get(desc?.categoryId ?? "");
|
|
2537
|
-
|
|
2955
|
+
this.graph.updateNodePolicy(n.nodeId, {
|
|
2538
2956
|
...cat?.policy,
|
|
2539
2957
|
...desc?.policy,
|
|
2540
2958
|
...n.params?.policy,
|
|
2541
|
-
};
|
|
2959
|
+
});
|
|
2960
|
+
// Initialize stats if missing
|
|
2542
2961
|
if (!existing.stats) {
|
|
2543
|
-
|
|
2962
|
+
this.graph.updateNodeStats(n.nodeId, {
|
|
2544
2963
|
runs: 0,
|
|
2545
2964
|
active: 0,
|
|
2546
2965
|
queued: 0,
|
|
2547
2966
|
progress: 0,
|
|
2548
|
-
};
|
|
2967
|
+
});
|
|
2549
2968
|
}
|
|
2550
2969
|
}
|
|
2551
2970
|
}
|
|
2552
2971
|
}
|
|
2553
2972
|
{
|
|
2554
|
-
const beforeEdges = this.graph.getEdges();
|
|
2555
2973
|
const beforeInbound = new Map();
|
|
2556
|
-
|
|
2974
|
+
const beforeOutTargets = new Map();
|
|
2975
|
+
this.graph.forEachEdge((e) => {
|
|
2976
|
+
// Build beforeInbound map
|
|
2557
2977
|
const set = beforeInbound.get(e.target.nodeId) ?? new Set();
|
|
2558
2978
|
set.add(e.target.handle);
|
|
2559
2979
|
beforeInbound.set(e.target.nodeId, set);
|
|
2560
|
-
|
|
2561
|
-
const beforeOutTargets = new Map();
|
|
2562
|
-
for (const e of beforeEdges) {
|
|
2980
|
+
// Build beforeOutTargets map
|
|
2563
2981
|
const tmap = beforeOutTargets.get(e.source.nodeId) ??
|
|
2564
2982
|
new Map();
|
|
2565
2983
|
const tset = tmap.get(e.source.handle) ?? new Set();
|
|
2566
2984
|
tset.add(`${e.target.nodeId}.${e.target.handle}`);
|
|
2567
2985
|
tmap.set(e.source.handle, tset);
|
|
2568
2986
|
beforeOutTargets.set(e.source.nodeId, tmap);
|
|
2569
|
-
}
|
|
2987
|
+
});
|
|
2570
2988
|
{
|
|
2571
2989
|
// Update handles and edges
|
|
2572
2990
|
const result = tryHandleResolving(def, registry, this.environment);
|
|
@@ -2581,7 +2999,11 @@ class GraphRuntime {
|
|
|
2581
2999
|
for (const [nodeId, handles] of result.resolved) {
|
|
2582
3000
|
this.graph.setResolvedHandles(nodeId, handles);
|
|
2583
3001
|
}
|
|
2584
|
-
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);
|
|
2585
3007
|
this.graph.setEdges(afterEdges);
|
|
2586
3008
|
for (const nodeId of result.pending) {
|
|
2587
3009
|
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
@@ -2596,23 +3018,22 @@ class GraphRuntime {
|
|
|
2596
3018
|
{
|
|
2597
3019
|
// Update inputs and propagate changes
|
|
2598
3020
|
const afterInbound = new Map();
|
|
2599
|
-
|
|
2600
|
-
for (const e of afterEdges) {
|
|
3021
|
+
this.graph.forEachEdge((e) => {
|
|
2601
3022
|
const set = afterInbound.get(e.target.nodeId) ?? new Set();
|
|
2602
3023
|
set.add(e.target.handle);
|
|
2603
3024
|
afterInbound.set(e.target.nodeId, set);
|
|
2604
|
-
}
|
|
3025
|
+
});
|
|
2605
3026
|
// Propagate changes on edges removed
|
|
2606
3027
|
for (const [nodeId, beforeSet] of beforeInbound) {
|
|
2607
3028
|
const currSet = afterInbound.get(nodeId) ?? new Set();
|
|
2608
|
-
|
|
2609
|
-
if (!node)
|
|
3029
|
+
if (!this.graph.hasNode(nodeId))
|
|
2610
3030
|
continue;
|
|
2611
3031
|
let changed = false;
|
|
2612
3032
|
for (const handle of Array.from(beforeSet)) {
|
|
2613
3033
|
if (!currSet.has(handle)) {
|
|
2614
|
-
|
|
2615
|
-
|
|
3034
|
+
const node = this.graph.getNode(nodeId);
|
|
3035
|
+
if (node && handle in node.inputs) {
|
|
3036
|
+
this.graph.deleteNodeInput(nodeId, handle);
|
|
2616
3037
|
changed = true;
|
|
2617
3038
|
}
|
|
2618
3039
|
}
|
|
@@ -2627,14 +3048,14 @@ class GraphRuntime {
|
|
|
2627
3048
|
}
|
|
2628
3049
|
// Propagate changes on edges added
|
|
2629
3050
|
const afterOutTargets = new Map();
|
|
2630
|
-
|
|
3051
|
+
this.graph.forEachEdge((e) => {
|
|
2631
3052
|
const targetMap = afterOutTargets.get(e.source.nodeId) ??
|
|
2632
3053
|
new Map();
|
|
2633
3054
|
const targetSet = targetMap.get(e.source.handle) ?? new Set();
|
|
2634
3055
|
targetSet.add(`${e.target.nodeId}.${e.target.handle}`);
|
|
2635
3056
|
targetMap.set(e.source.handle, targetSet);
|
|
2636
3057
|
afterOutTargets.set(e.source.nodeId, targetMap);
|
|
2637
|
-
}
|
|
3058
|
+
});
|
|
2638
3059
|
const setsEqual = (a, b) => {
|
|
2639
3060
|
if (!a && !b)
|
|
2640
3061
|
return true;
|
|
@@ -2682,14 +3103,14 @@ class GraphRuntime {
|
|
|
2682
3103
|
}
|
|
2683
3104
|
dispose() {
|
|
2684
3105
|
this.runContextManager.resolveAll();
|
|
2685
|
-
|
|
3106
|
+
this.graph.forEachNode((node) => {
|
|
2686
3107
|
node.runtime.onDeactivated?.();
|
|
2687
3108
|
node.runtime.dispose?.();
|
|
2688
3109
|
node.lifecycle?.dispose?.({
|
|
2689
3110
|
state: node.state,
|
|
2690
|
-
setState: (next) =>
|
|
3111
|
+
setState: (next) => this.graph.updateNodeState(node.nodeId, next),
|
|
2691
3112
|
});
|
|
2692
|
-
}
|
|
3113
|
+
});
|
|
2693
3114
|
this.graph.clear();
|
|
2694
3115
|
}
|
|
2695
3116
|
execute(nodeId, runContextIds) {
|