refine-apito
Version:
A data provider for Refine that connects to Apito - a headless CMS and backend builder.
1,089 lines (992 loc) • 34.8 kB
text/typescript
import {
BaseRecord,
CreateManyParams,
CreateManyResponse,
CreateParams,
CreateResponse,
CustomParams,
GetListParams,
GetListResponse,
GetOneParams,
GetOneResponse,
HttpError,
} from '@refinedev/core';
import { Client, CombinedError, cacheExchange, fetchExchange, gql } from '@urql/core';
import {
apitoConnectionFilterConditionType,
apitoGraphQLComposedTypeName,
apitoListCountKeyConditionType,
apitoListCountWhereInputType,
apitoListGraphQLTypeName,
apitoListKeyConditionType,
apitoMultipleResourceName,
apitoSingularGraphQLTypeName,
apitoModelName,
apitoSortInputType,
apitoWhereInputType,
apitoWhereRelationFilterConditionType,
buildApitoCreateMutation,
formatApitoConnectionSubselections,
} from './apitoGraphqlNames';
import {
ApitoGraphQLError,
CustomResponse,
ExtendedDataProvider,
ResponseType,
SingleResponseType,
} from './types';
/** Property names that must not come from user-controlled filter input (prototype pollution). */
const UNSAFE_DYNAMIC_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
function isSafeDynamicKey(key: unknown): key is string {
return typeof key === 'string' && key.length > 0 && !UNSAFE_DYNAMIC_KEYS.has(key);
}
/*
Apito Typical Graphql Error Response:
{
"data": {
"deleteTestLabel": null
},
"errors": [
{
"message": "there are 1 relations that are using this document, please delete them first",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"deleteTestLabel"
]
}
]
}
*/
/*
Apito Typical Graphql Success Response:
{
"data": {
"testLabelList": [
{
"data": {
"description": {
"text": null
},
"measure_unit": "mmol/l",
"name": "Corres. Urine Sugar",
"reference_range": "<7.8 mmol/l"
},
"id": "1ac785e3-a190-44a5-bc36-d858df8a3868",
"meta": {
"created_at": "2025-03-10T08:10:55Z",
"status": true,
"updated_at": "2025-03-10T08:10:55Z"
}
},
{
"data": {
"description": {
"text": null
},
"measure_unit": "mmol/l",
"name": "P Glucose (F)",
"reference_range": "3.6-5.6 mmol/l"
},
"id": "0c7e3a18-765c-4fed-a091-768578804fdc",
"meta": {
"created_at": "2025-03-10T08:10:05Z",
"status": true,
"updated_at": "2025-03-10T08:10:05Z"
}
},
{
"data": {
"description": {
"text": null
},
"measure_unit": "mmol/L",
"name": "T4",
"reference_range": "3.6-5.6 mmol/L"
},
"id": "13123014-8bb7-4850-9699-8eb4f0607305",
"meta": {
"created_at": "2025-02-17T13:32:56Z",
"status": true,
"updated_at": "2025-02-17T13:32:56Z"
}
},
{
"data": {
"description": {
"text": null
},
"measure_unit": "mg/dl",
"name": "S. Creatinine",
"reference_range": "0.6-1.2 mg/dl"
},
"id": "c9c9c9c9-c9c9-c9c9-c9c9-c9c9c9c9c9c9",
"meta": {
"created_at": "2025-02-17T13:32:56Z",
"status": true,
"updated_at": "2025-02-17T13:32:56Z"
}
}
],
"testLabelListCount": {
"total": 4
}
}
}
*/
/**
* Handles GraphQL errors from Apito responses
* @param error The error object from urql client
* @param onTokenExpired Optional callback for handling 403 token expiration
* @returns An HttpError object with appropriate status code and message
*/
const handleGraphQLError = (
error: CombinedError | undefined,
onTokenExpired?: () => void
): HttpError => {
if (!error) {
return {
message: 'Unknown error occurred',
statusCode: 500,
};
}
// Handle network errors
if (error.networkError) {
// Check for 403 status in network error
const statusCode =
(error.networkError as any).statusCode ||
(error.networkError as any).status;
if (statusCode === 403 || statusCode === 401) {
console.log('Token expired (403/401), triggering logout...');
onTokenExpired?.();
return {
message: 'Token expired. Please login again.',
statusCode: 403,
};
}
return {
message: `Network error: ${error.networkError.message}`,
statusCode: statusCode || 503, // Service Unavailable
};
}
// Handle GraphQL errors
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
// Check for authentication/authorization errors in GraphQL errors
const hasAuthError = error.graphQLErrors.some(
(err) =>
err.message.toLowerCase().includes('unauthorized') ||
err.message.toLowerCase().includes('forbidden') ||
err.message.toLowerCase().includes('token') ||
err.message.toLowerCase().includes('authentication') ||
err.message.toLowerCase().includes('authorization')
);
if (hasAuthError) {
console.log(
'Authentication error detected in GraphQL, triggering logout...'
);
onTokenExpired?.();
return {
message: 'Authentication failed. Please login again.',
statusCode: 403,
};
}
const errorMessages = error.graphQLErrors
.map((err) => err.message)
.join(', ');
return {
message: errorMessages,
statusCode: 400, // Bad Request for GraphQL validation errors
};
}
// Fallback error
return {
message: error.message || 'An error occurred during the GraphQL operation',
statusCode: 400,
};
};
const apitoDataProvider = (
apiUrl: string,
token: string,
onTokenExpired?: () => void
): ExtendedDataProvider => {
const client = new Client({
url: apiUrl,
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => ({
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
}),
preferGetMethod: false,
});
return {
getApiUrl: () => apiUrl,
getApiClient: () => {
return new Client({
url: apiUrl,
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => ({
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
}),
preferGetMethod: false,
});
},
getToken: () => token,
async getList<TData extends BaseRecord = BaseRecord>(
params: GetListParams
): Promise<GetListResponse<TData>> {
try {
const { resource, filters, sorters, pagination, meta } = params;
const connectionFields = meta?.connectionFields || {};
const aliasFields = meta?.aliasFields || {};
const reverseLookup = meta?.reverseLookup || {};
let data: TData[] = [];
let total = 0;
let query = null;
let variables = null;
if (meta?.gqlQuery) {
query = meta.gqlQuery;
variables = meta.variables;
const queryKey = meta.queryKey || resource;
const response = await client
.query<ResponseType>(query as any, variables)
.toPromise();
if (response.error) {
return Promise.reject(
handleGraphQLError(response.error, onTokenExpired)
);
}
const queryResponse = response?.data?.[queryKey];
const responseData = (
Array.isArray(queryResponse)
? (queryResponse as unknown as TData[])
: []
) as TData[];
const responseTotal = responseData.length ?? 0;
return {
data: responseData,
total: responseTotal,
};
} else {
const fields = meta?.fields || ['id']; // Fallback to 'id' if fields are not provided
const listPascal = apitoListGraphQLTypeName(resource);
const pluralResource = listPascal;
// Helper function to process filters recursively
const processFilter = (filter: any): any => {
const { field, operator, value } = filter;
// Handle special case where operator is "eq" and value is an array
if (operator === 'eq' && Array.isArray(value)) {
// Create a nested object structure for this case (null prototype + safe keys)
const nestedCondition = Object.create(null) as Record<
string,
Record<string, unknown>
>;
value.forEach((condition) => {
const {
field: subField,
operator: subOperator,
value: subValue,
} = condition;
if (
subField &&
subOperator &&
subValue !== undefined &&
isSafeDynamicKey(subField) &&
isSafeDynamicKey(subOperator)
) {
if (!nestedCondition[subField]) {
nestedCondition[subField] = Object.create(null) as Record<
string,
unknown
>;
}
nestedCondition[subField][subOperator] = subValue;
}
});
if (!isSafeDynamicKey(field)) {
return {};
}
return { [field]: nestedCondition };
}
// Handle OR operation
if (operator === 'or' && Array.isArray(value)) {
const orConditions = Object.create(null) as Record<string, unknown>;
value.forEach((condition) => {
const { field, operator, value } = condition;
if (field && operator && value !== undefined) {
// Adjust `data.name` to `name`
const adjustedField = field.startsWith('data.')
? field.replace('data.', '')
: field;
if (isSafeDynamicKey(adjustedField) && isSafeDynamicKey(operator)) {
orConditions[adjustedField] = { [operator]: value };
}
}
});
return { OR: orConditions };
}
// Handle AND operation
if (operator === 'and' && Array.isArray(value)) {
const andConditions = Object.create(null) as Record<string, unknown>;
value.forEach((condition) => {
const { field, operator, value } = condition;
if (field && operator && value !== undefined) {
// Adjust `data.name` to `name`
const adjustedField = field.startsWith('data.')
? field.replace('data.', '')
: field;
if (isSafeDynamicKey(adjustedField) && isSafeDynamicKey(operator)) {
andConditions[adjustedField] = { [operator]: value };
}
}
});
return { AND: andConditions };
}
// Handle regular field filters
if (field === '_key') {
const keyOp = operator || 'eq';
if (!isSafeDynamicKey(keyOp)) {
return {};
}
return { _key: { [keyOp]: value } };
}
if (field && field.includes('relation.')) {
const relationPath = field.replace('relation.', '').split('.');
if (!relationPath.length || !relationPath.every(isSafeDynamicKey)) {
return {};
}
const relationCondition = Object.create(null) as Record<string, any>;
// Build nested object structure
let current: Record<string, any> = relationCondition;
for (let i = 0; i < relationPath.length - 1; i++) {
const part = relationPath[i];
if (!current[part]) {
current[part] = Object.create(null) as Record<string, any>;
}
current = current[part];
}
const lastPart = relationPath[relationPath.length - 1];
if (
operator &&
value !== undefined &&
isSafeDynamicKey(lastPart) &&
isSafeDynamicKey(operator)
) {
current[lastPart] = { [operator]: value };
}
return { relation: relationCondition };
}
if (operator && value !== undefined && typeof field === 'string') {
// Adjust `data.name` to `name`
const adjustedField = field.startsWith('data.')
? field.replace('data.', '')
: field;
if (isSafeDynamicKey(adjustedField) && isSafeDynamicKey(operator)) {
return { [adjustedField]: { [operator]: value } };
}
}
return {};
};
// Process filters
let _key = null;
let relationWhere: Record<string, any> | null = null;
let where: Record<string, any> = {};
if (filters && filters.length > 0) {
filters.forEach((filter) => {
const processed = processFilter(filter);
// Extract _key if present
if (processed._key) {
_key = processed._key;
}
// Extract relation if present
else if (processed.relation) {
if (!relationWhere) {
relationWhere = {};
}
Object.assign(relationWhere, processed.relation);
}
// Handle OR/AND conditions
else if (processed.OR) {
where.OR = processed.OR;
} else if (processed.AND) {
where.AND = processed.AND;
}
// Handle regular conditions
else {
Object.assign(where, processed);
}
});
}
const hasKey = _key !== null;
const hasRelationWhere = relationWhere !== null;
const queryVariables = [
hasKey ? `$_key: ${apitoListKeyConditionType(resource)}` : null,
`$connection: ${apitoConnectionFilterConditionType(resource)}`,
`$where: ${apitoWhereInputType(resource)}`,
hasRelationWhere
? `$relationWhere: ${apitoWhereRelationFilterConditionType(resource)}`
: null,
hasKey ? `$_keyCount: ${apitoListCountKeyConditionType(resource)}` : null,
`$whereCount: ${apitoListCountWhereInputType(resource)}`,
hasRelationWhere
? `$relationWhereCount: ${apitoWhereRelationFilterConditionType(resource)}`
: null,
`$sort: ${apitoSortInputType(resource)}`,
`$page: Int`,
`$limit: Int`,
`$local: LOCAL_TYPE_ENUM`,
]
.filter(Boolean)
.join('\n');
const queryArguments = [
hasKey ? '_key: $_key' : null,
'connection: $connection',
'where: $where',
hasRelationWhere ? 'relation: $relationWhere' : null,
'sort: $sort',
'page: $page',
'limit: $limit',
'local: $local',
]
.filter(Boolean)
.join(', ');
const countArguments = [
hasKey ? '_key: $_keyCount' : null,
'connection: $connection',
'where: $whereCount',
hasRelationWhere ? 'relation: $relationWhereCount' : null,
'page: $page',
'limit: $limit',
]
.filter(Boolean)
.join(', ');
query = gql`
query Get${pluralResource}(
${queryVariables}
) {
${apitoMultipleResourceName(resource)}(${queryArguments}) {
id
data {
${fields.join('\n')}
}
${formatApitoConnectionSubselections(connectionFields, aliasFields)}
meta {
created_at
status
updated_at
}
}
${apitoMultipleResourceName(resource)}Count(${countArguments}) {
total
}
}
`;
variables = {
...(hasKey && { _key: _key }),
connection: reverseLookup || {},
where: where || {},
...(hasRelationWhere && { relationWhere: relationWhere }),
whereCount: where || {},
...(hasKey && { _keyCount: _key }),
...(hasRelationWhere && { relationWhereCount: relationWhere }),
sort: sorters?.reduce((acc: Record<string, any>, sorter: any) => {
const { field, order } = sorter;
if (field && order) {
acc[field] = order.toUpperCase(); // Convert to ASC/DESC
}
return acc;
}, {}),
page: pagination?.currentPage || 1,
limit: pagination?.pageSize || 10,
};
const response = await client
.query<ResponseType>(query as any, variables)
.toPromise();
if (response.error) {
return Promise.reject(
handleGraphQLError(response.error, onTokenExpired)
);
}
const listRoot = apitoMultipleResourceName(resource);
data = (response?.data?.[listRoot] ?? []) as unknown as TData[];
total =
'total' in (response?.data?.[`${listRoot}Count`] || {})
? (response?.data?.[`${listRoot}Count`] as SingleResponseType)
.total
: 0;
}
return {
data: data,
total: total,
};
} catch (error) {
if ((error as any).statusCode !== undefined) {
return Promise.reject(error);
}
const httpError: HttpError = {
message: (error as Error)?.message || 'Failed to fetch list data',
statusCode: 500,
};
return Promise.reject(httpError);
}
},
async getOne<TData extends BaseRecord = BaseRecord>(
params: GetOneParams
): Promise<GetOneResponse<TData>> {
try {
const { resource, id, meta } = params;
const fields = meta?.fields || ['id']; // Fallback to 'id' if fields are not provided
const connectionFields = meta?.connectionFields || {};
const aliasFields = meta?.aliasFields || {};
const singularField = apitoModelName(resource);
const singularPascal = apitoSingularGraphQLTypeName(resource);
const query = gql`
query Get${singularPascal}($id: String!) {
${singularField}(_id: $id) {
id
data {
${fields.join('\n')}
}
${formatApitoConnectionSubselections(connectionFields, aliasFields)}
meta {
created_at
status
updated_at
}
}
}
`;
const response = await client
.query<ResponseType>(query, { id })
.toPromise();
if (response.error) {
return Promise.reject(
handleGraphQLError(response.error, onTokenExpired)
);
}
const data = (response?.data?.[singularField] ?? {}) as TData;
return {
data: data,
};
} catch (error) {
if ((error as any).statusCode !== undefined) {
return Promise.reject(error);
}
const httpError: HttpError = {
message:
(error as Error)?.message ||
`Failed to fetch ${params.resource} with id ${params.id}`,
statusCode: 500,
};
return Promise.reject(httpError);
}
},
async create<TData extends BaseRecord = BaseRecord, TVariables = any>(
params: CreateParams<TVariables>
): Promise<CreateResponse<TData>> {
try {
const { resource, variables, meta } = params;
let query = null;
let _variables = null;
if (meta?.gqlMutation) {
query = meta.gqlMutation;
if (variables) {
_variables = variables;
} else {
_variables = meta.variables;
}
const response = await client
.mutation<ResponseType>(query as any, _variables)
.toPromise();
if (response.error) {
return Promise.reject(handleGraphQLError(response.error));
}
return {
data:
(
response?.data?.[
`create${apitoSingularGraphQLTypeName(resource)}`
] as SingleResponseType
)?.data ?? {},
};
} else {
try {
const { resource, variables, meta } = params;
const fields = (meta?.fields || ['id']) as string[]; // Fallback to 'id' if fields are not provided
const name = apitoSingularGraphQLTypeName(resource);
const query = gql(buildApitoCreateMutation(resource, fields));
const variableData = variables as Record<string, any>;
const response = await client
.mutation<ResponseType>(query, {
payload: variableData.data,
connect: variableData.connect,
})
.toPromise();
if (response.error) {
return Promise.reject(
handleGraphQLError(response.error, onTokenExpired)
);
}
const data = (response?.data?.[`create${name}`] ?? {}) as TData;
return { data: data };
} catch (error) {
if ((error as any).statusCode !== undefined) {
return Promise.reject(error);
}
const httpError: HttpError = {
message:
(error as Error)?.message ||
`Failed to create ${params.resource}`,
statusCode: 500,
};
return Promise.reject(httpError);
}
}
} catch (error) {
if ((error as any).statusCode !== undefined) {
return Promise.reject(error);
}
const httpError: HttpError = {
message:
(error as Error)?.message || `Failed to create ${params.resource}`,
statusCode: 500,
};
return Promise.reject(httpError);
}
},
async createMany<TData extends BaseRecord = BaseRecord, TVariables = any>(
params: CreateManyParams<TVariables>
): Promise<CreateManyResponse<TData>> {
try {
const { resource, variables, meta } = params;
const fields = meta?.fields || ['id']; // Fallback to 'id' if fields are not provided
const listPascal = apitoListGraphQLTypeName(resource);
const upsertPayloadType = apitoGraphQLComposedTypeName(
resource,
'List_Upsert_Payload'
);
const listConnectType = apitoGraphQLComposedTypeName(
resource,
'Relation_Connect_Payload'
);
const mutation = gql`
mutation Upsert${listPascal}($payloads: [${upsertPayloadType}!]!, $connect: ${listConnectType}) {
upsert${listPascal}(payloads: $payloads, connect: $connect, status: published) {
id
data {
${fields.join('\n')}
}
meta {
created_at
status
updated_at
}
}
}
`;
// Clean up the array by filtering out empty objects, null, or undefined values
const variableData = Array.isArray(variables)
? (variables as any[]).filter(
(item) =>
item !== null &&
item !== undefined &&
(typeof item !== 'object' || Object.keys(item).length > 0)
)
: (variables as Record<string, any>);
const response = await client
.mutation<ResponseType>(mutation, {
payloads: variableData,
//connect: variableData.connect,
})
.toPromise();
if (response.error) {
return Promise.reject(
handleGraphQLError(response.error, onTokenExpired)
);
}
const data = (response?.data?.[`upsert${listPascal}`] ??
[]) as unknown as TData[];
return { data: data };
} catch (error) {
if ((error as any).statusCode !== undefined) {
return Promise.reject(error);
}
const httpError: HttpError = {
message:
(error as Error)?.message ||
`Failed to create multiple ${params.resource} records`,
statusCode: 500,
};
return Promise.reject(httpError);
}
},
async update({ resource, id, variables, meta }) {
try {
let query = null;
let _variables = null;
if (meta?.gqlMutation) {
query = meta.gqlMutation;
if (variables) {
_variables = variables;
} else {
_variables = meta.variables;
}
const response = await client
.mutation<ResponseType>(query as any, _variables)
.toPromise();
if (response.error) {
return Promise.reject(handleGraphQLError(response.error));
}
return {
data:
(
response?.data?.[
`update${apitoSingularGraphQLTypeName(resource)}`
] as SingleResponseType
)?.data ?? {},
};
} else {
const fields = meta?.fields || ['id']; // Fallback to 'id' if fields are not provided
const deltaUpdate = meta?.deltaUpdate || false;
const includeRelations = meta?.relation !== false;
const name = apitoSingularGraphQLTypeName(resource);
const updatePayload = apitoGraphQLComposedTypeName(resource, 'Update_Payload');
const relConn = apitoGraphQLComposedTypeName(
resource,
'Relation_Connect_Payload'
);
const relDis = apitoGraphQLComposedTypeName(
resource,
'Relation_Disconnect_Payload'
);
const relationVarDefs = includeRelations
? `,
$connect: ${relConn},
$disconnect: ${relDis}`
: '';
const relationArgs = includeRelations
? `, connect: $connect, disconnect: $disconnect`
: '';
query = gql`
mutation Update${name}(
$id: String!,
$deltaUpdate: Boolean,
$payload: ${updatePayload}!${relationVarDefs}
) {
update${name}(_id: $id, deltaUpdate: $deltaUpdate, payload: $payload${relationArgs}, status: published) {
id
data {
${fields.join('\n')}
}
meta {
created_at
status
updated_at
}
}
}
`;
_variables = {
id: id,
deltaUpdate: deltaUpdate,
payload: (variables as Record<string, any>).data,
};
if (includeRelations) {
(_variables as Record<string, any>).connect = (
variables as Record<string, any>
).connect;
(_variables as Record<string, any>).disconnect = (
variables as Record<string, any>
).disconnect;
}
const response = await client
.mutation<ResponseType>(query as any, _variables)
.toPromise();
if (response.error) {
return Promise.reject(
handleGraphQLError(response.error, onTokenExpired)
);
}
return {
data:
(
response?.data?.[`update${name}`] as SingleResponseType
)?.data ?? {},
};
}
} catch (error) {
if ((error as any).statusCode !== undefined) {
return Promise.reject(error);
}
const httpError: HttpError = {
message:
(error as Error)?.message ||
`Failed to update ${resource} with id ${id}`,
statusCode: 500,
};
return Promise.reject(httpError);
}
},
async deleteOne({ resource, id }) {
try {
const name = apitoSingularGraphQLTypeName(resource);
const query = gql`
mutation Delete${name}($ids: [String]!) {
delete${name}(_ids: $ids) {
response
}
}
`;
const response = await client
.mutation<ResponseType>(query, { ids: [id] })
.toPromise();
// Check for GraphQL errors in the response
if (response.error) {
return Promise.reject(
handleGraphQLError(response.error, onTokenExpired)
);
}
// Check for errors in the data response (Apito specific error format)
if (response.data?.errors && Array.isArray(response.data.errors)) {
const errorMessages = (response.data.errors as ApitoGraphQLError[])
.map((err) => err.message)
.join(', ');
const httpError: HttpError = {
message: errorMessages,
statusCode: 400,
};
return Promise.reject(httpError);
}
return {
data:
(
response?.data?.[`delete${name}`] as SingleResponseType
)?.data ?? {},
};
} catch (error) {
if ((error as any).statusCode !== undefined) {
return Promise.reject(error);
}
const httpError: HttpError = {
message:
(error as Error)?.message ||
`Failed to delete ${resource} with id ${id}`,
statusCode: 500,
};
return Promise.reject(httpError);
}
},
async custom<TData extends BaseRecord = BaseRecord>(
params: CustomParams<any, any>
): Promise<CustomResponse<TData>> {
try {
const query = params?.meta?.gqlQuery;
const mutation = params?.meta?.gqlMutation;
let variables = params?.meta?.gqlVariables;
if (query && mutation) {
const httpError: HttpError = {
message:
'Query and mutation cannot both be provided for custom operation',
statusCode: 400,
};
return Promise.reject(httpError);
}
if (!query && !mutation) {
const httpError: HttpError = {
message: 'Query or mutation is required for custom operation',
statusCode: 400,
};
return Promise.reject(httpError);
}
const { filters } = params;
// Transform filters into a `where` object
const where = filters?.reduce(
(acc: Record<string, any>, filter: any) => {
const { field, operator, value } = filter;
if (operator && value !== undefined) {
// Adjust `data.name` to `name`
const adjustedField = field.startsWith('data.')
? field.replace('data.', '')
: field;
acc[adjustedField] = { [operator || 'eq']: value };
}
return acc;
},
{}
);
if (where) {
variables = {
...variables,
where: where || {},
};
}
// Convert payloads object with numeric keys to array
if (
variables?.payloads &&
typeof variables.payloads === 'object' &&
!Array.isArray(variables.payloads)
) {
variables = {
...variables,
payloads: Object.values(variables.payloads),
};
}
//debugger;
let response = null;
if (query) {
response = await client
.query<ResponseType>(query as any, variables)
.toPromise();
} else if (mutation) {
response = await client
.mutation<ResponseType>(mutation as any, variables)
.toPromise();
} else {
throw new Error('No query or mutation provided');
}
//debugger;
if (response.error) {
return Promise.reject(
handleGraphQLError(response.error, onTokenExpired)
);
}
// Check for errors in the data response (Apito specific error format)
if (response.data?.errors && Array.isArray(response.data.errors)) {
const errorMessages = (response.data.errors as ApitoGraphQLError[])
.map((err) => err.message)
.join(', ');
const httpError: HttpError = {
message: errorMessages,
statusCode: 400,
};
return Promise.reject(httpError);
}
//debugger;
return {
data: response?.data as TData,
};
} catch (error) {
if ((error as any).statusCode !== undefined) {
return Promise.reject(error);
}
const httpError: HttpError = {
message:
(error as Error)?.message || 'Failed to execute custom operation',
statusCode: 500,
};
return Promise.reject(httpError);
}
},
};
};
export default apitoDataProvider;