{"id":18009569,"url":"https://github.com/drittich/state-machine","last_synced_at":"2026-01-25T18:32:00.902Z","repository":{"id":163639454,"uuid":"638750734","full_name":"drittich/state-machine","owner":"drittich","description":"A simple convention-based finite state machine that lets you pass event data through to your transition actions.","archived":false,"fork":false,"pushed_at":"2024-11-11T04:52:02.000Z","size":55,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-25T09:43:27.881Z","etag":null,"topics":["state-machine","state-machine-cs","state-machines","statemachine","statemachine-library","statemachines"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/drittich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-10T02:59:46.000Z","updated_at":"2025-06-06T13:18:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"7829b84c-7733-42fe-a660-4290af5d573d","html_url":"https://github.com/drittich/state-machine","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/drittich/state-machine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drittich%2Fstate-machine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drittich%2Fstate-machine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drittich%2Fstate-machine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drittich%2Fstate-machine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/drittich","download_url":"https://codeload.github.com/drittich/state-machine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drittich%2Fstate-machine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28756442,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T16:32:25.380Z","status":"ssl_error","status_checked_at":"2026-01-25T16:32:09.189Z","response_time":113,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["state-machine","state-machine-cs","state-machines","statemachine","statemachine-library","statemachines"],"created_at":"2024-10-30T02:09:48.461Z","updated_at":"2026-01-25T18:32:00.882Z","avatar_url":"https://github.com/drittich.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# State Machine\n\n[![.NET 8 - Build](https://github.com/drittich/state-machine/actions/workflows/build.yml/badge.svg)](https://github.com/drittich/state-machine/actions/workflows/build.yml)\n[![.NET 8 - Tests](https://github.com/drittich/state-machine/actions/workflows/tests.yml/badge.svg)](https://github.com/drittich/state-machine/actions/workflows/tests.yml)\n\nA simple, extensible finite state machine that allows you to define states, events, transitions, and pass event data through to your transition actions.\n\n## Installation\n\nThe `drittich.StateMachine` library is available on [NuGet](https://www.nuget.org/packages/drittich.StateMachine). You can install it using the Package Manager Console:\n\n```bash\nInstall-Package drittich.StateMachine\n```\n\nOr using the .NET CLI:\n\n```bash\ndotnet add package drittich.StateMachine\n```\n\nThis will install the library and its dependencies.\n\n## Example Usage\n\nThe `StateMachine` class lets you define your own states, events, transitions, and a data transfer object (DTO) to pass data with events. This data can then be provided to the action that runs when a transition occurs.\n\nYou need to:\n\n- Define enums for your states and events.\n- Create a DTO class for event data.\n- Initialize the state machine with an initial state and a logger.\n- Add transitions that specify how the state machine moves from one state to another in response to events.\n\n### Define States and Events\n\n```csharp\nenum MyStates\n{\n    Initial,\n    SomeState,\n    SomeOtherState,\n    Complete\n}\n\nenum MyEvents\n{\n    SomethingHappened,\n    SomethingElseHappened,\n    SomeOtherRandomEvent\n}\n```\n\n### Create a DTO for Event Data\n\n```csharp\npublic class MyDto\n{\n    public int Prop1 { get; set; }\n}\n```\n\n### Initialize the State Machine\n\n```csharp\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\n\n// Create a logger (use NullLogger if you don't need logging)\nILogger\u003cStateMachine\u003cMyStates, MyEvents, MyDto\u003e\u003e logger = new NullLogger\u003cStateMachine\u003cMyStates, MyEvents, MyDto\u003e\u003e();\n\n// Initialize the state machine with the initial state\nvar sm = new StateMachine\u003cMyStates, MyEvents, MyDto\u003e(MyStates.Initial, logger);\n```\n\n### Define the Transitions\n\nWith the simplified AddTransition method, you can now add transitions directly without needing to create Transition objects explicitly:\n\n```csharp\n// Add transitions to the state machine\nsm.AddTransition(MyStates.Initial, MyEvents.SomethingHappened, MyStates.SomeState, SomeMethodToExecuteAsync);\nsm.AddTransition(MyStates.SomeState, MyEvents.SomethingElseHappened, MyStates.Complete, SomeOtherMethodToExecuteAsync);\n```\n\n### Define the Action Methods\n\n```csharp\nusing System.Threading;\nusing System.Threading.Tasks;\n\n// Action method for the first transition\nasync Task SomeMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)\n{\n    // Your action code here\n    await Task.Delay(100, cancellationToken);\n    Console.WriteLine(\"Executed SomeMethodToExecuteAsync\");\n}\n\n// Action method for the second transition\nasync Task SomeOtherMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)\n{\n    // Your action code here\n    await Task.Delay(100, cancellationToken);\n    Console.WriteLine(\"Executed SomeOtherMethodToExecuteAsync\");\n}\n```\n\n### Execute Transitions\n\n```csharp\nvar data = new MyDto { Prop1 = 1 };\n\n// Execute the first transition\nvar resultingState = await sm.GetNextAsync(MyEvents.SomethingHappened, data);\n// resultingState is MyStates.SomeState\n\n// Execute the second transition\nvar resultingState2 = await sm.GetNextAsync(MyEvents.SomethingElseHappened, data);\n// resultingState2 is MyStates.Complete\n```\n\n### Handle Invalid Transitions\n\nIf an invalid transition is attempted (no transition is defined for the current state and event), an InvalidTransitionException is thrown.\n\n```csharp\ntry\n{\n    // Attempt an invalid transition\n    var resultingState3 = await sm.GetNextAsync(MyEvents.SomeOtherRandomEvent, data);\n}\ncatch (InvalidTransitionException ex)\n{\n    // Handle the exception\n    Console.WriteLine($\"Invalid transition: {ex.Message}\");\n}\n```\n\n## Additional Features\n\n### Guard Conditions\n\nYou can add guard conditions to transitions to control whether the transition should occur based on the event data.\n\n```csharp\nsm.AddTransition(\n    MyStates.SomeState,\n    MyEvents.SomeOtherRandomEvent,\n    MyStates.Complete,\n    SomeOtherMethodToExecuteAsync,\n    guard: data =\u003e data.Prop1 \u003e 0\n);\n\n```\n\nIf the guard condition returns false, a GuardConditionFailedException is thrown, and the transition does not occur.\n\n### Cancellation Support\n\nThe action methods accept a CancellationToken, allowing transitions to be canceled if needed.\n\n```csharp\nvar cts = new CancellationTokenSource();\ncts.CancelAfter(500); // Cancel after 500ms\n\ntry\n{\n    await sm.GetNextAsync(MyEvents.SomethingHappened, data, cts.Token);\n}\ncatch (TaskCanceledException)\n{\n    Console.WriteLine(\"Transition was canceled.\");\n}\n```\n\n### Logging\n\nThe state machine uses ILogger to log information about transitions, warnings, and errors.\n\n- **Information:** Successful transitions.\n- **Warning:** Undefined transitions or guard condition failures.\n- **Error:** Exceptions thrown during actions.\n\n## Exception Handling\n\n- **InvalidTransitionException:** Thrown when no transition is defined for the current state and event.\n- **GuardConditionFailedException:** Thrown when a guard condition evaluates to false.\n- **TaskCanceledException:** Thrown when a transition is canceled via a CancellationToken.\n- **InvalidOperationException:** Thrown when attempting to add a duplicate transition.\n\n## Thread Safety\n\nThe `StateMachine` class is thread-safe and can handle concurrent transition attempts appropriately. It ensures that only one transition occurs at a time, maintaining the integrity of the `CurrentState`.\n\n## Complete Example\n\n```csharp\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\n\n// Define states and events\nenum MyStates\n{\n    Initial,\n    SomeState,\n    SomeOtherState,\n    Complete\n}\n\nenum MyEvents\n{\n    SomethingHappened,\n    SomethingElseHappened,\n    SomeOtherRandomEvent\n}\n\n// DTO for event data\npublic class MyDto\n{\n    public int Prop1 { get; set; }\n}\n\nclass Program\n{\n    static async Task Main(string[] args)\n    {\n        // Create a logger\n        ILogger\u003cStateMachine\u003cMyStates, MyEvents, MyDto\u003e\u003e logger = new NullLogger\u003cStateMachine\u003cMyStates, MyEvents, MyDto\u003e\u003e();\n\n        // Initialize the state machine\n        var sm = new StateMachine\u003cMyStates, MyEvents, MyDto\u003e(MyStates.Initial, logger);\n\n        // Define transitions\n        sm.AddTransition(MyStates.Initial, MyEvents.SomethingHappened, MyStates.SomeState, SomeMethodToExecuteAsync);\n\n        sm.AddTransition(MyStates.SomeState, MyEvents.SomethingElseHappened, MyStates.Complete, SomeOtherMethodToExecuteAsync);\n\n        // Event data\n        var data = new MyDto { Prop1 = 1 };\n\n        // Execute transitions\n        var resultingState = await sm.GetNextAsync(MyEvents.SomethingHappened, data);\n        Console.WriteLine($\"State after first transition: {resultingState}\");\n\n        var resultingState2 = await sm.GetNextAsync(MyEvents.SomethingElseHappened, data);\n        Console.WriteLine($\"State after second transition: {resultingState2}\");\n\n        // Handle invalid transition\n        try\n        {\n            await sm.GetNextAsync(MyEvents.SomeOtherRandomEvent, data);\n        }\n        catch (InvalidTransitionException ex)\n        {\n            Console.WriteLine($\"Invalid transition: {ex.Message}\");\n        }\n    }\n\n    // Action methods\n    static async Task SomeMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)\n    {\n        await Task.Delay(100, cancellationToken);\n        Console.WriteLine(\"Executed SomeMethodToExecuteAsync\");\n    }\n\n    static async Task SomeOtherMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)\n    {\n        await Task.Delay(100, cancellationToken);\n        Console.WriteLine(\"Executed SomeOtherMethodToExecuteAsync\");\n    }\n}\n\n```\n\n## Installation\nTo use the `StateMachine` class in your project, include the source code or compile it into a library that you can reference.\n\n## Dependencies\n- **.NET Standard 2.0** or higher.\n- `Microsoft.Extensions.Logging.Abstractions` for logging interfaces.\n\n\nInstall via NuGet:\n\n```bash\nInstall-Package Microsoft.Extensions.Logging.Abstractions\n```\n\n## License\nThis project is licensed under the MIT License.\n\n## Contributing\nContributions are welcome! Please submit a pull request or open an issue to discuss improvements or features.\n\n## Contact\nFor questions or support, please open an issue on the GitHub repository.\n\n***\n\n**Note:** Replace `SomeMethodToExecuteAsync` and `SomeOtherMethodToExecuteAsync` with your actual action methods. The DTO MyDto should contain the data relevant to your application.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrittich%2Fstate-machine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrittich%2Fstate-machine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrittich%2Fstate-machine/lists"}