appwrite-utils-cli 0.0.67 → 0.0.68
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/README.md +1 -0
- package/dist/migrations/dataLoader.d.ts +30 -3
- package/dist/migrations/dataLoader.js +77 -66
- package/package.json +2 -2
- package/src/migrations/dataLoader.ts +105 -90
package/README.md
CHANGED
@@ -132,6 +132,7 @@ This setup ensures that developers have robust tools at their fingertips to mana
|
|
132
132
|
|
133
133
|
### Changelog
|
134
134
|
|
135
|
+
- 0.0.68: Fixed the occasional case where, when mapping ID's from old data to new, there would be an array of ID's to match against. `idMappings` now supports arrays.
|
135
136
|
- 0.0.67: Fixed `updates` in `importDef`'s update mappings overwriting postImportActions from the original
|
136
137
|
- 0.0.57: Fixed `dataLoader`'s `idMapping`'s giving me issues
|
137
138
|
- 0.0.55: Added `documentExists` check to batch creation functionality to try to prevent duplicates
|
@@ -666,7 +666,16 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
666
666
|
converters?: string[] | undefined;
|
667
667
|
validationActions?: {
|
668
668
|
params: string[];
|
669
|
-
action: string;
|
669
|
+
action: string; /**
|
670
|
+
* Generates attribute mappings with post-import actions based on the provided attribute mappings.
|
671
|
+
* This method checks each mapping for a fileData attribute and adds a post-import action to create a file
|
672
|
+
* and update the field with the file's ID if necessary.
|
673
|
+
*
|
674
|
+
* @param attributeMappings - The attribute mappings from the import definition.
|
675
|
+
* @param context - The context object containing information about the database, collection, and document.
|
676
|
+
* @param item - The item being imported, used for resolving template paths in fileData mappings.
|
677
|
+
* @returns The attribute mappings updated with any necessary post-import actions.
|
678
|
+
*/
|
670
679
|
}[] | undefined;
|
671
680
|
postImportActions?: {
|
672
681
|
params: (string | Record<string, any>)[];
|
@@ -1286,7 +1295,16 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1286
1295
|
converters?: string[] | undefined;
|
1287
1296
|
validationActions?: {
|
1288
1297
|
params: string[];
|
1289
|
-
action: string;
|
1298
|
+
action: string; /**
|
1299
|
+
* Generates attribute mappings with post-import actions based on the provided attribute mappings.
|
1300
|
+
* This method checks each mapping for a fileData attribute and adds a post-import action to create a file
|
1301
|
+
* and update the field with the file's ID if necessary.
|
1302
|
+
*
|
1303
|
+
* @param attributeMappings - The attribute mappings from the import definition.
|
1304
|
+
* @param context - The context object containing information about the database, collection, and document.
|
1305
|
+
* @param item - The item being imported, used for resolving template paths in fileData mappings.
|
1306
|
+
* @returns The attribute mappings updated with any necessary post-import actions.
|
1307
|
+
*/
|
1290
1308
|
}[] | undefined;
|
1291
1309
|
postImportActions?: {
|
1292
1310
|
params: (string | Record<string, any>)[];
|
@@ -1690,7 +1708,16 @@ export declare class DataLoader {
|
|
1690
1708
|
converters?: string[] | undefined;
|
1691
1709
|
validationActions?: {
|
1692
1710
|
params: string[];
|
1693
|
-
action: string;
|
1711
|
+
action: string; /**
|
1712
|
+
* Generates attribute mappings with post-import actions based on the provided attribute mappings.
|
1713
|
+
* This method checks each mapping for a fileData attribute and adds a post-import action to create a file
|
1714
|
+
* and update the field with the file's ID if necessary.
|
1715
|
+
*
|
1716
|
+
* @param attributeMappings - The attribute mappings from the import definition.
|
1717
|
+
* @param context - The context object containing information about the database, collection, and document.
|
1718
|
+
* @param item - The item being imported, used for resolving template paths in fileData mappings.
|
1719
|
+
* @returns The attribute mappings updated with any necessary post-import actions.
|
1720
|
+
*/
|
1694
1721
|
}[] | undefined;
|
1695
1722
|
postImportActions?: {
|
1696
1723
|
params: (string | Record<string, any>)[];
|
@@ -441,90 +441,101 @@ export class DataLoader {
|
|
441
441
|
const targetCollectionKey = this.getCollectionKey(idMapping.targetCollection);
|
442
442
|
const fieldToSetKey = idMapping.fieldToSet || idMapping.sourceField;
|
443
443
|
const targetFieldKey = idMapping.targetFieldToMatch || idMapping.targetField;
|
444
|
-
const
|
444
|
+
const sourceValue = this.getValueFromData(collectionData.data[i].finalData, collectionData.data[i].context, idMapping.sourceField);
|
445
445
|
// Skip if value to match is missing or empty
|
446
|
-
if (!
|
447
|
-
_.isEmpty(
|
448
|
-
|
446
|
+
if (!sourceValue ||
|
447
|
+
_.isEmpty(sourceValue) ||
|
448
|
+
sourceValue === null)
|
449
449
|
continue;
|
450
450
|
const isFieldToSetArray = collectionConfig.attributes.find((attribute) => attribute.key === fieldToSetKey)?.array;
|
451
451
|
const targetCollectionData = this.importMap.get(targetCollectionKey);
|
452
452
|
if (!targetCollectionData || !targetCollectionData.data)
|
453
453
|
continue;
|
454
|
-
//
|
455
|
-
const
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
454
|
+
// Handle cases where sourceValue is an array
|
455
|
+
const sourceValues = Array.isArray(sourceValue)
|
456
|
+
? sourceValue
|
457
|
+
: [sourceValue];
|
458
|
+
const newData = [];
|
459
|
+
for (const valueToMatch of sourceValues) {
|
460
|
+
// Find matching data in the target collection
|
461
|
+
const foundData = targetCollectionData.data.filter(({ context, finalData }) => {
|
462
|
+
const targetValue = this.getValueFromData(finalData, context, targetFieldKey);
|
463
|
+
const isMatch = `${targetValue}` === `${valueToMatch}`;
|
464
|
+
// Ensure the targetValue is defined and not null
|
465
|
+
return (isMatch &&
|
466
|
+
targetValue !== undefined &&
|
467
|
+
targetValue !== null);
|
468
|
+
});
|
469
|
+
if (foundData.length) {
|
470
|
+
newData.push(...foundData.map((data) => {
|
471
|
+
return this.getValueFromData(data.finalData, data.context, idMapping.targetField);
|
472
|
+
}));
|
473
|
+
}
|
474
|
+
else {
|
475
|
+
logger.info(`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(idMapping, null, 2)}`);
|
476
|
+
}
|
477
|
+
}
|
463
478
|
const getCurrentDataFiltered = (currentData) => {
|
464
479
|
if (Array.isArray(currentData.finalData[fieldToSetKey])) {
|
465
|
-
return currentData.finalData[fieldToSetKey].filter((data) => `${data}`
|
480
|
+
return currentData.finalData[fieldToSetKey].filter((data) => !sourceValues.includes(`${data}`));
|
466
481
|
}
|
467
482
|
return currentData.finalData[fieldToSetKey];
|
468
483
|
};
|
469
484
|
// Get the current data to be updated
|
470
485
|
const currentDataFiltered = getCurrentDataFiltered(collectionData.data[i]);
|
471
|
-
|
472
|
-
|
473
|
-
//
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
486
|
+
if (newData.length) {
|
487
|
+
needsUpdate = true;
|
488
|
+
// Handle cases where current data is an array
|
489
|
+
if (isFieldToSetArray) {
|
490
|
+
if (!currentDataFiltered) {
|
491
|
+
// Set new data if current data is undefined
|
492
|
+
collectionData.data[i].finalData[fieldToSetKey] =
|
493
|
+
Array.isArray(newData) ? newData : [newData];
|
494
|
+
}
|
495
|
+
else {
|
496
|
+
if (Array.isArray(currentDataFiltered)) {
|
497
|
+
// Convert current data to array and merge if new data is non-empty array
|
498
|
+
collectionData.data[i].finalData[fieldToSetKey] = [
|
499
|
+
...new Set([...currentDataFiltered, ...newData].filter((value) => value !== null &&
|
500
|
+
value !== undefined &&
|
501
|
+
value !== "")),
|
502
|
+
];
|
503
|
+
}
|
504
|
+
else {
|
505
|
+
// Merge arrays if new data is non-empty array and filter for uniqueness
|
506
|
+
collectionData.data[i].finalData[fieldToSetKey] = [
|
507
|
+
...new Set([
|
508
|
+
...(Array.isArray(currentDataFiltered)
|
509
|
+
? currentDataFiltered
|
510
|
+
: [currentDataFiltered]),
|
511
|
+
...newData,
|
512
|
+
].filter((value) => value !== null &&
|
513
|
+
value !== undefined &&
|
514
|
+
value !== "")),
|
515
|
+
];
|
516
|
+
}
|
517
|
+
}
|
491
518
|
}
|
492
519
|
else {
|
493
|
-
if (
|
494
|
-
//
|
495
|
-
collectionData.data[i].finalData[fieldToSetKey] =
|
496
|
-
|
497
|
-
];
|
520
|
+
if (!currentDataFiltered) {
|
521
|
+
// Set new data if current data is undefined
|
522
|
+
collectionData.data[i].finalData[fieldToSetKey] =
|
523
|
+
Array.isArray(newData) ? newData[0] : newData;
|
498
524
|
}
|
499
|
-
else {
|
500
|
-
//
|
525
|
+
else if (Array.isArray(newData) && newData.length > 0) {
|
526
|
+
// Convert current data to array and merge if new data is non-empty array, then filter for uniqueness
|
527
|
+
// and take the first value, because it's an array and the attribute is not an array
|
501
528
|
collectionData.data[i].finalData[fieldToSetKey] = [
|
502
|
-
...new Set([
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
529
|
+
...new Set([currentDataFiltered, ...newData].filter((value) => value !== null &&
|
530
|
+
value !== undefined &&
|
531
|
+
value !== "")),
|
532
|
+
].slice(0, 1)[0];
|
533
|
+
}
|
534
|
+
else if (!Array.isArray(newData) &&
|
535
|
+
newData !== undefined) {
|
536
|
+
// Simply update the field if new data is not an array and defined
|
537
|
+
collectionData.data[i].finalData[fieldToSetKey] = newData;
|
509
538
|
}
|
510
|
-
}
|
511
|
-
}
|
512
|
-
else {
|
513
|
-
if (!currentDataFiltered) {
|
514
|
-
// Set new data if current data is undefined
|
515
|
-
collectionData.data[i].finalData[fieldToSetKey] =
|
516
|
-
Array.isArray(newData) ? newData[0] : newData;
|
517
|
-
}
|
518
|
-
else if (Array.isArray(newData) && newData.length > 0) {
|
519
|
-
// Convert current data to array and merge if new data is non-empty array, then filter for uniqueness
|
520
|
-
// and take the first value, because it's an array and the attribute is not an array
|
521
|
-
collectionData.data[i].finalData[fieldToSetKey] = [
|
522
|
-
...new Set([currentDataFiltered, ...newData].filter((value) => `${value}` !== `${valueToMatch}` && value)),
|
523
|
-
].slice(0, 1)[0];
|
524
|
-
}
|
525
|
-
else if (!Array.isArray(newData) && newData !== undefined) {
|
526
|
-
// Simply update the field if new data is not an array and defined
|
527
|
-
collectionData.data[i].finalData[fieldToSetKey] = newData;
|
528
539
|
}
|
529
540
|
}
|
530
541
|
}
|
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.68",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
@@ -33,7 +33,7 @@
|
|
33
33
|
},
|
34
34
|
"dependencies": {
|
35
35
|
"@types/inquirer": "^9.0.7",
|
36
|
-
"appwrite-utils": "^0.3.
|
36
|
+
"appwrite-utils": "^0.3.5",
|
37
37
|
"commander": "^12.0.0",
|
38
38
|
"inquirer": "^9.2.20",
|
39
39
|
"js-yaml": "^4.1.0",
|
@@ -542,7 +542,7 @@ export class DataLoader {
|
|
542
542
|
idMapping.fieldToSet || idMapping.sourceField;
|
543
543
|
const targetFieldKey =
|
544
544
|
idMapping.targetFieldToMatch || idMapping.targetField;
|
545
|
-
const
|
545
|
+
const sourceValue = this.getValueFromData(
|
546
546
|
collectionData.data[i].finalData,
|
547
547
|
collectionData.data[i].context,
|
548
548
|
idMapping.sourceField
|
@@ -550,9 +550,9 @@ export class DataLoader {
|
|
550
550
|
|
551
551
|
// Skip if value to match is missing or empty
|
552
552
|
if (
|
553
|
-
!
|
554
|
-
_.isEmpty(
|
555
|
-
|
553
|
+
!sourceValue ||
|
554
|
+
_.isEmpty(sourceValue) ||
|
555
|
+
sourceValue === null
|
556
556
|
)
|
557
557
|
continue;
|
558
558
|
|
@@ -565,28 +565,56 @@ export class DataLoader {
|
|
565
565
|
if (!targetCollectionData || !targetCollectionData.data)
|
566
566
|
continue;
|
567
567
|
|
568
|
-
//
|
569
|
-
const
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
568
|
+
// Handle cases where sourceValue is an array
|
569
|
+
const sourceValues = Array.isArray(sourceValue)
|
570
|
+
? sourceValue
|
571
|
+
: [sourceValue];
|
572
|
+
const newData = [];
|
573
|
+
|
574
|
+
for (const valueToMatch of sourceValues) {
|
575
|
+
// Find matching data in the target collection
|
576
|
+
const foundData = targetCollectionData.data.filter(
|
577
|
+
({ context, finalData }) => {
|
578
|
+
const targetValue = this.getValueFromData(
|
579
|
+
finalData,
|
580
|
+
context,
|
581
|
+
targetFieldKey
|
582
|
+
);
|
583
|
+
const isMatch = `${targetValue}` === `${valueToMatch}`;
|
584
|
+
// Ensure the targetValue is defined and not null
|
585
|
+
return (
|
586
|
+
isMatch &&
|
587
|
+
targetValue !== undefined &&
|
588
|
+
targetValue !== null
|
589
|
+
);
|
590
|
+
}
|
591
|
+
);
|
592
|
+
|
593
|
+
if (foundData.length) {
|
594
|
+
newData.push(
|
595
|
+
...foundData.map((data) => {
|
596
|
+
return this.getValueFromData(
|
597
|
+
data.finalData,
|
598
|
+
data.context,
|
599
|
+
idMapping.targetField
|
600
|
+
);
|
601
|
+
})
|
575
602
|
);
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
603
|
+
} else {
|
604
|
+
logger.info(
|
605
|
+
`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(
|
606
|
+
idMapping,
|
607
|
+
null,
|
608
|
+
2
|
609
|
+
)}`
|
582
610
|
);
|
583
611
|
}
|
584
|
-
|
612
|
+
}
|
585
613
|
|
586
614
|
const getCurrentDataFiltered = (currentData: any) => {
|
587
615
|
if (Array.isArray(currentData.finalData[fieldToSetKey])) {
|
588
616
|
return currentData.finalData[fieldToSetKey].filter(
|
589
|
-
(data: any) => `${data}`
|
617
|
+
(data: any) => !sourceValues.includes(`${data}`)
|
590
618
|
);
|
591
619
|
}
|
592
620
|
return currentData.finalData[fieldToSetKey];
|
@@ -596,87 +624,74 @@ export class DataLoader {
|
|
596
624
|
const currentDataFiltered = getCurrentDataFiltered(
|
597
625
|
collectionData.data[i]
|
598
626
|
);
|
599
|
-
// Log and skip if no matching data found
|
600
|
-
if (!foundData.length) {
|
601
|
-
// console.log(
|
602
|
-
// `No data found for collection ${collectionConfig.name}: - Target collection: ${targetCollectionKey} - Value to match: ${valueToMatch} - Field to set: ${fieldToSetKey} - Target field to match: ${targetFieldKey} - Target field value: ${idMapping.targetField}`
|
603
|
-
// );
|
604
|
-
logger.info(
|
605
|
-
`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(
|
606
|
-
idMapping,
|
607
|
-
null,
|
608
|
-
2
|
609
|
-
)}`
|
610
|
-
);
|
611
|
-
continue;
|
612
|
-
}
|
613
627
|
|
614
|
-
|
628
|
+
if (newData.length) {
|
629
|
+
needsUpdate = true;
|
615
630
|
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
);
|
623
|
-
return valueFound;
|
624
|
-
});
|
625
|
-
|
626
|
-
// Handle cases where current data is an array
|
627
|
-
if (isFieldToSetArray) {
|
628
|
-
if (!currentDataFiltered) {
|
629
|
-
// Set new data if current data is undefined
|
630
|
-
collectionData.data[i].finalData[fieldToSetKey] =
|
631
|
-
Array.isArray(newData) ? newData : [newData];
|
632
|
-
} else {
|
633
|
-
if (Array.isArray(currentDataFiltered)) {
|
634
|
-
// Convert current data to array and merge if new data is non-empty array
|
635
|
-
collectionData.data[i].finalData[fieldToSetKey] = [
|
636
|
-
...new Set(
|
637
|
-
[...currentDataFiltered, ...newData].filter(
|
638
|
-
(value: any) =>
|
639
|
-
`${value}` !== `${valueToMatch}` && value
|
640
|
-
)
|
641
|
-
),
|
642
|
-
];
|
631
|
+
// Handle cases where current data is an array
|
632
|
+
if (isFieldToSetArray) {
|
633
|
+
if (!currentDataFiltered) {
|
634
|
+
// Set new data if current data is undefined
|
635
|
+
collectionData.data[i].finalData[fieldToSetKey] =
|
636
|
+
Array.isArray(newData) ? newData : [newData];
|
643
637
|
} else {
|
644
|
-
|
638
|
+
if (Array.isArray(currentDataFiltered)) {
|
639
|
+
// Convert current data to array and merge if new data is non-empty array
|
640
|
+
collectionData.data[i].finalData[fieldToSetKey] = [
|
641
|
+
...new Set(
|
642
|
+
[...currentDataFiltered, ...newData].filter(
|
643
|
+
(value: any) =>
|
644
|
+
value !== null &&
|
645
|
+
value !== undefined &&
|
646
|
+
value !== ""
|
647
|
+
)
|
648
|
+
),
|
649
|
+
];
|
650
|
+
} else {
|
651
|
+
// Merge arrays if new data is non-empty array and filter for uniqueness
|
652
|
+
collectionData.data[i].finalData[fieldToSetKey] = [
|
653
|
+
...new Set(
|
654
|
+
[
|
655
|
+
...(Array.isArray(currentDataFiltered)
|
656
|
+
? currentDataFiltered
|
657
|
+
: [currentDataFiltered]),
|
658
|
+
...newData,
|
659
|
+
].filter(
|
660
|
+
(value: any) =>
|
661
|
+
value !== null &&
|
662
|
+
value !== undefined &&
|
663
|
+
value !== ""
|
664
|
+
)
|
665
|
+
),
|
666
|
+
];
|
667
|
+
}
|
668
|
+
}
|
669
|
+
} else {
|
670
|
+
if (!currentDataFiltered) {
|
671
|
+
// Set new data if current data is undefined
|
672
|
+
collectionData.data[i].finalData[fieldToSetKey] =
|
673
|
+
Array.isArray(newData) ? newData[0] : newData;
|
674
|
+
} else if (Array.isArray(newData) && newData.length > 0) {
|
675
|
+
// Convert current data to array and merge if new data is non-empty array, then filter for uniqueness
|
676
|
+
// and take the first value, because it's an array and the attribute is not an array
|
645
677
|
collectionData.data[i].finalData[fieldToSetKey] = [
|
646
678
|
...new Set(
|
647
|
-
[
|
648
|
-
...(Array.isArray(currentDataFiltered)
|
649
|
-
? currentDataFiltered
|
650
|
-
: [currentDataFiltered]),
|
651
|
-
...newData,
|
652
|
-
].filter(
|
679
|
+
[currentDataFiltered, ...newData].filter(
|
653
680
|
(value: any) =>
|
654
|
-
|
681
|
+
value !== null &&
|
682
|
+
value !== undefined &&
|
683
|
+
value !== ""
|
655
684
|
)
|
656
685
|
),
|
657
|
-
];
|
686
|
+
].slice(0, 1)[0];
|
687
|
+
} else if (
|
688
|
+
!Array.isArray(newData) &&
|
689
|
+
newData !== undefined
|
690
|
+
) {
|
691
|
+
// Simply update the field if new data is not an array and defined
|
692
|
+
collectionData.data[i].finalData[fieldToSetKey] = newData;
|
658
693
|
}
|
659
694
|
}
|
660
|
-
} else {
|
661
|
-
if (!currentDataFiltered) {
|
662
|
-
// Set new data if current data is undefined
|
663
|
-
collectionData.data[i].finalData[fieldToSetKey] =
|
664
|
-
Array.isArray(newData) ? newData[0] : newData;
|
665
|
-
} else if (Array.isArray(newData) && newData.length > 0) {
|
666
|
-
// Convert current data to array and merge if new data is non-empty array, then filter for uniqueness
|
667
|
-
// and take the first value, because it's an array and the attribute is not an array
|
668
|
-
collectionData.data[i].finalData[fieldToSetKey] = [
|
669
|
-
...new Set(
|
670
|
-
[currentDataFiltered, ...newData].filter(
|
671
|
-
(value: any) =>
|
672
|
-
`${value}` !== `${valueToMatch}` && value
|
673
|
-
)
|
674
|
-
),
|
675
|
-
].slice(0, 1)[0];
|
676
|
-
} else if (!Array.isArray(newData) && newData !== undefined) {
|
677
|
-
// Simply update the field if new data is not an array and defined
|
678
|
-
collectionData.data[i].finalData[fieldToSetKey] = newData;
|
679
|
-
}
|
680
695
|
}
|
681
696
|
}
|
682
697
|
}
|