appwrite-utils-cli 1.6.2 → 1.6.4

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 (62) hide show
  1. package/CONFIG_TODO.md +1189 -0
  2. package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
  3. package/dist/cli/commands/configCommands.js +7 -1
  4. package/dist/cli/commands/databaseCommands.js +23 -15
  5. package/dist/collections/attributes.d.ts +1 -1
  6. package/dist/collections/attributes.js +163 -66
  7. package/dist/collections/indexes.js +3 -17
  8. package/dist/collections/methods.js +38 -0
  9. package/dist/config/ConfigManager.d.ts +445 -0
  10. package/dist/config/ConfigManager.js +625 -0
  11. package/dist/config/index.d.ts +8 -0
  12. package/dist/config/index.js +7 -0
  13. package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
  14. package/dist/config/services/ConfigDiscoveryService.js +374 -0
  15. package/dist/config/services/ConfigLoaderService.d.ts +105 -0
  16. package/dist/config/services/ConfigLoaderService.js +410 -0
  17. package/dist/config/services/ConfigMergeService.d.ts +208 -0
  18. package/dist/config/services/ConfigMergeService.js +307 -0
  19. package/dist/config/services/ConfigValidationService.d.ts +214 -0
  20. package/dist/config/services/ConfigValidationService.js +310 -0
  21. package/dist/config/services/SessionAuthService.d.ts +225 -0
  22. package/dist/config/services/SessionAuthService.js +456 -0
  23. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
  24. package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
  25. package/dist/config/services/index.d.ts +13 -0
  26. package/dist/config/services/index.js +10 -0
  27. package/dist/interactiveCLI.js +8 -6
  28. package/dist/main.js +2 -2
  29. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
  30. package/dist/shared/operationQueue.js +1 -1
  31. package/dist/utils/ClientFactory.d.ts +87 -0
  32. package/dist/utils/ClientFactory.js +164 -0
  33. package/dist/utils/getClientFromConfig.js +4 -3
  34. package/dist/utils/helperFunctions.d.ts +1 -0
  35. package/dist/utils/helperFunctions.js +21 -5
  36. package/dist/utils/yamlConverter.d.ts +2 -0
  37. package/dist/utils/yamlConverter.js +21 -4
  38. package/dist/utilsController.d.ts +18 -15
  39. package/dist/utilsController.js +83 -131
  40. package/package.json +1 -1
  41. package/src/cli/commands/configCommands.ts +8 -1
  42. package/src/cli/commands/databaseCommands.ts +34 -20
  43. package/src/collections/attributes.ts +195 -150
  44. package/src/collections/indexes.ts +4 -19
  45. package/src/collections/methods.ts +46 -0
  46. package/src/config/ConfigManager.ts +808 -0
  47. package/src/config/index.ts +10 -0
  48. package/src/config/services/ConfigDiscoveryService.ts +463 -0
  49. package/src/config/services/ConfigLoaderService.ts +560 -0
  50. package/src/config/services/ConfigMergeService.ts +386 -0
  51. package/src/config/services/ConfigValidationService.ts +394 -0
  52. package/src/config/services/SessionAuthService.ts +565 -0
  53. package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
  54. package/src/config/services/index.ts +29 -0
  55. package/src/interactiveCLI.ts +9 -7
  56. package/src/main.ts +2 -2
  57. package/src/shared/operationQueue.ts +1 -1
  58. package/src/utils/ClientFactory.ts +186 -0
  59. package/src/utils/getClientFromConfig.ts +4 -3
  60. package/src/utils/helperFunctions.ts +27 -7
  61. package/src/utils/yamlConverter.ts +28 -2
  62. package/src/utilsController.ts +99 -187
@@ -17,8 +17,8 @@ import { logger } from "../shared/logging.js";
17
17
  import { MessageFormatter } from "../shared/messageFormatter.js";
18
18
  import { isDatabaseAdapter } from "../utils/typeGuards.js";
19
19
 
20
- // Threshold for treating min/max values as undefined (10 billion)
21
- const MIN_MAX_THRESHOLD = 10_000_000_000;
20
+ // Threshold for treating min/max values as undefined (1 trillion)
21
+ const MIN_MAX_THRESHOLD = 1_000_000_000_000;
22
22
 
