UNPKG

ra-data-graphql

Version:

A GraphQL data provider for react-admin

283 lines (247 loc) 8.76 kB
import merge from 'lodash/merge'; import get from 'lodash/get'; import pluralize from 'pluralize'; import { DataProvider, HttpError, GET_LIST, GET_ONE, GET_MANY, GET_MANY_REFERENCE, CREATE, UPDATE, DELETE, DELETE_MANY, UPDATE_MANY, } from 'ra-core'; import { ApolloClient, ApolloClientOptions, ApolloError, ApolloQueryResult, MutationOptions, WatchQueryOptions, QueryOptions, OperationVariables, ServerError, } from '@apollo/client'; import buildApolloClient from './buildApolloClient'; import { QUERY_TYPES as INNER_QUERY_TYPES, MUTATION_TYPES as INNER_MUTATION_TYPES, ALL_TYPES as INNER_ALL_TYPES, } from './constants'; import { introspectSchema, IntrospectionOptions, IntrospectionResult, } from './introspection'; export * from './introspection'; export const QUERY_TYPES = INNER_QUERY_TYPES; export const MUTATION_TYPES = INNER_MUTATION_TYPES; export const ALL_TYPES = INNER_ALL_TYPES; /** * Map dataProvider method names to GraphQL queries and mutations * * @example for the Customer resource: * dataProvider.getList() // query allCustomers() { ... } * dataProvider.getOne() // query Customer($id: id) { ... } * dataProvider.getMany() // query allCustomers($filter: { ids: [ids] }) { ... } * dataProvider.getManyReference() // query allCustomers($filter: { [target]: [id] }) { ... } * dataProvider.create() // mutation createCustomer($firstName: firstName, $lastName: lastName, ...) { ... } * dataProvider.update() // mutation updateCustomer($id: id, firstName: firstName, $lastName: lastName, ...) { ... } * dataProvider.delete() // mutation deleteCustomer($id: id) { ... } * // note that updateMany and deleteMany aren't mapped in this adapter */ export const defaultOptions = { resolveIntrospection: introspectSchema, introspection: { operationNames: { [GET_LIST]: resource => `all${pluralize(resource.name)}`, [GET_ONE]: resource => `${resource.name}`, [GET_MANY]: resource => `all${pluralize(resource.name)}`, [GET_MANY_REFERENCE]: resource => `all${pluralize(resource.name)}`, [CREATE]: resource => `create${resource.name}`, [UPDATE]: resource => `update${resource.name}`, [DELETE]: resource => `delete${resource.name}`, }, exclude: undefined, include: undefined, }, }; const getOptions = ( options: | GetQueryOptions | GetMutationOptions | GetWatchQueryOptions | undefined, raFetchMethod: string, resource: string ) => { if (typeof options === 'function') { return options(resource, raFetchMethod); } return options; }; export type BuildQueryResult = QueryOptions<OperationVariables, any> & { parseResponse: (response: ApolloQueryResult<any>) => any; }; export type BuildQuery = ( name: string, resource: string, params: any ) => BuildQueryResult; export type BuildQueryFactory = ( introspectionResults: IntrospectionResult ) => BuildQuery; export type GetQueryOptions = ( resource: string, raFetchMethod: string ) => Partial<QueryOptions<OperationVariables, any>>; export type GetMutationOptions = ( resource: string, raFetchMethod: string ) => Partial<MutationOptions<OperationVariables, any>>; export type GetWatchQueryOptions = ( resource: string, raFetchMethod: string ) => Partial<WatchQueryOptions<OperationVariables, any>>; export type Options = { client?: ApolloClient<unknown>; clientOptions?: Partial<ApolloClientOptions<unknown>>; introspection?: false | Partial<IntrospectionOptions>; override?: { [key: string]: (params: any) => BuildQueryResult; }; buildQuery: BuildQueryFactory; query?: GetQueryOptions; mutation?: GetMutationOptions; watchQuery?: GetWatchQueryOptions; }; const buildGraphQLProvider = (options: Options): GraphqlDataProvider => { const { client: clientObject, clientOptions, introspection, resolveIntrospection, buildQuery: buildQueryFactory, override = {}, ...otherOptions } = merge({}, defaultOptions, options); if ( Object.keys(override).length > 0 && process.env.NODE_ENV === 'production' ) { console.warn( 'The override option is deprecated. You should instead wrap the buildQuery function provided by the dataProvider you use.' ); } const client = clientObject || buildApolloClient(clientOptions); let introspectionResults; let introspectionResultsPromise; const callApollo = async (raFetchMethod, resource, params) => { if (introspection) { if (!introspectionResultsPromise) { introspectionResultsPromise = resolveIntrospection( client, introspection ); } introspectionResults = await introspectionResultsPromise; } const buildQuery = buildQueryFactory(introspectionResults); const overriddenBuildQuery = get( override, `${resource}.${raFetchMethod}` ); const { parseResponse, ...query } = overriddenBuildQuery ? { ...buildQuery(raFetchMethod, resource, params), ...overriddenBuildQuery(params), } : buildQuery(raFetchMethod, resource, params); const operation = getQueryOperation(query.query); if (operation === 'query') { const apolloQuery = { ...query, fetchPolicy: 'network-only', ...getOptions(otherOptions.query, raFetchMethod, resource), }; apolloQuery.context = merge( { fetchOptions: { signal: params?.signal, }, }, apolloQuery.context ); return ( client // @ts-ignore .query(apolloQuery) .then(response => parseResponse(response)) .catch(handleError) ); } const apolloQuery = { mutation: query.query, variables: query.variables, ...getOptions(otherOptions.mutation, raFetchMethod, resource), }; return ( client // @ts-ignore .mutate(apolloQuery) .then(parseResponse) .catch(handleError) ); }; const raDataProvider: GraphqlDataProvider = { create: (resource, params) => callApollo(CREATE, resource, params), delete: (resource, params) => callApollo(DELETE, resource, params), deleteMany: (resource, params) => callApollo(DELETE_MANY, resource, params), getList: (resource, params) => callApollo(GET_LIST, resource, params), getMany: (resource, params) => callApollo(GET_MANY, resource, params), getManyReference: (resource, params) => callApollo(GET_MANY_REFERENCE, resource, params), getOne: (resource, params) => callApollo(GET_ONE, resource, params), update: (resource, params) => callApollo(UPDATE, resource, params), updateMany: (resource, params) => callApollo(UPDATE_MANY, resource, params), getIntrospection: () => { if (introspection) { if (!introspectionResultsPromise) { introspectionResultsPromise = resolveIntrospection( client, introspection ); } return introspectionResultsPromise; } }, client, }; return raDataProvider; }; const handleError = (error: ApolloError) => { if (error?.networkError as ServerError) { throw new HttpError( (error?.networkError as ServerError)?.message, (error?.networkError as ServerError)?.statusCode ); } throw new HttpError(error.message, 200, error); }; const getQueryOperation = query => { if (query && query.definitions && query.definitions.length > 0) { return query.definitions[0].operation; } throw new Error('Unable to determine the query operation'); }; export type GetIntrospection = () => Promise<IntrospectionResult>; export type GraphqlDataProvider = DataProvider & { getIntrospection: GetIntrospection; client: ApolloClient<unknown>; }; export default buildGraphQLProvider;