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.