Repository
AT Protocol repositories are Merkle Search Trees (MSTs) with signed commits. The FSharp.ATProto.Repo package provides data structures for reading, writing, and verifying repository contents -- including MST operations, commit signing, and CAR v1 export.
Merkle Search Tree (MST)
The MST is the core data structure that stores records in a repository. Keys are strings in the form collection/rkey (e.g. app.bsky.feed.post/3jui7kd2z2y2e), and values are CIDs pointing to the record's DAG-CBOR block.
Types
type Entry =
{ Key : string
Value : Cid
Tree : Node option }
and Node =
{ Left : Node option
Entries : Entry list }
Building and Querying
open FSharp.ATProto.Repo
// Start with an empty tree
let tree = Mst.empty
// Build from a sorted list of entries
let tree2 =
Mst.create
[ ("app.bsky.feed.post/abc", cidA)
("app.bsky.feed.post/def", cidB) ]
// Insert and delete (both rebuild the tree)
let tree3 = tree2 |> Mst.insert "app.bsky.feed.post/ghi" cidC
let tree4 = tree3 |> Mst.delete "app.bsky.feed.post/abc"
// Look up a key
match Mst.lookup "app.bsky.feed.post/def" tree4 with
| Some cid -> printfn "Found: %s" (Cid.value cid)
| None -> printfn "Not found"
// List all entries in sorted order
let allPairs = Mst.allEntries tree4
Diffing
Compare two MST states to find what changed:
let (added, updated, deleted) = Mst.diff oldTree newTree
// added: (key, cid) list -- new keys
// updated: (key, cid) list -- keys with changed CIDs
// deleted: string list -- removed keys
Serialization
Serialize an MST to DAG-CBOR blocks and compute its root CID:
let (rootCid, blocks) = Mst.serialize tree5
// rootCid: Cid -- the MST root
// blocks: Map<string, byte[]> -- CID string -> DAG-CBOR bytes
// Deserialize from a block store
match Mst.deserialize blocks rootCid with
| Ok node -> printfn "Loaded %d entries" (Mst.allEntries node).Length
| Error msg -> printfn "Error: %s" msg
Signed Commits
Every repository state is anchored by a signed commit that references the MST root.
type SignedCommit =
{ Did : Did
Version : int // always 3
Data : Cid // MST root CID
Rev : string // TID-based revision
Prev : Cid option // previous commit CID
Sig : byte[] } // 64-byte ECDSA signature
Creating and Verifying Commits
open FSharp.ATProto.Repo
open FSharp.ATProto.Crypto
let keyPair = Keys.generate Algorithm.P256
let rev = Commit.createRev ()
// Create a signed commit
let commit = Commit.create did rootCid2 rev None keyPair
// Encode to DAG-CBOR
let commitBytes = Commit.encode commit
// Decode from DAG-CBOR
match Commit.decode commitBytes with
| Ok decoded -> printfn "Rev: %s" decoded.Rev
| Error msg -> printfn "Decode error: %s" msg
// Verify signature
match Commit.verify (Keys.publicKey keyPair) commitBytes with
| Ok true -> printfn "Valid signature"
| Ok false -> printfn "Invalid signature"
| Error msg -> printfn "Verification error: %s" msg
In-Memory Repository
The Repo module provides a higher-level wrapper combining the MST with repository metadata:
open FSharp.ATProto.Repo
open FSharp.ATProto.Syntax
let did2 = Did.parse "did:plc:example" |> Result.defaultWith failwith
let repo = Repo.empty did2
// Add and remove records
let repo2 = repo |> Repo.putRecord "app.bsky.feed.post/abc" recordCid
let repo3 = repo2 |> Repo.deleteRecord "app.bsky.feed.post/abc"
// Query
let maybeCid = Repo.getRecord "app.bsky.feed.post/abc" repo3
let allRecords = Repo.listRecords repo3
// Compute root CID and blocks
let (rootCid3, blocks3) = Repo.computeRoot repo3
CAR Export
Export a repository's blocks as a CAR v1 file:
let (rootCid4, blocks4) = Repo.computeRoot repo4
let carBytes = Repo.exportCar [ rootCid4 ] blocks4
// carBytes is a complete CAR v1 file ready for transport
The CAR format is used by AT Protocol sync endpoints (com.atproto.sync.getRepo, etc.) to transfer repository data.
val string: value: 'T -> string
--------------------
type string = System.String
namespace FSharp
--------------------
namespace Microsoft.FSharp
module Cid from FSharp.ATProto.Syntax
<summary> Functions for creating, validating, and extracting data from <see cref="Cid" /> values. </summary>
--------------------
type Cid = private | Cid of string override ToString: unit -> string
<summary> A Content Identifier (CID) used to reference content-addressed data in the AT Protocol. CIDs are self-describing content hashes that uniquely identify a piece of data. Only CIDv1 is supported; CIDv0 (starting with <c>Qmb</c>) is rejected. </summary>
<remarks> CIDs in the AT Protocol use CIDv1 with DAG-CBOR codec and SHA-256 hash. This type performs syntactic validation only (base-encoded alphanumeric string of 8-256 characters). See https://github.com/multiformats/cid for the CID specification. </remarks>
<summary> Merkle Search Tree (MST) data structure for AT Protocol repositories. </summary>
<summary> An empty MST. </summary>
<summary> Build an MST from a sorted list of (key, valueCid) pairs. Higher heightForKey values = closer to root. Layer 0 = leaf level. </summary>
<summary> Insert a key-value pair into the MST. Rebuilds the tree. </summary>
<summary> Delete a key from the MST. Rebuilds the tree. </summary>
<summary> Look up a key in the MST. </summary>
<summary> Extract the string representation of a CID. </summary>
<param name="cid">The CID to extract the value from.</param>
<returns>The CID string in its base-encoded form.</returns>
<summary> Collect all (key, value) pairs from an MST in sorted order. </summary>
<summary> An MST node. </summary>
<summary> Diff two MSTs, returning (added, updated, deleted) key-value pairs. </summary>
<summary> Serialize an MST node to DAG-CBOR and return (CID, block map). </summary>
<summary> Deserialize an MST from a block store starting from a root CID. </summary>
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
val byte: value: 'T -> byte (requires member op_Explicit)
--------------------
type byte = System.Byte
--------------------
type byte<'Measure> = byte
module Did from FSharp.ATProto.Syntax
<summary> Functions for creating, validating, and extracting data from <see cref="Did" /> values. </summary>
--------------------
type Did = private | Did of string override ToString: unit -> string
<summary> A decentralized identifier (DID) as defined by the AT Protocol. DIDs are the primary stable identifier for accounts. Two methods are currently supported: <c>did:plc:</c> (hosted, managed by PLC directory) and <c>did:web:</c> (self-hosted, DNS-based). </summary>
<remarks> See the AT Protocol specification: https://atproto.com/specs/did and the W3C DID specification: https://www.w3.org/TR/did-core/ </remarks>
<summary> Parse and validate a DID string. </summary>
<param name="s"> A DID string in the format <c>did:<method>:<method-specific-id></c> (e.g. <c>"did:plc:z72i7hdynmk6r22z27h6tvur"</c> or <c>"did:web:example.com"</c>). </param>
<returns><c>Ok</c> with a validated <see cref="Did" />, or <c>Error</c> with a message describing the validation failure. Validation failures include: null input, exceeding the 2048-character limit, invalid DID syntax, or invalid percent-encoding. </returns>
<example><code> match Did.parse "did:plc:z72i7hdynmk6r22z27h6tvur" with | Ok did -> printfn "Valid: %s" (Did.value did) | Error e -> printfn "Invalid: %s" e </code></example>
module Result from Microsoft.FSharp.Core
--------------------
[<Struct>] type Result<'T,'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError
<summary> Key management for P-256 and secp256k1 curves. </summary>
<summary> Generate a new key pair for the given algorithm. </summary>
<summary> The elliptic curve algorithm used for a key. </summary>
<summary> Signed commit objects for AT Protocol repositories. </summary>
<summary> Create a TID-based revision string for the current time. </summary>
<summary> Create and sign a commit. </summary>
<summary> Encode a signed commit to DAG-CBOR bytes (with signature). </summary>
<summary> Decode a signed commit from DAG-CBOR bytes. </summary>
<summary> Verify a signed commit's signature against a public key. </summary>
<summary> Get the public key from a key pair. </summary>
<summary> In-memory repository with MST, commit creation, and CAR export/import. </summary>
<summary> Create an empty repository for a DID. </summary>
<summary> Put a record into the repository. </summary>
<summary> Delete a record from the repository. </summary>
<summary> Get a record value by its key (collection/rkey path). </summary>
<summary> List all records in the repository. </summary>
<summary> Compute the MST root CID and collect all blocks. </summary>
<summary> An in-memory repository. </summary>
<summary> Serialize a CAR v1 file from roots and blocks. </summary>