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
@@ -93,6 +93,7 @@ export class DataLoader {
93
93
  * It iterates through the target object's keys and updates the source object if:
94
94
  * - The source object has the key.
95
95
  * - The target object's value for that key is not null, undefined, or an empty string.
96
+ * - If the target object has an array value, it concatenates the values and removes duplicates.
96
97
  *
97
98
  * @param source - The source object to be updated.
98
99
  * @param target - The target object with values to update the source object.
@@ -102,32 +103,53 @@ export class DataLoader {
102
103
  // Create a new object to hold the merged result
103
104
  const result = { ...source };
104
105
 
105
- Object.keys(update).forEach((key) => {
106
- const sourceValue = source[key];
107
- const updateValue = update[key];
108
-
109
- // If the update value is an array, concatenate and remove duplicates
110
- if (Array.isArray(updateValue)) {
111
- const sourceArray = Array.isArray(sourceValue) ? sourceValue : [];
112
- result[key] = [...new Set([...sourceArray, ...updateValue])];
113
- }
114
- // If the update value is an object, recursively merge
115
- else if (
116
- updateValue !== null &&
117
- typeof updateValue === "object" &&
118
- !(updateValue instanceof Date)
119
- ) {
120
- result[key] = this.mergeObjects(sourceValue, updateValue);
106
+ // Loop through the keys of the object we care about
107
+ for (const [key, value] of Object.entries(source)) {
108
+ // Check if the key exists in the target object
109
+ if (!Object.hasOwn(update, key)) {
110
+ // If the key doesn't exist, we can just skip it like bad cheese
111
+ continue;
121
112
  }
122
- // If the update value is not nullish, overwrite the source value
123
- else if (updateValue !== null && updateValue !== undefined) {
124
- result[key] = updateValue;
113
+ if (update[key] === value) {
114
+ continue;
125
115
  }
126
- // If the update value is nullish, keep the original value unless it doesn't exist
127
- else if (sourceValue === undefined || sourceValue === null) {
128
- result[key] = updateValue;
116
+ // If the value ain't here, we can just do whatever man
117
+ if (value === undefined || value === null || value === "") {
118
+ // If the update key is defined
119
+ if (
120
+ update[key] !== undefined &&
121
+ update[key] !== null &&
122
+ update[key] !== ""
123
+ ) {
124
+ // might as well use it eh?
125
+ result[key] = update[key];
126
+ }
127
+ // ELSE if the value is an array, because it would then not be === to those things above
128
+ } else if (Array.isArray(value)) {
129
+ // Get the update value
130
+ const updateValue = update[key];
131
+ // If the update value is an array, concatenate and remove duplicates
132
+ // and poopy data
133
+ if (Array.isArray(updateValue)) {
134
+ result[key] = [...new Set([...value, ...updateValue])].filter(
135
+ (item) => item !== null && item !== undefined && item !== ""
136
+ );
137
+ } else {
138
+ // If the update value is not an array, just use it
139
+ result[key] = [...value, updateValue].filter(
140
+ (item) => item !== null && item !== undefined && item !== ""
141
+ );
142
+ }
143
+ } else if (typeof value === "object") {
144
+ // If the value is an object, we need to merge it
145
+ if (typeof update[key] === "object") {
146
+ result[key] = this.mergeObjects(value, update[key]);
147
+ }
148
+ } else {
149
+ // Finally, the source value is defined, and not an array, so we don't care about the update value
150
+ continue;
129
151
  }
130
- });
152
+ }
131
153
 
132
154
  return result;
133
155
  }
@@ -162,8 +184,26 @@ export class DataLoader {
162
184
  // Method to generate a unique ID that doesn't conflict with existing IDs
163
185
  getTrueUniqueId(collectionName: string) {
164
186
  let newId = ID.unique();
165
- while (this.checkMapValuesForId(newId, collectionName)) {
187
+ let condition =
188
+ this.checkMapValuesForId(newId, collectionName) ||
189
+ this.userExistsMap.has(newId) ||
190
+ this.importMap
191
+ .get(this.getCollectionKey("users"))
192
+ ?.data.some(
193
+ (user) =>
194
+ user.finalData.docId === newId || user.finalData.userId === newId
195
+ );
196
+ while (condition) {
166
197
  newId = ID.unique();
198
+ condition =
199
+ this.checkMapValuesForId(newId, collectionName) ||
200
+ this.userExistsMap.has(newId) ||
201
+ this.importMap
202
+ .get(this.getCollectionKey("users"))
203
+ ?.data.some(
204
+ (user) =>
205
+ user.finalData.docId === newId || user.finalData.userId === newId
206
+ );
167
207
  }
168
208
  return newId;
169
209
  }
@@ -200,6 +240,9 @@ export class DataLoader {
200
240
  item,
201
241
  attributeMappings
202
242
  );
243
+ if (item["region"]) {
244
+ logger.info(`Converted item: ${JSON.stringify(convertedItem, null, 2)}`);
245
+ }
203
246
  // Run additional converter functions on the converted item, if any
204
247
  return this.importDataActions.runConverterFunctions(
205
248
  convertedItem,
@@ -271,6 +314,21 @@ export class DataLoader {
271
314
  this.phoneToUserIdMap.set(user.phone, user.$id);
272
315
  }
273
316
  this.userExistsMap.set(user.$id, true);
317
+ let importData = this.importMap.get(this.getCollectionKey("users"));
318
+ if (!importData) {
319
+ importData = {
320
+ data: [],
321
+ };
322
+ }
323
+ importData.data.push({
324
+ finalData: {
325
+ ...user,
326
+ userId: user.$id,
327
+ docId: user.$id,
328
+ },
329
+ rawData: user,
330
+ });
331
+ this.importMap.set(this.getCollectionKey("users"), importData);
274
332
  }
275
333
  return allUsers;
276
334
  }
@@ -335,7 +393,7 @@ export class DataLoader {
335
393
  }
336
394
  }
337
395
  console.log("Running update references");
338
- this.dealWithMergedUsers();
396
+ // this.dealWithMergedUsers();
339
397
  this.updateOldReferencesForNew();
340
398
  console.log("Done running update references");
341
399
  }
@@ -355,9 +413,15 @@ export class DataLoader {
355
413
  this.config.usersCollectionName
356
414
  );
357
415
  const usersCollectionPrimaryKeyFields = new Set();
416
+
358
417
  if (!this.config.collections) {
418
+ console.log("No collections found in configuration.");
359
419
  return;
360
420
  }
421
+
422
+ let needsUpdate = false;
423
+ let numUpdates = 0;
424
+
361
425
  // Collect primary key fields from the users collection definitions
362
426
  this.config.collections.forEach((collection) => {
363
427
  if (this.getCollectionKey(collection.name) === usersCollectionKey) {
@@ -373,45 +437,95 @@ export class DataLoader {
373
437
  }
374
438
  });
375
439
 
440
+ console.log(
441
+ `Primary key fields collected for users collection: ${[
442
+ ...usersCollectionPrimaryKeyFields,
443
+ ]}`
444
+ );
445
+
376
446
  // Iterate over all collections to update references based on merged users
377
447
  this.config.collections.forEach((collection) => {
378
448
  const collectionData = this.importMap.get(
379
449
  this.getCollectionKey(collection.name)
380
450
  );
381
- if (!collectionData || !collectionData.data) return;
451
+
452
+ if (!collectionData || !collectionData.data) {
453
+ console.log(`No data found for collection ${collection.name}`);
454
+ return;
455
+ }
456
+
382
457
  const collectionImportDefs = collection.importDefs;
383
458
  if (!collectionImportDefs || !collectionImportDefs.length) {
459
+ console.log(
460
+ `No import definitions found for collection ${collection.name}`
461
+ );
384
462
  return;
385
463
  }
464
+
386
465
  collectionImportDefs.forEach((importDef) => {
387
466
  importDef.idMappings?.forEach((idMapping) => {
388
467
  if (
389
468
  this.getCollectionKey(idMapping.targetCollection) ===
390
469
  usersCollectionKey
391
470
  ) {
471
+ const fieldToSetKey = idMapping.fieldToSet || idMapping.sourceField;
392
472
  const targetFieldKey =
393
473
  idMapping.targetFieldToMatch || idMapping.targetField;
474
+
394
475
  if (usersCollectionPrimaryKeyFields.has(targetFieldKey)) {
476
+ console.log(
477
+ `Processing collection ${collection.name} with target field ${targetFieldKey}`
478
+ );
479
+
395
480
  // Process each item in the collection
396
481
  collectionData.data.forEach((item) => {
397
- const oldId = item.context[idMapping.sourceField];
482
+ const oldId =
483
+ item.finalData[idMapping.sourceField] ||
484
+ item.context[idMapping.sourceField];
485
+
486
+ if (oldId === undefined || oldId === null) {
487
+ console.log(
488
+ `Skipping item with undefined or null oldId in collection ${collection.name}`
489
+ );
490
+ return;
491
+ }
492
+
398
493
  const newId = this.mergedUserMap.get(`${oldId}`);
399
494
 
400
495
  if (newId) {
496
+ needsUpdate = true;
497
+ numUpdates++;
498
+ console.log(
499
+ `Updating old ID ${oldId} to new ID ${newId} in collection ${collection.name}`
500
+ );
501
+
401
502
  // Update context to use new user ID
402
- item.finalData[
403
- idMapping.fieldToSet || idMapping.sourceField
404
- ] = newId;
503
+ item.finalData[fieldToSetKey] = newId;
504
+ item.context[fieldToSetKey] = newId;
505
+ } else {
506
+ console.log(
507
+ `No new ID found for old ID ${oldId} in mergedUserMap.`
508
+ );
405
509
  }
406
510
  });
407
511
  }
408
512
  }
409
513
  });
410
514
  });
515
+
516
+ if (needsUpdate) {
517
+ console.log(
518
+ `Updated ${numUpdates} references for collection ${collection.name}`
519
+ );
520
+ this.importMap.set(
521
+ this.getCollectionKey(collection.name),
522
+ collectionData
523
+ );
524
+ }
411
525
  });
412
526
  }
413
527
 
414
- async updateOldReferencesForNew() {
528
+ updateOldReferencesForNew() {
415
529
  if (!this.config.collections) {
416
530
  return;
417
531
  }
@@ -445,7 +559,12 @@ export class DataLoader {
445
559
  collectionData.data[i].context[idMapping.sourceField];
446
560
 
447
561
  // Skip if value to match is missing or empty
448
- if (!valueToMatch || _.isEmpty(valueToMatch)) continue;
562
+ if (
563
+ !valueToMatch ||
564
+ _.isEmpty(valueToMatch) ||
565
+ valueToMatch === null
566
+ )
567
+ continue;
449
568
 
450
569
  const isFieldToSetArray = collectionConfig.attributes.find(
451
570
  (attribute) => attribute.key === fieldToSetKey
@@ -612,12 +731,19 @@ export class DataLoader {
612
731
  * @param attributeMappings - The attribute mappings for the item.
613
732
  * @returns The transformed item with user-specific keys removed.
614
733
  */
615
- async prepareUserData(
734
+ prepareUserData(
616
735
  item: any,
617
736
  attributeMappings: AttributeMappings,
618
737
  primaryKeyField: string,
619
738
  newId: string
620
- ): Promise<any> {
739
+ ): {
740
+ transformedItem: any;
741
+ existingId: string | undefined;
742
+ userData: {
743
+ rawData: any;
744
+ finalData: z.infer<typeof AuthUserCreateSchema>;
745
+ };
746
+ } {
621
747
  let transformedItem = this.transformData(item, attributeMappings);
622
748
  const userData = AuthUserCreateSchema.safeParse(transformedItem);
623
749
  if (!userData.success) {
@@ -666,15 +792,24 @@ export class DataLoader {
666
792
  });
667
793
  if (userFound) {
668
794
  userFound.finalData.userId = existingId;
795
+ userFound.finalData.docId = existingId;
796
+ this.userExistsMap.set(existingId, true);
669
797
  }
670
- return [
798
+
799
+ const userKeys = ["email", "phone", "name", "labels", "prefs"];
800
+ userKeys.forEach((key) => {
801
+ if (transformedItem.hasOwnProperty(key)) {
802
+ delete transformedItem[key];
803
+ }
804
+ });
805
+ return {
671
806
  transformedItem,
672
807
  existingId,
673
- {
808
+ userData: {
674
809
  rawData: userFound?.rawData,
675
810
  finalData: userFound?.finalData,
676
811
  },
677
- ];
812
+ };
678
813
  } else {
679
814
  existingId = newId;
680
815
  userData.data.userId = existingId;
@@ -696,7 +831,11 @@ export class DataLoader {
696
831
  data: [...(usersMap?.data || []), userDataToAdd],
697
832
  });
698
833
 
699
- return [transformedItem, existingId, userDataToAdd];
834
+ return {
835
+ transformedItem,
836
+ existingId,
837
+ userData: userDataToAdd,
838
+ };
700
839
  }
701
840
 
702
841
  /**
@@ -760,7 +899,7 @@ export class DataLoader {
760
899
  // Iterate through each item in the raw data
761
900
  for (const item of rawData) {
762
901
  // Prepare user data, check for duplicates, and remove user-specific keys
763
- let [transformedItem, existingId, userData] = await this.prepareUserData(
902
+ let { transformedItem, existingId, userData } = this.prepareUserData(
764
903
  item,
765
904
  importDef.attributeMappings,
766
905
  importDef.primaryKeyField,
@@ -815,21 +954,30 @@ export class DataLoader {
815
954
  collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
816
955
  }
817
956
  }
818
- // Merge the final user data into the context
819
- context = { ...context, ...userData.finalData };
820
957
 
821
958
  // Handle merging for currentUserData
822
959
  for (let i = 0; i < currentUserData.data.length; i++) {
960
+ const currentUserDataItem = currentUserData.data[i];
961
+ const samePhones =
962
+ currentUserDataItem.finalData.phone &&
963
+ transformedItem.phone &&
964
+ currentUserDataItem.finalData.phone === transformedItem.phone;
965
+ const sameEmails =
966
+ currentUserDataItem.finalData.email &&
967
+ transformedItem.email &&
968
+ currentUserDataItem.finalData.email === transformedItem.email;
823
969
  if (
824
- (currentUserData.data[i].finalData.docId === existingId ||
825
- currentUserData.data[i].finalData.userId === existingId) &&
826
- !_.isEqual(currentUserData.data[i], userData)
970
+ (currentUserDataItem.finalData.docId === existingId ||
971
+ currentUserDataItem.finalData.userId === existingId) &&
972
+ (samePhones || sameEmails) &&
973
+ currentUserDataItem.finalData &&
974
+ userData.finalData
827
975
  ) {
828
- this.mergeObjects(
976
+ const userDataMerged = this.mergeObjects(
829
977
  currentUserData.data[i].finalData,
830
978
  userData.finalData
831
979
  );
832
- console.log("Merging user data", currentUserData.data[i].finalData);
980
+ currentUserData.data[i].finalData = userDataMerged;
833
981
  this.importMap.set(this.getCollectionKey("users"), currentUserData);
834
982
  }
835
983
  }
@@ -925,12 +1073,16 @@ export class DataLoader {
925
1073
  console.log(
926
1074
  `${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`
927
1075
  );
1076
+ const isRegions = collection.name.toLowerCase() === "regions";
928
1077
  // Iterate through each item in the raw data
929
1078
  for (const item of rawData) {
930
1079
  // Generate a new unique ID for the item
931
1080
  const itemIdNew = this.getTrueUniqueId(
932
1081
  this.getCollectionKey(collection.name)
933
1082
  );
1083
+ if (isRegions) {
1084
+ logger.info(`Creating region: ${JSON.stringify(item, null, 2)}`);
1085
+ }
934
1086
  // Retrieve the current collection data from the import map
935
1087
  const currentData = this.importMap.get(
936
1088
  this.getCollectionKey(collection.name)
@@ -942,6 +1094,11 @@ export class DataLoader {
942
1094
  item,
943
1095
  importDef.attributeMappings
944
1096
  );
1097
+ if (isRegions) {
1098
+ logger.info(
1099
+ `Transformed region: ${JSON.stringify(transformedData, null, 2)}`
1100
+ );
1101
+ }
945
1102
  // If a primary key field is defined, handle the ID mapping and check for duplicates
946
1103
  if (importDef.primaryKeyField) {
947
1104
  const oldId = item[importDef.primaryKeyField];
@@ -956,7 +1113,7 @@ export class DataLoader {
956
1113
  // Merge the transformed data into the context
957
1114
  context = { ...context, ...transformedData };
958
1115
  // Validate the item before proceeding
959
- const isValid = await this.importDataActions.validateItem(
1116
+ const isValid = this.importDataActions.validateItem(
960
1117
  transformedData,
961
1118
  importDef.attributeMappings,
962
1119
  context
@@ -996,6 +1153,7 @@ export class DataLoader {
996
1153
  }
997
1154
  }
998
1155
  }
1156
+
999
1157
  /**
1000
1158
  * Prepares the data for updating documents within a collection.
1001
1159
  * This method loads the raw data based on the import definition, transforms it according to the attribute mappings,
@@ -1006,7 +1164,7 @@ export class DataLoader {
1006
1164
  * @param collection - The collection configuration.
1007
1165
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
1008
1166
  */
1009
- async prepareUpdateData(
1167
+ prepareUpdateData(
1010
1168
  db: ConfigDatabase,
1011
1169
  collection: CollectionCreate,
1012
1170
  importDef: ImportDef
@@ -1047,11 +1205,7 @@ export class DataLoader {
1047
1205
  let newId: string | undefined;
1048
1206
  let oldId: string | undefined;
1049
1207
  // Determine the new ID for the item based on the primary key field or update mapping
1050
- if (importDef.primaryKeyField) {
1051
- oldId = item[importDef.primaryKeyField];
1052
- } else if (importDef.updateMapping) {
1053
- oldId = item[importDef.updateMapping.originalIdField];
1054
- }
1208
+ oldId = item[importDef.primaryKeyField];
1055
1209
  if (oldId) {
1056
1210
  newId = oldIdToNewIdMap?.get(`${oldId}`);
1057
1211
  if (
@@ -1074,8 +1228,13 @@ export class DataLoader {
1074
1228
  );
1075
1229
  continue;
1076
1230
  }
1231
+ const itemDataToUpdate = this.importMap
1232
+ .get(this.getCollectionKey(collection.name))
1233
+ ?.data.find(
1234
+ (data) => `${data.context[importDef.primaryKeyField]}` === `${oldId}`
1235
+ );
1077
1236
  // Log an error and continue to the next item if no new ID is found
1078
- if (!newId) {
1237
+ if (!newId && !itemDataToUpdate) {
1079
1238
  logger.error(
1080
1239
  `No new id found for collection ${
1081
1240
  collection.name
@@ -1086,15 +1245,24 @@ export class DataLoader {
1086
1245
  )} but it says it's supposed to have one...`
1087
1246
  );
1088
1247
  continue;
1248
+ } else if (itemDataToUpdate) {
1249
+ newId = itemDataToUpdate.finalData.docId;
1250
+ if (!newId) {
1251
+ logger.error(
1252
+ `No new id found for collection ${
1253
+ collection.name
1254
+ } for updateDef ${JSON.stringify(
1255
+ item,
1256
+ null,
1257
+ 2
1258
+ )} but it says it's supposed to have one...`
1259
+ );
1260
+ continue;
1261
+ }
1089
1262
  }
1090
- const itemDataToUpdate = this.importMap
1091
- .get(this.getCollectionKey(collection.name))
1092
- ?.data.find(
1093
- (data) => data.rawData[importDef.primaryKeyField] === oldId
1094
- );
1095
- if (!itemDataToUpdate) {
1263
+ if (!itemDataToUpdate || !newId) {
1096
1264
  logger.error(
1097
- `No data found for collection ${
1265
+ `No data or ID (docId) found for collection ${
1098
1266
  collection.name
1099
1267
  } for updateDef ${JSON.stringify(
1100
1268
  item,
@@ -1110,9 +1278,9 @@ export class DataLoader {
1110
1278
  );
1111
1279
  // Create a context object for the item, including the new ID and transformed data
1112
1280
  let context = this.createContext(db, collection, item, newId);
1113
- context = this.mergeObjects(context, transformedData);
1281
+ context = { ...context, ...transformedData };
1114
1282
  // Validate the item before proceeding
1115
- const isValid = await this.importDataActions.validateItem(
1283
+ const isValid = this.importDataActions.validateItem(
1116
1284
  item,
1117
1285
  importDef.attributeMappings,
1118
1286
  context
@@ -1144,6 +1312,7 @@ export class DataLoader {
1144
1312
  );
1145
1313
  itemDataToUpdate.context = context;
1146
1314
  itemDataToUpdate.importDef = newImportDef;
1315
+ currentData!.data.push(itemDataToUpdate);
1147
1316
  } else {
1148
1317
  // If no existing item matches, then add the new item
1149
1318
  currentData!.data.push({
@@ -1,9 +1,12 @@
1
1
  import { Databases, Query, type Models } from "node-appwrite";
2
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
2
3
 
3
4
  export const fetchAllDatabases = async (
4
5
  database: Databases
5
6
  ): Promise<Models.Database[]> => {
6
- const databases = await database.list([Query.limit(25)]);
7
+ const databases = await tryAwaitWithRetry(
8
+ async () => await database.list([Query.limit(25)])
9
+ );
7
10
  const allDatabases = databases.databases;
8
11
  let lastDatabaseId = allDatabases[allDatabases.length - 1].$id;
9
12
  if (databases.databases.length < 25) {
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AppwriteException,
2
3
  ID,
3
4
  Query,
4
5
  type Databases,
@@ -13,7 +14,7 @@ import type {
13
14
  } from "appwrite-utils";
14
15
  import type { ImportDataActions } from "./importDataActions.js";
15
16
  import _ from "lodash";
16
- import { areCollectionNamesSame } from "../utils/index.js";
17
+ import { areCollectionNamesSame, tryAwaitWithRetry } from "../utils/index.js";
17
18
  import type { SetupOptions } from "../utilsController.js";
18
19
  import { resolveAndUpdateRelationships } from "./relationships.js";
19
20
  import { UsersController } from "./users.js";
@@ -119,7 +120,7 @@ export class ImportController {
119
120
  dataLoader.getCollectionKey(collection.name)
120
121
  );
121
122
  const createBatches = (finalData: CollectionImportData["data"]) => {
122
- let maxBatchLength = 50;
123
+ let maxBatchLength = 25;
123
124
  const finalBatches: CollectionImportData["data"][] = [];
124
125
  for (let i = 0; i < finalData.length; i++) {
125
126
  if (i % maxBatchLength === 0) {
@@ -161,7 +162,7 @@ export class ImportController {
161
162
  .map((item) => {
162
163
  return usersController.createUserAndReturn(item.finalData);
163
164
  });
164
- await Promise.all(userBatchPromises);
165
+ const promiseResults = await Promise.allSettled(userBatchPromises);
165
166
  for (const item of batch) {
166
167
  if (item && item.finalData) {
167
168
  dataLoader.userExistsMap.set(
@@ -173,26 +174,8 @@ export class ImportController {
173
174
  );
174
175
  }
175
176
  }
176
- console.log("Finished importing users batch", batch.length);
177
+ console.log("Finished importing users batch");
177
178
  }
178
- // for (let i = 0; i < usersData.length; i++) {
179
- // const user = usersData[i];
180
- // if (user.finalData) {
181
- // const userId =
182
- // user.finalData.userId ||
183
- // user.context.userId ||
184
- // user.context.docId;
185
- // if (!dataLoader.userExistsMap.has(userId)) {
186
- // if (!user.finalData.userId) {
187
- // user.finalData.userId = userId;
188
- // }
189
- // await usersController.createUserAndReturn(user.finalData);
190
- // dataLoader.userExistsMap.set(userId, true);
191
- // } else {
192
- // console.log("Skipped existing user: ", userId);
193
- // }
194
- // }
195
- // }
196
179
  console.log("Finished importing users");
197
180
  }
198
181
  }
@@ -238,11 +221,14 @@ export class ImportController {
238
221
  if (!item.finalData) {
239
222
  return Promise.resolve();
240
223
  }
241
- return this.database.createDocument(
242
- db.$id,
243
- collection.$id,
244
- id,
245
- item.finalData
224
+ return tryAwaitWithRetry(
225
+ async () =>
226
+ await this.database.createDocument(
227
+ db.$id,
228
+ collection.$id,
229
+ id,
230
+ item.finalData
231
+ )
246
232
  );
247
233
  });
248
234
  // Wait for all promises in the current batch to resolve
@@ -16,6 +16,7 @@ import { convertObjectBySchema } from "./converters.js";
16
16
  import { type AfterImportActions } from "appwrite-utils";
17
17
  import { afterImportActions } from "./afterImportActions.js";
18
18
  import { logger } from "./logging.js";
19
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
19
20
 
20
21
  export class ImportDataActions {
21
22
  private db: Databases;
@@ -41,9 +42,19 @@ export class ImportDataActions {
41
42
  this.afterImportActionsDefinitions = afterImportActionsDefinitions;
42
43
  }
43
44
 
45
+ /**
46
+ * Runs converter functions on the item based on the provided attribute mappings.
47
+ *
48
+ * @param item - The item to be transformed.
49
+ * @param attributeMappings - The mappings that define how each attribute should be transformed.
50
+ * @returns The transformed item.
51
+ */
44
52
  runConverterFunctions(item: any, attributeMappings: AttributeMappings) {
45
53
  const conversionSchema = attributeMappings.reduce((schema, mapping) => {
46
54
  schema[mapping.targetKey] = (originalValue: any) => {
55
+ if (!mapping.converters) {
56
+ return originalValue;
57
+ }
47
58
  return mapping.converters?.reduce((value, converterName) => {
48
59
  let shouldProcessAsArray = false;
49
60
  if (
@@ -94,14 +105,15 @@ export class ImportDataActions {
94
105
  /**
95
106
  * Validates a single data item based on defined validation rules.
96
107
  * @param item The data item to validate.
108
+ * @param attributeMap The attribute mappings for the data item.
97
109
  * @param context The context for resolving templated parameters in validation rules.
98
110
  * @returns A promise that resolves to true if the item is valid, false otherwise.
99
111
  */
100
- async validateItem(
112
+ validateItem(
101
113
  item: any,
102
114
  attributeMap: AttributeMappings,
103
115
  context: { [key: string]: any }
104
- ): Promise<boolean> {
116
+ ): boolean {
105
117
  for (const mapping of attributeMap) {
106
118
  const { validationActions } = mapping;
107
119
  if (
@@ -167,7 +179,9 @@ export class ImportDataActions {
167
179
  }' with params ${params.join(", ")}...`
168
180
  );
169
181
  try {
170
- await this.executeAction(action, params, context, item);
182
+ await tryAwaitWithRetry(
183
+ async () => await this.executeAction(action, params, context, item)
184
+ );
171
185
  } catch (error) {
172
186
  logger.error(
173
187
  `Failed to execute post-import action '${action}' for attribute '${mapping.targetKey}':`,
@@ -1,5 +1,6 @@
1
1
  import { indexSchema, type Index } from "appwrite-utils";
2
2
  import { Databases, Query, type Models } from "node-appwrite";
3
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
3
4
  // import {}
4
5
 
5
6
  export const createOrUpdateIndex = async (
@@ -32,6 +33,8 @@ export const createOrUpdateIndexes = async (
32
33
  indexes: Index[]
33
34
  ) => {
34
35
  for (const index of indexes) {
35
- await createOrUpdateIndex(dbId, db, collectionId, index);
36
+ await tryAwaitWithRetry(
37
+ async () => await createOrUpdateIndex(dbId, db, collectionId, index)
38
+ );
36
39
  }
37
40
  };