abundantis

Crates.ioabundantis
lib.rsabundantis
version0.1.9
created_at2026-01-13 12:28:12.948246+00
updated_at2026-01-23 11:24:06.389002+00
descriptionHigh-performance unified environment variable management from multiple sources
homepage
repositoryhttps://github.com/ph1losof/abundantis
max_upload_size
id2040028
size309,645
Dmytro Pletenskyi (ph1losof)

documentation

README

Abundantis ðŸŒ―

High-performance unified environment variable management from multiple sources.

License: MIT/Apache Rust Async


Overview

Abundantis is a Rust crate for unified environment variable management across multiple sources. It serves as the foundational layer for the Ecolog ecosystem while being independently usable for any project.

Key Features

  • ðŸ§Đ Plugin Architecture - Add custom sources via trait implementations
  • 📁 Multiple Sources - File (dotenv), Shell, Memory, and Remote (prepared for future)
  • 🔗 Dependency Resolution - Full variable interpolation with cycle detection
  • 🏗 Workspace Support - Monorepo providers (Turbo, Nx, Lerna, pnpm, npm, Cargo, Custom)
  • ðŸ“Ą Event System - Async event bus for reactive updates
  • ðŸ’ū Multi-level Cache - Hot LRU cache + warm TTL cache
  • ⚡ High Performance - Zero-copy parsing, SIMD interpolation, lock-free concurrent access

What Makes It Different

Feature Traditional .env libs Abundantis
Source Aggregation Single source Multiple sources with precedence
Interpolation Simple ${VAR} Full shell syntax ${VAR:-default}, ${VAR:+alt}
Workspace Support ❌ ✅ Monorepo-aware context resolution
Async Support ❌ ✅ Tokio-based async API
Event System ❌ ✅ Reactive change notifications
Extensibility ❌ ✅ Plugin architecture for custom sources
Remote Sources ❌ 🔜 Architecture prepared (Vault, AWS, GCP, Azure)

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         Abundantis                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │
│  │  Source     │  │ Resolution  │  │  Workspace  │  │
│  │  Registry   │──│    Engine   │──│   Manager   │  │
│  └─────────────┘  └─────────────┘  └─────────────┘  │
│         │                 │                 │              │
│         ▾                 ▾                 ▾              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │
│  │   Event     │  │    Cache    │  │   Config    │  │
│  │    Bus      │  │   System    │  │   Loader    │  │
│  └─────────────┘  └─────────────┘  └─────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Design Principles

1. Performance-First Architecture

  • Zero-copy parsing via korni crate for .env file parsing
  • SIMD-accelerated interpolation via germi crate for variable resolution
  • Lock-free concurrent access using dashmap for shared state
  • Cache-friendly data structures with hashbrown for optimized hashing
  • Small string optimization with compact_str to reduce allocations

2. Modular Plugin System

  • Trait-based source abstraction via EnvSource and AsyncEnvSource traits
  • Dynamic source registration through SourceRegistry for extensibility
  • Priority-based resolution ensuring higher-priority sources override lower ones
  • Capability flags for source feature detection (READ, WRITE, WATCH, etc.)

3. Monorepo-Aware Design

  • Provider abstraction for different monorepo tools (Turbo, Nx, Lerna, pnpm, npm, Yarn, Cargo, Custom)
  • Context-aware resolution that maps file paths to workspace packages
  • Cascading environment support for hierarchical configurations
  • Package discovery through provider-specific config file parsing

Module Architecture

1. Source Management (source/)

Purpose: Provides extensible system for environment variable sources

Components:

  • traits.rs: Core source abstractions

    • EnvSource: Synchronous source trait
    • AsyncEnvSource: Asynchronous source trait (future-proofing)
    • SourceFactory: Factory pattern for dynamic source creation
    • SourceId, SourceType, Priority, SourceCapabilities: Source metadata
  • registry.rs: Centralized source management

    • Dynamic source registration and retrieval
    • Priority-based sorting for conflict resolution
    • Batch loading from all registered sources
    • Source metadata tracking
  • memory.rs: In-memory source for programmatic usage

    • Thread-safe variable storage using parking_lot::Mutex
    • Insertion order preservation using indexmap::IndexMap
    • Version tracking for change detection
    • Direct CRUD operations for testing and runtime configuration
  • file.rs: File-based .env source

    • LRU caching with lru crate
    • Automatic invalidation on file changes
    • Efficient parsing with korni
    • Path-aware variable origin tracking
  • shell.rs: Shell environment source

    • Direct std::env access
    • Highest priority (100) to override file sources
    • Immutable snapshot on first load

