# iqdb-cache v1.0.0 — Stable
**The cache is done.** v1.0.0 commits the public API under SemVer for the 1.x series — no breaking changes until 2.0. Nothing new ships here; this release promotes the surface designed and hardened across the 0.x line to stable, with every Definition-of-Done criterion satisfied.
## What is iqdb-cache?
An in-process caching layer between the database and an index. It wraps any `iqdb_index::IndexCore` as a `CachedIndex` — itself an `IndexCore`, so it drops in anywhere the wrapped index does — and memoizes search results, turning a repeated query into a memory read. It is opt-in and transparent: a database is correct with no cache, and wrapping one never changes *what* a search returns, only how fast a repeat returns.
## The 1.0 surface
```rust
use iqdb_cache::CachedIndex;
use iqdb_index::IndexCore;
use iqdb_types::{DistanceMetric, SearchParams};
let cached = CachedIndex::new(iqdb_cache::doc_stub::stub_index());
let params = SearchParams::new(3, DistanceMetric::Cosine);
let cold = cached.search(&[1.0, 0.0, 0.0], ¶ms).expect("search"); // miss
let warm = cached.search(&[1.0, 0.0, 0.0], ¶ms).expect("search"); // hit
assert_eq!(cold, warm);
```
The committed public API (frozen until 2.0):
- **`CachedIndex<I>`** — the wrapper. Tier 1: `new`. Tier 2: `with_capacity`, `with_config`. Tier 3: it *is* an `IndexCore`. Plus `capacity`, `ttl`, `policy`, `is_enabled`, `get_ref`, `into_inner`, `clear_cache`, `cache_stats`.
- **`CacheConfig`** — the builder: `new`, `capacity`, `ttl`, `no_ttl`, `policy`, `Default`.
- **`EvictionPolicy`** — `Lru` (default), `Lfu`, `Fifo`, `Arc` (`#[non_exhaustive]`).
- **`CacheStats`** — `hits`, `misses`, `evictions`, `len`, `capacity`; `lookups`, `hit_rate`.
- **`VERSION`**.
## The feature set
- **Transparent memoization.** Identical searches (same query and `SearchParams`) are served from a bounded LRU-by-default cache; the wrapper forwards every other `IndexCore` call unchanged.
- **Mutation-exact invalidation.** `insert` / `insert_batch` / `delete` invalidate the cache, so a search after a write is never stale.
- **Optional TTL.** A per-entry expiry (via `clock-lib`) bounds staleness from changes the wrapper cannot see; off by default, and the clock is never read without it.
- **Four eviction policies** — LRU, LFU, FIFO, ARC — selectable through one config knob, all arena-backed with amortized `O(1)` operations and zero `unsafe`.
- **Stats.** Lifetime hit / miss / eviction counters and a `hit_rate`.
- **Off by default.** Capacity `0` is a pure passthrough; a database opts in by wrapping.
## Definition of Done
Every criterion in `dev/DIRECTIVES.md` §7 is met:
1. ✅ Compiles clean on Windows and Linux, on stable and the 1.87 MSRV (the CI matrix also covers macOS).
2. ✅ `fmt`, `clippy --all-targets --all-features -D warnings`, `test --all-features`, and `cargo doc -D warnings` are clean.
3. ✅ `cargo audit` and `cargo deny check` pass.
4. ✅ No `unwrap` / `expect` / `todo!` / `dbg!` in shipping code; `#![forbid(unsafe_code)]` — **zero unsafe**.
5. ✅ A Tier-1 API (`CachedIndex::new`) headlines the docs.
6. ✅ Property tests cover every core invariant (transparency, no-stale-after-mutation, capacity bound) under every policy; `loom` covers the concurrent search path.
7. ✅ Hot paths carry `criterion` benchmarks; the one regression found during the policy rework was fixed back to within ~5% of baseline.
8. ✅ Every public item is documented with a runnable example; `docs/API.md`, the README, and version metadata are current.
9. ✅ All changes are recorded in the changelog.
Plus the project-specific invariants (§8): the database is correct with the cache disabled (the default), and a stale result is never returned after a mutation — both property-tested.
## Performance
On the reference machine, a top-10 search over 10,000 vectors at dim 64 (`cargo bench`):
| `uncached_scan` (full scan) | ~234 µs | 1× |
| `cache_hit` (default LRU) | ~250 ns | ~935× |
| `Fifo` hit | ~250 ns | ~935× |
| `Lru` hit | ~278 ns | ~840× |
| `Arc` hit | ~387 ns | ~605× |
| `Lfu` hit | ~1.17 µs | ~200× |
A configured TTL adds ~29 ns (one monotonic clock read) to a hit; with no TTL the clock is never touched.
## Breaking changes
**None.** 1.0.0 is the 0.6.0 surface, now stable. From here, only additive, non-breaking changes until 2.0.
## Verification
```bash
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo build --examples
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
RUSTFLAGS="--cfg loom" cargo test --test loom_iqdb_cache
cargo audit
cargo deny check
```
MSRV: Rust 1.87.
## Installation
```toml
[dependencies]
iqdb-cache = "1.0"
```
## Documentation
- [README](https://github.com/jamesgober/iqdb-cache/blob/main/README.md)
- [API reference](https://github.com/jamesgober/iqdb-cache/blob/main/docs/API.md)
- [ROADMAP](https://github.com/jamesgober/iqdb-cache/blob/main/dev/ROADMAP.md)
- [Standards (REPS)](https://github.com/jamesgober/iqdb-cache/blob/main/REPS.md)
- [CHANGELOG](https://github.com/jamesgober/iqdb-cache/blob/main/CHANGELOG.md)
---
**Full diff:** [`v0.6.0...v1.0.0`](https://github.com/jamesgober/iqdb-cache/compare/v0.6.0...v1.0.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/iqdb-cache/blob/main/CHANGELOG.md).