Skip to content
alya-langPublic

About

High-performance in-memory cache with LRU/LFU/FIFO eviction, TTL expiry, hooks, snapshots, and statistics for Alya

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

cache

CI License Alya Package Version

High-performance in-memory cache with LRU/LFU/FIFO eviction, TTL expiry, hooks, snapshots, and statistics for Alya.


🌟 Features

  • ⚑ Automatic Value Typing: One set/get pair handles strings, ints, bools (stored as 1/0), floats, arrays, and maps β€” no set_int / get_float variants to remember
  • 🧠 Four Eviction Policies: LRU, FIFO, LFU, and None (unbounded) with per-entry hit counters feeding the LFU ranking
  • ⏱️ TTL Expiry: Per-entry seconds, cache-wide defaults, ttl/ttl_ms inspection, eager expiry, touch, expire, and jittered writes for cache-stampede protection
  • πŸͺ Lifecycle Hooks: on_evict and on_expire callbacks receive the key and the live payload
  • πŸ“¦ Batch Operations: set_many, get_many, and delete_by_prefix keep multi-key workflows concise
  • πŸ”Ž Views & Memoization: keys, values, entries, plus get_or, get_or_insert, and get_or_compute (producer runs only on a miss)
  • πŸ“Š Statistics: Lifetime hit/miss/eviction counters, per-entry hits, CacheStats, hit_rate, and a one-line summary
  • πŸ’Ύ Disk Snapshots (snapshot feature, default-on): Line-based alyacache text format round-trips string/int/float entries through save / 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.alya and the snapshot slice in tests/test_snapshot.alya (the latter skips cleanly with --no-default-features)

Runtime Type Notes

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 Payloads

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.


πŸ“ Project Architecture

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.


πŸ“¦ Installation

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

Package Features

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-features

πŸš€ Quick Start

import "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()

πŸ“– API Reference

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.


πŸ§ͺ Running Tests & Benchmarks

Run the automated test suite using alya test:

alya test

Exercise feature selection (the snapshot slice is default-on):

alya test --no-default-features
alya test --features snapshot

Run the example demo and the benchmark suite:

alya run examples/demo.alya
alya run benches/bench_basic.alya

Check formatting and lint rules:

alya fmt . --check
alya lint . --check

Generate static API documentation:

alya doc . -o docs --markdown

πŸ’» Developer Tooling & VS Code Integration

This 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.json ready for interactive step-debugging via F5.
  • Predefined Tasks: Press Ctrl+Shift+B or run tasks (Test, Lint, Format, Build Docs) directly from the Command Palette.

🀝 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository and clone it locally
  2. Install dependencies:
    alya install
  3. Create your feature branch (git checkout -b feature/my-feature)
  4. Verify tests and formatting before opening a PR:
    alya test
    alya fmt . --check
    alya lint . --check
  5. Commit your changes (git commit -m "feat: add feature") and open a Pull Request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance in-memory cache with LRU/LFU/FIFO eviction, TTL expiry, hooks, snapshots, and statistics for Alya

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors