pondrs 0.3.0

A pipeline execution library
Documentation
# Dynamic Pipelines

Sometimes the set of steps in a pipeline isn't known at compile time. A config flag might enable or disable a step, or the number of steps might depend on runtime data. `StepVec` provides a type-erased, heap-allocated step container for these cases.

## `StepVec`

```rust,ignore
pub type StepVec<'a, E = PondError> = Vec<Box<dyn RunnableStep<E> + Send + Sync + 'a>>;
```

It implements `PipelineInfo` and `Steps<E>`, so it works everywhere tuples do — as the return type of a pipeline function, as the `steps` field of a `Pipeline`, and with `check()`, runners, and visualization.

Use `RunnableStep::boxed()` to convert a `Node` or `Pipeline` into a boxed trait object:

```rust,ignore
let step: Box<dyn RunnableStep<PondError> + Send + Sync + 'a> =
    Node { name: "n", func: |v| (v,), input: (&a,), output: (&b,) }.boxed();
```

## Conditional nodes

The primary use case is including or excluding nodes based on runtime configuration:

```rust,ignore
{{#include ../../../examples/dyn_steps/mod.rs:pipeline}}
```

The pipeline function returns `StepVec<'a>` instead of `impl Steps<PondError> + 'a`. Each node is `.boxed()` before being added to the vec, and conditional nodes are pushed with `if`.

## Nesting inside static pipelines

`StepVec` can be used as the `steps` of a `Pipeline`, which can itself be placed in a static tuple:

```rust,ignore
fn pipeline<'a>(cat: &'a Catalog, params: &'a Params) -> impl Steps<PondError> + 'a {
    let dynamic_section = Pipeline {
        name: "optional_reports",
        steps: {
            let mut s: StepVec<'a> = vec![
                Node { name: "base_report", ... }.boxed(),
            ];
            if params.detailed.0 {
                s.push(Node { name: "detailed_report", ... }.boxed());
            }
            s
        },
        input: (&cat.summary,),
        output: (&cat.report,),
    };

    (
        Node { name: "summarize", ... },
        dynamic_section,
    )
}
```

This lets you keep type safety for the fixed parts of your pipeline and only pay for dynamic dispatch where you need it.

## Validation

`check()` works identically for `StepVec` and tuple-based pipelines — it iterates the items and validates sequential ordering, duplicate outputs, and pipeline contracts. Since `StepVec` is built at runtime, validation applies to the *constructed* pipeline only, not hypothetical alternatives. An excluded conditional node won't be checked.

If a `StepVec` is wrapped in a `Pipeline` with declared outputs, those outputs must be produced by the nodes that are actually present. An empty `StepVec` in a `Pipeline` that declares outputs will correctly fail with `UnproducedPipelineOutput`.

## When to use `StepVec` vs tuples

Use **tuples** (the default) when all nodes are known at compile time. You get zero-cost dispatch and full type checking.

Use **`StepVec`** when you need:
- Conditional inclusion/exclusion of nodes based on config or params
- A variable number of nodes determined at runtime
- Nodes of heterogeneous types that can't be expressed in a single tuple