A lightweight, developer-first workflow engine built for .NET 8+. Define workflows using fluent DSL, handle state transitions, and manage tasks without external dependencies.
- Why Should I Use Meridian?
- Core Features
- Project Structure
- Installation
- Get Started in 5 Steps
- Feature Deep Dive
- Hooks (Event Handlers)
- Action Authorization
- Auto Actions
- File Attachments
- Tasks Per Action
- Visual Debugging (Console Flowchart)
- Available builtin Services / IWorkflowService<TData>
- Architecture
- Extending Meridian Workflow
- Sample Projects
- Use Cases
- Meridian vs Elsa vs Workflow Core
- Roadmap
- Contributing
- Status / Limitations
- License
- IWorkflowService<TData>
Meridian is designed with developers in mind. It offers a clean, type-safe, and highly extensible workflow engine you can embed directly into your .NET 8+ applications without complex configuration or external dependencies.
- β Fully type-safe fluent DSL
- π Powerful state transition management
- π§ Hook system for business logic
- π Fine-grained role/user-based action authorization
- π Built-in file handling and task generation
- βοΈ Minimal dependencies and cloud-ready
- π§© Clean architecture with plug-and-play extensions
- β‘ Suitable for microservices or monoliths
π‘ Note: Not all workflow engines are the same.
Meridian is focused on state-based, human-driven workflows (e.g., approvals, reviews),
unlike general-purpose engines such as Workflow Core or Elsa.
π See How is Meridian Different? for a detailed comparison.
- π― Type-Safe Workflow DSL
- Fluent API for intuitive workflow definitions
- Compile-time type checking
- Built-in validation
- π State Management
- Multi-step transitions
- State entry/exit hooks
- Auto-actions support
- State-specific validation rules
- β‘ Flexible Execution Models
- Parallel hook execution for independent operations
- Sequential execution for dependent operations
- Critical and non-critical hook handling
- π Hook Types
- Workflow-level hooks
- State-specific hooks (OnEnter/OnExit)
- Custom hook implementation support
- π Fine-grained Access Control
- Role-based authorization
- Group-based permissions
- Action-level security
- User context awareness
- π Built-in File Handling
- Pluggable storage providers
- File upload/download operations
- Attachment metadata management
- Support for multiple storage backends
- π§ Storage Configuration
- Disabled storage option for non-file workflows
- Custom provider implementation support
- π Task Tracking
- Automatic task generation
- Status tracking
- Assignment to users/roles/groups
- Task lifecycle management
- πΎ Flexible Storage
- Multiple database support
- Schema customization
- Table prefix configuration
- π Data Processing
- Automatic validation
- Change tracking
- JSON-based serialization
- Data comparison utilities
- π Comprehensive Tracking
- Detailed transition history
- State change logging
- User action tracking
- Timestamp-based auditing
β οΈ Specialized Exception Handling- Workflow-specific exceptions
- Detailed error contexts
- Operation-specific error types
- Clear error messages
- ποΈ Clean Architecture
- Dependency injection ready
- Interface-based design
- Extensible components
- π Easy Integration
- ASP.NET Core support
- Minimal dependencies
- Cloud-ready design
The Meridian Workflow project follows a clean architecture pattern with the following structure:
src/
Meridian.Core/
: Core domain logic and entitiesMeridian.AspNetCore/
: ASP.NET Core integrationMeridian.Application/
: Application layer (use cases)Meridian.Infrastructure/
: Infrastructure implementations
tests/
: Test projects.git/
: Git repositoryREADME.md
: Project documentationLICENSE.txt
: License informationMeridian.sln
: Solution file
-
src/ - Contains all source code organized in different projects:
- Meridian.Core: Contains the core domain logic, entities, and business rules
- Meridian.AspNetCore: Provides integration with ASP.NET Core
- Meridian.Application: Houses application-specific logic and use cases
- Meridian.Infrastructure: Implements infrastructure concerns (persistence, external services)
-
tests/ - Contains all test projects
-
README.md - Main documentation file
-
LICENSE.txt - Project license information
-
Meridian.sln - Visual Studio solution file
dotnet add package Meridian.Workflow --version 1.1.0
public class LeaveRequestData : IWorkflowData
{
public string Reason { get; set; } = string.Empty;
public int Days { get; set; }
}
public class LeaveRequestWorkflow : IWorkflowBootstrapper
{
public void Register(IWorkflowDefinitionBuilder builder)
{
builder.Define<LeaveRequestData>("LeaveRequest", definition =>
{
definition.State("Pending", state =>
{
state.Action("Approve", "Approved");
state.Action("Reject", "Rejected");
});
definition.State("Approved", state => state.IsCompleted());
definition.State("Rejected", state => state.IsRejected());
});
}
}
builder.Services.AddMeridianWorkflow(options =>
{
options.Workflows =
[
new LeaveRequestWorkflow(),
];
// other options...
});
public class MyClass
{
public MyClass(IWorkflowService<LeaveRequestData> leaveRequestWorkflow)
{
// use the leaveRequestWorkflow to create, execute action, get history, get request, get logged-in user tasks, ...etc
}
}
workflowDefinition.PrintToConsole();
- Why? Enables clean, reusable workflow definitions.
- Examples:
builder.Define<LeaveRequestData>("LeaveRequest", def =>
{
def.State("Pending", state =>
{
// State configuration
state.Action("Approve", "Approved", action =>
{
// Action configuration
});
state.Action("Reject", "Rejected");
});
});
Definition templates help you create reusable workflow patterns and keep your workflow definitions DRY (Don't Repeat Yourself). They are particularly useful when you have common states, actions, or behaviors that appear in multiple workflows.
π Key Benefits
- β»οΈ Reusable workflow patterns
- π― Consistent behavior across workflows
- π Reduced code duplication
- π οΈ Easy maintenance
- π§© Modular workflow design
π Common Use Cases
- Common States: Reuse standard states like Approved, Rejected, or UnderReview
- Standard Actions: Apply consistent actions like approve/reject patterns
- Security Templates: Reuse role and permission configurations
- Hook Templates: Apply common hooks across workflows
public static class GeneralWorkflowTemplates
{
public static IWorkflowDefinitionBuilder<LeaveRequestData> WithCommonStates(
this IWorkflowDefinitionBuilder<LeaveRequestData> workflowDefinition)
{
workflowDefinition
.State(GeneralWorkflowStates.Rejected, state =>
{
state.SendToSmartServicesHook();
})
.State(GeneralWorkflowStates.Updating, state =>
{
state.SendToSmartServicesHook();
})
.State(GeneralWorkflowStates.Approved, state =>
{
state.SendToSmartServicesHook();
state.IsCompleted();
});
return workflowDefinition;
}
public static IStateBuilder<LeaveRequestData> WithStandardRejectionActions(
this IStateBuilder<LeaveRequestData> state)
{
state.Action(GeneralWorkflowActions.Reject, GeneralWorkflowStates.Rejected);
state.Action(GeneralWorkflowActions.Incomplete, GeneralWorkflowStates.Updating);
return state;
}
}
// Usage
public class InitialApprovalWorkflow : IWorkflowBootstrapper
{
public void Register(IWorkflowDefinitionBuilder builder)
{
builder.Define<LeaveRequestData>("InitialApproval", definition =>
{
definition
.WithCommonStates();
.State(GeneralWorkflowStates.UnderReview, state =>
{
state.WithStandardRejectionActions();
})
});
}
}
// Hooks template
public static class CommonHooks
{
public static WorkflowState<LeaveRequestData> SendToSmartServicesHook(
this WorkflowState<LeaveRequestData> state)
{
state.AddHook(new WorkflowHookDescriptor<LeaveRequestData>
{
Hook = new SendRequestToSmartServices(),
IsAsync = true,
}, StateHookType.OnStateEnter);
return state;
}
}
- Purpose: Execute logic during request lifecycle (create, transition, entry/exit).
- Types:
- Workflow Definition
OnCreateHooks
(when a new request is created)OnTransitionHooks
(when request transitions)
- State
OnEnterHooks
(when request enters the state)OnExitHooks
(when request exits the state)
- Action
OnExecuteHooks
(when a user takes an action)
- Workflow Definition
You can use the AddHook
extension method in three ways:
- Pass a
WorkflowHookDescriptor<TData>
(full control) - Pass a class implementing
IWorkflowHook<TData>
- Pass a lambda delegate
Func<WorkflowContext<TData>, Task>
workflowDefinition.AddHook(new WorkflowHookDescriptor<LeaveRequestData>
{
Hook = new NewRequestWasCreated(),
IsAsync = false,
LogExecutionHistory = true
}, WorkflowHookType.OnRequestCreated);
workflowDefinition.AddHook(
new NewRequestWasCreated(),
cfg => {
cfg.IsAsync = false;
},
WorkflowHookType.OnRequestCreated);
workflowDefinition.AddHook(
async ctx =>
{
Console.WriteLine($"New request for {ctx.InputData?.EmployeeName}");
await Task.CompletedTask;
},
cfg => {
cfg.IsAsync = true;
cfg.LogExecutionHistory = false;
},
WorkflowHookType.OnRequestCreated);
workflowDefinition.State("StateName", state =>
{
state.AddHook(new WorkflowHookDescriptor<LeaveRequestData>
{
Hook = new SendRequestToSmartServices(),
IsAsync = true,
}, StateHookType.OnStateEnter);
});
workflowDefinition.State("StateName", state =>
{
state.AddHook(
new SendRequestToSmartServices(),
cfg => {
cfg.IsAsync = true;
},
StateHookType.OnStateEnter);
});
workflowDefinition.State("StateName", state =>
{
state.AddHook(
async ctx =>
{
Console.WriteLine("Entered state");
await Task.CompletedTask;
},
cfg => {
cfg.IsAsync = true;
},
StateHookType.OnStateEnter);
});
workflowDefinition.State("StateName", state =>
{
state.Action("actionName", "targetState", action =>
{
action.AddHook(new WorkflowHookDescriptor<LeaveRequestData>
{
Hook = new DoSomething(),
IsAsync = true,
});
});
});
workflowDefinition.State("StateName", state =>
{
state.Action("actionName", "targetState", action =>
{
action.AddHook(
new DoSomething(),
cfg => {
cfg.IsAsync = true;
});
});
});
workflowDefinition.State("StateName", state =>
{
state.Action("actionName", "targetState", action =>
{
action.AddHook(
async ctx =>
{
Console.WriteLine("Action executed");
await Task.CompletedTask;
},
cfg => {
cfg.IsAsync = true;
});
});
});
public class NewRequestWasCreated : IWorkflowHook<LeaveRequestData>
{
public Task ExecuteAsync(WorkflowContext<LeaveRequestData> context)
{
Console.WriteLine("Hook class executed");
return Task.CompletedTask;
}
}
Meridian Workflow provides built-in reusable hooks that simplify common workflow behaviors. One such hook is:
This hook compares the existing request data with the new input data during a transition and logs all field-level changes to the request history. It's useful for audit trails and understanding how data evolved over time.
Add the hook to a request transition:
definition.AddCompareDataAndLogHistory();
Or attach it manually to any hook-supported point (e.g., action execution):
action.AddHook(new WorkflowHookDescriptor<LeaveRequestData>
{
Hook = new CompareDataAndLogHook<LeaveRequestData>(),
Mode = HookExecutionMode.Parallel,
IsAsync = false,
LogExecutionHistory = false,
});
Meridian Workflow lets you define who can see or perform each action by assigning allowed users, roles, or groups.
This ensures that only authorized participants can interact with workflow actions during execution.
Use these methods for simple access control:
workflowDefinition
.State("StateName", state =>
{
state.Action("actionName", "targetState", action =>
{
action.AssignToGroups("group1", "group2");
action.AssignToUsers("user1", "user2");
action.AssignToRoles("role1", "role2", "role3");
});
});
β A user is authorized if they match any of the assigned roles, users, or groups.
For more complex scenarios (e.g., logical conditions, exclusions, or nested rules), use a fluent expression builder:
state.Action("Approve", "Approved", action => action.AssignTo(rules => rules
.Role("manager", "supervisor")
.Or(b => b.Group("finance_team"))
.And(b => b.Not(x => x.Role("intern")))
));
- Conditions are evaluated at runtime against the current user.
- The user is authorized if:
- They match any assigned user/role/group via the basic API
- OR they satisfy the advanced rule expression
- Use
.AssignToRoles
,.AssignToUsers
,.AssignToGroups
for straightforward workflows. - Use
.AssignTo(...)
when logic includes combinations or exceptions. - Both methods are supported and are combined using OR logic.
action.AssignToUsers("john");
action.AssignTo(r => r.Role("manager").Not(x => x.Role("blocked")));
π The above means:
- Allow if user is "john"
- OR if user has role "manager" AND is not in role "blocked"
Mark an action to be taken by condition
workflowDefinition
.State("StateName", state =>
{
state.Action("actionName", "targetState", action =>
{
action.IsAuto = true;
action.Condition = data => data.Department == "IT" && data.Priority > 10;
});
});
Meridian Workflow supports conditional transitions that dynamically determine the next state based on the data being processed. This enables flexible workflows that adapt to runtime conditions.
state.Action("Approve", "PendingManagerApproval", action => action
.AssignToRoles("Manager")
.When(data => data.Amount > 10000, "PendingDirectorApproval")
.When(data => data.Amount > 5000, "PendingSupervisorApproval")
);
- If
Amount > 10000
, transitions to"PendingDirectorApproval"
- If
Amount > 5000
and<= 10000
, transitions to"PendingSupervisorApproval"
- Otherwise, transitions to the default state
"PendingManagerApproval"
- Conditions are evaluated in the order they are defined.
- The first condition that returns
true
determines the transition. - If no condition matches, the default transition is used.
Place more specific conditions before more general ones:
.When(data => data.Amount > 10000, "HighValueApproval") // Specific
.When(data => data.Amount > 1000, "StandardApproval") // General
Always specify a meaningful default state:
state.Action("Review", "StandardReview", action => action
.When(data => data.IsUrgent, "ExpeditedReview")
// Falls back to "StandardReview" if not urgent
);
Ensure that conditions are mutually exclusive:
// Good - Conditions don't overlap
.When(data => data.Days > 30, "ExtendedLeave")
.When(data => data.Days > 15 && data.Days <= 30, "StandardLeave")
// Bad - Overlapping conditions
.When(data => data.Days > 15, "StandardLeave")
.When(data => data.Days > 30, "ExtendedLeave") // Never reached!
For complex conditional transitions, Meridian supports a more expressive syntax using a transition table. This allows you to define multiple branching paths in a single statement.
state.Action("Approve", action => action.TransitionTo(
(data => data.Amount > 10000, "PendingDirectorApproval", "Amount > 10000"),
(data => data.Amount > 5000, "PendingSupervisorApproval", "Amount > 5000"),
(data => true, "PendingManagerApproval", "Default")
));
- Transitions to
"PendingDirectorApproval"
ifAmount > 10000
- Otherwise, transitions to
"PendingSupervisorApproval"
ifAmount > 5000
- Otherwise, transitions to
"PendingManagerApproval"
(default case)
- Centralizes all conditional transitions in a single block
- Improves readability and maintainability
- Enables optional labeling for documentation and debugging
You can omit labels if not needed:
state.Action("Approve", action => action.TransitionTo(
(data => data.Amount > 10000, "PendingDirectorApproval"),
(data => data.Amount > 5000, "PendingSupervisorApproval"),
(data => true, "PendingManagerApproval")
));
- All conditions are evaluated in order.
- The first match determines the next state.
- Labels (optional) are used for visual tools and logs.
- Transition tables override
.When(...)
if both are defined.
- Keep the fallback case
data => true
as the last rule. - Use labels to describe business rules for better debugging and documentation.
- Do not mix
.When(...)
and.TransitionTo(...)
unless intentional β only one will be used.
// Good
.TransitionTo(
(d => d.Type == "Annual", "AnnualReview", "Annual leave"),
(d => d.Type == "Sick", "MedicalReview", "Sick leave"),
(d => true, "DefaultReview", "Fallback")
)
// Avoid this unless you know what you're doing
.When(d => d.Type == "Urgent", "Expedited")
.TransitionTo((d => true, "Standard"))
Meridian Workflow performs automatic model validation before executing an action to ensure data integrity.
To disable automatic validation on a specific action:
workflowDefinition
.State("StateName", state =>
{
state.Action("actionName", "targetState", action =>
{
action.DisableAutoValidation();
});
});
You can also define custom validation rules using the WithValidation method. This allows fine-grained, context-specific validation before the action executes.
action.WithValidation(data =>
{
var errors = new List<string>();
if (string.IsNullOrEmpty(data.Department))
{
errors.Add("Department cannot be empty");
}
return errors;
});
No extra effort is needed from developers. Just use:
public class LeaveRequestData : IWorkflowData
{
public WorkflowFile<WorkflowFileAttachment> MedicalReport { get; set; } = new();
}
The engine:
- Detects attachments
- Uploads via IWorkflowFileStorageProvider
- Replaces them with references
public interface IWorkflowFileStorageProvider<TReference>
{
Task<TReference> UploadAsync(WorkflowFileAttachment attachment);
}
You implement this to store on disk, S3, or DB.
Example
public class AttachmentReference
{
public Guid AttachmentId { get; set; }
public string Path { get; set; } = string.Empty;
public string? Source { get; set; }
}
public class WorkflowFileStorageProvider : IWorkflowFileStorageProvider<AttachmentReference>
{
public async Task<AttachmentReference> UploadAsync(IWorkflowAttachment attachmentFile)
{
return await Task.FromResult(new AttachmentReference
{
Path = "File path",
Source = "File source",
AttachmentId = Guid.NewGuid(),
});
}
}
Register the File Storage Provider
builder.Services.AddMeridianWorkflow(options =>
{
options.EnableAttachmentProcessor = true; // Optional, true by default
options.SetFileStorageProvider(typeof(WorkflowFileStorageProvider));
// other options...
});
π‘ Set
EnableAttachmentProcessor = false
to disable built-in attachment processing if you need full control.
Meridian Workflow supports Entity Framework Core out of the box, NOT FULLY TESTED with all providers:
You can integrate any EF Core-supported provider (PostgreSQL, SQLite, Oracle, etc.) by configuring the underlying
DbContext
.
builder.Services.AddMeridianWorkflow(options =>
{
options.ConfigureDb(db =>
{
db.Use(dbOptions => dbOptions.UseInMemoryDatabase("WorkflowTestDb"));
// db.Use(dbOptions => dbOptions.UseSqlServer("WorkflowTestDb"));
db.TablesPrefix = "Meridian_"; // Optional: Set custom table prefix
db.Schema = "MySchema"; // Optional: Set sechma (required with oracle)
});
});
π Schema and table prefixing allow you to isolate workflow data in shared databases.
Automatically creates a WorkflowRequestTask per action in a state.
public class WorkflowRequestTask
{
public string RequestId { get; set; }
public string Action { get; set; }
public string State { get; set; }
public List<string> AssignedToRoles { get; set; }
public WorkflowTaskStatus Status { get; set; }
}
On each transition:
- β Old tasks marked as completed
- β New tasks created for next state's actions
workflowDefinition
.State("StateName", state =>
{
})
// Use this extension
.PrintToConsole();
ββββββββββββββββββββββββββββββββββββββββββ
Workflow: LeaveRequest
ββββββββββββββββββββββββββββββββββββββββββ
State: Pending
ββ Approve β Approved
ββ Reject β Rejected
State: Approved
State: Rejected
Provides all operations to manage and execute workflow requests for a specific workflow type.
Method | Description |
---|---|
CreateRequestAsync |
Creates a new workflow request instance |
DoActionAsync |
Executes a specific action on a workflow request |
GetRequestAsync |
Retrieves a workflow request by ID |
GetUserTasksAsync |
Gets requests assigned to a specific user |
GetAvailableActions |
Gets available actions for a request |
GetCurrentState |
Gets the current state of a request |
GetRequestHistoryAsync |
Returns transition history for a request |
GetRequestWithHistoryAsync |
Returns request and its history together |
Description:
Creates a new workflow request for the current workflow definition.
Parameters:
Name | Type | Required | Description |
---|---|---|---|
inputData |
TData | β | Initial workflow input data |
createdBy |
string | β | ID of the user creating request |
Example:
await workflow.CreateRequestAsync(new LeaveRequestData { LeaveType = "Annual" }, "user123");
Description:
Executes a specific action (e.g., "Approve", "Reject") on an existing request.
Overloads:
- With only action and request ID
- With additional data to update the workflow
Parameters:
Name | Type | Required | Description |
---|---|---|---|
requestId |
Guid | β | ID of the request to act on |
action |
string | β | Action name to execute |
performedBy |
string | β | User performing the action |
userRoles |
List | β | User's roles |
userGroups |
List | β | User's groups |
data |
TData? | β | Optional updated request data |
Example:
await workflow.DoActionAsync(requestId, "Submit", "user123", roles, groups);
await workflow.DoActionAsync(requestId, "Update", "user123", roles, groups, newData);
Description:
Returns the workflow request by ID.
Parameters:
Name | Type | Required | Description |
---|---|---|---|
requestId |
Guid | β | ID of the workflow request |
Example:
var request = await workflow.GetRequestAsync(id);
Description:
Returns all requests where the user has available actions based on role/group.
Parameters:
Name | Type | Required | Description |
---|---|---|---|
userId |
string | β | ID of the user |
userRoles |
List | β | List of user's roles |
userGroups |
List | β | List of user's groups |
Example:
var tasks = await workflow.GetUserTasksAsync("user123", roles, groups);
Description:
Returns the list of actions the user can perform on the request.
Overloads:
- By passing the request object
- By passing the request ID
Parameters (Overload 1):
Name | Type | Required | Description |
---|---|---|---|
request |
WorkflowRequestInstance | β | Workflow request object |
userId |
string? | β | ID of the user |
userRoles |
List? | β | Roles of the user |
userGroups |
List? | β | Groups of the user |
Parameters (Overload 2):
Name | Type | Required | Description |
---|---|---|---|
requestId |
Guid | β | ID of the request |
userId |
string? | β | ID of the user |
userRoles |
List? | β | Roles of the user |
userGroups |
List? | β | Groups of the user |
Examples:
workflow.GetAvailableActions(request, "user", roles);
await workflow.GetAvailableActions(requestId, "user", roles);
Description:
Returns the current state of the specified workflow request.
Parameters:
Name | Type | Required | Description |
---|---|---|---|
request |
WorkflowRequestInstance | β | Request to check |
Example:
var state = workflow.GetCurrentState(request);
Description:
Returns the full transition history for a request.
Parameters:
Name | Type | Required | Description |
---|---|---|---|
requestId |
Guid | β | ID of the request |
Example:
var history = await workflow.GetRequestHistoryAsync(request.Id);
Description:
Returns both request and its full transition history.
Parameters:
Name | Type | Required | Description |
---|---|---|---|
requestId |
Guid | β | ID of the request |
Example:
var full = await workflow.GetRequestWithHistoryAsync(request.Id);
- Clean separation of Domain / Application / Infrastructure
- Plug-and-play registry-based engine resolver
- Reflection-based workflow registry
- Supports future runtime definitions (JSON)
Feature | You Can Plug In... |
---|---|
File Upload | IWorkflowFileStorageProvider<TReference> |
Hook Execution | Implement IWorkflowHook<TData> |
Custom Transition Logic | Add logic in hook or conditions |
π§ More coming soon...
Meridian Workflow can handle a wide variety of business scenarios:
-
π Leave Request Approval
- Simple multi-step approval with hooks and auto actions
-
π Document Review Workflow
- Handle file uploads, rejections, and resubmissions
-
π₯ HR Onboarding
- Automate onboarding steps with role-based task assignments
-
π οΈ Support Ticket Lifecycle
- Escalation, auto-routing, SLA tracking
-
π§Ύ Procurement Approvals
- Include price validation, PDF verification, and user-specific approvals
-
π Multi-Level Reviews
- Nested approval chains with sub-workflows and task delegation
-
MORE AND MORE!!
Meridian Workflow is not a replacement for heavy orchestration engines like Elsa, nor is it a drop-in alternative to Workflow Core. Each serves a different use case and design philosophy.
Feature | Meridian | Workflow Core |
---|---|---|
Workflow Type | State Machine (User/Approval-driven) | Step-based/Flow-based |
DSL | Fully type-safe Fluent API | Fluent + JSON |
Use Case Focus | Approval workflows, human interaction, business tasks | General-purpose orchestration |
Task Handling | Built-in task system with roles/users/groups | Requires custom implementation |
Authorization | Built-in role/group/user-based action authorization | Not built-in |
Extensibility | Hooks, Templates, Pluggable Features | Middleware/step extensions |
Simplicity & Dev Focus | Lightweight, zero-config, developer-first | More generic, more boilerplate |
Feature | Meridian | Elsa Workflows |
---|---|---|
Workflow Type | Human Workflow / Approval-based | Activity-based Orchestration |
Designer UI | β Not available | β Powerful designer (optional) |
DSL | β Fluent API (C#) | C# or JSON |
Persistence Model | Optional / Lightweight | Required (workflow instance tracking) |
Approval & Action Model | β States, Actions, Users, Tasks | β Not native |
Suitable For | Leave requests, ticket lifecycle, business processes | Long-running workflows, integrations |
Meridian is designed for human-centric workflows like approvals, reviews, multi-step user processes, and role-based transitions β all using clean, extensible code without a designer or runtime engine.
The following features are planned or under consideration for future releases:
- Timeout & Escalation Support
- Delegation & Reassignment
- Multi-request Relationships
- Sub-Workflows & Nested Workflows
- Conditional Transitions Upgrade
(Take a look at Conditional Actions in the Status / Limitations section) - JSON-based Workflow Definitions
π§ More coming soon...
Want to help improve Meridian Workflow?
- Report issues
- Submit new DSL extensions
- Create advanced real-world samples
- Help to add reusable hook
- Help to add Timeout / Escalation
- Help to add Delegation / Reassign
- Help to add Multi-Request Relationship
- Help to add Sub-Workflows
- Help to convert Fluent-DSL to JSON-DSL
Unit tests are not fully completed.
Contributions to improve coverage and test edge cases are welcome.
- Conditional Actions:
- Conditions are evaluated in definition order.
- No priority system for condition evaluation.
- No built-in conflict detection for overlapping conditions.
- No support for transition-specific validation or hooks.
Apache License 2.0. Free for use in open-source and commercial applications. Includes conditions for redistribution and attribution.