Skip to main content

CacheKit

Struct CacheKit 

Source
pub struct CacheKit { /* private fields */ }
Expand description

Cache client with optional L1 in-process cache layer.

Clone is cheap and shares everything: backend, L1 cache, single-flight state, and encryption layer. Clones exist so 'static background work (e.g. the SWR refresh spawned by #[cachekit]) can hold the client without borrowing it.

Implementations§

Source§

impl CacheKit

Source

pub fn builder() -> CacheKitBuilder

Create a new builder.

Source

pub fn from_env() -> Result<CacheKitBuilder, CachekitError>

Build from environment variables via crate::config::CachekitConfig::from_env.

Creates a crate::backend::cachekitio::CachekitIO backend from the config. Requires the cachekitio feature.

Source

pub async fn get<T: DeserializeOwned>( &self, key: &str, ) -> Result<Option<T>, CachekitError>

Retrieve and deserialize a value stored under key.

Returns None if the key does not exist. Checks L1 cache before hitting the backend.

Source

pub async fn interop_get<T: DeserializeOwned>( &self, key: &str, ) -> Result<Option<T>, CachekitError>

Retrieve and deserialize an interop-mode value stored under key.

Identical to Self::get except the payload is decoded with crate::interop::deserialize, which consumes exactly one MessagePack document and rejects trailing bytes (interop/v1 spec MUST). A Python-SDK-internal CK frame is rejected with a specific diagnostic instead of silently decoding as the integer 67.

Use with keys from crate::interop::interop_key on a client without a namespace prefix. There is no interop-specific write method: Self::set already writes plain MessagePack (no ByteStorage envelope), which is the interop value format.

§Errors

Returns CachekitError::Config if the client was built with CacheKitBuilder::namespace: the prefix would rewrite the storage key to {prefix}:{interop_key}, which no other SDK computes — every cross-SDK entry would silently miss. Interop keys carry their own namespace segment; failing loudly here beats a 100% miss rate that looks like a cold cache.

Source

pub async fn interop_get_swr<T: DeserializeOwned>( &self, key: &str, ) -> Result<SwrRead<T>, CachekitError>

Retrieve and deserialize an interop-mode value with SWR classification.

