Talk to Redis from the Scarlet programming language, with a typed result for every one of its 275 commands.
Documentation • Redis commands
git submodule add https://github.com/scarletindustries/redis_client lib/redis
Keep hyphens out of the directory name, because Scarlet reads a hyphen in an import path as a minus sign. The client speaks RESP3, so it needs Redis 6 or newer.
import scarlet/result
import ./lib/redis/redis.{Conn}
import ./lib/redis/strings
fn greet(c Conn) Result(Nil, redis.RedisError) {
_ <- result.then(strings.set(c, 'greeting', 'hello'))
value <- result.then(strings.get(c, 'greeting'))
println(value or '(nil)')
Ok(Nil)
}
redis.with_conn('127.0.0.1', 6379, greet)
with_conn closes the connection however the block ends, and each <- line
stops at the first command that fails. A missing key is None, and a refused
command is an Err that leaves the connection usable.
A hosted Redis usually comes as a URL, which with_conn_url takes directly.
rediss:// is TLS, and a trailing /2 selects database 2:
redis.with_conn_url('rediss://default:s3cret@cache.example.com:6380/2', greet)
A plain connection belongs to the process that opened it. When many processes
need Redis, start a pool. A pool is also a Conn, so every command works on it
unchanged, and it costs nothing to hand to other processes:
import ./lib/redis/pool
pool.with_pool('127.0.0.1', 6379, 4, greet)
Two commands on a pool may land on two connections, so wrap a WATCH or
MULTI in pool.with_conn to keep them on one. Subscribing needs a connection
of its own and fails on a pool.
redis.pipeline sends a batch in one round trip and returns one result per
command, in order. Against a local server, 500 SETs took 152ms one at a time
and 6ms pipelined.
replies <- result.then(redis.pipeline(c, [
['SET', 'hits', '0'],
['INCR', 'hits'],
['GET', 'hits'],
]))
transactions.transaction does the same inside MULTI and EXEC.
The commands live in modules named the way Redis groups them: strings, keys,
hashes, lists, sets, sorted_sets, streams, pubsub, geo, bitmaps,
hyperloglog, arrays, transactions, scripting, connection, server and
cluster. For anything not wrapped, send it yourself:
redis.command(c, ['OBJECT', 'ENCODING', 'user:1'])
docker compose up -d # redis on :5379
scripts/test # suite + pool; non-zero if a check fails
scarlet run example/tour.scrl
scarlet run example/pool.scrl
The TLS tests mint their own certificates and run against a second Redis behind a compose profile:
./scripts/tls-certs.sh # once, mints test/tls/
docker compose --profile tls up -d # redis TLS on :6380
SSL_CERT_FILE=test/tls/ca.crt scarlet run test/tls.scrl
scarlet run test/tls.scrl # the untrusted-issuer refusal
scripts/tls-records # one-record pipeline, no Redis
Never point the TLS tests at the plaintext port. Redis never answers the handshake there, and it hangs for ever.
