@bian-womp/spark-graph 0.3.32 → 0.3.33

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 (27) hide show
  1. package/lib/cjs/index.cjs +144 -38
  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 +16 -1
  6. package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
  7. package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
  8. package/lib/cjs/src/runtime/components/RuntimeValidatorManager.d.ts +31 -0
  9. package/lib/cjs/src/runtime/components/RuntimeValidatorManager.d.ts.map +1 -0
  10. package/lib/cjs/src/runtime/components/graph-utils.d.ts +11 -0
  11. package/lib/cjs/src/runtime/components/graph-utils.d.ts.map +1 -1
  12. package/lib/cjs/src/runtime/components/interfaces.d.ts +24 -1
  13. package/lib/cjs/src/runtime/components/interfaces.d.ts.map +1 -1
  14. package/lib/esm/index.js +143 -39
  15. package/lib/esm/index.js.map +1 -1
  16. package/lib/esm/src/index.d.ts +3 -0
  17. package/lib/esm/src/index.d.ts.map +1 -1
  18. package/lib/esm/src/runtime/GraphRuntime.d.ts +16 -1
  19. package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
  20. package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
  21. package/lib/esm/src/runtime/components/RuntimeValidatorManager.d.ts +31 -0
  22. package/lib/esm/src/runtime/components/RuntimeValidatorManager.d.ts.map +1 -0
  23. package/lib/esm/src/runtime/components/graph-utils.d.ts +11 -0
  24. package/lib/esm/src/runtime/components/graph-utils.d.ts.map +1 -1
  25. package/lib/esm/src/runtime/components/interfaces.d.ts +24 -1
  26. package/lib/esm/src/runtime/components/interfaces.d.ts.map +1 -1
  27. 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
@@ -2399,6 +2411,20 @@ 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) {
@@ -2857,6 +2883,61 @@ class NodeExecutor {
2857
2883
  }
2858
2884
  }
2859
2885
 
2886
+ /**
2887
+ * RuntimeValidatorManager component - manages runtime validators
2888
+ */
2889
+ class RuntimeValidatorManager {
2890
+ constructor(graph, registry) {
2891
+ this.graph = graph;
2892
+ this.registry = registry;
2893
+ this.validators = [];
2894
+ }
2895
+ /**
2896
+ * Set the registry (called when registry changes)
2897
+ */
2898
+ setRegistry(registry) {
2899
+ this.registry = registry;
2900
+ }
2901
+ /**
2902
+ * Register a runtime validator that will be called before node execution.
2903
+ * Validators are called in registration order - if any returns true, execution is blocked.
2904
+ */
2905
+ registerValidator(validator) {
2906
+ this.validators.push(validator);
2907
+ }
2908
+ /**
2909
+ * Unregister a runtime validator.
2910
+ */
2911
+ unregisterValidator(validator) {
2912
+ const index = this.validators.indexOf(validator);
2913
+ if (index >= 0) {
2914
+ this.validators.splice(index, 1);
2915
+ }
2916
+ }
2917
+ /**
2918
+ * Check if any runtime validator blocks execution for this node.
2919
+ * Returns RuntimeValidationError if execution should be blocked, null otherwise.
2920
+ */
2921
+ hasBlock(nodeId) {
2922
+ if (!this.registry)
2923
+ return null;
2924
+ for (const validator of this.validators) {
2925
+ try {
2926
+ const result = validator(nodeId, this.graph, this.registry);
2927
+ if (result !== false) {
2928
+ // Validator returned an error object
2929
+ return result;
2930
+ }
2931
+ }
2932
+ catch (err) {
2933
+ // Don't let validator errors break execution - log and continue
2934
+ console.error(`Runtime validator error for node ${nodeId}:`, err);
2935
+ }
2936
+ }
2937
+ return null;
2938
+ }
2939
+ }
2940
+
2860
2941
  // Types are now imported from components/types.ts (re-exported above)
2861
2942
  class GraphRuntime {
2862
2943
  constructor() {
@@ -2873,6 +2954,8 @@ class GraphRuntime {
2873
2954
  this.edgePropagator = new EdgePropagator(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this);
2874
2955
  // Create NodeExecutor with EdgePropagator and HandleResolver
2875
2956
  this.nodeExecutor = new NodeExecutor(this.graph, this.eventEmitter, this.runContextManager, this.handleResolver, this, this);
2957
+ // Create RuntimeValidatorManager
2958
+ this.runtimeValidatorManager = new RuntimeValidatorManager(this.graph);
2876
2959
  }
2877
2960
  static create(def, registry, opts) {
2878
2961
  const gr = new GraphRuntime();
@@ -2884,6 +2967,7 @@ class GraphRuntime {
2884
2967
  gr.handleResolver.setRegistry(registry);
2885
2968
  gr.handleResolver.setEnvironment(gr.environment);
2886
2969
  gr.nodeExecutor.setEnvironment(gr.environment);
2970
+ gr.runtimeValidatorManager.setRegistry(registry);
2887
2971
  // Precompute per-node resolved handles (use def-provided overrides; do not compute dynamically here)
2888
2972
  const initial = gr.isPaused()
2889
2973
  ? {
@@ -3101,6 +3185,26 @@ class GraphRuntime {
3101
3185
  this.handleResolver.scheduleRecomputeHandles(nodeId);
3102
3186
  }
3103
3187
  }
3188
+ /**
3189
+ * Register a runtime validator that will be called before node execution.
3190
+ * Validators are called in registration order - if any returns true, execution is blocked.
3191
+ */
3192
+ registerRuntimeValidator(validator) {
3193
+ this.runtimeValidatorManager.registerValidator(validator);
3194
+ }
3195
+ /**
3196
+ * Unregister a runtime validator.
3197
+ */
3198
+ unregisterRuntimeValidator(validator) {
3199
+ this.runtimeValidatorManager.unregisterValidator(validator);
3200
+ }
3201
+ /**
3202
+ * Check if any runtime validator blocks execution for this node.
3203
+ * Returns RuntimeValidationError if execution should be blocked, null otherwise.
3204
+ */
3205
+ hasRuntimeValidationBlock(nodeId) {
3206
+ return this.runtimeValidatorManager.hasBlock(nodeId);
3207
+ }
3104
3208
  getGraphDef() {
3105
3209
  const nodes = [];
3106
3210
  this.graph.forEachNode((n) => {
@@ -6158,6 +6262,7 @@ exports.BaseLogicOperation = BaseLogicOperation;
6158
6262
  exports.BaseMathOperation = BaseMathOperation;
6159
6263
  exports.CompositeCategory = CompositeCategory;
6160
6264
  exports.ComputeCategory = ComputeCategory;
6265
+ exports.Graph = Graph;
6161
6266
  exports.GraphBuilder = GraphBuilder;
6162
6267
  exports.GraphRuntime = GraphRuntime;
6163
6268
  exports.LevelLogger = LevelLogger;
@@ -6176,6 +6281,7 @@ exports.createValidationGraphDef = createValidationGraphDef;
6176
6281
  exports.createValidationGraphRegistry = createValidationGraphRegistry;
6177
6282
  exports.findMatchingPaths = findMatchingPaths;
6178
6283
  exports.generateId = generateId;
6284
+ exports.getEffectiveInputs = getEffectiveInputs;
6179
6285
  exports.getInputDeclaredTypes = getInputDeclaredTypes;
6180
6286
  exports.getInputHandleMetadata = getInputHandleMetadata;
6181
6287
  exports.getInputTypeId = getInputTypeId;