@zauto/api-connector 0.0.1 → 0.0.2
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/package.json +1 -1
- package/src/index.ts +136 -123
package/package.json
CHANGED
package/src/index.ts
CHANGED
@@ -489,9 +489,13 @@ export async function callApiWithMapping(
|
|
489
489
|
* @param input - Input data for the function
|
490
490
|
* @returns The final output of the function
|
491
491
|
*/
|
492
|
-
export async function executeDynamicFunction(
|
492
|
+
export async function executeDynamicFunction(
|
493
|
+
prisma: PrismaClient,
|
494
|
+
orgId: string,
|
495
|
+
functionName: string,
|
496
|
+
input: Record<string, any>,
|
497
|
+
) {
|
493
498
|
try {
|
494
|
-
|
495
499
|
const functionConfig = await prisma.functionConfig.findFirst({
|
496
500
|
where: { name: functionName, orgId },
|
497
501
|
});
|
@@ -501,45 +505,78 @@ export async function executeDynamicFunction(prisma: PrismaClient, orgId: string
|
|
501
505
|
}
|
502
506
|
|
503
507
|
const { logic } = functionConfig;
|
504
|
-
|
505
508
|
let currentData = input;
|
506
509
|
|
507
|
-
for (const
|
508
|
-
const
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
510
|
+
for (const stepKey of Object.keys(logic)) {
|
511
|
+
const step = logic[stepKey];
|
512
|
+
const {
|
513
|
+
apiId,
|
514
|
+
requestMapping,
|
515
|
+
responseMapping,
|
516
|
+
queryParamsMapping,
|
517
|
+
pathParamsMapping,
|
518
|
+
condition,
|
519
|
+
loopUntil,
|
520
|
+
onFailure,
|
521
|
+
maxIterations,
|
522
|
+
} = step;
|
523
|
+
|
524
|
+
// Evaluate the condition, if provided
|
525
|
+
if (condition && !evaluateCondition(condition, currentData)) {
|
526
|
+
console.warn(`Condition failed for step ${stepKey}.`);
|
527
|
+
if (onFailure === 'exit') {
|
528
|
+
console.warn(`Exiting execution due to failure in step ${stepKey}.`);
|
529
|
+
return currentData; // Return current data if exiting
|
530
|
+
}
|
531
|
+
continue; // Skip to the next step if onFailure is "continue"
|
525
532
|
}
|
526
533
|
|
527
|
-
|
534
|
+
console.log(`Executing step ${stepKey}...`);
|
535
|
+
let iterations = 0;
|
536
|
+
|
537
|
+
// Execute the step in a loop if `loopUntil` is specified
|
538
|
+
do {
|
539
|
+
if (maxIterations && iterations >= maxIterations) {
|
540
|
+
console.warn(`Max iterations reached for step ${stepKey}.`);
|
541
|
+
break;
|
542
|
+
}
|
543
|
+
iterations++;
|
544
|
+
const requestData = mapData(currentData, requestMapping);
|
545
|
+
const endpoint = await prisma.endpointConfig.findUnique({ where: { id: apiId } });
|
528
546
|
|
529
|
-
|
547
|
+
if (!endpoint) {
|
548
|
+
throw new Error(`Endpoint with ID ${apiId} not found.`);
|
549
|
+
}
|
530
550
|
|
531
|
-
|
551
|
+
const suburl = substitutePathParams(endpoint.suburl, currentData, pathParamsMapping);
|
552
|
+
const mappedQueryParams = mapData(currentData, queryParamsMapping, true);
|
532
553
|
|
533
|
-
|
554
|
+
try {
|
555
|
+
const apiResponse = await callApiWithMapping(prisma, apiId, requestData, mappedQueryParams, null, suburl);
|
556
|
+
const newData = mapData(apiResponse, responseMapping);
|
557
|
+
currentData = { ...currentData, ...newData };
|
534
558
|
|
535
|
-
|
536
|
-
|
559
|
+
// Log the loop status
|
560
|
+
console.log(`Step ${stepKey} loop iteration completed.`);
|
537
561
|
|
538
|
-
|
539
|
-
|
562
|
+
// Break the loop if `loopUntil` evaluates to true
|
563
|
+
if (loopUntil && evaluateCondition(loopUntil, currentData)) {
|
564
|
+
console.log(`LoopUntil condition met for step ${stepKey}, exiting loop.`);
|
565
|
+
break;
|
566
|
+
}
|
567
|
+
} catch (error) {
|
568
|
+
console.error(`API call failed for step ${stepKey}:`, error);
|
569
|
+
if (onFailure === 'exit') {
|
570
|
+
console.warn(`Exiting execution due to API call failure in step ${stepKey}.`);
|
571
|
+
throw new Error(`Execution stopped at step ${stepKey}.`);
|
572
|
+
}
|
573
|
+
console.warn(`Continuing to next step after failure in step ${stepKey}.`);
|
574
|
+
break; // Exit loop on failure
|
575
|
+
}
|
576
|
+
} while (loopUntil && !evaluateCondition(loopUntil, currentData));
|
540
577
|
}
|
541
578
|
|
542
|
-
return currentData;
|
579
|
+
return currentData; // Final data after executing all steps
|
543
580
|
} catch (error) {
|
544
581
|
console.error('Error executing dynamic function:', error);
|
545
582
|
throw new Error(`Failed to execute function "${functionName}".`);
|
@@ -590,67 +627,40 @@ function mapData(data: any, mapping: Record<string, any>, isQueryParams: boolean
|
|
590
627
|
|
591
628
|
return mapObject(data, mapping);
|
592
629
|
}
|
593
|
-
function evaluateExpression(template: string, data: Record<string, any>): any {
|
594
|
-
const expressionRegex = /\{([^}]+)\}((?:\.[a-zA-Z]+\([^)]*\))*)/;
|
595
|
-
const match = template.match(expressionRegex);
|
596
|
-
|
597
|
-
if (!match) {
|
598
|
-
return template.replace(/\{([^}]+)\}/g, (_, path) => {
|
599
|
-
// Handle nested paths (e.g., "address.line1")
|
600
|
-
return getNestedValue(data, path) ?? '';
|
601
|
-
});
|
602
|
-
}
|
603
|
-
|
604
|
-
const fieldPath = match[1];
|
605
|
-
const functionChain = match[2];
|
606
|
-
let value = getNestedValue(data, fieldPath);
|
607
|
-
|
608
|
-
if (!functionChain) return value;
|
609
|
-
|
610
|
-
const functionCallRegex = /\.([a-zA-Z]+)\(([^)]*)\)/g;
|
611
|
-
let fnMatch;
|
612
|
-
|
613
|
-
while ((fnMatch = functionCallRegex.exec(functionChain)) !== null) {
|
614
|
-
const fnName = fnMatch[1];
|
615
|
-
const fnArgs = fnMatch[2];
|
616
630
|
|
617
|
-
const args = parseFunctionArgs(fnArgs);
|
618
631
|
|
619
|
-
|
620
|
-
|
621
|
-
|
632
|
+
function evaluateExpression(template: string, data: Record<string, any>): any {
|
633
|
+
// Remove surrounding {} if present
|
634
|
+
const cleanTemplate = template.replace(/^\{|\}$/g, '');
|
635
|
+
|
636
|
+
// Check for method chaining
|
637
|
+
const parts = cleanTemplate.split('.');
|
638
|
+
let value = getNestedValue(data, parts[0]);
|
639
|
+
|
640
|
+
// Apply method chain
|
641
|
+
for (let i = 1; i < parts.length; i++) {
|
642
|
+
const methodMatch = parts[i].match(/(\w+)\(([^)]*)\)/);
|
643
|
+
if (methodMatch) {
|
644
|
+
const method = methodMatch[1];
|
645
|
+
const args = methodMatch[2] ? parseFunctionArgs(methodMatch[2]) : [];
|
646
|
+
|
647
|
+
switch (method) {
|
648
|
+
case 'split':
|
649
|
+
value = value.split(args[0] || ' ');
|
650
|
+
break;
|
651
|
+
case 'slice':
|
652
|
+
value = value.slice(args[0], args[1]);
|
653
|
+
break;
|
654
|
+
case 'join':
|
655
|
+
value = value.join(args[0] || '');
|
656
|
+
break;
|
657
|
+
case 'toLowerCase':
|
622
658
|
value = value.toLowerCase();
|
623
|
-
|
624
|
-
|
625
|
-
case 'touppercase':
|
626
|
-
if (typeof value === 'string') {
|
659
|
+
break;
|
660
|
+
case 'toUpperCase':
|
627
661
|
value = value.toUpperCase();
|
628
|
-
|
629
|
-
|
630
|
-
case 'split':
|
631
|
-
if (typeof value === 'string') {
|
632
|
-
|
633
|
-
const delimiter = args[0] ?? '';
|
634
|
-
value = value.split(delimiter);
|
635
|
-
}
|
636
|
-
break;
|
637
|
-
case 'slice':
|
638
|
-
if (typeof value === 'string' || Array.isArray(value)) {
|
639
|
-
const start = args[0] !== undefined ? args[0] : 0;
|
640
|
-
const end = args[1] !== undefined ? args[1] : undefined;
|
641
|
-
value = value.slice(start, end);
|
642
|
-
}
|
643
|
-
break;
|
644
|
-
case 'join':
|
645
|
-
if (Array.isArray(value)) {
|
646
|
-
const delimiter = args[0] || '';
|
647
|
-
value = value.join(delimiter);
|
648
|
-
}
|
649
|
-
break;
|
650
|
-
default:
|
651
|
-
|
652
|
-
console.warn(`Unrecognized function: ${fnName}`);
|
653
|
-
break;
|
662
|
+
break;
|
663
|
+
}
|
654
664
|
}
|
655
665
|
}
|
656
666
|
|
@@ -679,35 +689,29 @@ function parseFunctionArgs(argString: string): any[] {
|
|
679
689
|
});
|
680
690
|
}
|
681
691
|
|
682
|
-
|
683
692
|
function replaceValue(template: string, data: any): any {
|
684
693
|
if (!template.includes('{')) {
|
685
694
|
return template;
|
686
695
|
}
|
687
696
|
|
688
|
-
|
689
|
-
|
690
|
-
|
697
|
+
if (/^\{[^}]+\}$/.test(template)) {
|
698
|
+
return evaluateExpression(template, data);
|
699
|
+
}
|
700
|
+
return template.replace(/\{[^}]+\}/g, (match) => {
|
701
|
+
try {
|
702
|
+
const evaluated = evaluateExpression(match, data);
|
703
|
+
return evaluated?.toString ? evaluated.toString() : '';
|
704
|
+
} catch (error) {
|
705
|
+
console.warn(`Error evaluating ${match}:`, error);
|
706
|
+
return match;
|
707
|
+
}
|
691
708
|
});
|
692
709
|
}
|
693
710
|
|
694
|
-
/**
|
695
|
-
* Maps an object's fields based on a mapping configuration.
|
696
|
-
*
|
697
|
-
* @param obj - The input object
|
698
|
-
* @param mapping - The mapping configuration
|
699
|
-
* @returns The mapped object
|
700
|
-
*/
|
701
711
|
function mapObject(obj: Record<string, any>, mapping: Record<string, any>): Record<string, any> {
|
702
712
|
const result: Record<string, any> = {};
|
703
713
|
|
704
|
-
if (mapping.resourceType) {
|
705
|
-
result.resourceType = mapping.resourceType;
|
706
|
-
}
|
707
|
-
|
708
714
|
for (const [mappingKey, mappingValue] of Object.entries(mapping)) {
|
709
|
-
if (mappingKey === 'resourceType') continue;
|
710
|
-
|
711
715
|
if (typeof mappingValue === 'string') {
|
712
716
|
if (mappingValue.includes('[?]')) {
|
713
717
|
handleArrayShortcut(result, obj, mappingValue, mappingKey);
|
@@ -729,7 +733,7 @@ function mapObject(obj: Record<string, any>, mapping: Record<string, any>): Reco
|
|
729
733
|
}
|
730
734
|
});
|
731
735
|
} else if (typeof mappingValue === 'object' && mappingValue !== null) {
|
732
|
-
|
736
|
+
|
733
737
|
const nestedResult: Record<string, any> = {};
|
734
738
|
for (const [nestedKey, nestedValue] of Object.entries(mappingValue)) {
|
735
739
|
if (typeof nestedValue === 'string') {
|
@@ -761,28 +765,22 @@ function handleArrayShortcut(
|
|
761
765
|
const templateParts = entryTemplate.split(',');
|
762
766
|
const entry: Record<string, any> = {};
|
763
767
|
|
764
|
-
templateParts.forEach(part => {
|
768
|
+
templateParts.forEach((part) => {
|
765
769
|
const [field, valueTemplate] = part.split('=');
|
766
770
|
const fieldValue = replaceValue(valueTemplate, sourceObj);
|
767
771
|
entry[field] = fieldValue;
|
768
772
|
});
|
769
773
|
|
770
|
-
|
771
|
-
|
772
|
-
resultObj[arrayField].push(entry);
|
773
|
-
}
|
774
|
+
// Simply push the new entry every time, no uniqueness check:
|
775
|
+
resultObj[arrayField].push(entry);
|
774
776
|
}
|
777
|
+
|
775
778
|
function getNestedValue(obj: Record<string, any>, path: string): any {
|
776
779
|
return path.split('.').reduce((current, part) => {
|
777
780
|
return current?.[part];
|
778
781
|
}, obj);
|
779
782
|
}
|
780
|
-
|
781
|
-
* Prepares authentication headers based on the top-level API configuration.
|
782
|
-
*
|
783
|
-
* @param apiConfig - The top-level API configuration
|
784
|
-
* @returns A Record of authentication headers
|
785
|
-
*/
|
783
|
+
|
786
784
|
function getAuthHeaders(apiConfig: any): Record<string, string> {
|
787
785
|
switch (apiConfig.authType) {
|
788
786
|
case AuthType.BASIC:
|
@@ -812,13 +810,6 @@ function getAuthHeaders(apiConfig: any): Record<string, string> {
|
|
812
810
|
}
|
813
811
|
}
|
814
812
|
|
815
|
-
/**
|
816
|
-
* Recursively applies the response mapping to handle nested structures.
|
817
|
-
*
|
818
|
-
* @param response - The API response data
|
819
|
-
* @param responseMapping - The field mapping configuration
|
820
|
-
* @returns The mapped response
|
821
|
-
*/
|
822
813
|
function applyResponseMapping(
|
823
814
|
response: any,
|
824
815
|
responseMapping: Record<string, any>,
|
@@ -846,5 +837,27 @@ function applyResponseMapping(
|
|
846
837
|
}
|
847
838
|
}
|
848
839
|
|
840
|
+
/**
|
841
|
+
* Evaluates a condition dynamically.
|
842
|
+
*
|
843
|
+
* @param condition - A string representing the condition (e.g., "{id} !== null").
|
844
|
+
* @param data - The data context for evaluating the condition.
|
845
|
+
* @returns A boolean indicating whether the condition is met.
|
846
|
+
*/
|
847
|
+
function evaluateCondition(condition: string, data: Record<string, any>): boolean {
|
848
|
+
try {
|
849
|
+
const evaluatedCondition = condition.replace(/\{([^}]+)\}/g, (_, field) => {
|
850
|
+
const value = getNestedValue(data, field);
|
851
|
+
return JSON.stringify(value); // Safely inject values into the expression
|
852
|
+
});
|
853
|
+
|
854
|
+
return eval(evaluatedCondition); // Evaluate the resulting condition expression
|
855
|
+
} catch (error) {
|
856
|
+
console.error('Error evaluating condition:', error);
|
857
|
+
return false; // Default to false if condition evaluation fails
|
858
|
+
}
|
859
|
+
}
|
860
|
+
|
861
|
+
|
849
862
|
export { ApiConfigDto } from './dto/api-config.dto';
|
850
863
|
export { AuthType } from './dto/api-config.dto';
|