@dev-fastn-ai/react-core 2.3.6 → 2.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +465 -0
  2. package/dist/{react-core/src/core → core}/provider.d.ts +2 -2
  3. package/dist/{react-core/src/core → core}/use-configuration-form.d.ts +1 -1
  4. package/dist/{react-core/src/core → core}/use-configurations.d.ts +1 -1
  5. package/dist/core/use-connectors.d.ts +1 -0
  6. package/dist/{react-core/src/core → core}/use-field-options.d.ts +4 -1
  7. package/dist/index.cjs.js +39 -1655
  8. package/dist/index.cjs.js.map +1 -1
  9. package/dist/index.d.ts +10 -202
  10. package/dist/index.esm.js +39 -1654
  11. package/dist/index.esm.js.map +1 -1
  12. package/package.json +5 -3
  13. package/dist/core/src/core/activate-connector.d.ts +0 -40
  14. package/dist/core/src/core/config.d.ts +0 -3
  15. package/dist/core/src/core/configuration-form.d.ts +0 -27
  16. package/dist/core/src/core/configurations.d.ts +0 -2
  17. package/dist/core/src/core/connectors.d.ts +0 -7
  18. package/dist/core/src/core/execute-flow.d.ts +0 -9
  19. package/dist/core/src/core/register-refetch-functions.d.ts +0 -4
  20. package/dist/core/src/index.d.ts +0 -13
  21. package/dist/core/src/services/apis.d.ts +0 -86
  22. package/dist/core/src/types/config.d.ts +0 -10
  23. package/dist/core/src/types/index.d.ts +0 -272
  24. package/dist/core/src/utils/constants.d.ts +0 -12
  25. package/dist/core/src/utils/errors.d.ts +0 -22
  26. package/dist/core/src/utils/event-bus.d.ts +0 -6
  27. package/dist/core/src/utils/google-files-picker.d.ts +0 -13
  28. package/dist/core/src/utils/misc.d.ts +0 -40
  29. package/dist/react-core/src/core/use-connectors.d.ts +0 -1
  30. package/dist/react-core/src/index.d.ts +0 -7
package/README.md CHANGED
@@ -257,6 +257,22 @@ interface ConnectorField {
257
257
  | Primitive
258
258
  | Primitive[];
259
259
  readonly optionsSource?: SelectOptionSource;
260
+ readonly configs?: {
261
+ selection?: {
262
+ enable?: boolean;
263
+ type?: "MAPPING" | string;
264
+ isAddFields?: boolean;
265
+ isEditKeys?: boolean;
266
+ source?: {
267
+ flowId: string;
268
+ isSameProject: boolean;
269
+ };
270
+ destination?: {
271
+ flowId: string;
272
+ isSameProject: boolean;
273
+ };
274
+ };
275
+ };
260
276
  }
261
277
  ```
262
278
 
@@ -561,6 +577,7 @@ The form fields handle different value types based on the field type:
561
577
  - **Select fields**: Always return `{ label: string, value: string }` objects
562
578
  - **Multi-select fields**: Always return `{ label: string, value: string }[]` arrays
563
579
  - **Google Drive picker fields**: Always return `{ label: string, value: string }` objects or arrays
580
+ - **Mapping fields**: Always return `{ label: string, value: string }[]` arrays where `label` is the source field and `value` is the destination field
564
581
  - **Other fields**: Return primitive values (string, number, boolean)
565
582
 
566
583
  ```tsx
@@ -584,6 +601,13 @@ const formData = {
584
601
  { label: "document2.pdf", value: "file_id_2" }
585
602
  ],
586
603
 
604
+ // Mapping field - array of mapping objects
605
+ fieldMappings: [
606
+ { label: "First Name", value: "user_first_name" },
607
+ { label: "Email Address", value: "user_email" },
608
+ { label: "Phone Number", value: "user_phone" }
609
+ ],
610
+
587
611
  // Text field - primitive
588
612
  webhookUrl: "https://hooks.slack.com/...",
589
613
 
@@ -781,6 +805,438 @@ function GoogleFilesPickerField({ field, value, onChange, isMulti = false }) {
781
805
  }