Architectural Choice:

  • Trait-based abstraction enables runtime source registration
  • Arc-wrapped sources for zero-copy sharing across threads
  • Lazy loading with invalidation to minimize I/O

2. Configuration (config/)

Purpose: Hierarchical configuration system

Components:

  • types.rs: Configuration data structures
    • AbundantisConfig: Root configuration with Serde support
    • WorkspaceConfig: Monorepo detection and settings
    • ResolutionConfig: Variable resolution behavior
    • InterpolationConfig: Interpolation rules and limits
    • FileResolutionConfig: .env file merging strategy
    • CacheConfig: Cache tuning parameters

Architectural Choices:

  • Serde-based serialization for TOML/JSON config files
  • Default trait derivation for sensible out-of-the-box behavior
  • CompactString for all string fields to reduce memory overhead
  • Enum-based types for compile-time validation

3. Workspace Management (workspace/)

Purpose: Monorepo-aware context resolution

Components:

  • context.rs: Workspace and package context definitions

    • WorkspaceContext: Root, package, and env file mapping
    • PackageInfo: Package metadata (root, name, relative path)
    • Hash-based equality for fast caching
  • manager.rs: Workspace state and discovery

    • Package discovery via provider registry
    • Path-to-context mapping with cache
    • Cascading configuration support
    • Efficient context lookups using hashbrown::HashMap
  • provider/: Monorepo tool integrations

    • registry.rs: Provider registration and detection
    • cargo.rs: Cargo workspace detection
    • turbo.rs: Turborepo package discovery
    • nx.rs: Nx project detection
    • lerna.rs: Lerna workspace parsing
    • pnpm.rs: pnpm workspace support
    • npm.rs: npm/yarn workspace detection
    • custom.rs: User-defined roots

Architectural Choices:

  • Provider trait for uniform monorepo tool abstraction
  • Lazy discovery with caching for startup performance
  • Context caching using DashMap for concurrent access
  • Pattern-based package discovery using globset for flexibility

4. Resolution Engine (resolution/)

Purpose: Intelligent variable resolution with interpolation

Components:

  • ResolvedVariable: Fully resolved variable with metadata

    • Key, raw value, and resolved value
    • Source origin tracking
    • Warning flags for issues
    • Description support
  • DependencyGraph: Placeholder for future interpolation graph

    • Circular dependency detection
    • Reference graph traversal
  • ResolutionEngine: Core resolution logic

    • Multi-source aggregation
    • Priority-based conflict resolution
    • Interpolation with depth limiting
    • Async-friendly design
  • ResolutionCache: Multi-level caching system

    • Hot LRU cache for frequently accessed variables
    • TTL cache for time-based invalidation
    • Thread-safe operations

Architectural Choices:

  • SIMD interpolation via germi for performance
  • Graph-based dependency tracking for circular reference detection
  • Multi-level caching to reduce resolution overhead
  • Arc-wrapped results for zero-copy sharing

5. Error Handling (error.rs)

Purpose: Comprehensive error reporting with LSP integration

Components:

  • AbundantisError: Main error type with variants

    • Configuration errors
    • Workspace errors
    • Source errors
    • Resolution errors (circular dependency, max depth, undefined variable)
    • IO and runtime errors
  • SourceError: Source-specific errors

    • Parse errors with line/column information
    • Remote errors with provider context
    • Timeout and authentication errors
  • Diagnostic: LSP-compatible diagnostic messages

    • Severity levels (Error, Warning, Info, Hint)
    • Diagnostic codes for categorization
    • File and position tracking

Architectural Choices:

  • thiserror for ergonomic error handling with automatic Display implementation
  • Detailed error context with file paths and line numbers
  • LSP-ready diagnostics for IDE integration
  • Error chain preservation for debugging

6. Event System (events/)

Purpose: Reactive updates and notifications

Components:

  • AbundantisEvent: Event types

    • Source added/removed
    • Variables changed
    • Cache invalidated
  • EventSubscriber: Trait for event handling

    • Sync-only design (dyn-compatible)
    • Send + Sync bounds for thread safety
  • EventBus: Event publishing (stubbed)

    • Channel-based async messaging (planned)
    • Subscription management

Architectural Choices:

  • Trait-based subscriber pattern for extensibility
  • Dyn-compatible design removed async method to support trait objects
  • Future-proof structure for async event bus implementation

