@njdamstra/appwrite-utils-cli 1.8.9 → 1.10.1
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/CHANGELOG.md +16 -0
- package/CONFIG_TODO.md +1189 -0
- package/SELECTION_DIALOGS.md +146 -0
- package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
- package/dist/adapters/index.d.ts +7 -8
- package/dist/adapters/index.js +7 -9
- package/dist/backups/operations/bucketBackup.js +2 -2
- package/dist/backups/operations/collectionBackup.d.ts +1 -1
- package/dist/backups/operations/collectionBackup.js +3 -3
- package/dist/backups/operations/comprehensiveBackup.d.ts +1 -1
- package/dist/backups/operations/comprehensiveBackup.js +2 -2
- package/dist/backups/tracking/centralizedTracking.d.ts +1 -1
- package/dist/backups/tracking/centralizedTracking.js +2 -2
- package/dist/cli/commands/configCommands.js +51 -7
- package/dist/cli/commands/databaseCommands.d.ts +1 -0
- package/dist/cli/commands/databaseCommands.js +119 -9
- package/dist/cli/commands/functionCommands.js +3 -3
- package/dist/cli/commands/importFileCommands.d.ts +7 -0
- package/dist/cli/commands/importFileCommands.js +674 -0
- package/dist/cli/commands/schemaCommands.js +3 -3
- package/dist/cli/commands/storageCommands.js +2 -3
- package/dist/cli/commands/transferCommands.js +3 -5
- package/dist/collections/attributes.d.ts +1 -1
- package/dist/collections/attributes.js +85 -35
- package/dist/collections/indexes.js +2 -4
- package/dist/collections/methods.d.ts +1 -1
- package/dist/collections/methods.js +111 -192
- package/dist/collections/tableOperations.d.ts +1 -0
- package/dist/collections/tableOperations.js +90 -23
- package/dist/collections/transferOperations.d.ts +1 -1
- package/dist/collections/transferOperations.js +3 -4
- package/dist/collections/wipeOperations.d.ts +4 -3
- package/dist/collections/wipeOperations.js +112 -39
- package/dist/databases/methods.js +2 -2
- package/dist/databases/setup.js +2 -2
- package/dist/examples/yamlTerminologyExample.js +2 -2
- package/dist/functions/deployments.d.ts +1 -1
- package/dist/functions/deployments.js +5 -5
- package/dist/functions/fnConfigDiscovery.js +2 -2
- package/dist/functions/methods.js +16 -4
- package/dist/init.js +1 -1
- package/dist/interactiveCLI.d.ts +6 -1
- package/dist/interactiveCLI.js +64 -10
- package/dist/main.js +130 -177
- package/dist/migrations/afterImportActions.js +2 -3
- package/dist/migrations/appwriteToX.d.ts +97 -1
- package/dist/migrations/appwriteToX.js +9 -7
- package/dist/migrations/comprehensiveTransfer.js +3 -5
- package/dist/migrations/dataLoader.d.ts +194 -2
- package/dist/migrations/dataLoader.js +2 -5
- package/dist/migrations/importController.js +3 -4
- package/dist/migrations/importDataActions.js +3 -3
- package/dist/migrations/relationships.js +1 -2
- package/dist/migrations/services/DataTransformationService.js +2 -2
- package/dist/migrations/services/FileHandlerService.js +1 -1
- package/dist/migrations/services/ImportOrchestrator.js +4 -4
- package/dist/migrations/services/RateLimitManager.js +1 -1
- package/dist/migrations/services/RelationshipResolver.js +1 -1
- package/dist/migrations/services/UserMappingService.js +1 -1
- package/dist/migrations/services/ValidationService.js +1 -1
- package/dist/migrations/transfer.d.ts +8 -4
- package/dist/migrations/transfer.js +106 -55
- package/dist/migrations/yaml/YamlImportConfigLoader.js +1 -1
- package/dist/migrations/yaml/YamlImportIntegration.js +2 -2
- package/dist/migrations/yaml/generateImportSchemas.js +1 -1
- package/dist/setupCommands.d.ts +1 -1
- package/dist/setupCommands.js +5 -6
- package/dist/setupController.js +1 -1
- package/dist/shared/backupTracking.d.ts +1 -1
- package/dist/shared/backupTracking.js +2 -2
- package/dist/shared/confirmationDialogs.js +1 -1
- package/dist/shared/migrationHelpers.d.ts +1 -1
- package/dist/shared/migrationHelpers.js +3 -3
- package/dist/shared/operationQueue.d.ts +1 -1
- package/dist/shared/operationQueue.js +2 -3
- package/dist/shared/operationsTable.d.ts +1 -1
- package/dist/shared/operationsTable.js +2 -2
- package/dist/shared/progressManager.js +1 -1
- package/dist/shared/selectionDialogs.js +9 -8
- package/dist/storage/methods.js +4 -4
- package/dist/storage/schemas.d.ts +386 -2
- package/dist/tables/indexManager.d.ts +65 -0
- package/dist/tables/indexManager.js +294 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/users/methods.js +2 -3
- package/dist/utils/configMigration.js +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/loadConfigs.d.ts +2 -2
- package/dist/utils/loadConfigs.js +6 -7
- package/dist/utils/setupFiles.js +5 -7
- package/dist/utilsController.d.ts +15 -8
- package/dist/utilsController.js +57 -28
- package/package.json +8 -4
- package/src/adapters/index.ts +8 -34
- package/src/backups/operations/bucketBackup.ts +2 -2
- package/src/backups/operations/collectionBackup.ts +4 -4
- package/src/backups/operations/comprehensiveBackup.ts +3 -3
- package/src/backups/tracking/centralizedTracking.ts +3 -3
- package/src/cli/commands/configCommands.ts +72 -8
- package/src/cli/commands/databaseCommands.ts +161 -9
- package/src/cli/commands/functionCommands.ts +4 -3
- package/src/cli/commands/importFileCommands.ts +815 -0
- package/src/cli/commands/schemaCommands.ts +3 -3
- package/src/cli/commands/storageCommands.ts +2 -3
- package/src/cli/commands/transferCommands.ts +3 -6
- package/src/collections/attributes.ts +155 -39
- package/src/collections/indexes.ts +5 -7
- package/src/collections/methods.ts +115 -150
- package/src/collections/tableOperations.ts +92 -21
- package/src/collections/transferOperations.ts +4 -5
- package/src/collections/wipeOperations.ts +154 -51
- package/src/databases/methods.ts +2 -2
- package/src/databases/setup.ts +2 -2
- package/src/examples/yamlTerminologyExample.ts +2 -2
- package/src/functions/deployments.ts +6 -5
- package/src/functions/fnConfigDiscovery.ts +2 -2
- package/src/functions/methods.ts +19 -6
- package/src/init.ts +1 -1
- package/src/interactiveCLI.ts +78 -13
- package/src/main.ts +143 -287
- package/src/migrations/afterImportActions.ts +2 -3
- package/src/migrations/appwriteToX.ts +12 -8
- package/src/migrations/comprehensiveTransfer.ts +6 -6
- package/src/migrations/dataLoader.ts +2 -5
- package/src/migrations/importController.ts +3 -4
- package/src/migrations/importDataActions.ts +3 -3
- package/src/migrations/relationships.ts +1 -2
- package/src/migrations/services/DataTransformationService.ts +2 -2
- package/src/migrations/services/FileHandlerService.ts +1 -1
- package/src/migrations/services/ImportOrchestrator.ts +4 -4
- package/src/migrations/services/RateLimitManager.ts +1 -1
- package/src/migrations/services/RelationshipResolver.ts +1 -1
- package/src/migrations/services/UserMappingService.ts +1 -1
- package/src/migrations/services/ValidationService.ts +1 -1
- package/src/migrations/transfer.ts +126 -83
- package/src/migrations/yaml/YamlImportConfigLoader.ts +1 -1
- package/src/migrations/yaml/YamlImportIntegration.ts +2 -2
- package/src/migrations/yaml/generateImportSchemas.ts +1 -1
- package/src/setupCommands.ts +5 -6
- package/src/setupController.ts +1 -1
- package/src/shared/backupTracking.ts +3 -3
- package/src/shared/confirmationDialogs.ts +1 -1
- package/src/shared/migrationHelpers.ts +4 -4
- package/src/shared/operationQueue.ts +3 -4
- package/src/shared/operationsTable.ts +3 -3
- package/src/shared/progressManager.ts +1 -1
- package/src/shared/selectionDialogs.ts +9 -8
- package/src/storage/methods.ts +4 -4
- package/src/tables/indexManager.ts +409 -0
- package/src/types.ts +2 -2
- package/src/users/methods.ts +2 -3
- package/src/utils/configMigration.ts +1 -1
- package/src/utils/index.ts +1 -1
- package/src/utils/loadConfigs.ts +15 -7
- package/src/utils/setupFiles.ts +5 -7
- package/src/utilsController.ts +86 -32
- package/dist/adapters/AdapterFactory.d.ts +0 -94
- package/dist/adapters/AdapterFactory.js +0 -405
- package/dist/adapters/DatabaseAdapter.d.ts +0 -233
- package/dist/adapters/DatabaseAdapter.js +0 -50
- package/dist/adapters/LegacyAdapter.d.ts +0 -50
- package/dist/adapters/LegacyAdapter.js +0 -612
- package/dist/adapters/TablesDBAdapter.d.ts +0 -45
- package/dist/adapters/TablesDBAdapter.js +0 -571
- package/dist/config/ConfigManager.d.ts +0 -445
- package/dist/config/ConfigManager.js +0 -625
- package/dist/config/configMigration.d.ts +0 -87
- package/dist/config/configMigration.js +0 -390
- package/dist/config/configValidation.d.ts +0 -66
- package/dist/config/configValidation.js +0 -358
- package/dist/config/index.d.ts +0 -8
- package/dist/config/index.js +0 -7
- package/dist/config/services/ConfigDiscoveryService.d.ts +0 -126
- package/dist/config/services/ConfigDiscoveryService.js +0 -374
- package/dist/config/services/ConfigLoaderService.d.ts +0 -129
- package/dist/config/services/ConfigLoaderService.js +0 -540
- package/dist/config/services/ConfigMergeService.d.ts +0 -208
- package/dist/config/services/ConfigMergeService.js +0 -308
- package/dist/config/services/ConfigValidationService.d.ts +0 -214
- package/dist/config/services/ConfigValidationService.js +0 -310
- package/dist/config/services/SessionAuthService.d.ts +0 -225
- package/dist/config/services/SessionAuthService.js +0 -456
- package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +0 -1
- package/dist/config/services/__tests__/ConfigMergeService.test.js +0 -271
- package/dist/config/services/index.d.ts +0 -13
- package/dist/config/services/index.js +0 -10
- package/dist/config/yamlConfig.d.ts +0 -722
- package/dist/config/yamlConfig.js +0 -702
- package/dist/functions/pathResolution.d.ts +0 -37
- package/dist/functions/pathResolution.js +0 -185
- package/dist/shared/attributeMapper.d.ts +0 -20
- package/dist/shared/attributeMapper.js +0 -203
- package/dist/shared/errorUtils.d.ts +0 -54
- package/dist/shared/errorUtils.js +0 -95
- package/dist/shared/functionManager.d.ts +0 -48
- package/dist/shared/functionManager.js +0 -336
- package/dist/shared/indexManager.d.ts +0 -24
- package/dist/shared/indexManager.js +0 -151
- package/dist/shared/jsonSchemaGenerator.d.ts +0 -50
- package/dist/shared/jsonSchemaGenerator.js +0 -290
- package/dist/shared/logging.d.ts +0 -61
- package/dist/shared/logging.js +0 -116
- package/dist/shared/messageFormatter.d.ts +0 -39
- package/dist/shared/messageFormatter.js +0 -162
- package/dist/shared/pydanticModelGenerator.d.ts +0 -17
- package/dist/shared/pydanticModelGenerator.js +0 -615
- package/dist/shared/schemaGenerator.d.ts +0 -40
- package/dist/shared/schemaGenerator.js +0 -556
- package/dist/utils/ClientFactory.d.ts +0 -87
- package/dist/utils/ClientFactory.js +0 -212
- package/dist/utils/configDiscovery.d.ts +0 -78
- package/dist/utils/configDiscovery.js +0 -472
- package/dist/utils/constantsGenerator.d.ts +0 -31
- package/dist/utils/constantsGenerator.js +0 -321
- package/dist/utils/dataConverters.d.ts +0 -46
- package/dist/utils/dataConverters.js +0 -139
- package/dist/utils/directoryUtils.d.ts +0 -22
- package/dist/utils/directoryUtils.js +0 -59
- package/dist/utils/getClientFromConfig.d.ts +0 -39
- package/dist/utils/getClientFromConfig.js +0 -199
- package/dist/utils/helperFunctions.d.ts +0 -63
- package/dist/utils/helperFunctions.js +0 -156
- package/dist/utils/pathResolvers.d.ts +0 -53
- package/dist/utils/pathResolvers.js +0 -72
- package/dist/utils/projectConfig.d.ts +0 -119
- package/dist/utils/projectConfig.js +0 -171
- package/dist/utils/retryFailedPromises.d.ts +0 -2
- package/dist/utils/retryFailedPromises.js +0 -23
- package/dist/utils/sessionAuth.d.ts +0 -48
- package/dist/utils/sessionAuth.js +0 -164
- package/dist/utils/typeGuards.d.ts +0 -35
- package/dist/utils/typeGuards.js +0 -57
- package/dist/utils/validationRules.d.ts +0 -43
- package/dist/utils/validationRules.js +0 -42
- package/dist/utils/versionDetection.d.ts +0 -58
- package/dist/utils/versionDetection.js +0 -251
- package/dist/utils/yamlConverter.d.ts +0 -100
- package/dist/utils/yamlConverter.js +0 -428
- package/dist/utils/yamlLoader.d.ts +0 -70
- package/dist/utils/yamlLoader.js +0 -267
- package/src/adapters/AdapterFactory.ts +0 -510
- package/src/adapters/DatabaseAdapter.ts +0 -306
- package/src/adapters/LegacyAdapter.ts +0 -841
- package/src/adapters/TablesDBAdapter.ts +0 -773
- package/src/config/ConfigManager.ts +0 -808
- package/src/config/README.md +0 -274
- package/src/config/configMigration.ts +0 -575
- package/src/config/configValidation.ts +0 -445
- package/src/config/index.ts +0 -10
- package/src/config/services/ConfigDiscoveryService.ts +0 -463
- package/src/config/services/ConfigLoaderService.ts +0 -740
- package/src/config/services/ConfigMergeService.ts +0 -388
- package/src/config/services/ConfigValidationService.ts +0 -394
- package/src/config/services/SessionAuthService.ts +0 -565
- package/src/config/services/__tests__/ConfigMergeService.test.ts +0 -351
- package/src/config/services/index.ts +0 -29
- package/src/config/yamlConfig.ts +0 -761
- package/src/functions/pathResolution.ts +0 -227
- package/src/shared/attributeMapper.ts +0 -229
- package/src/shared/errorUtils.ts +0 -110
- package/src/shared/functionManager.ts +0 -525
- package/src/shared/indexManager.ts +0 -254
- package/src/shared/jsonSchemaGenerator.ts +0 -383
- package/src/shared/logging.ts +0 -149
- package/src/shared/messageFormatter.ts +0 -208
- package/src/shared/pydanticModelGenerator.ts +0 -618
- package/src/shared/schemaGenerator.ts +0 -644
- package/src/utils/ClientFactory.ts +0 -240
- package/src/utils/configDiscovery.ts +0 -557
- package/src/utils/constantsGenerator.ts +0 -369
- package/src/utils/dataConverters.ts +0 -159
- package/src/utils/directoryUtils.ts +0 -61
- package/src/utils/getClientFromConfig.ts +0 -257
- package/src/utils/helperFunctions.ts +0 -228
- package/src/utils/pathResolvers.ts +0 -81
- package/src/utils/projectConfig.ts +0 -299
- package/src/utils/retryFailedPromises.ts +0 -29
- package/src/utils/sessionAuth.ts +0 -230
- package/src/utils/typeGuards.ts +0 -65
- package/src/utils/validationRules.ts +0 -88
- package/src/utils/versionDetection.ts +0 -292
- package/src/utils/yamlConverter.ts +0 -542
- package/src/utils/yamlLoader.ts +0 -371
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { Storage, Permission, Role, Compression } from "node-appwrite";
|
|
4
|
-
import { MessageFormatter } from
|
|
4
|
+
import { MessageFormatter } from '@njdamstra/appwrite-utils-helpers';
|
|
5
5
|
import { listBuckets, createBucket as createBucketApi, deleteBucket as deleteBucketApi } from "../../storage/methods.js";
|
|
6
|
-
import { writeYamlConfig } from "
|
|
7
|
-
import { ConfigManager } from "../../config/ConfigManager.js";
|
|
6
|
+
import { writeYamlConfig, ConfigManager } from "@njdamstra/appwrite-utils-helpers";
|
|
8
7
|
export const storageCommands = {
|
|
9
8
|
async createBucket(cli) {
|
|
10
9
|
const storage = cli.controller.storage;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import { Databases, Storage } from "node-appwrite";
|
|
3
|
-
import { MessageFormatter } from
|
|
3
|
+
import { MessageFormatter } from '@njdamstra/appwrite-utils-helpers';
|
|
4
4
|
import { fetchAllDatabases } from "../../databases/methods.js";
|
|
5
5
|
import { listBuckets } from "../../storage/methods.js";
|
|
6
|
-
import { getClient } from "
|
|
6
|
+
import { getClient } from "@njdamstra/appwrite-utils-helpers";
|
|
7
7
|
import { ComprehensiveTransfer } from "../../migrations/comprehensiveTransfer.js";
|
|
8
8
|
export const transferCommands = {
|
|
9
9
|
async transferData(cli) {
|
|
@@ -91,9 +91,7 @@ export const transferCommands = {
|
|
|
91
91
|
fromDb,
|
|
92
92
|
targetDb,
|
|
93
93
|
isRemote,
|
|
94
|
-
collections: selectedCollections.
|
|
95
|
-
? selectedCollections.map((c) => c.$id)
|
|
96
|
-
: undefined,
|
|
94
|
+
collections: selectedCollections.map((c) => c.$id),
|
|
97
95
|
sourceBucket,
|
|
98
96
|
targetBucket,
|
|
99
97
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Databases, type Models } from "node-appwrite";
|
|
2
2
|
import { type Attribute } from "@njdamstra/appwrite-utils";
|
|
3
|
-
import type { DatabaseAdapter } from "
|
|
3
|
+
import type { DatabaseAdapter } from "@njdamstra/appwrite-utils-helpers";
|
|
4
4
|
/**
|
|
5
5
|
* Enhanced attribute creation with proper status monitoring and retry logic
|
|
6
6
|
*/
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { Query } from "node-appwrite";
|
|
2
2
|
import { attributeSchema, parseAttribute, } from "@njdamstra/appwrite-utils";
|
|
3
3
|
import { nameToIdMapping, enqueueOperation, markAttributeProcessed, isAttributeProcessed, } from "../shared/operationQueue.js";
|
|
4
|
-
import { delay, tryAwaitWithRetry, calculateExponentialBackoff, } from "
|
|
4
|
+
import { delay, tryAwaitWithRetry, calculateExponentialBackoff, } from "@njdamstra/appwrite-utils-helpers";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { Decimal } from "decimal.js";
|
|
7
|
-
import { logger } from "
|
|
8
|
-
import { MessageFormatter } from "../shared/messageFormatter.js";
|
|
9
|
-
import { isDatabaseAdapter } from "../utils/typeGuards.js";
|
|
7
|
+
import { logger, MessageFormatter, isDatabaseAdapter } from "@njdamstra/appwrite-utils-helpers";
|
|
10
8
|
// Extreme values that Appwrite may return, which should be treated as undefined
|
|
11
9
|
const EXTREME_MIN_INTEGER = -9223372036854776000;
|
|
12
10
|
const EXTREME_MAX_INTEGER = 9223372036854776000;
|
|
@@ -439,6 +437,39 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
|
|
|
439
437
|
? attribute.xdefault
|
|
440
438
|
: undefined, attribute.array || false);
|
|
441
439
|
break;
|
|
440
|
+
case "varchar":
|
|
441
|
+
await db.createVarcharAttribute(dbId, collectionId, attribute.key, attribute.size || 255, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
|
|
442
|
+
? attribute.xdefault
|
|
443
|
+
: undefined, attribute.array || false, attribute.encrypt);
|
|
444
|
+
break;
|
|
445
|
+
case "text":
|
|
446
|
+
case "mediumtext":
|
|
447
|
+
case "longtext": {
|
|
448
|
+
const createFn = attribute.type === "text"
|
|
449
|
+
? db.createTextAttribute.bind(db)
|
|
450
|
+
: attribute.type === "mediumtext"
|
|
451
|
+
? db.createMediumtextAttribute.bind(db)
|
|
452
|
+
: db.createLongtextAttribute.bind(db);
|
|
453
|
+
await createFn(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
|
|
454
|
+
? attribute.xdefault
|
|
455
|
+
: undefined, attribute.array || false, attribute.encrypt);
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
case "point":
|
|
459
|
+
await db.createPointAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
|
|
460
|
+
? attribute.xdefault
|
|
461
|
+
: undefined);
|
|
462
|
+
break;
|
|
463
|
+
case "line":
|
|
464
|
+
await db.createLineAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
|
|
465
|
+
? attribute.xdefault
|
|
466
|
+
: undefined);
|
|
467
|
+
break;
|
|
468
|
+
case "polygon":
|
|
469
|
+
await db.createPolygonAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
|
|
470
|
+
? attribute.xdefault
|
|
471
|
+
: undefined);
|
|
472
|
+
break;
|
|
442
473
|
case "relationship":
|
|
443
474
|
await db.createRelationshipAttribute(dbId, collectionId, attribute.relatedCollection, attribute.relationType, attribute.twoWay, attribute.key, attribute.twoWayKey, attribute.onDelete);
|
|
444
475
|
break;
|
|
@@ -448,6 +479,10 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
|
|
|
448
479
|
type: attribute.type,
|
|
449
480
|
supportedTypes: [
|
|
450
481
|
"string",
|
|
482
|
+
"varchar",
|
|
483
|
+
"text",
|
|
484
|
+
"mediumtext",
|
|
485
|
+
"longtext",
|
|
451
486
|
"integer",
|
|
452
487
|
"double",
|
|
453
488
|
"float",
|
|
@@ -457,6 +492,9 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
|
|
|
457
492
|
"ip",
|
|
458
493
|
"url",
|
|
459
494
|
"enum",
|
|
495
|
+
"point",
|
|
496
|
+
"line",
|
|
497
|
+
"polygon",
|
|
460
498
|
"relationship",
|
|
461
499
|
],
|
|
462
500
|
operation: "createLegacyAttribute",
|
|
@@ -474,12 +512,6 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
|
|
|
474
512
|
* Legacy attribute update using type-specific methods
|
|
475
513
|
*/
|
|
476
514
|
const updateLegacyAttribute = async (db, dbId, collectionId, attribute) => {
|
|
477
|
-
console.log(`DEBUG updateLegacyAttribute before normalizeMinMaxValues:`, {
|
|
478
|
-
key: attribute.key,
|
|
479
|
-
type: attribute.type,
|
|
480
|
-
min: attribute.min,
|
|
481
|
-
max: attribute.max
|
|
482
|
-
});
|
|
483
515
|
const { min: normalizedMin, max: normalizedMax } = normalizeMinMaxValues(attribute);
|
|
484
516
|
switch (attribute.type) {
|
|
485
517
|
case "string":
|
|
@@ -534,6 +566,39 @@ const updateLegacyAttribute = async (db, dbId, collectionId, attribute) => {
|
|
|
534
566
|
? attribute.xdefault
|
|
535
567
|
: null);
|
|
536
568
|
break;
|
|
569
|
+
case "varchar":
|
|
570
|
+
await db.updateVarcharAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
|
|
571
|
+
? attribute.xdefault
|
|
572
|
+
: null, attribute.size);
|
|
573
|
+
break;
|
|
574
|
+
case "text":
|
|
575
|
+
case "mediumtext":
|
|
576
|
+
case "longtext": {
|
|
577
|
+
const updateFn = attribute.type === "text"
|
|
578
|
+
? db.updateTextAttribute.bind(db)
|
|
579
|
+
: attribute.type === "mediumtext"
|
|
580
|
+
? db.updateMediumtextAttribute.bind(db)
|
|
581
|
+
: db.updateLongtextAttribute.bind(db);
|
|
582
|
+
await updateFn(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
|
|
583
|
+
? attribute.xdefault
|
|
584
|
+
: null);
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case "point":
|
|
588
|
+
await db.updatePointAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
|
|
589
|
+
? attribute.xdefault
|
|
590
|
+
: null);
|
|
591
|
+
break;
|
|
592
|
+
case "line":
|
|
593
|
+
await db.updateLineAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
|
|
594
|
+
? attribute.xdefault
|
|
595
|
+
: null);
|
|
596
|
+
break;
|
|
597
|
+
case "polygon":
|
|
598
|
+
await db.updatePolygonAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
|
|
599
|
+
? attribute.xdefault
|
|
600
|
+
: null);
|
|
601
|
+
break;
|
|
537
602
|
case "relationship":
|
|
538
603
|
await db.updateRelationshipAttribute(dbId, collectionId, attribute.key, attribute.onDelete);
|
|
539
604
|
break;
|
|
@@ -687,12 +752,22 @@ const getComparableFields = (type) => {
|
|
|
687
752
|
switch (type) {
|
|
688
753
|
case "string":
|
|
689
754
|
return [...baseFields, "size", "encrypt"];
|
|
755
|
+
case "varchar":
|
|
756
|
+
return [...baseFields, "size", "encrypt"];
|
|
757
|
+
case "text":
|
|
758
|
+
case "mediumtext":
|
|
759
|
+
case "longtext":
|
|
760
|
+
return [...baseFields, "encrypt"];
|
|
690
761
|
case "integer":
|
|
691
762
|
case "double":
|
|
692
763
|
case "float":
|
|
693
764
|
return [...baseFields, "min", "max"];
|
|
694
765
|
case "enum":
|
|
695
766
|
return [...baseFields, "elements"];
|
|
767
|
+
case "point":
|
|
768
|
+
case "line":
|
|
769
|
+
case "polygon":
|
|
770
|
+
return baseFields;
|
|
696
771
|
case "relationship":
|
|
697
772
|
return [
|
|
698
773
|
...baseFields,
|
|
@@ -981,35 +1056,10 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
|
|
|
981
1056
|
// MessageFormatter.info(
|
|
982
1057
|
// `Updating attribute with same key ${attribute.key} but different values`
|
|
983
1058
|
// );
|
|
984
|
-
// DEBUG: Log before object merge to detect corruption
|
|
985
|
-
if ((attribute.key === 'conversationType' || attribute.key === 'messageStreakCount')) {
|
|
986
|
-
console.log(`[DEBUG] MERGE - key="${attribute.key}"`, {
|
|
987
|
-
found: {
|
|
988
|
-
elements: foundAttribute?.elements,
|
|
989
|
-
min: foundAttribute?.min,
|
|
990
|
-
max: foundAttribute?.max
|
|
991
|
-
},
|
|
992
|
-
desired: {
|
|
993
|
-
elements: attribute?.elements,
|
|
994
|
-
min: attribute?.min,
|
|
995
|
-
max: attribute?.max
|
|
996
|
-
}
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
1059
|
finalAttribute = {
|
|
1000
1060
|
...foundAttribute,
|
|
1001
1061
|
...attribute,
|
|
1002
1062
|
};
|
|
1003
|
-
// DEBUG: Log after object merge to detect corruption
|
|
1004
|
-
if ((finalAttribute.key === 'conversationType' || finalAttribute.key === 'messageStreakCount')) {
|
|
1005
|
-
console.log(`[DEBUG] AFTER_MERGE - key="${finalAttribute.key}"`, {
|
|
1006
|
-
merged: {
|
|
1007
|
-
elements: finalAttribute?.elements,
|
|
1008
|
-
min: finalAttribute?.min,
|
|
1009
|
-
max: finalAttribute?.max
|
|
1010
|
-
}
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1013
1063
|
action = "update";
|
|
1014
1064
|
}
|
|
1015
1065
|
else if (!updateEnabled &&
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { indexSchema } from "@njdamstra/appwrite-utils";
|
|
2
|
-
import { Databases, IndexType, Query } from "node-appwrite";
|
|
3
|
-
import { delay, tryAwaitWithRetry, calculateExponentialBackoff } from "
|
|
4
|
-
import { isLegacyDatabases } from "../utils/typeGuards.js";
|
|
5
|
-
import { MessageFormatter } from "../shared/messageFormatter.js";
|
|
2
|
+
import { Databases, IndexType, OrderBy, Query } from "node-appwrite";
|
|
3
|
+
import { delay, tryAwaitWithRetry, calculateExponentialBackoff, isLegacyDatabases, MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
|
|
6
4
|
// System attributes that are always available for indexing in Appwrite
|
|
7
5
|
const SYSTEM_ATTRIBUTES = ['$id', '$createdAt', '$updatedAt', '$permissions'];
|
|
8
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Databases, type Models } from "node-appwrite";
|
|
2
2
|
import type { AppwriteConfig, CollectionCreate } from "@njdamstra/appwrite-utils";
|
|
3
|
-
import type { DatabaseAdapter } from "
|
|
3
|
+
import type { DatabaseAdapter } from "@njdamstra/appwrite-utils-helpers";
|
|
4
4
|
export { wipeDatabase, wipeCollection, wipeAllTables, wipeTableRows, } from "./wipeOperations.js";
|
|
5
5
|
export { transferDocumentsBetweenDbsLocalToLocal, transferDocumentsBetweenDbsLocalToRemote, } from "./transferOperations.js";
|
|
6
6
|
export declare const documentExists: (db: Databases | DatabaseAdapter, dbId: string, targetCollectionId: string, toCreateObject: any) => Promise<Models.Document | null>;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { Databases, ID, Permission, Query, } from "node-appwrite";
|
|
2
|
-
import { getAdapterFromConfig } from "
|
|
3
|
-
import { nameToIdMapping, processQueue, queuedOperations, clearProcessingState, isCollectionProcessed, markCollectionProcessed } from "../shared/operationQueue.js";
|
|
4
|
-
import { logger } from "
|
|
2
|
+
import { getAdapterFromConfig } from "@njdamstra/appwrite-utils-helpers";
|
|
3
|
+
import { nameToIdMapping, processQueue, queuedOperations, clearProcessingState, isCollectionProcessed, markCollectionProcessed, enqueueOperation } from "../shared/operationQueue.js";
|
|
4
|
+
import { logger, SchemaGenerator } from "@njdamstra/appwrite-utils-helpers";
|
|
5
5
|
// Legacy attribute/index helpers removed in favor of unified adapter path
|
|
6
|
-
import { SchemaGenerator } from "../shared/schemaGenerator.js";
|
|
7
6
|
import { isNull, isUndefined, isNil, isPlainObject, isString, } from "es-toolkit";
|
|
8
|
-
import { delay, tryAwaitWithRetry } from "
|
|
9
|
-
import { MessageFormatter } from "
|
|
10
|
-
import { isLegacyDatabases } from "
|
|
11
|
-
import { mapToCreateAttributeParams, mapToUpdateAttributeParams } from "../shared/attributeMapper.js";
|
|
7
|
+
import { delay, tryAwaitWithRetry } from "@njdamstra/appwrite-utils-helpers";
|
|
8
|
+
import { MessageFormatter, mapToCreateAttributeParams, mapToUpdateAttributeParams } from "@njdamstra/appwrite-utils-helpers";
|
|
9
|
+
import { isLegacyDatabases } from "@njdamstra/appwrite-utils-helpers";
|
|
12
10
|
import { diffTableColumns, isIndexEqualToIndex, diffColumnsDetailed, executeColumnOperations } from "./tableOperations.js";
|
|
11
|
+
import { createOrUpdateIndexesViaAdapter, deleteObsoleteIndexesViaAdapter } from "../tables/indexManager.js";
|
|
13
12
|
// Re-export wipe operations
|
|
14
13
|
export { wipeDatabase, wipeCollection, wipeAllTables, wipeTableRows, } from "./wipeOperations.js";
|
|
15
14
|
// Re-export transfer operations
|
|
@@ -287,63 +286,106 @@ export const createOrUpdateCollectionsViaAdapter = async (adapter, databaseId, c
|
|
|
287
286
|
}
|
|
288
287
|
MessageFormatter.info(`Summary → ➕ ${plan.toCreate.length} | 🔧 ${plan.toUpdate.length} | ♻️ ${plan.toRecreate.length} | ⏭️ ${plan.unchanged.length}`, { prefix: 'Attributes' });
|
|
289
288
|
}
|
|
290
|
-
// Relationship attributes — resolve relatedCollection to ID, then diff and create/update
|
|
291
|
-
const
|
|
292
|
-
if (
|
|
293
|
-
|
|
289
|
+
// Relationship attributes — resolve relatedCollection to ID, then diff and create/update with recreate support
|
|
290
|
+
const relsAll = (attributes || []).filter((a) => a.type === 'relationship');
|
|
291
|
+
if (relsAll.length > 0) {
|
|
292
|
+
const relsResolved = [];
|
|
293
|
+
const relsDeferred = [];
|
|
294
|
+
// Resolve related collections (names -> IDs) using cache or lookup.
|
|
295
|
+
// If not resolvable yet (target table created later in the same push), queue for later.
|
|
296
|
+
for (const attr of relsAll) {
|
|
294
297
|
const relNameOrId = attr.relatedCollection;
|
|
295
298
|
if (!relNameOrId)
|
|
296
299
|
continue;
|
|
297
300
|
let relId = nameToIdMapping.get(relNameOrId) || relNameOrId;
|
|
298
|
-
|
|
301
|
+
let resolved = false;
|
|
302
|
+
if (nameToIdMapping.has(relNameOrId)) {
|
|
303
|
+
resolved = true;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
// Try resolve by name
|
|
299
307
|
try {
|
|
300
308
|
const relList = await adapter.listTables({ databaseId, queries: [Query.equal('name', relNameOrId)] });
|
|
301
309
|
const relItems = relList.tables || [];
|
|
302
310
|
if (relItems[0]?.$id) {
|
|
303
311
|
relId = relItems[0].$id;
|
|
304
312
|
nameToIdMapping.set(relNameOrId, relId);
|
|
313
|
+
resolved = true;
|
|
305
314
|
}
|
|
306
315
|
}
|
|
307
316
|
catch { }
|
|
317
|
+
// If the relNameOrId looks like an ID but isn't resolved yet, attempt a direct get
|
|
318
|
+
if (!resolved && relNameOrId && relNameOrId.length >= 10) {
|
|
319
|
+
try {
|
|
320
|
+
const probe = await adapter.getTable({ databaseId, tableId: relNameOrId });
|
|
321
|
+
if (probe.data?.$id) {
|
|
322
|
+
nameToIdMapping.set(relNameOrId, relNameOrId);
|
|
323
|
+
relId = relNameOrId;
|
|
324
|
+
resolved = true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch { }
|
|
328
|
+
}
|
|
308
329
|
}
|
|
309
|
-
if (relId && typeof relId === 'string')
|
|
330
|
+
if (resolved && relId && typeof relId === 'string') {
|
|
310
331
|
attr.relatedCollection = relId;
|
|
332
|
+
relsResolved.push(attr);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// Defer until related table exists; queue a surgical operation
|
|
336
|
+
enqueueOperation({
|
|
337
|
+
type: 'attribute',
|
|
338
|
+
collectionId: tableId,
|
|
339
|
+
attribute: attr,
|
|
340
|
+
dependencies: [relNameOrId]
|
|
341
|
+
});
|
|
342
|
+
relsDeferred.push(attr);
|
|
343
|
+
}
|
|
311
344
|
}
|
|
345
|
+
// Compute a detailed plan for immediately resolvable relationships
|
|
312
346
|
const tableInfo2 = await adapter.getTable({ databaseId, tableId });
|
|
313
347
|
const existingCols2 = tableInfo2.data?.columns || tableInfo2.data?.attributes || [];
|
|
314
|
-
const
|
|
315
|
-
// Relationship plan with icons
|
|
348
|
+
const relPlan = diffColumnsDetailed(relsResolved, existingCols2);
|
|
349
|
+
// Relationship plan with icons (includes recreates)
|
|
316
350
|
{
|
|
317
351
|
const parts = [];
|
|
318
|
-
if (
|
|
319
|
-
parts.push(`➕ ${
|
|
320
|
-
if (
|
|
321
|
-
parts.push(`🔧 ${
|
|
322
|
-
if (
|
|
323
|
-
parts.push(
|
|
352
|
+
if (relPlan.toCreate.length)
|
|
353
|
+
parts.push(`➕ ${relPlan.toCreate.length} (${relPlan.toCreate.map((a) => a.key).join(', ')})`);
|
|
354
|
+
if (relPlan.toUpdate.length)
|
|
355
|
+
parts.push(`🔧 ${relPlan.toUpdate.length} (${relPlan.toUpdate.map((u) => u.attribute?.key ?? u.key).join(', ')})`);
|
|
356
|
+
if (relPlan.toRecreate.length)
|
|
357
|
+
parts.push(`♻️ ${relPlan.toRecreate.length} (${relPlan.toRecreate.map((r) => r.newAttribute?.key ?? r?.key).join(', ')})`);
|
|
358
|
+
if (relPlan.unchanged.length)
|
|
359
|
+
parts.push(`⏭️ ${relPlan.unchanged.length}`);
|
|
324
360
|
MessageFormatter.info(`Plan → ${parts.join(' | ') || 'no changes'}`, { prefix: 'Relationships' });
|
|
325
361
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
362
|
+
// Execute plan using the same operation executor to properly handle deletes/recreates
|
|
363
|
+
const relResults = await executeColumnOperations(adapter, databaseId, tableId, relPlan);
|
|
364
|
+
if (relResults.success.length > 0) {
|
|
365
|
+
const totalRelationships = relPlan.toCreate.length + relPlan.toUpdate.length + relPlan.toRecreate.length + relPlan.unchanged.length;
|
|
366
|
+
const activeRelationships = relPlan.toCreate.length + relPlan.toUpdate.length + relPlan.toRecreate.length;
|
|
367
|
+
if (relResults.success.length !== activeRelationships) {
|
|
368
|
+
// Show both counts when they differ (usually due to recreations)
|
|
369
|
+
MessageFormatter.success(`Processed ${relResults.success.length} operations for ${activeRelationships} relationship${activeRelationships === 1 ? '' : 's'}`, { prefix: 'Relationships' });
|
|
329
370
|
}
|
|
330
|
-
|
|
331
|
-
MessageFormatter.
|
|
371
|
+
else {
|
|
372
|
+
MessageFormatter.success(`Processed ${relResults.success.length} relationship${relResults.success.length === 1 ? '' : 's'}`, { prefix: 'Relationships' });
|
|
332
373
|
}
|
|
333
374
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
catch (e) {
|
|
339
|
-
MessageFormatter.error(`Failed to create relationship ${attr.key}`, e instanceof Error ? e : new Error(String(e)), { prefix: 'Attributes' });
|
|
375
|
+
if (relResults.errors.length > 0) {
|
|
376
|
+
MessageFormatter.error(`${relResults.errors.length} relationship operations failed:`, undefined, { prefix: 'Relationships' });
|
|
377
|
+
for (const err of relResults.errors) {
|
|
378
|
+
MessageFormatter.error(` ${err.column}: ${err.error}`, undefined, { prefix: 'Relationships' });
|
|
340
379
|
}
|
|
341
380
|
}
|
|
381
|
+
if (relsDeferred.length > 0) {
|
|
382
|
+
MessageFormatter.info(`Deferred ${relsDeferred.length} relationship(s) until related tables become available`, { prefix: 'Relationships' });
|
|
383
|
+
}
|
|
342
384
|
}
|
|
343
385
|
// Wait for all attributes to become available before creating indexes
|
|
344
386
|
const allAttrKeys = [
|
|
345
387
|
...nonRel.map((a) => a.key),
|
|
346
|
-
...
|
|
388
|
+
...relsAll.filter((a) => a.relatedCollection).map((a) => a.key)
|
|
347
389
|
];
|
|
348
390
|
if (allAttrKeys.length > 0) {
|
|
349
391
|
for (const attrKey of allAttrKeys) {
|
|
@@ -378,171 +420,37 @@ export const createOrUpdateCollectionsViaAdapter = async (adapter, databaseId, c
|
|
|
378
420
|
}
|
|
379
421
|
}
|
|
380
422
|
}
|
|
381
|
-
//
|
|
423
|
+
// Index management: create/update indexes using clean adapter-based system
|
|
382
424
|
const localTableConfig = config.collections?.find(c => c.name === collectionData.name || c.$id === collectionData.$id);
|
|
383
425
|
const idxs = (localTableConfig?.indexes ?? indexes ?? []);
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
// Show a concise plan with icons before executing
|
|
390
|
-
const idxPlanPlus = [];
|
|
391
|
-
const idxPlanPlusMinus = [];
|
|
392
|
-
const idxPlanSkip = [];
|
|
393
|
-
for (const idx of idxs) {
|
|
394
|
-
const found = existingIdx.find((i) => i.key === idx.key);
|
|
395
|
-
if (found) {
|
|
396
|
-
if (isIndexEqualToIndex(found, idx))
|
|
397
|
-
idxPlanSkip.push(idx.key);
|
|
398
|
-
else
|
|
399
|
-
idxPlanPlusMinus.push(idx.key);
|
|
400
|
-
}
|
|
401
|
-
else
|
|
402
|
-
idxPlanPlus.push(idx.key);
|
|
403
|
-
}
|
|
404
|
-
const planParts = [];
|
|
405
|
-
if (idxPlanPlus.length)
|
|
406
|
-
planParts.push(`➕ ${idxPlanPlus.length} (${idxPlanPlus.join(', ')})`);
|
|
407
|
-
if (idxPlanPlusMinus.length)
|
|
408
|
-
planParts.push(`🔧 ${idxPlanPlusMinus.length} (${idxPlanPlusMinus.join(', ')})`);
|
|
409
|
-
if (idxPlanSkip.length)
|
|
410
|
-
planParts.push(`⏭️ ${idxPlanSkip.length}`);
|
|
411
|
-
MessageFormatter.info(`Plan → ${planParts.join(' | ') || 'no changes'}`, { prefix: 'Indexes' });
|
|
412
|
-
const created = [];
|
|
413
|
-
const updated = [];
|
|
414
|
-
const skipped = [];
|
|
415
|
-
for (const idx of idxs) {
|
|
416
|
-
const found = existingIdx.find((i) => i.key === idx.key);
|
|
417
|
-
if (found) {
|
|
418
|
-
if (isIndexEqualToIndex(found, idx)) {
|
|
419
|
-
MessageFormatter.info(`Index ${idx.key} unchanged`, { prefix: 'Indexes' });
|
|
420
|
-
skipped.push(idx.key);
|
|
421
|
-
}
|
|
422
|
-
else {
|
|
423
|
-
try {
|
|
424
|
-
await adapter.deleteIndex({ databaseId, tableId, key: idx.key });
|
|
425
|
-
await delay(100);
|
|
426
|
-
}
|
|
427
|
-
catch { }
|
|
428
|
-
try {
|
|
429
|
-
await adapter.createIndex({ databaseId, tableId, key: idx.key, type: idx.type, attributes: idx.attributes, orders: idx.orders || [] });
|
|
430
|
-
updated.push(idx.key);
|
|
431
|
-
}
|
|
432
|
-
catch (e) {
|
|
433
|
-
const msg = (e?.message || '').toString().toLowerCase();
|
|
434
|
-
if (msg.includes('already exists')) {
|
|
435
|
-
MessageFormatter.info(`Index ${idx.key} already exists after delete attempt, skipping`, { prefix: 'Indexes' });
|
|
436
|
-
skipped.push(idx.key);
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
throw e;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
try {
|
|
446
|
-
await adapter.createIndex({ databaseId, tableId, key: idx.key, type: idx.type, attributes: idx.attributes, orders: idx.orders || [] });
|
|
447
|
-
created.push(idx.key);
|
|
448
|
-
}
|
|
449
|
-
catch (e) {
|
|
450
|
-
const msg = (e?.message || '').toString().toLowerCase();
|
|
451
|
-
if (msg.includes('already exists')) {
|
|
452
|
-
MessageFormatter.info(`Index ${idx.key} already exists (create), skipping`, { prefix: 'Indexes' });
|
|
453
|
-
skipped.push(idx.key);
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
throw e;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
// Wait for index availability
|
|
461
|
-
const maxWait = 60000;
|
|
462
|
-
const start = Date.now();
|
|
463
|
-
let lastStatus = '';
|
|
464
|
-
while (Date.now() - start < maxWait) {
|
|
465
|
-
try {
|
|
466
|
-
const li = await adapter.listIndexes({ databaseId, tableId });
|
|
467
|
-
const list = li.data || li.indexes || [];
|
|
468
|
-
const cur = list.find((i) => i.key === idx.key);
|
|
469
|
-
if (cur) {
|
|
470
|
-
if (cur.status === 'available')
|
|
471
|
-
break;
|
|
472
|
-
if (cur.status === 'failed' || cur.status === 'stuck') {
|
|
473
|
-
throw new Error(cur.error || `Index ${idx.key} failed`);
|
|
474
|
-
}
|
|
475
|
-
lastStatus = cur.status;
|
|
476
|
-
}
|
|
477
|
-
await delay(2000);
|
|
478
|
-
}
|
|
479
|
-
catch {
|
|
480
|
-
await delay(2000);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
await delay(150);
|
|
484
|
-
}
|
|
485
|
-
MessageFormatter.info(`Summary → ➕ ${created.length} | 🔧 ${updated.length} | ⏭️ ${skipped.length}`, { prefix: 'Indexes' });
|
|
486
|
-
}
|
|
487
|
-
catch (e) {
|
|
488
|
-
MessageFormatter.error(`Failed to list/create indexes`, e instanceof Error ? e : new Error(String(e)), { prefix: 'Indexes' });
|
|
489
|
-
}
|
|
490
|
-
// Deletions for indexes: remove remote indexes not declared in YAML/config
|
|
491
|
-
try {
|
|
492
|
-
const desiredIndexKeys = new Set((indexes || []).map((i) => i.key));
|
|
493
|
-
const idxRes = await adapter.listIndexes({ databaseId, tableId });
|
|
494
|
-
const existingIdx = idxRes.data || idxRes.indexes || [];
|
|
495
|
-
const extraIdx = existingIdx
|
|
496
|
-
.filter((i) => i?.key && !desiredIndexKeys.has(i.key))
|
|
497
|
-
.map((i) => i.key);
|
|
498
|
-
if (extraIdx.length > 0) {
|
|
499
|
-
MessageFormatter.info(`Plan → 🗑️ ${extraIdx.length} indexes (${extraIdx.join(', ')})`, { prefix: 'Indexes' });
|
|
500
|
-
const deleted = [];
|
|
501
|
-
const errors = [];
|
|
502
|
-
for (const key of extraIdx) {
|
|
503
|
-
try {
|
|
504
|
-
await adapter.deleteIndex({ databaseId, tableId, key });
|
|
505
|
-
// Optionally wait for index to disappear
|
|
506
|
-
const start = Date.now();
|
|
507
|
-
const maxWait = 30000;
|
|
508
|
-
while (Date.now() - start < maxWait) {
|
|
509
|
-
try {
|
|
510
|
-
const li = await adapter.listIndexes({ databaseId, tableId });
|
|
511
|
-
const list = li.data || li.indexes || [];
|
|
512
|
-
if (!list.find((ix) => ix.key === key))
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
catch { }
|
|
516
|
-
await delay(1000);
|
|
517
|
-
}
|
|
518
|
-
deleted.push(key);
|
|
519
|
-
}
|
|
520
|
-
catch (e) {
|
|
521
|
-
errors.push({ key, error: e?.message || String(e) });
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
if (deleted.length) {
|
|
525
|
-
MessageFormatter.success(`Deleted ${deleted.length} indexes: ${deleted.join(', ')}`, { prefix: 'Indexes' });
|
|
526
|
-
}
|
|
527
|
-
if (errors.length) {
|
|
528
|
-
MessageFormatter.error(`${errors.length} index deletions failed`, undefined, { prefix: 'Indexes' });
|
|
529
|
-
errors.forEach(er => MessageFormatter.error(` ${er.key}: ${er.error}`, undefined, { prefix: 'Indexes' }));
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
MessageFormatter.info(`Plan → 🗑️ 0 indexes`, { prefix: 'Indexes' });
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
catch (e) {
|
|
537
|
-
MessageFormatter.warning(`Could not evaluate index deletions: ${e?.message || e}`, { prefix: 'Indexes' });
|
|
538
|
-
}
|
|
426
|
+
// Create/update indexes with proper planning and execution
|
|
427
|
+
await createOrUpdateIndexesViaAdapter(adapter, databaseId, tableId, idxs, indexes);
|
|
428
|
+
// Handle obsolete index deletions
|
|
429
|
+
const desiredIndexKeys = new Set((indexes || []).map((i) => i.key));
|
|
430
|
+
await deleteObsoleteIndexesViaAdapter(adapter, databaseId, tableId, desiredIndexKeys);
|
|
539
431
|
// Deletions: remove columns/attributes that are present remotely but not in desired config
|
|
540
432
|
try {
|
|
541
433
|
const desiredKeys = new Set((attributes || []).map((a) => a.key));
|
|
434
|
+
// Also track case-insensitive keys to avoid double-deletion of renames (handled as recreates)
|
|
435
|
+
const desiredKeysLower = new Set((attributes || []).map((a) => a.key?.toLowerCase()));
|
|
542
436
|
const tableInfo3 = await adapter.getTable({ databaseId, tableId });
|
|
543
437
|
const existingCols3 = tableInfo3.data?.columns || tableInfo3.data?.attributes || [];
|
|
544
438
|
const toDelete = existingCols3
|
|
545
|
-
.filter((col) =>
|
|
439
|
+
.filter((col) => {
|
|
440
|
+
if (!col?.key)
|
|
441
|
+
return false;
|
|
442
|
+
// Exact match - keep it
|
|
443
|
+
if (desiredKeys.has(col.key))
|
|
444
|
+
return false;
|
|
445
|
+
// Case-insensitive match (rename scenario) - already handled as recreate, don't delete again
|
|
446
|
+
if (desiredKeysLower.has(col.key?.toLowerCase()))
|
|
447
|
+
return false;
|
|
448
|
+
// Don't delete child-side relationship attributes - they're auto-managed by Appwrite
|
|
449
|
+
// for two-way relationships and deleting them would break the parent relationship
|
|
450
|
+
if (col.type === 'relationship' && col.side === 'child')
|
|
451
|
+
return false;
|
|
452
|
+
return true;
|
|
453
|
+
})
|
|
546
454
|
.map((col) => col.key);
|
|
547
455
|
if (toDelete.length > 0) {
|
|
548
456
|
MessageFormatter.info(`Plan → 🗑️ ${toDelete.length} (${toDelete.join(', ')})`, { prefix: 'Attributes' });
|
|
@@ -555,7 +463,9 @@ export const createOrUpdateCollectionsViaAdapter = async (adapter, databaseId, c
|
|
|
555
463
|
const idxRes = await adapter.listIndexes({ databaseId, tableId });
|
|
556
464
|
const ilist = idxRes.data || idxRes.indexes || [];
|
|
557
465
|
for (const idx of ilist) {
|
|
558
|
-
const attrs = Array.isArray(idx.attributes)
|
|
466
|
+
const attrs = Array.isArray(idx.attributes)
|
|
467
|
+
? idx.attributes
|
|
468
|
+
: (Array.isArray(idx.columns) ? idx.columns : []);
|
|
559
469
|
if (attrs.includes(key)) {
|
|
560
470
|
MessageFormatter.info(`🗑️ Deleting index '${idx.key}' referencing '${key}'`, { prefix: 'Indexes' });
|
|
561
471
|
await adapter.deleteIndex({ databaseId, tableId, key: idx.key });
|
|
@@ -647,6 +557,15 @@ export const createOrUpdateCollectionsViaAdapter = async (adapter, databaseId, c
|
|
|
647
557
|
}
|
|
648
558
|
}
|
|
649
559
|
}
|
|
560
|
+
// Process any remaining queued operations to complete relationship sync
|
|
561
|
+
try {
|
|
562
|
+
MessageFormatter.info(`🔄 Processing final operation queue for database ${databaseId}`, { prefix: "Tables" });
|
|
563
|
+
await processQueue(adapter, databaseId);
|
|
564
|
+
MessageFormatter.info(`✅ Operation queue processing completed`, { prefix: "Tables" });
|
|
565
|
+
}
|
|
566
|
+
catch (error) {
|
|
567
|
+
MessageFormatter.error(`Failed to process operation queue`, error instanceof Error ? error : new Error(String(error)), { prefix: 'Tables' });
|
|
568
|
+
}
|
|
650
569
|
};
|
|
651
570
|
export const generateMockData = async (database, databaseId, configCollections) => {
|
|
652
571
|
for (const { collection, mockFunction } of configCollections) {
|
|
@@ -45,6 +45,7 @@ export declare function isIndexEqualToIndex(a: any, b: any): boolean;
|
|
|
45
45
|
/**
|
|
46
46
|
* Enhanced version of columns diff with detailed change analysis
|
|
47
47
|
* Order: desired first, then existing (matches internal usage here)
|
|
48
|
+
* Handles case-insensitive key matches as renames (recreates)
|
|
48
49
|
*/
|
|
49
50
|
export declare function diffColumnsDetailed(desiredAttributes: Attribute[], existingColumns: any[]): ColumnOperationPlan;
|
|
50
51
|
/**
|