Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
93d32ae
fix: pieces can be removed from a data-set
SgtPooki Nov 19, 2025
9abd9ea
Merge branch 'master' into 8-add-rm---piece-piececid-command
SgtPooki Nov 24, 2025
09a2f91
fix: handle pieces pending removal
SgtPooki Nov 25, 2025
e7e2fc4
fix: display piece onchain vs pdpserver status
SgtPooki Nov 25, 2025
2c4b84f
fix: rm cmd requires --piece and --data-set
SgtPooki Nov 25, 2025
ffd4d01
fix: fix default to orphaned piece status
SgtPooki Nov 25, 2025
d139558
Merge branch 'master' into 8-add-rm---piece-piececid-command
SgtPooki Dec 4, 2025
9069e21
deps: upgrade to latest synapse
SgtPooki Dec 4, 2025
28c0883
fix: use new createStorageContext signature in rm
SgtPooki Dec 4, 2025
2d9fc7f
test: fix mock merge issue
SgtPooki Dec 4, 2025
01f6e6c
chore: remove unnecessary as string casting
SgtPooki Dec 4, 2025
b60eae6
fix: logger is no longer required for createStorageContext
SgtPooki Dec 4, 2025
6fcb9d6
test: remove-piece is tested
SgtPooki Dec 4, 2025
362cca6
test: assert runRmPiece happy path
SgtPooki Dec 4, 2025
6e372f6
chore: remove outdated comment
SgtPooki Dec 4, 2025
e77693f
Update src/core/data-set/get-detailed-data-set.ts
SgtPooki Dec 4, 2025
414aec3
Update src/rm/remove-piece.ts
SgtPooki Dec 4, 2025
2198e51
Update src/rm/remove-piece.ts
SgtPooki Dec 4, 2025
a66dd7a
Update src/rm/remove-piece.ts
SgtPooki Dec 4, 2025
797def7
chore: update jsdoc comments in runRmPiece
SgtPooki Dec 4, 2025
f0450a2
fix: removePiece requires storageContext
SgtPooki Dec 6, 2025
f8c977f
fix: document more about piece orphans
SgtPooki Dec 6, 2025
c2a60d3
Update src/core/data-set/types.ts
SgtPooki Dec 6, 2025
f31e0ed
Update src/rm/remove-piece.ts
SgtPooki Dec 6, 2025
701a78c
fix: export Warning type
SgtPooki Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
"types": "./dist/core/payments/index.d.ts",
"default": "./dist/core/payments/index.js"
},
"./core/piece": {
"types": "./dist/core/piece/index.d.ts",
"default": "./dist/core/piece/index.js"
},
"./core/synapse": {
"types": "./dist/core/synapse/index.d.ts",
"default": "./dist/core/synapse/index.js"
Expand Down
2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { addCommand } from './commands/add.js'
import { dataSetCommand } from './commands/data-set.js'
import { importCommand } from './commands/import.js'
import { paymentsCommand } from './commands/payments.js'
import { rmCommand } from './commands/rm.js'
import { serverCommand } from './commands/server.js'
import { checkForUpdate, type UpdateCheckStatus } from './common/version-check.js'
import { version as packageVersion } from './core/utils/version.js'
Expand All @@ -24,6 +25,7 @@ program.addCommand(paymentsCommand)
program.addCommand(dataSetCommand)
program.addCommand(importCommand)
program.addCommand(addCommand)
program.addCommand(rmCommand)

// Default action - show help if no command specified
program.action(() => {
Expand Down
19 changes: 19 additions & 0 deletions src/commands/rm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Command } from 'commander'
import { runRmPiece } from '../rm/index.js'
import { addAuthOptions } from '../utils/cli-options.js'

export const rmCommand = new Command('rm')
.description('Remove a Piece from a DataSet')
.option('--piece <cid>', 'Piece CID to remove')
.option('--data-set <id>', 'DataSet ID to remove the piece from')
.option('--wait-for-confirmation', 'Wait for transaction confirmation before exiting')
.action(async (options) => {
try {
await runRmPiece(options)
} catch {
// Error already displayed by clack UI in runRmPiece
process.exit(1)
}
})

addAuthOptions(rmCommand)
1 change: 1 addition & 0 deletions src/core/piece/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './remove-piece.js'
215 changes: 215 additions & 0 deletions src/core/piece/remove-piece.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/**
* Remove piece functionality
*
* This module demonstrates the pattern for removing pieces from Data Sets
* via Synapse SDK. It supports two usage patterns:
*
* 1. With dataSetId - creates a temporary storage context (CLI usage)
* 2. With existing StorageContext - reuses context (library/server usage)
*
* Progress events allow callers to track transaction submission and confirmation.
*/
import type { StorageContext, Synapse } from '@filoz/synapse-sdk'
import type { Logger } from 'pino'
import { createStorageContext } from '../synapse/index.js'
import { getErrorMessage } from '../utils/errors.js'
import type { ProgressEvent, ProgressEventHandler } from '../utils/types.js'

/**
* Progress events emitted during piece removal
*
* These events allow callers to track the removal process:
* - submitting: Transaction is being submitted to blockchain
* - submitted: Transaction submitted successfully, txHash available
* - confirming: Waiting for transaction confirmation (if waitForConfirmation=true)
* - confirmation-failed: Confirmation wait timed out (non-fatal, tx may still succeed)
* - complete: Removal process finished
*
* Note: Errors are propagated via thrown exceptions, not events (similar to upload pattern)
*/
export type RemovePieceProgressEvents =
| ProgressEvent<'remove-piece:submitting', { pieceCid: string; dataSetId: number }>
| ProgressEvent<'remove-piece:submitted', { pieceCid: string; dataSetId: number; txHash: `0x${string}` | string }>
| ProgressEvent<'remove-piece:confirming', { pieceCid: string; dataSetId: number; txHash: `0x${string}` | string }>
| ProgressEvent<
'remove-piece:confirmation-failed',
{ pieceCid: string; dataSetId: number; txHash: `0x${string}` | string; message: string }
>
| ProgressEvent<'remove-piece:complete', { txHash: `0x${string}` | string; confirmed: boolean }>

/**
* Number of block confirmations to wait for when waitForConfirmation=true
*/
const WAIT_CONFIRMATIONS = 1

/**
* Timeout in milliseconds for waiting for transaction confirmation
* Set to 2 minutes - generous default for Calibration network finality
*/
const WAIT_TIMEOUT_MS = 2 * 60 * 1000

/**
* Base options for piece removal
*/
interface RemovePieceOptionsBase {
/** Initialized Synapse SDK instance */
synapse: Synapse
/** Optional progress event handler for tracking removal status */
onProgress?: ProgressEventHandler<RemovePieceProgressEvents> | undefined
/** Whether to wait for transaction confirmation before returning (default: false) */
waitForConfirmation?: boolean | undefined
/** Optional logger for tracking removal operations */
logger?: Logger | undefined
}

/**
* Options for removing a piece when you have a dataSetId
*
* This is the typical CLI usage pattern - you know the dataSetId and want
* to remove a piece from it. A temporary storage context will be created.
*
* Note: logger is required in this mode for storage context creation.
*/
interface RemovePieceOptionsWithDataSetId extends RemovePieceOptionsBase {
/** The Data Set ID containing the piece to remove */
dataSetId: number
/**
* Logger instance (required for storage context creation)
* @see https://github.com/filecoin-project/filecoin-pin/issues/252
*/
logger: Logger
}

/**
* Options for removing a piece when you have an existing StorageContext
*
* This is useful for library/server usage where you already have a storage
* context and want to remove multiple pieces without recreating the context.
*/
interface RemovePieceOptionsWithStorage extends RemovePieceOptionsBase {
/** Existing storage context bound to a Data Set */
storage: StorageContext
}

/**
* Options for removing a piece from a Data Set
*
* Supports two patterns:
* - With dataSetId: Creates temporary storage context (CLI pattern)
* - With storage: Reuses existing context (library/server pattern)
*/
export type RemovePieceOptions = RemovePieceOptionsWithDataSetId | RemovePieceOptionsWithStorage

/**
* Remove a piece from a Data Set
*
* This function demonstrates the pattern for removing pieces via Synapse SDK.
* It supports two usage patterns:
*
* Pattern 1 - With dataSetId (typical CLI usage):
* ```typescript
* const txHash = await removePiece('baga...', {
* synapse,
* dataSetId: 42,
* logger,
* onProgress: (event) => console.log(event.type),
* waitForConfirmation: true
* })
* ```
*
* Pattern 2 - With existing StorageContext (library/server usage):
* ```typescript
* const { storage } = await createStorageContext(synapse, logger, {...})
* const txHash = await removePiece('baga...', {
* synapse,
* storage,
* onProgress: (event) => console.log(event.type)
* })
* ```
*
* @param pieceCid - The Piece CID to remove from the Data Set
* @param options - Configuration options (dataSetId or storage context)
* @returns Transaction hash of the removal operation
* @throws Error if storage context is not bound to a Data Set
*/
export async function removePiece(pieceCid: string, options: RemovePieceOptions): Promise<`0x${string}` | string> {
// Check dataSetId first
if (isRemovePieceOptionsWithDataSetId(options)) {
const { dataSetId, logger, synapse } = options
const { storage } = await createStorageContext(synapse, logger, { dataset: { useExisting: dataSetId } })
return executeRemovePiece(pieceCid, dataSetId, storage, options)
}

// Handle existing storage context (library/server usage)
if (isRemovePieceOptionsWithStorage(options)) {
const dataSetId = options.storage.dataSetId
if (dataSetId == null) {
throw new Error(
'Storage context must be bound to a Data Set before removing pieces. Use createStorageContext with dataset.useExisting to bind to a Data Set.'
)
}
return executeRemovePiece(pieceCid, dataSetId, options.storage, options)
}

// Should never get here, but we need some clear error message if we do.
throw new Error('Invalid options: must provide either dataSetId or storage context')
}

/**
* Type guard to check if options include dataSetId
*/
function isRemovePieceOptionsWithDataSetId(options: RemovePieceOptions): options is RemovePieceOptionsWithDataSetId {
return 'dataSetId' in options
}

/**
* Type guard to check if options include storage context
*/
function isRemovePieceOptionsWithStorage(options: RemovePieceOptions): options is RemovePieceOptionsWithStorage {
return 'storage' in options && options.storage != null
}

/**
* Execute the piece removal operation
*
* This internal function handles the actual removal:
* 1. Submits transaction via storageContext.deletePiece()
* 2. Optionally waits for confirmation if requested
* 3. Emits progress events throughout the process
*
* @param pieceCid - The Piece CID to remove
* @param dataSetId - The Data Set ID (for progress events)
* @param storageContext - Storage context bound to the Data Set
* @param options - Base options including callbacks and confirmation settings
* @returns Transaction hash of the removal
*/
async function executeRemovePiece(
pieceCid: string,
dataSetId: number,
storageContext: StorageContext,
options: RemovePieceOptionsBase
): Promise<`0x${string}` | string> {
const { onProgress, synapse, waitForConfirmation } = options

onProgress?.({ type: 'remove-piece:submitting', data: { pieceCid, dataSetId } })
const txHash = await storageContext.deletePiece(pieceCid)
onProgress?.({ type: 'remove-piece:submitted', data: { pieceCid, dataSetId, txHash } })

let isConfirmed = false
if (waitForConfirmation === true) {
onProgress?.({ type: 'remove-piece:confirming', data: { pieceCid, dataSetId, txHash } })
try {
await synapse.getProvider().waitForTransaction(txHash, WAIT_CONFIRMATIONS, WAIT_TIMEOUT_MS)
isConfirmed = true
} catch (error: unknown) {
// Confirmation timeout is non-fatal - transaction may still succeed
onProgress?.({
type: 'remove-piece:confirmation-failed',
data: { pieceCid, dataSetId, txHash, message: getErrorMessage(error) as string },
})
}
}

onProgress?.({ type: 'remove-piece:complete', data: { txHash, confirmed: isConfirmed } })
return txHash
}
1 change: 1 addition & 0 deletions src/rm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './remove-piece.js'
Loading
Loading