atproto user agency toolkit for individuals and groups
at main 94 lines 2.5 kB view raw
1/** 2 * SQLite-backed Blockstore for Helia. 3 * 4 * Replaces FsBlockstore to avoid creating thousands of tiny files 5 * that hammer macOS fseventsd. All blocks stored in a single SQLite table. 6 */ 7 8import type Database from "better-sqlite3"; 9import { CID } from "multiformats"; 10 11export class SqliteBlockstore { 12 private db: Database.Database; 13 14 constructor(db: Database.Database) { 15 this.db = db; 16 this.db.exec(` 17 CREATE TABLE IF NOT EXISTS ipfs_blocks ( 18 cid TEXT PRIMARY KEY, 19 bytes BLOB NOT NULL 20 ) 21 `); 22 } 23 24 async put(key: CID, val: Uint8Array): Promise<CID> { 25 this.db 26 .prepare("INSERT OR REPLACE INTO ipfs_blocks (cid, bytes) VALUES (?, ?)") 27 .run(key.toString(), Buffer.from(val)); 28 return key; 29 } 30 31 async * get(key: CID): AsyncGenerator<Uint8Array> { 32 const row = this.db 33 .prepare("SELECT bytes FROM ipfs_blocks WHERE cid = ?") 34 .get(key.toString()) as { bytes: Buffer } | undefined; 35 if (!row) { 36 throw new Error(`Block not found: ${key.toString()}`); 37 } 38 yield new Uint8Array(row.bytes); 39 } 40 41 async has(key: CID): Promise<boolean> { 42 const row = this.db 43 .prepare("SELECT 1 FROM ipfs_blocks WHERE cid = ?") 44 .get(key.toString()); 45 return row !== undefined; 46 } 47 48 async delete(key: CID): Promise<void> { 49 this.db 50 .prepare("DELETE FROM ipfs_blocks WHERE cid = ?") 51 .run(key.toString()); 52 } 53 54 async * putMany(source: AsyncIterable<{ cid: CID; block: Uint8Array }> | Iterable<{ cid: CID; block: Uint8Array }>): AsyncGenerator<CID> { 55 for await (const { cid, block } of source) { 56 await this.put(cid, block); 57 yield cid; 58 } 59 } 60 61 async * getMany(source: AsyncIterable<CID> | Iterable<CID>): AsyncGenerator<{ cid: CID; block: Uint8Array }> { 62 for await (const cid of source) { 63 let block: Uint8Array = new Uint8Array(0); 64 for await (const chunk of this.get(cid)) { 65 block = chunk; 66 } 67 yield { cid, block }; 68 } 69 } 70 71 async * deleteMany(source: AsyncIterable<CID> | Iterable<CID>): AsyncGenerator<CID> { 72 for await (const cid of source) { 73 await this.delete(cid); 74 yield cid; 75 } 76 } 77 78 /** 79 * Delete all blocks from the blockstore. 80 * Used during full disconnect to wipe the node clean. 81 */ 82 clear(): void { 83 this.db.prepare("DELETE FROM ipfs_blocks").run(); 84 } 85 86 async * getAll(): AsyncGenerator<{ cid: CID; block: Uint8Array }> { 87 const rows = this.db 88 .prepare("SELECT cid, bytes FROM ipfs_blocks") 89 .all() as Array<{ cid: string; bytes: Buffer }>; 90 for (const row of rows) { 91 yield { cid: CID.parse(row.cid), block: new Uint8Array(row.bytes) }; 92 } 93 } 94}