Skip to content

Frankiecache

In-memory key/value cache with optional TTL (time-to-live). Zero dependencies — pure .fk, no external libraries. State resets on process restart — use the database for persistence.

Introduced in v1.16.


Functions

cache_set(key, value, ttl: 0)

Store a value under key. If ttl > 0, the entry expires after that many seconds. Returns the stored value.

cache_set("user:42", {name: "Alice"})           # no expiry
cache_set("session:abc", {user: "Alice"}, ttl: 300)  # expires in 5 min

cache_get(key)

Retrieve a cached value. Returns nil if the key does not exist or has expired.

user = cache_get("user:42")
if user == nil
  # cache miss — load from database
end

cache_set?(key)

Returns true if the key exists and has not expired.

if cache_set?("config")
  cfg = cache_get("config")
end

cache_delete(key)

Remove a single key from the cache.

cache_delete("user:42")

cache_clear()

Remove all entries from the cache.

cache_clear()

cache_size()

Return the number of live (non-expired) entries.

puts "#{cache_size()} entries in cache"

cache_fetch(key, ttl: 0)

Note: Block-based cache_fetch is not yet supported in this version of Frankie's stitch system. Use the explicit set/get pattern below instead.


Quick Start

stitch "frankiecache"

# Store user data for 5 minutes
cache_set("user:42", {name: "Alice", role: "admin"}, ttl: 300)

user = cache_get("user:42")
if user != nil
  puts "Cache hit: #{user["name"]}"
else
  puts "Cache miss"
end

puts cache_size()   # 1

Cache-Aside Pattern

The most common caching pattern — check the cache first, load from the database on a miss, then cache the result:

stitch "frankiecache"

def get_user(db, id)
  key    = "user:#{id}"
  cached = cache_get(key)
  if cached != nil
    return cached
  end
  user = db.find_one("users", {id: id})
  if user != nil
    cache_set(key, user, ttl: 60)
  end
  return user
end

db = db_open("app.db")
user = get_user(db, 42)
puts user["name"]

TTL-Based Rate Limiting (simple)

stitch "frankiecache"

def allow_request?(ip)
  key   = "rate:#{ip}"
  count = cache_get(key)
  if count == nil
    cache_set(key, 1, ttl: 60)
    return true
  end
  if count >= 30
    return false
  end
  cache_set(key, count + 1, ttl: 60)
  return true
end

puts allow_request?("10.0.0.1")   # true

Config Caching

stitch "frankiecache"

def load_config()
  cached = cache_get("app_config")
  if cached != nil
    return cached
  end
  cfg = json_parse(file_read("config.json"))
  cache_set("app_config", cfg, ttl: 300)
  return cfg
end

config = load_config()
puts config["database"]["host"]

Cache Stats and Inspection

stitch "frankiecache"

cache_set("a", 1)
cache_set("b", 2, ttl: 10)
cache_set("c", 3, ttl: 10)

puts "size: #{cache_size()}"           # 3
puts "a exists: #{cache_set?("a")}"    # true
puts "x exists: #{cache_set?("x")}"    # false

cache_delete("b")
puts "after delete: #{cache_size()}"   # 2

cache_clear()
puts "after clear: #{cache_size()}"    # 0

Notes

  • The cache is process-local and in-memory — it does not persist across restarts and is not shared between processes.
  • TTL is checked lazily on cache_get — expired entries are evicted on access, not by a background timer.
  • cache_size() counts only live (non-expired) entries by iterating and checking TTLs.
  • For persistent or distributed caching, use an external store (Redis, SQLite, etc.) and call it from your Frankie script directly.