diff --git a/.agents/skills/fbp/references/fbp-evaluator.md b/.agents/skills/fbp/references/fbp-evaluator.md index 6595eb4..1edb62e 100644 --- a/.agents/skills/fbp/references/fbp-evaluator.md +++ b/.agents/skills/fbp/references/fbp-evaluator.md @@ -25,8 +25,8 @@ import type { NodeDefinitionWithImpl } from '@fbp/evaluator'; // Define node implementations const addDef: NodeDefinitionWithImpl = { context: 'js', + name: 'add', category: 'math', - type: 'js/math/add', inputs: [ { name: 'a', type: 'number' }, { name: 'b', type: 'number' } @@ -39,8 +39,8 @@ const addDef: NodeDefinitionWithImpl = { const constNumberDef: NodeDefinitionWithImpl = { context: 'js', + name: 'number', category: 'const', - type: 'js/const/number', props: [{ name: 'value', type: 'number' }], outputs: [{ name: 'value', type: 'number' }], impl: (inputs, props) => ({ @@ -52,9 +52,9 @@ const constNumberDef: NodeDefinitionWithImpl = { const graph: Graph = { name: 'simple-add', nodes: [ - { name: 'num1', type: 'js/const/number', props: [{ name: 'value', value: 5 }] }, - { name: 'num2', type: 'js/const/number', props: [{ name: 'value', value: 3 }] }, - { name: 'add', type: 'js/math/add' } + { name: 'num1', type: 'const:number', props: [{ name: 'value', value: 5 }] }, + { name: 'num2', type: 'const:number', props: [{ name: 'value', value: 3 }] }, + { name: 'add', type: 'math:add' } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'add', port: 'a' } }, @@ -117,7 +117,8 @@ Supports ports that accept multiple incoming edges. Values are collected in edge ```typescript const mergeDef: NodeDefinitionWithImpl = { - type: 'js/array/merge', + name: 'merge', + category: 'array', inputs: [{ name: 'items', type: 'any', multi: true }], outputs: [{ name: 'array', type: 'any[]' }], impl: (inputs) => ({ @@ -147,8 +148,8 @@ const result = evaluate(graph, { const mathNodes: NodeDefinitionWithImpl[] = [ { context: 'js', + name: 'add', category: 'math', - type: 'js/math/add', inputs: [ { name: 'a', type: 'number' }, { name: 'b', type: 'number' } @@ -158,8 +159,8 @@ const mathNodes: NodeDefinitionWithImpl[] = [ }, { context: 'js', + name: 'multiply', category: 'math', - type: 'js/math/multiply', inputs: [ { name: 'a', type: 'number' }, { name: 'b', type: 'number' } @@ -169,8 +170,8 @@ const mathNodes: NodeDefinitionWithImpl[] = [ }, { context: 'js', + name: 'negate', category: 'math', - type: 'js/math/negate', inputs: [{ name: 'value', type: 'number' }], outputs: [{ name: 'negated', type: 'number' }], impl: (inputs) => ({ negated: -(inputs.value ?? 0) }) @@ -180,7 +181,7 @@ const mathNodes: NodeDefinitionWithImpl[] = [ ## Best Practices -1. Use descriptive type paths like `context/category/name` (e.g., `js/math/add`) +1. Use short names on definitions (e.g., `name: 'add'`); the task_identifier `category:name` is used in `Node.type` (e.g., `type: 'math:add'`) 2. Always provide default values in `impl` functions for missing inputs 3. Keep node implementations pure — no side effects 4. Use `multi: true` for ports that should accept multiple connections diff --git a/.agents/skills/fbp/references/fbp-graph-editor.md b/.agents/skills/fbp/references/fbp-graph-editor.md index 6b12779..1d51307 100644 --- a/.agents/skills/fbp/references/fbp-graph-editor.md +++ b/.agents/skills/fbp/references/fbp-graph-editor.md @@ -26,8 +26,8 @@ const graph: Graph = { definitions: [ { context: 'js', + name: 'add', category: 'math', - type: 'js/math/add', inputs: [ { name: 'a', type: 'number' }, { name: 'b', type: 'number' } @@ -38,7 +38,7 @@ const graph: Graph = { } ], nodes: [ - { name: 'add1', type: 'js/math/add', meta: { x: 100, y: 100 } } + { name: 'add1', type: 'math:add', meta: { x: 100, y: 100 } } ], edges: [] }; @@ -60,7 +60,7 @@ The editor uses SVG for rendering, providing crisp visuals at any zoom level. Pa ### Node Rendering -Nodes display their fully-qualified type paths (e.g., `js/math/add`) and show input/output ports based on their definition. +Nodes display their type (e.g., `math:add`) and show input/output ports based on their definition. ### Bezier Edge Connections @@ -157,15 +157,15 @@ const initialGraph: Graph = { name: 'calculator', definitions: [ { - type: 'js/const/number', context: 'js', + name: 'number', category: 'const', props: [{ name: 'value', type: 'number', default: 0 }], outputs: [{ name: 'value', type: 'number' }] }, { - type: 'js/math/add', context: 'js', + name: 'add', category: 'math', inputs: [ { name: 'a', type: 'number' }, @@ -175,9 +175,9 @@ const initialGraph: Graph = { } ], nodes: [ - { name: 'num1', type: 'js/const/number', meta: { x: 50, y: 50 }, props: [{ name: 'value', value: 5 }] }, - { name: 'num2', type: 'js/const/number', meta: { x: 50, y: 150 }, props: [{ name: 'value', value: 3 }] }, - { name: 'add', type: 'js/math/add', meta: { x: 250, y: 100 } } + { name: 'num1', type: 'const:number', meta: { x: 50, y: 50 }, props: [{ name: 'value', value: 5 }] }, + { name: 'num2', type: 'const:number', meta: { x: 50, y: 150 }, props: [{ name: 'value', value: 3 }] }, + { name: 'add', type: 'math:add', meta: { x: 250, y: 100 } } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'add', port: 'a' } }, diff --git a/.agents/skills/fbp/references/fbp-spec.md b/.agents/skills/fbp/references/fbp-spec.md index 1c02cfb..06d9b5e 100644 --- a/.agents/skills/fbp/references/fbp-spec.md +++ b/.agents/skills/fbp/references/fbp-spec.md @@ -175,7 +175,7 @@ const newGraph = setPosition(graph, '/add1', 100, 200); }, { "name": "add1", - "type": "math/add", + "type": "math:add", "meta": { "x": 200, "y": 50 } }, { diff --git a/.agents/skills/fbp/references/fbp-types.md b/.agents/skills/fbp/references/fbp-types.md index d21e10a..aec0a7f 100644 --- a/.agents/skills/fbp/references/fbp-types.md +++ b/.agents/skills/fbp/references/fbp-types.md @@ -32,6 +32,7 @@ Node identity equals name within parent scope. Names may be hierarchical paths b ```typescript interface Graph { name?: string; + context?: string; // Default context for node lookups nodes: Node[]; edges: Edge[]; definitions?: NodeDefinition[]; @@ -44,7 +45,8 @@ interface Graph { ```typescript interface Node { name: string; // Unique within parent scope - type: string; // References a NodeDefinition + type: string; // References a NodeDefinition.name + context?: string; // Override the graph-level context meta?: NodeMeta; // Position and metadata props?: PropValue[]; // Property values nodes?: Node[]; // Child nodes (makes this a subnet) @@ -71,12 +73,15 @@ interface PortRef { ```typescript interface NodeDefinition { - type: string; // Unique identifier (e.g., "math/add") - context?: string; // Namespace (e.g., "math", "ui") - category?: string; // Palette category + context: string; // Execution context (e.g., "js", "core") + name: string; // Short identifier (e.g., "add", "number", "Page") + category: string; // Required grouping + part of composite key (e.g., "math", "json") inputs?: PortDef[]; // Input port definitions outputs?: PortDef[]; // Output port definitions props?: PropDef[]; // Property definitions + graph?: Graph; // Inline subgraph (for composite definitions) + volatile?: boolean; // If true, re-evaluate on every tick + runtime?: string; // Execution runtime (e.g., "inline", "http") icon?: string; description?: string; } @@ -88,6 +93,7 @@ interface NodeDefinition { interface PortDef { name: string; type?: string; // Data type (e.g., "string", "number", "any") + schema?: Record; // JSON Schema for complex types multi?: boolean; // Accepts multiple connections description?: string; } @@ -95,8 +101,11 @@ interface PortDef { interface PropDef { name: string; type?: string; + schema?: Record; // JSON Schema for complex types default?: any; description?: string; + required?: boolean; + options?: string[]; // Valid values for enum/select types } interface PropValue { diff --git a/packages/evaluator/__tests__/core-nodes.test.js b/packages/evaluator/__tests__/core-nodes.test.js index 1c0f75a..9a518c8 100644 --- a/packages/evaluator/__tests__/core-nodes.test.js +++ b/packages/evaluator/__tests__/core-nodes.test.js @@ -18,7 +18,7 @@ describe('core nodes', () => { }, { name: 'select', - type: 'core/json/select', + type: 'json:select', props: [{ name: 'path', type: 'string', value: 'user.name' }] } ], @@ -47,7 +47,7 @@ describe('core nodes', () => { }, { name: 'select', - type: 'core/json/select', + type: 'json:select', props: [{ name: 'path', type: 'string', value: 'response.data.users.1.id' }] } ], @@ -76,7 +76,7 @@ describe('core nodes', () => { }, { name: 'select', - type: 'core/json/select', + type: 'json:select', props: [{ name: 'path', type: 'string', value: 'baz.qux' }] } ], diff --git a/packages/evaluator/__tests__/core-nodes.test.ts b/packages/evaluator/__tests__/core-nodes.test.ts index 45c6790..cfcde8e 100644 --- a/packages/evaluator/__tests__/core-nodes.test.ts +++ b/packages/evaluator/__tests__/core-nodes.test.ts @@ -20,7 +20,7 @@ describe('core nodes', () => { }, { name: 'select', - type: 'select', + type: 'json:select', props: [{ name: 'path', type: 'string', value: 'user.name' }] } ], @@ -53,7 +53,7 @@ describe('core nodes', () => { }, { name: 'select', - type: 'select', + type: 'json:select', props: [{ name: 'path', type: 'string', value: 'response.data.users.1.id' }] } ], @@ -86,7 +86,7 @@ describe('core nodes', () => { }, { name: 'select', - type: 'select', + type: 'json:select', props: [{ name: 'path', type: 'string', value: 'baz.qux' }] } ], diff --git a/packages/evaluator/__tests__/digital-assets.test.ts b/packages/evaluator/__tests__/digital-assets.test.ts index b99cf32..803dd18 100644 --- a/packages/evaluator/__tests__/digital-assets.test.ts +++ b/packages/evaluator/__tests__/digital-assets.test.ts @@ -17,8 +17,8 @@ describe('digital assets', () => { context: 'js', nodes: [ { name: 'input_x', type: 'graphInput', props: [{ name: 'portName', type: 'string', value: 'x' }] }, - { name: 'two', type: 'number', props: [{ name: 'value', type: 'number', value: 2 }] }, - { name: 'mul', type: 'multiply' }, + { name: 'two', type: 'const:number', props: [{ name: 'value', type: 'number', value: 2 }] }, + { name: 'mul', type: 'math:multiply' }, { name: 'output_result', type: 'graphOutput', props: [{ name: 'portName', type: 'string', value: 'result' }] } ], edges: [ @@ -36,8 +36,8 @@ describe('digital assets', () => { name: 'use-double', context: 'js', nodes: [ - { name: 'num', type: 'number', props: [{ name: 'value', type: 'number', value: 7 }] }, - { name: 'dbl', type: 'double' } + { name: 'num', type: 'const:number', props: [{ name: 'value', type: 'number', value: 7 }] }, + { name: 'dbl', type: 'math:double' } ], edges: [ { src: { node: 'num', port: 'value' }, dst: { node: 'dbl', port: 'x' } } @@ -59,10 +59,10 @@ describe('digital assets', () => { name: 'double-then-add', context: 'js', nodes: [ - { name: 'num1', type: 'number', props: [{ name: 'value', type: 'number', value: 5 }] }, - { name: 'num2', type: 'number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'dbl', type: 'double' }, - { name: 'add', type: 'add' } + { name: 'num1', type: 'const:number', props: [{ name: 'value', type: 'number', value: 5 }] }, + { name: 'num2', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'dbl', type: 'math:double' }, + { name: 'add', type: 'math:add' } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'dbl', port: 'x' } }, @@ -108,9 +108,9 @@ describe('digital assets', () => { { name: 'p_wb', type: 'graphProp', props: [{ name: 'propName', type: 'string', value: 'weight_b' }] }, { name: 'out', type: 'graphOutput', props: [{ name: 'portName', type: 'string', value: 'result' }] }, // Computation - { name: 'mul_a', type: 'multiply' }, - { name: 'mul_b', type: 'multiply' }, - { name: 'sum', type: 'add' } + { name: 'mul_a', type: 'math:multiply' }, + { name: 'mul_b', type: 'math:multiply' }, + { name: 'sum', type: 'math:add' } ], edges: [ { src: { node: 'in_a', port: 'value' }, dst: { node: 'mul_a', port: 'a' } }, @@ -131,9 +131,9 @@ describe('digital assets', () => { name: 'weighted-add-defaults', context: 'js', nodes: [ - { name: 'a', type: 'number', props: [{ name: 'value', type: 'number', value: 10 }] }, - { name: 'b', type: 'number', props: [{ name: 'value', type: 'number', value: 20 }] }, - { name: 'wadd', type: 'weighted-add' } + { name: 'a', type: 'const:number', props: [{ name: 'value', type: 'number', value: 10 }] }, + { name: 'b', type: 'const:number', props: [{ name: 'value', type: 'number', value: 20 }] }, + { name: 'wadd', type: 'math:weighted-add' } ], edges: [ { src: { node: 'a', port: 'value' }, dst: { node: 'wadd', port: 'a' } }, @@ -156,9 +156,9 @@ describe('digital assets', () => { name: 'weighted-add-custom', context: 'js', nodes: [ - { name: 'a', type: 'number', props: [{ name: 'value', type: 'number', value: 10 }] }, - { name: 'b', type: 'number', props: [{ name: 'value', type: 'number', value: 20 }] }, - { name: 'wadd', type: 'weighted-add', props: [ + { name: 'a', type: 'const:number', props: [{ name: 'value', type: 'number', value: 10 }] }, + { name: 'b', type: 'const:number', props: [{ name: 'value', type: 'number', value: 20 }] }, + { name: 'wadd', type: 'math:weighted-add', props: [ { name: 'weight_a', type: 'number', value: 0.7 }, { name: 'weight_b', type: 'number', value: 0.3 } ]} @@ -184,19 +184,19 @@ describe('digital assets', () => { name: 'two-weighted-adds', context: 'js', nodes: [ - { name: 'a', type: 'number', props: [{ name: 'value', type: 'number', value: 10 }] }, - { name: 'b', type: 'number', props: [{ name: 'value', type: 'number', value: 20 }] }, + { name: 'a', type: 'const:number', props: [{ name: 'value', type: 'number', value: 10 }] }, + { name: 'b', type: 'const:number', props: [{ name: 'value', type: 'number', value: 20 }] }, // Instance 1: weight_a=2, weight_b=3 - { name: 'wadd1', type: 'weighted-add', props: [ + { name: 'wadd1', type: 'math:weighted-add', props: [ { name: 'weight_a', type: 'number', value: 2 }, { name: 'weight_b', type: 'number', value: 3 } ]}, // Instance 2: weight_a=0.5, weight_b=0.5 - { name: 'wadd2', type: 'weighted-add', props: [ + { name: 'wadd2', type: 'math:weighted-add', props: [ { name: 'weight_a', type: 'number', value: 0.5 }, { name: 'weight_b', type: 'number', value: 0.5 } ]}, - { name: 'final_add', type: 'add' } + { name: 'final_add', type: 'math:add' } ], edges: [ { src: { node: 'a', port: 'value' }, dst: { node: 'wadd1', port: 'a' } }, @@ -247,8 +247,8 @@ describe('digital assets', () => { context: 'js', nodes: [ { name: 'input_x', type: 'graphInput', props: [{ name: 'portName', type: 'string', value: 'x' }] }, - { name: 'two', type: 'number', props: [{ name: 'value', type: 'number', value: 2 }] }, - { name: 'mul', type: 'multiply' }, + { name: 'two', type: 'const:number', props: [{ name: 'value', type: 'number', value: 2 }] }, + { name: 'mul', type: 'math:multiply' }, { name: 'output_result', type: 'graphOutput', props: [{ name: 'portName', type: 'string', value: 'result' }] } ], edges: [ @@ -271,8 +271,8 @@ describe('digital assets', () => { context: 'js', nodes: [ { name: 'input_x', type: 'graphInput', props: [{ name: 'portName', type: 'string', value: 'x' }] }, - { name: 'dbl1', type: 'double' }, - { name: 'dbl2', type: 'double' }, + { name: 'dbl1', type: 'math:double' }, + { name: 'dbl2', type: 'math:double' }, { name: 'output_result', type: 'graphOutput', props: [{ name: 'portName', type: 'string', value: 'result' }] } ], edges: [ @@ -290,8 +290,8 @@ describe('digital assets', () => { name: 'use-quadruple', context: 'js', nodes: [ - { name: 'num', type: 'number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'quad', type: 'quadruple' } + { name: 'num', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'quad', type: 'math:quadruple' } ], edges: [ { src: { node: 'num', port: 'value' }, dst: { node: 'quad', port: 'x' } } @@ -327,8 +327,8 @@ describe('digital assets', () => { context: 'js', nodes: [ { name: 'input_x', type: 'graphInput', props: [{ name: 'portName', type: 'string', value: 'x' }] }, - { name: 'three', type: 'number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'mul', type: 'multiply' }, + { name: 'three', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'mul', type: 'math:multiply' }, { name: 'output_result', type: 'graphOutput', props: [{ name: 'portName', type: 'string', value: 'result' }] } ], edges: [ @@ -340,8 +340,8 @@ describe('digital assets', () => { } ], nodes: [ - { name: 'num', type: 'number', props: [{ name: 'value', type: 'number', value: 5 }] }, - { name: 'trip', type: 'triple' } + { name: 'num', type: 'const:number', props: [{ name: 'value', type: 'number', value: 5 }] }, + { name: 'trip', type: 'math:triple' } ], edges: [ { src: { node: 'num', port: 'value' }, dst: { node: 'trip', port: 'x' } } @@ -367,7 +367,8 @@ describe('digital assets', () => { it('should throw if digital asset has no output boundary nodes', async () => { const emptyAssetDef: NodeDefinitionWithImpl = { context: 'js', - name: 'broken/empty', + name: 'empty', + category: 'broken', inputs: [{ name: 'x', type: 'number' }], outputs: [{ name: 'result', type: 'number' }], graph: { @@ -384,8 +385,8 @@ describe('digital assets', () => { name: 'use-empty', context: 'js', nodes: [ - { name: 'num', type: 'number', props: [{ name: 'value', type: 'number', value: 5 }] }, - { name: 'empty', type: 'broken/empty' } + { name: 'num', type: 'const:number', props: [{ name: 'value', type: 'number', value: 5 }] }, + { name: 'empty', type: 'broken:empty' } ], edges: [ { src: { node: 'num', port: 'value' }, dst: { node: 'empty', port: 'x' } } @@ -405,7 +406,8 @@ describe('digital assets', () => { it('should throw if leaf node has no impl', async () => { const noImplDef: NodeDefinitionWithImpl = { context: 'js', - name: 'broken/no-impl', + name: 'no-impl', + category: 'broken', inputs: [{ name: 'x', type: 'number' }], outputs: [{ name: 'result', type: 'number' }] // No impl, no graph @@ -415,8 +417,8 @@ describe('digital assets', () => { name: 'use-no-impl', context: 'js', nodes: [ - { name: 'num', type: 'number', props: [{ name: 'value', type: 'number', value: 5 }] }, - { name: 'broken', type: 'broken/no-impl' } + { name: 'num', type: 'const:number', props: [{ name: 'value', type: 'number', value: 5 }] }, + { name: 'broken', type: 'broken:no-impl' } ], edges: [ { src: { node: 'num', port: 'value' }, dst: { node: 'broken', port: 'x' } } @@ -427,7 +429,7 @@ describe('digital assets', () => { definitions: [...mathDefinitions, noImplDef], outputNode: 'broken', outputPort: 'result' - })).rejects.toThrow('No implementation found for node: js:broken/no-impl'); + })).rejects.toThrow('No implementation found for node: js:broken:no-impl'); }); }); }); diff --git a/packages/evaluator/__tests__/evaluate.test.js b/packages/evaluator/__tests__/evaluate.test.js index 2e53463..fd33c68 100644 --- a/packages/evaluator/__tests__/evaluate.test.js +++ b/packages/evaluator/__tests__/evaluate.test.js @@ -9,9 +9,9 @@ describe('evaluate', () => { const graph = { name: 'simple-add', nodes: [ - { name: 'num1', type: 'js/const/number', props: [{ name: 'value', type: 'number', value: 5 }] }, - { name: 'num2', type: 'js/const/number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'add', type: 'js/math/add' } + { name: 'num1', type: 'const:number', props: [{ name: 'value', type: 'number', value: 5 }] }, + { name: 'num2', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'add', type: 'math:add' } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'add', port: 'a' } }, @@ -29,11 +29,11 @@ describe('evaluate', () => { const graph = { name: 'chained-math', nodes: [ - { name: 'num1', type: 'js/const/number', props: [{ name: 'value', type: 'number', value: 2 }] }, - { name: 'num2', type: 'js/const/number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'num3', type: 'js/const/number', props: [{ name: 'value', type: 'number', value: 4 }] }, - { name: 'add', type: 'js/math/add' }, - { name: 'multiply', type: 'js/math/multiply' } + { name: 'num1', type: 'const:number', props: [{ name: 'value', type: 'number', value: 2 }] }, + { name: 'num2', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'num3', type: 'const:number', props: [{ name: 'value', type: 'number', value: 4 }] }, + { name: 'add', type: 'math:add' }, + { name: 'multiply', type: 'math:multiply' } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'add', port: 'a' } }, @@ -56,17 +56,17 @@ describe('evaluate', () => { const trackingDefs = math_definitions_1.mathDefinitions.map(def => ({ ...def, impl: (inputs, props) => { - evaluatedNodes.push(def.type); + evaluatedNodes.push(def.name); return def.impl(inputs, props); } })); const graph = { name: 'lazy-test', nodes: [ - { name: 'num1', type: 'js/const/number', props: [{ name: 'value', type: 'number', value: 5 }] }, - { name: 'num2', type: 'js/const/number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'unused', type: 'js/const/number', props: [{ name: 'value', type: 'number', value: 999 }] }, - { name: 'add', type: 'js/math/add' } + { name: 'num1', type: 'const:number', props: [{ name: 'value', type: 'number', value: 5 }] }, + { name: 'num2', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'unused', type: 'const:number', props: [{ name: 'value', type: 'number', value: 999 }] }, + { name: 'add', type: 'math:add' } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'add', port: 'a' } }, @@ -80,9 +80,9 @@ describe('evaluate', () => { outputPort: 'sum' }); // 'unused' node should NOT be evaluated - expect(evaluatedNodes).toContain('js/const/number'); - expect(evaluatedNodes).toContain('js/math/add'); - expect(evaluatedNodes.filter(n => n === 'js/const/number').length).toBe(2); // Only num1 and num2 + expect(evaluatedNodes).toContain('number'); + expect(evaluatedNodes).toContain('add'); + expect(evaluatedNodes.filter(n => n === 'number').length).toBe(2); // Only num1 and num2 }); }); describe('UI vdom generation', () => { @@ -92,7 +92,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'ui/layout/Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: 'min-h-screen' } @@ -119,7 +119,7 @@ describe('evaluate', () => { nodes: [ { name: 'form', - type: 'ui/form/Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'myForm' }, { name: 'className', type: 'string', value: 'flex gap-4' } @@ -127,7 +127,7 @@ describe('evaluate', () => { }, { name: 'emailInput', - type: 'ui/form/Input', + type: 'form:Input', props: [ { name: 'key', type: 'string', value: 'email' }, { name: 'name', type: 'string', value: 'email' }, @@ -137,7 +137,7 @@ describe('evaluate', () => { }, { name: 'submitButton', - type: 'ui/form/Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'submit' }, { name: 'type', type: 'string', value: 'submit' }, @@ -180,7 +180,7 @@ describe('evaluate', () => { nodes: [ { name: 'form', - type: 'ui/form/Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'myForm' }, { name: 'className', type: 'string', value: '' } @@ -188,7 +188,7 @@ describe('evaluate', () => { }, { name: 'first', - type: 'ui/form/Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'first' }, { name: 'text', type: 'string', value: 'First' } @@ -196,7 +196,7 @@ describe('evaluate', () => { }, { name: 'second', - type: 'ui/form/Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'second' }, { name: 'text', type: 'string', value: 'Second' } @@ -224,7 +224,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'ui/layout/Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: 'min-h-screen' } @@ -232,7 +232,7 @@ describe('evaluate', () => { }, { name: 'form', - type: 'ui/form/Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'newsletterForm' }, { name: 'className', type: 'string', value: 'mt-10 flex gap-x-4' } @@ -240,7 +240,7 @@ describe('evaluate', () => { }, { name: 'emailInput', - type: 'ui/form/Input', + type: 'form:Input', props: [ { name: 'key', type: 'string', value: 'email' }, { name: 'name', type: 'string', value: 'email' }, @@ -250,7 +250,7 @@ describe('evaluate', () => { }, { name: 'submitButton', - type: 'ui/form/Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'submit' }, { name: 'type', type: 'string', value: 'submit' }, @@ -334,7 +334,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'ui/layout/Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: 'container' } @@ -342,7 +342,7 @@ describe('evaluate', () => { }, { name: 'header', - type: 'ui/content/Text', + type: 'content:Text', props: [ { name: 'key', type: 'string', value: 'header' }, { name: 'content', type: 'string', value: 'Welcome' } @@ -350,7 +350,7 @@ describe('evaluate', () => { }, { name: 'form', - type: 'ui/form/Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'signupForm' }, { name: 'className', type: 'string', value: 'flex gap-4' } @@ -358,7 +358,7 @@ describe('evaluate', () => { }, { name: 'emailInput', - type: 'ui/form/Input', + type: 'form:Input', props: [ { name: 'key', type: 'string', value: 'email' }, { name: 'name', type: 'string', value: 'email' }, @@ -368,7 +368,7 @@ describe('evaluate', () => { }, { name: 'submitButton', - type: 'ui/form/Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'submit' }, { name: 'type', type: 'string', value: 'submit' }, @@ -400,7 +400,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'ui/layout/Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: 'container' } @@ -408,7 +408,7 @@ describe('evaluate', () => { }, { name: 'header', - type: 'ui/content/Text', + type: 'content:Text', props: [ { name: 'key', type: 'string', value: 'header' }, { name: 'content', type: 'string', value: 'Welcome' } @@ -424,7 +424,7 @@ describe('evaluate', () => { nodes: [ { name: 'form', - type: 'ui/form/Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'signupForm' }, { name: 'className', type: 'string', value: 'flex gap-4' } @@ -432,7 +432,7 @@ describe('evaluate', () => { }, { name: 'emailInput', - type: 'ui/form/Input', + type: 'form:Input', props: [ { name: 'key', type: 'string', value: 'email' }, { name: 'name', type: 'string', value: 'email' }, @@ -442,7 +442,7 @@ describe('evaluate', () => { }, { name: 'submitButton', - type: 'ui/form/Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'submit' }, { name: 'type', type: 'string', value: 'submit' }, @@ -484,7 +484,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'ui/layout/Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: '' } @@ -492,7 +492,7 @@ describe('evaluate', () => { }, { name: 'welcomeText', - type: 'ui/content/Text', + type: 'content:Text', props: [ { name: 'key', type: 'string', value: 'welcome' }, { name: 'content', type: 'string', value: 'Hello World' } @@ -514,7 +514,7 @@ describe('evaluate', () => { }, { name: 'form', - type: 'ui/form/Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'wrapper' }, { name: 'className', type: 'string', value: 'form-wrapper' } diff --git a/packages/evaluator/__tests__/evaluate.test.ts b/packages/evaluator/__tests__/evaluate.test.ts index 4c1b664..ddb4a78 100644 --- a/packages/evaluator/__tests__/evaluate.test.ts +++ b/packages/evaluator/__tests__/evaluate.test.ts @@ -10,9 +10,9 @@ describe('evaluate', () => { name: 'simple-add', context: 'js', nodes: [ - { name: 'num1', type: 'number', props: [{ name: 'value', type: 'number', value: 5 }] }, - { name: 'num2', type: 'number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'add', type: 'add' } + { name: 'num1', type: 'const:number', props: [{ name: 'value', type: 'number', value: 5 }] }, + { name: 'num2', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'add', type: 'math:add' } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'add', port: 'a' } }, @@ -34,11 +34,11 @@ describe('evaluate', () => { name: 'chained-math', context: 'js', nodes: [ - { name: 'num1', type: 'number', props: [{ name: 'value', type: 'number', value: 2 }] }, - { name: 'num2', type: 'number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'num3', type: 'number', props: [{ name: 'value', type: 'number', value: 4 }] }, - { name: 'add', type: 'add' }, - { name: 'multiply', type: 'multiply' } + { name: 'num1', type: 'const:number', props: [{ name: 'value', type: 'number', value: 2 }] }, + { name: 'num2', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'num3', type: 'const:number', props: [{ name: 'value', type: 'number', value: 4 }] }, + { name: 'add', type: 'math:add' }, + { name: 'multiply', type: 'math:multiply' } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'add', port: 'a' } }, @@ -74,10 +74,10 @@ describe('evaluate', () => { name: 'lazy-test', context: 'js', nodes: [ - { name: 'num1', type: 'number', props: [{ name: 'value', type: 'number', value: 5 }] }, - { name: 'num2', type: 'number', props: [{ name: 'value', type: 'number', value: 3 }] }, - { name: 'unused', type: 'number', props: [{ name: 'value', type: 'number', value: 999 }] }, - { name: 'add', type: 'add' } + { name: 'num1', type: 'const:number', props: [{ name: 'value', type: 'number', value: 5 }] }, + { name: 'num2', type: 'const:number', props: [{ name: 'value', type: 'number', value: 3 }] }, + { name: 'unused', type: 'const:number', props: [{ name: 'value', type: 'number', value: 999 }] }, + { name: 'add', type: 'math:add' } ], edges: [ { src: { node: 'num1', port: 'value' }, dst: { node: 'add', port: 'a' } }, @@ -107,7 +107,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: 'min-h-screen' } @@ -138,7 +138,7 @@ describe('evaluate', () => { nodes: [ { name: 'form', - type: 'Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'myForm' }, { name: 'className', type: 'string', value: 'flex gap-4' } @@ -146,7 +146,7 @@ describe('evaluate', () => { }, { name: 'emailInput', - type: 'Input', + type: 'form:Input', props: [ { name: 'key', type: 'string', value: 'email' }, { name: 'name', type: 'string', value: 'email' }, @@ -156,7 +156,7 @@ describe('evaluate', () => { }, { name: 'submitButton', - type: 'Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'submit' }, { name: 'type', type: 'string', value: 'submit' }, @@ -203,7 +203,7 @@ describe('evaluate', () => { nodes: [ { name: 'form', - type: 'Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'myForm' }, { name: 'className', type: 'string', value: '' } @@ -211,7 +211,7 @@ describe('evaluate', () => { }, { name: 'first', - type: 'Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'first' }, { name: 'text', type: 'string', value: 'First' } @@ -219,7 +219,7 @@ describe('evaluate', () => { }, { name: 'second', - type: 'Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'second' }, { name: 'text', type: 'string', value: 'Second' } @@ -251,7 +251,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: 'min-h-screen' } @@ -259,7 +259,7 @@ describe('evaluate', () => { }, { name: 'form', - type: 'Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'newsletterForm' }, { name: 'className', type: 'string', value: 'mt-10 flex gap-x-4' } @@ -267,7 +267,7 @@ describe('evaluate', () => { }, { name: 'emailInput', - type: 'Input', + type: 'form:Input', props: [ { name: 'key', type: 'string', value: 'email' }, { name: 'name', type: 'string', value: 'email' }, @@ -277,7 +277,7 @@ describe('evaluate', () => { }, { name: 'submitButton', - type: 'Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'submit' }, { name: 'type', type: 'string', value: 'submit' }, @@ -366,7 +366,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: 'container' } @@ -374,7 +374,7 @@ describe('evaluate', () => { }, { name: 'header', - type: 'Text', + type: 'content:Text', props: [ { name: 'key', type: 'string', value: 'header' }, { name: 'content', type: 'string', value: 'Welcome' } @@ -382,7 +382,7 @@ describe('evaluate', () => { }, { name: 'form', - type: 'Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'signupForm' }, { name: 'className', type: 'string', value: 'flex gap-4' } @@ -390,7 +390,7 @@ describe('evaluate', () => { }, { name: 'emailInput', - type: 'Input', + type: 'form:Input', props: [ { name: 'key', type: 'string', value: 'email' }, { name: 'name', type: 'string', value: 'email' }, @@ -400,7 +400,7 @@ describe('evaluate', () => { }, { name: 'submitButton', - type: 'Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'submit' }, { name: 'type', type: 'string', value: 'submit' }, @@ -436,7 +436,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: 'container' } @@ -444,7 +444,7 @@ describe('evaluate', () => { }, { name: 'header', - type: 'Text', + type: 'content:Text', props: [ { name: 'key', type: 'string', value: 'header' }, { name: 'content', type: 'string', value: 'Welcome' } @@ -460,7 +460,7 @@ describe('evaluate', () => { nodes: [ { name: 'form', - type: 'Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'signupForm' }, { name: 'className', type: 'string', value: 'flex gap-4' } @@ -468,7 +468,7 @@ describe('evaluate', () => { }, { name: 'emailInput', - type: 'Input', + type: 'form:Input', props: [ { name: 'key', type: 'string', value: 'email' }, { name: 'name', type: 'string', value: 'email' }, @@ -478,7 +478,7 @@ describe('evaluate', () => { }, { name: 'submitButton', - type: 'Button', + type: 'form:Button', props: [ { name: 'key', type: 'string', value: 'submit' }, { name: 'type', type: 'string', value: 'submit' }, @@ -524,7 +524,7 @@ describe('evaluate', () => { nodes: [ { name: 'page', - type: 'Page', + type: 'layout:Page', props: [ { name: 'key', type: 'string', value: 'home' }, { name: 'className', type: 'string', value: '' } @@ -532,7 +532,7 @@ describe('evaluate', () => { }, { name: 'welcomeText', - type: 'Text', + type: 'content:Text', props: [ { name: 'key', type: 'string', value: 'welcome' }, { name: 'content', type: 'string', value: 'Hello World' } @@ -554,7 +554,7 @@ describe('evaluate', () => { }, { name: 'form', - type: 'Form', + type: 'form:Form', props: [ { name: 'key', type: 'string', value: 'wrapper' }, { name: 'className', type: 'string', value: 'form-wrapper' } diff --git a/packages/evaluator/src/definitions/core.js b/packages/evaluator/src/definitions/core.js index 0db6fc7..f452f9d 100644 --- a/packages/evaluator/src/definitions/core.js +++ b/packages/evaluator/src/definitions/core.js @@ -18,9 +18,9 @@ exports.coreDefinitions = exports.stringConcatDef = exports.stringTemplateDef = * - value: any (the extracted value) */ exports.jsonSelectDef = { - context: 'core', + context: 'js', + name: 'select', category: 'json', - type: 'core/json/select', icon: 'circle', inputs: [ { name: 'obj', type: 'json' } @@ -72,9 +72,9 @@ exports.jsonSelectDef = { * - value: json (the constructed object) */ exports.jsonObjectDef = { - context: 'core', + context: 'js', + name: 'object', category: 'json', - type: 'core/json/object', icon: 'braces', inputs: [], // Dynamic inputs - any input name is valid outputs: [ @@ -100,9 +100,9 @@ exports.jsonObjectDef = { * - error: json (the error if failed) */ exports.flowGuardDef = { - context: 'core', + context: 'js', + name: 'guard', category: 'flow', - type: 'core/flow/guard', icon: 'zap', inputs: [ { name: 'ok', type: 'boolean' }, @@ -146,9 +146,9 @@ exports.flowGuardDef = { * - value: string (the resulting string) */ exports.stringTemplateDef = { - context: 'core', + context: 'js', + name: 'template', category: 'string', - type: 'core/string/template', icon: 'quote', inputs: [], // Dynamic inputs based on template placeholders outputs: [ @@ -188,9 +188,9 @@ exports.stringTemplateDef = { * - value: string (the resulting string) */ exports.stringConcatDef = { - context: 'core', + context: 'js', + name: 'concat', category: 'string', - type: 'core/string/concat', icon: 'link', inputs: [ { name: 'value', type: 'string' } diff --git a/packages/evaluator/src/definitions/math.js b/packages/evaluator/src/definitions/math.js index 4683186..3e986c6 100644 --- a/packages/evaluator/src/definitions/math.js +++ b/packages/evaluator/src/definitions/math.js @@ -6,8 +6,8 @@ exports.mathDefinitions = exports.multiplyDef = exports.addDef = exports.constNu */ exports.constNumberDef = { context: 'js', + name: 'number', category: 'const', - type: 'js/const/number', icon: 'hash', outputs: [{ name: 'value', type: 'number' }], props: [{ name: 'value', type: 'number', default: 0 }], @@ -18,8 +18,8 @@ exports.constNumberDef = { }; exports.addDef = { context: 'js', + name: 'add', category: 'math', - type: 'js/math/add', icon: 'plus', inputs: [ { name: 'a', type: 'number' }, @@ -33,8 +33,8 @@ exports.addDef = { }; exports.multiplyDef = { context: 'js', + name: 'multiply', category: 'math', - type: 'js/math/multiply', icon: 'x', inputs: [ { name: 'a', type: 'number' }, diff --git a/packages/evaluator/src/definitions/net.js b/packages/evaluator/src/definitions/net.js index c8a00f1..e35326f 100644 --- a/packages/evaluator/src/definitions/net.js +++ b/packages/evaluator/src/definitions/net.js @@ -24,9 +24,9 @@ exports.netDefinitions = exports.graphqlRequestDef = void 0; * - ok: boolean (true if request succeeded without errors) */ exports.graphqlRequestDef = { - context: 'net', + context: 'js', + name: 'request', category: 'graphql', - type: 'net/graphql/request', icon: 'graphql', inputs: [ { name: 'variables', type: 'json' }, diff --git a/packages/evaluator/src/definitions/ui.js b/packages/evaluator/src/definitions/ui.js index 1de0f83..0a261b6 100644 --- a/packages/evaluator/src/definitions/ui.js +++ b/packages/evaluator/src/definitions/ui.js @@ -25,8 +25,8 @@ function coerceValue(value, valueType) { // Boundary node definitions for graph inputs/outputs exports.graphInputDef = { context: 'core', + name: 'graphInput', category: 'graph', - type: 'core/graph/input', icon: 'arrow-right', inputs: [], outputs: [{ name: 'value', type: 'any' }], @@ -39,8 +39,8 @@ exports.graphInputDef = { }; exports.graphOutputDef = { context: 'core', + name: 'graphOutput', category: 'graph', - type: 'core/graph/output', icon: 'arrow-left', inputs: [{ name: 'value', type: 'any' }], outputs: [], @@ -52,8 +52,8 @@ exports.graphOutputDef = { }; exports.graphPropDef = { context: 'core', + name: 'graphProp', category: 'graph', - type: 'core/graph/prop', icon: 'settings', inputs: [], outputs: [{ name: 'value', type: 'any' }], @@ -65,9 +65,9 @@ exports.graphPropDef = { impl: (_inputs, props) => ({ value: coerceValue(props?.value ?? props?.default, props?.valueType ?? 'any') }) }; exports.pageDef = { - context: 'ui', + context: 'js', + name: 'Page', category: 'layout', - type: 'ui/layout/Page', icon: 'file', inputs: [ { name: 'children', type: 'Element[]', multi: true } @@ -88,9 +88,9 @@ exports.pageDef = { }) }; exports.formDef = { - context: 'ui', + context: 'js', + name: 'Form', category: 'form', - type: 'ui/form/Form', icon: 'file-text', inputs: [ { name: 'children', type: 'Element[]', multi: true } @@ -111,9 +111,9 @@ exports.formDef = { }) }; exports.inputDef = { - context: 'ui', + context: 'js', + name: 'Input', category: 'form', - type: 'ui/form/Input', icon: 'text-cursor', inputs: [], outputs: [{ name: 'element', type: 'Element' }], @@ -137,9 +137,9 @@ exports.inputDef = { }) }; exports.buttonDef = { - context: 'ui', + context: 'js', + name: 'Button', category: 'form', - type: 'ui/form/Button', icon: 'square', inputs: [], outputs: [{ name: 'element', type: 'Element' }], @@ -161,9 +161,9 @@ exports.buttonDef = { }) }; exports.textDef = { - context: 'ui', + context: 'js', + name: 'Text', category: 'content', - type: 'ui/content/Text', icon: 'type', inputs: [], outputs: [{ name: 'element', type: 'Element' }], diff --git a/packages/evaluator/src/evaluate.ts b/packages/evaluator/src/evaluate.ts index b303d4f..b79f450 100644 --- a/packages/evaluator/src/evaluate.ts +++ b/packages/evaluator/src/evaluate.ts @@ -2,10 +2,20 @@ import type { Graph, Node, Edge, Port } from '@fbp/types'; import type { NodeDefinitionWithImpl, EvaluateOptions } from './types'; /** - * Build a definition map key from context and name. + * Build a definition map key from context, category, and name. */ -function defKey(context: string, name: string): string { - return `${context}:${name}`; +function defKey(context: string, category: string, name: string): string { + return `${context}:${category}:${name}`; +} + +/** + * Parse a task_identifier (e.g. 'math:add') into [category, name]. + * Boundary nodes (no colon) return ['core', type]. + */ +function parseTaskIdentifier(type: string): [string, string] { + const idx = type.indexOf(':'); + if (idx === -1) return ['core', type]; + return [type.slice(0, idx), type.slice(idx + 1)]; } /** @@ -28,7 +38,7 @@ export async function evaluate(graph: Graph, options: EvaluateOptions): Promise< const defMap = new Map(); for (const def of definitions) { - defMap.set(defKey(def.context, def.name), def); + defMap.set(defKey(def.context, def.category, def.name), def); } // Graph-level context used as default for node lookups @@ -169,7 +179,8 @@ export async function evaluate(graph: Graph, options: EvaluateOptions): Promise< // Get the definition for this node — resolve context from node override or graph default const nodeContext = node.context || graphContext; - const definition = defMap.get(defKey(nodeContext, node.type)); + const [nodeCategory, nodeDefName] = parseTaskIdentifier(node.type); + const definition = defMap.get(defKey(nodeContext, nodeCategory, nodeDefName)); if (!definition) { throw new Error(`No definition found for node: ${nodeContext}:${node.type}`); } diff --git a/packages/evaluator/src/index.d.ts b/packages/evaluator/src/index.d.ts index bf445d8..6a3b9bb 100644 --- a/packages/evaluator/src/index.d.ts +++ b/packages/evaluator/src/index.d.ts @@ -1,5 +1,5 @@ export { evaluate } from './evaluate'; -export type { NodeImplFn, NodeDefinitionWithImpl, EvaluateOptions, PropDefinitionWithOptions } from './types'; +export type { NodeImplFn, NodeDefinitionWithImpl, EvaluateOptions } from './types'; export { mathDefinitions, constNumberDef, addDef, multiplyDef } from './definitions/math'; export { uiDefinitions, pageDef, formDef, inputDef, buttonDef, textDef, graphInputDef, graphOutputDef, graphPropDef } from './definitions/ui'; export { coreDefinitions, jsonSelectDef, jsonObjectDef, flowGuardDef, stringTemplateDef, stringConcatDef } from './definitions/core'; diff --git a/packages/evaluator/src/index.ts b/packages/evaluator/src/index.ts index 5faf65e..b35b21b 100644 --- a/packages/evaluator/src/index.ts +++ b/packages/evaluator/src/index.ts @@ -1,5 +1,5 @@ export { evaluate } from './evaluate'; -export type { NodeImplFn, NodeDefinitionWithImpl, EvaluateOptions, PropDefinitionWithOptions } from './types'; +export type { NodeImplFn, NodeDefinitionWithImpl, EvaluateOptions } from './types'; // Re-export node definitions for convenience export { mathDefinitions, constNumberDef, addDef, multiplyDef } from './definitions/math'; diff --git a/packages/evaluator/src/types.d.ts b/packages/evaluator/src/types.d.ts index 09ac359..1dad0bf 100644 --- a/packages/evaluator/src/types.d.ts +++ b/packages/evaluator/src/types.d.ts @@ -1,24 +1,16 @@ -import type { NodeDefinition, PropDefinition } from '@fbp/types'; +import type { NodeDefinition } from '@fbp/types'; /** * A function that implements a node's computation. * Takes inputs (keyed by port name) and props, returns outputs (keyed by port name). * Can be sync or async - the evaluator will await the result. */ export type NodeImplFn = (inputs: Record, props: Record) => Record | Promise>; -/** - * Extended PropDefinition with runtime-only options for enum/select types. - * The options field is not part of the JSON schema but is used by the UI. - */ -export interface PropDefinitionWithOptions extends PropDefinition { - options?: string[]; -} /** * A NodeDefinition extended with an optional implementation function. * The impl function is not part of the JSON schema (not serializable), * but is used at runtime for evaluation. */ -export interface NodeDefinitionWithImpl extends Omit { - props?: PropDefinitionWithOptions[]; +export interface NodeDefinitionWithImpl extends NodeDefinition { impl?: NodeImplFn; } /** diff --git a/packages/evaluator/src/types.ts b/packages/evaluator/src/types.ts index a1aa427..3f9c63e 100644 --- a/packages/evaluator/src/types.ts +++ b/packages/evaluator/src/types.ts @@ -1,4 +1,4 @@ -import type { NodeDefinition, PropDefinition } from '@fbp/types'; +import type { NodeDefinition } from '@fbp/types'; /** * A function that implements a node's computation. @@ -10,21 +10,12 @@ export type NodeImplFn = ( props: Record ) => Record | Promise>; -/** - * Extended PropDefinition with runtime-only options for enum/select types. - * The options field is not part of the JSON schema but is used by the UI. - */ -export interface PropDefinitionWithOptions extends PropDefinition { - options?: string[]; -} - /** * A NodeDefinition extended with an optional implementation function. * The impl function is not part of the JSON schema (not serializable), * but is used at runtime for evaluation. */ -export interface NodeDefinitionWithImpl extends Omit { - props?: PropDefinitionWithOptions[]; +export interface NodeDefinitionWithImpl extends NodeDefinition { impl?: NodeImplFn; } diff --git a/packages/spec/src/renderer.d.ts b/packages/spec/src/renderer.d.ts index 270d354..efed13d 100644 --- a/packages/spec/src/renderer.d.ts +++ b/packages/spec/src/renderer.d.ts @@ -167,4 +167,4 @@ export interface MultiTabEditorState { /** Shared clipboard */ clipboard: ClipboardState; } -export type { Node, Graph, Edge, PortRef, PortDef, PropDef, PropValue, NodeDefinition, NodeMeta, DefinitionLibrary, } from './types'; +export type { Node, Graph, Edge, PortRef, PortDef, PropDef, PropValue, NodeDefinition, NodeMeta, } from './types'; diff --git a/packages/spec/src/types.d.ts b/packages/spec/src/types.d.ts index ec33b78..a27913b 100644 --- a/packages/spec/src/types.d.ts +++ b/packages/spec/src/types.d.ts @@ -20,6 +20,7 @@ export interface PropValue { export interface Node { name: string; type: string; + context?: string; meta?: NodeMeta; props?: PropValue[]; nodes?: Node[]; @@ -27,6 +28,7 @@ export interface Node { } export interface Graph { name?: string; + context?: string; nodes: Node[]; edges: Edge[]; definitions?: NodeDefinition[]; @@ -35,22 +37,29 @@ export interface Graph { export interface PortDef { name: string; type?: string; + schema?: Record; multi?: boolean; description?: string; } export interface PropDef { name: string; type?: string; + schema?: Record; default?: any; description?: string; + required?: boolean; + options?: string[]; } export interface NodeDefinition { - type: string; - context?: string; - category?: string; + context: string; + name: string; + category: string; inputs?: PortDef[]; outputs?: PortDef[]; props?: PropDef[]; + graph?: Graph; + volatile?: boolean; + runtime?: string; icon?: string; description?: string; } diff --git a/packages/spec/src/types.ts b/packages/spec/src/types.ts index ba4d884..72b7d50 100644 --- a/packages/spec/src/types.ts +++ b/packages/spec/src/types.ts @@ -47,16 +47,19 @@ export interface PropDef { schema?: Record; default?: any; description?: string; + required?: boolean; + options?: string[]; } export interface NodeDefinition { context: string; name: string; - category?: string; + category: string; inputs?: PortDef[]; outputs?: PortDef[]; props?: PropDef[]; graph?: Graph; volatile?: boolean; + runtime?: string; icon?: string; description?: string; } \ No newline at end of file diff --git a/packages/types/src/types.d.ts b/packages/types/src/types.d.ts index 76275bf..268c4d0 100644 --- a/packages/types/src/types.d.ts +++ b/packages/types/src/types.d.ts @@ -7,6 +7,7 @@ export interface Metadata { export interface Port { name: string; type: string; + schema?: Record; description?: string; optional?: boolean; multi?: boolean; @@ -23,9 +24,11 @@ export interface Prop { export interface PropDefinition { name: string; type: string; + schema?: Record; default?: any; description?: string; required?: boolean; + options?: string[]; } export interface EdgeEndpoint { node: string; @@ -42,21 +45,23 @@ export interface Group { nodes: string[]; meta?: Metadata; } -export type NodeKind = "node" | "subnet" | "graphInput" | "graphOutput" | "graphProp"; export interface NodeDefinition { context: string; + name: string; category: string; - type: string; inputs?: Port[]; outputs?: Port[]; props?: PropDefinition[]; + graph?: Graph; + volatile?: boolean; + runtime?: string; description?: string; icon?: string; } export interface Node { name: string; type: string; - kind?: NodeKind; + context?: string; meta?: Metadata; props?: Prop[]; inputs?: Port[]; @@ -67,6 +72,7 @@ export interface Node { } export interface Graph { name: string; + context?: string; definitions?: NodeDefinition[]; inputs?: Port[]; outputs?: Port[]; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index d271328..8c38f25 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -28,6 +28,7 @@ export interface PropDefinition { default?: any; description?: string; required?: boolean; + options?: string[]; } export interface EdgeEndpoint { node: string; @@ -47,12 +48,13 @@ export interface Group { export interface NodeDefinition { context: string; name: string; - category?: string; + category: string; inputs?: Port[]; outputs?: Port[]; props?: PropDefinition[]; graph?: Graph; volatile?: boolean; + runtime?: string; description?: string; icon?: string; }