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.
- package/CONFIG_TODO.md +1189 -0
- package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
- package/dist/cli/commands/configCommands.js +7 -1
- package/dist/cli/commands/databaseCommands.js +23 -15
- package/dist/collections/attributes.d.ts +1 -1
- package/dist/collections/attributes.js +163 -66
- package/dist/collections/indexes.js +3 -17
- package/dist/collections/methods.js +38 -0
- package/dist/config/ConfigManager.d.ts +445 -0
- package/dist/config/ConfigManager.js +625 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.js +7 -0
- package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
- package/dist/config/services/ConfigDiscoveryService.js +374 -0
- package/dist/config/services/ConfigLoaderService.d.ts +105 -0
- package/dist/config/services/ConfigLoaderService.js +410 -0
- package/dist/config/services/ConfigMergeService.d.ts +208 -0
- package/dist/config/services/ConfigMergeService.js +307 -0
- package/dist/config/services/ConfigValidationService.d.ts +214 -0
- package/dist/config/services/ConfigValidationService.js +310 -0
- package/dist/config/services/SessionAuthService.d.ts +225 -0
- package/dist/config/services/SessionAuthService.js +456 -0
- package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
- package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
- package/dist/config/services/index.d.ts +13 -0
- package/dist/config/services/index.js +10 -0
- package/dist/interactiveCLI.js +8 -6
- package/dist/main.js +2 -2
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
- package/dist/shared/operationQueue.js +1 -1
- package/dist/utils/ClientFactory.d.ts +87 -0
- package/dist/utils/ClientFactory.js +164 -0
- package/dist/utils/getClientFromConfig.js +4 -3
- package/dist/utils/helperFunctions.d.ts +1 -0
- package/dist/utils/helperFunctions.js +21 -5
- package/dist/utils/yamlConverter.d.ts +2 -0
- package/dist/utils/yamlConverter.js +21 -4
- package/dist/utilsController.d.ts +18 -15
- package/dist/utilsController.js +83 -131
- package/package.json +1 -1
- package/src/cli/commands/configCommands.ts +8 -1
- package/src/cli/commands/databaseCommands.ts +34 -20
- package/src/collections/attributes.ts +195 -150
- package/src/collections/indexes.ts +4 -19
- package/src/collections/methods.ts +46 -0
- package/src/config/ConfigManager.ts +808 -0
- package/src/config/index.ts +10 -0
- package/src/config/services/ConfigDiscoveryService.ts +463 -0
- package/src/config/services/ConfigLoaderService.ts +560 -0
- package/src/config/services/ConfigMergeService.ts +386 -0
- package/src/config/services/ConfigValidationService.ts +394 -0
- package/src/config/services/SessionAuthService.ts +565 -0
- package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
- package/src/config/services/index.ts +29 -0
- package/src/interactiveCLI.ts +9 -7
- package/src/main.ts +2 -2
- package/src/shared/operationQueue.ts +1 -1
- package/src/utils/ClientFactory.ts +186 -0
- package/src/utils/getClientFromConfig.ts +4 -3
- package/src/utils/helperFunctions.ts +27 -7
- package/src/utils/yamlConverter.ts +28 -2
- 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 (
|
21
|
-
const MIN_MAX_THRESHOLD =
|
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
|
-
|
146
|
-
|
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
|
-
|
150
|
-
|
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
|
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
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(`➕
|
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(`🔄
|
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
|
-
|
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.
|
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.
|
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(
|
249
|
+
MessageFormatter.warning(`⏳ Retrying ${indexesToProcess.length} failed indexes...`);
|
265
250
|
await delay(5000);
|
266
251
|
}
|
267
252
|
}
|