@bian-womp/spark-graph 0.2.21 → 0.2.23
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 +194 -15
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/categories.d.ts +2 -13
- package/lib/cjs/src/core/categories.d.ts.map +1 -1
- package/lib/cjs/src/core/types.d.ts +16 -6
- package/lib/cjs/src/core/types.d.ts.map +1 -1
- package/lib/cjs/src/examples/arrays.d.ts +5 -0
- package/lib/cjs/src/examples/arrays.d.ts.map +1 -0
- package/lib/cjs/src/examples/run.d.ts.map +1 -1
- package/lib/cjs/src/examples/shared.d.ts.map +1 -1
- package/lib/cjs/src/index.d.ts +2 -1
- package/lib/cjs/src/index.d.ts.map +1 -1
- package/lib/cjs/src/plugins/composite.d.ts.map +1 -1
- package/lib/cjs/src/runtime/GraphRuntime.d.ts +6 -1
- package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/esm/index.js +194 -16
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/categories.d.ts +2 -13
- package/lib/esm/src/core/categories.d.ts.map +1 -1
- package/lib/esm/src/core/types.d.ts +16 -6
- package/lib/esm/src/core/types.d.ts.map +1 -1
- package/lib/esm/src/examples/arrays.d.ts +5 -0
- package/lib/esm/src/examples/arrays.d.ts.map +1 -0
- package/lib/esm/src/examples/run.d.ts.map +1 -1
- package/lib/esm/src/examples/shared.d.ts.map +1 -1
- package/lib/esm/src/index.d.ts +2 -1
- package/lib/esm/src/index.d.ts.map +1 -1
- package/lib/esm/src/plugins/composite.d.ts.map +1 -1
- package/lib/esm/src/runtime/GraphRuntime.d.ts +6 -1
- package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/package.json +2 -2
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.
|
|
@@ -434,9 +443,11 @@ class GraphRuntime {
|
|
|
434
443
|
}
|
|
435
444
|
static create(def, registry, opts) {
|
|
436
445
|
const gr = new GraphRuntime();
|
|
446
|
+
gr.registry = registry;
|
|
437
447
|
gr.environment = opts?.environment ?? {};
|
|
438
448
|
// Precompute per-node resolved handles (use def-provided overrides; do not compute dynamically here)
|
|
439
|
-
|
|
449
|
+
const initial = GraphRuntime.computeResolvedHandleMap(def, registry, gr.environment);
|
|
450
|
+
gr.resolvedByNode = initial.map;
|
|
440
451
|
// Instantiate nodes
|
|
441
452
|
for (const n of def.nodes) {
|
|
442
453
|
const desc = registry.nodes.get(n.typeId);
|
|
@@ -462,8 +473,10 @@ class GraphRuntime {
|
|
|
462
473
|
params: n.params,
|
|
463
474
|
policy: {
|
|
464
475
|
...cat.policy,
|
|
465
|
-
...
|
|
476
|
+
...desc.policy,
|
|
477
|
+
...n.params?.policy,
|
|
466
478
|
},
|
|
479
|
+
runSeq: 0,
|
|
467
480
|
activeControllers: new Set(),
|
|
468
481
|
queue: [],
|
|
469
482
|
stats: {
|
|
@@ -507,6 +520,9 @@ class GraphRuntime {
|
|
|
507
520
|
: JSON.parse(JSON.stringify(value));
|
|
508
521
|
}
|
|
509
522
|
}
|
|
523
|
+
// Schedule async recompute only for nodes that indicated Promise-based resolveHandles
|
|
524
|
+
for (const nodeId of initial.pending)
|
|
525
|
+
gr.scheduleRecomputeHandles(nodeId);
|
|
510
526
|
return gr;
|
|
511
527
|
}
|
|
512
528
|
on(event, handler) {
|
|
@@ -547,6 +563,9 @@ class GraphRuntime {
|
|
|
547
563
|
// Only schedule if all inbound inputs are present (or there are none)
|
|
548
564
|
if (anyChanged && this.allInboundHaveValue(nodeId))
|
|
549
565
|
this.scheduleInputsChanged(nodeId);
|
|
566
|
+
// Recompute dynamic handles for this node when its direct inputs change
|
|
567
|
+
if (anyChanged)
|
|
568
|
+
this.scheduleRecomputeHandles(nodeId);
|
|
550
569
|
}
|
|
551
570
|
}
|
|
552
571
|
getOutput(nodeId, output) {
|
|
@@ -619,12 +638,14 @@ class GraphRuntime {
|
|
|
619
638
|
now - node.lastScheduledAt < policy.debounceMs) {
|
|
620
639
|
// debounce: replace latest queued
|
|
621
640
|
node.queue.splice(0, node.queue.length);
|
|
622
|
-
|
|
641
|
+
node.runSeq += 1;
|
|
642
|
+
const rid = `${nodeId}:${node.runSeq}:${now}`;
|
|
623
643
|
node.queue.push({ runId: rid, inputs: { ...node.inputs } });
|
|
624
644
|
return;
|
|
625
645
|
}
|
|
626
646
|
node.lastScheduledAt = now;
|
|
627
|
-
|
|
647
|
+
node.runSeq += 1;
|
|
648
|
+
const rid = `${nodeId}:${node.runSeq}:${now}`;
|
|
628
649
|
node.latestRunId = rid;
|
|
629
650
|
const startRun = (runId, capturedInputs, onDone) => {
|
|
630
651
|
const controller = new AbortController();
|
|
@@ -847,6 +868,8 @@ class GraphRuntime {
|
|
|
847
868
|
io: "input",
|
|
848
869
|
runtimeTypeId: getTypedOutputTypeId(next),
|
|
849
870
|
});
|
|
871
|
+
// Recompute dynamic handles for the destination node on input change
|
|
872
|
+
this.scheduleRecomputeHandles(e.target.nodeId);
|
|
850
873
|
if (!this.paused && this.allInboundHaveValue(e.target.nodeId))
|
|
851
874
|
this.scheduleInputsChanged(e.target.nodeId);
|
|
852
875
|
}
|
|
@@ -904,8 +927,9 @@ class GraphRuntime {
|
|
|
904
927
|
}
|
|
905
928
|
}
|
|
906
929
|
// Helper: build map of resolved handles per node from def (prefer def.resolvedHandles, otherwise registry statics)
|
|
907
|
-
static computeResolvedHandleMap(def, registry) {
|
|
930
|
+
static computeResolvedHandleMap(def, registry, environment) {
|
|
908
931
|
const out = new Map();
|
|
932
|
+
const pending = new Set();
|
|
909
933
|
for (const n of def.nodes) {
|
|
910
934
|
const desc = registry.nodes.get(n.typeId);
|
|
911
935
|
if (!desc)
|
|
@@ -913,13 +937,47 @@ class GraphRuntime {
|
|
|
913
937
|
const overrideInputs = n.resolvedHandles?.inputs;
|
|
914
938
|
const overrideOutputs = n.resolvedHandles?.outputs;
|
|
915
939
|
const overrideDefaults = n.resolvedHandles?.inputDefaults;
|
|
916
|
-
//
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
940
|
+
// Resolve dynamic handles if available (initial pass: inputs may be undefined)
|
|
941
|
+
let dyn = {};
|
|
942
|
+
try {
|
|
943
|
+
if (typeof desc.resolveHandles === "function") {
|
|
944
|
+
const maybe = desc.resolveHandles({
|
|
945
|
+
environment: environment || {},
|
|
946
|
+
params: n.params,
|
|
947
|
+
inputs: undefined,
|
|
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
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
catch {
|
|
960
|
+
// ignore dynamic resolution errors at this stage
|
|
961
|
+
}
|
|
962
|
+
// Merge base with dynamic and overrides (allow partial resolvedHandles)
|
|
963
|
+
const inputs = {
|
|
964
|
+
...desc.inputs,
|
|
965
|
+
...dyn.inputs,
|
|
966
|
+
...overrideInputs,
|
|
967
|
+
};
|
|
968
|
+
const outputs = {
|
|
969
|
+
...desc.outputs,
|
|
970
|
+
...dyn.outputs,
|
|
971
|
+
...overrideOutputs,
|
|
972
|
+
};
|
|
973
|
+
const inputDefaults = {
|
|
974
|
+
...desc.inputDefaults,
|
|
975
|
+
...dyn.inputDefaults,
|
|
976
|
+
...overrideDefaults,
|
|
977
|
+
};
|
|
920
978
|
out.set(n.nodeId, { inputs, outputs, inputDefaults });
|
|
921
979
|
}
|
|
922
|
-
return out;
|
|
980
|
+
return { map: out, pending };
|
|
923
981
|
}
|
|
924
982
|
// Helper: build runtime edges with coercions using resolved handles
|
|
925
983
|
static buildEdges(def, registry, resolvedByNode) {
|
|
@@ -1098,6 +1156,10 @@ class GraphRuntime {
|
|
|
1098
1156
|
}
|
|
1099
1157
|
setEnvironment(env) {
|
|
1100
1158
|
this.environment = { ...env };
|
|
1159
|
+
// Recompute dynamic handles for all nodes when environment changes
|
|
1160
|
+
for (const nodeId of this.nodes.keys()) {
|
|
1161
|
+
this.scheduleRecomputeHandles(nodeId);
|
|
1162
|
+
}
|
|
1101
1163
|
}
|
|
1102
1164
|
// Export a GraphDefinition reflecting the current runtime view
|
|
1103
1165
|
getGraphDef() {
|
|
@@ -1254,8 +1316,10 @@ class GraphRuntime {
|
|
|
1254
1316
|
params: n.params,
|
|
1255
1317
|
policy: {
|
|
1256
1318
|
...cat.policy,
|
|
1257
|
-
...
|
|
1319
|
+
...desc.policy,
|
|
1320
|
+
...n.params?.policy,
|
|
1258
1321
|
},
|
|
1322
|
+
runSeq: 0,
|
|
1259
1323
|
activeControllers: new Set(),
|
|
1260
1324
|
queue: [],
|
|
1261
1325
|
stats: {
|
|
@@ -1314,8 +1378,9 @@ class GraphRuntime {
|
|
|
1314
1378
|
tmap.set(e.source.handle, tset);
|
|
1315
1379
|
prevOutTargets.set(e.source.nodeId, tmap);
|
|
1316
1380
|
}
|
|
1317
|
-
// Precompute per-node resolved handles for updated graph
|
|
1318
|
-
|
|
1381
|
+
// Precompute per-node resolved handles for updated graph (include dynamic)
|
|
1382
|
+
const resolved = GraphRuntime.computeResolvedHandleMap(def, registry, this.environment);
|
|
1383
|
+
this.resolvedByNode = resolved.map;
|
|
1319
1384
|
// Rebuild edges mapping with coercions
|
|
1320
1385
|
this.edges = GraphRuntime.buildEdges(def, registry, this.resolvedByNode);
|
|
1321
1386
|
// Build new inbound map
|
|
@@ -1474,6 +1539,87 @@ class GraphRuntime {
|
|
|
1474
1539
|
if (byHandle.size === 0)
|
|
1475
1540
|
this.arrayInputBuckets.delete(nodeId);
|
|
1476
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);
|
|
1545
|
+
}
|
|
1546
|
+
// Schedule a recomputation of dynamic handles for a node (async to avoid mutating during propagation)
|
|
1547
|
+
scheduleRecomputeHandles(nodeId) {
|
|
1548
|
+
// If no registry or node not found, skip
|
|
1549
|
+
if (!this.registry)
|
|
1550
|
+
return;
|
|
1551
|
+
const node = this.nodes.get(nodeId);
|
|
1552
|
+
if (!node)
|
|
1553
|
+
return;
|
|
1554
|
+
setTimeout(() => {
|
|
1555
|
+
void this.recomputeHandlesForNode(nodeId);
|
|
1556
|
+
}, 0);
|
|
1557
|
+
}
|
|
1558
|
+
// Recompute dynamic handles for a single node using current inputs/environment
|
|
1559
|
+
async recomputeHandlesForNode(nodeId) {
|
|
1560
|
+
const registry = this.registry;
|
|
1561
|
+
const node = this.nodes.get(nodeId);
|
|
1562
|
+
if (!node)
|
|
1563
|
+
return;
|
|
1564
|
+
const desc = registry.nodes.get(node.typeId);
|
|
1565
|
+
if (!desc)
|
|
1566
|
+
return;
|
|
1567
|
+
const resolveHandles = desc.resolveHandles;
|
|
1568
|
+
if (typeof resolveHandles !== "function")
|
|
1569
|
+
return;
|
|
1570
|
+
const token = (this.recomputeTokenByNode.get(nodeId) ?? 0) + 1;
|
|
1571
|
+
this.recomputeTokenByNode.set(nodeId, token);
|
|
1572
|
+
let r;
|
|
1573
|
+
try {
|
|
1574
|
+
const res = resolveHandles({
|
|
1575
|
+
environment: this.environment || {},
|
|
1576
|
+
params: node.params,
|
|
1577
|
+
inputs: node.inputs || {},
|
|
1578
|
+
});
|
|
1579
|
+
r = await unwrapMaybePromise(res);
|
|
1580
|
+
}
|
|
1581
|
+
catch {
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
// If a newer recompute was scheduled, drop this result
|
|
1585
|
+
if ((this.recomputeTokenByNode.get(nodeId) ?? 0) !== token)
|
|
1586
|
+
return;
|
|
1587
|
+
const inputs = { ...desc.inputs, ...r?.inputs };
|
|
1588
|
+
const outputs = { ...desc.outputs, ...r?.outputs };
|
|
1589
|
+
const inputDefaults = { ...desc.inputDefaults, ...r?.inputDefaults };
|
|
1590
|
+
const next = { inputs, outputs, inputDefaults };
|
|
1591
|
+
const before = this.resolvedByNode.get(nodeId);
|
|
1592
|
+
// Compare shallow-structurally via JSON
|
|
1593
|
+
if (JSON.stringify(before) === JSON.stringify(next))
|
|
1594
|
+
return;
|
|
1595
|
+
this.resolvedByNode.set(nodeId, next);
|
|
1596
|
+
this.updateNodeHandles(nodeId, next, registry);
|
|
1597
|
+
// Seed defaults for newly introduced inputs that are not inbound
|
|
1598
|
+
const inbound = this.edges
|
|
1599
|
+
.filter((e) => e.target.nodeId === nodeId)
|
|
1600
|
+
.map((e) => e.target.handle);
|
|
1601
|
+
for (const [handle, value] of Object.entries(inputDefaults)) {
|
|
1602
|
+
if (value === undefined)
|
|
1603
|
+
continue;
|
|
1604
|
+
if (inbound.includes(handle))
|
|
1605
|
+
continue;
|
|
1606
|
+
if (node.inputs[handle] === undefined) {
|
|
1607
|
+
node.inputs[handle] =
|
|
1608
|
+
typeof structuredClone === "function"
|
|
1609
|
+
? structuredClone(value)
|
|
1610
|
+
: JSON.parse(JSON.stringify(value));
|
|
1611
|
+
// Emit input value event for seeded defaults
|
|
1612
|
+
this.emit("value", {
|
|
1613
|
+
nodeId,
|
|
1614
|
+
handle,
|
|
1615
|
+
value: node.inputs[handle],
|
|
1616
|
+
io: "input",
|
|
1617
|
+
runtimeTypeId: getTypedOutputTypeId(node.inputs[handle]),
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
// Notify graph updated for UI parity
|
|
1622
|
+
this.emit("invalidate", { reason: "graph-updated" });
|
|
1477
1623
|
}
|
|
1478
1624
|
}
|
|
1479
1625
|
|
|
@@ -1950,7 +2096,7 @@ const ComputeCategory = {
|
|
|
1950
2096
|
}
|
|
1951
2097
|
},
|
|
1952
2098
|
}),
|
|
1953
|
-
policy: {
|
|
2099
|
+
policy: { asyncConcurrency: "switch" },
|
|
1954
2100
|
};
|
|
1955
2101
|
|
|
1956
2102
|
const CompositeCategory = (registry) => ({
|
|
@@ -2006,7 +2152,6 @@ const CompositeCategory = (registry) => ({
|
|
|
2006
2152
|
},
|
|
2007
2153
|
};
|
|
2008
2154
|
},
|
|
2009
|
-
policy: { mode: "hybrid" },
|
|
2010
2155
|
});
|
|
2011
2156
|
|
|
2012
2157
|
// Helpers
|
|
@@ -3100,6 +3245,39 @@ function registerProgressNodes(registry) {
|
|
|
3100
3245
|
});
|
|
3101
3246
|
}
|
|
3102
3247
|
|
|
3248
|
+
function installLogging(engine) {
|
|
3249
|
+
engine.on("value", (e) => {
|
|
3250
|
+
const t = e.runtimeTypeId ? ` <${e.runtimeTypeId}>` : "";
|
|
3251
|
+
console.log(`[value:${e.io}]`, `${e.nodeId}.${e.handle}`, e.value, t);
|
|
3252
|
+
});
|
|
3253
|
+
engine.on("stats", (s) => {
|
|
3254
|
+
if (s.kind === "node-progress") {
|
|
3255
|
+
const pct = Math.round((s.progress ?? 0) * 100);
|
|
3256
|
+
console.log(`[progress] ${s.runId || s.nodeId}: ${pct}%`);
|
|
3257
|
+
}
|
|
3258
|
+
else if (s.kind === "node-done") {
|
|
3259
|
+
console.log(`[done] ${s.runId || s.nodeId} in ${s.durationMs ?? 0}ms`);
|
|
3260
|
+
}
|
|
3261
|
+
else if (s.kind === "node-start") {
|
|
3262
|
+
console.log(`[start] ${s.runId || s.nodeId}`);
|
|
3263
|
+
}
|
|
3264
|
+
else if (s.kind === "edge-start") {
|
|
3265
|
+
console.log(`[edge] ${s.source.nodeId}.${s.source.handle} -> ${s.target.nodeId}.${s.target.handle}`);
|
|
3266
|
+
}
|
|
3267
|
+
else if (s.kind === "edge-done") {
|
|
3268
|
+
console.log(`[edge] ${s.source.nodeId}.${s.source.handle} -> ${s.target.nodeId}.${s.target.handle} in ${s.durationMs ?? 0}ms`);
|
|
3269
|
+
}
|
|
3270
|
+
});
|
|
3271
|
+
engine.on("error", (e) => {
|
|
3272
|
+
if (e.kind === "node-run") {
|
|
3273
|
+
console.warn(`[error] ${e.runId || e.nodeId}`, e.err?.message ?? e.err);
|
|
3274
|
+
}
|
|
3275
|
+
else if (e.kind === "edge-convert") {
|
|
3276
|
+
console.warn(`[error] ${e.edgeId} ${e.source.nodeId}.${e.source.handle} -> ${e.target.nodeId}.${e.target.handle}`, e.err?.message ?? e.err);
|
|
3277
|
+
}
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3103
3281
|
function makeBasicGraphDefinition() {
|
|
3104
3282
|
return {
|
|
3105
3283
|
nodes: [
|
|
@@ -3303,6 +3481,7 @@ exports.createValidationGraphRegistry = createValidationGraphRegistry;
|
|
|
3303
3481
|
exports.getInputTypeId = getInputTypeId;
|
|
3304
3482
|
exports.getTypedOutputTypeId = getTypedOutputTypeId;
|
|
3305
3483
|
exports.getTypedOutputValue = getTypedOutputValue;
|
|
3484
|
+
exports.installLogging = installLogging;
|
|
3306
3485
|
exports.isInputPrivate = isInputPrivate;
|
|
3307
3486
|
exports.isTypedOutput = isTypedOutput;
|
|
3308
3487
|
exports.registerDelayNode = registerDelayNode;
|