mirror of
https://github.com/wassname/talk.git
synced 2026-07-06 05:17:19 +08:00
Merge branch 'master' into less-lazy-assets
This commit is contained in:
@@ -1,293 +0,0 @@
|
||||
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];
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import reduceDocument, {createTypeGetter} from '../graphql/reduceDocument';
|
||||
import {transformDocument, createTypeGetter} from 'graphql-ast-tools';
|
||||
import {addTypenameToDocument} from 'apollo-client/queries/queryTransform';
|
||||
|
||||
/**
|
||||
@@ -10,7 +10,7 @@ export function createGraphQLService(registry, {
|
||||
introspectionData,
|
||||
optimize = false,
|
||||
}) {
|
||||
const reduceOptions = {
|
||||
const transformOptions = {
|
||||
typeGetter: optimize && introspectionData ? createTypeGetter(introspectionData) : null,
|
||||
|
||||
// Use shared fragment map.
|
||||
@@ -27,7 +27,7 @@ export function createGraphQLService(registry, {
|
||||
document = registry.resolveFragments(document);
|
||||
|
||||
if (optimize) {
|
||||
document = reduceDocument(document, reduceOptions);
|
||||
document = transformDocument(document, transformOptions);
|
||||
}
|
||||
|
||||
// We also add typenames to the document which apollo would usually do,
|
||||
|
||||
+1
-1
@@ -61,7 +61,6 @@
|
||||
"@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",
|
||||
@@ -107,6 +106,7 @@
|
||||
"fs-extra": "^4.0.1",
|
||||
"gql-merge": "^0.0.4",
|
||||
"graphql": "^0.9.1",
|
||||
"graphql-ast-tools": "^0.1.5",
|
||||
"graphql-docs": "0.2.0",
|
||||
"graphql-errors": "^2.1.0",
|
||||
"graphql-redis-subscriptions": "1.3.0",
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
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"
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@coralproject/graphql-anywhere-optimized/-/graphql-anywhere-optimized-0.1.3.tgz#f92f3906bb04f001aef725697237786752c49bd6"
|
||||
dependencies:
|
||||
graphql-ast-tools "^0.1.6"
|
||||
|
||||
"@kadira/storybook-deployer@^1.1.0":
|
||||
version "1.2.0"
|
||||
@@ -3519,6 +3521,12 @@ graphql-anywhere@^3.0.1:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-3.1.0.tgz#3ea0d8e8646b5cee68035016a9a7557c15c21e96"
|
||||
|
||||
graphql-ast-tools@^0.1.5, graphql-ast-tools@^0.1.6:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/graphql-ast-tools/-/graphql-ast-tools-0.1.6.tgz#48eb656434bf4c7dba2a0d4784f1fdb988ab70ed"
|
||||
dependencies:
|
||||
apollo-utilities "^1.0.3"
|
||||
|
||||
graphql-docs@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql-docs/-/graphql-docs-0.2.0.tgz#cf803f9c9d354fa03e89073d74e419261a5bfa74"
|
||||
|
||||
Reference in New Issue
Block a user