appwrite-utils-cli 0.0.37 → 0.0.39

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 +1 -0
  2. package/dist/migrations/attributes.js +21 -20
  3. package/dist/migrations/collections.js +10 -13
  4. package/dist/migrations/converters.js +15 -1
  5. package/dist/migrations/dataLoader.d.ts +12 -21
  6. package/dist/migrations/dataLoader.js +168 -58
  7. package/dist/migrations/databases.js +2 -1
  8. package/dist/migrations/importController.js +6 -24
  9. package/dist/migrations/importDataActions.d.ts +9 -1
  10. package/dist/migrations/importDataActions.js +14 -2
  11. package/dist/migrations/indexes.js +2 -1
  12. package/dist/migrations/migrationHelper.js +2 -1
  13. package/dist/migrations/queue.js +3 -2
  14. package/dist/migrations/setupDatabase.js +10 -12
  15. package/dist/migrations/storage.js +14 -13
  16. package/dist/migrations/users.js +14 -36
  17. package/dist/utils/helperFunctions.d.ts +10 -1
  18. package/dist/utils/helperFunctions.js +27 -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/converters.ts +17 -1
  24. package/src/migrations/dataLoader.ts +232 -63
  25. package/src/migrations/databases.ts +4 -1
  26. package/src/migrations/importController.ts +13 -27
  27. package/src/migrations/importDataActions.ts +17 -3
  28. package/src/migrations/indexes.ts +4 -1
  29. package/src/migrations/migrationHelper.ts +9 -5
  30. package/src/migrations/queue.ts +5 -6
  31. package/src/migrations/setupDatabase.ts +35 -18
  32. package/src/migrations/storage.ts +50 -36
  33. package/src/migrations/users.ts +28 -38
  34. package/src/utils/helperFunctions.ts +33 -1
  35. package/src/utilsController.ts +3 -1
