appwrite-utils-cli 0.10.86 → 1.0.2

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 (178) hide show
  1. package/.appwrite/.yaml_schemas/appwrite-config.schema.json +380 -0
  2. package/.appwrite/.yaml_schemas/collection.schema.json +255 -0
  3. package/.appwrite/collections/Categories.yaml +182 -0
  4. package/.appwrite/collections/ExampleCollection.yaml +36 -0
  5. package/.appwrite/collections/Posts.yaml +227 -0
  6. package/.appwrite/collections/Users.yaml +149 -0
  7. package/.appwrite/config.yaml +109 -0
  8. package/.appwrite/import/README.md +148 -0
  9. package/.appwrite/import/categories-import.yaml +129 -0
  10. package/.appwrite/import/posts-import.yaml +208 -0
  11. package/.appwrite/import/users-import.yaml +130 -0
  12. package/.appwrite/importData/categories.json +194 -0
  13. package/.appwrite/importData/posts.json +270 -0
  14. package/.appwrite/importData/users.json +220 -0
  15. package/.appwrite/schemas/categories.json +128 -0
  16. package/.appwrite/schemas/exampleCollection.json +52 -0
  17. package/.appwrite/schemas/posts.json +173 -0
  18. package/.appwrite/schemas/users.json +125 -0
  19. package/README.md +264 -33
  20. package/dist/collections/attributes.js +3 -2
  21. package/dist/collections/methods.js +56 -38
  22. package/dist/config/yamlConfig.d.ts +501 -0
  23. package/dist/config/yamlConfig.js +452 -0
  24. package/dist/databases/setup.d.ts +6 -0
  25. package/dist/databases/setup.js +119 -0
  26. package/dist/functions/methods.d.ts +1 -1
  27. package/dist/functions/methods.js +5 -2
  28. package/dist/functions/openapi.d.ts +4 -0
  29. package/dist/functions/openapi.js +60 -0
  30. package/dist/interactiveCLI.d.ts +5 -0
  31. package/dist/interactiveCLI.js +194 -49
  32. package/dist/main.js +91 -30
  33. package/dist/migrations/afterImportActions.js +2 -2
  34. package/dist/migrations/appwriteToX.d.ts +10 -0
  35. package/dist/migrations/appwriteToX.js +15 -4
  36. package/dist/migrations/backup.d.ts +16 -16
  37. package/dist/migrations/dataLoader.d.ts +83 -1
  38. package/dist/migrations/dataLoader.js +4 -4
  39. package/dist/migrations/importController.js +25 -18
  40. package/dist/migrations/importDataActions.js +2 -2
  41. package/dist/migrations/logging.d.ts +9 -1
  42. package/dist/migrations/logging.js +41 -22
  43. package/dist/migrations/migrationHelper.d.ts +4 -4
  44. package/dist/migrations/relationships.js +1 -1
  45. package/dist/migrations/services/DataTransformationService.d.ts +55 -0
  46. package/dist/migrations/services/DataTransformationService.js +158 -0
  47. package/dist/migrations/services/FileHandlerService.d.ts +75 -0
  48. package/dist/migrations/services/FileHandlerService.js +236 -0
  49. package/dist/migrations/services/ImportOrchestrator.d.ts +97 -0
  50. package/dist/migrations/services/ImportOrchestrator.js +488 -0
  51. package/dist/migrations/services/RateLimitManager.d.ts +138 -0
  52. package/dist/migrations/services/RateLimitManager.js +279 -0
  53. package/dist/migrations/services/RelationshipResolver.d.ts +120 -0
  54. package/dist/migrations/services/RelationshipResolver.js +332 -0
  55. package/dist/migrations/services/UserMappingService.d.ts +109 -0
  56. package/dist/migrations/services/UserMappingService.js +277 -0
  57. package/dist/migrations/services/ValidationService.d.ts +74 -0
  58. package/dist/migrations/services/ValidationService.js +260 -0
  59. package/dist/migrations/transfer.d.ts +0 -6
  60. package/dist/migrations/transfer.js +16 -132
  61. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +384 -0
  62. package/dist/migrations/yaml/YamlImportConfigLoader.js +375 -0
  63. package/dist/migrations/yaml/YamlImportIntegration.d.ts +87 -0
  64. package/dist/migrations/yaml/YamlImportIntegration.js +330 -0
  65. package/dist/migrations/yaml/generateImportSchemas.d.ts +17 -0
  66. package/dist/migrations/yaml/generateImportSchemas.js +575 -0
  67. package/dist/schemas/authUser.d.ts +9 -9
  68. package/dist/shared/attributeManager.d.ts +17 -0
  69. package/dist/shared/attributeManager.js +273 -0
  70. package/dist/shared/confirmationDialogs.d.ts +75 -0
  71. package/dist/shared/confirmationDialogs.js +236 -0
  72. package/dist/shared/functionManager.d.ts +48 -0
  73. package/dist/shared/functionManager.js +322 -0
  74. package/dist/shared/indexManager.d.ts +24 -0
  75. package/dist/shared/indexManager.js +150 -0
  76. package/dist/shared/jsonSchemaGenerator.d.ts +51 -0
  77. package/dist/shared/jsonSchemaGenerator.js +313 -0
  78. package/dist/shared/logging.d.ts +10 -0
  79. package/dist/shared/logging.js +46 -0
  80. package/dist/shared/messageFormatter.d.ts +37 -0
  81. package/dist/shared/messageFormatter.js +152 -0
  82. package/dist/shared/migrationHelpers.d.ts +173 -0
  83. package/dist/shared/migrationHelpers.js +142 -0
  84. package/dist/shared/operationLogger.d.ts +3 -0
  85. package/dist/shared/operationLogger.js +25 -0
  86. package/dist/shared/operationQueue.d.ts +13 -0
  87. package/dist/shared/operationQueue.js +79 -0
  88. package/dist/shared/progressManager.d.ts +62 -0
  89. package/dist/shared/progressManager.js +215 -0
  90. package/dist/shared/schemaGenerator.d.ts +18 -0
  91. package/dist/shared/schemaGenerator.js +523 -0
  92. package/dist/storage/methods.d.ts +3 -1
  93. package/dist/storage/methods.js +144 -55
  94. package/dist/storage/schemas.d.ts +56 -16
  95. package/dist/types.d.ts +2 -2
  96. package/dist/types.js +1 -1
  97. package/dist/users/methods.d.ts +16 -0
  98. package/dist/users/methods.js +276 -0
  99. package/dist/utils/configMigration.d.ts +1 -0
  100. package/dist/utils/configMigration.js +262 -0
  101. package/dist/utils/dataConverters.d.ts +46 -0
  102. package/dist/utils/dataConverters.js +139 -0
  103. package/dist/utils/loadConfigs.d.ts +15 -4
  104. package/dist/utils/loadConfigs.js +379 -51
  105. package/dist/utils/schemaStrings.js +2 -1
  106. package/dist/utils/setupFiles.d.ts +2 -1
  107. package/dist/utils/setupFiles.js +723 -28
  108. package/dist/utils/validationRules.d.ts +43 -0
  109. package/dist/utils/validationRules.js +42 -0
  110. package/dist/utils/yamlConverter.d.ts +48 -0
  111. package/dist/utils/yamlConverter.js +98 -0
  112. package/dist/utilsController.js +65 -43
  113. package/package.json +19 -15
  114. package/src/collections/attributes.ts +3 -2
  115. package/src/collections/methods.ts +85 -51
  116. package/src/config/yamlConfig.ts +488 -0
  117. package/src/{migrations/setupDatabase.ts → databases/setup.ts} +11 -5
  118. package/src/functions/methods.ts +8 -4
  119. package/src/functions/templates/count-docs-in-collection/package.json +25 -0
  120. package/src/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
  121. package/src/functions/templates/typescript-node/package.json +24 -0
  122. package/src/functions/templates/typescript-node/tsconfig.json +28 -0
  123. package/src/functions/templates/uv/README.md +31 -0
  124. package/src/functions/templates/uv/pyproject.toml +29 -0
  125. package/src/interactiveCLI.ts +226 -61
  126. package/src/main.ts +111 -37
  127. package/src/migrations/afterImportActions.ts +2 -2
  128. package/src/migrations/appwriteToX.ts +17 -4
  129. package/src/migrations/dataLoader.ts +4 -4
  130. package/src/migrations/importController.ts +30 -22
  131. package/src/migrations/importDataActions.ts +2 -2
  132. package/src/migrations/relationships.ts +1 -1
  133. package/src/migrations/services/DataTransformationService.ts +196 -0
  134. package/src/migrations/services/FileHandlerService.ts +311 -0
  135. package/src/migrations/services/ImportOrchestrator.ts +669 -0
  136. package/src/migrations/services/RateLimitManager.ts +363 -0
  137. package/src/migrations/services/RelationshipResolver.ts +461 -0
  138. package/src/migrations/services/UserMappingService.ts +345 -0
  139. package/src/migrations/services/ValidationService.ts +349 -0
  140. package/src/migrations/transfer.ts +22 -228
  141. package/src/migrations/yaml/YamlImportConfigLoader.ts +427 -0
  142. package/src/migrations/yaml/YamlImportIntegration.ts +419 -0
  143. package/src/migrations/yaml/generateImportSchemas.ts +589 -0
  144. package/src/shared/attributeManager.ts +429 -0
  145. package/src/shared/confirmationDialogs.ts +327 -0
  146. package/src/shared/functionManager.ts +515 -0
  147. package/src/shared/indexManager.ts +253 -0
  148. package/src/shared/jsonSchemaGenerator.ts +403 -0
  149. package/src/shared/logging.ts +74 -0
  150. package/src/shared/messageFormatter.ts +195 -0
  151. package/src/{migrations/migrationHelper.ts → shared/migrationHelpers.ts} +22 -4
  152. package/src/{migrations/helper.ts → shared/operationLogger.ts} +7 -2
  153. package/src/{migrations/queue.ts → shared/operationQueue.ts} +1 -1
  154. package/src/shared/progressManager.ts +278 -0
  155. package/src/{migrations/schemaStrings.ts → shared/schemaGenerator.ts} +71 -17
  156. package/src/storage/methods.ts +199 -78
  157. package/src/types.ts +2 -2
  158. package/src/{migrations/users.ts → users/methods.ts} +2 -2
  159. package/src/utils/configMigration.ts +349 -0
  160. package/src/utils/loadConfigs.ts +416 -52
  161. package/src/utils/schemaStrings.ts +2 -1
  162. package/src/utils/setupFiles.ts +742 -40
  163. package/src/{migrations → utils}/validationRules.ts +1 -1
  164. package/src/utils/yamlConverter.ts +131 -0
  165. package/src/utilsController.ts +75 -54
  166. package/src/functions/templates/poetry/README.md +0 -30
  167. package/src/functions/templates/poetry/pyproject.toml +0 -16
  168. package/src/migrations/attributes.ts +0 -561
  169. package/src/migrations/backup.ts +0 -205
  170. package/src/migrations/databases.ts +0 -39
  171. package/src/migrations/dbHelpers.ts +0 -92
  172. package/src/migrations/indexes.ts +0 -40
  173. package/src/migrations/logging.ts +0 -29
  174. package/src/migrations/storage.ts +0 -538
  175. /package/src/{migrations → functions}/openapi.ts +0 -0
  176. /package/src/functions/templates/{poetry → uv}/src/__init__.py +0 -0
  177. /package/src/functions/templates/{poetry → uv}/src/index.py +0 -0
  178. /package/src/{migrations/converters.ts → utils/dataConverters.ts} +0 -0
