# HydraCache
HydraCache is a Rust-native local async cache that is designed to grow toward database result caching and distributed synchronization later.
## Status
HydraCache is in early development. The current implementation targets the first local-cache release.
## Why HydraCache?
HydraCache is not trying to replace low-level cache engines, databases, or
query processors. It is an application-facing cache layer for Rust services.
Compared with using Moka directly, HydraCache adds a smaller product-shaped API:
loader helpers, TTLs, tag invalidation, local single-flight, codec-backed
storage, and lightweight stats in one place.
Compared with ORM-level caches, HydraCache keeps freshness explicit. Keys,
tags, and invalidation are application-controlled instead of hidden behind a
large persistence framework.
Compared with Redis-style caches, HydraCache is embedded and local-first. The
first version needs no server, proxy, daemon, or network hop.
Compared with ReadySet or Noria-style query engines, HydraCache deliberately
does not try to incrementally maintain SQL result graphs. It is a lightweight
cache library first, with database-result caching planned as an adapter layer.
The long-term direction is:
```text
simple local cache -> database result-cache adapter -> optional distributed synchronization
```
## v0 Scope
The first version includes:
- local async cache runtime
- `HydraCache::local()` builder
- `get`
- `put`
- `get_or_load`
- `get_or_insert_with`
- `try_get_or_insert_with`
- local single-flight miss deduplication
- `contains_key`
- per-entry TTL and default TTL
- tag-aware invalidation
- key invalidation
- `remove` as a local-cache alias for key invalidation
- `flush`
- `postcard` codec over `Bytes`
- lightweight stats
- single-flight join stats
- tag-generation invalidation safety
- Moka-backed local storage
Out of scope for v0:
- SQLx adapter
- proc macros
- distributed invalidation
- cluster roles
- generation counters
- persistence
## Example
```rust
use std::time::Duration;
use hydracache::{CacheOptions, HydraCache};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
id: u64,
name: String,
}
async fn load_user(id: u64) -> Result<User, std::io::Error> {
Ok(User {
id,
name: format!("user-{id}"),
})
}
# async fn example() -> hydracache::CacheResult<()> {
let cache = HydraCache::local()
.default_ttl(Duration::from_secs(300))
.max_capacity(10_000)
.build();
let user = cache
.try_get_or_insert_with(
"user:42",
CacheOptions::new()
.ttl(Duration::from_secs(60))
.tags(["user:42", "users"]),
|| async { load_user(42).await },
)
.await?;
cache.invalidate_tag("user:42").await?;
# Ok(())
# }
```
## API Notes
`get` returns `Ok(None)` when the key is missing or expired.
`get_or_load` runs the loader on a miss and stores the loaded value with the provided `CacheOptions`.
`get_or_insert_with` is the short local-cache spelling for infallible async loaders.
`try_get_or_insert_with` is the fallible-loader spelling. It behaves the same as `get_or_load`.
Concurrent `get_or_load` calls for the same missing key share one loader execution. Cache hits bypass single-flight entirely.
If a tag is invalidated while a tagged loader is still running, HydraCache skips
storing that stale loader result. Callers after the invalidation start or join a
fresh in-flight load instead of joining the stale one.
`contains_key` checks whether a key currently maps to a usable value. Expired entries are removed and reported as absent.
`remove` and `invalidate_key` both remove one key. `remove` is the shorter local-cache spelling; `invalidate_key` is kept for consistency with tag invalidation.
`invalidate_tag` removes all entries currently associated with the tag.
Use `CacheOptions::tag("users")` for one tag and `CacheOptions::tags(["users", "user:42"])` for multiple tags.
`stats` returns lightweight counters for hits, misses, loads, single-flight joins, stale load discards, invalidations, and evictions. v0 does not wire backend eviction listeners yet, so `evictions` remains zero.
## Release Plan
The v0 release plan is maintained here:
- [docs/plans/V0_RELEASE_PLAN.md](docs/plans/V0_RELEASE_PLAN.md)
- [docs/plans/V0_3_LOCAL_ERGONOMICS_PLAN.md](docs/plans/V0_3_LOCAL_ERGONOMICS_PLAN.md)
## Workspace
- `crates/hydracache-core` - core public types, codec, options, errors, stats
- `crates/hydracache` - user-facing local cache runtime
- `crates/hydracache-macros` - future macro ergonomics
- `crates/hydracache-sqlx` - future SQLx-first adapter layer