@njdamstra/appwrite-utils-cli 1.8.9 → 1.10.0

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 (284) 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 +2 -35
  25. package/dist/collections/indexes.js +1 -3
  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 +55 -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 +63 -9
  44. package/dist/main.js +130 -177
  45. package/dist/migrations/afterImportActions.js +2 -3
  46. package/dist/migrations/appwriteToX.d.ts +1 -1
  47. package/dist/migrations/appwriteToX.js +9 -7
  48. package/dist/migrations/comprehensiveTransfer.js +3 -5
  49. package/dist/migrations/dataLoader.js +2 -5
  50. package/dist/migrations/importController.js +3 -4
  51. package/dist/migrations/importDataActions.js +3 -3
  52. package/dist/migrations/relationships.js +1 -2
  53. package/dist/migrations/services/DataTransformationService.js +2 -2
  54. package/dist/migrations/services/FileHandlerService.js +1 -1
  55. package/dist/migrations/services/ImportOrchestrator.js +4 -4
  56. package/dist/migrations/services/RateLimitManager.js +1 -1
  57. package/dist/migrations/services/RelationshipResolver.js +1 -1
  58. package/dist/migrations/services/UserMappingService.js +1 -1
  59. package/dist/migrations/services/ValidationService.js +1 -1
  60. package/dist/migrations/transfer.d.ts +8 -4
  61. package/dist/migrations/transfer.js +106 -55
  62. package/dist/migrations/yaml/YamlImportConfigLoader.js +1 -1
  63. package/dist/migrations/yaml/YamlImportIntegration.js +2 -2
  64. package/dist/migrations/yaml/generateImportSchemas.js +1 -1
  65. package/dist/setupCommands.d.ts +1 -1
  66. package/dist/setupCommands.js +5 -6
  67. package/dist/setupController.js +1 -1
  68. package/dist/shared/backupTracking.d.ts +1 -1
  69. package/dist/shared/backupTracking.js +2 -2
  70. package/dist/shared/confirmationDialogs.js +1 -1
  71. package/dist/shared/migrationHelpers.d.ts +1 -1
  72. package/dist/shared/migrationHelpers.js +3 -3
  73. package/dist/shared/operationQueue.d.ts +1 -1
  74. package/dist/shared/operationQueue.js +2 -3
  75. package/dist/shared/operationsTable.d.ts +1 -1
  76. package/dist/shared/operationsTable.js +2 -2
  77. package/dist/shared/progressManager.js +1 -1
  78. package/dist/shared/selectionDialogs.js +9 -8
  79. package/dist/storage/methods.js +4 -4
  80. package/dist/storage/schemas.d.ts +2 -2
  81. package/dist/tables/indexManager.d.ts +65 -0
  82. package/dist/tables/indexManager.js +294 -0
  83. package/dist/types.d.ts +2 -2
  84. package/dist/types.js +1 -1
  85. package/dist/users/methods.js +2 -3
  86. package/dist/utils/configMigration.js +1 -1
  87. package/dist/utils/index.d.ts +1 -1
  88. package/dist/utils/index.js +1 -1
  89. package/dist/utils/loadConfigs.d.ts +2 -2
  90. package/dist/utils/loadConfigs.js +6 -7
  91. package/dist/utils/setupFiles.js +5 -7
  92. package/dist/utilsController.d.ts +15 -8
  93. package/dist/utilsController.js +57 -28
  94. package/package.json +7 -3
  95. package/src/adapters/index.ts +8 -34
  96. package/src/backups/operations/bucketBackup.ts +2 -2
  97. package/src/backups/operations/collectionBackup.ts +4 -4
  98. package/src/backups/operations/comprehensiveBackup.ts +3 -3
  99. package/src/backups/tracking/centralizedTracking.ts +3 -3
  100. package/src/cli/commands/configCommands.ts +72 -8
  101. package/src/cli/commands/databaseCommands.ts +161 -9
  102. package/src/cli/commands/functionCommands.ts +4 -3
  103. package/src/cli/commands/importFileCommands.ts +815 -0
  104. package/src/cli/commands/schemaCommands.ts +3 -3
  105. package/src/cli/commands/storageCommands.ts +2 -3
  106. package/src/cli/commands/transferCommands.ts +3 -6
  107. package/src/collections/attributes.ts +3 -39
  108. package/src/collections/indexes.ts +2 -4
  109. package/src/collections/methods.ts +115 -150
  110. package/src/collections/tableOperations.ts +57 -21
  111. package/src/collections/transferOperations.ts +4 -5
  112. package/src/collections/wipeOperations.ts +154 -51
  113. package/src/databases/methods.ts +2 -2
  114. package/src/databases/setup.ts +2 -2
  115. package/src/examples/yamlTerminologyExample.ts +2 -2
  116. package/src/functions/deployments.ts +6 -5
  117. package/src/functions/fnConfigDiscovery.ts +2 -2
  118. package/src/functions/methods.ts +17 -4
  119. package/src/init.ts +1 -1
  120. package/src/interactiveCLI.ts +75 -10
  121. package/src/main.ts +143 -287
  122. package/src/migrations/afterImportActions.ts +2 -3
  123. package/src/migrations/appwriteToX.ts +12 -8
  124. package/src/migrations/comprehensiveTransfer.ts +6 -6
  125. package/src/migrations/dataLoader.ts +2 -5
  126. package/src/migrations/importController.ts +3 -4
  127. package/src/migrations/importDataActions.ts +3 -3
  128. package/src/migrations/relationships.ts +1 -2
  129. package/src/migrations/services/DataTransformationService.ts +2 -2
  130. package/src/migrations/services/FileHandlerService.ts +1 -1
  131. package/src/migrations/services/ImportOrchestrator.ts +4 -4
  132. package/src/migrations/services/RateLimitManager.ts +1 -1
  133. package/src/migrations/services/RelationshipResolver.ts +1 -1
  134. package/src/migrations/services/UserMappingService.ts +1 -1
  135. package/src/migrations/services/ValidationService.ts +1 -1
  136. package/src/migrations/transfer.ts +126 -83
  137. package/src/migrations/yaml/YamlImportConfigLoader.ts +1 -1
  138. package/src/migrations/yaml/YamlImportIntegration.ts +2 -2
  139. package/src/migrations/yaml/generateImportSchemas.ts +1 -1
  140. package/src/setupCommands.ts +5 -6
  141. package/src/setupController.ts +1 -1
  142. package/src/shared/backupTracking.ts +3 -3
  143. package/src/shared/confirmationDialogs.ts +1 -1
  144. package/src/shared/migrationHelpers.ts +4 -4
  145. package/src/shared/operationQueue.ts +3 -4
  146. package/src/shared/operationsTable.ts +3 -3
  147. package/src/shared/progressManager.ts +1 -1
  148. package/src/shared/selectionDialogs.ts +9 -8
  149. package/src/storage/methods.ts +4 -4
  150. package/src/tables/indexManager.ts +409 -0
  151. package/src/types.ts +2 -2
  152. package/src/users/methods.ts +2 -3
  153. package/src/utils/configMigration.ts +1 -1
  154. package/src/utils/index.ts +1 -1
  155. package/src/utils/loadConfigs.ts +15 -7
  156. package/src/utils/setupFiles.ts +5 -7
  157. package/src/utilsController.ts +86 -32
  158. package/dist/adapters/AdapterFactory.d.ts +0 -94
  159. package/dist/adapters/AdapterFactory.js +0 -405
  160. package/dist/adapters/DatabaseAdapter.d.ts +0 -233
  161. package/dist/adapters/DatabaseAdapter.js +0 -50
  162. package/dist/adapters/LegacyAdapter.d.ts +0 -50
  163. package/dist/adapters/LegacyAdapter.js +0 -612
  164. package/dist/adapters/TablesDBAdapter.d.ts +0 -45
  165. package/dist/adapters/TablesDBAdapter.js +0 -571
  166. package/dist/config/ConfigManager.d.ts +0 -445
  167. package/dist/config/ConfigManager.js +0 -625
  168. package/dist/config/configMigration.d.ts +0 -87
  169. package/dist/config/configMigration.js +0 -390
  170. package/dist/config/configValidation.d.ts +0 -66
  171. package/dist/config/configValidation.js +0 -358
  172. package/dist/config/index.d.ts +0 -8
  173. package/dist/config/index.js +0 -7
  174. package/dist/config/services/ConfigDiscoveryService.d.ts +0 -126
  175. package/dist/config/services/ConfigDiscoveryService.js +0 -374
  176. package/dist/config/services/ConfigLoaderService.d.ts +0 -129
  177. package/dist/config/services/ConfigLoaderService.js +0 -540
  178. package/dist/config/services/ConfigMergeService.d.ts +0 -208
  179. package/dist/config/services/ConfigMergeService.js +0 -308
  180. package/dist/config/services/ConfigValidationService.d.ts +0 -214
  181. package/dist/config/services/ConfigValidationService.js +0 -310
  182. package/dist/config/services/SessionAuthService.d.ts +0 -225
  183. package/dist/config/services/SessionAuthService.js +0 -456
  184. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +0 -1
  185. package/dist/config/services/__tests__/ConfigMergeService.test.js +0 -271
  186. package/dist/config/services/index.d.ts +0 -13
  187. package/dist/config/services/index.js +0 -10
  188. package/dist/config/yamlConfig.d.ts +0 -722
  189. package/dist/config/yamlConfig.js +0 -702
  190. package/dist/functions/pathResolution.d.ts +0 -37
  191. package/dist/functions/pathResolution.js +0 -185
  192. package/dist/shared/attributeMapper.d.ts +0 -20
  193. package/dist/shared/attributeMapper.js +0 -203
  194. package/dist/shared/errorUtils.d.ts +0 -54
  195. package/dist/shared/errorUtils.js +0 -95
  196. package/dist/shared/functionManager.d.ts +0 -48
  197. package/dist/shared/functionManager.js +0 -336
  198. package/dist/shared/indexManager.d.ts +0 -24
  199. package/dist/shared/indexManager.js +0 -151
  200. package/dist/shared/jsonSchemaGenerator.d.ts +0 -50
  201. package/dist/shared/jsonSchemaGenerator.js +0 -290
  202. package/dist/shared/logging.d.ts +0 -61
  203. package/dist/shared/logging.js +0 -116
  204. package/dist/shared/messageFormatter.d.ts +0 -39
  205. package/dist/shared/messageFormatter.js +0 -162
  206. package/dist/shared/pydanticModelGenerator.d.ts +0 -17
  207. package/dist/shared/pydanticModelGenerator.js +0 -615
  208. package/dist/shared/schemaGenerator.d.ts +0 -40
  209. package/dist/shared/schemaGenerator.js +0 -556
  210. package/dist/utils/ClientFactory.d.ts +0 -87
  211. package/dist/utils/ClientFactory.js +0 -212
  212. package/dist/utils/configDiscovery.d.ts +0 -78
  213. package/dist/utils/configDiscovery.js +0 -472
  214. package/dist/utils/constantsGenerator.d.ts +0 -31
  215. package/dist/utils/constantsGenerator.js +0 -321
  216. package/dist/utils/dataConverters.d.ts +0 -46
  217. package/dist/utils/dataConverters.js +0 -139
  218. package/dist/utils/directoryUtils.d.ts +0 -22
  219. package/dist/utils/directoryUtils.js +0 -59
  220. package/dist/utils/getClientFromConfig.d.ts +0 -39
  221. package/dist/utils/getClientFromConfig.js +0 -199
  222. package/dist/utils/helperFunctions.d.ts +0 -63
  223. package/dist/utils/helperFunctions.js +0 -156
  224. package/dist/utils/pathResolvers.d.ts +0 -53
  225. package/dist/utils/pathResolvers.js +0 -72
  226. package/dist/utils/projectConfig.d.ts +0 -119
  227. package/dist/utils/projectConfig.js +0 -171
  228. package/dist/utils/retryFailedPromises.d.ts +0 -2
  229. package/dist/utils/retryFailedPromises.js +0 -23
  230. package/dist/utils/sessionAuth.d.ts +0 -48
  231. package/dist/utils/sessionAuth.js +0 -164
  232. package/dist/utils/typeGuards.d.ts +0 -35
  233. package/dist/utils/typeGuards.js +0 -57
  234. package/dist/utils/validationRules.d.ts +0 -43
  235. package/dist/utils/validationRules.js +0 -42
  236. package/dist/utils/versionDetection.d.ts +0 -58
  237. package/dist/utils/versionDetection.js +0 -251
  238. package/dist/utils/yamlConverter.d.ts +0 -100
  239. package/dist/utils/yamlConverter.js +0 -428
  240. package/dist/utils/yamlLoader.d.ts +0 -70
  241. package/dist/utils/yamlLoader.js +0 -267
  242. package/src/adapters/AdapterFactory.ts +0 -510
  243. package/src/adapters/DatabaseAdapter.ts +0 -306
  244. package/src/adapters/LegacyAdapter.ts +0 -841
  245. package/src/adapters/TablesDBAdapter.ts +0 -773
  246. package/src/config/ConfigManager.ts +0 -808
  247. package/src/config/README.md +0 -274
  248. package/src/config/configMigration.ts +0 -575
  249. package/src/config/configValidation.ts +0 -445
  250. package/src/config/index.ts +0 -10
  251. package/src/config/services/ConfigDiscoveryService.ts +0 -463
  252. package/src/config/services/ConfigLoaderService.ts +0 -740
  253. package/src/config/services/ConfigMergeService.ts +0 -388
  254. package/src/config/services/ConfigValidationService.ts +0 -394
  255. package/src/config/services/SessionAuthService.ts +0 -565
  256. package/src/config/services/__tests__/ConfigMergeService.test.ts +0 -351
  257. package/src/config/services/index.ts +0 -29
  258. package/src/config/yamlConfig.ts +0 -761
  259. package/src/functions/pathResolution.ts +0 -227
  260. package/src/shared/attributeMapper.ts +0 -229
  261. package/src/shared/errorUtils.ts +0 -110
  262. package/src/shared/functionManager.ts +0 -525
  263. package/src/shared/indexManager.ts +0 -254
  264. package/src/shared/jsonSchemaGenerator.ts +0 -383
  265. package/src/shared/logging.ts +0 -149
  266. package/src/shared/messageFormatter.ts +0 -208
  267. package/src/shared/pydanticModelGenerator.ts +0 -618
  268. package/src/shared/schemaGenerator.ts +0 -644
  269. package/src/utils/ClientFactory.ts +0 -240
  270. package/src/utils/configDiscovery.ts +0 -557
  271. package/src/utils/constantsGenerator.ts +0 -369
  272. package/src/utils/dataConverters.ts +0 -159
  273. package/src/utils/directoryUtils.ts +0 -61
  274. package/src/utils/getClientFromConfig.ts +0 -257
  275. package/src/utils/helperFunctions.ts +0 -228
  276. package/src/utils/pathResolvers.ts +0 -81
  277. package/src/utils/projectConfig.ts +0 -299
  278. package/src/utils/retryFailedPromises.ts +0 -29
  279. package/src/utils/sessionAuth.ts +0 -230
  280. package/src/utils/typeGuards.ts +0 -65
  281. package/src/utils/validationRules.ts +0 -88
  282. package/src/utils/versionDetection.ts +0 -292
  283. package/src/utils/yamlConverter.ts +0 -542
  284. 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;
@@ -474,12 +472,6 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
474
472
  * Legacy attribute update using type-specific methods
475
473
  */
476
474
  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
475
  const { min: normalizedMin, max: normalizedMax } = normalizeMinMaxValues(attribute);
484
476
  switch (attribute.type) {
485
477
  case "string":
@@ -981,35 +973,10 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
981
973
  // MessageFormatter.info(
982
974
  // `Updating attribute with same key ${attribute.key} but different values`
983
975
  // );
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
976
  finalAttribute = {
1000
977
  ...foundAttribute,
1001
978
  ...attribute,
1002
979
  };
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
980
  action = "update";
1014
981
  }
1015
982
  else if (!updateEnabled &&
@@ -1,8 +1,6 @@
1
1
  import { indexSchema } from "@njdamstra/appwrite-utils";
2
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";
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
  /**