Skip to content

Commit 68977e8

Browse files
committed
update README
1 parent 9b76554 commit 68977e8

File tree

1 file changed

+315
-2
lines changed

1 file changed

+315
-2
lines changed

README.md

Lines changed: 315 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,315 @@
1-
# cache
2-
HTTP cache engine for Vix. Pluggable cache stores, cache policies, and keying strategies for high-performance HTTP response caching.
1+
# Vix Cache
2+
3+
Offline-first HTTP cache • Deterministic policy • Memory + File stores • LRU eviction • Cache keys • Pruning
4+
5+
The Vix **cache** module provides a small, fast, predictable caching layer designed for **offline-first runtimes**.
6+
7+
It is built around a few explicit primitives:
8+
9+
* `Cache` — the main entry point
10+
* `CachePolicy` — deterministic caching rules (TTL, stale windows)
11+
* `CacheContext` — request context (online/offline/network-error)
12+
* Stores: `MemoryStore`, `FileStore`, `LruMemoryStore`
13+
* `CacheKey` — stable key builder (normalizes query + optional vary headers)
14+
15+
This module is used by higher layers (e.g. HTTP GET cache middleware), but it is intentionally usable as a **standalone library**.
16+
17+
---
18+
19+
## Why this module exists
20+
21+
In offline-first systems, caching is not just a performance optimization.
22+
It is part of **correctness under failure**:
23+
24+
* if the device is **offline**, you must be able to serve previously validated data
25+
* if the network is **unstable**, you must be able to fall back to stale data safely
26+
* behavior must be **observable**, explicit, and testable
27+
28+
This module makes all caching decisions explicit with:
29+
30+
* a `CachePolicy`
31+
* a `CacheContext`
32+
* a time `t_ms`
33+
34+
So you can test cache behavior as pure logic.
35+
36+
---
37+
38+
## Quick start (run the smoke tests)
39+
40+
The module includes runnable smoke tests (copy/paste friendly examples).
41+
42+
```bash
43+
vix run modules/cache/tests/cache_smoke_test.cpp
44+
vix run modules/cache/tests/cache_context_mapper_smoke_test.cpp
45+
```
46+
47+
(Exact paths may differ in your tree; the files shown below are the reference examples.)
48+
49+
---
50+
51+
## Core concepts
52+
53+
### 1) CachePolicy
54+
55+
`CachePolicy` defines deterministic time windows:
56+
57+
* `ttl_ms` — fresh window (normal caching)
58+
* `allow_stale_if_offline` + `stale_if_offline_ms` — stale window when offline
59+
* `allow_stale_if_error` + `stale_if_error_ms` — stale window on network errors
60+
61+
Example:
62+
63+
```cpp
64+
vix::cache::CachePolicy policy;
65+
policy.ttl_ms = 100;
66+
policy.allow_stale_if_offline = true;
67+
policy.stale_if_offline_ms = 10'000;
68+
policy.allow_stale_if_error = true;
69+
policy.stale_if_error_ms = 5'000;
70+
```
71+
72+
---
73+
74+
### 2) CacheContext
75+
76+
The same cached entry may be acceptable or rejected depending on the context:
77+
78+
* `CacheContext::Online()`
79+
* `CacheContext::Offline()`
80+
* `CacheContext::NetworkError()`
81+
82+
Example:
83+
84+
```cpp
85+
auto got = cache.get(key, t_now, vix::cache::CacheContext::Offline());
86+
```
87+
88+
This is how you express: “accept stale data when offline, but not forever”.
89+
90+
---
91+
92+
### 3) Stores
93+
94+
The cache separates **policy** from **storage**.
95+
96+
#### MemoryStore
97+
98+
* in-memory map
99+
* fastest
100+
* ideal for servers / ephemeral caching
101+
102+
#### FileStore
103+
104+
* persists cache to disk
105+
* reloads on startup
106+
* suitable for local-first apps and edge runtimes
107+
108+
#### LruMemoryStore
109+
110+
* in-memory LRU eviction
111+
* bounded size via `max_entries`
112+
* good for servers that must cap memory
113+
114+
---
115+
116+
## Minimal usage
117+
118+
```cpp
119+
#include <memory>
120+
#include <vix/cache/Cache.hpp>
121+
#include <vix/cache/CacheEntry.hpp>
122+
#include <vix/cache/CachePolicy.hpp>
123+
#include <vix/cache/CacheContext.hpp>
124+
#include <vix/cache/MemoryStore.hpp>
125+
126+
int main()
127+
{
128+
using namespace vix::cache;
129+
130+
auto store = std::make_shared<MemoryStore>();
131+
132+
CachePolicy policy;
133+
policy.ttl_ms = 10'000;
134+
policy.allow_stale_if_offline = true;
135+
policy.stale_if_offline_ms = 60'000;
136+
137+
Cache cache(policy, store);
138+
139+
const std::string key = "GET:/api/users?page=1";
140+
const std::int64_t t0 = 1000; // example time
141+
142+
CacheEntry e;
143+
e.status = 200;
144+
e.body = R"({\"ok\":true})";
145+
e.created_at_ms = t0;
146+
147+
cache.put(key, e);
148+
149+
auto got = cache.get(key, t0 + 5, CacheContext::Online());
150+
if (got) {
151+
// use got->status / got->body / got->headers
152+
}
153+
}
154+
```
155+
156+
---
157+
158+
## Offline-first behavior (from the context mapper smoke test)
159+
160+
The recommended pattern is:
161+
162+
1. Determine connectivity context (online/offline/error)
163+
2. If **offline** → cache only
164+
3. If **online** → attempt network
165+
4. If **network error** → fall back to cache with `NetworkError` context
166+
167+
This logic is demonstrated as a testable pure function in:
168+
169+
* `cache_context_mapper_smoke_test.cpp`
170+
171+
It validates the key offline-first behaviors:
172+
173+
* offline + cached entry → **CacheHit**
174+
* offline + no entry → **OfflineMiss**
175+
* online + network ok → **NetOk** + cache populated
176+
* online + network error → **CacheHit** if `allow_stale_if_error`
177+
178+
---
179+
180+
## FileStore persistence
181+
182+
The file store smoke test validates:
183+
184+
* entries persist to disk
185+
* new cache instance can reload the data
186+
* offline stale rules still apply after reload
187+
188+
Example config (conceptual):
189+
190+
```cpp
191+
vix::cache::FileStore::Config cfg;
192+
cfg.file_path = "./build/.vix_test/cache_http.json";
193+
cfg.pretty_json = true;
194+
195+
auto store = std::make_shared<vix::cache::FileStore>(cfg);
196+
```
197+
198+
---
199+
200+
## Header normalization
201+
202+
When inserting entries, the cache normalizes header keys to **lower-case**.
203+
204+
This ensures consistent lookup and avoids duplicated keys (case variants).
205+
206+
The smoke test verifies:
207+
208+
* `Content-Type` becomes `content-type`
209+
* `X-Powered-By` becomes `x-powered-by`
210+
211+
---
212+
213+
## LRU eviction
214+
215+
`LruMemoryStore` supports eviction via `max_entries`.
216+
217+
The smoke test demonstrates:
218+
219+
* LRU behavior is stable
220+
* touching an entry updates its recency
221+
* inserting beyond capacity evicts the least recently used key
222+
223+
Example:
224+
225+
```cpp
226+
auto store = std::make_shared<vix::cache::LruMemoryStore>(
227+
vix::cache::LruMemoryStore::Config{.max_entries = 2048}
228+
);
229+
```
230+
231+
---
232+
233+
## Pruning stale entries
234+
235+
`Cache::prune(t_ms)` removes entries that are too old under the active policy.
236+
237+
The test demonstrates:
238+
239+
* stale entries removed
240+
* fresh entries kept
241+
242+
This is useful for:
243+
244+
* periodic cleanup loops
245+
* memory-bounded caches
246+
* long-running edge runtimes
247+
248+
---
249+
250+
## CacheKey builder
251+
252+
`CacheKey::fromRequest()` generates stable cache keys by:
253+
254+
* normalizing query params order (`b=2&a=1` → `a=1&b=2`)
255+
* optionally varying on selected headers (`Accept`, etc.)
256+
257+
Example:
258+
259+
```cpp
260+
std::unordered_map<std::string, std::string> headers;
261+
headers["Accept"] = "application/json";
262+
headers["X-Device"] = "mobile";
263+
264+
auto k = vix::cache::CacheKey::fromRequest(
265+
"GET",
266+
"/api/users",
267+
"b=2&a=1",
268+
headers,
269+
{"Accept"}
270+
);
271+
```
272+
273+
This is critical to avoid cache fragmentation and to keep behavior deterministic.
274+
275+
---
276+
277+
## How cache fits in the umbrella
278+
279+
* `cache` provides the offline-first caching core
280+
* `middleware` builds HTTP GET caching on top (control headers, debug headers, bypass)
281+
* `sync` and offline engines can reuse the same policy/context model
282+
283+
---
284+
285+
## Directory layout
286+
287+
Typical layout:
288+
289+
```
290+
modules/cache/
291+
292+
├─ include/vix/cache/
293+
│ ├─ Cache.hpp
294+
│ ├─ CacheEntry.hpp
295+
│ ├─ CachePolicy.hpp
296+
│ ├─ CacheContext.hpp
297+
│ ├─ CacheContextMapper.hpp
298+
│ ├─ CacheKey.hpp
299+
│ ├─ MemoryStore.hpp
300+
│ ├─ FileStore.hpp
301+
│ ├─ LruMemoryStore.hpp
302+
│ └─ ...
303+
304+
└─ tests/
305+
├─ cache_smoke_test.cpp
306+
└─ cache_context_mapper_smoke_test.cpp
307+
```
308+
309+
---
310+
311+
## License
312+
313+
MIT — same as Vix.cpp
314+
315+
Repository: [https://github.com/vixcpp/vix](https://github.com/vixcpp/vix)

0 commit comments

Comments
 (0)