This document defines the public contract for src/redis/redis.lua.
All operations are async and must be called within lev.run().
redis.connect(config) accepts an optional config that is either:
String form: "<host>:<port>" or "<host>:<port>/<db>"
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".
host must be a non-empty string.
port must be an integer in range 1..65535.
db when set must be a non-negative integer.
timeout when set must be a positive number.
auth when set must be a table with non-empty string fields user and pass.
Unknown/extra table fields are ignored by the Redis client.
Invalid configs return nil, err with a clear validation error message.
connect() returns a client table with these fields and methods:
| Member | Description |
|---|---|
cfg | Normalized 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:
On success: decoded Lua value (see RESP type mapping below).
Non-existent key (NULL bulk string): nil, "not found".
Redis error: nil, error_message.
Network/protocol error: nil, error_message.
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:
Success: { value }
NULL bulk string: { nil }
Redis error: { nil, error_message }
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 prefix | RESP type | Lua type | Notes |
|---|---|---|---|
+ | Simple string | string | e.g. "OK", "PONG" |
- | Error | nil, string | Error text returned as second value |
: | Integer | number | |
$ | Bulk string | string | NULL bulk string → nil, "not found" |
* | Array | table | Nested arrays supported; NULL array → nil, "not found" |
The module maintains a connection pool keyed by host:port/db[+ssl].
close() returns the socket to the pool (default behavior).
close(true) force-closes the socket.
connect() checks the pool first. Connections idle less than 5 seconds
are reused immediately; older connections are validated with PING
before reuse. Failed validation discards the connection.
Pool size is controlled by REDIS_CLIENT_POOL_SIZE environment
variable (default 20).
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)