782
806
  ```
783
807
 
808
+ ### **Mapping Fields**
809
+
810
+ Mapping fields allow users to create relationships between source fields and destination fields. They are commonly used for data transformation scenarios where you need to map fields from one system to another (e.g., mapping CSV columns to database fields, or API response fields to form inputs).
811
+
812
+ #### **Field Type and Value Structure**
813
+
814
+ Mapping fields use the `ConnectorFieldType.MAPPING` type and always work with arrays of mapping objects:
815
+
816
+ ```tsx
817
+ // Mapping field value structure
818
+ const mappingValue = [
819
+ {
820
+ label: "Source Field Name", // The source field identifier
821
+ value: "destination_value" // The selected destination field value
822
+ },
823
+ {
824
+ label: "Email Address",
825
+ value: "user_email"
826
+ },
827
+ {
828
+ label: "Full Name",
829
+ value: "display_name"
830
+ }
831
+ ];
832
+ ```
833
+
834
+ #### **Field Configuration**
835
+
836
+ Mapping fields require specific configuration in the `ConnectorField`:
837
+
838
+ ```tsx
839
+ interface MappingFieldConfig {
840
+ // Field type must be "mapping"
841
+ type: "mapping";
842
+
843
+ // Configuration for source and destination options
844
+ configs: {
845
+ selection: {
846
+ // Enable mapping functionality
847
+ enable: true;
848
+ type: "MAPPING";
849
+
850
+ // Configuration for adding/editing fields
851
+ isAddFields?: boolean; // Allow users to add new source fields
852
+ isEditKeys?: boolean; // Allow users to edit source field names
853
+
854
+ // Source options configuration
855
+ source: {
856
+ flowId: string; // ID of the flow providing source options
857
+ isSameProject: boolean; // Whether source is in same project
858
+ };
859
+
860
+ // Destination options configuration
861
+ destination: {
862
+ flowId: string; // ID of the flow providing destination options
863
+ isSameProject: boolean; // Whether destination is in same project
864
+ };
865
+ };
866
+ };
867
+
868
+ // Options source for dynamic loading
869
+ optionsSource: {
870
+ getOptions: (pagination, context, searchQuery) => Promise<OptionsResult>;
871
+ };
872
+ }
873
+ ```
874
+
875
+ #### **Complete Mapping Field Component**
876
+
877
+ Here's a full implementation of a mapping field component using the `useFieldOptions` hook:
878
+
879
+ ```tsx
880
+ import { useCallback, useState, useEffect } from "react";
881
+ import { useFieldOptions } from "@fastn-ai/react-core";
882
+ import Select from "react-select";
883
+
884
+ interface MappingFieldProps {
885
+ field: ConnectorField;
886
+ value: MappingValue[];
887
+ onChange: (value: MappingValue[]) => void;
888
+ }
889
+
890
+ interface MappingValue {
891
+ label: string; // Source field name
892
+ value: string; // Destination field value
893
+ }
894
+
895
+ function MappingField({ field, value, onChange }: MappingFieldProps) {
896
+ // Get mapping-specific functions from useFieldOptions
897
+ const {
898
+ selectionConfig,
899
+ getSourceOptions,
900
+ getDestinationOptions,
901
+ } = useFieldOptions(field);
902
+
903
+ const mappingValue = Array.isArray(value) ? value : [];
904
+
905
+ // State for source and destination options
906
+ const [sourceOptions, setSourceOptions] = useState<SelectOption[]>([]);
907
+ const [destinationOptions, setDestinationOptions] = useState<SelectOption[]>([]);
908
+ const [sourceLoading, setSourceLoading] = useState(false);
909
+ const [destinationLoading, setDestinationLoading] = useState(false);
910
+
911
+ // Initialize source options from existing mappings
912
+ const initialSourceOptions = mappingValue.length > 0
913
+ ? mappingValue.map((item) => ({
914
+ label: item.label,
915
+ value: item.label,
916
+ }))
917
+ : [];
918
+
919
+ // Load source options
920
+ const loadSourceOptions = useCallback(async () => {
921
+ setSourceLoading(true);
922
+ try {
923
+ const result = await getSourceOptions({ id: "configurationId" });
924
+ const rawOptions = result?.options || [];
925
+
926
+ const newSourceOptions = Array.isArray(rawOptions)
927
+ ? rawOptions.map((option) =>
928
+ typeof option === "string"
929
+ ? { label: option, value: option }
930
+ : option
931
+ )
932
+ : [];
933
+ setSourceOptions(newSourceOptions);
934
+ } catch (err) {
935
+ console.error("Error loading source options:", err);
936
+ } finally {
937
+ setSourceLoading(false);
938
+ }
939
+ }, [getSourceOptions]);
940
+
941
+ // Load destination options
942
+ const loadDestinationOptions = useCallback(async () => {
943
+ setDestinationLoading(true);
944
+ try {
945
+ const result = await getDestinationOptions({ id: "configurationId" });
946
+ const rawOptions = result?.options || [];
947
+
948
+ const newDestinationOptions = Array.isArray(rawOptions)
949
+ ? rawOptions.map((option) =>
950
+ typeof option === "string"
951
+ ? { label: option, value: option }
952
+ : option
953
+ )
954
+ : [];
955
+ setDestinationOptions(newDestinationOptions);
956
+ } catch (err) {
957
+ console.error("Error loading destination options:", err);
958
+ } finally {
959
+ setDestinationLoading(false);
960
+ }
961
+ }, [getDestinationOptions]);
962
+
963
+ // Load options on mount
964
+ useEffect(() => {
965
+ // Load source options only if no existing mappings
966
+ if (mappingValue.length === 0) {
967
+ loadSourceOptions();
968
+ } else {
969
+ setSourceOptions(initialSourceOptions);
970
+ }
971
+ loadDestinationOptions();
972
+ }, []);
973
+
974
+ // Add new mapping field
975
+ const handleAddField = () => {
976
+ const newFieldLabel = `Field ${sourceOptions.length + 1}`;
977
+ const newSourceOption = {
978
+ label: newFieldLabel,
979
+ value: newFieldLabel,
980
+ };
981
+
982
+ setSourceOptions([...sourceOptions, newSourceOption]);
983
+
984
+ const newMappingValue = [
985
+ ...mappingValue,
986
+ {
987
+ label: newFieldLabel,
988
+ value: "",
989
+ },
990
+ ];
991
+ onChange(newMappingValue);
992
+ };
993
+
994
+ // Remove mapping field
995
+ const handleRemoveField = (sourceOption: SelectOption, index: number) => {
996
+ const newSourceOptions = sourceOptions.filter((_, i) => i !== index);
997
+ setSourceOptions(newSourceOptions);
998
+
999
+ const newMappingValue = mappingValue.filter(
1000
+ (mapping) => mapping.label !== sourceOption.label
1001
+ );
1002
+ onChange(newMappingValue);
1003
+ };
1004
+
1005
+ // Update source field label
1006
+ const handleSourceLabelChange = (index: number, sourceOption: SelectOption, newLabel: string) => {
1007
+ const newSourceOptions = [...sourceOptions];
1008
+ newSourceOptions[index] = {
1009
+ ...sourceOption,
1010
+ label: newLabel,
1011
+ };
1012
+ setSourceOptions(newSourceOptions);
1013
+
1014
+ const existingIndex = mappingValue.findIndex(
1015
+ (mapping) => mapping.label === sourceOption.label
1016
+ );
1017
+ if (existingIndex >= 0) {
1018
+ const newMappingValue = [...mappingValue];
1019
+ newMappingValue[existingIndex] = {
1020
+ label: newLabel,
1021
+ value: newMappingValue[existingIndex].value,
1022
+ };
1023
+ onChange(newMappingValue);
1024
+ }
1025
+ };
1026
+
1027
+ // Update destination mapping
1028
+ const handleDestinationChange = (sourceOption: SelectOption, selected: SelectOption | null) => {
1029
+ const existingIndex = mappingValue.findIndex(
1030
+ (mapping) => mapping.label === sourceOption.label
1031
+ );
1032
+
1033
+ let newValue;
1034
+ if (existingIndex >= 0) {
1035
+ // Update existing mapping
1036
+ newValue = [...mappingValue];
1037
+ newValue[existingIndex] = {
1038
+ label: sourceOption.label,
1039
+ value: selected?.value || "",
1040
+ };
1041
+ } else {
1042
+ // Add new mapping
1043
+ newValue = [
1044
+ ...mappingValue,
1045
+ {
1046
+ label: sourceOption.label,
1047
+ value: selected?.value || "",
1048
+ },
1049
+ ];
1050
+ }
1051
+ onChange(newValue);
1052
+ };
1053
+
1054
+ return (
1055
+ <div className="mapping-field-container">
1056
+ {/* Field label and add button */}
1057
+ <div className="mapping-field-header">
1058
+ <label className="field-label">
1059
+ {field.label}
1060
+ {field.required && <span className="required"> *</span>}
1061
+ </label>
1062
+
1063
+ {selectionConfig?.isAddFields && (
1064
+ <button
1065
+ type="button"
1066
+ onClick={handleAddField}
1067
+ className="add-field-btn"
1068
+ >
1069
+ + Add Field
1070
+ </button>
1071
+ )}
1072
+ </div>
1073
+
1074
+ {/* Loading state */}
1075
+ {sourceLoading ? (
1076
+ <div className="loading-message">Loading source options...</div>
1077
+ ) : sourceOptions.length > 0 ? (
1078
+ /* Mapping rows */
1079
+ sourceOptions.map((sourceOption, index) => (
1080
+ <div key={index} className="mapping-row">
1081
+ {/* Source field (editable if configured) */}
1082
+ <div className="source-field">
1083
+ {selectionConfig?.isEditKeys ? (
1084
+ <EditableLabel
1085
+ value={sourceOption.label}
1086
+ onSave={(newLabel) =>
1087
+ handleSourceLabelChange(index, sourceOption, newLabel)
1088
+ }
1089
+ />
1090
+ ) : (
1091
+ <span className="source-label">{sourceOption.label}</span>
1092
+ )}
1093
+ </div>
1094
+
1095
+ {/* Arrow separator */}
1096
+ <div className="mapping-arrow">→</div>
1097
+
1098
+ {/* Destination field selector */}
1099
+ <div className="destination-field">
1100
+ <Select
1101
+ value={(() => {
1102
+ const mapping = mappingValue.find(
1103
+ (mapping) => mapping.label === sourceOption.label
1104
+ );
1105
+ return mapping
1106
+ ? destinationOptions.find(
1107
+ (option) => option.value === mapping.value
1108
+ ) || null
1109
+ : null;
1110
+ })()}
1111
+ onChange={(selected) =>
1112
+ handleDestinationChange(sourceOption, selected)
1113
+ }
1114
+ options={destinationOptions}
1115
+ isLoading={destinationLoading}
1116
+ placeholder="Select destination..."
1117
+ className="destination-select"
1118
+ noOptionsMessage={() =>
1119
+ destinationLoading
1120
+ ? "Loading destination options..."
1121
+ : "No destination options found"
1122
+ }
1123
+ />
1124
+ </div>
1125
+
1126
+ {/* Remove field button */}
1127
+ {selectionConfig?.isAddFields && (
1128
+ <button
1129
+ type="button"
1130
+ onClick={() => handleRemoveField(sourceOption, index)}
1131
+ className="remove-field-btn"
1132
+ >
1133
+ 🗑️
1134
+ </button>
1135
+ )}
1136
+ </div>
1137
+ ))
1138
+ ) : (
1139
+ <div className="no-options-message">No source options available</div>
1140
+ )}
1141
+
1142
+ {field.description && (
1143
+ <div className="field-description">{field.description}</div>
1144
+ )}
1145
+ </div>
1146
+ );
1147
+ }
1148
+ ```
1149
+
1150
+ #### **useFieldOptions Hook for Mapping Fields**
1151
+
1152
+ The `useFieldOptions` hook provides specialized functions for mapping fields:
1153
+
1154
+ ```tsx
1155
+ const {
1156
+ // Configuration from field.configs.selection
1157
+ selectionConfig: {
1158
+ isAddFields: boolean; // Allow adding new source fields
1159
+ isEditKeys: boolean; // Allow editing source field names
1160
+ source: { // Source options configuration
1161
+ flowId: string;
1162
+ isSameProject: boolean;
1163
+ };
1164
+ destination: { // Destination options configuration
1165
+ flowId: string;
1166
+ isSameProject: boolean;
1167
+ };
1168
+ },
1169
+
1170
+ // Function to load source options
1171
+ getSourceOptions: (context: any) => Promise<OptionsResult>;
1172
+
1173
+ // Function to load destination options
1174
+ getDestinationOptions: (context: any) => Promise<OptionsResult>;
1175
+
1176
+ // Standard field options (for non-mapping fields)
1177
+ options: SelectOption[];
1178
+ loading: boolean;
1179
+ // ... other standard options
1180
+ } = useFieldOptions(field);
1181
+ ```
1182
+
1183
+ #### **Mapping Field Usage Example**
1184
+
1185
+ ```tsx
1186
+ function ConfigurationForm({ configurationId }) {
1187
+ const { data: configurationForm } = useConfigurationForm({ configurationId });
1188
+ const [formData, setFormData] = useState({});
1189
+
1190
+ return (
1191
+ <form>
1192
+ {configurationForm?.fields.map((field) => {
1193
+ if (field.type === "mapping") {
1194
+ return (
1195
+ <MappingField
1196
+ key={field.key}
1197
+ field={field}
1198
+ value={formData[field.key] || []}
1199
+ onChange={(value) =>
1200
+ setFormData((prev) => ({ ...prev, [field.key]: value }))
1201
+ }
1202
+ />
1203
+ );
1204
+ }
1205
+
1206
+ // Handle other field types...
1207
+ return <FormField key={field.key} field={field} />;
1208
+ })}
1209
+ </form>
1210
+ );
1211
+ }
1212
+ ```
1213
+
1214
+ #### **Common Mapping Field Use Cases**
1215
+
1216
+ 1. **CSV Column Mapping**: Map CSV headers to database fields
1217
+ 2. **API Response Mapping**: Map API response fields to form inputs
1218
+ 3. **Data Transformation**: Map source data fields to destination schema
1219
+ 4. **Integration Field Mapping**: Map fields between different systems (CRM to Email Marketing, etc.)
1220
+
1221
+ ```tsx
1222
+ // Example mapping values for different use cases
1223
+
1224
+ // CSV to Database mapping
1225
+ const csvMappingValue = [
1226
+ { label: "First Name", value: "user_first_name" },
1227
+ { label: "Last Name", value: "user_last_name" },
1228
+ { label: "Email", value: "user_email" },
1229
+ { label: "Phone", value: "user_phone" }
1230
+ ];
1231
+
1232
+ // API Response to Form mapping
1233
+ const apiMappingValue = [
1234
+ { label: "api_user_id", value: "userId" },
1235
+ { label: "api_user_email", value: "email" },
1236
+ { label: "api_user_profile", value: "profile" }
1237
+ ];
1238
+ ```
1239
+
784
1240
  ### **Generic Form Field Component**
785
1241
 
786
1242
  Create a reusable component that handles different field types with proper value handling:
@@ -872,6 +1328,15 @@ function FormField({ field, value, onChange }) {
872
1328
  />
873
1329
  );
874
1330
 
1331
+ case "mapping":
1332
+ return (
1333
+ <MappingField
1334
+ field={field}
1335
+ value={value || []}
1336
+ onChange={onChange}
1337
+ />
1338
+ );
1339
+
875
1340
  default:
876
1341
  return (
877
1342
  <div className="field-container">
@@ -1,6 +1,6 @@
1
1
  import { QueryClient } from "@tanstack/react-query";
2
- import { Fastn } from "../../../core/src/index";
3
- import type { FastnConfig } from "../../../core/src/types";
2
+ import { Fastn } from "@dev-fastn-ai/core";
3
+ import type { FastnConfig } from "@dev-fastn-ai/core";
4
4
  export declare const FastnProvider: ({ children, config, queryClient, }: {
5
5
  children: React.ReactNode;
6
6
  config: FastnConfig;
@@ -1,2 +1,2 @@
1
1
  import { GetConfigurationFormInput } from "@dev-fastn-ai/core";
2
- export declare const useConfigurationForm: (input: GetConfigurationFormInput) => import("@tanstack/react-query").UseQueryResult<import("../../../core/src").ConfigurationForm, Error>;
2
+ export declare const useConfigurationForm: (input: GetConfigurationFormInput) => import("@tanstack/react-query").UseQueryResult<import("@dev-fastn-ai/core").ConfigurationForm, Error>;
@@ -1,2 +1,2 @@
1
1
  import { GetConfigurationsInput } from "@dev-fastn-ai/core";
2
- export declare const useConfigurations: (input: GetConfigurationsInput) => import("@tanstack/react-query").UseQueryResult<import("../../../core/src").Configuration[], Error>;
2
+ export declare const useConfigurations: (input: GetConfigurationsInput) => import("@tanstack/react-query").UseQueryResult<import("@dev-fastn-ai/core").Configuration[], Error>;
@@ -0,0 +1 @@
1
+ export declare const useConnectors: () => import("@tanstack/react-query").UseQueryResult<import("@dev-fastn-ai/core").Connector[], Error>;
@@ -1,4 +1,4 @@
1
- import type { ConnectorField, SelectOption } from "@dev-fastn-ai/core";
1
+ import type { ConnectorField, SelectOption, OptionsResult } from "@dev-fastn-ai/core";
2
2
  /**
3
3
  * Custom hook to manage async select field options with search, pagination, and error handling using React Query.
4
4
  *
@@ -23,6 +23,9 @@ import type { ConnectorField, SelectOption } from "@dev-fastn-ai/core";
23
23
  * ```
24
24
  */
25
25
  export declare function useFieldOptions(field: ConnectorField): {
26
+ getSourceOptions: (context: any) => Promise<OptionsResult | null>;
27
+ getDestinationOptions: (context: any) => Promise<OptionsResult | null>;
28
+ selectionConfig: any;
26
29
  options: SelectOption[];
27
30
  loading: boolean;
28
31
  loadingMore: boolean;