appwrite-utils-cli 1.7.6 → 1.7.8
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/SELECTION_DIALOGS.md +146 -0
- package/dist/adapters/DatabaseAdapter.d.ts +1 -0
- package/dist/adapters/LegacyAdapter.js +15 -3
- package/dist/adapters/TablesDBAdapter.js +15 -3
- package/dist/cli/commands/databaseCommands.js +90 -23
- package/dist/collections/wipeOperations.d.ts +2 -2
- package/dist/collections/wipeOperations.js +37 -139
- package/dist/main.js +175 -4
- package/dist/migrations/appwriteToX.d.ts +27 -2
- package/dist/migrations/appwriteToX.js +293 -69
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
- package/dist/migrations/yaml/generateImportSchemas.js +23 -8
- package/dist/shared/schemaGenerator.js +25 -12
- package/dist/shared/selectionDialogs.d.ts +214 -0
- package/dist/shared/selectionDialogs.js +516 -0
- package/dist/utils/configDiscovery.d.ts +4 -4
- package/dist/utils/configDiscovery.js +66 -30
- package/dist/utils/yamlConverter.d.ts +1 -0
- package/dist/utils/yamlConverter.js +26 -3
- package/dist/utilsController.d.ts +6 -1
- package/dist/utilsController.js +91 -2
- package/package.json +1 -1
- package/src/adapters/DatabaseAdapter.ts +2 -1
- package/src/adapters/LegacyAdapter.ts +95 -82
- package/src/adapters/TablesDBAdapter.ts +62 -47
- package/src/cli/commands/databaseCommands.ts +134 -34
- package/src/collections/wipeOperations.ts +62 -224
- package/src/main.ts +276 -34
- package/src/migrations/appwriteToX.ts +385 -90
- package/src/migrations/yaml/generateImportSchemas.ts +26 -8
- package/src/shared/schemaGenerator.ts +29 -12
- package/src/shared/selectionDialogs.ts +716 -0
- package/src/utils/configDiscovery.ts +83 -39
- package/src/utils/yamlConverter.ts +29 -3
- package/src/utilsController.ts +116 -4
|
@@ -15,9 +15,15 @@ export function collectionToYaml(collection, config = {
|
|
|
15
15
|
const yamlData = {
|
|
16
16
|
name: collection.name,
|
|
17
17
|
id: collection.$id,
|
|
18
|
-
documentSecurity: collection.documentSecurity,
|
|
19
18
|
enabled: collection.enabled,
|
|
20
19
|
};
|
|
20
|
+
// Use appropriate security field based on terminology
|
|
21
|
+
if (config.useTableTerminology) {
|
|
22
|
+
yamlData.rowSecurity = collection.documentSecurity;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
yamlData.documentSecurity = collection.documentSecurity;
|
|
26
|
+
}
|
|
21
27
|
// Convert permissions
|
|
22
28
|
if (collection.$permissions && collection.$permissions.length > 0) {
|
|
23
29
|
yamlData.permissions = collection.$permissions.map(p => ({
|
|
@@ -158,6 +164,11 @@ export function normalizeYamlData(yamlData) {
|
|
|
158
164
|
columns: undefined
|
|
159
165
|
}));
|
|
160
166
|
}
|
|
167
|
+
// Normalize security fields - prefer documentSecurity for consistency
|
|
168
|
+
if (yamlData.rowSecurity !== undefined && yamlData.documentSecurity === undefined) {
|
|
169
|
+
normalized.documentSecurity = yamlData.rowSecurity;
|
|
170
|
+
delete normalized.rowSecurity;
|
|
171
|
+
}
|
|
161
172
|
return normalized;
|
|
162
173
|
}
|
|
163
174
|
/**
|
|
@@ -165,7 +176,8 @@ export function normalizeYamlData(yamlData) {
|
|
|
165
176
|
*/
|
|
166
177
|
export function usesTableTerminology(yamlData) {
|
|
167
178
|
return !!(yamlData.columns && yamlData.columns.length > 0) ||
|
|
168
|
-
!!(yamlData.indexes?.some(idx => !!idx.columns))
|
|
179
|
+
!!(yamlData.indexes?.some(idx => !!idx.columns)) ||
|
|
180
|
+
yamlData.rowSecurity !== undefined;
|
|
169
181
|
}
|
|
170
182
|
/**
|
|
171
183
|
* Converts between attribute and column terminology
|
|
@@ -190,6 +202,11 @@ export function convertTerminology(yamlData, toTableTerminology) {
|
|
|
190
202
|
attributes: idx.attributes // Keep both for compatibility
|
|
191
203
|
}));
|
|
192
204
|
}
|
|
205
|
+
// Convert security field
|
|
206
|
+
if (yamlData.documentSecurity !== undefined && yamlData.rowSecurity === undefined) {
|
|
207
|
+
converted.rowSecurity = yamlData.documentSecurity;
|
|
208
|
+
delete converted.documentSecurity;
|
|
209
|
+
}
|
|
193
210
|
return converted;
|
|
194
211
|
}
|
|
195
212
|
else {
|
|
@@ -272,7 +289,6 @@ export function generateYamlTemplate(entityName, config) {
|
|
|
272
289
|
const template = {
|
|
273
290
|
name: entityName,
|
|
274
291
|
id: entityName.toLowerCase().replace(/\s+/g, '_'),
|
|
275
|
-
documentSecurity: false,
|
|
276
292
|
enabled: true,
|
|
277
293
|
permissions: [
|
|
278
294
|
{
|
|
@@ -294,6 +310,13 @@ export function generateYamlTemplate(entityName, config) {
|
|
|
294
310
|
],
|
|
295
311
|
importDefs: []
|
|
296
312
|
};
|
|
313
|
+
// Use appropriate security field based on terminology
|
|
314
|
+
if (config.useTableTerminology) {
|
|
315
|
+
template.rowSecurity = false;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
template.documentSecurity = false;
|
|
319
|
+
}
|
|
297
320
|
// Assign fields with correct property name
|
|
298
321
|
template[fieldsKey] = fieldsArray;
|
|
299
322
|
template.indexes = indexesArray;
|
|
@@ -4,6 +4,7 @@ import { type AfterImportActions, type ConverterFunctions, type ValidationRules
|
|
|
4
4
|
import { type TransferOptions } from "./migrations/transfer.js";
|
|
5
5
|
import type { DatabaseAdapter } from './adapters/DatabaseAdapter.js';
|
|
6
6
|
import { type ValidationResult } from "./config/configValidation.js";
|
|
7
|
+
import type { DatabaseSelection, BucketSelection } from "./shared/selectionDialogs.js";
|
|
7
8
|
export interface SetupOptions {
|
|
8
9
|
databases?: Models.Database[];
|
|
9
10
|
collections?: string[];
|
|
@@ -60,6 +61,9 @@ export declare class UtilsController {
|
|
|
60
61
|
ensureDatabasesExist(databases?: Models.Database[]): Promise<void>;
|
|
61
62
|
ensureCollectionsExist(database: Models.Database, collections?: Models.Collection[]): Promise<void>;
|
|
62
63
|
getDatabasesByIds(ids: string[]): Promise<Models.Database[] | undefined>;
|
|
64
|
+
fetchAllBuckets(): Promise<{
|
|
65
|
+
buckets: Models.Bucket[];
|
|
66
|
+
}>;
|
|
63
67
|
wipeOtherDatabases(databasesToKeep: Models.Database[]): Promise<void>;
|
|
64
68
|
wipeUsers(): Promise<void>;
|
|
65
69
|
backupDatabase(database: Models.Database, format?: 'json' | 'zip'): Promise<void>;
|
|
@@ -78,7 +82,8 @@ export declare class UtilsController {
|
|
|
78
82
|
}[], collections?: Models.Collection[]): Promise<void>;
|
|
79
83
|
generateSchemas(): Promise<void>;
|
|
80
84
|
importData(options?: SetupOptions): Promise<void>;
|
|
81
|
-
synchronizeConfigurations(databases?: Models.Database[], config?: AppwriteConfig): Promise<void>;
|
|
85
|
+
synchronizeConfigurations(databases?: Models.Database[], config?: AppwriteConfig, databaseSelections?: DatabaseSelection[], bucketSelections?: BucketSelection[]): Promise<void>;
|
|
86
|
+
selectiveSync(databaseSelections: DatabaseSelection[], bucketSelections: BucketSelection[]): Promise<void>;
|
|
82
87
|
syncDb(databases?: Models.Database[], collections?: Models.Collection[]): Promise<void>;
|
|
83
88
|
getAppwriteFolderPath(): string | undefined;
|
|
84
89
|
transferData(options: TransferOptions): Promise<void>;
|
package/dist/utilsController.js
CHANGED
|
@@ -25,6 +25,7 @@ import { configureLogging, updateLogger, logger } from "./shared/logging.js";
|
|
|
25
25
|
import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
|
|
26
26
|
import { SchemaGenerator } from "./shared/schemaGenerator.js";
|
|
27
27
|
import { findYamlConfig } from "./config/yamlConfig.js";
|
|
28
|
+
import { createImportSchemas } from "./migrations/yaml/generateImportSchemas.js";
|
|
28
29
|
import { validateCollectionsTablesConfig, reportValidationResults, validateWithStrictMode } from "./config/configValidation.js";
|
|
29
30
|
import { ConfigManager } from "./config/ConfigManager.js";
|
|
30
31
|
import { ClientFactory } from "./utils/ClientFactory.js";
|
|
@@ -225,6 +226,24 @@ export class UtilsController {
|
|
|
225
226
|
]);
|
|
226
227
|
return dbs.databases;
|
|
227
228
|
}
|
|
229
|
+
async fetchAllBuckets() {
|
|
230
|
+
await this.init();
|
|
231
|
+
if (!this.storage) {
|
|
232
|
+
MessageFormatter.warning("Storage not initialized - buckets will be empty", { prefix: "Controller" });
|
|
233
|
+
return { buckets: [] };
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const result = await this.storage.listBuckets([
|
|
237
|
+
Query.limit(1000) // Increase limit to get all buckets
|
|
238
|
+
]);
|
|
239
|
+
MessageFormatter.success(`Found ${result.buckets.length} buckets`, { prefix: "Controller" });
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
MessageFormatter.error(`Failed to fetch buckets: ${error.message || error}`, error instanceof Error ? error : undefined, { prefix: "Controller" });
|
|
244
|
+
return { buckets: [] };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
228
247
|
async wipeOtherDatabases(databasesToKeep) {
|
|
229
248
|
await this.init();
|
|
230
249
|
if (!this.database) {
|
|
@@ -448,7 +467,7 @@ export class UtilsController {
|
|
|
448
467
|
const importController = new ImportController(this.config, this.database, this.storage, this.appwriteFolderPath, importDataActions, options, options.databases);
|
|
449
468
|
await importController.run(options.collections);
|
|
450
469
|
}
|
|
451
|
-
async synchronizeConfigurations(databases, config) {
|
|
470
|
+
async synchronizeConfigurations(databases, config, databaseSelections, bucketSelections) {
|
|
452
471
|
await this.init();
|
|
453
472
|
if (!this.storage) {
|
|
454
473
|
MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
|
|
@@ -463,8 +482,25 @@ export class UtilsController {
|
|
|
463
482
|
MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
|
|
464
483
|
return;
|
|
465
484
|
}
|
|
485
|
+
// If selections are provided, filter the databases accordingly
|
|
486
|
+
let filteredDatabases = databases;
|
|
487
|
+
if (databaseSelections && databaseSelections.length > 0) {
|
|
488
|
+
// Convert selections to Models.Database format
|
|
489
|
+
filteredDatabases = [];
|
|
490
|
+
const allDatabases = databases ? databases : await fetchAllDatabases(this.database);
|
|
491
|
+
for (const selection of databaseSelections) {
|
|
492
|
+
const database = allDatabases.find(db => db.$id === selection.databaseId);
|
|
493
|
+
if (database) {
|
|
494
|
+
filteredDatabases.push(database);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
MessageFormatter.warning(`Database with ID ${selection.databaseId} not found`, { prefix: "Controller" });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
MessageFormatter.info(`Syncing ${filteredDatabases.length} selected databases out of ${allDatabases.length} available`, { prefix: "Controller" });
|
|
501
|
+
}
|
|
466
502
|
const appwriteToX = new AppwriteToX(configToUse, this.appwriteFolderPath, this.storage);
|
|
467
|
-
await appwriteToX.toSchemas(
|
|
503
|
+
await appwriteToX.toSchemas(filteredDatabases);
|
|
468
504
|
// Update the controller's config with the synchronized collections
|
|
469
505
|
this.config = appwriteToX.updatedConfig;
|
|
470
506
|
// Write the updated config back to disk
|
|
@@ -472,6 +508,59 @@ export class UtilsController {
|
|
|
472
508
|
const yamlConfigPath = findYamlConfig(this.appwriteFolderPath);
|
|
473
509
|
const isYamlProject = !!yamlConfigPath;
|
|
474
510
|
await generator.updateConfig(this.config, isYamlProject);
|
|
511
|
+
// Regenerate JSON schemas to reflect any table terminology fixes
|
|
512
|
+
try {
|
|
513
|
+
MessageFormatter.progress("Regenerating JSON schemas...", { prefix: "Sync" });
|
|
514
|
+
await createImportSchemas(this.appwriteFolderPath);
|
|
515
|
+
MessageFormatter.success("JSON schemas regenerated successfully", { prefix: "Sync" });
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
// Log error but don't fail the sync process
|
|
519
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
520
|
+
MessageFormatter.warning(`Failed to regenerate JSON schemas, but sync completed: ${errorMessage}`, { prefix: "Sync" });
|
|
521
|
+
logger.warn("Schema regeneration failed during sync:", error);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async selectiveSync(databaseSelections, bucketSelections) {
|
|
525
|
+
await this.init();
|
|
526
|
+
if (!this.database) {
|
|
527
|
+
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
MessageFormatter.progress("Starting selective sync...", { prefix: "Controller" });
|
|
531
|
+
// Convert database selections to Models.Database format
|
|
532
|
+
const selectedDatabases = [];
|
|
533
|
+
for (const dbSelection of databaseSelections) {
|
|
534
|
+
// Get the full database object from the controller
|
|
535
|
+
const databases = await fetchAllDatabases(this.database);
|
|
536
|
+
const database = databases.find(db => db.$id === dbSelection.databaseId);
|
|
537
|
+
if (database) {
|
|
538
|
+
selectedDatabases.push(database);
|
|
539
|
+
MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
|
|
540
|
+
// Log selected tables for this database
|
|
541
|
+
if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
|
|
542
|
+
MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (selectedDatabases.length === 0) {
|
|
550
|
+
MessageFormatter.warning("No valid databases selected for sync", { prefix: "Controller" });
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
// Log bucket selections if provided
|
|
554
|
+
if (bucketSelections && bucketSelections.length > 0) {
|
|
555
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
|
|
556
|
+
for (const bucketSelection of bucketSelections) {
|
|
557
|
+
const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
|
|
558
|
+
MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// Perform selective sync using the enhanced synchronizeConfigurations method
|
|
562
|
+
await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
|
|
563
|
+
MessageFormatter.success("Selective sync completed successfully!", { prefix: "Controller" });
|
|
475
564
|
}
|
|
476
565
|
async syncDb(databases = [], collections = []) {
|
|
477
566
|
await this.init();
|
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.8",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -86,7 +86,8 @@ export interface BulkUpsertRowsParams {
|
|
|
86
86
|
export interface BulkDeleteRowsParams {
|
|
87
87
|
databaseId: string;
|
|
88
88
|
tableId: string;
|
|
89
|
-
rowIds: string[];
|
|
89
|
+
rowIds: string[]; // Empty array = wipe mode (use Query.limit), otherwise specific IDs to delete
|
|
90
|
+
batchSize?: number; // Optional batch size for wipe mode (default 250)
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
// Index operation parameters
|
|
@@ -7,32 +7,33 @@
|
|
|
7
7
|
* older Appwrite instances.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Query } from "node-appwrite";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
type
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
10
|
+
import { Query } from "node-appwrite";
|
|
11
|
+
import { chunk } from "es-toolkit";
|
|
12
|
+
import {
|
|
13
|
+
BaseAdapter,
|
|
14
|
+
type CreateRowParams,
|
|
15
|
+
type UpdateRowParams,
|
|
16
|
+
type ListRowsParams,
|
|
17
|
+
type DeleteRowParams,
|
|
18
|
+
type CreateTableParams,
|
|
19
|
+
type UpdateTableParams,
|
|
20
|
+
type ListTablesParams,
|
|
21
|
+
type DeleteTableParams,
|
|
22
|
+
type GetTableParams,
|
|
23
|
+
type BulkCreateRowsParams,
|
|
24
|
+
type BulkUpsertRowsParams,
|
|
25
|
+
type BulkDeleteRowsParams,
|
|
26
|
+
type CreateIndexParams,
|
|
27
|
+
type ListIndexesParams,
|
|
28
|
+
type DeleteIndexParams,
|
|
29
|
+
type CreateAttributeParams,
|
|
30
|
+
type UpdateAttributeParams,
|
|
31
|
+
type DeleteAttributeParams,
|
|
32
|
+
type ApiResponse,
|
|
33
|
+
type AdapterMetadata,
|
|
34
|
+
AdapterError,
|
|
35
|
+
UnsupportedOperationError
|
|
36
|
+
} from './DatabaseAdapter.js';
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* LegacyAdapter - Translates TablesDB calls to legacy Databases API
|
|
@@ -587,62 +588,74 @@ export class LegacyAdapter extends BaseAdapter {
|
|
|
587
588
|
throw new UnsupportedOperationError('bulkUpsertRows', 'legacy');
|
|
588
589
|
}
|
|
589
590
|
|
|
590
|
-
async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
591
|
-
try {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
params.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
591
|
+
async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
592
|
+
try {
|
|
593
|
+
let queries: string[];
|
|
594
|
+
|
|
595
|
+
// Wipe mode: use Query.limit for deleting without fetching
|
|
596
|
+
if (params.rowIds.length === 0) {
|
|
597
|
+
const batchSize = params.batchSize || 250;
|
|
598
|
+
queries = [Query.limit(batchSize)];
|
|
599
|
+
}
|
|
600
|
+
// Specific IDs mode: chunk into batches of 80-90 to stay within Appwrite limits
|
|
601
|
+
// (max 100 IDs per Query.equal, and queries must be < 4096 chars total)
|
|
602
|
+
else {
|
|
603
|
+
const ID_BATCH_SIZE = 85; // Safe batch size for Query.equal
|
|
604
|
+
const idBatches = chunk(params.rowIds, ID_BATCH_SIZE);
|
|
605
|
+
queries = idBatches.map(batch => Query.equal('$id', batch));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const result = await this.databases.deleteDocuments(
|
|
609
|
+
params.databaseId,
|
|
610
|
+
params.tableId, // Maps tableId to collectionId
|
|
611
|
+
queries
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
data: result,
|
|
616
|
+
total: params.rowIds.length || (result as any).total || 0
|
|
617
|
+
};
|
|
618
|
+
} catch (error) {
|
|
619
|
+
// If deleteDocuments with queries fails, fall back to individual deletes
|
|
620
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
621
|
+
|
|
622
|
+
// Check if the error indicates that deleteDocuments with queries is not supported
|
|
623
|
+
if (errorMessage.includes('not supported') || errorMessage.includes('invalid') || errorMessage.includes('queries')) {
|
|
624
|
+
// Fall back to individual deletions
|
|
625
|
+
const results = [];
|
|
626
|
+
const errors = [];
|
|
627
|
+
|
|
628
|
+
for (const rowId of params.rowIds) {
|
|
629
|
+
try {
|
|
630
|
+
await this.deleteRow({
|
|
631
|
+
databaseId: params.databaseId,
|
|
632
|
+
tableId: params.tableId,
|
|
633
|
+
id: rowId
|
|
634
|
+
});
|
|
635
|
+
results.push({ id: rowId, deleted: true });
|
|
636
|
+
} catch (individualError) {
|
|
637
|
+
errors.push({
|
|
638
|
+
rowId,
|
|
639
|
+
error: individualError instanceof Error ? individualError.message : 'Unknown error'
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return {
|
|
645
|
+
data: results,
|
|
646
|
+
total: results.length,
|
|
647
|
+
errors: errors.length > 0 ? errors : undefined
|
|
648
|
+
};
|
|
649
|
+
} else {
|
|
650
|
+
// Re-throw the original error if it's not a support issue
|
|
651
|
+
throw new AdapterError(
|
|
652
|
+
`Failed to bulk delete rows (legacy): ${errorMessage}`,
|
|
653
|
+
'BULK_DELETE_ROWS_FAILED',
|
|
654
|
+
error instanceof Error ? error : undefined
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
646
659
|
|
|
647
660
|
// Metadata and Capabilities
|
|
648
661
|
|
|
@@ -6,32 +6,33 @@
|
|
|
6
6
|
* and returns Models.Row instead of Models.Document.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { Query } from "node-appwrite";
|
|
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
|
-
type
|
|
33
|
-
|
|
34
|
-
|
|
9
|
+
import { Query } from "node-appwrite";
|
|
10
|
+
import { chunk } from "es-toolkit";
|
|
11
|
+
import {
|
|
12
|
+
BaseAdapter,
|
|
13
|
+
type DatabaseAdapter,
|
|
14
|
+
type CreateRowParams,
|
|
15
|
+
type UpdateRowParams,
|
|
16
|
+
type ListRowsParams,
|
|
17
|
+
type DeleteRowParams,
|
|
18
|
+
type CreateTableParams,
|
|
19
|
+
type UpdateTableParams,
|
|
20
|
+
type ListTablesParams,
|
|
21
|
+
type DeleteTableParams,
|
|
22
|
+
type GetTableParams,
|
|
23
|
+
type BulkCreateRowsParams,
|
|
24
|
+
type BulkUpsertRowsParams,
|
|
25
|
+
type BulkDeleteRowsParams,
|
|
26
|
+
type CreateIndexParams,
|
|
27
|
+
type ListIndexesParams,
|
|
28
|
+
type DeleteIndexParams,
|
|
29
|
+
type CreateAttributeParams,
|
|
30
|
+
type UpdateAttributeParams,
|
|
31
|
+
type DeleteAttributeParams,
|
|
32
|
+
type ApiResponse,
|
|
33
|
+
type AdapterMetadata,
|
|
34
|
+
AdapterError
|
|
35
|
+
} from './DatabaseAdapter.js';
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* TablesDBAdapter implementation for native TablesDB API
|
|
@@ -514,27 +515,41 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
514
515
|
}
|
|
515
516
|
}
|
|
516
517
|
|
|
517
|
-
async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
518
|
-
try {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
queries
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
518
|
+
async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
519
|
+
try {
|
|
520
|
+
let queries: string[];
|
|
521
|
+
|
|
522
|
+
// Wipe mode: use Query.limit for deleting without fetching
|
|
523
|
+
if (params.rowIds.length === 0) {
|
|
524
|
+
const batchSize = params.batchSize || 250;
|
|
525
|
+
queries = [Query.limit(batchSize)];
|
|
526
|
+
}
|
|
527
|
+
// Specific IDs mode: chunk into batches of 80-90 to stay within Appwrite limits
|
|
528
|
+
// (max 100 IDs per Query.equal, and queries must be < 4096 chars total)
|
|
529
|
+
else {
|
|
530
|
+
const ID_BATCH_SIZE = 85; // Safe batch size for Query.equal
|
|
531
|
+
const idBatches = chunk(params.rowIds, ID_BATCH_SIZE);
|
|
532
|
+
queries = idBatches.map(batch => Query.equal('$id', batch));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const result = await this.tablesDB.deleteRows({
|
|
536
|
+
databaseId: params.databaseId,
|
|
537
|
+
tableId: params.tableId,
|
|
538
|
+
queries: queries
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
data: result,
|
|
543
|
+
total: params.rowIds.length || (result as any).total || 0
|
|
544
|
+
};
|
|
545
|
+
} catch (error) {
|
|
546
|
+
throw new AdapterError(
|
|
547
|
+
`Failed to bulk delete rows: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
548
|
+
'BULK_DELETE_ROWS_FAILED',
|
|
549
|
+
error instanceof Error ? error : undefined
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
538
553
|
|
|
539
554
|
// Metadata and Capabilities
|
|
540
555
|
getMetadata(): AdapterMetadata {
|