duroxide 0.1.27

Durable code execution framework for Rust
Documentation
# ContinueAsNew Semantics

ContinueAsNew (CAN) terminates the current execution and starts a new execution for the same instance with fresh input and empty history.

## API

```rust
// Returns a future that never resolves - use with `return` and `await`
fn continue_as_new(&self, input: impl Into<String>) -> impl Future<Output = Result<String, String>>
fn continue_as_new_typed<T: Serialize>(&self, input: &T) -> impl Future<Output = Result<String, String>>
fn continue_as_new_versioned(&self, version: &str, input: &str) -> impl Future<Output = Result<String, String>>
```

**Usage pattern:**
```rust
async fn my_orchestration(ctx: OrchestrationContext, input: String) -> Result<String, String> {
    // Do some work...
    let result = ctx.schedule_activity("DoWork", input.clone()).await?;
    
    if should_continue(&result) {
        // MUST use `return` and `.await` - future never resolves
        return ctx.continue_as_new(result).await;
    }
    
    Ok(result)
}
```

**Important:** `continue_as_new()` returns a future that **never resolves**. You must:
1. Call `.await` on it
2. Use `return` to ensure code after is unreachable

The compiler will warn about unreachable code if you forget `return`.

## Key Points

- **Terminal for current execution:** The current execution appends `OrchestrationContinuedAsNew { input }` and stops.
- **New execution history:** The runtime creates a new execution (incremented execution_id) and stamps a fresh `OrchestrationStarted` with the provided input. The provider simply persists what the runtime sends.
- **Event targeting:** Activity/timer/external events always target the currently active execution. After CAN, the next execution must resubscribe/re-arm as needed.
- **Result propagation:** The initial start handle resolves with an empty output when the first execution continues-as-new; the runtime then automatically starts the next execution.

## Flow

1. Orchestrator calls `return ctx.continue_as_new(new_input).await`.
2. Runtime extracts pending actions (including `ContinueAsNew`) before checking future state.
3. Runtime appends terminal `OrchestrationContinuedAsNew` to the current execution.
4. Runtime enqueues a `WorkItem::ContinueAsNew` and later processes it as a new execution with empty history, stamping `OrchestrationStarted { name, version, input: new_input, parent_instance?, parent_id? }`.
5. Provider receives `ack_orchestration_item(lock_token, execution_id = prev + 1, ...)` and atomically persists the new execution row, updates `instances.current_execution_id = MAX(..., execution_id)`, and appends events.

## Practical Tips

- Re-create subscriptions and timers in the new execution; carry forward any needed state via the new input.
- External events sent before the new execution subscribes will be dropped; raise them after the new execution has subscribed.
- Use deterministic correlation IDs if you coordinate sub-orchestrations across executions.
- **Sessions survive ContinueAsNew** — the session row persists, and the new execution
  can schedule activities on the same `session_id`. Activities route to the same worker.

## Pruning Old Executions

Long-running workflows using ContinueAsNew will accumulate execution history over time. Use `prune_executions` to clean up old executions while preserving the current execution:

```rust
use duroxide::PruneOptions;

// Prune to only the current execution (recommended)
client.prune_executions("my-instance", PruneOptions {
    keep_last: None,  // No count-based retention
    ..Default::default()
}).await?;

// Keep the last 10 executions
client.prune_executions("my-instance", PruneOptions {
    keep_last: Some(10),
    ..Default::default()
}).await?;

// Or prune by age (executions older than 30 days)
let thirty_days_ago = now_ms - (30 * 24 * 60 * 60 * 1000);
client.prune_executions("my-instance", PruneOptions {
    completed_before: Some(thirty_days_ago),
    ..Default::default()
}).await?;
```

**Safety guarantees:**
- The **current execution** (highest execution_id) is **never** pruned
- This protection applies to both running and terminal instances
- Pruning can be done while the workflow is actively running

**Note on `keep_last` semantics:** Since the current execution is always protected and is always the highest execution_id, `keep_last: None`, `Some(0)`, and `Some(1)` are all equivalent—they all prune down to exactly the current execution. Use `None` for clarity when you want to prune all historical executions.