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.

§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

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.

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.

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.

§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.

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. 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.

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. The #[cachekit] macro dedups it on the cold-miss Self::single_flight locks 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 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.

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).

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.

Encrypts first on a client with encryption configured — see Self::set_with_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.

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.

Source

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.

Source

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.

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.

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

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, 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?;
Source

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 as secure, 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 as CachekitConfig::from_env validates 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();
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(), 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()?;
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, 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§

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