# @flowgix/core

A powerful and flexible core library for the Flowgix plugin system, written in F# and compiled to JavaScript using Fable. This library provides the foundation for building node-based visual programming environments with a robust plugin architecture.

## Features

- **Plugin System**: A robust plugin architecture that allows for easy extension of functionality
  - Dynamic plugin registration and unregistration
  - Plugin lifecycle management (initialization and cleanup)
  - Custom plugin configurations and engines
- **Node Graph Engine**: A powerful engine for evaluating node graphs with support for:
  - Custom node types with type-safe inputs and outputs
  - Port types with automatic type conversion
  - Control system for node configuration
  - Circular dependency detection
  - Caching for performance optimization
- **Type Safety**: Full TypeScript support with comprehensive type definitions
  - Strong typing for all core components
  - Type-safe plugin development
  - Runtime type checking and validation
- **Extensibility**: Easy to extend with custom node types, port types, and plugins
  - Flexible plugin API
  - Custom resolvers for ports and nodes
  - Support for custom renderers and controls

## Installation

### NPM (JavaScript/TypeScript)
```bash
npm install @flowgix/core
```

### NuGet (.NET/C#/F#)
```bash
# Using the .NET CLI
dotnet add package Flowgix.Core

# Using the Package Manager Console
Install-Package Flowgix.Core
```

### Package References

#### C# (.csproj)
```xml
<PackageReference Include="Flowgix.Core" Version="0.1.0" />
```

#### F# (.fsproj)
```xml
<PackageReference Include="Flowgix.Core" Version="0.1.0" />
```

## Prerequisites

