ESC
Type to search...
S
Soli Docs

Cache

Persistent caching backed by SoliKV with automatic TTL expiration. Values survive restarts, are shared across server instances, and are namespaced under soli:cache: so they never collide with raw KV data.

How it works

  • Values are JSON-serialized on set and parsed back on get — works for any value JSON.stringify would handle.
  • Keys are transparently prefixed with soli:cache:; Cache.keys returns them with the prefix stripped.
  • TTL expiration is enforced by SoliKV — there is no Soli-side reaper. Default TTL is 3600s (1 hour).
  • Misses return null. Cache.fetch turns the cache-aside pattern into one expression.

Configuration

Cache reuses the SoliKV connection. Configure via env vars or programmatically:

Variable Description Default
SOLIKV_RESP_HOST SoliKV host localhost
SOLIKV_RESP_PORT RESP port 6380
SOLIKV_TOKEN Bearer token (optional)
SOLIKV_RESP_HOST=localhost
SOLIKV_RESP_PORT=6380
SOLIKV_TOKEN=my-secret-token
Cache.configure("my-solikv-host", "my-secret-token")

Read & Write

Cache.set(key, value, ttl_seconds?)

Store a value, JSON-serialized. ttl_seconds defaults to 3600.

Cache.set("user:123", { "name": "Alice" })
Cache.set("session", data, 1800)   # 30 minute TTL

Returns: null

Cache.get(key)

Retrieve a value. Returns null if the key is missing or expired.

user = Cache.get("user:123")
if user != null
  println("Cached user: " + user["name"])
end
Cache.fetch(key, ttl?) do ... end cache-aside

Returns the cached value on hit. On miss, runs the block, caches the result, and returns it. Without a block it behaves like Cache.get.

user = Cache.fetch("user:" + str(user_id), 300) do
  User.find(user_id)
end

If the block raises, nothing is cached.

Cache.delete(key)

Remove a key. Returns true if it existed.

Cache.has(key)

Check whether a key exists. Returns Bool.

TTL Management

Cache.ttl(key)

Seconds until expiration, or null if the key is missing or has no TTL.

Cache.touch(key, ttl)

Reset the TTL of an existing key. Returns true on success, false if the key does not exist.

Cache.touch("user:123", 3600)   # extend by 1 hour

Inspection & Bulk Operations

Method Description
Cache.keys All cache keys with the soli:cache: prefix stripped. Returns Array.
Cache.size Number of cached entries. Returns Int.
Cache.clear Remove every key under the cache prefix. Other SoliKV data is untouched.
Cache.clear_expired() No-op. SoliKV expires keys on its own — kept for API symmetry with file-based caches.

Connection

Cache.configure(host, token?)

Override the SoliKV connection at runtime. Affects KV too — both share one client.

Cache.configure("kv.internal:6380", env("SOLIKV_TOKEN"))

Instance Form

Call cache() to get a thin wrapper instance. All instances share the same backing store — useful when you want to pass a cache object as a dependency rather than reaching for the static class.

c = cache()
c.set("greeting", "hello")
c.get("greeting")   # => "hello"

Common Patterns

Cache-aside reads

Wrap an expensive read with Cache.fetch — one expression replaces the read-check-set dance.

user = Cache.fetch("user:" + str(user_id), 300) do
  User.find(user_id)
end
fn get_user_cached(user_id) {
  key = "user:" + str(user_id)
  cached = Cache.get(key)
  return cached if cached != null

  user = User.find(user_id)
  Cache.set(key, user, 300)
  user
}

Write-through invalidation

Drop the cache entry whenever the underlying record changes — simpler than trying to keep them in sync.

class User {
  static fn update(id, attrs) {
    user = db.update("users", id, attrs)
    Cache.delete("user:" + str(id))
    user
  }
}

Short-TTL fragment caching

Cache rendered fragments or aggregated views for a few seconds to absorb traffic spikes without going stale.

fn index {
  stats = Cache.fetch("dashboard:stats", 30) do
    compute_dashboard_stats()
  end
  return render("dashboard/index", { "stats": stats })
}

See also