appwrite-utils-cli 1.5.2 → 1.6.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 (233) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +251 -29
  3. package/dist/adapters/AdapterFactory.d.ts +10 -3
  4. package/dist/adapters/AdapterFactory.js +213 -17
  5. package/dist/adapters/TablesDBAdapter.js +60 -17
  6. package/dist/backups/operations/bucketBackup.d.ts +19 -0
  7. package/dist/backups/operations/bucketBackup.js +197 -0
  8. package/dist/backups/operations/collectionBackup.d.ts +30 -0
  9. package/dist/backups/operations/collectionBackup.js +201 -0
  10. package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
  11. package/dist/backups/operations/comprehensiveBackup.js +238 -0
  12. package/dist/backups/schemas/bucketManifest.d.ts +93 -0
  13. package/dist/backups/schemas/bucketManifest.js +33 -0
  14. package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
  15. package/dist/backups/schemas/comprehensiveManifest.js +32 -0
  16. package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
  17. package/dist/backups/tracking/centralizedTracking.js +274 -0
  18. package/dist/cli/commands/configCommands.d.ts +8 -0
  19. package/dist/cli/commands/configCommands.js +160 -0
  20. package/dist/cli/commands/databaseCommands.d.ts +13 -0
  21. package/dist/cli/commands/databaseCommands.js +478 -0
  22. package/dist/cli/commands/functionCommands.d.ts +7 -0
  23. package/dist/cli/commands/functionCommands.js +289 -0
  24. package/dist/cli/commands/schemaCommands.d.ts +7 -0
  25. package/dist/cli/commands/schemaCommands.js +134 -0
  26. package/dist/cli/commands/transferCommands.d.ts +5 -0
  27. package/dist/cli/commands/transferCommands.js +384 -0
  28. package/dist/collections/attributes.d.ts +5 -4
  29. package/dist/collections/attributes.js +539 -246
  30. package/dist/collections/indexes.js +39 -37
  31. package/dist/collections/methods.d.ts +2 -16
  32. package/dist/collections/methods.js +90 -538
  33. package/dist/collections/transferOperations.d.ts +7 -0
  34. package/dist/collections/transferOperations.js +331 -0
  35. package/dist/collections/wipeOperations.d.ts +16 -0
  36. package/dist/collections/wipeOperations.js +328 -0
  37. package/dist/config/configMigration.d.ts +87 -0
  38. package/dist/config/configMigration.js +390 -0
  39. package/dist/config/configValidation.d.ts +66 -0
  40. package/dist/config/configValidation.js +358 -0
  41. package/dist/config/yamlConfig.d.ts +455 -1
  42. package/dist/config/yamlConfig.js +145 -52
  43. package/dist/databases/methods.js +3 -2
  44. package/dist/databases/setup.d.ts +1 -2
  45. package/dist/databases/setup.js +9 -87
  46. package/dist/examples/yamlTerminologyExample.d.ts +42 -0
  47. package/dist/examples/yamlTerminologyExample.js +269 -0
  48. package/dist/functions/deployments.js +11 -10
  49. package/dist/functions/methods.d.ts +1 -1
  50. package/dist/functions/methods.js +5 -4
  51. package/dist/init.js +9 -9
  52. package/dist/interactiveCLI.d.ts +8 -17
  53. package/dist/interactiveCLI.js +181 -1172
  54. package/dist/main.js +364 -21
  55. package/dist/migrations/afterImportActions.js +22 -30
  56. package/dist/migrations/appwriteToX.js +71 -25
  57. package/dist/migrations/dataLoader.js +35 -26
  58. package/dist/migrations/importController.js +29 -30
  59. package/dist/migrations/relationships.js +13 -12
  60. package/dist/migrations/services/ImportOrchestrator.js +16 -19
  61. package/dist/migrations/transfer.js +46 -46
  62. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
  63. package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
  64. package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
  65. package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
  66. package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
  67. package/dist/migrations/yaml/generateImportSchemas.js +736 -7
  68. package/dist/schemas/authUser.d.ts +1 -1
  69. package/dist/setupController.js +3 -2
  70. package/dist/shared/backupMetadataSchema.d.ts +94 -0
  71. package/dist/shared/backupMetadataSchema.js +38 -0
  72. package/dist/shared/backupTracking.d.ts +18 -0
  73. package/dist/shared/backupTracking.js +176 -0
  74. package/dist/shared/confirmationDialogs.js +15 -15
  75. package/dist/shared/errorUtils.d.ts +54 -0
  76. package/dist/shared/errorUtils.js +95 -0
  77. package/dist/shared/functionManager.js +20 -19
  78. package/dist/shared/indexManager.js +12 -11
  79. package/dist/shared/jsonSchemaGenerator.js +10 -26
  80. package/dist/shared/logging.d.ts +51 -0
  81. package/dist/shared/logging.js +70 -0
  82. package/dist/shared/messageFormatter.d.ts +2 -0
  83. package/dist/shared/messageFormatter.js +10 -0
  84. package/dist/shared/migrationHelpers.d.ts +6 -16
  85. package/dist/shared/migrationHelpers.js +24 -21
  86. package/dist/shared/operationLogger.d.ts +8 -1
  87. package/dist/shared/operationLogger.js +11 -24
  88. package/dist/shared/operationQueue.d.ts +28 -1
  89. package/dist/shared/operationQueue.js +268 -66
  90. package/dist/shared/operationsTable.d.ts +26 -0
  91. package/dist/shared/operationsTable.js +286 -0
  92. package/dist/shared/operationsTableSchema.d.ts +48 -0
  93. package/dist/shared/operationsTableSchema.js +35 -0
  94. package/dist/shared/relationshipExtractor.d.ts +56 -0
  95. package/dist/shared/relationshipExtractor.js +138 -0
  96. package/dist/shared/schemaGenerator.d.ts +19 -1
  97. package/dist/shared/schemaGenerator.js +56 -75
  98. package/dist/storage/backupCompression.d.ts +20 -0
  99. package/dist/storage/backupCompression.js +67 -0
  100. package/dist/storage/methods.d.ts +16 -2
  101. package/dist/storage/methods.js +98 -14
  102. package/dist/users/methods.js +9 -8
  103. package/dist/utils/configDiscovery.d.ts +78 -0
  104. package/dist/utils/configDiscovery.js +430 -0
  105. package/dist/utils/directoryUtils.d.ts +22 -0
  106. package/dist/utils/directoryUtils.js +59 -0
  107. package/dist/utils/getClientFromConfig.d.ts +17 -8
  108. package/dist/utils/getClientFromConfig.js +162 -17
  109. package/dist/utils/helperFunctions.d.ts +16 -2
  110. package/dist/utils/helperFunctions.js +19 -5
  111. package/dist/utils/loadConfigs.d.ts +34 -9
  112. package/dist/utils/loadConfigs.js +236 -316
  113. package/dist/utils/pathResolvers.d.ts +53 -0
  114. package/dist/utils/pathResolvers.js +72 -0
  115. package/dist/utils/projectConfig.d.ts +119 -0
  116. package/dist/utils/projectConfig.js +171 -0
  117. package/dist/utils/retryFailedPromises.js +4 -2
  118. package/dist/utils/sessionAuth.d.ts +48 -0
  119. package/dist/utils/sessionAuth.js +164 -0
  120. package/dist/utils/sessionPreservationExample.d.ts +1666 -0
  121. package/dist/utils/sessionPreservationExample.js +101 -0
  122. package/dist/utils/setupFiles.js +301 -41
  123. package/dist/utils/typeGuards.d.ts +35 -0
  124. package/dist/utils/typeGuards.js +57 -0
  125. package/dist/utils/versionDetection.js +145 -9
  126. package/dist/utils/yamlConverter.d.ts +53 -3
  127. package/dist/utils/yamlConverter.js +232 -13
  128. package/dist/utils/yamlLoader.d.ts +70 -0
  129. package/dist/utils/yamlLoader.js +263 -0
  130. package/dist/utilsController.d.ts +36 -3
  131. package/dist/utilsController.js +186 -56
  132. package/package.json +12 -2
  133. package/src/adapters/AdapterFactory.ts +263 -35
  134. package/src/adapters/TablesDBAdapter.ts +225 -36
  135. package/src/backups/operations/bucketBackup.ts +277 -0
  136. package/src/backups/operations/collectionBackup.ts +310 -0
  137. package/src/backups/operations/comprehensiveBackup.ts +342 -0
  138. package/src/backups/schemas/bucketManifest.ts +78 -0
  139. package/src/backups/schemas/comprehensiveManifest.ts +76 -0
  140. package/src/backups/tracking/centralizedTracking.ts +352 -0
  141. package/src/cli/commands/configCommands.ts +194 -0
  142. package/src/cli/commands/databaseCommands.ts +635 -0
  143. package/src/cli/commands/functionCommands.ts +379 -0
  144. package/src/cli/commands/schemaCommands.ts +163 -0
  145. package/src/cli/commands/transferCommands.ts +457 -0
  146. package/src/collections/attributes.ts +900 -621
  147. package/src/collections/attributes.ts.backup +1555 -0
  148. package/src/collections/indexes.ts +116 -114
  149. package/src/collections/methods.ts +295 -968
  150. package/src/collections/transferOperations.ts +516 -0
  151. package/src/collections/wipeOperations.ts +501 -0
  152. package/src/config/README.md +274 -0
  153. package/src/config/configMigration.ts +575 -0
  154. package/src/config/configValidation.ts +445 -0
  155. package/src/config/yamlConfig.ts +168 -55
  156. package/src/databases/methods.ts +3 -2
  157. package/src/databases/setup.ts +11 -138
  158. package/src/examples/yamlTerminologyExample.ts +341 -0
  159. package/src/functions/deployments.ts +14 -12
  160. package/src/functions/methods.ts +11 -11
  161. package/src/functions/templates/hono-typescript/README.md +286 -0
  162. package/src/functions/templates/hono-typescript/package.json +26 -0
  163. package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  164. package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  165. package/src/functions/templates/hono-typescript/src/app.ts +180 -0
  166. package/src/functions/templates/hono-typescript/src/context.ts +103 -0
  167. package/src/functions/templates/hono-typescript/src/index.ts +54 -0
  168. package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  169. package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
  170. package/src/functions/templates/typescript-node/package.json +2 -1
  171. package/src/functions/templates/typescript-node/src/context.ts +103 -0
  172. package/src/functions/templates/typescript-node/src/index.ts +18 -12
  173. package/src/functions/templates/uv/pyproject.toml +1 -0
  174. package/src/functions/templates/uv/src/context.py +125 -0
  175. package/src/functions/templates/uv/src/index.py +35 -5
  176. package/src/init.ts +9 -11
  177. package/src/interactiveCLI.ts +278 -1596
  178. package/src/main.ts +418 -24
  179. package/src/migrations/afterImportActions.ts +71 -44
  180. package/src/migrations/appwriteToX.ts +100 -34
  181. package/src/migrations/dataLoader.ts +48 -34
  182. package/src/migrations/importController.ts +44 -39
  183. package/src/migrations/relationships.ts +28 -18
  184. package/src/migrations/services/ImportOrchestrator.ts +24 -27
  185. package/src/migrations/transfer.ts +159 -121
  186. package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
  187. package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
  188. package/src/migrations/yaml/generateImportSchemas.ts +751 -12
  189. package/src/setupController.ts +3 -2
  190. package/src/shared/backupMetadataSchema.ts +93 -0
  191. package/src/shared/backupTracking.ts +211 -0
  192. package/src/shared/confirmationDialogs.ts +19 -19
  193. package/src/shared/errorUtils.ts +110 -0
  194. package/src/shared/functionManager.ts +21 -20
  195. package/src/shared/indexManager.ts +12 -11
  196. package/src/shared/jsonSchemaGenerator.ts +38 -52
  197. package/src/shared/logging.ts +75 -0
  198. package/src/shared/messageFormatter.ts +14 -1
  199. package/src/shared/migrationHelpers.ts +45 -38
  200. package/src/shared/operationLogger.ts +11 -36
  201. package/src/shared/operationQueue.ts +322 -93
  202. package/src/shared/operationsTable.ts +338 -0
  203. package/src/shared/operationsTableSchema.ts +60 -0
  204. package/src/shared/relationshipExtractor.ts +214 -0
  205. package/src/shared/schemaGenerator.ts +179 -219
  206. package/src/storage/backupCompression.ts +88 -0
  207. package/src/storage/methods.ts +131 -34
  208. package/src/users/methods.ts +11 -9
  209. package/src/utils/configDiscovery.ts +502 -0
  210. package/src/utils/directoryUtils.ts +61 -0
  211. package/src/utils/getClientFromConfig.ts +205 -22
  212. package/src/utils/helperFunctions.ts +23 -5
  213. package/src/utils/loadConfigs.ts +313 -345
  214. package/src/utils/pathResolvers.ts +81 -0
  215. package/src/utils/projectConfig.ts +299 -0
  216. package/src/utils/retryFailedPromises.ts +4 -2
  217. package/src/utils/sessionAuth.ts +230 -0
  218. package/src/utils/setupFiles.ts +322 -54
  219. package/src/utils/typeGuards.ts +65 -0
  220. package/src/utils/versionDetection.ts +218 -64
  221. package/src/utils/yamlConverter.ts +296 -13
  222. package/src/utils/yamlLoader.ts +364 -0
  223. package/src/utilsController.ts +314 -110
  224. package/tests/README.md +497 -0
  225. package/tests/adapters/AdapterFactory.test.ts +277 -0
  226. package/tests/integration/syncOperations.test.ts +463 -0
  227. package/tests/jest.config.js +25 -0
  228. package/tests/migration/configMigration.test.ts +546 -0
  229. package/tests/setup.ts +62 -0
  230. package/tests/testUtils.ts +340 -0
  231. package/tests/utils/loadConfigs.test.ts +350 -0
  232. package/tests/validation/configValidation.test.ts +412 -0
  233. package/src/utils/schemaStrings.ts +0 -517
