UNPKG

filecoin-pin

Version:

Bridge IPFS content to Filecoin Onchain Cloud using familiar tools

152 lines 5.17 kB
/** * Shared base class for CAR blockstore implementations. * Contains all platform-agnostic blockstore logic. */ import toBuffer from 'it-to-buffer'; import { CID } from 'multiformats/cid'; import varint from 'varint'; /** * Base CAR blockstore with pluggable storage backend */ export class CARBlockstoreBase { rootCID; backend; blockOffsets = new Map(); stats; events; currentOffset = 0; finalized = false; initialized = false; constructor(rootCID, backend, events) { this.rootCID = rootCID; this.backend = backend; this.events = events; this.stats = { blocksWritten: 0, missingBlocks: new Set(), totalSize: 0, startTime: Date.now(), finalized: false, }; } async initialize() { if (this.initialized) return; const { headerSize } = await this.backend.initialize(this.rootCID); this.currentOffset = headerSize; this.initialized = true; } async put(cid, block, _options) { if (this.finalized) { throw new Error('Cannot put blocks in finalized CAR blockstore'); } if (!this.initialized) { await this.initialize(); } // Skip if already stored (deduplication) const cidStr = cid.toString(); if (this.blockOffsets.has(cidStr)) { return cid; } // Calculate the varint that will be written const totalSectionLength = cid.bytes.length + block.length; const varintBytes = varint.encode(totalSectionLength); const varintLength = varintBytes.length; const currentOffset = this.currentOffset; // Block data starts after the varint and CID const blockStart = currentOffset + varintLength + cid.bytes.length; // Store the offset information BEFORE writing const offset = { blockStart, blockLength: block.length, }; this.blockOffsets.set(cidStr, offset); // Update offset for next block this.currentOffset = blockStart + block.length; // Write block to storage backend await this.backend.writeBlock(cid, block, offset); // Update statistics this.stats.blocksWritten++; this.stats.totalSize += block.length; return cid; } async *get(cid, options) { const cidStr = cid.toString(); const offset = this.blockOffsets.get(cidStr); if (offset == null) { this.stats.missingBlocks.add(cidStr); this.events?.emit('block:missing', { cid }); const error = new Error(`Block not found: ${cidStr}`); error.code = 'ERR_NOT_FOUND'; throw error; } if (this.backend.readBlock == null) { throw new Error('Read operation not supported by this blockstore'); } // Check abort signal before starting options?.signal?.throwIfAborted(); // Stream chunks directly from backend for await (const chunk of this.backend.readBlock(cid, offset)) { options?.signal?.throwIfAborted(); yield chunk; } } async has(cid, _options) { const cidStr = cid.toString(); return this.blockOffsets.has(cidStr); } async delete(_cid, _options) { throw new Error('Delete operation not supported on CAR writing blockstore'); } async *putMany(source, _options) { for await (const { cid, bytes } of source) { const block = bytes instanceof Uint8Array ? bytes : await toBuffer(bytes); yield await this.put(cid, block); } } async *getMany(source, options) { for await (const cid of source) { options?.signal?.throwIfAborted(); const bytes = this.get(cid, options); yield { cid, bytes }; } } // biome-ignore lint/correctness/useYield: This method throws immediately and intentionally never yields async *deleteMany(_source, _options) { throw new Error('DeleteMany operation not supported on CAR writing blockstore'); } async *getAll(options) { for (const [cidStr] of this.blockOffsets.entries()) { options?.signal?.throwIfAborted(); const cid = CID.parse(cidStr); const bytes = this.get(cid, options); yield { cid, bytes }; } } async finalize() { if (this.finalized) { return this.stats; } // Backend finalize will throw appropriate error if needed await this.backend.finalize(); this.finalized = true; this.stats.finalized = true; return this.stats; } getStats() { return { ...this.stats, missingBlocks: new Set(this.stats.missingBlocks), // Return a copy }; } async cleanup() { try { this.finalized = true; await this.backend.cleanup(); } catch { // Ignore cleanup errors } } } //# sourceMappingURL=car-blockstore-base.js.map