id_effect 0.2.0

Effect<A, E, R> (sync + async), context/layers, pipe — interpreter-style, no bundled executor
Documentation
# Pools — Reusing Expensive Resources

Creating a database connection takes time: DNS lookup, TCP handshake, TLS, authentication. Creating one per request is wasteful. A pool maintains a set of connections and lends them out, returning them when done.

id_effect provides `Pool` and `KeyedPool` as first-class effect constructs.

## Pool: Basic Connection Pool

```rust
use id_effect::Pool;

// Create a pool of up to 10 connections
let pool: Pool<Connection> = Pool::new(
    || open_connection("postgres://localhost/app"),  // factory
    10,                                               // max size
);
```

The pool lazily creates connections up to the max. Idle connections are kept alive for reuse.

## Using a Pool Connection

```rust
pool.with_resource(|conn: &Connection| {
    effect! {
        let rows = ~ conn.query("SELECT * FROM users");
        rows.into_iter().map(User::from_row).collect::<Vec<_>>()
    }
})
```

`with_resource` acquires a connection from the pool, runs the effect, and returns the connection automatically when done — regardless of success, failure, or cancellation. No `acquire_release` boilerplate; the pool handles it.

## Waiting for Availability

If all connections are in use, `with_resource` waits until one becomes available:

```rust
// Concurrent requests share the pool; each waits its turn
fiber_all(vec![
    pool.with_resource(|c| query_a(c)),
    pool.with_resource(|c| query_b(c)),
    pool.with_resource(|c| query_c(c)),
])
```

The pool queues waiters and notifies them as connections are returned.

## KeyedPool: Multiple Named Pools

For scenarios with multiple distinct pools (e.g., read replica + write primary):

```rust
use id_effect::KeyedPool;

let pools: KeyedPool<&str, Connection> = KeyedPool::new(
    |key: &&str| open_connection(key),
    5,  // max per key
);

// Get a connection for the write primary
pools.with_resource("write-primary", |conn| { ... })

// Get a connection for the read replica
pools.with_resource("read-replica", |conn| { ... })
```

Each key has its own independently bounded pool.

## Pool as a Service

In practice, pools live in the effect environment as services:

```rust
service_key!(DbPoolKey: Pool<Connection>);

fn query_users() -> Effect<Vec<User>, DbError, impl NeedsDbPool> {
    effect! {
        let pool = ~ DbPoolKey;
        ~ pool.with_resource(|conn| {
            effect! {
                let rows = ~ conn.query("SELECT * FROM users");
                rows.iter().map(User::from_row).collect::<Vec<_>>()
            }
        })
    }
}
```

The pool is provided via a Layer:

```rust
let pool_layer = LayerFn::new(|config: &Tagged<ConfigKey>| {
    effect! {
        let pool = Pool::new(
            || Connection::open(config.value().db_url()),
            config.value().pool_size(),
        );
        tagged::<DbPoolKey>(pool)
    }
});
```

Pool creation, lifecycle, and cleanup are all handled by the Layer. Business code sees only `NeedsDbPool`.