11#!/usr/bin/env bun
22
33/**
4- * Clean stats data from Arkiv since a given timestamp.
4+ * Clean stats data from Arkiv since a given timestamp or delete a specific entity .
55 *
66 * Usage:
77 * bun clean.ts --timestamp TIMESTAMP # Clean all stats (hourly and daily) since timestamp
88 * bun clean.ts --timestamp TIMESTAMP --type hourly # Clean only hourly stats since timestamp
99 * bun clean.ts --timestamp TIMESTAMP --type daily # Clean only daily stats since timestamp
10+ * bun clean.ts --entity-key ENTITY_KEY # Delete a specific entity by its key
11+ *
12+ * Options:
13+ * --dry-run # Dry run, don't delete anything
1014 *
1115 * Timestamp can be provided as:
1216 * - Unix timestamp in seconds (e.g., 1704067200)
@@ -24,8 +28,7 @@ import { kaolin, localhost } from "@arkiv-network/sdk/chains";
2428import { eq , gt } from "@arkiv-network/sdk/query" ;
2529import type { Chain } from "viem" ;
2630import { defineChain } from "viem" ;
27-
28- const DATA_VERSION = "0.12" ;
31+ import { DATA_VERSION } from "./src/arkiv" ;
2932
3033type AggregatedDataType = "hourly" | "daily" ;
3134
@@ -105,6 +108,7 @@ async function getStatsEntitiesSinceTimestamp(
105108 . buildQuery ( )
106109 . where ( whereConditions )
107110 . withAttributes ( )
111+ . withPayload ( )
108112 . limit ( limit ) ;
109113
110114 const entities : Entity [ ] = [ ] ;
@@ -119,6 +123,33 @@ async function getStatsEntitiesSinceTimestamp(
119123 return entities ;
120124}
121125
126+ /**
127+ * Delete a single entity by its key
128+ */
129+ async function deleteEntityByKey (
130+ entityKey : string ,
131+ dryRun ?: boolean ,
132+ ) : Promise < void > {
133+ const key = entityKey . startsWith ( "0x" )
134+ ? ( entityKey as `0x${string } `)
135+ : ( `0x${ entityKey } ` as `0x${string } `) ;
136+
137+ console . log ( `\n🗑️ Deleting entity: ${ key } ` ) ;
138+
139+ if ( dryRun ) {
140+ console . log ( " (Dry run - would delete this entity)" ) ;
141+ return ;
142+ }
143+
144+ try {
145+ await arkivWalletClient . deleteEntity ( { entityKey : key } ) ;
146+ console . log ( `\n✅ Successfully deleted entity: ${ key } ` ) ;
147+ } catch ( error ) {
148+ console . error ( `\n❌ Failed to delete entity ${ key } :` , error ) ;
149+ throw error ;
150+ }
151+ }
152+
122153/**
123154 * Delete entities in batches
124155 */
@@ -142,7 +173,7 @@ async function deleteEntities(entities: Entity[]): Promise<void> {
142173 } ) ) ;
143174
144175 try {
145- const receipt = await arkivWalletClient . mutateEntities ( {
176+ await arkivWalletClient . mutateEntities ( {
146177 deletes,
147178 } ) ;
148179 deletedCount += batch . length ;
@@ -164,7 +195,11 @@ async function deleteEntities(entities: Entity[]): Promise<void> {
164195/**
165196 * Main cleanup function
166197 */
167- async function cleanStats ( timestamp : number , statsType ?: AggregatedDataType ) {
198+ async function cleanStats (
199+ timestamp : number ,
200+ statsType ?: AggregatedDataType ,
201+ dryRun ?: boolean ,
202+ ) {
168203 const timestampDate = new Date ( timestamp * 1000 ) ;
169204 console . log ( `\n🧹 Cleaning stats data since ${ timestampDate . toISOString ( ) } ` ) ;
170205 if ( statsType ) {
@@ -201,6 +236,14 @@ async function cleanStats(timestamp: number, statsType?: AggregatedDataType) {
201236 console . log ( ` - Daily stats: ${ dailyCount } ` ) ;
202237 }
203238
239+ if ( dryRun ) {
240+ // show all entities
241+ entities . forEach ( ( entity ) => {
242+ console . log ( entity . key ) ;
243+ console . log ( entity . toText ( ) ) ;
244+ } ) ;
245+ process . exit ( 0 ) ;
246+ }
204247 // Confirm deletion
205248 console . log ( "\n⚠️ This will permanently delete the above entities." ) ;
206249 console . log ( " Press Ctrl+C to cancel, or wait 3 seconds to continue..." ) ;
@@ -221,44 +264,89 @@ async function cleanStats(timestamp: number, statsType?: AggregatedDataType) {
221264//
222265function printUsageAndExit ( ) {
223266 console . error (
224- "Usage: bun clean.ts --timestamp TIMESTAMP [--type hourly|daily ]\n" +
267+ "Usage: bun clean.ts [ --timestamp TIMESTAMP | --entity-key KEY] [options ]\n" +
225268 " --timestamp TIMESTAMP Unix timestamp (seconds) or ISO 8601 date string\n" +
226- " --type TYPE Optional: 'hourly' or 'daily' (default: both)" ,
269+ " --entity-key KEY Entity key to delete (0x-prefixed hex string)\n" +
270+ " --type TYPE Optional: 'hourly' or 'daily' (only with --timestamp, default: both)\n" +
271+ " --dry-run Dry run, don't delete anything" ,
227272 ) ;
228273 process . exit ( 1 ) ;
229274}
230275
231276const args = process . argv . slice ( 2 ) ;
232277const timestampIdx = args . indexOf ( "--timestamp" ) ;
278+ const entityKeyIdx = args . indexOf ( "--entity-key" ) ;
233279const typeIdx = args . indexOf ( "--type" ) ;
280+ const dryRun = args . includes ( "--dry-run" ) ;
234281
235- if ( timestampIdx === - 1 ) {
236- console . error ( "❌ Missing required argument: --timestamp" ) ;
282+ // Validate that either timestamp or entity-key is provided, but not both
283+ if ( timestampIdx === - 1 && entityKeyIdx === - 1 ) {
284+ console . error ( "❌ Missing required argument: --timestamp or --entity-key" ) ;
237285 printUsageAndExit ( ) ;
238286}
239287
240- const timestampStr = args [ timestampIdx + 1 ] ;
241- if ( ! timestampStr ) {
242- console . error ( "❌ Missing timestamp value" ) ;
288+ if ( timestampIdx !== - 1 && entityKeyIdx !== - 1 ) {
289+ console . error (
290+ "❌ Cannot use both --timestamp and --entity-key. Use one or the other." ,
291+ ) ;
243292 printUsageAndExit ( ) ;
244293}
245294
246- let statsType : AggregatedDataType | undefined ;
247- if ( typeIdx !== - 1 ) {
248- const typeValue = args [ typeIdx + 1 ] ;
249- if ( typeValue !== "hourly" && typeValue !== "daily" ) {
250- console . error ( "❌ Invalid stats type. Must be 'hourly' or 'daily' " ) ;
295+ // Handle entity-key mode
296+ if ( entityKeyIdx !== - 1 ) {
297+ const entityKey = args [ entityKeyIdx + 1 ] ;
298+ if ( ! entityKey ) {
299+ console . error ( "❌ Missing entity key value " ) ;
251300 printUsageAndExit ( ) ;
252301 }
253- statsType = typeValue ;
254- }
255302
256- ( async ( ) => {
257- try {
258- const timestamp = parseTimestamp ( timestampStr ) ;
259- await cleanStats ( timestamp , statsType ) ;
260- } catch ( error ) {
261- console . error ( "❌ Error:" , error ) ;
262- process . exit ( 1 ) ;
303+ if ( typeIdx !== - 1 ) {
304+ console . error (
305+ "❌ --type option is only valid with --timestamp, not with --entity-key" ,
306+ ) ;
307+ printUsageAndExit ( ) ;
308+ }
309+
310+ ( async ( ) => {
311+ try {
312+ if ( dryRun ) {
313+ console . log ( "🧹 Dry run, not deleting anything" ) ;
314+ }
315+ await deleteEntityByKey ( entityKey , dryRun ) ;
316+ console . log ( "\n🎉 Entity deletion completed successfully!" ) ;
317+ } catch ( error ) {
318+ console . error ( "❌ Error:" , error ) ;
319+ process . exit ( 1 ) ;
320+ }
321+ } ) ( ) ;
322+ } else {
323+ // Handle timestamp mode
324+ const timestampStr = args [ timestampIdx + 1 ] ;
325+ if ( ! timestampStr ) {
326+ console . error ( "❌ Missing timestamp value" ) ;
327+ printUsageAndExit ( ) ;
263328 }
264- } ) ( ) ;
329+
330+ let statsType : AggregatedDataType | undefined ;
331+ if ( typeIdx !== - 1 ) {
332+ const typeValue = args [ typeIdx + 1 ] ;
333+ if ( typeValue !== "hourly" && typeValue !== "daily" ) {
334+ console . error ( "❌ Invalid stats type. Must be 'hourly' or 'daily'" ) ;
335+ printUsageAndExit ( ) ;
336+ }
337+ statsType = typeValue as AggregatedDataType ;
338+ }
339+
340+ ( async ( ) => {
341+ try {
342+ if ( dryRun ) {
343+ console . log ( "🧹 Dry run, not deleting anything" ) ;
344+ }
345+ const timestamp = parseTimestamp ( timestampStr ) ;
346+ await cleanStats ( timestamp , statsType , dryRun ) ;
347+ } catch ( error ) {
348+ console . error ( "❌ Error:" , error ) ;
349+ process . exit ( 1 ) ;
350+ }
351+ } ) ( ) ;
352+ }
0 commit comments