7. Builder Pattern (core/)

Purpose: Fluent API for Abundantis construction

Components:

  • AbundantisBuilder: Builder implementation
    • Fluent API with method chaining
    • Async/sync variants
    • Configuration validation

Architectural Choices:

  • Builder pattern for complex configuration
  • Derive Default for easy construction
  • Option-based setters for flexible configuration

Data Flow

Initialization Flow

1. Load Configuration (AbundantisConfig)
   ↓
2. Initialize WorkspaceManager
   - Detect monorepo type
   - Discover packages
   - Build context cache
   ↓
3. Initialize SourceRegistry
   - Register default sources (file, shell, memory)
   - Register custom sources if configured
   ↓
4. Initialize ResolutionEngine
   - Setup dependency graph
   - Initialize cache
   ↓
5. Initialize EventBus
   - Setup subscribers
   ↓
6. Create Abundantis instance
   ↓
7. Load all sources
   - Each source produces a Snapshot
   - Store in SourceRegistry

Resolution Flow

Request: get("DATABASE_URL", path)
   ↓
1. WorkspaceManager.context_for_file(path)
   - Lookup in context cache
   - Return WorkspaceContext
   ↓
2. ResolutionEngine.resolve(key, context, registry)
   - Load all source snapshots
   - Aggregate variables by priority
   ↓
3. For each source (highest to lowest priority):
   - Find variable in source
   - If found, apply interpolation
     - Detect circular dependencies
     - Resolve references recursively
     - Apply depth limit (default: 64)
   - Return resolved value
   ↓
4. Return ResolvedVariable with metadata

Caching Strategy

Level 1: Memory Source Cache
  - Direct variable access in memory
  - No locking needed for reads

Level 2: Source Snapshot Cache
  - Cached parsed variables per source
  - LRU eviction (default: 1000 entries)
  - Invalidated on source changes

Level 3: Resolution Cache
  - Cached resolved variables
  - Hot cache for frequently accessed keys
  - TTL-based invalidation (default: 5m)

Performance

Abundantis is optimized for speed:

Operation Performance Notes
Cached variable lookup < 100ns Hot cache access
File source load < 1ms Zero-copy parsing via korni
Variable interpolation < 500Ξs SIMD via germi
All variables enumeration < 5ms For 1000 variables
Memory overhead ~1MB For 10,000 variables

Optimizations

  • Zero-copy parsing - Uses Cow<'a, str> from korni
  • SIMD interpolation - memchr and optimized algorithms in germi
  • Lock-free reads - DashMap for concurrent cache access
  • Small strings - CompactString optimization for keys/values
  • Cache-friendly - hashbrown with inline arrays for small maps
  • Lazy loading - Sources load on-demand, not at construction

Performance Characteristics

Time Complexity

  • Variable lookup: O(1) - Hash map access
  • Source loading: O(n) - n = number of sources
  • Resolution: O(d) - d = interpolation depth (max: 64)
  • Context lookup: O(1) - Cached with hash map

Space Complexity

  • Memory: O(v) - v = total variables across all sources
  • Cache: O(c) - c = cache size (default: 1000)
  • Metadata: O(s) - s = number of sources

Expected Benchmarks

  • Parse 1000 variables: < 1ms
  • Resolve simple variable: < 10Ξs
  • Resolve with interpolation (depth 5): < 100Ξs
  • Concurrent access (100 threads): Lock-free reads

Installation

Add to your Cargo.toml:

[dependencies]
abundantis = "0.1"

With specific features:

[dependencies]
abundantis = { version = "0.1", features = ["full"] }

Features

  • file (default) - FileSource for .env files
  • shell (default) - ShellSource for process environment
  • async - Async runtime support (tokio) for async sources and APIs
  • watch - File watching via notify with debouncing
  • full - Enables all features

Quick Start

Basic Usage

use abundantis::{Abundantis, config::MonorepoProviderType};

#[tokio::main]
async fn main() -> abundantis::Result<()> {
    let abundantis = Abundantis::builder()
        .root(".")
        .provider(MonorepoProviderType::Custom)
        .with_shell()
        .env_files(vec![".env", ".env.local"])
        .build()
        .await?;

    // Get a variable
    if let Some(var) = abundantis.get("DATABASE_URL").await {
        println!("Database: {}", var.resolved_value);
        println!("Source: {:?}", var.source);
    }

    // Get all variables for a file
    let vars = abundantis.all_for_file(Path::new("src/main.rs")).await;
    for var in vars {
        println!("{} = {}", var.key, var.resolved_value);
    }

    Ok(())
}

