LeanRedis

Lean Lake Version Redis License

Async Redis client library for Lean 4.

Typed commands, RESP2/RESP3 support, native async TCP, and a design built for explicit state transitions and scripted testability.

Highlights

  • ๐Ÿš€ Async-only public client API
  • ๐Ÿ”Œ Native Lean TCP transport built on Std.Internal.IO.Async
  • ๐Ÿง  RESP2 and RESP3 parsing, encoding, and protocol fallback
  • ๐Ÿงฑ Clear layering: Client -> Connection.Manager -> Connection.Runtime -> RESP codec
  • ๐Ÿ”„ Opt-in background reconnect with fixed-interval or exponential backoff strategies
  • ๐Ÿ“ฃ Async connection lifecycle event callbacks for disconnect and reconnect logging
  • ๐Ÿงช Transport abstraction makes mocked and scripted transports easy to use in tests
  • ๐Ÿ—‚๏ธ Typed command families for strings, hashes, lists, sets, and sorted sets
  • ๐Ÿงช Scripted tests for protocol, transport, connection, and typed command decoding
  • ๐Ÿ› ๏ธ Modular internal layout split by command family for easier review and maintenance

Supported Features

Core:

  • RESP parser and encoder
  • RESP2 / RESP3 bootstrap negotiation
  • default TCP transport
  • connection bootstrap and opt-in background reconnect
  • async client lifecycle, reconnect events, and connection state inspection

Command families:

  • ๐Ÿ” Connection: AUTH, PING, SELECT
  • ๐Ÿ“ Strings: GET, SET, MGET, MSET, INCR, DECR, GETEX, and related commands
  • ๐Ÿงพ Hashes: HGET, HSET, HMGET, HMSET, HGETALL, HSCAN, and related commands
  • ๐Ÿ“š Lists: LPUSH, RPUSH, LPOP, RPOP, LRANGE, LPOS, and related commands
  • ๐Ÿงฉ Sets: SADD, SREM, SMEMBERS, SINTER, SUNION, SSCAN, and related commands
  • ๐Ÿ“ˆ Sorted sets: ZADD, ZSCORE, ZRANGE, ZINTER, ZUNION, ZSCAN, and related commands

Current non-goals for v1:

  • sync API
  • blocking Redis command variants
  • pub/sub mode
  • pipelines / transactions
  • cluster / sentinel support
  • TLS transport

Feature Snapshot

AreaStatusNotes
RESP2 supportYesparser, encoder, bootstrap fallback
RESP3 supportYesparser, encoder, typed reply handling
Async client APIYespublic API is async-only
Native TCP transportYesbuilt on Std.Internal.IO.Async
Mockable custom transportsYestransport is a typeclass over the concrete handle type
Connection bootstrapYesauth, HELLO negotiation, DB select
Background reconnectYesopt-in client-owned reconnect worker with pluggable strategies
Connection event callbacksYesasync handlers, fire-and-forget delivery
String commandsYesmainstream v1 coverage
Hash commandsYesincludes HSCAN
List commandsYesnon-blocking mainstream coverage
Set commandsYesincludes SSCAN
Sorted set commandsYesincludes ZSCAN
Scripted transport testsYesprotocol, runtime, manager, client
Pipelines / transactionsNonot part of v1
Pub/SubNonot part of v1
TLSNointended as future extension
Cluster / SentinelNonot part of v1

Requirements

  • Lean 4.30.0
  • Lake

Toolchain is pinned in lean-toolchain.

Installation

This repository is currently install-from-source.

  1. Clone the repository.
  2. Build the project:
lake build
  1. Build the test target:
lake build LeanRedisTest

Quick Start

import LeanRedis

open LeanRedis
open Std.Internal.IO.Async

def example : Async (Option String) := do
  let client <- Client.newDefault {
    endpoint := { host := "127.0.0.1", port := 6379 }
    reconnectStrategy := .exponentialBackoff {}
  }
  let _ <- client.connect
  let _ <- client.set "greeting" "hello"
  client.get "greeting"

Examples

Basic connection commands:

def pingExample : Async (Option String) := do
  let client <- LeanRedis.Client.newDefault {
    endpoint := { host := "127.0.0.1", port := 6379 }
  }
  let _ <- client.connect
  client.ping

Reconnect and event callbacks:

def reconnectingExample : Async Unit := do
  let client <- LeanRedis.Client.newDefault {
    endpoint := { host := "127.0.0.1", port := 6379 }
    reconnectStrategy := .exponentialBackoff {
      baseDelayMs := 100
      maxDelayMs := 5_000
      jitter := true
    }
  }
  let _sub <- client.onEvent fun event => do
    IO.println s!"redis event: {repr event}"
  let _ <- client.connect
  pure ()

