torb/redis - a Redis and Valkey client for TorbScript: RESP2/RESP3, TLS, pipelining, transactions, pub/sub, pooling
  • Python 95.2%
  • Shell 4.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Orion 1c3f37f104
Some checks failed
ci / ci (pull_request) Successful in 20s
ci / integration (pull_request) Successful in 18s
pdisc / pdisc (pull_request) Successful in 21s
ci / ci (push) Failing after 8s
ci / integration (push) Failing after 6s
pdisc / pdisc (push) Successful in 7s
chore: install pdisc (frontmatter, INDEX, CI gate)
2026-10-07 08:10:38 +02:00
.forgejo/workflows chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:38 +02:00
.githooks chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:38 +02:00
.github/workflows ci: install the locked dependencies before checking, testing and publishing 2026-09-29 07:11:12 +02:00
scripts test(redis): an integration suite against real Redis and Valkey in Docker 2026-09-28 23:40:14 +02:00
src docs(redis): README, MIT LICENSE, CI/publish workflows and doc-link fixes 2026-09-28 23:53:55 +02:00
tests test(redis): an integration suite against real Redis and Valkey in Docker 2026-09-28 23:40:14 +02:00
tools/pdisc chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:38 +02:00
.gitignore feat(redis): RESP2/RESP3 codec and a buffered connection 2026-09-28 22:50:49 +02:00
.pdisc.yaml chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:38 +02:00
INDEX.md chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:38 +02:00
LICENSE docs(redis): README, MIT LICENSE, CI/publish workflows and doc-link fixes 2026-09-28 23:53:55 +02:00
project.trb feat(redis): RESP2/RESP3 codec and a buffered connection 2026-09-28 22:50:49 +02:00
README.md chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:38 +02:00

title summary keywords type updated body_hash
torb/redis: Redis and Valkey client for TorbScript README of the TorbScript client for Redis and Valkey: RESP2/RESP3, typed commands, pipelining, transactions, pub/sub and a connection pool. Read when using Redis from a TorbScript program.
redis
valkey
resp3
pipelining
pub/sub
multi/exec
connection pool
reference 2026-10-07 cc281caa6b52

torb/redis

A TorbScript client for Redis and Valkey: RESP2 and RESP3 (negotiated with HELLO 3, falling back for a server older than Redis 6), typed commands for the common families, pipelining, MULTI/EXEC transactions, pub/sub, and a small connection pool.

Quick start

use RedisClient from "torb/redis"

const client = RedisClient.open("redis://localhost:6379")?
using leased = client.acquire().await()?
leased.connection.set("greeting", "hello").await()?
print leased.connection.get("greeting").await()?   // Some("hello")

A single connection, with no pool, is just as direct:

use RedisConnection from "torb/redis"

var connection = RedisConnection.open("redis://localhost:6379").await()?
connection.incr("visits").await()?

rediss:// connects over TLS; redis://user:secret@host:port/3 carries a username, a password and a database index, the way the IANA redis URI scheme defines it.

