@bian-womp/spark-graph 0.2.14 → 0.2.16

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
@@ -406,6 +406,8 @@ class GraphRuntime {
406
406
  constructor() {
407
407
  this.nodes = new Map();
408
408
  this.edges = [];
409
+ // Current resolved handles per node (registry statics merged with per-node overrides)
410
+ this.resolvedByNode = new Map();
409
411
  this.listeners = new Map();
410
412
  this.environment = {};
411
413
  this.paused = false;
@@ -433,6 +435,8 @@ class GraphRuntime {
433
435
  static create(def, registry, opts) {
434
436
  const gr = new GraphRuntime();
435
437
  gr.environment = opts?.environment ?? {};
438
+ // Precompute per-node resolved handles (use def-provided overrides; do not compute dynamically here)
439
+ gr.resolvedByNode = GraphRuntime.computeResolvedHandleMap(def, registry);
436
440
  // Instantiate nodes
437
441
  for (const n of def.nodes) {
438
442
  const desc = registry.nodes.get(n.typeId);
@@ -472,46 +476,7 @@ class GraphRuntime {
472
476
  gr.nodes.set(n.nodeId, rn);
473
477
  }
474
478
  // Instantiate edges
475
- gr.edges = def.edges.map((e) => {
476
- // infer type from source output if missing
477
- const srcNode = def.nodes.find((n) => n.nodeId === e.source.nodeId);
478
- const dstNode = def.nodes.find((n) => n.nodeId === e.target.nodeId);
479
- let effectiveTypeId = e.typeId;
480
- let srcDeclared;
481
- let dstDeclared;
482
- if (srcNode) {
483
- const srcDesc = registry.nodes.get(srcNode.typeId);
484
- if (srcDesc) {
485
- srcDeclared = srcDesc.outputs[e.source.handle];
486
- }
487
- }
488
- if (!effectiveTypeId) {
489
- effectiveTypeId = Array.isArray(srcDeclared)
490
- ? srcDeclared[0]
491
- : srcDeclared;
492
- }
493
- if (dstNode) {
494
- const dstDesc = registry.nodes.get(dstNode.typeId);
495
- if (dstDesc) {
496
- dstDeclared = getInputTypeId(dstDesc.inputs, e.target.handle);
497
- }
498
- }
499
- // Attach dynamic convert/convertAsync aware of union sources and typed outputs
500
- const { convert, convertAsync } = GraphRuntime.buildEdgeConverters(srcDeclared, dstDeclared, registry);
501
- return {
502
- id: e.id,
503
- source: { ...e.source },
504
- target: { ...e.target },
505
- typeId: effectiveTypeId ?? "untyped",
506
- convert,
507
- convertAsync,
508
- srcUnionTypes: Array.isArray(srcDeclared)
509
- ? [...srcDeclared]
510
- : undefined,
511
- dstDeclared,
512
- stats: { runs: 0, inFlight: false, progress: 0 },
513
- };
514
- });
479
+ gr.edges = GraphRuntime.buildEdges(def, registry, gr.resolvedByNode);
515
480
  // After nodes and edges exist, seed registry- and graph-level defaults
516
481
  for (const n of def.nodes) {
517
482
  const node = gr.nodes.get(n.nodeId);
@@ -941,6 +906,61 @@ class GraphRuntime {
941
906
  }
942
907
  }
943
908
  }
909
+ // Helper: build map of resolved handles per node from def (prefer def.resolvedHandles, otherwise registry statics)
910
+ static computeResolvedHandleMap(def, registry) {
911
+ const out = new Map();
912
+ for (const n of def.nodes) {
913
+ const desc = registry.nodes.get(n.typeId);
914
+ if (!desc)
915
+ continue;
916
+ const overrideInputs = n.resolvedHandles?.inputs;
917
+ const overrideOutputs = n.resolvedHandles?.outputs;
918
+ // Merge base with overrides (allow partial resolvedHandles)
919
+ const inputs = { ...desc.inputs, ...overrideInputs };
920
+ const outputs = { ...desc.outputs, ...overrideOutputs };
921
+ out.set(n.nodeId, { inputs, outputs });
922
+ }
923
+ return out;
924
+ }
925
+ // Helper: build runtime edges with coercions using resolved handles
926
+ static buildEdges(def, registry, resolvedByNode) {
927
+ return def.edges.map((e) => {
928
+ const srcNode = def.nodes.find((n) => n.nodeId === e.source.nodeId);
929
+ const dstNode = def.nodes.find((n) => n.nodeId === e.target.nodeId);
930
+ let effectiveTypeId = e.typeId;
931
+ let srcDeclared;
932
+ let dstDeclared;
933
+ if (srcNode) {
934
+ const resolved = resolvedByNode.get(srcNode.nodeId);
935
+ if (resolved)
936
+ srcDeclared = resolved.outputs[e.source.handle];
937
+ }
938
+ if (!effectiveTypeId) {
939
+ effectiveTypeId = Array.isArray(srcDeclared)
940
+ ? srcDeclared[0]
941
+ : srcDeclared;
942
+ }
943
+ if (dstNode) {
944
+ const resolved = resolvedByNode.get(dstNode.nodeId);
945
+ if (resolved)
946
+ dstDeclared = getInputTypeId(resolved.inputs, e.target.handle);
947
+ }
948
+ const { convert, convertAsync } = GraphRuntime.buildEdgeConverters(srcDeclared, dstDeclared, registry);
949
+ return {
950
+ id: e.id,
951
+ source: { ...e.source },
952
+ target: { ...e.target },
953
+ typeId: effectiveTypeId ?? "untyped",
954
+ convert,
955
+ convertAsync,
956
+ srcUnionTypes: Array.isArray(srcDeclared)
957
+ ? [...srcDeclared]
958
+ : undefined,
959
+ dstDeclared,
960
+ stats: { runs: 0, inFlight: false, progress: 0 },
961
+ };
962
+ });
963
+ }
944
964
  reemitNodeOutputs(nodeId) {
945
965
  const node = this.nodes.get(nodeId);
946
966
  if (!node)
@@ -949,6 +969,39 @@ class GraphRuntime {
949
969
  this.propagate(nodeId, handle, value);
950
970
  }
951
971
  }
972
+ // Update resolved handles for a single node and refresh edge converters/types that touch it
973
+ updateNodeHandles(nodeId, handles, registry) {
974
+ this.resolvedByNode.set(nodeId, handles);
975
+ // Recompute edge converter/type for edges where this node is source or target
976
+ for (const e of this.edges) {
977
+ let srcDeclared = e.typeId;
978
+ let dstDeclared = e.dstDeclared;
979
+ if (e.source.nodeId === nodeId) {
980
+ const resolved = this.resolvedByNode.get(nodeId);
981
+ srcDeclared = resolved
982
+ ? resolved.outputs[e.source.handle]
983
+ : srcDeclared;
984
+ // If edge had no explicit typeId, infer from updated src
985
+ if (!e.typeId) {
986
+ e.typeId = Array.isArray(srcDeclared)
987
+ ? srcDeclared?.[0]
988
+ : srcDeclared;
989
+ }
990
+ }
991
+ if (e.target.nodeId === nodeId) {
992
+ const resolved = this.resolvedByNode.get(nodeId);
993
+ if (resolved) {
994
+ dstDeclared = getInputTypeId(resolved.inputs, e.target.handle);
995
+ e.dstDeclared = dstDeclared;
996
+ }
997
+ }
998
+ const conv = GraphRuntime.buildEdgeConverters(srcDeclared, dstDeclared, registry);
999
+ e.convert = conv.convert;
1000
+ e.convertAsync = conv.convertAsync;
1001
+ }
1002
+ // Invalidate downstream for this node so UI refreshes
1003
+ this.invalidateDownstream(nodeId);
1004
+ }
952
1005
  launch() {
953
1006
  // call onActivated for nodes that implement it
954
1007
  for (const node of this.nodes.values()) {
@@ -1241,42 +1294,10 @@ class GraphRuntime {
1241
1294
  tmap.set(e.source.handle, tset);
1242
1295
  prevOutTargets.set(e.source.nodeId, tmap);
1243
1296
  }
1297
+ // Precompute per-node resolved handles for updated graph
1298
+ this.resolvedByNode = GraphRuntime.computeResolvedHandleMap(def, registry);
1244
1299
  // Rebuild edges mapping with coercions
1245
- this.edges = def.edges.map((e) => {
1246
- const srcNode = def.nodes.find((nn) => nn.nodeId === e.source.nodeId);
1247
- const dstNode = def.nodes.find((nn) => nn.nodeId === e.target.nodeId);
1248
- let effectiveTypeId = e.typeId;
1249
- let srcDeclared;
1250
- let dstDeclared;
1251
- if (srcNode) {
1252
- const srcDesc = registry.nodes.get(srcNode.typeId);
1253
- if (srcDesc) {
1254
- srcDeclared = srcDesc.outputs[e.source.handle];
1255
- }
1256
- }
1257
- if (!effectiveTypeId) {
1258
- effectiveTypeId = Array.isArray(srcDeclared)
1259
- ? srcDeclared[0]
1260
- : srcDeclared;
1261
- }
1262
- if (dstNode) {
1263
- const dstDesc = registry.nodes.get(dstNode.typeId);
1264
- if (dstDesc) {
1265
- dstDeclared = getInputTypeId(dstDesc.inputs, e.target.handle);
1266
- }
1267
- }
1268
- const { convert, convertAsync } = GraphRuntime.buildEdgeConverters(srcDeclared, dstDeclared, registry);
1269
- return {
1270
- id: e.id,
1271
- source: { ...e.source },
1272
- target: { ...e.target },
1273
- typeId: effectiveTypeId ?? "untyped",
1274
- convert,
1275
- convertAsync,
1276
- dstDeclared,
1277
- stats: { runs: 0, inFlight: false, progress: 0 },
1278
- };
1279
- });
1300
+ this.edges = GraphRuntime.buildEdges(def, registry, this.resolvedByNode);
1280
1301
  // Build new inbound map
1281
1302
  const nextInbound = new Map();
1282
1303
  for (const e of this.edges) {
@@ -2643,6 +2664,31 @@ function setupBasicGraphRegistry() {
2643
2664
  return { Result: arr.slice(s, e) };
2644
2665
  },
2645
2666
  });
2667
+ // Compose array from dynamic item inputs
2668
+ registry.registerNode({
2669
+ id: "base.array.compose",
2670
+ categoryId: "compute",
2671
+ inputs: { Length: "base.float" },
2672
+ outputs: { Items: "base.object" },
2673
+ resolveHandles: ({ inputs }) => {
2674
+ const maxLen = 64;
2675
+ const raw = inputs?.Length ?? 0;
2676
+ const n = Math.max(0, Math.min(maxLen, Math.trunc(Number(raw ?? 0))));
2677
+ if (!Number.isFinite(n))
2678
+ return { inputs: {} };
2679
+ const dyn = {};
2680
+ for (let i = 0; i < n; i++)
2681
+ dyn[`Item${i}`] = { typeId: "base.object" };
2682
+ return { inputs: dyn };
2683
+ },
2684
+ inputDefaults: { Length: 0 },
2685
+ impl: (ins) => {
2686
+ const length = Math.max(0, Math.trunc(Number(ins.Length ?? 0)));
2687
+ if (!Number.isFinite(length))
2688
+ return { Items: [] };
2689
+ return { Items: Array.from({ length }, (_, i) => ins[`Item${i}`]) };
2690
+ },
2691
+ });
2646
2692
  // Select
2647
2693
  registry.registerNode({
2648
2694
  id: "base.select",