@bian-womp/spark-graph 0.2.22 → 0.2.24

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 CHANGED
@@ -402,6 +402,13 @@ class Registry {
402
402
  }
403
403
  }
404
404
 
405
+ // Helper: typed promise detection and unwrapping for T | Promise<T>
406
+ function isPromise(value) {
407
+ return !!value && typeof value.then === "function";
408
+ }
409
+ async function unwrapMaybePromise(value) {
410
+ return isPromise(value) ? await value : value;
411
+ }
405
412
  class GraphRuntime {
406
413
  constructor() {
407
414
  this.nodes = new Map();
@@ -410,6 +417,8 @@ class GraphRuntime {
410
417
  this.resolvedByNode = new Map();
411
418
  this.listeners = new Map();
412
419
  this.environment = {};
420
+ // Token to guard async resolveHandles recomputes per node
421
+ this.recomputeTokenByNode = new Map();
413
422
  this.paused = false;
414
423
  // For array-typed target inputs, keep per-edge contributions so successive runs
415
424
  // from the same source replace their slice instead of accumulating forever.
@@ -437,7 +446,8 @@ class GraphRuntime {
437
446
  gr.registry = registry;
438
447
  gr.environment = opts?.environment ?? {};
439
448
  // Precompute per-node resolved handles (use def-provided overrides; do not compute dynamically here)
440
- gr.resolvedByNode = GraphRuntime.computeResolvedHandleMap(def, registry, gr.environment);
449
+ const initial = GraphRuntime.computeResolvedHandleMap(def, registry, gr.environment);
450
+ gr.resolvedByNode = initial.map;
441
451
  // Instantiate nodes
442
452
  for (const n of def.nodes) {
443
453
  const desc = registry.nodes.get(n.typeId);
@@ -510,6 +520,9 @@ class GraphRuntime {
510
520
  : JSON.parse(JSON.stringify(value));
511
521
  }
512
522
  }
523
+ // Schedule async recompute only for nodes that indicated Promise-based resolveHandles
524
+ for (const nodeId of initial.pending)
525
+ gr.scheduleRecomputeHandles(nodeId);
513
526
  return gr;
514
527
  }
515
528
  on(event, handler) {
@@ -916,6 +929,7 @@ class GraphRuntime {
916
929
  // Helper: build map of resolved handles per node from def (prefer def.resolvedHandles, otherwise registry statics)
917
930
  static computeResolvedHandleMap(def, registry, environment) {
918
931
  const out = new Map();
932
+ const pending = new Set();
919
933
  for (const n of def.nodes) {
920
934
  const desc = registry.nodes.get(n.typeId);
921
935
  if (!desc)
@@ -927,11 +941,19 @@ class GraphRuntime {
927
941
  let dyn = {};
928
942
  try {
929
943
  if (typeof desc.resolveHandles === "function") {
930
- dyn = desc.resolveHandles({
944
+ const maybe = desc.resolveHandles({
931
945
  environment: environment || {},
932
946
  params: n.params,
933
947
  inputs: undefined,
934
948
  });
949
+ // Only use sync results here; async results are applied via recompute later
950
+ if (isPromise(maybe)) {
951
+ // mark node as pending async recompute
952
+ pending.add(n.nodeId);
953
+ }
954
+ else {
955
+ dyn = maybe || {};
956
+ }
935
957
  }
936
958
  }
937
959
  catch {
@@ -955,7 +977,7 @@ class GraphRuntime {
955
977
  };
956
978
  out.set(n.nodeId, { inputs, outputs, inputDefaults });
957
979
  }
958
- return out;
980
+ return { map: out, pending };
959
981
  }
960
982
  // Helper: build runtime edges with coercions using resolved handles
961
983
  static buildEdges(def, registry, resolvedByNode) {
@@ -1357,7 +1379,8 @@ class GraphRuntime {
1357
1379
  prevOutTargets.set(e.source.nodeId, tmap);
1358
1380
  }
1359
1381
  // Precompute per-node resolved handles for updated graph (include dynamic)
1360
- this.resolvedByNode = GraphRuntime.computeResolvedHandleMap(def, registry, this.environment);
1382
+ const resolved = GraphRuntime.computeResolvedHandleMap(def, registry, this.environment);
1383
+ this.resolvedByNode = resolved.map;
1361
1384
  // Rebuild edges mapping with coercions
1362
1385
  this.edges = GraphRuntime.buildEdges(def, registry, this.resolvedByNode);
1363
1386
  // Build new inbound map
@@ -1516,6 +1539,9 @@ class GraphRuntime {
1516
1539
  if (byHandle.size === 0)
1517
1540
  this.arrayInputBuckets.delete(nodeId);
1518
1541
  }
1542
+ // Schedule async recompute for nodes that indicated Promise-based resolveHandles in this update
1543
+ for (const nodeId of resolved.pending)
1544
+ this.scheduleRecomputeHandles(nodeId);
1519
1545
  }
1520
1546
  // Schedule a recomputation of dynamic handles for a node (async to avoid mutating during propagation)
1521
1547
  scheduleRecomputeHandles(nodeId) {
@@ -1526,16 +1552,11 @@ class GraphRuntime {
1526
1552
  if (!node)
1527
1553
  return;
1528
1554
  setTimeout(() => {
1529
- try {
1530
- this.recomputeHandlesForNode(nodeId);
1531
- }
1532
- catch {
1533
- // ignore recompute errors
1534
- }
1555
+ void this.recomputeHandlesForNode(nodeId);
1535
1556
  }, 0);
1536
1557
  }
1537
1558
  // Recompute dynamic handles for a single node using current inputs/environment
1538
- recomputeHandlesForNode(nodeId) {
1559
+ async recomputeHandlesForNode(nodeId) {
1539
1560
  const registry = this.registry;
1540
1561
  const node = this.nodes.get(nodeId);
1541
1562
  if (!node)
@@ -1546,17 +1567,23 @@ class GraphRuntime {
1546
1567
  const resolveHandles = desc.resolveHandles;
1547
1568
  if (typeof resolveHandles !== "function")
1548
1569
  return;
1570
+ const token = (this.recomputeTokenByNode.get(nodeId) ?? 0) + 1;
1571
+ this.recomputeTokenByNode.set(nodeId, token);
1549
1572
  let r;
1550
1573
  try {
1551
- r = resolveHandles({
1574
+ const res = resolveHandles({
1552
1575
  environment: this.environment || {},
1553
1576
  params: node.params,
1554
1577
  inputs: node.inputs || {},
1555
1578
  });
1579
+ r = await unwrapMaybePromise(res);
1556
1580
  }
1557
1581
  catch {
1558
1582
  return;
1559
1583
  }
1584
+ // If a newer recompute was scheduled, drop this result
1585
+ if ((this.recomputeTokenByNode.get(nodeId) ?? 0) !== token)
1586
+ return;
1560
1587
  const inputs = { ...desc.inputs, ...r?.inputs };
1561
1588
  const outputs = { ...desc.outputs, ...r?.outputs };
1562
1589
  const inputDefaults = { ...desc.inputDefaults, ...r?.inputDefaults };