Identical to Self::interop_get except an L1 hit is classified against the client’s stale-while-revalidate freshness window:

  • SwrRead::Fresh — L1 hit within swr_threshold_ratio of the entry’s TTL (±10% jitter), or any L2 hit. Use directly.
  • SwrRead::Stale — L1 hit past the threshold but before hard expiry: the value is returned without touching the backend or origin, and the caller should schedule exactly one background refresh (dedup via Self::single_flight — this is what the #[cachekit] macro generates). The accompanying SwrToken makes completion conditional, so a newer set/delete always wins.
  • SwrRead::Miss — nothing usable anywhere: normal blocking miss.

A hard-expired L1 entry is a SwrRead::Miss, never Stale — moka drops entries at their TTL, so SWR cannot serve past hard expiry.

With SWR disabled (CacheKitBuilder::swr_enabled(false)), without the l1 feature, on wasm32, or under unsync, this behaves exactly like Self::interop_get: hits are Fresh, Stale is never produced.

§Errors

Same as Self::interop_get (including the namespaced-client rejection).

Source

pub async fn set<T: Serialize>( &self, key: &str, value: &T, ) -> Result<(), CachekitError>

Serialize and store value under key using the client’s default TTL.

Source

pub async fn set_with_ttl<T: Serialize>( &self, key: &str, value: &T, ttl: Duration, ) -> Result<(), CachekitError>

Serialize and store value under key with an explicit ttl.

Returns CachekitError::Config if ttl is less than 1 second.

Source

pub async fn delete(&self, key: &str) -> Result<bool, CachekitError>

Delete key and return true if it existed.

Invalidates the L1 entry regardless of the backend result.

Source

pub async fn exists(&self, key: &str) -> Result<bool, CachekitError>

Return true if key exists without fetching the value.

Source

pub fn stats(&self) -> L1Stats

Live hit/miss counters for this client and all of its clones.

Every value read — get, interop_get, their SWR variants, and the SecureCache equivalents — is counted by where it was served from. exists and reads that fail with a backend error are not counted. The same counters feed the cachekit.io backends’ X-CacheKit-* telemetry headers, so for a backend used by this client alone, what you see here is what the SaaS sees (a backend shared by several clients reports the first client built over it — see Backend::attach_metrics).

let stats = cache.stats();
let reads = stats.l1_hits + stats.l2_hits + stats.misses;
eprintln!("L1 hit rate {:.1}% over {reads} reads", stats.l1_hit_rate() * 100.0);
Source

pub fn l1_entry_count(&self) -> Option<u64>

Number of entries currently held in L1, or None when L1 is disabled (or the l1 feature is off).

Exact at the time of the call: moka’s pending expirations and evictions are applied first, which makes this a housekeeping call, not a free counter read — poll it, don’t put it on a hot path.

Source

pub fn circuit_state(&self) -> Option<CircuitState>

Current circuit-breaker state, or None when this client has no breaker (reliability disabled, or configured with circuit_breaker: None).

Reads the live state: an open circuit whose open_timeout has elapsed reports CircuitState::HalfOpen. With the tracing feature every transition is also emitted on the cachekit::reliability target — warn when the circuit opens, info for half-open and closed.

Source

pub async fn single_flight(&self, key: &str) -> SingleFlight

Begin a cold-miss single-flight for key (see crate::flight).

Call after a cache miss, before computing the value. Concurrent in-process fills of the same key are collapsed to one; with the reliability feature and a lock-capable backend (CachekitIO, Redis), fills are also suppressed across processes via a distributed fill lock. The #[cachekit] macro does this automatically.

The key is namespaced like every cache operation but not validated — this call is infallible; an invalid key simply fails later at the actual cache operation.

Source

pub fn secure_cache(&self) -> Result<SecureCache<'_>, CachekitError>

Return a SecureCache handle that encrypts all values before storage.

L1 stores ciphertext (not plaintext) to preserve the zero-knowledge property across all cache layers.

§Errors

Returns CachekitError::Config if no encryption layer is configured. Configure encryption via CacheKitBuilder::encryption / CacheKitBuilder::encryption_from_bytes, or start from the CacheKit::secure(url, master_key_hex) / CacheKit::secure_from_env(url) preset (redis feature).

Source§

impl CacheKit

Source

pub async fn minimal(redis_url: &str) -> Result<CacheKitBuilder, CachekitError>

Minimal — speed-first Redis cache, no extras.

  • Backend: Redis (connects eagerly; fails fast — a dropped connection is not re-established)
  • L1 cache: on (1 000 entries, no SWR / invalidation) — an L1 hit is served as-is until it expires, so a read may return an entry up to its TTL (300 s by default) after another process changed it in Redis. Chain .no_l1() to read through every time.
  • Encryption: no
  • Reliability: off — no retry, no circuit breaker, no backpressure; every backend error propagates on first failure and backend concurrency is unbounded
  • Default TTL: 300 s

Good for: product catalogs, public data, development.

§Errors

Returns CachekitError if the URL is invalid or Redis is unreachable.

§Example
let cache = cachekit::CacheKit::minimal("redis://localhost:6379").await?
    .namespace("myapp")
    .build()?;
Source

pub async fn production( redis_url: &str, ) -> Result<CacheKitBuilder, CachekitError>

Production — reliability-first Redis cache with L1.

  • Backend: Redis (connects eagerly, failing fast if unreachable; auto-reconnects after a dropped connection with exponential backoff, 100 ms → 30 s, retrying indefinitely)
  • L1 cache: on (1 000 entries)
  • Encryption: no
  • Reliability: on — retry with backoff + jitter, circuit breaker, backpressure (max 100 concurrent backend ops)
  • Default TTL: 600 s

Good for: user sessions, API responses, production services.

§Errors

Returns CachekitError if the URL is invalid or Redis is unreachable.

§Example
let cache = cachekit::CacheKit::production("redis://localhost:6379").await?
    .namespace("api")
    .build()?;
Source

pub async fn secure( redis_url: &str, master_key_hex: &str, ) -> Result<CacheKitBuilder, CachekitError>

Secure — zero-knowledge encrypted Redis cache.

  • Backend: Redis (connects eagerly, failing fast if unreachable; auto-reconnects after a dropped connection with exponential backoff, 100 ms → 30 s, retrying indefinitely)
  • L1 cache: on (1 000 entries, stores ciphertext)
  • Encryption: AES-256-GCM with HKDF-SHA256
  • Reliability: on — retry with backoff + jitter, circuit breaker, backpressure (max 100 concurrent backend ops)
  • Default TTL: 600 s
  • Tenant ID: "default" for both key derivation and AAD (override via .encryption())

Good for: PII, payments, GDPR/HIPAA-sensitive data.

master_key_hex is the master key as a hex string — the same string CACHEKIT_MASTER_KEY holds, decoded to the same key bytes by every CacheKit SDK. This is the cross-SDK-portable key path. Use exactly 32 bytes (64 hex chars): shorter is rejected, and longer is accepted here but not by every SDK. Generate one with openssl rand -hex 32.

Use CacheKit::secure_from_env to read the key from CACHEKIT_MASTER_KEY. There is no raw-bytes preset: decoded key bytes go to CacheKitBuilder::encryption_from_bytes, and the ASCII bytes of a hex string must never go there — they derive a key no other SDK derives.

The key is validated before any Redis connection is attempted — a bad key is a deterministic local error, never masked by (or paying for) network I/O.

§Errors

Returns CachekitError::Config if master_key_hex is not hex or decodes to fewer than 32 bytes, or if the URL is invalid; another CachekitError if Redis is unreachable.

§Example
// 64 hex chars (32 bytes) from your secret store — `openssl rand -hex 32`.
let master_key_hex = std::env::var("APP_CACHE_MASTER_KEY")?;
let cache = cachekit::CacheKit::secure("redis://localhost:6379", &master_key_hex)
    .await?
    .build()?;
let secure = cache.secure_cache()?;
Source

pub async fn secure_from_env( redis_url: &str, ) -> Result<CacheKitBuilder, CachekitError>

Secure, master key from the environment — CacheKit::secure with the hex key read from CACHEKIT_MASTER_KEY.

Identical preset to secure, same hex decoding, so the same CACHEKIT_MASTER_KEY value derives the same key bytes as every other CacheKit SDK. Use exactly 32 bytes (64 hex chars), the only length every SDK accepts. Never falls back to plaintext.

§Errors

Returns CachekitError::Config when CACHEKIT_MASTER_KEY is unset, empty, not hex, or shorter than 32 bytes — all before any Redis I/O. Otherwise as secure.

§Example
let cache = cachekit::CacheKit::secure_from_env("redis://localhost:6379")
    .await?
    .build()?;
let secure = cache.secure_cache()?;
Source

pub fn io(api_key: &str) -> Result<CacheKitBuilder, CachekitError>

CachekitIO — managed SaaS cache, zero infrastructure.

  • Backend: cachekit.io HTTP API
  • L1 cache: on (1 000 entries)
  • Encryption: no (add via .encryption())
  • Reliability: on — retry with backoff + jitter, circuit breaker, backpressure (max 100 concurrent backend ops)
  • Default TTL: 3 600 s

Good for: serverless, edge compute, managed caching without Redis.

Use CacheKit::io_from_env to read the key from CACHEKIT_API_KEY.

§Errors

Returns CachekitError::Config if api_key is empty.

§Example
let api_key = std::env::var("CACHEKIT_API_KEY")?;
let cache = cachekit::CacheKit::io(&api_key)?
    .namespace("edge")
    .build()?;
Source

pub fn io_from_env() -> Result<CacheKitBuilder, CachekitError>

CachekitIO, API key from the environment — CacheKit::io with the key read from CACHEKIT_API_KEY.

Identical preset to io. Reads only CACHEKIT_API_KEY — unlike CacheKit::from_env, never CACHEKIT_MASTER_KEY, so no encryption is activated.

§Errors

Returns CachekitError::Config when CACHEKIT_API_KEY is unset or empty.

§Example
let cache = cachekit::CacheKit::io_from_env()?
    .namespace("edge")
    .build()?;

Trait Implementations§

Source§

impl Clone for CacheKit

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more