@bian-womp/spark-graph 0.1.13 → 0.1.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 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) {
@@ -589,7 +622,7 @@ class GraphRuntime {
589
622
  await new Promise((r) => setTimeout(r, delay));
590
623
  return exec(attempt + 1);
591
624
  }
592
- this.emit("error", { nodeId, runId, err });
625
+ this.emit("error", { kind: "node-run", nodeId, runId, err });
593
626
  }
594
627
  finally {
595
628
  if (timeoutId)
@@ -749,6 +782,14 @@ class GraphRuntime {
749
782
  }
750
783
  }
751
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
+ }
752
793
  launch() {
753
794
  // call onActivated for nodes that implement it
754
795
  for (const node of this.nodes.values()) {
@@ -773,6 +814,11 @@ class GraphRuntime {
773
814
  });
774
815
  node.runtime.onActivated?.(ctx);
775
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
+ }
776
822
  }
777
823
  triggerExternal(nodeId, event) {
778
824
  const node = this.nodes.get(nodeId);
@@ -808,8 +854,11 @@ class GraphRuntime {
808
854
  this.edges = [];
809
855
  this.listeners.clear();
810
856
  }
857
+ getNodeIds() {
858
+ return Array.from(this.nodes.keys());
859
+ }
811
860
  // Unsafe helpers for serializer: read-only accessors and hydration
812
- __unsafe_getNodeData(nodeId) {
861
+ getNodeData(nodeId) {
813
862
  const node = this.nodes.get(nodeId);
814
863
  if (!node)
815
864
  return undefined;
@@ -821,33 +870,12 @@ class GraphRuntime {
821
870
  stats: { ...node.stats },
822
871
  };
823
872
  }
824
- __unsafe_getEnvironment() {
873
+ getEnvironment() {
825
874
  return { ...this.environment };
826
875
  }
827
876
  setEnvironment(env) {
828
877
  this.environment = { ...env };
829
878
  }
830
- __unsafe_setEnvironment(env) {
831
- this.setEnvironment(env);
832
- }
833
- __unsafe_hydrateNode(nodeId, data, opts) {
834
- const node = this.nodes.get(nodeId);
835
- if (!node)
836
- return;
837
- if (opts?.replace) {
838
- node.inputs = {};
839
- node.outputs = {};
840
- node.state = {};
841
- }
842
- if (data.inputs)
843
- Object.assign(node.inputs, data.inputs);
844
- if (data.outputs)
845
- Object.assign(node.outputs, data.outputs);
846
- if (data.state !== undefined)
847
- node.state = data.state;
848
- if (data.params)
849
- node.params = data.params;
850
- }
851
879
  async whenIdle() {
852
880
  const isIdle = () => {
853
881
  for (const n of this.nodes.values()) {
@@ -879,14 +907,6 @@ class GraphRuntime {
879
907
  __unsafe_invalidateDownstream(nodeId) {
880
908
  this.invalidateDownstream(nodeId);
881
909
  }
882
- __unsafe_reemitNodeOutputs(nodeId) {
883
- const node = this.nodes.get(nodeId);
884
- if (!node)
885
- return;
886
- for (const [handle, value] of Object.entries(node.outputs)) {
887
- this.propagate(nodeId, handle, value);
888
- }
889
- }
890
910
  __unsafe_scheduleInputsChanged(nodeId) {
891
911
  this.scheduleInputsChanged(nodeId);
892
912
  }
@@ -1050,12 +1070,41 @@ class GraphRuntime {
1050
1070
  }
1051
1071
  }
1052
1072
  }
1053
- 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
+ }
1054
1102
  this.scheduleInputsChanged(nodeId);
1103
+ }
1055
1104
  }
1056
1105
  // Re-emit existing outputs to populate new edges
1057
1106
  for (const nodeId of this.nodes.keys()) {
1058
- this.__unsafe_reemitNodeOutputs(nodeId);
1107
+ this.reemitNodeOutputs(nodeId);
1059
1108
  }
1060
1109
  }
1061
1110
  }
@@ -1685,6 +1734,9 @@ function setupBasicGraphRegistry() {
1685
1734
  Factor: "base.float",
1686
1735
  },
1687
1736
  outputs: { Value: "base.float[]" },
