@bian-womp/spark-graph 0.3.32 → 0.3.34

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.
Files changed (29) hide show
  1. package/lib/cjs/index.cjs +152 -42
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/index.d.ts +3 -0
  4. package/lib/cjs/src/index.d.ts.map +1 -1
  5. package/lib/cjs/src/runtime/GraphRuntime.d.ts +17 -2
  6. package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
  7. package/lib/cjs/src/runtime/components/NodeExecutor.d.ts +1 -1
  8. package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
  9. package/lib/cjs/src/runtime/components/RuntimeValidatorManager.d.ts +31 -0
  10. package/lib/cjs/src/runtime/components/RuntimeValidatorManager.d.ts.map +1 -0
  11. package/lib/cjs/src/runtime/components/graph-utils.d.ts +11 -0
  12. package/lib/cjs/src/runtime/components/graph-utils.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/components/interfaces.d.ts +25 -2
  14. package/lib/cjs/src/runtime/components/interfaces.d.ts.map +1 -1
  15. package/lib/esm/index.js +151 -43
  16. package/lib/esm/index.js.map +1 -1
  17. package/lib/esm/src/index.d.ts +3 -0
  18. package/lib/esm/src/index.d.ts.map +1 -1
  19. package/lib/esm/src/runtime/GraphRuntime.d.ts +17 -2
  20. package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
  21. package/lib/esm/src/runtime/components/NodeExecutor.d.ts +1 -1
  22. package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
  23. package/lib/esm/src/runtime/components/RuntimeValidatorManager.d.ts +31 -0
  24. package/lib/esm/src/runtime/components/RuntimeValidatorManager.d.ts.map +1 -0
  25. package/lib/esm/src/runtime/components/graph-utils.d.ts +11 -0
  26. package/lib/esm/src/runtime/components/graph-utils.d.ts.map +1 -1
  27. package/lib/esm/src/runtime/components/interfaces.d.ts +25 -2
  28. package/lib/esm/src/runtime/components/interfaces.d.ts.map +1 -1
  29. package/package.json +2 -2
package/lib/cjs/index.cjs CHANGED
@@ -1617,6 +1617,55 @@ function buildEdgeConverters(srcDeclared, dstDeclared, registry, edgeLabel) {
1617
1617
  },
1618
1618
  };
1619
1619
  }
1620
+ /**
1621
+ * Compute effective inputs for a node by merging real inputs with defaults.
1622
+ * This is a shared utility used by both NodeExecutor and runtime validators.
1623
+ *
1624
+ * @param nodeId - The node ID to compute effective inputs for
1625
+ * @param graph - Graph component to access node and handle information
1626
+ * @param registry - Registry to access node type descriptors and defaults
1627
+ * @returns Record of effective input values (real inputs merged with defaults)
1628
+ */
1629
+ function getEffectiveInputs(nodeId, graph, registry) {
1630
+ const node = graph.getNode(nodeId);
1631
+ if (!node)
1632
+ return {};
1633
+ const desc = registry.nodes.get(node.typeId);
1634
+ if (!desc)
1635
+ return {};
1636
+ const resolved = graph.getResolvedHandles(nodeId);
1637
+ const regDefaults = desc.inputDefaults ?? {};
1638
+ const dynDefaults = resolved?.inputDefaults ?? {};
1639
+ // Identify which handles are dynamically resolved (not in registry statics)
1640
+ const staticHandles = new Set(Object.keys(desc.inputs ?? {}));
1641
+ const dynamicHandles = new Set(Object.keys(resolved?.inputs ?? {}).filter((h) => !staticHandles.has(h)));
1642
+ // Precedence: dynamic > registry
1643
+ const mergedDefaults = {
1644
+ ...regDefaults,
1645
+ ...dynDefaults,
1646
+ };
1647
+ // Start with real inputs only (no defaults)
1648
+ const effective = { ...node.inputs };
1649
+ // Build set of inbound handles (wired inputs)
1650
+ const inboundEdges = graph.getInboundEdges(nodeId);
1651
+ const inbound = new Set(inboundEdges.map((e) => e.target.handle));
1652
+ // Apply defaults only for:
1653
+ // 1. Unbound handles that have no explicit value
1654
+ // 2. Static handles (not dynamically resolved)
1655
+ for (const [handle, defaultValue] of Object.entries(mergedDefaults)) {
1656
+ if (defaultValue === undefined)
1657
+ continue;
1658
+ if (inbound.has(handle))
1659
+ continue; // Don't override wired inputs
1660
+ if (effective[handle] !== undefined)
1661
+ continue; // Already has value
1662
+ if (dynamicHandles.has(handle))
1663
+ continue; // Skip defaults for dynamic handles
1664
+ // Clone to avoid shared references
1665
+ effective[handle] = structuredClone(defaultValue);
1666
+ }
1667
+ return effective;
1668
+ }
1620
1669
 
