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.
§Encryption
A client built with encryption configured (by any of the builder’s
encryption methods, such as
encryption_from_env, or the
secure / secure_from_env preset) encrypts every
value it writes and decrypts every value it reads — Self::get,
Self::interop_get, Self::interop_get_swr, Self::set and
Self::set_with_ttl — and L1 holds ciphertext. Such a client has no
plaintext value path, and the SecureCache handle from secure_cache()
reads and writes the same entries. Self::delete and Self::exists
carry no value and are the same either way.
Implementations§
Source§impl CacheKit
impl CacheKit
Sourcepub fn builder() -> CacheKitBuilder
pub fn builder() -> CacheKitBuilder
Create a new builder.
Sourcepub fn from_env() -> Result<CacheKitBuilder, CachekitError>
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.
CACHEKIT_MASTER_KEY does not make the client encrypt: a key in the
environment is a key source, never a switch (protocol
spec/intent-presets.md § Encryption Activation, rule 2). State the
intent instead — encrypt with the environment’s keys, or don’t:
let encrypted = cachekit::CacheKit::from_env()?
.encryption_from_env()?
.build()?;
let plaintext = cachekit::CacheKit::from_env()?.no_encryption().build()?;Releases up to 0.11 encrypted whenever the variable was set. So that
such a deployment cannot silently start writing plaintext, a builder
from from_env() that saw CACHEKIT_MASTER_KEY and states neither
intent fails at CacheKitBuilder::build with
CachekitError::Config. This check is transitional and goes away
in a later release; without the variable, from_env()?.build()?
builds a plaintext client.
A set key is validated here: one that is empty, not hex, shorter than
32 bytes or not UTF-8 is a CachekitError::Config.
The default TTL is 300 s, and no environment variable changes it. Chain
CacheKitBuilder::default_ttl on the returned builder, or pass a TTL
per call with CacheKit::set_with_ttl.
Sourcepub async fn get<T: DeserializeOwned>(
&self,
key: &str,
) -> Result<Option<T>, CachekitError>
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.
§Errors
On a client with encryption configured, returns
CachekitError::Encryption — not a miss — if the stored entry fails
decryption. The key’s L1 copy is dropped first and the backend entry
is left in place, so the next read reaches the backend: a hit once the
entry is replaced with ciphertext this client can decrypt, a miss once
it expires or is deleted. A decrypted entry that does not decode as T
is CachekitError::Serialization naming only T, never quoting the
value.
Sourcepub async fn interop_get<T: DeserializeOwned>(
&self,
key: &str,
) -> Result<Option<T>, CachekitError>
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. Both consume exactly one MessagePack
document and reject trailing bytes (interop/v1 spec MUST); this one
also names a Python-SDK-internal CK frame with a specific diagnostic
instead of a generic trailing-bytes error.
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. On a client with
encryption configured the stored bytes are AES-GCM ciphertext of that
MessagePack, decrypted here before decoding: in interop mode the
AES-GCM plaintext is the plain MessagePack value bytes, so the AAD
(v0x03, format="msgpack", compressed="False") verifies cross-SDK
unchanged.
Unlike this read, Self::set / Self::set_with_ttl do not check
the client namespace: on a client with CacheKitBuilder::namespace
set they return Ok and store the entry under
{namespace}:{interop_key}, which no interop reader addresses. Interop
writes need a client without a namespace.
§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. With encryption
configured, an entry that fails decryption is
CachekitError::Encryption, with the key’s L1 copy dropped as for
Self::get.
Sourcepub async fn interop_get_swr<T: DeserializeOwned>(
&self,
key: &str,
) -> Result<SwrRead<T>, CachekitError>
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 withinswr_threshold_ratioof 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. The#[cachekit]macro dedups it on the cold-missSelf::single_flightlocks without waiting for another worker’s fill: a refresh that finds either lock held, or whose lock call fails, stands down and the stale value keeps being served. The accompanyingSwrTokenmakes 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.
On a client with encryption configured, staleness is judged on the L1
ciphertext entry (freshness metadata never exposes plaintext),
then the value is decrypted as for Self::interop_get.
§Errors
Same as Self::interop_get (including the namespaced-client
rejection).
Sourcepub async fn set<T: Serialize>(
&self,
key: &str,
value: &T,
) -> Result<(), CachekitError>
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.
Encrypts first on a client with encryption configured — see
Self::set_with_ttl.
Sourcepub async fn set_with_ttl<T: Serialize>(
&self,
key: &str,
value: &T,
ttl: Duration,
) -> Result<(), CachekitError>
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.
On a client with encryption configured the value is encrypted first: the backend and L1 receive only the AES-256-GCM ciphertext, and the payload limit applies to the ciphertext.
A positive sub-second ttl is sent to the backend as 1 second.
Returns CachekitError::Config if ttl is zero.
The client namespace is not checked here: an interop key written on a
namespaced client lands under {namespace}:{interop_key}, which no
interop reader addresses — see Self::interop_get.
Sourcepub async fn delete(&self, key: &str) -> Result<bool, CachekitError>
pub async fn delete(&self, key: &str) -> Result<bool, CachekitError>
Delete key and return true if it existed.
CachekitIO and WorkersCachekitIO return true on every successful
delete, whether or not the key existed: the server does not report
existence on DELETE.
Invalidates the L1 entry regardless of the backend result.
Sourcepub async fn exists(&self, key: &str) -> Result<bool, CachekitError>
pub async fn exists(&self, key: &str) -> Result<bool, CachekitError>
Return true if key exists without fetching the value.
A live L1 entry answers without a backend call. That check does not count as a use of the entry for L1 eviction; only reads do.
Sourcepub fn stats(&self) -> L1Stats
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);Sourcepub fn l1_entry_count(&self) -> Option<u64>
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.
Sourcepub fn circuit_state(&self) -> Option<CircuitState>
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.
Sourcepub async fn single_flight(&self, key: &str) -> SingleFlight
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.
Sourcepub fn secure_cache(&self) -> Result<SecureCache<'_>, CachekitError>
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.
The handle encrypts exactly what this client’s own value methods
already encrypt (see Encryption), and the two
read each other’s entries. What it adds is the guarantee: code that
must never run unencrypted asks for the handle, which fails on a
client without encryption, and it carries the
previous_key_hits rotation signal.
§Errors
Returns CachekitError::Config if no encryption layer is configured.
Configure encryption via CacheKitBuilder::encryption /
CacheKitBuilder::encryption_from_env /
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
impl CacheKit
Sourcepub async fn minimal(redis_url: &str) -> Result<CacheKitBuilder, CachekitError>
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()?;Sourcepub async fn production(
redis_url: &str,
) -> Result<CacheKitBuilder, CachekitError>
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()?;Sourcepub async fn secure(
redis_url: &str,
master_key_hex: &str,
) -> Result<CacheKitBuilder, CachekitError>
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, on every value read
and write, not only those through the
secure_cache()handle (see Encryption) - 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: exactly 32
decoded key bytes go to CacheKitBuilder::encryption_from_bytes,
which rejects any other length — including the 64 ASCII bytes of a
hex string, which would 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()?;
// Encrypted before it leaves the process; L1 holds the ciphertext.
cache.set("user:42:ssn", &"123-45-6789").await?;Sourcepub async fn secure_from_env(
redis_url: &str,
) -> Result<CacheKitBuilder, CachekitError>
pub async fn secure_from_env( redis_url: &str, ) -> Result<CacheKitBuilder, CachekitError>
Secure, keys from the environment — CacheKit::secure with the
hex key read from CACHEKIT_MASTER_KEY, plus decrypt-only rotation
keys from CACHEKIT_PREVIOUS_MASTER_KEYS.
Reads exactly two variables:
CACHEKIT_MASTER_KEY(required) — the current key; writes encrypt under it. Same hex decoding assecure, so the same value derives the same key bytes as every other CacheKit SDK. Use exactly 32 bytes (64 hex chars), the only length every SDK accepts.CACHEKIT_PREVIOUS_MASTER_KEYS(optional) — comma-separated hex keys, at most 3, tried in order when the current key fails to decrypt. Unset or blank means none. Validated exactly asCachekitConfig::from_envvalidates it; the current key must not appear in the list.
Otherwise the identical preset to secure, tenant
"default" for every key. Never falls back to plaintext.
§Errors
Returns CachekitError::Config — all before any Redis I/O — when
CACHEKIT_MASTER_KEY is unset, empty, not hex, or shorter than 32
bytes, or when CACHEKIT_PREVIOUS_MASTER_KEYS is set but not valid
UTF-8, or is non-blank with an empty or invalid entry, more than 3
entries, or a repeat of the current key.
Otherwise as secure.
§Example
let cache = cachekit::CacheKit::secure_from_env("redis://localhost:6379")
.await?
.build()?;
cache.set("user:42:ssn", &"123-45-6789").await?; // encrypted
// Reads served by each CACHEKIT_PREVIOUS_MASTER_KEYS entry, for rotation.
let drain = cache.secure_cache()?.previous_key_hits();Sourcepub fn io(api_key: &str) -> Result<CacheKitBuilder, CachekitError>
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(), which then encrypts every value read and write) - 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()?;Sourcepub fn io_from_env() -> Result<CacheKitBuilder, CachekitError>
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, never CACHEKIT_MASTER_KEY: like every
constructor but secure, it does not encrypt unless you chain an
encryption call such as
.encryption_from_env().
§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§
Auto Trait Implementations§
impl !RefUnwindSafe for CacheKit
impl !UnwindSafe for CacheKit
impl Freeze for CacheKit
impl Send for CacheKit
impl Sync for CacheKit
impl Unpin for CacheKit
impl UnsafeUnpin for CacheKit
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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