diff --git a/client/coral-admin/src/containers/Header.js b/client/coral-admin/src/containers/Header.js index a5c1f0afc..c107d58f9 100644 --- a/client/coral-admin/src/containers/Header.js +++ b/client/coral-admin/src/containers/Header.js @@ -19,6 +19,6 @@ export default withQuery(gql` } `, { options: { - pollInterval: 5000 + pollInterval: 10000 } })(Header); diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 3a458ff83..702ca4d81 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -19,7 +19,7 @@ import update from 'immutability-helper'; import {notify} from 'coral-framework/actions/notification'; const commentConnectionFragment = gql` - fragment CoralAdmin_Moderation_CommentConnection on CommentConnection { + fragment CoralAdmin_UserDetail_CommentConnection on CommentConnection { nodes { ...${getDefinitionName(UserDetailComment.fragments.comment)} } @@ -155,7 +155,7 @@ export const withUserDetailQuery = withQuery(gql` author_id: $author_id, statuses: $statuses }) { - ...CoralAdmin_Moderation_CommentConnection + ...CoralAdmin_UserDetail_CommentConnection } ...${getDefinitionName(UserDetailComment.fragments.root)} ${getSlotFragmentSpreads(slots, 'root')} diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 1b75d23cc..ca6f0224a 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -138,7 +138,7 @@ class ModerationContainer extends Component { }, ]; - this.subscriptions = parameters.map((param) => this.props.data.subscribeToMore(param)); + this.subscriptions = parameters.map((param) => this.props.data.subscribeToMoreThrottled(param)); } unsubscribe() { diff --git a/client/coral-embed-stream/src/containers/ExtendableTabPanel.js b/client/coral-embed-stream/src/containers/ExtendableTabPanel.js index 362f6bf89..3fca664e7 100644 --- a/client/coral-embed-stream/src/containers/ExtendableTabPanel.js +++ b/client/coral-embed-stream/src/containers/ExtendableTabPanel.js @@ -1,7 +1,6 @@ import React from 'react'; import ExtendableTabPanel from '../components/ExtendableTabPanel'; import {connect} from 'react-redux'; -import omit from 'lodash/omit'; import {TabPane} from 'coral-ui'; import ExtendableTab from '../components/ExtendableTab'; import {getShallowChanges} from 'coral-framework/utils'; @@ -128,7 +127,7 @@ ExtendableTabPanelContainer.propTypes = { }; const mapStateToProps = (state) => ({ - reduxState: omit(state, 'apollo'), + reduxState: state, }); export default connect(mapStateToProps, null)(ExtendableTabPanelContainer); diff --git a/client/coral-embed-stream/src/tabs/stream/components/Stream.js b/client/coral-embed-stream/src/tabs/stream/components/Stream.js index c81a5d797..e37b91ba6 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Stream.js @@ -252,7 +252,7 @@ class Stream extends React.Component { enable={asset.settings.infoBoxEnable} /> {questionBoxEnable && ( - ({ - reduxState: omit(state, 'apollo'), + reduxState: state, }); export default connect(mapStateToProps, null)(IfSlotIsEmpty); diff --git a/client/coral-framework/components/IfSlotIsNotEmpty.js b/client/coral-framework/components/IfSlotIsNotEmpty.js index 6186e2166..a498ad3aa 100644 --- a/client/coral-framework/components/IfSlotIsNotEmpty.js +++ b/client/coral-framework/components/IfSlotIsNotEmpty.js @@ -1,7 +1,6 @@ import React, {Children} from 'react'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; -import omit from 'lodash/omit'; import {getShallowChanges} from 'coral-framework/utils'; class IfSlotIsNotEmpty extends React.Component { @@ -39,7 +38,7 @@ IfSlotIsNotEmpty.propTypes = { }; const mapStateToProps = (state) => ({ - reduxState: omit(state, 'apollo'), + reduxState: state, }); export default connect(mapStateToProps, null)(IfSlotIsNotEmpty); diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index e1acd82f4..0404df80f 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -2,7 +2,6 @@ import React from 'react'; import cn from 'classnames'; import styles from './Slot.css'; import {connect} from 'react-redux'; -import omit from 'lodash/omit'; import kebabCase from 'lodash/kebabCase'; import PropTypes from 'prop-types'; import isEqual from 'lodash/isEqual'; @@ -121,7 +120,7 @@ Slot.propTypes = { }; const mapStateToProps = (state) => ({ - reduxState: omit(state, 'apollo'), + reduxState: state, }); export default connect(mapStateToProps, null)(Slot); diff --git a/client/coral-framework/components/TalkProvider.js b/client/coral-framework/components/TalkProvider.js index 418bb3760..1b9c5ca9c 100644 --- a/client/coral-framework/components/TalkProvider.js +++ b/client/coral-framework/components/TalkProvider.js @@ -9,17 +9,18 @@ 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, + store: this.props.store, }; } render() { - const {children, client, store} = this.props; + const {children, client} = this.props; return ( - + {children} ); @@ -31,10 +32,11 @@ 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, + store: PropTypes.object, }; export default TalkProvider; diff --git a/client/coral-framework/graphql/reduceDocument.js b/client/coral-framework/graphql/reduceDocument.js new file mode 100644 index 000000000..12e0954d7 --- /dev/null +++ b/client/coral-framework/graphql/reduceDocument.js @@ -0,0 +1,293 @@ +import { + getMainDefinition, + getFragmentDefinitions, + createFragmentMap, + shouldInclude, + getOperationDefinition, +} from 'apollo-utilities'; + +function getDirectivesID(directives) { + let id = ''; + directives.forEach((directive) => { + id += `@${directive.name.value}(`; + let first = true; + directive.arguments.forEach((arg) => { + if (!first) { + id += ','; + } + first = false; + const value = arg.value.kind === 'Variable' + ? `$${arg.value.name.value}` + : arg.value.value; + id += `${arg.name.value}:${value}`; + }); + id += ')'; + }); + return id; +} + +// If two definitions have the same id, they can be merged. +function getDefinitionID(definition) { + + // Only merge when directives are exactly the same. + const trailing = definition.directives.length + ? `_${getDirectivesID(definition.directives)}` + : ''; + + switch (definition.kind) { + case 'FragmentSpread': + return `FragmentSpread_${definition.name.value}`; + case 'Field': + return `Field_${definition.alias ? definition.alias.value : definition.name.value}${trailing}`; + case 'InlineFragment': + return `InlineFragment_${definition.typeCondition.name.value}${trailing}`; + default: + throw new Error(`unknown definition kind ${definition.kind}`); + } +} + +/** + * Merge selections of 2 definitions. + */ +export function mergeDefinitions(a, b) { + const name = getDefinitionID(a); + + if (!!a.selectionSet !== !!b.selectionSet) { + throw Error(`incompatible field definition for ${name}`); + } + + if (!a.selectionSet) { + return b; + } + + const selectionSet = mergeSelectionSets(a.selectionSet, b.selectionSet); + + return { + ...b, + selectionSet, + }; +} + +/** + * Merge selectionSets + */ +export function mergeSelectionSets(a, b) { + const selectionsMap = [...a.selections, ...b.selections].reduce((o, sel) => { + const selName = getDefinitionID(sel); + if (!(selName in o)) { + o[selName] = sel; + return o; + } + o[selName] = mergeDefinitions(o[selName], sel); + return o; + }, {}); + + const selections = Object.keys(selectionsMap).map((key) => selectionsMap[key]); + + return { + ...b, + selections, + }; +} + +function getFragmentOrDie(name, execContext) { + const { + rawFragmentMap, + fragmentMap, + } = execContext; + + if (!(name in fragmentMap)) { + const fragment = rawFragmentMap[name]; + + if (!fragment) { + throw new Error(`fragment ${fragment.name.value} does not exist`); + } + + const typeCondition = fragment.typeCondition.name.value; + const transformed = transformDefinition(fragment, execContext, `type.${typeCondition}`, typeCondition); + fragmentMap[name] = transformed; + } + + return fragmentMap[name]; +} + +/** + * Return selections with resolved named fragments and directives. + */ +function getTransformedSelections(definition, path, gqlType, execContext) { + const { + variables, + } = execContext; + + const selectionsMap = definition.selectionSet.selections.reduce((o, sel) => { + if (variables && !shouldInclude(sel, variables)) { + + // Skip this entirely + return o; + } + if (sel.kind !== 'FragmentSpread') { + const transformed = transformDefinition(sel, execContext, path, gqlType); + const name = getDefinitionID(sel); + + // Merge existing value. + if (name in o) { + o[name] = mergeDefinitions(o[name], transformed); + return o; + } + + o[name] = transformed; + return o; + } + + const fragment = getFragmentOrDie(sel.name.value, execContext); + const typeCondition = fragment.typeCondition.name.value; + + // Turn NamedFragment into an InlineFragment. + if (gqlType !== typeCondition || fragment.directives.length) { + const node = { + ...fragment, + kind: 'InlineFragment', + }; + const name = getDefinitionID(node); + + // Merge existing value. + if (name in o) { + o[name] = mergeDefinitions(o[name], node); + return o; + } + + o[name] = node; + return o; + } + + // Merge NamedFragment directly into selections. + const fragmentSelections = fragment.selectionSet.selections; + fragmentSelections.forEach((s) => { + + if (variables && !shouldInclude(s, variables)) { + + // Skip this entirely + return; + } + + const selName = getDefinitionID(s); + if (!(selName in o)) { + o[selName] = s; + return; + } + + o[selName] = mergeDefinitions(o[selName], s); + }); + return o; + }, {}); + + const selections = Object.keys(selectionsMap).map((key) => selectionsMap[key]); + return selections; +} + +/** + * Resolve named fragments and directives in a definition. + */ +function transformDefinition(definition, execContext, path = '', type = null) { + if (!definition.selectionSet) { + return definition; + } + + const {typeGetter} = execContext; + + if (definition.kind === 'Field') { + const fieldName = definition.name.value; + path = `${path}.${fieldName}`; + + if (typeGetter) { + type = typeGetter(path); + } + } + + // InlineFragments + else if(!type && typeGetter) { + type = typeGetter(path); + } + + return { + ...definition, + selectionSet: { + ...definition.selectionSet, + selections: getTransformedSelections(definition, path, type, execContext), + }, + }; +} + +export default function reduceDocument(document, options = {}) { + const mainDefinition = getMainDefinition(document); + const fragments = getFragmentDefinitions(document); + const operationDefinition = getOperationDefinition(document); + const path = operationDefinition + ? operationDefinition.operation + : `type.${mainDefinition.typeCondition.name.value}`; + + const execContext = { + rawFragmentMap: createFragmentMap(fragments), + fragmentMap: options.fragmentMap || {}, + variables: options.variables, + typeGetter: options.typeGetter || (() => null), + }; + + return { + kind: 'Document', + definitions: [transformDefinition(mainDefinition, execContext, path)], + }; +} + +function getObjectType(fieldType) { + if (['NON_NULL', 'LIST'].indexOf(fieldType.kind) > -1) { + return getObjectType(fieldType.ofType); + } + return fieldType.name; +} + +function getFieldType(parentType, fieldName) { + const field = parentType.fields.find((f) => f.name === fieldName); + return getObjectType(field.type); +} + +export function createTypeGetter(introspectionData) { + const types = {}; + introspectionData.__schema.types.forEach((type) => types[type.name] = type); + + const result = { + 'query': introspectionData.__schema.queryType.name, + 'mutation': introspectionData.__schema.mutationType.name, + 'subscription': introspectionData.__schema.subscriptionType.name, + }; + + return (path) => { + if (result[path]) { + return result[path]; + } + let currentPath = ''; + 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; + continue; + } + result[nextPath] = getFieldType(types[result[currentPath]], part); + currentPath = nextPath; + } + return result[path]; + }; +} 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 9548827ac..8f0155a11 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -3,6 +3,8 @@ import {graphql} from 'react-apollo'; import {getDefinitionName, separateDataAndRoot, getResponseErrors} from '../utils'; import PropTypes from 'prop-types'; import hoistStatics from 'recompose/hoistStatics'; +import {getOperationName} from 'apollo-client/queries/getFromAST'; +import throttle from 'lodash/throttle'; const withSkipOnErrors = (reducer) => (prev, action, ...rest) => { if (action.type === 'APOLLO_MUTATION_RESULT' && getResponseErrors(action.result)) { @@ -39,24 +41,30 @@ 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, }; // Lazily resolve fragments from graphRegistry to support circular dependencies. memoized = null; + resolvedDocument = null; lastNetworkStatus = null; data = null; name = ''; + // Pending subscription data. + subscriptionQueue = []; + get graphqlRegistry() { - return this.context.graphqlRegistry; + return this.context.graphql.registry; + } + + get client() { + return this.context.client; } 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); } emitWhenNeeded(data) { @@ -72,6 +80,66 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => { this.context.eventEmitter.emit(`query.${this.name}.${status}`, {variables, data: root}); } + // Handle any pending susbcription data in the subscription queue at max once every second. + // Updates are batched in written into apollo in one go. + processSubscriptionQueue = throttle(() => { + const variables = typeof this.wrappedOptions === 'function' + ? this.wrappedOptions(this.props).variables + : this.wrappedOptions.variables; + + const previousResult = this.client.readQuery({ + query: this.resolvedDocument, + variables, + }); + + let result = previousResult; + + this.subscriptionQueue.forEach(([updateQuery, data]) => { + result = updateQuery(result, {subscriptionData: {data}}); + }); + + if (result !== previousResult) { + this.client.writeQuery({ + query: this.resolvedDocument, + variables, + data: result, + }); + } + this.subscriptionQueue = []; + }, 1000); + + subscribeToMoreThrottled = ({document, variables, updateQuery}) => { + + // We need to add the typenames and resolve fragments. + const query = this.resolveDocument(document); + const handler = (error, data) => { + if (error) { + + // TODO: shuld this show a notification? + console.error(error); + return; + } + if (data) { + this.subscriptionQueue.push([updateQuery, data]); + + // Triggers the throttled subscription queue processor. + this.processSubscriptionQueue(); + } + }; + + // Start subscription. + const request = { + query, + variables, + operationName: getOperationName(query), + }; + + const id = this.client.networkInterface.subscribe(request, handler); + + // Return unsubscribe callback. + return () => this.client.networkInterface.unsubscribe(id); + }; + nextData(data) { this.emitWhenNeeded(data); @@ -102,6 +170,7 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => { stopPolling: data.stopPolling, refetch: data.refetch, updateQuery: data.updateQuery, + subscribeToMoreThrottled: this.subscribeToMoreThrottled, subscribeToMore: (stmArgs) => { const resolvedDocument = this.resolveDocument(stmArgs.document); @@ -190,10 +259,10 @@ export default (document, config = {}) => hoistStatics((WrappedComponent) => { getWrapped = () => { if (!this.memoized) { - const resolvedDocument = this.resolveDocument(document); - this.name = getDefinitionName(resolvedDocument); + this.resolvedDocument = this.resolveDocument(document); + this.name = getDefinitionName(this.resolvedDocument); this.memoized = graphql( - resolvedDocument, + this.resolvedDocument, {...this.wrappedConfig, options: this.wrappedOptions}, )(WrappedComponent); } diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 85144fe60..3ef6629c1 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,13 @@ 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, + optimize: process.env.NODE_ENV === 'production', + }, + ); if (!notification) { // Use default notification service (pym based) @@ -134,7 +141,7 @@ export async function createContext({ plugins, eventEmitter, rest, - graphqlRegistry, + graphql, notification, storage, history, @@ -143,13 +150,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)); @@ -159,21 +166,28 @@ export async function createContext({ pym.sendMessage('event', JSON.stringify({eventName, value})); }); + // Create our redux store. const finalReducers = { ...reducers, ...plugins.getReducers(), - apollo: client.reducer(), }; store = createStore(finalReducers, [ - client.middleware(), thunk.withExtraArgument(context), - apolloErrorReporter, createReduxEmitter(eventEmitter), ]); context.store = store; + // Create apollo redux store. + context.apolloStore = createStore({ + apollo: client.reducer(), + }, [ + client.middleware(), + apolloErrorReporter, + createReduxEmitter(eventEmitter), + ]); + // Run pre initialization. if (preInit) { await preInit(context); diff --git a/client/coral-framework/services/graphql.js b/client/coral-framework/services/graphql.js new file mode 100644 index 000000000..859d57d8a --- /dev/null +++ b/client/coral-framework/services/graphql.js @@ -0,0 +1,39 @@ +import reduceDocument, {createTypeGetter} from '../graphql/reduceDocument'; +import {addTypenameToDocument} from 'apollo-client/queries/queryTransform'; + +/** + * createGraphQLService + * @param {string} basename base path of the url + * @return {Object} histor service + */ +export function createGraphQLService(registry, { + introspectionData, + optimize = false, +}) { + const reduceOptions = { + typeGetter: optimize && introspectionData ? createTypeGetter(introspectionData) : null, + + // Use shared fragment map. + // Attention: Fragment names must be unique otherwise weird things will happen. + fragmentMap: {}, + }; + + return { + registry, + resolveDocument(documentOrCallback, props, context) { + let document = typeof documentOrCallback === 'function' + ? documentOrCallback(props, context) + : documentOrCallback; + document = registry.resolveFragments(document); + + if (optimize) { + document = reduceDocument(document, reduceOptions); + } + + // 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); + }, + }; +} diff --git a/package.json b/package.json index 63a1b7119..5ac69b6e6 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,10 @@ }, "homepage": "https://github.com/coralproject/talk#readme", "dependencies": { + "@coralproject/graphql-anywhere-optimized": "^0.1.0", "accepts": "^1.3.4", "apollo-client": "^1.9.1", + "apollo-utilities": "^1.0.3", "app-module-path": "^2.2.0", "autoprefixer": "^6.5.2", "babel-cli": "6.26.0", @@ -105,7 +107,6 @@ "fs-extra": "^4.0.1", "gql-merge": "^0.0.4", "graphql": "^0.9.1", - "graphql-anywhere": "^3.1.0", "graphql-docs": "0.2.0", "graphql-errors": "^2.1.0", "graphql-redis-subscriptions": "1.3.0", diff --git a/webpack.config.js b/webpack.config.js index d9c8ba4a0..60a49b6eb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -120,6 +120,7 @@ const config = { }, resolve: { alias: { + 'graphql-anywhere': '@coralproject/graphql-anywhere-optimized', 'plugin-api': path.resolve(__dirname, 'plugin-api/'), plugins: path.resolve(__dirname, 'plugins/'), pluginsConfig: pluginsPath diff --git a/yarn.lock b/yarn.lock index 882cf3e29..78ded3707 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,6 +11,10 @@ eslint-plugin-promise "^3.3.1" eslint-plugin-react "^7.3.0" +"@coralproject/graphql-anywhere-optimized@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@coralproject/graphql-anywhere-optimized/-/graphql-anywhere-optimized-0.1.0.tgz#3456f16f790d8593b0ca4355910578ab1c6edab5" + "@kadira/storybook-deployer@^1.1.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@kadira/storybook-deployer/-/storybook-deployer-1.2.0.tgz#1708f5cb37fa08ab4173b1bd99df6f4717dfae12" @@ -259,6 +263,10 @@ apollo-link-core@^0.5.0: graphql-tag "^2.4.2" zen-observable-ts "^0.4.4" +apollo-utilities@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.3.tgz#bf435277609850dd442cf1d5c2e8bc6655eaa943" + app-module-path@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" @@ -3507,7 +3515,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -graphql-anywhere@^3.0.1, graphql-anywhere@^3.1.0: +graphql-anywhere@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-3.1.0.tgz#3ea0d8e8646b5cee68035016a9a7557c15c21e96"