appwrite-utils-cli 1.7.5 → 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/LegacyAdapter.js +43 -21
- package/dist/adapters/TablesDBAdapter.js +8 -1
- package/dist/collections/wipeOperations.js +16 -9
- package/package.json +1 -1
- package/src/adapters/LegacyAdapter.ts +82 -52
- package/src/adapters/TablesDBAdapter.ts +47 -40
- package/src/collections/wipeOperations.ts +168 -161
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* code can use modern TablesDB patterns while maintaining compatibility with
|
|
7
7
|
* older Appwrite instances.
|
|
8
8
|
*/
|
|
9
|
+
import { Query } from "node-appwrite";
|
|
9
10
|
import { BaseAdapter, AdapterError, UnsupportedOperationError } from './DatabaseAdapter.js';
|
|
10
11
|
/**
|
|
11
12
|
* LegacyAdapter - Translates TablesDB calls to legacy Databases API
|
|
@@ -308,30 +309,51 @@ export class LegacyAdapter extends BaseAdapter {
|
|
|
308
309
|
throw new UnsupportedOperationError('bulkUpsertRows', 'legacy');
|
|
309
310
|
}
|
|
310
311
|
async bulkDeleteRows(params) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
312
|
+
try {
|
|
313
|
+
// Try to use deleteDocuments with queries first (more efficient)
|
|
314
|
+
const queries = params.rowIds.map(id => Query.equal('$id', id));
|
|
315
|
+
const result = await this.databases.deleteDocuments(params.databaseId, params.tableId, // Maps tableId to collectionId
|
|
316
|
+
queries);
|
|
317
|
+
return {
|
|
318
|
+
data: result,
|
|
319
|
+
total: params.rowIds.length
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
// If deleteDocuments with queries fails, fall back to individual deletes
|
|
324
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
325
|
+
// Check if the error indicates that deleteDocuments with queries is not supported
|
|
326
|
+
if (errorMessage.includes('not supported') || errorMessage.includes('invalid') || errorMessage.includes('queries')) {
|
|
327
|
+
// Fall back to individual deletions
|
|
328
|
+
const results = [];
|
|
329
|
+
const errors = [];
|
|
330
|
+
for (const rowId of params.rowIds) {
|
|
331
|
+
try {
|
|
332
|
+
await this.deleteRow({
|
|
333
|
+
databaseId: params.databaseId,
|
|
334
|
+
tableId: params.tableId,
|
|
335
|
+
id: rowId
|
|
336
|
+
});
|
|
337
|
+
results.push({ id: rowId, deleted: true });
|
|
338
|
+
}
|
|
339
|
+
catch (individualError) {
|
|
340
|
+
errors.push({
|
|
341
|
+
rowId,
|
|
342
|
+
error: individualError instanceof Error ? individualError.message : 'Unknown error'
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
data: results,
|
|
348
|
+
total: results.length,
|
|
349
|
+
errors: errors.length > 0 ? errors : undefined
|
|
350
|
+
};
|
|
322
351
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
327
|
-
});
|
|
352
|
+
else {
|
|
353
|
+
// Re-throw the original error if it's not a support issue
|
|
354
|
+
throw new AdapterError(`Failed to bulk delete rows (legacy): ${errorMessage}`, 'BULK_DELETE_ROWS_FAILED', error instanceof Error ? error : undefined);
|
|
328
355
|
}
|
|
329
356
|
}
|
|
330
|
-
return {
|
|
331
|
-
data: results,
|
|
332
|
-
total: results.length,
|
|
333
|
-
errors: errors.length > 0 ? errors : undefined
|
|
334
|
-
};
|
|
335
357
|
}
|
|
336
358
|
// Metadata and Capabilities
|
|
337
359
|
getMetadata() {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* without any translation layer. It uses object notation parameters
|
|
6
6
|
* and returns Models.Row instead of Models.Document.
|
|
7
7
|
*/
|
|
8
|
+
import { Query } from "node-appwrite";
|
|
8
9
|
import { BaseAdapter, AdapterError } from './DatabaseAdapter.js';
|
|
9
10
|
/**
|
|
10
11
|
* TablesDBAdapter implementation for native TablesDB API
|
|
@@ -269,7 +270,13 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
269
270
|
}
|
|
270
271
|
async bulkDeleteRows(params) {
|
|
271
272
|
try {
|
|
272
|
-
|
|
273
|
+
// Convert rowIds to queries for the deleteRows API
|
|
274
|
+
const queries = params.rowIds.map(id => Query.equal('$id', id));
|
|
275
|
+
const result = await this.tablesDB.deleteRows({
|
|
276
|
+
databaseId: params.databaseId,
|
|
277
|
+
tableId: params.tableId,
|
|
278
|
+
queries: queries
|
|
279
|
+
});
|
|
273
280
|
return {
|
|
274
281
|
data: result,
|
|
275
282
|
total: params.rowIds.length
|
|
@@ -208,8 +208,8 @@ export const wipeTableRows = async (adapter, databaseId, tableId) => {
|
|
|
208
208
|
if (adapter.bulkDeleteRows) {
|
|
209
209
|
try {
|
|
210
210
|
// Attempt bulk deletion (available in TablesDB)
|
|
211
|
-
await tryBulkDeletion(adapter, databaseId, tableId, rowIds, BULK_DELETE_BATCH_SIZE, MAX_CONCURRENT_OPERATIONS);
|
|
212
|
-
totalDeleted +=
|
|
211
|
+
const deletedCount = await tryBulkDeletion(adapter, databaseId, tableId, rowIds, BULK_DELETE_BATCH_SIZE, MAX_CONCURRENT_OPERATIONS);
|
|
212
|
+
totalDeleted += deletedCount;
|
|
213
213
|
progress.update(totalDeleted);
|
|
214
214
|
}
|
|
215
215
|
catch (bulkError) {
|
|
@@ -224,15 +224,15 @@ export const wipeTableRows = async (adapter, databaseId, tableId) => {
|
|
|
224
224
|
else {
|
|
225
225
|
MessageFormatter.progress(`Bulk deletion failed (${errorMessage}), falling back to individual deletion for ${rows.length} rows`, { prefix: "Wipe" });
|
|
226
226
|
}
|
|
227
|
-
await tryIndividualDeletion(adapter, databaseId, tableId, rows, INDIVIDUAL_DELETE_BATCH_SIZE, MAX_CONCURRENT_OPERATIONS, progress, totalDeleted);
|
|
228
|
-
totalDeleted +=
|
|
227
|
+
const deletedCount = await tryIndividualDeletion(adapter, databaseId, tableId, rows, INDIVIDUAL_DELETE_BATCH_SIZE, MAX_CONCURRENT_OPERATIONS, progress, totalDeleted);
|
|
228
|
+
totalDeleted += deletedCount;
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
else {
|
|
232
232
|
// Bulk deletion not available, use optimized individual deletion
|
|
233
233
|
MessageFormatter.progress(`Using individual deletion for ${rows.length} rows (bulk deletion not available)`, { prefix: "Wipe" });
|
|
234
|
-
await tryIndividualDeletion(adapter, databaseId, tableId, rows, INDIVIDUAL_DELETE_BATCH_SIZE, MAX_CONCURRENT_OPERATIONS, progress, totalDeleted);
|
|
235
|
-
totalDeleted +=
|
|
234
|
+
const deletedCount = await tryIndividualDeletion(adapter, databaseId, tableId, rows, INDIVIDUAL_DELETE_BATCH_SIZE, MAX_CONCURRENT_OPERATIONS, progress, totalDeleted);
|
|
235
|
+
totalDeleted += deletedCount;
|
|
236
236
|
}
|
|
237
237
|
// Set up cursor for next iteration
|
|
238
238
|
if (rows.length < FETCH_BATCH_SIZE) {
|
|
@@ -268,9 +268,11 @@ async function tryBulkDeletion(adapter, databaseId, tableId, rowIds, batchSize,
|
|
|
268
268
|
}
|
|
269
269
|
const limit = pLimit(maxConcurrent);
|
|
270
270
|
const batches = chunk(rowIds, batchSize);
|
|
271
|
+
let successfullyDeleted = 0;
|
|
271
272
|
const deletePromises = batches.map((batch) => limit(async () => {
|
|
272
273
|
try {
|
|
273
|
-
await tryAwaitWithRetry(async () => adapter.bulkDeleteRows({ databaseId, tableId, rowIds: batch }));
|
|
274
|
+
const result = await tryAwaitWithRetry(async () => adapter.bulkDeleteRows({ databaseId, tableId, rowIds: batch }));
|
|
275
|
+
successfullyDeleted += batch.length; // Assume success if no error thrown
|
|
274
276
|
}
|
|
275
277
|
catch (error) {
|
|
276
278
|
const errorMessage = error.message || String(error);
|
|
@@ -286,6 +288,7 @@ async function tryBulkDeletion(adapter, databaseId, tableId, rowIds, batchSize,
|
|
|
286
288
|
}
|
|
287
289
|
}));
|
|
288
290
|
await Promise.all(deletePromises);
|
|
291
|
+
return successfullyDeleted;
|
|
289
292
|
}
|
|
290
293
|
/**
|
|
291
294
|
* Helper function for fallback individual deletion
|
|
@@ -294,16 +297,19 @@ async function tryIndividualDeletion(adapter, databaseId, tableId, rows, batchSi
|
|
|
294
297
|
const limit = pLimit(maxConcurrent);
|
|
295
298
|
const batches = chunk(rows, batchSize);
|
|
296
299
|
let processedInBatch = 0;
|
|
300
|
+
let successfullyDeleted = 0;
|
|
297
301
|
const deletePromises = batches.map((batch) => limit(async () => {
|
|
298
302
|
const batchDeletePromises = batch.map(async (row) => {
|
|
299
303
|
try {
|
|
300
304
|
await tryAwaitWithRetry(async () => adapter.deleteRow({ databaseId, tableId, id: row.$id }));
|
|
305
|
+
successfullyDeleted++;
|
|
301
306
|
}
|
|
302
307
|
catch (error) {
|
|
303
308
|
const errorMessage = error.message || String(error);
|
|
304
309
|
// Enhanced error handling for row deletion
|
|
305
310
|
if (errorMessage.includes("Row with the requested ID could not be found")) {
|
|
306
|
-
// Row already deleted,
|
|
311
|
+
// Row already deleted, count as success since it's gone
|
|
312
|
+
successfullyDeleted++;
|
|
307
313
|
}
|
|
308
314
|
else if (isCriticalError(errorMessage)) {
|
|
309
315
|
// Critical error, log and rethrow to stop operation
|
|
@@ -320,9 +326,10 @@ async function tryIndividualDeletion(adapter, databaseId, tableId, rows, batchSi
|
|
|
320
326
|
}
|
|
321
327
|
}
|
|
322
328
|
processedInBatch++;
|
|
323
|
-
progress.update(baseDeleted +
|
|
329
|
+
progress.update(baseDeleted + successfullyDeleted);
|
|
324
330
|
});
|
|
325
331
|
await Promise.all(batchDeletePromises);
|
|
326
332
|
}));
|
|
327
333
|
await Promise.all(deletePromises);
|
|
334
|
+
return successfullyDeleted;
|
|
328
335
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appwrite-utils-cli",
|
|
3
3
|
"description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.6",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -7,31 +7,32 @@
|
|
|
7
7
|
* older Appwrite instances.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type
|
|
14
|
-
type
|
|
15
|
-
type
|
|
16
|
-
type
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
type
|
|
21
|
-
type
|
|
22
|
-
type
|
|
23
|
-
type
|
|
24
|
-
type
|
|
25
|
-
type
|
|
26
|
-
type
|
|
27
|
-
type
|
|
28
|
-
type
|
|
29
|
-
type
|
|
30
|
-
type
|
|
31
|
-
type
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
10
|
+
import { Query } from "node-appwrite";
|
|
11
|
+
import {
|
|
12
|
+
BaseAdapter,
|
|
13
|
+
type CreateRowParams,
|
|
14
|
+
type UpdateRowParams,
|
|
15
|
+
type ListRowsParams,
|
|
16
|
+
type DeleteRowParams,
|
|
17
|
+
type CreateTableParams,
|
|
18
|
+
type UpdateTableParams,
|
|
19
|
+
type ListTablesParams,
|
|
20
|
+
type DeleteTableParams,
|
|
21
|
+
type GetTableParams,
|
|
22
|
+
type BulkCreateRowsParams,
|
|
23
|
+
type BulkUpsertRowsParams,
|
|
24
|
+
type BulkDeleteRowsParams,
|
|
25
|
+
type CreateIndexParams,
|
|
26
|
+
type ListIndexesParams,
|
|
27
|
+
type DeleteIndexParams,
|
|
28
|
+
type CreateAttributeParams,
|
|
29
|
+
type UpdateAttributeParams,
|
|
30
|
+
type DeleteAttributeParams,
|
|
31
|
+
type ApiResponse,
|
|
32
|
+
type AdapterMetadata,
|
|
33
|
+
AdapterError,
|
|
34
|
+
UnsupportedOperationError
|
|
35
|
+
} from './DatabaseAdapter.js';
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* LegacyAdapter - Translates TablesDB calls to legacy Databases API
|
|
@@ -586,33 +587,62 @@ export class LegacyAdapter extends BaseAdapter {
|
|
|
586
587
|
throw new UnsupportedOperationError('bulkUpsertRows', 'legacy');
|
|
587
588
|
}
|
|
588
589
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
590
|
+
async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
591
|
+
try {
|
|
592
|
+
// Try to use deleteDocuments with queries first (more efficient)
|
|
593
|
+
const queries = params.rowIds.map(id => Query.equal('$id', id));
|
|
594
|
+
|
|
595
|
+
const result = await this.databases.deleteDocuments(
|
|
596
|
+
params.databaseId,
|
|
597
|
+
params.tableId, // Maps tableId to collectionId
|
|
598
|
+
queries
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
data: result,
|
|
603
|
+
total: params.rowIds.length
|
|
604
|
+
};
|
|
605
|
+
} catch (error) {
|
|
606
|
+
// If deleteDocuments with queries fails, fall back to individual deletes
|
|
607
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
608
|
+
|
|
609
|
+
// Check if the error indicates that deleteDocuments with queries is not supported
|
|
610
|
+
if (errorMessage.includes('not supported') || errorMessage.includes('invalid') || errorMessage.includes('queries')) {
|
|
611
|
+
// Fall back to individual deletions
|
|
612
|
+
const results = [];
|
|
613
|
+
const errors = [];
|
|
614
|
+
|
|
615
|
+
for (const rowId of params.rowIds) {
|
|
616
|
+
try {
|
|
617
|
+
await this.deleteRow({
|
|
618
|
+
databaseId: params.databaseId,
|
|
619
|
+
tableId: params.tableId,
|
|
620
|
+
id: rowId
|
|
621
|
+
});
|
|
622
|
+
results.push({ id: rowId, deleted: true });
|
|
623
|
+
} catch (individualError) {
|
|
624
|
+
errors.push({
|
|
625
|
+
rowId,
|
|
626
|
+
error: individualError instanceof Error ? individualError.message : 'Unknown error'
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
data: results,
|
|
633
|
+
total: results.length,
|
|
634
|
+
errors: errors.length > 0 ? errors : undefined
|
|
635
|
+
};
|
|
636
|
+
} else {
|
|
637
|
+
// Re-throw the original error if it's not a support issue
|
|
638
|
+
throw new AdapterError(
|
|
639
|
+
`Failed to bulk delete rows (legacy): ${errorMessage}`,
|
|
640
|
+
'BULK_DELETE_ROWS_FAILED',
|
|
641
|
+
error instanceof Error ? error : undefined
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
616
646
|
|
|
617
647
|
// Metadata and Capabilities
|
|
618
648
|
|
|
@@ -6,31 +6,32 @@
|
|
|
6
6
|
* and returns Models.Row instead of Models.Document.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
type
|
|
15
|
-
type
|
|
16
|
-
type
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
type
|
|
21
|
-
type
|
|
22
|
-
type
|
|
23
|
-
type
|
|
24
|
-
type
|
|
25
|
-
type
|
|
26
|
-
type
|
|
27
|
-
type
|
|
28
|
-
type
|
|
29
|
-
type
|
|
30
|
-
type
|
|
31
|
-
type
|
|
32
|
-
|
|
33
|
-
|
|
9
|
+
import { Query } from "node-appwrite";
|
|
10
|
+
import {
|
|
11
|
+
BaseAdapter,
|
|
12
|
+
type DatabaseAdapter,
|
|
13
|
+
type CreateRowParams,
|
|
14
|
+
type UpdateRowParams,
|
|
15
|
+
type ListRowsParams,
|
|
16
|
+
type DeleteRowParams,
|
|
17
|
+
type CreateTableParams,
|
|
18
|
+
type UpdateTableParams,
|
|
19
|
+
type ListTablesParams,
|
|
20
|
+
type DeleteTableParams,
|
|
21
|
+
type GetTableParams,
|
|
22
|
+
type BulkCreateRowsParams,
|
|
23
|
+
type BulkUpsertRowsParams,
|
|
24
|
+
type BulkDeleteRowsParams,
|
|
25
|
+
type CreateIndexParams,
|
|
26
|
+
type ListIndexesParams,
|
|
27
|
+
type DeleteIndexParams,
|
|
28
|
+
type CreateAttributeParams,
|
|
29
|
+
type UpdateAttributeParams,
|
|
30
|
+
type DeleteAttributeParams,
|
|
31
|
+
type ApiResponse,
|
|
32
|
+
type AdapterMetadata,
|
|
33
|
+
AdapterError
|
|
34
|
+
} from './DatabaseAdapter.js';
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* TablesDBAdapter implementation for native TablesDB API
|
|
@@ -513,21 +514,27 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
513
514
|
}
|
|
514
515
|
}
|
|
515
516
|
|
|
516
|
-
|
|
517
|
-
try {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
517
|
+
async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
518
|
+
try {
|
|
519
|
+
// Convert rowIds to queries for the deleteRows API
|
|
520
|
+
const queries = params.rowIds.map(id => Query.equal('$id', id));
|
|
521
|
+
const result = await this.tablesDB.deleteRows({
|
|
522
|
+
databaseId: params.databaseId,
|
|
523
|
+
tableId: params.tableId,
|
|
524
|
+
queries: queries
|
|
525
|
+
});
|
|
526
|
+
return {
|
|
527
|
+
data: result,
|
|
528
|
+
total: params.rowIds.length
|
|
529
|
+
};
|
|
530
|
+
} catch (error) {
|
|
531
|
+
throw new AdapterError(
|
|
532
|
+
`Failed to bulk delete rows: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
533
|
+
'BULK_DELETE_ROWS_FAILED',
|
|
534
|
+
error instanceof Error ? error : undefined
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
531
538
|
|
|
532
539
|
// Metadata and Capabilities
|
|
533
540
|
getMetadata(): AdapterMetadata {
|
|
@@ -296,65 +296,65 @@ export const wipeTableRows = async (
|
|
|
296
296
|
// Try to use bulk deletion first, fall back to individual deletion
|
|
297
297
|
const rowIds = rows.map((row: any) => row.$id);
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
if (adapter.bulkDeleteRows) {
|
|
301
|
-
try {
|
|
302
|
-
// Attempt bulk deletion (available in TablesDB)
|
|
303
|
-
await tryBulkDeletion(adapter, databaseId, tableId, rowIds, BULK_DELETE_BATCH_SIZE, MAX_CONCURRENT_OPERATIONS);
|
|
304
|
-
totalDeleted +=
|
|
305
|
-
progress.update(totalDeleted);
|
|
306
|
-
} catch (bulkError) {
|
|
307
|
-
// Enhanced error handling: categorize the error and decide on fallback strategy
|
|
308
|
-
const errorMessage = bulkError instanceof Error ? bulkError.message : String(bulkError);
|
|
309
|
-
|
|
310
|
-
if (isRetryableError(errorMessage)) {
|
|
311
|
-
MessageFormatter.progress(
|
|
312
|
-
`Bulk deletion encountered retryable error, retrying with individual deletion for ${rows.length} rows`,
|
|
313
|
-
{ prefix: "Wipe" }
|
|
314
|
-
);
|
|
315
|
-
} else if (isBulkNotSupportedError(errorMessage)) {
|
|
316
|
-
MessageFormatter.progress(
|
|
317
|
-
`Bulk deletion not supported by server, switching to individual deletion for ${rows.length} rows`,
|
|
318
|
-
{ prefix: "Wipe" }
|
|
319
|
-
);
|
|
320
|
-
} else {
|
|
321
|
-
MessageFormatter.progress(
|
|
322
|
-
`Bulk deletion failed (${errorMessage}), falling back to individual deletion for ${rows.length} rows`,
|
|
323
|
-
{ prefix: "Wipe" }
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
await tryIndividualDeletion(
|
|
328
|
-
adapter,
|
|
329
|
-
databaseId,
|
|
330
|
-
tableId,
|
|
331
|
-
rows,
|
|
332
|
-
INDIVIDUAL_DELETE_BATCH_SIZE,
|
|
333
|
-
MAX_CONCURRENT_OPERATIONS,
|
|
334
|
-
progress,
|
|
335
|
-
totalDeleted
|
|
336
|
-
);
|
|
337
|
-
totalDeleted +=
|
|
338
|
-
}
|
|
339
|
-
} else {
|
|
340
|
-
// Bulk deletion not available, use optimized individual deletion
|
|
341
|
-
MessageFormatter.progress(
|
|
342
|
-
`Using individual deletion for ${rows.length} rows (bulk deletion not available)`,
|
|
343
|
-
{ prefix: "Wipe" }
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
await tryIndividualDeletion(
|
|
347
|
-
adapter,
|
|
348
|
-
databaseId,
|
|
349
|
-
tableId,
|
|
350
|
-
rows,
|
|
351
|
-
INDIVIDUAL_DELETE_BATCH_SIZE,
|
|
352
|
-
MAX_CONCURRENT_OPERATIONS,
|
|
353
|
-
progress,
|
|
354
|
-
totalDeleted
|
|
355
|
-
);
|
|
356
|
-
totalDeleted +=
|
|
357
|
-
}
|
|
299
|
+
// Check if bulk deletion is available and try it first
|
|
300
|
+
if (adapter.bulkDeleteRows) {
|
|
301
|
+
try {
|
|
302
|
+
// Attempt bulk deletion (available in TablesDB)
|
|
303
|
+
const deletedCount = await tryBulkDeletion(adapter, databaseId, tableId, rowIds, BULK_DELETE_BATCH_SIZE, MAX_CONCURRENT_OPERATIONS);
|
|
304
|
+
totalDeleted += deletedCount;
|
|
305
|
+
progress.update(totalDeleted);
|
|
306
|
+
} catch (bulkError) {
|
|
307
|
+
// Enhanced error handling: categorize the error and decide on fallback strategy
|
|
308
|
+
const errorMessage = bulkError instanceof Error ? bulkError.message : String(bulkError);
|
|
309
|
+
|
|
310
|
+
if (isRetryableError(errorMessage)) {
|
|
311
|
+
MessageFormatter.progress(
|
|
312
|
+
`Bulk deletion encountered retryable error, retrying with individual deletion for ${rows.length} rows`,
|
|
313
|
+
{ prefix: "Wipe" }
|
|
314
|
+
);
|
|
315
|
+
} else if (isBulkNotSupportedError(errorMessage)) {
|
|
316
|
+
MessageFormatter.progress(
|
|
317
|
+
`Bulk deletion not supported by server, switching to individual deletion for ${rows.length} rows`,
|
|
318
|
+
{ prefix: "Wipe" }
|
|
319
|
+
);
|
|
320
|
+
} else {
|
|
321
|
+
MessageFormatter.progress(
|
|
322
|
+
`Bulk deletion failed (${errorMessage}), falling back to individual deletion for ${rows.length} rows`,
|
|
323
|
+
{ prefix: "Wipe" }
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const deletedCount = await tryIndividualDeletion(
|
|
328
|
+
adapter,
|
|
329
|
+
databaseId,
|
|
330
|
+
tableId,
|
|
331
|
+
rows,
|
|
332
|
+
INDIVIDUAL_DELETE_BATCH_SIZE,
|
|
333
|
+
MAX_CONCURRENT_OPERATIONS,
|
|
334
|
+
progress,
|
|
335
|
+
totalDeleted
|
|
336
|
+
);
|
|
337
|
+
totalDeleted += deletedCount;
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
// Bulk deletion not available, use optimized individual deletion
|
|
341
|
+
MessageFormatter.progress(
|
|
342
|
+
`Using individual deletion for ${rows.length} rows (bulk deletion not available)`,
|
|
343
|
+
{ prefix: "Wipe" }
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
const deletedCount = await tryIndividualDeletion(
|
|
347
|
+
adapter,
|
|
348
|
+
databaseId,
|
|
349
|
+
tableId,
|
|
350
|
+
rows,
|
|
351
|
+
INDIVIDUAL_DELETE_BATCH_SIZE,
|
|
352
|
+
MAX_CONCURRENT_OPERATIONS,
|
|
353
|
+
progress,
|
|
354
|
+
totalDeleted
|
|
355
|
+
);
|
|
356
|
+
totalDeleted += deletedCount;
|
|
357
|
+
}
|
|
358
358
|
|
|
359
359
|
// Set up cursor for next iteration
|
|
360
360
|
if (rows.length < FETCH_BATCH_SIZE) {
|
|
@@ -393,109 +393,116 @@ export const wipeTableRows = async (
|
|
|
393
393
|
/**
|
|
394
394
|
* Helper function to attempt bulk deletion of row IDs
|
|
395
395
|
*/
|
|
396
|
-
async function tryBulkDeletion(
|
|
397
|
-
adapter: DatabaseAdapter,
|
|
398
|
-
databaseId: string,
|
|
399
|
-
tableId: string,
|
|
400
|
-
rowIds: string[],
|
|
401
|
-
batchSize: number,
|
|
402
|
-
maxConcurrent: number
|
|
403
|
-
): Promise<
|
|
404
|
-
if (!adapter.bulkDeleteRows) {
|
|
405
|
-
throw new Error("Bulk deletion not available on this adapter");
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const limit = pLimit(maxConcurrent);
|
|
409
|
-
const batches = chunk(rowIds, batchSize);
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
396
|
+
async function tryBulkDeletion(
|
|
397
|
+
adapter: DatabaseAdapter,
|
|
398
|
+
databaseId: string,
|
|
399
|
+
tableId: string,
|
|
400
|
+
rowIds: string[],
|
|
401
|
+
batchSize: number,
|
|
402
|
+
maxConcurrent: number
|
|
403
|
+
): Promise<number> {
|
|
404
|
+
if (!adapter.bulkDeleteRows) {
|
|
405
|
+
throw new Error("Bulk deletion not available on this adapter");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const limit = pLimit(maxConcurrent);
|
|
409
|
+
const batches = chunk(rowIds, batchSize);
|
|
410
|
+
let successfullyDeleted = 0;
|
|
411
|
+
|
|
412
|
+
const deletePromises = batches.map((batch) =>
|
|
413
|
+
limit(async () => {
|
|
414
|
+
try {
|
|
415
|
+
const result = await tryAwaitWithRetry(async () =>
|
|
416
|
+
adapter.bulkDeleteRows!({ databaseId, tableId, rowIds: batch })
|
|
417
|
+
);
|
|
418
|
+
successfullyDeleted += batch.length; // Assume success if no error thrown
|
|
419
|
+
} catch (error: any) {
|
|
420
|
+
const errorMessage = error.message || String(error);
|
|
421
|
+
|
|
422
|
+
// Enhanced error handling for bulk deletion
|
|
423
|
+
if (isCriticalError(errorMessage)) {
|
|
424
|
+
MessageFormatter.error(
|
|
425
|
+
`Critical error in bulk deletion batch: ${errorMessage}`,
|
|
426
|
+
error,
|
|
427
|
+
{ prefix: "Wipe" }
|
|
428
|
+
);
|
|
429
|
+
throw error;
|
|
430
|
+
} else {
|
|
431
|
+
// For non-critical errors in bulk deletion, re-throw to trigger fallback
|
|
432
|
+
throw new Error(`Bulk deletion batch failed: ${errorMessage}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
await Promise.all(deletePromises);
|
|
439
|
+
return successfullyDeleted;
|
|
440
|
+
}
|
|
438
441
|
|
|
439
442
|
/**
|
|
440
443
|
* Helper function for fallback individual deletion
|
|
441
444
|
*/
|
|
442
|
-
async function tryIndividualDeletion(
|
|
443
|
-
adapter: DatabaseAdapter,
|
|
444
|
-
databaseId: string,
|
|
445
|
-
tableId: string,
|
|
446
|
-
rows: any[],
|
|
447
|
-
batchSize: number,
|
|
448
|
-
maxConcurrent: number,
|
|
449
|
-
progress: any,
|
|
450
|
-
baseDeleted: number
|
|
451
|
-
): Promise<
|
|
452
|
-
const limit = pLimit(maxConcurrent);
|
|
453
|
-
const batches = chunk(rows, batchSize);
|
|
454
|
-
let processedInBatch = 0;
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
445
|
+
async function tryIndividualDeletion(
|
|
446
|
+
adapter: DatabaseAdapter,
|
|
447
|
+
databaseId: string,
|
|
448
|
+
tableId: string,
|
|
449
|
+
rows: any[],
|
|
450
|
+
batchSize: number,
|
|
451
|
+
maxConcurrent: number,
|
|
452
|
+
progress: any,
|
|
453
|
+
baseDeleted: number
|
|
454
|
+
): Promise<number> {
|
|
455
|
+
const limit = pLimit(maxConcurrent);
|
|
456
|
+
const batches = chunk(rows, batchSize);
|
|
457
|
+
let processedInBatch = 0;
|
|
458
|
+
let successfullyDeleted = 0;
|
|
459
|
+
|
|
460
|
+
const deletePromises = batches.map((batch) =>
|
|
461
|
+
limit(async () => {
|
|
462
|
+
const batchDeletePromises = batch.map(async (row: any) => {
|
|
463
|
+
try {
|
|
464
|
+
await tryAwaitWithRetry(async () =>
|
|
465
|
+
adapter.deleteRow({ databaseId, tableId, id: row.$id })
|
|
466
|
+
);
|
|
467
|
+
successfullyDeleted++;
|
|
468
|
+
} catch (error: any) {
|
|
469
|
+
const errorMessage = error.message || String(error);
|
|
470
|
+
|
|
471
|
+
// Enhanced error handling for row deletion
|
|
472
|
+
if (errorMessage.includes("Row with the requested ID could not be found")) {
|
|
473
|
+
// Row already deleted, count as success since it's gone
|
|
474
|
+
successfullyDeleted++;
|
|
475
|
+
} else if (isCriticalError(errorMessage)) {
|
|
476
|
+
// Critical error, log and rethrow to stop operation
|
|
477
|
+
MessageFormatter.error(
|
|
478
|
+
`Critical error deleting row ${row.$id}: ${errorMessage}`,
|
|
479
|
+
error,
|
|
480
|
+
{ prefix: "Wipe" }
|
|
481
|
+
);
|
|
482
|
+
throw error;
|
|
483
|
+
} else if (isRetryableError(errorMessage)) {
|
|
484
|
+
// Retryable error, will be handled by tryAwaitWithRetry
|
|
485
|
+
MessageFormatter.progress(
|
|
486
|
+
`Retryable error for row ${row.$id}, will retry`,
|
|
487
|
+
{ prefix: "Wipe" }
|
|
488
|
+
);
|
|
489
|
+
} else {
|
|
490
|
+
// Other non-critical errors, log but continue
|
|
491
|
+
MessageFormatter.error(
|
|
492
|
+
`Failed to delete row ${row.$id}: ${errorMessage}`,
|
|
493
|
+
error,
|
|
494
|
+
{ prefix: "Wipe" }
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
processedInBatch++;
|
|
499
|
+
progress.update(baseDeleted + successfullyDeleted);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
await Promise.all(batchDeletePromises);
|
|
503
|
+
})
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
await Promise.all(deletePromises);
|
|
507
|
+
return successfullyDeleted;
|
|
508
|
+
}
|