What is here

  • Connecting: RedisConnection.open(url) or .connect(options); AUTH and SELECT run as part of the handshake, and RESP3 is used wherever the server answers HELLO 3.

  • Typed commands, each an extend RedisConnection in src/commands/:

    • strings: get, set (with SetCondition.IfNotExists/IfExists and Expiry.Seconds/Milliseconds/KeepTtl), incr, incrBy, decr, decrBy, append, strlen, mget, mset.
    • keys: del, exists, expire, pexpire, persist, ttl, pttl, keyType, rename, scan.
    • hashes: hset, hget, hdel, hexists, hgetAll, hkeys, hvals, hlen, hincrBy, hmset.
    • lists: lpush, rpush, lpop, rpop, lrange, llen, lindex, lrem, ltrim.
    • sets: sadd, srem, smembers, sisMember, scard, sunion, sinter, sdiff.
    • sorted sets: zadd, zscore, zincrBy, zrem, zrank, zcard, zrange, zrangeWithScores.
    • anything else: connection.command(textCommand("OBJECT", ["ENCODING", "key"])).await() reaches every command this package has no typed wrapper for, and reads back the raw RespValue.
  • Pipelining (src/pipeline.trb): connection.pipeline() queues frames and reads every reply together.

    var pipeline = connection.pipeline()
    pipeline.queueCommand("SET", ["a", "1"]).await()?
    pipeline.queueCommand("INCR", ["a"]).await()?
    const replies = pipeline.execute().await()?
    
  • Transactions (src/transaction.trb): WATCH, MULTI, queued commands, EXEC (None where a watched key changed) or DISCARD.

    var transaction = connection.transaction()
    transaction.watch(["balance"]).await()?
    transaction.begin().await()?
    transaction.queueCommand("DECRBY", ["balance", "10"]).await()?
    match transaction.exec().await()? {
      Some(replies) => print replies
      None => print "balance changed under us; retry"
    }
    
  • Pub/sub (src/pubsub.trb): a connection given over entirely to SUBSCRIBE/PSUBSCRIBE.

    var subscription = connection.subscription()
    subscription.subscribeTo(["news"]).await()?
    loop {
      const message = subscription.next().await()?
      print "{message.channel}: {message.payload}"
    }
    
  • A small pool (src/pool.trb, src/client.trb): RedisClient keeps at most a fixed number of connections to one server open, waiting for one to come back where more are asked for than that.

  • The RESP model (src/resp.trb): RespValue covers every RESP2 and RESP3 type - a RESP3 big number is kept as its decimal digits, since std/number has no arbitrary-precision integer yet.

  • Errors (src/error.trb): one RedisError, with isWrongType(), isNoAuth(), isMoved(), isAsk(), isLoading(), isBusy(), isReadonly(), isExecAbort() and isRetryable() reading a server's error code, Nil read as Option, never a null.

Developing

$ torb format --check .        # the layout torb format writes
$ torb check .                  # types, exhaustiveness, everything torb check finds
$ torb test tests/unit          # the codec and URL parsing, no server needed
$ torb doc --check .             # every doc comment link resolves, every example runs

The integration suite needs a real server:

$ sh scripts/start-servers.sh   # valkey/valkey:8-alpine on :16379, redis:7-alpine on :16380
$ torb test tests/integration
$ sh scripts/stop-servers.sh

torb format . writes the layout instead of only checking it. .github/workflows/ci.yml and .forgejo/workflows/ci.yml run torb format --check, torb check, torb test tests/unit and torb doc --check on every push and pull request, and a second job runs torb test tests/integration against Valkey and Redis service containers.

Publishing

torb publish needs name, version, description and license in project.trb, and it refuses a version that is published already - a published version is never replaced, so publishing again means raising version first.

Try it without uploading anything:

$ torb publish --dry-run

Publishing from CI (publish.yml) needs no token at all - trusted publishing. Before the first tag, an owner of torb's account on the registry (packages.torb.dev) has to add a trusted publisher for torb/redis that names:

Setting GitHub Actions (.github/workflows/publish.yml) Forgejo Actions (.forgejo/workflows/publish.yml)
Repository torb/redis git.torb.dev/torb/redis
Workflow file publish.yml publish.yml
Ref refs/tags/v* refs/tags/v*
Environment release (or delete the environment: line of the workflow to name none) none - a Forgejo token never names one

Once that is configured, pushing a tag v0.1.0 (matching the version in project.trb) runs publish.yml, which installs torb and runs torb publish with an identity token instead of a secret.

Files

redis/
├─ project.trb                  name, version, description, license
├─ src/
│  ├─ lib.trb                    the public surface: "torb/redis" resolves to this file
│  ├─ resp.trb                   RespValue and the command frame
│  ├─ error.trb                  RedisError
│  ├─ url.trb                    ConnectOptions.tryFrom, the redis://rediss:// URL
│  ├─ transport.trb               TCP or TLS, one type for both
│  ├─ connection.trb              RedisConnection: the handshake and readReply
│  ├─ reply.trb                   RespValue -> the type a command promises
│  ├─ commands/                   one file per family, each an extend RedisConnection
│  ├─ pipeline.trb, transaction.trb, pubsub.trb
│  ├─ pool.trb, client.trb        RedisPool, PooledConnection, RedisClient
├─ tests/
│  ├─ unit/                       the codec and URL parsing; no server needed
│  └─ integration/                against real Valkey and Redis (scripts/start-servers.sh)
├─ scripts/                      start-servers.sh, stop-servers.sh
├─ .github/workflows/            ci.yml (push, pull request), publish.yml (tag v*)
├─ .forgejo/workflows/           the same, for git.torb.dev
├─ LICENSE                       MIT
└─ .gitignore