23
23
  // Extreme values that Appwrite may return, which should be treated as undefined
24
24
  const EXTREME_MIN_INTEGER = -9223372036854776000;
@@ -142,12 +142,22 @@ const normalizeMinMaxValues = (attribute: Attribute): { min?: number; max?: numb
142
142
  * This is used when comparing database attributes with config attributes
143
143
  */
144
144
  const normalizeAttributeForComparison = (attribute: Attribute): Attribute => {
145
- if (!hasMinMaxProperties(attribute)) {
146
- return attribute;
145
+ const normalized: any = { ...attribute };
146
+
147
+ // Normalize min/max for numeric types
148
+ if (hasMinMaxProperties(attribute)) {
149
+ const { min, max } = normalizeMinMaxValues(attribute);
150
+ normalized.min = min;
151
+ normalized.max = max;
147
152
  }
148
153
 
149
- const { min, max } = normalizeMinMaxValues(attribute);
150
- return { ...(attribute as any), min, max };
154
+ // Remove xdefault if null/undefined to ensure consistent comparison
155
+ // Appwrite sets xdefault: null for required attributes, but config files omit it
156
+ if ('xdefault' in normalized && (normalized.xdefault === null || normalized.xdefault === undefined)) {
157
+ delete normalized.xdefault;
158
+ }
159
+
160
+ return normalized;
151
161
  };
152
162
 
153
163
  /**
@@ -434,7 +444,7 @@ const updateLegacyAttribute = async (
434
444
  collectionId,
435
445
  attribute.key,
436
446
  attribute.required || false,
437
- (attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
447
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null,
438
448
  attribute.size
439
449
  );
440
450
  break;
@@ -444,7 +454,7 @@ const updateLegacyAttribute = async (
444
454
  collectionId,
445
455
  attribute.key,
446
456
  attribute.required || false,
447
- (attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
457
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null,
448
458
  normalizedMin !== undefined ? parseInt(String(normalizedMin)) : undefined,
449
459
  normalizedMax !== undefined ? parseInt(String(normalizedMax)) : undefined
450
460
  );
@@ -458,7 +468,7 @@ const updateLegacyAttribute = async (
458
468
  attribute.required || false,
459
469
  normalizedMin !== undefined ? Number(normalizedMin) : undefined,
460
470
  normalizedMax !== undefined ? Number(normalizedMax) : undefined,
461
- attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
471
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null
462
472
  );
463
473
  break;
464
474
  case "boolean":
@@ -467,7 +477,7 @@ const updateLegacyAttribute = async (
467
477
  collectionId,
468
478
  attribute.key,
469
479
  attribute.required || false,
470
- attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
480
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null
471
481
  );
472
482
  break;
473
483
  case "datetime":
@@ -476,7 +486,7 @@ const updateLegacyAttribute = async (
476
486
  collectionId,
477
487
  attribute.key,
478
488
  attribute.required || false,
479
- attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
489
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null
480
490
  );
481
491
  break;
482
492
  case "email":
@@ -485,7 +495,7 @@ const updateLegacyAttribute = async (
485
495
  collectionId,
486
496
  attribute.key,
487
497
  attribute.required || false,
488
- attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
498
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null
489
499
  );
490
500
  break;
491
501
  case "ip":
@@ -494,7 +504,7 @@ const updateLegacyAttribute = async (
494
504
  collectionId,
495
505
  attribute.key,
496
506
  attribute.required || false,
497
- attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
507
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null
498
508
  );
499
509
  break;
500
510
  case "url":
@@ -503,7 +513,7 @@ const updateLegacyAttribute = async (
503
513
  collectionId,
504
514
  attribute.key,
505
515
  attribute.required || false,
506
- attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
516
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null
507
517
  );
508
518
  break;
509
519
  case "enum":
@@ -513,7 +523,7 @@ const updateLegacyAttribute = async (
513
523
  attribute.key,
514
524
  (attribute as any).elements || [],
515
525
  attribute.required || false,
516
- attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
526
+ !attribute.required && (attribute as any).xdefault !== undefined ? (attribute as any).xdefault : null
517
527
  );
518
528
  break;
519
529
  case "relationship":
@@ -569,18 +579,7 @@ const waitForAttributeAvailable = async (
569
579
  // Calculate exponential backoff: 2s, 4s, 8s, 16s, 30s (capped at 30s)
570
580
  if (retryCount > 0) {
571
581
  const exponentialDelay = calculateExponentialBackoff(retryCount);
572
- MessageFormatter.info(
573
- chalk.blue(
574
- `Waiting for attribute '${attributeKey}' to become available (retry ${retryCount}, backoff: ${exponentialDelay}ms)...`
575
- )
576
- );
577
582
  await delay(exponentialDelay);
578
- } else {
579
- MessageFormatter.info(
580
- chalk.blue(
581
- `Waiting for attribute '${attributeKey}' to become available...`
582
- )
583
- );
584
583
  }
585
584
 
586
585
  while (Date.now() - startTime < maxWaitTime) {
@@ -597,10 +596,6 @@ const waitForAttributeAvailable = async (
597
596
  return false;
598
597
  }
599
598
 
600
- MessageFormatter.info(
601
- chalk.gray(`Attribute '${attributeKey}' status: ${attribute.status}`)
602
- );
603
-
604
599
  const statusInfo = {
605
600
  attributeKey,
606
601
  status: attribute.status,
@@ -613,27 +608,14 @@ const waitForAttributeAvailable = async (
613
608
 
614
609
  switch (attribute.status) {
615
610
  case "available":
616
- MessageFormatter.info(
617
- chalk.green(`✅ Attribute '${attributeKey}' is now available`)
618
- );
619
611
  logger.info(`Attribute '${attributeKey}' became available`, statusInfo);
620
612
  return true;
621
613
 
622
614
  case "failed":
623
- MessageFormatter.info(
624
- chalk.red(
625
- `❌ Attribute '${attributeKey}' failed: ${attribute.error}`
626
- )
627
- );
628
615
  logger.error(`Attribute '${attributeKey}' failed`, statusInfo);
629
616
  return false;
630
617
 
631
618
  case "stuck":
632
- MessageFormatter.info(
633
- chalk.yellow(
634
- `⚠️ Attribute '${attributeKey}' is stuck, will retry...`
635
- )
636
- );
637
619
  logger.warn(`Attribute '${attributeKey}' is stuck`, statusInfo);
638
620
  return false;
639
621
 
@@ -792,6 +774,45 @@ const deleteAndRecreateCollection = async (
792
774
  }
793
775
  };
794
776
 
777
+ /**
778
+ * Get the fields that should be compared for a specific attribute type
779
+ * Only returns fields that are valid for the given type to avoid false positives
780
+ */
781
+ const getComparableFields = (type: string): string[] => {
782
+ const baseFields = ["key", "type", "array", "required", "xdefault"];
783
+
784
+ switch (type) {
785
+ case "string":
786
+ return [...baseFields, "size", "encrypted"];
787
+
788
+ case "integer":
789
+ case "double":
790
+ case "float":
791
+ return [...baseFields, "min", "max"];
792
+
793
+ case "enum":
794
+ return [...baseFields, "elements"];
795
+
796
+ case "relationship":
797
+ return [...baseFields, "relationType", "twoWay", "twoWayKey", "onDelete", "relatedCollection"];
798
+
799
+ case "boolean":
800
+ case "datetime":
801
+ case "email":
802
+ case "ip":
803
+ case "url":
804
+ return baseFields;
805
+
806
+ default:
807
+ // Fallback to all fields for unknown types
808
+ return [
809
+ "key", "type", "array", "encrypted", "required", "size",
810
+ "min", "max", "xdefault", "elements", "relationType",
811
+ "twoWay", "twoWayKey", "onDelete", "relatedCollection"
812
+ ];
813
+ }
814
+ };
815
+
795
816
  const attributesSame = (
796
817
  databaseAttribute: Attribute,
797
818
  configAttribute: Attribute
@@ -800,25 +821,12 @@ const attributesSame = (
800
821
  const normalizedDbAttr = normalizeAttributeForComparison(databaseAttribute);
801
822
  const normalizedConfigAttr = normalizeAttributeForComparison(configAttribute);
802
823
 
803
- const attributesToCheck = [
804
- "key",
805
- "type",
806
- "array",
807
- "encrypted",
808
- "required",
809
- "size",
810
- "min",
811
- "max",
812
- "xdefault",
813
- "elements",
814
- "relationType",
815
- "twoWay",
816
- "twoWayKey",
817
- "onDelete",
818
- "relatedCollection",
819
- ];
820
-
821
- return attributesToCheck.every((attr) => {
824
+ // Use type-specific field list to avoid false positives from irrelevant fields
825
+ const attributesToCheck = getComparableFields(normalizedConfigAttr.type);
826
+
827
+ const differences: string[] = [];
828
+
829
+ const result = attributesToCheck.every((attr) => {
822
830
  // Check if both objects have the attribute
823
831
  const dbHasAttr = attr in normalizedDbAttr;
824
832
  const configHasAttr = attr in normalizedConfigAttr;
@@ -838,16 +846,40 @@ const attributesSame = (
838
846
 
839
847
  // Normalize booleans: treat undefined and false as equivalent
840
848
  if (typeof dbValue === "boolean" || typeof configValue === "boolean") {
841
- return Boolean(dbValue) === Boolean(configValue);
849
+ const boolMatch = Boolean(dbValue) === Boolean(configValue);
850
+ if (!boolMatch) {
851
+ differences.push(`${attr}: db=${dbValue} config=${configValue}`);
852
+ }
853
+ return boolMatch;
842
854
  }
843
855
  // For numeric comparisons, compare numbers if both are numeric-like
844
856
  if (
845
857
  (typeof dbValue === "number" || (typeof dbValue === "string" && dbValue !== "" && !isNaN(Number(dbValue)))) &&
846
858
  (typeof configValue === "number" || (typeof configValue === "string" && configValue !== "" && !isNaN(Number(configValue))))
847
859
  ) {
848
- return Number(dbValue) === Number(configValue);
860
+ const numMatch = Number(dbValue) === Number(configValue);
861
+ if (!numMatch) {
862
+ differences.push(`${attr}: db=${dbValue} config=${configValue}`);
863
+ }
864
+ return numMatch;
849
865
  }
850
- return dbValue === configValue;
866
+
867
+ // For array comparisons (e.g., enum elements), use order-independent equality
868
+ if (Array.isArray(dbValue) && Array.isArray(configValue)) {
869
+ const arrayMatch =
870
+ dbValue.length === configValue.length &&
871
+ dbValue.every((val) => configValue.includes(val));
872
+ if (!arrayMatch) {
873
+ differences.push(`${attr}: db=${JSON.stringify(dbValue)} config=${JSON.stringify(configValue)}`);
874
+ }
875
+ return arrayMatch;
876
+ }
877
+
878
+ const match = dbValue === configValue;
879
+ if (!match) {
880
+ differences.push(`${attr}: db=${JSON.stringify(dbValue)} config=${JSON.stringify(configValue)}`);
881
+ }
882
+ return match;
851
883
  }
852
884
 
853
885
  // If neither has the attribute, consider it the same
@@ -860,23 +892,50 @@ const attributesSame = (
860
892
  const dbValue = normalizedDbAttr[attr as keyof typeof normalizedDbAttr];
861
893
  // Consider default-false booleans as equal to missing in config
862
894
  if (typeof dbValue === "boolean") {
863
- return dbValue === false; // missing in config equals false in db
895
+ const match = dbValue === false; // missing in config equals false in db
896
+ if (!match) {
897
+ differences.push(`${attr}: db=${dbValue} config=<missing>`);
898
+ }
899
+ return match;
864
900
  }
865
- return dbValue === undefined || dbValue === null;
901
+ const match = dbValue === undefined || dbValue === null;
902
+ if (!match) {
903
+ differences.push(`${attr}: db=${JSON.stringify(dbValue)} config=<missing>`);
904
+ }
905
+ return match;
866
906
  }
867
907
 
868
908
  if (!dbHasAttr && configHasAttr) {
869
909
  const configValue = normalizedConfigAttr[attr as keyof typeof normalizedConfigAttr];
870
910
  // Consider default-false booleans as equal to missing in db
871
911
  if (typeof configValue === "boolean") {
872
- return configValue === false; // missing in db equals false in config
912
+ const match = configValue === false; // missing in db equals false in config
913
+ if (!match) {
914
+ differences.push(`${attr}: db=<missing> config=${configValue}`);
915
+ }
916
+ return match;
873
917
  }
874
- return configValue === undefined || configValue === null;
918
+ const match = configValue === undefined || configValue === null;
919
+ if (!match) {
920
+ differences.push(`${attr}: db=<missing> config=${JSON.stringify(configValue)}`);
921
+ }
922
+ return match;
875
923
  }
876
924
 
877
925
  // If we reach here, the attributes are different
926
+ differences.push(`${attr}: unexpected comparison state`);
878
927
  return false;
879
928
  });
929
+
930
+ // Log differences if any were found
931
+ if (differences.length > 0) {
932
+ logger.debug(`Attribute '${normalizedDbAttr.key}' comparison found differences:`, {
933
+ differences,
934
+ operation: 'attributesSame'
935
+ });
936
+ }
937
+
938
+ return result;
880
939
  };
881
940
 
882
941
  /**
@@ -890,14 +949,6 @@ export const createOrUpdateAttributeWithStatusCheck = async (
890
949
  retryCount: number = 0,
891
950
  maxRetries: number = 5
892
951
  ): Promise<boolean> => {
893
- MessageFormatter.info(
894
- chalk.blue(
895
- `Creating/updating attribute '${attribute.key}' (attempt ${
896
- retryCount + 1
897
- }/${maxRetries + 1})`
898
- )
899
- );
900
-
901
952
  try {
902
953
  // First, try to create/update the attribute using existing logic
903
954
  const result = await createOrUpdateAttribute(db, dbId, collection, attribute);
@@ -913,6 +964,12 @@ export const createOrUpdateAttributeWithStatusCheck = async (
913
964
  return true;
914
965
  }
915
966
 
967
+ // If collection creation failed, return false to indicate failure
968
+ if (result === "error") {
969
+ MessageFormatter.error(`Failed to create collection for attribute '${attribute.key}'`);
970
+ return false;
971
+ }
972
+
916
973
  // Now wait for the attribute to become available
917
974
  const success = await waitForAttributeAvailable(
918
975
  db,
@@ -1059,7 +1116,7 @@ export const createOrUpdateAttribute = async (
1059
1116
  dbId: string,
1060
1117
  collection: Models.Collection,
1061
1118
  attribute: Attribute
1062
- ): Promise<"queued" | "processed"> => {
1119
+ ): Promise<"queued" | "processed" | "error"> => {
1063
1120
  let action = "create";
1064
1121
  let foundAttribute: Attribute | undefined;
1065
1122
  const updateEnabled = true;
@@ -1069,7 +1126,7 @@ export const createOrUpdateAttribute = async (
1069
1126
  (attr: any) => attr.key === attribute.key
1070
1127
  ) as unknown as any;
1071
1128
  foundAttribute = parseAttribute(collectionAttr);
1072
-
1129
+
1073
1130
  } catch (error) {
1074
1131
  foundAttribute = undefined;
1075
1132
  }
@@ -1110,7 +1167,7 @@ export const createOrUpdateAttribute = async (
1110
1167
  return "processed";
1111
1168
  }
1112
1169
 
1113
-
1170
+
1114
1171
 
1115
1172
  // Relationship attribute logic with adjustments
1116
1173
  let collectionFoundViaRelatedCollection: Models.Collection | undefined;
@@ -1181,7 +1238,57 @@ export const createOrUpdateAttribute = async (
1181
1238
  }
1182
1239
  }
1183
1240
  finalAttribute = parseAttribute(finalAttribute);
1184
-
1241
+
1242
+ // Ensure collection/table exists - create it if it doesn't
1243
+ try {
1244
+ await (isDatabaseAdapter(db)
1245
+ ? db.getTable({ databaseId: dbId, tableId: collection.$id })
1246
+ : db.getCollection(dbId, collection.$id));
1247
+ } catch (error) {
1248
+ // Collection doesn't exist - create it
1249
+ if ((error as any).code === 404 ||
1250
+ (error instanceof Error && (
1251
+ error.message.includes('collection_not_found') ||
1252
+ error.message.includes('Collection with the requested ID could not be found')
1253
+ ))) {
1254
+
1255
+ MessageFormatter.info(`Collection '${collection.name}' doesn't exist, creating it first...`);
1256
+
1257
+ try {
1258
+ if (isDatabaseAdapter(db)) {
1259
+ await db.createTable({
1260
+ databaseId: dbId,
1261
+ id: collection.$id,
1262
+ name: collection.name,
1263
+ permissions: collection.$permissions || [],
1264
+ documentSecurity: collection.documentSecurity ?? false,
1265
+ enabled: collection.enabled ?? true
1266
+ });
1267
+ } else {
1268
+ await db.createCollection(
1269
+ dbId,
1270
+ collection.$id,
1271
+ collection.name,
1272
+ collection.$permissions || [],
1273
+ collection.documentSecurity ?? false,
1274
+ collection.enabled ?? true
1275
+ );
1276
+ }
1277
+
1278
+ MessageFormatter.success(`Created collection '${collection.name}'`);
1279
+ await delay(500); // Wait for collection to be ready
1280
+ } catch (createError) {
1281
+ MessageFormatter.error(
1282
+ `Failed to create collection '${collection.name}'`,
1283
+ createError instanceof Error ? createError : new Error(String(createError))
1284
+ );
1285
+ return "error";
1286
+ }
1287
+ } else {
1288
+ // Other error - re-throw
1289
+ throw error;
1290
+ }
1291
+ }
1185
1292
 
1186
1293
  // Use adapter-based attribute creation/update
1187
1294
  if (action === "create") {
@@ -1205,12 +1312,6 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
1205
1312
  collection: Models.Collection,
1206
1313
  attributes: Attribute[]
1207
1314
  ): Promise<boolean> => {
1208
- MessageFormatter.info(
1209
- chalk.green(
1210
- `Creating/Updating attributes for collection: ${collection.name} with status monitoring`
1211
- )
1212
- );
1213
-
1214
1315
  const existingAttributes: Attribute[] =
1215
1316
  // @ts-expect-error
1216
1317
  collection.attributes.map((attr) => parseAttribute(attr)) || [];
@@ -1265,12 +1366,6 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
1265
1366
  }
1266
1367
 
1267
1368
  // First, get fresh collection data and determine which attributes actually need processing
1268
- MessageFormatter.info(
1269
- chalk.blue(
1270
- `Analyzing ${attributes.length} attributes to determine which need processing...`
1271
- )
1272
- );
1273
-
1274
1369
  let currentCollection = collection;
1275
1370
  try {
1276
1371
  currentCollection = isDatabaseAdapter(db)
@@ -1301,44 +1396,28 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
1301
1396
  const attributesToProcess = attributes.filter((attribute) => {
1302
1397
  // Skip if already processed in this session
1303
1398
  if (isAttributeProcessed(currentCollection.$id, attribute.key)) {
1304
- MessageFormatter.info(
1305
- chalk.gray(`⏭️ Attribute '${attribute.key}' already processed in this session (skipping)`)
1306
- );
1307
1399
  return false;
1308
1400
  }
1309
1401
 
1310
1402
  const existing = existingAttributesMap.get(attribute.key);
1311
1403
  if (!existing) {
1312
- MessageFormatter.info(`➕ New attribute: ${attribute.key}`);
1404
+ MessageFormatter.info(`➕ ${attribute.key}`);
1313
1405
  return true;
1314
1406
  }
1315
1407
 
1316
- const needsUpdate = !attributesSame(existing, attribute);
1408
+ const needsUpdate = !attributesSame(existing, parseAttribute(attribute));
1317
1409
  if (needsUpdate) {
1318
- MessageFormatter.info(`🔄 Changed attribute: ${attribute.key}`);
1410
+ MessageFormatter.info(`🔄 ${attribute.key}`);
1319
1411
  } else {
1320
- MessageFormatter.info(
1321
- chalk.gray(`✅ Unchanged attribute: ${attribute.key} (skipping)`)
1322
- );
1412
+ MessageFormatter.info(chalk.gray(`✅ ${attribute.key}`));
1323
1413
  }
1324
1414
  return needsUpdate;
1325
1415
  });
1326
1416
 
1327
1417
  if (attributesToProcess.length === 0) {
1328
- MessageFormatter.info(
1329
- chalk.green(
1330
- `✅ All ${attributes.length} attributes are already up to date for collection: ${collection.name}`
1331
- )
1332
- );
1333
1418
  return true;
1334
1419
  }
1335
1420
 
1336
- MessageFormatter.info(
1337
- chalk.blue(
1338
- `Creating ${attributesToProcess.length} attributes sequentially with status monitoring...`
1339
- )
1340
- );
1341
-
1342
1421
  let remainingAttributes = [...attributesToProcess];
1343
1422
  let overallRetryCount = 0;
1344
1423
  const maxOverallRetries = 3;
@@ -1350,21 +1429,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
1350
1429
  const attributesToProcessThisRound = [...remainingAttributes];
1351
1430
  remainingAttributes = []; // Reset for next iteration
1352
1431
 
1353
- MessageFormatter.info(
1354
- chalk.blue(
1355
- `\n=== Attempt ${
1356
- overallRetryCount + 1
1357
- }/${maxOverallRetries} - Processing ${
1358
- attributesToProcessThisRound.length
1359
- } attributes ===`
1360
- )
1361
- );
1362
-
1363
1432
  for (const attribute of attributesToProcessThisRound) {
1364
- MessageFormatter.info(
1365
- chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`)
1366
- );
1367
-
1368
1433
  const success = await createOrUpdateAttributeWithStatusCheck(
1369
1434
  db,
1370
1435
  dbId,
@@ -1373,10 +1438,6 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
1373
1438
  );
1374
1439
 
1375
1440
  if (success) {
1376
- MessageFormatter.info(
1377
- chalk.green(`✅ Successfully created attribute: ${attribute.key}`)
1378
- );
1379
-
1380
1441
  // Mark this specific attribute as processed
1381
1442
  markAttributeProcessed(currentCollection.$id, attribute.key);
1382
1443
 
@@ -1394,21 +1455,12 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
1394
1455
  // Add delay between successful attributes
1395
1456
  await delay(1000);
1396
1457
  } else {
1397
- MessageFormatter.info(
1398
- chalk.red(
1399
- `❌ Failed to create attribute: ${attribute.key}, will retry in next round`
1400
- )
1401
- );
1458
+ MessageFormatter.info(chalk.red(`❌ ${attribute.key}`));
1402
1459
  remainingAttributes.push(attribute); // Add back to retry list
1403
1460
  }
1404
1461
  }
1405
1462
 
1406
1463
  if (remainingAttributes.length === 0) {
1407
- MessageFormatter.info(
1408
- chalk.green(
1409
- `\n✅ Successfully created all ${attributesToProcess.length} attributes for collection: ${collection.name}`
1410
- )
1411
- );
1412
1464
  return true;
1413
1465
  }
1414
1466
 
@@ -1416,9 +1468,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
1416
1468
 
1417
1469
  if (overallRetryCount < maxOverallRetries) {
1418
1470
  MessageFormatter.info(
1419
- chalk.yellow(
1420
- `\n⏳ Waiting 5 seconds before retrying ${attributesToProcess.length} failed attributes...`
1421
- )
1471
+ chalk.yellow(`⏳ Retrying ${remainingAttributes.length} failed attributes...`)
1422
1472
  );
1423
1473
  await delay(5000);
1424
1474
 
@@ -1427,13 +1477,8 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
1427
1477
  currentCollection = isDatabaseAdapter(db)
1428
1478
  ? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data as Models.Collection
1429
1479
  : await db.getCollection(dbId, collection.$id);
1430
- MessageFormatter.info(`Refreshed collection data for retry`);
1431
1480
  } catch (error) {
1432
- MessageFormatter.info(
1433
- chalk.yellow(
1434
- `Warning: Could not refresh collection data for retry: ${error}`
1435
- )
1436
- );
1481
+ // Silently continue if refresh fails
1437
1482
  }
1438
1483
  }
1439
1484
  }
@@ -35,12 +35,9 @@ const waitForIndexAvailable = async (
35
35
  // Calculate exponential backoff: 2s, 4s, 8s, 16s, 30s (capped at 30s)
36
36
  if (retryCount > 0) {
37
37
  const exponentialDelay = calculateExponentialBackoff(retryCount);
38
- MessageFormatter.info(`Waiting for index '${indexKey}' to become available (retry ${retryCount}, backoff: ${exponentialDelay}ms)...`);
39
38
  await delay(exponentialDelay);
40
- } else {
41
- MessageFormatter.info(`Waiting for index '${indexKey}' to become available...`);
42
39
  }
43
-
40
+
44
41
  while (Date.now() - startTime < maxWaitTime) {
45
42
  try {
46
43
  const indexList = await (isLegacyDatabases(db)
@@ -56,15 +53,8 @@ const waitForIndexAvailable = async (
56
53
  return false;
57
54
  }
58
55
 
59
- if (isLegacyDatabases(db)) {
60
- MessageFormatter.debug(`Index '${indexKey}' status: ${(index as any).status}`);
61
- } else {
62
- MessageFormatter.debug(`Index '${indexKey}' detected (TablesDB)`);
63
- }
64
-
65
56
  switch (index.status) {
66
57
  case 'available':
67
- MessageFormatter.success(`Index '${indexKey}' is now available (type: ${index.type}, attributes: [${index.attributes.join(', ')}])`);
68
58
  return true;
69
59
 
70
60
  case 'failed':
@@ -229,11 +219,7 @@ export const createOrUpdateIndexesWithStatusCheck = async (
229
219
  const remainingIndexes = [...indexesToProcess];
230
220
  indexesToProcess = []; // Reset for next iteration
231
221
 
232
- MessageFormatter.info(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${remainingIndexes.length} indexes ===`);
233
-
234
222
  for (const index of remainingIndexes) {
235
- MessageFormatter.info(`\n--- Processing index: ${index.key} (type: ${index.type}, attributes: [${index.attributes.join(', ')}]) ---`);
236
-
237
223
  const success = await createOrUpdateIndexWithStatusCheck(
238
224
  dbId,
239
225
  db,
@@ -243,25 +229,24 @@ export const createOrUpdateIndexesWithStatusCheck = async (
243
229
  );
244
230
 
245
231
  if (success) {
246
- MessageFormatter.success(`Successfully created index: ${index.key} (type: ${index.type})`);
232
+ MessageFormatter.info(`✅ ${index.key} (${index.type})`);
247
233
 
248
234
  // Add delay between successful indexes
249
235
  await delay(1000);
250
236
  } else {
251
- MessageFormatter.error(`Failed to create index: ${index.key} (type: ${index.type}), will retry in next round`);
237
+ MessageFormatter.info(`❌ ${index.key} (${index.type})`);
252
238
  indexesToProcess.push(index); // Add back to retry list
253
239
  }
254
240
  }
255
241
 
256
242
  if (indexesToProcess.length === 0) {
257
- MessageFormatter.success(`\nSuccessfully created all ${indexes.length} indexes for collection '${collectionId}'`);
258
243
  return true;
259
244
  }
260
245
 
261
246
  overallRetryCount++;
262
247
 
263
248
  if (overallRetryCount < maxOverallRetries) {
264
- MessageFormatter.warning(`\nWaiting 5 seconds before retrying ${indexesToProcess.length} failed indexes...`);
249
+ MessageFormatter.warning(`⏳ Retrying ${indexesToProcess.length} failed indexes...`);
265
250
  await delay(5000);
266
251
  }
267
252
  }