An open API service indexing awesome lists of open source software.

https://github.com/cachekit-io/cachekit-rs

Production-ready Rust caching SDK for CacheKit protocol
https://github.com/cachekit-io/cachekit-rs

Last synced: about 1 month ago
JSON representation

Production-ready Rust caching SDK for CacheKit protocol

Awesome Lists containing this project

README

          

# cachekit-rs

**Production-ready caching for Rust — dual-layer L1/L2, zero-knowledge encryption, multi-backend.**

[![Crates.io](https://img.shields.io/crates/v/cachekit-rs.svg)](https://crates.io/crates/cachekit-rs)
[![docs.rs](https://docs.rs/cachekit-rs/badge.svg)](https://docs.rs/cachekit-rs)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![MSRV](https://img.shields.io/badge/MSRV-1.85-blue.svg)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html)

[Features](#features) · [Quick Start](#quick-start) · [Encryption](#zero-knowledge-encryption) · [Backends](#backends) · [Architecture](#architecture)

---

## Overview

`cachekit-rs` is the Rust SDK for [cachekit.io](https://cachekit.io). Plug in a backend, get dual-layer caching with optional client-side encryption. Bytes never leave your process unencrypted unless you say so.

| Component | What it does |
|:----------|:-------------|
| **CacheKit** | `get` / `set` / `delete` / `exists` with automatic L1 → L2 layering |
| **SecureCache** | Transparent AES-256-GCM encryption before storage (zero-knowledge) |
| **Backend** | Pluggable trait — cachekit.io SaaS, Redis, Cloudflare Workers |
| **L1 Cache** | In-process [moka](https://crates.io/crates/moka) cache with write-through + backfill |

> [!TIP]
> For the Python SDK with decorators, see [`cachekit`](https://github.com/cachekit-io/cachekit).
> For the low-level compression/encryption primitives, see [`cachekit-core`](https://crates.io/crates/cachekit-core).

---

## Features

| Feature | Default | Description |
|:--------|:-------:|:------------|
| `cachekitio` | ✅ | HTTP backend for [api.cachekit.io](https://api.cachekit.io) via [reqwest](https://crates.io/crates/reqwest) + rustls |
| `encryption` | ✅ | Zero-knowledge AES-256-GCM via [cachekit-core](https://crates.io/crates/cachekit-core) |
| `l1` | ✅ | In-process L1 cache via [moka](https://crates.io/crates/moka) |
| `redis` | ❌ | Redis backend via [fred](https://crates.io/crates/fred) (native only) |
| `workers` | ❌ | Cloudflare Workers backend via [worker](https://crates.io/crates/worker) |
| `macros` | ❌ | `#[cachekit]` proc-macro decorator |

```toml
# Defaults: SaaS + encryption + L1
[dependencies]
cachekit-rs = "0.2"

# With Redis backend
[dependencies]
cachekit-rs = { version = "0.2", features = ["redis"] }

# For Cloudflare Workers (no L1, no Redis)
[dependencies]
cachekit-rs = { version = "0.2", default-features = false, features = ["workers", "encryption"] }
```

> [!WARNING]
> **Mutually exclusive features:**
> - `workers` + `redis` — Workers runtime cannot use fred
> - `workers` + `l1` — moka requires std threads unavailable in wasm32

---

## Quick Start

### From Environment Variables

```rust
use cachekit::prelude::*;

#[tokio::main]
async fn main() -> Result<(), CachekitError> {
let cache = CacheKit::from_env()?.build()?;

cache.set("greeting", &"Hello, world!").await?;
let val: String = cache.get("greeting").await?.unwrap();
println!("{val}");

Ok(())
}
```

### Builder API

```rust
use std::sync::Arc;
use std::time::Duration;
use cachekit::prelude::*;
use cachekit::backend::cachekitio::CachekitIO;

let backend = CachekitIO::builder()
.api_key("ck_live_...")
.build()?;

let cache = CacheKit::builder()
.backend(Arc::new(backend))
.default_ttl(Duration::from_secs(600))
.namespace("myapp")
.l1_capacity(5000)
.build()?;
```

> [!IMPORTANT]
> Never hardcode API keys or master keys. Use environment variables or a secrets manager.

---

## Zero-Knowledge Encryption

Call `.secure()` to get an encrypted cache handle. All values are encrypted client-side with AES-256-GCM before hitting any backend. The backend only ever sees ciphertext.

```rust
let cache = CacheKit::from_env()?.build()?;
let secure = cache.secure()?;

// Encrypt → store (backend sees only ciphertext)
secure.set("user:42:ssn", &"123-45-6789").await?;

// Retrieve → decrypt (transparent to caller)
let ssn: String = secure.get("user:42:ssn").await?.unwrap();
```

```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Your Code │────>│ SecureCache │────>│ Backend │
│ │ │ AES-256-GCM │ │ (cachekit.io│
│ plaintext │ │ encrypt / │ │ or Redis) │
│ │<────│ decrypt │<────│ │
└──────────────┘ └──────────────┘ └──────────────┘
L1 stores ciphertext
(zero-knowledge preserved)
```

Security Properties

| Property | Implementation |
|:---------|:---------------|
| **Encryption** | AES-256-GCM (AEAD) via [cachekit-core](https://crates.io/crates/cachekit-core) (`ring` on native, `aes-gcm` on wasm32) |
| **Key Derivation** | HKDF-SHA256 — per-tenant cryptographic isolation |
| **AAD Binding** | Cache key bound to ciphertext (prevents substitution attacks) |
| **Memory Safety** | [zeroize](https://crates.io/crates/zeroize) on drop for all key material |
| **L1 Guarantee** | L1 stores ciphertext, never plaintext |

**AAD v0x03 wire format:**

```text
[version(0x03)][len(4)][tenant_id][len(4)][cache_key][len(4)][format][len(4)][compressed]
```

Each field is length-prefixed with a 4-byte big-endian u32 to prevent boundary-confusion attacks.
Cross-SDK compatible — ciphertext produced by the Python SDK decrypts with the Rust SDK and vice versa.

---

## Backends

### cachekit.io SaaS (default)

HTTP backend targeting [api.cachekit.io](https://api.cachekit.io) with session tracking, L1 metrics headers, SSRF-safe URL validation, distributed locking, and TTL inspection.

```rust
use cachekit::backend::cachekitio::CachekitIO;

let backend = CachekitIO::builder()
.api_key("ck_live_...")
.api_url("https://api.cachekit.io") // optional, this is the default
.build()?;
```

### Redis

Native Redis via [fred](https://crates.io/crates/fred) with cluster support. Requires the `redis` feature flag.

```toml
cachekit-rs = { version = "0.2", features = ["redis"] }
```

```rust
use cachekit::backend::redis::RedisBackend;

let backend = RedisBackend::builder()
.url("redis://localhost:6379")
.build()?;
backend.connect().await?; // explicit connect required
```

### Cloudflare Workers

`wasm32-unknown-unknown` backend using `worker::Fetch`. Requires the `workers` feature with default features disabled.

```toml
cachekit-rs = { version = "0.2", default-features = false, features = ["workers", "encryption"] }
```

Custom Backend

Implement the `Backend` trait to plug in any storage:

```rust
use async_trait::async_trait;
use cachekit::backend::{Backend, HealthStatus};
use cachekit::error::BackendError;
use std::time::Duration;

struct MyBackend;

#[async_trait]
impl Backend for MyBackend {
async fn get(&self, key: &str) -> Result>, BackendError> { todo!() }
async fn set(&self, key: &str, value: Vec, ttl: Option) -> Result<(), BackendError> { todo!() }
async fn delete(&self, key: &str) -> Result { todo!() }
async fn exists(&self, key: &str) -> Result { todo!() }
async fn health(&self) -> Result { todo!() }
}
```

Optional extension traits: `TtlInspectable` (TTL queries), `LockableBackend` (distributed locking).

---

## Dual-Layer Caching

When the `l1` feature is enabled (default), CacheKit maintains an in-process [moka](https://crates.io/crates/moka) cache in front of the backend:

```
┌─────────────────────────────────────────────────────────┐
│ CacheKit Client │
├─────────────────────────────────────────────────────────┤
│ │
│ GET path: │
│ L1 hit (~50ns) ──► return immediately │
│ L1 miss ──► L2 backend ──► backfill L1 (30s cap) │
│ │
│ SET path: │
│ write to L2 backend ──► write-through to L1 │
│ │
│ DELETE path: │
│ invalidate L1 first ──► delete from L2 backend │
│ │
├─────────────┬───────────────────────────────────────────┤
│ L1 (moka) │ L2 (cachekit.io / Redis / Workers) │
│ ~50ns │ ~2–50ms │
└─────────────┴───────────────────────────────────────────┘
```

| Behavior | Detail |
|:---------|:-------|
| **Write-through** | `set()` writes to L2 first, then L1 |
| **Backfill on miss** | L2 hits populate L1 with a capped 30s TTL |
| **Invalidate-first** | `delete()` evicts L1 before touching L2 |
| **Encrypted L1** | `SecureCache` stores ciphertext in L1 (never plaintext) |
| **Default capacity** | 1,000 entries (configurable via `.l1_capacity()`) |

---

## Environment Variables

| Variable | Required | Description |
|:---------|:--------:|:------------|
| `CACHEKIT_API_KEY` | ✅ | API key for cachekit.io |
| `CACHEKIT_API_URL` | ❌ | Override API endpoint (default: `https://api.cachekit.io`) |
| `CACHEKIT_MASTER_KEY` | ❌ | Hex-encoded master key (min 32 bytes) for encryption |
| `CACHEKIT_DEFAULT_TTL` | ❌ | Default TTL in seconds (min 1, default: 300) |

> [!CAUTION]
> `CACHEKIT_API_URL` must use HTTPS and must not point to a private IP address.
> Both constraints are enforced at configuration time.

---

## Architecture

```
cachekit-rs/
├── crates/
│ ├── cachekit/ # Main SDK crate
│ │ └── src/
│ │ ├── lib.rs # Public API + prelude
│ │ ├── client.rs # CacheKit, SecureCache, CacheKitBuilder
│ │ ├── config.rs # CachekitConfig + from_env()
│ │ ├── encryption.rs # AES-256-GCM + AAD v0x03
│ │ ├── error.rs # CachekitError, BackendError
│ │ ├── key.rs # Blake2b-256 cache key generation
│ │ ├── metrics.rs # L1 hit-rate metrics headers
│ │ ├── session.rs # SDK session tracking
│ │ ├── url_validator.rs # SSRF-safe URL validation
│ │ ├── serializer/ # MessagePack serialization
│ │ ├── l1/ # moka-based L1 cache (feature = "l1")
│ │ └── backend/
│ │ ├── mod.rs # Backend + TtlInspectable + LockableBackend traits
│ │ ├── cachekitio.rs # cachekit.io HTTP backend
│ │ ├── cachekitio_lock.rs # Distributed locking
│ │ ├── cachekitio_ttl.rs # TTL inspection
│ │ ├── redis.rs # Redis backend (feature = "redis")
│ │ └── workers.rs # Workers backend (feature = "workers")
│ │
│ └── cachekit-macros/ # Proc-macro crate
│ └── src/lib.rs # #[cachekit] decorator

├── Cargo.toml # Workspace root
└── Makefile # Development commands
```

---

## Development

```bash
make quick-check # fmt + clippy + test (run before every commit)
make test # cargo test --all-features
make build # cargo build --release
make build-wasm # wasm32-unknown-unknown (workers feature)
```

## Minimum Supported Rust Version

**Rust 1.85** or later (Edition 2021).

## License

MIT — see [LICENSE](LICENSE) for details.

---

**[Documentation](https://docs.rs/cachekit-rs)** · **[cachekit.io](https://cachekit.io)** · **[GitHub](https://github.com/cachekit-io/cachekit-rs)**