/** * Recovery export functions for replicated atproto account data. * * Provides CAR file export (for repo import to a new PDS) and blob * export for disaster recovery scenarios. */ import { BlockMap, blocksToCarFile } from "@atproto/repo"; import { CID } from "@atproto/lex-data"; import type { SyncStorage } from "./sync-storage.js"; import type { BlockStore } from "../ipfs.js"; /** * Assemble a CAR v1 file from all replicated blocks for a DID. * * Uses @atproto/repo's blocksToCarFile which produces spec-compliant * CAR v1 with the commit root as the single root CID. * * Returns null if the DID has no sync state or root CID. */ export async function exportRepoAsCar( did: string, syncStorage: SyncStorage, blockStore: BlockStore, ): Promise<{ car: Uint8Array; rootCid: string; blockCount: number } | null> { const state = syncStorage.getState(did); if (!state?.rootCid) return null; const blockCids = syncStorage.getBlockCids(did); const blocks = new BlockMap(); let blockCount = 0; for (const cidStr of blockCids) { const bytes = await blockStore.getBlock(cidStr); if (bytes) { blocks.set(CID.parse(cidStr), bytes); blockCount++; } } if (blockCount === 0) return null; const root = CID.parse(state.rootCid); const car = await blocksToCarFile(root, blocks); return { car, rootCid: state.rootCid, blockCount }; } /** * Export all blob bytes for a DID. * * Returns an array of { cid, bytes } for every tracked blob CID * that has data in the blockstore. Blobs without data are skipped. */ export async function exportBlobs( did: string, syncStorage: SyncStorage, blockStore: BlockStore, ): Promise> { const blobCids = syncStorage.getBlobCids(did); const results: Array<{ cid: string; bytes: Uint8Array }> = []; for (const cid of blobCids) { const bytes = await blockStore.getBlock(cid); if (bytes) { results.push({ cid, bytes }); } } return results; }