@bian-womp/spark-graph 0.1.12 → 0.1.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 CHANGED
@@ -464,6 +464,39 @@ class GraphRuntime {
464
464
  stats: { runs: 0, inFlight: false, progress: 0 },
465
465
  };
466
466
  });
467
+ // After nodes and edges exist, seed registry- and graph-level defaults
468
+ for (const n of def.nodes) {
469
+ const node = gr.nodes.get(n.nodeId);
470
+ const desc = registry.nodes.get(n.typeId);
471
+ if (!node || !desc)
472
+ continue;
473
+ // Resolve registry-level defaults (object or function)
474
+ const regDefaults = typeof desc.inputDefaults === "function"
475
+ ? desc.inputDefaults({
476
+ params: n.params,
477
+ environment: gr.environment,
478
+ })
479
+ : desc.inputDefaults ?? {};
480
+ const graphDefaults = n.initialInputs ?? {};
481
+ // Apply precedence: graph-level overrides registry-level
482
+ const merged = {
483
+ ...regDefaults,
484
+ ...graphDefaults,
485
+ };
486
+ for (const [handle, value] of Object.entries(merged)) {
487
+ // Only seed if input has no inbound wiring
488
+ const hasInbound = gr.edges.some((e) => e.target.nodeId === n.nodeId && e.target.handle === handle);
489
+ if (hasInbound)
490
+ continue;
491
+ if (value === undefined)
492
+ continue;
493
+ // Clone to avoid accidental shared references
494
+ node.inputs[handle] =
495
+ typeof structuredClone === "function"
496
+ ? structuredClone(value)
497
+ : JSON.parse(JSON.stringify(value));
498
+ }
499
+ }
467
500
  return gr;
468
501
  }
469
502
  on(event, handler) {
@@ -494,8 +527,11 @@ class GraphRuntime {
494
527
  // Emit value event for input updates
495
528
  this.emit("value", { nodeId, handle, value, io: "input" });
496
529
  }
497
- if (!this.paused)
498
- this.scheduleInputsChanged(nodeId);
530
+ if (!this.paused) {
531
+ // Only schedule if all inbound inputs are present (or there are none)
532
+ if (this.allInboundHaveValue(nodeId))
533
+ this.scheduleInputsChanged(nodeId);
534
+ }
499
535
  }
500
536
  getOutput(nodeId, output) {
501
537
  const node = this.nodes.get(nodeId);
@@ -586,7 +622,7 @@ class GraphRuntime {
586
622
  await new Promise((r) => setTimeout(r, delay));
587
623
  return exec(attempt + 1);
588
624
  }
589
- this.emit("error", { nodeId, runId, err });
625
+ this.emit("error", { kind: "node-run", nodeId, runId, err });
590
626
  }
591
627
  finally {
592
628
  if (timeoutId)
@@ -641,6 +677,20 @@ class GraphRuntime {
641
677
  // switch or merge
642
678
  startRun(rid, { ...node.inputs });
643
679
  }
680
+ // Returns true if all inbound handles for the node currently have a value
681
+ allInboundHaveValue(nodeId) {
682
+ const node = this.nodes.get(nodeId);
683
+ if (!node)
684
+ return false;
685
+ const inbound = this.edges.filter((e) => e.target.nodeId === nodeId);
686
+ if (inbound.length === 0)
687
+ return true;
688
+ for (const e of inbound) {
689
+ if (!(e.target.handle in node.inputs))
690
+ return false;
691
+ }
692
+ return true;
693
+ }
644
694
  invalidateDownstream(nodeId) {
645
695
  // Notifies dependents; for now we propagate current outputs
646
696
  for (const e of this.edges.filter((e) => e.source.nodeId === nodeId)) {
@@ -676,7 +726,7 @@ class GraphRuntime {
676
726
  value: v,
677
727
  io: "input",
678
728
  });
679
- if (!this.paused)
729
+ if (!this.paused && this.allInboundHaveValue(e.target.nodeId))
680
730
  this.scheduleInputsChanged(e.target.nodeId);
681
731
  };
682
732
  if (e.convertAsync) {
@@ -732,6 +782,14 @@ class GraphRuntime {
732
782
  }
733
783
  }
734
784
  }
785
+ reemitNodeOutputs(nodeId) {
786
+ const node = this.nodes.get(nodeId);
787
+ if (!node)
788
+ return;
789
+ for (const [handle, value] of Object.entries(node.outputs)) {
790
+ this.propagate(nodeId, handle, value);
791
+ }
792
+ }
735
793
  launch() {
736
794
  // call onActivated for nodes that implement it
737
795
  for (const node of this.nodes.values()) {
@@ -756,6 +814,11 @@ class GraphRuntime {
756
814
  });
757
815
  node.runtime.onActivated?.(ctx);
758
816
  }
