filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
152 lines • 5.17 kB
JavaScript
/**
* 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