1621
1670
  /**
1622
1671
  * HandleResolver component - manages dynamic handle resolution
@@ -2253,47 +2302,10 @@ class NodeExecutor {
2253
2302
  * Compute effective inputs for a node by merging real inputs with defaults
2254
2303
  */
2255
2304
  getEffectiveInputs(nodeId) {
2256
- const node = this.graph.getNode(nodeId);
2257
- if (!node)
2258
- return {};
2259
2305
  const registry = this.graph.getRegistry();
2260
2306
  if (!registry)
2261
2307
  return {};
2262
- const desc = registry.nodes.get(node.typeId);
2263
- if (!desc)
2264
- return {};
2265
- const resolved = this.graph.getResolvedHandles(nodeId);
2266
- const regDefaults = desc.inputDefaults ?? {};
2267
- const dynDefaults = resolved?.inputDefaults ?? {};
2268
- // Identify which handles are dynamically resolved (not in registry statics)
2269
- const staticHandles = new Set(Object.keys(desc.inputs ?? {}));
2270
- const dynamicHandles = new Set(Object.keys(resolved?.inputs ?? {}).filter((h) => !staticHandles.has(h)));
2271
- // Precedence: dynamic > registry
2272
- const mergedDefaults = {
2273
- ...regDefaults,
2274
- ...dynDefaults,
2275
- };
2276
- // Start with real inputs only (no defaults)
2277
- const effective = { ...node.inputs };
2278
- // Build set of inbound handles (wired inputs)
2279
- const inboundEdges = this.graph.getInboundEdges(nodeId);
2280
- const inbound = new Set(inboundEdges.map((e) => e.target.handle));
2281
- // Apply defaults only for:
2282
- // 1. Unbound handles that have no explicit value
2283
- // 2. Static handles (not dynamically resolved)
2284
- for (const [handle, defaultValue] of Object.entries(mergedDefaults)) {
2285
- if (defaultValue === undefined)
2286
- continue;
2287
- if (inbound.has(handle))
2288
- continue; // Don't override wired inputs
2289
- if (effective[handle] !== undefined)
2290
- continue; // Already has value
2291
- if (dynamicHandles.has(handle))
2292
- continue; // Skip defaults for dynamic handles
2293
- // Clone to avoid shared references
2294
- effective[handle] = structuredClone(defaultValue);
2295
- }
2296
- return effective;
2308
+ return getEffectiveInputs(nodeId, this.graph, registry);
2297
2309
  }
2298
2310
  /**
2299
2311
  * Create an execution context for a node
@@ -2368,7 +2380,7 @@ class NodeExecutor {
2368
2380
  /**
2369
2381
  * Internal method for executing inputs changed (also used by GraphRuntime)
2370
2382
  */
