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)
static async create(
storage: RepoStorage,
did: string,
keypair: Keypair,
initialWrites?: RecordCreateOp[]
): Promise<Repo>
Creates a new repository.Block storage implementation
DID of the repository owner
initialWrites
RecordCreateOp[]
default:"[]"
Optional initial records to create
static async load(storage: RepoStorage, cid?: CID): Promise<Repo>
Loads an existing repository from storage.Block storage containing the repository
Optional commit CID. If not provided, loads the root commit from storage.
Instance Methods:
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)
Signing key for the new commit
async formatCommit(
toWrite: RecordWriteOp | RecordWriteOp[],
keypair: Keypair
): Promise<CommitData>
Formats a commit without applying it to storage. Useful for preparing commits for transmission.
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 NSID (e.g., ‘app.bsky.feed.post’)
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')
static async create(
storage: ReadableBlockstore,
entries?: NodeEntry[],
opts?: Partial<MstOpts>
): Promise<MST>
Creates a new 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:
async add(key: string, value: CID): Promise<MST>
Adds or updates a key/value pair. Returns a new MST instance.
async update(key: string, value: CID): Promise<MST>
Updates an existing key. Throws if key doesn’t exist.
async delete(key: string): Promise<MST>
Deletes a key. Returns a new MST instance.
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.Maximum number of entries to return
Start listing after this key
Stop listing before this key
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
)
function exportCarSlice(
storage: ReadableBlockstore,
root: CID,
since?: CID | null
): Promise<Uint8Array>
Exports blocks as a CAR file.storage
ReadableBlockstore
required
Block storage
Root commit CID to export
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