Skip to content

Commit e892e20

Browse files
authored
Update Documentation (#29)
* Update READMEs * Code cleanup * Refine documentation * Update version number * Fix failing unit test
1 parent 5ec006f commit e892e20

20 files changed

Lines changed: 526 additions & 378 deletions

README.md

Lines changed: 16 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -13,107 +13,7 @@ PipeForge is a lightweight, composable pipeline framework for .NET. It makes ste
1313

1414
PipeForge is available on [NuGet.org](https://www.nuget.org/packages/PipeForge/) and can be installed using a NuGet package manager or the .NET CLI.
1515

16-
## Usage
17-
18-
Pipelines are designed to operate on specific class, referred to as the **context**. Multiple pipeline steps are created in code to operate on that context. Steps are annotated with an attribute indicating the order in which they should be executed. Finally, the pipeline runner is given an instance of the context to run against.
19-
20-
The following example uses dependency injection, and is the recommended approach to using PipeForge. For more advanced scenarios, see the full [documentation](https://scottoffen.github.io/pipeforge).
21-
22-
> [!NOTE]
23-
> I'm suffixing my context class with the word `Context`, and my steps with the word `Step` for demonstration purposes only.
24-
25-
### Create Your Context
26-
27-
```csharp
28-
public class SampleContext
29-
{
30-
private readonly List<string> _steps = new();
31-
32-
public void AddStep(string stepName)
33-
{
34-
if (string.IsNullOrWhiteSpace(stepName))
35-
{
36-
throw new ArgumentException("Step name cannot be null or whitespace.", nameof(stepName));
37-
}
38-
39-
_steps.Add(stepName);
40-
}
41-
42-
public int StepCount => _steps.Count;
43-
44-
public override string ToString()
45-
{
46-
return string.Join("", _steps);
47-
}
48-
}
49-
```
50-
51-
### Create Your Steps
52-
53-
```csharp
54-
[PipelineStep(1)]
55-
public class HelloStep : PipelineStep<SampleContext>
56-
{
57-
public override async Task InvokeAsync(SampleContext context, PipelineDelegate<SampleContext> next, CancellationToken cancellationToken = default)
58-
{
59-
context.AddStep("Hello");
60-
await next(context, cancellationToken);
61-
}
62-
}
63-
64-
[PipelineStep(2)]
65-
public class WorldStep : PipelineStep<SampleContext>
66-
{
67-
public override async Task InvokeAsync(SampleContext context, PipelineDelegate<SampleContext> next, CancellationToken cancellationToken = default)
68-
{
69-
context.AddStep("World");
70-
await next(context, cancellationToken);
71-
}
72-
}
73-
74-
[PipelineStep(3)]
75-
public class PunctuationStep : PipelineStep<SampleContext>
76-
{
77-
public override async Task InvokeAsync(SampleContext context, PipelineDelegate<SampleContext> next, CancellationToken cancellationToken = default)
78-
{
79-
context.AddStep("1");
80-
await next(context, cancellationToken);
81-
}
82-
}
83-
```
84-
85-
### Use Dependency Injection
86-
87-
The extension method will discover and register all steps for the given `T` context, as well as register an instance of `IPipelineRunner<T>` that can be injected into services.
88-
89-
```csharp
90-
services.AddPipelineFor<SampleContext>();
91-
```
92-
93-
### Execute Pipeline
94-
95-
Get an instance of `IPipelineRunnere<SampleContext>` from your dependency injection container.
96-
97-
```csharp
98-
public class SampleService
99-
{
100-
private readonly IPipelineRunner<SampleContext> _pipelineRunner;
101-
102-
public SampleService(IPipelineRunner<SampleContext> pipelineRunner)
103-
{
104-
_pipelineRunner = pipelineRunner;
105-
}
106-
107-
public async Task RunPipeline(SampleContext context, CancellationToken? token = default)
108-
{
109-
await _pipelineRunner.ExecuteAsync(context, token);
110-
var result = context.ToString();
111-
// result = "HelloWorld!"
112-
}
113-
}
114-
```
115-
116-
## Support
16+
## Usage and Support
11717

11818
- Check out the project documentation https://scottoffen.github.io/pipeforge.
11919

@@ -123,6 +23,21 @@ public class SampleService
12323

12424
- **Issues created to ask "how to" questions will be closed.**
12525

26+
## Use Cases
27+
28+
While PipeForge is fundamentally a pipeline framework, it can also serve as the foundation for higher-level workflows. These workflows are built by composing individual pipeline steps that handle branching, retries, fallbacks, and decision logic - making it ideal for orchestrating complex processes like AI chains, data enrichment, or multi-stage validation.
29+
30+
| | |
31+
|-|-|
32+
| Game Loop and Simulation Ticks | Model turn-based or tick-based game logic using structured steps for input handling, state updates, AI, and rule enforcement. Ideal for simulations, server-side logic, or deterministic turn resolution. |
33+
| Middleware-Style Request Processing | Build lightweight, modular request pipelines similar to ASP.NET middleware, without requiring a full web host. |
34+
| DevOps and Automation Pipelines | Express deployment checks, file transforms, and system hooks as repeatable, testable steps. |
35+
| Security and Auditing Pipelines | Enforce policies, redact sensitive data, and log events in a structured, traceable flow. |
36+
| ETL and Data Processing Pipelines | Break down validation, transformation, and persistence into clean, maintainable processing steps. |
37+
| LLM and AI Workflows | Orchestrate prompt generation, model calls, fallback handling, and response parsing using composable pipelines. |
38+
| Business Logic and Domain Orchestration | Replace brittle `if` chains and nested logic with clearly structured rule execution and orchestration flows. |
39+
40+
12641
## Contributing
12742

12843
We welcome contributions from the community! In order to ensure the best experience for everyone, before creating an issue or submitting a pull request, please see the [contributing guidelines](CONTRIBUTING.md) and the [code of conduct](CODE_OF_CONDUCT.md). Failure to adhere to these guidelines can result in significant delays in getting your contributions included in the project.

docs/docs/describe-pipelines.md

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
---
2-
sidebar_position: 6
2+
sidebar_position: 8
33
title: Describing Pipelines
44
---
55

6-
# Describing Pipelines
6+
PipeForge provides built-in support for inspecting pipeline structure and behavior at runtime. This is especially useful for observability, testing, documentation, and UI tooling.
77

8-
The `Describe()` method on `IPipelineRunner<T>` is used to inspect and document the pipeline configuration at runtime. It returns a JSON string describing each registered pipeline step.
8+
## Describe()
99

10-
This method is useful for diagnostics, tooling, and dynamically displaying pipeline behavior in user interfaces or logs - but only if you've taken the time to add the necessary metadata to your step classes.
10+
The `Describe()` method on `IPipelineRunner<T>` outputs a JSON array representing each registered step. It includes key metadata such as name, order, and short-circuit configuration.
1111

12-
## Output Format
12+
:::warning[Side Effects]
1313

14-
The JSON output contains an array of objects, each representing a pipeline step with the following fields:
14+
This method **instantiates all steps**, triggering constructor injection and service resolution. Use it only when such side effects are acceptable.
1515

16-
* `Order`: The zero-based order in which the step appears in the pipeline
17-
* `Name`: The value of the step's `Name` property
18-
* `Description`: The step's `Description`, if defined
19-
* `MayShortCircuit`: A boolean indicating whether the step might short-circuit execution
20-
* `ShortCircuitCondition`: The value of the step's `ShortCircuitCondition`, if any
16+
:::
17+
18+
### Output Format
2119

22-
Example output:
20+
| Field | Description |
21+
| ----------------------- | ------------------------------------------------------- |
22+
| `Order` | Step's position in the execution sequence |
23+
| `Name` | Value of the step’s `Name` property |
24+
| `Description` | Value of the step’s `Description`, if provided |
25+
| `MayShortCircuit` | Indicates if the step may halt pipeline execution |
26+
| `ShortCircuitCondition` | Explanation of the short-circuit trigger, if applicable |
27+
28+
### Example Output
2329

2430
```json
2531
[
@@ -40,26 +46,59 @@ Example output:
4046
]
4147
```
4248

43-
:::warning Warning
44-
45-
The `Order` value in the JSON output represents the execution order of the steps. This value is assigned based on the order in which the steps will be executed, and it may differ from the `Order` specified in each step's `PipelineStep` attribute.
49+
:::info[A Note About Order]
4650

47-
For example, if you have only two steps with `PipelineStep(Order = 3)` and `PipelineStep(Order = 4)`, the JSON output will show `Order` values of `0` and `1`, respectively - reflecting their relative execution sequence, not their original attribute values.
51+
The `Order` shown in the output reflects the **actual runtime execution sequence**, not the numeric value assigned via the `[PipelineStep]` attribute. For example, two steps with attributes `Order = 3` and `Order = 4` will appear in the output as `Order = 0` and `Order = 1` if they are the only steps registered.
4852

4953
:::
5054

51-
## Instantiation Behavior
55+
### Use Cases
5256

53-
Calling `Describe()` **will instantiate all steps** in the pipeline by accessing their `Lazy<T>` wrappers. This may result in constructor injection or other side effects associated with instantiating the step class. Use this method only when you are prepared for that overhead.
57+
* Logging pipeline structure for observability
58+
* Generating runtime or admin UI documentation
59+
* Verifying step order and metadata in unit tests
5460

55-
## Use Cases
61+
If you need to inspect step configuration without triggering instantiation, consider capturing step metadata during registration or design time.
5662

57-
* Logging pipeline structure for observability
58-
* Generating runtime documentation or UI
59-
* Verifying step order and metadata during tests or builds
63+
## DescribeSchema()
64+
65+
The `DescribeSchema()` method returns a [JSON Schema v7](https://json-schema.org/specification.html) document that describes the metadata shape of a pipeline step. This is ideal for tools that visualize or validate pipeline structures.
66+
67+
### When to Use
6068

61-
If you need to inspect step configuration without triggering instantiation, consider maintaining parallel metadata or restricting usage of `Describe()` to controlled environments.
69+
* Exposing step definitions through APIs or dashboards
70+
* Powering UI editors or metadata-driven configuration
71+
* Documenting expected step shape for developers
72+
* Validating configuration or orchestration input
6273

63-
## Conclusion
74+
### Schema Fields
75+
76+
| Property | Type | Description |
77+
| ----------------------- | ------- | --------------------------------------------------- |
78+
| `Order` | integer | Execution order of the step (inferred at runtime) |
79+
| `Name` | string | Display name of the step |
80+
| `Description` | string | Optional summary of the step’s purpose |
81+
| `MayShortCircuit` | boolean | Indicates if the step may halt execution early |
82+
| `ShortCircuitCondition` | string | Optional explanation of the short-circuit condition |
83+
84+
Only `Order` is required. All other fields are optional and serve documentation, inspection, or visualization purposes.
85+
86+
### Example Output
87+
88+
```json
89+
{
90+
"$schema": "http://json-schema.org/draft-07/schema#",
91+
"title": "PipelineStep",
92+
"type": "object",
93+
"properties": {
94+
"Order": { "type": "integer", "description": "Execution order of the step (inferred)" },
95+
"Name": { "type": "string", "description": "Display name of the step" },
96+
"Description": { "type": "string", "description": "Optional description of the step" },
97+
"MayShortCircuit": { "type": "boolean", "description": "Whether the step may halt pipeline execution early" },
98+
"ShortCircuitCondition": { "type": "string", "description": "Explanation of the short-circuit condition, if any" }
99+
},
100+
"required": ["Order"]
101+
}
102+
```
64103

65-
Use `Describe()` to introspect your pipeline at runtime, but be aware that it will force full resolution of every registered step.
104+
Use this schema to build validation layers, generate dynamic forms, or publish step definitions in API documentation.

docs/docs/diagnostics.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 8
2+
sidebar_position: 9
33
title: Diagnostics
44
---
55

@@ -43,7 +43,3 @@ listener.Subscribe(new Observer((name, payload) =>
4343
* You can use this mechanism to generate timing metrics, debug issues, or visualize step execution.
4444
* Diagnostic events are low-overhead and safe to leave enabled in production.
4545
* Combine this with the `Describe()` method for a full picture of pipeline structure and execution behavior.
46-
47-
## Conclusion
48-
49-
PipeForge diagnostics give you deep visibility into pipeline execution with minimal effort. Whether you're debugging a failing step or building runtime instrumentation, the diagnostics hooks are ready to help.

docs/docs/index.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
2-
sidebar_position: 1
2+
sidebar_position: 0
33
title: Getting Started
44
---
55

66
# Welcome to PipeForge
77

8-
PipeForge is a lightweight, composable, lazy-instantiation pipeline framework for .NET. It simplifies step-based processing while remaining discoverable and testable. Inspired by middleware pipelines and modern dependency injection patterns, PipeForge gives you structured control over sequential logic flows - without the ceremony.
8+
PipeForge is a lightweight, composable pipeline framework for .NET that makes step-based workflows easy to build, test, and maintain. With lazy instantiation and modern dependency injection, it gives you structured control over execution flow - without the heavy scaffolding of base classes, rigid lifecycles, or tightly coupled framework logic. Inspired by the simplicity of middleware pipelines, PipeForge favors clear, minimal structure over hidden complexity.
99

10-
Pipelines operate on a specific class known as the **context**. Each pipeline step is a discrete unit of work, written in code and annotated with metadata indicating its order and (optional) filter. These steps are lazily instantiated and executed in sequence by the pipeline runner.
10+
Each pipeline operates on a specific class called the **context**, which flows through each step in sequence. Steps are discrete units of work, written as regular code and annotated with metadata to define their order and optional filters. They’re lazily instantiated - only created when needed - and executed by the pipeline runner.
1111

12-
At any point, the pipeline can **short-circuit**, halting execution - and preventing the instantiation of any remaining steps.
12+
At any point, a step can **short-circuit** the pipeline, halting further execution and preventing the instantiation of remaining steps.
1313

1414
## Sample Context
1515

@@ -18,7 +18,7 @@ For the purposes of this documentation, the following sample context will be use
1818
```csharp title="SampleContext.cs"
1919
public class SampleContext
2020
{
21-
private readonly List<string> _steps = new();
21+
public readonly List<string> Steps = new();
2222

2323
public void AddStep(string stepName)
2424
{
@@ -27,22 +27,22 @@ public class SampleContext
2727
throw new ArgumentException("Step name cannot be null or whitespace.", nameof(stepName));
2828
}
2929

30-
_steps.Add(stepName);
30+
Steps.Add(stepName);
3131
}
3232

33-
public int StepCount => _steps.Count;
33+
public int StepCount => Steps.Count;
3434

3535
public override string ToString()
3636
{
37-
return string.Join(",", _steps);
37+
return string.Join(",", Steps);
3838
}
3939
}
4040
```
4141

4242
This context allows us to:
4343
- Track pipeline progress via `AddStep()`
44+
- Evaluate the order and number of step executions
4445
- Print step execution history using `ToString()`
45-
- Assert how many steps ran using `StepCount`
4646
- Simulate errors by passing null or empty step names
4747

4848
## Installation

0 commit comments

Comments
 (0)