appwrite-utils-cli 1.0.9 → 1.1.1
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/README.md +48 -0
- package/dist/collections/attributes.d.ts +8 -0
- package/dist/collections/attributes.js +195 -0
- package/dist/collections/indexes.d.ts +8 -0
- package/dist/collections/indexes.js +150 -0
- package/dist/collections/methods.js +105 -53
- package/dist/interactiveCLI.js +134 -42
- package/dist/migrations/comprehensiveTransfer.d.ts +28 -0
- package/dist/migrations/comprehensiveTransfer.js +225 -7
- package/dist/migrations/transfer.js +29 -40
- package/package.json +1 -1
- package/src/collections/attributes.ts +339 -0
- package/src/collections/indexes.ts +264 -0
- package/src/collections/methods.ts +175 -87
- package/src/interactiveCLI.ts +137 -42
- package/src/migrations/comprehensiveTransfer.ts +348 -14
- package/src/migrations/transfer.ts +48 -99
@@ -8,6 +8,165 @@ import { nameToIdMapping, enqueueOperation } from "../shared/operationQueue.js";
|
|
8
8
|
import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
9
9
|
import chalk from "chalk";
|
10
10
|
|
11
|
+
// Interface for attribute with status (fixing the type issue)
|
12
|
+
interface AttributeWithStatus {
|
13
|
+
key: string;
|
14
|
+
type: string;
|
15
|
+
status: 'available' | 'processing' | 'deleting' | 'stuck' | 'failed';
|
16
|
+
error: string;
|
17
|
+
required: boolean;
|
18
|
+
array?: boolean;
|
19
|
+
$createdAt: string;
|
20
|
+
$updatedAt: string;
|
21
|
+
[key: string]: any; // For type-specific fields
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Wait for attribute to become available, with retry logic for stuck attributes and exponential backoff
|
26
|
+
*/
|
27
|
+
const waitForAttributeAvailable = async (
|
28
|
+
db: Databases,
|
29
|
+
dbId: string,
|
30
|
+
collectionId: string,
|
31
|
+
attributeKey: string,
|
32
|
+
maxWaitTime: number = 60000, // 1 minute
|
33
|
+
retryCount: number = 0,
|
34
|
+
maxRetries: number = 5
|
35
|
+
): Promise<boolean> => {
|
36
|
+
const startTime = Date.now();
|
37
|
+
let checkInterval = 2000; // Start with 2 seconds
|
38
|
+
|
39
|
+
// Calculate exponential backoff: 2s, 4s, 8s, 16s, 30s (capped at 30s)
|
40
|
+
if (retryCount > 0) {
|
41
|
+
const exponentialDelay = Math.min(2000 * Math.pow(2, retryCount), 30000);
|
42
|
+
console.log(chalk.blue(`Waiting for attribute '${attributeKey}' to become available (retry ${retryCount}, backoff: ${exponentialDelay}ms)...`));
|
43
|
+
await delay(exponentialDelay);
|
44
|
+
} else {
|
45
|
+
console.log(chalk.blue(`Waiting for attribute '${attributeKey}' to become available...`));
|
46
|
+
}
|
47
|
+
|
48
|
+
while (Date.now() - startTime < maxWaitTime) {
|
49
|
+
try {
|
50
|
+
const collection = await db.getCollection(dbId, collectionId);
|
51
|
+
const attribute = (collection.attributes as any[]).find(
|
52
|
+
(attr: AttributeWithStatus) => attr.key === attributeKey
|
53
|
+
) as AttributeWithStatus | undefined;
|
54
|
+
|
55
|
+
if (!attribute) {
|
56
|
+
console.log(chalk.red(`Attribute '${attributeKey}' not found`));
|
57
|
+
return false;
|
58
|
+
}
|
59
|
+
|
60
|
+
console.log(chalk.gray(`Attribute '${attributeKey}' status: ${attribute.status}`));
|
61
|
+
|
62
|
+
switch (attribute.status) {
|
63
|
+
case 'available':
|
64
|
+
console.log(chalk.green(`✅ Attribute '${attributeKey}' is now available`));
|
65
|
+
return true;
|
66
|
+
|
67
|
+
case 'failed':
|
68
|
+
console.log(chalk.red(`❌ Attribute '${attributeKey}' failed: ${attribute.error}`));
|
69
|
+
return false;
|
70
|
+
|
71
|
+
case 'stuck':
|
72
|
+
console.log(chalk.yellow(`⚠️ Attribute '${attributeKey}' is stuck, will retry...`));
|
73
|
+
return false;
|
74
|
+
|
75
|
+
case 'processing':
|
76
|
+
// Continue waiting
|
77
|
+
break;
|
78
|
+
|
79
|
+
case 'deleting':
|
80
|
+
console.log(chalk.yellow(`Attribute '${attributeKey}' is being deleted`));
|
81
|
+
break;
|
82
|
+
|
83
|
+
default:
|
84
|
+
console.log(chalk.yellow(`Unknown status '${attribute.status}' for attribute '${attributeKey}'`));
|
85
|
+
break;
|
86
|
+
}
|
87
|
+
|
88
|
+
await delay(checkInterval);
|
89
|
+
} catch (error) {
|
90
|
+
console.log(chalk.red(`Error checking attribute status: ${error}`));
|
91
|
+
return false;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
// Timeout reached
|
96
|
+
console.log(chalk.yellow(`⏰ Timeout waiting for attribute '${attributeKey}' (${maxWaitTime}ms)`));
|
97
|
+
|
98
|
+
// If we have retries left and this isn't the last retry, try recreating
|
99
|
+
if (retryCount < maxRetries) {
|
100
|
+
console.log(chalk.yellow(`🔄 Retrying attribute creation (attempt ${retryCount + 1}/${maxRetries})`));
|
101
|
+
return false; // Signal that we need to retry
|
102
|
+
}
|
103
|
+
|
104
|
+
return false;
|
105
|
+
};
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Wait for all attributes in a collection to become available
|
109
|
+
*/
|
110
|
+
const waitForAllAttributesAvailable = async (
|
111
|
+
db: Databases,
|
112
|
+
dbId: string,
|
113
|
+
collectionId: string,
|
114
|
+
attributeKeys: string[],
|
115
|
+
maxWaitTime: number = 60000
|
116
|
+
): Promise<string[]> => {
|
117
|
+
console.log(chalk.blue(`Waiting for ${attributeKeys.length} attributes to become available...`));
|
118
|
+
|
119
|
+
const failedAttributes: string[] = [];
|
120
|
+
|
121
|
+
for (const attributeKey of attributeKeys) {
|
122
|
+
const success = await waitForAttributeAvailable(db, dbId, collectionId, attributeKey, maxWaitTime);
|
123
|
+
if (!success) {
|
124
|
+
failedAttributes.push(attributeKey);
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
return failedAttributes;
|
129
|
+
};
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Delete collection and recreate with retry logic
|
133
|
+
*/
|
134
|
+
const deleteAndRecreateCollection = async (
|
135
|
+
db: Databases,
|
136
|
+
dbId: string,
|
137
|
+
collection: Models.Collection,
|
138
|
+
retryCount: number
|
139
|
+
): Promise<Models.Collection | null> => {
|
140
|
+
try {
|
141
|
+
console.log(chalk.yellow(`🗑️ Deleting collection '${collection.name}' for retry ${retryCount}`));
|
142
|
+
|
143
|
+
// Delete the collection
|
144
|
+
await db.deleteCollection(dbId, collection.$id);
|
145
|
+
console.log(chalk.yellow(`Deleted collection '${collection.name}'`));
|
146
|
+
|
147
|
+
// Wait a bit before recreating
|
148
|
+
await delay(2000);
|
149
|
+
|
150
|
+
// Recreate the collection
|
151
|
+
console.log(chalk.blue(`🔄 Recreating collection '${collection.name}'`));
|
152
|
+
const newCollection = await db.createCollection(
|
153
|
+
dbId,
|
154
|
+
collection.$id,
|
155
|
+
collection.name,
|
156
|
+
collection.$permissions,
|
157
|
+
collection.documentSecurity,
|
158
|
+
collection.enabled
|
159
|
+
);
|
160
|
+
|
161
|
+
console.log(chalk.green(`✅ Recreated collection '${collection.name}'`));
|
162
|
+
return newCollection;
|
163
|
+
|
164
|
+
} catch (error) {
|
165
|
+
console.log(chalk.red(`Failed to delete/recreate collection '${collection.name}': ${error}`));
|
166
|
+
return null;
|
167
|
+
}
|
168
|
+
};
|
169
|
+
|
11
170
|
const attributesSame = (
|
12
171
|
databaseAttribute: Attribute,
|
13
172
|
configAttribute: Attribute
|
@@ -72,6 +231,87 @@ const attributesSame = (
|
|
72
231
|
});
|
73
232
|
};
|
74
233
|
|
234
|
+
/**
|
235
|
+
* Enhanced attribute creation with proper status monitoring and retry logic
|
236
|
+
*/
|
237
|
+
export const createOrUpdateAttributeWithStatusCheck = async (
|
238
|
+
db: Databases,
|
239
|
+
dbId: string,
|
240
|
+
collection: Models.Collection,
|
241
|
+
attribute: Attribute,
|
242
|
+
retryCount: number = 0,
|
243
|
+
maxRetries: number = 5
|
244
|
+
): Promise<boolean> => {
|
245
|
+
console.log(chalk.blue(`Creating/updating attribute '${attribute.key}' (attempt ${retryCount + 1}/${maxRetries + 1})`));
|
246
|
+
|
247
|
+
try {
|
248
|
+
// First, try to create/update the attribute using existing logic
|
249
|
+
await createOrUpdateAttribute(db, dbId, collection, attribute);
|
250
|
+
|
251
|
+
// Now wait for the attribute to become available
|
252
|
+
const success = await waitForAttributeAvailable(
|
253
|
+
db,
|
254
|
+
dbId,
|
255
|
+
collection.$id,
|
256
|
+
attribute.key,
|
257
|
+
60000, // 1 minute timeout
|
258
|
+
retryCount,
|
259
|
+
maxRetries
|
260
|
+
);
|
261
|
+
|
262
|
+
if (success) {
|
263
|
+
return true;
|
264
|
+
}
|
265
|
+
|
266
|
+
// If not successful and we have retries left, delete collection and try again
|
267
|
+
if (retryCount < maxRetries) {
|
268
|
+
console.log(chalk.yellow(`Attribute '${attribute.key}' failed/stuck, retrying...`));
|
269
|
+
|
270
|
+
// Get fresh collection data
|
271
|
+
const freshCollection = await db.getCollection(dbId, collection.$id);
|
272
|
+
|
273
|
+
// Delete and recreate collection
|
274
|
+
const newCollection = await deleteAndRecreateCollection(db, dbId, freshCollection, retryCount + 1);
|
275
|
+
|
276
|
+
if (newCollection) {
|
277
|
+
// Retry with the new collection
|
278
|
+
return await createOrUpdateAttributeWithStatusCheck(
|
279
|
+
db,
|
280
|
+
dbId,
|
281
|
+
newCollection,
|
282
|
+
attribute,
|
283
|
+
retryCount + 1,
|
284
|
+
maxRetries
|
285
|
+
);
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
console.log(chalk.red(`❌ Failed to create attribute '${attribute.key}' after ${maxRetries + 1} attempts`));
|
290
|
+
return false;
|
291
|
+
|
292
|
+
} catch (error) {
|
293
|
+
console.log(chalk.red(`Error creating attribute '${attribute.key}': ${error}`));
|
294
|
+
|
295
|
+
if (retryCount < maxRetries) {
|
296
|
+
console.log(chalk.yellow(`Retrying attribute '${attribute.key}' due to error...`));
|
297
|
+
|
298
|
+
// Wait a bit before retry
|
299
|
+
await delay(2000);
|
300
|
+
|
301
|
+
return await createOrUpdateAttributeWithStatusCheck(
|
302
|
+
db,
|
303
|
+
dbId,
|
304
|
+
collection,
|
305
|
+
attribute,
|
306
|
+
retryCount + 1,
|
307
|
+
maxRetries
|
308
|
+
);
|
309
|
+
}
|
310
|
+
|
311
|
+
return false;
|
312
|
+
}
|
313
|
+
};
|
314
|
+
|
75
315
|
export const createOrUpdateAttribute = async (
|
76
316
|
db: Databases,
|
77
317
|
dbId: string,
|
@@ -514,6 +754,105 @@ export const createOrUpdateAttribute = async (
|
|
514
754
|
}
|
515
755
|
};
|
516
756
|
|
757
|
+
/**
|
758
|
+
* Enhanced collection attribute creation with proper status monitoring
|
759
|
+
*/
|
760
|
+
export const createUpdateCollectionAttributesWithStatusCheck = async (
|
761
|
+
db: Databases,
|
762
|
+
dbId: string,
|
763
|
+
collection: Models.Collection,
|
764
|
+
attributes: Attribute[]
|
765
|
+
): Promise<boolean> => {
|
766
|
+
console.log(
|
767
|
+
chalk.green(
|
768
|
+
`Creating/Updating attributes for collection: ${collection.name} with status monitoring`
|
769
|
+
)
|
770
|
+
);
|
771
|
+
|
772
|
+
const existingAttributes: Attribute[] =
|
773
|
+
// @ts-expect-error
|
774
|
+
collection.attributes.map((attr) => parseAttribute(attr)) || [];
|
775
|
+
|
776
|
+
const attributesToRemove = existingAttributes.filter(
|
777
|
+
(attr) => !attributes.some((a) => a.key === attr.key)
|
778
|
+
);
|
779
|
+
const indexesToRemove = collection.indexes.filter((index) =>
|
780
|
+
attributesToRemove.some((attr) => index.attributes.includes(attr.key))
|
781
|
+
);
|
782
|
+
|
783
|
+
// Handle attribute removal first
|
784
|
+
if (attributesToRemove.length > 0) {
|
785
|
+
if (indexesToRemove.length > 0) {
|
786
|
+
console.log(
|
787
|
+
chalk.red(
|
788
|
+
`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove
|
789
|
+
.map((index) => index.key)
|
790
|
+
.join(", ")}`
|
791
|
+
)
|
792
|
+
);
|
793
|
+
for (const index of indexesToRemove) {
|
794
|
+
await tryAwaitWithRetry(
|
795
|
+
async () => await db.deleteIndex(dbId, collection.$id, index.key)
|
796
|
+
);
|
797
|
+
await delay(500); // Longer delay for deletions
|
798
|
+
}
|
799
|
+
}
|
800
|
+
for (const attr of attributesToRemove) {
|
801
|
+
console.log(
|
802
|
+
chalk.red(
|
803
|
+
`Removing attribute: ${attr.key} as it is no longer in the collection`
|
804
|
+
)
|
805
|
+
);
|
806
|
+
await tryAwaitWithRetry(
|
807
|
+
async () => await db.deleteAttribute(dbId, collection.$id, attr.key)
|
808
|
+
);
|
809
|
+
await delay(500); // Longer delay for deletions
|
810
|
+
}
|
811
|
+
}
|
812
|
+
|
813
|
+
// Create attributes ONE BY ONE with proper status checking
|
814
|
+
console.log(chalk.blue(`Creating ${attributes.length} attributes sequentially with status monitoring...`));
|
815
|
+
|
816
|
+
let currentCollection = collection;
|
817
|
+
const failedAttributes: string[] = [];
|
818
|
+
|
819
|
+
for (const attribute of attributes) {
|
820
|
+
console.log(chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`));
|
821
|
+
|
822
|
+
const success = await createOrUpdateAttributeWithStatusCheck(
|
823
|
+
db,
|
824
|
+
dbId,
|
825
|
+
currentCollection,
|
826
|
+
attribute
|
827
|
+
);
|
828
|
+
|
829
|
+
if (success) {
|
830
|
+
console.log(chalk.green(`✅ Successfully created attribute: ${attribute.key}`));
|
831
|
+
|
832
|
+
// Get updated collection data for next iteration
|
833
|
+
try {
|
834
|
+
currentCollection = await db.getCollection(dbId, collection.$id);
|
835
|
+
} catch (error) {
|
836
|
+
console.log(chalk.yellow(`Warning: Could not refresh collection data: ${error}`));
|
837
|
+
}
|
838
|
+
|
839
|
+
// Add delay between successful attributes
|
840
|
+
await delay(1000);
|
841
|
+
} else {
|
842
|
+
console.log(chalk.red(`❌ Failed to create attribute: ${attribute.key}`));
|
843
|
+
failedAttributes.push(attribute.key);
|
844
|
+
}
|
845
|
+
}
|
846
|
+
|
847
|
+
if (failedAttributes.length > 0) {
|
848
|
+
console.log(chalk.red(`\n❌ Failed to create ${failedAttributes.length} attributes: ${failedAttributes.join(', ')}`));
|
849
|
+
return false;
|
850
|
+
}
|
851
|
+
|
852
|
+
console.log(chalk.green(`\n✅ Successfully created all ${attributes.length} attributes for collection: ${collection.name}`));
|
853
|
+
return true;
|
854
|
+
};
|
855
|
+
|
517
856
|
export const createUpdateCollectionAttributes = async (
|
518
857
|
db: Databases,
|
519
858
|
dbId: string,
|
@@ -1,6 +1,270 @@
|
|
1
1
|
import { indexSchema, type Index } from "appwrite-utils";
|
2
2
|
import { Databases, IndexType, Query, type Models } from "node-appwrite";
|
3
3
|
import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
4
|
+
import chalk from "chalk";
|
5
|
+
|
6
|
+
// Interface for index with status
|
7
|
+
interface IndexWithStatus {
|
8
|
+
key: string;
|
9
|
+
type: string;
|
10
|
+
status: 'available' | 'processing' | 'deleting' | 'stuck' | 'failed';
|
11
|
+
error: string;
|
12
|
+
attributes: string[];
|
13
|
+
orders?: string[];
|
14
|
+
$createdAt: string;
|
15
|
+
$updatedAt: string;
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Wait for index to become available, with retry logic for stuck indexes and exponential backoff
|
20
|
+
*/
|
21
|
+
const waitForIndexAvailable = async (
|
22
|
+
db: Databases,
|
23
|
+
dbId: string,
|
24
|
+
collectionId: string,
|
25
|
+
indexKey: string,
|
26
|
+
maxWaitTime: number = 60000, // 1 minute
|
27
|
+
retryCount: number = 0,
|
28
|
+
maxRetries: number = 5
|
29
|
+
): Promise<boolean> => {
|
30
|
+
const startTime = Date.now();
|
31
|
+
let checkInterval = 2000; // Start with 2 seconds
|
32
|
+
|
33
|
+
// Calculate exponential backoff: 2s, 4s, 8s, 16s, 30s (capped at 30s)
|
34
|
+
if (retryCount > 0) {
|
35
|
+
const exponentialDelay = Math.min(2000 * Math.pow(2, retryCount), 30000);
|
36
|
+
console.log(chalk.blue(`Waiting for index '${indexKey}' to become available (retry ${retryCount}, backoff: ${exponentialDelay}ms)...`));
|
37
|
+
await delay(exponentialDelay);
|
38
|
+
} else {
|
39
|
+
console.log(chalk.blue(`Waiting for index '${indexKey}' to become available...`));
|
40
|
+
}
|
41
|
+
|
42
|
+
while (Date.now() - startTime < maxWaitTime) {
|
43
|
+
try {
|
44
|
+
const indexList = await db.listIndexes(dbId, collectionId);
|
45
|
+
const index = indexList.indexes.find(
|
46
|
+
(idx: any) => idx.key === indexKey
|
47
|
+
) as IndexWithStatus | undefined;
|
48
|
+
|
49
|
+
if (!index) {
|
50
|
+
console.log(chalk.red(`Index '${indexKey}' not found`));
|
51
|
+
return false;
|
52
|
+
}
|
53
|
+
|
54
|
+
console.log(chalk.gray(`Index '${indexKey}' status: ${index.status}`));
|
55
|
+
|
56
|
+
switch (index.status) {
|
57
|
+
case 'available':
|
58
|
+
console.log(chalk.green(`✅ Index '${indexKey}' is now available`));
|
59
|
+
return true;
|
60
|
+
|
61
|
+
case 'failed':
|
62
|
+
console.log(chalk.red(`❌ Index '${indexKey}' failed: ${index.error}`));
|
63
|
+
return false;
|
64
|
+
|
65
|
+
case 'stuck':
|
66
|
+
console.log(chalk.yellow(`⚠️ Index '${indexKey}' is stuck, will retry...`));
|
67
|
+
return false;
|
68
|
+
|
69
|
+
case 'processing':
|
70
|
+
// Continue waiting
|
71
|
+
break;
|
72
|
+
|
73
|
+
case 'deleting':
|
74
|
+
console.log(chalk.yellow(`Index '${indexKey}' is being deleted`));
|
75
|
+
break;
|
76
|
+
|
77
|
+
default:
|
78
|
+
console.log(chalk.yellow(`Unknown status '${index.status}' for index '${indexKey}'`));
|
79
|
+
break;
|
80
|
+
}
|
81
|
+
|
82
|
+
await delay(checkInterval);
|
83
|
+
} catch (error) {
|
84
|
+
console.log(chalk.red(`Error checking index status: ${error}`));
|
85
|
+
return false;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
// Timeout reached
|
90
|
+
console.log(chalk.yellow(`⏰ Timeout waiting for index '${indexKey}' (${maxWaitTime}ms)`));
|
91
|
+
|
92
|
+
// If we have retries left and this isn't the last retry, try recreating
|
93
|
+
if (retryCount < maxRetries) {
|
94
|
+
console.log(chalk.yellow(`🔄 Retrying index creation (attempt ${retryCount + 1}/${maxRetries})`));
|
95
|
+
return false; // Signal that we need to retry
|
96
|
+
}
|
97
|
+
|
98
|
+
return false;
|
99
|
+
};
|
100
|
+
|
101
|
+
/**
|
102
|
+
* Delete collection and recreate for index retry (reused from attributes.ts)
|
103
|
+
*/
|
104
|
+
const deleteAndRecreateCollectionForIndex = async (
|
105
|
+
db: Databases,
|
106
|
+
dbId: string,
|
107
|
+
collection: Models.Collection,
|
108
|
+
retryCount: number
|
109
|
+
): Promise<Models.Collection | null> => {
|
110
|
+
try {
|
111
|
+
console.log(chalk.yellow(`🗑️ Deleting collection '${collection.name}' for index retry ${retryCount}`));
|
112
|
+
|
113
|
+
// Delete the collection
|
114
|
+
await db.deleteCollection(dbId, collection.$id);
|
115
|
+
console.log(chalk.yellow(`Deleted collection '${collection.name}'`));
|
116
|
+
|
117
|
+
// Wait a bit before recreating
|
118
|
+
await delay(2000);
|
119
|
+
|
120
|
+
// Recreate the collection
|
121
|
+
console.log(chalk.blue(`🔄 Recreating collection '${collection.name}'`));
|
122
|
+
const newCollection = await db.createCollection(
|
123
|
+
dbId,
|
124
|
+
collection.$id,
|
125
|
+
collection.name,
|
126
|
+
collection.$permissions,
|
127
|
+
collection.documentSecurity,
|
128
|
+
collection.enabled
|
129
|
+
);
|
130
|
+
|
131
|
+
console.log(chalk.green(`✅ Recreated collection '${collection.name}'`));
|
132
|
+
return newCollection;
|
133
|
+
|
134
|
+
} catch (error) {
|
135
|
+
console.log(chalk.red(`Failed to delete/recreate collection '${collection.name}': ${error}`));
|
136
|
+
return null;
|
137
|
+
}
|
138
|
+
};
|
139
|
+
|
140
|
+
/**
|
141
|
+
* Enhanced index creation with proper status monitoring and retry logic
|
142
|
+
*/
|
143
|
+
export const createOrUpdateIndexWithStatusCheck = async (
|
144
|
+
dbId: string,
|
145
|
+
db: Databases,
|
146
|
+
collectionId: string,
|
147
|
+
collection: Models.Collection,
|
148
|
+
index: Index,
|
149
|
+
retryCount: number = 0,
|
150
|
+
maxRetries: number = 5
|
151
|
+
): Promise<boolean> => {
|
152
|
+
console.log(chalk.blue(`Creating/updating index '${index.key}' (attempt ${retryCount + 1}/${maxRetries + 1})`));
|
153
|
+
|
154
|
+
try {
|
155
|
+
// First, try to create/update the index using existing logic
|
156
|
+
await createOrUpdateIndex(dbId, db, collectionId, index);
|
157
|
+
|
158
|
+
// Now wait for the index to become available
|
159
|
+
const success = await waitForIndexAvailable(
|
160
|
+
db,
|
161
|
+
dbId,
|
162
|
+
collectionId,
|
163
|
+
index.key,
|
164
|
+
60000, // 1 minute timeout
|
165
|
+
retryCount,
|
166
|
+
maxRetries
|
167
|
+
);
|
168
|
+
|
169
|
+
if (success) {
|
170
|
+
return true;
|
171
|
+
}
|
172
|
+
|
173
|
+
// If not successful and we have retries left, delete collection and try again
|
174
|
+
if (retryCount < maxRetries) {
|
175
|
+
console.log(chalk.yellow(`Index '${index.key}' failed/stuck, retrying...`));
|
176
|
+
|
177
|
+
// Get fresh collection data
|
178
|
+
const freshCollection = await db.getCollection(dbId, collectionId);
|
179
|
+
|
180
|
+
// Delete and recreate collection
|
181
|
+
const newCollection = await deleteAndRecreateCollectionForIndex(db, dbId, freshCollection, retryCount + 1);
|
182
|
+
|
183
|
+
if (newCollection) {
|
184
|
+
// Retry with the new collection
|
185
|
+
return await createOrUpdateIndexWithStatusCheck(
|
186
|
+
dbId,
|
187
|
+
db,
|
188
|
+
newCollection.$id,
|
189
|
+
newCollection,
|
190
|
+
index,
|
191
|
+
retryCount + 1,
|
192
|
+
maxRetries
|
193
|
+
);
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
console.log(chalk.red(`❌ Failed to create index '${index.key}' after ${maxRetries + 1} attempts`));
|
198
|
+
return false;
|
199
|
+
|
200
|
+
} catch (error) {
|
201
|
+
console.log(chalk.red(`Error creating index '${index.key}': ${error}`));
|
202
|
+
|
203
|
+
if (retryCount < maxRetries) {
|
204
|
+
console.log(chalk.yellow(`Retrying index '${index.key}' due to error...`));
|
205
|
+
|
206
|
+
// Wait a bit before retry
|
207
|
+
await delay(2000);
|
208
|
+
|
209
|
+
return await createOrUpdateIndexWithStatusCheck(
|
210
|
+
dbId,
|
211
|
+
db,
|
212
|
+
collectionId,
|
213
|
+
collection,
|
214
|
+
index,
|
215
|
+
retryCount + 1,
|
216
|
+
maxRetries
|
217
|
+
);
|
218
|
+
}
|
219
|
+
|
220
|
+
return false;
|
221
|
+
}
|
222
|
+
};
|
223
|
+
|
224
|
+
/**
|
225
|
+
* Enhanced index creation with status monitoring for all indexes
|
226
|
+
*/
|
227
|
+
export const createOrUpdateIndexesWithStatusCheck = async (
|
228
|
+
dbId: string,
|
229
|
+
db: Databases,
|
230
|
+
collectionId: string,
|
231
|
+
collection: Models.Collection,
|
232
|
+
indexes: Index[]
|
233
|
+
): Promise<boolean> => {
|
234
|
+
console.log(chalk.blue(`Creating/updating ${indexes.length} indexes with status monitoring...`));
|
235
|
+
|
236
|
+
const failedIndexes: string[] = [];
|
237
|
+
|
238
|
+
for (const index of indexes) {
|
239
|
+
console.log(chalk.blue(`\n--- Processing index: ${index.key} ---`));
|
240
|
+
|
241
|
+
const success = await createOrUpdateIndexWithStatusCheck(
|
242
|
+
dbId,
|
243
|
+
db,
|
244
|
+
collectionId,
|
245
|
+
collection,
|
246
|
+
index
|
247
|
+
);
|
248
|
+
|
249
|
+
if (success) {
|
250
|
+
console.log(chalk.green(`✅ Successfully created index: ${index.key}`));
|
251
|
+
|
252
|
+
// Add delay between successful indexes
|
253
|
+
await delay(1000);
|
254
|
+
} else {
|
255
|
+
console.log(chalk.red(`❌ Failed to create index: ${index.key}`));
|
256
|
+
failedIndexes.push(index.key);
|
257
|
+
}
|
258
|
+
}
|
259
|
+
|
260
|
+
if (failedIndexes.length > 0) {
|
261
|
+
console.log(chalk.red(`\n❌ Failed to create ${failedIndexes.length} indexes: ${failedIndexes.join(', ')}`));
|
262
|
+
return false;
|
263
|
+
}
|
264
|
+
|
265
|
+
console.log(chalk.green(`\n✅ Successfully created all ${indexes.length} indexes`));
|
266
|
+
return true;
|
267
|
+
};
|
4
268
|
|
5
269
|
export const createOrUpdateIndex = async (
|
6
270
|
dbId: string,
|