Skip to content

Releases: javaquasar/hydracache

v0.15.0

08 Jun 22:35

Choose a tag to compare

HydraCache 0.15.0 Release Notes

HydraCache 0.15.0 improves the explicit local-cache macro API introduced in
0.14.0.

What's New

  • Added tags = ... to cacheable!(...).
  • Kept repeated tag = ... support and allowed it to combine with tags = ....
  • Added cacheable_infallible!(...) for loaders that return a value directly
    instead of Result<T, E>.
  • Re-exported cacheable_infallible! from the base hydracache crate.
  • Expanded README and cargo-doc examples for tags = ..., TagSet, and
    cacheable_infallible!(...).
  • Added crates/hydracache/examples/cacheable_function.rs as a live example of
    cache hit, invalidation, and infallible function caching.

Examples

use hydracache::{cacheable, cacheable_infallible, HydraCache};

# async fn example() -> hydracache::CacheResult<()> {
let cache = HydraCache::local().build();

let value = cacheable!(
    cache = cache,
    key = "expensive:42",
    tags = ["expensive", "expensive:42"],
    ttl_secs = 60,
    load = || async { Ok::<_, std::io::Error>(42_u64) },
)
.await?;

let total = cacheable_infallible!(
    cache = cache,
    key = "expensive-total",
    tags = ["expensive"],
    ttl_secs = 60,
    load = || async { 1_u64 },
)
.await?;

assert_eq!(value, 42);
assert_eq!(total, 1);
# Ok(())
# }

Why It Matters

This release keeps the macro explicit and local-first while making common call
sites shorter:

  • use tags = [...] when several invalidation tags belong to one value;
  • use cacheable_infallible! when the loader cannot fail and Ok::<_, E>(...)
    would be only ceremony;
  • keep cacheable! for fallible async loaders, HTTP calls, filesystem reads,
    repository calls, or other application work.

Compatibility

This release is additive. Existing cacheable!, HydraCache, TypedCache,
DbCache, QueryCachePolicy, HydraCacheEntity, SQLx helper, and explicit
runtime APIs remain available.

Validation

Release validation passed:

  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --locked
  • cargo test --workspace --all-targets --locked
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • cargo test --doc --workspace --locked
  • cargo doc --workspace --no-deps --locked
  • cargo run -p hydracache --example cacheable_function --locked
  • cargo llvm-cov --workspace --all-targets --locked --summary-only
  • cargo package --workspace --allow-dirty --locked

Coverage summary in this local run:

  • functions: 97.65%
  • visible source lines: 99.45%
  • regions: 97.60%

The 0.15.0 macro/runtime code is covered by parser unit tests, async runtime
tests, and trybuild compile-pass coverage. The lower workspace-level coverage
summary came from the Docker-backed SQLx integration path being skipped in this
local environment.

v0.14.0

08 Jun 22:34

Choose a tag to compare

HydraCache 0.14.0 Release Notes

HydraCache 0.14.0 adds cacheable!(...), the first local-cache macro for
ordinary expensive async work. This release keeps function caching separate from
database result caching: no SQLx, entity, collection, or repository concepts are
required.

What's New

  • Added cacheable!(...) in hydracache-macros.
  • Re-exported cacheable! from hydracache.
  • Required explicit cache, key, and load options.
  • Supported repeated tag options.
  • Supported one TTL option: ttl = Duration or ttl_secs = u64.
  • Kept the generated path on top of HydraCache::get_or_load, so existing
    single-flight, TTL, tag invalidation, codec, and stats behavior still applies.

Example

use hydracache::{cacheable, HydraCache};

# async fn example() -> hydracache::CacheResult<()> {
let cache = HydraCache::local().build();

let value = cacheable!(
    cache = cache,
    key = "expensive:42",
    tag = "expensive",
    ttl_secs = 60,
    load = || async { Ok::<_, std::io::Error>(42_u64) },
)
.await?;

assert_eq!(value, 42);
# Ok(())
# }

Why It Matters

