@bian-womp/spark-graph 0.3.36 → 0.3.38
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 +171 -67
- 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/Graph.d.ts +4 -13
- package/lib/cjs/src/runtime/components/Graph.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts +5 -0
- package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/RunContextManager.d.ts +11 -0
- package/lib/cjs/src/runtime/components/RunContextManager.d.ts.map +1 -1
- package/lib/cjs/src/runtime/components/types.d.ts +13 -4
- package/lib/cjs/src/runtime/components/types.d.ts.map +1 -1
- package/lib/esm/index.js +171 -67
- 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/Graph.d.ts +4 -13
- package/lib/esm/src/runtime/components/Graph.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts +5 -0
- package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/RunContextManager.d.ts +11 -0
- package/lib/esm/src/runtime/components/RunContextManager.d.ts.map +1 -1
- package/lib/esm/src/runtime/components/types.d.ts +13 -4
- package/lib/esm/src/runtime/components/types.d.ts.map +1 -1
- package/package.json +2 -2
package/lib/cjs/index.cjs
CHANGED
|
@@ -1148,6 +1148,7 @@ class RunContextManager {
|
|
|
1148
1148
|
pendingNodes: 0,
|
|
1149
1149
|
pendingEdges: 0,
|
|
1150
1150
|
pendingResolvers: 0,
|
|
1151
|
+
pendingQueued: 0,
|
|
1151
1152
|
skipPropagateValues: options?.skipPropagateValues ?? false,
|
|
1152
1153
|
propagate: options?.propagate ?? true,
|
|
1153
1154
|
resolve,
|
|
@@ -1179,6 +1180,47 @@ class RunContextManager {
|
|
|
1179
1180
|
hasActiveRunContexts() {
|
|
1180
1181
|
return this.runContexts.size > 0;
|
|
1181
1182
|
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Increment queued work count for a run-context.
|
|
1185
|
+
* Used by asyncConcurrency: "queue" to keep contexts alive while work is queued.
|
|
1186
|
+
*/
|
|
1187
|
+
incrementQueued(id, nodeId) {
|
|
1188
|
+
const ctx = this.runContexts.get(id);
|
|
1189
|
+
if (!ctx) {
|
|
1190
|
+
this.logger.debug("increment-queued-context-not-found", {
|
|
1191
|
+
runContextId: id,
|
|
1192
|
+
nodeId,
|
|
1193
|
+
});
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
ctx.pendingQueued++;
|
|
1197
|
+
this.logger.debug("increment-queued", {
|
|
1198
|
+
runContextId: id,
|
|
1199
|
+
nodeId,
|
|
1200
|
+
pendingQueued: ctx.pendingQueued,
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Decrement queued work count for a run-context.
|
|
1205
|
+
* Called when queued work is either started or dropped.
|
|
1206
|
+
*/
|
|
1207
|
+
decrementQueued(id, nodeId) {
|
|
1208
|
+
const ctx = this.runContexts.get(id);
|
|
1209
|
+
if (!ctx) {
|
|
1210
|
+
this.logger.debug("decrement-queued-context-not-found", {
|
|
1211
|
+
runContextId: id,
|
|
1212
|
+
nodeId,
|
|
1213
|
+
});
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
ctx.pendingQueued--;
|
|
1217
|
+
this.logger.debug("decrement-queued", {
|
|
1218
|
+
runContextId: id,
|
|
1219
|
+
nodeId,
|
|
1220
|
+
pendingQueued: ctx.pendingQueued,
|
|
1221
|
+
});
|
|
1222
|
+
this.finishRunContextIfPossible(id);
|
|
1223
|
+
}
|
|
1182
1224
|
startNodeRun(id, nodeId) {
|
|
1183
1225
|
const ctx = this.runContexts.get(id);
|
|
1184
1226
|
if (!ctx) {
|
|
@@ -1291,7 +1333,8 @@ class RunContextManager {
|
|
|
1291
1333
|
}
|
|
1292
1334
|
if (ctx.pendingNodes > 0 ||
|
|
1293
1335
|
ctx.pendingEdges > 0 ||
|
|
1294
|
-
ctx.pendingResolvers > 0
|
|
1336
|
+
ctx.pendingResolvers > 0 ||
|
|
1337
|
+
ctx.pendingQueued > 0) {
|
|
1295
1338
|
return; // Still has pending work
|
|
1296
1339
|
}
|
|
1297
1340
|
this.logger.info("finish-run-context", {
|
|
@@ -2459,8 +2502,8 @@ class NodeExecutor {
|
|
|
2459
2502
|
}
|
|
2460
2503
|
// Handle debouncing
|
|
2461
2504
|
const now = Date.now();
|
|
2462
|
-
if (this.shouldDebounce(
|
|
2463
|
-
this.handleDebouncedSchedule(node, nodeId, now);
|
|
2505
|
+
if (this.shouldDebounce(node, now)) {
|
|
2506
|
+
this.handleDebouncedSchedule(node, nodeId, now, runContextIds);
|
|
2464
2507
|
return;
|
|
2465
2508
|
}
|
|
2466
2509
|
// Prepare execution plan
|
|
@@ -2471,7 +2514,7 @@ class NodeExecutor {
|
|
|
2471
2514
|
/**
|
|
2472
2515
|
* Check if execution should be debounced
|
|
2473
2516
|
*/
|
|
2474
|
-
shouldDebounce(
|
|
2517
|
+
shouldDebounce(node, now) {
|
|
2475
2518
|
const policy = node.policy ?? {};
|
|
2476
2519
|
const lastScheduledAt = node.lastScheduledAt;
|
|
2477
2520
|
return !!(policy.debounceMs &&
|
|
@@ -2481,13 +2524,23 @@ class NodeExecutor {
|
|
|
2481
2524
|
/**
|
|
2482
2525
|
* Handle debounced scheduling by replacing the latest queued item
|
|
2483
2526
|
*/
|
|
2484
|
-
handleDebouncedSchedule(node, nodeId, now) {
|
|
2527
|
+
handleDebouncedSchedule(node, nodeId, now, runContextIds) {
|
|
2528
|
+
// Decrement pendingQueued for any existing queued items before replacing
|
|
2529
|
+
if (node.queue.length > 0) {
|
|
2530
|
+
this.decrementQueuedForPlans(node.queue, nodeId);
|
|
2531
|
+
}
|
|
2485
2532
|
const effectiveInputs = this.getEffectiveInputs(nodeId);
|
|
2486
2533
|
const runSeq = this.graph.incrementNodeRunSeq(nodeId);
|
|
2487
|
-
const
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2534
|
+
const runId = `${nodeId}:${runSeq}:${now}`;
|
|
2535
|
+
const policySnapshot = node.policy ? { ...node.policy } : undefined;
|
|
2536
|
+
const plan = {
|
|
2537
|
+
runId,
|
|
2538
|
+
effectiveInputs,
|
|
2539
|
+
runContextIdsForRun: runContextIds,
|
|
2540
|
+
timestamp: now,
|
|
2541
|
+
policy: policySnapshot,
|
|
2542
|
+
};
|
|
2543
|
+
this.graph.replaceNodeQueue(nodeId, [plan]);
|
|
2491
2544
|
}
|
|
2492
2545
|
/**
|
|
2493
2546
|
* Prepare execution plan with all necessary information
|
|
@@ -2546,12 +2599,19 @@ class NodeExecutor {
|
|
|
2546
2599
|
const currentNode = this.graph.getNode(nodeId);
|
|
2547
2600
|
if (!currentNode)
|
|
2548
2601
|
return;
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2602
|
+
// Keep the originating run-context alive while work is queued by
|
|
2603
|
+
// incrementing queued counters for the plan's run-contexts.
|
|
2604
|
+
if (plan.runContextIdsForRun) {
|
|
2605
|
+
for (const rcId of plan.runContextIdsForRun) {
|
|
2606
|
+
this.runContextManager.incrementQueued(rcId, nodeId);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
this.graph.addToNodeQueue(nodeId, plan);
|
|
2553
2610
|
if (currentNode.queue.length > maxQ) {
|
|
2554
|
-
this.graph.shiftNodeQueue(nodeId);
|
|
2611
|
+
const dropped = this.graph.shiftNodeQueue(nodeId);
|
|
2612
|
+
if (dropped) {
|
|
2613
|
+
this.decrementQueuedForPlans(dropped, nodeId);
|
|
2614
|
+
}
|
|
2555
2615
|
}
|
|
2556
2616
|
this.processQueue(node, nodeId);
|
|
2557
2617
|
}
|
|
@@ -2569,18 +2629,12 @@ class NodeExecutor {
|
|
|
2569
2629
|
if (!next)
|
|
2570
2630
|
return;
|
|
2571
2631
|
this.graph.setNodeLatestRunId(nodeId, next.runId);
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
runId: next.runId,
|
|
2576
|
-
effectiveInputs: next.inputs,
|
|
2577
|
-
runContextIdsForRun: activeRunContextIds.size > 0 ? activeRunContextIds : undefined,
|
|
2578
|
-
timestamp: Date.now(),
|
|
2579
|
-
policy: policySnapshot,
|
|
2580
|
-
};
|
|
2581
|
-
this.startRun(node, nodeId, plan, () => {
|
|
2632
|
+
// Start the run first (which increments pendingNodes), then decrement
|
|
2633
|
+
// pendingQueued to ensure the run context stays alive.
|
|
2634
|
+
this.startRun(node, nodeId, next, () => {
|
|
2582
2635
|
setTimeout(processNext, 0);
|
|
2583
2636
|
});
|
|
2637
|
+
this.decrementQueuedForPlans(next, nodeId);
|
|
2584
2638
|
};
|
|
2585
2639
|
processNext();
|
|
2586
2640
|
}
|
|
@@ -2611,6 +2665,20 @@ class NodeExecutor {
|
|
|
2611
2665
|
}
|
|
2612
2666
|
}
|
|
2613
2667
|
}
|
|
2668
|
+
/**
|
|
2669
|
+
* Decrement pendingQueued counters for plans' run-contexts.
|
|
2670
|
+
* Used when queued work is being started, dropped, or cancelled.
|
|
2671
|
+
*/
|
|
2672
|
+
decrementQueuedForPlans(plans, nodeId) {
|
|
2673
|
+
const plansArray = Array.isArray(plans) ? plans : [plans];
|
|
2674
|
+
for (const plan of plansArray) {
|
|
2675
|
+
if (plan.runContextIdsForRun) {
|
|
2676
|
+
for (const rcId of plan.runContextIdsForRun) {
|
|
2677
|
+
this.runContextManager.decrementQueued(rcId, nodeId);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2614
2682
|
/**
|
|
2615
2683
|
* Create execution controller and update node stats
|
|
2616
2684
|
*/
|
|
@@ -2839,6 +2907,10 @@ class NodeExecutor {
|
|
|
2839
2907
|
}
|
|
2840
2908
|
this.graph.clearNodeControllers(nodeId);
|
|
2841
2909
|
this.graph.updateNodeStats(nodeId, { active: 0 });
|
|
2910
|
+
// Decrement pendingQueued for any queued items before clearing the queue
|
|
2911
|
+
if (node.queue.length > 0) {
|
|
2912
|
+
this.decrementQueuedForPlans(node.queue, nodeId);
|
|
2913
|
+
}
|
|
2842
2914
|
this.graph.clearNodeQueue(nodeId);
|
|
2843
2915
|
}
|
|
2844
2916
|
/**
|
|
@@ -3064,22 +3136,33 @@ class GraphRuntime {
|
|
|
3064
3136
|
if (value !== undefined && registry) {
|
|
3065
3137
|
const desc = registry.nodes.get(node.typeId);
|
|
3066
3138
|
const resolved = this.graph.getResolvedHandles(nodeId);
|
|
3067
|
-
// Get
|
|
3068
|
-
const
|
|
3069
|
-
?
|
|
3139
|
+
// Get declared types (may be union); prefer resolved handles over registry statics
|
|
3140
|
+
const declaredTypes = resolved
|
|
3141
|
+
? getInputDeclaredTypes(resolved.inputs, handle)
|
|
3070
3142
|
: desc
|
|
3071
|
-
?
|
|
3143
|
+
? getInputDeclaredTypes(desc.inputs, handle)
|
|
3072
3144
|
: undefined;
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3145
|
+
const typeIds = Array.isArray(declaredTypes)
|
|
3146
|
+
? declaredTypes
|
|
3147
|
+
: declaredTypes
|
|
3148
|
+
? [declaredTypes]
|
|
3149
|
+
: [];
|
|
3150
|
+
if (typeIds.length > 0) {
|
|
3151
|
+
const isValidForAny = typeIds.some((tId) => {
|
|
3152
|
+
const typeDesc = registry.types.get(tId);
|
|
3153
|
+
// If no validate function, consider it valid
|
|
3154
|
+
if (!typeDesc?.validate)
|
|
3155
|
+
return true;
|
|
3156
|
+
return typeDesc.validate(value);
|
|
3157
|
+
});
|
|
3158
|
+
if (!isValidForAny) {
|
|
3159
|
+
const typeLabel = typeIds.join("|");
|
|
3160
|
+
const errorMessage = `Invalid value for input ${nodeId}.${handle} (type ${typeLabel}): ${JSON.stringify(value)}`;
|
|
3078
3161
|
this.eventEmitter.emit("error", {
|
|
3079
3162
|
kind: "input-validation",
|
|
3080
3163
|
nodeId,
|
|
3081
3164
|
handle,
|
|
3082
|
-
typeId,
|
|
3165
|
+
typeId: typeLabel,
|
|
3083
3166
|
value,
|
|
3084
3167
|
message: errorMessage,
|
|
3085
3168
|
});
|
|
@@ -3669,11 +3752,30 @@ class GraphBuilder {
|
|
|
3669
3752
|
return { inputs: {}, outputs: {} };
|
|
3670
3753
|
const desc = this.registry.nodes.get(n.typeId);
|
|
3671
3754
|
const resolved = n.resolvedHandles || {};
|
|
3672
|
-
|
|
3755
|
+
// Merge inputs properly, handling union types and metadata
|
|
3756
|
+
const inputs = {};
|
|
3757
|
+
// First, add all static handles
|
|
3758
|
+
if (desc?.inputs) {
|
|
3759
|
+
for (const [handle, staticDesc] of Object.entries(desc.inputs)) {
|
|
3760
|
+
inputs[handle] = staticDesc;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
// Then, merge resolved handles (which may override/extend static handles)
|
|
3764
|
+
if (resolved.inputs) {
|
|
3765
|
+
for (const [handle, resolvedDesc] of Object.entries(resolved.inputs)) {
|
|
3766
|
+
const staticDesc = desc?.inputs?.[handle];
|
|
3767
|
+
const merged = mergeInputHandleDescriptors(staticDesc, resolvedDesc);
|
|
3768
|
+
if (merged) {
|
|
3769
|
+
inputs[handle] = merged;
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3673
3773
|
const outputs = { ...desc?.outputs, ...resolved.outputs };
|
|
3674
3774
|
return { inputs, outputs };
|
|
3675
3775
|
};
|
|
3676
3776
|
const normOut = (decl) => Array.isArray(decl) ? decl : decl ? [decl] : [];
|
|
3777
|
+
const getEnumTypes = (decl) => normOut(decl).filter((t) => t.startsWith("enum:"));
|
|
3778
|
+
const hasArrayType = (decl) => normOut(decl).some((t) => t.endsWith("[]"));
|
|
3677
3779
|
const canFlow = (from, to) => {
|
|
3678
3780
|
if (!to || !from)
|
|
3679
3781
|
return true;
|
|
@@ -3723,19 +3825,20 @@ class GraphBuilder {
|
|
|
3723
3825
|
if (paramKey === "policy")
|
|
3724
3826
|
continue;
|
|
3725
3827
|
// Check if this param corresponds to an input handle
|
|
3726
|
-
const
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3828
|
+
const declaredTypes = getInputDeclaredTypes(effectiveHandles.inputs, paramKey);
|
|
3829
|
+
const enumTypes = getEnumTypes(declaredTypes);
|
|
3830
|
+
if (enumTypes.length > 0 &&
|
|
3831
|
+
typeof paramValue === "number" &&
|
|
3832
|
+
!enumTypes.some((et) => validateEnumValue(et, paramValue, n.nodeId))) {
|
|
3833
|
+
const enumDef = this.registry.enums.get(enumTypes[0]);
|
|
3834
|
+
const validValues = enumDef
|
|
3835
|
+
? Array.from(enumDef.valueToLabel.keys()).join(", ")
|
|
3836
|
+
: "unknown";
|
|
3837
|
+
pushIssue("error", "ENUM_VALUE_INVALID", `Node ${n.nodeId} param ${paramKey} has invalid enum value ${paramValue}. Valid values: ${validValues}`, {
|
|
3838
|
+
nodeId: n.nodeId,
|
|
3839
|
+
input: paramKey,
|
|
3840
|
+
typeId: enumTypes.join("|"),
|
|
3841
|
+
});
|
|
3739
3842
|
}
|
|
3740
3843
|
}
|
|
3741
3844
|
}
|
|
@@ -3744,19 +3847,20 @@ class GraphBuilder {
|
|
|
3744
3847
|
if (resolved?.inputDefaults) {
|
|
3745
3848
|
const effectiveHandles = getEffectiveHandles(n);
|
|
3746
3849
|
for (const [handle, defaultValue] of Object.entries(resolved.inputDefaults)) {
|
|
3747
|
-
const
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3850
|
+
const declaredTypes = getInputDeclaredTypes(effectiveHandles.inputs, handle);
|
|
3851
|
+
const enumTypes = getEnumTypes(declaredTypes);
|
|
3852
|
+
if (enumTypes.length > 0 &&
|
|
3853
|
+
typeof defaultValue === "number" &&
|
|
3854
|
+
!enumTypes.some((et) => validateEnumValue(et, defaultValue, n.nodeId))) {
|
|
3855
|
+
const enumDef = this.registry.enums.get(enumTypes[0]);
|
|
3856
|
+
const validValues = enumDef
|
|
3857
|
+
? Array.from(enumDef.valueToLabel.keys()).join(", ")
|
|
3858
|
+
: "unknown";
|
|
3859
|
+
pushIssue("warning", "ENUM_DEFAULT_INVALID", `Node ${n.nodeId} input default ${handle} has invalid enum value ${defaultValue}. Valid values: ${validValues}`, {
|
|
3860
|
+
nodeId: n.nodeId,
|
|
3861
|
+
input: handle,
|
|
3862
|
+
typeId: enumTypes.join("|"),
|
|
3863
|
+
});
|
|
3760
3864
|
}
|
|
3761
3865
|
}
|
|
3762
3866
|
}
|
|
@@ -3856,8 +3960,8 @@ class GraphBuilder {
|
|
|
3856
3960
|
inboundCounts.set(inboundKey, (inboundCounts.get(inboundKey) ?? 0) + 1);
|
|
3857
3961
|
// If the target input is declared as an array type, allow multi-inbound (runtime will append)
|
|
3858
3962
|
if (dstNode) {
|
|
3859
|
-
const declaredIn =
|
|
3860
|
-
if (
|
|
3963
|
+
const declaredIn = getInputDeclaredTypes((effByNodeId.get(dstNode.nodeId) || { inputs: {} }).inputs, e.target.handle);
|
|
3964
|
+
if (hasArrayType(declaredIn)) {
|
|
3861
3965
|
inboundArrayOk.add(inboundKey);
|
|
3862
3966
|
}
|
|
3863
3967
|
}
|
|
@@ -3886,10 +3990,10 @@ class GraphBuilder {
|
|
|
3886
3990
|
const innerDesc = innerNode
|
|
3887
3991
|
? this.registry.nodes.get(innerNode.typeId)
|
|
3888
3992
|
: undefined;
|
|
3889
|
-
const
|
|
3890
|
-
?
|
|
3993
|
+
const typeIds = innerDesc
|
|
3994
|
+
? getInputDeclaredTypes(innerDesc.inputs, map.handle)
|
|
3891
3995
|
: undefined;
|
|
3892
|
-
inputTypes[outerIn] =
|
|
3996
|
+
inputTypes[outerIn] = typeIds ?? "unknown";
|
|
3893
3997
|
}
|
|
3894
3998
|
for (const [outerOut, map] of Object.entries(exposure.outputs)) {
|
|
3895
3999
|
const innerNode = def.nodes.find((n) => n.nodeId === map.nodeId);
|
|
@@ -3898,7 +4002,7 @@ class GraphBuilder {
|
|
|
3898
4002
|
: undefined;
|
|
3899
4003
|
const typeId = innerDesc ? innerDesc.outputs[map.handle] : undefined;
|
|
3900
4004
|
const single = Array.isArray(typeId) ? typeId[0] : typeId;
|
|
3901
|
-
outputTypes[outerOut] = single ?? "
|
|
4005
|
+
outputTypes[outerOut] = single ?? "unknown";
|
|
3902
4006
|
}
|
|
3903
4007
|
return {
|
|
3904
4008
|
id: nodeTypeId,
|