diff --git a/client/coral-framework/components/TalkProvider.js b/client/coral-framework/components/TalkProvider.js index 880b4c9d9..1b9c5ca9c 100644 --- a/client/coral-framework/components/TalkProvider.js +++ b/client/coral-framework/components/TalkProvider.js @@ -9,7 +9,7 @@ class TalkProvider extends React.Component { pym: this.props.pym, plugins: this.props.plugins, rest: this.props.rest, - graphqlRegistry: this.props.graphqlRegistry, + graphql: this.props.graphql, notification: this.props.notification, storage: this.props.storage, history: this.props.history, @@ -32,7 +32,7 @@ TalkProvider.childContextTypes = { eventEmitter: PropTypes.object, plugins: PropTypes.object, rest: PropTypes.func, - graphqlRegistry: PropTypes.object, + graphql: PropTypes.object, notification: PropTypes.object, storage: PropTypes.object, history: PropTypes.object, diff --git a/client/coral-framework/graphql/reduceDocument.js b/client/coral-framework/graphql/reduceDocument.js index d0a9cc30f..3e0118eba 100644 --- a/client/coral-framework/graphql/reduceDocument.js +++ b/client/coral-framework/graphql/reduceDocument.js @@ -179,7 +179,9 @@ export default function reduceDocument(document, options = {}) { const mainDefinition = getMainDefinition(document); const fragments = getFragmentDefinitions(document); const operationDefinition = getOperationDefinition(document); - const path = operationDefinition.operation; + const path = operationDefinition + ? operationDefinition.operation + : `type.${mainDefinition.typeCondition.name.value}`; const execContext = { fragmentMap: createFragmentMap(fragments), @@ -224,6 +226,17 @@ export function createTypeGetter(introspectionData) { const parts = path.split('.'); for (let i = 0; i < parts.length; i++) { const part = parts[i]; + + // Handle special path e.g. 'type.ROOT_QUERY.fieldName' + if (part === 'type') { + const type = parts[i + 1]; + const nextPath = `type.${type}`; + result[nextPath] = type; + currentPath = nextPath; + i++; + continue; + } + const nextPath = currentPath ? `${currentPath}.${part}` : part; if (nextPath in result) { currentPath = nextPath; diff --git a/client/coral-framework/hocs/withFragments.js b/client/coral-framework/hocs/withFragments.js index be6d07fed..3411e7552 100644 --- a/client/coral-framework/hocs/withFragments.js +++ b/client/coral-framework/hocs/withFragments.js @@ -64,18 +64,15 @@ function hasEqualLeaves(a, b, path = '') { export default (fragments) => hoistStatics((BaseComponent) => { class WithFragments extends React.Component { static contextTypes = { - graphqlRegistry: PropTypes.object, + graphql: PropTypes.object, }; get graphqlRegistry() { - return this.context.graphqlRegistry; + return this.context.graphql.registry; } resolveDocument(documentOrCallback) { - const document = typeof documentOrCallback === 'function' - ? documentOrCallback(this.props, this.context) - : documentOrCallback; - return this.graphqlRegistry.resolveFragments(document); + return this.context.graphql.resolveDocument(documentOrCallback, this.props, this.context); } fragments = mapValues(fragments, (val) => this.resolveDocument(val)); diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index cb6bc3789..639dbd461 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -42,18 +42,15 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => { static contextTypes = { eventEmitter: PropTypes.object, store: PropTypes.object, - graphqlRegistry: PropTypes.object, + graphql: PropTypes.object, }; get graphqlRegistry() { - return this.context.graphqlRegistry; + return this.context.graphql.registry; } resolveDocument(documentOrCallback) { - const document = typeof documentOrCallback === 'function' - ? documentOrCallback(this.props, this.context) - : documentOrCallback; - return this.graphqlRegistry.resolveFragments(document); + return this.context.graphql.resolveDocument(documentOrCallback, this.props, this.context); } // Lazily resolve fragments from graphRegistry to support circular dependencies. diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index dac2bb221..8f0155a11 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -4,12 +4,7 @@ import {getDefinitionName, separateDataAndRoot, getResponseErrors} from '../util import PropTypes from 'prop-types'; import hoistStatics from 'recompose/hoistStatics'; import {getOperationName} from 'apollo-client/queries/getFromAST'; -import {addTypenameToDocument} from 'apollo-client/queries/queryTransform'; import throttle from 'lodash/throttle'; -import reduceDocument, {createTypeGetter} from '../graphql/reduceDocument'; -import introspectionData from '../graphql/introspection.json'; - -const typeGetter = createTypeGetter(introspectionData); const withSkipOnErrors = (reducer) => (prev, action, ...rest) => { if (action.type === 'APOLLO_MUTATION_RESULT' && getResponseErrors(action.result)) { @@ -46,7 +41,7 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => { return class WithQuery extends React.Component { static contextTypes = { eventEmitter: PropTypes.object, - graphqlRegistry: PropTypes.object, + graphql: PropTypes.object, client: PropTypes.object, }; @@ -61,7 +56,7 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => { subscriptionQueue = []; get graphqlRegistry() { - return this.context.graphqlRegistry; + return this.context.graphql.registry; } get client() { @@ -69,15 +64,7 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => { } resolveDocument(documentOrCallback) { - let document = typeof documentOrCallback === 'function' - ? documentOrCallback(this.props, this.context) - : documentOrCallback; - document = reduceDocument(this.graphqlRegistry.resolveFragments(document), {typeGetter}); - - // We also add typenames to the document which apollo would usually do, - // but we also use the network interface in subscriptions directly - // which require the resolved typenames. - return addTypenameToDocument(document); + return this.context.graphql.resolveDocument(documentOrCallback, this.props, this.context); } emitWhenNeeded(data) { @@ -124,7 +111,7 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => { subscribeToMoreThrottled = ({document, variables, updateQuery}) => { // We need to add the typenames and resolve fragments. - const query = addTypenameToDocument(this.graphqlRegistry.resolveFragments(document)); + const query = this.resolveDocument(document); const handler = (error, data) => { if (error) { diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 9862ff23c..4fcc83a52 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -12,6 +12,7 @@ import {BASE_PATH} from 'coral-framework/constants/url'; import {createPluginsService} from './plugins'; import {createNotificationService} from './notification'; import {createGraphQLRegistry} from './graphqlRegistry'; +import {createGraphQLService} from './graphql'; import globalFragments from 'coral-framework/graphql/fragments'; import {createStorage, createPymStorage} from 'coral-framework/services/storage'; import {createHistory} from 'coral-framework/services/history'; @@ -121,7 +122,10 @@ export async function createContext({ introspectionData, }); const plugins = createPluginsService(pluginsConfig); - const graphqlRegistry = createGraphQLRegistry(plugins.getSlotFragments.bind(plugins)); + const graphql = createGraphQLService( + createGraphQLRegistry(plugins.getSlotFragments.bind(plugins)), + introspectionData, + ); if (!notification) { // Use default notification service (pym based) @@ -134,7 +138,7 @@ export async function createContext({ plugins, eventEmitter, rest, - graphqlRegistry, + graphql, notification, storage, history, @@ -143,13 +147,13 @@ export async function createContext({ }; // Load framework fragments. - Object.keys(globalFragments).forEach((key) => graphqlRegistry.addFragment(key, globalFragments[key])); + Object.keys(globalFragments).forEach((key) => graphql.registry.addFragment(key, globalFragments[key])); // Register graphql extension - graphqlRegistry.add(graphqlExtension); + graphql.registry.add(graphqlExtension); // Register plugin graphql extensions. - plugins.getGraphQLExtensions().forEach((ext) => graphqlRegistry.add(ext)); + plugins.getGraphQLExtensions().forEach((ext) => graphql.registry.add(ext)); // Load plugin translations. plugins.getTranslations().forEach((t) => loadTranslations(t)); diff --git a/client/coral-framework/services/graphql.js b/client/coral-framework/services/graphql.js new file mode 100644 index 000000000..42e9cd8ad --- /dev/null +++ b/client/coral-framework/services/graphql.js @@ -0,0 +1,26 @@ +import reduceDocument, {createTypeGetter} from '../graphql/reduceDocument'; +import {addTypenameToDocument} from 'apollo-client/queries/queryTransform'; + +/** + * createHistory returns the history service for react router + * @param {string} basename base path of the url + * @return {Object} histor service + */ +export function createGraphQLService(registry, introspectionData) { + const typeGetter = createTypeGetter(introspectionData); + + return { + registry, + resolveDocument(documentOrCallback, props, context) { + let document = typeof documentOrCallback === 'function' + ? documentOrCallback(props, context) + : documentOrCallback; + document = reduceDocument(registry.resolveFragments(document), {typeGetter}); + + // We also add typenames to the document which apollo would usually do, + // but we also use the network interface in subscriptions directly + // which require the resolved typenames. + return addTypenameToDocument(document); + }, + }; +}