cacheable! gives application code a shorter spelling for ordinary async
function/result caching while preserving the main HydraCache design rule: cache
keys, tags, TTLs, and loaders remain explicit and visible at the call site.

This is intentionally different from the database adapter API:

  • Use cacheable! for expensive calculations, HTTP calls, filesystem reads, or
    non-database service calls.
  • Use hydracache-db or hydracache-sqlx for database/repository result
    caching with entity and collection invalidation semantics.

Known Boundaries

  • cacheable! is explicit-only: callers provide the cache, key, tags, TTL, and
    loader.
  • It is not an attribute macro yet and does not rewrite function definitions.
  • It does not derive keys or tags from function arguments.
  • It does not replace HydraCache::get_or_load; it is a compact spelling on
    top of the same runtime path and bounds.

Compatibility

This release is additive. Existing HydraCache, TypedCache, DbCache,
QueryCachePolicy, HydraCacheEntity, SQLx helper, and explicit
get_or_load(...) APIs remain available.

Validation

Release validation passed:

  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --locked
  • cargo test --workspace --all-targets --locked
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • cargo test --doc --workspace --locked
  • cargo doc --workspace --no-deps --locked
  • cargo llvm-cov --workspace --all-targets --locked --summary-only
  • cargo package --workspace --allow-dirty --locked

Coverage summary:

  • functions: 100.00%
  • visible source lines: 100.00%
  • regions: 98.53%

v0.13.0

02 Jun 14:40

Choose a tag to compare

HydraCache 0.13.0 Release Notes

HydraCache 0.13.0 adds the first ergonomic macro on top of the
database-neutral QueryCachePolicy API.

Highlights

  • Added query_cache_policy!(...).
  • Re-exported query_cache_policy! from hydracache-db.
  • Re-exported query_cache_policy! from hydracache-sqlx.
  • Added compile-pass and compile-fail tests for policy macro usage.
  • Kept loaders explicit through load(...), fetch_with(...), and SQLx
    helper methods.

Example

use hydracache_db::query_cache_policy;

let user_id = 42_i64;
let policy = query_cache_policy!(
    name = "load-user",
    entity = User,
    id = user_id,
    tag = "tenant:7",
    ttl_secs = 60,
);

let user = queries
    .cached_with::<User>(policy)
    .load(|| repo.find_user(user_id))
    .await?;

Manual policies remain available:

let policy = QueryCachePolicy::named("load-user")
    .for_cache_entity::<User>(user_id)
    .tag("tenant:7")
    .ttl(Duration::from_secs(60));

Macro Options

  • entity = Type, id = expr: use CacheEntity metadata for key and tags.
  • key = expr: set an explicit logical key.
  • collection = expr: set a collection key and tag.
  • name = expr: set a diagnostic operation name.
  • tag = expr: add an invalidation tag; may be repeated.
  • collection_tag = expr: add an escaped collection tag; may be repeated.
  • ttl = expr: set an explicit Duration.
  • ttl_secs = expr: set TTL via Duration::from_secs(expr).

Exactly one key source is required: entity + id, key, or collection.

Compatibility

This release is additive. Existing QueryCachePolicy, DbQuery, and SQLx
helper APIs continue to work.

v0.12.0

02 Jun 13:17

Choose a tag to compare

HydraCache 0.12.0 Release Notes

HydraCache 0.12.0 adds reusable database query cache policies and a shorter
repository-style load API.

Highlights

  • Added QueryCachePolicy to hydracache-db.
  • Added DbCache::cached_with::<T>(policy).
  • Added DbQuery::with_policy(policy) and DbQuery::cache_policy().
  • Added DbQuery::collection(name).
  • Added DbQuery::load(...) as an alias for fetch_with(...).
  • Re-exported QueryCachePolicy from hydracache-sqlx.
  • Kept existing .cached().key().tag().ttl().fetch_with(...),
    for_entity(...), and SQLx helper APIs available.

Example

use std::time::Duration;

use hydracache_db::QueryCachePolicy;

let policy = QueryCachePolicy::named("load-user")
    .for_cache_entity::<User>(42)
    .ttl(Duration::from_secs(60));

