NxGraph 1.1.0

dotnet add package NxGraph --version 1.1.0
                    
NuGet\Install-Package NxGraph -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NxGraph" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NxGraph" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="NxGraph" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NxGraph --version 1.1.0
                    
#r "nuget: NxGraph, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NxGraph@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NxGraph&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=NxGraph&version=1.1.0
                    
Install as a Cake Tool

NxGraph

A lean, high‑performance finite state machine (FSM) / stateflow library for .NET with a clean authoring DSL, strong validation, first‑class observability, and export tools (Mermaid, tracing, replay). Designed for correctness on hot paths (allocation‑free), production diagnostics, and pleasant authoring.


Why NxGraph

  • Simple, fast core: array‑backed Graph of Node[] and Transition[], single edge per node. Cache‑friendly and easy to reason about.
  • Explicit branching: choices/switches are modeled by director nodes, keeping the graph sparse and predictable.
  • Production diagnostics: validators, Mermaid exporter, tracing observers, and replay tooling.
  • Authoring ergonomics: fluent DSL with StartWith, .To(...), .If(...), .Switch(...), .WaitFor(...), .Timeout(...).

Features

  • ✅ Allocation‑aware execution (ValueTask<Result> hot paths)
  • ✅ Clear DSL for building graphs
  • ✅ Directors for branching (If, Switch, choice predicates)
  • ✅ Time primitives: WaitFor(TimeSpan), Timeout(TimeSpan) wrappers
  • ✅ Strong validation: broken edges, self‑loops, reachability, terminal paths
  • ✅ Observers: lifecycle + node/transition hooks; exceptions bubble by default
  • ✅ Mermaid exporter for architecture/ops visuals
  • ✅ Replay trace capture and deterministic playback
  • ✅ (Optional) OpenTelemetry‑style tracing via Activity
  • ✅ Serialization (JSON/MessagePack) for graphs and replays

Quick start

using System.Threading;
using System.Threading.Tasks;
using NxGraph;
using NxGraph.Authoring;

// 1) Define state logic (no allocations on hot path)
static ValueTask<Result> Acquire(CancellationToken ct) => ResultHelpers.Success;
static ValueTask<Result> Process(CancellationToken ct) => ResultHelpers.Success;
static ValueTask<Result> Release(CancellationToken ct) => ResultHelpers.Success;

// 2) Build the graph with the DSL
var graph = GraphBuilder
    .StartWith(Acquire)
    .To(Process)
    .To(Release)
    .Build();

// 3) Execute
var sm = graph.ToStateMachine();
await sm.ExecuteAsync(CancellationToken.None);

What you get

  • Deterministic, single‑edge execution
  • Easy branching via directors (see below)
  • Hooks for tracing/visualization

Core concepts

  • Graph: immutable structure of nodes and single outgoing transitions.
  • Node: wraps ILogic — work that returns a Result (Success, Failure, etc.).
  • Director: a special node that chooses the next node (e.g., If, Switch).
  • Transition: an index from node i → j.
  • State machine: a runtime over a Graph that executes from a start node until terminal.

Authoring DSL

Linear flows

static ValueTask<Result> Start(CancellationToken _) => ResultHelpers.Success;
static ValueTask<Result> End(CancellationToken _) => ResultHelpers.Success;

var graph = GraphBuilder
    .StartWith(Start)
    .To(End)
    .Build();

Branching with directors

static ValueTask<Result> Start(CancellationToken _) => ResultHelpers.Success;
static bool IsPremium() => true; // your predicate
static ValueTask<Result> Premium(CancellationToken _) => ResultHelpers.Success;
static ValueTask<Result> Standard(CancellationToken _) => ResultHelpers.Success;

var graph = GraphBuilder
    .StartWith(Start)
    .If(IsPremium)
        .Then(Premium)
        .Else(Standard)
    .Build();

Switch example:

static ValueTask<Result> Start(CancellationToken _) => ResultHelpers.Success;

static int RouteKey() => 2;
static ValueTask<Result> One(CancellationToken _) => ResultHelpers.Success;
static ValueTask<Result> Two(CancellationToken _) => ResultHelpers.Success;
static ValueTask<Result> Other(CancellationToken _) => ResultHelpers.Success;

var graph = GraphBuilder
    .StartWith(Start)
    .Switch(RouteKey)
        .Case(1, One)
        .Case(2, Two)
        .Default(Other)
    .End()
    .Build();

Delays & timeouts

var graph = GraphBuilder
    .StartWith(Start)
    .WaitFor(250.Milliseconds())
    .To(End)
    .Build();

