Named connections
One connection registry shared by the cache, KV, rate limiter and broadcast — the <NAME>_REDIS_URL → REDIS_URL scheme, configuring each concern's server independently, and why enablement is explicit per concern.
Four subsystems can talk to a RESP server: the query cache, the durable KV (on its redis backend), the rate limiter, and the broadcast bus. Rather than each parsing its own env and opening its own socket, they share one connection registry — so you point each concern at the same server or a different one purely by configuration.
The shareable thing is the connection, not the contract: the cache uses tag sets, the limiter atomic Lua, broadcast pub/sub. The registry deals in connections; each consumer keeps its own semantics on top. (That's also why they stay separate concerns — forcing a rate limiter onto a plain get/set cache would break it.)
The <NAME>_REDIS_URL → REDIS_URL scheme
Every named connection resolves its own var first, then the shared default:
REDIS_URL the shared default for every named connection
CACHE_REDIS_URL the cache's own server (overrides REDIS_URL for the cache)
KV_REDIS_URL the KV's own server
RATELIMIT_REDIS_URL the rate limiter's own server
BROADCAST_REDIS_URL the broadcast bus's own serverSo one server for everything is just REDIS_URL; splitting a concern onto its own server is one extra var. Each also has a matching <NAME>_REDIS_DRIVER (resp default, or http for Upstash REST) and <NAME>_REDIS_TOKEN, falling back to REDIS_DRIVER / REDIS_TOKEN / UPSTASH_REDIS_REST_TOKEN.
Why
_REDIS_when Voltro supports Valkey / KeyDB / Dragonfly / Upstash too? Because the URL value isredis://…for all of them — there is novalkey://orresp://scheme; they all speak the Redis wire protocol._REDIS_URLnames the protocol, not the vendor, andREDIS_URLis the de-facto env var every PaaS injects. The<NAME>_REDIS_URLvar is the connection for a concern's redis backend specifically — the KV ondatabaseuses your SQL connection, and broadcast on NATS usesBROADCAST_URL/NATS_URL.
Enablement is explicit per concern — a shared REDIS_URL is NOT a master switch
Setting REDIS_URL does not silently turn every concern on. Each concern has its own on/off selector; the URL is only the connection detail, read once you've opted in:
| Concern | Enablement selector | Reads a redis url only when |
|---|---|---|
| Cache | CACHE_BACKEND=memory|redis (default memory) |
= redis |
| KV | KV_BACKEND=database|redis|memory (default database) |
= redis |
| Rate limiter | store: 'memory' | 'postgres' | 'redis' (plugin option) |
redis |
| Broadcast | opt-in via BROADCAST_URL / BROADCAST_REDIS_URL / BROADCAST_PROVIDER / the connection option |
explicitly opted in |
So REDIS_URL set for the cache leaves the KV on database and broadcast on its in-process memory bus. You enable each deliberately. (Broadcast is the one that used to infer "on" from any url; it no longer does — see broadcast.)
The registry API
Inside an Effect program, RedisConnections hands out pooled clients by name:
import { RedisConnections, layerConnections } from '@voltro/kv/connection'
import { Effect } from 'effect'
const program = Effect.gen(function* () {
const connections = yield* RedisConnections
const client = yield* connections.client('cache') // pooled RESP command client
// …get / set / eval / zrange / scan / del …
})
program.pipe(Effect.provide(layerConnections))client(name)opens (once, pooled — one socket per name for the layer's lifetime) and returns the RESP command client. Concurrent first-callers collapse to a single connect; the socket is closed on shutdown.config(name)resolves just theRedisConnectionConfig(url/driver/token) without opening it — for consumers that build their own client, e.g. a dedicated pub/sub connection over raw ioredis (which the REST driver can't do anyway).
A one-off resolve without the service is connectionConfig(name):
import { connectionConfig } from '@voltro/kv/connection'
const cfg = connectionConfig('broadcast') // Effect<RedisConnectionConfig, ConfigError>Adding a new named connection
There is no registry code to change for a new connection — the scheme is convention-based. A new consumer just picks a name and calls connections.client('<name>'); it automatically reads <NAME>_REDIS_URL → REDIS_URL. You only write code when the concern needs its own contract (atomic Lua, pub/sub) rather than plain get/set — then resolve config(name) and build the client shape you need.
Package
The registry ships in @voltro/kv, importable narrowly as @voltro/kv/connection. Exports: RedisConnections (the service), layerConnections (the pooled registry layer), connectionConfig (env resolution), connect (build a client from a config), commandRetry (the shared bounded retry), and the RespClient / RedisConnectionConfig types. It consolidates the RESP client that used to be duplicated across @voltro/cache and @voltro/plugin-ratelimit.