@@ -65,6 +65,7 @@ export class DataLoader {
65
65
  * It iterates through the target object's keys and updates the source object if:
66
66
  * - The source object has the key.
67
67
  * - The target object's value for that key is not null, undefined, or an empty string.
68
+ * - If the target object has an array value, it concatenates the values and removes duplicates.
68
69
  *
69
70
  * @param source - The source object to be updated.
70
71
  * @param target - The target object with values to update the source object.
@@ -73,29 +74,51 @@ export class DataLoader {
73
74
  mergeObjects(source, update) {
74
75
  // Create a new object to hold the merged result
75
76
  const result = { ...source };
76
- Object.keys(update).forEach((key) => {
77
- const sourceValue = source[key];
78
- const updateValue = update[key];
79
- // If the update value is an array, concatenate and remove duplicates
80
- if (Array.isArray(updateValue)) {
81
- const sourceArray = Array.isArray(sourceValue) ? sourceValue : [];
82
- result[key] = [...new Set([...sourceArray, ...updateValue])];
83
- }
84
- // If the update value is an object, recursively merge
85
- else if (updateValue !== null &&
86
- typeof updateValue === "object" &&
87
- !(updateValue instanceof Date)) {
88
- result[key] = this.mergeObjects(sourceValue, updateValue);
89
- }
90
- // If the update value is not nullish, overwrite the source value
91
- else if (updateValue !== null && updateValue !== undefined) {
92
- result[key] = updateValue;
93
- }
94
- // If the update value is nullish, keep the original value unless it doesn't exist
95
- else if (sourceValue === undefined || sourceValue === null) {
96
- result[key] = updateValue;
77
+ // Loop through the keys of the object we care about
78
+ for (const [key, value] of Object.entries(source)) {
79
+ // Check if the key exists in the target object
80
+ if (!Object.hasOwn(update, key)) {
81
+ // If the key doesn't exist, we can just skip it like bad cheese
82
+ continue;
97
83
  }
98
- });
84
+ if (update[key] === value) {
85
+ continue;
86
+ }
87
+ // If the value ain't here, we can just do whatever man
88
+ if (value === undefined || value === null || value === "") {
89
+ // If the update key is defined
90
+ if (update[key] !== undefined &&
91
+ update[key] !== null &&
92
+ update[key] !== "") {
93
+ // might as well use it eh?
94
+ result[key] = update[key];
95
+ }
96
+ // ELSE if the value is an array, because it would then not be === to those things above
97
+ }
98
+ else if (Array.isArray(value)) {
99
+ // Get the update value
100
+ const updateValue = update[key];
101
+ // If the update value is an array, concatenate and remove duplicates
102
+ // and poopy data
103
+ if (Array.isArray(updateValue)) {
104
+ result[key] = [...new Set([...value, ...updateValue])].filter((item) => item !== null && item !== undefined && item !== "");
105
+ }
106
+ else {
107
+ // If the update value is not an array, just use it
108
+ result[key] = [...value, updateValue].filter((item) => item !== null && item !== undefined && item !== "");
109
+ }
110
+ }
111
+ else if (typeof value === "object") {
112
+ // If the value is an object, we need to merge it
113
+ if (typeof update[key] === "object") {
114
+ result[key] = this.mergeObjects(value, update[key]);
115
+ }
116
+ }
117
+ else {
118
+ // Finally, the source value is defined, and not an array, so we don't care about the update value
119
+ continue;
120
+ }
121
+ }
99
122
  return result;
100
123
  }
101
124
  // Method to load data from a file specified in the import definition
@@ -125,8 +148,19 @@ export class DataLoader {
125
148
  // Method to generate a unique ID that doesn't conflict with existing IDs
126
149
  getTrueUniqueId(collectionName) {
127
150
  let newId = ID.unique();
128
- while (this.checkMapValuesForId(newId, collectionName)) {
151
+ let condition = this.checkMapValuesForId(newId, collectionName) ||
152
+ this.userExistsMap.has(newId) ||
153
+ this.importMap
154
+ .get(this.getCollectionKey("users"))
155
+ ?.data.some((user) => user.finalData.docId === newId || user.finalData.userId === newId);
156
+ while (condition) {
129
157
  newId = ID.unique();
158
+ condition =
159
+ this.checkMapValuesForId(newId, collectionName) ||
160
+ this.userExistsMap.has(newId) ||
161
+ this.importMap
162
+ .get(this.getCollectionKey("users"))
163
+ ?.data.some((user) => user.finalData.docId === newId || user.finalData.userId === newId);
130
164
  }
131
165
  return newId;
132
166
  }
@@ -153,6 +187,9 @@ export class DataLoader {
153
187
  transformData(item, attributeMappings) {
154
188
  // Convert the item using the attribute mappings provided
155
189
  const convertedItem = convertObjectByAttributeMappings(item, attributeMappings);
190
+ if (item["region"]) {
191
+ logger.info(`Converted item: ${JSON.stringify(convertedItem, null, 2)}`);
192
+ }
156
193
  // Run additional converter functions on the converted item, if any
157
194
  return this.importDataActions.runConverterFunctions(convertedItem, attributeMappings);
158
195
  }
@@ -209,6 +246,21 @@ export class DataLoader {
209
246
  this.phoneToUserIdMap.set(user.phone, user.$id);
210
247
  }
211
248
  this.userExistsMap.set(user.$id, true);
249
+ let importData = this.importMap.get(this.getCollectionKey("users"));
250
+ if (!importData) {
251
+ importData = {
252
+ data: [],
253
+ };
254
+ }
255
+ importData.data.push({
256
+ finalData: {
257
+ ...user,
258
+ userId: user.$id,
259
+ docId: user.$id,
260
+ },
261
+ rawData: user,
262
+ });
263
+ this.importMap.set(this.getCollectionKey("users"), importData);
212
264
  }
213
265
  return allUsers;
214
266
  }
@@ -262,7 +314,7 @@ export class DataLoader {
262
314
  }
263
315
  }
264
316
  console.log("Running update references");
265
- this.dealWithMergedUsers();
317
+ // this.dealWithMergedUsers();
266
318
  this.updateOldReferencesForNew();
267
319
  console.log("Done running update references");
268
320
  }
@@ -280,8 +332,11 @@ export class DataLoader {
280
332
  const usersCollectionKey = this.getCollectionKey(this.config.usersCollectionName);
281
333
  const usersCollectionPrimaryKeyFields = new Set();
282
334
  if (!this.config.collections) {
335
+ console.log("No collections found in configuration.");
283
336
  return;
284
337
  }
338
+ let needsUpdate = false;
339
+ let numUpdates = 0;
285
340
  // Collect primary key fields from the users collection definitions
286
341
  this.config.collections.forEach((collection) => {
287
342
  if (this.getCollectionKey(collection.name) === usersCollectionKey) {
@@ -296,37 +351,61 @@ export class DataLoader {
296
351
  });
297
352
  }
298
353
  });
354
+ console.log(`Primary key fields collected for users collection: ${[
355
+ ...usersCollectionPrimaryKeyFields,
356
+ ]}`);
299
357
  // Iterate over all collections to update references based on merged users
300
358
  this.config.collections.forEach((collection) => {
301
359
  const collectionData = this.importMap.get(this.getCollectionKey(collection.name));
302
- if (!collectionData || !collectionData.data)
360
+ if (!collectionData || !collectionData.data) {
361
+ console.log(`No data found for collection ${collection.name}`);
303
362
  return;
363
+ }
304
364
  const collectionImportDefs = collection.importDefs;
305
365
  if (!collectionImportDefs || !collectionImportDefs.length) {
366
+ console.log(`No import definitions found for collection ${collection.name}`);
306
367
  return;
307
368
  }
308
369
  collectionImportDefs.forEach((importDef) => {
309
370
  importDef.idMappings?.forEach((idMapping) => {
310
371
  if (this.getCollectionKey(idMapping.targetCollection) ===
311
372
  usersCollectionKey) {
373
+ const fieldToSetKey = idMapping.fieldToSet || idMapping.sourceField;
312
374
  const targetFieldKey = idMapping.targetFieldToMatch || idMapping.targetField;
313
375
  if (usersCollectionPrimaryKeyFields.has(targetFieldKey)) {
376
+ console.log(`Processing collection ${collection.name} with target field ${targetFieldKey}`);
314
377
  // Process each item in the collection
315
378
  collectionData.data.forEach((item) => {
316
- const oldId = item.context[idMapping.sourceField];
379
+ const oldId = item.finalData[idMapping.sourceField] ||
380
+ item.context[idMapping.sourceField];
381
+ if (oldId === undefined || oldId === null) {
382
+ console.log(`Skipping item with undefined or null oldId in collection ${collection.name}`);
383
+ return;
384
+ }
317
385
  const newId = this.mergedUserMap.get(`${oldId}`);
318
386
  if (newId) {
387
+ needsUpdate = true;
388
+ numUpdates++;
389
+ console.log(`Updating old ID ${oldId} to new ID ${newId} in collection ${collection.name}`);
319
390
  // Update context to use new user ID
320
- item.finalData[idMapping.fieldToSet || idMapping.sourceField] = newId;
391
+ item.finalData[fieldToSetKey] = newId;
392
+ item.context[fieldToSetKey] = newId;
393
+ }
394
+ else {
395
+ console.log(`No new ID found for old ID ${oldId} in mergedUserMap.`);
321
396
  }
322
397
  });
323
398
  }
324
399
  }
325
400
  });
326
401
  });
402
+ if (needsUpdate) {
403
+ console.log(`Updated ${numUpdates} references for collection ${collection.name}`);
404
+ this.importMap.set(this.getCollectionKey(collection.name), collectionData);
405
+ }
327
406
  });
328
407
  }
329
- async updateOldReferencesForNew() {
408
+ updateOldReferencesForNew() {
330
409
  if (!this.config.collections) {
331
410
  return;
332
411
  }
@@ -348,7 +427,9 @@ export class DataLoader {
348
427
  const targetFieldKey = idMapping.targetFieldToMatch || idMapping.targetField;
349
428
  const valueToMatch = collectionData.data[i].context[idMapping.sourceField];
350
429
  // Skip if value to match is missing or empty
351
- if (!valueToMatch || _.isEmpty(valueToMatch))
430
+ if (!valueToMatch ||
431
+ _.isEmpty(valueToMatch) ||
432
+ valueToMatch === null)
352
433
  continue;
353
434
  const isFieldToSetArray = collectionConfig.attributes.find((attribute) => attribute.key === fieldToSetKey)?.array;
354
435
  const targetCollectionData = this.importMap.get(targetCollectionKey);
@@ -467,7 +548,7 @@ export class DataLoader {
467
548
  * @param attributeMappings - The attribute mappings for the item.
468
549
  * @returns The transformed item with user-specific keys removed.
469
550
  */
470
- async prepareUserData(item, attributeMappings, primaryKeyField, newId) {
551
+ prepareUserData(item, attributeMappings, primaryKeyField, newId) {
471
552
  let transformedItem = this.transformData(item, attributeMappings);
472
553
  const userData = AuthUserCreateSchema.safeParse(transformedItem);
473
554
  if (!userData.success) {
@@ -515,15 +596,23 @@ export class DataLoader {
515
596
  });
516
597
  if (userFound) {
517
598
  userFound.finalData.userId = existingId;
599
+ userFound.finalData.docId = existingId;
600
+ this.userExistsMap.set(existingId, true);
518
601
  }
519
- return [
602
+ const userKeys = ["email", "phone", "name", "labels", "prefs"];
603
+ userKeys.forEach((key) => {
604
+ if (transformedItem.hasOwnProperty(key)) {
605
+ delete transformedItem[key];
606
+ }
607
+ });
608
+ return {
520
609
  transformedItem,
521
610
  existingId,
522
- {
611
+ userData: {
523
612
  rawData: userFound?.rawData,
524
613
  finalData: userFound?.finalData,
525
614
  },
526
- ];
615
+ };
527
616
  }
528
617
  else {
529
618
  existingId = newId;
@@ -543,7 +632,11 @@ export class DataLoader {
543
632
  this.importMap.set(this.getCollectionKey("users"), {
544
633
  data: [...(usersMap?.data || []), userDataToAdd],
545
634
  });
546
- return [transformedItem, existingId, userDataToAdd];
635
+ return {
636
+ transformedItem,
637
+ existingId,
638
+ userData: userDataToAdd,
639
+ };
547
640
  }
548
641
  /**
549
642
  * Prepares the data for creating user collection documents.
@@ -588,7 +681,7 @@ export class DataLoader {
588
681
  // Iterate through each item in the raw data
589
682
  for (const item of rawData) {
590
683
  // Prepare user data, check for duplicates, and remove user-specific keys
591
- let [transformedItem, existingId, userData] = await this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
684
+ let { transformedItem, existingId, userData } = this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
592
685
  logger.info(`In create user -- transformedItem: ${JSON.stringify(transformedItem, null, 2)}`);
593
686
  // Generate a new unique ID for the item or use existing ID
594
687
  if (!existingId) {
@@ -620,15 +713,22 @@ export class DataLoader {
620
713
  collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
621
714
  }
622
715
  }
623
- // Merge the final user data into the context
624
- context = { ...context, ...userData.finalData };
625
716
  // Handle merging for currentUserData
626
717
  for (let i = 0; i < currentUserData.data.length; i++) {
627
- if ((currentUserData.data[i].finalData.docId === existingId ||
628
- currentUserData.data[i].finalData.userId === existingId) &&
629
- !_.isEqual(currentUserData.data[i], userData)) {
630
- this.mergeObjects(currentUserData.data[i].finalData, userData.finalData);
631
- console.log("Merging user data", currentUserData.data[i].finalData);
718
+ const currentUserDataItem = currentUserData.data[i];
719
+ const samePhones = currentUserDataItem.finalData.phone &&
720
+ transformedItem.phone &&
721
+ currentUserDataItem.finalData.phone === transformedItem.phone;
722
+ const sameEmails = currentUserDataItem.finalData.email &&
723
+ transformedItem.email &&
724
+ currentUserDataItem.finalData.email === transformedItem.email;
725
+ if ((currentUserDataItem.finalData.docId === existingId ||
726
+ currentUserDataItem.finalData.userId === existingId) &&
727
+ (samePhones || sameEmails) &&
728
+ currentUserDataItem.finalData &&
729
+ userData.finalData) {
730
+ const userDataMerged = this.mergeObjects(currentUserData.data[i].finalData, userData.finalData);
731
+ currentUserData.data[i].finalData = userDataMerged;
632
732
  this.importMap.set(this.getCollectionKey("users"), currentUserData);
633
733
  }
634
734
  }
@@ -691,16 +791,23 @@ export class DataLoader {
691
791
  .set(this.getCollectionKey(collection.name), oldIdToNewIdMapNew)
692
792
  .get(this.getCollectionKey(collection.name));
693
793
  console.log(`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`);
794
+ const isRegions = collection.name.toLowerCase() === "regions";
694
795
  // Iterate through each item in the raw data
695
796
  for (const item of rawData) {
696
797
  // Generate a new unique ID for the item
697
798
  const itemIdNew = this.getTrueUniqueId(this.getCollectionKey(collection.name));
799
+ if (isRegions) {
800
+ logger.info(`Creating region: ${JSON.stringify(item, null, 2)}`);
801
+ }
698
802
  // Retrieve the current collection data from the import map
699
803
  const currentData = this.importMap.get(this.getCollectionKey(collection.name));
700
804
  // Create a context object for the item, including the new ID
701
805
  let context = this.createContext(db, collection, item, itemIdNew);
702
806
  // Transform the item data based on the attribute mappings
703
807
  const transformedData = this.transformData(item, importDef.attributeMappings);
808
+ if (isRegions) {
809
+ logger.info(`Transformed region: ${JSON.stringify(transformedData, null, 2)}`);
810
+ }
704
811
  // If a primary key field is defined, handle the ID mapping and check for duplicates
705
812
  if (importDef.primaryKeyField) {
706
813
  const oldId = item[importDef.primaryKeyField];
@@ -713,7 +820,7 @@ export class DataLoader {
713
820
  // Merge the transformed data into the context
714
821
  context = { ...context, ...transformedData };
715
822
  // Validate the item before proceeding
716
- const isValid = await this.importDataActions.validateItem(transformedData, importDef.attributeMappings, context);
823
+ const isValid = this.importDataActions.validateItem(transformedData, importDef.attributeMappings, context);
717
824
  if (!isValid) {
718
825
  continue;
719
826
  }
@@ -751,7 +858,7 @@ export class DataLoader {
751
858
  * @param collection - The collection configuration.
752
859
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
753
860
  */
754
- async prepareUpdateData(db, collection, importDef) {
861
+ prepareUpdateData(db, collection, importDef) {
755
862
  // Retrieve the current collection data and old-to-new ID map from the import map
756
863
  const currentData = this.importMap.get(this.getCollectionKey(collection.name));
757
864
  const oldIdToNewIdMap = this.oldIdToNewIdPerCollectionMap.get(this.getCollectionKey(collection.name));
@@ -773,12 +880,7 @@ export class DataLoader {
773
880
  let newId;
774
881
  let oldId;
775
882
  // Determine the new ID for the item based on the primary key field or update mapping
776
- if (importDef.primaryKeyField) {
777
- oldId = item[importDef.primaryKeyField];
778
- }
779
- else if (importDef.updateMapping) {
780
- oldId = item[importDef.updateMapping.originalIdField];
781
- }
883
+ oldId = item[importDef.primaryKeyField];
782
884
  if (oldId) {
783
885
  newId = oldIdToNewIdMap?.get(`${oldId}`);
784
886
  if (!newId &&
@@ -796,24 +898,31 @@ export class DataLoader {
796
898
  logger.error(`No old ID found (to update another document with) in prepareUpdateData for ${collection.name}, ${JSON.stringify(item, null, 2)}`);
797
899
  continue;
798
900
  }
901
+ const itemDataToUpdate = this.importMap
902
+ .get(this.getCollectionKey(collection.name))
903
+ ?.data.find((data) => `${data.context[importDef.primaryKeyField]}` === `${oldId}`);
799
904
  // Log an error and continue to the next item if no new ID is found
800
- if (!newId) {
905
+ if (!newId && !itemDataToUpdate) {
801
906
  logger.error(`No new id found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but it says it's supposed to have one...`);
802
907
  continue;
803
908
  }
804
- const itemDataToUpdate = this.importMap
805
- .get(this.getCollectionKey(collection.name))
806
- ?.data.find((data) => data.rawData[importDef.primaryKeyField] === oldId);
807
- if (!itemDataToUpdate) {
808
- logger.error(`No data found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but it says it's supposed to have one...`);
909
+ else if (itemDataToUpdate) {
910
+ newId = itemDataToUpdate.finalData.docId;
911
+ if (!newId) {
912
+ logger.error(`No new id found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but it says it's supposed to have one...`);
913
+ continue;
914
+ }
915
+ }
916
+ if (!itemDataToUpdate || !newId) {
917
+ logger.error(`No data or ID (docId) found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but it says it's supposed to have one...`);
809
918
  continue;
810
919
  }
811
920
  transformedData = this.mergeObjects(itemDataToUpdate.finalData, transformedData);
812
921
  // Create a context object for the item, including the new ID and transformed data
813
922
  let context = this.createContext(db, collection, item, newId);
814
- context = this.mergeObjects(context, transformedData);
923
+ context = { ...context, ...transformedData };
815
924
  // Validate the item before proceeding
816
- const isValid = await this.importDataActions.validateItem(item, importDef.attributeMappings, context);
925
+ const isValid = this.importDataActions.validateItem(item, importDef.attributeMappings, context);
817
926
  // Log info and continue to the next item if it's invalid
818
927
  if (!isValid) {
819
928
  logger.info(`Skipping item: ${JSON.stringify(item, null, 2)} because it's invalid`);
@@ -832,6 +941,7 @@ export class DataLoader {
832
941
  itemDataToUpdate.finalData = this.mergeObjects(itemDataToUpdate.finalData, transformedData);
833
942
  itemDataToUpdate.context = context;
834
943
  itemDataToUpdate.importDef = newImportDef;
944
+ currentData.data.push(itemDataToUpdate);
835
945
  }
836
946
  else {
837
947
  // If no existing item matches, then add the new item
@@ -1,6 +1,7 @@
1
1
  import { Databases, Query } from "node-appwrite";
2
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
2
3
  export const fetchAllDatabases = async (database) => {
3
- const databases = await database.list([Query.limit(25)]);
4
+ const databases = await tryAwaitWithRetry(async () => await database.list([Query.limit(25)]));
4
5
  const allDatabases = databases.databases;
5
6
  let lastDatabaseId = allDatabases[allDatabases.length - 1].$id;
6
7
  if (databases.databases.length < 25) {
@@ -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);
@@ -11,16 +11,24 @@ export declare class ImportDataActions {
11
11
  private validityRuleDefinitions;
12
12
  private afterImportActionsDefinitions;
13
13
  constructor(db: Databases, storage: Storage, config: AppwriteConfig, converterDefinitions: ConverterFunctions, validityRuleDefinitions: ValidationRules, afterImportActionsDefinitions: AfterImportActions);
14
+ /**
15
+ * Runs converter functions on the item based on the provided attribute mappings.
16
+ *
17
+ * @param item - The item to be transformed.
18
+ * @param attributeMappings - The mappings that define how each attribute should be transformed.
19
+ * @returns The transformed item.
20
+ */
14
21
  runConverterFunctions(item: any, attributeMappings: AttributeMappings): any;
15
22
  /**
16
23
  * Validates a single data item based on defined validation rules.
17
24
  * @param item The data item to validate.
25
+ * @param attributeMap The attribute mappings for the data item.
18
26
  * @param context The context for resolving templated parameters in validation rules.
19
27
  * @returns A promise that resolves to true if the item is valid, false otherwise.
20
28
  */
21
29
  validateItem(item: any, attributeMap: AttributeMappings, context: {
22
30
  [key: string]: any;
23
- }): Promise<boolean>;
31
+ }): boolean;
24
32
  executeAfterImportActions(item: any, attributeMap: AttributeMappings, context: {
25
33
  [key: string]: any;
26
34
  }): 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;
@@ -20,9 +21,19 @@ export class ImportDataActions {
20
21
  this.validityRuleDefinitions = validityRuleDefinitions;
21
22
  this.afterImportActionsDefinitions = afterImportActionsDefinitions;
22
23
  }
24
+ /**
25
+ * Runs converter functions on the item based on the provided attribute mappings.
26
+ *
27
+ * @param item - The item to be transformed.
28
+ * @param attributeMappings - The mappings that define how each attribute should be transformed.
29
+ * @returns The transformed item.
30
+ */
23
31
  runConverterFunctions(item, attributeMappings) {
24
32
  const conversionSchema = attributeMappings.reduce((schema, mapping) => {
25
33
  schema[mapping.targetKey] = (originalValue) => {
34
+ if (!mapping.converters) {
35
+ return originalValue;
36
+ }
26
37
  return mapping.converters?.reduce((value, converterName) => {
27
38
  let shouldProcessAsArray = false;
28
39
  if ((converterName.includes("[Arr]") ||
@@ -65,10 +76,11 @@ export class ImportDataActions {
65
76
  /**
66
77
  * Validates a single data item based on defined validation rules.
67
78
  * @param item The data item to validate.
79
+ * @param attributeMap The attribute mappings for the data item.
68
80
  * @param context The context for resolving templated parameters in validation rules.
69
81
  * @returns A promise that resolves to true if the item is valid, false otherwise.
70
82
  */
71
- async validateItem(item, attributeMap, context) {
83
+ validateItem(item, attributeMap, context) {
72
84
  for (const mapping of attributeMap) {
73
85
  const { validationActions } = mapping;
74
86
  if (!validationActions ||
@@ -111,7 +123,7 @@ export class ImportDataActions {
111
123
  const { action, params } = actionDef;
112
124
  console.log(`Executing post-import action '${action}' for attribute '${mapping.targetKey}' with params ${params.join(", ")}...`);
113
125
  try {
114
- await this.executeAction(action, params, context, item);
126
+ await tryAwaitWithRetry(async () => await this.executeAction(action, params, context, item));
115
127
  }
116
128
  catch (error) {
117
129
  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}`);