pub fn fetch<'a, T, F, Fut>(
ctx: &Ctx,
key: &'a str,
ttl: Duration,
compute: F,
) -> impl Future<Output = Result<T>> + Send + use<'a, T, F, Fut>Expand description
Read-through cache, like Rails’ Rails.cache.fetch: the value under key in KV, or else the result of compute.
On a hit, the stored JSON is decoded as T and compute does not run. On
a miss, compute runs and its value is stored for ttl (at least
MIN_TTL, truncated to whole seconds). KV
failures (over a daily limit, a value from an older deploy that no longer
decodes as T) are logged with LOG_PREFIX
and the value is computed, so the handler keeps working.
Stored values are the JSON of T; put a version in the key (:v1) and
bump it when T changes. The returned future is Send, so it can be
awaited in axum handlers. A key already read or written during the same
request is answered from memory (Rails’ local cache). With
STORE_VAR set to "null", compute always
runs and nothing is stored.
Free plan (September 2026): one KV read per call (100,000 a day), plus one
KV write on a miss (1,000 a day; one per second per key). A key
refreshed every ttl seconds costs up to 86,400 / ttl writes a day.
§Errors
Error::Internal(500) when theCACHEbinding is missing, naming the fix:ocre g cacheaddsCACHE: bindings.kv(),to cloudflare.config.ts.Error::Internalwhenkeyis empty or longer than 512 bytes, orttlis below 60 seconds.Error::Internalwhen the computed value does not serialize to JSON.- Any error returned by
compute, unchanged (nothing is stored).
KV read and write failures are not errors: they are logged.
§Examples
use std::time::Duration;
use axum::{Json, extract::State};
use ocre::{Ctx, Result};
async fn index(State(ctx): State<Ctx>) -> Result<Json<Vec<Post>>> {
let posts: Vec<Post> = ocre::cache::fetch(&ctx, "posts:recent:v1", Duration::from_secs(600), || async {
recent(&ctx).await // e.g. a D1 query
})
.await?;
Ok(Json(posts))
}