@@ -0,0 +1,429 @@
1
+ import { type Databases, type Models } from "node-appwrite";
2
+ import {
3
+ parseAttribute,
4
+ type Attribute,
5
+ type CollectionCreate,
6
+ } from "appwrite-utils";
7
+ import { nameToIdMapping, type QueuedOperation, enqueueOperation } from "./operationQueue.js";
8
+ import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
9
+ import chalk from "chalk";
10
+ import pLimit from "p-limit";
11
+
12
+ // Concurrency limits for different operations
13
+ const attributeLimit = pLimit(3); // Low limit for attribute operations
14
+ const queryLimit = pLimit(25); // Higher limit for read operations
15
+
16
+ export const attributesSame = (
17
+ databaseAttribute: Attribute,
18
+ configAttribute: Attribute
19
+ ): boolean => {
20
+ const attributesToCheck = [
21
+ "key",
22
+ "type",
23
+ "array",
24
+ "encrypted",
25
+ "required",
26
+ "size",
27
+ "min",
28
+ "max",
29
+ "xdefault",
30
+ "elements",
31
+ "relationType",
32
+ "twoWay",
33
+ "twoWayKey",
34
+ "onDelete",
35
+ "relatedCollection",
36
+ ];
37
+
38
+ return attributesToCheck.every((attr) => {
39
+ // Special handling for min/max values
40
+ if (attr === "min" || attr === "max") {
41
+ const dbValue = databaseAttribute[attr as keyof typeof databaseAttribute];
42
+ const configValue = configAttribute[attr as keyof typeof configAttribute];
43
+
44
+ // Use type-specific default values when comparing
45
+ if (databaseAttribute.type === "integer") {
46
+ const defaultMin = attr === "min" ? -2147483647 : undefined;
47
+ const defaultMax = attr === "max" ? 2147483647 : undefined;
48
+ return (dbValue ?? defaultMin) === (configValue ?? defaultMax);
49
+ }
50
+ if (databaseAttribute.type === "double" || databaseAttribute.type === "float") {
51
+ const defaultMin = attr === "min" ? -2147483647 : undefined;
52
+ const defaultMax = attr === "max" ? 2147483647 : undefined;
53
+ return (dbValue ?? defaultMin) === (configValue ?? defaultMax);
54
+ }
55
+ }
56
+
57
+ // Check if both objects have the attribute
58
+ const dbHasAttr = attr in databaseAttribute;
59
+ const configHasAttr = attr in configAttribute;
60
+
61
+ // If both have the attribute, compare values
62
+ if (dbHasAttr && configHasAttr) {
63
+ const dbValue = databaseAttribute[attr as keyof typeof databaseAttribute];
64
+ const configValue = configAttribute[attr as keyof typeof configAttribute];
65
+
66
+ // Consider undefined and null as equivalent
67
+ if (
68
+ (dbValue === undefined || dbValue === null) &&
69
+ (configValue === undefined || configValue === null)
70
+ ) {
71
+ return true;
72
+ }
73
+
74
+ return dbValue === configValue;
75
+ }
76
+
77
+ // If neither has the attribute, consider it the same
78
+ if (!dbHasAttr && !configHasAttr) {
79
+ return true;
80
+ }
81
+
82
+ // If one has the attribute and the other doesn't, check if it's undefined or null
83
+ if (dbHasAttr && !configHasAttr) {
84
+ const dbValue = databaseAttribute[attr as keyof typeof databaseAttribute];
85
+ return dbValue === undefined || dbValue === null;
86
+ }
87
+
88
+ if (!dbHasAttr && configHasAttr) {
89
+ const configValue = configAttribute[attr as keyof typeof configAttribute];
90
+ return configValue === undefined || configValue === null;
91
+ }
92
+
93
+ // If we reach here, the attributes are different
94
+ return false;
95
+ });
96
+ };
97
+
98
+ export const createOrUpdateAttribute = async (
99
+ db: Databases,
100
+ dbId: string,
101
+ collection: Models.Collection,
102
+ attribute: Attribute,
103
+ options: {
104
+ updateEnabled?: boolean;
105
+ useQueue?: boolean;
106
+ verbose?: boolean;
107
+ } = {}
108
+ ): Promise<void> => {
109
+ const { updateEnabled = true, useQueue = true, verbose = false } = options;
110
+
111
+ let action = "create";
112
+ let foundAttribute: Attribute | undefined;
113
+ let finalAttribute: any = attribute;
114
+
115
+ try {
116
+ const collectionAttr = collection.attributes.find(
117
+ // @ts-expect-error - Appwrite type issues
118
+ (attr) => attr.key === attribute.key
119
+ ) as unknown as any;
120
+ foundAttribute = parseAttribute(collectionAttr);
121
+
122
+ if (verbose) {
123
+ console.log(`Found attribute: ${JSON.stringify(foundAttribute)}`);
124
+ }
125
+ } catch (error) {
126
+ foundAttribute = undefined;
127
+ }
128
+
129
+ if (
130
+ foundAttribute &&
131
+ attributesSame(foundAttribute, attribute) &&
132
+ updateEnabled
133
+ ) {
134
+ if (verbose) {
135
+ console.log(chalk.green(`✓ Attribute ${attribute.key} is up to date`));
136
+ }
137
+ return;
138
+ }
139
+
140
+ if (foundAttribute) {
141
+ action = "update";
142
+ if (verbose) {
143
+ console.log(chalk.yellow(`⚠ Updating attribute ${attribute.key}`));
144
+ }
145
+ } else {
146
+ if (verbose) {
147
+ console.log(chalk.blue(`+ Creating attribute ${attribute.key}`));
148
+ }
149
+ }
150
+
151
+ // Handle relationship attributes with nameToIdMapping
152
+ if (attribute.type === "relationship" && attribute.relatedCollection) {
153
+ const relatedCollectionId = nameToIdMapping.get(attribute.relatedCollection);
154
+ if (relatedCollectionId) {
155
+ finalAttribute = {
156
+ ...attribute,
157
+ relatedCollection: relatedCollectionId,
158
+ };
159
+ }
160
+ }
161
+
162
+ // Handle BigInt values for integer, double and float types
163
+ if (attribute.type === "integer" || attribute.type === "double" || attribute.type === "float") {
164
+ if (typeof finalAttribute.min === "bigint") {
165
+ finalAttribute.min = Number(finalAttribute.min);
166
+ }
167
+ if (typeof finalAttribute.max === "bigint") {
168
+ finalAttribute.max = Number(finalAttribute.max);
169
+ }
170
+ }
171
+
172
+ const queuedOperation: QueuedOperation = {
173
+ type: "attribute",
174
+ collectionId: collection.$id,
175
+ attribute: finalAttribute,
176
+ collection,
177
+ };
178
+
179
+ const executeOperation = async () => {
180
+ await attributeLimit(async () => {
181
+ if (action === "update" && foundAttribute) {
182
+ // Delete existing attribute first
183
+ await tryAwaitWithRetry(async () => {
184
+ await db.deleteAttribute(dbId, collection.$id, attribute.key);
185
+ });
186
+ await delay(250);
187
+ }
188
+
189
+ // Create attribute based on type
190
+ switch (finalAttribute.type) {
191
+ case "string":
192
+ await tryAwaitWithRetry(async () => {
193
+ await db.createStringAttribute(
194
+ dbId,
195
+ collection.$id,
196
+ finalAttribute.key,
197
+ finalAttribute.size,
198
+ finalAttribute.required,
199
+ finalAttribute.xdefault,
200
+ finalAttribute.array,
201
+ finalAttribute.encrypted
202
+ );
203
+ });
204
+ break;
205
+ case "integer":
206
+ await tryAwaitWithRetry(async () => {
207
+ await db.createIntegerAttribute(
208
+ dbId,
209
+ collection.$id,
210
+ finalAttribute.key,
211
+ finalAttribute.required,
212
+ finalAttribute.min,
213
+ finalAttribute.max,
214
+ finalAttribute.xdefault,
215
+ finalAttribute.array
216
+ );
217
+ });
218
+ break;
219
+ case "double":
220
+ case "float": // Backward compatibility
221
+ await tryAwaitWithRetry(async () => {
222
+ await db.createFloatAttribute(
223
+ dbId,
224
+ collection.$id,
225
+ finalAttribute.key,
226
+ finalAttribute.required,
227
+ finalAttribute.min,
228
+ finalAttribute.max,
229
+ finalAttribute.xdefault,
230
+ finalAttribute.array
231
+ );
232
+ });
233
+ break;
234
+ case "boolean":
235
+ await tryAwaitWithRetry(async () => {
236
+ await db.createBooleanAttribute(
237
+ dbId,
238
+ collection.$id,
239
+ finalAttribute.key,
240
+ finalAttribute.required,
241
+ finalAttribute.xdefault,
242
+ finalAttribute.array
243
+ );
244
+ });
245
+ break;
246
+ case "datetime":
247
+ await tryAwaitWithRetry(async () => {
248
+ await db.createDatetimeAttribute(
249
+ dbId,
250
+ collection.$id,
251
+ finalAttribute.key,
252
+ finalAttribute.required,
253
+ finalAttribute.xdefault,
254
+ finalAttribute.array
255
+ );
256
+ });
257
+ break;
258
+ case "email":
259
+ await tryAwaitWithRetry(async () => {
260
+ await db.createEmailAttribute(
261
+ dbId,
262
+ collection.$id,
263
+ finalAttribute.key,
264
+ finalAttribute.required,
265
+ finalAttribute.xdefault,
266
+ finalAttribute.array
267
+ );
268
+ });
269
+ break;
270
+ case "ip":
271
+ await tryAwaitWithRetry(async () => {
272
+ await db.createIpAttribute(
273
+ dbId,
274
+ collection.$id,
275
+ finalAttribute.key,
276
+ finalAttribute.required,
277
+ finalAttribute.xdefault,
278
+ finalAttribute.array
279
+ );
280
+ });
281
+ break;
282
+ case "url":
283
+ await tryAwaitWithRetry(async () => {
284
+ await db.createUrlAttribute(
285
+ dbId,
286
+ collection.$id,
287
+ finalAttribute.key,
288
+ finalAttribute.required,
289
+ finalAttribute.xdefault,
290
+ finalAttribute.array
291
+ );
292
+ });
293
+ break;
294
+ case "enum":
295
+ await tryAwaitWithRetry(async () => {
296
+ await db.createEnumAttribute(
297
+ dbId,
298
+ collection.$id,
299
+ finalAttribute.key,
300
+ finalAttribute.elements,
301
+ finalAttribute.required,
302
+ finalAttribute.xdefault,
303
+ finalAttribute.array
304
+ );
305
+ });
306
+ break;
307
+ case "relationship":
308
+ await tryAwaitWithRetry(async () => {
309
+ await db.createRelationshipAttribute(
310
+ dbId,
311
+ collection.$id,
312
+ finalAttribute.relatedCollection,
313
+ finalAttribute.relationType,
314
+ finalAttribute.twoWay,
315
+ finalAttribute.key,
316
+ finalAttribute.twoWayKey,
317
+ finalAttribute.onDelete
318
+ );
319
+ });
320
+ break;
321
+ default:
322
+ throw new Error(`Unknown attribute type: ${finalAttribute.type}`);
323
+ }
324
+ });
325
+ };
326
+
327
+ if (useQueue) {
328
+ enqueueOperation(queuedOperation);
329
+ } else {
330
+ await executeOperation();
331
+ }
332
+ };
333
+
334
+ export const createUpdateCollectionAttributes = async (
335
+ db: Databases,
336
+ dbId: string,
337
+ collection: Models.Collection,
338
+ collectionConfig: CollectionCreate,
339
+ options: {
340
+ updateEnabled?: boolean;
341
+ useQueue?: boolean;
342
+ verbose?: boolean;
343
+ } = {}
344
+ ): Promise<void> => {
345
+ if (!collectionConfig.attributes) return;
346
+
347
+ const { verbose = false } = options;
348
+
349
+ if (verbose) {
350
+ console.log(chalk.blue(`Processing ${collectionConfig.attributes.length} attributes for collection ${collection.name}`));
351
+ }
352
+
353
+ for (const attribute of collectionConfig.attributes) {
354
+ try {
355
+ await createOrUpdateAttribute(db, dbId, collection, attribute, options);
356
+
357
+ if (verbose) {
358
+ console.log(chalk.green(`✓ Processed attribute ${attribute.key}`));
359
+ }
360
+
361
+ // Add delay between attribute operations to prevent rate limiting
362
+ await delay(250);
363
+ } catch (error) {
364
+ console.error(chalk.red(`❌ Failed to process attribute ${attribute.key}:`), error);
365
+ throw error;
366
+ }
367
+ }
368
+ };
369
+
370
+ export const deleteObsoleteAttributes = async (
371
+ db: Databases,
372
+ dbId: string,
373
+ collection: Models.Collection,
374
+ collectionConfig: CollectionCreate,
375
+ options: {
376
+ useQueue?: boolean;
377
+ verbose?: boolean;
378
+ } = {}
379
+ ): Promise<void> => {
380
+ const { useQueue = true, verbose = false } = options;
381
+
382
+ const configAttributes = collectionConfig.attributes || [];
383
+ const configAttributeKeys = new Set(configAttributes.map(attr => attr.key));
384
+
385
+ // Find attributes that exist in the database but not in the config
386
+ const obsoleteAttributes = collection.attributes.filter(
387
+ // @ts-expect-error - Appwrite type issues
388
+ (attr) => !configAttributeKeys.has(attr.key)
389
+ );
390
+
391
+ if (obsoleteAttributes.length === 0) {
392
+ return;
393
+ }
394
+
395
+ if (verbose) {
396
+ console.log(chalk.yellow(`🗑️ Removing ${obsoleteAttributes.length} obsolete attributes from collection ${collection.name}`));
397
+ }
398
+
399
+ for (const attr of obsoleteAttributes) {
400
+ const queuedOperation: QueuedOperation = {
401
+ type: "attribute",
402
+ collectionId: collection.$id,
403
+ // @ts-expect-error - Appwrite type issues
404
+ attribute: { key: attr.key, type: "delete" } as Attribute,
405
+ collection,
406
+ };
407
+
408
+ const executeOperation = async () => {
409
+ await attributeLimit(() =>
410
+ tryAwaitWithRetry(async () => {
411
+ // @ts-expect-error - Appwrite type issues
412
+ await db.deleteAttribute(dbId, collection.$id, attr.key);
413
+ })
414
+ );
415
+ };
416
+
417
+ if (useQueue) {
418
+ enqueueOperation(queuedOperation);
419
+ } else {
420
+ await executeOperation();
421
+ await delay(250);
422
+ }
423
+
424
+ if (verbose) {
425
+ // @ts-expect-error - Appwrite type issues
426
+ console.log(chalk.gray(`🗑️ Deleted obsolete attribute ${attr.key}`));
427
+ }
428
+ }
429
+ };