Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 38 additions & 27 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 49 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

## 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.
`cachekit-rs` is the Rust SDK for [cachekit.io](https://cachekit.io). Pick an [intent preset](#intent-presets-recommended) — `minimal`, `production`, `encrypted`, or `io` — and get a pre-configured cache in one call, from bare Redis speed to dual-layer with client-side encryption. Pick `encrypted` and bytes never leave your process in plaintext.

| Component | What it does |
|:----------|:-------------|
Expand Down Expand Up @@ -53,15 +53,15 @@
```toml
# Defaults: SaaS + encryption + L1
[dependencies]
cachekit-rs = "0.5"
cachekit-rs = "0.7"

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

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

> [!WARNING]
Expand All @@ -76,6 +76,47 @@ cachekit-rs = { version = "0.5", default-features = false, features = ["workers"

## Quick Start

### Intent Presets (recommended)

One call that names your use case. Each preset returns a pre-configured builder you can still override before `.build()`:

| Preset | When to use | Backend | L1 | Encryption | Reliability¹ | Auto-reconnect² | Default TTL |
|:-------|:------------|:--------|:--:|:----------:|:------------:|:---------------:|:-----------:|
| `CacheKit::minimal(url)` | Development, public data, product catalogs — speed first, no extras | Redis³ | ❌ | ❌ | ❌ | ❌ | 300 s |
| `CacheKit::production(url)` | User sessions, API responses, production services | Redis³ | ✅ | ❌ | ✅ | ✅ | 600 s |
| `CacheKit::encrypted(url, key)` | PII, payments, GDPR/HIPAA-sensitive data — zero-knowledge AES-256-GCM | Redis³ | ✅ | ✅ | ✅ | ✅ | 600 s |
| `CacheKit::io(api_key)` | Serverless, edge compute, managed caching without running Redis | cachekit.io | ✅ | ❌ | ✅ | n/a (HTTP) | 3 600 s |

¹ Retry with backoff + jitter, circuit breaker, backpressure — the [reliability stack](#reliability). Requires the default-on `reliability` feature.
² See the resilience contract below.
³ Requires the `redis` feature flag; `encrypted` also needs the default-on `encryption` feature.

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

#[tokio::main]
async fn main() -> Result<(), CachekitError> {
// Needs: cachekit-rs = { version = "0.7", features = ["redis"] }
let cache = CacheKit::production("redis://localhost:6379").await?
.namespace("api")
.build()?;

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

Ok(())
}
```

**Resilience contract** — connection failures, at construction and mid-run:

- `production` / `encrypted` **auto-reconnect**: a dropped connection is re-established with exponential backoff (100 ms → 30 s cap), retrying indefinitely.
- `minimal` is **fail-fast**: a dropped connection is not re-established — every subsequent operation errors until you rebuild the client.
- **Initial** connections fail fast for every Redis preset: a bad URL or unreachable Redis errors immediately at construction, never enters a retry loop. `io` opens no connection at construction: an empty API key fails at construction, while an invalid key or unreachable endpoint surfaces at the first request.
- `encrypted` validates the master key **before** any Redis connection is attempted — a bad key is a deterministic local error, never masked by (or paying for) network I/O.
- Auto-reconnect is connection-level repair, distinct from the per-operation [reliability stack](#reliability) (retry, circuit breaker, backpressure) that `production` / `encrypted` / `io` also enable. `minimal` has neither — every failure is yours to handle.

### From Environment Variables

```rust
Expand Down Expand Up @@ -228,7 +269,7 @@ let backend = CachekitIO::builder()
Native Redis via [fred](https://crates.io/crates/fred) with cluster support, TTL inspection, and distributed locking (`SET NX PX` acquire, atomic Lua compare-and-delete release, `<key>:lock` namespace shared with cachekit-py). Requires the `redis` feature flag.

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

```rust
Expand All @@ -249,7 +290,7 @@ Memcached via [rust-memcache](https://crates.io/crates/memcache) (single server,
TTLs above memcached's 30-day ceiling are clamped (larger values would be misread as absolute timestamps); values above the item-size limit (default 1 MiB) fail loudly client-side, and a server-side "object too large" classifies as permanent (never retried). Requires the `memcached` feature flag.

```toml
cachekit-rs = { version = "0.5", features = ["memcached"] }
cachekit-rs = { version = "0.7", features = ["memcached"] }
```

```rust
Expand All @@ -266,7 +307,7 @@ let backend = MemcachedBackend::builder()
Local disk cache, **byte-compatible with cachekit-py's File backend** — a py and an rs process pointed at the same directory read each other's entries (Blake2b-128 hashed filenames, shared 14-byte header, atomic write-then-rename, lazy expiry). Implements `TtlInspectable` (TTL read off the on-disk header, in-place refresh). Concurrency matches py: same-process operations serialize on a backend-wide lock (py's `RLock`); on unix, reads and in-place TTL rewrites take advisory `flock` while writes stay lock-free via atomic rename; and expired-entry unlinks are inode-validated so a stale read decision doesn't delete a concurrent writer's fresh entry. On unix the cache directory must be owned by you and not group/other-writable. Not yet ported from py: LRU eviction and size caps — the directory grows until entries expire or you clear it. Requires the `file` feature flag and a tokio runtime (I/O runs via `spawn_blocking`).

```toml
cachekit-rs = { version = "0.5", features = ["file"] }
cachekit-rs = { version = "0.7", features = ["file"] }
```

```rust
Expand All @@ -282,7 +323,7 @@ let backend = FileBackend::builder()
`wasm32-unknown-unknown` backend using `worker::Fetch`, with distributed locking and TTL inspection against the SaaS lock/TTL endpoints. Requires the `workers` feature with default features disabled.

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

<details>
Expand Down
8 changes: 8 additions & 0 deletions crates/cachekit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ homepage = "https://cachekit.io"
keywords = ["cache", "redis", "encryption", "moka", "cloudflare-workers"]
categories = ["caching", "web-programming", "cryptography"]

# docs.rs builds with default features only unless told otherwise; without this
# the Redis intent presets (minimal/production/encrypted) and the optional
# backends never appear in the rendered docs. `workers` stays off — mutually
# exclusive with redis/l1/reliability/memcached/file (the five compile_error
# guards in lib.rs).
[package.metadata.docs.rs]
features = ["cachekitio", "redis", "encryption", "l1", "reliability", "macros", "memcached", "file"]

[lib]
name = "cachekit"

Expand Down
Loading