appwrite-utils-cli 0.0.38 → 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.
@@ -94,7 +94,12 @@ export const convertObjectByAttributeMappings = (obj, attributeMappings) => {
94
94
  const values = mapping.oldKeys
95
95
  .map((oldKey) => resolveValue(obj, oldKey))
96
96
  .flat(Infinity);
97
- result[mapping.targetKey] = values.filter((value) => value !== undefined);
97
+ if (values.length > 0) {
98
+ result[mapping.targetKey] = values.filter((value) => value !== undefined);
99
+ }
100
+ else {
101
+ result[mapping.targetKey] = null;
102
+ }
98
103
  }
99
104
  else if (mapping.oldKey) {
100
105
  // Resolve single oldKey
@@ -104,6 +109,15 @@ export const convertObjectByAttributeMappings = (obj, attributeMappings) => {
104
109
  ? value.flat(Infinity)
105
110
  : value;
106
111
  }
112
+ else {
113
+ result[mapping.targetKey] = null;
114
+ }
115
+ }
116
+ }
117
+ // Ensure any keys in the original object that are not mapped are copied over
118
+ for (const key of Object.keys(obj)) {
119
+ if (!Object.keys(result).includes(key)) {
120
+ result[key] = obj[key];
107
121
  }
108
122
  }
109
123
  return result;
