High-performance in-memory cache with LRU/LFU/FIFO eviction, TTL expiry, hooks, snapshots, and statistics for Alya.
- β‘ Automatic Value Typing: One
set/getpair handles strings, ints, bools (stored as1/0), floats, arrays, and maps β noset_int/get_floatvariants to remember - π§ Four Eviction Policies:
LRU,FIFO,LFU, andNone(unbounded) with per-entry hit counters feeding the LFU ranking - β±οΈ TTL Expiry: Per-entry seconds, cache-wide defaults,
ttl/ttl_msinspection, eager expiry,touch,expire, and jittered writes for cache-stampede protection - πͺ Lifecycle Hooks:
on_evictandon_expirecallbacks receive the key and the live payload - π¦ Batch Operations:
set_many,get_many, anddelete_by_prefixkeep multi-key workflows concise - π Views & Memoization:
keys,values,entries, plusget_or,get_or_insert, andget_or_compute(producer runs only on a miss) - π Statistics: Lifetime hit/miss/eviction counters, per-entry hits,
CacheStats,hit_rate, and a one-linesummary - πΎ Disk Snapshots (
snapshotfeature, default-on): Line-basedalyacachetext format round-trips string/int/float entries throughsave/load - π§© Template-Conformant Layout: Public facade (
src/lib.alya), data models (src/types.alya), storage engine (src/core/store.alya), fixed-point float codec (src/core/codec.alya), and a feature slice with fallback stubs (src/core/snapshot.alya) - π§ͺ Dual Test Suites: Core behavior in
tests/test_basic.alyaand the snapshot slice intests/test_snapshot.alya(the latter skips cleanly with--no-default-features)
set classifies each payload at the call site: floats are encoded to a portable decimal string inside the store and decoded by every read, so get returns a real float for == comparisons and arithmetic. Strings and ints are stored directly and tagged for snapshots; bools are ints at runtime (true reads back as 1).
Reads are kind-aware end to end: binding a payload to a local keeps its runtime tag, so say v, str(v), v is float, v == 0.75, and float arithmetic all see a float even when the static type is dynamic.
One boundary remains: a payload whose kind is not statically known at the set call site β e.g. a float forwarded through several generic helpers from a dynamic source β falls back to the best-effort runtime check, where float bits and ints are indistinguishable. Pass such payloads directly to set, or through a call chain whose argument kinds are statically known, for exact float round-trips.
null means "absent": set returns false and leaves the key untouched, get on a missing key returns null, and get_or_insert / get_or_compute never store a null result. Because int 0 and null share the zero word at runtime, the runtime guard lives in the facade where the payload kind is still known from the call site; a statically int-typed 0 is stored normally.
cache/
βββ .alyalint # Linter configuration (rules, exclusions, severity overrides)
βββ .alyafmt # Formatter configuration
βββ .alyatest # Test discovery configuration
βββ .editorconfig # Uniform formatting rules across IDEs and editors
βββ .gitignore # Ecosystem standard ignore filters
βββ .vscode/ # VS Code workspace settings, DAP launch configurations & tasks
βββ alya.toml # Package manifest with the default-on `snapshot` feature
βββ src/
β βββ lib.alya # Public API facade: constructors + the full Cache.* surface
β βββ types.alya # EvictionPolicy, Cache, CacheMeta, CacheStats and helper methods
β βββ core/
β βββ codec.alya # Fixed-point encode/decode for float payloads
β βββ store.alya # Storage engine: insert/read/evict/expire/batch/stats
β βββ snapshot.alya # Feature-gated line-based `alyacache` save/load slice
βββ examples/
β βββ demo.alya # Runnable walkthrough across every capability
βββ tests/
β βββ test_basic.alya # Core suite: policies, TTL, hooks, batches, stats, memoization
β βββ test_snapshot.alya # `snapshot` feature suite (skips under --no-default-features)
βββ benches/
βββ bench_basic.alya # Micro-benchmarks for writes, reads, and batch throughput
Note
Visibility: Only pub symbols are exported. The engine internals in src/core/store.alya (victim selection, order bookkeeping, hook dispatch) stay private and are reached exclusively through the Cache.* facade.
Add cache to the [dependencies] section in your alya.toml:
[dependencies]
cache = { git = "https://github.com/alya-lang/cache", branch = "main" }Or install it directly using the Alya package CLI:
alya add cache --git https://github.com/alya-lang/cache --branch main
alya install| Feature | Default | Description |
|---|---|---|
snapshot |
β | Disk persistence slice: Cache.save / Cache.load with the line-based alyacache text format (src/core/snapshot.alya, with fallback stubs when disabled). |
# Full build (default)
alya install
alya test
# Slim build (core only)
alya install --no-default-features
alya test --no-default-featuresimport "cache" as pkg
function main()
# 1. Pick a policy and capacity (TTL defaults to 0 = never expires)
let c = pkg::lru_cache(128, 0)
# 2. Autotyped writes: strings, ints, bools, floats, arrays, maps
c.set("user:1", "Ada")
c.set("visits", 42)
c.set("active", true)
c.set("ratio", 0.75)
c.set("tags", ["math", "code"])
# 3. Reads decode floats automatically
say "user: " + c.get("user:1")
say "visits: " + str(c.get("visits"))
say "ratio: " + str(c.get("ratio") == 0.75)
say "active: " + str(c.get("active"))
# 4. TTL and memoization
c.set("session:1", "token", 60)
say "ttl: " + str(c.ttl("session:1"))
let load = |key: string| => "profile:" + key
say "memo: " + c.get_or_compute("u:1", load)
# 5. Batch helpers and stats
c.set_many({ "a": 1, "b": 2 })
say "keys: " + str(len(c.keys()))
say c.summary()
end
main()
| Symbol | Visibility | Description |
|---|---|---|
new_cache(capacity = 128, default_ttl = 0, policy = EvictionPolicy.LRU) |
pub function |
Constructs a cache with explicit capacity, default TTL (seconds), and eviction policy. |
lru_cache(capacity = 128, default_ttl = 0) |
pub function |
Least-recently-used cache; reads refresh recency. |
fifo_cache(capacity = 128, default_ttl = 0) |
pub function |
First-in-first-out cache; reads do not refresh recency. |
lfu_cache(capacity = 128, default_ttl = 0) |
pub function |
Least-frequently-used cache; reads refresh the per-entry counter. |
unbounded_cache(default_ttl = 0) |
pub function |
Capacity 0 and EvictionPolicy.None: never evicts. |
Cache.set(key, value, ttl = -1) |
pub method |
Autotyped insert/overwrite; ttl < 0 uses the cache default, 0 never expires. Rejects null payloads. |
Cache.get(key) |
pub method |
Live read: counts a hit/miss, bumps the entry counter, refreshes LRU recency, decodes floats. |
Cache.peek(key) |
pub method |
Read without counters or recency changes (expired entries are still pruned). |
Cache.take(key) |
pub method |
Reads and removes in one step (counted like get). |
Cache.get_or(key, fallback) |
pub method |
Returns the live value or fallback on a miss. |
Cache.get_or_insert(key, value, ttl = -1) |
pub method |
Inserts value on a miss; null values are returned but never stored. |
Cache.get_or_compute(key, producer, ttl = -1) |
pub method |
Runs producer(key) on a miss; a null result is returned but not stored. |
Cache.has(key) |
pub method |
true when the key exists and is live (pure probe, no counters/recency). |
Cache.delete(key) |
pub method |
Removes an entry; true when one was removed. |
Cache.clear() |
pub method |
Removes every entry and resets hit/miss/eviction counters. |
Cache.size() |
pub method |
Entry count (including expired-but-unpruned entries; call prune for a clean count). |
Cache.is_empty() |
pub method |
true when the cache holds no entries. |
Cache.keys() |
pub method |
Live keys in recency order (prunes expired entries first). |
Cache.values() |
pub method |
Live values aligned with keys(). |
Cache.entries() |
pub method |
Map of all live key -> value pairs. |
Cache.set_many(values, ttl = -1) |
pub method |
Inserts every map entry; returns the number stored (null payloads skipped). |
Cache.get_many(key_list) |
pub method |
Reads many keys; returns a map of live key -> value pairs, counted like get. |
Cache.delete_by_prefix(prefix) |
pub method |
Removes every key starting with prefix; returns the number deleted. |
Cache.prune() |
pub method |
Eagerly removes expired entries; returns the number pruned. |
Cache.ttl(key) |
pub method |
Remaining TTL in whole seconds (0 = no expiry or missing). |
Cache.ttl_ms(key) |
pub method |
Remaining TTL in milliseconds (0 = no expiry or missing). |
Cache.expire(key, ttl) |
pub method |
Resets an entry's TTL (0 = persist forever, < 0 = cache default); returns true when the entry is live. |
Cache.touch(key, ttl = -1) |
pub method |
Refreshes recency and/or TTL without rewriting the payload. |
Cache.set_jittered(key, value, ttl, max_jitter = 0) |
pub method |
Insert with a deterministic 0..max_jitter second spread to avoid synchronized expiry. |
Cache.set_capacity(capacity) |
pub method |
Updates capacity, evicting excess entries when shrinking (<= 0 = unbounded). |
Cache.set_default_ttl(default_ttl) |
pub method |
Updates the TTL used by later set calls (existing entries keep their expiry). |
Cache.on_evict(cb) |
pub method |
Registers fn(key, value) invoked after each capacity eviction. |
Cache.on_expire(cb) |
pub method |
Registers fn(key, value) invoked when an expired entry is removed. |
Cache.stats() |
pub method |
Returns a CacheStats snapshot (size, capacity, hits, misses, evictions, policy). |
Cache.entry_hits(key) |
pub method |
Successful reads recorded for one key (LFU frequency signal). |
Cache.hit_rate() |
pub method |
Whole-percent hit ratio string (e.g. "80%", "0%" with no traffic). |
Cache.summary() |
pub method |
One-line utilization summary (size, capacity, hits, misses, evictions, policy). |
Cache.save(path) |
pub method (snapshot, default-on) |
Persists string/int/float entries to a file; returns the number written. |
Cache.load(path) |
pub method (snapshot, default-on) |
Merges entries from a snapshot file; returns the number restored. |
encode_float(v) |
pub function (src/core/codec.alya) |
Encodes a float to its portable fixed-point string. |
decode_float(s) |
pub function (src/core/codec.alya) |
Decodes a fixed-point string back to a float. |
format_hit_rate(hits, misses) |
pub function |
Formats a hit ratio from raw counters as a whole-percent string. |
EvictionPolicy |
pub enum |
None = 0, LRU = 1, FIFO = 2, LFU = 3. |
Cache |
pub struct |
Storage container (store, meta, order, capacity, default_ttl, policy, counters, hooks). |
CacheMeta |
pub struct |
Per-entry metadata (expires_at, inserted_at, entry_hits, kind). |
CacheMeta.is_expired() |
pub method |
true when the entry's absolute expiry has passed. |
CacheMeta.ttl_remaining() |
pub method |
Remaining TTL in whole seconds (0 = no expiry or elapsed). |
CacheMeta.ttl_remaining_ms() |
pub method |
Remaining TTL in milliseconds. |
CacheStats |
pub struct |
Utilization sample (size, capacity, hits, misses, evictions, policy). |
CacheStats.total_requests() |
pub method |
Sum of hits and misses. |
CacheStats.hit_rate() |
pub method |
Whole-percent hit ratio for the sample. |
CacheStats.summary() |
pub method |
One-line summary of the sample. |
Tip
Documentation: Public symbols carry ## Markdown docstrings, so alya doc . -o docs --markdown generates the full reference (including parameter and return notes) straight from the sources.
Run the automated test suite using alya test:
alya testExercise feature selection (the snapshot slice is default-on):
alya test --no-default-features
alya test --features snapshotRun the example demo and the benchmark suite:
alya run examples/demo.alya
alya run benches/bench_basic.alyaCheck formatting and lint rules:
alya fmt . --check
alya lint . --checkGenerate static API documentation:
alya doc . -o docs --markdownThis package comes preconfigured with recommended workspace settings and tasks for Visual Studio Code:
- LSP & Formatting: Auto-formatting on save and real-time Language Server diagnostics via
alya-lang.vscode-alya. - DAP Debugging: Launch configurations in
.vscode/launch.jsonready for interactive step-debugging viaF5. - Predefined Tasks: Press
Ctrl+Shift+Bor run tasks (Test,Lint,Format,Build Docs) directly from the Command Palette.
Contributions are welcome! Please follow these steps:
- Fork the repository and clone it locally
- Install dependencies:
alya install
- Create your feature branch (
git checkout -b feature/my-feature) - Verify tests and formatting before opening a PR:
alya test alya fmt . --check alya lint . --check
- Commit your changes (
git commit -m "feat: add feature") and open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.