atproto libraries implementation in ocaml
1# AT Protocol OCaml Libraries 2 3A comprehensive implementation of the [AT Protocol](https://atproto.com/) (Authenticated Transfer Protocol) in OCaml. This library suite enables developers to build decentralized social networking applications using OCaml's strong type system and functional programming paradigm. 4 5## Features 6 7- **Complete AT Protocol support** - Syntax validation, cryptography, repositories, identity resolution, and more 8- **Runtime-agnostic I/O** - Uses OCaml 5.4 algebraic effects for pluggable async runtimes (eio, lwt, etc.) 9- **Fully tested** - Passes all 272 tests from the official [atproto-interop-tests](https://github.com/bluesky-social/atproto-interop-tests) 10- **Modular design** - 11 independent packages, use only what you need 11- **Pure OCaml** - No external processes or services required for core functionality 12 13## Installation 14 15### From opam (when published) 16 17```bash 18opam install atproto 19``` 20 21### From source 22 23```bash 24git clone https://github.com/gdiazlo/atproto.git 25cd atproto 26opam install . --deps-only 27dune build 28``` 29 30## Package Overview 31 32The library is organized into layered packages following the AT Protocol architecture: 33 34### Foundation Layer 35 36| Package | Description | 37|---------|-------------| 38| `atproto-multibase` | Base encoding (base32-sortable, base58btc) | 39| `atproto-syntax` | Identifier parsing: handles, DIDs, NSIDs, TIDs, AT-URIs | 40| `atproto-crypto` | P-256/K-256 elliptic curves, did:key, JWT signing | 41 42### Data Layer 43 44| Package | Description | 45|---------|-------------| 46| `atproto-ipld` | DAG-CBOR encoding, CIDs, CAR files, blobs | 47| `atproto-mst` | Merkle Search Tree for content-addressed storage | 48| `atproto-repo` | Repository operations, commits, record management | 49 50### Identity Layer 51 52| Package | Description | 53|---------|-------------| 54| `atproto-identity` | DID resolution (did:plc, did:web), handle resolution | 55 56### Network Layer 57 58| Package | Description | 59|---------|-------------| 60| `atproto-effects` | I/O effect types for runtime abstraction | 61| `atproto-xrpc` | XRPC HTTP API client and server | 62| `atproto-sync` | Firehose event streams, repository sync | 63 64### Application Layer 65 66| Package | Description | 67|---------|-------------| 68| `atproto-lexicon` | Lexicon schema parsing and validation | 69| `atproto-api` | High-level client API, rich text facets | 70 71## Quick Start 72 73### Parsing AT Protocol Identifiers 74 75```ocaml 76open Atproto_syntax 77 78(* Parse a handle *) 79let handle = Handle.of_string "alice.bsky.social" |> Result.get_ok 80let () = assert (Handle.to_string handle = "alice.bsky.social") 81 82(* Parse a DID *) 83let did = Did.of_string "did:plc:z72i7hdynmk6r22z27h6tvur" |> Result.get_ok 84let () = assert (Did.method_ did = "plc") 85 86(* Parse an AT-URI *) 87let uri = At_uri.of_string "at://did:plc:xyz/app.bsky.feed.post/abc123" |> Result.get_ok 88let () = assert (At_uri.collection uri = Some "app.bsky.feed.post") 89 90(* Generate a TID (timestamp-based identifier) *) 91let tid = Tid.make () 92let () = Printf.printf "New TID: %s\n" (Tid.to_string tid) 93``` 94 95### Working with Cryptography 96 97```ocaml 98open Atproto_crypto 99 100(* Generate a P-256 key pair *) 101let keypair = P256.generate () 102 103(* Sign data *) 104let data = Bytes.of_string "Hello, AT Protocol!" 105let signature = P256.sign keypair.private_key data 106 107(* Verify signature *) 108let valid = P256.verify keypair.public_key data signature 109let () = assert valid 110 111(* Encode public key as did:key *) 112let did_key = Did_key.encode_p256 keypair.public_key 113let () = Printf.printf "DID: %s\n" did_key 114``` 115 116### DAG-CBOR and CIDs 117 118```ocaml 119open Atproto_ipld 120 121(* Create a record *) 122let record = Dag_cbor.Map [ 123 ("$type", Dag_cbor.String "app.bsky.feed.post"); 124 ("text", Dag_cbor.String "Hello from OCaml!"); 125 ("createdAt", Dag_cbor.String "2024-01-01T00:00:00.000Z"); 126] 127 128(* Encode to DAG-CBOR *) 129let bytes = Dag_cbor.encode record 130 131(* Compute CID *) 132let cid = Cid.of_dag_cbor bytes 133let () = Printf.printf "CID: %s\n" (Cid.to_string cid) 134``` 135 136### Firehose Event Stream 137 138```ocaml 139open Atproto_sync 140open Atproto_effects 141 142(* Define an effect handler for real I/O *) 143let run_with_eio f = 144 (* Implementation depends on your async runtime *) 145 ... 146 147(* Subscribe to the firehose *) 148let () = run_with_eio (fun () -> 149 let config = Firehose.{ 150 endpoint = "wss://bsky.network"; 151 cursor = None; 152 } in 153 Firehose.subscribe config (fun event -> 154 match event with 155 | Firehose.Commit { repo; ops; _ } -> 156 Printf.printf "Commit from %s with %d ops\n" repo (List.length ops) 157 | Firehose.Handle { did; handle } -> 158 Printf.printf "Handle update: %s -> %s\n" did handle 159 | _ -> () 160 ) 161) 162``` 163 164### Identity Resolution 165 166```ocaml 167open Atproto_identity 168open Atproto_effects 169 170(* Resolve a DID to get the DID document *) 171let () = run_with_effects (fun () -> 172 match Did_resolver.resolve "did:plc:z72i7hdynmk6r22z27h6tvur" with 173 | Ok doc -> 174 Printf.printf "Handle: %s\n" (Option.value ~default:"none" doc.also_known_as) 175 | Error e -> 176 Printf.printf "Resolution failed: %s\n" e 177) 178 179(* Resolve a handle to get the DID *) 180let () = run_with_effects (fun () -> 181 match Handle_resolver.resolve "bsky.app" with 182 | Ok did -> Printf.printf "DID: %s\n" did 183 | Error e -> Printf.printf "Resolution failed: %s\n" e 184) 185``` 186 187## Effects System 188 189The libraries use OCaml 5.4 algebraic effects for I/O operations, making them runtime-agnostic. You provide effect handlers for: 190 191- `Http_request` / `Http_get` - HTTP requests 192- `Dns_txt` / `Dns_a` - DNS lookups 193- `Ws_connect` / `Ws_recv` / `Ws_close` - WebSocket operations 194- `Now` / `Sleep` - Time operations 195- `Random_bytes` - Cryptographic randomness 196 197Example handler with eio: 198 199```ocaml 200open Atproto_effects.Effects 201 202let run_with_eio ~env f = 203 let open Effect.Deep in 204 try_with f () { 205 effc = fun (type a) (eff : a Effect.t) -> 206 match eff with 207 | Http_get { url } -> Some (fun (k : (a, _) continuation) -> 208 let response = Eio_client.get ~env url in 209 continue k (Ok response)) 210 | Now -> Some (fun k -> 211 continue k (Ptime_clock.now ())) 212 | _ -> None 213 } 214``` 215 216## Running Tests 217 218```bash 219# Run all tests 220dune runtest 221 222# Run tests for a specific package 223dune runtest test/syntax 224 225# Run with verbose output 226dune runtest --force --verbose 227``` 228 229## Examples 230 231See the `examples/` directory for complete examples: 232 233- **firehose_demo** - Connect to the Bluesky firehose and print events 234 235```bash 236dune exec examples/firehose_demo/firehose_demo.exe 237``` 238 239## Requirements 240 241- OCaml >= 5.1 (5.4 recommended for full effects support) 242- dune >= 3.20 243 244### Dependencies 245 246- `mirage-crypto-ec` - Elliptic curve cryptography 247- `digestif` - SHA-256 hashing 248- `zarith` - Big integers for low-S normalization 249- `cbor` - CBOR encoding (wrapped for DAG-CBOR) 250- `yojson` - JSON parsing 251- `uri` - URI handling 252- `ptime` - Time handling 253 254## Architecture 255 256``` 257 +------------------+ 258 | atproto-api | High-level client 259 +--------+---------+ 260 | 261 +--------------------+--------------------+ 262 | | | 263+-------v-------+ +-------v-------+ +-------v-------+ 264| atproto-xrpc | |atproto-identity| | atproto-sync | 265+-------+-------+ +-------+-------+ +-------+-------+ 266 | | | 267 +--------------------+--------------------+ 268 | 269 +--------v---------+ 270 | atproto-effects | I/O abstraction 271 +------------------+ 272 | 273 +--------------------+--------------------+ 274 | | | 275+-------v-------+ +-------v-------+ +-------v-------+ 276| atproto-repo | |atproto-lexicon| | | 277+-------+-------+ +---------------+ | | 278 | | | 279+-------v-------+ | | 280| atproto-mst | | | 281+-------+-------+ | | 282 | | | 283+-------v-------+ | | 284| atproto-ipld | | | 285+-------+-------+ | | 286 | +---------------+ | | 287 +----------->| atproto-crypto|<---+ | 288 | +-------+-------+ | 289 | | | 290+-------v-------+ +-------v-------+ | 291|atproto-syntax |<---+atproto-multibase|<-----------------+ 292+---------------+ +---------------+ 293``` 294 295## License 296 297ISC License - see [LICENSE](LICENSE) file. 298 299## Contributing 300 301Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. 302 303## Resources 304 305- [AT Protocol Specification](https://atproto.com/specs) 306- [Bluesky Documentation](https://docs.bsky.app/) 307- [AT Protocol Interop Tests](https://github.com/bluesky-social/atproto-interop-tests) 308- [Reference Implementation (TypeScript)](https://github.com/bluesky-social/atproto) 309 310## Status 311 312This implementation is feature-complete for core AT Protocol functionality. It passes all official interoperability tests. However, it has not yet been extensively tested against production AT Protocol infrastructure.