ATProto Personal Data Server storage for OCaml
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

ocaml-linkedin: apply dune fmt

Pure formatting changes from `dune fmt`: doc comment placement moves
from above the binding to below it for `type`s, multi-line `match`
expressions collapse onto one line where they fit, and infix operator
applications pick up spaces (`Soup.($?)` -> `Soup.( $? )`). No
semantic changes.

+90 -59
+83 -59
README.md
··· 11 11 - Offline repository manipulation (backup, migration, inspection) 12 12 - CAR import/export for interoperability 13 13 14 - ## Storage Layout 14 + ## Installation 15 15 16 - ``` 17 - <repo>/ 18 - ├── pds.db # SQLite database 19 - │ ├── blocks table # CID → DAG-CBOR bytes 20 - │ ├── refs table # name → CID (branches) 21 - │ └── meta table # did, version, etc. 22 - └── blobs/ # Large binary data 23 - ├── ba/ # First 2 chars of CID 24 - │ └── bafyrei... # Full CID as filename 25 - └── ... 16 + Install with opam: 17 + 18 + ```sh 19 + $ opam install pds 26 20 ``` 27 21 28 - ## Installation 22 + If opam cannot find the package, it may not yet be released in the public 23 + `opam-repository`. Add the overlay repository, then install it: 29 24 30 - ``` 31 - opam install pds 25 + ```sh 26 + $ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git 27 + $ opam update 28 + $ opam install pds 32 29 ``` 33 30 34 31 ## Usage 32 + 33 + ### Create a repo, put and get records 35 34 36 35 ```ocaml 37 - (* Create a new repository *) 38 - let repo = 36 + let () = 37 + Eio_main.run @@ fun env -> 39 38 Eio.Switch.run @@ fun sw -> 40 - Pds.v ~sw (Eio.Path.(fs / "my-repo")) ~did:(Atp.Did.of_string_exn "did:web:example.com") 41 - in 39 + let fs = Eio.Stdenv.fs env in 40 + let repo = 41 + Pds.v ~sw Eio.Path.(fs / "my-repo") 42 + ~did:(Atp.Did.of_string_exn "did:web:example.com") 43 + in 44 + Pds.put repo 45 + ~collection:"app.bsky.feed.post" 46 + ~rkey:"abc123" 47 + record_bytes; 48 + (match Pds.find repo ~collection:"app.bsky.feed.post" ~rkey:"abc123" with 49 + | Some data -> Fmt.pr "record: %d bytes@." (String.length data) 50 + | None -> Fmt.pr "not found@."); 51 + Pds.close repo 52 + ``` 42 53 43 - (* Store records *) 44 - Pds.put repo ~collection:"app.bsky.feed.post" ~rkey:"abc123" record_bytes; 54 + ### List a collection 45 55 46 - (* Read records *) 47 - let data = Pds.find repo ~collection:"app.bsky.feed.post" ~rkey:"abc123" in 56 + ```ocaml 57 + let list_posts repo = 58 + Pds.list repo ~collection:"app.bsky.feed.post" 59 + |> List.iter (fun (rkey, cid) -> 60 + Fmt.pr "%s -> %a@." rkey Atp.Cid.pp cid) 61 + ``` 48 62 49 - (* List collection *) 50 - let records = Pds.list repo ~collection:"app.bsky.feed.post" in 63 + ### Blobs 51 64 52 - (* Store blobs *) 53 - let blob_ref = Pds.put_blob repo ~mime_type:"image/png" image_bytes in 54 - 55 - (* Export as CAR *) 56 - let car_data = Pds.export_car repo in 65 + ```ocaml 66 + let store_image repo image_bytes = 67 + Pds.put_blob repo ~mime_type:"image/png" image_bytes 68 + ``` 57 69 58 - (* Import from CAR *) 59 - let count = Pds.import_car repo car_data in 70 + ### Import / export 60 71 61 - (* Close *) 62 - Pds.close repo 72 + ```ocaml 73 + let backup repo = Pds.export_car repo 74 + let restore repo car = Pds.import_car repo car 63 75 ``` 64 76 65 77 ## API 66 78 67 79 ### Repository 68 80 69 - - `Pds.v ~sw path ~did` - Create a new repository 70 - - `Pds.open_ ~sw path` - Open an existing repository 71 - - `Pds.did t` - Get the repository's DID 72 - - `Pds.close t` - Close the repository 81 + - `Pds.v ~sw path ~did` -- create a new repository 82 + - `Pds.open_ ~sw path` -- open an existing repository 83 + - `Pds.did t` 84 + - `Pds.close t` 73 85 74 86 ### Records 75 87 76 - - `Pds.find t ~collection ~rkey` - Read a record 77 - - `Pds.put t ~collection ~rkey data` - Write a record 78 - - `Pds.delete t ~collection ~rkey` - Delete a record 79 - - `Pds.list t ~collection` - List records in a collection 88 + - `Pds.put t ~collection ~rkey data` 89 + - `Pds.find t ~collection ~rkey` -- returns `string option` 90 + - `Pds.delete t ~collection ~rkey` 91 + - `Pds.list t ~collection` -- returns `(rkey * cid) list` 80 92 81 93 ### Blobs 82 94 83 - - `Pds.put_blob t ~mime_type data` - Store a blob 84 - - `Pds.blob t cid` - Read a blob 95 + - `Pds.put_blob t ~mime_type data` -- returns `Atp.Blob_ref.t` 96 + - `Pds.blob t cid` -- returns `string option` 85 97 86 - ### State 98 + ### Repository state 99 + 100 + - `Pds.head t` -- current commit CID 101 + - `Pds.set_head t cid` 102 + - `Pds.checkout t` -- MST at HEAD 103 + - `Pds.blockstore t` -- underlying blockstore 104 + 105 + ### Named refs 87 106 88 - - `Pds.head t` - Get current commit CID 89 - - `Pds.set_head t cid` - Update HEAD to a given CID 90 - - `Pds.checkout t` - Get MST at HEAD 91 - - `Pds.blockstore t` - Access the underlying blockstore 107 + - `Pds.ref t name` / `set_ref t name cid` / `delete_ref t name` 108 + - `Pds.list_refs t` 92 109 93 - ### Named Refs 110 + ### CAR 94 111 95 - - `Pds.ref t name` - Get CID for a named ref 96 - - `Pds.set_ref t name cid` - Set a named ref 97 - - `Pds.delete_ref t name` - Delete a named ref 98 - - `Pds.list_refs t` - List all named refs 112 + - `Pds.import_car t data` -- returns number of imported blocks 113 + - `Pds.export_car t` 99 114 100 - ### Import/Export 115 + ## Storage layout 101 116 102 - - `Pds.import_car t data` - Import blocks from CAR 103 - - `Pds.export_car t` - Export repository as CAR 117 + ``` 118 + <repo>/ 119 + ├── pds.db # SQLite database 120 + │ ├── blocks table # CID → DAG-CBOR bytes 121 + │ ├── refs table # name → CID (branches) 122 + │ └── meta table # did, version, etc. 123 + └── blobs/ # Large binary data 124 + ├── ba/ # First 2 chars of CID 125 + │ └── bafyrei... # Full CID as filename 126 + └── ... 127 + ``` 104 128 105 129 ## Related Work 106 130 107 - - [ocaml-atp](https://tangled.org/anil.recoil.org/ocaml-atp) - ATProto primitives (MST, CID, DAG-CBOR, CAR) 108 - - [ocaml-sqlite](https://tangled.org/gazagnaire.org/ocaml-sqlite) - SQLite key-value store (used internally) 109 - - [Bluesky PDS](https://github.com/bluesky-social/pds) - Reference TypeScript implementation 131 + - [ocaml-atp](https://tangled.org/anil.recoil.org/ocaml-atp) -- ATProto primitives (MST, CID, DAG-CBOR, CAR) 132 + - [ocaml-sqlite](https://tangled.org/gazagnaire.org/ocaml-sqlite) -- SQLite key-value store (used internally) 133 + - [Bluesky PDS](https://github.com/bluesky-social/pds) -- Reference TypeScript implementation 110 134 111 135 ## Licence 112 136
+4
dune
··· 1 1 (env 2 2 (dev 3 3 (flags :standard %{dune-warnings}))) 4 + 5 + (mdx 6 + (files README.md) 7 + (libraries pds eio eio.unix))
+2
dune-project
··· 1 1 (lang dune 3.21) 2 + (using mdx 0.4) 2 3 3 4 (name pds) 4 5 ··· 21 22 (atp (>= 0.1)) 22 23 (sqlite (>= 0.1)) 23 24 (alcotest :with-test) 25 + (mdx :with-test) 24 26 (eio_main :with-test)))
+1
pds.opam
··· 16 16 "atp" {>= "0.1"} 17 17 "sqlite" {>= "0.1"} 18 18 "alcotest" {with-test} 19 + "mdx" {with-test} 19 20 "eio_main" {with-test} 20 21 "odoc" {with-doc} 21 22 ]