let user = queries
    .cached_with::<User>(policy)
    .load(|| repo.find_user(42))
    .await?;

Why This Matters

QueryCachePolicy makes the reusable part of a cached database operation
explicit: diagnostic name, key, tags, and TTL. The loader remains caller-owned,
so applications can use SQLx, Diesel, SeaORM, raw clients, or repository
methods without forcing HydraCache to become a database abstraction layer.

Compatibility

This release is additive for public APIs. Existing query descriptors and SQLx
helper calls continue to work.

with_policy(...) replaces the current descriptor policy. It does not merge
old and new tags or TTLs.

Publish Order

Publish in dependency order:

  1. hydracache-core
  2. hydracache
  3. hydracache-macros
  4. hydracache-db
  5. hydracache-sqlx

Wait for crates.io index propagation between dependent crates.

v0.11.1

02 Jun 11:47

Choose a tag to compare

HydraCache 0.11.1 Release Notes

HydraCache 0.11.1 is a patch release for public documentation and crate
metadata. It does not change runtime behavior or public API semantics.

Highlights

  • Clarified that CacheEntity and HydraCacheEntity belong to the
    database-neutral hydracache-db layer.
  • Kept the hydracache-sqlx re-exports supported as adapter convenience for
    SQLx users.
  • Updated README examples to show the canonical import boundary.
  • Documented that publishing smoke tests may intentionally import through
    hydracache-sqlx when verifying adapter re-exports.

Canonical Entity Import

Use hydracache-db for database-neutral entity metadata:

use hydracache_db::{CacheEntity, HydraCacheEntity};
use hydracache_sqlx::DbCache;

#[derive(serde::Serialize, serde::Deserialize, HydraCacheEntity)]
#[hydracache(entity = "user", collection = "users", id = i64)]
struct User {
    id: i64,
    name: String,
}

# async fn example(queries: DbCache) -> hydracache_sqlx::Result<()> {
let user = queries
    .for_entity::<User>(42)
    .fetch_with(|| async {
        Ok::<_, std::io::Error>(User {
            id: 42,
            name: "Ada".to_owned(),
        })
    })
    .await?;

assert_eq!(user.id, 42);
assert_eq!(User::cache_key_for(&42), "user:42");
assert_eq!(User::collection_tag(), Some("users".to_owned()));
# Ok(())
# }

hydracache-sqlx::{CacheEntity, HydraCacheEntity} remains valid for users who
want to depend only on the SQLx adapter crate in application code.

Publish Order

Publish in dependency order:

  1. hydracache-core
  2. hydracache
  3. hydracache-macros
  4. hydracache-db
  5. hydracache-sqlx

Wait for crates.io index propagation between dependent crates.

v0.11.0

02 Jun 10:23

Choose a tag to compare

HydraCache 0.11.0 Release Notes

HydraCache 0.11.0 adds a derive macro for domain entity cache metadata.

Highlights

  • Added the publishable hydracache-macros crate.
  • Added #[derive(HydraCacheEntity)].
  • Re-exported HydraCacheEntity from hydracache-db.
  • Re-exported HydraCacheEntity from hydracache-sqlx.
  • Kept manual CacheEntity implementations fully supported.
  • Added compile-pass and compile-fail macro tests with trybuild.
  • Updated live documentation examples to use the derive path.

Example

use hydracache_sqlx::{CacheEntity, HydraCacheEntity};

#[derive(HydraCacheEntity)]
#[hydracache(entity = "user", collection = "users", id = i64)]
struct User;

assert_eq!(User::cache_key_for(&42), "user:42");
assert_eq!(User::entity_tag_for(&42), "user:42");
assert_eq!(User::collection_tag(), Some("users".to_owned()));

Semantics

  • entity is required and becomes CacheEntity::ENTITY.
  • id is required and becomes CacheEntity::Id.
  • collection is optional and becomes CacheEntity::COLLECTION.
  • Generated keys and tags still use the existing CacheKeyBuilder escaping
    rules through the CacheEntity default methods.
  • The macro does not change DbCache, DbQuery, SQLx execution, loaders,
    single-flight, TTLs, or invalidation semantics.