In-Memory Usage

use abundantis::{MemorySource, Abundantis};

#[tokio::main]
async fn main() -> abundantis::Result<()> {
    let abundantis = Abundantis::builder()
        .with_source(MemorySource::new())
        .build()
        .await?;

    // Set variables programmatically
    // (for testing or dynamic config)

    let vars = abundantis.all().await;
    Ok(())
}

Event Subscriptions

use abundantis::{Abundantis, events::{AbundantisEvent, EventSubscriber}};

struct MySubscriber;

impl EventSubscriber for MySubscriber {
    fn on_event(&self, event: &AbundantisEvent) {
        match event {
            AbundantisEvent::VariablesChanged { added, removed, .. } => {
                println!("Added: {:?}, Removed: {:?}", added, removed);
            }
            _ => {}
        }
    }
}

#[tokio::main]
async fn main() -> abundantis::Result<()> {
    let subscriber = std::sync::Arc::new(MySubscriber);

    let abundantis = Abundantis::builder()
        .subscribe(subscriber)
        .build()
        .await?;

    // Subscribe to channel for async handling
    let mut rx = abundantis.event_bus().subscribe_channel();
    while let Ok(event) = rx.recv().await {
        println!("Event: {:?}", event);
    }

    Ok(())
}

Configuration

Builder Options

Method Description Default
root(path) Workspace root directory current_dir()
provider(type) Monorepo provider type Required
with_shell() Include shell environment source false
env_files(patterns) Env file patterns to scan .env, .env.local, .env.development, .env.production
cascading(enabled) Enable cascading env inheritance false
interpolation(enabled) Enable variable interpolation true
max_interpolation_depth(n) Max recursion depth for interpolation 64
precedence(order) Source priority order [Shell, File]
watch(enabled) Enable file watching false
event_buffer_size(n) Event channel buffer size 256
subscribe(subscriber) Add event subscriber None

Source Precedence

Sources are checked in the order specified. Higher priority wins conflicts:

use abundantis::{config::SourcePrecedence, Abundantis};

let abundantis = Abundantis::builder()
    .precedence(vec![
        SourcePrecedence::Shell,    // Highest priority (100)
        SourcePrecedence::File,     // Medium priority (50)
        // SourcePrecedence::Remote, // Prepared for future (75)
    ])
    .build()
    .await?;

Workspace Providers

Abundantis includes built-in support for popular monorepo tools:

Provider Config File Detection
Turbo turbo.json Delegates to pnpm/npm/yarn
Nx nx.json ✅
Lerna lerna.json ✅
pnpm pnpm-workspace.yaml ✅
npm/yarn package.json workspaces ✅
Cargo Cargo.toml ✅
Custom Configured via API ✅
use abundantis::{config::MonorepoProviderType, Abundantis};

// Auto-detect provider
let abundantis = Abundantis::builder()
    .provider(MonorepoProviderType::Turbo)
    .build()
    .await?;

Type System & Error Handling

Trait Hierarchy

EnvSource (synchronous)
  ├── MemorySource
  ├── FileSource
  └── ShellSource

AsyncEnvSource (asynchronous, future-proofing)
  └── (future remote sources)

SourceFactory
  ├── FileSourceFactory
  ├── ShellSourceFactory
  └── MemorySourceFactory

MonorepoProvider
  ├── CargoProvider
  ├── TurboProvider
  ├── NxProvider
  ├── LernaProvider
  ├── PnpmProvider
  ├── NpmProvider
  └── CustomProvider

Error Categories

  1. Configuration Errors: Invalid or missing configuration
  2. Workspace Errors: Monorepo detection/discovery failures
  3. Source Errors: Source-specific failures (IO, parse, auth)
  4. Resolution Errors: Variable resolution failures (circular refs, max depth)
  5. IO Errors: Filesystem operations
  6. Runtime Errors: Tokio runtime and async execution

Error Propagation

SourceError → AbundantisError::Source → Result<T>

Diagnostic Reporting

Severity Levels: Error, Warning, Info, Hint Diagnostic Codes: EDFxxx (env file), RESxxx (resolution), WSxxx (workspace)


Custom Sources

You can extend Abundantis with custom environment variable sources:

use abundantis::{
    source::{EnvSource, SourceId, SourceType, Priority, SourceCapabilities, SourceSnapshot, ParsedVariable, VariableSource},
    error::SourceError,
};

