UNPKG

ra-data-graphql

Version:

A GraphQL data provider for react-admin

171 lines (147 loc) 4.55 kB
import { getIntrospectionQuery, IntrospectionField, IntrospectionObjectType, IntrospectionQuery, IntrospectionSchema, IntrospectionType, } from 'graphql'; import { ApolloClient, gql } from '@apollo/client'; import { ALL_TYPES } from './constants'; /** * @param {ApolloClient} client The Apollo client * @param {Object} options The introspection options */ export const introspectSchema = async ( client: ApolloClient<unknown>, options: IntrospectionOptions ) => { const schema = options.schema ? options.schema : await fetchSchema(client); const queries = getQueriesFromSchema(schema); const types = getTypesFromSchema(schema); const resources = getResources(types, queries, options); return { types, queries, resources, schema, }; }; export type IntrospectionOptions = { schema?: IntrospectionSchema; operationNames: { [key: string]: (type: IntrospectionType) => string; }; exclude?: string[] | ((type: IntrospectionType) => boolean); include?: string[] | ((type: IntrospectionType) => boolean); }; export type IntrospectedResource = { type: IntrospectionObjectType; }; export type IntrospectionResult = { types: IntrospectionType[]; queries: IntrospectionObjectType[]; resources: IntrospectedResource[]; schema: IntrospectionSchema; }; const fetchSchema = ( client: ApolloClient<unknown> ): Promise<IntrospectionSchema> => client .query<IntrospectionQuery>({ fetchPolicy: 'network-only', query: gql` ${getIntrospectionQuery()} `, }) .then(({ data: { __schema } }) => __schema); const getQueriesFromSchema = ( schema: IntrospectionSchema ): IntrospectionField[] => schema.types.reduce((acc, type) => { if ( type.name !== schema.queryType?.name && type.name !== schema.mutationType?.name && (type as IntrospectionObjectType).fields ) { return acc; } return [...acc, ...((type as IntrospectionObjectType).fields || [])]; }, []); const getTypesFromSchema = (schema: IntrospectionSchema) => schema.types.filter( type => type.name !== (schema.queryType && schema.queryType.name) && type.name !== (schema.mutationType && schema.mutationType.name) ); const getResources = ( types: IntrospectionType[], queries: IntrospectionField[], options: IntrospectionOptions ): IntrospectedResource[] => { const filteredResources = types.filter(type => isResource(type, queries, options) ); return filteredResources.map(type => buildResource(type as IntrospectionObjectType, queries, options) ); }; const isResource = ( type: IntrospectionType, queries: IntrospectionField[], options: IntrospectionOptions ) => { if (isResourceIncluded(type, options)) return true; if (isResourceExcluded(type, options)) return false; const operations = Object.keys(options.operationNames).map(operation => options.operationNames[operation](type) ); const hasAtLeastOneOperation = operations.some(operation => queries.find(({ name }) => name === operation) ); return hasAtLeastOneOperation; }; export const isResourceIncluded = ( type: IntrospectionType, { include }: Partial<IntrospectionOptions> = {} ) => { if (Array.isArray(include)) { return include.includes(type.name); } if (typeof include === 'function') { return include(type); } return false; }; export const isResourceExcluded = ( type: IntrospectionType, { exclude }: Partial<IntrospectionOptions> = {} ) => { if (Array.isArray(exclude)) { return exclude.includes(type.name); } if (typeof exclude === 'function') { return exclude(type); } return false; }; const buildResource = ( type: IntrospectionObjectType, queries: IntrospectionField[], options: IntrospectionOptions ): IntrospectedResource => ALL_TYPES.reduce( (acc, raFetchMethod) => { const query = queries.find( ({ name }) => options.operationNames[raFetchMethod] && name === options.operationNames[raFetchMethod](type) ); if (!query) return acc; return { ...acc, [raFetchMethod]: query, }; }, { type } );