@bian-womp/spark-graph 0.3.14 → 0.3.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 +150 -15
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/builder/GraphBuilder.d.ts.map +1 -1
- package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/EdgePropagator.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/HandleResolver.d.ts +15 -1
- package/lib/cjs/src/runtime/components/HandleResolver.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts +3 -2
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/interfaces.d.ts +3 -0
- package/lib/cjs/src/runtime/components/interfaces.d.ts.map +1 -1
- package/lib/esm/index.js +150 -15
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/builder/GraphBuilder.d.ts.map +1 -1
- package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/EdgePropagator.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/HandleResolver.d.ts +15 -1
- package/lib/esm/src/runtime/components/HandleResolver.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts +3 -2
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/interfaces.d.ts +3 -0
- package/lib/esm/src/runtime/components/interfaces.d.ts.map +1 -1
- package/package.json +2 -2
package/lib/cjs/index.cjs
CHANGED
|
@@ -1334,6 +1334,8 @@ class HandleResolver {
|
|
|
1334
1334
|
this.registry = registry;
|
|
1335
1335
|
this.recomputeTokenByNode = new Map();
|
|
1336
1336
|
this.environment = {};
|
|
1337
|
+
this.pendingResolutions = new Map();
|
|
1338
|
+
this.pendingResolutionRunContexts = new Map();
|
|
1337
1339
|
this.environment = environment ?? {};
|
|
1338
1340
|
}
|
|
1339
1341
|
setRegistry(registry) {
|
|
@@ -1342,6 +1344,33 @@ class HandleResolver {
|
|
|
1342
1344
|
setEnvironment(environment) {
|
|
1343
1345
|
this.environment = environment;
|
|
1344
1346
|
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Check if handle resolution is pending for a node
|
|
1349
|
+
*/
|
|
1350
|
+
isResolvingHandles(nodeId) {
|
|
1351
|
+
return this.pendingResolutions.has(nodeId);
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Get the promise for pending handle resolution, or null if none
|
|
1355
|
+
*/
|
|
1356
|
+
getPendingResolution(nodeId) {
|
|
1357
|
+
return this.pendingResolutions.get(nodeId) || null;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Track additional run contexts for a pending resolution
|
|
1361
|
+
*/
|
|
1362
|
+
trackRunContextsForPendingResolution(nodeId, runContextIds) {
|
|
1363
|
+
if (!this.pendingResolutions.has(nodeId))
|
|
1364
|
+
return;
|
|
1365
|
+
const tracked = this.pendingResolutionRunContexts.get(nodeId) ?? new Set();
|
|
1366
|
+
for (const runContextId of runContextIds) {
|
|
1367
|
+
if (!tracked.has(runContextId)) {
|
|
1368
|
+
this.runContextManager.startHandleResolution(runContextId, nodeId);
|
|
1369
|
+
tracked.add(runContextId);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
this.pendingResolutionRunContexts.set(nodeId, tracked);
|
|
1373
|
+
}
|
|
1345
1374
|
/**
|
|
1346
1375
|
* Schedule async recomputation of handles for a node
|
|
1347
1376
|
*/
|
|
@@ -1354,14 +1383,25 @@ class HandleResolver {
|
|
|
1354
1383
|
return;
|
|
1355
1384
|
// Track resolver start for all active run-contexts
|
|
1356
1385
|
const activeRunContextIds = this.graph.getNodeRunContextIds(nodeId);
|
|
1386
|
+
const trackedRunContextIds = new Set(activeRunContextIds);
|
|
1357
1387
|
if (activeRunContextIds.size > 0) {
|
|
1358
1388
|
for (const runContextId of activeRunContextIds) {
|
|
1359
1389
|
this.runContextManager.startHandleResolution(runContextId, nodeId);
|
|
1360
1390
|
}
|
|
1361
1391
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1392
|
+
// Create and track the resolution promise
|
|
1393
|
+
const resolutionPromise = new Promise((resolve) => {
|
|
1394
|
+
setTimeout(async () => {
|
|
1395
|
+
// Get all tracked run contexts (including any added during pending state)
|
|
1396
|
+
const allTracked = this.pendingResolutionRunContexts.get(nodeId) ?? trackedRunContextIds;
|
|
1397
|
+
await this.recomputeHandlesForNode(nodeId, allTracked.size > 0 ? allTracked : undefined);
|
|
1398
|
+
this.pendingResolutions.delete(nodeId);
|
|
1399
|
+
this.pendingResolutionRunContexts.delete(nodeId);
|
|
1400
|
+
resolve();
|
|
1401
|
+
}, 0);
|
|
1402
|
+
});
|
|
1403
|
+
this.pendingResolutions.set(nodeId, resolutionPromise);
|
|
1404
|
+
this.pendingResolutionRunContexts.set(nodeId, trackedRunContextIds);
|
|
1365
1405
|
}
|
|
1366
1406
|
// Update resolved handles for a single node and refresh edge converters/types that touch it
|
|
1367
1407
|
updateNodeHandles(nodeId, handles) {
|
|
@@ -1906,10 +1946,11 @@ class EdgePropagator {
|
|
|
1906
1946
|
* NodeExecutor component - handles node execution scheduling and lifecycle
|
|
1907
1947
|
*/
|
|
1908
1948
|
class NodeExecutor {
|
|
1909
|
-
constructor(graph, eventEmitter, runContextManager, edgePropagator, runtime, environment) {
|
|
1949
|
+
constructor(graph, eventEmitter, runContextManager, handleResolver, edgePropagator, runtime, environment) {
|
|
1910
1950
|
this.graph = graph;
|
|
1911
1951
|
this.eventEmitter = eventEmitter;
|
|
1912
1952
|
this.runContextManager = runContextManager;
|
|
1953
|
+
this.handleResolver = handleResolver;
|
|
1913
1954
|
this.edgePropagator = edgePropagator;
|
|
1914
1955
|
this.runtime = runtime;
|
|
1915
1956
|
this.environment = {};
|
|
@@ -2069,10 +2110,31 @@ class NodeExecutor {
|
|
|
2069
2110
|
// Early validation for auto-mode paused state
|
|
2070
2111
|
if (this.runtime.isPaused())
|
|
2071
2112
|
return;
|
|
2072
|
-
// Attach run-context IDs if provided
|
|
2113
|
+
// Attach run-context IDs if provided - do this BEFORE checking for pending resolution
|
|
2114
|
+
// so that handle resolution can track these run contexts
|
|
2073
2115
|
if (runContextIds) {
|
|
2074
2116
|
this.graph.addNodeRunContextIds(nodeId, runContextIds);
|
|
2075
2117
|
}
|
|
2118
|
+
// Check if handles are being resolved - wait for resolution before executing
|
|
2119
|
+
// Do this AFTER setting up run contexts so handle resolution can track them
|
|
2120
|
+
if (this.handleResolver && this.handleResolver.isResolvingHandles(nodeId)) {
|
|
2121
|
+
// Track run contexts for the pending resolution
|
|
2122
|
+
if (runContextIds && runContextIds.size > 0) {
|
|
2123
|
+
this.handleResolver.trackRunContextsForPendingResolution(nodeId, runContextIds);
|
|
2124
|
+
}
|
|
2125
|
+
const pendingResolution = this.handleResolver.getPendingResolution(nodeId);
|
|
2126
|
+
if (pendingResolution) {
|
|
2127
|
+
// Wait for resolution to complete, then re-execute
|
|
2128
|
+
pendingResolution.then(() => {
|
|
2129
|
+
// Re-check node still exists and conditions
|
|
2130
|
+
const nodeAfter = this.graph.getNode(nodeId);
|
|
2131
|
+
if (nodeAfter) {
|
|
2132
|
+
this.execute(nodeId, runContextIds);
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2076
2138
|
// Handle debouncing
|
|
2077
2139
|
const now = Date.now();
|
|
2078
2140
|
if (this.shouldDebounce(nodeId, node, now)) {
|
|
@@ -2515,8 +2577,8 @@ class GraphRuntime {
|
|
|
2515
2577
|
this.runContextManager = new RunContextManager(this.graph);
|
|
2516
2578
|
this.handleResolver = new HandleResolver(this.graph, this.eventEmitter, this.runContextManager, this);
|
|
2517
2579
|
this.edgePropagator = new EdgePropagator(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this);
|
|
2518
|
-
// Create NodeExecutor with EdgePropagator
|
|
2519
|
-
this.nodeExecutor = new NodeExecutor(this.graph, this.eventEmitter, this.runContextManager, this, this);
|
|
2580
|
+
// Create NodeExecutor with EdgePropagator and HandleResolver
|
|
2581
|
+
this.nodeExecutor = new NodeExecutor(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this, this);
|
|
2520
2582
|
}
|
|
2521
2583
|
static create(def, registry, opts) {
|
|
2522
2584
|
const gr = new GraphRuntime();
|
|
@@ -2829,19 +2891,30 @@ class GraphRuntime {
|
|
|
2829
2891
|
const releasePause = this.requestPause();
|
|
2830
2892
|
try {
|
|
2831
2893
|
const ins = payload?.inputs || {};
|
|
2894
|
+
const nodesWithChangedInputs = new Set();
|
|
2832
2895
|
for (const [nodeId, map] of Object.entries(ins)) {
|
|
2833
2896
|
if (!this.graph.hasNode(nodeId))
|
|
2834
2897
|
continue;
|
|
2898
|
+
let nodeChanged = false;
|
|
2835
2899
|
for (const [h, v] of Object.entries(map || {})) {
|
|
2900
|
+
const node = this.graph.getNode(nodeId);
|
|
2901
|
+
const prev = node?.inputs[h];
|
|
2836
2902
|
const clonedValue = structuredClone(v);
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
nodeId,
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2903
|
+
const same = valuesEqual(prev, clonedValue);
|
|
2904
|
+
if (!same) {
|
|
2905
|
+
this.graph.updateNodeInput(nodeId, h, clonedValue);
|
|
2906
|
+
this.eventEmitter.emit("value", {
|
|
2907
|
+
nodeId,
|
|
2908
|
+
handle: h,
|
|
2909
|
+
value: clonedValue,
|
|
2910
|
+
io: "input",
|
|
2911
|
+
runtimeTypeId: getTypedOutputTypeId(clonedValue),
|
|
2912
|
+
});
|
|
2913
|
+
nodeChanged = true;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
if (nodeChanged) {
|
|
2917
|
+
nodesWithChangedInputs.add(nodeId);
|
|
2845
2918
|
}
|
|
2846
2919
|
}
|
|
2847
2920
|
const outs = payload?.outputs || {};
|
|
@@ -2860,6 +2933,10 @@ class GraphRuntime {
|
|
|
2860
2933
|
});
|
|
2861
2934
|
}
|
|
2862
2935
|
}
|
|
2936
|
+
// Trigger handle resolution for nodes with changed inputs
|
|
2937
|
+
for (const nodeId of nodesWithChangedInputs) {
|
|
2938
|
+
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
2939
|
+
}
|
|
2863
2940
|
if (opts?.invalidate) {
|
|
2864
2941
|
for (const nodeId of this.graph.getNodeIds()) {
|
|
2865
2942
|
this.invalidateDownstream(nodeId);
|
|
@@ -3042,6 +3119,8 @@ class GraphRuntime {
|
|
|
3042
3119
|
}
|
|
3043
3120
|
if (changed) {
|
|
3044
3121
|
this.edgePropagator.clearArrayBuckets(nodeId);
|
|
3122
|
+
// Trigger handle resolution when inputs are removed
|
|
3123
|
+
this.handleResolver.scheduleRecomputeHandles(nodeId);
|
|
3045
3124
|
if (this.runMode === "auto" &&
|
|
3046
3125
|
this.graph.allInboundHaveValue(nodeId)) {
|
|
3047
3126
|
this.execute(nodeId);
|
|
@@ -3162,6 +3241,17 @@ class GraphBuilder {
|
|
|
3162
3241
|
const arr = Array.isArray(from) ? from : [from];
|
|
3163
3242
|
return arr.every((s) => s === to || !!this.registry.canCoerce(s, to));
|
|
3164
3243
|
};
|
|
3244
|
+
// Helper to validate enum value
|
|
3245
|
+
const validateEnumValue = (typeId, value, nodeId, handle) => {
|
|
3246
|
+
if (!typeId.startsWith("enum:"))
|
|
3247
|
+
return true; // Not an enum type
|
|
3248
|
+
const enumDef = this.registry.enums.get(typeId);
|
|
3249
|
+
if (!enumDef)
|
|
3250
|
+
return true; // Enum not registered, skip validation
|
|
3251
|
+
if (typeof value !== "number")
|
|
3252
|
+
return false; // Enum values must be numbers
|
|
3253
|
+
return enumDef.valueToLabel.has(value);
|
|
3254
|
+
};
|
|
3165
3255
|
const pushIssue = (level, code, message, data) => {
|
|
3166
3256
|
issues.push({ level, code, message, data });
|
|
3167
3257
|
};
|
|
@@ -3185,6 +3275,51 @@ class GraphBuilder {
|
|
|
3185
3275
|
if (!this.registry.categories.has(nodeType.categoryId)) {
|
|
3186
3276
|
pushIssue("error", "CATEGORY_MISSING", `Unknown category ${nodeType.categoryId} for node type ${n.typeId}`);
|
|
3187
3277
|
}
|
|
3278
|
+
// Validate enum values in node params
|
|
3279
|
+
if (n.params) {
|
|
3280
|
+
const effectiveHandles = getEffectiveHandles(n);
|
|
3281
|
+
for (const [paramKey, paramValue] of Object.entries(n.params)) {
|
|
3282
|
+
// Skip policy and other non-input params
|
|
3283
|
+
if (paramKey === "policy")
|
|
3284
|
+
continue;
|
|
3285
|
+
// Check if this param corresponds to an input handle
|
|
3286
|
+
const inputTypeId = getInputTypeId(effectiveHandles.inputs, paramKey);
|
|
3287
|
+
if (inputTypeId && inputTypeId.startsWith("enum:")) {
|
|
3288
|
+
if (!validateEnumValue(inputTypeId, paramValue, n.nodeId)) {
|
|
3289
|
+
const enumDef = this.registry.enums.get(inputTypeId);
|
|
3290
|
+
const validValues = enumDef
|
|
3291
|
+
? Array.from(enumDef.valueToLabel.keys()).join(", ")
|
|
3292
|
+
: "unknown";
|
|
3293
|
+
pushIssue("error", "ENUM_VALUE_INVALID", `Node ${n.nodeId} param ${paramKey} has invalid enum value ${paramValue}. Valid values: ${validValues}`, {
|
|
3294
|
+
nodeId: n.nodeId,
|
|
3295
|
+
input: paramKey,
|
|
3296
|
+
typeId: inputTypeId,
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
// Validate enum values in input defaults
|
|
3303
|
+
const resolved = n.resolvedHandles;
|
|
3304
|
+
if (resolved?.inputDefaults) {
|
|
3305
|
+
const effectiveHandles = getEffectiveHandles(n);
|
|
3306
|
+
for (const [handle, defaultValue] of Object.entries(resolved.inputDefaults)) {
|
|
3307
|
+
const inputTypeId = getInputTypeId(effectiveHandles.inputs, handle);
|
|
3308
|
+
if (inputTypeId && inputTypeId.startsWith("enum:")) {
|
|
3309
|
+
if (!validateEnumValue(inputTypeId, defaultValue, n.nodeId)) {
|
|
3310
|
+
const enumDef = this.registry.enums.get(inputTypeId);
|
|
3311
|
+
const validValues = enumDef
|
|
3312
|
+
? Array.from(enumDef.valueToLabel.keys()).join(", ")
|
|
3313
|
+
: "unknown";
|
|
3314
|
+
pushIssue("warning", "ENUM_DEFAULT_INVALID", `Node ${n.nodeId} input default ${handle} has invalid enum value ${defaultValue}. Valid values: ${validValues}`, {
|
|
3315
|
+
nodeId: n.nodeId,
|
|
3316
|
+
input: handle,
|
|
3317
|
+
typeId: inputTypeId,
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3188
3323
|
}
|
|
3189
3324
|
// edges validation: nodes exist, handles exist, type exists
|
|
3190
3325
|
const inboundCounts = new Map();
|