2371
- execute(nodeId, runContextIds) {
2383
+ execute(nodeId, runContextIds, canSkipHandleResolution) {
2372
2384
  const node = this.graph.getNode(nodeId);
2373
2385
  if (!node)
2374
2386
  return;
@@ -2399,11 +2411,29 @@ class NodeExecutor {
2399
2411
  // Early validation for auto-mode paused state
2400
2412
  if (this.runtime.isPaused())
2401
2413
  return;
2414
+ // Check runtime validators (check current state, not just graph definition)
2415
+ const runtimeValidationError = this.runtime.hasRuntimeValidationBlock(nodeId);
2416
+ if (runtimeValidationError) {
2417
+ this.eventEmitter.emit("error", {
2418
+ kind: "system",
2419
+ message: runtimeValidationError.message,
2420
+ code: runtimeValidationError.code || "RUNTIME_VALIDATION_BLOCKED",
2421
+ details: {
2422
+ nodeId,
2423
+ ...runtimeValidationError.details,
2424
+ },
2425
+ });
2426
+ return;
2427
+ }
2402
2428
  // Attach run-context IDs if provided - do this BEFORE checking for pending resolution
2403
2429
  // so that handle resolution can track these run contexts
2404
2430
  if (runContextIds) {
2405
2431
  this.graph.addNodeRunContextIds(nodeId, runContextIds);
2406
2432
  }
2433
+ if (!canSkipHandleResolution &&
2434
+ !this.handleResolver.getPendingResolution(nodeId)) {
2435
+ this.handleResolver.scheduleRecomputeHandles(nodeId);
2436
+ }
2407
2437
  // Check if handles are being resolved - wait for resolution before executing
2408
2438
  // Do this AFTER setting up run contexts so handle resolution can track them
2409
2439
  const pendingResolution = this.handleResolver.getPendingResolution(nodeId);
@@ -2418,7 +2448,7 @@ class NodeExecutor {
2418
2448
  // Re-check node still exists and conditions
2419
2449
  const nodeAfter = this.graph.getNode(nodeId);
2420
2450
  if (nodeAfter) {
2421
- this.execute(nodeId, runContextIds);
2451
+ this.execute(nodeId, runContextIds, true);
2422
2452
  }
2423
2453
  if (runContextIds && runContextIds.size > 0) {
2424
2454
  for (const id of runContextIds) {
@@ -2857,6 +2887,61 @@ class NodeExecutor {
2857
2887
  }
2858
2888
  }
2859
2889
 
2890
+ /**
2891
+ * RuntimeValidatorManager component - manages runtime validators
2892
+ */
2893
+ class RuntimeValidatorManager {
2894
+ constructor(graph, registry) {
2895
+ this.graph = graph;
2896
+ this.registry = registry;
2897
+ this.validators = [];
2898
+ }
2899
+ /**
2900
+ * Set the registry (called when registry changes)
2901
+ */
2902
+ setRegistry(registry) {
2903
+ this.registry = registry;
2904
+ }
2905
+ /**
2906
+ * Register a runtime validator that will be called before node execution.
2907
+ * Validators are called in registration order - if any returns true, execution is blocked.
2908
+ */
2909
+ registerValidator(validator) {
2910
+ this.validators.push(validator);
2911
+ }
2912
+ /**
2913
+ * Unregister a runtime validator.
2914
+ */
2915
+ unregisterValidator(validator) {
2916
+ const index = this.validators.indexOf(validator);
2917
+ if (index >= 0) {
2918
+ this.validators.splice(index, 1);
2919
+ }
2920
+ }
2921
+ /**
2922
+ * Check if any runtime validator blocks execution for this node.
2923
+ * Returns RuntimeValidationError if execution should be blocked, null otherwise.
2924
+ */
2925
+ hasBlock(nodeId) {
2926
+ if (!this.registry)
2927
+ return null;
2928
+ for (const validator of this.validators) {
2929
+ try {
2930
+ const result = validator(nodeId, this.graph, this.registry);
2931
+ if (result !== false) {
2932
+ // Validator returned an error object
2933
+ return result;
2934
+ }
2935
+ }
2936
+ catch (err) {
2937
+ // Don't let validator errors break execution - log and continue
2938
+ console.error(`Runtime validator error for node ${nodeId}:`, err);
2939
+ }
2940
+ }
2941
+ return null;
2942
+ }
2943
+ }
2944
+
2860
2945
  // Types are now imported from components/types.ts (re-exported above)
2861
2946
  class GraphRuntime {
2862
2947
  constructor() {
@@ -2873,6 +2958,8 @@ class GraphRuntime {
2873
2958
  this.edgePropagator = new EdgePropagator(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this);
2874
2959
  // Create NodeExecutor with EdgePropagator and HandleResolver
2875
2960
  this.nodeExecutor = new NodeExecutor(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this, this);
2961
+ // Create RuntimeValidatorManager
2962
+ this.runtimeValidatorManager = new RuntimeValidatorManager(this.graph);
2876
2963
  }
2877
2964
  static create(def, registry, opts) {
2878
2965
  const gr = new GraphRuntime();
@@ -2884,6 +2971,7 @@ class GraphRuntime {
2884
2971
  gr.handleResolver.setRegistry(registry);
2885
2972
  gr.handleResolver.setEnvironment(gr.environment);
2886
2973
  gr.nodeExecutor.setEnvironment(gr.environment);
2974
+ gr.runtimeValidatorManager.setRegistry(registry);
2887
2975
  // Precompute per-node resolved handles (use def-provided overrides; do not compute dynamically here)
2888
2976
  const initial = gr.isPaused()
2889
2977
  ? {
@@ -3101,6 +3189,26 @@ class GraphRuntime {
3101
3189
  this.handleResolver.scheduleRecomputeHandles(nodeId);
3102
3190
  }
3103
3191
  }
3192
+ /**
3193
+ * Register a runtime validator that will be called before node execution.
3194
+ * Validators are called in registration order - if any returns true, execution is blocked.
3195
+ */
3196
+ registerRuntimeValidator(validator) {
3197
+ this.runtimeValidatorManager.registerValidator(validator);
3198
+ }
3199
+ /**
3200
+ * Unregister a runtime validator.
3201
+ */
3202
+ unregisterRuntimeValidator(validator) {
3203
+ this.runtimeValidatorManager.unregisterValidator(validator);
3204
+ }
3205
+ /**
3206
+ * Check if any runtime validator blocks execution for this node.
3207
+ * Returns RuntimeValidationError if execution should be blocked, null otherwise.
3208
+ */
3209
+ hasRuntimeValidationBlock(nodeId) {
3210
+ return this.runtimeValidatorManager.hasBlock(nodeId);
3211
+ }
3104
3212
  getGraphDef() {
3105
3213
  const nodes = [];
3106
3214
  this.graph.forEachNode((n) => {
@@ -3534,8 +3642,8 @@ class GraphRuntime {
3534
3642
  });
3535
3643
  this.graph.clear();
3536
3644
  }
3537
- execute(nodeId, runContextIds) {
3538
- this.nodeExecutor.execute(nodeId, runContextIds);
3645
+ execute(nodeId, runContextIds, resolveHandles) {
3646
+ this.nodeExecutor.execute(nodeId, runContextIds, resolveHandles);
3539
3647
  }
3540
3648
  propagate(srcNodeId, srcHandle, value, runContextIds) {
3541
3649
  this.edgePropagator.propagate(srcNodeId, srcHandle, value, runContextIds);
@@ -6158,6 +6266,7 @@ exports.BaseLogicOperation = BaseLogicOperation;
6158
6266
  exports.BaseMathOperation = BaseMathOperation;
6159
6267
  exports.CompositeCategory = CompositeCategory;
6160
6268
  exports.ComputeCategory = ComputeCategory;
6269
+ exports.Graph = Graph;
6161
6270
  exports.GraphBuilder = GraphBuilder;
6162
6271
  exports.GraphRuntime = GraphRuntime;
6163
6272
  exports.LevelLogger = LevelLogger;
@@ -6176,6 +6285,7 @@ exports.createValidationGraphDef = createValidationGraphDef;
6176
6285
  exports.createValidationGraphRegistry = createValidationGraphRegistry;
6177
6286
  exports.findMatchingPaths = findMatchingPaths;
6178
6287
  exports.generateId = generateId;
6288
+ exports.getEffectiveInputs = getEffectiveInputs;
6179
6289
  exports.getInputDeclaredTypes = getInputDeclaredTypes;
6180
6290
  exports.getInputHandleMetadata = getInputHandleMetadata;
6181
6291
  exports.getInputTypeId = getInputTypeId;