{"id":50510614,"url":"https://github.com/ecyrbe/lean-redis","last_synced_at":"2026-06-04T22:00:36.483Z","repository":{"id":361580928,"uuid":"1252794587","full_name":"ecyrbe/lean-redis","owner":"ecyrbe","description":"full feature async redis client for lean 4","archived":false,"fork":false,"pushed_at":"2026-06-02T18:00:12.000Z","size":191,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T20:25:22.186Z","etag":null,"topics":["lean4","redis"],"latest_commit_sha":null,"homepage":"https://ecyrbe.github.io/lean-redis/docs/","language":"Lean","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ecyrbe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-28T22:00:32.000Z","updated_at":"2026-06-02T18:00:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ecyrbe/lean-redis","commit_stats":null,"previous_names":["ecyrbe/lean-redis"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ecyrbe/lean-redis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Flean-redis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Flean-redis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Flean-redis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Flean-redis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ecyrbe","download_url":"https://codeload.github.com/ecyrbe/lean-redis/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecyrbe%2Flean-redis/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33878990,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lean4","redis"],"created_at":"2026-06-02T20:00:34.905Z","updated_at":"2026-06-03T21:00:44.837Z","avatar_url":"https://github.com/ecyrbe.png","language":"Lean","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\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\u003e\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` -\u003e `Connection.Manager` -\u003e `Connection.Runtime` -\u003e 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 generic, 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- 🔑 Generic: `DEL`, `EXISTS`, `EXPIRE`, `TTL`, `KEYS`, `TYPE`, `SCAN`, `SORT`, `RENAME`, `COPY`, 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| Generic commands | Yes | key lifecycle, lookup, and server-side operations |\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 =\u003e 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 =\u003e\n      ref.set (replies.extract 1 replies.size)\n      pure reply\n  | none =\u003e\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- `LeanRedis/Command/Generic.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- `LeanRedis/Client/Generic.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, generic, 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","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecyrbe%2Flean-redis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fecyrbe%2Flean-redis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecyrbe%2Flean-redis/lists"}