atproto user agency toolkit for individuals and groups
1/**
2 * Recovery export functions for replicated atproto account data.
3 *
4 * Provides CAR file export (for repo import to a new PDS) and blob
5 * export for disaster recovery scenarios.
6 */
7
8import { BlockMap, blocksToCarFile } from "@atproto/repo";
9import { CID } from "@atproto/lex-data";
10import type { SyncStorage } from "./sync-storage.js";
11import type { BlockStore } from "../ipfs.js";
12
13/**
14 * Assemble a CAR v1 file from all replicated blocks for a DID.
15 *
16 * Uses @atproto/repo's blocksToCarFile which produces spec-compliant
17 * CAR v1 with the commit root as the single root CID.
18 *
19 * Returns null if the DID has no sync state or root CID.
20 */
21export async function exportRepoAsCar(
22 did: string,
23 syncStorage: SyncStorage,
24 blockStore: BlockStore,
25): Promise<{ car: Uint8Array; rootCid: string; blockCount: number } | null> {
26 const state = syncStorage.getState(did);
27 if (!state?.rootCid) return null;
28
29 const blockCids = syncStorage.getBlockCids(did);
30 const blocks = new BlockMap();
31 let blockCount = 0;
32
33 for (const cidStr of blockCids) {
34 const bytes = await blockStore.getBlock(cidStr);
35 if (bytes) {
36 blocks.set(CID.parse(cidStr), bytes);
37 blockCount++;
38 }
39 }
40
41 if (blockCount === 0) return null;
42
43 const root = CID.parse(state.rootCid);
44 const car = await blocksToCarFile(root, blocks);
45
46 return { car, rootCid: state.rootCid, blockCount };
47}
48
49/**
50 * Export all blob bytes for a DID.
51 *
52 * Returns an array of { cid, bytes } for every tracked blob CID
53 * that has data in the blockstore. Blobs without data are skipped.
54 */
55export async function exportBlobs(
56 did: string,
57 syncStorage: SyncStorage,
58 blockStore: BlockStore,
59): Promise<Array<{ cid: string; bytes: Uint8Array }>> {
60 const blobCids = syncStorage.getBlobCids(did);
61 const results: Array<{ cid: string; bytes: Uint8Array }> = [];
62
63 for (const cid of blobCids) {
64 const bytes = await blockStore.getBlock(cid);
65 if (bytes) {
66 results.push({ cid, bytes });
67 }
68 }
69
70 return results;
71}