filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
208 lines • 8.58 kB
JavaScript
/**
* Get Data Set Pieces
*
* Functions for retrieving pieces from a dataset with optional metadata enrichment.
*
* @module core/data-set/get-data-set-pieces
*/
import { getSizeFromPieceCID } from '@filoz/synapse-core/piece';
import { METADATA_KEYS, PDPServer, PDPVerifier, WarmStorageService, } from '@filoz/synapse-sdk';
import { reconcilePieceStatus } from '../piece/piece-status.js';
import { isStorageContextWithDataSetId } from './type-guards.js';
import { PieceStatus } from './types.js';
/**
* Get all pieces for a dataset from a StorageContext
*
* This function uses the StorageContext.getPieces() async generator to retrieve
* all pieces in a dataset. Optionally fetches metadata for each piece from WarmStorage.
*
* Example usage:
* ```typescript
* const result = await getDataSetPieces(storageContext, {
* includeMetadata: true,
* batchSize: 100
* })
*
* console.log(`Found ${result.pieces.length} pieces`)
* for (const piece of result.pieces) {
* console.log(` ${piece.pieceCid}`)
* if (piece.rootIpfsCid) {
* console.log(` IPFS: ${piece.rootIpfsCid}`)
* }
* }
* ```
*
* @param storageContext - Storage context from upload or dataset resolution
* @param options - Optional configuration
* @returns Pieces and warnings
*/
export async function getDataSetPieces(synapse, storageContext, options) {
const logger = options?.logger;
const includeMetadata = options?.includeMetadata ?? false;
const signal = options?.signal;
if (!isStorageContextWithDataSetId(storageContext)) {
throw new Error('Storage context does not have a dataset ID');
}
const pieces = [];
const warnings = [];
// call PDPVerifier.getScheduledRemovals to get the list of pieces that are scheduled for removal
let scheduledRemovals = [];
let pdpServerPieces = null;
try {
const warmStorage = await WarmStorageService.create(synapse.getProvider(), synapse.getWarmStorageAddress());
const pdpVerifier = new PDPVerifier(synapse.getProvider(), warmStorage.getPDPVerifierAddress());
scheduledRemovals = await pdpVerifier.getScheduledRemovals(storageContext.dataSetId);
try {
const providerInfo = await synapse.getProviderInfo(storageContext.provider.serviceProvider);
const pdpServer = new PDPServer(null, providerInfo.products?.PDP?.data?.serviceURL ?? '');
const dataSet = await pdpServer.getDataSet(storageContext.dataSetId);
pdpServerPieces = dataSet.pieces;
}
catch (error) {
logger?.warn({ error }, 'Failed to fetch provider data for scheduled removals and orphan detection');
warnings.push({
code: 'PROVIDER_DATA_UNAVAILABLE',
message: 'Failed to fetch provider data; orphan detection disabled',
context: { dataSetId: storageContext.dataSetId, error: String(error) },
});
}
}
catch (error) {
logger?.warn({ error }, 'Failed to get scheduled removals');
warnings.push({
code: 'SCHEDULED_REMOVALS_UNAVAILABLE',
message: 'Failed to get scheduled removals',
context: { dataSetId: storageContext.dataSetId, error: String(error) },
});
}
// Use the async generator to fetch all pieces
try {
const getPiecesOptions = { ...(signal && { signal }) };
const providerPiecesById = pdpServerPieces ? new Map(pdpServerPieces.map((piece) => [piece.pieceId, piece])) : null;
for await (const piece of storageContext.getPieces(getPiecesOptions)) {
const pieceId = piece.pieceId;
const pieceCid = piece.pieceCid;
const { status, warning } = reconcilePieceStatus({
pieceId,
pieceCid,
scheduledRemovals,
providerPiecesById,
});
const pieceInfo = {
pieceId,
pieceCid: pieceCid.toString(),
status,
};
if (warning) {
warnings.push(warning);
}
// Calculate piece size from CID
try {
pieceInfo.size = getSizeFromPieceCID(pieceCid);
}
catch (error) {
logger?.warn({ pieceId: piece.pieceId, pieceCid: piece.pieceCid.toString(), error }, 'Failed to calculate piece size from CID');
}
pieces.push(pieceInfo);
}
if (providerPiecesById !== null) {
// reconcilePieceStatus removes provider matches as we stream on-chain pieces.
// Remaining entries are only reported by the provider, which are off-chain orphans.
for (const piece of providerPiecesById.values()) {
// add the rest of the pieces to the pieces list
pieces.push({
pieceId: piece.pieceId,
pieceCid: piece.pieceCid.toString(),
status: PieceStatus.OFFCHAIN_ORPHANED,
});
warnings.push({
code: 'OFFCHAIN_ORPHANED',
message: 'Piece is reported by provider but not on-chain',
context: { pieceId: piece.pieceId, pieceCid: piece.pieceCid.toString() },
});
}
}
pieces.sort((a, b) => a.pieceId - b.pieceId);
}
catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw error;
}
// If getPieces fails completely, throw - this is a critical error
logger?.error({ dataSetId: storageContext.dataSetId, error }, 'Failed to retrieve pieces from dataset');
throw new Error(`Failed to retrieve pieces for dataset ${storageContext.dataSetId}: ${String(error)}`);
}
// Optionally enrich with metadata
if (includeMetadata && pieces.length > 0) {
await enrichPiecesWithMetadata(synapse, storageContext, pieces, warnings, logger);
}
// Calculate total size from pieces that have sizes
const piecesWithSizes = pieces.filter((p) => p.size != null);
const result = {
pieces,
dataSetId: storageContext.dataSetId,
warnings,
};
if (piecesWithSizes.length > 0) {
result.totalSizeBytes = piecesWithSizes.reduce((sum, piece) => sum + BigInt(piece.size), 0n);
}
return result;
}
/**
* Internal helper: Enrich pieces with metadata from WarmStorage
*
* This function fetches metadata for each piece and extracts:
* - rootIpfsCid (from METADATA_KEYS.IPFS_ROOT_CID)
* - Full metadata object
*
* Non-fatal errors are added to the warnings array.
*/
async function enrichPiecesWithMetadata(synapse, storageContext, pieces, warnings, logger) {
const dataSetId = storageContext.dataSetId;
// Create WarmStorage service instance
let warmStorage;
try {
warmStorage = await WarmStorageService.create(synapse.getProvider(), synapse.getWarmStorageAddress());
}
catch (error) {
// If we can't create the service, warn and return
logger?.warn({ error }, 'Failed to create WarmStorageService for metadata enrichment');
warnings.push({
code: 'WARM_STORAGE_INIT_FAILED',
message: 'Failed to initialize WarmStorageService for metadata enrichment',
context: { error: String(error) },
});
return;
}
// Fetch metadata for each piece
for (const piece of pieces) {
try {
const metadata = await warmStorage.getPieceMetadata(dataSetId, piece.pieceId);
// Extract root IPFS CID if available
const rootIpfsCid = metadata[METADATA_KEYS.IPFS_ROOT_CID];
if (rootIpfsCid) {
piece.rootIpfsCid = rootIpfsCid;
}
// Store full metadata
piece.metadata = metadata;
}
catch (error) {
// Non-fatal: piece exists but metadata fetch failed
logger?.warn({
dataSetId,
pieceId: piece.pieceId,
error,
}, 'Failed to fetch metadata for piece');
warnings.push({
code: 'METADATA_FETCH_FAILED',
message: `Failed to fetch metadata for piece ${piece.pieceId}`,
context: {
pieceId: piece.pieceId,
dataSetId,
error: String(error),
},
});
}
}
}
//# sourceMappingURL=get-data-set-pieces.js.map