noema 0.4.0

Noema IOC and DI framework for Rust
Documentation
# Configuration (`configurations`)

Typed config structs that resolve like any other singleton. You pick the **source** at runtime (`if`s in `main`); `load(source).await` hydrates every `config!(T)` from any linked crate; later `resolve::<T>()` is `Arc::clone`.

Enable:

```toml
noema = { version = "0.3", features = ["configurations"] }
```

Depends on `di`. Source methods are **async** so a Vault/SSM/HTTP provider can `.await`.

## Quick path

```rust
use noema::config::{load, EnvSource, Configurable};
use noema::resolve;

#[derive(Configurable)]
struct DatabaseConfig {
    #[config(name = "DATABASE_URL")]
    url: String,
    #[config(name = "DATABASE_POOL_SIZE", default = 10)]
    pool_size: u32,
    #[config(name = "DATABASE_SSL")]
    ssl: Option<bool>,
}

noema::config!(DatabaseConfig);

#[tokio::main]
async fn main() {
    if std::env::var("CONFIG_FILE").is_ok() {
        // your FileSource / VaultSource
        load(EnvSource).await.expect("config");
    } else {
        load(EnvSource).await.expect("config");
    }

    let db = resolve::<DatabaseConfig>();
    println!("{} pool={}", db.url, db.pool_size);
}
```

`config!(T)` can live next to the type in any crate. `main` does **not** list the types. The crate that calls `config!` must be linked into the binary.

## Field attributes

| Attribute | Effect |
|-----------|--------|
| `name = "KEY"` | Source key (env var or provider path). Default: field name |
| `default = "10"` or `default = 10` | Used when the key is absent |
| field type `Option<T>` | Absent key → `None` (not an error) |

Values parse with `FromStr`.

## Custom provider

```rust
use async_trait::async_trait;
use noema::config::{ConfigSource, ConfigError};

struct VaultSource { /* client */ }

#[async_trait]
impl ConfigSource for VaultSource {
    async fn load(&self) -> Result<(), ConfigError> {
        // prefetch / login
        Ok(())
    }

    async fn get(&self, key: &str) -> Result<Option<String>, ConfigError> {
        // HTTP / SDK
        let _ = key;
        Ok(None)
    }
}
```

`get_or_default` has a default impl on the trait (`get` then `default`).

## Rules

- Call `load(source).await` **before** any `resolve` of a config type.
- `load` once per process. A second `load` errors (`AlreadyLoaded`).
- After `load`, two `resolve::<T>()` calls return `Arc` pointer-equal clones.
- Do not also `dependency!(singleton, T)` for the same `T` (duplicate `Resolver`).
- Inject as usual: `#[derive(Injectable)] struct S { db: Arc<DatabaseConfig> }`.
- No `Any`, no `TypeId`. Registry is an `RwLock<Vec<fn>>`; each fn sets a typed `OnceLock`.