appwrite-utils-cli 0.0.36 → 0.0.38

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 (35) hide show
  1. package/README.md +2 -0
  2. package/dist/migrations/attributes.js +21 -20
  3. package/dist/migrations/collections.js +10 -13
  4. package/dist/migrations/dataLoader.d.ts +14 -23
  5. package/dist/migrations/dataLoader.js +248 -170
  6. package/dist/migrations/databases.js +2 -1
  7. package/dist/migrations/importController.js +6 -24
  8. package/dist/migrations/importDataActions.d.ts +1 -1
  9. package/dist/migrations/importDataActions.js +3 -2
  10. package/dist/migrations/indexes.js +2 -1
  11. package/dist/migrations/migrationHelper.js +2 -1
  12. package/dist/migrations/queue.js +3 -2
  13. package/dist/migrations/setupDatabase.js +10 -12
  14. package/dist/migrations/storage.js +14 -13
  15. package/dist/migrations/users.js +14 -36
  16. package/dist/utils/helperFunctions.d.ts +10 -1
  17. package/dist/utils/helperFunctions.js +27 -0
  18. package/dist/utils/setupFiles.js +4 -0
  19. package/dist/utilsController.js +2 -1
  20. package/package.json +2 -2
  21. package/src/migrations/attributes.ts +204 -143
  22. package/src/migrations/collections.ts +49 -33
  23. package/src/migrations/dataLoader.ts +377 -240
  24. package/src/migrations/databases.ts +4 -1
  25. package/src/migrations/importController.ts +13 -27
  26. package/src/migrations/importDataActions.ts +6 -3
  27. package/src/migrations/indexes.ts +4 -1
  28. package/src/migrations/migrationHelper.ts +9 -5
  29. package/src/migrations/queue.ts +5 -6
  30. package/src/migrations/setupDatabase.ts +35 -18
  31. package/src/migrations/storage.ts +50 -36
  32. package/src/migrations/users.ts +28 -38
  33. package/src/utils/helperFunctions.ts +33 -1
  34. package/src/utils/setupFiles.ts +4 -0
  35. package/src/utilsController.ts +3 -1
@@ -1,6 +1,6 @@
1
- import { ID, Query, } from "node-appwrite";
1
+ import { AppwriteException, ID, Query, } from "node-appwrite";
2
2
  import _ from "lodash";
3
- import { areCollectionNamesSame } from "../utils/index.js";
3
+ import { areCollectionNamesSame, tryAwaitWithRetry } from "../utils/index.js";
4
4
  import { resolveAndUpdateRelationships } from "./relationships.js";
5
5
  import { UsersController } from "./users.js";
6
6
  import { logger } from "./logging.js";