817
+ // After activation, schedule nodes that have all inbound inputs present
818
+ for (const nodeId of this.nodes.keys()) {
819
+ if (this.allInboundHaveValue(nodeId))
820
+ this.scheduleInputsChanged(nodeId);
821
+ }
759
822
  }
760
823
  triggerExternal(nodeId, event) {
761
824
  const node = this.nodes.get(nodeId);
@@ -791,8 +854,11 @@ class GraphRuntime {
791
854
  this.edges = [];
792
855
  this.listeners.clear();
793
856
  }
857
+ getNodeIds() {
858
+ return Array.from(this.nodes.keys());
859
+ }
794
860
  // Unsafe helpers for serializer: read-only accessors and hydration
795
- __unsafe_getNodeData(nodeId) {
861
+ getNodeData(nodeId) {
796
862
  const node = this.nodes.get(nodeId);
797
863
  if (!node)
798
864
  return undefined;
@@ -804,33 +870,12 @@ class GraphRuntime {
804
870
  stats: { ...node.stats },
805
871
  };
806
872
  }
807
- __unsafe_getEnvironment() {
873
+ getEnvironment() {
808
874
  return { ...this.environment };
809
875
  }
810
876
  setEnvironment(env) {
811
877
  this.environment = { ...env };
812
878
  }
813
- __unsafe_setEnvironment(env) {
814
- this.setEnvironment(env);
815
- }
816
- __unsafe_hydrateNode(nodeId, data, opts) {
817
- const node = this.nodes.get(nodeId);
818
- if (!node)
819
- return;
820
- if (opts?.replace) {
821
- node.inputs = {};
822
- node.outputs = {};
823
- node.state = {};
824
- }
825
- if (data.inputs)
826
- Object.assign(node.inputs, data.inputs);
827
- if (data.outputs)
828
- Object.assign(node.outputs, data.outputs);
829
- if (data.state !== undefined)
830
- node.state = data.state;
831
- if (data.params)
832
- node.params = data.params;
833
- }
834
879
  async whenIdle() {
835
880
  const isIdle = () => {
836
881
  for (const n of this.nodes.values()) {
@@ -862,14 +907,6 @@ class GraphRuntime {
862
907
  __unsafe_invalidateDownstream(nodeId) {
863
908
  this.invalidateDownstream(nodeId);
864
909
  }
865
- __unsafe_reemitNodeOutputs(nodeId) {
866
- const node = this.nodes.get(nodeId);
867
- if (!node)
868
- return;
869
- for (const [handle, value] of Object.entries(node.outputs)) {
870
- this.propagate(nodeId, handle, value);
871
- }
872
- }
873
910
  __unsafe_scheduleInputsChanged(nodeId) {
874
911
  this.scheduleInputsChanged(nodeId);
875
912
  }
@@ -1033,12 +1070,41 @@ class GraphRuntime {
1033
1070
  }
1034
1071
  }
1035
1072
  }
1036
- if (changed)
1073
+ // If input lost inbound, try to re-seed from defaults
1074
+ if (changed) {
1075
+ const defNode = def.nodes.find((n) => n.nodeId === nodeId);
1076
+ if (defNode) {
1077
+ const desc = registry.nodes.get(defNode.typeId);
1078
+ if (desc) {
1079
+ const regDefaults = typeof desc.inputDefaults === "function"
1080
+ ? desc.inputDefaults({
1081
+ params: defNode.params,
1082
+ environment: this.environment,
1083
+ })
1084
+ : desc.inputDefaults ?? {};
1085
+ const graphDefaults = defNode.initialInputs ?? {};
1086
+ const merged = {
1087
+ ...regDefaults,
1088
+ ...graphDefaults,
1089
+ };
1090
+ for (const h of Array.from(prevSet)) {
1091
+ if (!currSet.has(h) && node.inputs[h] === undefined) {
1092
+ const v = merged[h];
1093
+ if (v !== undefined)
1094
+ node.inputs[h] =
1095
+ typeof structuredClone === "function"
1096
+ ? structuredClone(v)
1097
+ : JSON.parse(JSON.stringify(v));
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1037
1102
  this.scheduleInputsChanged(nodeId);
1103
+ }
1038
1104
  }
1039
1105
  // Re-emit existing outputs to populate new edges
1040
1106
  for (const nodeId of this.nodes.keys()) {
1041
- this.__unsafe_reemitNodeOutputs(nodeId);
1107
+ this.reemitNodeOutputs(nodeId);
1042
1108
  }
1043
1109
  }
1044
1110
  }
@@ -1668,6 +1734,9 @@ function setupBasicGraphRegistry() {
1668
1734
  Factor: "base.float",
1669
1735
  },
1670
1736
  outputs: { Value: "base.float[]" },
1737
+ inputDefaults: {
1738
+ Factor: 0.5,
1739
+ },
1671
1740
  impl: (ins) => {
1672
1741
  const [a, b] = broadcast(ins.ValueA, ins.ValueB);
1673
1742
  const t = Number(ins.Factor ?? 0);
@@ -1719,6 +1788,8 @@ function setupBasicGraphRegistry() {
1719
1788
  B: "base.float[]",
1720
1789
  },
1721
1790
  outputs: { Result: "base.float[]" },
1791
+ // Registry-level defaults: Add by default, A=[1], B=[1]
1792
+ inputDefaults: { Operation: 0, A: [1], B: [1] },
1722
1793
  impl: (ins) => {
1723
1794
  // Gracefully handle missing inputs by treating them as zeros
1724
1795
  const a = ins.A === undefined ? [] : asArray(ins.A);
@@ -1841,6 +1912,8 @@ function setupBasicGraphRegistry() {
1841
1912
  Seed: "base.float",
1842
1913
  },
1843
1914
  outputs: { Values: "base.vec3[]" },
1915
+ // Registry-level defaults for convenience
1916
+ inputDefaults: { Domain: 10, Min: [0, 0, 0], Max: [1, 1, 1], Seed: 1 },
1844
1917
  impl: (ins) => {
1845
1918
  const len = Math.trunc(ins.Domain);
1846
1919
  const min = ins.Min ?? [0, 0, 0];
@@ -1860,12 +1933,14 @@ function registerDelayNode(registry) {
1860
1933
  registry.registerNode({
1861
1934
  id: "async.delay",
1862
1935
  categoryId: "compute",
1863
- inputs: { x: "base.float", ms: "base.float" },
1864
- outputs: { out: "base.float" },
1936
+ inputs: { Value: "base.float", DelayMs: "base.float" },
1937
+ outputs: { Output: "base.float" },
1865
1938
  impl: async (ins, ctx) => {
1866
- const ms = Number(ins.ms ?? 200);
1867
- const xRaw = ins.x;
1868
- if (xRaw === undefined || xRaw === null || Number.isNaN(Number(xRaw))) {
1939
+ const ms = Number(ins.DelayMs ?? 200);
1940
+ const valueRaw = ins.Value;
1941
+ if (valueRaw === undefined ||
1942
+ valueRaw === null ||
1943
+ Number.isNaN(Number(valueRaw))) {
1869
1944
  return; // wait until x is present to avoid NaN emissions
1870
1945
  }
1871
1946
  await new Promise((resolve, reject) => {
@@ -1878,7 +1953,7 @@ function registerDelayNode(registry) {
1878
1953
  return onAbort();
1879
1954
  ctx.abortSignal.addEventListener("abort", onAbort, { once: true });
1880
1955
  });
1881
- return { out: Number(xRaw) };
1956
+ return { Output: Number(valueRaw) };
1882
1957
  },
1883
1958
  });
1884
1959
  }
@@ -1933,7 +2008,12 @@ function makeBasicGraphDefinition() {
1933
2008
  return {
1934
2009
  nodes: [
1935
2010
  { nodeId: "n1", typeId: "base.math" },
1936
- { nodeId: "n2", typeId: "base.math" },
2011
+ {
2012
+ nodeId: "n2",
2013
+ typeId: "base.math",
2014
+ // Graph-level defaults override registry if provided
2015
+ initialInputs: { Operation: 2, B: [10] }, // Multiply by 10
2016
+ },
1937
2017
  // Transitivity demo nodes
1938
2018
  { nodeId: "n3", typeId: "base.compare" },
1939
2019
  { nodeId: "n4", typeId: "base.randomXYZs" },
@@ -1992,7 +2072,7 @@ function createAsyncGraphDef() {
1992
2072
  {
1993
2073
  id: "e1",
1994
2074
  source: { nodeId: "n1", handle: "Result" },
1995
- target: { nodeId: "n2", handle: "x" },
2075
+ target: { nodeId: "n2", handle: "Value" },
1996
2076
  },
1997
2077
  // Demonstrate async edge conversion: vec3[] -> float[] using coercion
1998
2078
  {
@@ -2031,7 +2111,7 @@ function createProgressGraphDef() {
2031
2111
  const def = {
2032
2112
  nodes: [
2033
2113
  { nodeId: "steps", typeId: "base.number" },
2034
- { nodeId: "async.delay", typeId: "base.number" },
2114
+ { nodeId: "delay", typeId: "base.number" },
2035
2115
  { nodeId: "work", typeId: "async.progress" },
2036
2116
  ],
2037
2117
  edges: [
@@ -2042,7 +2122,7 @@ function createProgressGraphDef() {
2042
2122
  },
2043
2123
  {
2044
2124
  id: "e2",
2045
- source: { nodeId: "async.delay", handle: "Result" },
2125
+ source: { nodeId: "delay", handle: "Result" },
2046
2126
  target: { nodeId: "work", handle: "DelayMs" },
2047
2127
  },
2048
2128
  // not wiring ShouldError to show manual input driven error later