appwrite-utils-cli 0.0.38 → 0.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/migrations/converters.js +15 -1
- package/dist/migrations/dataLoader.d.ts +2 -2
- package/dist/migrations/dataLoader.js +207 -175
- package/dist/migrations/importDataActions.d.ts +8 -0
- package/dist/migrations/importDataActions.js +11 -0
- package/dist/migrations/migrationHelper.js +1 -1
- package/package.json +1 -1
- package/src/migrations/converters.ts +17 -1
- package/src/migrations/dataLoader.ts +280 -248
- package/src/migrations/importDataActions.ts +11 -0
- package/src/migrations/migrationHelper.ts +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 ||
|
|
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
|
-
})
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
})
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
*/
|
|
@@ -3,7 +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 "
|
|
6
|
+
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
|
7
7
|
/**
|
|
8
8
|
* Object that contains the context for an action that needs to be executed after import
|
|
9
9
|
* Used in the afterImportActionsDefinitions
|
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.
|
|
4
|
+
"version": "0.0.40",
|
|
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
|
-
|
|
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
|
|