@@ -3,7 +3,9 @@ import type { Attribute } from "appwrite-utils";
3
3
  import { createOrUpdateAttributeWithStatusCheck } from "../collections/attributes.js";
4
4
  import { fetchAndCacheCollectionByName } from "../collections/methods.js";
5
5
  import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
6
- import chalk from "chalk";
6
+ import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
7
+ import { logger } from "../shared/logging.js";
8
+ import { MessageFormatter } from "../shared/messageFormatter.js";
7
9
 
8
10
  export interface QueuedOperation {
9
11
  type: "attribute";
@@ -12,128 +14,355 @@ export interface QueuedOperation {
12
14
  collection?: Models.Collection;
13
15
  dependencies?: string[];
14
16
  }
17
+
18
+ // Global state management
15
19
  export const queuedOperations: QueuedOperation[] = [];
16
20
  export const nameToIdMapping: Map<string, string> = new Map();
21
+ export const processedCollections: Set<string> = new Set();
22
+ export const processedAttributes: Set<string> = new Set(); // format: "collectionId:attributeKey"
17
23
 
18
24
  export const enqueueOperation = (operation: QueuedOperation) => {
25
+ // Avoid duplicate queue entries for same attribute
26
+ const attributeKey = operation.attribute?.key;
27
+ const collectionId = operation.collectionId;
28
+
29
+ logger.info('Enqueueing operation', {
30
+ type: operation.type,
31
+ attributeKey,
32
+ collectionId,
33
+ dependencies: operation.dependencies,
34
+ queueSizeBefore: queuedOperations.length,
35
+ operation: 'enqueueOperation'
36
+ });
37
+
38
+ if (attributeKey && collectionId) {
39
+ const duplicateIndex = queuedOperations.findIndex(
40
+ (op) => op.collectionId === collectionId && op.attribute?.key === attributeKey
41
+ );
42
+
43
+ if (duplicateIndex !== -1) {
44
+ MessageFormatter.info(`Replacing existing queue entry for attribute: ${attributeKey}`);
45
+ logger.info('Replacing duplicate queue entry', {
46
+ attributeKey,
47
+ collectionId,
48
+ duplicateIndex,
49
+ operation: 'enqueueOperation'
50
+ });
51
+ queuedOperations[duplicateIndex] = operation;
52
+ return;
53
+ }
54
+ }
55
+
19
56
  queuedOperations.push(operation);
57
+ logger.debug('Operation enqueued successfully', {
58
+ attributeKey,
59
+ collectionId,
60
+ queueSizeAfter: queuedOperations.length,
61
+ operation: 'enqueueOperation'
62
+ });
63
+ };
64
+
65
+ /**
66
+ * Clear all caches and processing state - use between operations
67
+ */
68
+ export const clearProcessingState = () => {
69
+ const sizeBefore = {
70
+ collections: processedCollections.size,
71
+ attributes: processedAttributes.size,
72
+ nameMapping: nameToIdMapping.size
73
+ };
74
+
75
+ processedCollections.clear();
76
+ processedAttributes.clear();
77
+ nameToIdMapping.clear();
78
+
79
+ MessageFormatter.success("Cleared processing state caches");
80
+ logger.info('Processing state cleared', {
81
+ sizeBefore,
82
+ operation: 'clearProcessingState'
83
+ });
84
+ };
85
+
86
+ /**
87
+ * Check if a collection has already been fully processed
88
+ */
89
+ export const isCollectionProcessed = (collectionId: string): boolean => {
90
+ return processedCollections.has(collectionId);
91
+ };
92
+
93
+ /**
94
+ * Mark a collection as fully processed
95
+ */
96
+ export const markCollectionProcessed = (collectionId: string, collectionName?: string) => {
97
+ processedCollections.add(collectionId);
98
+
99
+ const logData = {
100
+ collectionId,
101
+ collectionName,
102
+ totalProcessedCollections: processedCollections.size,
103
+ operation: 'markCollectionProcessed'
104
+ };
105
+
106
+ if (collectionName) {
107
+ MessageFormatter.success(`Marked collection '${collectionName}' (${collectionId}) as processed`);
108
+ }
109
+
110
+ logger.info('Collection marked as processed', logData);
111
+ };
112
+
113
+ /**
114
+ * Check if a specific attribute has been processed
115
+ */
116
+ export const isAttributeProcessed = (collectionId: string, attributeKey: string): boolean => {
117
+ return processedAttributes.has(`${collectionId}:${attributeKey}`);
20
118
  };
21
119
 
22
- export const processQueue = async (db: Databases, dbId: string) => {
23
- console.log("---------------------------------");
24
- console.log(`Starting Queue processing of ${dbId}`);
25
- console.log("---------------------------------");
120
+ /**
121
+ * Mark a specific attribute as processed
122
+ */
123
+ export const markAttributeProcessed = (collectionId: string, attributeKey: string) => {
124
+ const identifier = `${collectionId}:${attributeKey}`;
125
+ processedAttributes.add(identifier);
126
+
127
+ logger.debug('Attribute marked as processed', {
128
+ collectionId,
129
+ attributeKey,
130
+ identifier,
131
+ totalProcessedAttributes: processedAttributes.size,
132
+ operation: 'markAttributeProcessed'
133
+ });
134
+ };
135
+
136
+ /**
137
+ * Process only specific attributes in the queue, not entire collections
138
+ * This prevents triggering full collection re-processing cycles
139
+ */
140
+ export const processQueue = async (db: Databases | DatabaseAdapter, dbId: string) => {
141
+ const startTime = Date.now();
142
+
143
+ if (queuedOperations.length === 0) {
144
+ MessageFormatter.info("No queued operations to process");
145
+ logger.info('Queue processing skipped - no operations', {
146
+ dbId,
147
+ operation: 'processQueue'
148
+ });
149
+ return;
150
+ }
151
+
152
+ MessageFormatter.section(`Starting surgical queue processing of ${queuedOperations.length} operations for ${dbId}`);
153
+
154
+ logger.info('Starting queue processing', {
155
+ dbId,
156
+ queueSize: queuedOperations.length,
157
+ operations: queuedOperations.map(op => ({
158
+ type: op.type,
159
+ attributeKey: op.attribute?.key,
160
+ collectionId: op.collectionId,
161
+ dependencies: op.dependencies
162
+ })),
163
+ operation: 'processQueue'
164
+ });
165
+
26
166
  let progress = true;
167
+ let attempts = 0;
168
+ const maxAttempts = 3; // Prevent infinite loops
27
169
 
28
- while (progress) {
170
+ while (progress && attempts < maxAttempts) {
29
171
  progress = false;
30
- console.log("Processing queued operations:");
31
- for (let i = 0; i < queuedOperations.length; i++) {
172
+ attempts++;
173
+
174
+ MessageFormatter.info(`Queue processing attempt ${attempts}/${maxAttempts}`);
175
+
176
+ logger.info('Queue processing attempt started', {
177
+ attempt: attempts,
178
+ maxAttempts,
179
+ remainingOperations: queuedOperations.length,
180
+ dbId,
181
+ operation: 'processQueue'
182
+ });
183
+
184
+ for (let i = queuedOperations.length - 1; i >= 0; i--) {
32
185
  const operation = queuedOperations[i];
33
- let collectionFound: Models.Collection | undefined;
34
-
35
- // Handle relationship attribute operations
36
- if (operation.attribute?.type === "relationship") {
37
- // Attempt to resolve the collection directly if collectionId is specified
38
- if (operation.collectionId) {
39
- console.log(`\tFetching collection by ID: ${operation.collectionId}`);
40
- try {
41
- collectionFound = await tryAwaitWithRetry(
42
- async () => await db.getCollection(dbId, operation.collectionId!)
43
- );
44
- } catch (e) {
45
- console.log(
46
- `\tCollection not found by ID: ${operation.collectionId}`
47
- );
48
- }
49
- }
50
- // Attempt to resolve related collection if specified and not already found
51
- if (!collectionFound && operation.attribute?.relatedCollection) {
52
- // First, try treating relatedCollection as an ID
53
- try {
54
- const relAttr: any = operation.attribute as any;
55
- const byId = await tryAwaitWithRetry(
56
- async () => await db.getCollection(dbId, relAttr.relatedCollection as string)
57
- );
58
- // We still need the target collection (operation.collectionId) to create the attribute on,
59
- // so only use this branch to warm caches/mappings and continue to dependency checks.
60
- // Do not override collectionFound with the related collection.
61
- } catch (_) {
62
- // Not an ID or not found; fall back to name-based cache
63
- }
64
-
65
- // Warm cache by name (used by attribute creation path), but do not use as target collection
66
- const relAttr: any = operation.attribute as any;
67
- await fetchAndCacheCollectionByName(
68
- db,
69
- dbId,
70
- relAttr.relatedCollection
71
- );
72
- }
73
- // Handle dependencies if collection still not found
74
- if (!collectionFound) {
75
- for (const dep of operation.dependencies || []) {
76
- collectionFound = await fetchAndCacheCollectionByName(
77
- db,
78
- dbId,
79
- dep
80
- );
81
- if (collectionFound) break; // Break early if collection is found
82
- }
83
- }
84
- } else if (operation.collectionId) {
85
- // Handle non-relationship operations with a specified collectionId
86
- console.log(`\tFetching collection by ID: ${operation.collectionId}`);
87
- try {
88
- collectionFound = await tryAwaitWithRetry(
89
- async () => await db.getCollection(dbId, operation.collectionId!)
90
- );
91
- } catch (e) {
92
- console.log(
93
- `\tCollection not found by ID: ${operation.collectionId}`
94
- );
186
+
187
+ if (!operation.attribute || !operation.collectionId) {
188
+ MessageFormatter.warning("Invalid operation, removing from queue");
189
+ queuedOperations.splice(i, 1);
190
+ continue;
191
+ }
192
+
193
+ const attributeKey = operation.attribute.key;
194
+ const collectionId = operation.collectionId;
195
+
196
+ // Skip if this specific attribute was already processed
197
+ if (isAttributeProcessed(collectionId, attributeKey)) {
198
+ MessageFormatter.debug(`Attribute '${attributeKey}' already processed, removing from queue`);
199
+ logger.debug('Removing already processed attribute from queue', {
200
+ attributeKey,
201
+ collectionId,
202
+ queueIndex: i,
203
+ operation: 'processQueue'
204
+ });
205
+ queuedOperations.splice(i, 1);
206
+ continue;
207
+ }
208
+
209
+ let targetCollection: Models.Collection | undefined;
210
+
211
+ // Resolve the target collection (where the attribute will be created)
212
+ try {
213
+ targetCollection = await tryAwaitWithRetry(
214
+ async () => {
215
+ if ('getMetadata' in db && typeof db.getMetadata === 'function') {
216
+ // DatabaseAdapter
217
+ return (await (db as DatabaseAdapter).getTable({ databaseId: dbId, tableId: collectionId })).data;
218
+ } else {
219
+ // Legacy Databases
220
+ return await (db as Databases).getCollection(dbId, collectionId);
221
+ }
222
+ }
223
+ );
224
+ } catch (e) {
225
+ const errorMessage = e instanceof Error ? e.message : String(e);
226
+ MessageFormatter.error(`Target collection ${collectionId} not found, removing from queue`);
227
+ logger.error('Target collection not found during queue processing', {
228
+ collectionId,
229
+ attributeKey,
230
+ error: errorMessage,
231
+ operation: 'processQueue'
232
+ });
233
+ queuedOperations.splice(i, 1);
234
+ continue;
235
+ }
236
+
237
+ // For relationship attributes, ensure the related collection exists
238
+ let canProcess = true;
239
+ if (operation.attribute.type === "relationship") {
240
+ const relatedCollection = operation.attribute.relatedCollection;
241
+ if (relatedCollection) {
242
+ // Try to resolve related collection by ID first, then by name
243
+ let relatedFound = false;
244
+
245
+ try {
246
+ await tryAwaitWithRetry(
247
+ async () => {
248
+ if ('getMetadata' in db && typeof db.getMetadata === 'function') {
249
+ // DatabaseAdapter
250
+ return (await (db as DatabaseAdapter).getTable({ databaseId: dbId, tableId: relatedCollection })).data;
251
+ } else {
252
+ // Legacy Databases
253
+ return await (db as Databases).getCollection(dbId, relatedCollection);
254
+ }
255
+ }
256
+ );
257
+ relatedFound = true;
258
+ nameToIdMapping.set(relatedCollection, relatedCollection); // Cache the ID mapping
259
+ } catch (_) {
260
+ // Try by name lookup
261
+ const cachedId = nameToIdMapping.get(relatedCollection);
262
+ if (cachedId) {
263
+ try {
264
+ await tryAwaitWithRetry(
265
+ async () => {
266
+ if ('getMetadata' in db && typeof db.getMetadata === 'function') {
267
+ // DatabaseAdapter
268
+ return (await (db as DatabaseAdapter).getTable({ databaseId: dbId, tableId: cachedId })).data;
269
+ } else {
270
+ // Legacy Databases
271
+ return await (db as Databases).getCollection(dbId, cachedId);
272
+ }
273
+ }
274
+ );
275
+ relatedFound = true;
276
+ } catch (_) {
277
+ nameToIdMapping.delete(relatedCollection); // Remove stale cache
278
+ }
279
+ }
280
+
281
+ if (!relatedFound) {
282
+ // Final attempt: search by name
283
+ try {
284
+ const collections = 'getMetadata' in db && typeof db.getMetadata === 'function'
285
+ ? await (db as DatabaseAdapter).listTables({ databaseId: dbId, queries: [Query.equal("name", relatedCollection)] })
286
+ : await (db as Databases).listCollections(dbId, [Query.equal("name", relatedCollection)]);
287
+
288
+ if (collections.total && collections.total > 0) {
289
+ const firstCollection = 'getMetadata' in db && typeof db.getMetadata === 'function'
290
+ ? (collections as any).tables?.[0]
291
+ : (collections as any).collections?.[0];
292
+ nameToIdMapping.set(relatedCollection, firstCollection.$id);
293
+ relatedFound = true;
294
+ }
295
+ } catch (_) {
296
+ // Related collection truly doesn't exist yet
297
+ }
298
+ }
299
+ }
300
+
301
+ if (!relatedFound) {
302
+ MessageFormatter.warning(
303
+ `Related collection '${relatedCollection}' not ready for attribute '${attributeKey}', keeping in queue`
304
+ );
305
+ canProcess = false;
306
+ }
95
307
  }
96
308
  }
97
309
 
98
- // Process the operation if the collection is found
99
- if (collectionFound && operation.attribute) {
100
- console.log(chalk.cyan(
101
- `\t📋 Queue processing relationship attribute: ${operation.attribute.key} for collection: ${collectionFound.name}`
102
- ));
310
+ if (canProcess && targetCollection) {
311
+ MessageFormatter.progress(
312
+ `Processing queued ${operation.attribute.type} attribute: '${attributeKey}' for collection: '${targetCollection.name}'`
313
+ );
314
+
103
315
  const success = await createOrUpdateAttributeWithStatusCheck(
104
316
  db,
105
317
  dbId,
106
- collectionFound,
318
+ targetCollection,
107
319
  operation.attribute
108
320
  );
109
-
321
+
110
322
  if (success) {
111
- console.log(chalk.green(`\t✅ Successfully processed queued attribute: ${operation.attribute.key}`));
323
+ MessageFormatter.success(`Successfully processed queued attribute: '${attributeKey}'`);
324
+ logger.info('Queued attribute processed successfully', {
325
+ attributeKey,
326
+ collectionId,
327
+ targetCollectionName: targetCollection.name,
328
+ operation: 'processQueue'
329
+ });
330
+ markAttributeProcessed(collectionId, attributeKey);
112
331
  queuedOperations.splice(i, 1);
113
- i--; // Adjust index since we're modifying the array
114
332
  progress = true;
115
333
  } else {
116
- console.log(chalk.red(`\t❌ Failed to process queued attribute: ${operation.attribute.key}, removing from queue`));
334
+ MessageFormatter.error(`Failed to process queued attribute: '${attributeKey}', removing from queue`);
335
+ logger.error('Failed to process queued attribute', {
336
+ attributeKey,
337
+ collectionId,
338
+ targetCollectionName: targetCollection.name,
339
+ operation: 'processQueue'
340
+ });
117
341
  queuedOperations.splice(i, 1);
118
- i--; // Adjust index since we're modifying the array
119
342
  }
120
- } else {
121
- console.log(chalk.yellow(
122
- `\t⚠️ Collection not found for queued operation, removing from queue: ${operation.attribute?.key || 'unknown'}`
123
- ));
124
- queuedOperations.splice(i, 1);
125
- i--; // Adjust index since we're modifying the array
126
343
  }
127
344
  }
128
- console.log(`\tFinished processing queued operations`);
345
+
346
+ if (queuedOperations.length === 0) {
347
+ break;
348
+ }
349
+
350
+ MessageFormatter.info(`Remaining operations after attempt ${attempts}: ${queuedOperations.length}`);
129
351
  }
130
352
 
131
353
  if (queuedOperations.length > 0) {
132
- console.error("Unresolved operations remain due to unmet dependencies.");
133
- console.log(queuedOperations);
354
+ MessageFormatter.warning(
355
+ `${queuedOperations.length} operations remain unresolved after ${maxAttempts} attempts:`
356
+ );
357
+ queuedOperations.forEach((op, index) => {
358
+ MessageFormatter.warning(
359
+ ` ${index + 1}. ${op.attribute?.type} attribute '${op.attribute?.key}' for collection ${op.collectionId}`
360
+ );
361
+ });
362
+ MessageFormatter.warning("These may have unmet dependencies or require manual intervention");
363
+ } else {
364
+ MessageFormatter.success("All queued operations processed successfully");
134
365
  }
135
366
 
136
- console.log("---------------------------------");
137
- console.log(`Queue processing complete for ${dbId}`);
138
- console.log("---------------------------------");
367
+ MessageFormatter.section(`Surgical queue processing complete for ${dbId}`);
139
368
  };