@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 +127 -47
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/types.d.ts +5 -0
- package/lib/cjs/src/core/types.d.ts.map +1 -1
- package/lib/cjs/src/examples/progress.d.ts.map +1 -1
- package/lib/cjs/src/examples/shared.d.ts.map +1 -1
- package/lib/cjs/src/examples/simple.d.ts.map +1 -1
- package/lib/cjs/src/index.d.ts +1 -1
- package/lib/cjs/src/index.d.ts.map +1 -1
- package/lib/cjs/src/runtime/GraphRuntime.d.ts +9 -86
- package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/esm/index.js +127 -47
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/types.d.ts +5 -0
- package/lib/esm/src/core/types.d.ts.map +1 -1
- package/lib/esm/src/examples/progress.d.ts.map +1 -1
- package/lib/esm/src/examples/shared.d.ts.map +1 -1
- package/lib/esm/src/examples/simple.d.ts.map +1 -1
- package/lib/esm/src/index.d.ts +1 -1
- package/lib/esm/src/index.d.ts.map +1 -1
- package/lib/esm/src/runtime/GraphRuntime.d.ts +9 -86
- package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/package.json +4 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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: {
|
|
1864
|
-
outputs: {
|
|
1936
|
+
inputs: { Value: "base.float", DelayMs: "base.float" },
|
|
1937
|
+
outputs: { Output: "base.float" },
|
|
1865
1938
|
impl: async (ins, ctx) => {
|
|
1866
|
-
const ms = Number(ins.
|
|
1867
|
-
const
|
|
1868
|
-
if (
|
|
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 {
|
|
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
|
-
{
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|