String operations:

def stringExample : Async (Option String) := do
  let client <- LeanRedis.Client.newDefault {
    endpoint := { host := "127.0.0.1", port := 6379 }
  }
  let _ <- client.connect
  let _ <- client.set "counter" "1"
  let _ <- client.incr "counter"
  client.get "counter"

Hash operations:

def hashExample : Async (Array (String ร— String)) := do
  let client <- LeanRedis.Client.newDefault {
    endpoint := { host := "127.0.0.1", port := 6379 }
  }
  let _ <- client.connect
  let _ <- client.hSet "user:1" #[("name", "alice"), ("role", "admin")]
  client.hGetAll "user:1"

Sorted set operations:

def sortedSetExample : Async (Array LeanRedis.SortedSetEntry) := do
  let client <- LeanRedis.Client.newDefault {
    endpoint := { host := "127.0.0.1", port := 6379 }
  }
  let _ <- client.connect
  let _ <- client.zAdd "scores" #[
    { score := "10", member := "alice" },
    { score := "20", member := "bob" }
  ]
  client.zRangeWithScores "scores" 0 (-1)

Mocked transport for tests:

import LeanRedis

open LeanRedis
open Std.Internal.IO.Async

structure FakeTransport where
  replies : IO.Ref (Array ByteArray)

private def popReply (ref : IO.Ref (Array ByteArray)) : IO ByteArray := do
  let replies <- ref.get
  match replies[0]? with
  | some reply =>
      ref.set (replies.extract 1 replies.size)
      pure reply
  | none =>
      pure ByteArray.empty

instance : Transport.Transport FakeTransport where
  connect _ := do
    let replies <- IO.mkRef #["+PONG\r\n".toUTF8]
    pure { replies }

  recv transport _ := do
    let bytes <- popReply transport.replies
    if bytes.isEmpty then
      pure { bytes := ByteArray.empty, disconnect? := some .closedByPeer }
    else
      pure { bytes }

  send _ _ := pure ()
  close _ := pure ()

def pingWithMock : Async (Option String) := do
  let client : Client FakeTransport <- Client.new {
    endpoint := { host := "mock", port := 0 }
  }
  let _ <- client.connect
  client.ping

This is the same mechanism used by the library test suite for scripted bootstrap, partial replies, and disconnect scenarios.

API Overview

Main public entry points:

  • Client.new
  • Client.newDefault
  • Client.connect
  • Client.disconnect
  • Client.isConnected
  • Client.connectionStatus
  • Client.onEvent
  • Client.offEvent
  • Client.currentState

Design notes:

  • new* allocates client state only
  • new* is IO because it allocates mutable client state, but it does not open a connection
  • connect performs transport setup and Redis bootstrap
  • commands fail fast while disconnected or reconnecting
  • remote disconnects trigger background reconnect only when reconnectStrategy is enabled
  • onEvent and offEvent are lightweight IO registration calls; callback delivery is fire-and-forget
  • command methods are typed and async
  • command families are split into dedicated modules internally

Testing

Build the test target with:

lake build LeanRedisTest

The test suite covers:

  • RESP parser basics
  • incremental parsing across fragmented inputs
  • command encoding
  • bootstrap encoding and negotiation behavior
  • scripted transport behavior
  • connection bootstrap and reconnect scenarios
  • typed client decoding for all implemented command families
  • runtime-level scripted partial-read and disconnect handling

Tests live under Test/ and are primarily Lean-native #guard_msgs / #eval checks.

Project Layout

Public modules:

  • LeanRedis
  • LeanRedis.Command
  • LeanRedis.Client

Internal command layout:

  • LeanRedis/Command/Base.lean
  • LeanRedis/Command/Connection.lean
  • LeanRedis/Command/String.lean
  • LeanRedis/Command/Hash.lean
  • LeanRedis/Command/List.lean
  • LeanRedis/Command/Set.lean
  • LeanRedis/Command/SortedSet.lean

Internal client layout:

  • LeanRedis/Client/Internal.lean
  • LeanRedis/Client/Connection.lean
  • LeanRedis/Client/String.lean
  • LeanRedis/Client/Hash.lean
  • LeanRedis/Client/List.lean
  • LeanRedis/Client/Set.lean
  • LeanRedis/Client/SortedSet.lean

Status

Implemented and verified:

  • architecture and module boundaries
  • RESP protocol support
  • transport abstraction and default TCP transport
  • connection management
  • async public client API
  • connection, string, hash, list, set, and sorted-set command families

Tracking details live in docs/features/TODO.md.

License

MIT License. See LICENSE.