struct MyCustomSource {
    id: SourceId,
}

impl MyCustomSource {
    fn new() -> Self {
        Self {
            id: SourceId::new("my-custom"),
        }
    }
}

impl EnvSource for MyCustomSource {
    fn id(&self) -> &SourceId {
        &self.id
    }

    fn source_type(&self) -> SourceType {
        SourceType::File  // Or Memory, Remote
    }

    fn priority(&self) -> Priority {
        Priority::FILE  // Or custom value
    }

    fn capabilities(&self) -> SourceCapabilities {
        SourceCapabilities::READ | SourceCapabilities::CACHEABLE
    }

    fn load(&self) -> Result<SourceSnapshot, SourceError> {
        // Load your variables here
        let vars = vec![
            ParsedVariable::simple(
                "CUSTOM_VAR",
                "custom_value",
                VariableSource::Memory,
            ),
        ];

        Ok(SourceSnapshot {
            source_id: self.id.clone(),
            variables: vars.into(),
            timestamp: std::time::Instant::now(),
            version: None,
        })
    }

    fn has_changed(&self) -> bool {
        // Return true if source needs reload
        true
    }

    fn invalidate(&self) {
        // Clear any internal cache
    }
}

Roadmap

v0.1 (Current)

  • ✅ FileSource with korni integration
  • ✅ ShellSource for process environment
  • ✅ MemorySource for testing
  • ✅ Monorepo workspace providers (Turbo, Nx, Lerna, etc.)
  • ✅ Dependency graph with cycle detection
  • ✅ Interpolation via germi
  • ✅ Multi-level cache
  • ✅ Async event bus

v0.2 (Planned)

  • 🔜 Remote source architecture
  • 🔜 File watching with debouncing
  • 🔜 Configuration file loading
  • 🔜 Builder pattern completion
  • 🔜 Sync API wrappers

v0.3 (Future)

  • 🔜 HashiCorp Vault source
  • 🔜 AWS Secrets Manager source
  • 🔜 GCP Secret Manager source
  • 🔜 Azure Key Vault source
  • 🔜 Write-back support for sources
  • 🔜 Version history for sources
  • 🔜 Async source implementations

Testing

Run tests:

cargo test --all-features

Run benchmarks:

cargo bench --all-features

Test Organization

tests/
├── config_tests.rs       (42 tests)
├── error_tests.rs        (38 tests)
├── integration_tests.rs  (35 tests)
├── memory_source_tests.rs (27 tests)
├── source_traits_tests.rs (32 tests)
└── workspace_tests.rs     (26 tests)

Total: 212 tests

Build System

Features

  • default: Enables file and shell sources
  • file: File source with korni parsing
  • shell: Shell environment source
  • async: Async runtime with tokio
  • watch: File system watching with notify
  • full: All features enabled

Feature Flags in Code

#[cfg(feature = "async")]
#[cfg(feature = "file")]
#[cfg(feature = "shell")]
#[cfg(feature = "watch")]

Dependencies

Core Dependencies

  • korni: Zero-copy .env file parsing
  • germi: SIMD-accelerated interpolation
  • hashbrown: Optimized HashMap implementation
  • parking_lot: Fast mutex/rwlock primitives
  • compact_str: Small string optimization
  • dashmap: Concurrent HashMap for lock-free reads
  • bitflags: Type-safe flag bitmasks

Optional Dependencies

  • tokio: Async runtime (feature: async)
  • notify: File system watching (feature: watch)
  • serde: Serialization/deserialization
  • toml: TOML config file support
  • serde_json: JSON config file support

Security Considerations

  1. Secret Redaction: Diagnostic messages filter sensitive values
  2. Path Validation: All paths are canonicalized to prevent directory traversal
  3. Error Messages: Don't leak internal paths or secrets
  4. Source Priority: Shell environment (highest priority) is trusted

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

Areas for Contribution

  • Remote source implementations (Vault, AWS, GCP, Azure)
  • Additional monorepo providers
  • Performance optimizations
  • Documentation improvements
  • Bug fixes and edge cases

License

Licensed under either of:

at your option.


Acknowledgments

  • korni - Zero-copy .env parser
  • germi - SIMD shell interpolation
  • Ecolog - Inspiration and ecosystem

Related Projects

  • Ecolog - LSP-powered environment variable tooling (part of the same ecosystem)
  • dotenvy - Rust dotenv library
  • shellexpand - Shell expansion in Rust
Commit count: 2

cargo fmt