For development:
- [.NET SDK](https://dotnet.microsoft.com/download) (version 8.0 or later)
- [Node.js](https://nodejs.org/) (version 18+ or 20+)
- [npm](https://www.npmjs.com/)

## Development Setup

1. Install Fable as a local tool:
```bash
dotnet new tool-manifest
dotnet tool install fable
```

2. Install npm dependencies:
```bash
npm install
```

## Building

### One-time Build
```bash
npm run build
```

### Watch Mode (for development)
```bash
npm run watch
```

## Testing

### Run Tests Once
```bash
npm test
```

### Watch Mode for Tests
```bash
npm run test:watch
```

## Publishing NuGet Packages

### Prerequisites
- A NuGet account with an API key
- The package version updated in `Flowgix.Core.nuspec`
- All tests passing
- A clean build

### Steps to Publish

1. Update the version in `Flowgix.Core.nuspec`:
```xml
<version>0.1.0</version>
```

2. Build the project:
```bash
npm run build
```

3. Run tests to ensure everything is working:
```bash
npm test
```

4. Create the NuGet package:
```bash
dotnet pack Flowgix.Core.sln -c Release
```

5. Push the package to NuGet:
```bash
dotnet nuget push bin/Release/Flowgix.Core.0.1.0.nupkg -k YOUR_API_KEY -s https://api.nuget.org/v3/index.json
```

Replace `YOUR_API_KEY` with your NuGet API key. You can get an API key from your NuGet account settings.

### Troubleshooting
- If you get a 403 error, make sure your API key is correct and has the right permissions
- If you get a 409 error, the package version already exists. Update the version number and try again
- If you get build errors, make sure all tests are passing and the build is clean

## Project Structure

```
flowgix-core/
├── src/
│   ├── Types.fs        # Core type definitions
│   ├── Plugin.fs       # Plugin system implementation
│   ├── Engine.fs       # Node graph engine
│   ├── index.ts        # TypeScript declarations
│   └── example.ts      # Usage example
├── tests/              # Test files
├── dist/              # Compiled output
├── package.json       # npm package configuration
├── tsconfig.json      # TypeScript configuration
├── babel.config.js    # Babel configuration
└── jest.config.cjs    # Jest configuration
```

## Usage

### TS Example

```typescript
import { IFlowgixEngine, IPluginManager, type NodeType, type PortType } from 'flowgix-core';

// Define port types
const numberPort: PortType = {
    name: 'number',
    type: 'number',
    resolver: (type, inputData, value) => Number(value)
};

// Define node types
const addNumbers: NodeType = {
    id: 'add-numbers',
    name: 'Add Numbers',
    description: 'Adds two numbers together',
    category: 'Math',
    inputs: ['a', 'b'],
    outputs: ['result'],
    controls: [],
    resolver: (node, inputData, nodeType, context) => {
        const a = Number(inputData['a']);
        const b = Number(inputData['b']);
        return { result: a + b };
    }
};

// Create a plugin
const mathPlugin = {
    id: 'math-plugin',
    name: 'Math Plugin',
    description: 'Basic mathematical operations',
    nodeTypes: [addNumbers],
    portTypes: [numberPort]
};

// Initialize the plugin manager
const pluginManager: IPluginManager = {
    registerPlugin: (registration) => { /* ... */ },
    unregisterPlugin: (pluginId) => { /* ... */ },
    getPluginConfig: (pluginId) => { /* ... */ },
    getPluginEngine: (pluginId) => { /* ... */ },
    getPlugins: () => { /* ... */ },
    onInit: () => { /* ... */ },
    onCleanup: () => { /* ... */ }
};

// Create and evaluate a graph
const engine: IFlowgixEngine = {
    createConfig: (nodeTypes, portTypes, controls) => { /* ... */ },
    createEngine: (config, portResolver, nodeResolver) => { /* ... */ },
    evaluateGraph: (engine, nodes) => { /* ... */ }
};

const config = engine.createConfig(
    [addNumbers],
    [numberPort],
    []
);

const rootEngine = engine.createEngine(
    config,
    (type, inputData, value) => Number(value),
    (node, inputData, nodeType, context) => {
        const resolver = nodeType.resolver;
        if (resolver) {
            return resolver(node, inputData, nodeType, context);
        }
        return {};
    }
);

const nodes = new Map([
    ['node1', {
        id: 'node1',
        type: 'add-numbers',
        root: true,
        inputData: { a: 5, b: 3 },
        connections: {}
    }]
]);

const result = engine.evaluateGraph(rootEngine, nodes);
console.log(result); // { result: 8 }
```

### F# Example

```fsharp
open Flowgix.Core

// Define port types
let numberPort = {
    name = "number"
    type = "number"
    resolver = fun type inputData value -> 
        match value with
        | :? string as s -> float s
        | :? float as f -> f
        | _ -> 0.0
}

// Define node types
let addNumbers = {
    id = "add-numbers"
    name = "Add Numbers"
    description = "Adds two numbers together"
    category = "Math"
    inputs = ["a"; "b"]
    outputs = ["result"]
    controls = []
    resolver = fun node inputData nodeType context ->
        let a = float inputData["a"]
        let b = float inputData["b"]
        Map [("result", a + b)]
}

// Create a plugin
let mathPlugin = {
    id = "math-plugin"
    name = "Math Plugin"
    description = "Basic mathematical operations"
    nodeTypes = [addNumbers]
    portTypes = [numberPort]
}

// Initialize the plugin manager
let pluginManager = {
    registerPlugin = fun registration -> ()
    unregisterPlugin = fun pluginId -> ()
    getPluginConfig = fun pluginId -> Some mathPlugin
    getPluginEngine = fun pluginId -> None
    getPlugins = fun () -> [mathPlugin]
    onInit = fun () -> ()
    onCleanup = fun () -> ()
}

// Create and evaluate a graph
let engine = {
    createConfig = fun nodeTypes portTypes controls -> 
        { nodeTypes = nodeTypes; portTypes = portTypes; controls = controls }
    createEngine = fun config portResolver nodeResolver -> 
        { config = config; portResolver = portResolver; nodeResolver = nodeResolver }
    evaluateGraph = fun engine nodes -> 
        let node = Map.find "node1" nodes
        let result = engine.nodeResolver node node.inputData addNumbers Map.empty
        result
}

let config = engine.createConfig [addNumbers] [numberPort] []

let rootEngine = engine.createEngine(
    config,
    (fun type inputData value -> float value),
    (fun node inputData nodeType context ->
        match nodeType.resolver with
        | Some resolver -> resolver node inputData nodeType context
        | None -> Map.empty)
)

let nodes = Map [
    ("node1", {
        id = "node1"
        type = "add-numbers"
        root = true
        inputData = Map [("a", 5.0); ("b", 3.0)]
        connections = Map.empty
    })
]

let result = engine.evaluateGraph(rootEngine, nodes)
printfn "%A" result // Map [("result", 8.0)]
```

### C# Example

```csharp
using Flowgix.Core;

// Define port types
var numberPort = new PortType
{
    Name = "number",
    Type = "number",
    Resolver = (type, inputData, value) => Convert.ToDouble(value)
};

// Define node types
var addNumbers = new NodeType
{
    Id = "add-numbers",
    Name = "Add Numbers",
    Description = "Adds two numbers together",
    Category = "Math",
    Inputs = new[] { "a", "b" },
    Outputs = new[] { "result" },
    Controls = Array.Empty<ControlType>(),
    Resolver = (node, inputData, nodeType, context) =>
    {
        var a = Convert.ToDouble(inputData["a"]);
        var b = Convert.ToDouble(inputData["b"]);
        return new Dictionary<string, object> { { "result", a + b } };
    }
};

// Create a plugin
var mathPlugin = new FlowgixPlugin
{
    Id = "math-plugin",
    Name = "Math Plugin",
    Description = "Basic mathematical operations",
    NodeTypes = new[] { addNumbers },
    PortTypes = new[] { numberPort }
};

// Initialize the plugin manager
var pluginManager = new PluginManager
{
    RegisterPlugin = registration => { },
    UnregisterPlugin = pluginId => { },
    GetPluginConfig = pluginId => mathPlugin,
    GetPluginEngine = pluginId => null,
    GetPlugins = () => new[] { mathPlugin },
    OnInit = () => { },
    OnCleanup = () => { }
};

// Create and evaluate a graph
var engine = new FlowgixEngine
{
    CreateConfig = (nodeTypes, portTypes, controls) =>
        new FlowgixConfig { NodeTypes = nodeTypes, PortTypes = portTypes, Controls = controls },
    CreateEngine = (config, portResolver, nodeResolver) =>
        new FlowgixRootEngine { Config = config, PortResolver = portResolver, NodeResolver = nodeResolver },
    EvaluateGraph = (engine, nodes) =>
    {
        var node = nodes["node1"];
        var result = engine.NodeResolver(node, node.InputData, addNumbers, new Dictionary<string, object>());
        return result;
    }
};

var config = engine.CreateConfig(new[] { addNumbers }, new[] { numberPort }, Array.Empty<Control>());

var rootEngine = engine.CreateEngine(
    config,
    (type, inputData, value) => Convert.ToDouble(value),
    (node, inputData, nodeType, context) =>
    {
        if (nodeType.Resolver != null)
        {
            return nodeType.Resolver(node, inputData, nodeType, context);
        }
        return new Dictionary<string, object>();
    }
);

var nodes = new Dictionary<string, FlumeNode>
{
    ["node1"] = new FlumeNode
    {
        Id = "node1",
        Type = "add-numbers",
        Root = true,
        InputData = new Dictionary<string, object> { { "a", 5.0 }, { "b", 3.0 } },
        Connections = new Dictionary<string, object>()
    }
};

var result = engine.EvaluateGraph(rootEngine, nodes);
Console.WriteLine(string.Join(", ", result.Select(kvp => $"{kvp.Key}: {kvp.Value}"))); // result: 8
```

## Core Components

### Control Types
The library supports various control types for node configuration:
- Text
- Number
- Checkbox
- Select
- MultiSelect
- Custom
- Boolean
- Object

### Port Types
Built-in port types include:
- NumberPort
- TextPort
- BooleanPort
- OutcomeActionCollectionPort
- LLMAgentPort
- CustomPort (for custom implementations)

### Node Types
Core node types include:
- AddNumbers
- FlowgixResults
- OutcomeAttributes
- DivideNumbers
- CustomNode (for custom implementations)

## API Reference

### Core Types

#### `ControlType`
```typescript
interface ControlType {
    type: string;
    label: string;
    name: string;
    portName: string;
    defaultValue: any;
}
```

#### `PortType`
```typescript
interface PortType {
    name: string;
    type: string;
    resolver?: (type: string, inputData: any, value: any) => any;
}
```

#### `NodeType`
```typescript
interface NodeType {
    id: string;
    name: string;
    description: string;
    category: string;
    inputs: string[];
    outputs: string[];
    controls: ControlType[];
    resolver?: (node: any, inputData: any, nodeType: NodeType, context: any) => Record<string, any>;
}
```

### Plugin System

#### `IPluginManager`
```typescript
interface IPluginManager {
    registerPlugin(registration: PluginRegistration): void;
    unregisterPlugin(pluginId: string): void;
    getPluginConfig(pluginId: string): FlowgixPlugin | undefined;
    getPluginEngine(pluginId: string): FlowgixRootEngine | undefined;
    getPlugins(): FlowgixPlugin[];
    onInit(): void;
    onCleanup(): void;
}
```

### Engine

#### `IFlowgixEngine`
```typescript
interface IFlowgixEngine {
    createConfig(
        nodeTypes: NodeType[],
        portTypes: PortType[],
        controls: Control[]
    ): FlowgixConfig;

    createEngine(
        config: FlowgixConfig,
        portResolver: (type: string, inputData: any, value: any) => any,
        nodeResolver: (node: FlumeNode, inputData: any, nodeType: NodeType, context: any) => Record<string, any>
    ): FlowgixRootEngine;

    evaluateGraph(engine: FlowgixRootEngine, nodes: Map<string, FlumeNode>): Record<string, any>;
}
```

## Publishing

To publish this package to npm:

1. Build the package:
```bash
npm run build
```

2. Test the package:
```bash
npm test
```

3. Update the version in package.json:
```bash
npm version patch  # for bug fixes
npm version minor  # for new features
npm version major  # for breaking changes
```

4. Login to npm (if not already logged in):
```bash
npm login
```

5. Publish the package:
```bash
npm publish
```

For scoped packages (e.g., @your-org/flowgix-core):
```bash
npm publish --access public
```

### Pre-publish Checklist
- [ ] All tests pass
- [ ] Build is successful
- [ ] Version is updated
- [ ] README is up to date
- [ ] No sensitive data in the package
- [ ] All necessary files are included
- [ ] package.json has correct metadata

## Contributing

1. Fork the repository at [github.com/flowgix/flowgix-core](https://github.com/flowgix/flowgix-core)
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Support

For support, please open an issue in the [GitHub repository](https://github.com/flowgix/flowgix-core/issues). 