// Timeout wrapper for a long-running state
var timeoutGraph = GraphBuilder
    .StartWith(Start).ToWithTimeout(500.Milliseconds(), _=> ResultHelpers.Failure)
    .To(Release)
    .Build();

Timeout cancels the wrapped logic if it exceeds the specified duration and routes to the next node. Prefer passing a linked CancellationToken inside your logic for graceful stops.

Agents (dependency injection)

Provide an agent (context/service) to all nodes that opt‑in via IAgentSettable<T>.

public sealed class AppAgent { public required ILogger Log { get; init; } }

public sealed class WorkState : ILogic, IAgentSettable<AppAgent>
{
    private AppAgent _agent = default!;
    public void SetAgent(AppAgent agent) => _agent = agent;
    public ValueTask<Result> ExecuteAsync(CancellationToken ct)
    {
        _agent.Log.LogInformation("working");
        return ResultHelpers.Success;
    }
}

var g = GraphBuilder.StartWith(new WorkState()).Build();
var sm = g.ToStateMachine<AppAgent>();
sm.SetAgent(new AppAgent { Log = logger });

Execution

var sm = graph.ToStateMachine(observer: myObserver);
var status = await sm.ExecuteAsync(ct);
  • Threading: execution is reentrancy‑guarded; call ExecuteAsync once per instance.
  • Cancellation: all logic receives a CancellationToken.
  • Errors: exceptions propagate unless you wrap logic/observer.

Validation

Validate a graph before running it.

GraphValidationResult results = GraphBuilder
    .StartWith(_ => ResultHelpers.Success).To(_ => ResultHelpers.Success)
    .Build().Validate();
if (results.HasErrors)
{
    foreach (GraphDiagnostic res in results.Diagnostics)
    {
        Console.WriteLine(res);
    }
}

//or throw exceptions in case of invalid graphs
GraphBuilder
    .StartWith(_ => ResultHelpers.Success).To(_ => ResultHelpers.Success)
    .Build().ValidateAndThrowIfErrorsDebug();

Checks include:

  • Broken edges (out of range)
  • Self‑loops (optional severity)
  • Reachability from the start node
  • Terminal path exists (no infinite director cycles)

Observability

State machine observers

Subscribe to lifecycle, node, and transition events.

public sealed class ConsoleObserver : IAsyncStateMachineObserver
{
    public ValueTask OnStartedAsync(int id, CancellationToken ct) => Write("started");
    public ValueTask OnNodeEnteredAsync(int id, int idx, CancellationToken ct) => Write($"enter {idx}");
    public ValueTask OnTransitionAsync(int id, int from, int to, string? label, CancellationToken ct) => Write($"{from}->{to} {label}");
    public ValueTask OnNodeExitedAsync(int id, int idx, CancellationToken ct) => Write($"exit {idx}");
    public ValueTask OnStoppedAsync(int id, CancellationToken ct) => Write("stopped");
    static ValueTask Write(string s) { Console.WriteLine(s); return ValueTask.CompletedTask; }
}

var sm = graph.ToStateMachine(observer: new ConsoleObserver());
await sm.ExecuteAsync(CancellationToken.None);

Observer exceptions bubble by default; wrap if you want best‑effort.

Tracing (OpenTelemetry‑friendly)

A built‑in tracing observer maps machine/node lifecycles to Activity spans/events so you can export to Jaeger/Tempo/Zipkin.

using var observer = new TracingObserver(activitySource);
var sm = graph.ToStateMachine(observer);
await sm.ExecuteAsync(ct);

Replay

Record execution for offline visualization or debugging and play it back deterministically.

var recorder = new ReplayRecorder();
var sm = graph.ToStateMachine(observer: recorder);
await sm.ExecuteAsync(ct);

var replay = new StateMachineReplay(recorder.GetEvents().Span);
replay.ReplayAll(
    evt => Console.WriteLine($"{evt.Timestamp:O} {evt.Type} {evt.Message}")
);


Visualization

Mermaid export

Export a static diagram for docs/PRs.

string mermaid = GraphBuilder.StartWith(_ => ResultHelpers.Success).SetName("Start")
            .To(_ => ResultHelpers.Success).SetName("Process" )
            .To(_ => ResultHelpers.Success).SetName("Release")
            .Build()
            .ToMermaid();
Console.WriteLine("Mermaid:");
Console.WriteLine(mermaid);

Example output:

flowchart LR
    0([Start]) --> 1([Process])
    1 --> 2([Release])
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on NxGraph:

Package Downloads
NxGraph.Serialization.Abstraction

NxGraph Serialization Abstraction

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 63 9/7/2025
1.0.3 145 9/4/2025
1.0.1 141 9/4/2025
1.0.0 231 9/3/2025