ESC
Type to search...
S
Soli Docs

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

See also