packages/redis/src/resp.nu — RESP (REdis Serialization Protocol, RESP2) codec, pure and I/O-free so it can be unit-tested without a server.
A request is an array of bulk strings: *<n>\r\n ( $<len>\r\n<arg>\r\n ) x n
A reply is one of: simple string (+), error (-), integer (:), bulk string ($, with $-1 = nil) or array (, with -1 = nil). Arrays nest arbitrarily (pub/sub, XRANGE, COMMAND, …), so a reply is decoded into a flat node arena — a Vec RNode where array nodes hold the pool indices of their children. One Vec plus one String per node: no nested Vecs in structs beyond the child-index list. A reply is a plain value: dropping it drops the arena.
( resp_encode args ) → ( Vec u ) request bytes ( resp_parse buf start ) → RespParse one reply (or Incomplete) ( resp_reply_free reply ) → v early release (optional)
Node walking (power users): resp_reply_root / resp_node_kind / resp_node_int / resp_node_str / resp_node_is_nil / resp_node_arr_len / resp_node_arr_at
: RNode: RNode {
i kind
i ival // integer value; for arrays, the element count
String sval // string / error text (empty otherwise)
( Vec i ) kids // child node indices (only populated for arrays)
}
RNode.kind: 0 nil · 1 integer · 2 string (simple or bulk) · 3 error · 4 array
: RedisReply: RedisReply {
( Vec RNode ) nodes
i root
}
A decoded reply: the node arena plus the index of the root node.
: RespParse: RespParse {
i status
RedisReply reply
i consumed
}
Result of one parse attempt over a byte buffer. status: 0 ok · 1 incomplete (need more bytes) · 2 malformed consumed: bytes consumed from start when status == 0
: RespBuilder: RespBuilder {
( Vec RNode ) nodes
i status
i cur
}
Mutable parse state threaded through the recursive descent (an inout local, so the recursion shares one cursor / status / arena).
@ resp_encode ( Vec String ) args → ( Vec u )Encode a command — an array of bulk strings — into RESP request bytes.
@ resp_parse ( Vec u ) buf i start → RespParseParse one reply from buf starting at start. On Incomplete (status 1) the returned reply holds a (partial) arena; it is dropped with the RespParse.
@ resp_reply_free sink RedisReply r → vLet go of r now rather than at the end of its owner's scope.
@ resp_reply_root RedisReply r → i@ resp_node_kind RedisReply r i node → i@ resp_node_int RedisReply r i node → i@ resp_node_str RedisReply r i node → sBorrowed view of a string/error node's text (lifetime tied to r).
@ resp_node_is_nil RedisReply r i node → b@ resp_node_arr_len RedisReply r i node → i@ resp_node_arr_at RedisReply r i node i k → ipackages/redis/src/redis.nu — a pure-NURL Redis client. Speaks RESP2 over a plain libc TCP socket, or over the pure-NURL TLS client (rediss://) with no OpenSSL on the target. The RESP wire format lives in resp.nu; this file owns the connection, the request/reply round-trip and an ergonomic command surface (GET / SET / INCR / lists / hashes / sets / pub-sub …).
( redis_connect host port ) → !RedisConn RedisErr ( redis_connect_tls host port sni verify ) → !RedisConn RedisErr ( redis_auth conn user password ) → !v RedisErr ( redis_command conn args ) → !RedisReply RedisErr (raw) ( redis_get conn key ) / ( redis_set … ) … → typed helpers ( redis_close conn ) → v early release (optional)
Build arbitrary commands with the arg builder: : ( Vec String ) a ( redis_args ) ( redis_arg a SET ) ( redis_arg a key ) ( redis_arg a val ) : !RedisReply RedisErr r ( redis_command c a )
A RedisConn is a handle: every copy is the same connection, and the last owner closes it (the TLS session or the socket). Replies, strings, messages and argument vectors are plain values. Nothing here is released by hand; the *free functions and redisclose are optional early releases.
: | RedisErr: | RedisErr {
RedisConnFail // could not open the TCP socket
RedisTls // TLS upgrade failed (handshake / cert error)
RedisProtocol // malformed RESP from the server
RedisIo // socket read/write failure or unexpected EOF
RedisServerError // server returned a RESP error — see redis_last_error
RedisType // reply was not the type the helper expected
}
@ redis_err_name RedisErr e → s: RedisStr: RedisStr {
i found
String val
}
Optional bulk-string reply. found == 0 means the server answered nil (e.g. GET on a missing key); val is owned and empty in that case. A named struct rather than ?String so it round-trips through !T E.
@ redis_str_is_nil RedisStr s → b@ redis_str_val RedisStr s → s@ redis_str_free sink RedisStr s → vLet go of s now rather than at the end of its owner's scope.
: RedisMessage: RedisMessage {
i kind
String channel
String pattern
String payload
i count
}
A pub/sub frame. kind: 0 message · 1 subscribe · 2 unsubscribe · 3 pmessage · 4 psubscribe · 5 punsubscribe · -1 unrecognised. pattern is set only for the p* variants; count is the live subscription count carried by (un)subscribe confirmations.
@ redis_message_kind RedisMessage m → i@ redis_message_channel RedisMessage m → s@ redis_message_pattern RedisMessage m → s@ redis_message_payload RedisMessage m → s@ redis_message_count RedisMessage m → i@ redis_message_is_payload RedisMessage m → bTrue for an actual published message (message / pmessage), false for a subscribe/unsubscribe control frame.
@ redis_message_free sink RedisMessage m → vLet go of m now rather than at the end of its owner's scope.
: RedisConnImpl: RedisConnImpl {
i tls // 0 plaintext, 1 TLS
i raw // raw socket fd (plaintext reads / fd the TLS layer took over)
TcpConn tcp // plaintext transport (writes via tcp_write_all)
TlsConn tc // TLS transport (when tls = 1)
( Vec u ) rxbuf // bytes read but not yet parsed into a reply
String lasterr // last server error text
String host_name
i port_num
i db_index
}
% Drop RedisConnImpl { @ drop RedisConnImpl c → v { ( __r_close_transport . c tls . c tc . c raw ) } }The transport is the connection's raw resource: its last owner closes the TLS session or the socket, as redis_close did. The buffer and the strings go with the drop glue.
: RedisConn { s ctl }@ RedisConn_share RedisConn h → RedisConn@ RedisConn_drop sink RedisConn h → v@ redis_command RedisConn c__h ( Vec String ) args → !RedisReply RedisErrSend a command (array of bulk strings) and return its reply. A RESP error reply is surfaced as RedisServerError with the text in conn.lasterr. The caller keeps args and owns the returned reply.
@ redis_args → ( Vec String )@ redis_arg ( Vec String ) v s a → v@ redis_arg_i ( Vec String ) v i n → v@ redis_args_free sink ( Vec String ) v → vLet go of v now rather than at the end of its owner's scope.
@ redis_connect s host i port → !RedisConn RedisErr@ redis_connect_tls s host i port s server_name i verify → !RedisConn RedisErrverify != 0 → verify-full (chain + hostname); verify == 0 → encrypt only.
@ redis_close sink RedisConn c → vLet go of c now rather than at the end of its owner's scope (the last owner closes the connection).
@ redis_last_error RedisConn c__h → s@ redis_is_tls RedisConn c__h → b@ redis_auth RedisConn c__h s user s password → !v RedisErrAUTH: one-arg (password only) or two-arg (ACL user + password). Pass an empty user for the legacy single-argument form.
@ redis_select RedisConn c__h i db → !v RedisErr@ redis_ping RedisConn c__h → !b RedisErr@ redis_echo RedisConn c__h s msg → !RedisStr RedisErr@ redis_set RedisConn c__h s key s val → !v RedisErr@ redis_get RedisConn c__h s key → !RedisStr RedisErr@ redis_del RedisConn c__h s key → !i RedisErr@ redis_exists RedisConn c__h s key → !b RedisErr@ redis_incr RedisConn c__h s key → !i RedisErr@ redis_decr RedisConn c__h s key → !i RedisErr@ redis_incrby RedisConn c__h s key i n → !i RedisErr@ redis_expire RedisConn c__h s key i secs → !b RedisErr@ redis_ttl RedisConn c__h s key → !i RedisErr@ redis_keys RedisConn c__h s pattern → !( Vec String ) RedisErr@ redis_lpush RedisConn c__h s key s val → !i RedisErr@ redis_rpush RedisConn c__h s key s val → !i RedisErr@ redis_llen RedisConn c__h s key → !i RedisErr@ redis_lrange RedisConn c__h s key i start i stop → !( Vec String ) RedisErr@ redis_hset RedisConn c__h s key s field s val → !i RedisErr@ redis_hget RedisConn c__h s key s field → !RedisStr RedisErr@ redis_hgetall RedisConn c__h s key → !( Vec String ) RedisErrFlat [field, value, field, value, …] of the whole hash.
@ redis_sadd RedisConn c__h s key s member → !i RedisErr@ redis_smembers RedisConn c__h s key → !( Vec String ) RedisErr@ redis_publish RedisConn c__h s channel s msg → !i RedisErr@ redis_subscribe RedisConn c__h s channel → !RedisMessage RedisErrSubscribe to a channel (or pattern). Returns the subscription confirmation; thereafter call redis_next_message to receive published messages. While subscribed only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING are valid commands.
@ redis_unsubscribe RedisConn c__h s channel → !RedisMessage RedisErr@ redis_psubscribe RedisConn c__h s pattern → !RedisMessage RedisErr@ redis_punsubscribe RedisConn c__h s pattern → !RedisMessage RedisErr@ redis_next_message RedisConn c__h → !RedisMessage RedisErrBlock until the next pub/sub frame arrives, then decode it. Returns a message / pmessage for delivered payloads, or a (un)subscribe control frame; RedisIo on a closed connection.
@ redis_flushdb RedisConn c__h → !v RedisErrpackages/redis/src/main.nu — the redis command: a small redis-cli-style client built on the pure-NURL Redis client. Connects over RESP2 with an optional pure-NURL TLS upgrade (rediss://, no OpenSSL on the target), authenticates with AUTH, runs one command with -c or starts a REPL.
redis [flags] ["redis://[user:pass@]host:port/db"] -h HOST host (default $REDIS_HOST or 127.0.0.1) -p PORT port (default $REDIS_PORT or 6379) -a PASSWORD AUTH password (default $REDIS_PASSWORD) --user USER ACL username for AUTH (Redis 6+) -n DB SELECT database index (default 0) --tls connect over TLS (verify-full) --insecure with --tls / rediss://, skip certificate verification -c "CMD ARGS" run one command and exit; otherwise start a REPL A rediss:// URL implies --tls.
& c @ isatty i32 fd → i32: ConnInfo: ConnInfo {
String host
i port
String user
String password
i db
i tlsmode // 0 plain, 1 tls insecure, 2 tls verify-full
}
@ main → i