KV
Full-featured key-value store backed by SoliKV. Supports strings, counters, lists, sets, and hashes with Redis-compatible commands via a lightweight REST API. Shares the same connection as Cache but operates on raw keys with no prefix.
Configuration
KV shares the SoliKV connection with Cache. Configure via environment variables 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
KV.configure("my-solikv-host", "my-secret-token")
Read & Write
KV.set(key, value, ttl?)
Store a value. Optionally set TTL in seconds. Without TTL the key lives forever.
KV.set("user:1", { "name": "Alice" })
KV.set("temp:token", "abc123", 60) # expires in 60 seconds
KV.get(key)
Retrieve a value. Returns null if the key doesn't exist.
user = KV.get("user:1") # => { "name": "Alice" }
missing = KV.get("nope") # => null
KV.delete(key)
Delete a key. Returns true if the key was removed, false if it didn't exist.
KV.exists(key)
Check if a key exists. Returns Bool.
if KV.exists("user:1")
println("User exists")
end
KV.keys(pattern?)
denied by default
List keys matching a glob pattern. Defaults to "*".
Gated: Bulk key enumeration is O(N) and exposes the entire keyspace. Set SOLI_KV_ALLOW_ADMIN=1 to enable.
all = KV.keys # all keys
users = KV.keys("user:*") # keys starting with "user:"
KV.type(key)
Get the data type of a key — string, list, set, hash, or none.
KV.rename(key, newkey)
Rename a key. Errors if newkey already exists.
TTL Management
KV.ttl(key)
Remaining TTL in seconds. Returns null if the key has no expiry or doesn't exist.
KV.expire(key, seconds)
Set a TTL on an existing key. Returns true if successful.
KV.expire("user:1", 3600) # key expires in 1 hour
KV.persist(key)
Remove the TTL and make the key persistent. Returns true on success.
Counters
KV.incr(key) / KV.decr(key)
Atomically increment or decrement by 1. Creates the key with value 0 if it doesn't exist. Returns the new value.
KV.incr("visits") # => 1
KV.incr("visits") # => 2
KV.decr("visits") # => 1
KV.incrby(key, amount) / KV.decrby(key, amount)
Increment or decrement by a specified amount. Returns the new value.
KV.incrby("score", 10) # => 10
KV.decrby("score", 3) # => 7
Lists
KV.lpush(key, ...values) / KV.rpush(key, ...values)
Push values to the head (left) or tail (right) of a list. Returns the new list length.
KV.rpush("queue", "job1")
KV.rpush("queue", "job2")
KV.lpush("queue", "urgent")
KV.lpop(key) / KV.rpop(key)
Remove and return the first (left) or last (right) element.
KV.lrange(key, start, stop)
Get a range of elements. Use 0, -1 for the entire list.
all = KV.lrange("queue", 0, -1)
KV.llen(key)
Get the length of a list.
Sets
KV.sadd(key, ...members) / KV.srem(key, ...members)
Add or remove members from a set. Returns the number of elements actually added or removed.
KV.sadd("tags", "rust", "soli", "redis")
KV.srem("tags", "redis")
KV.smembers(key)
Get all members of a set. Returns an array.
KV.sismember(key, member)
Check if a value is a member of a set. Returns Bool.
if KV.sismember("beta-users", user_id)
render("beta/feature")
end
KV.scard(key)
Get the number of members in a set.
Hashes
KV.hset(key, field, value)
Set a field in a hash. Creates the hash if it doesn't exist.
KV.hset("user:1", "name", "Alice")
KV.hset("user:1", "email", "[email protected]")
KV.hget(key, field)
Get a single field value from a hash. Returns null if the field or key doesn't exist.
KV.hgetall(key)
Get all fields and values as a Soli Hash.
user = KV.hgetall("user:1")
println(user["name"]) # => "Alice"
KV.hdel(key, ...fields)
Delete one or more fields from a hash. Returns the number of fields removed.
KV.hexists(key, field)
Check if a field exists in a hash. Returns Bool.
KV.hkeys(key) / KV.hlen(key)
Get all field names in a hash or the number of fields. hkeys returns an array, hlen returns an int.
Server Commands
KV.ping
Check connectivity with the SoliKV server. Returns "PONG".
KV.dbsize
Total number of keys in the SoliKV database.
KV.flushdb
denied by default
Delete all keys. Gated behind SOLI_KV_ALLOW_ADMIN=1.
KV.cmd(...args)
Run a raw SoliKV command. The first argument is the command verb, filtered through the admin denylist.
KV.cmd("SET", "key", "value")
KV.cmd("GET", "key")
KV.cmd("EXPIRE", "key", 60)
Admin denylist
KV.cmd, KV.flushdb, and KV.keys refuse destructive or keyspace-wide operations by default. The command verb is matched (case-insensitive) against:
FLUSHALL FLUSHDB KEYS SCAN
CONFIG DEBUG SHUTDOWN MONITOR CLIENT
SLAVEOF REPLICAOF BGREWRITEAOF BGSAVE SAVE
CLUSTER FAILOVER RESET ACL
SCRIPT EVAL EVALSHA FUNCTION
To enable raw admin access, set SOLI_KV_ALLOW_ADMIN=1 on the process that needs it. Leave this unset on workers reachable from user traffic — a controller bug or template injection cannot reach KV.cmd("FLUSHALL").
Common Patterns
Rate limiting with counters
def check_rate_limit(ip)
key = "rate:" + ip
count = KV.incr(key)
KV.expire(key, 60) if count == 1
return count <= 100
end
Job queue with lists
KV.rpush("jobs", JSON.stringify({"type": "email", "to": "[email protected]"}))
raw = KV.lpop("jobs")
if raw != null
job = JSON.parse(raw)
process_job(job)
end
User sessions with hashes
KV.hset("session:abc", "user_id", str(user.id))
KV.hset("session:abc", "role", user.role)
KV.expire("session:abc", 3600)
# Later
session = KV.hgetall("session:abc")
println("User ID: " + session["user_id"])
Feature flags with sets
KV.sadd("features:beta", "user:1", "user:2")
if KV.sismember("features:beta", "user:" + str(user.id))
render("beta/feature")
else
render("stable/feature")
end