Backward Compatibility

Manual metadata remains valid:

impl CacheEntity for User {
    type Id = i64;

    const ENTITY: &'static str = "user";
    const COLLECTION: Option<&'static str> = Some("users");
}

The full-control descriptor API also remains available:

queries.cached::<User>().key("user:42").tag("user:42")

Publish Order

hydracache-macros is now a published crate. Publish in dependency order:

  1. hydracache-core
  2. hydracache
  3. hydracache-macros
  4. hydracache-db
  5. hydracache-sqlx

Wait for crates.io index propagation between dependent crates.

Validation

  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --locked
  • cargo test --workspace --all-targets --locked
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • cargo test --doc --workspace --locked
  • cargo doc --workspace --no-deps --locked
  • cargo llvm-cov --workspace --all-targets --locked --summary-only

The SQLx Postgres integration test uses testcontainers. It runs against a real
database when Docker is available and skips successfully when Docker is not
available.

Stable cargo-llvm-cov does not count the exported proc-macro entrypoint as a
normal covered function, even though trybuild executes the macro through
rustc. The macro parser, expansion logic, crate-path resolution, successful
compile cases, and compile-fail diagnostics are covered by unit tests and
trybuild; the remaining function-summary gap is the thin
proc_macro::TokenStream wrapper.

v0.10.0

02 Jun 10:19

Choose a tag to compare

HydraCache 0.10.0 Release Notes

HydraCache 0.10.0 adds domain entity cache metadata for database result
caching.

Highlights

  • Added CacheEntity in hydracache-db.
  • Re-exported CacheEntity from hydracache-sqlx.
  • Added DbCache::for_entity<T: CacheEntity>(id).
  • Added DbQuery::for_cache_entity(id) for existing descriptor chains.
  • Automatically adds the entity tag and optional collection tag from metadata.
  • Kept entity(kind, id), collection(name), and the explicit
    .cached().key().tag() API unchanged.
  • Extended unit tests and real Postgres SQLx integration tests for the new
    metadata-driven path.

Example

use hydracache::HydraCache;
use hydracache_sqlx::{CacheEntity, DbCache};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
    id: i64,
    name: String,
}

impl CacheEntity for User {
    type Id = i64;

    const ENTITY: &'static str = "user";
    const COLLECTION: Option<&'static str> = Some("users");
}

# async fn example() -> hydracache_sqlx::Result<()> {
let queries = DbCache::new(HydraCache::local().build(), "db");

let user = queries
    .for_entity::<User>(42)
    .fetch_with(|| async {
        Ok::<_, std::io::Error>(User {
            id: 42,
            name: "Ada".to_owned(),
        })
    })
    .await?;

assert_eq!(user.id, 42);
# Ok(())
# }

Semantics

  • CacheEntity::ENTITY provides the entity key/tag prefix.
  • CacheEntity::COLLECTION optionally provides a broader invalidation tag.
  • DbCache::for_entity::<User>(42) creates key user:42, tag user:42, and
    tag users for the example above.
  • DbQuery::for_cache_entity(42) applies the same metadata to an existing
    descriptor and preserves existing tags.
  • All generated segments use CacheKeyBuilder escaping.

Backward Compatibility

The 0.9.0 helpers remain available:

queries.entity::<User>("user", 42).collection_tag("users")

The full-control API also remains available:

queries.cached::<User>().key("user:42").tag("user:42")

CacheEntity is an additive metadata layer and the intended future expansion
target for a derive macro.

Validation

  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --locked
  • cargo test --workspace --all-targets --locked
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • cargo test --doc --workspace --locked
  • cargo doc --workspace --no-deps --locked
  • cargo llvm-cov --workspace --all-targets --locked --summary-only
  • cargo package -p hydracache-core --locked --allow-dirty

The SQLx Postgres integration test uses testcontainers. It runs against a real
database when Docker is available and skips successfully when Docker is not
available.

Dependent package verification must be run in publish order after
hydracache-core 0.10.0 and then hydracache 0.10.0 are available on
crates.io.

