Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/bluesky-social/atproto/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The @atproto/repo package provides TypeScript implementation of AT Protocol repositories and the Merkle Search Tree (MST) data structure. Repositories are signed, content-addressed key/value stores that hold user data records.

Installation

npm install @atproto/repo

Core Concepts

Repository Structure

An AT Protocol repository consists of:
  • Commit: Signed metadata pointing to the current data state
  • MST (Merkle Search Tree): Content-addressed tree structure storing record keys and CIDs
  • Blocks: CBOR-encoded record data and MST nodes
Repositories use content addressing (CIDs) to reference data, creating a verifiable, immutable history of changes.

Merkle Search Tree (MST)

The MST is an ordered, deterministic tree structure where:
  • Keys are sorted alphabetically
  • Insertion order doesn’t affect the final tree structure
  • Each key is hashed to determine its layer in the tree
  • Subtrees are referenced by their CID
This enables efficient diffing, syncing, and verification of repository state.

Main Classes

Repo

The main repository class for creating and updating repositories.
import { Repo } from '@atproto/repo'
import { MemoryBlockstore } from '@atproto/repo'
import { Secp256k1Keypair } from '@atproto/crypto'

// Create storage
const storage = new MemoryBlockstore()

// Create a keypair
const keypair = await Secp256k1Keypair.create()

// Create new repository
const repo = await Repo.create(
  storage,
  'did:plc:example123',
  keypair,
  [
    {
      action: 'create',
      collection: 'app.bsky.feed.post',
      rkey: 'self',
      record: { text: 'Hello, world!', createdAt: new Date().toISOString() }
    }
  ]
)

console.log('Created repo with DID:', repo.did)
Repo.create
Promise<Repo>
static async create(
  storage: RepoStorage,
  did: string,
  keypair: Keypair,
  initialWrites?: RecordCreateOp[]
): Promise<Repo>
Creates a new repository.
storage
RepoStorage
required
Block storage implementation
did
string
required
DID of the repository owner
keypair
Keypair
required
Signing key for commits
initialWrites
RecordCreateOp[]
default:"[]"
Optional initial records to create
Repo.load
Promise<Repo>
static async load(storage: RepoStorage, cid?: CID): Promise<Repo>
Loads an existing repository from storage.
storage
RepoStorage
required
Block storage containing the repository
cid
CID
Optional commit CID. If not provided, loads the root commit from storage.
Instance Methods:
applyWrites
Promise<Repo>
async applyWrites(
  toWrite: RecordWriteOp | RecordWriteOp[],
  keypair: Keypair
): Promise<Repo>
Applies record operations and creates a new commit. Returns a new Repo instance with updated state.
toWrite
RecordWriteOp | RecordWriteOp[]
required
One or more record operations (create, update, or delete)
keypair
Keypair
required
Signing key for the new commit
formatCommit
Promise<CommitData>
async formatCommit(
  toWrite: RecordWriteOp | RecordWriteOp[],
  keypair: Keypair
): Promise<CommitData>
Formats a commit without applying it to storage. Useful for preparing commits for transmission.
applyCommit
Promise<Repo>
async applyCommit(commitData: CommitData): Promise<Repo>
Applies a pre-formatted commit to storage.
getRecord
Promise<RepoRecord | null>
async getRecord(collection: string, rkey: string): Promise<RepoRecord | null>
Retrieves a record from the repository.
collection
string
required
Collection NSID (e.g., ‘app.bsky.feed.post’)
rkey
string
required
Record key
Properties:
  • did: string - Repository DID
  • cid: CID - Current commit CID
  • commit: Commit - Current commit object
  • data: MST - Current MST root
  • storage: RepoStorage - Block storage

MST

Merkle Search Tree implementation.
import { MST, MemoryBlockstore } from '@atproto/repo'
import { CID } from 'multiformats/cid'

const storage = new MemoryBlockstore()