1737
+ inputDefaults: {
1738
+ Factor: 0.5,
1739
+ },
1688
1740
  impl: (ins) => {
1689
1741
  const [a, b] = broadcast(ins.ValueA, ins.ValueB);
1690
1742
  const t = Number(ins.Factor ?? 0);
@@ -1736,6 +1788,8 @@ function setupBasicGraphRegistry() {
1736
1788
  B: "base.float[]",
1737
1789
  },
1738
1790
  outputs: { Result: "base.float[]" },
1791
+ // Registry-level defaults: Add by default, A=[1], B=[1]
1792
+ inputDefaults: { Operation: 0, A: [1], B: [1] },
1739
1793
  impl: (ins) => {
1740
1794
  // Gracefully handle missing inputs by treating them as zeros
1741
1795
  const a = ins.A === undefined ? [] : asArray(ins.A);
@@ -1858,6 +1912,8 @@ function setupBasicGraphRegistry() {
1858
1912
  Seed: "base.float",
1859
1913
  },
1860
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 },
1861
1917
  impl: (ins) => {
1862
1918
  const len = Math.trunc(ins.Domain);
1863
1919
  const min = ins.Min ?? [0, 0, 0];
@@ -1877,12 +1933,14 @@ function registerDelayNode(registry) {
1877
1933
  registry.registerNode({
1878
1934
  id: "async.delay",
1879
1935
  categoryId: "compute",
1880
- inputs: { x: "base.float", ms: "base.float" },
1881
- outputs: { out: "base.float" },
1936
+ inputs: { Value: "base.float", DelayMs: "base.float" },
1937
+ outputs: { Output: "base.float" },
1882
1938
  impl: async (ins, ctx) => {
1883
- const ms = Number(ins.ms ?? 200);
1884
- const xRaw = ins.x;
1885
- 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))) {
1886
1944
  return; // wait until x is present to avoid NaN emissions
1887
1945
  }
1888
1946
  await new Promise((resolve, reject) => {
@@ -1895,7 +1953,7 @@ function registerDelayNode(registry) {
1895
1953
  return onAbort();
1896
1954
  ctx.abortSignal.addEventListener("abort", onAbort, { once: true });
1897
1955
  });
1898
- return { out: Number(xRaw) };
1956
+ return { Output: Number(valueRaw) };
1899
1957
  },
1900
1958
  });
1901
1959
  }
@@ -1950,7 +2008,12 @@ function makeBasicGraphDefinition() {
1950
2008
  return {
1951
2009
  nodes: [
1952
2010
  { nodeId: "n1", typeId: "base.math" },
1953
- { 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
+ },
1954
2017
  // Transitivity demo nodes
1955
2018
  { nodeId: "n3", typeId: "base.compare" },
1956
2019
  { nodeId: "n4", typeId: "base.randomXYZs" },
@@ -2009,7 +2072,7 @@ function createAsyncGraphDef() {
2009
2072
  {
2010
2073
  id: "e1",
2011
2074
  source: { nodeId: "n1", handle: "Result" },
2012
- target: { nodeId: "n2", handle: "x" },
2075
+ target: { nodeId: "n2", handle: "Value" },
2013
2076
  },
2014
2077
  // Demonstrate async edge conversion: vec3[] -> float[] using coercion
2015
2078
  {
@@ -2048,7 +2111,7 @@ function createProgressGraphDef() {
2048
2111
  const def = {
2049
2112
  nodes: [
2050
2113
  { nodeId: "steps", typeId: "base.number" },
2051
- { nodeId: "async.delay", typeId: "base.number" },
2114
+ { nodeId: "delay", typeId: "base.number" },
2052
2115
  { nodeId: "work", typeId: "async.progress" },
2053
2116
  ],
2054
2117
  edges: [
@@ -2059,7 +2122,7 @@ function createProgressGraphDef() {
2059
2122
  },
2060
2123
  {
2061
2124
  id: "e2",
2062
- source: { nodeId: "async.delay", handle: "Result" },
2125
+ source: { nodeId: "delay", handle: "Result" },
2063
2126
  target: { nodeId: "work", handle: "DelayMs" },
2064
2127
  },
2065
2128
  // not wiring ShouldError to show manual input driven error later