v0.9.0

01 Jun 14:17

Choose a tag to compare

HydraCache 0.9.0 Release Notes

HydraCache 0.9.0 adds database query ergonomics on top of the explicit
hydracache-db and hydracache-sqlx APIs.

Highlights

  • Added DbCache::entity for entity-shaped query results.
  • Added DbCache::collection for collection-shaped query results.
  • Added DbQuery::for_entity to set an entity key and entity tag on an existing
    query descriptor.
  • Added DbQuery::collection_tag to associate entity results with broader
    invalidation groups.
  • Kept cached().key().tag().ttl().fetch_with(...) as the full-control API.
  • Extended the real Postgres testcontainers integration test to verify the new
    helpers with SqlxQueryExt.

Example

use hydracache::HydraCache;
use hydracache_sqlx::{DbCache, SqlxQueryExt};

# async fn example(pool: sqlx::PgPool) -> hydracache_sqlx::Result<()> {
let cache = HydraCache::local().build();
let queries = DbCache::new(cache, "db");

let (id, name): (i64, String) = queries
    .entity::<(i64, String)>("user", 42)
    .collection_tag("users")
    .fetch_one(
        pool.clone(),
        sqlx::query_as("select id, name from users where id = $1").bind(42_i64),
    )
    .await?;

assert_eq!(id, 42);
assert!(!name.is_empty());
# Ok(())
# }

Semantics

  • entity::<T>("user", 42) creates logical key user:42 and tag user:42.
  • collection::<T>("users") creates logical key users and tag users.
  • collection_tag("users") adds a tag without changing the current key.
  • Generated entity and collection segments use CacheKeyBuilder escaping, so
    : and % inside one segment cannot accidentally create extra segments.
  • Explicit .key(...) can still override a generated key when needed.

Backward Compatibility

The existing .cached::<T>().key(...).tag(...).ttl(...).fetch_with(...) API is
unchanged and remains the full-control layer. The new helpers are ergonomic
shortcuts over the same descriptor model, not a replacement.

Validation

  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --locked
  • cargo test --workspace --all-targets --locked
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • cargo test --doc --workspace --locked
  • cargo doc --workspace --no-deps --locked
  • cargo package -p hydracache-core --locked --allow-dirty

The SQLx Postgres integration test uses testcontainers. It runs against a real
database when Docker is available and skips successfully when Docker is not
available.

Dependent package verification must be run in publish order after
hydracache-core 0.9.0 and then hydracache 0.9.0 are available on crates.io.

v0.8.0

01 Jun 12:59

Choose a tag to compare

HydraCache 0.8.0 Release Notes

HydraCache 0.8.0 makes the SQLx adapter more ergonomic while keeping the
database-neutral cache boundary intact.

Highlights

  • Added SqlxQueryExt in hydracache-sqlx.
  • Added fetch_one, fetch_optional, and fetch_all helpers for common
    pool-backed SQLx reads.
  • Kept fetch_with as the escape hatch for SQLx macros, transactions,
    repository methods, and custom database flows.
  • Added DbQuery::fetch_value_with for adapter crates that need to cache
    shapes such as Option<T> and Vec<T>.
  • Extended the real Postgres testcontainers integration test to cover helper
    cache hits, optional results, list results, tag invalidation, and failed SQL
    not being cached.

Example

use hydracache::HydraCache;
use hydracache_sqlx::{DbCache, SqlxQueryExt};

# async fn example(pool: sqlx::PgPool) -> hydracache_sqlx::Result<()> {
let cache = HydraCache::local().build();
let queries = DbCache::new(cache, "db");

let (id, name): (i64, String) = queries
    .cached::<(i64, String)>()
    .key("user:42")
    .tag("user:42")
    .fetch_one(
        pool.clone(),
        sqlx::query_as("select id, name from users where id = $1").bind(42_i64),
    )
    .await?;

assert_eq!(id, 42);
assert!(!name.is_empty());
# Ok(())
# }

