1- import { HttpError , type RequestJsonErrors , request } from 'iso-web/http'
1+ import { HttpError , type RequestErrors , request } from 'iso-web/http'
22import type { Account , Chain , Client , Hex , Transport } from 'viem'
3- import { DeletePieceError } from '../errors/pdp.ts'
3+ import { DeletePieceError , TooManyPiecesQueuedError } from '../errors/pdp.ts'
4+ import { AtLeastOnePieceRequiredError , TooManyPiecesError } from '../errors/warm-storage.ts'
45import { signSchedulePieceRemovals } from '../typed-data/sign-schedule-piece-removals.ts'
5- import { RETRY_CONSTANTS } from '../utils/constants.ts'
6+ import { RETRY_CONSTANTS , SIZE_CONSTANTS } from '../utils/constants.ts'
67
7- export namespace deletePiece {
8+ const MAX_CURIO_PIECE_ID = ( 1n << 63n ) - 1n
9+
10+ export namespace deletePieces {
811 export type OptionsType = {
912 serviceURL : string
1013 dataSetId : bigint
11- pieceId : bigint
14+ pieceIds : bigint [ ]
1215 extraData : Hex
1316 /** The number of retries. Defaults to 2. */
1417 retryCount ?: number
@@ -18,47 +21,86 @@ export namespace deletePiece {
1821 export type OutputType = {
1922 hash : Hex
2023 }
21- export type ErrorType = DeletePieceError | RequestJsonErrors
24+ export type ErrorType =
25+ | AtLeastOnePieceRequiredError
26+ | TooManyPiecesError
27+ | RangeError
28+ | DeletePieceError
29+ | RequestErrors
2230}
2331
2432/**
25- * Delete a piece from a data set on the PDP API.
33+ * Delete pieces from a data set on the PDP API in one transaction .
2634 *
2735 * DELETE /pdp/data-sets/{dataSetId}/pieces/{pieceId}
2836 *
29- * @param options - {@link deletePiece.OptionsType}
30- * @returns Hash of the delete operation {@link deletePiece.OutputType}
31- * @throws Errors {@link deletePiece.ErrorType}
37+ * Curio uses the first piece ID in the URL for backwards-compatible routing and
38+ * the pieceIds request field as the authoritative list when it is non-empty.
39+ *
40+ * @param options - {@link deletePieces.OptionsType}
41+ * @returns Hash of the delete operation {@link deletePieces.OutputType}
42+ * @throws Errors {@link deletePieces.ErrorType}
3243 */
33- export async function deletePiece ( options : deletePiece . OptionsType ) : Promise < deletePiece . OutputType > {
34- const { serviceURL, dataSetId, pieceId, extraData } = options
35- const response = await request . json . delete < { txHash : Hex } > (
36- new URL ( `pdp/data-sets/${ dataSetId } /pieces/${ pieceId } ` , serviceURL ) ,
37- {
38- body : { extraData } ,
39- timeout : RETRY_CONSTANTS . TIMEOUT ,
40- retry : {
41- retries : options . retryCount ,
42- minTimeout : options . retryDelay ?? RETRY_CONSTANTS . RETRY_DELAY ,
43- shouldRetry : ( ctx ) => HttpError . is ( ctx . error ) && ctx . error . code === 429 ,
44- } ,
45- }
46- )
44+ export async function deletePieces ( options : deletePieces . OptionsType ) : Promise < deletePieces . OutputType > {
45+ const { serviceURL, dataSetId, extraData } = options
46+ const pieceIds = normalizeDeletePieceIds ( options . pieceIds )
47+
48+ // Curio accepts uint64 JSON numbers. Construct the array from bigint decimal
49+ // strings so IDs above Number.MAX_SAFE_INTEGER are not rounded by JSON.stringify.
50+ const body = `{"extraData":${ JSON . stringify ( extraData ) } ,"pieceIds":[${ pieceIds . join ( ',' ) } ]}`
51+ const response = await request . delete ( new URL ( `pdp/data-sets/${ dataSetId } /pieces/${ pieceIds [ 0 ] } ` , serviceURL ) , {
52+ body,
53+ headers : { 'content-type' : 'application/json' } ,
54+ timeout : RETRY_CONSTANTS . TIMEOUT ,
55+ retry : {
56+ retries : options . retryCount ,
57+ minTimeout : options . retryDelay ?? RETRY_CONSTANTS . RETRY_DELAY ,
58+ } ,
59+ } )
4760
4861 if ( response . error ) {
4962 if ( HttpError . is ( response . error ) ) {
63+ if ( response . error . code === 429 ) {
64+ throw new TooManyPiecesQueuedError ( )
65+ }
5066 throw new DeletePieceError ( await response . error . response . text ( ) )
5167 }
5268 throw response . error
5369 }
5470
55- return { hash : response . result . txHash }
71+ const result = ( await response . result . json ( ) ) as { txHash : Hex }
72+ return { hash : result . txHash }
5673}
5774
58- export namespace schedulePieceDeletion {
75+ /**
76+ * Validate a delete-pieces batch before signing or sending it.
77+ */
78+ export function validateDeletePiecesBatch ( pieceCount : number ) : void {
79+ if ( ! Number . isInteger ( pieceCount ) || pieceCount < 1 ) {
80+ throw new AtLeastOnePieceRequiredError ( )
81+ }
82+ if ( pieceCount > SIZE_CONSTANTS . MAX_DELETE_PIECES_BATCH_SIZE ) {
83+ throw new TooManyPiecesError ( pieceCount , SIZE_CONSTANTS . MAX_DELETE_PIECES_BATCH_SIZE )
84+ }
85+ }
86+
87+ function normalizeDeletePieceIds ( pieceIds : bigint [ ] ) : bigint [ ] {
88+ const normalized = [ ...new Set ( pieceIds ) ]
89+ validateDeletePiecesBatch ( normalized . length )
90+
91+ for ( const pieceId of normalized ) {
92+ if ( pieceId < 0n || pieceId > MAX_CURIO_PIECE_ID ) {
93+ throw new RangeError ( `Piece ID ${ pieceId } is outside Curio's supported range of 0 to ${ MAX_CURIO_PIECE_ID } ` )
94+ }
95+ }
96+
97+ return normalized
98+ }
99+
100+ export namespace schedulePieceDeletions {
59101 export type OptionsType = {
60- /** The piece ID to delete. */
61- pieceId : bigint
102+ /** The piece IDs to delete. Duplicate IDs are removed before signing . */
103+ pieceIds : bigint [ ]
62104 /** The data set ID to delete the piece from. */
63105 dataSetId : bigint
64106 /** The client data set id (nonce) to use for the signature. Must be unique for each data set. */
@@ -70,23 +112,23 @@ export namespace schedulePieceDeletion {
70112 /** The delay with exponential backoff between retries in milliseconds. Defaults to {@link RETRY_CONSTANTS.RETRY_DELAY}. */
71113 retryDelay ?: number
72114 }
73- export type OutputType = deletePiece . OutputType
74- export type ErrorType = deletePiece . ErrorType
115+ export type OutputType = deletePieces . OutputType
116+ export type ErrorType = deletePieces . ErrorType
75117}
76118
77119/**
78- * Schedule a piece deletion
120+ * Schedule piece deletions in one transaction.
79121 *
80122 * Call the Service Provider API to schedule the piece deletion.
81123 *
82124 * @param client - The client to use to schedule the piece deletion.
83- * @param options - {@link schedulePieceDeletion .OptionsType}
84- * @returns schedule piece deletion operation hash {@link schedulePieceDeletion .OutputType}
85- * @throws Errors {@link schedulePieceDeletion .ErrorType}
125+ * @param options - {@link schedulePieceDeletions .OptionsType}
126+ * @returns Schedule piece deletions operation hash {@link schedulePieceDeletions .OutputType}
127+ * @throws Errors {@link schedulePieceDeletions .ErrorType}
86128 *
87129 * @example
88130 * ```ts
89- * import { schedulePieceDeletion } from '@filoz/synapse-core/sp'
131+ * import { schedulePieceDeletions } from '@filoz/synapse-core/sp'
90132 * import { createWalletClient, http } from 'viem'
91133 * import { privateKeyToAccount } from 'viem/accounts'
92134 * import { calibration } from '@filoz/synapse-core/chains'
@@ -98,8 +140,8 @@ export namespace schedulePieceDeletion {
98140 * transport: http(),
99141 * })
100142 *
101- * const result = await schedulePieceDeletion (client, {
102- * pieceId: 1n ,
143+ * const result = await schedulePieceDeletions (client, {
144+ * pieceIds: [1n, 2n] ,
103145 * dataSetId: 1n,
104146 * clientDataSetId: 1n,
105147 * serviceURL: 'https://pdp.example.com',
@@ -108,19 +150,56 @@ export namespace schedulePieceDeletion {
108150 * console.log(result.hash)
109151 * ```
110152 */
111- export async function schedulePieceDeletion (
153+ export async function schedulePieceDeletions (
112154 client : Client < Transport , Chain , Account > ,
113- options : schedulePieceDeletion . OptionsType
114- ) : Promise < schedulePieceDeletion . OutputType > {
115- return deletePiece ( {
155+ options : schedulePieceDeletions . OptionsType
156+ ) : Promise < schedulePieceDeletions . OutputType > {
157+ const pieceIds = normalizeDeletePieceIds ( options . pieceIds )
158+
159+ return deletePieces ( {
116160 serviceURL : options . serviceURL ,
117161 dataSetId : options . dataSetId ,
118- pieceId : options . pieceId ,
162+ pieceIds ,
119163 extraData : await signSchedulePieceRemovals ( client , {
120164 clientDataSetId : options . clientDataSetId ,
121- pieceIds : [ options . pieceId ] ,
165+ pieceIds,
122166 } ) ,
123167 retryCount : options . retryCount ,
124168 retryDelay : options . retryDelay ,
125169 } )
126170}
171+
172+ export namespace deletePiece {
173+ export type OptionsType = Omit < deletePieces . OptionsType , 'pieceIds' > & { pieceId : bigint }
174+ export type OutputType = deletePieces . OutputType
175+ export type ErrorType = deletePieces . ErrorType
176+ }
177+
178+ /**
179+ * Delete one piece from a data set on the PDP API.
180+ *
181+ * @deprecated Use {@link deletePieces} instead.
182+ */
183+ export function deletePiece ( options : deletePiece . OptionsType ) : Promise < deletePiece . OutputType > {
184+ const { pieceId, ...rest } = options
185+ return deletePieces ( { ...rest , pieceIds : [ pieceId ] } )
186+ }
187+
188+ export namespace schedulePieceDeletion {
189+ export type OptionsType = Omit < schedulePieceDeletions . OptionsType , 'pieceIds' > & { pieceId : bigint }
190+ export type OutputType = schedulePieceDeletions . OutputType
191+ export type ErrorType = schedulePieceDeletions . ErrorType
192+ }
193+
194+ /**
195+ * Schedule one piece deletion.
196+ *
197+ * @deprecated Use {@link schedulePieceDeletions} instead.
198+ */
199+ export function schedulePieceDeletion (
200+ client : Client < Transport , Chain , Account > ,
201+ options : schedulePieceDeletion . OptionsType
202+ ) : Promise < schedulePieceDeletion . OutputType > {
203+ const { pieceId, ...rest } = options
204+ return schedulePieceDeletions ( client , { ...rest , pieceIds : [ pieceId ] } )
205+ }
0 commit comments