// Create empty MST
const mst = await MST.create(storage)

// Add entries
const cid1 = CID.parse('bafyreiabc123...')
const mst2 = await mst.add('app.bsky.feed.post/abc123', cid1)

const cid2 = CID.parse('bafyreiabc456...')
const mst3 = await mst2.add('app.bsky.feed.post/xyz789', cid2)

// Get entry
const value = await mst3.get('app.bsky.feed.post/abc123')
console.log(value?.equals(cid1)) // true

// Delete entry
const mst4 = await mst3.delete('app.bsky.feed.post/abc123')
MST.create
Promise<MST>
static async create(
  storage: ReadableBlockstore,
  entries?: NodeEntry[],
  opts?: Partial<MstOpts>
): Promise<MST>
Creates a new MST.
MST.load
MST
static load(
  storage: ReadableBlockstore,
  cid: CID,
  opts?: Partial<MstOpts>
): MST
Loads an MST from a CID. This is a lazy load - data is fetched on demand.
Instance Methods:
add
Promise<MST>
async add(key: string, value: CID): Promise<MST>
Adds or updates a key/value pair. Returns a new MST instance.
update
Promise<MST>
async update(key: string, value: CID): Promise<MST>
Updates an existing key. Throws if key doesn’t exist.
delete
Promise<MST>
async delete(key: string): Promise<MST>
Deletes a key. Returns a new MST instance.
get
Promise<CID | null>
async get(key: string): Promise<CID | null>
Retrieves the CID for a key.
list
AsyncIterable<{ key: string; value: CID }>
async *list(
  count?: number,
  after?: string,
  before?: string
): AsyncIterable<{ key: string; value: CID }>
Lists entries in the tree.
count
number
Maximum number of entries to return
after
string
Start listing after this key
before
string
Stop listing before this key
getPointer
Promise<CID>
async getPointer(): Promise<CID>
Gets the CID of this MST node.

Storage Implementations

MemoryBlockstore

In-memory block storage for testing and development.
import { MemoryBlockstore } from '@atproto/repo'

const storage = new MemoryBlockstore()

RepoStorage Interface

Interface for implementing custom storage backends.
interface RepoStorage extends ReadableBlockstore {
  applyCommit(commit: CommitData): Promise<void>
  getRoot(): Promise<CID | null>
}

interface ReadableBlockstore {
  has(cid: CID): Promise<boolean>
  get(cid: CID): Promise<Uint8Array>
  getBytes(cid: CID): Promise<Uint8Array>
  readObj<T>(cid: CID, def: { schema: z.ZodType<T> }): Promise<T>
}

Record Operations

RecordWriteOp Types

import { WriteOpAction } from '@atproto/repo'
import type { RecordWriteOp } from '@atproto/repo'

// Create operation
const createOp: RecordWriteOp = {
  action: WriteOpAction.Create,
  collection: 'app.bsky.feed.post',
  rkey: '3jzfcijpj2z2a',
  record: {
    text: 'Hello, world!',
    createdAt: new Date().toISOString()
  }
}

// Update operation
const updateOp: RecordWriteOp = {
  action: WriteOpAction.Update,
  collection: 'app.bsky.feed.post',
  rkey: '3jzfcijpj2z2a',
  record: {
    text: 'Updated text',
    createdAt: new Date().toISOString()
  }
}

// Delete operation
const deleteOp: RecordWriteOp = {
  action: WriteOpAction.Delete,
  collection: 'app.bsky.feed.post',
  rkey: '3jzfcijpj2z2a'
}

CAR File Utilities

exportCarSlice

Exports a repository slice as a CAR (Content Addressable aRchive) file.
import { exportCarSlice } from '@atproto/repo'

