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_URLREDIS_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 server

So 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 is redis://… for all of them — there is no valkey:// or resp:// scheme; they all speak the Redis wire protocol. _REDIS_URL names the protocol, not the vendor, and REDIS_URL is the de-facto env var every PaaS injects. The <NAME>_REDIS_URL var is the connection for a concern's redis backend specifically — the KV on database uses your SQL connection, and broadcast on NATS uses BROADCAST_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 the RedisConnectionConfig (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_URLREDIS_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.