@@ -71,7 +71,7 @@ export class ImportController {
71
71
  dataLoader.getCollectionKey(collection.name);
72
72
  const importOperationId = dataLoader.collectionImportOperations.get(dataLoader.getCollectionKey(collection.name));
73
73
  const createBatches = (finalData) => {
74
- let maxBatchLength = 50;
74
+ let maxBatchLength = 25;
75
75
  const finalBatches = [];
76
76
  for (let i = 0; i < finalData.length; i++) {
77
77
  if (i % maxBatchLength === 0) {
@@ -109,7 +109,7 @@ export class ImportController {
109
109
  .map((item) => {
110
110
  return usersController.createUserAndReturn(item.finalData);
111
111
  });
112
- await Promise.all(userBatchPromises);
112
+ const promiseResults = await Promise.allSettled(userBatchPromises);
113
113
  for (const item of batch) {
114
114
  if (item && item.finalData) {
115
115
  dataLoader.userExistsMap.set(item.finalData.userId ||
@@ -118,26 +118,8 @@ export class ImportController {
118
118
  item.context.docId, true);
119
119
  }
120
120
  }
121
- console.log("Finished importing users batch", batch.length);
121
+ console.log("Finished importing users batch");
122
122
  }
123
- // for (let i = 0; i < usersData.length; i++) {
124
- // const user = usersData[i];
125
- // if (user.finalData) {
126
- // const userId =
127
- // user.finalData.userId ||
128
- // user.context.userId ||
129
- // user.context.docId;
130
- // if (!dataLoader.userExistsMap.has(userId)) {
131
- // if (!user.finalData.userId) {
132
- // user.finalData.userId = userId;
133
- // }
134
- // await usersController.createUserAndReturn(user.finalData);
135
- // dataLoader.userExistsMap.set(userId, true);
136
- // } else {
137
- // console.log("Skipped existing user: ", userId);
138
- // }
139
- // }
140
- // }
141
123
  console.log("Finished importing users");
142
124
  }
143
125
  }
@@ -173,7 +155,7 @@ export class ImportController {
173
155
  if (!item.finalData) {
174
156
  return Promise.resolve();
175
157
  }
176
- return this.database.createDocument(db.$id, collection.$id, id, item.finalData);
158
+ return tryAwaitWithRetry(async () => await this.database.createDocument(db.$id, collection.$id, id, item.finalData));
177
159
  });
178
160
  // Wait for all promises in the current batch to resolve
179
161
  await Promise.allSettled(batchPromises);
@@ -20,7 +20,7 @@ export declare class ImportDataActions {
20
20
  */
21
21
  validateItem(item: any, attributeMap: AttributeMappings, context: {
22
22
  [key: string]: any;
23
- }): Promise<boolean>;
23
+ }): boolean;
24
24
  executeAfterImportActions(item: any, attributeMap: AttributeMappings, context: {
25
25
  [key: string]: any;
26
26
  }): Promise<void>;
@@ -5,6 +5,7 @@ import { convertObjectBySchema } from "./converters.js";
5
5
  import {} from "appwrite-utils";
6
6
  import { afterImportActions } from "./afterImportActions.js";
7
7
  import { logger } from "./logging.js";
8
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
8
9
  export class ImportDataActions {
9
10
  db;
10
11
  storage;
@@ -68,7 +69,7 @@ export class ImportDataActions {
68
69
  * @param context The context for resolving templated parameters in validation rules.
69
70
  * @returns A promise that resolves to true if the item is valid, false otherwise.
70
71
  */
71
- async validateItem(item, attributeMap, context) {
72
+ validateItem(item, attributeMap, context) {
72
73
  for (const mapping of attributeMap) {
73
74
  const { validationActions } = mapping;
74
75
  if (!validationActions ||
@@ -111,7 +112,7 @@ export class ImportDataActions {
111
112
  const { action, params } = actionDef;
112
113
  console.log(`Executing post-import action '${action}' for attribute '${mapping.targetKey}' with params ${params.join(", ")}...`);
113
114
  try {
114
- await this.executeAction(action, params, context, item);
115
+ await tryAwaitWithRetry(async () => await this.executeAction(action, params, context, item));
115
116
  }
116
117
  catch (error) {
117
118
  logger.error(`Failed to execute post-import action '${action}' for attribute '${mapping.targetKey}':`, error);
@@ -1,5 +1,6 @@
1
1
  import { indexSchema } from "appwrite-utils";
2
2
  import { Databases, Query } from "node-appwrite";
3
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
3
4
  // import {}
4
5
  export const createOrUpdateIndex = async (dbId, db, collectionId, index) => {
5
6
  const existingIndex = await db.listIndexes(dbId, collectionId, [
@@ -13,6 +14,6 @@ export const createOrUpdateIndex = async (dbId, db, collectionId, index) => {
13
14
  };
14
15
  export const createOrUpdateIndexes = async (dbId, db, collectionId, indexes) => {
15
16
  for (const index of indexes) {
16
- await createOrUpdateIndex(dbId, db, collectionId, index);
17
+ await tryAwaitWithRetry(async () => await createOrUpdateIndex(dbId, db, collectionId, index));
17
18
  }
18
19
  };
@@ -3,6 +3,7 @@ import { BatchSchema, OperationSchema } from "./backup.js";
3
3
  import { AttributeMappingsSchema } from "appwrite-utils";
4
4
  import { z } from "zod";
5
5
  import { logger } from "./logging.js";
6
+ import { tryAwaitWithRetry } from "src/utils/helperFunctions.js";
6
7
  /**
7
8
  * Object that contains the context for an action that needs to be executed after import
8
9
  * Used in the afterImportActionsDefinitions
@@ -90,7 +91,7 @@ export const findOrCreateOperation = async (database, collectionId, operationTyp
90
91
  }
91
92
  };
92
93
  export const updateOperation = async (database, operationId, updateFields) => {
93
- await database.updateDocument("migrations", "currentOperations", operationId, updateFields);
94
+ await tryAwaitWithRetry(async () => await database.updateDocument("migrations", "currentOperations", operationId, updateFields));
94
95
  };
95
96
  // Actual max 1073741824
96
97
  export const maxDataLength = 1073741820;
@@ -2,6 +2,7 @@ import { Query } from "node-appwrite";
2
2
  import { createOrUpdateAttribute } from "./attributes.js";
3
3
  import _ from "lodash";
4
4
  import { fetchAndCacheCollectionByName } from "./collections.js";
5
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
5
6
  export const queuedOperations = [];
6
7
  export const nameToIdMapping = new Map();
7
8
  export const enqueueOperation = (operation) => {
@@ -24,7 +25,7 @@ export const processQueue = async (db, dbId) => {
24
25
  if (operation.collectionId) {
25
26
  console.log(`\tFetching collection by ID: ${operation.collectionId}`);
26
27
  try {
27
- collectionFound = await db.getCollection(dbId, operation.collectionId);
28
+ collectionFound = await tryAwaitWithRetry(async () => await db.getCollection(dbId, operation.collectionId));
28
29
  }
29
30
  catch (e) {
30
31
  console.log(`\tCollection not found by ID: ${operation.collectionId}`);
@@ -47,7 +48,7 @@ export const processQueue = async (db, dbId) => {
47
48
  // Handle non-relationship operations with a specified collectionId
48
49
  console.log(`\tFetching collection by ID: ${operation.collectionId}`);
49
50
  try {
50
- collectionFound = await db.getCollection(dbId, operation.collectionId);
51
+ collectionFound = await tryAwaitWithRetry(async () => await db.getCollection(dbId, operation.collectionId));
51
52
  }
52
53
  catch (e) {
53
54
  console.log(`\tCollection not found by ID: ${operation.collectionId}`);
@@ -2,7 +2,7 @@ import { Databases, ID, Query, Storage } from "node-appwrite";
2
2
  import { createOrUpdateAttribute } from "./attributes.js";
3
3
  import { createOrUpdateCollections, generateSchemas, wipeDatabase, } from "./collections.js";
4
4
  import { getMigrationCollectionSchemas } from "./backup.js";
5
- import { areCollectionNamesSame, toCamelCase } from "../utils/index.js";
5
+ import { areCollectionNamesSame, toCamelCase, tryAwaitWithRetry, } from "../utils/index.js";
6
6
  import { backupDatabase, initOrGetBackupStorage, initOrGetDocumentStorage, wipeDocumentStorage, } from "./storage.js";
7
7
  import {} from "appwrite-utils";
8
8
  import { nameToIdMapping } from "./queue.js";
@@ -17,17 +17,15 @@ export const setupMigrationDatabase = async (config) => {
17
17
  const dbCollections = [];
18
18
  const migrationCollectionsSetup = getMigrationCollectionSchemas();
19
19
  try {
20
- db = await database.get("migrations");
20
+ db = await tryAwaitWithRetry(async () => await database.get("migrations"));
21
21
  console.log("Migrations database found");
22
22
  }
23
23
  catch (e) {
24
- db = await database.create("migrations", "Migrations", true);
24
+ db = await tryAwaitWithRetry(async () => await database.create("migrations", "Migrations", true));
25
25
  console.log("Migrations database created");
26
26
  }
27
27
  if (db) {
28
- const collectionsPulled = await database.listCollections(db.$id, [
29
- Query.limit(25),
30
- ]);
28
+ const collectionsPulled = await tryAwaitWithRetry(async () => await database.listCollections(db.$id, [Query.limit(25)]));
31
29
  dbCollections.push(...collectionsPulled.collections);
32
30
  }
33
31
  console.log(`Collections in migrations database: ${dbCollections.length}`);
@@ -36,14 +34,14 @@ export const setupMigrationDatabase = async (config) => {
36
34
  const collectionId = toCamelCase(collectionName); // Convert name to toCamelCase for the ID
37
35
  let collectionFound = null;
38
36
  try {
39
- collectionFound = await database.getCollection(db.$id, collectionId);
37
+ collectionFound = await tryAwaitWithRetry(async () => await database.getCollection(db.$id, collectionId));
40
38
  }
41
39
  catch (e) {
42
40
  console.log(`Collection not found: ${collectionId}`);
43
41
  }
44
42
  if (!collectionFound) {
45
43
  // Create the collection with the provided configuration
46
- collectionFound = await database.createCollection(db.$id, collectionId, collectionName, undefined, collection.documentSecurity, collection.enabled);
44
+ collectionFound = await tryAwaitWithRetry(async () => await database.createCollection(db.$id, collectionId, collectionName, undefined, collection.documentSecurity, collection.enabled));
47
45
  }
48
46
  for (const attribute of attributes) {
49
47
  await createOrUpdateAttribute(database, db.$id, collectionFound, attribute);
@@ -61,10 +59,10 @@ export const ensureDatabasesExist = async (config) => {
61
59
  name: "Migrations",
62
60
  });
63
61
  const dbNames = databasesToEnsure.map((db) => db.name);
64
- const existingDatabases = await database.list([Query.equal("name", dbNames)]);
62
+ const existingDatabases = await tryAwaitWithRetry(async () => await database.list([Query.equal("name", dbNames)]));
65
63
  for (const db of databasesToEnsure) {
66
64
  if (!existingDatabases.databases.some((d) => d.name === db.name)) {
67
- await database.create(db.$id || ID.unique(), db.name, true);
65
+ await tryAwaitWithRetry(async () => await database.create(db.$id || ID.unique(), db.name, true));
68
66
  console.log(`${db.name} database created`);
69
67
  }
70
68
  }
@@ -73,10 +71,10 @@ export const wipeOtherDatabases = async (database, config) => {
73
71
  const databasesToKeep = config.databases.map((db) => db.name.toLowerCase().trim().replace(" ", ""));
74
72
  databasesToKeep.push("migrations");
75
73
  console.log(`Databases to keep: ${databasesToKeep.join(", ")}`);
76
- const allDatabases = await database.list([Query.limit(500)]);
74
+ const allDatabases = await tryAwaitWithRetry(async () => await database.list([Query.limit(500)]));
77
75
  for (const db of allDatabases.databases) {
78
76
  if (!databasesToKeep.includes(db.name.toLowerCase().trim().replace(" ", ""))) {
79
- await database.delete(db.$id);
77
+ await tryAwaitWithRetry(async () => await database.delete(db.$id));
80
78
  console.log(`Deleted database: ${db.name}`);
81
79
  }
82
80
  }
@@ -1,12 +1,13 @@
1
1
  import { Storage, Databases, Query, InputFile, ID, Permission, } from "node-appwrite";
2
2
  import {} from "./backup.js";
3
3
  import { splitIntoBatches } from "./migrationHelper.js";
4
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
4
5
  export const logOperation = async (db, dbId, operationDetails, operationId) => {
5
6
  try {
6
7
  let operation;
7
8
  if (operationId) {
8
9
  // Update existing operation log
9
- operation = await db.updateDocument("migrations", "currentOperations", operationId, operationDetails);
10
+ operation = await tryAwaitWithRetry(async () => await db.updateDocument("migrations", "currentOperations", operationId, operationDetails));
10
11
  }
11
12
  else {
12
13
  // Create new operation log
@@ -22,29 +23,29 @@ export const logOperation = async (db, dbId, operationDetails, operationId) => {
22
23
  };
23
24
  export const initOrGetBackupStorage = async (storage) => {
24
25
  try {
25
- const backupStorage = await storage.getBucket("backupStorage");
26
+ const backupStorage = await tryAwaitWithRetry(async () => await storage.getBucket("backupStorage"));
26
27
  return backupStorage;
27
28
  }
28
29
  catch (e) {
29
30
  // ID backupStorage
30
31
  // Name Backups Storage
31
- const backupStorage = await storage.createBucket("backupStorage", "Backups Storage");
32
+ const backupStorage = await tryAwaitWithRetry(async () => await storage.createBucket("backupStorage", "Backups Storage"));
32
33
  return backupStorage;
33
34
  }
34
35
  };
35
36
  export const initOrGetDocumentStorage = async (storage, config, dbName) => {
36
37
  try {
37
- await storage.getBucket(`${config.documentBucketId}_${dbName.toLowerCase().replace(" ", "")}`);
38
+ await tryAwaitWithRetry(async () => await storage.getBucket(`${config.documentBucketId}_${dbName.toLowerCase().replace(" ", "")}`));
38
39
  }
39
40
  catch (e) {
40
41
  // ID documentStorage
41
42
  // Name Document Storage
42
- const documentStorage = await storage.createBucket(`${config.documentBucketId}_${dbName.toLowerCase().replace(" ", "")}`, `Document Storage ${dbName}`, [
43
+ const documentStorage = await tryAwaitWithRetry(async () => await storage.createBucket(`${config.documentBucketId}_${dbName.toLowerCase().replace(" ", "")}`, `Document Storage ${dbName}`, [
43
44
  Permission.read("any"),
44
45
  Permission.create("users"),
45
46
  Permission.update("users"),
46
47
  Permission.delete("users"),
47
- ]);
48
+ ]));
48
49
  return documentStorage;
49
50
  }
50
51
  };
@@ -61,7 +62,7 @@ export const wipeDocumentStorage = async (storage, config, dbName) => {
61
62
  if (lastFileId) {
62
63
  queries.push(Query.cursorAfter(lastFileId));
63
64
  }
64
- const filesPulled = await storage.listFiles(bucketId, queries);
65
+ const filesPulled = await tryAwaitWithRetry(async () => await storage.listFiles(bucketId, queries));
65
66
  if (filesPulled.files.length === 0) {
66
67
  console.log("No files found, done!");
67
68
  moreFiles = false;
@@ -124,7 +125,7 @@ export const backupDatabase = async (database, databaseId, storage) => {
124
125
  // Fetch and backup the database details
125
126
  let db;
126
127
  try {
127
- db = await database.get(databaseId);
128
+ db = await tryAwaitWithRetry(async () => await database.get(databaseId));
128
129
  }
129
130
  catch (e) {
130
131
  console.error(`Error fetching database: ${e}`);
@@ -146,25 +147,25 @@ export const backupDatabase = async (database, databaseId, storage) => {
146
147
  let progress = 0;
147
148
  let total = 0; // Initialize total to 0, will be updated dynamically
148
149
  while (moreCollections) {
149
- const collectionResponse = await database.listCollections(databaseId, [
150
+ const collectionResponse = await tryAwaitWithRetry(async () => await database.listCollections(databaseId, [
150
151
  Query.limit(500), // Adjust the limit as needed
151
152
  ...(lastCollectionId ? [Query.cursorAfter(lastCollectionId)] : []),
152
- ]);
153
+ ]));
153
154
  total += collectionResponse.collections.length; // Update total with number of collections
154
155
  for (const { $id: collectionId, name: collectionName, } of collectionResponse.collections) {
155
156
  let collectionDocumentCount = 0; // Initialize document count for the current collection
156
157
  try {
157
- const collection = await database.getCollection(databaseId, collectionId);
158
+ const collection = await tryAwaitWithRetry(async () => await database.getCollection(databaseId, collectionId));
158
159
  progress++;
159
160
  data.collections.push(JSON.stringify(collection));
160
161
  // Initialize pagination for documents within the current collection
161
162
  let lastDocumentId = "";
162
163
  let moreDocuments = true;
163
164
  while (moreDocuments) {
164
- const documentResponse = await database.listDocuments(databaseId, collectionId, [
165
+ const documentResponse = await tryAwaitWithRetry(async () => await database.listDocuments(databaseId, collectionId, [
165
166
  Query.limit(500), // Adjust the limit as needed
166
167
  ...(lastDocumentId ? [Query.cursorAfter(lastDocumentId)] : []),
167
- ]);
168
+ ]));
168
169
  total += documentResponse.documents.length; // Update total with number of documents
169
170
  collectionDocumentCount += documentResponse.documents.length; // Update document count for the current collection
170
171
  let documentPromises = [];
@@ -3,6 +3,7 @@ import { AuthUserSchema, } from "../schemas/authUser.js";
3
3
  import _ from "lodash";
4
4
  import { logger } from "./logging.js";
5
5
  import { splitIntoBatches } from "./migrationHelper.js";
6
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
6
7
  export class UsersController {
7
8
  config;
8
9
  users;
@@ -22,49 +23,25 @@ export class UsersController {
22
23
  this.users = new Users(this.config.appwriteClient);
23
24
  }
24
25
  async wipeUsers() {
25
- const users = await this.users.list([Query.limit(200)]);
26
- const allUsers = users.users;
27
- let lastDocumentId;
28
- if (users.users.length >= 200) {
29
- lastDocumentId = users.users[users.users.length - 1].$id;
30
- }
31
- while (lastDocumentId) {
32
- const moreUsers = await this.users.list([
33
- Query.limit(200),
34
- Query.cursorAfter(lastDocumentId),
35
- ]);
36
- allUsers.push(...moreUsers.users);
37
- if (moreUsers.users.length < 200) {
38
- break;
39
- }
40
- lastDocumentId = moreUsers.users[moreUsers.users.length - 1].$id;
41
- }
26
+ const allUsers = await this.getAllUsers();
42
27
  console.log("Deleting all users...");
43
- const createBatches = (finalData) => {
44
- let maxBatchLength = 5;
28
+ const createBatches = (finalData, batchSize) => {
45
29
  const finalBatches = [];
46
- for (let i = 0; i < finalData.length; i++) {
47
- if (i % maxBatchLength === 0) {
48
- finalBatches.push([]);
49
- }
50
- finalBatches[finalBatches.length - 1].push(finalData[i]);
30
+ for (let i = 0; i < finalData.length; i += batchSize) {
31
+ finalBatches.push(finalData.slice(i, i + batchSize));
51
32
  }
52
33
  return finalBatches;
53
34
  };
54
- // const userPromises: Promise<string>[] = [];
55
35
  let usersDeleted = 0;
56
- for (const user of allUsers) {
57
- await this.users.delete(user.$id);
58
- usersDeleted++;
36
+ const batchedUserPromises = createBatches(allUsers, 50); // Batch size of 10
37
+ for (const batch of batchedUserPromises) {
38
+ console.log(`Deleting ${batch.length} users...`);
39
+ await Promise.all(batch.map((user) => tryAwaitWithRetry(async () => await this.users.delete(user.$id))));
40
+ usersDeleted += batch.length;
59
41
  if (usersDeleted % 100 === 0) {
60
42
  console.log(`Deleted ${usersDeleted} users...`);
61
43
  }
62
44
  }
63
- // const batchedUserPromises = createBatches(userPromises);
64
- // for (const batch of batchedUserPromises) {
65
- // console.log(`Deleting ${batch.length} users...`);
66
- // await Promise.all(batch);
67
- // }
68
45
  }
69
46
  async getAllUsers() {
70
47
  const allUsers = [];
@@ -76,10 +53,10 @@ export class UsersController {
76
53
  let lastDocumentId = users.users[users.users.length - 1].$id;
77
54
  allUsers.push(...users.users);
78
55
  while (lastDocumentId) {
79
- const moreUsers = await this.users.list([
56
+ const moreUsers = await tryAwaitWithRetry(async () => await this.users.list([
80
57
  Query.limit(200),
81
58
  Query.cursorAfter(lastDocumentId),
82
- ]);
59
+ ]));
83
60
  allUsers.push(...moreUsers.users);
84
61
  lastDocumentId = moreUsers.users[moreUsers.users.length - 1].$id;
85
62
  if (moreUsers.users.length < 200) {
@@ -113,7 +90,8 @@ export class UsersController {
113
90
  }
114
91
  catch (e) {
115
92
  if (e instanceof AppwriteException) {
116
- if (e.message.includes("fetch failed")) {
93
+ if (e.message.toLowerCase().includes("fetch failed") ||
94
+ e.message.toLowerCase().includes("server error")) {
117
95
  const numberOfAttempts = numAttempts || 0;
118
96
  if (numberOfAttempts > 5) {
119
97
  throw e;
@@ -1,4 +1,4 @@
1
- import type { Models } from "node-appwrite";
1
+ import { type Models } from "node-appwrite";
2
2
  import type { CollectionImportData } from "../migrations/dataLoader.js";
3
3
  import type { ConfigCollection } from "appwrite-utils";
4
4
  export declare const toPascalCase: (str: string) => string;
@@ -35,3 +35,12 @@ export declare const getFileViewUrl: (endpoint: string, projectId: string, bucke
35
35
  */
36
36
  export declare const getFileDownloadUrl: (endpoint: string, projectId: string, bucketId: string, fileId: string, jwt?: Models.Jwt) => string;
37
37
  export declare const finalizeByAttributeMap: (appwriteFolderPath: string, collection: ConfigCollection, item: CollectionImportData["data"][number]) => Promise<any>;
38
+ export declare let numTimesFailedTotal: number;
39
+ /**
40
+ * Tries to execute the given createFunction and retries up to 5 times if it fails.
41
+ *
42
+ * @param {() => Promise<any>} createFunction - The function to be executed.
43
+ * @param {number} [attemptNum=0] - The number of attempts made so far (default: 0).
44
+ * @return {Promise<any>} - A promise that resolves to the result of the createFunction or rejects with an error if it fails after 5 attempts.
45
+ */
46
+ export declare const tryAwaitWithRetry: <T>(createFunction: () => Promise<T>, attemptNum?: number) => Promise<T>;
@@ -1,3 +1,4 @@
1
+ import { AppwriteException } from "node-appwrite";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  export const toPascalCase = (str) => {
@@ -78,3 +79,29 @@ export const finalizeByAttributeMap = async (appwriteFolderPath, collection, ite
78
79
  ...item.finalData,
79
80
  });
80
81
  };
82
+ export let numTimesFailedTotal = 0;
83
+ /**
84
+ * Tries to execute the given createFunction and retries up to 5 times if it fails.
85
+ *
86
+ * @param {() => Promise<any>} createFunction - The function to be executed.
87
+ * @param {number} [attemptNum=0] - The number of attempts made so far (default: 0).
88
+ * @return {Promise<any>} - A promise that resolves to the result of the createFunction or rejects with an error if it fails after 5 attempts.
89
+ */
90
+ export const tryAwaitWithRetry = async (createFunction, attemptNum = 0) => {
91
+ try {
92
+ return await createFunction();
93
+ }
94
+ catch (error) {
95
+ if (error instanceof AppwriteException &&
96
+ (error.message.toLowerCase().includes("fetch failed") ||
97
+ error.message.toLowerCase().includes("server error"))) {
98
+ numTimesFailedTotal++;
99
+ console.log(`Fetch failed on attempt ${attemptNum}. Retrying...`);
100
+ if (attemptNum > 5) {
101
+ throw error;
102
+ }
103
+ return tryAwaitWithRetry(createFunction, attemptNum + 1);
104
+ }
105
+ throw error;
106
+ }
107
+ };
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, writeFileSync, existsSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { findAppwriteConfig } from "./loadConfigs.js";
4
+ import { ID } from "node-appwrite";
4
5
  // Example base configuration using types from appwrite-utils
5
6
  const baseConfig = {
6
7
  appwriteEndpoint: "https://cloud.appwrite.io/v1",
@@ -68,6 +69,9 @@ export const createEmptyCollection = (collectionName) => {
68
69
  const emptyCollection = `import type { CollectionCreate } from "appwrite-utils";
69
70
 
70
71
  const ${collectionName}: Partial<CollectionCreate> = {
72
+ $id: '${ID.unique()}',
73
+ documentSecurity: false,
74
+ enabled: true,
71
75
  name: '${collectionName}',
72
76
  $permissions: [
73
77
  { permission: 'read', target: 'any' },
@@ -4,7 +4,7 @@ import path from "path";
4
4
  import fs from "fs";
5
5
  import { load } from "js-yaml";
6
6
  import { ImportDataActions } from "./migrations/importDataActions.js";
7
- import { readFileSync } from "./utils/helperFunctions.js";
7
+ import { numTimesFailedTotal } from "./utils/helperFunctions.js";
8
8
  import { afterImportActions } from "./migrations/afterImportActions.js";
9
9
  import { converterFunctions, validationRules, AppwriteConfigSchema, } from "appwrite-utils";
10
10
  import { ImportController } from "./migrations/importController.js";
@@ -117,6 +117,7 @@ export class UtilsController {
117
117
  await importController.run();
118
118
  console.log("Import data complete.");
119
119
  }
120
+ console.log("Total number of times Fetch failed: ", numTimesFailedTotal);
120
121
  // if (options.checkDuplicates) {
121
122
  // await this.checkDuplicates();
122
123
  // }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "0.0.36",
4
+ "version": "0.0.38",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@types/inquirer": "^9.0.7",
36
- "appwrite-utils": "^0.2.5",
36
+ "appwrite-utils": "^0.2.6",
37
37
  "commander": "^12.0.0",
38
38
  "inquirer": "^9.2.20",
39
39
  "js-yaml": "^4.1.0",