@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.
Files changed (285) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/CONFIG_TODO.md +1189 -0
  3. package/SELECTION_DIALOGS.md +146 -0
  4. package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
  5. package/dist/adapters/index.d.ts +7 -8
  6. package/dist/adapters/index.js +7 -9
  7. package/dist/backups/operations/bucketBackup.js +2 -2
  8. package/dist/backups/operations/collectionBackup.d.ts +1 -1
  9. package/dist/backups/operations/collectionBackup.js +3 -3
  10. package/dist/backups/operations/comprehensiveBackup.d.ts +1 -1
  11. package/dist/backups/operations/comprehensiveBackup.js +2 -2
  12. package/dist/backups/tracking/centralizedTracking.d.ts +1 -1
  13. package/dist/backups/tracking/centralizedTracking.js +2 -2
  14. package/dist/cli/commands/configCommands.js +51 -7
  15. package/dist/cli/commands/databaseCommands.d.ts +1 -0
  16. package/dist/cli/commands/databaseCommands.js +119 -9
  17. package/dist/cli/commands/functionCommands.js +3 -3
  18. package/dist/cli/commands/importFileCommands.d.ts +7 -0
  19. package/dist/cli/commands/importFileCommands.js +674 -0
  20. package/dist/cli/commands/schemaCommands.js +3 -3
  21. package/dist/cli/commands/storageCommands.js +2 -3
  22. package/dist/cli/commands/transferCommands.js +3 -5
  23. package/dist/collections/attributes.d.ts +1 -1
  24. package/dist/collections/attributes.js +85 -35
  25. package/dist/collections/indexes.js +2 -4
  26. package/dist/collections/methods.d.ts +1 -1
  27. package/dist/collections/methods.js +111 -192
  28. package/dist/collections/tableOperations.d.ts +1 -0
  29. package/dist/collections/tableOperations.js +90 -23
  30. package/dist/collections/transferOperations.d.ts +1 -1
  31. package/dist/collections/transferOperations.js +3 -4
  32. package/dist/collections/wipeOperations.d.ts +4 -3
  33. package/dist/collections/wipeOperations.js +112 -39
  34. package/dist/databases/methods.js +2 -2
  35. package/dist/databases/setup.js +2 -2
  36. package/dist/examples/yamlTerminologyExample.js +2 -2
  37. package/dist/functions/deployments.d.ts +1 -1
  38. package/dist/functions/deployments.js +5 -5
  39. package/dist/functions/fnConfigDiscovery.js +2 -2
  40. package/dist/functions/methods.js +16 -4
  41. package/dist/init.js +1 -1
  42. package/dist/interactiveCLI.d.ts +6 -1
  43. package/dist/interactiveCLI.js +64 -10
  44. package/dist/main.js +130 -177
  45. package/dist/migrations/afterImportActions.js +2 -3
  46. package/dist/migrations/appwriteToX.d.ts +97 -1
  47. package/dist/migrations/appwriteToX.js +9 -7
  48. package/dist/migrations/comprehensiveTransfer.js +3 -5
  49. package/dist/migrations/dataLoader.d.ts +194 -2
  50. package/dist/migrations/dataLoader.js +2 -5
  51. package/dist/migrations/importController.js +3 -4
  52. package/dist/migrations/importDataActions.js +3 -3
  53. package/dist/migrations/relationships.js +1 -2
  54. package/dist/migrations/services/DataTransformationService.js +2 -2
  55. package/dist/migrations/services/FileHandlerService.js +1 -1
  56. package/dist/migrations/services/ImportOrchestrator.js +4 -4
  57. package/dist/migrations/services/RateLimitManager.js +1 -1
  58. package/dist/migrations/services/RelationshipResolver.js +1 -1
  59. package/dist/migrations/services/UserMappingService.js +1 -1
  60. package/dist/migrations/services/ValidationService.js +1 -1
  61. package/dist/migrations/transfer.d.ts +8 -4
  62. package/dist/migrations/transfer.js +106 -55
  63. package/dist/migrations/yaml/YamlImportConfigLoader.js +1 -1
  64. package/dist/migrations/yaml/YamlImportIntegration.js +2 -2
  65. package/dist/migrations/yaml/generateImportSchemas.js +1 -1
  66. package/dist/setupCommands.d.ts +1 -1
  67. package/dist/setupCommands.js +5 -6
  68. package/dist/setupController.js +1 -1
  69. package/dist/shared/backupTracking.d.ts +1 -1
  70. package/dist/shared/backupTracking.js +2 -2
  71. package/dist/shared/confirmationDialogs.js +1 -1
  72. package/dist/shared/migrationHelpers.d.ts +1 -1
  73. package/dist/shared/migrationHelpers.js +3 -3
  74. package/dist/shared/operationQueue.d.ts +1 -1
  75. package/dist/shared/operationQueue.js +2 -3
  76. package/dist/shared/operationsTable.d.ts +1 -1
  77. package/dist/shared/operationsTable.js +2 -2
  78. package/dist/shared/progressManager.js +1 -1
  79. package/dist/shared/selectionDialogs.js +9 -8
  80. package/dist/storage/methods.js +4 -4
  81. package/dist/storage/schemas.d.ts +386 -2
  82. package/dist/tables/indexManager.d.ts +65 -0
  83. package/dist/tables/indexManager.js +294 -0
  84. package/dist/types.d.ts +2 -2
  85. package/dist/types.js +1 -1
  86. package/dist/users/methods.js +2 -3
  87. package/dist/utils/configMigration.js +1 -1
  88. package/dist/utils/index.d.ts +1 -1
  89. package/dist/utils/index.js +1 -1
  90. package/dist/utils/loadConfigs.d.ts +2 -2
  91. package/dist/utils/loadConfigs.js +6 -7
  92. package/dist/utils/setupFiles.js +5 -7
  93. package/dist/utilsController.d.ts +15 -8
  94. package/dist/utilsController.js +57 -28
  95. package/package.json +8 -4
  96. package/src/adapters/index.ts +8 -34
  97. package/src/backups/operations/bucketBackup.ts +2 -2
  98. package/src/backups/operations/collectionBackup.ts +4 -4
  99. package/src/backups/operations/comprehensiveBackup.ts +3 -3
  100. package/src/backups/tracking/centralizedTracking.ts +3 -3
  101. package/src/cli/commands/configCommands.ts +72 -8
  102. package/src/cli/commands/databaseCommands.ts +161 -9
  103. package/src/cli/commands/functionCommands.ts +4 -3
  104. package/src/cli/commands/importFileCommands.ts +815 -0
  105. package/src/cli/commands/schemaCommands.ts +3 -3
  106. package/src/cli/commands/storageCommands.ts +2 -3
  107. package/src/cli/commands/transferCommands.ts +3 -6
  108. package/src/collections/attributes.ts +155 -39
  109. package/src/collections/indexes.ts +5 -7
  110. package/src/collections/methods.ts +115 -150
  111. package/src/collections/tableOperations.ts +92 -21
  112. package/src/collections/transferOperations.ts +4 -5
  113. package/src/collections/wipeOperations.ts +154 -51
  114. package/src/databases/methods.ts +2 -2
  115. package/src/databases/setup.ts +2 -2
  116. package/src/examples/yamlTerminologyExample.ts +2 -2
  117. package/src/functions/deployments.ts +6 -5
  118. package/src/functions/fnConfigDiscovery.ts +2 -2
  119. package/src/functions/methods.ts +19 -6
  120. package/src/init.ts +1 -1
  121. package/src/interactiveCLI.ts +78 -13
  122. package/src/main.ts +143 -287
  123. package/src/migrations/afterImportActions.ts +2 -3
  124. package/src/migrations/appwriteToX.ts +12 -8
  125. package/src/migrations/comprehensiveTransfer.ts +6 -6
  126. package/src/migrations/dataLoader.ts +2 -5
  127. package/src/migrations/importController.ts +3 -4
  128. package/src/migrations/importDataActions.ts +3 -3
  129. package/src/migrations/relationships.ts +1 -2
  130. package/src/migrations/services/DataTransformationService.ts +2 -2
  131. package/src/migrations/services/FileHandlerService.ts +1 -1
  132. package/src/migrations/services/ImportOrchestrator.ts +4 -4
  133. package/src/migrations/services/RateLimitManager.ts +1 -1
  134. package/src/migrations/services/RelationshipResolver.ts +1 -1
  135. package/src/migrations/services/UserMappingService.ts +1 -1
  136. package/src/migrations/services/ValidationService.ts +1 -1
  137. package/src/migrations/transfer.ts +126 -83
  138. package/src/migrations/yaml/YamlImportConfigLoader.ts +1 -1
  139. package/src/migrations/yaml/YamlImportIntegration.ts +2 -2
  140. package/src/migrations/yaml/generateImportSchemas.ts +1 -1
  141. package/src/setupCommands.ts +5 -6
  142. package/src/setupController.ts +1 -1
  143. package/src/shared/backupTracking.ts +3 -3
  144. package/src/shared/confirmationDialogs.ts +1 -1
  145. package/src/shared/migrationHelpers.ts +4 -4
  146. package/src/shared/operationQueue.ts +3 -4
  147. package/src/shared/operationsTable.ts +3 -3
  148. package/src/shared/progressManager.ts +1 -1
  149. package/src/shared/selectionDialogs.ts +9 -8
  150. package/src/storage/methods.ts +4 -4
  151. package/src/tables/indexManager.ts +409 -0
  152. package/src/types.ts +2 -2
  153. package/src/users/methods.ts +2 -3
  154. package/src/utils/configMigration.ts +1 -1
  155. package/src/utils/index.ts +1 -1
  156. package/src/utils/loadConfigs.ts +15 -7
  157. package/src/utils/setupFiles.ts +5 -7
  158. package/src/utilsController.ts +86 -32
  159. package/dist/adapters/AdapterFactory.d.ts +0 -94
  160. package/dist/adapters/AdapterFactory.js +0 -405
  161. package/dist/adapters/DatabaseAdapter.d.ts +0 -233
  162. package/dist/adapters/DatabaseAdapter.js +0 -50
  163. package/dist/adapters/LegacyAdapter.d.ts +0 -50
  164. package/dist/adapters/LegacyAdapter.js +0 -612
  165. package/dist/adapters/TablesDBAdapter.d.ts +0 -45
  166. package/dist/adapters/TablesDBAdapter.js +0 -571
  167. package/dist/config/ConfigManager.d.ts +0 -445
  168. package/dist/config/ConfigManager.js +0 -625
  169. package/dist/config/configMigration.d.ts +0 -87
  170. package/dist/config/configMigration.js +0 -390
  171. package/dist/config/configValidation.d.ts +0 -66
  172. package/dist/config/configValidation.js +0 -358
  173. package/dist/config/index.d.ts +0 -8
  174. package/dist/config/index.js +0 -7
  175. package/dist/config/services/ConfigDiscoveryService.d.ts +0 -126
  176. package/dist/config/services/ConfigDiscoveryService.js +0 -374
  177. package/dist/config/services/ConfigLoaderService.d.ts +0 -129
  178. package/dist/config/services/ConfigLoaderService.js +0 -540
  179. package/dist/config/services/ConfigMergeService.d.ts +0 -208
  180. package/dist/config/services/ConfigMergeService.js +0 -308
  181. package/dist/config/services/ConfigValidationService.d.ts +0 -214
  182. package/dist/config/services/ConfigValidationService.js +0 -310
  183. package/dist/config/services/SessionAuthService.d.ts +0 -225
  184. package/dist/config/services/SessionAuthService.js +0 -456
  185. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +0 -1
  186. package/dist/config/services/__tests__/ConfigMergeService.test.js +0 -271
  187. package/dist/config/services/index.d.ts +0 -13
  188. package/dist/config/services/index.js +0 -10
  189. package/dist/config/yamlConfig.d.ts +0 -722
  190. package/dist/config/yamlConfig.js +0 -702
  191. package/dist/functions/pathResolution.d.ts +0 -37
  192. package/dist/functions/pathResolution.js +0 -185
  193. package/dist/shared/attributeMapper.d.ts +0 -20
  194. package/dist/shared/attributeMapper.js +0 -203
  195. package/dist/shared/errorUtils.d.ts +0 -54
  196. package/dist/shared/errorUtils.js +0 -95
  197. package/dist/shared/functionManager.d.ts +0 -48
  198. package/dist/shared/functionManager.js +0 -336
  199. package/dist/shared/indexManager.d.ts +0 -24
  200. package/dist/shared/indexManager.js +0 -151
  201. package/dist/shared/jsonSchemaGenerator.d.ts +0 -50
  202. package/dist/shared/jsonSchemaGenerator.js +0 -290
  203. package/dist/shared/logging.d.ts +0 -61
  204. package/dist/shared/logging.js +0 -116
  205. package/dist/shared/messageFormatter.d.ts +0 -39
  206. package/dist/shared/messageFormatter.js +0 -162
  207. package/dist/shared/pydanticModelGenerator.d.ts +0 -17
  208. package/dist/shared/pydanticModelGenerator.js +0 -615
  209. package/dist/shared/schemaGenerator.d.ts +0 -40
  210. package/dist/shared/schemaGenerator.js +0 -556
  211. package/dist/utils/ClientFactory.d.ts +0 -87
  212. package/dist/utils/ClientFactory.js +0 -212
  213. package/dist/utils/configDiscovery.d.ts +0 -78
  214. package/dist/utils/configDiscovery.js +0 -472
  215. package/dist/utils/constantsGenerator.d.ts +0 -31
  216. package/dist/utils/constantsGenerator.js +0 -321
  217. package/dist/utils/dataConverters.d.ts +0 -46
  218. package/dist/utils/dataConverters.js +0 -139
  219. package/dist/utils/directoryUtils.d.ts +0 -22
  220. package/dist/utils/directoryUtils.js +0 -59
  221. package/dist/utils/getClientFromConfig.d.ts +0 -39
  222. package/dist/utils/getClientFromConfig.js +0 -199
  223. package/dist/utils/helperFunctions.d.ts +0 -63
  224. package/dist/utils/helperFunctions.js +0 -156
  225. package/dist/utils/pathResolvers.d.ts +0 -53
  226. package/dist/utils/pathResolvers.js +0 -72
  227. package/dist/utils/projectConfig.d.ts +0 -119
  228. package/dist/utils/projectConfig.js +0 -171
  229. package/dist/utils/retryFailedPromises.d.ts +0 -2
  230. package/dist/utils/retryFailedPromises.js +0 -23
  231. package/dist/utils/sessionAuth.d.ts +0 -48
  232. package/dist/utils/sessionAuth.js +0 -164
  233. package/dist/utils/typeGuards.d.ts +0 -35
  234. package/dist/utils/typeGuards.js +0 -57
  235. package/dist/utils/validationRules.d.ts +0 -43
  236. package/dist/utils/validationRules.js +0 -42
  237. package/dist/utils/versionDetection.d.ts +0 -58
  238. package/dist/utils/versionDetection.js +0 -251
  239. package/dist/utils/yamlConverter.d.ts +0 -100
  240. package/dist/utils/yamlConverter.js +0 -428
  241. package/dist/utils/yamlLoader.d.ts +0 -70
  242. package/dist/utils/yamlLoader.js +0 -267
  243. package/src/adapters/AdapterFactory.ts +0 -510
  244. package/src/adapters/DatabaseAdapter.ts +0 -306
  245. package/src/adapters/LegacyAdapter.ts +0 -841
  246. package/src/adapters/TablesDBAdapter.ts +0 -773
  247. package/src/config/ConfigManager.ts +0 -808
  248. package/src/config/README.md +0 -274
  249. package/src/config/configMigration.ts +0 -575
  250. package/src/config/configValidation.ts +0 -445
  251. package/src/config/index.ts +0 -10
  252. package/src/config/services/ConfigDiscoveryService.ts +0 -463
  253. package/src/config/services/ConfigLoaderService.ts +0 -740
  254. package/src/config/services/ConfigMergeService.ts +0 -388
  255. package/src/config/services/ConfigValidationService.ts +0 -394
  256. package/src/config/services/SessionAuthService.ts +0 -565
  257. package/src/config/services/__tests__/ConfigMergeService.test.ts +0 -351
  258. package/src/config/services/index.ts +0 -29
  259. package/src/config/yamlConfig.ts +0 -761
  260. package/src/functions/pathResolution.ts +0 -227
  261. package/src/shared/attributeMapper.ts +0 -229
  262. package/src/shared/errorUtils.ts +0 -110
  263. package/src/shared/functionManager.ts +0 -525
  264. package/src/shared/indexManager.ts +0 -254
  265. package/src/shared/jsonSchemaGenerator.ts +0 -383
  266. package/src/shared/logging.ts +0 -149
  267. package/src/shared/messageFormatter.ts +0 -208
  268. package/src/shared/pydanticModelGenerator.ts +0 -618
  269. package/src/shared/schemaGenerator.ts +0 -644
  270. package/src/utils/ClientFactory.ts +0 -240
  271. package/src/utils/configDiscovery.ts +0 -557
  272. package/src/utils/constantsGenerator.ts +0 -369
  273. package/src/utils/dataConverters.ts +0 -159
  274. package/src/utils/directoryUtils.ts +0 -61
  275. package/src/utils/getClientFromConfig.ts +0 -257
  276. package/src/utils/helperFunctions.ts +0 -228
  277. package/src/utils/pathResolvers.ts +0 -81
  278. package/src/utils/projectConfig.ts +0 -299
  279. package/src/utils/retryFailedPromises.ts +0 -29
  280. package/src/utils/sessionAuth.ts +0 -230
  281. package/src/utils/typeGuards.ts +0 -65
  282. package/src/utils/validationRules.ts +0 -88
  283. package/src/utils/versionDetection.ts +0 -292
  284. package/src/utils/yamlConverter.ts +0 -542
  285. 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 "../../shared/messageFormatter.js";
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 "../../config/yamlConfig.js";
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 "../../shared/messageFormatter.js";
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 "../../utils/getClientFromConfig.js";
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.length > 0
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 "../adapters/DatabaseAdapter.js";
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 "../utils/helperFunctions.js";
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 "../shared/logging.js";
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 "../utils/helperFunctions.js";
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 "../adapters/DatabaseAdapter.js";
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 "../utils/getClientFromConfig.js";
3
- import { nameToIdMapping, processQueue, queuedOperations, clearProcessingState, isCollectionProcessed, markCollectionProcessed } from "../shared/operationQueue.js";
4
- import { logger } from "../shared/logging.js";
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 "../utils/helperFunctions.js";
9
- import { MessageFormatter } from "../shared/messageFormatter.js";
10
- import { isLegacyDatabases } from "../utils/typeGuards.js";
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 rels = (attributes || []).filter((a) => a.type === 'relationship');
292
- if (rels.length > 0) {
293
- for (const attr of rels) {
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
- if (!nameToIdMapping.has(relNameOrId)) {
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 { toCreate: relCreate, toUpdate: relUpdate, unchanged: relUnchanged } = diffTableColumns(existingCols2, rels);
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 (relCreate.length)
319
- parts.push(`➕ ${relCreate.length} (${relCreate.map((a) => a.key).join(', ')})`);
320
- if (relUpdate.length)
321
- parts.push(`🔧 ${relUpdate.length} (${relUpdate.map((a) => a.key).join(', ')})`);
322
- if (relUnchanged.length)
323
- parts.push(`⏭️ ${relUnchanged.length}`);
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
- for (const attr of relUpdate) {
327
- try {
328
- await updateAttr(tableId, attr);
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
- catch (e) {
331
- MessageFormatter.error(`Failed to update relationship ${attr.key}`, e instanceof Error ? e : new Error(String(e)), { prefix: 'Attributes' });
371
+ else {
372
+ MessageFormatter.success(`Processed ${relResults.success.length} relationship${relResults.success.length === 1 ? '' : 's'}`, { prefix: 'Relationships' });
332
373
  }
333
374
  }
334
- for (const attr of relCreate) {
335
- try {
336
- await createAttr(tableId, attr);
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
- ...rels.filter((a) => a.relatedCollection).map((a) => a.key)
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
- // Prefer local config indexes, but fall back to collection's own indexes if no local config exists (TablesDB path)
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
- // Compare with existing indexes and create/update accordingly with status checks
385
- try {
386
- const existingIdxRes = await adapter.listIndexes({ databaseId, tableId });
387
- const existingIdx = existingIdxRes.data || existingIdxRes.indexes || [];
388
- MessageFormatter.debug(`Existing index keys: ${existingIdx.map((i) => i.key).join(', ')}`, undefined, { prefix: 'Indexes' });
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) => col?.key && !desiredKeys.has(col.key))
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) ? 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
  /**