Redis Client Contract

Overview

This document defines the public contract for src/redis/redis.lua.

All operations are async and must be called within lev.run().

Connection config

redis.connect(config) accepts an optional config that is either:

  1. String form: "<host>:<port>" or "<host>:<port>/<db>"

  2. Table form:

{
	host = "127.0.0.1", -- required
	port = 6379, -- optional, defaults to 6379
	db = 13, -- optional
	timeout = 2, -- optional socket timeout
	ssl = true, -- optional TLS mode
	auth = { user = "u", pass = "p" }, -- optional AUTH
	-- extra fields are ignored by redis client; callers may keep module-specific metadata here
}

When config is nil, defaults to "127.0.0.1:6379".

Validation rules

Invalid configs return nil, err with a clear validation error message.

Client object

connect() returns a client table with these fields and methods:

MemberDescription
cfgNormalized config table (read-only by convention)
cmd(...)Execute a single Redis command
pipeline(commands)Execute multiple commands in one round-trip
read()Read one raw RESP value
close(no_keepalive)Close or pool the connection

client:cmd(...)

Executes a Redis command. Arguments are the command name followed by its arguments (all converted to strings).

local val, err = client:cmd("SET", "key", "value")  -- val = "OK"
local val, err = client:cmd("GET", "key")            -- val = "value"
local val, err = client:cmd("GET", "missing")        -- val = nil, err = "not found"
local val, err = client:cmd("INCR", "counter")       -- val = 42 (number)

Returns:

client:pipeline(commands)

Sends all commands in a single round-trip and reads all responses. Each entry in commands is an array of arguments (same as cmd varargs).

local results, err = client:pipeline({
	{ "SET", "k1", "v1" },
	{ "SET", "k2", "v2" },
	{ "GET", "k1" },
	{ "GET", "missing" },
	{ "INCR", "k1" },       -- type error: k1 is a string
})
-- results[1] = { "OK" }
-- results[2] = { "OK" }
-- results[3] = { "v1" }
-- results[4] = { nil }                  -- NULL → single nil element
-- results[5] = { nil, "ERR ..." }       -- per-command Redis error

Returns an array of result tables in command order:

If a network error occurs mid-read, the entire call returns nil, err. Empty or nil input returns {}.

client:read()

Reads one raw RESP response. Returns { type = "...", value = ... } or nil, err. Useful for advanced scenarios (e.g. pub/sub message reading) where you need the RESP type tag.

client:close(no_keepalive)

Closes the connection. By default the underlying socket is returned to the connection pool for reuse. Pass true to force-close the socket. The socket is also force-closed when the pool is at capacity.

Always returns true.

RESP type mapping

RESP prefixRESP typeLua typeNotes
+Simple stringstringe.g. "OK", "PONG"
-Errornil, stringError text returned as second value
:Integernumber
$Bulk stringstringNULL bulk string → nil, "not found"
*ArraytableNested arrays supported; NULL array → nil, "not found"

Connection pooling

The module maintains a connection pool keyed by host:port/db[+ssl].

Usage examples

local lev = require("lev")
local redis = require("redis")

lev.run(function()
	-- Default connection (127.0.0.1:6379)
	local client, err = redis.connect()
	if not client then error(err) end

	client:cmd("SET", "greeting", "hello")
	local val = client:cmd("GET", "greeting")  -- "hello"

	-- Pipeline
	local results = client:pipeline({
		{ "SET", "a", "1" },
		{ "SET", "b", "2" },
		{ "MGET", "a", "b" },
	})
	-- results[3][1] = { "1", "2" }

	client:close()  -- return to pool

	-- Authenticated TLS connection to specific db
	local secure, err = redis.connect({
		host = "redis.example.com",
		port = 6380,
		ssl = true,
		auth = { user = "default", pass = "secret" },
		db = 3,
		timeout = 5,
	})
	if secure then
		secure:cmd("SET", "x", "y")
		secure:close(true)  -- force-close, skip pool
	end
end)