id_effect 0.2.0

Effect<A, E, R> (sync + async), context/layers, pipe — interpreter-style, no bundled executor
Documentation
# Built-in Schedules — exponential, fibonacci, forever

id_effect provides a library of common schedule types. This section catalogs them with typical use cases.

## exponential

```rust
Schedule::exponential(Duration::from_millis(100))
// Delays: 100ms, 200ms, 400ms, 800ms, 1600ms, ...
```

The standard backoff pattern. Each delay doubles. Use for most network retry scenarios — it backs off quickly and gives the downstream service time to recover.

```rust
// With cap: 100ms, 200ms, 400ms, 800ms, 800ms, 800ms (capped at 800ms)
Schedule::exponential(Duration::from_millis(100))
    .with_max_delay(Duration::from_millis(800))
```

## fibonacci

```rust
Schedule::fibonacci(Duration::from_millis(100))
// Delays: 100ms, 100ms, 200ms, 300ms, 500ms, 800ms, 1300ms, ...
```

Fibonacci backoff grows more gradually than exponential — useful when you want more retries in the early attempts before backing off significantly.

## spaced

```rust
Schedule::spaced(Duration::from_secs(5))
// Delays: 5s, 5s, 5s, 5s, ... (constant)
```

Fixed interval. Use for polling (checking status every N seconds), heartbeats, or scenarios where the delay should be predictable.

## forever

```rust
Schedule::forever()
// No delay between repetitions; runs indefinitely
```

Run an effect as fast as possible, forever. Use with `repeat` for continuous background jobs (e.g., metrics collection, background syncs). Almost always combined with `.take(n)` or `.until_total_duration(d)`.

## Limiters

Limiters constrain any schedule:

```rust
// Stop after N attempts
schedule.take(5)

// Stop after N successes (for repeat)
schedule.take_successes(3)

// Stop after total elapsed time
schedule.until_total_duration(Duration::from_secs(30))

// Stop after N total elapsed retries including initial
schedule.take_with_initial(6)  // 1 initial + 5 retries
```

## Jitter

```rust
// Add random delay in [0, jitter_max) to each wait
schedule.with_jitter(Duration::from_millis(50))

// Alternatively, full jitter (random in [0, delay])
schedule.with_full_jitter()
```

Jitter prevents retry storms. When 1000 clients all hit a failing service and all retry at the same time, they create a "thundering herd." Jitter spreads the retries out.

## Combining with &&

```rust
let bounded_exponential = Schedule::exponential(Duration::from_millis(100))
    .take(5)
    .with_jitter(Duration::from_millis(20))
    .until_total_duration(Duration::from_secs(10));
```

The `&&` (chain) operation means "continue only if both schedules agree to continue." The first schedule that says "done" wins.