Semantics

  • Cache hits do not execute SQLx.
  • Cache misses execute SQLx once per key under local single-flight.
  • fetch_optional caches None.
  • fetch_all caches empty vectors.
  • SQLx failures are returned and are not stored.

Validation

  • cargo fmt --all
  • cargo check --workspace --all-targets
  • cargo test --workspace --all-targets
  • cargo clippy --workspace --all-targets --locked -- -D warnings
  • cargo test --doc --workspace --locked
  • cargo doc --workspace --no-deps --locked
  • cargo package -p hydracache-core --locked --allow-dirty

Dependent package verification must be run in publish order after
hydracache-core 0.8.0 is available on crates.io.

v0.7.0

01 Jun 12:59

Choose a tag to compare

HydraCache 0.7.0

Summary

0.7.0 introduces hydracache-db, the database-neutral result-cache adapter
crate, and hydracache-sqlx, the first SQLx-facing integration crate.

The release starts the SQLx direction carefully: SQLx remains responsible for
database execution, compile-time macros, pools, transactions, and row mapping.
HydraCache owns the cache boundary around that execution: explicit keys, tags,
TTL, local single-flight, serialization, and invalidation.

Added

  • hydracache-db workspace crate.
  • hydracache-sqlx workspace crate over hydracache-db.
  • DbCache namespaced adapter over an existing HydraCache.
  • DbQuery<T> query result-cache descriptor.
  • DbCache::cached::<T>() for cache descriptors without duplicate SQL text.
  • DbCache::named::<T>("name") for optional diagnostic labels.
  • SqlxCache and SqlxQuery compatibility aliases from hydracache-sqlx.
  • Explicit query keys via key and key_builder.
  • Query tags via tag, tags, and tag_set.
  • Per-query TTL via ttl.
  • fetch_with for cache lookup plus caller-owned SQLx loading.
  • DbCacheError::MissingKey for descriptors without explicit keys.
  • Post-publish verification coverage for the SQLx adapter crate.
  • Postgres testcontainers integration coverage for hydracache-sqlx.

Example

use hydracache::HydraCache;
use hydracache_db::DbCache;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
    id: i64,
    name: String,
}

# async fn example() -> hydracache_sqlx::Result<()> {
let local = HydraCache::local().build();
let queries = DbCache::new(local, "db");

let user = queries
    .cached::<User>()
    .key("user:42")
    .tag("user:42")
    .fetch_with(|| async {
        Ok::<_, std::io::Error>(User {
            id: 42,
            name: "Ada".to_owned(),
        })
    })
    .await?;
# Ok(())
# }

Why fetch_with First

The adapter does not hide SQLx behind a second query API yet. That is deliberate:
the first version should be easy to reason about, easy to test, and hard to
misuse.

Applications can keep using sqlx::query!, sqlx::query_as!, transactions,
pools, and their own connection lifetimes. HydraCache only wraps the result with
cache behavior.

cached::<T>() is the preferred entry point because it avoids duplicating SQL
text in the cache descriptor and in the SQLx loader. named::<T>("load-user")
can be used when diagnostics need a human-readable operation name. query_as
is kept as a compatibility/diagnostic alias when the SQL text itself is a useful
label.

Verification

  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --locked
  • cargo test --workspace --locked
  • cargo test -p hydracache-sqlx --test postgres_testcontainers --locked -- --nocapture
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked
  • cargo package -p hydracache-core --locked --allow-dirty

The Postgres integration test uses testcontainers. It runs against a real
Postgres container when Docker is available and skips cleanly when Docker is not
available in the environment.

hydracache, hydracache-db, and hydracache-sqlx package verification
requires hydracache-core 0.7.0 and then hydracache 0.7.0 to be visible in
the crates.io index first.

MSRV Dependency Note

hydracache-sqlx adds SQLx and testcontainers integration coverage, which
pulls modern async/database ecosystem dependencies. HydraCache now declares Rust
1.88 as MSRV for 0.7.0, allowing the lockfile to use current ICU/IDNA and
testcontainers dependency graphs instead of pinning older transitive versions.

The earlier Rust 1.85 pinning decision is tracked historically in
TD-0001.