const carBytes = await exportCarSlice(
  storage,
  commitCid,
  since // optional: previous commit CID for incremental export
)
exportCarSlice
Promise<Uint8Array>
function exportCarSlice(
  storage: ReadableBlockstore,
  root: CID,
  since?: CID | null
): Promise<Uint8Array>
Exports blocks as a CAR file.
storage
ReadableBlockstore
required
Block storage
root
CID
required
Root commit CID to export
since
CID | null
Previous commit CID for incremental export

importCarSlice

Imports blocks from a CAR file.
import { importCarSlice } from '@atproto/repo'

const carBytes = new Uint8Array([...])
const blocks = await importCarSlice(carBytes)

for (const block of blocks) {
  await storage.put(block.cid, block.bytes)
}

Data Structures

BlockMap

Map of CIDs to block bytes.
import { BlockMap } from '@atproto/repo'
import { CID } from 'multiformats/cid'

const blocks = new BlockMap()

// Add block
const record = { text: 'Hello' }
const cid = await blocks.add(record)

// Get block
const bytes = blocks.get(cid)

// Check existence
if (blocks.has(cid)) {
  console.log('Block exists')
}

CidSet

Set of CIDs.
import { CidSet } from '@atproto/repo'
import { CID } from 'multiformats/cid'

const cids = new CidSet()
cids.add(someCid)

if (cids.has(someCid)) {
  console.log('CID in set')
}

const array = cids.toList()

Complete Example

import { Repo, MemoryBlockstore, WriteOpAction } from '@atproto/repo'
import { Secp256k1Keypair } from '@atproto/crypto'
import type { RecordWriteOp } from '@atproto/repo'

async function main() {
  // Setup
  const storage = new MemoryBlockstore()
  const keypair = await Secp256k1Keypair.create()
  const did = 'did:plc:example123'
  
  // Create repository with initial post
  let repo = await Repo.create(storage, did, keypair, [
    {
      action: WriteOpAction.Create,
      collection: 'app.bsky.feed.post',
      rkey: 'self',
      record: {
        text: 'First post!',
        createdAt: new Date().toISOString()
      }
    }
  ])
  
  console.log('Initial commit:', repo.cid.toString())
  console.log('Revision:', repo.commit.rev)
  
  // Add more records
  const ops: RecordWriteOp[] = [
    {
      action: WriteOpAction.Create,
      collection: 'app.bsky.feed.post',
      rkey: '3jzfcijpj2z2a',
      record: {
        text: 'Second post!',
        createdAt: new Date().toISOString()
      }
    },
    {
      action: WriteOpAction.Create,
      collection: 'app.bsky.actor.profile',
      rkey: 'self',
      record: {
        displayName: 'Alice',
        description: 'Hello from AT Proto'
      }
    }
  ]
  
  repo = await repo.applyWrites(ops, keypair)
  console.log('After writes:', repo.cid.toString())
  
  // Read records
  const post = await repo.getRecord('app.bsky.feed.post', 'self')
  console.log('Post:', post)
  
  const profile = await repo.getRecord('app.bsky.actor.profile', 'self')
  console.log('Profile:', profile)
  
  // Update a record
  repo = await repo.applyWrites(
    {
      action: WriteOpAction.Update,
      collection: 'app.bsky.actor.profile',
      rkey: 'self',
      record: {
        displayName: 'Alice Smith',
        description: 'Updated description'
      }
    },
    keypair
  )
  
  // Delete a record
  repo = await repo.applyWrites(
    {
      action: WriteOpAction.Delete,
      collection: 'app.bsky.feed.post',
      rkey: '3jzfcijpj2z2a'
    },
    keypair
  )
  
  console.log('Final commit:', repo.cid.toString())
  console.log('Final revision:', repo.commit.rev)
  
  // List all records in a collection
  const entries = []
  for await (const entry of repo.data.list()) {
    if (entry.key.startsWith('app.bsky.feed.post/')) {
      entries.push(entry)
    }
  }
  console.log('Posts remaining:', entries.length)
}

main()

Additional Resources

Build docs developers (and LLMs) love