@@ -1747,7 +1747,7 @@ export declare class DataLoader {
1747
1747
  * @param collection - The collection configuration.
1748
1748
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
1749
1749
  */
1750
- prepareUserCollectionCreateData(db: ConfigDatabase, collection: CollectionCreate, importDef: ImportDef): void;
1750
+ prepareUserCollectionCreateData(db: ConfigDatabase, collection: CollectionCreate, importDef: ImportDef): Promise<void>;
1751
1751
  /**
1752
1752
  * Prepares the data for creating documents in a collection.
1753
1753
  * This involves loading the data, transforming it, and handling ID mappings.
@@ -1756,7 +1756,7 @@ export declare class DataLoader {
1756
1756
  * @param collection - The collection configuration.
1757
1757
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
1758
1758
  */
1759
- prepareCreateData(db: ConfigDatabase, collection: CollectionCreate, importDef: ImportDef): void;
1759
+ prepareCreateData(db: ConfigDatabase, collection: CollectionCreate, importDef: ImportDef): Promise<void>;
1760
1760
  /**
1761
1761
  * Prepares the data for updating documents within a collection.
1762
1762
  * This method loads the raw data based on the import definition, transforms it according to the attribute mappings,
@@ -74,29 +74,51 @@ export class DataLoader {
74
74
  mergeObjects(source, update) {
75
75
  // Create a new object to hold the merged result
76
76
  const result = { ...source };
77
- Object.keys(update).forEach((key) => {
78
- const sourceValue = source[key];
79
- const updateValue = update[key];
80
- // If the update value is an array, concatenate and remove duplicates
81
- if (Array.isArray(updateValue)) {
82
- const sourceArray = Array.isArray(sourceValue) ? sourceValue : [];
83
- result[key] = [...new Set([...sourceArray, ...updateValue])];
84
- }
85
- // If the update value is an object, recursively merge
86
- else if (updateValue !== null &&
87
- typeof updateValue === "object" &&
88
- !(updateValue instanceof Date)) {
89
- result[key] = this.mergeObjects(sourceValue, updateValue);
90
- }
91
- // If the update value is not nullish, overwrite the source value
92
- else if (updateValue !== null && updateValue !== undefined) {
93
- result[key] = updateValue;
94
- }
95
- // If the update value is nullish, keep the original value unless it doesn't exist
96
- else if (sourceValue === undefined || sourceValue === null) {
97
- 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;
98
83
  }
99
- });
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
+ }
100
122
  return result;
101
123
  }
102
124
  // Method to load data from a file specified in the import definition
@@ -165,6 +187,9 @@ export class DataLoader {
165
187
  transformData(item, attributeMappings) {
166
188
  // Convert the item using the attribute mappings provided
167
189
  const convertedItem = convertObjectByAttributeMappings(item, attributeMappings);
190
+ if (item["region"]) {
191
+ logger.info(`Converted item: ${JSON.stringify(convertedItem, null, 2)}`);
192
+ }
168
193
  // Run additional converter functions on the converted item, if any
169
194
  return this.importDataActions.runConverterFunctions(convertedItem, attributeMappings);
170
195
  }
@@ -271,12 +296,12 @@ export class DataLoader {
271
296
  for (const createDef of createDefs) {
272
297
  if (!isUsersCollection) {
273
298
  console.log(`${collection.name} is not users collection`);
274
- this.prepareCreateData(db, collection, createDef);
299
+ await this.prepareCreateData(db, collection, createDef);
275
300
  }
276
301
  else {
277
302
  // Special handling for users collection if needed
278
303
  console.log(`${collection.name} is users collection`);
279
- this.prepareUserCollectionCreateData(db, collection, createDef);
304
+ await this.prepareUserCollectionCreateData(db, collection, createDef);
280
305
  }
281
306
  }
282
307
  for (const updateDef of updateDefs) {
@@ -285,7 +310,7 @@ export class DataLoader {
285
310
  continue;
286
311
  }
287
312
  // Prepare the update data for the collection
288
- this.prepareUpdateData(db, collection, updateDef);
313
+ await this.prepareUpdateData(db, collection, updateDef);
289
314
  }
290
315
  }
291
316
  console.log("Running update references");
@@ -402,7 +427,9 @@ export class DataLoader {
402
427
  const targetFieldKey = idMapping.targetFieldToMatch || idMapping.targetField;
403
428
  const valueToMatch = collectionData.data[i].context[idMapping.sourceField];
404
429
  // Skip if value to match is missing or empty
405
- if (!valueToMatch || _.isEmpty(valueToMatch))
430
+ if (!valueToMatch ||
431
+ _.isEmpty(valueToMatch) ||
432
+ valueToMatch === null)
406
433
  continue;
407
434
  const isFieldToSetArray = collectionConfig.attributes.find((attribute) => attribute.key === fieldToSetKey)?.array;
408
435
  const targetCollectionData = this.importMap.get(targetCollectionKey);
@@ -620,7 +647,7 @@ export class DataLoader {
620
647
  * @param collection - The collection configuration.
621
648
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
622
649
  */
623
- prepareUserCollectionCreateData(db, collection, importDef) {
650
+ async prepareUserCollectionCreateData(db, collection, importDef) {
624
651
  // Load the raw data based on the import definition
625
652
  const rawData = this.loadData(importDef);
626
653
  const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
@@ -635,108 +662,107 @@ export class DataLoader {
635
662
  if (!operationId) {
636
663
  throw new Error(`No import operation found for collection ${collection.name}`);
637
664
  }
638
- updateOperation(this.database, operationId, {
665
+ await updateOperation(this.database, operationId, {
639
666
  status: "ready",
640
667
  total: rawData.length,
641
- }).then(() => {
642
- // Retrieve the current user data and the current collection data from the import map
643
- const currentUserData = this.importMap.get(this.getCollectionKey("users"));
644
- const currentData = this.importMap.get(this.getCollectionKey(collection.name));
645
- // Log errors if the necessary data is not found in the import map
646
- if (!currentUserData) {
647
- logger.error(`No data found for collection ${"users"} for createDef but it says it's supposed to have one...`);
648
- return;
649
- }
650
- else if (!currentData) {
651
- logger.error(`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`);
652
- return;
668
+ });
669
+ // Retrieve the current user data and the current collection data from the import map
670
+ const currentUserData = this.importMap.get(this.getCollectionKey("users"));
671
+ const currentData = this.importMap.get(this.getCollectionKey(collection.name));
672
+ // Log errors if the necessary data is not found in the import map
673
+ if (!currentUserData) {
674
+ logger.error(`No data found for collection ${"users"} for createDef but it says it's supposed to have one...`);
675
+ return;
676
+ }
677
+ else if (!currentData) {
678
+ logger.error(`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`);
679
+ return;
680
+ }
681
+ // Iterate through each item in the raw data
682
+ for (const item of rawData) {
683
+ // Prepare user data, check for duplicates, and remove user-specific keys
684
+ let { transformedItem, existingId, userData } = this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
685
+ logger.info(`In create user -- transformedItem: ${JSON.stringify(transformedItem, null, 2)}`);
686
+ // Generate a new unique ID for the item or use existing ID
687
+ if (!existingId) {
688
+ // No existing user ID, generate a new unique ID
689
+ existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
690
+ transformedItem.docId = existingId; // Assign the new ID to the transformed data's docId field
653
691
  }
654
- // Iterate through each item in the raw data
655
- for (const item of rawData) {
656
- // Prepare user data, check for duplicates, and remove user-specific keys
657
- let { transformedItem, existingId, userData } = this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
658
- logger.info(`In create user -- transformedItem: ${JSON.stringify(transformedItem, null, 2)}`);
659
- // Generate a new unique ID for the item or use existing ID
660
- if (!existingId) {
661
- // No existing user ID, generate a new unique ID
662
- existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
663
- transformedItem.docId = existingId; // Assign the new ID to the transformed data's docId field
664
- }
665
- // Create a context object for the item, including the new ID
666
- let context = this.createContext(db, collection, item, existingId);
667
- // Merge the transformed data into the context
668
- context = { ...context, ...transformedItem, ...userData.finalData };
669
- // If a primary key field is defined, handle the ID mapping and check for duplicates
670
- if (importDef.primaryKeyField) {
671
- const oldId = item[importDef.primaryKeyField];
672
- // Check if the oldId already exists to handle potential duplicates
673
- if (this.oldIdToNewIdPerCollectionMap
674
- .get(this.getCollectionKey(collection.name))
675
- ?.has(`${oldId}`)) {
676
- // Found a duplicate oldId, now decide how to merge or handle these duplicates
677
- for (const data of currentData.data) {
678
- if (data.finalData.docId === oldId ||
679
- data.finalData.userId === oldId) {
680
- transformedItem = this.mergeObjects(data.finalData, transformedItem);
681
- }
692
+ // Create a context object for the item, including the new ID
693
+ let context = this.createContext(db, collection, item, existingId);
694
+ // Merge the transformed data into the context
695
+ context = { ...context, ...transformedItem, ...userData.finalData };
696
+ // If a primary key field is defined, handle the ID mapping and check for duplicates
697
+ if (importDef.primaryKeyField) {
698
+ const oldId = item[importDef.primaryKeyField];
699
+ // Check if the oldId already exists to handle potential duplicates
700
+ if (this.oldIdToNewIdPerCollectionMap
701
+ .get(this.getCollectionKey(collection.name))
702
+ ?.has(`${oldId}`)) {
703
+ // Found a duplicate oldId, now decide how to merge or handle these duplicates
704
+ for (const data of currentData.data) {
705
+ if (data.finalData.docId === oldId ||
706
+ data.finalData.userId === oldId) {
707
+ transformedItem = this.mergeObjects(data.finalData, transformedItem);
682
708
  }
683
709
  }
684
- else {
685
- // No duplicate found, simply map the oldId to the new itemId
686
- collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
687
- }
688
710
  }
689
- // Handle merging for currentUserData
690
- for (let i = 0; i < currentUserData.data.length; i++) {
691
- const currentUserDataItem = currentUserData.data[i];
692
- const samePhones = currentUserDataItem.finalData.phone &&
693
- transformedItem.phone &&
694
- currentUserDataItem.finalData.phone === transformedItem.phone;
695
- const sameEmails = currentUserDataItem.finalData.email &&
696
- transformedItem.email &&
697
- currentUserDataItem.finalData.email === transformedItem.email;
698
- if ((currentUserDataItem.finalData.docId === existingId ||
699
- currentUserDataItem.finalData.userId === existingId) &&
700
- (samePhones || sameEmails) &&
701
- currentUserDataItem.finalData &&
702
- userData.finalData) {
703
- const userDataMerged = this.mergeObjects(currentUserData.data[i].finalData, userData.finalData);
704
- currentUserData.data[i].finalData = userDataMerged;
705
- this.importMap.set(this.getCollectionKey("users"), currentUserData);
706
- }
711
+ else {
712
+ // No duplicate found, simply map the oldId to the new itemId
713
+ collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
707
714
  }
708
- // Update the attribute mappings with any actions that need to be performed post-import
709
- const mappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, transformedItem);
710
- // Update the import definition with the new attribute mappings
711
- const newImportDef = {
712
- ...importDef,
713
- attributeMappings: mappingsWithActions,
714
- };
715
- let foundData = false;
716
- for (let i = 0; i < currentData.data.length; i++) {
717
- if (currentData.data[i].finalData.docId === existingId ||
718
- currentData.data[i].finalData.userId === existingId) {
719
- currentData.data[i].finalData = this.mergeObjects(currentData.data[i].finalData, transformedItem);
720
- currentData.data[i].context = context;
721
- currentData.data[i].importDef = newImportDef;
722
- this.importMap.set(this.getCollectionKey(collection.name), currentData);
723
- this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
724
- foundData = true;
725
- }
715
+ }
716
+ // Handle merging for currentUserData
717
+ for (let i = 0; i < currentUserData.data.length; i++) {
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;
732
+ this.importMap.set(this.getCollectionKey("users"), currentUserData);
726
733
  }
727
- if (!foundData) {
728
- // Add new data to the associated collection
729
- currentData.data.push({
730
- rawData: item,
731
- context: context,
732
- importDef: newImportDef,
733
- finalData: transformedItem,
734
- });
734
+ }
735
+ // Update the attribute mappings with any actions that need to be performed post-import
736
+ const mappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, transformedItem);
737
+ // Update the import definition with the new attribute mappings
738
+ const newImportDef = {
739
+ ...importDef,
740
+ attributeMappings: mappingsWithActions,
741
+ };
742
+ let foundData = false;
743
+ for (let i = 0; i < currentData.data.length; i++) {
744
+ if (currentData.data[i].finalData.docId === existingId ||
745
+ currentData.data[i].finalData.userId === existingId) {
746
+ currentData.data[i].finalData = this.mergeObjects(currentData.data[i].finalData, transformedItem);
747
+ currentData.data[i].context = context;
748
+ currentData.data[i].importDef = newImportDef;
735
749
  this.importMap.set(this.getCollectionKey(collection.name), currentData);
736
750
  this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
751
+ foundData = true;
737
752
  }
738
753
  }
739
- });
754
+ if (!foundData) {
755
+ // Add new data to the associated collection
756
+ currentData.data.push({
757
+ rawData: item,
758
+ context: context,
759
+ importDef: newImportDef,
760
+ finalData: transformedItem,
761
+ });
762
+ this.importMap.set(this.getCollectionKey(collection.name), currentData);
763
+ this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
764
+ }
765
+ }
740
766
  }
741
767
  /**
742
768
  * Prepares the data for creating documents in a collection.
@@ -746,75 +772,81 @@ export class DataLoader {
746
772
  * @param collection - The collection configuration.
747
773
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
748
774
  */
749
- prepareCreateData(db, collection, importDef) {
775
+ async prepareCreateData(db, collection, importDef) {
750
776
  // Load the raw data based on the import definition
751
777
  const rawData = this.loadData(importDef);
752
778
  const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
753
779
  if (!operationId) {
754
780
  throw new Error(`No import operation found for collection ${collection.name}`);
755
781
  }
756
- updateOperation(this.database, operationId, {
782
+ await updateOperation(this.database, operationId, {
757
783
  status: "ready",
758
784
  total: rawData.length,
759
- }).then(() => {
760
- // Initialize a new map for old ID to new ID mappings
761
- const oldIdToNewIdMapNew = new Map();
762
- // Retrieve or initialize the collection-specific old ID to new ID map
763
- const collectionOldIdToNewIdMap = this.oldIdToNewIdPerCollectionMap.get(this.getCollectionKey(collection.name)) ||
764
- this.oldIdToNewIdPerCollectionMap
765
- .set(this.getCollectionKey(collection.name), oldIdToNewIdMapNew)
766
- .get(this.getCollectionKey(collection.name));
767
- console.log(`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`);
768
- // Iterate through each item in the raw data
769
- for (const item of rawData) {
770
- // Generate a new unique ID for the item
771
- const itemIdNew = this.getTrueUniqueId(this.getCollectionKey(collection.name));
772
- // Retrieve the current collection data from the import map
773
- const currentData = this.importMap.get(this.getCollectionKey(collection.name));
774
- // Create a context object for the item, including the new ID
775
- let context = this.createContext(db, collection, item, itemIdNew);
776
- // Transform the item data based on the attribute mappings
777
- const transformedData = this.transformData(item, importDef.attributeMappings);
778
- // If a primary key field is defined, handle the ID mapping and check for duplicates
779
- if (importDef.primaryKeyField) {
780
- const oldId = item[importDef.primaryKeyField];
781
- if (collectionOldIdToNewIdMap?.has(`${oldId}`)) {
782
- logger.error(`Collection ${collection.name} has multiple documents with the same primary key ${oldId}`);
783
- continue;
784
- }
785
- collectionOldIdToNewIdMap?.set(`${oldId}`, `${itemIdNew}`);
786
- }
787
- // Merge the transformed data into the context
788
- context = { ...context, ...transformedData };
789
- // Validate the item before proceeding
790
- const isValid = this.importDataActions.validateItem(transformedData, importDef.attributeMappings, context);
791
- if (!isValid) {
792
- continue;
793
- }
794
- // Update the attribute mappings with any actions that need to be performed post-import
795
- const mappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, transformedData);
796
- // Update the import definition with the new attribute mappings
797
- const newImportDef = {
798
- ...importDef,
799
- attributeMappings: mappingsWithActions,
800
- };
801
- // If the current collection data exists, add the item with its context and final data
802
- if (currentData && currentData.data) {
803
- currentData.data.push({
804
- rawData: item,
805
- context: context,
806
- importDef: newImportDef,
807
- finalData: transformedData,
808
- });
809
- this.importMap.set(this.getCollectionKey(collection.name), currentData);
810
- this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
811
- }
812
- else {
813
- logger.error(`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`);
785
+ });
786
+ // Initialize a new map for old ID to new ID mappings
787
+ const oldIdToNewIdMapNew = new Map();
788
+ // Retrieve or initialize the collection-specific old ID to new ID map
789
+ const collectionOldIdToNewIdMap = this.oldIdToNewIdPerCollectionMap.get(this.getCollectionKey(collection.name)) ||
790
+ this.oldIdToNewIdPerCollectionMap
791
+ .set(this.getCollectionKey(collection.name), oldIdToNewIdMapNew)
792
+ .get(this.getCollectionKey(collection.name));
793
+ console.log(`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`);
794
+ const isRegions = collection.name.toLowerCase() === "regions";
795
+ // Iterate through each item in the raw data
796
+ for (const item of rawData) {
797
+ // Generate a new unique ID for the item
798
+ const itemIdNew = this.getTrueUniqueId(this.getCollectionKey(collection.name));
799
+ if (isRegions) {
800
+ logger.info(`Creating region: ${JSON.stringify(item, null, 2)}`);
801
+ }
802
+ // Retrieve the current collection data from the import map
803
+ const currentData = this.importMap.get(this.getCollectionKey(collection.name));
804
+ // Create a context object for the item, including the new ID
805
+ let context = this.createContext(db, collection, item, itemIdNew);
806
+ // Transform the item data based on the attribute mappings
807
+ const transformedData = this.transformData(item, importDef.attributeMappings);
808
+ if (isRegions) {
809
+ logger.info(`Transformed region: ${JSON.stringify(transformedData, null, 2)}`);
810
+ }
811
+ // If a primary key field is defined, handle the ID mapping and check for duplicates
812
+ if (importDef.primaryKeyField) {
813
+ const oldId = item[importDef.primaryKeyField];
814
+ if (collectionOldIdToNewIdMap?.has(`${oldId}`)) {
815
+ logger.error(`Collection ${collection.name} has multiple documents with the same primary key ${oldId}`);
814
816
  continue;
815
817
  }
818
+ collectionOldIdToNewIdMap?.set(`${oldId}`, `${itemIdNew}`);
816
819
  }
817
- });
820
+ // Merge the transformed data into the context
821
+ context = { ...context, ...transformedData };
822
+ // Validate the item before proceeding
823
+ const isValid = this.importDataActions.validateItem(transformedData, importDef.attributeMappings, context);
824
+ if (!isValid) {
825
+ continue;
826
+ }
827
+ // Update the attribute mappings with any actions that need to be performed post-import
828
+ const mappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, transformedData);
829
+ // Update the import definition with the new attribute mappings
830
+ const newImportDef = {
831
+ ...importDef,
832
+ attributeMappings: mappingsWithActions,
833
+ };
834
+ // If the current collection data exists, add the item with its context and final data
835
+ if (currentData && currentData.data) {
836
+ currentData.data.push({
837
+ rawData: item,
838
+ context: context,
839
+ importDef: newImportDef,
840
+ finalData: transformedData,
841
+ });
842
+ this.importMap.set(this.getCollectionKey(collection.name), currentData);
843
+ this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
844
+ }
845
+ else {
846
+ logger.error(`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`);
847
+ continue;
848
+ }
849
+ }
818
850
  }
819
851
  /**
820
852
  * Prepares the data for updating documents within a collection.
@@ -11,10 +11,18 @@ 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,9 +21,19 @@ export class ImportDataActions {
21
21
  this.validityRuleDefinitions = validityRuleDefinitions;
22
22
  this.afterImportActionsDefinitions = afterImportActionsDefinitions;
23
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
+ */
24
31
  runConverterFunctions(item, attributeMappings) {
25
32
  const conversionSchema = attributeMappings.reduce((schema, mapping) => {
26
33
  schema[mapping.targetKey] = (originalValue) => {
34
+ if (!mapping.converters) {
35
+ return originalValue;
36
+ }
27
37
  return mapping.converters?.reduce((value, converterName) => {
28
38
  let shouldProcessAsArray = false;
29
39
  if ((converterName.includes("[Arr]") ||
@@ -66,6 +76,7 @@ export class ImportDataActions {
66
76
  /**
67
77
  * Validates a single data item based on defined validation rules.
68
78
  * @param item The data item to validate.
79
+ * @param attributeMap The attribute mappings for the data item.
69
80
  * @param context The context for resolving templated parameters in validation rules.
70
81
  * @returns A promise that resolves to true if the item is valid, false otherwise.
71
82
  */
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.38",
4
+ "version": "0.0.39",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -108,7 +108,13 @@ export const convertObjectByAttributeMappings = (
108
108
  const values = mapping.oldKeys
109
109
  .map((oldKey) => resolveValue(obj, oldKey))
110
110
  .flat(Infinity);
111
- result[mapping.targetKey] = values.filter((value) => value !== undefined);
111
+ if (values.length > 0) {
112
+ result[mapping.targetKey] = values.filter(
113
+ (value) => value !== undefined
114
+ );
115
+ } else {
116
+ result[mapping.targetKey] = null;
117
+ }
112
118
  } else if (mapping.oldKey) {
113
119
  // Resolve single oldKey
114
120
  const value = resolveValue(obj, mapping.oldKey);
@@ -116,9 +122,19 @@ export const convertObjectByAttributeMappings = (
116
122
  result[mapping.targetKey] = Array.isArray(value)
117
123
  ? value.flat(Infinity)
118
124
  : value;
125
+ } else {
126
+ result[mapping.targetKey] = null;
119
127
  }
120
128
  }
121
129
  }
130
+
131
+ // Ensure any keys in the original object that are not mapped are copied over
132
+ for (const key of Object.keys(obj)) {
133
+ if (!Object.keys(result).includes(key)) {
134
+ result[key] = obj[key];
135
+ }
136
+ }
137
+
122
138
  return result;
123
139
  };
124
140
 
@@ -103,32 +103,53 @@ export class DataLoader {
103
103
  // Create a new object to hold the merged result
104
104
  const result = { ...source };
105
105
 
106
- Object.keys(update).forEach((key) => {
107
- const sourceValue = source[key];
108
- const updateValue = update[key];
109
-
110
- // If the update value is an array, concatenate and remove duplicates
111
- if (Array.isArray(updateValue)) {
112
- const sourceArray = Array.isArray(sourceValue) ? sourceValue : [];
113
- result[key] = [...new Set([...sourceArray, ...updateValue])];
114
- }
115
- // If the update value is an object, recursively merge
116
- else if (
117
- updateValue !== null &&
118
- typeof updateValue === "object" &&
119
- !(updateValue instanceof Date)
120
- ) {
121
- 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;
122
112
  }
123
- // If the update value is not nullish, overwrite the source value
124
- else if (updateValue !== null && updateValue !== undefined) {
125
- result[key] = updateValue;
113
+ if (update[key] === value) {
114
+ continue;
126
115
  }
127
- // If the update value is nullish, keep the original value unless it doesn't exist
128
- else if (sourceValue === undefined || sourceValue === null) {
129
- 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;
130
151
  }
131
- });
152
+ }
132
153
 
133
154
  return result;
134
155
  }
@@ -219,6 +240,9 @@ export class DataLoader {
219
240
  item,
220
241
  attributeMappings
221
242
  );
243
+ if (item["region"]) {
244
+ logger.info(`Converted item: ${JSON.stringify(convertedItem, null, 2)}`);
245
+ }
222
246
  // Run additional converter functions on the converted item, if any
223
247
  return this.importDataActions.runConverterFunctions(
224
248
  convertedItem,
@@ -346,11 +370,15 @@ export class DataLoader {
346
370
  for (const createDef of createDefs) {
347
371
  if (!isUsersCollection) {
348
372
  console.log(`${collection.name} is not users collection`);
349
- this.prepareCreateData(db, collection, createDef);
373
+ await this.prepareCreateData(db, collection, createDef);
350
374
  } else {
351
375
  // Special handling for users collection if needed
352
376
  console.log(`${collection.name} is users collection`);
353
- this.prepareUserCollectionCreateData(db, collection, createDef);
377
+ await this.prepareUserCollectionCreateData(
378
+ db,
379
+ collection,
380
+ createDef
381
+ );
354
382
  }
355
383
  }
356
384
  for (const updateDef of updateDefs) {
@@ -361,7 +389,7 @@ export class DataLoader {
361
389
  continue;
362
390
  }
363
391
  // Prepare the update data for the collection
364
- this.prepareUpdateData(db, collection, updateDef);
392
+ await this.prepareUpdateData(db, collection, updateDef);
365
393
  }
366
394
  }
367
395
  console.log("Running update references");
@@ -531,7 +559,12 @@ export class DataLoader {
531
559
  collectionData.data[i].context[idMapping.sourceField];
532
560
 
533
561
  // Skip if value to match is missing or empty
534
- if (!valueToMatch || _.isEmpty(valueToMatch)) continue;
562
+ if (
563
+ !valueToMatch ||
564
+ _.isEmpty(valueToMatch) ||
565
+ valueToMatch === null
566
+ )
567
+ continue;
535
568
 
536
569
  const isFieldToSetArray = collectionConfig.attributes.find(
537
570
  (attribute) => attribute.key === fieldToSetKey
@@ -814,11 +847,11 @@ export class DataLoader {
814
847
  * @param collection - The collection configuration.
815
848
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
816
849
  */
817
- prepareUserCollectionCreateData(
850
+ async prepareUserCollectionCreateData(
818
851
  db: ConfigDatabase,
819
852
  collection: CollectionCreate,
820
853
  importDef: ImportDef
821
- ): void {
854
+ ): Promise<void> {
822
855
  // Load the raw data based on the import definition
823
856
  const rawData = this.loadData(importDef);
824
857
  const operationId = this.collectionImportOperations.get(
@@ -842,157 +875,136 @@ export class DataLoader {
842
875
  `No import operation found for collection ${collection.name}`
843
876
  );
844
877
  }
845
- updateOperation(this.database, operationId, {
878
+ await updateOperation(this.database, operationId, {
846
879
  status: "ready",
847
880
  total: rawData.length,
848
- }).then(() => {
849
- // Retrieve the current user data and the current collection data from the import map
850
- const currentUserData = this.importMap.get(
851
- this.getCollectionKey("users")
881
+ });
882
+ // Retrieve the current user data and the current collection data from the import map
883
+ const currentUserData = this.importMap.get(this.getCollectionKey("users"));
884
+ const currentData = this.importMap.get(
885
+ this.getCollectionKey(collection.name)
886
+ );
887
+ // Log errors if the necessary data is not found in the import map
888
+ if (!currentUserData) {
889
+ logger.error(
890
+ `No data found for collection ${"users"} for createDef but it says it's supposed to have one...`
852
891
  );
853
- const currentData = this.importMap.get(
854
- this.getCollectionKey(collection.name)
892
+ return;
893
+ } else if (!currentData) {
894
+ logger.error(
895
+ `No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`
896
+ );
897
+ return;
898
+ }
899
+ // Iterate through each item in the raw data
900
+ for (const item of rawData) {
901
+ // Prepare user data, check for duplicates, and remove user-specific keys
902
+ let { transformedItem, existingId, userData } = this.prepareUserData(
903
+ item,
904
+ importDef.attributeMappings,
905
+ importDef.primaryKeyField,
906
+ this.getTrueUniqueId(this.getCollectionKey("users"))
855
907
  );
856
- // Log errors if the necessary data is not found in the import map
857
- if (!currentUserData) {
858
- logger.error(
859
- `No data found for collection ${"users"} for createDef but it says it's supposed to have one...`
860
- );
861
- return;
862
- } else if (!currentData) {
863
- logger.error(
864
- `No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`
865
- );
866
- return;
867
- }
868
- // Iterate through each item in the raw data
869
- for (const item of rawData) {
870
- // Prepare user data, check for duplicates, and remove user-specific keys
871
- let { transformedItem, existingId, userData } = this.prepareUserData(
872
- item,
873
- importDef.attributeMappings,
874
- importDef.primaryKeyField,
875
- this.getTrueUniqueId(this.getCollectionKey("users"))
876
- );
877
908
 
878
- logger.info(
879
- `In create user -- transformedItem: ${JSON.stringify(
880
- transformedItem,
881
- null,
882
- 2
883
- )}`
884
- );
909
+ logger.info(
910
+ `In create user -- transformedItem: ${JSON.stringify(
911
+ transformedItem,
912
+ null,
913
+ 2
914
+ )}`
915
+ );
885
916
 
886
- // Generate a new unique ID for the item or use existing ID
887
- if (!existingId) {
888
- // No existing user ID, generate a new unique ID
889
- existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
890
- transformedItem.docId = existingId; // Assign the new ID to the transformed data's docId field
891
- }
917
+ // Generate a new unique ID for the item or use existing ID
918
+ if (!existingId) {
919
+ // No existing user ID, generate a new unique ID
920
+ existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
921
+ transformedItem.docId = existingId; // Assign the new ID to the transformed data's docId field
922
+ }
892
923
 
893
- // Create a context object for the item, including the new ID
894
- let context = this.createContext(db, collection, item, existingId);
924
+ // Create a context object for the item, including the new ID
925
+ let context = this.createContext(db, collection, item, existingId);
895
926
 
896
- // Merge the transformed data into the context
897
- context = { ...context, ...transformedItem, ...userData.finalData };
927
+ // Merge the transformed data into the context
928
+ context = { ...context, ...transformedItem, ...userData.finalData };
898
929
 
899
- // If a primary key field is defined, handle the ID mapping and check for duplicates
900
- if (importDef.primaryKeyField) {
901
- const oldId = item[importDef.primaryKeyField];
930
+ // If a primary key field is defined, handle the ID mapping and check for duplicates
931
+ if (importDef.primaryKeyField) {
932
+ const oldId = item[importDef.primaryKeyField];
902
933
 
903
- // Check if the oldId already exists to handle potential duplicates
904
- if (
905
- this.oldIdToNewIdPerCollectionMap
906
- .get(this.getCollectionKey(collection.name))
907
- ?.has(`${oldId}`)
908
- ) {
909
- // Found a duplicate oldId, now decide how to merge or handle these duplicates
910
- for (const data of currentData.data) {
911
- if (
912
- data.finalData.docId === oldId ||
913
- data.finalData.userId === oldId
914
- ) {
915
- transformedItem = this.mergeObjects(
916
- data.finalData,
917
- transformedItem
918
- );
919
- }
934
+ // Check if the oldId already exists to handle potential duplicates
935
+ if (
936
+ this.oldIdToNewIdPerCollectionMap
937
+ .get(this.getCollectionKey(collection.name))
938
+ ?.has(`${oldId}`)
939
+ ) {
940
+ // Found a duplicate oldId, now decide how to merge or handle these duplicates
941
+ for (const data of currentData.data) {
942
+ if (
943
+ data.finalData.docId === oldId ||
944
+ data.finalData.userId === oldId
945
+ ) {
946
+ transformedItem = this.mergeObjects(
947
+ data.finalData,
948
+ transformedItem
949
+ );
920
950
  }
921
- } else {
922
- // No duplicate found, simply map the oldId to the new itemId
923
- collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
924
951
  }
952
+ } else {
953
+ // No duplicate found, simply map the oldId to the new itemId
954
+ collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
925
955
  }
956
+ }
926
957
 
927
- // Handle merging for currentUserData
928
- for (let i = 0; i < currentUserData.data.length; i++) {
929
- const currentUserDataItem = currentUserData.data[i];
930
- const samePhones =
931
- currentUserDataItem.finalData.phone &&
932
- transformedItem.phone &&
933
- currentUserDataItem.finalData.phone === transformedItem.phone;
934
- const sameEmails =
935
- currentUserDataItem.finalData.email &&
936
- transformedItem.email &&
937
- currentUserDataItem.finalData.email === transformedItem.email;
938
- if (
939
- (currentUserDataItem.finalData.docId === existingId ||
940
- currentUserDataItem.finalData.userId === existingId) &&
941
- (samePhones || sameEmails) &&
942
- currentUserDataItem.finalData &&
958
+ // Handle merging for currentUserData
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;
969
+ if (
970
+ (currentUserDataItem.finalData.docId === existingId ||
971
+ currentUserDataItem.finalData.userId === existingId) &&
972
+ (samePhones || sameEmails) &&
973
+ currentUserDataItem.finalData &&
974
+ userData.finalData
975
+ ) {
976
+ const userDataMerged = this.mergeObjects(
977
+ currentUserData.data[i].finalData,
943
978
  userData.finalData
944
- ) {
945
- const userDataMerged = this.mergeObjects(
946
- currentUserData.data[i].finalData,
947
- userData.finalData
948
- );
949
- currentUserData.data[i].finalData = userDataMerged;
950
- this.importMap.set(this.getCollectionKey("users"), currentUserData);
951
- }
979
+ );
980
+ currentUserData.data[i].finalData = userDataMerged;
981
+ this.importMap.set(this.getCollectionKey("users"), currentUserData);
952
982
  }
953
- // Update the attribute mappings with any actions that need to be performed post-import
954
- const mappingsWithActions = this.getAttributeMappingsWithActions(
955
- importDef.attributeMappings,
956
- context,
957
- transformedItem
958
- );
959
- // Update the import definition with the new attribute mappings
960
- const newImportDef = {
961
- ...importDef,
962
- attributeMappings: mappingsWithActions,
963
- };
983
+ }
984
+ // Update the attribute mappings with any actions that need to be performed post-import
985
+ const mappingsWithActions = this.getAttributeMappingsWithActions(
986
+ importDef.attributeMappings,
987
+ context,
988
+ transformedItem
989
+ );
990
+ // Update the import definition with the new attribute mappings
991
+ const newImportDef = {
992
+ ...importDef,
993
+ attributeMappings: mappingsWithActions,
994
+ };
964
995
 
965
- let foundData = false;
966
- for (let i = 0; i < currentData.data.length; i++) {
967
- if (
968
- currentData.data[i].finalData.docId === existingId ||
969
- currentData.data[i].finalData.userId === existingId
970
- ) {
971
- currentData.data[i].finalData = this.mergeObjects(
972
- currentData.data[i].finalData,
973
- transformedItem
974
- );
975
- currentData.data[i].context = context;
976
- currentData.data[i].importDef = newImportDef;
977
- this.importMap.set(
978
- this.getCollectionKey(collection.name),
979
- currentData
980
- );
981
- this.oldIdToNewIdPerCollectionMap.set(
982
- this.getCollectionKey(collection.name),
983
- collectionOldIdToNewIdMap!
984
- );
985
- foundData = true;
986
- }
987
- }
988
- if (!foundData) {
989
- // Add new data to the associated collection
990
- currentData.data.push({
991
- rawData: item,
992
- context: context,
993
- importDef: newImportDef,
994
- finalData: transformedItem,
995
- });
996
+ let foundData = false;
997
+ for (let i = 0; i < currentData.data.length; i++) {
998
+ if (
999
+ currentData.data[i].finalData.docId === existingId ||
1000
+ currentData.data[i].finalData.userId === existingId
1001
+ ) {
1002
+ currentData.data[i].finalData = this.mergeObjects(
1003
+ currentData.data[i].finalData,
1004
+ transformedItem
1005
+ );
1006
+ currentData.data[i].context = context;
1007
+ currentData.data[i].importDef = newImportDef;
996
1008
  this.importMap.set(
997
1009
  this.getCollectionKey(collection.name),
998
1010
  currentData
@@ -1001,9 +1013,24 @@ export class DataLoader {
1001
1013
  this.getCollectionKey(collection.name),
1002
1014
  collectionOldIdToNewIdMap!
1003
1015
  );
1016
+ foundData = true;
1004
1017
  }
1005
1018
  }
1006
- });
1019
+ if (!foundData) {
1020
+ // Add new data to the associated collection
1021
+ currentData.data.push({
1022
+ rawData: item,
1023
+ context: context,
1024
+ importDef: newImportDef,
1025
+ finalData: transformedItem,
1026
+ });
1027
+ this.importMap.set(this.getCollectionKey(collection.name), currentData);
1028
+ this.oldIdToNewIdPerCollectionMap.set(
1029
+ this.getCollectionKey(collection.name),
1030
+ collectionOldIdToNewIdMap!
1031
+ );
1032
+ }
1033
+ }
1007
1034
  }
1008
1035
 
1009
1036
  /**
@@ -1014,11 +1041,11 @@ export class DataLoader {
1014
1041
  * @param collection - The collection configuration.
1015
1042
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
1016
1043
  */
1017
- prepareCreateData(
1044
+ async prepareCreateData(
1018
1045
  db: ConfigDatabase,
1019
1046
  collection: CollectionCreate,
1020
1047
  importDef: ImportDef
1021
- ): void {
1048
+ ): Promise<void> {
1022
1049
  // Load the raw data based on the import definition
1023
1050
  const rawData = this.loadData(importDef);
1024
1051
  const operationId = this.collectionImportOperations.get(
@@ -1029,97 +1056,102 @@ export class DataLoader {
1029
1056
  `No import operation found for collection ${collection.name}`
1030
1057
  );
1031
1058
  }
1032
- updateOperation(this.database, operationId, {
1059
+ await updateOperation(this.database, operationId, {
1033
1060
  status: "ready",
1034
1061
  total: rawData.length,
1035
- }).then(() => {
1036
- // Initialize a new map for old ID to new ID mappings
1037
- const oldIdToNewIdMapNew = new Map<string, string>();
1038
- // Retrieve or initialize the collection-specific old ID to new ID map
1039
- const collectionOldIdToNewIdMap =
1040
- this.oldIdToNewIdPerCollectionMap.get(
1041
- this.getCollectionKey(collection.name)
1042
- ) ||
1043
- this.oldIdToNewIdPerCollectionMap
1044
- .set(this.getCollectionKey(collection.name), oldIdToNewIdMapNew)
1045
- .get(this.getCollectionKey(collection.name));
1046
- console.log(
1047
- `${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`
1062
+ });
1063
+ // Initialize a new map for old ID to new ID mappings
1064
+ const oldIdToNewIdMapNew = new Map<string, string>();
1065
+ // Retrieve or initialize the collection-specific old ID to new ID map
1066
+ const collectionOldIdToNewIdMap =
1067
+ this.oldIdToNewIdPerCollectionMap.get(
1068
+ this.getCollectionKey(collection.name)
1069
+ ) ||
1070
+ this.oldIdToNewIdPerCollectionMap
1071
+ .set(this.getCollectionKey(collection.name), oldIdToNewIdMapNew)
1072
+ .get(this.getCollectionKey(collection.name));
1073
+ console.log(
1074
+ `${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`
1075
+ );
1076
+ const isRegions = collection.name.toLowerCase() === "regions";
1077
+ // Iterate through each item in the raw data
1078
+ for (const item of rawData) {
1079
+ // Generate a new unique ID for the item
1080
+ const itemIdNew = this.getTrueUniqueId(
1081
+ this.getCollectionKey(collection.name)
1048
1082
  );
1049
- // Iterate through each item in the raw data
1050
- for (const item of rawData) {
1051
- // Generate a new unique ID for the item
1052
- const itemIdNew = this.getTrueUniqueId(
1053
- this.getCollectionKey(collection.name)
1054
- );
1055
- // Retrieve the current collection data from the import map
1056
- const currentData = this.importMap.get(
1057
- this.getCollectionKey(collection.name)
1058
- );
1059
- // Create a context object for the item, including the new ID
1060
- let context = this.createContext(db, collection, item, itemIdNew);
1061
- // Transform the item data based on the attribute mappings
1062
- const transformedData = this.transformData(
1063
- item,
1064
- importDef.attributeMappings
1065
- );
1066
- // If a primary key field is defined, handle the ID mapping and check for duplicates
1067
- if (importDef.primaryKeyField) {
1068
- const oldId = item[importDef.primaryKeyField];
1069
- if (collectionOldIdToNewIdMap?.has(`${oldId}`)) {
1070
- logger.error(
1071
- `Collection ${collection.name} has multiple documents with the same primary key ${oldId}`
1072
- );
1073
- continue;
1074
- }
1075
- collectionOldIdToNewIdMap?.set(`${oldId}`, `${itemIdNew}`);
1076
- }
1077
- // Merge the transformed data into the context
1078
- context = { ...context, ...transformedData };
1079
- // Validate the item before proceeding
1080
- const isValid = this.importDataActions.validateItem(
1081
- transformedData,
1082
- importDef.attributeMappings,
1083
- context
1084
- );
1085
- if (!isValid) {
1086
- continue;
1087
- }
1088
- // Update the attribute mappings with any actions that need to be performed post-import
1089
- const mappingsWithActions = this.getAttributeMappingsWithActions(
1090
- importDef.attributeMappings,
1091
- context,
1092
- transformedData
1083
+ if (isRegions) {
1084
+ logger.info(`Creating region: ${JSON.stringify(item, null, 2)}`);
1085
+ }
1086
+ // Retrieve the current collection data from the import map
1087
+ const currentData = this.importMap.get(
1088
+ this.getCollectionKey(collection.name)
1089
+ );
1090
+ // Create a context object for the item, including the new ID
1091
+ let context = this.createContext(db, collection, item, itemIdNew);
1092
+ // Transform the item data based on the attribute mappings
1093
+ const transformedData = this.transformData(
1094
+ item,
1095
+ importDef.attributeMappings
1096
+ );
1097
+ if (isRegions) {
1098
+ logger.info(
1099
+ `Transformed region: ${JSON.stringify(transformedData, null, 2)}`
1093
1100
  );
1094
- // Update the import definition with the new attribute mappings
1095
- const newImportDef = {
1096
- ...importDef,
1097
- attributeMappings: mappingsWithActions,
1098
- };
1099
- // If the current collection data exists, add the item with its context and final data
1100
- if (currentData && currentData.data) {
1101
- currentData.data.push({
1102
- rawData: item,
1103
- context: context,
1104
- importDef: newImportDef,
1105
- finalData: transformedData,
1106
- });
1107
- this.importMap.set(
1108
- this.getCollectionKey(collection.name),
1109
- currentData
1110
- );
1111
- this.oldIdToNewIdPerCollectionMap.set(
1112
- this.getCollectionKey(collection.name),
1113
- collectionOldIdToNewIdMap!
1114
- );
1115
- } else {
1101
+ }
1102
+ // If a primary key field is defined, handle the ID mapping and check for duplicates
1103
+ if (importDef.primaryKeyField) {
1104
+ const oldId = item[importDef.primaryKeyField];
1105
+ if (collectionOldIdToNewIdMap?.has(`${oldId}`)) {
1116
1106
  logger.error(
1117
- `No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`
1107
+ `Collection ${collection.name} has multiple documents with the same primary key ${oldId}`
1118
1108
  );
1119
1109
  continue;
1120
1110
  }
1111
+ collectionOldIdToNewIdMap?.set(`${oldId}`, `${itemIdNew}`);
1121
1112
  }
1122
- });
1113
+ // Merge the transformed data into the context
1114
+ context = { ...context, ...transformedData };
1115
+ // Validate the item before proceeding
1116
+ const isValid = this.importDataActions.validateItem(
1117
+ transformedData,
1118
+ importDef.attributeMappings,
1119
+ context
1120
+ );
1121
+ if (!isValid) {
1122
+ continue;
1123
+ }
1124
+ // Update the attribute mappings with any actions that need to be performed post-import
1125
+ const mappingsWithActions = this.getAttributeMappingsWithActions(
1126
+ importDef.attributeMappings,
1127
+ context,
1128
+ transformedData
1129
+ );
1130
+ // Update the import definition with the new attribute mappings
1131
+ const newImportDef = {
1132
+ ...importDef,
1133
+ attributeMappings: mappingsWithActions,
1134
+ };
1135
+ // If the current collection data exists, add the item with its context and final data
1136
+ if (currentData && currentData.data) {
1137
+ currentData.data.push({
1138
+ rawData: item,
1139
+ context: context,
1140
+ importDef: newImportDef,
1141
+ finalData: transformedData,
1142
+ });
1143
+ this.importMap.set(this.getCollectionKey(collection.name), currentData);
1144
+ this.oldIdToNewIdPerCollectionMap.set(
1145
+ this.getCollectionKey(collection.name),
1146
+ collectionOldIdToNewIdMap!
1147
+ );
1148
+ } else {
1149
+ logger.error(
1150
+ `No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`
1151
+ );
1152
+ continue;
1153
+ }
1154
+ }
1123
1155
  }
1124
1156
 
1125
1157
  /**
@@ -42,9 +42,19 @@ export class ImportDataActions {
42
42
  this.afterImportActionsDefinitions = afterImportActionsDefinitions;
43
43
  }
44
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
+ */
45
52
  runConverterFunctions(item: any, attributeMappings: AttributeMappings) {
46
53
  const conversionSchema = attributeMappings.reduce((schema, mapping) => {
47
54
  schema[mapping.targetKey] = (originalValue: any) => {
55
+ if (!mapping.converters) {
56
+ return originalValue;
57
+ }
48
58
  return mapping.converters?.reduce((value, converterName) => {
49
59
  let shouldProcessAsArray = false;
50
60
  if (
@@ -95,6 +105,7 @@ export class ImportDataActions {
95
105
  /**
96
106
  * Validates a single data item based on defined validation rules.
97
107
  * @param item The data item to validate.
108
+ * @param attributeMap The attribute mappings for the data item.
98
109
  * @param context The context for resolving templated parameters in validation rules.
99
110
  * @returns A promise that resolves to true if the item is valid, false otherwise.
100
111
  */