[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"NbdlihZsL1":3},"\u003Cdiv align=\"center\">\n\n# LeanRedis\n\n[![Lean](https://img.shields.io/badge/Lean-4.30.0-0f4c81)](https://lean-lang.org/)\n[![Lake](https://img.shields.io/badge/build-Lake-blue)](https://github.com/leanprover/lake)\n[![Version](https://img.shields.io/badge/version-0.1.0-2ea44f)](./lakefile.lean)\n[![Redis](https://img.shields.io/badge/Redis-6%20%7C%207%20%7C%208-red)](https://redis.io/)\n[![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)\n\nAsync Redis client library for Lean 4.\n\nTyped commands, RESP2/RESP3 support, native async TCP, and a design built for explicit state transitions and scripted testability.\n\n\u003C/div>\n\n## Highlights\n\n- 🚀 Async-only public client API\n- 🔌 Native Lean TCP transport built on `Std.Internal.IO.Async`\n- 🧠 RESP2 and RESP3 parsing, encoding, and protocol fallback\n- 🧱 Clear layering: `Client` -> `Connection.Manager` -> `Connection.Runtime` -> RESP codec\n- 🔄 Opt-in background reconnect with fixed-interval or exponential backoff strategies\n- 📣 Async connection lifecycle event callbacks for disconnect and reconnect logging\n- 🧪 Transport abstraction makes mocked and scripted transports easy to use in tests\n- 🗂️ Typed command families for strings, hashes, lists, sets, and sorted sets\n- 🧪 Scripted tests for protocol, transport, connection, and typed command decoding\n- 🛠️ Modular internal layout split by command family for easier review and maintenance\n\n## Supported Features\n\nCore:\n- RESP parser and encoder\n- RESP2 / RESP3 bootstrap negotiation\n- default TCP transport\n- connection bootstrap and opt-in background reconnect\n- async client lifecycle, reconnect events, and connection state inspection\n\nCommand families:\n- 🔐 Connection: `AUTH`, `PING`, `SELECT`\n- 📝 Strings: `GET`, `SET`, `MGET`, `MSET`, `INCR`, `DECR`, `GETEX`, and related commands\n- 🧾 Hashes: `HGET`, `HSET`, `HMGET`, `HMSET`, `HGETALL`, `HSCAN`, and related commands\n- 📚 Lists: `LPUSH`, `RPUSH`, `LPOP`, `RPOP`, `LRANGE`, `LPOS`, and related commands\n- 🧩 Sets: `SADD`, `SREM`, `SMEMBERS`, `SINTER`, `SUNION`, `SSCAN`, and related commands\n- 📈 Sorted sets: `ZADD`, `ZSCORE`, `ZRANGE`, `ZINTER`, `ZUNION`, `ZSCAN`, and related commands\n\nCurrent non-goals for v1:\n- sync API\n- blocking Redis command variants\n- pub/sub mode\n- pipelines / transactions\n- cluster / sentinel support\n- TLS transport\n\n## Feature Snapshot\n\n| Area | Status | Notes |\n| --- | --- | --- |\n| RESP2 support | Yes | parser, encoder, bootstrap fallback |\n| RESP3 support | Yes | parser, encoder, typed reply handling |\n| Async client API | Yes | public API is async-only |\n| Native TCP transport | Yes | built on `Std.Internal.IO.Async` |\n| Mockable custom transports | Yes | transport is a typeclass over the concrete handle type |\n| Connection bootstrap | Yes | auth, HELLO negotiation, DB select |\n| Background reconnect | Yes | opt-in client-owned reconnect worker with pluggable strategies |\n| Connection event callbacks | Yes | async handlers, fire-and-forget delivery |\n| String commands | Yes | mainstream v1 coverage |\n| Hash commands | Yes | includes `HSCAN` |\n| List commands | Yes | non-blocking mainstream coverage |\n| Set commands | Yes | includes `SSCAN` |\n| Sorted set commands | Yes | includes `ZSCAN` |\n| Scripted transport tests | Yes | protocol, runtime, manager, client |\n| Pipelines / transactions | No | not part of v1 |\n| Pub/Sub | No | not part of v1 |\n| TLS | No | intended as future extension |\n| Cluster / Sentinel | No | not part of v1 |\n\n## Requirements\n\n- Lean `4.30.0`\n- Lake\n\nToolchain is pinned in `lean-toolchain`.\n\n## Installation\n\nThis repository is currently install-from-source.\n\n1. Clone the repository.\n2. Build the project:\n\n```bash\nlake build\n```\n\n3. Build the test target:\n\n```bash\nlake build LeanRedisTest\n```\n\n## Quick Start\n\n```lean\nimport LeanRedis\n\nopen LeanRedis\nopen Std.Internal.IO.Async\n\ndef example : Async (Option String) := do\n  let client \u003C- Client.newDefault {\n    endpoint := { host := \"127.0.0.1\", port := 6379 }\n    reconnectStrategy := .exponentialBackoff {}\n  }\n  let _ \u003C- client.connect\n  let _ \u003C- client.set \"greeting\" \"hello\"\n  client.get \"greeting\"\n```\n\n## Examples\n\nBasic connection commands:\n\n```lean\ndef pingExample : Async (Option String) := do\n  let client \u003C- LeanRedis.Client.newDefault {\n    endpoint := { host := \"127.0.0.1\", port := 6379 }\n  }\n  let _ \u003C- client.connect\n  client.ping\n```\n\nReconnect and event callbacks:\n\n```lean\ndef reconnectingExample : Async Unit := do\n  let client \u003C- LeanRedis.Client.newDefault {\n    endpoint := { host := \"127.0.0.1\", port := 6379 }\n    reconnectStrategy := .exponentialBackoff {\n      baseDelayMs := 100\n      maxDelayMs := 5_000\n      jitter := true\n    }\n  }\n  let _sub \u003C- client.onEvent fun event => do\n    IO.println s!\"redis event: {repr event}\"\n  let _ \u003C- client.connect\n  pure ()\n```\n\nString operations:\n\n```lean\ndef stringExample : Async (Option String) := do\n  let client \u003C- LeanRedis.Client.newDefault {\n    endpoint := { host := \"127.0.0.1\", port := 6379 }\n  }\n  let _ \u003C- client.connect\n  let _ \u003C- client.set \"counter\" \"1\"\n  let _ \u003C- client.incr \"counter\"\n  client.get \"counter\"\n```\n\nHash operations:\n\n```lean\ndef hashExample : Async (Array (String × String)) := do\n  let client \u003C- LeanRedis.Client.newDefault {\n    endpoint := { host := \"127.0.0.1\", port := 6379 }\n  }\n  let _ \u003C- client.connect\n  let _ \u003C- client.hSet \"user:1\" #[(\"name\", \"alice\"), (\"role\", \"admin\")]\n  client.hGetAll \"user:1\"\n```\n\nSorted set operations:\n\n```lean\ndef sortedSetExample : Async (Array LeanRedis.SortedSetEntry) := do\n  let client \u003C- LeanRedis.Client.newDefault {\n    endpoint := { host := \"127.0.0.1\", port := 6379 }\n  }\n  let _ \u003C- client.connect\n  let _ \u003C- client.zAdd \"scores\" #[\n    { score := \"10\", member := \"alice\" },\n    { score := \"20\", member := \"bob\" }\n  ]\n  client.zRangeWithScores \"scores\" 0 (-1)\n```\n\nMocked transport for tests:\n\n```lean\nimport LeanRedis\n\nopen LeanRedis\nopen Std.Internal.IO.Async\n\nstructure FakeTransport where\n  replies : IO.Ref (Array ByteArray)\n\nprivate def popReply (ref : IO.Ref (Array ByteArray)) : IO ByteArray := do\n  let replies \u003C- ref.get\n  match replies[0]? with\n  | some reply =>\n      ref.set (replies.extract 1 replies.size)\n      pure reply\n  | none =>\n      pure ByteArray.empty\n\ninstance : Transport.Transport FakeTransport where\n  connect _ := do\n    let replies \u003C- IO.mkRef #[\"+PONG\\r\\n\".toUTF8]\n    pure { replies }\n\n  recv transport _ := do\n    let bytes \u003C- popReply transport.replies\n    if bytes.isEmpty then\n      pure { bytes := ByteArray.empty, disconnect? := some .closedByPeer }\n    else\n      pure { bytes }\n\n  send _ _ := pure ()\n  close _ := pure ()\n\ndef pingWithMock : Async (Option String) := do\n  let client : Client FakeTransport \u003C- Client.new {\n    endpoint := { host := \"mock\", port := 0 }\n  }\n  let _ \u003C- client.connect\n  client.ping\n```\n\nThis is the same mechanism used by the library test suite for scripted bootstrap, partial replies, and disconnect scenarios.\n\n## API Overview\n\nMain public entry points:\n\n- `Client.new`\n- `Client.newDefault`\n- `Client.connect`\n- `Client.disconnect`\n- `Client.isConnected`\n- `Client.connectionStatus`\n- `Client.onEvent`\n- `Client.offEvent`\n- `Client.currentState`\n\nDesign notes:\n\n- `new*` allocates client state only\n- `new*` is `IO` because it allocates mutable client state, but it does not open a connection\n- `connect` performs transport setup and Redis bootstrap\n- commands fail fast while disconnected or reconnecting\n- remote disconnects trigger background reconnect only when `reconnectStrategy` is enabled\n- `onEvent` and `offEvent` are lightweight `IO` registration calls; callback delivery is fire-and-forget\n- command methods are typed and async\n- command families are split into dedicated modules internally\n\n## Testing\n\nBuild the test target with:\n\n```bash\nlake build LeanRedisTest\n```\n\nThe test suite covers:\n\n- RESP parser basics\n- incremental parsing across fragmented inputs\n- command encoding\n- bootstrap encoding and negotiation behavior\n- scripted transport behavior\n- connection bootstrap and reconnect scenarios\n- typed client decoding for all implemented command families\n- runtime-level scripted partial-read and disconnect handling\n\nTests live under `Test/` and are primarily Lean-native `#guard_msgs` / `#eval` checks.\n\n## Project Layout\n\nPublic modules:\n\n- `LeanRedis`\n- `LeanRedis.Command`\n- `LeanRedis.Client`\n\nInternal command layout:\n\n- `LeanRedis/Command/Base.lean`\n- `LeanRedis/Command/Connection.lean`\n- `LeanRedis/Command/String.lean`\n- `LeanRedis/Command/Hash.lean`\n- `LeanRedis/Command/List.lean`\n- `LeanRedis/Command/Set.lean`\n- `LeanRedis/Command/SortedSet.lean`\n\nInternal client layout:\n\n- `LeanRedis/Client/Internal.lean`\n- `LeanRedis/Client/Connection.lean`\n- `LeanRedis/Client/String.lean`\n- `LeanRedis/Client/Hash.lean`\n- `LeanRedis/Client/List.lean`\n- `LeanRedis/Client/Set.lean`\n- `LeanRedis/Client/SortedSet.lean`\n\n## Status\n\nImplemented and verified:\n\n- architecture and module boundaries\n- RESP protocol support\n- transport abstraction and default TCP transport\n- connection management\n- async public client API\n- connection, string, hash, list, set, and sorted-set command families\n\nTracking details live in `docs/features/TODO.md`.\n\n## License\n\nMIT License. See [`LICENSE`](./LICENSE).\n",1780242000787]